pub(crate) mod channel; pub(crate) mod music_artist; pub(crate) mod music_charts; pub(crate) mod music_details; pub(crate) mod music_genres; pub(crate) mod music_item; pub(crate) mod music_new; pub(crate) mod music_playlist; pub(crate) mod music_search; pub(crate) mod player; pub(crate) mod playlist; pub(crate) mod search; pub(crate) mod trends; pub(crate) mod url_endpoint; pub(crate) mod video_details; pub(crate) mod video_item; pub(crate) use channel::Channel; pub(crate) use channel::ChannelAbout; pub(crate) use music_artist::MusicArtist; pub(crate) use music_artist::MusicArtistAlbums; pub(crate) use music_charts::MusicCharts; pub(crate) use music_details::MusicDetails; pub(crate) use music_details::MusicLyrics; pub(crate) use music_details::MusicRelated; pub(crate) use music_genres::MusicGenre; pub(crate) use music_genres::MusicGenres; pub(crate) use music_item::MusicContinuation; pub(crate) use music_new::MusicNew; pub(crate) use music_playlist::MusicPlaylist; pub(crate) use music_search::MusicSearch; pub(crate) use music_search::MusicSearchSuggestion; pub(crate) use player::Player; pub(crate) use playlist::Playlist; pub(crate) use search::Search; pub(crate) use search::SearchSuggestion; pub(crate) use trends::Startpage; pub(crate) use trends::Trending; pub(crate) use url_endpoint::ResolvedUrl; pub(crate) use video_details::VideoComments; pub(crate) use video_details::VideoDetails; pub(crate) use video_item::YouTubeListItem; pub(crate) use video_item::YouTubeListMapper; #[cfg(feature = "rss")] pub(crate) mod channel_rss; #[cfg(feature = "rss")] pub(crate) use channel_rss::ChannelRss; use std::borrow::Cow; use std::marker::PhantomData; use serde::{ de::{IgnoredAny, Visitor}, Deserialize, }; use serde_with::{serde_as, DisplayFromStr, VecSkipError}; use crate::error::ExtractionError; use crate::serializer::{text::Text, MapResult, VecSkipErrorWrap}; use self::video_item::YouTubeListRenderer; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ContentRenderer { pub content: T, } #[derive(Debug)] pub(crate) struct ContentsRenderer { pub contents: Vec, } #[derive(Debug, Deserialize)] pub(crate) struct ContentsRendererLogged { #[serde(alias = "items")] pub contents: MapResult>, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Tab { pub tab_renderer: ContentRenderer, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct SectionList { pub section_list_renderer: ContentsRenderer, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct TwoColumnBrowseResults { pub two_column_browse_results_renderer: ContentsRenderer, } #[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ThumbnailsWrap { #[serde(default)] pub thumbnail: Thumbnails, } /// List of images in different resolutions. /// Not only used for thumbnails, but also for avatars and banners. #[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Thumbnails { #[serde(default)] pub thumbnails: Vec, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Thumbnail { pub url: String, pub width: u32, pub height: u32, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ContinuationItemRenderer { pub continuation_endpoint: ContinuationEndpoint, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ContinuationEndpoint { pub continuation_command: ContinuationCommand, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ContinuationCommand { pub token: String, } #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Icon { pub icon_type: IconType, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub(crate) enum IconType { /// Checkmark for verified channels Check, /// Music note for verified artists OfficialArtistBadge, /// Like button Like, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ChannelBadge { pub metadata_badge_renderer: ChannelBadgeRenderer, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ChannelBadgeRenderer { pub style: ChannelBadgeStyle, } #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub(crate) enum ChannelBadgeStyle { BadgeStyleTypeVerified, BadgeStyleTypeVerifiedArtist, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Alert { pub alert_renderer: AlertRenderer, } #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct AlertRenderer { #[serde_as(as = "Text")] pub text: String, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ResponseContext { pub visitor_data: Option, } // CONTINUATION #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct Continuation { /// Number of search results #[serde_as(as = "Option")] pub estimated_results: Option, #[serde( alias = "onResponseReceivedCommands", alias = "onResponseReceivedEndpoints" )] #[serde_as(as = "Option>")] pub on_response_received_actions: Option>>, /// Used for channel video rich grid renderer /// /// A/B test seen on 19.10.2022 pub continuation_contents: Option, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ContinuationActionWrap { #[serde(alias = "reloadContinuationItemsCommand")] pub append_continuation_items_action: ContinuationAction, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ContinuationAction { pub continuation_items: MapResult>, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RichGridContinuationContents { pub rich_grid_continuation: YouTubeListRenderer, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct MusicContinuationData { #[serde(alias = "nextRadioContinuationData")] pub next_continuation_data: MusicContinuationDataInner, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct MusicContinuationDataInner { pub continuation: String, } // ERROR #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ErrorResponse { pub error: ErrorResponseContent, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ErrorResponseContent { pub message: String, } // DESERIALIZER impl<'de, T> Deserialize<'de> for ContentsRenderer where T: Deserialize<'de>, { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { struct ItemVisitor(PhantomData); impl<'de, T> Visitor<'de> for ItemVisitor where T: Deserialize<'de>, { type Value = ContentsRenderer; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { formatter.write_str("map") } fn visit_map(self, mut map: A) -> Result where A: serde::de::MapAccess<'de>, { let mut contents = None; while let Some(k) = map.next_key::>()? { if k == "contents" || k == "tabs" || k == "items" { contents = Some(ContentsRenderer { contents: map.next_value::>()?.0, }); } else { map.next_value::()?; } } contents.ok_or(serde::de::Error::missing_field("contents")) } } deserializer.deserialize_map(ItemVisitor(PhantomData::)) } } // MAPPING impl From for crate::model::Thumbnail { fn from(tn: Thumbnail) -> Self { crate::model::Thumbnail { url: tn.url, width: tn.width, height: tn.height, } } } impl From for Vec { fn from(ts: Thumbnails) -> Self { ts.thumbnails .into_iter() .map(|t| crate::model::Thumbnail { url: t.url, width: t.width, height: t.height, }) .collect() } } impl From> for crate::model::Verification { fn from(badges: Vec) -> Self { badges.get(0).map_or(crate::model::Verification::None, |b| { match b.metadata_badge_renderer.style { ChannelBadgeStyle::BadgeStyleTypeVerified => Self::Verified, ChannelBadgeStyle::BadgeStyleTypeVerifiedArtist => Self::Artist, } }) } } impl From for crate::model::Verification { fn from(icon: Icon) -> Self { match icon.icon_type { IconType::Check => Self::Verified, IconType::OfficialArtistBadge => Self::Artist, IconType::Like => Self::None, } } } pub(crate) fn alerts_to_err(id: &str, alerts: Option>) -> ExtractionError { ExtractionError::NotFound { id: id.to_owned(), msg: alerts .map(|alerts| { alerts .into_iter() .map(|a| a.alert_renderer.text) .collect::>() .join(" ") .into() }) .unwrap_or_default(), } }