diff --git a/src/client/channel.rs b/src/client/channel.rs index 17ee868..b892f17 100644 --- a/src/client/channel.rs +++ b/src/client/channel.rs @@ -7,7 +7,7 @@ use url::Url; use crate::{ error::{Error, ExtractionError}, model::{Channel, ChannelInfo, Paginator, PlaylistItem, VideoItem}, - param::{ChannelOrder, Language}, + param::Language, serializer::MapResult, timeago, util::{self, TryRemove}, @@ -26,11 +26,7 @@ struct QChannel<'a> { #[derive(Debug, Serialize)] enum Params { #[serde(rename = "EgZ2aWRlb3PyBgQKAjoA")] - VideosLatest, - #[serde(rename = "EgZ2aWRlb3MYAiAAMAE%3D")] - VideosOldest, - #[serde(rename = "EgZ2aWRlb3MYASAAMAE%3D")] - VideosPopular, + Videos, #[serde(rename = "EglwbGF5bGlzdHMgAQ%3D%3D")] Playlists, #[serde(rename = "EgVhYm91dPIGBAoCEgA%3D")] @@ -41,25 +37,12 @@ impl RustyPipeQuery { pub async fn channel_videos( &self, channel_id: &str, - ) -> Result>, Error> { - self.channel_videos_ordered(channel_id, ChannelOrder::default()) - .await - } - - pub async fn channel_videos_ordered( - &self, - channel_id: &str, - order: ChannelOrder, ) -> Result>, Error> { let context = self.get_context(ClientType::Desktop, true, None).await; let request_body = QChannel { context, browse_id: channel_id, - params: match order { - ChannelOrder::Latest => Params::VideosLatest, - ChannelOrder::Oldest => Params::VideosOldest, - ChannelOrder::Popular => Params::VideosPopular, - }, + params: Params::Videos, }; self.execute_request::( diff --git a/src/client/mod.rs b/src/client/mod.rs index d5c82d5..eb46e40 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -183,7 +183,6 @@ struct RustyPipeRef { reporter: Option>, n_http_retries: u32, consent_cookie: String, - visitor_data: Option, cache: CacheHolder, default_opts: RustyPipeOpts, } @@ -194,6 +193,7 @@ struct RustyPipeOpts { country: Country, report: bool, strict: bool, + visitor_data: Option, } pub struct RustyPipeBuilder { @@ -201,7 +201,6 @@ pub struct RustyPipeBuilder { reporter: Option>, n_http_retries: u32, user_agent: String, - visitor_data: Option, default_opts: RustyPipeOpts, } @@ -218,6 +217,7 @@ impl Default for RustyPipeOpts { country: Country::Us, report: false, strict: false, + visitor_data: None, } } } @@ -294,7 +294,6 @@ impl RustyPipeBuilder { reporter: Some(Box::new(FileReporter::default())), n_http_retries: 2, user_agent: DEFAULT_UA.to_owned(), - visitor_data: None, } } @@ -335,7 +334,6 @@ impl RustyPipeBuilder { CONSENT_COOKIE_YES, rand::thread_rng().gen_range(100..1000) ), - visitor_data: self.visitor_data, cache: CacheHolder { desktop_client: RwLock::new(cdata.desktop_client), music_client: RwLock::new(cdata.music_client), @@ -439,8 +437,15 @@ impl RustyPipeBuilder { self } + /// Set the default YouTube visitor data cookie pub fn visitor_data(mut self, visitor_data: &str) -> Self { - self.visitor_data = Some(visitor_data.to_owned()); + self.default_opts.visitor_data = Some(visitor_data.to_owned()); + self + } + + /// Set the default YouTube visitor data cookie to an optional value + pub fn visitor_data_opt(mut self, visitor_data: Option) -> Self { + self.default_opts.visitor_data = visitor_data; self } } @@ -748,6 +753,18 @@ impl RustyPipeQuery { self } + /// Set the YouTube visitor data cookie + pub fn visitor_data(mut self, visitor_data: &str) -> Self { + self.opts.visitor_data = Some(visitor_data.to_owned()); + self + } + + /// Set the YouTube visitor data cookie to an optional value + pub fn visitor_data_opt(mut self, visitor_data: Option) -> Self { + self.opts.visitor_data = visitor_data; + self + } + /// Create a new context object, which is included in every request to /// the YouTube API and contains language, country and device parameters. /// @@ -768,7 +785,7 @@ impl RustyPipeQuery { true => self.opts.country, false => Country::Us, }; - let visitor_data = self.client.inner.visitor_data.as_deref().or(visitor_data); + let visitor_data = self.opts.visitor_data.as_deref().or(visitor_data); match ctype { ClientType::Desktop => YTContext { diff --git a/src/param/mod.rs b/src/param/mod.rs index e5b69f3..69f8911 100644 --- a/src/param/mod.rs +++ b/src/param/mod.rs @@ -7,19 +7,6 @@ pub use locale::{Country, Language}; use serde::{Deserialize, Serialize}; pub use stream_filter::StreamFilter; -/// Channel video sort order -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] -#[non_exhaustive] -pub enum ChannelOrder { - /// Output the latest videos first - #[default] - Latest, - /// Output the oldest videos first - Oldest, - /// Output the most viewed videos first - Popular, -} - /// YouTube API endpoint to fetch continuations from #[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "lowercase")] diff --git a/tests/youtube.rs b/tests/youtube.rs index f2f0860..d48da87 100644 --- a/tests/youtube.rs +++ b/tests/youtube.rs @@ -10,10 +10,7 @@ use rustypipe::model::richtext::ToPlaintext; use rustypipe::model::{ AudioCodec, AudioFormat, Channel, UrlTarget, Verification, VideoCodec, VideoFormat, YouTubeItem, }; -use rustypipe::param::{ - search_filter::{self, SearchFilter}, - ChannelOrder, -}; +use rustypipe::param::search_filter::{self, SearchFilter}; //#PLAYER @@ -260,20 +257,41 @@ async fn get_player( } #[rstest] -#[case::not_found("86abcdefghi", "extraction error: Video cant be played because of deletion/censorship. Reason (from YT): This video is unavailable")] -#[case::deleted("64DYi_8ESh0", "extraction error: Video cant be played because of deletion/censorship. Reason (from YT): This video is unavailable")] -#[case::censored("6SJNVb0GnPI", "extraction error: Video cant be played because of deletion/censorship. Reason (from YT): This video has been removed for violating YouTube's policy on hate speech. Learn more about combating hate speech in your country.")] +#[case::not_found( + "86abcdefghi", + "extraction error: Video cant be played because of deletion/censorship. Reason (from YT): " +)] +#[case::deleted( + "64DYi_8ESh0", + "extraction error: Video cant be played because of deletion/censorship. Reason (from YT): " +)] +#[case::censored( + "6SJNVb0GnPI", + "extraction error: Video cant be played because of deletion/censorship. Reason (from YT): " +)] // This video is geoblocked outside of Japan, so expect this test case to fail when using a Japanese IP address. -#[case::geoblock("sJL6WA-aGkQ", "extraction error: Video cant be played because of DRM/Geoblock. Reason (from YT): The uploader has not made this video available in your country")] -#[case::drm("1bfOsni7EgI", "extraction error: Video cant be played because of DRM/Geoblock. Reason (from YT): This video can only be played on newer versions of Android or other supported devices.")] -#[case::private("s7_qI6_mIXc", "extraction error: Video cant be played because of private video. Reason (from YT): This video is private")] -#[case::t1("CUO8secmc0g", "extraction error: Video cant be played because of DRM/Geoblock. Reason (from YT): Playback on other websites has been disabled by the video owner")] +#[case::geoblock( + "sJL6WA-aGkQ", + "extraction error: Video cant be played because of DRM/Geoblock. Reason (from YT): " +)] +#[case::drm( + "1bfOsni7EgI", + "extraction error: Video cant be played because of DRM/Geoblock. Reason (from YT): " +)] +#[case::private( + "s7_qI6_mIXc", + "extraction error: Video cant be played because of private video. Reason (from YT): " +)] +#[case::t1( + "CUO8secmc0g", + "extraction error: Video cant be played because of DRM/Geoblock. Reason (from YT): " +)] #[tokio::test] async fn get_player_error(#[case] id: &str, #[case] msg: &str) { let rp = RustyPipe::builder().strict().build(); let err = rp.query().player(id).await.unwrap_err(); - assert_eq!(err.to_string(), msg); + assert!(err.to_string().starts_with(msg), "got error msg: {}", err); } //#PLAYLIST @@ -860,16 +878,12 @@ async fn get_video_comments() { //#CHANNEL -#[rstest] -#[case::latest(ChannelOrder::Latest)] -#[case::oldest(ChannelOrder::Oldest)] -#[case::popular(ChannelOrder::Popular)] #[tokio::test] -async fn channel_videos(#[case] order: ChannelOrder) { +async fn channel_videos() { let rp = RustyPipe::builder().strict().build(); let channel = rp .query() - .channel_videos_ordered("UC2DjFE7Xf11URZqWBigcVOQ", order) + .channel_videos("UC2DjFE7Xf11URZqWBigcVOQ") .await .unwrap(); @@ -885,21 +899,7 @@ async fn channel_videos(#[case] order: ChannelOrder) { let first_video_date = first_video.publish_date.unwrap(); let age_days = (OffsetDateTime::now_utc() - first_video_date).whole_days(); - match order { - ChannelOrder::Latest => { - assert!(age_days < 60, "latest video older than 60 days") - } - ChannelOrder::Oldest => { - assert!(age_days > 4700, "oldest video newer than 4700 days") - } - ChannelOrder::Popular => { - assert!( - first_video.view_count.unwrap() > 2300000, - "most popular video < 2.3M views" - ) - } - _ => unimplemented!(), - } + assert!(age_days < 60, "latest video older than 60 days"); let next = channel.content.next(&rp.query()).await.unwrap().unwrap(); assert!(