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,
|
param::Language,
|
||||||
serializer::{
|
serializer::{
|
||||||
text::{AccessibilityText, Text, TextComponent},
|
text::{AccessibilityText, AttributedText, Text, TextComponent},
|
||||||
MapResult,
|
MapResult,
|
||||||
},
|
},
|
||||||
util::{self, timeago, TryRemove},
|
util::{self, timeago, TryRemove},
|
||||||
|
|
@ -369,6 +369,9 @@ pub(crate) struct ChannelFullMetadata {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
pub primary_links: Vec<PrimaryLink>,
|
pub primary_links: Vec<PrimaryLink>,
|
||||||
|
#[serde(default)]
|
||||||
|
// #[serde_as(as = "VecSkipError<_>")]
|
||||||
|
pub links: Vec<ExternalLink>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
|
|
@ -380,6 +383,22 @@ pub(crate) struct PrimaryLink {
|
||||||
pub navigation_endpoint: NavigationEndpoint,
|
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 {
|
trait IsLive {
|
||||||
fn is_live(&self) -> bool;
|
fn is_live(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
@ -726,6 +745,18 @@ impl YouTubeListMapper<YouTubeItem> {
|
||||||
self.corrected_query = Some(corrected_query);
|
self.corrected_query = Some(corrected_query);
|
||||||
}
|
}
|
||||||
YouTubeListItem::ChannelAboutFullMetadataRenderer(meta) => {
|
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 {
|
self.channel_info = Some(ChannelInfo {
|
||||||
create_date: timeago::parse_textual_date_or_warn(
|
create_date: timeago::parse_textual_date_or_warn(
|
||||||
self.lang,
|
self.lang,
|
||||||
|
|
@ -736,11 +767,7 @@ impl YouTubeListMapper<YouTubeItem> {
|
||||||
view_count: meta
|
view_count: meta
|
||||||
.view_count_text
|
.view_count_text
|
||||||
.and_then(|txt| util::parse_numeric_or_warn(&txt, &mut self.warnings)),
|
.and_then(|txt| util::parse_numeric_or_warn(&txt, &mut self.warnings)),
|
||||||
links: meta
|
links,
|
||||||
.primary_links
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|l| l.navigation_endpoint.url().map(|url| (l.title, url)))
|
|
||||||
.collect(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
YouTubeListItem::RichItemRenderer { content } => {
|
YouTubeListItem::RichItemRenderer { content } => {
|
||||||
|
|
|
||||||
|
|
@ -200,8 +200,12 @@ impl<'de> Deserialize<'de> for TextComponent {
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
let mut text = RichTextInternal::deserialize(deserializer)?;
|
let text = RichTextInternal::deserialize(deserializer)?;
|
||||||
Ok(text.runs.swap_remove(0).into())
|
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 {
|
impl TryFrom<TextComponent> for crate::model::ChannelId {
|
||||||
type Error = ();
|
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 {
|
impl TextComponents {
|
||||||
/// Return the string representation of the first text component
|
/// Return the string representation of the first text component
|
||||||
pub fn first_str(&self) -> &str {
|
pub fn first_str(&self) -> &str {
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ pub fn retry_delay(
|
||||||
/// Also strips google analytics tracking parameters
|
/// Also strips google analytics tracking parameters
|
||||||
/// (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`) because google analytics is bad.
|
/// (`utm_source`, `utm_medium`, `utm_campaign`, `utm_content`) because google analytics is bad.
|
||||||
pub fn sanitize_yt_url(url: &str) -> String {
|
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()?;
|
let mut parsed_url = Url::parse(url).ok()?;
|
||||||
|
|
||||||
// Convert redirect url
|
// Convert redirect url
|
||||||
|
|
@ -260,7 +260,7 @@ pub fn sanitize_yt_url(url: &str) -> String {
|
||||||
Some(parsed_url.to_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 {
|
pub fn div_ceil(a: u32, b: u32) -> u32 {
|
||||||
|
|
|
||||||
Reference in a new issue