Compare commits

..

2 commits

13 changed files with 1543 additions and 55 deletions

View file

@ -64,15 +64,22 @@ mod tests {
use crate::{client::response, model::ChannelRss};
#[test]
fn map_channel_rss() {
let xml_path = Path::new("testfiles/channel_rss/base.xml");
use rstest::rstest;
#[rstest]
#[case::base("base")]
#[case::no_likes("no_likes")]
#[case::no_channel_id("no_channel_id")]
fn map_channel_rss(#[case] name: &str) {
let filename = format!("testfiles/channel_rss/{}.xml", name);
let xml_path = Path::new(&filename);
let xml_file = File::open(xml_path).unwrap();
let feed: response::ChannelRss =
quick_xml::de::from_reader(BufReader::new(xml_file)).unwrap();
let map_res: ChannelRss = feed.into();
insta::assert_ron_snapshot!("map_channel_rss", map_res);
insta::assert_ron_snapshot!(format!("map_channel_rss_{}", name), map_res);
}
}

View file

@ -1,10 +1,8 @@
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::{DefaultOnError, VecSkipError};
use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError};
use super::url_endpoint::NavigationEndpoint;
use super::{Alert, ChannelBadge, ContentsRenderer, ResponseContext, Thumbnails, YouTubeListItem};
use crate::serializer::ignore_any;
use crate::serializer::{text::Text, MapResult, VecLogError};
#[serde_as]
@ -108,7 +106,7 @@ pub(crate) enum ChannelContent {
},
ChannelAboutFullMetadataRenderer(ChannelFullMetadata),
#[default]
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}
@ -154,7 +152,7 @@ pub(crate) enum CarouselHeaderRendererItem {
#[serde(default)]
avatar: Thumbnails,
},
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}

View file

