From e2eda901b17d69acceafc28d3a28119b689df25a Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Tue, 22 Aug 2023 22:58:28 +0200 Subject: [PATCH] fix: add support for new channel about data model --- src/client/response/video_item.rs | 39 ++++++++++++++++++++++++++----- src/serializer/text.rs | 33 ++++++++++++++++++++++++-- src/util/mod.rs | 4 ++-- 3 files changed, 66 insertions(+), 10 deletions(-) diff --git a/src/client/response/video_item.rs b/src/client/response/video_item.rs index d030ed4..3283f42 100644 --- a/src/client/response/video_item.rs +++ b/src/client/response/video_item.rs @@ -14,7 +14,7 @@ use crate::{ }, param::Language, serializer::{ - text::{AccessibilityText, Text, TextComponent}, + text::{AccessibilityText, AttributedText, Text, TextComponent}, MapResult, }, util::{self, timeago, TryRemove}, @@ -369,6 +369,9 @@ pub(crate) struct ChannelFullMetadata { #[serde(default)] #[serde_as(as = "VecSkipError<_>")] pub primary_links: Vec, + #[serde(default)] + // #[serde_as(as = "VecSkipError<_>")] + pub links: Vec, } #[serde_as] @@ -380,6 +383,22 @@ pub(crate) struct PrimaryLink { pub navigation_endpoint: NavigationEndpoint, } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ExternalLink { + pub channel_external_link_view_model: ExternalLinkInner, +} + +#[serde_as] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct ExternalLinkInner { + #[serde_as(as = "AttributedText")] + pub title: TextComponent, + #[serde_as(as = "AttributedText")] + pub link: TextComponent, +} + trait IsLive { fn is_live(&self) -> bool; } @@ -726,6 +745,18 @@ impl YouTubeListMapper { self.corrected_query = Some(corrected_query); } YouTubeListItem::ChannelAboutFullMetadataRenderer(meta) => { + let mut links = meta + .primary_links + .into_iter() + .filter_map(|l| l.navigation_endpoint.url().map(|url| (l.title, url))) + .collect::>(); + for l in meta.links { + let l = l.channel_external_link_view_model; + if let TextComponent::Web { url, .. } = l.link { + links.push((l.title.into(), util::sanitize_yt_url(&url))); + } + } + self.channel_info = Some(ChannelInfo { create_date: timeago::parse_textual_date_or_warn( self.lang, @@ -736,11 +767,7 @@ impl YouTubeListMapper { view_count: meta .view_count_text .and_then(|txt| util::parse_numeric_or_warn(&txt, &mut self.warnings)), - links: meta - .primary_links - .into_iter() - .filter_map(|l| l.navigation_endpoint.url().map(|url| (l.title, url))) - .collect(), + links, }); } YouTubeListItem::RichItemRenderer { content } => { diff --git a/src/serializer/text.rs b/src/serializer/text.rs index f8d070d..f76e1a9 100644 --- a/src/serializer/text.rs +++ b/src/serializer/text.rs @@ -200,8 +200,12 @@ impl<'de> Deserialize<'de> for TextComponent { where D: Deserializer<'de>, { - let mut text = RichTextInternal::deserialize(deserializer)?; - Ok(text.runs.swap_remove(0).into()) + let text = RichTextInternal::deserialize(deserializer)?; + text.runs + .into_iter() + .next() + .map(TextComponent::from) + .ok_or(serde::de::Error::invalid_length(0, &"at least 1")) } } @@ -289,6 +293,20 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText { } } +impl<'de> DeserializeAs<'de, TextComponent> for AttributedText { + fn deserialize_as(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let components: TextComponents = AttributedText::deserialize_as(deserializer)?; + components + .0 + .into_iter() + .next() + .ok_or(serde::de::Error::invalid_length(0, &"at least 1")) + } +} + impl TryFrom for crate::model::ChannelId { type Error = (); @@ -404,6 +422,17 @@ impl TextComponent { } } +impl From for String { + fn from(value: TextComponent) -> Self { + match value { + TextComponent::Video { text, .. } + | TextComponent::Browse { text, .. } + | TextComponent::Web { text, .. } + | TextComponent::Text { text } => text, + } + } +} + impl TextComponents { /// Return the string representation of the first text component pub fn first_str(&self) -> &str { diff --git a/src/util/mod.rs b/src/util/mod.rs index 363d48d..5a6b82b 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -224,7 +224,7 @@ pub fn retry_delay( /// Also strips google analytics tracking parameters /// (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`) because google analytics is bad. pub fn sanitize_yt_url(url: &str) -> String { - fn sanitize_yt_url_inner(url: &str) -> Option { + fn try_sanitize_yt_url(url: &str) -> Option { let mut parsed_url = Url::parse(url).ok()?; // Convert redirect url @@ -260,7 +260,7 @@ pub fn sanitize_yt_url(url: &str) -> String { Some(parsed_url.to_string()) } - sanitize_yt_url_inner(url).unwrap_or_else(|| url.to_string()) + try_sanitize_yt_url(url).unwrap_or_else(|| url.to_string()) } pub fn div_ceil(a: u32, b: u32) -> u32 {