Compare commits
No commits in common. "4ebee5856e10137da736c5fe84b37fcce647e2ff" and "9d0ae0e9c24a3ddc47d3ef7214d600a51ba3333a" have entirely different histories.
4ebee5856e
...
9d0ae0e9c2
4 changed files with 72 additions and 59 deletions
|
@ -7,7 +7,7 @@ use url::Url;
|
|||
use crate::{
|
||||
error::{Error, ExtractionError},
|
||||
model::{Channel, ChannelInfo, Paginator, PlaylistItem, VideoItem},
|
||||
param::Language,
|
||||
param::{ChannelOrder, Language},
|
||||
serializer::MapResult,
|
||||
timeago,
|
||||
util::{self, TryRemove},
|
||||
|
@ -26,7 +26,11 @@ struct QChannel<'a> {
|
|||
#[derive(Debug, Serialize)]
|
||||
enum Params {
|
||||
#[serde(rename = "EgZ2aWRlb3PyBgQKAjoA")]
|
||||
Videos,
|
||||
VideosLatest,
|
||||
#[serde(rename = "EgZ2aWRlb3MYAiAAMAE%3D")]
|
||||
VideosOldest,
|
||||
#[serde(rename = "EgZ2aWRlb3MYASAAMAE%3D")]
|
||||
VideosPopular,
|
||||
#[serde(rename = "EglwbGF5bGlzdHMgAQ%3D%3D")]
|
||||
Playlists,
|
||||
#[serde(rename = "EgVhYm91dPIGBAoCEgA%3D")]
|
||||
|
@ -37,12 +41,25 @@ impl RustyPipeQuery {
|
|||
pub async fn channel_videos(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||
self.channel_videos_ordered(channel_id, ChannelOrder::default())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn channel_videos_ordered(
|
||||
&self,
|
||||
channel_id: &str,
|
||||
order: ChannelOrder,
|
||||
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
||||
let request_body = QChannel {
|
||||
context,
|
||||
browse_id: channel_id,
|
||||
params: Params::Videos,
|
||||
params: match order {
|
||||
ChannelOrder::Latest => Params::VideosLatest,
|
||||
ChannelOrder::Oldest => Params::VideosOldest,
|
||||
ChannelOrder::Popular => Params::VideosPopular,
|
||||
},
|
||||
};
|
||||
|
||||
self.execute_request::<response::Channel, _, _>(
|
||||
|
|
|
@ -183,6 +183,7 @@ struct RustyPipeRef {
|
|||
reporter: Option<Box<dyn Reporter>>,
|
||||
n_http_retries: u32,
|
||||
consent_cookie: String,
|
||||
visitor_data: Option<String>,
|
||||
cache: CacheHolder,
|
||||
default_opts: RustyPipeOpts,
|
||||
}
|
||||
|
@ -193,7 +194,6 @@ struct RustyPipeOpts {
|
|||
country: Country,
|
||||
report: bool,
|
||||
strict: bool,
|
||||
visitor_data: Option<String>,
|
||||
}
|
||||
|
||||
pub struct RustyPipeBuilder {
|
||||
|
@ -201,6 +201,7 @@ pub struct RustyPipeBuilder {
|
|||
reporter: Option<Box<dyn Reporter>>,
|
||||
n_http_retries: u32,
|
||||
user_agent: String,
|
||||
visitor_data: Option<String>,
|
||||
default_opts: RustyPipeOpts,
|
||||
}
|
||||
|
||||
|
@ -217,7 +218,6 @@ impl Default for RustyPipeOpts {
|
|||
country: Country::Us,
|
||||
report: false,
|
||||
strict: false,
|
||||
visitor_data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -294,6 +294,7 @@ impl RustyPipeBuilder {
|
|||
reporter: Some(Box::new(FileReporter::default())),
|
||||
n_http_retries: 2,
|
||||
user_agent: DEFAULT_UA.to_owned(),
|
||||
visitor_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,6 +335,7 @@ 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),
|
||||
|
@ -437,15 +439,8 @@ impl RustyPipeBuilder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the default YouTube visitor data cookie
|
||||
pub fn visitor_data(mut self, visitor_data: &str) -> Self {
|
||||
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<String>) -> Self {
|
||||
self.default_opts.visitor_data = visitor_data;
|
||||
self.visitor_data = Some(visitor_data.to_owned());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -753,18 +748,6 @@ 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<String>) -> 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.
|
||||
///
|
||||
|
@ -785,7 +768,7 @@ impl RustyPipeQuery {
|
|||
true => self.opts.country,
|
||||
false => Country::Us,
|
||||
};
|
||||
let visitor_data = self.opts.visitor_data.as_deref().or(visitor_data);
|
||||
let visitor_data = self.client.inner.visitor_data.as_deref().or(visitor_data);
|
||||
|
||||
match ctype {
|
||||
ClientType::Desktop => YTContext {
|
||||
|
|
|
@ -7,6 +7,19 @@ 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")]
|
||||
|
|
|
@ -10,7 +10,10 @@ use rustypipe::model::richtext::ToPlaintext;
|
|||
use rustypipe::model::{
|
||||
AudioCodec, AudioFormat, Channel, UrlTarget, Verification, VideoCodec, VideoFormat, YouTubeItem,
|
||||
};
|
||||
use rustypipe::param::search_filter::{self, SearchFilter};
|
||||
use rustypipe::param::{
|
||||
search_filter::{self, SearchFilter},
|
||||
ChannelOrder,
|
||||
};
|
||||
|
||||
//#PLAYER
|
||||
|
||||
|
@ -257,41 +260,20 @@ async fn get_player(
|
|||
}
|
||||
|
||||
#[rstest]
|
||||
#[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): "
|
||||
)]
|
||||
#[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.")]
|
||||
// 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): "
|
||||
)]
|
||||
#[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): "
|
||||
)]
|
||||
#[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")]
|
||||
#[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!(err.to_string().starts_with(msg), "got error msg: {}", err);
|
||||
assert_eq!(err.to_string(), msg);
|
||||
}
|
||||
|
||||
//#PLAYLIST
|
||||
|
@ -878,12 +860,16 @@ 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() {
|
||||
async fn channel_videos(#[case] order: ChannelOrder) {
|
||||
let rp = RustyPipe::builder().strict().build();
|
||||
let channel = rp
|
||||
.query()
|
||||
.channel_videos("UC2DjFE7Xf11URZqWBigcVOQ")
|
||||
.channel_videos_ordered("UC2DjFE7Xf11URZqWBigcVOQ", order)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
@ -899,7 +885,21 @@ async fn channel_videos() {
|
|||
let first_video_date = first_video.publish_date.unwrap();
|
||||
let age_days = (OffsetDateTime::now_utc() - first_video_date).whole_days();
|
||||
|
||||
assert!(age_days < 60, "latest video older than 60 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!(),
|
||||
}
|
||||
|
||||
let next = channel.content.next(&rp.query()).await.unwrap().unwrap();
|
||||
assert!(
|
||||
|
|
Loading…
Add table
Reference in a new issue