@ -1,6 +1,8 @@
use serde::Deserialize;
use time::OffsetDateTime;
use crate::util;
use super::Thumbnail;
#[derive(Debug, Deserialize)]
@ -9,6 +11,7 @@ pub(crate) struct ChannelRss {
pub channel_id: String,
#[serde(rename = "$unflatten=title")]
pub title: String,
pub author: Author,
#[serde(rename = "$unflatten=published", with = "time::serde::rfc3339")]
pub create_date: OffsetDateTime,
pub entry: Vec<Entry>,
@ -18,6 +21,8 @@ pub(crate) struct ChannelRss {
pub(crate) struct Entry {
#[serde(rename = "$unflatten=yt:videoId")]
pub video_id: String,
#[serde(rename = "$unflatten=yt:channelId")]
pub channel_id: String,
#[serde(rename = "$unflatten=title")]
pub title: String,
#[serde(rename = "$unflatten=published", with = "time::serde::rfc3339")]
@ -56,10 +61,42 @@ pub(crate) struct Statistics {
pub views: u64,
}
#[derive(Debug, Deserialize)]
pub(crate) struct Author {
pub uri: String,
}
impl From<ChannelRss> for crate::model::ChannelRss {
fn from(feed: ChannelRss) -> Self {
let id = if feed.channel_id.is_empty() {
feed.entry
.iter()
.find_map(|entry| {
if !entry.channel_id.is_empty() {
Some(entry.channel_id.to_owned())
} else {
None
}
})
.or_else(|| {
feed.author
.uri
.strip_prefix("https://www.youtube.com/channel/")
.and_then(|id| {
if util::CHANNEL_ID_REGEX.is_match(id).unwrap_or_default() {
Some(id.to_owned())
} else {
None
}
})
})
.unwrap_or_default()
} else {
feed.channel_id
};
Self {
id: feed.channel_id,
id,
name: feed.title,
videos: feed
.entry

View file

@ -1,5 +1,5 @@
use serde::Deserialize;
use serde_with::{serde_as, DefaultOnError, VecSkipError};
use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError};
use crate::{
model::{
@ -8,7 +8,6 @@ use crate::{
},
param::Language,
serializer::{
ignore_any,
text::{Text, TextComponents},
MapResult, VecLogError,
},
@ -31,7 +30,7 @@ pub(crate) enum ItemSection {
#[serde_as(as = "VecLogError<_>")]
contents: MapResult<Vec<MusicResponseItem>>,
},
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}
@ -201,7 +200,7 @@ pub(crate) struct PlaylistPanelRenderer {
#[serde(rename_all = "camelCase")]
pub(crate) enum PlaylistPanelVideo {
PlaylistPanelVideoRenderer(QueueMusicItem),
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}

View file

@ -1,7 +1,7 @@
use serde::Deserialize;
use serde_with::{serde_as, VecSkipError};
use serde_with::{rust::deserialize_ignore_any, serde_as, VecSkipError};
use crate::serializer::{ignore_any, text::Text};
use crate::serializer::text::Text;
use super::{music_item::MusicShelf, ContentsRenderer, SectionList, Tab};
@ -28,7 +28,7 @@ pub(crate) enum ItemSection {
#[serde_as(as = "VecSkipError<_>")]
contents: Vec<ShowingResultsFor>,
},
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}

View file

@ -1,8 +1,10 @@
use serde::Deserialize;
use serde_with::{json::JsonString, serde_as, DefaultOnError, VecSkipError};
use serde_with::{
json::JsonString, rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError,
};
use crate::serializer::text::{Text, TextComponent};
use crate::serializer::{ignore_any, MapResult, VecLogError};
use crate::serializer::{MapResult, VecLogError};
use crate::util::MappingError;
use super::{
@ -156,7 +158,7 @@ pub(crate) enum PlaylistItem {
continuation_endpoint: ContinuationEndpoint,
},
/// No video list item (e.g. ad) or unimplemented item
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}

View file

@ -1,12 +1,10 @@
#![allow(clippy::enum_variant_names)]
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::{DefaultOnError, VecSkipError};
use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError};
use crate::serializer::text::TextComponent;
use crate::serializer::{
ignore_any,
text::{AccessibilityText, AttributedText, Text, TextComponents},
MapResult, VecLogError,
};
@ -102,7 +100,7 @@ pub(crate) enum VideoResultsItem {
/// 1. sectionIdentifier: "comments-entry-point", contains number of comments
/// 2. sectionIdentifier: "comment-item-section", contains continuation token
ItemSectionRenderer(#[serde_as(deserialize_as = "DefaultOnError")] ItemSection),
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}
@ -335,7 +333,7 @@ pub(crate) enum EngagementPanelRenderer {
/// (Description already included in `VideoSecondaryInfoRenderer`)
/// - `engagement-panel-searchable-transcript`
/// (basically video subtitles in a different format)
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}

View file

@ -1,7 +1,9 @@
use fancy_regex::Regex;
use once_cell::sync::Lazy;
use serde::Deserialize;
use serde_with::{json::JsonString, serde_as, DefaultOnError, VecSkipError};
use serde_with::{
json::JsonString, rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError,
};
use time::{Duration, OffsetDateTime};
use super::{ChannelBadge, ContinuationEndpoint, Thumbnails};
@ -9,7 +11,6 @@ use crate::{
model::{Channel, ChannelId, ChannelItem, ChannelTag, PlaylistItem, VideoItem, YouTubeItem},
param::Language,
serializer::{
ignore_any,
text::{AccessibilityText, Text, TextComponent},
MapResult, VecLogError,
},
@ -69,7 +70,7 @@ pub(crate) enum YouTubeListItem {
/// Unimplemented:
/// - compactPlaylistRenderer (recommended playlists)
/// - compactRadioRenderer (recommended mix)
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,221 @@
---
source: src/client/channel_rss.rs
expression: map_res
---
ChannelRss(
id: "UCdfxp4cUWsWryZOy-o427dw",
name: "Nestlé",
videos: [
ChannelRssVideo(
id: "Kti0T9JF0go",
title: "KitKat V PTC York Production | Nestlé B-Roll",
description: "Footage from the production for the test launch of KitKat V in 2021 at Nestlés Confectionery Product Technology Center in York. \n\nYou can download this B-roll stock footage from our corporate website here: http://www.nestle.com/media/videos\n\nFor regular Nestlé updates, follow:\nhttps://www.facebook.com/Nestle \nhttps://www.instagram.com/Nestle \nhttps://www.twitter.com/Nestle",
thumbnail: Thumbnail(
url: "https://i4.ytimg.com/vi/Kti0T9JF0go/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-29T08:00:11Z",
update_date: "2022-08-29T08:18:04Z",
view_count: 422,
like_count: 0,
),
ChannelRssVideo(
id: "0L2TOCyvCH8",
title: "KitKat V Hamburg Production | Nestlé B-Roll",
description: "Footage from the production line of KitKat V at Nestlés factory in Hamburg, Germany.\n\nYou can download this B-roll stock footage from our corporate website here: http://www.nestle.com/media/videos\n\nFor regular Nestlé updates, follow:\nhttps://www.facebook.com/Nestle \nhttps://www.instagram.com/Nestle \nhttps://www.twitter.com/Nestle",
thumbnail: Thumbnail(
url: "https://i1.ytimg.com/vi/0L2TOCyvCH8/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-29T08:00:01Z",
update_date: "2022-08-29T08:18:08Z",
view_count: 500,
like_count: 0,
),
ChannelRssVideo(
id: "PIXvtaQmnjA",
title: "R+D Accelerator | Nestlé Innovates",
description: "Our R&D Accelerator brings together Nestlé scientists, students and start-ups to advance science and technology by accelerating the development of innovative products and systems.\u{a0}\u{200b}Got an idea to submit? Go ahead! https://rdaccelerator.nestle.com/\n\n#Nestlé Innovates #shorts",
thumbnail: Thumbnail(
url: "https://i1.ytimg.com/vi/PIXvtaQmnjA/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:38:47Z",
update_date: "2022-08-26T14:44:53Z",
view_count: 103,
like_count: 0,
),
ChannelRssVideo(
id: "OAq0FBdxuuI",
title: "R+D Accelerator | Nestlé Innovates",
description: "Do you have an innovation you could unlock with Nestlé partnership? Our R&D Accelerator harnesses the expertise of Nestlés global R&D network in food and nutrition as well as leading academic institutions and a wide range of innovation partners, suppliers and start-ups. Find out how to advance your idea with science and technology here: https://rdaccelerator.nestle.com/\n\n#NestleInnovates #shorts",
thumbnail: Thumbnail(
url: "https://i4.ytimg.com/vi/OAq0FBdxuuI/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:37:43Z",
update_date: "2022-08-26T14:08:38Z",
view_count: 70,
like_count: 0,
),
ChannelRssVideo(
id: "rh2ryMOIQ8k",
title: "R+D Accelerator | Nestlé Innovates",
description: "Calling all food and nutrition entrepreneurs! Got an idea adn looking for the science, technology and experts to put it into action? Check out our R&D Accelerator that brings all those together in the pursuit of developing innovative products and systems: https://rdaccelerator.nestle.com/\n\n#NestleInnovates #shorts",
thumbnail: Thumbnail(
url: "https://i3.ytimg.com/vi/rh2ryMOIQ8k/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:36:04Z",
update_date: "2022-08-26T08:24:59Z",
view_count: 85,
like_count: 0,
),
ChannelRssVideo(
id: "Q0xHfgCmb0g",
title: "R+D Accelerator | Nestlé Innovates",
description: "Our R&D Accelerator brings together Nestlé scientists, students and start-ups to advance science and technology by accelerating the development of innovative products and systems.\u{a0}\u{200b}Got an idea to submit? Go ahead! https://rdaccelerator.nestle.com/\n\n#NestleInnovates #shorts",
thumbnail: Thumbnail(
url: "https://i2.ytimg.com/vi/Q0xHfgCmb0g/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:34:38Z",
update_date: "2022-09-07T19:15:24Z",
view_count: 77,
like_count: 0,
),
ChannelRssVideo(
id: "jD_EcIB4kq4",
title: "Brain Health | Nestlé Innovates",
description: "Aging is a complex journey. Getting the most out of your health shouldnt have to be. Thats why our teams have dug into the data to support with living the fullest life in our later years. Check it out: https://nes.tl/HealthScience\n\n#NestleInnovates #shorts",
thumbnail: Thumbnail(
url: "https://i3.ytimg.com/vi/jD_EcIB4kq4/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:27:15Z",
update_date: "2022-08-26T08:38:04Z",
view_count: 39,
like_count: 0,
),
ChannelRssVideo(
id: "KNSHhxGGAik",
title: "Gut Microbiome | Nestlé Innovates",
description: "In-depth research has given us an understanding of the gut microbiome. Now check out the health solutions weve worked on as a result: https://nes.tl/HealthScience\n\n#NestleInnovates #shorts",
thumbnail: Thumbnail(
url: "https://i4.ytimg.com/vi/KNSHhxGGAik/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:26:13Z",
update_date: "2022-08-26T08:29:53Z",
view_count: 78,
like_count: 0,
),
ChannelRssVideo(
id: "bPtEA9yjHVU",
title: "Cellular Decline | Nestlé Innovates",
description: "Age-associated cellular decline can reduce a person\'s energy level, muscle function, immune response, and overall health. Our research and development teams believe health science can help address this. Discover the results their work has yielded: https://nes.tl/HealthScience\n\n#NestleInnovates #shorts",
thumbnail: Thumbnail(
url: "https://i3.ytimg.com/vi/bPtEA9yjHVU/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:24:26Z",
update_date: "2022-08-26T08:23:43Z",
view_count: 61,
like_count: 0,
),
ChannelRssVideo(
id: "T0ugeidZ44k",
title: "Nescafé Gold Roastery Collection | Nestlé Innovates",
description: "Our Nescafé Gold Blend Roastery Collection is carefully crafted for a great taste thanks to our teams\' experimenting with roasting levels and times. With a range of flavors and intensities to choose from, theres something for everyone. Find your fave: https://nes.tl/NescafeGoldCollection \n\n#NestleInnovates #Shorts",
thumbnail: Thumbnail(
url: "https://i1.ytimg.com/vi/T0ugeidZ44k/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:21:43Z",
update_date: "2022-08-26T08:35:57Z",
view_count: 61,
like_count: 0,
),
ChannelRssVideo(
id: "DhMa-669pKU",
title: "Garden Gourmet Vuna | Nestlé Innovates",
description: "Cracking open a jar of Vuna you don\'t believe you\'re eating anything other than tuna. This is thanks to our creative R&D colleagues. Check out this groundbreaking creation made from only six ingredients: https://nes.tl/VUNA-Story \n\n#NestleInnovates #Shorts",
thumbnail: Thumbnail(
url: "https://i1.ytimg.com/vi/DhMa-669pKU/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:15:24Z",
update_date: "2022-08-26T06:51:51Z",
view_count: 281,
like_count: 0,
),
ChannelRssVideo(
id: "IVHIRHyEha0",
title: "Wunda | Nestlé Innovates",
description: "A milk-alternative powered by pea-protein and carbon neutral from creation sounds to good to be true? Not so. Check out the innovation that brought Wunda to life: https://nes.tl/WundaStory \n\n#NestleInnovates #Shorts",
thumbnail: Thumbnail(
url: "https://i2.ytimg.com/vi/IVHIRHyEha0/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:14:29Z",
update_date: "2022-08-26T06:49:23Z",
view_count: 63,
like_count: 0,
),
ChannelRssVideo(
id: "EKsM6WrGR7k",
title: "Plant-based Milo | Nestlé Innovates",
description: "Our Milo teams wanted to ensure that everyone has the chance to enjoy their iconic chocolate-malt flavor and crunch. That\'s why they\'ve created nutritious, plant-based options. Learn more here: https://nes.tl/MiloPlantBased \n\n#NestleInnovates #Shorts",
thumbnail: Thumbnail(
url: "https://i2.ytimg.com/vi/EKsM6WrGR7k/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:13:14Z",
update_date: "2022-08-26T06:59:11Z",
view_count: 93,
like_count: 0,
),
ChannelRssVideo(
id: "VhpNpEC5RNs",
title: "Smarties Paper Packaging | Nestlé Innovates",
description: "Have you heard about the first global confectionery brand to use recyclable paper packaging? Hint: they\'re pretty smart. Get the detes here: https://nes.tl/SmartiesPaperStory\n\n#NestleInnovates #shorts",
thumbnail: Thumbnail(
url: "https://i3.ytimg.com/vi/VhpNpEC5RNs/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:11:48Z",
update_date: "2022-08-26T08:54:09Z",
view_count: 145,
like_count: 0,
),
ChannelRssVideo(
id: "Z1lTKnUzSDk",
title: "Nescafé Gold Iced Latte | Nestlé Innovates",
description: "Our coffee teams developed Nescafe Gold Iced Lattes a range for those who want to enjoy refreshing coffee shop style drinks at home. See how we\'re meeting all tastes and preferences through innovation: https://nes.tl/DailyGrinds \n\n#NestleInnovates #shorts",
thumbnail: Thumbnail(
url: "https://i3.ytimg.com/vi/Z1lTKnUzSDk/hqdefault.jpg",
width: 480,
height: 360,
),
publish_date: "2022-08-25T15:10:23Z",
update_date: "2022-08-26T06:54:55Z",
view_count: 119,
like_count: 0,
),
],
create_date: "2009-10-07T14:00:36Z",
)

View file

@ -10,8 +10,6 @@ pub use vec_log_err::VecLogError;
use std::fmt::Debug;
use serde::{de::IgnoredAny, Deserializer};
/// This represents a result from a deserializing/mapping operation.
/// It holds the desired content (`c`) and a list of warning messages,
/// if there occurred minor error during the deserializing or mapping
@ -43,33 +41,10 @@ where
}
}
/// Deserialization method that consumes anything and returns an empty value.
/// Intended to be used for a wildcard enum option.
///
/// Example:
/// ```rs
/// #[derive(Deserialize)]
/// enum Fruit {
/// Apple {
/// red: bool,
/// },
/// Banana {
/// yellow: bool,
/// },
/// #[serde(other, deserialize_with = "deserialize_blackhole")]
/// None,
/// }
/// ```
pub fn ignore_any<'de, D>(deserializer: D) -> Result<(), D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_ignored_any(IgnoredAny).and(Ok(()))
}
#[cfg(test)]
mod tests {
use serde::Deserialize;
use serde_with::rust::deserialize_ignore_any;
use super::*;
@ -81,7 +56,7 @@ mod tests {
Banana {
yellow: bool,
},
#[serde(other, deserialize_with = "ignore_any")]
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}

File diff suppressed because it is too large Load diff