diff --git a/src/client/channel.rs b/src/client/channel.rs index 116504c..c8d3c42 100644 --- a/src/client/channel.rs +++ b/src/client/channel.rs @@ -533,16 +533,7 @@ fn map_channel_content( Ok(MapResult::ok(content)) } - None => match alerts { - Some(alerts) => Err(ExtractionError::ContentUnavailable( - alerts - .into_iter() - .map(|a| a.alert_renderer.text) - .collect::>() - .join(" "), - )), - None => Err(ExtractionError::InvalidData("no contents".into())), - }, + None => Err(response::alerts_to_err(alerts)), } } diff --git a/src/client/playlist.rs b/src/client/playlist.rs index 2db5079..296226a 100644 --- a/src/client/playlist.rs +++ b/src/client/playlist.rs @@ -68,8 +68,12 @@ impl MapResponse for response::Playlist { lang: Language, _deobf: Option<&Deobfuscator>, ) -> Result, ExtractionError> { - // TODO: think about a deserializer that deserializes only first list item - let mut tcbr_contents = self.contents.two_column_browse_results_renderer.contents; + let (contents, header) = match (self.contents, self.header) { + (Some(contents), Some(header)) => (contents, header), + _ => return Err(response::alerts_to_err(self.alerts)), + }; + + let mut tcbr_contents = contents.two_column_browse_results_renderer.contents; let video_items = some_or_bail!( some_or_bail!( some_or_bail!( @@ -121,11 +125,11 @@ impl MapResponse for response::Playlist { } None => { let header_banner = some_or_bail!( - self.header.playlist_header_renderer.playlist_header_banner, + header.playlist_header_renderer.playlist_header_banner, Err(ExtractionError::InvalidData("no thumbnail found".into())) ); - let mut byline = self.header.playlist_header_renderer.byline; + let mut byline = header.playlist_header_renderer.byline; let last_update_txt = byline .try_swap_remove(1) .map(|b| b.playlist_byline_renderer.text); @@ -140,14 +144,14 @@ impl MapResponse for response::Playlist { let n_videos = match ctoken { Some(_) => { ok_or_bail!( - util::parse_numeric(&self.header.playlist_header_renderer.num_videos_text), + util::parse_numeric(&header.playlist_header_renderer.num_videos_text), Err(ExtractionError::InvalidData("no video count".into())) ) } None => videos.len() as u64, }; - let playlist_id = self.header.playlist_header_renderer.playlist_id; + let playlist_id = header.playlist_header_renderer.playlist_id; if playlist_id != id { return Err(ExtractionError::WrongResult(format!( "got wrong playlist id {}, expected {}", @@ -155,10 +159,9 @@ impl MapResponse for response::Playlist { ))); } - let name = self.header.playlist_header_renderer.title; - let description = self.header.playlist_header_renderer.description_text; - let channel = self - .header + let name = header.playlist_header_renderer.title; + let description = header.playlist_header_renderer.description_text; + let channel = header .playlist_header_renderer .owner_text .and_then(|link| ChannelId::try_from(link).ok()); diff --git a/src/client/response/mod.rs b/src/client/response/mod.rs index 0cb2efe..3f364ba 100644 --- a/src/client/response/mod.rs +++ b/src/client/response/mod.rs @@ -25,6 +25,7 @@ pub use channel_rss::ChannelRss; use serde::Deserialize; use serde_with::{json::JsonString, serde_as, DefaultOnError, VecSkipError}; +use crate::error::ExtractionError; use crate::serializer::{ ignore_any, text::{Text, TextComponent}, @@ -471,3 +472,16 @@ impl IsShort for Vec { }) } } + +pub fn alerts_to_err(alerts: Option>) -> ExtractionError { + match alerts { + Some(alerts) => ExtractionError::ContentUnavailable( + alerts + .into_iter() + .map(|a| a.alert_renderer.text) + .collect::>() + .join(" "), + ), + None => ExtractionError::InvalidData("no contents".into()), + } +} diff --git a/src/client/response/playlist.rs b/src/client/response/playlist.rs index e26d6d4..8f2f521 100644 --- a/src/client/response/playlist.rs +++ b/src/client/response/playlist.rs @@ -5,14 +5,17 @@ use serde_with::{DefaultOnError, VecSkipError}; use crate::serializer::text::{Text, TextComponent}; use crate::serializer::{MapResult, VecLogError}; -use super::{ContentRenderer, ContentsRenderer, ThumbnailsWrap, VideoListItem}; +use super::{Alert, ContentRenderer, ContentsRenderer, ThumbnailsWrap, VideoListItem}; +#[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Playlist { - pub contents: Contents, - pub header: Header, + pub contents: Option, + pub header: Option
, pub sidebar: Option, + #[serde_as(as = "Option")] + pub alerts: Option>, } #[serde_as] diff --git a/tests/youtube.rs b/tests/youtube.rs index ed9cab4..e7c3a81 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -211,6 +211,21 @@ async fn playlist_cont2() { assert!(playlist.videos.count.unwrap() > 100); } +#[tokio::test] +async fn playlist_not_found() { + let rp = RustyPipe::builder().strict().build(); + let err = rp + .query() + .playlist("PLbZIPy20-1pN7mqjckepWF78ndb6ci_qz") + .await + .unwrap_err(); + + assert!(matches!( + err, + Error::Extraction(ExtractionError::ContentUnavailable(_)) + )); +} + //#VIDEO DETAILS #[tokio::test]