feat!: generate random visitorData, remove RustyPipeQuery::get_context and YTContext<'a> from public API
This commit is contained in:
parent
43805aca66
commit
448657bcb5
22 changed files with 99 additions and 258 deletions
|
|
@ -6,7 +6,7 @@ use indicatif::{ProgressBar, ProgressStyle};
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rustypipe::client::{ClientType, RustyPipe, RustyPipeQuery, YTContext};
|
use rustypipe::client::{ClientType, RustyPipe, RustyPipeQuery};
|
||||||
use rustypipe::model::{MusicItem, YouTubeItem};
|
use rustypipe::model::{MusicItem, YouTubeItem};
|
||||||
use rustypipe::param::search_filter::{ItemType, SearchFilter};
|
use rustypipe::param::search_filter::{ItemType, SearchFilter};
|
||||||
use rustypipe::param::ChannelVideoTab;
|
use rustypipe::param::ChannelVideoTab;
|
||||||
|
|
@ -57,7 +57,6 @@ pub struct ABTestRes {
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct QVideo<'a> {
|
struct QVideo<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
video_id: &'a str,
|
video_id: &'a str,
|
||||||
content_check_ok: bool,
|
content_check_ok: bool,
|
||||||
racy_check_ok: bool,
|
racy_check_ok: bool,
|
||||||
|
|
@ -66,7 +65,6 @@ struct QVideo<'a> {
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QBrowse<'a> {
|
struct QBrowse<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
browse_id: &'a str,
|
browse_id: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
params: Option<&'a str>,
|
params: Option<&'a str>,
|
||||||
|
|
@ -153,9 +151,7 @@ pub async fn run_all_tests(n: usize, concurrency: usize) -> Vec<ABTestRes> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn attributed_text_description(rp: &RustyPipeQuery) -> Result<bool> {
|
pub async fn attributed_text_description(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
let context = rp.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let q = QVideo {
|
let q = QVideo {
|
||||||
context,
|
|
||||||
video_id: "ZeerrnuLi5E",
|
video_id: "ZeerrnuLi5E",
|
||||||
content_check_ok: false,
|
content_check_ok: false,
|
||||||
racy_check_ok: false,
|
racy_check_ok: false,
|
||||||
|
|
@ -190,13 +186,11 @@ pub async fn channel_handles_in_search_results(rp: &RustyPipeQuery) -> Result<bo
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn trends_video_tab(rp: &RustyPipeQuery) -> Result<bool> {
|
pub async fn trends_video_tab(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
let context = rp.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let res = rp
|
let res = rp
|
||||||
.raw(
|
.raw(
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context,
|
|
||||||
browse_id: "FEtrending",
|
browse_id: "FEtrending",
|
||||||
params: None,
|
params: None,
|
||||||
},
|
},
|
||||||
|
|
@ -207,13 +201,11 @@ pub async fn trends_video_tab(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn trends_page_header_renderer(rp: &RustyPipeQuery) -> Result<bool> {
|
pub async fn trends_page_header_renderer(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
let context = rp.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let res = rp
|
let res = rp
|
||||||
.raw(
|
.raw(
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context,
|
|
||||||
browse_id: "FEtrending",
|
browse_id: "FEtrending",
|
||||||
params: None,
|
params: None,
|
||||||
},
|
},
|
||||||
|
|
@ -237,7 +229,6 @@ pub async fn discography_page(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
ClientType::DesktopMusic,
|
ClientType::DesktopMusic,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context: rp.get_context(ClientType::DesktopMusic, true, None).await,
|
|
||||||
browse_id: id,
|
browse_id: id,
|
||||||
params: None,
|
params: None,
|
||||||
},
|
},
|
||||||
|
|
@ -301,7 +292,6 @@ pub async fn channel_about_modal(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context: rp.get_context(ClientType::Desktop, true, None).await,
|
|
||||||
browse_id: id,
|
browse_id: id,
|
||||||
params: None,
|
params: None,
|
||||||
},
|
},
|
||||||
|
|
@ -317,7 +307,6 @@ pub async fn like_button_viewmodel(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"next",
|
"next",
|
||||||
&QVideo {
|
&QVideo {
|
||||||
context: rp.get_context(ClientType::Desktop, true, None).await,
|
|
||||||
video_id: "ZeerrnuLi5E",
|
video_id: "ZeerrnuLi5E",
|
||||||
content_check_ok: true,
|
content_check_ok: true,
|
||||||
racy_check_ok: true,
|
racy_check_ok: true,
|
||||||
|
|
@ -341,7 +330,6 @@ pub async fn music_playlist_two_column(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
ClientType::DesktopMusic,
|
ClientType::DesktopMusic,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context: rp.get_context(ClientType::DesktopMusic, true, None).await,
|
|
||||||
browse_id: id,
|
browse_id: id,
|
||||||
params: None,
|
params: None,
|
||||||
},
|
},
|
||||||
|
|
@ -355,14 +343,7 @@ pub async fn comments_framework_update(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
let continuation =
|
let continuation =
|
||||||
"Eg0SC3dMZHBSN2d1S3k4GAYyJSIRIgt3TGRwUjdndUt5ODAAeAJCEGNvbW1lbnRzLXNlY3Rpb24%3D";
|
"Eg0SC3dMZHBSN2d1S3k4GAYyJSIRIgt3TGRwUjdndUt5ODAAeAJCEGNvbW1lbnRzLXNlY3Rpb24%3D";
|
||||||
let res = rp
|
let res = rp
|
||||||
.raw(
|
.raw(ClientType::Desktop, "next", &QCont { continuation })
|
||||||
ClientType::Desktop,
|
|
||||||
"next",
|
|
||||||
&QCont {
|
|
||||||
context: rp.get_context(ClientType::Desktop, true, None).await,
|
|
||||||
continuation,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
Ok(res.contains("\"frameworkUpdates\""))
|
Ok(res.contains("\"frameworkUpdates\""))
|
||||||
|
|
@ -375,7 +356,6 @@ pub async fn channel_shorts_lockup(rp: &RustyPipeQuery) -> Result<bool> {
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context: rp.get_context(ClientType::Desktop, true, None).await,
|
|
||||||
browse_id: id,
|
browse_id: id,
|
||||||
params: Some("EgZzaG9ydHPyBgUKA5oBAA%3D%3D"),
|
params: Some("EgZzaG9ydHPyBgUKA5oBAA%3D%3D"),
|
||||||
},
|
},
|
||||||
|
|
@ -392,7 +372,6 @@ pub async fn playlist_page_header_renderer(rp: &RustyPipeQuery) -> Result<bool>
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context: rp.get_context(ClientType::Desktop, true, None).await,
|
|
||||||
browse_id: id,
|
browse_id: id,
|
||||||
params: None,
|
params: None,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -95,11 +95,7 @@ struct HeaderRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_album_type(query: &RustyPipeQuery, id: &str) -> String {
|
async fn get_album_type(query: &RustyPipeQuery, id: &str) -> String {
|
||||||
let context = query
|
|
||||||
.get_context(ClientType::DesktopMusic, true, None)
|
|
||||||
.await;
|
|
||||||
let body = QBrowse {
|
let body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: id,
|
browse_id: id,
|
||||||
params: None,
|
params: None,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -350,7 +350,6 @@ async fn get_channel(query: &RustyPipeQuery, channel_id: &str) -> Result<Channel
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context: query.get_context(ClientType::Desktop, true, None).await,
|
|
||||||
browse_id: channel_id,
|
browse_id: channel_id,
|
||||||
params: Some("EgZ2aWRlb3MYASAAMAE"),
|
params: Some("EgZ2aWRlb3MYASAAMAE"),
|
||||||
},
|
},
|
||||||
|
|
@ -392,7 +391,6 @@ async fn get_channel(query: &RustyPipeQuery, channel_id: &str) -> Result<Channel
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"browse",
|
"browse",
|
||||||
&QCont {
|
&QCont {
|
||||||
context: query.get_context(ClientType::Desktop, true, None).await,
|
|
||||||
continuation: &popular_token,
|
continuation: &popular_token,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -431,9 +429,6 @@ async fn music_channel_subscribers(query: &RustyPipeQuery, channel_id: &str) ->
|
||||||
ClientType::DesktopMusic,
|
ClientType::DesktopMusic,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context: query
|
|
||||||
.get_context(ClientType::DesktopMusic, true, None)
|
|
||||||
.await,
|
|
||||||
browse_id: channel_id,
|
browse_id: channel_id,
|
||||||
params: None,
|
params: None,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,6 @@ async fn get_channel_vlengths(
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"browse",
|
"browse",
|
||||||
&QBrowse {
|
&QBrowse {
|
||||||
context: query.get_context(ClientType::Desktop, true, None).await,
|
|
||||||
browse_id: channel_id,
|
browse_id: channel_id,
|
||||||
params: Some("EgZ2aWRlb3MYASAAMAE"),
|
params: Some("EgZ2aWRlb3MYASAAMAE"),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use ordered_hash_map::OrderedHashMap;
|
use ordered_hash_map::OrderedHashMap;
|
||||||
use rustypipe::{client::YTContext, model::AlbumType, param::Language};
|
use rustypipe::{model::AlbumType, param::Language};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::{serde_as, DefaultOnError, VecSkipError};
|
use serde_with::{serde_as, DefaultOnError, VecSkipError};
|
||||||
|
|
||||||
|
|
@ -116,7 +116,6 @@ pub enum ExtItemType {
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct QBrowse<'a> {
|
pub struct QBrowse<'a> {
|
||||||
pub context: YTContext<'a>,
|
|
||||||
pub browse_id: &'a str,
|
pub browse_id: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub params: Option<&'a str>,
|
pub params: Option<&'a str>,
|
||||||
|
|
@ -125,7 +124,6 @@ pub struct QBrowse<'a> {
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct QCont<'a> {
|
pub struct QCont<'a> {
|
||||||
pub context: YTContext<'a>,
|
|
||||||
pub continuation: &'a str,
|
pub continuation: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,11 @@ use crate::{
|
||||||
util::{self, timeago, ProtoBuilder},
|
util::{self, timeago, ProtoBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{response, ClientType, MapRespCtx, MapResponse, QContinuation, RustyPipeQuery};
|
||||||
response, ClientType, MapRespCtx, MapResponse, QContinuation, RustyPipeQuery, YTContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QChannel<'a> {
|
struct QChannel<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
browse_id: &'a str,
|
browse_id: &'a str,
|
||||||
params: ChannelTab,
|
params: ChannelTab,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -63,9 +60,7 @@ impl RustyPipeQuery {
|
||||||
operation: &str,
|
operation: &str,
|
||||||
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
) -> Result<Channel<Paginator<VideoItem>>, Error> {
|
||||||
let channel_id = channel_id.as_ref();
|
let channel_id = channel_id.as_ref();
|
||||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let request_body = QChannel {
|
let request_body = QChannel {
|
||||||
context,
|
|
||||||
browse_id: channel_id,
|
browse_id: channel_id,
|
||||||
params,
|
params,
|
||||||
query,
|
query,
|
||||||
|
|
@ -125,12 +120,10 @@ impl RustyPipeQuery {
|
||||||
tab: ChannelVideoTab,
|
tab: ChannelVideoTab,
|
||||||
order: ChannelOrder,
|
order: ChannelOrder,
|
||||||
) -> Result<Paginator<VideoItem>, Error> {
|
) -> Result<Paginator<VideoItem>, Error> {
|
||||||
let visitor_data = Some(self.get_visitor_data().await?);
|
|
||||||
|
|
||||||
self.continuation(
|
self.continuation(
|
||||||
order_ctoken(channel_id.as_ref(), tab, order, &random_target()),
|
order_ctoken(channel_id.as_ref(), tab, order, &random_target()),
|
||||||
ContinuationEndpoint::Browse,
|
ContinuationEndpoint::Browse,
|
||||||
visitor_data.as_deref(),
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -158,9 +151,7 @@ impl RustyPipeQuery {
|
||||||
channel_id: S,
|
channel_id: S,
|
||||||
) -> Result<Channel<Paginator<PlaylistItem>>, Error> {
|
) -> Result<Channel<Paginator<PlaylistItem>>, Error> {
|
||||||
let channel_id = channel_id.as_ref();
|
let channel_id = channel_id.as_ref();
|
||||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let request_body = QChannel {
|
let request_body = QChannel {
|
||||||
context,
|
|
||||||
browse_id: channel_id,
|
browse_id: channel_id,
|
||||||
params: ChannelTab::Playlists,
|
params: ChannelTab::Playlists,
|
||||||
query: None,
|
query: None,
|
||||||
|
|
@ -183,9 +174,7 @@ impl RustyPipeQuery {
|
||||||
channel_id: S,
|
channel_id: S,
|
||||||
) -> Result<ChannelInfo, Error> {
|
) -> Result<ChannelInfo, Error> {
|
||||||
let channel_id = channel_id.as_ref();
|
let channel_id = channel_id.as_ref();
|
||||||
let context = self.get_context(ClientType::Desktop, false, None).await;
|
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
|
||||||
continuation: &channel_info_ctoken(channel_id, &random_target()),
|
continuation: &channel_info_ctoken(channel_id, &random_target()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ impl ClientType {
|
||||||
/// YouTube context request parameter
|
/// YouTube context request parameter
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct YTContext<'a> {
|
struct YTContext<'a> {
|
||||||
client: ClientInfo<'a>,
|
client: ClientInfo<'a>,
|
||||||
/// only used on desktop
|
/// only used on desktop
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -119,8 +119,7 @@ struct ClientInfo<'a> {
|
||||||
platform: &'a str,
|
platform: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
original_url: Option<&'a str>,
|
original_url: Option<&'a str>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
visitor_data: &'a str,
|
||||||
visitor_data: Option<&'a str>,
|
|
||||||
hl: Language,
|
hl: Language,
|
||||||
gl: Country,
|
gl: Country,
|
||||||
time_zone: &'a str,
|
time_zone: &'a str,
|
||||||
|
|
@ -136,7 +135,7 @@ impl Default for ClientInfo<'_> {
|
||||||
device_model: None,
|
device_model: None,
|
||||||
platform: "",
|
platform: "",
|
||||||
original_url: None,
|
original_url: None,
|
||||||
visitor_data: None,
|
visitor_data: "",
|
||||||
hl: Language::En,
|
hl: Language::En,
|
||||||
gl: Country::Us,
|
gl: Country::Us,
|
||||||
time_zone: "UTC",
|
time_zone: "UTC",
|
||||||
|
|
@ -173,17 +172,22 @@ struct ThirdParty<'a> {
|
||||||
embed_url: &'a str,
|
embed_url: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct QBody<'a, T> {
|
||||||
|
context: YTContext<'a>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
body: T,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QBrowse<'a> {
|
struct QBrowse<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
browse_id: &'a str,
|
browse_id: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QBrowseParams<'a> {
|
struct QBrowseParams<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
browse_id: &'a str,
|
browse_id: &'a str,
|
||||||
params: &'a str,
|
params: &'a str,
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +195,6 @@ struct QBrowseParams<'a> {
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QContinuation<'a> {
|
struct QContinuation<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
continuation: &'a str,
|
continuation: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1126,18 +1129,17 @@ impl RustyPipeQuery {
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
/// - `ctype`: Client type (`Desktop`, `DesktopMusic`, `Android`, ...)
|
/// - `ctype`: Client type (`Desktop`, `DesktopMusic`, `Android`, ...)
|
||||||
/// - `localized`: Whether to include the configured language and country
|
/// - `localized`: Whether to include the configured language and country
|
||||||
pub async fn get_context<'a>(
|
async fn get_context<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
ctype: ClientType,
|
ctype: ClientType,
|
||||||
localized: bool,
|
localized: bool,
|
||||||
visitor_data: Option<&'a str>,
|
visitor_data: &'a str,
|
||||||
) -> YTContext {
|
) -> YTContext {
|
||||||
let (hl, gl) = if localized {
|
let (hl, gl) = if localized {
|
||||||
(self.opts.lang, self.opts.country)
|
(self.opts.lang, self.opts.country)
|
||||||
} else {
|
} else {
|
||||||
(Language::En, Country::Us)
|
(Language::En, Country::Us)
|
||||||
};
|
};
|
||||||
let visitor_data = visitor_data.or(self.opts.visitor_data.as_deref());
|
|
||||||
|
|
||||||
match ctype {
|
match ctype {
|
||||||
ClientType::Desktop => YTContext {
|
ClientType::Desktop => YTContext {
|
||||||
|
|
@ -1451,11 +1453,22 @@ impl RustyPipeQuery {
|
||||||
) -> Result<M, Error> {
|
) -> Result<M, Error> {
|
||||||
tracing::debug!("getting {}({})", operation, id);
|
tracing::debug!("getting {}({})", operation, id);
|
||||||
|
|
||||||
|
let visitor_data = ctx_src
|
||||||
|
.visitor_data
|
||||||
|
.or(self.opts.visitor_data.as_deref())
|
||||||
|
.map(Cow::Borrowed)
|
||||||
|
.unwrap_or_else(|| util::random_visitor_data(self.opts.country).into());
|
||||||
|
|
||||||
|
let context = self
|
||||||
|
.get_context(ctype, !ctx_src.unlocalized, &visitor_data)
|
||||||
|
.await;
|
||||||
|
let req_body = QBody { context, body };
|
||||||
|
|
||||||
let ctx = MapRespCtx {
|
let ctx = MapRespCtx {
|
||||||
id,
|
id,
|
||||||
lang: self.opts.lang,
|
lang: self.opts.lang,
|
||||||
deobf: ctx_src.deobf,
|
deobf: ctx_src.deobf,
|
||||||
visitor_data: ctx_src.visitor_data.or(self.opts.visitor_data.as_deref()),
|
visitor_data: Some(&visitor_data),
|
||||||
client_type: ctype,
|
client_type: ctype,
|
||||||
artist: ctx_src.artist,
|
artist: ctx_src.artist,
|
||||||
};
|
};
|
||||||
|
|
@ -1463,7 +1476,7 @@ impl RustyPipeQuery {
|
||||||
let request = self
|
let request = self
|
||||||
.request_builder(ctype, endpoint, ctx.visitor_data)
|
.request_builder(ctype, endpoint, ctx.visitor_data)
|
||||||
.await
|
.await
|
||||||
.json(body)
|
.json(&req_body)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
let req_res = self.yt_request::<R, M>(&request, &ctx).await?;
|
let req_res = self.yt_request::<R, M>(&request, &ctx).await?;
|
||||||
|
|
@ -1563,47 +1576,6 @@ impl RustyPipeQuery {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
/// Execute a request to the YouTube API, then map the response.
|
|
||||||
///
|
|
||||||
/// Creates a report in case of failure for easy debugging.
|
|
||||||
///
|
|
||||||
/// # Parameters
|
|
||||||
/// - `ctype`: Client type (`Desktop`, `DesktopMusic`, `Android`, ...)
|
|
||||||
/// - `operation`: Name of the RustyPipe operation (only for reporting, e.g. `get_player`)
|
|
||||||
/// - `id`: ID of the requested entity (Video ID, Channel ID, ...).
|
|
||||||
/// The ID is included in reports and is also passed to the mapper for validating the response.
|
|
||||||
/// Set it to an empty string if you are not requesting an entity with an ID.
|
|
||||||
/// - `method`: HTTP method
|
|
||||||
/// - `endpoint`: YouTube API endpoint (`https://www.youtube.com/youtubei/v1/<XYZ>?key=...`)
|
|
||||||
/// - `body`: Serializable request body to be sent in json format
|
|
||||||
/// - `visitor_data`: YouTube visitor data cookie
|
|
||||||
async fn execute_request_vdata<
|
|
||||||
R: DeserializeOwned + MapResponse<M> + Debug,
|
|
||||||
M,
|
|
||||||
B: Serialize + ?Sized,
|
|
||||||
>(
|
|
||||||
&self,
|
|
||||||
ctype: ClientType,
|
|
||||||
operation: &str,
|
|
||||||
id: &str,
|
|
||||||
endpoint: &str,
|
|
||||||
body: &B,
|
|
||||||
visitor_data: Option<&str>,
|
|
||||||
) -> Result<M, Error> {
|
|
||||||
self.execute_request_deobf::<R, M, B>(
|
|
||||||
ctype,
|
|
||||||
operation,
|
|
||||||
id,
|
|
||||||
endpoint,
|
|
||||||
body,
|
|
||||||
visitor_data,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Execute a request to the YouTube API and return the response string
|
/// Execute a request to the YouTube API and return the response string
|
||||||
///
|
///
|
||||||
/// # Parameters
|
/// # Parameters
|
||||||
|
|
@ -1616,10 +1588,20 @@ impl RustyPipeQuery {
|
||||||
endpoint: &str,
|
endpoint: &str,
|
||||||
body: &B,
|
body: &B,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
|
let visitor_data = self
|
||||||
|
.opts
|
||||||
|
.visitor_data
|
||||||
|
.as_deref()
|
||||||
|
.map(Cow::Borrowed)
|
||||||
|
.unwrap_or_else(|| util::random_visitor_data(self.opts.country).into());
|
||||||
|
|
||||||
|
let context = self.get_context(ctype, true, &visitor_data).await;
|
||||||
|
let req_body = QBody { context, body };
|
||||||
|
|
||||||
let request = self
|
let request = self
|
||||||
.request_builder(ctype, endpoint, None)
|
.request_builder(ctype, endpoint, None)
|
||||||
.await
|
.await
|
||||||
.json(body)
|
.json(&req_body)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
self.client.http_request_txt(&request).await
|
self.client.http_request_txt(&request).await
|
||||||
|
|
@ -1646,6 +1628,7 @@ struct MapRespCtxSource<'a> {
|
||||||
visitor_data: Option<&'a str>,
|
visitor_data: Option<&'a str>,
|
||||||
deobf: Option<&'a DeobfData>,
|
deobf: Option<&'a DeobfData>,
|
||||||
artist: Option<ArtistId>,
|
artist: Option<ArtistId>,
|
||||||
|
unlocalized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MapRespCtx<'a> {
|
impl<'a> MapRespCtx<'a> {
|
||||||
|
|
@ -1663,15 +1646,6 @@ impl<'a> MapRespCtx<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MapRespCtxSource<'a> {
|
|
||||||
fn visitor_data(visitor_data: &'a str) -> Self {
|
|
||||||
Self {
|
|
||||||
visitor_data: Some(visitor_data),
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement this for YouTube API response structs that need to be mapped to
|
/// Implement this for YouTube API response structs that need to be mapped to
|
||||||
/// RustyPipe models.
|
/// RustyPipe models.
|
||||||
trait MapResponse<T> {
|
trait MapResponse<T> {
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,7 @@ impl RustyPipeQuery {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn _music_artist(&self, artist_id: &str, all_albums: bool) -> Result<MusicArtist, Error> {
|
async fn _music_artist(&self, artist_id: &str, all_albums: bool) -> Result<MusicArtist, Error> {
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: artist_id,
|
browse_id: artist_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -84,24 +82,18 @@ impl RustyPipeQuery {
|
||||||
filter: Option<AlbumFilter>,
|
filter: Option<AlbumFilter>,
|
||||||
order: Option<AlbumOrder>,
|
order: Option<AlbumOrder>,
|
||||||
) -> Result<Vec<AlbumItem>, Error> {
|
) -> Result<Vec<AlbumItem>, Error> {
|
||||||
let visitor_data = self.get_visitor_data().await?;
|
|
||||||
let context = self
|
|
||||||
.get_context(ClientType::DesktopMusic, true, Some(&visitor_data))
|
|
||||||
.await;
|
|
||||||
let request_body = QBrowseParams {
|
let request_body = QBrowseParams {
|
||||||
context: context.clone(),
|
|
||||||
browse_id: &format!("{}{}", util::ARTIST_DISCOGRAPHY_PREFIX, artist_id),
|
browse_id: &format!("{}{}", util::ARTIST_DISCOGRAPHY_PREFIX, artist_id),
|
||||||
params: &albums_param(filter, order),
|
params: &albums_param(filter, order),
|
||||||
};
|
};
|
||||||
|
|
||||||
let first_page = self
|
let first_page = self
|
||||||
.execute_request_ctx::<response::MusicArtistAlbums, _, _>(
|
.execute_request::<response::MusicArtistAlbums, _, _>(
|
||||||
ClientType::DesktopMusic,
|
ClientType::DesktopMusic,
|
||||||
"music_artist_albums",
|
"music_artist_albums",
|
||||||
artist_id,
|
artist_id,
|
||||||
"browse",
|
"browse",
|
||||||
&request_body,
|
&request_body,
|
||||||
MapRespCtxSource::visitor_data(&visitor_data),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
@ -109,10 +101,7 @@ impl RustyPipeQuery {
|
||||||
let mut ctoken = first_page.ctoken;
|
let mut ctoken = first_page.ctoken;
|
||||||
|
|
||||||
while let Some(tkn) = &ctoken {
|
while let Some(tkn) = &ctoken {
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation { continuation: tkn };
|
||||||
context: context.clone(),
|
|
||||||
continuation: tkn,
|
|
||||||
};
|
|
||||||
let resp: Paginator<MusicItem> = self
|
let resp: Paginator<MusicItem> = self
|
||||||
.execute_request_ctx::<response::MusicContinuation, Paginator<MusicItem>, _>(
|
.execute_request_ctx::<response::MusicContinuation, Paginator<MusicItem>, _>(
|
||||||
ClientType::DesktopMusic,
|
ClientType::DesktopMusic,
|
||||||
|
|
@ -122,7 +111,6 @@ impl RustyPipeQuery {
|
||||||
&request_body,
|
&request_body,
|
||||||
MapRespCtxSource {
|
MapRespCtxSource {
|
||||||
artist: Some(first_page.artist.clone()),
|
artist: Some(first_page.artist.clone()),
|
||||||
visitor_data: Some(&visitor_data),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,12 @@ use crate::{
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
response::{self, music_item::MusicListMapper, url_endpoint::MusicPageType},
|
response::{self, music_item::MusicListMapper, url_endpoint::MusicPageType},
|
||||||
ClientType, MapRespCtx, MapResponse, RustyPipeQuery, YTContext,
|
ClientType, MapRespCtx, MapResponse, RustyPipeQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QCharts<'a> {
|
struct QCharts<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
browse_id: &'a str,
|
browse_id: &'a str,
|
||||||
params: &'a str,
|
params: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -34,9 +33,7 @@ impl RustyPipeQuery {
|
||||||
/// Get the YouTube Music charts for a given country
|
/// Get the YouTube Music charts for a given country
|
||||||
#[tracing::instrument(skip(self), level = "error")]
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
pub async fn music_charts(&self, country: Option<Country>) -> Result<MusicCharts, Error> {
|
pub async fn music_charts(&self, country: Option<Country>) -> Result<MusicCharts, Error> {
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QCharts {
|
let request_body = QCharts {
|
||||||
context,
|
|
||||||
browse_id: "FEmusic_charts",
|
browse_id: "FEmusic_charts",
|
||||||
params: "sgYPRkVtdXNpY19leHBsb3Jl",
|
params: "sgYPRkVtdXNpY19leHBsb3Jl",
|
||||||
form_data: country.map(|c| FormData {
|
form_data: country.map(|c| FormData {
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,11 @@ use super::{
|
||||||
self,
|
self,
|
||||||
music_item::{map_queue_item, MusicListMapper},
|
music_item::{map_queue_item, MusicListMapper},
|
||||||
},
|
},
|
||||||
ClientType, MapRespCtx, MapRespCtxSource, MapResponse, QBrowse, RustyPipeQuery, YTContext,
|
ClientType, MapRespCtx, MapResponse, QBrowse, RustyPipeQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct QMusicDetails<'a> {
|
struct QMusicDetails<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
video_id: &'a str,
|
video_id: &'a str,
|
||||||
enable_persistent_playlist_panel: bool,
|
enable_persistent_playlist_panel: bool,
|
||||||
is_audio_only: bool,
|
is_audio_only: bool,
|
||||||
|
|
@ -30,7 +29,6 @@ struct QMusicDetails<'a> {
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct QRadio<'a> {
|
struct QRadio<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
playlist_id: &'a str,
|
playlist_id: &'a str,
|
||||||
params: &'a str,
|
params: &'a str,
|
||||||
enable_persistent_playlist_panel: bool,
|
enable_persistent_playlist_panel: bool,
|
||||||
|
|
@ -46,9 +44,7 @@ impl RustyPipeQuery {
|
||||||
video_id: S,
|
video_id: S,
|
||||||
) -> Result<TrackDetails, Error> {
|
) -> Result<TrackDetails, Error> {
|
||||||
let video_id = video_id.as_ref();
|
let video_id = video_id.as_ref();
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QMusicDetails {
|
let request_body = QMusicDetails {
|
||||||
context,
|
|
||||||
video_id,
|
video_id,
|
||||||
enable_persistent_playlist_panel: true,
|
enable_persistent_playlist_panel: true,
|
||||||
is_audio_only: true,
|
is_audio_only: true,
|
||||||
|
|
@ -71,9 +67,7 @@ impl RustyPipeQuery {
|
||||||
#[tracing::instrument(skip(self), level = "error")]
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
pub async fn music_lyrics<S: AsRef<str> + Debug>(&self, lyrics_id: S) -> Result<Lyrics, Error> {
|
pub async fn music_lyrics<S: AsRef<str> + Debug>(&self, lyrics_id: S) -> Result<Lyrics, Error> {
|
||||||
let lyrics_id = lyrics_id.as_ref();
|
let lyrics_id = lyrics_id.as_ref();
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: lyrics_id,
|
browse_id: lyrics_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -96,9 +90,7 @@ impl RustyPipeQuery {
|
||||||
related_id: S,
|
related_id: S,
|
||||||
) -> Result<MusicRelated, Error> {
|
) -> Result<MusicRelated, Error> {
|
||||||
let related_id = related_id.as_ref();
|
let related_id = related_id.as_ref();
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: related_id,
|
browse_id: related_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -121,12 +113,7 @@ impl RustyPipeQuery {
|
||||||
radio_id: S,
|
radio_id: S,
|
||||||
) -> Result<Paginator<TrackItem>, Error> {
|
) -> Result<Paginator<TrackItem>, Error> {
|
||||||
let radio_id = radio_id.as_ref();
|
let radio_id = radio_id.as_ref();
|
||||||
let visitor_data = self.get_visitor_data().await?;
|
|
||||||
let context = self
|
|
||||||
.get_context(ClientType::DesktopMusic, true, Some(&visitor_data))
|
|
||||||
.await;
|
|
||||||
let request_body = QRadio {
|
let request_body = QRadio {
|
||||||
context,
|
|
||||||
playlist_id: radio_id,
|
playlist_id: radio_id,
|
||||||
params: "wAEB8gECeAE%3D",
|
params: "wAEB8gECeAE%3D",
|
||||||
enable_persistent_playlist_panel: true,
|
enable_persistent_playlist_panel: true,
|
||||||
|
|
@ -134,13 +121,12 @@ impl RustyPipeQuery {
|
||||||
tuner_setting_value: "AUTOMIX_SETTING_NORMAL",
|
tuner_setting_value: "AUTOMIX_SETTING_NORMAL",
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_request_ctx::<response::MusicDetails, _, _>(
|
self.execute_request::<response::MusicDetails, _, _>(
|
||||||
ClientType::DesktopMusic,
|
ClientType::DesktopMusic,
|
||||||
"music_radio",
|
"music_radio",
|
||||||
radio_id,
|
radio_id,
|
||||||
"next",
|
"next",
|
||||||
&request_body,
|
&request_body,
|
||||||
MapRespCtxSource::visitor_data(&visitor_data),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ impl RustyPipeQuery {
|
||||||
/// Get a list of moods and genres from YouTube Music
|
/// Get a list of moods and genres from YouTube Music
|
||||||
#[tracing::instrument(skip(self), level = "error")]
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
pub async fn music_genres(&self) -> Result<Vec<MusicGenreItem>, Error> {
|
pub async fn music_genres(&self) -> Result<Vec<MusicGenreItem>, Error> {
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: "FEmusic_moods_and_genres",
|
browse_id: "FEmusic_moods_and_genres",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -38,9 +36,7 @@ impl RustyPipeQuery {
|
||||||
genre_id: S,
|
genre_id: S,
|
||||||
) -> Result<MusicGenre, Error> {
|
) -> Result<MusicGenre, Error> {
|
||||||
let genre_id = genre_id.as_ref();
|
let genre_id = genre_id.as_ref();
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QBrowseParams {
|
let request_body = QBrowseParams {
|
||||||
context,
|
|
||||||
browse_id: "FEmusic_moods_and_genres_category",
|
browse_id: "FEmusic_moods_and_genres_category",
|
||||||
params: genre_id,
|
params: genre_id,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@ impl RustyPipeQuery {
|
||||||
/// Get the new albums that were released on YouTube Music
|
/// Get the new albums that were released on YouTube Music
|
||||||
#[tracing::instrument(skip(self), level = "error")]
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
pub async fn music_new_albums(&self) -> Result<Vec<AlbumItem>, Error> {
|
pub async fn music_new_albums(&self) -> Result<Vec<AlbumItem>, Error> {
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: "FEmusic_new_releases_albums",
|
browse_id: "FEmusic_new_releases_albums",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -32,9 +30,7 @@ impl RustyPipeQuery {
|
||||||
/// Get the new music videos that were released on YouTube Music
|
/// Get the new music videos that were released on YouTube Music
|
||||||
#[tracing::instrument(skip(self), level = "error")]
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
pub async fn music_new_videos(&self) -> Result<Vec<TrackItem>, Error> {
|
pub async fn music_new_videos(&self) -> Result<Vec<TrackItem>, Error> {
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: "FEmusic_new_releases_videos",
|
browse_id: "FEmusic_new_releases_videos",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ use super::{
|
||||||
self,
|
self,
|
||||||
music_item::{map_album_type, map_artist_id, map_artists, MusicListMapper},
|
music_item::{map_album_type, map_artist_id, map_artists, MusicListMapper},
|
||||||
},
|
},
|
||||||
ClientType, MapRespCtx, MapRespCtxSource, MapResponse, QBrowse, RustyPipeQuery,
|
ClientType, MapRespCtx, MapResponse, QBrowse, RustyPipeQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl RustyPipeQuery {
|
impl RustyPipeQuery {
|
||||||
|
|
@ -28,30 +28,16 @@ impl RustyPipeQuery {
|
||||||
playlist_id: S,
|
playlist_id: S,
|
||||||
) -> Result<MusicPlaylist, Error> {
|
) -> Result<MusicPlaylist, Error> {
|
||||||
let playlist_id = playlist_id.as_ref();
|
let playlist_id = playlist_id.as_ref();
|
||||||
// YTM playlists require visitor data for continuations to work
|
|
||||||
let visitor_data = if playlist_id.starts_with("RD") {
|
|
||||||
Some(self.get_visitor_data().await?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let context = self
|
|
||||||
.get_context(ClientType::DesktopMusic, true, visitor_data.as_deref())
|
|
||||||
.await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: &format!("VL{playlist_id}"),
|
browse_id: &format!("VL{playlist_id}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_request_ctx::<response::MusicPlaylist, _, _>(
|
self.execute_request::<response::MusicPlaylist, _, _>(
|
||||||
ClientType::DesktopMusic,
|
ClientType::DesktopMusic,
|
||||||
"music_playlist",
|
"music_playlist",
|
||||||
playlist_id,
|
playlist_id,
|
||||||
"browse",
|
"browse",
|
||||||
&request_body,
|
&request_body,
|
||||||
MapRespCtxSource {
|
|
||||||
visitor_data: visitor_data.as_deref(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
@ -63,9 +49,7 @@ impl RustyPipeQuery {
|
||||||
album_id: S,
|
album_id: S,
|
||||||
) -> Result<MusicAlbum, Error> {
|
) -> Result<MusicAlbum, Error> {
|
||||||
let album_id = album_id.as_ref();
|
let album_id = album_id.as_ref();
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: album_id,
|
browse_id: album_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,11 @@ use crate::{
|
||||||
serializer::MapResult,
|
serializer::MapResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{response, ClientType, MapRespCtx, MapResponse, RustyPipeQuery, YTContext};
|
use super::{response, ClientType, MapRespCtx, MapResponse, RustyPipeQuery};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QSearch<'a> {
|
struct QSearch<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
query: &'a str,
|
query: &'a str,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
params: Option<&'a str>,
|
params: Option<&'a str>,
|
||||||
|
|
@ -29,7 +28,6 @@ struct QSearch<'a> {
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QSearchSuggestion<'a> {
|
struct QSearchSuggestion<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
input: &'a str,
|
input: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -44,9 +42,7 @@ impl RustyPipeQuery {
|
||||||
filter: Option<MusicSearchFilter>,
|
filter: Option<MusicSearchFilter>,
|
||||||
) -> Result<MusicSearchResult<T>, Error> {
|
) -> Result<MusicSearchResult<T>, Error> {
|
||||||
let query = query.as_ref();
|
let query = query.as_ref();
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
|
||||||
let request_body = QSearch {
|
let request_body = QSearch {
|
||||||
context,
|
|
||||||
query,
|
query,
|
||||||
params: filter.map(MusicSearchFilter::params),
|
params: filter.map(MusicSearchFilter::params),
|
||||||
};
|
};
|
||||||
|
|
@ -132,11 +128,7 @@ impl RustyPipeQuery {
|
||||||
query: S,
|
query: S,
|
||||||
) -> Result<MusicSearchSuggestion, Error> {
|
) -> Result<MusicSearchSuggestion, Error> {
|
||||||
let query = query.as_ref();
|
let query = query.as_ref();
|
||||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
let request_body = QSearchSuggestion { input: query };
|
||||||
let request_body = QSearchSuggestion {
|
|
||||||
context,
|
|
||||||
input: query,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.execute_request::<response::MusicSearchSuggestion, _, _>(
|
self.execute_request::<response::MusicSearchSuggestion, _, _>(
|
||||||
ClientType::DesktopMusic,
|
ClientType::DesktopMusic,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::error::{Error, ExtractionError};
|
use crate::error::{Error, ExtractionError};
|
||||||
|
|
@ -25,16 +24,7 @@ impl RustyPipeQuery {
|
||||||
) -> Result<Paginator<T>, Error> {
|
) -> Result<Paginator<T>, Error> {
|
||||||
let ctoken = ctoken.as_ref();
|
let ctoken = ctoken.as_ref();
|
||||||
if endpoint.is_music() {
|
if endpoint.is_music() {
|
||||||
// Visitor data is required for YTM continuations
|
|
||||||
let visitor_data = match visitor_data {
|
|
||||||
Some(vd) => Cow::Borrowed(vd),
|
|
||||||
None => Cow::Owned(self.get_visitor_data().await?),
|
|
||||||
};
|
|
||||||
let context = self
|
|
||||||
.get_context(ClientType::DesktopMusic, true, Some(&visitor_data))
|
|
||||||
.await;
|
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
|
||||||
continuation: ctoken,
|
continuation: ctoken,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -45,59 +35,60 @@ impl RustyPipeQuery {
|
||||||
ctoken,
|
ctoken,
|
||||||
endpoint.as_str(),
|
endpoint.as_str(),
|
||||||
&request_body,
|
&request_body,
|
||||||
MapRespCtxSource::visitor_data(&visitor_data),
|
MapRespCtxSource {
|
||||||
|
visitor_data,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(map_ytm_paginator(p, Some(&visitor_data), endpoint))
|
Ok(map_ytm_paginator(p, endpoint))
|
||||||
} else {
|
} else {
|
||||||
let context = self
|
|
||||||
.get_context(ClientType::Desktop, true, visitor_data)
|
|
||||||
.await;
|
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
|
||||||
continuation: ctoken,
|
continuation: ctoken,
|
||||||
};
|
};
|
||||||
|
|
||||||
let p = self
|
let p = self
|
||||||
.execute_request::<response::Continuation, Paginator<YouTubeItem>, _>(
|
.execute_request_ctx::<response::Continuation, Paginator<YouTubeItem>, _>(
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"continuation",
|
"continuation",
|
||||||
ctoken,
|
ctoken,
|
||||||
endpoint.as_str(),
|
endpoint.as_str(),
|
||||||
&request_body,
|
&request_body,
|
||||||
|
MapRespCtxSource {
|
||||||
|
visitor_data,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(map_yt_paginator(p, visitor_data, endpoint))
|
Ok(map_yt_paginator(p, endpoint))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_yt_paginator<T: FromYtItem>(
|
fn map_yt_paginator<T: FromYtItem>(
|
||||||
p: Paginator<YouTubeItem>,
|
p: Paginator<YouTubeItem>,
|
||||||
visitor_data: Option<&str>,
|
|
||||||
endpoint: ContinuationEndpoint,
|
endpoint: ContinuationEndpoint,
|
||||||
) -> Paginator<T> {
|
) -> Paginator<T> {
|
||||||
Paginator {
|
Paginator {
|
||||||
count: p.count,
|
count: p.count,
|
||||||
items: p.items.into_iter().filter_map(T::from_yt_item).collect(),
|
items: p.items.into_iter().filter_map(T::from_yt_item).collect(),
|
||||||
ctoken: p.ctoken,
|
ctoken: p.ctoken,
|
||||||
visitor_data: visitor_data.map(str::to_owned),
|
visitor_data: p.visitor_data,
|
||||||
endpoint,
|
endpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_ytm_paginator<T: FromYtItem>(
|
fn map_ytm_paginator<T: FromYtItem>(
|
||||||
p: Paginator<MusicItem>,
|
p: Paginator<MusicItem>,
|
||||||
visitor_data: Option<&str>,
|
|
||||||
endpoint: ContinuationEndpoint,
|
endpoint: ContinuationEndpoint,
|
||||||
) -> Paginator<T> {
|
) -> Paginator<T> {
|
||||||
Paginator {
|
Paginator {
|
||||||
count: p.count,
|
count: p.count,
|
||||||
items: p.items.into_iter().filter_map(T::from_ytm_item).collect(),
|
items: p.items.into_iter().filter_map(T::from_ytm_item).collect(),
|
||||||
ctoken: p.ctoken,
|
ctoken: p.ctoken,
|
||||||
visitor_data: visitor_data.map(str::to_owned),
|
visitor_data: p.visitor_data,
|
||||||
endpoint,
|
endpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -430,7 +421,7 @@ mod tests {
|
||||||
let map_res: MapResult<Paginator<YouTubeItem>> =
|
let map_res: MapResult<Paginator<YouTubeItem>> =
|
||||||
items.map_response(&MapRespCtx::test("")).unwrap();
|
items.map_response(&MapRespCtx::test("")).unwrap();
|
||||||
let paginator: Paginator<VideoItem> =
|
let paginator: Paginator<VideoItem> =
|
||||||
map_yt_paginator(map_res.c, None, ContinuationEndpoint::Browse);
|
map_yt_paginator(map_res.c, ContinuationEndpoint::Browse);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
map_res.warnings.is_empty(),
|
map_res.warnings.is_empty(),
|
||||||
|
|
@ -453,7 +444,7 @@ mod tests {
|
||||||
let map_res: MapResult<Paginator<YouTubeItem>> =
|
let map_res: MapResult<Paginator<YouTubeItem>> =
|
||||||
items.map_response(&MapRespCtx::test("")).unwrap();
|
items.map_response(&MapRespCtx::test("")).unwrap();
|
||||||
let paginator: Paginator<PlaylistItem> =
|
let paginator: Paginator<PlaylistItem> =
|
||||||
map_yt_paginator(map_res.c, None, ContinuationEndpoint::Browse);
|
map_yt_paginator(map_res.c, ContinuationEndpoint::Browse);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
map_res.warnings.is_empty(),
|
map_res.warnings.is_empty(),
|
||||||
|
|
@ -476,7 +467,7 @@ mod tests {
|
||||||
let map_res: MapResult<Paginator<MusicItem>> =
|
let map_res: MapResult<Paginator<MusicItem>> =
|
||||||
items.map_response(&MapRespCtx::test("")).unwrap();
|
items.map_response(&MapRespCtx::test("")).unwrap();
|
||||||
let paginator: Paginator<TrackItem> =
|
let paginator: Paginator<TrackItem> =
|
||||||
map_ytm_paginator(map_res.c, None, ContinuationEndpoint::MusicBrowse);
|
map_ytm_paginator(map_res.c, ContinuationEndpoint::MusicBrowse);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
map_res.warnings.is_empty(),
|
map_res.warnings.is_empty(),
|
||||||
|
|
@ -497,7 +488,7 @@ mod tests {
|
||||||
let map_res: MapResult<Paginator<MusicItem>> =
|
let map_res: MapResult<Paginator<MusicItem>> =
|
||||||
items.map_response(&MapRespCtx::test("")).unwrap();
|
items.map_response(&MapRespCtx::test("")).unwrap();
|
||||||
let paginator: Paginator<MusicPlaylistItem> =
|
let paginator: Paginator<MusicPlaylistItem> =
|
||||||
map_ytm_paginator(map_res.c, None, ContinuationEndpoint::MusicBrowse);
|
map_ytm_paginator(map_res.c, ContinuationEndpoint::MusicBrowse);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
map_res.warnings.is_empty(),
|
map_res.warnings.is_empty(),
|
||||||
|
|
|
||||||
|
|
@ -24,14 +24,13 @@ use super::{
|
||||||
self,
|
self,
|
||||||
player::{self, Format},
|
player::{self, Format},
|
||||||
},
|
},
|
||||||
ClientType, MapRespCtx, MapRespCtxSource, MapResponse, MapResult, RustyPipeQuery, YTContext,
|
ClientType, MapRespCtx, MapRespCtxSource, MapResponse, MapResult, RustyPipeQuery,
|
||||||
DEFAULT_PLAYER_CLIENT_ORDER,
|
DEFAULT_PLAYER_CLIENT_ORDER,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QPlayer<'a> {
|
struct QPlayer<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
/// Website playback context
|
/// Website playback context
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
playback_context: Option<QPlaybackContext<'a>>,
|
playback_context: Option<QPlaybackContext<'a>>,
|
||||||
|
|
@ -124,16 +123,10 @@ impl RustyPipeQuery {
|
||||||
client_type: ClientType,
|
client_type: ClientType,
|
||||||
) -> Result<VideoPlayer, Error> {
|
) -> Result<VideoPlayer, Error> {
|
||||||
let video_id = video_id.as_ref();
|
let video_id = video_id.as_ref();
|
||||||
// let vdata = self.get_visitor_data().await?;
|
let deobf = self.client.get_deobf_data().await?;
|
||||||
let (context, deobf) = tokio::join!(
|
|
||||||
self.get_context(client_type, false, None),
|
|
||||||
self.client.get_deobf_data()
|
|
||||||
);
|
|
||||||
let deobf = deobf?;
|
|
||||||
|
|
||||||
let request_body = if client_type.is_web() {
|
let request_body = if client_type.is_web() {
|
||||||
QPlayer {
|
QPlayer {
|
||||||
context,
|
|
||||||
playback_context: Some(QPlaybackContext {
|
playback_context: Some(QPlaybackContext {
|
||||||
content_playback_context: QContentPlaybackContext {
|
content_playback_context: QContentPlaybackContext {
|
||||||
signature_timestamp: &deobf.sts,
|
signature_timestamp: &deobf.sts,
|
||||||
|
|
@ -148,7 +141,6 @@ impl RustyPipeQuery {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QPlayer {
|
QPlayer {
|
||||||
context,
|
|
||||||
playback_context: None,
|
playback_context: None,
|
||||||
cpn: Some(util::generate_content_playback_nonce()),
|
cpn: Some(util::generate_content_playback_nonce()),
|
||||||
video_id,
|
video_id,
|
||||||
|
|
|
||||||
|
|
@ -13,40 +13,23 @@ use crate::{
|
||||||
util::{self, dictionary, timeago, TryRemove},
|
util::{self, dictionary, timeago, TryRemove},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{response, ClientType, MapRespCtx, MapResponse, MapResult, QBrowse, RustyPipeQuery};
|
||||||
response, ClientType, MapRespCtx, MapRespCtxSource, MapResponse, MapResult, QBrowse,
|
|
||||||
RustyPipeQuery,
|
|
||||||
};
|
|
||||||
|
|
||||||
impl RustyPipeQuery {
|
impl RustyPipeQuery {
|
||||||
/// Get a YouTube playlist
|
/// Get a YouTube playlist
|
||||||
#[tracing::instrument(skip(self), level = "error")]
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
pub async fn playlist<S: AsRef<str> + Debug>(&self, playlist_id: S) -> Result<Playlist, Error> {
|
pub async fn playlist<S: AsRef<str> + Debug>(&self, playlist_id: S) -> Result<Playlist, Error> {
|
||||||
let playlist_id = playlist_id.as_ref();
|
let playlist_id = playlist_id.as_ref();
|
||||||
// YTM playlists require visitor data for continuations to work
|
|
||||||
let visitor_data: Option<String> = if playlist_id.starts_with("RD") {
|
|
||||||
Some(self.get_visitor_data().await?)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let context = self
|
|
||||||
.get_context(ClientType::Desktop, true, visitor_data.as_deref())
|
|
||||||
.await;
|
|
||||||
let request_body = QBrowse {
|
let request_body = QBrowse {
|
||||||
context,
|
|
||||||
browse_id: &format!("VL{playlist_id}"),
|
browse_id: &format!("VL{playlist_id}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_request_ctx::<response::Playlist, _, _>(
|
self.execute_request::<response::Playlist, _, _>(
|
||||||
ClientType::Desktop,
|
ClientType::Desktop,
|
||||||
"playlist",
|
"playlist",
|
||||||
playlist_id,
|
playlist_id,
|
||||||
"browse",
|
"browse",
|
||||||
&request_body,
|
&request_body,
|
||||||
MapRespCtxSource {
|
|
||||||
visitor_data: visitor_data.as_deref(),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@ use crate::{
|
||||||
param::search_filter::SearchFilter,
|
param::search_filter::SearchFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{response, ClientType, MapRespCtx, MapResponse, MapResult, RustyPipeQuery, YTContext};
|
use super::{response, ClientType, MapRespCtx, MapResponse, MapResult, RustyPipeQuery};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QSearch<'a> {
|
struct QSearch<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
query: &'a str,
|
query: &'a str,
|
||||||
params: &'a str,
|
params: &'a str,
|
||||||
}
|
}
|
||||||
|
|
@ -30,9 +29,7 @@ impl RustyPipeQuery {
|
||||||
query: S,
|
query: S,
|
||||||
) -> Result<SearchResult<T>, Error> {
|
) -> Result<SearchResult<T>, Error> {
|
||||||
let query = query.as_ref();
|
let query = query.as_ref();
|
||||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let request_body = QSearch {
|
let request_body = QSearch {
|
||||||
context,
|
|
||||||
query,
|
query,
|
||||||
params: "8AEB",
|
params: "8AEB",
|
||||||
};
|
};
|
||||||
|
|
@ -55,9 +52,7 @@ impl RustyPipeQuery {
|
||||||
filter: &SearchFilter,
|
filter: &SearchFilter,
|
||||||
) -> Result<SearchResult<T>, Error> {
|
) -> Result<SearchResult<T>, Error> {
|
||||||
let query = query.as_ref();
|
let query = query.as_ref();
|
||||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let request_body = QSearch {
|
let request_body = QSearch {
|
||||||
context,
|
|
||||||
query,
|
query,
|
||||||
params: &filter.encode(),
|
params: &filter.encode(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,7 @@ impl RustyPipeQuery {
|
||||||
/// Get the videos from the YouTube trending page
|
/// Get the videos from the YouTube trending page
|
||||||
#[tracing::instrument(skip(self), level = "error")]
|
#[tracing::instrument(skip(self), level = "error")]
|
||||||
pub async fn trending(&self) -> Result<Vec<VideoItem>, Error> {
|
pub async fn trending(&self) -> Result<Vec<VideoItem>, Error> {
|
||||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let request_body = QBrowseParams {
|
let request_body = QBrowseParams {
|
||||||
context,
|
|
||||||
browse_id: "FEtrending",
|
browse_id: "FEtrending",
|
||||||
params: "4gIOGgxtb3N0X3BvcHVsYXI%3D",
|
params: "4gIOGgxtb3N0X3BvcHVsYXI%3D",
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,13 +11,12 @@ use crate::{
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
response::{self, url_endpoint::NavigationEndpoint},
|
response::{self, url_endpoint::NavigationEndpoint},
|
||||||
ClientType, MapRespCtx, MapResponse, RustyPipeQuery, YTContext,
|
ClientType, MapRespCtx, MapResponse, RustyPipeQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QResolveUrl<'a> {
|
struct QResolveUrl {
|
||||||
context: YTContext<'a>,
|
|
||||||
url: String,
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -299,9 +298,7 @@ impl RustyPipeQuery {
|
||||||
url_path: &str,
|
url_path: &str,
|
||||||
ctype: ClientType,
|
ctype: ClientType,
|
||||||
) -> Result<UrlTarget, Error> {
|
) -> Result<UrlTarget, Error> {
|
||||||
let context = self.get_context(ctype, true, None).await;
|
|
||||||
let request_body = QResolveUrl {
|
let request_body = QResolveUrl {
|
||||||
context,
|
|
||||||
url: format!(
|
url: format!(
|
||||||
"https://{}.youtube.com{}",
|
"https://{}.youtube.com{}",
|
||||||
match ctype {
|
match ctype {
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,11 @@ use crate::{
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
response::{self, video_details::Payload, IconType},
|
response::{self, video_details::Payload, IconType},
|
||||||
ClientType, MapRespCtx, MapResponse, QContinuation, RustyPipeQuery, YTContext,
|
ClientType, MapRespCtx, MapResponse, QContinuation, RustyPipeQuery,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct QVideo<'a> {
|
struct QVideo<'a> {
|
||||||
context: YTContext<'a>,
|
|
||||||
/// YouTube video ID
|
/// YouTube video ID
|
||||||
video_id: &'a str,
|
video_id: &'a str,
|
||||||
/// Set to true to allow extraction of streams with sensitive content
|
/// Set to true to allow extraction of streams with sensitive content
|
||||||
|
|
@ -37,9 +36,7 @@ impl RustyPipeQuery {
|
||||||
video_id: S,
|
video_id: S,
|
||||||
) -> Result<VideoDetails, Error> {
|
) -> Result<VideoDetails, Error> {
|
||||||
let video_id = video_id.as_ref();
|
let video_id = video_id.as_ref();
|
||||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
|
||||||
let request_body = QVideo {
|
let request_body = QVideo {
|
||||||
context,
|
|
||||||
video_id,
|
video_id,
|
||||||
content_check_ok: true,
|
content_check_ok: true,
|
||||||
racy_check_ok: true,
|
racy_check_ok: true,
|
||||||
|
|
@ -63,11 +60,7 @@ impl RustyPipeQuery {
|
||||||
visitor_data: Option<&str>,
|
visitor_data: Option<&str>,
|
||||||
) -> Result<Paginator<Comment>, Error> {
|
) -> Result<Paginator<Comment>, Error> {
|
||||||
let ctoken = ctoken.as_ref();
|
let ctoken = ctoken.as_ref();
|
||||||
let context = self
|
|
||||||
.get_context(ClientType::Desktop, true, visitor_data)
|
|
||||||
.await;
|
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
|
||||||
continuation: ctoken,
|
continuation: ctoken,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,29 @@ pub fn random_uuid() -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate a random visitor data cookie
|
||||||
|
pub fn random_visitor_data(country: Country) -> String {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let mut pb_e2 = ProtoBuilder::new();
|
||||||
|
pb_e2.string(2, "");
|
||||||
|
pb_e2.varint(4, rng.gen_range(1..256));
|
||||||
|
|
||||||
|
let mut pb_e = ProtoBuilder::new();
|
||||||
|
pb_e.string(1, &country.to_string());
|
||||||
|
pb_e.embedded(2, pb_e2);
|
||||||
|
|
||||||
|
let mut pb = ProtoBuilder::new();
|
||||||
|
pb.string(1, &random_string(CONTENT_PLAYBACK_NONCE_ALPHABET, 11));
|
||||||
|
pb.varint(
|
||||||
|
5,
|
||||||
|
(time::OffsetDateTime::now_utc().unix_timestamp() as u64)
|
||||||
|
.saturating_sub(rng.gen_range(0..600_000)),
|
||||||
|
);
|
||||||
|
pb.embedded(6, pb_e);
|
||||||
|
pb.to_base64()
|
||||||
|
}
|
||||||
|
|
||||||
/// Split an URL into its base string and parameter map
|
/// Split an URL into its base string and parameter map
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
|
|
|
||||||
Reference in a new issue