From e2eda901b17d69acceafc28d3a28119b689df25a Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Tue, 22 Aug 2023 22:58:28 +0200 Subject: [PATCH 1/3] 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 { From 93e5ad22e96ac1fcadb9d9ebcbacbaae42f4d059 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Tue, 22 Aug 2023 23:35:07 +0200 Subject: [PATCH 2/3] feat(cli): add vdata argument --- cli/src/main.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index a79b12a..e14607d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -22,6 +22,9 @@ struct Cli { /// Always generate a report (used for debugging) #[clap(long)] report: bool, + /// YouTube visitor data cookie + #[clap(long)] + vdata: Option, } #[derive(Subcommand)] @@ -391,13 +394,14 @@ async fn main() { let cli = Cli::parse(); - let mut storage_dir = dirs::data_dir().expect("no data dir"); - storage_dir.push("rustypipe"); - std::fs::create_dir_all(&storage_dir).expect("could not create data dir"); - - let mut rp = RustyPipe::builder().storage_dir(storage_dir); + let mut rp = RustyPipe::builder().visitor_data_opt(cli.vdata); if cli.report { rp = rp.report(); + } else { + let mut storage_dir = dirs::data_dir().expect("no data dir"); + storage_dir.push("rustypipe"); + std::fs::create_dir_all(&storage_dir).expect("could not create data dir"); + rp = rp.storage_dir(storage_dir); } let rp = rp.build().unwrap(); From 22e298ff98964de53c51d950fdd04aa09e457308 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Wed, 23 Aug 2023 11:28:00 +0200 Subject: [PATCH 3/3] fix: a/b test 8: parsing view count for tracks --- Justfile | 3 + cli/src/main.rs | 4 +- codegen/src/abtest.rs | 16 + notes/AB_Tests.md | 20 +- notes/_img/ab_8.png | Bin 0 -> 13967 bytes notes/_img/ab_8_old.png | Bin 0 -> 13453 bytes src/client/music_search.rs | 2 +- src/client/response/music_item.rs | 277 ++++++++++++------ src/client/response/url_endpoint.rs | 23 +- ...__map_music_search_suggestion_default.snap | 5 +- src/deobfuscate.rs | 9 +- src/model/richtext.rs | 3 +- ...text__tests__t_attributed_description.snap | 2 +- src/serializer/text.rs | 10 +- tests/snapshots/youtube__music_album_ep.snap | 10 +- .../youtube__music_album_ep_intl.snap | 10 +- .../youtube__music_album_no_artist.snap | 9 +- .../youtube__music_album_no_artist_intl.snap | 8 +- .../youtube__music_album_no_year.snap | 2 +- .../youtube__music_album_no_year_intl.snap | 2 +- .../youtube__music_album_one_artist.snap | 36 +-- .../youtube__music_album_one_artist_intl.snap | 36 +-- .../snapshots/youtube__music_album_show.snap | 28 +- .../youtube__music_album_show_intl.snap | 28 +- .../youtube__music_album_single.snap | 2 +- .../youtube__music_album_single_intl.snap | 2 +- .../youtube__music_album_unavailable.snap | 24 +- ...youtube__music_album_unavailable_intl.snap | 24 +- .../youtube__music_album_various_artists.snap | 12 +- ...ube__music_album_various_artists_intl.snap | 13 +- ...outube__music_album_version_no_artist.snap | 9 +- ...e__music_album_version_no_artist_intl.snap | 8 +- tests/youtube.rs | 12 +- 33 files changed, 389 insertions(+), 260 deletions(-) create mode 100644 notes/_img/ab_8.png create mode 100644 notes/_img/ab_8_old.png diff --git a/Justfile b/Justfile index c2f3fdf..4628423 100644 --- a/Justfile +++ b/Justfile @@ -7,6 +7,9 @@ unittest: testyt: cargo test --features=rss --test youtube +testyt-localized: + YT_LANG=th cargo test --features=rss --test youtube + testintl: #!/usr/bin/env bash LANGUAGES=( diff --git a/cli/src/main.rs b/cli/src/main.rs index e14607d..3bef694 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -20,10 +20,10 @@ struct Cli { #[clap(subcommand)] command: Commands, /// Always generate a report (used for debugging) - #[clap(long)] + #[clap(long, global = true)] report: bool, /// YouTube visitor data cookie - #[clap(long)] + #[clap(long, global = true)] vdata: Option, } diff --git a/codegen/src/abtest.rs b/codegen/src/abtest.rs index cc9491a..b9d7f09 100644 --- a/codegen/src/abtest.rs +++ b/codegen/src/abtest.rs @@ -24,6 +24,7 @@ pub enum ABTest { TrendsPageHeaderRenderer = 5, DiscographyPage = 6, ShortDateFormat = 7, + TrackViewcount = 8, PlaylistsForShorts = 9, } @@ -96,6 +97,7 @@ pub async fn run_test( ABTest::DiscographyPage => discography_page(&query).await, ABTest::ShortDateFormat => short_date_format(&query).await, ABTest::PlaylistsForShorts => playlists_for_shorts(&query).await, + ABTest::TrackViewcount => track_viewcount(&query).await, } .unwrap(); pb.inc(1); @@ -246,6 +248,20 @@ pub async fn short_date_format(rp: &RustyPipeQuery) -> Result { })) } +pub async fn track_viewcount(rp: &RustyPipeQuery) -> Result { + let res = rp.music_search("lieblingsmensch namika").await?; + + let track = &res + .tracks + .iter() + .find(|a| a.id == "6485PhOtHzY") + .unwrap_or_else(|| { + panic!("could not find track, got {:#?}", &res.tracks); + }); + + Ok(track.view_count.is_some()) +} + pub async fn playlists_for_shorts(rp: &RustyPipeQuery) -> Result { let playlist = rp.playlist("UUSHh8gHdtzO2tXd593_bjErWg").await?; let v1 = playlist diff --git a/notes/AB_Tests.md b/notes/AB_Tests.md index f4294cd..745b1e1 100644 --- a/notes/AB_Tests.md +++ b/notes/AB_Tests.md @@ -293,7 +293,7 @@ The data model for the video shelves did not change. **NEW** -![A/B test 4 old screenshot](./_img/ab_4_new.png) +![A/B test 4 new screenshot](./_img/ab_4_new.png) ## [5] Page header renderer on the Trending page @@ -371,20 +371,32 @@ visitor data cookie to be set, as it was the case with the old system. **OLD** -![A/B test 4 old screenshot](./_img/ab_6_old.png) +![A/B test 6 old screenshot](./_img/ab_6_old.png) **NEW** -![A/B test 4 old screenshot](./_img/ab_6_new.png) +![A/B test 6 screenshot](./_img/ab_6_new.png) ## [7] Short timeago format - **Encountered on:** 28.05.2023 -- **Impact:** 🟡 Medium +- **Impact:** 🟢 Low YouTube changed their date format from the long format (_21 hours ago_, _3 days ago_) to a short format (_21h ago_, _3d ago_). +## [8] Track playback count in search results and artist views + +- **Encountered on:** 29.06.2023 +- **Impact:** 🟡 Medium + +YouTube added the track playback count to search results and top artist tracks. +In exchange, they removed the "Song" type identifier from search results. + +![A/B test 8 old screenshot](./_img/ab_8_old.png) + +![A/B test 8 screenshot](./_img/ab_8.png) + ## [9] Playlists for Shorts - **Encountered on:** 26.06.2023 diff --git a/notes/_img/ab_8.png b/notes/_img/ab_8.png new file mode 100644 index 0000000000000000000000000000000000000000..42f426b397a292e8eb8d3716fdd4f1519c3f7eca GIT binary patch literal 13967 zcmY+LWmJ@JwDt!C1Q8q>lp0zY8l(m3?(UYB?h;3kmhR>c3?b4Xjetl=cQ=SMNS~W` zz30PWErky-^UQwszV^O;`+mYyl%#QSP(x%T-fDOm?Jsx+X-u^69Uky+CGAvKSNkVhdLpq=)zJRK zEH0MFGmH2{dK4x`xFJj!S(utaifJZJpI7`d0v{dy>8tS1K{9x;i}e?r{*Hnh7rk^R zbMR8LK3}?nfV(vQ`hl76o~^D!jTZsWXb?ezR@MH2PFtwygI3p$f4-Sj&$Pbt(5alU zU*Zxho3^hzNTtxSe?51huxkKu>T*wB@I4r=!mcvFg{{u-xzN27N5d{mB^JkIuKEbY zg254x0R$8i0*UKG3xWo_;^ISx;GtcnsUeWRr;YJp5^&h7l35)2jbQoD*h759sJi5E zG*VM&FbXE(3p6MN5-ExaMTMZ@qr&Lm2roW-U(1=}XRqyVdc)`J>$t>7$`kLec4EF; z_M=E9qsM6^e}Kz&>Ocs{%_BLaFtK0=D5<$D4K)JZU#Vk&NsTa(jnmL%!dJ7*Op{5n ztd}pE$CpWI>c&}tNO`HnJR%NyNutn+MjMIV8BEX5Hyekenx>?J`W#QoWvCUWh?EO0q#!A6HdY(M13DnK0VeOc( zNCY&4;}=U0vXGIUMxQ}5O#KnrgB)vvQiX2_n8x~e{8PF}h#*wz zCnvu+(hCL^3*0+}E-PR{KH%X8eOktZi5gHtM8!S_memeg397r1X*C}N7)VyS_a=52 zgwg(~LZd}+{8ETFjpRuBg5c=P;Oa@Sd0xK1Uj@HzMxmvAZ&x14&C{)?Un^0STiE(v z?n^;NMmEOB?3MHU{2P{O10Q9&RpZmrUb=pX!Cv|U>{r7Sm7E=e!valT#3M#fhnL})9G<_e{T1erwgOB*FV{Ckw$it~h%jN<_uVxIp zw@x_~aa=`%2ou>&v4-d(Nt4*$>d;7tVc}Hs)sx1ucVeP#iA`rxr|&mxB?`Zt?G$t}L?pNjN=_>&p$&bF;e*|xK=F*QJpROxCwvL2i^>N~VR{to&85GI> zv#QJq0pVcaGl9VPq*2|yZRSD!VKqHrmQXWz6OBONRlD`zG2o^$aVB9>FQdYup;DxL zPLGLxtl2u+OxM|IyK=f+{|I~Z>pQw1rPa5P+1=f%$tx!zO4%o2YfmfWkJ$U!xudp) zCMnN}HYr7X3RVwe@A5{XQ zCd5(|27%E)xci-cMawXS2Sp2g=lyTRNk1`%16P)2Eb^(eQeiI(o+;@E(I6&#Rl1nr z2rO9D0G5jYd19zCgP3YC)*88~NzzLQ#}I@M6Pke~#Zg6$5TNUasTN64;A_)QHZB@( zeRxcnlMwx!r^GzBYo)lX>}K$Nz*yzC_@<_&7?HaM_1xaI+yqv`CPGLE&I@APP`1YP zIHkRt2wPiQy|w`VEw%Lh8ut;sTp-7$sfCD2x_&em+H_TeV=9(-QRF2`e_SvWwZo8l zPYg52s6}qVuR`WW3OfN@4E@Q!mhjc3Cij>!nIHR~)gs6W9d#+Q>s$YuSUY=efaX#y zK3G0TR7?NpTFvy8s9Qg3BoijODMSKYtkbPD$PZs>;W;A|bs2fR)ymbQqo}B8XJ@yC zz9hk!T2@vT*=?3dGHrj)=#Hng>dZoHtK382DCu0PBthrM@l^mvHoQANn5$@4LPu`F zp@QR4o>&m(I))!)+4Ez_=Ut~OgP`_NRSzvCb9J>snp6b=JWeEie--nxtAK&hLiiTC z{`1|^3#$H?FD*EEIhasggP#tO(vZ@A_$HCEBtKt*Ku}YofyQvVf7EmEtE!B{yvL38 zHUyVa$m@LOohNJU%w5H99>EWmuk@!9eE5!z4tCb|UyDol-MHr|t%BSmI8ZQ6(J-OJ zs92$SuGKph-K~oHaH+G;h321p7HqYW>T^WR71$R`^($v6oz)Qy>~OBPP4N?zP6&O537qAmWrm{X)!hA~=)9Jl8A^Z7WZv)|HGG@;AgtuXFQ|Pzw=
    YKLV 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 literal 0 HcmV?d00001 diff --git a/notes/_img/ab_8_old.png b/notes/_img/ab_8_old.png new file mode 100644 index 0000000000000000000000000000000000000000..036e1114d8691926a50bda4f2a88d552242189f3 GIT binary patch literal 13453 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 literal 0 HcmV?d00001 diff --git a/src/client/music_search.rs b/src/client/music_search.rs index f844cf9..5c3d15c 100644 --- a/src/client/music_search.rs +++ b/src/client/music_search.rs @@ -339,7 +339,7 @@ impl MapResponse for response::MusicSearchSuggestion { _deobf: Option<&crate::deobfuscate::DeobfData>, _vdata: Option<&str>, ) -> Result, ExtractionError> { - let mut mapper = MusicListMapper::new(lang); + let mut mapper = MusicListMapper::new_search_suggest(lang); let mut terms = Vec::new(); for section in self.contents { diff --git a/src/client/response/music_item.rs b/src/client/response/music_item.rs index 675b042..6c7f3cf 100644 --- a/src/client/response/music_item.rs +++ b/src/client/response/music_item.rs @@ -16,7 +16,9 @@ use crate::{ }; use super::{ - url_endpoint::{BrowseEndpointWrap, MusicPageType, NavigationEndpoint, PageType}, + url_endpoint::{ + BrowseEndpointWrap, MusicPageType, MusicVideoType, NavigationEndpoint, PageType, + }, ContentsRenderer, MusicContinuationData, Thumbnails, ThumbnailsWrap, }; @@ -429,6 +431,7 @@ pub(crate) struct MusicListMapper { artists: Option<(Vec, bool)>, album: Option, artist_page: bool, + search_suggestion: bool, items: Vec, warnings: Vec, /// True if unknown items were mapped @@ -450,6 +453,20 @@ impl MusicListMapper { artists: None, album: None, artist_page: false, + search_suggestion: false, + items: Vec::new(), + warnings: Vec::new(), + has_unknown: false, + } + } + + pub fn new_search_suggest(lang: Language) -> Self { + Self { + lang, + artists: None, + album: None, + artist_page: false, + search_suggestion: true, items: Vec::new(), warnings: Vec::new(), has_unknown: false, @@ -463,6 +480,7 @@ impl MusicListMapper { artists: Some((vec![artist], false)), album: None, artist_page: true, + search_suggestion: false, items: Vec::new(), warnings: Vec::new(), has_unknown: false, @@ -476,6 +494,7 @@ impl MusicListMapper { artists: Some((artists, by_va)), album: Some(album), artist_page: false, + search_suggestion: false, items: Vec::new(), warnings: Vec::new(), has_unknown: false, @@ -515,6 +534,7 @@ impl MusicListMapper { let c1 = columns.next(); let c2 = columns.next(); let c3 = columns.next(); + let c4 = columns.next(); let title = c1.as_ref().map(|col| col.renderer.text.to_string()); @@ -532,10 +552,8 @@ impl MusicListMapper { c1.and_then(|c1| { c1.renderer.text.0.into_iter().next().and_then(|t| match t { crate::serializer::text::TextComponent::Video { - video_id, - is_video, - .. - } => Some((MusicPageType::Track { is_video }, video_id)), + video_id, vtype, .. + } => Some((MusicPageType::Track { vtype }, video_id)), crate::serializer::text::TextComponent::Browse { page_type, browse_id, @@ -549,8 +567,12 @@ impl MusicListMapper { item.playlist_item_data.map(|d| { ( MusicPageType::Track { - is_video: self.album.is_none() - && !first_tn.map(|tn| tn.height == tn.width).unwrap_or_default(), + vtype: MusicVideoType::from_is_video( + self.album.is_none() + && !first_tn + .map(|tn| tn.height == tn.width) + .unwrap_or_default(), + ), }, d.video_id, ) @@ -561,7 +583,9 @@ impl MusicListMapper { util::video_id_from_thumbnail_url(&tn.url).map(|id| { ( MusicPageType::Track { - is_video: self.album.is_none() && tn.width != tn.height, + vtype: MusicVideoType::from_is_video( + self.album.is_none() && tn.width != tn.height, + ), }, id, ) @@ -571,19 +595,28 @@ impl MusicListMapper { match pt_id { // Track - Some((MusicPageType::Track { is_video }, id)) => { + Some((MusicPageType::Track { vtype }, id)) => { let title = title.ok_or_else(|| format!("track {id}: could not get title"))?; - let (artists_p, album_p, duration_p) = match item.flex_column_display_style { + #[derive(Default)] + struct Parsed { + artists: Option, + album: Option, + duration: Option, + view_count: Option, + } + + let p = match item.flex_column_display_style { // Search result FlexColumnDisplayStyle::TwoLines => { - // Is this a related track? - if !is_video && item.item_height == ItemHeight::Compact { - ( - c2.map(TextComponents::from), - c3.map(TextComponents::from), - None, - ) + // Is this a related track (from the "similar titles" tab in the player)? + if vtype != MusicVideoType::Video && item.item_height == ItemHeight::Compact + { + Parsed { + artists: c2.map(TextComponents::from), + album: c3.map(TextComponents::from), + ..Default::default() + } } else { let mut subtitle_parts = c2 .ok_or_else(|| format!("track {id}: could not get subtitle"))? @@ -594,62 +627,98 @@ impl MusicListMapper { // Is this a related video? if item.item_height == ItemHeight::Compact { - (subtitle_parts.next(), subtitle_parts.next(), None) + Parsed { + artists: subtitle_parts.next(), + view_count: subtitle_parts.next(), + ..Default::default() + } + } + // Is this an item from search suggestion? + else if self.search_suggestion { + // Skip first part (track type) + subtitle_parts.next(); + Parsed { + artists: subtitle_parts.next(), + album: c3.map(TextComponents::from), + view_count: subtitle_parts.next(), + ..Default::default() + } } // Is it a podcast episode? - else if subtitle_parts.len() <= 3 && c3.is_some() { - (subtitle_parts.next_back(), None, None) + else if vtype == MusicVideoType::Episode { + Parsed { + artists: subtitle_parts.next_back(), + ..Default::default() + } } else { // Skip first part (track type) if subtitle_parts.len() > 3 - || (is_video && subtitle_parts.len() == 2) + || (vtype == MusicVideoType::Video && subtitle_parts.len() == 2) { subtitle_parts.next(); } - ( - subtitle_parts.next(), - subtitle_parts.next(), - subtitle_parts.next(), - ) + match vtype { + MusicVideoType::Video => Parsed { + artists: subtitle_parts.next(), + view_count: subtitle_parts.next(), + duration: subtitle_parts.next(), + ..Default::default() + }, + _ => Parsed { + artists: subtitle_parts.next(), + album: subtitle_parts.next(), + duration: subtitle_parts.next(), + view_count: c3.map(TextComponents::from), + }, + } } } } // Playlist item - FlexColumnDisplayStyle::Default => ( - c2.map(TextComponents::from), - c3.map(TextComponents::from), - item.fixed_columns + FlexColumnDisplayStyle::Default => { + let artists = c2.map(TextComponents::from); + let duration = item + .fixed_columns .into_iter() .next() - .map(TextComponents::from), - ), + .map(TextComponents::from); + if self.album.is_some() { + Parsed { + artists, + view_count: c3.map(TextComponents::from), + duration, + ..Default::default() + } + } else if self.artist_page && c4.is_some() { + Parsed { + artists, + view_count: c3.map(TextComponents::from), + album: c4.map(TextComponents::from), + duration, + } + } else { + Parsed { + artists, + album: c3.map(TextComponents::from), + duration, + ..Default::default() + } + } + } }; - let duration = duration_p.and_then(|p| util::parse_video_length(p.first_str())); - - let (album, view_count) = match (item.flex_column_display_style, is_video) { - // The album field contains the view count for search videos - (FlexColumnDisplayStyle::TwoLines, true) => ( - None, - album_p.and_then(|p| { - util::parse_large_numstr_or_warn( - p.first_str(), - self.lang, - &mut self.warnings, - ) - }), - ), - (_, false) => ( - album_p - .and_then(|p| p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok())), - None, - ), - (FlexColumnDisplayStyle::Default, true) => (None, None), - }; - let album = album.or_else(|| self.album.clone()); - - let (mut artists, by_va) = map_artists(artists_p); + let duration = p + .duration + .and_then(|p| util::parse_video_length(p.first_str())); + let album = p + .album + .and_then(|p| p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok())) + .or_else(|| self.album.clone()); + let view_count = p.view_count.and_then(|p| { + util::parse_large_numstr_or_warn(p.first_str(), self.lang, &mut self.warnings) + }); + let (mut artists, by_va) = map_artists(p.artists); // Extract artist id from dropdown menu let artist_id = map_artist_id_fallback(item.menu, artists.first()); @@ -685,7 +754,7 @@ impl MusicListMapper { artist_id, album, view_count, - is_video, + is_video: vtype.is_video(), track_nr, by_va, })); @@ -807,7 +876,7 @@ impl MusicListMapper { match item.navigation_endpoint.music_page() { Some((page_type, id)) => match page_type { - MusicPageType::Track { is_video } => { + MusicPageType::Track { vtype } => { let (artists, by_va) = map_artists(subtitle_p1); self.items.push(MusicItem::Track(TrackItem { @@ -825,7 +894,7 @@ impl MusicListMapper { &mut self.warnings, ) }), - is_video, + is_video: vtype.is_video(), track_nr: None, by_va, })); @@ -976,43 +1045,61 @@ impl MusicListMapper { })); Some(MusicItemType::Album) } - MusicPageType::Track { is_video } => { - let (artists, by_va) = map_artists(subtitle_p2); - let duration = - subtitle_p4.and_then(|p| util::parse_video_length(p.first_str())); - let (album, view_count) = if is_video { - ( - None, - subtitle_p3.and_then(|p| { - util::parse_large_numstr_or_warn( - p.first_str(), - self.lang, - &mut self.warnings, - ) - }), - ) - } else { - ( - subtitle_p3.and_then(|p| { - p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok()) - }), - None, - ) - }; + MusicPageType::Track { vtype } => { + if vtype == MusicVideoType::Episode { + let (artists, by_va) = map_artists(subtitle_p3); - self.items.push(MusicItem::Track(TrackItem { - id, - name: card.title, - duration, - cover: card.thumbnail.into(), - artist_id: artists.first().and_then(|a| a.id.clone()), - artists, - album, - view_count, - is_video, - track_nr: None, - by_va, - })); + self.items.push(MusicItem::Track(TrackItem { + id, + name: card.title, + duration: None, + cover: card.thumbnail.into(), + artist_id: artists.first().and_then(|a| a.id.clone()), + artists, + album: None, + view_count: None, + is_video: vtype.is_video(), + track_nr: None, + by_va, + })); + } else { + let (artists, by_va) = map_artists(subtitle_p2); + let duration = + subtitle_p4.and_then(|p| util::parse_video_length(p.first_str())); + let (album, view_count) = if vtype.is_video() { + ( + None, + subtitle_p3.and_then(|p| { + util::parse_large_numstr_or_warn( + p.first_str(), + self.lang, + &mut self.warnings, + ) + }), + ) + } else { + ( + subtitle_p3.and_then(|p| { + p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok()) + }), + None, + ) + }; + + self.items.push(MusicItem::Track(TrackItem { + id, + name: card.title, + duration, + cover: card.thumbnail.into(), + artist_id: artists.first().and_then(|a| a.id.clone()), + artists, + album, + view_count, + is_video: vtype.is_video(), + track_nr: None, + by_va, + })); + } Some(MusicItemType::Track) } MusicPageType::Playlist => { diff --git a/src/client/response/url_endpoint.rs b/src/client/response/url_endpoint.rs index 4477103..bbdee34 100644 --- a/src/client/response/url_endpoint.rs +++ b/src/client/response/url_endpoint.rs @@ -151,6 +151,22 @@ pub(crate) enum MusicVideoType { Video, #[serde(rename = "MUSIC_VIDEO_TYPE_ATV")] Track, + #[serde(rename = "MUSIC_VIDEO_TYPE_PODCAST_EPISODE")] + Episode, +} + +impl MusicVideoType { + pub fn is_video(self) -> bool { + self != Self::Track + } + + pub fn from_is_video(is_video: bool) -> Self { + if is_video { + Self::Video + } else { + Self::Track + } + } } #[derive(Default, Debug, Clone, Copy, Deserialize, PartialEq, Eq)] @@ -189,7 +205,7 @@ pub(crate) enum MusicPageType { Artist, Album, Playlist, - Track { is_video: bool }, + Track { vtype: MusicVideoType }, Unknown, None, } @@ -221,11 +237,10 @@ impl NavigationEndpoint { } else { Some(( MusicPageType::Track { - is_video: watch_endpoint + vtype: watch_endpoint .watch_endpoint_music_supported_configs .watch_endpoint_music_config - .music_video_type - == MusicVideoType::Video, + .music_video_type, }, watch_endpoint.video_id, )) diff --git a/src/client/snapshots/rustypipe__client__music_search__tests__map_music_search_suggestion_default.snap b/src/client/snapshots/rustypipe__client__music_search__tests__map_music_search_suggestion_default.snap index 85ed274..719c930 100644 --- a/src/client/snapshots/rustypipe__client__music_search__tests__map_music_search_suggestion_default.snap +++ b/src/client/snapshots/rustypipe__client__music_search__tests__map_music_search_suggestion_default.snap @@ -67,7 +67,10 @@ MusicSearchSuggestion( ), ], artist_id: Some("UC56hLMPuEsERdmTBbR_JGHA"), - album: None, + album: Some(AlbumId( + id: "MPREb_kz546sNB1mH", + name: "Was Spaß macht...", + )), view_count: None, is_video: false, track_nr: None, diff --git a/src/deobfuscate.rs b/src/deobfuscate.rs index 33cb494..b7e99f5 100644 --- a/src/deobfuscate.rs +++ b/src/deobfuscate.rs @@ -142,14 +142,15 @@ fn get_sig_fn(player_js: &str) -> Result { let function_pattern = Regex::new(&function_pattern_str) .map_err(|_| DeobfError::Other("could not parse function pattern regex"))?; - let deobfuscate_function = "var ".to_owned() - + function_pattern + let deobfuscate_function = format!( + "var {};", + function_pattern .captures(player_js) .ok_or(DeobfError::Extraction("deobf function"))? .get(1) .unwrap() .as_str() - + ";"; + ); static HELPER_OBJECT_NAME_REGEX: Lazy = Lazy::new(|| Regex::new(r#";([A-Za-z0-9_\$]{2,3})\...\("#).unwrap()); @@ -203,7 +204,7 @@ fn get_nsig_fn_name(player_js: &str) -> Result { .as_str() .parse::() .or(Err(DeobfError::Other("could not parse array_num")))?; - let array_pattern_str = format!(r#"var {}\s*=\s*\[(.+?)][;,]"#, regex::escape(function_name)); + let array_pattern_str = format!(r#"var {}\s*=\s*\[(.+?)]"#, regex::escape(function_name)); let array_pattern = Regex::new(&array_pattern_str).or(Err(DeobfError::Other( "could not parse helper pattern regex", )))?; diff --git a/src/model/richtext.rs b/src/model/richtext.rs index 57e8abd..4afe63b 100644 --- a/src/model/richtext.rs +++ b/src/model/richtext.rs @@ -170,6 +170,7 @@ mod tests { use once_cell::sync::Lazy; + use crate::client::response::url_endpoint::MusicVideoType; use crate::serializer::text; static TEXT_SOURCE: Lazy = Lazy::new(|| { @@ -177,7 +178,7 @@ mod tests { text::TextComponent::Text { text: "🎧Listen and download aespa's debut single \"Black Mamba\": ".to_owned() }, text::TextComponent::Web { text: "https://smarturl.it/aespa_BlackMamba".to_owned(), url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbFY1QmpQamJPSms0Z1FnVTlQUS00ZFhBZnBJZ3xBQ3Jtc0tuRGJBanludGoyRnphb2dZWVd3cUNnS3dEd0FnNHFOZEY1NHBJaHFmLXpaWUJwX3ZucDZxVnpGeHNGX1FpMzFkZW9jQkI2Mi1wNGJ1UVFNN3h1MnN3R3JLMzdxU01nZ01POHBGcmxHU2puSUk1WHRzQQ&q=https%3A%2F%2Fsmarturl.it%2Faespa_BlackMamba&v=ZeerrnuLi5E".to_owned() }, text::TextComponent::Text { text: "\n🐍The Debut Stage ".to_owned() }, - text::TextComponent::Video { text: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned(), start_time: 0, is_video: true }, + text::TextComponent::Video { text: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned(), start_time: 0, vtype: MusicVideoType::Video }, text::TextComponent::Text { text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: ".to_owned() }, text::TextComponent::Web { text: "https://www.ticketmaster.com/event/0A...".to_owned(), url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbFpUMEZiaXJWWkszaVZXaEM0emxWU1JQV3NoQXxBQ3Jtc0tuU2g4VWNPNE5UY3hoSWYtamFzX0h4bUVQLVJiRy1ubDZrTnh3MUpGdDNSaUo0ZlMyT3lUM28ycUVBdHJLMndGcDhla3BkOFpxSVFfOS1QdVJPVHBUTEV1LXpOV0J2QXdhV05lV210cEJtZUJMeHdaTQ&q=https%3A%2F%2Fwww.ticketmaster.com%2Fevent%2F0A005CCD9E871F6E&v=ZeerrnuLi5E".to_owned() }, text::TextComponent::Text { text: "\n\nSubscribe to aespa Official YouTube Channel!\n".to_owned() }, diff --git a/src/serializer/snapshots/rustypipe__serializer__text__tests__t_attributed_description.snap b/src/serializer/snapshots/rustypipe__serializer__text__tests__t_attributed_description.snap index 4966a2f..25c6179 100644 --- a/src/serializer/snapshots/rustypipe__serializer__text__tests__t_attributed_description.snap +++ b/src/serializer/snapshots/rustypipe__serializer__text__tests__t_attributed_description.snap @@ -19,7 +19,7 @@ SAttributed { text: "aespa 에스파 'Black ...", video_id: "Ky5RT5oGg0w", start_time: 0, - is_video: true, + vtype: Video, }, Text { text: "\n\n🎟\u{fe0f} aespa Showcase SYNK in LA! Tickets now on sale: ", diff --git a/src/serializer/text.rs b/src/serializer/text.rs index f76e1a9..c3c9617 100644 --- a/src/serializer/text.rs +++ b/src/serializer/text.rs @@ -94,8 +94,7 @@ pub(crate) enum TextComponent { text: String, video_id: String, start_time: u32, - /// True if the item is a video, false if it is a YTM track - is_video: bool, + vtype: MusicVideoType, }, Browse { text: String, @@ -167,11 +166,10 @@ fn map_text_component(text: String, nav: Option) -> TextComp text, video_id: watch_endpoint.video_id, start_time: watch_endpoint.start_time_seconds, - is_video: watch_endpoint + vtype: watch_endpoint .watch_endpoint_music_supported_configs .watch_endpoint_music_config - .music_video_type - == MusicVideoType::Video, + .music_video_type, }, Some(NavigationEndpoint::Browse { browse_endpoint, @@ -612,7 +610,7 @@ mod tests { text: "DEEP", video_id: "wZIoIgz5mbs", start_time: 0, - is_video: true, + vtype: Video, }, } "###); diff --git a/tests/snapshots/youtube__music_album_ep.snap b/tests/snapshots/youtube__music_album_ep.snap index 161096d..10e5e03 100644 --- a/tests/snapshots/youtube__music_album_ep.snap +++ b/tests/snapshots/youtube__music_album_ep.snap @@ -35,7 +35,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "Waldbrand", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -56,7 +56,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "Waldbrand", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -77,7 +77,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "Waldbrand", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -98,7 +98,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "Waldbrand", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, @@ -119,7 +119,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "Waldbrand", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, diff --git a/tests/snapshots/youtube__music_album_ep_intl.snap b/tests/snapshots/youtube__music_album_ep_intl.snap index 72f3ef4..c48bcd4 100644 --- a/tests/snapshots/youtube__music_album_ep_intl.snap +++ b/tests/snapshots/youtube__music_album_ep_intl.snap @@ -35,7 +35,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -56,7 +56,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -77,7 +77,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -98,7 +98,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, @@ -119,7 +119,7 @@ MusicAlbum( id: "MPREb_u1I69lSAe5v", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, diff --git a/tests/snapshots/youtube__music_album_no_artist.snap b/tests/snapshots/youtube__music_album_no_artist.snap index c401024..60a3b5b 100644 --- a/tests/snapshots/youtube__music_album_no_artist.snap +++ b/tests/snapshots/youtube__music_album_no_artist.snap @@ -1,6 +1,5 @@ --- source: tests/youtube.rs -assertion_line: 1391 expression: album --- MusicAlbum( @@ -31,7 +30,7 @@ MusicAlbum( id: "MPREb_bqWA6mAZFWS", name: "Pedha Rasi Peddamma Katha", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -52,7 +51,7 @@ MusicAlbum( id: "MPREb_bqWA6mAZFWS", name: "Pedha Rasi Peddamma Katha", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -73,7 +72,7 @@ MusicAlbum( id: "MPREb_bqWA6mAZFWS", name: "Pedha Rasi Peddamma Katha", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -94,7 +93,7 @@ MusicAlbum( id: "MPREb_bqWA6mAZFWS", name: "Pedha Rasi Peddamma Katha", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, diff --git a/tests/snapshots/youtube__music_album_no_artist_intl.snap b/tests/snapshots/youtube__music_album_no_artist_intl.snap index 82c381b..c450608 100644 --- a/tests/snapshots/youtube__music_album_no_artist_intl.snap +++ b/tests/snapshots/youtube__music_album_no_artist_intl.snap @@ -30,7 +30,7 @@ MusicAlbum( id: "MPREb_bqWA6mAZFWS", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -51,7 +51,7 @@ MusicAlbum( id: "MPREb_bqWA6mAZFWS", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -72,7 +72,7 @@ MusicAlbum( id: "MPREb_bqWA6mAZFWS", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -93,7 +93,7 @@ MusicAlbum( id: "MPREb_bqWA6mAZFWS", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, diff --git a/tests/snapshots/youtube__music_album_no_year.snap b/tests/snapshots/youtube__music_album_no_year.snap index f5a730a..e9a913e 100644 --- a/tests/snapshots/youtube__music_album_no_year.snap +++ b/tests/snapshots/youtube__music_album_no_year.snap @@ -51,7 +51,7 @@ MusicAlbum( id: "MPREb_F3Af9UZZVxX", name: "La Ultima Vez (Remix)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, diff --git a/tests/snapshots/youtube__music_album_no_year_intl.snap b/tests/snapshots/youtube__music_album_no_year_intl.snap index 163b70b..2ff6a5f 100644 --- a/tests/snapshots/youtube__music_album_no_year_intl.snap +++ b/tests/snapshots/youtube__music_album_no_year_intl.snap @@ -51,7 +51,7 @@ MusicAlbum( id: "MPREb_F3Af9UZZVxX", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, diff --git a/tests/snapshots/youtube__music_album_one_artist.snap b/tests/snapshots/youtube__music_album_one_artist.snap index 3fe60ce..85f2d34 100644 --- a/tests/snapshots/youtube__music_album_one_artist.snap +++ b/tests/snapshots/youtube__music_album_one_artist.snap @@ -35,7 +35,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -56,7 +56,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -77,7 +77,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -98,7 +98,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, @@ -119,7 +119,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, @@ -140,7 +140,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(6), by_va: false, @@ -161,7 +161,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(7), by_va: false, @@ -182,7 +182,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(8), by_va: false, @@ -203,7 +203,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(9), by_va: false, @@ -224,7 +224,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(10), by_va: false, @@ -245,7 +245,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(11), by_va: false, @@ -266,7 +266,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(12), by_va: false, @@ -287,7 +287,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(13), by_va: false, @@ -308,7 +308,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(14), by_va: false, @@ -329,7 +329,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(15), by_va: false, @@ -350,7 +350,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(16), by_va: false, @@ -371,7 +371,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(17), by_va: false, @@ -392,7 +392,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "Märchen enden gut", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(18), by_va: false, diff --git a/tests/snapshots/youtube__music_album_one_artist_intl.snap b/tests/snapshots/youtube__music_album_one_artist_intl.snap index 5314034..e667a66 100644 --- a/tests/snapshots/youtube__music_album_one_artist_intl.snap +++ b/tests/snapshots/youtube__music_album_one_artist_intl.snap @@ -35,7 +35,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -56,7 +56,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -77,7 +77,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -98,7 +98,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, @@ -119,7 +119,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, @@ -140,7 +140,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(6), by_va: false, @@ -161,7 +161,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(7), by_va: false, @@ -182,7 +182,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(8), by_va: false, @@ -203,7 +203,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(9), by_va: false, @@ -224,7 +224,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(10), by_va: false, @@ -245,7 +245,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(11), by_va: false, @@ -266,7 +266,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(12), by_va: false, @@ -287,7 +287,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(13), by_va: false, @@ -308,7 +308,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(14), by_va: false, @@ -329,7 +329,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(15), by_va: false, @@ -350,7 +350,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(16), by_va: false, @@ -371,7 +371,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(17), by_va: false, @@ -392,7 +392,7 @@ MusicAlbum( id: "MPREb_nlBWQROfvjo", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(18), by_va: false, diff --git a/tests/snapshots/youtube__music_album_show.snap b/tests/snapshots/youtube__music_album_show.snap index e66b686..f202333 100644 --- a/tests/snapshots/youtube__music_album_show.snap +++ b/tests/snapshots/youtube__music_album_show.snap @@ -35,7 +35,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -56,7 +56,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -77,7 +77,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -98,7 +98,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, @@ -119,7 +119,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, @@ -140,7 +140,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(6), by_va: false, @@ -161,7 +161,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(7), by_va: false, @@ -182,7 +182,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(8), by_va: false, @@ -203,7 +203,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(9), by_va: false, @@ -224,7 +224,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(10), by_va: false, @@ -245,7 +245,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(11), by_va: false, @@ -266,7 +266,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(12), by_va: false, @@ -287,7 +287,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(13), by_va: false, @@ -308,7 +308,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "Folge 2: Eiszeit (Das Original-Hörspiel zur TV-Serie)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(14), by_va: false, diff --git a/tests/snapshots/youtube__music_album_show_intl.snap b/tests/snapshots/youtube__music_album_show_intl.snap index b8dda33..bac426b 100644 --- a/tests/snapshots/youtube__music_album_show_intl.snap +++ b/tests/snapshots/youtube__music_album_show_intl.snap @@ -35,7 +35,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -56,7 +56,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -77,7 +77,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -98,7 +98,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, @@ -119,7 +119,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, @@ -140,7 +140,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(6), by_va: false, @@ -161,7 +161,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(7), by_va: false, @@ -182,7 +182,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(8), by_va: false, @@ -203,7 +203,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(9), by_va: false, @@ -224,7 +224,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(10), by_va: false, @@ -245,7 +245,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(11), by_va: false, @@ -266,7 +266,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(12), by_va: false, @@ -287,7 +287,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(13), by_va: false, @@ -308,7 +308,7 @@ MusicAlbum( id: "MPREb_cwzk8EUwypZ", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(14), by_va: false, diff --git a/tests/snapshots/youtube__music_album_single.snap b/tests/snapshots/youtube__music_album_single.snap index 5061942..7b557d0 100644 --- a/tests/snapshots/youtube__music_album_single.snap +++ b/tests/snapshots/youtube__music_album_single.snap @@ -43,7 +43,7 @@ MusicAlbum( id: "MPREb_bHfHGoy7vuv", name: "Der Himmel reißt auf", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, diff --git a/tests/snapshots/youtube__music_album_single_intl.snap b/tests/snapshots/youtube__music_album_single_intl.snap index 3df94d9..c831216 100644 --- a/tests/snapshots/youtube__music_album_single_intl.snap +++ b/tests/snapshots/youtube__music_album_single_intl.snap @@ -43,7 +43,7 @@ MusicAlbum( id: "MPREb_bHfHGoy7vuv", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, diff --git a/tests/snapshots/youtube__music_album_unavailable.snap b/tests/snapshots/youtube__music_album_unavailable.snap index 2efb6db..ef83f26 100644 --- a/tests/snapshots/youtube__music_album_unavailable.snap +++ b/tests/snapshots/youtube__music_album_unavailable.snap @@ -30,7 +30,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -51,7 +51,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -72,7 +72,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -93,7 +93,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, @@ -114,7 +114,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(6), by_va: false, @@ -135,7 +135,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(7), by_va: false, @@ -156,7 +156,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(8), by_va: false, @@ -177,7 +177,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(9), by_va: false, @@ -198,7 +198,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(11), by_va: false, @@ -219,7 +219,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(12), by_va: false, @@ -240,7 +240,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(13), by_va: false, @@ -261,7 +261,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "13 Reasons Why (Season 3)", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(18), by_va: false, diff --git a/tests/snapshots/youtube__music_album_unavailable_intl.snap b/tests/snapshots/youtube__music_album_unavailable_intl.snap index 1b7b540..cdf785d 100644 --- a/tests/snapshots/youtube__music_album_unavailable_intl.snap +++ b/tests/snapshots/youtube__music_album_unavailable_intl.snap @@ -30,7 +30,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -51,7 +51,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -72,7 +72,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -93,7 +93,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, @@ -114,7 +114,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(6), by_va: false, @@ -135,7 +135,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(7), by_va: false, @@ -156,7 +156,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(8), by_va: false, @@ -177,7 +177,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(9), by_va: false, @@ -198,7 +198,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(11), by_va: false, @@ -219,7 +219,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(12), by_va: false, @@ -240,7 +240,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(13), by_va: false, @@ -261,7 +261,7 @@ MusicAlbum( id: "MPREb_AzuWg8qAVVl", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(18), by_va: false, diff --git a/tests/snapshots/youtube__music_album_various_artists.snap b/tests/snapshots/youtube__music_album_various_artists.snap index 0a335da..eac36e1 100644 --- a/tests/snapshots/youtube__music_album_various_artists.snap +++ b/tests/snapshots/youtube__music_album_various_artists.snap @@ -30,7 +30,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "<Queendom2> FINAL", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -51,7 +51,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "<Queendom2> FINAL", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -72,7 +72,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "<Queendom2> FINAL", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -93,7 +93,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "<Queendom2> FINAL", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, @@ -114,7 +114,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "<Queendom2> FINAL", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, @@ -135,7 +135,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "<Queendom2> FINAL", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(6), by_va: false, diff --git a/tests/snapshots/youtube__music_album_various_artists_intl.snap b/tests/snapshots/youtube__music_album_various_artists_intl.snap index 8f3c587..f44751e 100644 --- a/tests/snapshots/youtube__music_album_various_artists_intl.snap +++ b/tests/snapshots/youtube__music_album_various_artists_intl.snap @@ -1,6 +1,5 @@ --- source: tests/youtube.rs -assertion_line: 1396 expression: album --- MusicAlbum( @@ -31,7 +30,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -52,7 +51,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -73,7 +72,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -94,7 +93,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, @@ -115,7 +114,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(5), by_va: false, @@ -136,7 +135,7 @@ MusicAlbum( id: "MPREb_8QkDeEIawvX", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(6), by_va: false, diff --git a/tests/snapshots/youtube__music_album_version_no_artist.snap b/tests/snapshots/youtube__music_album_version_no_artist.snap index c5f9504..f1fb92f 100644 --- a/tests/snapshots/youtube__music_album_version_no_artist.snap +++ b/tests/snapshots/youtube__music_album_version_no_artist.snap @@ -1,6 +1,5 @@ --- source: tests/youtube.rs -assertion_line: 1391 expression: album --- MusicAlbum( @@ -44,7 +43,7 @@ MusicAlbum( id: "MPREb_h8ltx5oKvyY", name: "Pedha Rasi Peddamma Katha", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -65,7 +64,7 @@ MusicAlbum( id: "MPREb_h8ltx5oKvyY", name: "Pedha Rasi Peddamma Katha", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -86,7 +85,7 @@ MusicAlbum( id: "MPREb_h8ltx5oKvyY", name: "Pedha Rasi Peddamma Katha", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -107,7 +106,7 @@ MusicAlbum( id: "MPREb_h8ltx5oKvyY", name: "Pedha Rasi Peddamma Katha", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, diff --git a/tests/snapshots/youtube__music_album_version_no_artist_intl.snap b/tests/snapshots/youtube__music_album_version_no_artist_intl.snap index 805a4be..da0a781 100644 --- a/tests/snapshots/youtube__music_album_version_no_artist_intl.snap +++ b/tests/snapshots/youtube__music_album_version_no_artist_intl.snap @@ -43,7 +43,7 @@ MusicAlbum( id: "MPREb_h8ltx5oKvyY", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(1), by_va: false, @@ -64,7 +64,7 @@ MusicAlbum( id: "MPREb_h8ltx5oKvyY", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(2), by_va: false, @@ -85,7 +85,7 @@ MusicAlbum( id: "MPREb_h8ltx5oKvyY", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(3), by_va: false, @@ -106,7 +106,7 @@ MusicAlbum( id: "MPREb_h8ltx5oKvyY", name: "[name]", )), - view_count: None, + view_count: "[view_count]", is_video: false, track_nr: Some(4), by_va: false, diff --git a/tests/youtube.rs b/tests/youtube.rs index 6b54b00..933ef18 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -1429,7 +1429,7 @@ fn music_album(#[case] name: &str, #[case] id: &str, rp: RustyPipe, unlocalized: if unlocalized { insta::assert_ron_snapshot!(format!("music_album_{name}"), album, - {".cover" => "[cover]"} + {".cover" => "[cover]", ".tracks[].view_count" => "[view_count]"} ); } else { insta::assert_ron_snapshot!(format!("music_album_{name}_intl"), album, @@ -1439,6 +1439,7 @@ fn music_album(#[case] name: &str, #[case] id: &str, rp: RustyPipe, unlocalized: ".description" => "[description]", ".artists[].name" => "[name]", ".tracks[].name" => "[name]", + ".tracks[].view_count" => "[view_count]", ".tracks[].album.name" => "[name]", ".tracks[].artists[].name" => "[name]", ".variants[].artists[].name" => "[name]", @@ -1730,16 +1731,11 @@ async fn music_search_episode() { let rp = RustyPipe::builder().strict().build().unwrap(); let res = rp .query() - .music_search_videos("Blond - Da muss man dabei gewesen sein: Das Hörspiel - Fall #1") + .music_search("Blond - Da muss man dabei gewesen sein: Das Hörspiel - Fall #1") .await .unwrap(); - let track = &res - .items - .items - .iter() - .find(|a| a.id == "Zq_-LDy7AgE") - .unwrap(); + let track = &res.tracks.iter().find(|a| a.id == "Zq_-LDy7AgE").unwrap(); assert_eq!( track.name,