fix: add support for new channel about data model
This commit is contained in:
parent
57628d1392
commit
e2eda901b1
3 changed files with 66 additions and 10 deletions
|
|
@ -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<PrimaryLink>,
|
||||
#[serde(default)]
|
||||
// #[serde_as(as = "VecSkipError<_>")]
|
||||
pub links: Vec<ExternalLink>,
|
||||
}
|
||||
|
||||
#[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<YouTubeItem> {
|
|||
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::<Vec<_>>();
|
||||
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<YouTubeItem> {
|
|||
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 } => {
|
||||
|
|
|
|||
|
|
@ -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<D>(deserializer: D) -> Result<TextComponent, D::Error>
|
||||
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<TextComponent> for crate::model::ChannelId {
|
||||
type Error = ();
|
||||
|
||||
|
|
@ -404,6 +422,17 @@ impl TextComponent {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<TextComponent> 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 {
|
||||
|
|
|
|||
|
|
@ -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<String> {
|
||||
fn try_sanitize_yt_url(url: &str) -> Option<String> {
|
||||
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 {
|
||||
|
|
|
|||
Reference in a new issue