Compare commits
No commits in common. "db6a479bcfab96deed27be9b6c3a00f41ab3ef0b" and "a3e3269fb322712bc746b88146553d3d86d930ce" have entirely different histories.
db6a479bcf
...
a3e3269fb3
24 changed files with 280 additions and 887 deletions
|
@ -11,4 +11,3 @@ repos:
|
||||||
- id: cargo-fmt
|
- id: cargo-fmt
|
||||||
- id: cargo-check
|
- id: cargo-check
|
||||||
- id: cargo-clippy
|
- id: cargo-clippy
|
||||||
args: ["--", "-D", "warnings"]
|
|
||||||
|
|
|
@ -19,9 +19,9 @@ use super::{
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QChannel<'a> {
|
struct QChannel {
|
||||||
context: YTContext,
|
context: YTContext,
|
||||||
browse_id: &'a str,
|
browse_id: String,
|
||||||
params: Params,
|
params: Params,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QChannel {
|
let request_body = QChannel {
|
||||||
context,
|
context,
|
||||||
browse_id: channel_id,
|
browse_id: channel_id.to_owned(),
|
||||||
params: match order {
|
params: match order {
|
||||||
ChannelOrder::Latest => Params::VideosLatest,
|
ChannelOrder::Latest => Params::VideosLatest,
|
||||||
ChannelOrder::Oldest => Params::VideosOldest,
|
ChannelOrder::Oldest => Params::VideosOldest,
|
||||||
|
@ -81,7 +81,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
context,
|
||||||
continuation: ctoken,
|
continuation: ctoken.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_request::<response::ChannelCont, _, _>(
|
self.execute_request::<response::ChannelCont, _, _>(
|
||||||
|
@ -101,7 +101,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QChannel {
|
let request_body = QChannel {
|
||||||
context,
|
context,
|
||||||
browse_id: channel_id,
|
browse_id: channel_id.to_owned(),
|
||||||
params: Params::Playlists,
|
params: Params::Playlists,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
context,
|
||||||
continuation: ctoken,
|
continuation: ctoken.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_request::<response::ChannelCont, _, _>(
|
self.execute_request::<response::ChannelCont, _, _>(
|
||||||
|
@ -139,7 +139,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QChannel {
|
let request_body = QChannel {
|
||||||
context,
|
context,
|
||||||
browse_id: channel_id,
|
browse_id: channel_id.to_owned(),
|
||||||
params: Params::Info,
|
params: Params::Info,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ impl MapResponse<Channel<Paginator<ChannelVideo>>> for response::Channel {
|
||||||
id,
|
id,
|
||||||
lang,
|
lang,
|
||||||
)?,
|
)?,
|
||||||
warnings,
|
warnings: warnings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ impl MapResponse<Channel<Paginator<ChannelPlaylist>>> for response::Channel {
|
||||||
id,
|
id,
|
||||||
lang,
|
lang,
|
||||||
)?,
|
)?,
|
||||||
warnings,
|
warnings: warnings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ impl RustyPipeQuery {
|
||||||
msgs: Vec::new(),
|
msgs: Vec::new(),
|
||||||
deobf_data: None,
|
deobf_data: None,
|
||||||
http_request: crate::report::HTTPRequest {
|
http_request: crate::report::HTTPRequest {
|
||||||
url,
|
url: url,
|
||||||
method: "GET".to_owned(),
|
method: "GET".to_owned(),
|
||||||
req_header: BTreeMap::new(),
|
req_header: BTreeMap::new(),
|
||||||
req_body: String::new(),
|
req_body: String::new(),
|
||||||
|
|
|
@ -5,7 +5,6 @@ mod pagination;
|
||||||
mod player;
|
mod player;
|
||||||
mod playlist;
|
mod playlist;
|
||||||
mod response;
|
mod response;
|
||||||
mod search;
|
|
||||||
mod video_details;
|
mod video_details;
|
||||||
|
|
||||||
#[cfg(feature = "rss")]
|
#[cfg(feature = "rss")]
|
||||||
|
@ -125,9 +124,9 @@ struct ThirdParty {
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QContinuation<'a> {
|
struct QContinuation {
|
||||||
context: YTContext,
|
context: YTContext,
|
||||||
continuation: &'a str,
|
continuation: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_UA: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0";
|
const DEFAULT_UA: &str = "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0";
|
||||||
|
@ -515,11 +514,11 @@ impl RustyPipe {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| {
|
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(Error::from(
|
||||||
Error::from(ExtractionError::InvalidData(
|
ExtractionError::InvalidData(
|
||||||
"Could not find desktop client version in sw.js".into(),
|
"Could not find desktop client version in sw.js".into(),
|
||||||
))
|
),
|
||||||
})
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_html = async {
|
let from_html = async {
|
||||||
|
@ -533,11 +532,11 @@ impl RustyPipe {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| {
|
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(Error::from(
|
||||||
Error::from(ExtractionError::InvalidData(
|
ExtractionError::InvalidData(
|
||||||
"Could not find desktop client version in sw.js".into(),
|
"Could not find desktop client version in sw.js".into(),
|
||||||
))
|
),
|
||||||
})
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
match from_swjs.await {
|
match from_swjs.await {
|
||||||
|
@ -562,11 +561,9 @@ impl RustyPipe {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| {
|
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(Error::from(
|
||||||
Error::from(ExtractionError::InvalidData(
|
ExtractionError::InvalidData("Could not find music client version in sw.js".into()),
|
||||||
"Could not find music client version in sw.js".into(),
|
))
|
||||||
))
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_html = async {
|
let from_html = async {
|
||||||
|
@ -580,11 +577,11 @@ impl RustyPipe {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| {
|
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(Error::from(
|
||||||
Error::from(ExtractionError::InvalidData(
|
ExtractionError::InvalidData(
|
||||||
"Could not find music client version on html page".into(),
|
"Could not find music client version on html page".into(),
|
||||||
))
|
),
|
||||||
})
|
))
|
||||||
};
|
};
|
||||||
|
|
||||||
match from_swjs.await {
|
match from_swjs.await {
|
||||||
|
@ -994,14 +991,12 @@ impl RustyPipeQuery {
|
||||||
if !mapres.warnings.is_empty() {
|
if !mapres.warnings.is_empty() {
|
||||||
create_report(
|
create_report(
|
||||||
Level::WRN,
|
Level::WRN,
|
||||||
Some(ExtractionError::DeserializationWarnings.to_string()),
|
Some(ExtractionError::Warnings.to_string()),
|
||||||
mapres.warnings,
|
mapres.warnings,
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.opts.strict {
|
if self.opts.strict {
|
||||||
return Err(Error::Extraction(
|
return Err(Error::Extraction(ExtractionError::Warnings));
|
||||||
ExtractionError::DeserializationWarnings,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} else if self.opts.report {
|
} else if self.opts.report {
|
||||||
create_report(Level::DBG, None, vec![]);
|
create_report(Level::DBG, None, vec![]);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
ChannelPlaylist, ChannelVideo, Comment, Paginator, PlaylistVideo, RecommendedVideo, SearchItem,
|
ChannelPlaylist, ChannelVideo, Comment, Paginator, PlaylistVideo, RecommendedVideo,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::RustyPipeQuery;
|
use super::RustyPipeQuery;
|
||||||
|
@ -225,47 +225,3 @@ impl Paginator<Comment> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Paginator<SearchItem> {
|
|
||||||
pub async fn next(&self, query: RustyPipeQuery) -> Result<Option<Self>> {
|
|
||||||
Ok(match &self.ctoken {
|
|
||||||
Some(ctoken) => Some(query.search_continuation(ctoken).await?),
|
|
||||||
None => None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn extend(&mut self, query: RustyPipeQuery) -> Result<bool> {
|
|
||||||
match self.next(query).await {
|
|
||||||
Ok(Some(paginator)) => {
|
|
||||||
let mut items = paginator.items;
|
|
||||||
self.items.append(&mut items);
|
|
||||||
self.ctoken = paginator.ctoken;
|
|
||||||
Ok(true)
|
|
||||||
}
|
|
||||||
Ok(None) => Ok(false),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn extend_pages(&mut self, query: RustyPipeQuery, n_pages: usize) -> Result<()> {
|
|
||||||
for _ in 0..n_pages {
|
|
||||||
match self.extend(query.clone()).await {
|
|
||||||
Ok(false) => break,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn extend_limit(&mut self, query: RustyPipeQuery, n_items: usize) -> Result<()> {
|
|
||||||
while self.items.len() < n_items {
|
|
||||||
match self.extend(query.clone()).await {
|
|
||||||
Ok(false) => break,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ use super::{
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
struct QPlayer<'a> {
|
struct QPlayer {
|
||||||
context: YTContext,
|
context: YTContext,
|
||||||
/// Website playback context
|
/// Website playback context
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
@ -35,7 +35,7 @@ struct QPlayer<'a> {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
cpn: Option<String>,
|
cpn: Option<String>,
|
||||||
/// YouTube video ID
|
/// YouTube video ID
|
||||||
video_id: &'a str,
|
video_id: String,
|
||||||
/// Set to true to allow extraction of streams with sensitive content
|
/// Set to true to allow extraction of streams with sensitive content
|
||||||
content_check_ok: bool,
|
content_check_ok: bool,
|
||||||
/// Probably refers to allowing sensitive content, too
|
/// Probably refers to allowing sensitive content, too
|
||||||
|
@ -82,7 +82,7 @@ impl RustyPipeQuery {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
cpn: None,
|
cpn: None,
|
||||||
video_id,
|
video_id: video_id.to_owned(),
|
||||||
content_check_ok: true,
|
content_check_ok: true,
|
||||||
racy_check_ok: true,
|
racy_check_ok: true,
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ impl RustyPipeQuery {
|
||||||
context,
|
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: video_id.to_owned(),
|
||||||
content_check_ok: true,
|
content_check_ok: true,
|
||||||
racy_check_ok: true,
|
racy_check_ok: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
context,
|
||||||
continuation: ctoken,
|
continuation: ctoken.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_request::<response::PlaylistCont, _, _>(
|
self.execute_request::<response::PlaylistCont, _, _>(
|
||||||
|
@ -143,7 +143,7 @@ impl MapResponse<Playlist> for response::Playlist {
|
||||||
Err(ExtractionError::InvalidData("no video count".into()))
|
Err(ExtractionError::InvalidData("no video count".into()))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
None => videos.len() as u64,
|
None => videos.len() as u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
let playlist_id = self.header.playlist_header_renderer.playlist_id;
|
let playlist_id = self.header.playlist_header_renderer.playlist_id;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use crate::serializer::ignore_any;
|
||||||
use crate::serializer::{text::Text, MapResult, VecLogError};
|
use crate::serializer::{text::Text, MapResult, VecLogError};
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub header: Header,
|
pub header: Header,
|
||||||
|
@ -19,13 +19,13 @@ pub struct Channel {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChannelCont {
|
pub struct ChannelCont {
|
||||||
pub on_response_received_actions: Vec<OnResponseReceivedAction>,
|
pub on_response_received_actions: Vec<OnResponseReceivedAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Contents {
|
pub struct Contents {
|
||||||
pub two_column_browse_results_renderer: TabsRenderer,
|
pub two_column_browse_results_renderer: TabsRenderer,
|
||||||
|
@ -34,26 +34,26 @@ pub struct Contents {
|
||||||
/// YouTube channel tab view. Contains multiple tabs
|
/// YouTube channel tab view. Contains multiple tabs
|
||||||
/// (Home, Videos, Playlists, About...). We can ignore unknown tabs.
|
/// (Home, Videos, Playlists, About...). We can ignore unknown tabs.
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TabsRenderer {
|
pub struct TabsRenderer {
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
pub tabs: Vec<TabRendererWrap>,
|
pub tabs: Vec<TabRendererWrap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TabRendererWrap {
|
pub struct TabRendererWrap {
|
||||||
pub tab_renderer: ContentRenderer<SectionListRendererWrap>,
|
pub tab_renderer: ContentRenderer<SectionListRendererWrap>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SectionListRendererWrap {
|
pub struct SectionListRendererWrap {
|
||||||
pub section_list_renderer: SectionListRenderer,
|
pub section_list_renderer: SectionListRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SectionListRenderer {
|
pub struct SectionListRenderer {
|
||||||
pub contents: Vec<ItemSectionRendererWrap>,
|
pub contents: Vec<ItemSectionRendererWrap>,
|
||||||
|
@ -63,14 +63,14 @@ pub struct SectionListRenderer {
|
||||||
pub target_id: Option<String>,
|
pub target_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ItemSectionRendererWrap {
|
pub struct ItemSectionRendererWrap {
|
||||||
pub item_section_renderer: ContentsRenderer<ChannelContent>,
|
pub item_section_renderer: ContentsRenderer<ChannelContent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum ChannelContent {
|
pub enum ChannelContent {
|
||||||
GridRenderer {
|
GridRenderer {
|
||||||
|
@ -83,14 +83,14 @@ pub enum ChannelContent {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
pub c4_tabbed_header_renderer: HeaderRenderer,
|
pub c4_tabbed_header_renderer: HeaderRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct HeaderRenderer {
|
pub struct HeaderRenderer {
|
||||||
pub channel_id: String,
|
pub channel_id: String,
|
||||||
|
@ -114,26 +114,26 @@ pub struct HeaderRenderer {
|
||||||
pub tv_banner: Thumbnails,
|
pub tv_banner: Thumbnails,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub channel_metadata_renderer: ChannelMetadataRenderer,
|
pub channel_metadata_renderer: ChannelMetadataRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChannelMetadataRenderer {
|
pub struct ChannelMetadataRenderer {
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub vanity_channel_url: Option<String>,
|
pub vanity_channel_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Microformat {
|
pub struct Microformat {
|
||||||
pub microformat_data_renderer: MicroformatDataRenderer,
|
pub microformat_data_renderer: MicroformatDataRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MicroformatDataRenderer {
|
pub struct MicroformatDataRenderer {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -141,7 +141,7 @@ pub struct MicroformatDataRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChannelFullMetadata {
|
pub struct ChannelFullMetadata {
|
||||||
#[serde_as(as = "Text")]
|
#[serde_as(as = "Text")]
|
||||||
|
@ -153,7 +153,7 @@ pub struct ChannelFullMetadata {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PrimaryLink {
|
pub struct PrimaryLink {
|
||||||
#[serde_as(as = "Text")]
|
#[serde_as(as = "Text")]
|
||||||
|
@ -161,26 +161,26 @@ pub struct PrimaryLink {
|
||||||
pub navigation_endpoint: NavigationEndpoint,
|
pub navigation_endpoint: NavigationEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NavigationEndpoint {
|
pub struct NavigationEndpoint {
|
||||||
pub url_endpoint: UrlEndpoint,
|
pub url_endpoint: UrlEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UrlEndpoint {
|
pub struct UrlEndpoint {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OnResponseReceivedAction {
|
pub struct OnResponseReceivedAction {
|
||||||
pub append_continuation_items_action: AppendAction,
|
pub append_continuation_items_action: AppendAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AppendAction {
|
pub struct AppendAction {
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
#[serde_as(as = "VecLogError<_>")]
|
||||||
|
|
|
@ -3,7 +3,7 @@ use serde::Deserialize;
|
||||||
|
|
||||||
use super::Thumbnail;
|
use super::Thumbnail;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct ChannelRss {
|
pub struct ChannelRss {
|
||||||
#[serde(rename = "$unflatten=yt:channelId")]
|
#[serde(rename = "$unflatten=yt:channelId")]
|
||||||
pub channel_id: String,
|
pub channel_id: String,
|
||||||
|
@ -14,7 +14,7 @@ pub struct ChannelRss {
|
||||||
pub entry: Vec<Entry>,
|
pub entry: Vec<Entry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Entry {
|
pub struct Entry {
|
||||||
#[serde(rename = "$unflatten=yt:videoId")]
|
#[serde(rename = "$unflatten=yt:videoId")]
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
|
@ -28,7 +28,7 @@ pub struct Entry {
|
||||||
pub media_group: MediaGroup,
|
pub media_group: MediaGroup,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct MediaGroup {
|
pub struct MediaGroup {
|
||||||
#[serde(rename = "$unflatten=media:thumbnail")]
|
#[serde(rename = "$unflatten=media:thumbnail")]
|
||||||
pub thumbnail: Thumbnail,
|
pub thumbnail: Thumbnail,
|
||||||
|
@ -38,7 +38,7 @@ pub struct MediaGroup {
|
||||||
pub community: Community,
|
pub community: Community,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Community {
|
pub struct Community {
|
||||||
#[serde(rename = "$unflatten=media:starRating")]
|
#[serde(rename = "$unflatten=media:starRating")]
|
||||||
pub rating: Rating,
|
pub rating: Rating,
|
||||||
|
@ -46,12 +46,12 @@ pub struct Community {
|
||||||
pub statistics: Statistics,
|
pub statistics: Statistics,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Rating {
|
pub struct Rating {
|
||||||
pub count: u64,
|
pub count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Statistics {
|
pub struct Statistics {
|
||||||
pub views: u64,
|
pub views: u64,
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ pub mod channel;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod playlist;
|
pub mod playlist;
|
||||||
pub mod playlist_music;
|
pub mod playlist_music;
|
||||||
pub mod search;
|
|
||||||
pub mod video_details;
|
pub mod video_details;
|
||||||
|
|
||||||
pub use channel::Channel;
|
pub use channel::Channel;
|
||||||
|
@ -11,8 +10,6 @@ pub use player::Player;
|
||||||
pub use playlist::Playlist;
|
pub use playlist::Playlist;
|
||||||
pub use playlist::PlaylistCont;
|
pub use playlist::PlaylistCont;
|
||||||
pub use playlist_music::PlaylistMusic;
|
pub use playlist_music::PlaylistMusic;
|
||||||
pub use search::Search;
|
|
||||||
pub use search::SearchCont;
|
|
||||||
pub use video_details::VideoComments;
|
pub use video_details::VideoComments;
|
||||||
pub use video_details::VideoDetails;
|
pub use video_details::VideoDetails;
|
||||||
pub use video_details::VideoRecommendations;
|
pub use video_details::VideoRecommendations;
|
||||||
|
@ -30,20 +27,20 @@ use crate::serializer::{
|
||||||
text::{Text, TextComponent},
|
text::{Text, TextComponent},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ContentRenderer<T> {
|
pub struct ContentRenderer<T> {
|
||||||
pub content: T,
|
pub content: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ContentsRenderer<T> {
|
pub struct ContentsRenderer<T> {
|
||||||
#[serde(alias = "tabs")]
|
#[serde(alias = "tabs")]
|
||||||
pub contents: Vec<T>,
|
pub contents: Vec<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ThumbnailsWrap {
|
pub struct ThumbnailsWrap {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -52,14 +49,14 @@ pub struct ThumbnailsWrap {
|
||||||
|
|
||||||
/// List of images in different resolutions.
|
/// List of images in different resolutions.
|
||||||
/// Not only used for thumbnails, but also for avatars and banners.
|
/// Not only used for thumbnails, but also for avatars and banners.
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Thumbnails {
|
pub struct Thumbnails {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub thumbnails: Vec<Thumbnail>,
|
pub thumbnails: Vec<Thumbnail>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Thumbnail {
|
pub struct Thumbnail {
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
@ -67,17 +64,13 @@ pub struct Thumbnail {
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum VideoListItem {
|
pub enum VideoListItem {
|
||||||
/// Video on channel page
|
|
||||||
GridVideoRenderer(GridVideoRenderer),
|
GridVideoRenderer(GridVideoRenderer),
|
||||||
/// Video in recommendations
|
|
||||||
CompactVideoRenderer(CompactVideoRenderer),
|
CompactVideoRenderer(CompactVideoRenderer),
|
||||||
/// Video in playlist
|
|
||||||
PlaylistVideoRenderer(PlaylistVideoRenderer),
|
PlaylistVideoRenderer(PlaylistVideoRenderer),
|
||||||
|
|
||||||
/// Playlist on channel page
|
|
||||||
GridPlaylistRenderer(GridPlaylistRenderer),
|
GridPlaylistRenderer(GridPlaylistRenderer),
|
||||||
|
|
||||||
/// Continauation items are located at the end of a list
|
/// Continauation items are located at the end of a list
|
||||||
|
@ -97,7 +90,7 @@ pub enum VideoListItem {
|
||||||
|
|
||||||
/// Video displayed on a channel page
|
/// Video displayed on a channel page
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GridVideoRenderer {
|
pub struct GridVideoRenderer {
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
|
@ -118,7 +111,7 @@ pub struct GridVideoRenderer {
|
||||||
|
|
||||||
/// Video displayed in recommendations
|
/// Video displayed in recommendations
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompactVideoRenderer {
|
pub struct CompactVideoRenderer {
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
|
@ -152,7 +145,7 @@ pub struct CompactVideoRenderer {
|
||||||
|
|
||||||
/// Video displayed in a playlist
|
/// Video displayed in a playlist
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistVideoRenderer {
|
pub struct PlaylistVideoRenderer {
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
|
@ -167,7 +160,7 @@ pub struct PlaylistVideoRenderer {
|
||||||
|
|
||||||
/// Playlist displayed on a channel page
|
/// Playlist displayed on a channel page
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GridPlaylistRenderer {
|
pub struct GridPlaylistRenderer {
|
||||||
pub playlist_id: String,
|
pub playlist_id: String,
|
||||||
|
@ -179,7 +172,7 @@ pub struct GridPlaylistRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UpcomingEventData {
|
pub struct UpcomingEventData {
|
||||||
/// Unixtime in seconds
|
/// Unixtime in seconds
|
||||||
|
@ -187,26 +180,26 @@ pub struct UpcomingEventData {
|
||||||
pub start_time: i64,
|
pub start_time: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ContinuationItemRenderer {
|
pub struct ContinuationItemRenderer {
|
||||||
pub continuation_endpoint: ContinuationEndpoint,
|
pub continuation_endpoint: ContinuationEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ContinuationEndpoint {
|
pub struct ContinuationEndpoint {
|
||||||
pub continuation_command: ContinuationCommand,
|
pub continuation_command: ContinuationCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ContinuationCommand {
|
pub struct ContinuationCommand {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Icon {
|
pub struct Icon {
|
||||||
pub icon_type: IconType,
|
pub icon_type: IconType,
|
||||||
|
@ -223,14 +216,14 @@ pub enum IconType {
|
||||||
Like,
|
Like,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoOwner {
|
pub struct VideoOwner {
|
||||||
pub video_owner_renderer: VideoOwnerRenderer,
|
pub video_owner_renderer: VideoOwnerRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoOwnerRenderer {
|
pub struct VideoOwnerRenderer {
|
||||||
pub title: TextComponent,
|
pub title: TextComponent,
|
||||||
|
@ -242,13 +235,13 @@ pub struct VideoOwnerRenderer {
|
||||||
pub badges: Vec<ChannelBadge>,
|
pub badges: Vec<ChannelBadge>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChannelBadge {
|
pub struct ChannelBadge {
|
||||||
pub metadata_badge_renderer: ChannelBadgeRenderer,
|
pub metadata_badge_renderer: ChannelBadgeRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChannelBadgeRenderer {
|
pub struct ChannelBadgeRenderer {
|
||||||
pub style: ChannelBadgeStyle,
|
pub style: ChannelBadgeStyle,
|
||||||
|
@ -261,19 +254,19 @@ pub enum ChannelBadgeStyle {
|
||||||
BadgeStyleTypeVerifiedArtist,
|
BadgeStyleTypeVerifiedArtist,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TimeOverlay {
|
pub struct TimeOverlay {
|
||||||
pub thumbnail_overlay_time_status_renderer: TimeOverlayRenderer,
|
pub thumbnail_overlay_time_status_renderer: TimeOverlayRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TimeOverlayRenderer {
|
pub struct TimeOverlayRenderer {
|
||||||
/// `29:54`
|
/// `29:54`
|
||||||
///
|
///
|
||||||
/// Is `LIVE` in case of a livestream and `SHORTS` in case of a short video
|
/// Is `LIVE` in case of a livestream
|
||||||
#[serde_as(as = "Text")]
|
#[serde_as(as = "Text")]
|
||||||
pub text: String,
|
pub text: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -281,7 +274,7 @@ pub struct TimeOverlayRenderer {
|
||||||
pub style: TimeOverlayStyle,
|
pub style: TimeOverlayStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum TimeOverlayStyle {
|
pub enum TimeOverlayStyle {
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -292,7 +285,7 @@ pub enum TimeOverlayStyle {
|
||||||
|
|
||||||
/// Badges are displayed on the video thumbnail and
|
/// Badges are displayed on the video thumbnail and
|
||||||
/// show certain video properties (e.g. active livestream)
|
/// show certain video properties (e.g. active livestream)
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoBadge {
|
pub struct VideoBadge {
|
||||||
pub metadata_badge_renderer: VideoBadgeRenderer,
|
pub metadata_badge_renderer: VideoBadgeRenderer,
|
||||||
|
@ -300,7 +293,7 @@ pub struct VideoBadge {
|
||||||
|
|
||||||
/// Badges are displayed on the video thumbnail and
|
/// Badges are displayed on the video thumbnail and
|
||||||
/// show certain video properties (e.g. active livestream)
|
/// show certain video properties (e.g. active livestream)
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoBadgeRenderer {
|
pub struct VideoBadgeRenderer {
|
||||||
pub style: VideoBadgeStyle,
|
pub style: VideoBadgeStyle,
|
||||||
|
@ -316,7 +309,7 @@ pub enum VideoBadgeStyle {
|
||||||
// YouTube Music
|
// YouTube Music
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MusicItem {
|
pub struct MusicItem {
|
||||||
pub thumbnail: MusicThumbnailRenderer,
|
pub thumbnail: MusicThumbnailRenderer,
|
||||||
|
@ -329,21 +322,21 @@ pub struct MusicItem {
|
||||||
pub fixed_columns: Vec<MusicColumn>,
|
pub fixed_columns: Vec<MusicColumn>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MusicThumbnailRenderer {
|
pub struct MusicThumbnailRenderer {
|
||||||
#[serde(alias = "croppedSquareThumbnailRenderer")]
|
#[serde(alias = "croppedSquareThumbnailRenderer")]
|
||||||
pub music_thumbnail_renderer: ThumbnailsWrap,
|
pub music_thumbnail_renderer: ThumbnailsWrap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistItemData {
|
pub struct PlaylistItemData {
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MusicContentsRenderer<T> {
|
pub struct MusicContentsRenderer<T> {
|
||||||
pub contents: Vec<T>,
|
pub contents: Vec<T>,
|
||||||
|
@ -351,7 +344,7 @@ pub struct MusicContentsRenderer<T> {
|
||||||
pub continuations: Option<Vec<MusicContinuation>>,
|
pub continuations: Option<Vec<MusicContinuation>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct MusicColumn {
|
pub struct MusicColumn {
|
||||||
#[serde(
|
#[serde(
|
||||||
rename = "musicResponsiveListItemFlexColumnRenderer",
|
rename = "musicResponsiveListItemFlexColumnRenderer",
|
||||||
|
@ -361,18 +354,18 @@ pub struct MusicColumn {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct MusicColumnRenderer {
|
pub struct MusicColumnRenderer {
|
||||||
pub text: TextComponent,
|
pub text: TextComponent,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MusicContinuation {
|
pub struct MusicContinuation {
|
||||||
pub next_continuation_data: MusicContinuationData,
|
pub next_continuation_data: MusicContinuationData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MusicContinuationData {
|
pub struct MusicContinuationData {
|
||||||
pub continuation: String,
|
pub continuation: String,
|
||||||
|
|
|
@ -8,7 +8,7 @@ use serde_with::{json::JsonString, DefaultOnError};
|
||||||
use super::Thumbnails;
|
use super::Thumbnails;
|
||||||
use crate::serializer::{text::Text, MapResult, VecLogError};
|
use crate::serializer::{text::Text, MapResult, VecLogError};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
pub playability_status: PlayabilityStatus,
|
pub playability_status: PlayabilityStatus,
|
||||||
|
@ -18,7 +18,7 @@ pub struct Player {
|
||||||
pub microformat: Option<Microformat>,
|
pub microformat: Option<Microformat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum PlayabilityStatus {
|
pub enum PlayabilityStatus {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -36,11 +36,11 @@ pub enum PlayabilityStatus {
|
||||||
Error { reason: String },
|
Error { reason: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
pub struct Empty {}
|
pub struct Empty {}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct StreamingData {
|
pub struct StreamingData {
|
||||||
#[serde_as(as = "JsonString")]
|
#[serde_as(as = "JsonString")]
|
||||||
|
@ -58,7 +58,7 @@ pub struct StreamingData {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Format {
|
pub struct Format {
|
||||||
pub itag: u32,
|
pub itag: u32,
|
||||||
|
@ -152,7 +152,7 @@ pub enum FormatType {
|
||||||
FormatStreamTypeOtf,
|
FormatStreamTypeOtf,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Clone, Debug, Deserialize)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct ColorInfo {
|
pub struct ColorInfo {
|
||||||
pub primaries: Primaries,
|
pub primaries: Primaries,
|
||||||
|
@ -166,7 +166,7 @@ pub enum Primaries {
|
||||||
ColorPrimariesBt2020,
|
ColorPrimariesBt2020,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Clone, Debug, Deserialize)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
pub struct AudioTrack {
|
pub struct AudioTrack {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
@ -174,20 +174,20 @@ pub struct AudioTrack {
|
||||||
pub audio_is_default: bool,
|
pub audio_is_default: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Captions {
|
pub struct Captions {
|
||||||
pub player_captions_tracklist_renderer: PlayerCaptionsTracklistRenderer,
|
pub player_captions_tracklist_renderer: PlayerCaptionsTracklistRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlayerCaptionsTracklistRenderer {
|
pub struct PlayerCaptionsTracklistRenderer {
|
||||||
pub caption_tracks: Vec<CaptionTrack>,
|
pub caption_tracks: Vec<CaptionTrack>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CaptionTrack {
|
pub struct CaptionTrack {
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
|
@ -197,7 +197,7 @@ pub struct CaptionTrack {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoDetails {
|
pub struct VideoDetails {
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
|
@ -215,7 +215,7 @@ pub struct VideoDetails {
|
||||||
pub is_live_content: bool,
|
pub is_live_content: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Microformat {
|
pub struct Microformat {
|
||||||
#[serde(alias = "microformatDataRenderer")]
|
#[serde(alias = "microformatDataRenderer")]
|
||||||
|
@ -223,7 +223,7 @@ pub struct Microformat {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlayerMicroformatRenderer {
|
pub struct PlayerMicroformatRenderer {
|
||||||
#[serde(alias = "familySafe")]
|
#[serde(alias = "familySafe")]
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::serializer::{MapResult, VecLogError};
|
||||||
|
|
||||||
use super::{ContentRenderer, ContentsRenderer, ThumbnailsWrap, VideoListItem};
|
use super::{ContentRenderer, ContentsRenderer, ThumbnailsWrap, VideoListItem};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Playlist {
|
pub struct Playlist {
|
||||||
pub contents: Contents,
|
pub contents: Contents,
|
||||||
|
@ -16,59 +16,59 @@ pub struct Playlist {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistCont {
|
pub struct PlaylistCont {
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
pub on_response_received_actions: Vec<OnResponseReceivedAction>,
|
pub on_response_received_actions: Vec<OnResponseReceivedAction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Contents {
|
pub struct Contents {
|
||||||
pub two_column_browse_results_renderer: ContentsRenderer<Tab>,
|
pub two_column_browse_results_renderer: ContentsRenderer<Tab>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub tab_renderer: ContentRenderer<SectionList>,
|
pub tab_renderer: ContentRenderer<SectionList>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SectionList {
|
pub struct SectionList {
|
||||||
pub section_list_renderer: ContentsRenderer<ItemSection>,
|
pub section_list_renderer: ContentsRenderer<ItemSection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ItemSection {
|
pub struct ItemSection {
|
||||||
pub item_section_renderer: ContentsRenderer<PlaylistVideoListRenderer>,
|
pub item_section_renderer: ContentsRenderer<PlaylistVideoListRenderer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistVideoListRenderer {
|
pub struct PlaylistVideoListRenderer {
|
||||||
pub playlist_video_list_renderer: PlaylistVideoList,
|
pub playlist_video_list_renderer: PlaylistVideoList,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistVideoList {
|
pub struct PlaylistVideoList {
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
#[serde_as(as = "VecLogError<_>")]
|
||||||
pub contents: MapResult<Vec<VideoListItem>>,
|
pub contents: MapResult<Vec<VideoListItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
pub playlist_header_renderer: HeaderRenderer,
|
pub playlist_header_renderer: HeaderRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct HeaderRenderer {
|
pub struct HeaderRenderer {
|
||||||
pub playlist_id: String,
|
pub playlist_id: String,
|
||||||
|
@ -87,48 +87,48 @@ pub struct HeaderRenderer {
|
||||||
pub byline: Vec<Byline>,
|
pub byline: Vec<Byline>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistHeaderBanner {
|
pub struct PlaylistHeaderBanner {
|
||||||
pub hero_playlist_thumbnail_renderer: ThumbnailsWrap,
|
pub hero_playlist_thumbnail_renderer: ThumbnailsWrap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Byline {
|
pub struct Byline {
|
||||||
pub playlist_byline_renderer: BylineRenderer,
|
pub playlist_byline_renderer: BylineRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BylineRenderer {
|
pub struct BylineRenderer {
|
||||||
#[serde_as(as = "Text")]
|
#[serde_as(as = "Text")]
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Sidebar {
|
pub struct Sidebar {
|
||||||
pub playlist_sidebar_renderer: SidebarRenderer,
|
pub playlist_sidebar_renderer: SidebarRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SidebarRenderer {
|
pub struct SidebarRenderer {
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
pub items: Vec<SidebarItemPrimary>,
|
pub items: Vec<SidebarItemPrimary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SidebarItemPrimary {
|
pub struct SidebarItemPrimary {
|
||||||
pub playlist_sidebar_primary_info_renderer: SidebarPrimaryInfoRenderer,
|
pub playlist_sidebar_primary_info_renderer: SidebarPrimaryInfoRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SidebarPrimaryInfoRenderer {
|
pub struct SidebarPrimaryInfoRenderer {
|
||||||
pub thumbnail_renderer: PlaylistThumbnailRenderer,
|
pub thumbnail_renderer: PlaylistThumbnailRenderer,
|
||||||
|
@ -139,7 +139,7 @@ pub struct SidebarPrimaryInfoRenderer {
|
||||||
pub stats: Vec<String>,
|
pub stats: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistThumbnailRenderer {
|
pub struct PlaylistThumbnailRenderer {
|
||||||
// the alternative field name is used by YTM playlists
|
// the alternative field name is used by YTM playlists
|
||||||
|
@ -147,14 +147,14 @@ pub struct PlaylistThumbnailRenderer {
|
||||||
pub playlist_video_thumbnail_renderer: ThumbnailsWrap,
|
pub playlist_video_thumbnail_renderer: ThumbnailsWrap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OnResponseReceivedAction {
|
pub struct OnResponseReceivedAction {
|
||||||
pub append_continuation_items_action: AppendAction,
|
pub append_continuation_items_action: AppendAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AppendAction {
|
pub struct AppendAction {
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
#[serde_as(as = "VecLogError<_>")]
|
||||||
|
|
|
@ -9,33 +9,33 @@ use super::{
|
||||||
ContentRenderer, ContentsRenderer, MusicContentsRenderer, MusicContinuation, MusicItem,
|
ContentRenderer, ContentsRenderer, MusicContentsRenderer, MusicContinuation, MusicItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistMusic {
|
pub struct PlaylistMusic {
|
||||||
pub contents: Contents,
|
pub contents: Contents,
|
||||||
pub header: Header,
|
pub header: Header,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Contents {
|
pub struct Contents {
|
||||||
pub single_column_browse_results_renderer: ContentsRenderer<Tab>,
|
pub single_column_browse_results_renderer: ContentsRenderer<Tab>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
pub tab_renderer: ContentRenderer<SectionList>,
|
pub tab_renderer: ContentRenderer<SectionList>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SectionList {
|
pub struct SectionList {
|
||||||
/// Includes a continuation token for fetching recommendations
|
/// Includes a continuation token for fetching recommendations
|
||||||
pub section_list_renderer: MusicContentsRenderer<ItemSection>,
|
pub section_list_renderer: MusicContentsRenderer<ItemSection>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ItemSection {
|
pub struct ItemSection {
|
||||||
#[serde(alias = "musicPlaylistShelfRenderer")]
|
#[serde(alias = "musicPlaylistShelfRenderer")]
|
||||||
|
@ -43,7 +43,7 @@ pub struct ItemSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MusicShelf {
|
pub struct MusicShelf {
|
||||||
/// Playlist ID (only for playlists)
|
/// Playlist ID (only for playlists)
|
||||||
|
@ -55,20 +55,20 @@ pub struct MusicShelf {
|
||||||
pub continuations: Option<Vec<MusicContinuation>>,
|
pub continuations: Option<Vec<MusicContinuation>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PlaylistMusicItem {
|
pub struct PlaylistMusicItem {
|
||||||
pub music_responsive_list_item_renderer: MusicItem,
|
pub music_responsive_list_item_renderer: MusicItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Header {
|
pub struct Header {
|
||||||
pub music_detail_header_renderer: HeaderRenderer,
|
pub music_detail_header_renderer: HeaderRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct HeaderRenderer {
|
pub struct HeaderRenderer {
|
||||||
#[serde_as(as = "crate::serializer::text::Text")]
|
#[serde_as(as = "crate::serializer::text::Text")]
|
||||||
|
|
|
@ -1,215 +0,0 @@
|
||||||
use serde::Deserialize;
|
|
||||||
use serde_with::json::JsonString;
|
|
||||||
use serde_with::{serde_as, VecSkipError};
|
|
||||||
|
|
||||||
use crate::serializer::ignore_any;
|
|
||||||
use crate::serializer::{
|
|
||||||
text::{Text, TextComponent},
|
|
||||||
MapResult, VecLogError,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{ChannelBadge, ContentsRenderer, ContinuationEndpoint, Thumbnails, TimeOverlay};
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Search {
|
|
||||||
#[serde_as(as = "Option<JsonString>")]
|
|
||||||
pub estimated_results: Option<u64>,
|
|
||||||
pub contents: Contents,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SearchCont {
|
|
||||||
#[serde_as(as = "Option<JsonString>")]
|
|
||||||
pub estimated_results: Option<u64>,
|
|
||||||
pub on_response_received_commands: Vec<SearchContCommand>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SearchContCommand {
|
|
||||||
pub append_continuation_items_action: SearchContAction,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct SearchContAction {
|
|
||||||
pub continuation_items: Vec<SectionListItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct Contents {
|
|
||||||
pub two_column_search_results_renderer: TwoColumnSearchResultsRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TwoColumnSearchResultsRenderer {
|
|
||||||
pub primary_contents: PrimaryContents,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PrimaryContents {
|
|
||||||
pub section_list_renderer: ContentsRenderer<SectionListItem>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum SectionListItem {
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
ItemSectionRenderer {
|
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
|
||||||
contents: MapResult<Vec<SearchItem>>,
|
|
||||||
},
|
|
||||||
/// Continuation token to fetch more search results
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
ContinuationItemRenderer {
|
|
||||||
continuation_endpoint: ContinuationEndpoint,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub enum SearchItem {
|
|
||||||
/// Video in search results
|
|
||||||
VideoRenderer(VideoRenderer),
|
|
||||||
/// Playlist in search results
|
|
||||||
PlaylistRenderer(PlaylistRenderer),
|
|
||||||
/// Channel displayed in search results
|
|
||||||
ChannelRenderer(ChannelRenderer),
|
|
||||||
|
|
||||||
/// Corrected search query
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
ShowingResultsForRenderer {
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
corrected_query: String,
|
|
||||||
},
|
|
||||||
/// No search result item (e.g. ad) or unimplemented item
|
|
||||||
///
|
|
||||||
/// Unimplemented:
|
|
||||||
/// - shelfRenderer (e.g. Latest from channel, For you)
|
|
||||||
#[serde(other, deserialize_with = "ignore_any")]
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Video displayed in search results
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct VideoRenderer {
|
|
||||||
pub video_id: String,
|
|
||||||
pub thumbnail: Thumbnails,
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
pub title: String,
|
|
||||||
#[serde(rename = "shortBylineText")]
|
|
||||||
pub channel: TextComponent,
|
|
||||||
pub channel_thumbnail_supported_renderers: ChannelThumbnailSupportedRenderers,
|
|
||||||
#[serde_as(as = "Option<Text>")]
|
|
||||||
pub published_time_text: Option<String>,
|
|
||||||
#[serde_as(as = "Option<Text>")]
|
|
||||||
pub length_text: Option<String>,
|
|
||||||
/// Contains `No views` if the view count is zero
|
|
||||||
#[serde_as(as = "Option<Text>")]
|
|
||||||
pub view_count_text: Option<String>,
|
|
||||||
/// Channel verification badge
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub owner_badges: Vec<ChannelBadge>,
|
|
||||||
/// Contains Short/Live tag
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub thumbnail_overlays: Vec<TimeOverlay>,
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub detailed_metadata_snippets: Vec<DetailedMetadataSnippet>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Playlist displayed in search results
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PlaylistRenderer {
|
|
||||||
pub playlist_id: String,
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
pub title: String,
|
|
||||||
/// The first item of this list contains the playlist thumbnail,
|
|
||||||
/// subsequent items contain very small thumbnails of the next playlist videos
|
|
||||||
pub thumbnails: Vec<Thumbnails>,
|
|
||||||
#[serde_as(as = "JsonString")]
|
|
||||||
pub video_count: u64,
|
|
||||||
#[serde(rename = "shortBylineText")]
|
|
||||||
pub channel: TextComponent,
|
|
||||||
/// Channel verification badge
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub owner_badges: Vec<ChannelBadge>,
|
|
||||||
/// First 2 videos
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub videos: Vec<ChildVideoRendererWrap>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Channel displayed in search results
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ChannelRenderer {
|
|
||||||
pub channel_id: String,
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
pub title: String,
|
|
||||||
pub thumbnail: Thumbnails,
|
|
||||||
/// Abbreviated channel description
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
pub description_snippet: String,
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
pub video_count_text: String,
|
|
||||||
#[serde_as(as = "Option<Text>")]
|
|
||||||
pub subscriber_count_text: Option<String>,
|
|
||||||
/// Channel verification badge
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
|
||||||
pub owner_badges: Vec<ChannelBadge>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ChannelThumbnailSupportedRenderers {
|
|
||||||
pub channel_thumbnail_with_link_renderer: ChannelThumbnailWithLinkRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ChannelThumbnailWithLinkRenderer {
|
|
||||||
pub thumbnail: Thumbnails,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ChildVideoRendererWrap {
|
|
||||||
pub child_video_renderer: ChildVideoRenderer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct ChildVideoRenderer {
|
|
||||||
pub video_id: String,
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
pub title: String,
|
|
||||||
#[serde_as(as = "Option<Text>")]
|
|
||||||
pub length_text: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[serde_as]
|
|
||||||
#[derive(Debug, Deserialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct DetailedMetadataSnippet {
|
|
||||||
#[serde_as(as = "Text")]
|
|
||||||
pub snippet_text: String,
|
|
||||||
}
|
|
|
@ -4,10 +4,12 @@ use serde::Deserialize;
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
use serde_with::{DefaultOnError, VecSkipError};
|
use serde_with::{DefaultOnError, VecSkipError};
|
||||||
|
|
||||||
|
use crate::serializer::text::TextComponents;
|
||||||
|
use crate::serializer::MapResult;
|
||||||
use crate::serializer::{
|
use crate::serializer::{
|
||||||
ignore_any,
|
ignore_any,
|
||||||
text::{AccessibilityText, AttributedText, Text, TextComponents},
|
text::{AccessibilityText, AttributedText, Text},
|
||||||
MapResult, VecLogError,
|
VecLogError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -20,26 +22,26 @@ use super::{
|
||||||
|
|
||||||
/// Video details response
|
/// Video details response
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoDetails {
|
pub struct VideoDetails {
|
||||||
/// Video metadata + recommended videos
|
/// Video metadata + recommended videos
|
||||||
pub contents: Contents,
|
pub contents: Contents,
|
||||||
/// Video ID
|
/// Video ID
|
||||||
pub current_video_endpoint: CurrentVideoEndpoint,
|
pub current_video_endpoint: CurrentVideoEndpoint,
|
||||||
/// Video chapters + comment section
|
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
#[serde_as(as = "VecLogError<_>")]
|
||||||
|
/// Video chapters + comment section
|
||||||
pub engagement_panels: MapResult<Vec<EngagementPanel>>,
|
pub engagement_panels: MapResult<Vec<EngagementPanel>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Video details main object, contains video metadata and recommended videos
|
/// Video details main object, contains video metadata and recommended videos
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Contents {
|
pub struct Contents {
|
||||||
pub two_column_watch_next_results: TwoColumnWatchNextResults,
|
pub two_column_watch_next_results: TwoColumnWatchNextResults,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TwoColumnWatchNextResults {
|
pub struct TwoColumnWatchNextResults {
|
||||||
/// Metadata about the video
|
/// Metadata about the video
|
||||||
|
@ -51,7 +53,7 @@ pub struct TwoColumnWatchNextResults {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Metadata about the video
|
/// Metadata about the video
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoResultsWrap {
|
pub struct VideoResultsWrap {
|
||||||
pub results: VideoResults,
|
pub results: VideoResults,
|
||||||
|
@ -59,7 +61,7 @@ pub struct VideoResultsWrap {
|
||||||
|
|
||||||
/// Video metadata items
|
/// Video metadata items
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoResults {
|
pub struct VideoResults {
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
#[serde_as(as = "VecLogError<_>")]
|
||||||
|
@ -68,7 +70,7 @@ pub struct VideoResults {
|
||||||
|
|
||||||
/// Video metadata item
|
/// Video metadata item
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum VideoResultsItem {
|
pub enum VideoResultsItem {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -102,14 +104,14 @@ pub enum VideoResultsItem {
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ViewCount {
|
pub struct ViewCount {
|
||||||
pub video_view_count_renderer: ViewCountRenderer,
|
pub video_view_count_renderer: ViewCountRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ViewCountRenderer {
|
pub struct ViewCountRenderer {
|
||||||
/// View count (`232,975,196 views`)
|
/// View count (`232,975,196 views`)
|
||||||
|
@ -120,7 +122,7 @@ pub struct ViewCountRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like/Dislike buttons
|
/// Like/Dislike buttons
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoActions {
|
pub struct VideoActions {
|
||||||
pub menu_renderer: VideoActionsMenu,
|
pub menu_renderer: VideoActionsMenu,
|
||||||
|
@ -128,7 +130,7 @@ pub struct VideoActions {
|
||||||
|
|
||||||
/// Like/Dislike buttons
|
/// Like/Dislike buttons
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoActionsMenu {
|
pub struct VideoActionsMenu {
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
|
@ -141,7 +143,7 @@ pub struct VideoActionsMenu {
|
||||||
///
|
///
|
||||||
/// See: https://github.com/TeamNewPipe/NewPipeExtractor/pull/926
|
/// See: https://github.com/TeamNewPipe/NewPipeExtractor/pull/926
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum TopLevelButton {
|
pub enum TopLevelButton {
|
||||||
ToggleButtonRenderer(ToggleButton),
|
ToggleButtonRenderer(ToggleButton),
|
||||||
|
@ -152,7 +154,7 @@ pub enum TopLevelButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Like/Dislike button
|
/// Like/Dislike button
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ToggleButtonWrap {
|
pub struct ToggleButtonWrap {
|
||||||
pub toggle_button_renderer: ToggleButton,
|
pub toggle_button_renderer: ToggleButton,
|
||||||
|
@ -160,7 +162,7 @@ pub struct ToggleButtonWrap {
|
||||||
|
|
||||||
/// Like/Dislike button
|
/// Like/Dislike button
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ToggleButton {
|
pub struct ToggleButton {
|
||||||
/// Icon type: `LIKE` / `DISLIKE`
|
/// Icon type: `LIKE` / `DISLIKE`
|
||||||
|
@ -174,7 +176,7 @@ pub struct ToggleButton {
|
||||||
|
|
||||||
/// Shows additional video metadata. Its only known use is for
|
/// Shows additional video metadata. Its only known use is for
|
||||||
/// the Creative Commonse License.
|
/// the Creative Commonse License.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MetadataRowContainer {
|
pub struct MetadataRowContainer {
|
||||||
pub metadata_row_container_renderer: MetadataRowContainerRenderer,
|
pub metadata_row_container_renderer: MetadataRowContainerRenderer,
|
||||||
|
@ -182,14 +184,14 @@ pub struct MetadataRowContainer {
|
||||||
|
|
||||||
/// Shows additional video metadata. Its only known use is for
|
/// Shows additional video metadata. Its only known use is for
|
||||||
/// the Creative Commonse License.
|
/// the Creative Commonse License.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MetadataRowContainerRenderer {
|
pub struct MetadataRowContainerRenderer {
|
||||||
pub rows: Vec<MetadataRow>,
|
pub rows: Vec<MetadataRow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional video metadata item (Creative Commons License)
|
/// Additional video metadata item (Creative Commons License)
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MetadataRow {
|
pub struct MetadataRow {
|
||||||
pub metadata_row_renderer: MetadataRowRenderer,
|
pub metadata_row_renderer: MetadataRowRenderer,
|
||||||
|
@ -197,7 +199,7 @@ pub struct MetadataRow {
|
||||||
|
|
||||||
/// Additional video metadata item (Creative Commons License)
|
/// Additional video metadata item (Creative Commons License)
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MetadataRowRenderer {
|
pub struct MetadataRowRenderer {
|
||||||
// `License`
|
// `License`
|
||||||
|
@ -212,13 +214,13 @@ pub struct MetadataRowRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains current video ID
|
/// Contains current video ID
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CurrentVideoEndpoint {
|
pub struct CurrentVideoEndpoint {
|
||||||
pub watch_endpoint: CurrentVideoWatchEndpoint,
|
pub watch_endpoint: CurrentVideoWatchEndpoint,
|
||||||
}
|
}
|
||||||
/// Contains current video ID
|
/// Contains current video ID
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CurrentVideoWatchEndpoint {
|
pub struct CurrentVideoWatchEndpoint {
|
||||||
pub video_id: String,
|
pub video_id: String,
|
||||||
|
@ -229,7 +231,7 @@ pub struct CurrentVideoWatchEndpoint {
|
||||||
/// 1. CommentsEntryPointHeaderRenderer: contains number of comments
|
/// 1. CommentsEntryPointHeaderRenderer: contains number of comments
|
||||||
/// 2. ContinuationItemRenderer: contains continuation token
|
/// 2. ContinuationItemRenderer: contains continuation token
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case", tag = "sectionIdentifier")]
|
#[serde(rename_all = "kebab-case", tag = "sectionIdentifier")]
|
||||||
pub enum ItemSection {
|
pub enum ItemSection {
|
||||||
CommentsEntryPoint {
|
CommentsEntryPoint {
|
||||||
|
@ -245,7 +247,7 @@ pub enum ItemSection {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Item section containing comment count
|
/// Item section containing comment count
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ItemSectionCommentCount {
|
pub struct ItemSectionCommentCount {
|
||||||
pub comments_entry_point_header_renderer: CommentsEntryPointHeaderRenderer,
|
pub comments_entry_point_header_renderer: CommentsEntryPointHeaderRenderer,
|
||||||
|
@ -253,7 +255,7 @@ pub struct ItemSectionCommentCount {
|
||||||
|
|
||||||
/// Renderer of item section containing comment count
|
/// Renderer of item section containing comment count
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentsEntryPointHeaderRenderer {
|
pub struct CommentsEntryPointHeaderRenderer {
|
||||||
#[serde_as(as = "Text")]
|
#[serde_as(as = "Text")]
|
||||||
|
@ -261,14 +263,14 @@ pub struct CommentsEntryPointHeaderRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Item section containing comments ctoken
|
/// Item section containing comments ctoken
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ItemSectionComments {
|
pub struct ItemSectionComments {
|
||||||
pub continuation_item_renderer: ContinuationItemRenderer,
|
pub continuation_item_renderer: ContinuationItemRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Video recommendations
|
/// Video recommendations
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RecommendationResultsWrap {
|
pub struct RecommendationResultsWrap {
|
||||||
pub secondary_results: RecommendationResults,
|
pub secondary_results: RecommendationResults,
|
||||||
|
@ -276,7 +278,7 @@ pub struct RecommendationResultsWrap {
|
||||||
|
|
||||||
/// Video recommendations
|
/// Video recommendations
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RecommendationResults {
|
pub struct RecommendationResults {
|
||||||
/// Can be `None` for age-restricted videos
|
/// Can be `None` for age-restricted videos
|
||||||
|
@ -286,7 +288,7 @@ pub struct RecommendationResults {
|
||||||
|
|
||||||
/// The engagement panels are displayed below the video and contain chapter markers
|
/// The engagement panels are displayed below the video and contain chapter markers
|
||||||
/// and the comment section.
|
/// and the comment section.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct EngagementPanel {
|
pub struct EngagementPanel {
|
||||||
pub engagement_panel_section_list_renderer: EngagementPanelRenderer,
|
pub engagement_panel_section_list_renderer: EngagementPanelRenderer,
|
||||||
|
@ -294,7 +296,7 @@ pub struct EngagementPanel {
|
||||||
|
|
||||||
/// The engagement panels are displayed below the video and contain chapter markers
|
/// The engagement panels are displayed below the video and contain chapter markers
|
||||||
/// and the comment section.
|
/// and the comment section.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case", tag = "targetId")]
|
#[serde(rename_all = "kebab-case", tag = "targetId")]
|
||||||
pub enum EngagementPanelRenderer {
|
pub enum EngagementPanelRenderer {
|
||||||
/// Chapter markers
|
/// Chapter markers
|
||||||
|
@ -313,7 +315,7 @@ pub enum EngagementPanelRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chapter markers
|
/// Chapter markers
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChapterMarkersContent {
|
pub struct ChapterMarkersContent {
|
||||||
pub macro_markers_list_renderer: MacroMarkersListRenderer,
|
pub macro_markers_list_renderer: MacroMarkersListRenderer,
|
||||||
|
@ -321,7 +323,7 @@ pub struct ChapterMarkersContent {
|
||||||
|
|
||||||
/// Chapter markers
|
/// Chapter markers
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MacroMarkersListRenderer {
|
pub struct MacroMarkersListRenderer {
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
#[serde_as(as = "VecLogError<_>")]
|
||||||
|
@ -329,7 +331,7 @@ pub struct MacroMarkersListRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chapter marker
|
/// Chapter marker
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MacroMarkersListItem {
|
pub struct MacroMarkersListItem {
|
||||||
pub macro_markers_list_item_renderer: MacroMarkersListItemRenderer,
|
pub macro_markers_list_item_renderer: MacroMarkersListItemRenderer,
|
||||||
|
@ -337,7 +339,7 @@ pub struct MacroMarkersListItem {
|
||||||
|
|
||||||
/// Chapter marker
|
/// Chapter marker
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MacroMarkersListItemRenderer {
|
pub struct MacroMarkersListItemRenderer {
|
||||||
/// Contains chapter start time in seconds
|
/// Contains chapter start time in seconds
|
||||||
|
@ -350,13 +352,13 @@ pub struct MacroMarkersListItemRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Contains chapter start time in seconds
|
/// Contains chapter start time in seconds
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MacroMarkersListItemOnTap {
|
pub struct MacroMarkersListItemOnTap {
|
||||||
pub watch_endpoint: MacroMarkersListItemWatchEndpoint,
|
pub watch_endpoint: MacroMarkersListItemWatchEndpoint,
|
||||||
}
|
}
|
||||||
/// Contains chapter start time in seconds
|
/// Contains chapter start time in seconds
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MacroMarkersListItemWatchEndpoint {
|
pub struct MacroMarkersListItemWatchEndpoint {
|
||||||
/// Chapter start time in seconds
|
/// Chapter start time in seconds
|
||||||
|
@ -365,7 +367,7 @@ pub struct MacroMarkersListItemWatchEndpoint {
|
||||||
|
|
||||||
/// Comment section header
|
/// Comment section header
|
||||||
/// (contains continuation tokens for fetching top/latest comments)
|
/// (contains continuation tokens for fetching top/latest comments)
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentItemSectionHeader {
|
pub struct CommentItemSectionHeader {
|
||||||
pub engagement_panel_title_header_renderer: CommentItemSectionHeaderRenderer,
|
pub engagement_panel_title_header_renderer: CommentItemSectionHeaderRenderer,
|
||||||
|
@ -374,7 +376,7 @@ pub struct CommentItemSectionHeader {
|
||||||
/// Comment section header
|
/// Comment section header
|
||||||
/// (contains continuation tokens for fetching top/latest comments)
|
/// (contains continuation tokens for fetching top/latest comments)
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentItemSectionHeaderRenderer {
|
pub struct CommentItemSectionHeaderRenderer {
|
||||||
/// Approximate comment count (e.g. `81`, `2.2K`, `705K`)
|
/// Approximate comment count (e.g. `81`, `2.2K`, `705K`)
|
||||||
|
@ -388,7 +390,7 @@ pub struct CommentItemSectionHeaderRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Comment section menu
|
/// Comment section menu
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentItemSectionHeaderMenu {
|
pub struct CommentItemSectionHeaderMenu {
|
||||||
pub sort_filter_sub_menu_renderer: CommentItemSectionHeaderMenuRenderer,
|
pub sort_filter_sub_menu_renderer: CommentItemSectionHeaderMenuRenderer,
|
||||||
|
@ -399,14 +401,14 @@ pub struct CommentItemSectionHeaderMenu {
|
||||||
/// Items:
|
/// Items:
|
||||||
/// - Top comments
|
/// - Top comments
|
||||||
/// - Latest comments
|
/// - Latest comments
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentItemSectionHeaderMenuRenderer {
|
pub struct CommentItemSectionHeaderMenuRenderer {
|
||||||
pub sub_menu_items: Vec<CommentItemSectionHeaderMenuItem>,
|
pub sub_menu_items: Vec<CommentItemSectionHeaderMenuItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Comment section menu item
|
/// Comment section menu item
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentItemSectionHeaderMenuItem {
|
pub struct CommentItemSectionHeaderMenuItem {
|
||||||
/// Continuation token for fetching comments
|
/// Continuation token for fetching comments
|
||||||
|
@ -418,14 +420,14 @@ pub struct CommentItemSectionHeaderMenuItem {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// Video recommendations continuation response
|
/// Video recommendations continuation response
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoRecommendations {
|
pub struct VideoRecommendations {
|
||||||
pub on_response_received_endpoints: Vec<RecommendationsContItem>,
|
pub on_response_received_endpoints: Vec<RecommendationsContItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Video recommendations continuation
|
/// Video recommendations continuation
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RecommendationsContItem {
|
pub struct RecommendationsContItem {
|
||||||
pub append_continuation_items_action: AppendRecommendations,
|
pub append_continuation_items_action: AppendRecommendations,
|
||||||
|
@ -433,7 +435,7 @@ pub struct RecommendationsContItem {
|
||||||
|
|
||||||
/// Video recommendations continuation
|
/// Video recommendations continuation
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AppendRecommendations {
|
pub struct AppendRecommendations {
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
#[serde_as(as = "VecLogError<_>")]
|
||||||
|
@ -446,7 +448,7 @@ pub struct AppendRecommendations {
|
||||||
|
|
||||||
/// Video comments continuation response
|
/// Video comments continuation response
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct VideoComments {
|
pub struct VideoComments {
|
||||||
/// - Initial response: 2*reloadContinuationItemsCommand
|
/// - Initial response: 2*reloadContinuationItemsCommand
|
||||||
|
@ -464,7 +466,7 @@ pub struct VideoComments {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Video comments continuation
|
/// Video comments continuation
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentsContItem {
|
pub struct CommentsContItem {
|
||||||
#[serde(alias = "reloadContinuationItemsCommand")]
|
#[serde(alias = "reloadContinuationItemsCommand")]
|
||||||
|
@ -473,7 +475,7 @@ pub struct CommentsContItem {
|
||||||
|
|
||||||
/// Video comments continuation action
|
/// Video comments continuation action
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AppendComments {
|
pub struct AppendComments {
|
||||||
#[serde_as(as = "VecLogError<_>")]
|
#[serde_as(as = "VecLogError<_>")]
|
||||||
|
@ -481,7 +483,7 @@ pub struct AppendComments {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum CommentListItem {
|
pub enum CommentListItem {
|
||||||
/// Top-level comment
|
/// Top-level comment
|
||||||
|
@ -511,14 +513,14 @@ pub enum CommentListItem {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
pub comment_renderer: CommentRenderer,
|
pub comment_renderer: CommentRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentRenderer {
|
pub struct CommentRenderer {
|
||||||
/// Author name
|
/// Author name
|
||||||
|
@ -544,24 +546,24 @@ pub struct CommentRenderer {
|
||||||
// pub vote_count: Option<String>,
|
// pub vote_count: Option<String>,
|
||||||
pub author_comment_badge: Option<AuthorCommentBadge>,
|
pub author_comment_badge: Option<AuthorCommentBadge>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub reply_count: u64,
|
pub reply_count: u32,
|
||||||
/// Buttons for comment interaction (Like/Dislike/Reply)
|
/// Buttons for comment interaction (Like/Dislike/Reply)
|
||||||
pub action_buttons: CommentActionButtons,
|
pub action_buttons: CommentActionButtons,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AuthorEndpoint {
|
pub struct AuthorEndpoint {
|
||||||
pub browse_endpoint: BrowseEndpoint,
|
pub browse_endpoint: BrowseEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BrowseEndpoint {
|
pub struct BrowseEndpoint {
|
||||||
pub browse_id: String,
|
pub browse_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
|
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
pub enum CommentPriority {
|
pub enum CommentPriority {
|
||||||
/// Default rendering priority
|
/// Default rendering priority
|
||||||
|
@ -573,7 +575,7 @@ pub enum CommentPriority {
|
||||||
|
|
||||||
/// Does not contain replies directly but a continuation token
|
/// Does not contain replies directly but a continuation token
|
||||||
/// for fetching them.
|
/// for fetching them.
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Replies {
|
pub struct Replies {
|
||||||
pub comment_replies_renderer: RepliesRenderer,
|
pub comment_replies_renderer: RepliesRenderer,
|
||||||
|
@ -582,7 +584,7 @@ pub struct Replies {
|
||||||
/// Does not contain replies directly but a continuation token
|
/// Does not contain replies directly but a continuation token
|
||||||
/// for fetching them.
|
/// for fetching them.
|
||||||
#[serde_as]
|
#[serde_as]
|
||||||
#[derive(Default, Debug, Deserialize)]
|
#[derive(Default, Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RepliesRenderer {
|
pub struct RepliesRenderer {
|
||||||
#[serde_as(as = "VecSkipError<_>")]
|
#[serde_as(as = "VecSkipError<_>")]
|
||||||
|
@ -591,7 +593,7 @@ pub struct RepliesRenderer {
|
||||||
|
|
||||||
/// These are the buttons for comment interaction (Like/Dislike/Reply).
|
/// These are the buttons for comment interaction (Like/Dislike/Reply).
|
||||||
/// Contains the CreatorHeart.
|
/// Contains the CreatorHeart.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentActionButtons {
|
pub struct CommentActionButtons {
|
||||||
pub comment_action_buttons_renderer: CommentActionButtonsRenderer,
|
pub comment_action_buttons_renderer: CommentActionButtonsRenderer,
|
||||||
|
@ -599,7 +601,7 @@ pub struct CommentActionButtons {
|
||||||
|
|
||||||
/// These are the buttons for comment interaction (Like/Dislike/Reply).
|
/// These are the buttons for comment interaction (Like/Dislike/Reply).
|
||||||
/// Contains the CreatorHeart.
|
/// Contains the CreatorHeart.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CommentActionButtonsRenderer {
|
pub struct CommentActionButtonsRenderer {
|
||||||
pub like_button: ToggleButtonWrap,
|
pub like_button: ToggleButtonWrap,
|
||||||
|
@ -607,27 +609,27 @@ pub struct CommentActionButtonsRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Video creators can endorse comments by marking them with a ❤️.
|
/// Video creators can endorse comments by marking them with a ❤️.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CreatorHeart {
|
pub struct CreatorHeart {
|
||||||
pub creator_heart_renderer: CreatorHeartRenderer,
|
pub creator_heart_renderer: CreatorHeartRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Video creators can endorse comments by marking them with a ❤️.
|
/// Video creators can endorse comments by marking them with a ❤️.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CreatorHeartRenderer {
|
pub struct CreatorHeartRenderer {
|
||||||
pub is_hearted: bool,
|
pub is_hearted: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AuthorCommentBadge {
|
pub struct AuthorCommentBadge {
|
||||||
pub author_comment_badge_renderer: AuthorCommentBadgeRenderer,
|
pub author_comment_badge_renderer: AuthorCommentBadgeRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// YouTube channel badge (verified) of the comment author
|
/// YouTube channel badge (verified) of the comment author
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AuthorCommentBadgeRenderer {
|
pub struct AuthorCommentBadgeRenderer {
|
||||||
/// Verified: `CHECK`
|
/// Verified: `CHECK`
|
||||||
|
|
|
@ -1,265 +0,0 @@
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
deobfuscate::Deobfuscator,
|
|
||||||
error::{Error, ExtractionError},
|
|
||||||
model::{
|
|
||||||
ChannelId, ChannelTag, Language, Paginator, SearchChannel, SearchItem, SearchPlaylist,
|
|
||||||
SearchPlaylistVideo, SearchResult, SearchVideo,
|
|
||||||
},
|
|
||||||
timeago,
|
|
||||||
util::{self, TryRemove},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
response::{self, IsLive, IsShort},
|
|
||||||
ClientType, MapResponse, MapResult, QContinuation, RustyPipeQuery, YTContext,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct QSearch<'a> {
|
|
||||||
context: YTContext,
|
|
||||||
query: &'a str,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
params: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RustyPipeQuery {
|
|
||||||
pub async fn search(self, query: &str) -> Result<SearchResult, Error> {
|
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
|
||||||
let request_body = QSearch {
|
|
||||||
context,
|
|
||||||
query,
|
|
||||||
params: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.execute_request::<response::Search, _, _>(
|
|
||||||
ClientType::Desktop,
|
|
||||||
"search",
|
|
||||||
query,
|
|
||||||
"search",
|
|
||||||
&request_body,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn search_continuation(self, ctoken: &str) -> Result<Paginator<SearchItem>, Error> {
|
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
|
||||||
let request_body = QContinuation {
|
|
||||||
context,
|
|
||||||
continuation: ctoken,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.execute_request::<response::SearchCont, _, _>(
|
|
||||||
ClientType::Desktop,
|
|
||||||
"search",
|
|
||||||
ctoken,
|
|
||||||
"search",
|
|
||||||
&request_body,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapResponse<SearchResult> for response::Search {
|
|
||||||
fn map_response(
|
|
||||||
self,
|
|
||||||
_id: &str,
|
|
||||||
lang: Language,
|
|
||||||
_deobf: Option<&Deobfuscator>,
|
|
||||||
) -> Result<MapResult<SearchResult>, ExtractionError> {
|
|
||||||
let section_list_items = self
|
|
||||||
.contents
|
|
||||||
.two_column_search_results_renderer
|
|
||||||
.primary_contents
|
|
||||||
.section_list_renderer
|
|
||||||
.contents;
|
|
||||||
|
|
||||||
let (items, ctoken) = map_section_list_items(section_list_items)?;
|
|
||||||
|
|
||||||
let mut warnings = items.warnings;
|
|
||||||
let (mut mapped, corrected_query) = map_search_items(items.c, lang);
|
|
||||||
warnings.append(&mut mapped.warnings);
|
|
||||||
|
|
||||||
Ok(MapResult {
|
|
||||||
c: SearchResult {
|
|
||||||
items: Paginator::new(self.estimated_results, mapped.c, ctoken),
|
|
||||||
corrected_query,
|
|
||||||
},
|
|
||||||
warnings,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MapResponse<Paginator<SearchItem>> for response::SearchCont {
|
|
||||||
fn map_response(
|
|
||||||
self,
|
|
||||||
_id: &str,
|
|
||||||
lang: Language,
|
|
||||||
_deobf: Option<&Deobfuscator>,
|
|
||||||
) -> Result<MapResult<Paginator<SearchItem>>, ExtractionError> {
|
|
||||||
let mut commands = self.on_response_received_commands;
|
|
||||||
let cont_command = some_or_bail!(
|
|
||||||
commands.try_swap_remove(0),
|
|
||||||
Err(ExtractionError::InvalidData(
|
|
||||||
"no item section renderer".into()
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
let (items, ctoken) = map_section_list_items(
|
|
||||||
cont_command
|
|
||||||
.append_continuation_items_action
|
|
||||||
.continuation_items,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut warnings = items.warnings;
|
|
||||||
let (mut mapped, _) = map_search_items(items.c, lang);
|
|
||||||
warnings.append(&mut mapped.warnings);
|
|
||||||
|
|
||||||
Ok(MapResult {
|
|
||||||
c: Paginator::new(self.estimated_results, mapped.c, ctoken),
|
|
||||||
warnings,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_section_list_items(
|
|
||||||
section_list_items: Vec<response::search::SectionListItem>,
|
|
||||||
) -> Result<(MapResult<Vec<response::search::SearchItem>>, Option<String>), ExtractionError> {
|
|
||||||
let mut items = None;
|
|
||||||
let mut ctoken = None;
|
|
||||||
section_list_items.into_iter().for_each(|item| match item {
|
|
||||||
response::search::SectionListItem::ItemSectionRenderer { contents } => {
|
|
||||||
items = Some(contents);
|
|
||||||
}
|
|
||||||
response::search::SectionListItem::ContinuationItemRenderer {
|
|
||||||
continuation_endpoint,
|
|
||||||
} => {
|
|
||||||
ctoken = Some(continuation_endpoint.continuation_command.token);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let items = some_or_bail!(
|
|
||||||
items,
|
|
||||||
Err(ExtractionError::InvalidData(
|
|
||||||
"no item section renderer".into()
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok((items, ctoken))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_search_items(
|
|
||||||
items: Vec<response::search::SearchItem>,
|
|
||||||
lang: Language,
|
|
||||||
) -> (MapResult<Vec<SearchItem>>, Option<String>) {
|
|
||||||
let mut warnings = Vec::new();
|
|
||||||
|
|
||||||
let mut c_query = None;
|
|
||||||
let mapped_items = items
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|item| match item {
|
|
||||||
response::search::SearchItem::VideoRenderer(mut video) => {
|
|
||||||
match ChannelId::try_from(video.channel) {
|
|
||||||
Ok(channel) => Some(SearchItem::Video(SearchVideo {
|
|
||||||
id: video.video_id,
|
|
||||||
title: video.title,
|
|
||||||
length: video
|
|
||||||
.length_text
|
|
||||||
.and_then(|txt| util::parse_video_length_or_warn(&txt, &mut warnings)),
|
|
||||||
thumbnail: video.thumbnail.into(),
|
|
||||||
channel: ChannelTag {
|
|
||||||
id: channel.id,
|
|
||||||
name: channel.name,
|
|
||||||
avatar: video
|
|
||||||
.channel_thumbnail_supported_renderers
|
|
||||||
.channel_thumbnail_with_link_renderer
|
|
||||||
.thumbnail
|
|
||||||
.into(),
|
|
||||||
verification: video.owner_badges.into(),
|
|
||||||
subscriber_count: None,
|
|
||||||
},
|
|
||||||
publish_date: video.published_time_text.as_ref().and_then(|txt| {
|
|
||||||
timeago::parse_timeago_or_warn(lang, txt, &mut warnings)
|
|
||||||
}),
|
|
||||||
publish_date_txt: video.published_time_text,
|
|
||||||
view_count: video
|
|
||||||
.view_count_text
|
|
||||||
.and_then(|txt| util::parse_numeric_or_warn(&txt, &mut warnings)),
|
|
||||||
is_live: video.thumbnail_overlays.is_live(),
|
|
||||||
is_short: video.thumbnail_overlays.is_short(),
|
|
||||||
short_description: video
|
|
||||||
.detailed_metadata_snippets
|
|
||||||
.try_swap_remove(0)
|
|
||||||
.map(|s| s.snippet_text)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
})),
|
|
||||||
Err(e) => {
|
|
||||||
warnings.push(e.to_string());
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
response::search::SearchItem::PlaylistRenderer(mut playlist) => {
|
|
||||||
Some(SearchItem::Playlist(SearchPlaylist {
|
|
||||||
id: playlist.playlist_id,
|
|
||||||
name: playlist.title,
|
|
||||||
thumbnail: playlist
|
|
||||||
.thumbnails
|
|
||||||
.try_swap_remove(0)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.into(),
|
|
||||||
video_count: playlist.video_count,
|
|
||||||
first_videos: playlist
|
|
||||||
.videos
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| SearchPlaylistVideo {
|
|
||||||
id: v.child_video_renderer.video_id,
|
|
||||||
title: v.child_video_renderer.title,
|
|
||||||
length: v.child_video_renderer.length_text.and_then(|txt| {
|
|
||||||
util::parse_video_length_or_warn(&txt, &mut warnings)
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
response::search::SearchItem::ChannelRenderer(channel) => {
|
|
||||||
Some(SearchItem::Channel(SearchChannel {
|
|
||||||
id: channel.channel_id,
|
|
||||||
name: channel.title,
|
|
||||||
avatar: channel.thumbnail.into(),
|
|
||||||
verification: channel.owner_badges.into(),
|
|
||||||
subscriber_count: channel
|
|
||||||
.subscriber_count_text
|
|
||||||
.and_then(|txt| util::parse_numeric_or_warn(&txt, &mut warnings)),
|
|
||||||
short_description: channel.description_snippet,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
response::search::SearchItem::ShowingResultsForRenderer { corrected_query } => {
|
|
||||||
c_query = Some(corrected_query);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
response::search::SearchItem::None => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
(
|
|
||||||
MapResult {
|
|
||||||
c: mapped_items,
|
|
||||||
warnings,
|
|
||||||
},
|
|
||||||
c_query,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::client::RustyPipe;
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn t1() {
|
|
||||||
let rp = RustyPipe::builder().strict().build();
|
|
||||||
let result = rp.query().search("doobydoobap").await.unwrap();
|
|
||||||
dbg!(&result);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,10 +19,10 @@ use super::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
struct QVideo<'a> {
|
struct QVideo {
|
||||||
context: YTContext,
|
context: YTContext,
|
||||||
/// YouTube video ID
|
/// YouTube video ID
|
||||||
video_id: &'a str,
|
video_id: String,
|
||||||
/// Set to true to allow extraction of streams with sensitive content
|
/// Set to true to allow extraction of streams with sensitive content
|
||||||
content_check_ok: bool,
|
content_check_ok: bool,
|
||||||
/// Probably refers to allowing sensitive content, too
|
/// Probably refers to allowing sensitive content, too
|
||||||
|
@ -34,7 +34,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QVideo {
|
let request_body = QVideo {
|
||||||
context,
|
context,
|
||||||
video_id,
|
video_id: video_id.to_owned(),
|
||||||
content_check_ok: true,
|
content_check_ok: true,
|
||||||
racy_check_ok: true,
|
racy_check_ok: true,
|
||||||
};
|
};
|
||||||
|
@ -56,7 +56,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
context,
|
||||||
continuation: ctoken,
|
continuation: ctoken.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_request::<response::VideoRecommendations, _, _>(
|
self.execute_request::<response::VideoRecommendations, _, _>(
|
||||||
|
@ -73,7 +73,7 @@ impl RustyPipeQuery {
|
||||||
let context = self.get_context(ClientType::Desktop, true).await;
|
let context = self.get_context(ClientType::Desktop, true).await;
|
||||||
let request_body = QContinuation {
|
let request_body = QContinuation {
|
||||||
context,
|
context,
|
||||||
continuation: ctoken,
|
continuation: ctoken.to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.execute_request::<response::VideoComments, _, _>(
|
self.execute_request::<response::VideoComments, _, _>(
|
||||||
|
@ -181,7 +181,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
|
||||||
};
|
};
|
||||||
|
|
||||||
let comment_count = comment_count_section.and_then(|s| {
|
let comment_count = comment_count_section.and_then(|s| {
|
||||||
util::parse_large_numstr::<u64>(
|
util::parse_large_numstr::<u32>(
|
||||||
&s.comments_entry_point_header_renderer.comment_count,
|
&s.comments_entry_point_header_renderer.comment_count,
|
||||||
lang,
|
lang,
|
||||||
)
|
)
|
||||||
|
@ -411,7 +411,7 @@ impl MapResponse<Paginator<Comment>> for response::VideoComments {
|
||||||
count_text,
|
count_text,
|
||||||
} => {
|
} => {
|
||||||
comment_count = count_text.and_then(|txt| {
|
comment_count = count_text.and_then(|txt| {
|
||||||
util::parse_numeric_or_warn::<u64>(&txt, &mut warnings)
|
util::parse_numeric_or_warn::<u32>(&txt, &mut warnings)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -56,12 +56,22 @@ fn parse_cr_header(cr_header: &str) -> Result<(u64, u64)> {
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
captures.get(2).unwrap().as_str().parse().map_err(|_| {
|
captures
|
||||||
DownloadError::Progressive("could not parse range header number".into())
|
.get(2)
|
||||||
})?,
|
.unwrap()
|
||||||
captures.get(3).unwrap().as_str().parse().map_err(|_| {
|
.as_str()
|
||||||
DownloadError::Progressive("could not parse range header number".into())
|
.parse()
|
||||||
})?,
|
.or(Err(DownloadError::Progressive(
|
||||||
|
"could not parse range header number".into(),
|
||||||
|
)))?,
|
||||||
|
captures
|
||||||
|
.get(3)
|
||||||
|
.unwrap()
|
||||||
|
.as_str()
|
||||||
|
.parse()
|
||||||
|
.or(Err(DownloadError::Progressive(
|
||||||
|
"could not parse range header number".into(),
|
||||||
|
)))?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +96,7 @@ async fn download_single_file<P: Into<PathBuf>>(
|
||||||
|
|
||||||
// If the url is from googlevideo, extract file size from clen parameter
|
// If the url is from googlevideo, extract file size from clen parameter
|
||||||
let (url_base, url_params) =
|
let (url_base, url_params) =
|
||||||
util::url_to_params(url).map_err(|e| DownloadError::Other(e.to_string().into()))?;
|
util::url_to_params(url).or_else(|e| Err(DownloadError::Other(e.to_string().into())))?;
|
||||||
let is_gvideo = url_base.ends_with(".googlevideo.com/videoplayback");
|
let is_gvideo = url_base.ends_with(".googlevideo.com/videoplayback");
|
||||||
if is_gvideo {
|
if is_gvideo {
|
||||||
size = url_params.get("clen").and_then(|s| s.parse::<u64>().ok());
|
size = url_params.get("clen").and_then(|s| s.parse::<u64>().ok());
|
||||||
|
@ -110,9 +120,9 @@ async fn download_single_file<P: Into<PathBuf>>(
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(|_| {
|
.or(Err(DownloadError::Progressive(
|
||||||
DownloadError::Progressive("could not convert Content-Range header to string".into())
|
"could not convert Content-Range header to string".into(),
|
||||||
})?;
|
)))?;
|
||||||
|
|
||||||
let (_, original_size) = parse_cr_header(cr_header)?;
|
let (_, original_size) = parse_cr_header(cr_header)?;
|
||||||
|
|
||||||
|
@ -197,9 +207,9 @@ async fn download_chunks_by_header(
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
.to_str()
|
.to_str()
|
||||||
.map_err(|_| {
|
.or(Err(DownloadError::Progressive(
|
||||||
DownloadError::Progressive("could not convert Content-Range header to string".into())
|
"could not convert Content-Range header to string".into(),
|
||||||
})?;
|
)))?;
|
||||||
|
|
||||||
let (parsed_offset, parsed_size) = parse_cr_header(cr_header)?;
|
let (parsed_offset, parsed_size) = parse_cr_header(cr_header)?;
|
||||||
|
|
||||||
|
|
|
@ -81,8 +81,8 @@ pub enum ExtractionError {
|
||||||
InvalidData(Cow<'static, str>),
|
InvalidData(Cow<'static, str>),
|
||||||
#[error("got wrong result from YT: {0}")]
|
#[error("got wrong result from YT: {0}")]
|
||||||
WrongResult(String),
|
WrongResult(String),
|
||||||
#[error("Warnings during deserialization/mapping in strict mode")]
|
#[error("Warnings during deserialization/mapping")]
|
||||||
DeserializationWarnings,
|
Warnings,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal error
|
/// Internal error
|
||||||
|
|
127
src/model/mod.rs
127
src/model/mod.rs
|
@ -393,7 +393,7 @@ pub struct Playlist {
|
||||||
/// Playlist videos
|
/// Playlist videos
|
||||||
pub videos: Paginator<PlaylistVideo>,
|
pub videos: Paginator<PlaylistVideo>,
|
||||||
/// Number of videos in the playlist
|
/// Number of videos in the playlist
|
||||||
pub video_count: u64,
|
pub video_count: u32,
|
||||||
/// Playlist thumbnail
|
/// Playlist thumbnail
|
||||||
pub thumbnail: Vec<Thumbnail>,
|
pub thumbnail: Vec<Thumbnail>,
|
||||||
/// Playlist description in plaintext format
|
/// Playlist description in plaintext format
|
||||||
|
@ -453,11 +453,11 @@ pub struct VideoDetails {
|
||||||
pub view_count: u64,
|
pub view_count: u64,
|
||||||
/// Number of likes
|
/// Number of likes
|
||||||
///
|
///
|
||||||
/// [`None`] if the like count was hidden by the creator.
|
/// `None` if the like count was hidden by the creator.
|
||||||
pub like_count: Option<u32>,
|
pub like_count: Option<u32>,
|
||||||
/// Video publishing date. Start date in case of a livestream.
|
/// Video publishing date. Start date in case of a livestream.
|
||||||
///
|
///
|
||||||
/// [`None`] if the date could not be parsed.
|
/// `None` if the date could not be parsed.
|
||||||
pub publish_date: Option<DateTime<Local>>,
|
pub publish_date: Option<DateTime<Local>>,
|
||||||
/// Textual video publishing date (e.g. `Aug 2, 2013`, depends on language)
|
/// Textual video publishing date (e.g. `Aug 2, 2013`, depends on language)
|
||||||
pub publish_date_txt: String,
|
pub publish_date_txt: String,
|
||||||
|
@ -516,7 +516,7 @@ pub struct RecommendedVideo {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// Video length in seconds.
|
/// Video length in seconds.
|
||||||
///
|
///
|
||||||
/// Is [`None`] for livestreams.
|
/// Is `None` for livestreams.
|
||||||
pub length: Option<u32>,
|
pub length: Option<u32>,
|
||||||
/// Video thumbnail
|
/// Video thumbnail
|
||||||
pub thumbnail: Vec<Thumbnail>,
|
pub thumbnail: Vec<Thumbnail>,
|
||||||
|
@ -524,15 +524,15 @@ pub struct RecommendedVideo {
|
||||||
pub channel: ChannelTag,
|
pub channel: ChannelTag,
|
||||||
/// Video publishing date.
|
/// Video publishing date.
|
||||||
///
|
///
|
||||||
/// [`None`] if the date could not be parsed.
|
/// `None` if the date could not be parsed.
|
||||||
pub publish_date: Option<DateTime<Local>>,
|
pub publish_date: Option<DateTime<Local>>,
|
||||||
/// Textual video publish date (e.g. `11 months ago`, depends on language)
|
/// Textual video publish date (e.g. `11 months ago`, depends on language)
|
||||||
///
|
///
|
||||||
/// Is [`None`] for livestreams.
|
/// Is `None` for livestreams.
|
||||||
pub publish_date_txt: Option<String>,
|
pub publish_date_txt: Option<String>,
|
||||||
/// View count
|
/// View count
|
||||||
///
|
///
|
||||||
/// [`None`] if it could not be extracted.
|
/// `None` if it could not be extracted.
|
||||||
pub view_count: Option<u64>,
|
pub view_count: Option<u64>,
|
||||||
/// Is the video an active livestream?
|
/// Is the video an active livestream?
|
||||||
pub is_live: bool,
|
pub is_live: bool,
|
||||||
|
@ -554,7 +554,7 @@ pub struct ChannelTag {
|
||||||
pub verification: Verification,
|
pub verification: Verification,
|
||||||
/// Approximate number of subscribers
|
/// Approximate number of subscribers
|
||||||
///
|
///
|
||||||
/// [`None`] if hidden by the owner or not present.
|
/// `None` if hidden by the owner or not present.
|
||||||
///
|
///
|
||||||
/// Info: This is only present in the `VideoDetails` response
|
/// Info: This is only present in the `VideoDetails` response
|
||||||
pub subscriber_count: Option<u64>,
|
pub subscriber_count: Option<u64>,
|
||||||
|
@ -600,14 +600,14 @@ pub struct Comment {
|
||||||
pub author: Option<ChannelTag>,
|
pub author: Option<ChannelTag>,
|
||||||
/// Comment publishing date.
|
/// Comment publishing date.
|
||||||
///
|
///
|
||||||
/// [`None`] if the date could not be parsed.
|
/// `None` if the date could not be parsed.
|
||||||
pub publish_date: Option<DateTime<Local>>,
|
pub publish_date: Option<DateTime<Local>>,
|
||||||
/// Textual comment publish date (e.g. `14 hours ago`), depends on language setting
|
/// Textual comment publish date (e.g. `14 hours ago`), depends on language setting
|
||||||
pub publish_date_txt: String,
|
pub publish_date_txt: String,
|
||||||
/// Number of comment likes
|
/// Number of comment likes
|
||||||
pub like_count: Option<u32>,
|
pub like_count: Option<u32>,
|
||||||
/// Number of replies
|
/// Number of replies
|
||||||
pub reply_count: u64,
|
pub reply_count: u32,
|
||||||
/// Paginator to fetch comment replies
|
/// Paginator to fetch comment replies
|
||||||
pub replies: Paginator<Comment>,
|
pub replies: Paginator<Comment>,
|
||||||
/// Is the comment from the channel owner?
|
/// Is the comment from the channel owner?
|
||||||
|
@ -635,7 +635,7 @@ pub struct Channel<T> {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// Channel subscriber count
|
/// Channel subscriber count
|
||||||
///
|
///
|
||||||
/// [`None`] if the subscriber count was hidden by the owner
|
/// `None` if the subscriber count was hidden by the owner
|
||||||
/// or could not be parsed.
|
/// or could not be parsed.
|
||||||
pub subscriber_count: Option<u64>,
|
pub subscriber_count: Option<u64>,
|
||||||
/// Channel avatar / profile picture
|
/// Channel avatar / profile picture
|
||||||
|
@ -667,22 +667,22 @@ pub struct ChannelVideo {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
/// Video length in seconds.
|
/// Video length in seconds.
|
||||||
///
|
///
|
||||||
/// Is [`None`] for livestreams.
|
/// Is `None` for livestreams.
|
||||||
pub length: Option<u32>,
|
pub length: Option<u32>,
|
||||||
/// Video thumbnail
|
/// Video thumbnail
|
||||||
pub thumbnail: Vec<Thumbnail>,
|
pub thumbnail: Vec<Thumbnail>,
|
||||||
/// Video publishing date.
|
/// Video publishing date.
|
||||||
///
|
///
|
||||||
/// [`None`] if the date could not be parsed.
|
/// `None` if the date could not be parsed.
|
||||||
/// May be in the future for upcoming videos
|
/// May be in the future for upcoming videos
|
||||||
pub publish_date: Option<DateTime<Local>>,
|
pub publish_date: Option<DateTime<Local>>,
|
||||||
/// Textual video publish date (e.g. `11 months ago`, depends on language)
|
/// Textual video publish date (e.g. `11 months ago`, depends on language)
|
||||||
///
|
///
|
||||||
/// Is [`None`] for livestreams and upcoming videos.
|
/// Is `None` for livestreams and upcoming videos.
|
||||||
pub publish_date_txt: Option<String>,
|
pub publish_date_txt: Option<String>,
|
||||||
/// Number of views / current viewers in case of a livestream.
|
/// Number of views / current viewers in case of a livestream.
|
||||||
///
|
///
|
||||||
/// [`None`] if it could not be extracted.
|
/// `None` if it could not be extracted.
|
||||||
pub view_count: Option<u64>,
|
pub view_count: Option<u64>,
|
||||||
/// Is the video an active livestream?
|
/// Is the video an active livestream?
|
||||||
pub is_live: bool,
|
pub is_live: bool,
|
||||||
|
@ -703,7 +703,7 @@ pub struct ChannelPlaylist {
|
||||||
/// Playlist thumbnail
|
/// Playlist thumbnail
|
||||||
pub thumbnail: Vec<Thumbnail>,
|
pub thumbnail: Vec<Thumbnail>,
|
||||||
/// Number of playlist videos
|
/// Number of playlist videos
|
||||||
pub video_count: Option<u64>,
|
pub video_count: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional channel metadata fetched from the "About" tab.
|
/// Additional channel metadata fetched from the "About" tab.
|
||||||
|
@ -753,98 +753,5 @@ pub struct ChannelRssVideo {
|
||||||
/// Number of likes
|
/// Number of likes
|
||||||
///
|
///
|
||||||
/// Zero if the like count was hidden by the creator.
|
/// Zero if the like count was hidden by the creator.
|
||||||
pub like_count: u64,
|
pub like_count: u32,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct SearchResult {
|
|
||||||
pub items: Paginator<SearchItem>,
|
|
||||||
pub corrected_query: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
pub enum SearchItem {
|
|
||||||
Video(SearchVideo),
|
|
||||||
Playlist(SearchPlaylist),
|
|
||||||
Channel(SearchChannel),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// YouTube video from the search results
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct SearchVideo {
|
|
||||||
/// Unique YouTube video ID
|
|
||||||
pub id: String,
|
|
||||||
/// Video title
|
|
||||||
pub title: String,
|
|
||||||
/// Video length in seconds.
|
|
||||||
///
|
|
||||||
/// Is [`None`] for livestreams.
|
|
||||||
pub length: Option<u32>,
|
|
||||||
/// Video thumbnail
|
|
||||||
pub thumbnail: Vec<Thumbnail>,
|
|
||||||
/// Channel of the video
|
|
||||||
pub channel: ChannelTag,
|
|
||||||
/// Video publishing date.
|
|
||||||
///
|
|
||||||
/// [`None`] if the date could not be parsed.
|
|
||||||
pub publish_date: Option<DateTime<Local>>,
|
|
||||||
/// Textual video publish date (e.g. `11 months ago`, depends on language)
|
|
||||||
///
|
|
||||||
/// Is [`None`] for livestreams.
|
|
||||||
pub publish_date_txt: Option<String>,
|
|
||||||
/// View count
|
|
||||||
///
|
|
||||||
/// [`None`] if it could not be extracted.
|
|
||||||
pub view_count: Option<u64>,
|
|
||||||
/// Is the video an active livestream?
|
|
||||||
pub is_live: bool,
|
|
||||||
/// Is the video a YouTube Short video (vertical and <60s)?
|
|
||||||
pub is_short: bool,
|
|
||||||
/// Abbreviated video description
|
|
||||||
pub short_description: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Playlist from the search results
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct SearchPlaylist {
|
|
||||||
/// Unique YouTube Playlist-ID (e.g. `PL5dDx681T4bR7ZF1IuWzOv1omlRbE7PiJ`)
|
|
||||||
pub id: String,
|
|
||||||
/// Playlist name
|
|
||||||
pub name: String,
|
|
||||||
/// Playlist thumbnail
|
|
||||||
pub thumbnail: Vec<Thumbnail>,
|
|
||||||
/// Number of playlist videos
|
|
||||||
pub video_count: u64,
|
|
||||||
/// First 2 videos
|
|
||||||
pub first_videos: Vec<SearchPlaylistVideo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct SearchPlaylistVideo {
|
|
||||||
pub id: String,
|
|
||||||
pub title: String,
|
|
||||||
pub length: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Channel from the search results
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct SearchChannel {
|
|
||||||
/// Unique YouTube channel ID
|
|
||||||
pub id: String,
|
|
||||||
/// Channel name
|
|
||||||
pub name: String,
|
|
||||||
/// Channel avatar/profile picture
|
|
||||||
pub avatar: Vec<Thumbnail>,
|
|
||||||
/// Channel verification mark
|
|
||||||
pub verification: Verification,
|
|
||||||
/// Approximate number of subscribers
|
|
||||||
///
|
|
||||||
/// [`None`] if hidden by the owner or not present.
|
|
||||||
pub subscriber_count: Option<u64>,
|
|
||||||
/// Abbreviated channel description
|
|
||||||
pub short_description: String,
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub struct Paginator<T> {
|
||||||
///
|
///
|
||||||
/// Don't use this number to check if all items were fetched or for
|
/// Don't use this number to check if all items were fetched or for
|
||||||
/// iterating over the items.
|
/// iterating over the items.
|
||||||
pub count: Option<u64>,
|
pub count: Option<u32>,
|
||||||
/// Content of the paginator
|
/// Content of the paginator
|
||||||
pub items: Vec<T>,
|
pub items: Vec<T>,
|
||||||
/// The continuation token is passed to the YouTube API to fetch
|
/// The continuation token is passed to the YouTube API to fetch
|
||||||
|
@ -41,7 +41,7 @@ impl<T> Default for Paginator<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Paginator<T> {
|
impl<T> Paginator<T> {
|
||||||
pub(crate) fn new(count: Option<u64>, items: Vec<T>, ctoken: Option<String>) -> Self {
|
pub(crate) fn new(count: Option<u32>, items: Vec<T>, ctoken: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
count: match ctoken {
|
count: match ctoken {
|
||||||
Some(_) => count,
|
Some(_) => count,
|
||||||
|
|
|
@ -98,8 +98,11 @@ impl FileReporter {
|
||||||
|
|
||||||
fn _report(&self, report: &Report) -> Result<()> {
|
fn _report(&self, report: &Report) -> Result<()> {
|
||||||
let report_path = get_report_path(&self.path, report, "json")?;
|
let report_path = get_report_path(&self.path, report, "json")?;
|
||||||
serde_json::to_writer_pretty(&File::create(report_path)?, &report)
|
serde_json::to_writer_pretty(&File::create(report_path)?, &report).or_else(|e| {
|
||||||
.map_err(|e| Error::Other(format!("could not serialize report. err: {}", e).into()))?;
|
Err(Error::Other(
|
||||||
|
format!("could not serialize report. err: {}", e).into(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -305,18 +305,23 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut buf = String::with_capacity(until - i_utf16);
|
let mut buf = String::with_capacity(until - i_utf16);
|
||||||
for c in chars.by_ref() {
|
loop {
|
||||||
buf.push(c);
|
match chars.next() {
|
||||||
|
Some(c) => {
|
||||||
|
buf.push(c);
|
||||||
|
|
||||||
// is character on Basic Multilingual Plane -> 16bit in UTF-16,
|
// is character on Basic Multilingual Plane -> 16bit in UTF-16,
|
||||||
// counts as 1 JS character, otherwise 32bit, counts as 2 JS characters
|
// counts as 1 JS character, otherwise 32bit, counts as 2 JS characters
|
||||||
if (c as u32) > 0xffff {
|
if (c as u32) > 0xffff {
|
||||||
i_utf16 += 1;
|
i_utf16 += 1;
|
||||||
};
|
};
|
||||||
i_utf16 += 1;
|
i_utf16 += 1;
|
||||||
|
|
||||||
if i_utf16 >= until {
|
if i_utf16 >= until {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
|
@ -334,7 +339,7 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
|
||||||
|
|
||||||
// Replace no-break spaces, trim off whitespace and prefix character
|
// Replace no-break spaces, trim off whitespace and prefix character
|
||||||
let txt_link = txt_link.trim();
|
let txt_link = txt_link.trim();
|
||||||
let txt_link = txt_link.replace('\u{a0}', " ");
|
let txt_link = txt_link.replace("\u{a0}", " ");
|
||||||
|
|
||||||
static LINK_PREFIX: Lazy<Regex> = Lazy::new(|| Regex::new("^[/•] *").unwrap());
|
static LINK_PREFIX: Lazy<Regex> = Lazy::new(|| Regex::new("^[/•] *").unwrap());
|
||||||
let txt_link = LINK_PREFIX.replace(&txt_link, "");
|
let txt_link = LINK_PREFIX.replace(&txt_link, "");
|
||||||
|
|
|
@ -43,8 +43,11 @@ pub fn generate_content_playback_nonce() -> String {
|
||||||
///
|
///
|
||||||
/// `example.com/api?k1=v1&k2=v2 => example.com/api; {k1: v1, k2: v2}`
|
/// `example.com/api?k1=v1&k2=v2 => example.com/api; {k1: v1, k2: v2}`
|
||||||
pub fn url_to_params(url: &str) -> Result<(String, BTreeMap<String, String>)> {
|
pub fn url_to_params(url: &str) -> Result<(String, BTreeMap<String, String>)> {
|
||||||
let mut parsed_url = Url::parse(url)
|
let mut parsed_url = Url::parse(url).or_else(|e| {
|
||||||
.map_err(|e| Error::Other(format!("could not parse url `{}` err: {}", url, e).into()))?;
|
Err(Error::Other(
|
||||||
|
format!("could not parse url `{}` err: {}", url, e).into(),
|
||||||
|
))
|
||||||
|
})?;
|
||||||
let url_params: BTreeMap<String, String> = parsed_url
|
let url_params: BTreeMap<String, String> = parsed_url
|
||||||
.query_pairs()
|
.query_pairs()
|
||||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
Loading…
Add table
Reference in a new issue