diff --git a/src/client/channel.rs b/src/client/channel.rs index 89334d4..6238f68 100644 --- a/src/client/channel.rs +++ b/src/client/channel.rs @@ -162,7 +162,7 @@ impl MapResponse>> for response::Channel { self, id: &str, lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>>, ExtractionError> { let content = map_channel_content(self.contents, self.alerts)?; @@ -202,7 +202,7 @@ impl MapResponse>> for response::Channel { self, id: &str, lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>>, ExtractionError> { let content = map_channel_content(self.contents, self.alerts)?; @@ -236,7 +236,7 @@ impl MapResponse> for response::Channel { self, id: &str, lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let content = map_channel_content(self.contents, self.alerts)?; let channel_data = map_channel( diff --git a/src/client/mod.rs b/src/client/mod.rs index 0615b6a..6ec5717 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -35,7 +35,7 @@ use tokio::sync::RwLock; use crate::{ cache::{CacheStorage, FileStorage}, - deobfuscate::DeobfData, + deobfuscate::{DeobfData, Deobfuscator}, error::{Error, ExtractionError}, param::{Country, Language}, report::{FileReporter, Level, Report, Reporter}, @@ -146,14 +146,6 @@ struct QBrowse<'a> { browse_id: &'a str, } -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct QBrowseParams<'a> { - context: YTContext<'a>, - browse_id: &'a str, - params: &'a str, -} - #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] struct QContinuation<'a> { @@ -714,19 +706,20 @@ impl RustyPipe { } /// Instantiate a new deobfuscator from either cached or extracted YouTube JavaScript code. - async fn get_deobf_data(&self) -> Result { + async fn get_deobf(&self) -> Result { // Write lock here to prevent concurrent tasks from fetching the same data let mut deobf_data = self.inner.cache.deobf.write().await; match deobf_data.get() { - Some(deobf_data) => Ok(deobf_data.clone()), + Some(deobf_data) => Ok(Deobfuscator::new(deobf_data.clone())?), None => { log::debug!("getting deobfuscator"); - let new_data = DeobfData::download(self.inner.http.clone()).await?; - *deobf_data = CacheEntry::from(new_data.clone()); + let data = DeobfData::download(self.inner.http.clone()).await?; + let new_deobf = Deobfuscator::new(data.clone())?; + *deobf_data = CacheEntry::from(data); drop(deobf_data); self.store_cache().await; - Ok(new_data) + Ok(new_deobf) } } } @@ -1034,7 +1027,7 @@ impl RustyPipeQuery { id: &str, endpoint: &str, body: &B, - deobf: Option<&DeobfData>, + deobf: Option<&Deobfuscator>, ) -> Result { log::debug!("getting {}({})", operation, id); @@ -1063,7 +1056,7 @@ impl RustyPipeQuery { operation: format!("{operation}({id})"), error, msgs, - deobf_data: deobf.cloned(), + deobf_data: deobf.map(Deobfuscator::get_data), http_request: crate::report::HTTPRequest { url: request_url, method: "POST".to_string(), @@ -1204,7 +1197,7 @@ trait MapResponse { self, id: &str, lang: Language, - deobf: Option<&DeobfData>, + deobf: Option<&Deobfuscator>, ) -> Result, ExtractionError>; } diff --git a/src/client/music_artist.rs b/src/client/music_artist.rs index 218ec04..287a50b 100644 --- a/src/client/music_artist.rs +++ b/src/client/music_artist.rs @@ -3,6 +3,7 @@ use std::{borrow::Cow, rc::Rc}; use futures::{stream, StreamExt}; use once_cell::sync::Lazy; use regex::Regex; +use serde::Serialize; use crate::{ error::{Error, ExtractionError}, @@ -13,9 +14,17 @@ use crate::{ use super::{ response::{self, music_item::MusicListMapper, url_endpoint::PageType}, - ClientType, MapResponse, QBrowse, QBrowseParams, RustyPipeQuery, + ClientType, MapResponse, QBrowse, RustyPipeQuery, YTContext, }; +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct QBrowseParams<'a> { + context: YTContext<'a>, + browse_id: &'a str, + params: &'a str, +} + impl RustyPipeQuery { /// Get a YouTube Music artist page /// @@ -136,7 +145,7 @@ impl MapResponse for response::MusicArtist { self, id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { let mapped = map_artist_page(self, id, lang, false)?; Ok(MapResult { @@ -151,7 +160,7 @@ impl MapResponse<(MusicArtist, Vec)> for response::MusicArtist { self, id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result)>, ExtractionError> { map_artist_page(self, id, lang, true) } @@ -182,15 +191,14 @@ fn map_artist_page( } } - let sections = res - .contents - .single_column_browse_results_renderer - .contents - .into_iter() - .next() - .and_then(|tab| tab.tab_renderer.content) - .map(|c| c.section_list_renderer.contents) - .unwrap_or_default(); + let mut content = res.contents.single_column_browse_results_renderer.contents; + let sections = content + .try_swap_remove(0) + .ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))? + .tab_renderer + .content + .section_list_renderer + .contents; let mut mapper = MusicListMapper::with_artist( lang, @@ -320,7 +328,7 @@ impl MapResponse> for response::MusicArtistAlbums { self, id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { // dbg!(&self); diff --git a/src/client/music_charts.rs b/src/client/music_charts.rs index 574787e..ff0007d 100644 --- a/src/client/music_charts.rs +++ b/src/client/music_charts.rs @@ -59,7 +59,7 @@ impl MapResponse for response::MusicCharts { self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, crate::error::ExtractionError> { let countries = self .framework_updates diff --git a/src/client/music_details.rs b/src/client/music_details.rs index e40c4d8..346dd81 100644 --- a/src/client/music_details.rs +++ b/src/client/music_details.rs @@ -156,7 +156,7 @@ impl MapResponse for response::MusicDetails { self, id: &str, lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { let tabs = self .contents @@ -232,7 +232,7 @@ impl MapResponse> for response::MusicDetails { self, _id: &str, lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let tabs = self .contents @@ -287,7 +287,7 @@ impl MapResponse for response::MusicLyrics { self, _id: &str, _lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { let lyrics = self .contents @@ -317,7 +317,7 @@ impl MapResponse for response::MusicRelated { self, _id: &str, lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { // Find artist let artist_id = self diff --git a/src/client/music_genres.rs b/src/client/music_genres.rs index 380b4b0..2b9dad9 100644 --- a/src/client/music_genres.rs +++ b/src/client/music_genres.rs @@ -1,5 +1,7 @@ use std::borrow::Cow; +use serde::Serialize; + use crate::{ error::{Error, ExtractionError}, model::{MusicGenre, MusicGenreItem, MusicGenreSection}, @@ -8,9 +10,17 @@ use crate::{ use super::{ response::{self, music_item::MusicListMapper}, - ClientType, MapResponse, QBrowse, QBrowseParams, RustyPipeQuery, + ClientType, MapResponse, QBrowse, RustyPipeQuery, YTContext, }; +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct QGenre<'a> { + context: YTContext<'a>, + browse_id: &'a str, + params: &'a str, +} + impl RustyPipeQuery { /// Get a list of moods and genres from YouTube Music pub async fn music_genres(&self) -> Result, Error> { @@ -34,7 +44,7 @@ impl RustyPipeQuery { pub async fn music_genre>(&self, genre_id: S) -> Result { let genre_id = genre_id.as_ref(); let context = self.get_context(ClientType::DesktopMusic, true, None).await; - let request_body = QBrowseParams { + let request_body = QGenre { context, browse_id: "FEmusic_moods_and_genres_category", params: genre_id, @@ -56,7 +66,7 @@ impl MapResponse> for response::MusicGenres { self, _id: &str, _lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let content = self .contents @@ -109,7 +119,7 @@ impl MapResponse for response::MusicGenre { self, id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { // dbg!(&self); diff --git a/src/client/music_new.rs b/src/client/music_new.rs index de20415..2546254 100644 --- a/src/client/music_new.rs +++ b/src/client/music_new.rs @@ -51,7 +51,7 @@ impl MapResponse> for response::MusicNew { self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let items = self .contents diff --git a/src/client/music_playlist.rs b/src/client/music_playlist.rs index 3a646ca..1e1e24a 100644 --- a/src/client/music_playlist.rs +++ b/src/client/music_playlist.rs @@ -57,19 +57,6 @@ impl RustyPipeQuery { ) .await?; - // In rare cases, albums may have track numbers =0 (example: MPREb_RM0QfZ0eSKL) - // They should be replaced with the track number derived from the previous track. - let mut n_prev = 0; - for track in album.tracks.iter_mut() { - let tn = track.track_nr.unwrap_or_default(); - if tn == 0 { - n_prev += 1; - track.track_nr = Some(n_prev); - } else { - n_prev = tn; - } - } - // YouTube Music is replacing album tracks with their respective music videos. To get the original // tracks, we have to fetch the album as a playlist and replace the offending track ids. if let Some(playlist_id) = &album.playlist_id { @@ -80,7 +67,7 @@ impl RustyPipeQuery { .enumerate() .filter_map(|(i, track)| { if track.is_video { - track.track_nr.map(|n| (i, n)) + Some((i, track.name.to_owned())) } else { None } @@ -88,12 +75,21 @@ impl RustyPipeQuery { .collect::>(); if !to_replace.is_empty() { - let playlist = self.playlist_w_unavail(playlist_id).await?; + let playlist = self.music_playlist(playlist_id).await?; - for (i, track_n) in to_replace { - if let Some(t) = playlist.videos.items.get(track_n as usize - 1) { - album.tracks[i].id = t.id.to_owned(); - album.tracks[i].duration = Some(t.length); + for (i, title) in to_replace { + let found_track = playlist.tracks.items.iter().find_map(|track| { + if track.name == title && !track.is_video { + Some((track.id.to_owned(), track.duration)) + } else { + None + } + }); + if let Some((track_id, duration)) = found_track { + album.tracks[i].id = track_id; + if let Some(duration) = duration { + album.tracks[i].duration = Some(duration); + } album.tracks[i].is_video = false; } } @@ -108,7 +104,7 @@ impl MapResponse for response::MusicPlaylist { self, id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { // dbg!(&self); @@ -246,7 +242,7 @@ impl MapResponse for response::MusicPlaylist { self, id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { // dbg!(&self); diff --git a/src/client/music_search.rs b/src/client/music_search.rs index 833152b..80de03f 100644 --- a/src/client/music_search.rs +++ b/src/client/music_search.rs @@ -230,7 +230,7 @@ impl MapResponse for response::MusicSearch { self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, crate::error::ExtractionError> { // dbg!(&self); @@ -284,7 +284,7 @@ impl MapResponse> for response::MusicSearc self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { // dbg!(&self); @@ -339,7 +339,7 @@ impl MapResponse for response::MusicSearchSuggestion { self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { let mut mapper = MusicListMapper::new(lang); let mut terms = Vec::new(); diff --git a/src/client/pagination.rs b/src/client/pagination.rs index 8a599b9..97f8e47 100644 --- a/src/client/pagination.rs +++ b/src/client/pagination.rs @@ -98,7 +98,7 @@ impl MapResponse> for response::Continuation { self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let items = self .on_response_received_actions @@ -130,7 +130,7 @@ impl MapResponse> for response::MusicContinuation { self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let mut mapper = MusicListMapper::new(lang); let mut continuations = Vec::new(); diff --git a/src/client/player.rs b/src/client/player.rs index f7bcbdc..c95b6c0 100644 --- a/src/client/player.rs +++ b/src/client/player.rs @@ -30,7 +30,7 @@ struct QPlayer<'a> { context: YTContext<'a>, /// Website playback context #[serde(skip_serializing_if = "Option::is_none")] - playback_context: Option>, + playback_context: Option, /// Content playback nonce (mobile only, 16 random chars) #[serde(skip_serializing_if = "Option::is_none")] cpn: Option, @@ -44,15 +44,15 @@ struct QPlayer<'a> { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -struct QPlaybackContext<'a> { - content_playback_context: QContentPlaybackContext<'a>, +struct QPlaybackContext { + content_playback_context: QContentPlaybackContext, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -struct QContentPlaybackContext<'a> { +struct QContentPlaybackContext { /// Signature timestamp extracted from player.js - signature_timestamp: &'a str, + signature_timestamp: String, /// Referer URL from website referer: String, } @@ -95,7 +95,7 @@ impl RustyPipeQuery { let video_id = video_id.as_ref(); let (context, deobf) = tokio::join!( self.get_context(client_type, false, None), - self.client.get_deobf_data() + self.client.get_deobf() ); let deobf = deobf?; @@ -104,7 +104,7 @@ impl RustyPipeQuery { context, playback_context: Some(QPlaybackContext { content_playback_context: QContentPlaybackContext { - signature_timestamp: &deobf.sts, + signature_timestamp: deobf.get_sts(), referer: format!("https://www.youtube.com/watch?v={video_id}"), }, }), @@ -141,10 +141,9 @@ impl MapResponse for response::Player { self, id: &str, _lang: Language, - deobf: Option<&crate::deobfuscate::DeobfData>, + deobf: Option<&Deobfuscator>, ) -> Result, ExtractionError> { - let deobf = Deobfuscator::new(deobf.unwrap()) - .map_err(|e| ExtractionError::InvalidData(e.to_string().into()))?; + let deobf = deobf.unwrap(); let mut warnings = vec![]; // Check playability status @@ -254,21 +253,21 @@ impl MapResponse for response::Player { match (f.is_video(), f.is_audio()) { (true, true) => { - let mut map_res = map_video_stream(f, &deobf, &mut last_nsig); + let mut map_res = map_video_stream(f, deobf, &mut last_nsig); warnings.append(&mut map_res.warnings); if let Some(c) = map_res.c { video_streams.push(c); }; } (true, false) => { - let mut map_res = map_video_stream(f, &deobf, &mut last_nsig); + let mut map_res = map_video_stream(f, deobf, &mut last_nsig); warnings.append(&mut map_res.warnings); if let Some(c) = map_res.c { video_only_streams.push(c); }; } (false, true) => { - let mut map_res = map_audio_stream(f, &deobf, &mut last_nsig); + let mut map_res = map_audio_stream(f, deobf, &mut last_nsig); warnings.append(&mut map_res.warnings); if let Some(c) = map_res.c { audio_streams.push(c); @@ -613,19 +612,20 @@ mod tests { use std::{fs::File, io::BufReader}; use path_macro::path; - use rstest::rstest; + use rstest::{fixture, rstest}; use super::*; use crate::deobfuscate::DeobfData; - static DEOBF_DATA: Lazy = Lazy::new(|| { - DeobfData { - js_url: "https://www.youtube.com/s/player/c8b8a173/player_ias.vflset/en_US/base.js".to_owned(), - sig_fn: "var oB={B4:function(a){a.reverse()},xm:function(a,b){a.splice(0,b)},dC:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}};var Vva=function(a){a=a.split(\"\");oB.dC(a,42);oB.xm(a,3);oB.dC(a,48);oB.B4(a,68);return a.join(\"\")};var deobf_sig=Vva;".to_owned(), - nsig_fn: "Ska=function(a){var b=a.split(\"\"),c=[-1505243983,function(d,e){e=(e%d.length+d.length)%d.length;d.splice(-e).reverse().forEach(function(f){d.unshift(f)})},\n-1692381986,function(d,e){e=(e%d.length+d.length)%d.length;var f=d[0];d[0]=d[e];d[e]=f},\n-262444939,\"unshift\",function(d){for(var e=d.length;e;)d.push(d.splice(--e,1)[0])},\n1201502951,-546377604,-504264123,-1978377336,1042456724,function(d,e){for(e=(e%d.length+d.length)%d.length;e--;)d.unshift(d.pop())},\n711986897,406699922,-1842537993,-1678108293,1803491779,1671716087,12778705,-718839990,null,null,-1617525823,342523552,-1338406651,-399705108,-696713950,b,function(d,e){e=(e%d.length+d.length)%d.length;d.splice(0,1,d.splice(e,1,d[0])[0])},\nfunction(d,e){e=(e%d.length+d.length)%d.length;d.splice(e,1)},\n-980602034,356396192,null,-1617525823,function(d,e,f){var h=f.length;d.forEach(function(l,m,n){this.push(n[m]=f[(f.indexOf(l)-f.indexOf(this[m])+m+h--)%f.length])},e.split(\"\"))},\n-1029864222,-641353250,-1681901809,-1391247867,1707415199,-1957855835,b,function(){for(var d=64,e=[];++d-e.length-32;)switch(d){case 58:d=96;continue;case 91:d=44;break;case 65:d=47;continue;case 46:d=153;case 123:d-=58;default:e.push(String.fromCharCode(d))}return e},\n-1936558978,-1505243983,function(d){d.reverse()},\n1296889058,-1813915420,-943019300,function(d,e,f){var h=f.length;d.forEach(function(l,m,n){this.push(n[m]=f[(f.indexOf(l)-f.indexOf(this[m])+m+h--)%f.length])},e.split(\"\"))},\n\"join\",b,-2061642263];c[21]=c;c[22]=c;c[33]=c;try{c[3](c[33],c[9]),c[29](c[22],c[25]),c[29](c[22],c[19]),c[29](c[33],c[17]),c[29](c[21],c[2]),c[29](c[42],c[10]),c[1](c[52],c[40]),c[12](c[28],c[8]),c[29](c[21],c[45]),c[1](c[21],c[48]),c[44](c[26]),c[39](c[5],c[2]),c[31](c[53],c[16]),c[30](c[29],c[8]),c[51](c[29],c[6],c[44]()),c[4](c[43],c[1]),c[2](c[23],c[42]),c[2](c[0],c[46]),c[38](c[14],c[52]),c[32](c[5]),c[26](c[29],c[46]),c[26](c[5],c[13]),c[28](c[1],c[37]),c[26](c[31],c[13]),c[26](c[1],c[34]),\nc[46](c[1],c[32],c[40]()),c[26](c[50],c[44]),c[17](c[50],c[51]),c[0](c[3],c[24]),c[32](c[13]),c[43](c[3],c[51]),c[0](c[34],c[17]),c[16](c[45],c[53]),c[29](c[44],c[13]),c[42](c[1],c[50]),c[47](c[22],c[53]),c[37](c[22]),c[13](c[52],c[21]),c[6](c[43],c[34]),c[6](c[31],c[46])}catch(d){return\"enhanced_except_gZYB_un-_w8_\"+a}return b.join(\"\")};var deobf_nsig=Ska;".to_owned(), - sts: "19201".to_owned(), + #[fixture] + fn deobf() -> Deobfuscator { + Deobfuscator::new(DeobfData { + js_url: "https://www.youtube.com/s/player/c8b8a173/player_ias.vflset/en_US/base.js".to_owned(), + sig_fn: "var oB={B4:function(a){a.reverse()},xm:function(a,b){a.splice(0,b)},dC:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}};var Vva=function(a){a=a.split(\"\");oB.dC(a,42);oB.xm(a,3);oB.dC(a,48);oB.B4(a,68);return a.join(\"\")};var deobf_sig=Vva;".to_owned(), + nsig_fn: "Ska=function(a){var b=a.split(\"\"),c=[-1505243983,function(d,e){e=(e%d.length+d.length)%d.length;d.splice(-e).reverse().forEach(function(f){d.unshift(f)})},\n-1692381986,function(d,e){e=(e%d.length+d.length)%d.length;var f=d[0];d[0]=d[e];d[e]=f},\n-262444939,\"unshift\",function(d){for(var e=d.length;e;)d.push(d.splice(--e,1)[0])},\n1201502951,-546377604,-504264123,-1978377336,1042456724,function(d,e){for(e=(e%d.length+d.length)%d.length;e--;)d.unshift(d.pop())},\n711986897,406699922,-1842537993,-1678108293,1803491779,1671716087,12778705,-718839990,null,null,-1617525823,342523552,-1338406651,-399705108,-696713950,b,function(d,e){e=(e%d.length+d.length)%d.length;d.splice(0,1,d.splice(e,1,d[0])[0])},\nfunction(d,e){e=(e%d.length+d.length)%d.length;d.splice(e,1)},\n-980602034,356396192,null,-1617525823,function(d,e,f){var h=f.length;d.forEach(function(l,m,n){this.push(n[m]=f[(f.indexOf(l)-f.indexOf(this[m])+m+h--)%f.length])},e.split(\"\"))},\n-1029864222,-641353250,-1681901809,-1391247867,1707415199,-1957855835,b,function(){for(var d=64,e=[];++d-e.length-32;)switch(d){case 58:d=96;continue;case 91:d=44;break;case 65:d=47;continue;case 46:d=153;case 123:d-=58;default:e.push(String.fromCharCode(d))}return e},\n-1936558978,-1505243983,function(d){d.reverse()},\n1296889058,-1813915420,-943019300,function(d,e,f){var h=f.length;d.forEach(function(l,m,n){this.push(n[m]=f[(f.indexOf(l)-f.indexOf(this[m])+m+h--)%f.length])},e.split(\"\"))},\n\"join\",b,-2061642263];c[21]=c;c[22]=c;c[33]=c;try{c[3](c[33],c[9]),c[29](c[22],c[25]),c[29](c[22],c[19]),c[29](c[33],c[17]),c[29](c[21],c[2]),c[29](c[42],c[10]),c[1](c[52],c[40]),c[12](c[28],c[8]),c[29](c[21],c[45]),c[1](c[21],c[48]),c[44](c[26]),c[39](c[5],c[2]),c[31](c[53],c[16]),c[30](c[29],c[8]),c[51](c[29],c[6],c[44]()),c[4](c[43],c[1]),c[2](c[23],c[42]),c[2](c[0],c[46]),c[38](c[14],c[52]),c[32](c[5]),c[26](c[29],c[46]),c[26](c[5],c[13]),c[28](c[1],c[37]),c[26](c[31],c[13]),c[26](c[1],c[34]),\nc[46](c[1],c[32],c[40]()),c[26](c[50],c[44]),c[17](c[50],c[51]),c[0](c[3],c[24]),c[32](c[13]),c[43](c[3],c[51]),c[0](c[34],c[17]),c[16](c[45],c[53]),c[29](c[44],c[13]),c[42](c[1],c[50]),c[47](c[22],c[53]),c[37](c[22]),c[13](c[52],c[21]),c[6](c[43],c[34]),c[6](c[31],c[46])}catch(d){return\"enhanced_except_gZYB_un-_w8_\"+a}return b.join(\"\")};var deobf_nsig=Ska;".to_owned(), + sts: "19201".to_owned(), + }).unwrap() } - }); #[rstest] #[case::desktop("desktop")] @@ -633,13 +633,13 @@ mod tests { #[case::tv_html5_embed("tvhtml5embed")] #[case::android("android")] #[case::ios("ios")] - fn map_player_data(#[case] name: &str) { + fn map_player_data(#[case] name: &str, deobf: Deobfuscator) { let json_path = path!("testfiles" / "player" / format!("{name}_video.json")); let json_file = File::open(json_path).unwrap(); let resp: response::Player = serde_json::from_reader(BufReader::new(json_file)).unwrap(); let map_res = resp - .map_response("pPvd8UxmSbQ", Language::En, Some(&DEOBF_DATA)) + .map_response("pPvd8UxmSbQ", Language::En, Some(&deobf)) .unwrap(); assert!( @@ -661,11 +661,10 @@ mod tests { }); } - #[test] - fn cipher_to_url() { + #[rstest] + fn cipher_to_url(deobf: Deobfuscator) { let signature_cipher = "s=w%3DAe%3DA6aDNQLkViKS7LOm9QtxZJHKwb53riq9qEFw-ecBWJCAiA%3DcEg0tn3dty9jEHszfzh4Ud__bg9CEHVx4ix-7dKsIPAhIQRw8JQ0qOA&sp=sig&url=https://rr5---sn-h0jelnez.googlevideo.com/videoplayback%3Fexpire%3D1659376413%26ei%3Dvb7nYvH5BMK8gAfBj7ToBQ%26ip%3D2003%253Ade%253Aaf06%253A6300%253Ac750%253A1b77%253Ac74a%253A80e3%26id%3Do-AB_BABwrXZJN428ZwDxq5ScPn2AbcGODnRlTVhCQ3mj2%26itag%3D251%26source%3Dyoutube%26requiressl%3Dyes%26mh%3DhH%26mm%3D31%252C26%26mn%3Dsn-h0jelnez%252Csn-4g5ednsl%26ms%3Dau%252Conr%26mv%3Dm%26mvi%3D5%26pl%3D37%26initcwndbps%3D1588750%26spc%3DlT-Khi831z8dTejFIRCvCEwx_6romtM%26vprv%3D1%26mime%3Daudio%252Fwebm%26ns%3Db_Mq_qlTFcSGlG9RpwpM9xQH%26gir%3Dyes%26clen%3D3781277%26dur%3D229.301%26lmt%3D1655510291473933%26mt%3D1659354538%26fvip%3D5%26keepalive%3Dyes%26fexp%3D24001373%252C24007246%26c%3DWEB%26rbqsm%3Dfr%26txp%3D4532434%26n%3Dd2g6G2hVqWIXxedQ%26sparams%3Dexpire%252Cei%252Cip%252Cid%252Citag%252Csource%252Crequiressl%252Cspc%252Cvprv%252Cmime%252Cns%252Cgir%252Cclen%252Cdur%252Clmt%26lsparams%3Dmh%252Cmm%252Cmn%252Cms%252Cmv%252Cmvi%252Cpl%252Cinitcwndbps%26lsig%3DAG3C_xAwRQIgCKCGJ1iu4wlaGXy3jcJyU3inh9dr1FIfqYOZEG_MdmACIQCbungkQYFk7EhD6K2YvLaHFMjKOFWjw001_tLb0lPDtg%253D%253D"; let mut last_nsig: [String; 2] = ["".to_owned(), "".to_owned()]; - let deobf = Deobfuscator::new(&DEOBF_DATA).unwrap(); let map_res = map_url( &None, &Some(signature_cipher.to_owned()), diff --git a/src/client/playlist.rs b/src/client/playlist.rs index fae3622..bd60bc9 100644 --- a/src/client/playlist.rs +++ b/src/client/playlist.rs @@ -3,16 +3,15 @@ use std::{borrow::Cow, convert::TryFrom}; use time::OffsetDateTime; use crate::{ + deobfuscate::Deobfuscator, error::{Error, ExtractionError}, model::{paginator::Paginator, ChannelId, Playlist, PlaylistVideo}, + param::Language, timeago, util::{self, TryRemove}, }; -use super::{ - response, ClientType, MapResponse, MapResult, QBrowse, QBrowseParams, QContinuation, - RustyPipeQuery, -}; +use super::{response, ClientType, MapResponse, MapResult, QBrowse, QContinuation, RustyPipeQuery}; impl RustyPipeQuery { /// Get a YouTube playlist @@ -34,29 +33,6 @@ impl RustyPipeQuery { .await } - /// Get a YouTube playlist including unavailable tracks - pub(crate) async fn playlist_w_unavail>( - &self, - playlist_id: S, - ) -> Result { - let playlist_id = playlist_id.as_ref(); - let context = self.get_context(ClientType::Desktop, true, None).await; - let request_body = QBrowseParams { - context, - browse_id: &format!("VL{playlist_id}"), - params: "wgYCCAA%3D", - }; - - self.execute_request::( - ClientType::Desktop, - "playlist", - playlist_id, - "browse", - &request_body, - ) - .await - } - /// Get more playlist items using the given continuation token pub async fn playlist_continuation>( &self, @@ -84,8 +60,8 @@ impl MapResponse for response::Playlist { fn map_response( self, id: &str, - lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + lang: Language, + _deobf: Option<&Deobfuscator>, ) -> Result, ExtractionError> { let (contents, header) = match (self.contents, self.header) { (Some(contents), Some(header)) => (contents, header), @@ -207,8 +183,8 @@ impl MapResponse> for response::PlaylistCont { fn map_response( self, _id: &str, - _lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _lang: Language, + _deobf: Option<&Deobfuscator>, ) -> Result>, ExtractionError> { let mut actions = self.on_response_received_actions; let action = actions @@ -259,8 +235,6 @@ mod tests { use path_macro::path; use rstest::rstest; - use crate::param::Language; - use super::*; #[rstest] diff --git a/src/client/response/channel_rss.rs b/src/client/response/channel_rss.rs index 2ce9859..b2a963a 100644 --- a/src/client/response/channel_rss.rs +++ b/src/client/response/channel_rss.rs @@ -11,7 +11,6 @@ pub(crate) struct ChannelRss { pub author: Author, #[serde(rename = "published", with = "time::serde::rfc3339")] pub create_date: OffsetDateTime, - #[serde(default)] pub entry: Vec, } diff --git a/src/client/response/music_artist.rs b/src/client/response/music_artist.rs index c886105..e27f3a0 100644 --- a/src/client/response/music_artist.rs +++ b/src/client/response/music_artist.rs @@ -14,7 +14,7 @@ use super::{ #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct MusicArtist { - pub contents: SingleColumnBrowseResult>>>, + pub contents: SingleColumnBrowseResult>>, pub header: Header, } diff --git a/src/client/search.rs b/src/client/search.rs index 475f9f4..15b1988 100644 --- a/src/client/search.rs +++ b/src/client/search.rs @@ -3,9 +3,10 @@ use std::borrow::Cow; use serde::{de::IgnoredAny, Serialize}; use crate::{ + deobfuscate::Deobfuscator, error::{Error, ExtractionError}, model::{paginator::Paginator, SearchResult, YouTubeItem}, - param::search_filter::SearchFilter, + param::{search_filter::SearchFilter, Language}, }; use super::{response, ClientType, MapResponse, MapResult, RustyPipeQuery, YTContext}; @@ -96,8 +97,8 @@ impl MapResponse for response::Search { fn map_response( self, _id: &str, - lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + lang: Language, + _deobf: Option<&Deobfuscator>, ) -> Result, ExtractionError> { let items = self .contents diff --git a/src/client/trends.rs b/src/client/trends.rs index 043086a..6a0fba9 100644 --- a/src/client/trends.rs +++ b/src/client/trends.rs @@ -53,7 +53,7 @@ impl MapResponse> for response::Startpage { self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let mut contents = self.contents.two_column_browse_results_renderer.tabs; let grid = contents @@ -77,7 +77,7 @@ impl MapResponse> for response::Trending { self, _id: &str, lang: crate::param::Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let mut contents = self.contents.two_column_browse_results_renderer.tabs; let items = contents diff --git a/src/client/url_resolver.rs b/src/client/url_resolver.rs index 19d2fd0..c3caa64 100644 --- a/src/client/url_resolver.rs +++ b/src/client/url_resolver.rs @@ -301,7 +301,7 @@ impl MapResponse for response::ResolvedUrl { self, _id: &str, _lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { let browse_endpoint = self .endpoint diff --git a/src/client/video_details.rs b/src/client/video_details.rs index 732827f..55830cf 100644 --- a/src/client/video_details.rs +++ b/src/client/video_details.rs @@ -82,7 +82,7 @@ impl MapResponse for response::VideoDetails { self, id: &str, lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result, ExtractionError> { let mut warnings = Vec::new(); @@ -367,7 +367,7 @@ impl MapResponse> for response::VideoComments { self, _id: &str, lang: Language, - _deobf: Option<&crate::deobfuscate::DeobfData>, + _deobf: Option<&crate::deobfuscate::Deobfuscator>, ) -> Result>, ExtractionError> { let received_endpoints = self.on_response_received_endpoints; let mut warnings = received_endpoints.warnings; diff --git a/src/deobfuscate.rs b/src/deobfuscate.rs index fd59501..26f65b4 100644 --- a/src/deobfuscate.rs +++ b/src/deobfuscate.rs @@ -9,6 +9,7 @@ use crate::{error::DeobfError, util}; type Result = core::result::Result; pub struct Deobfuscator { + data: DeobfData, ctx: quick_js::Context, } @@ -41,13 +42,13 @@ impl DeobfData { } impl Deobfuscator { - pub fn new(data: &DeobfData) -> Result { + pub fn new(data: DeobfData) -> Result { let ctx = quick_js::Context::new().or(Err(DeobfError::Other("could not create QuickJS rt")))?; ctx.eval(&data.sig_fn)?; ctx.eval(&data.nsig_fn)?; - Ok(Self { ctx }) + Ok(Self { data, ctx }) } pub fn deobfuscate_sig(&self, sig: &str) -> Result { @@ -73,6 +74,14 @@ impl Deobfuscator { }, ) } + + pub fn get_sts(&self) -> String { + self.data.sts.to_owned() + } + + pub fn get_data(&self) -> DeobfData { + self.data.to_owned() + } } const DEOBF_SIG_FUNC_NAME: &str = "deobf_sig"; @@ -313,7 +322,7 @@ c[36](c[8],c[32]),c[20](c[25],c[10]),c[2](c[22],c[8]),c[32](c[20],c[16]),c[32](c #[fixture] fn deobf() -> Deobfuscator { - Deobfuscator::new(&DeobfData { + Deobfuscator::new(DeobfData { js_url: String::default(), sig_fn: SIG_DEOBF_FUNC.to_owned(), nsig_fn: NSIG_DEOBF_FUNC.to_owned(), @@ -397,7 +406,7 @@ c[36](c[8],c[32]),c[20](c[25],c[10]),c[2](c[22],c[8]),c[32](c[20],c[16]),c[32](c fn t_update() { let client = Client::new(); let deobf_data = tokio_test::block_on(DeobfData::download(client)).unwrap(); - let deobf = Deobfuscator::new(&deobf_data).unwrap(); + let deobf = Deobfuscator::new(deobf_data).unwrap(); let deobf_sig = deobf.deobfuscate_sig("GOqGOqGOq0QJ8wRAIgaryQHfplJ9xJSKFywyaSMHuuwZYsoMTAvRvfm51qIGECIA5061zWeyfMPX9hEl_U6f9J0tr7GTJMKyPf5XNrJb5fb5i").unwrap(); println!("{deobf_sig}"); diff --git a/tests/snapshots/youtube__music_album_ep.snap b/tests/snapshots/youtube__music_album_ep.snap index ad3d47b..161096d 100644 --- a/tests/snapshots/youtube__music_album_ep.snap +++ b/tests/snapshots/youtube__music_album_ep.snap @@ -43,7 +43,7 @@ MusicAlbum( TrackItem( id: "Jz-26iiDuYs", name: "Waldbrand", - duration: Some(209), + duration: Some(208), cover: [], artists: [ ArtistId( diff --git a/tests/snapshots/youtube__music_album_tn_zero.snap b/tests/snapshots/youtube__music_album_tn_zero.snap deleted file mode 100644 index c4e7f6e..0000000 --- a/tests/snapshots/youtube__music_album_tn_zero.snap +++ /dev/null @@ -1,780 +0,0 @@ ---- -source: tests/youtube.rs -expression: album ---- -MusicAlbum( - id: "MPREb_RM0QfZ0eSKL", - playlist_id: Some("OLAK5uy_kJpQ8rrI50kwRV-FTS92jdE-RAkUnFFTc"), - name: "Wake Your Mind (Deluxe Edition)", - cover: "[cover]", - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - description: Some("Wake Your Mind is the fifth studio album by German Trance duo Cosmic Gate. It was released on October 24, 2011 as a digital release on Beatport and October 31, 2011 on all other digital retailers.\n\nFrom Wikipedia (https://en.wikipedia.org/wiki/Wake_Your_Mind) under Creative Commons Attribution CC-BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0/legalcode)"), - album_type: Album, - year: Some(2011), - by_va: false, - tracks: [ - TrackItem( - id: "i2BXHjoK6Pc", - name: "Sometimes They Come Back for More", - duration: Some(448), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCeaHkytFYZHuP_5My8uhaRQ"), - name: "Arnej", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(1), - by_va: false, - ), - TrackItem( - id: "HbjCfOa8P5Y", - name: "Be Your Sound", - duration: Some(252), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCU-OklRKmlSN9FUhyvwkylg"), - name: "Emma Hewitt", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(2), - by_va: false, - ), - TrackItem( - id: "qRicdCPpo9Q", - name: "Wake Your Mind", - duration: Some(365), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCc7OiFZMRwpZXRJ6jQas2pg"), - name: "Cary Brothers", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(3), - by_va: false, - ), - TrackItem( - id: "Sdvmezb4uTw", - name: "The Theme", - duration: Some(260), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(4), - by_va: false, - ), - TrackItem( - id: "hNDXx4vaoKs", - name: "All Around You", - duration: Some(334), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UC5hgy_aZwBDZLfxrMHFoD_Q"), - name: "Aruna", - ), - ArtistId( - id: Some("UCq346_97fIcWXPiGOtqLPtg"), - name: "Shane 54", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(5), - by_va: false, - ), - TrackItem( - id: "qB1Y4-O9MRM", - name: "Never Apart", - duration: Some(330), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCkWEAKM8DvaE0dEqPgxK-3Q"), - name: "Alana Aldea", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(6), - by_va: false, - ), - TrackItem( - id: "bZkY60_Ohvs", - name: "Over the Rainbow", - duration: Some(293), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate and J\'Something", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(7), - by_va: false, - ), - TrackItem( - id: "znQfnmObaDg", - name: "Nothing Ever Lasts", - duration: Some(368), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UC2-3iA9cVO3zmfqBpI_9SAw"), - name: "Andrew Bayer", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(8), - by_va: false, - ), - TrackItem( - id: "Mu0HlrLCP44", - name: "Calm Down", - duration: Some(337), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCU-OklRKmlSN9FUhyvwkylg"), - name: "Emma Hewitt", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(9), - by_va: false, - ), - TrackItem( - id: "87L2Lqeaz4Y", - name: "Barra", - duration: Some(222), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(10), - by_va: false, - ), - TrackItem( - id: "ZPrAwsjeUBo", - name: "Drifting Away", - duration: Some(336), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCU22cXBIulEYuIjQ3ez7Cbg"), - name: "Cathy Burton", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(11), - by_va: false, - ), - TrackItem( - id: "Y9FknSw3x6U", - name: "Flying Blind", - duration: Some(365), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCYMm13OXcL9llzuervQ1_Ig"), - name: "JES", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(12), - by_va: false, - ), - TrackItem( - id: "w9SUevHpYaU", - name: "Perfect Stranger", - duration: Some(331), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(13), - by_va: false, - ), - TrackItem( - id: "3UweyDiE1Og", - name: "Beautiful Destruction", - duration: Some(361), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCkWEAKM8DvaE0dEqPgxK-3Q"), - name: "Alana Aldea", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(14), - by_va: false, - ), - TrackItem( - id: "huS3sgQ7ZiI", - name: "Free Falling [Barra]", - duration: Some(226), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UC5hgy_aZwBDZLfxrMHFoD_Q"), - name: "Aruna", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(15), - by_va: false, - ), - TrackItem( - id: "PbTuejdARwQ", - name: "Sometimes They Come Back for More (Stoneface & Terminal Remix)", - duration: Some(463), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate and Arnej", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(16), - by_va: false, - ), - TrackItem( - id: "B1u98t6fwjs", - name: "Be Your Sound (Orjan Nilsen Remix)", - duration: Some(537), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCU-OklRKmlSN9FUhyvwkylg"), - name: "Emma Hewitt", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(17), - by_va: false, - ), - TrackItem( - id: "npQDHZh3xps", - name: "Wake Your Mind (Tritonal Remix)", - duration: Some(416), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCc7OiFZMRwpZXRJ6jQas2pg"), - name: "Cary Brothers", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(18), - by_va: false, - ), - TrackItem( - id: "mbgIthuB8dY", - name: "The Blue Theme (Ferry Corsten Fix)", - duration: Some(446), - cover: [], - artists: [ - ArtistId( - id: Some("UC53Zmeku4tigP7KwAHxWX8A"), - name: "System F", - ), - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC53Zmeku4tigP7KwAHxWX8A"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(19), - by_va: false, - ), - TrackItem( - id: "uMfVA0Atofk", - name: "All Around You (Alexander Popov Remix)", - duration: Some(437), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCq346_97fIcWXPiGOtqLPtg"), - name: "Shane 54", - ), - ArtistId( - id: Some("UC5hgy_aZwBDZLfxrMHFoD_Q"), - name: "Aruna", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(20), - by_va: false, - ), - TrackItem( - id: "W_8gRFJOLsY", - name: "Never Apart (Steve Brian Remix)", - duration: Some(406), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCkWEAKM8DvaE0dEqPgxK-3Q"), - name: "Alana Aldea", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(21), - by_va: false, - ), - TrackItem( - id: "p_0jK0XDrg8", - name: "Over the Rainbow (W&W Remix)", - duration: Some(339), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCx6EvdNe0luLrC_Rj8Wk10w"), - name: "J’Something", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(22), - by_va: false, - ), - TrackItem( - id: "CdCjMlyjpAg", - name: "Nothing Ever Lasts (Nitrous Oxide Remix)", - duration: Some(455), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate and Andrew Bayer", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(23), - by_va: false, - ), - TrackItem( - id: "c7yShI25Y-Q", - name: "Calm Down (Omnia Remix)", - duration: Some(391), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCU-OklRKmlSN9FUhyvwkylg"), - name: "Emma Hewitt", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(24), - by_va: false, - ), - TrackItem( - id: "gJB7xwuvREs", - name: "Drifting Away (Faruk Sabanci Remix)", - duration: Some(393), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ArtistId( - id: Some("UCU22cXBIulEYuIjQ3ez7Cbg"), - name: "Cathy Burton", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(25), - by_va: false, - ), - TrackItem( - id: "lfOhL0ah0lw", - name: "Flying Blind (Tom Fall Remix)", - duration: Some(469), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate and JES", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(26), - by_va: false, - ), - TrackItem( - id: "ilrtbPk2RE8", - name: "Perfect Stranger (Wezz Devall Remix)", - duration: Some(404), - cover: [], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album: Some(AlbumId( - id: "MPREb_RM0QfZ0eSKL", - name: "Wake Your Mind (Deluxe Edition)", - )), - view_count: None, - is_video: false, - track_nr: Some(27), - by_va: false, - ), - ], - variants: [ - AlbumItem( - id: "MPREb_75NZMCMZQW4", - name: "Wake Your Mind", - cover: [ - Thumbnail( - url: "https://lh3.googleusercontent.com/gta0XN_TQLselp1ymFyIACP2_Px4wvoSdI0XKOAWKqlSuYvRGLg9FuKPX0DkJifUYAm7fNJmRpupyvgO=w226-h226-l90-rj", - width: 226, - height: 226, - ), - Thumbnail( - url: "https://lh3.googleusercontent.com/gta0XN_TQLselp1ymFyIACP2_Px4wvoSdI0XKOAWKqlSuYvRGLg9FuKPX0DkJifUYAm7fNJmRpupyvgO=w544-h544-l90-rj", - width: 544, - height: 544, - ), - ], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album_type: Album, - year: None, - by_va: false, - ), - AlbumItem( - id: "MPREb_csntSntqO8R", - name: "Wake Your Mind", - cover: [ - Thumbnail( - url: "https://lh3.googleusercontent.com/Rxmu8lBHszFtHGyToeorDBCpT9pmNQBWZLq7KXfxysktTx-ebcrIOBwpfuNbaNtGvrAfTSvAZelB5dXT6w=w226-h226-l90-rj", - width: 226, - height: 226, - ), - Thumbnail( - url: "https://lh3.googleusercontent.com/Rxmu8lBHszFtHGyToeorDBCpT9pmNQBWZLq7KXfxysktTx-ebcrIOBwpfuNbaNtGvrAfTSvAZelB5dXT6w=w544-h544-l90-rj", - width: 544, - height: 544, - ), - ], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album_type: Album, - year: None, - by_va: false, - ), - AlbumItem( - id: "MPREb_lidKOifLvXm", - name: "Wake Your Mind (The Extended Mixes)", - cover: [ - Thumbnail( - url: "https://lh3.googleusercontent.com/Odk-iPowyYddbohhb20Zf23qopAWms68hiWS1uHX_ej4Gab0-Dh3ZuhBIdumE6rqk5XD1faZhVBK59lg1Q=w226-h226-l90-rj", - width: 226, - height: 226, - ), - Thumbnail( - url: "https://lh3.googleusercontent.com/Odk-iPowyYddbohhb20Zf23qopAWms68hiWS1uHX_ej4Gab0-Dh3ZuhBIdumE6rqk5XD1faZhVBK59lg1Q=w544-h544-l90-rj", - width: 544, - height: 544, - ), - ], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album_type: Album, - year: None, - by_va: false, - ), - AlbumItem( - id: "MPREb_qSDBQBGK1bP", - name: "Wake Your Mind (Deluxe Edition)", - cover: [ - Thumbnail( - url: "https://lh3.googleusercontent.com/R39ek9HrT7nWzVZNj2GUR3owNlbgyT7e-W-5SuPRRpLgbrE_OSTAy70LzLlk42ftNtbRJQYSMrat8VfSFg=w226-h226-l90-rj", - width: 226, - height: 226, - ), - Thumbnail( - url: "https://lh3.googleusercontent.com/R39ek9HrT7nWzVZNj2GUR3owNlbgyT7e-W-5SuPRRpLgbrE_OSTAy70LzLlk42ftNtbRJQYSMrat8VfSFg=w544-h544-l90-rj", - width: 544, - height: 544, - ), - ], - artists: [ - ArtistId( - id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - name: "Cosmic Gate", - ), - ], - artist_id: Some("UC2JiS71Dbgd_4bB4hMKebeg"), - album_type: Album, - year: None, - by_va: false, - ), - ], -) diff --git a/tests/youtube.rs b/tests/youtube.rs index a3ddee6..4eadd4c 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -996,17 +996,6 @@ mod channel_rss { assert!(!channel.videos.is_empty()); } - #[rstest] - fn get_channel_rss_empty(rp: RustyPipe) { - let channel = - tokio_test::block_on(rp.query().channel_rss("UC4fJNIVEOQ1fk15B_sqoOqg")).unwrap(); - - assert_eq!(channel.id, "UC4fJNIVEOQ1fk15B_sqoOqg"); - assert_eq!(channel.name, "Bilal Saeed - Topic"); - - assert!(channel.videos.is_empty()); - } - #[rstest] fn get_channel_rss_not_found(rp: RustyPipe) { let err = @@ -1289,7 +1278,6 @@ fn music_playlist_not_found(rp: RustyPipe) { #[case::no_year("no_year", "MPREb_F3Af9UZZVxX")] #[case::version_no_artist("version_no_artist", "MPREb_h8ltx5oKvyY")] #[case::no_artist("no_artist", "MPREb_bqWA6mAZFWS")] -#[case::tn_zero("tn_zero", "MPREb_RM0QfZ0eSKL")] fn music_album(#[case] name: &str, #[case] id: &str, rp: RustyPipe) { let album = tokio_test::block_on(rp.query().music_album(id)).unwrap();