Compare commits

..

3 commits

Author SHA1 Message Date
db6a479bcf chore(pre-commit): error on clippy warnings 2022-10-10 01:12:28 +02:00
ecb84e32e1 feat: add search 2022-10-10 01:09:13 +02:00
6251ec1bd9 fix: remove clone from response models 2022-10-09 14:52:22 +02:00
24 changed files with 887 additions and 280 deletions

View file

@ -11,3 +11,4 @@ repos:
- id: cargo-fmt - id: cargo-fmt
- id: cargo-check - id: cargo-check
- id: cargo-clippy - id: cargo-clippy
args: ["--", "-D", "warnings"]

View file

@ -19,9 +19,9 @@ use super::{
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct QChannel { struct QChannel<'a> {
context: YTContext, context: YTContext,
browse_id: String, browse_id: &'a str,
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.to_owned(), browse_id: channel_id,
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.to_owned(), continuation: ctoken,
}; };
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.to_owned(), browse_id: channel_id,
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.to_owned(), continuation: ctoken,
}; };
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.to_owned(), browse_id: channel_id,
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,
}) })
} }
} }

View file

@ -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(),

View file

@ -5,6 +5,7 @@ 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")]
@ -124,9 +125,9 @@ struct ThirdParty {
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct QContinuation { struct QContinuation<'a> {
context: YTContext, context: YTContext,
continuation: String, continuation: &'a str,
} }
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";
@ -514,11 +515,11 @@ impl RustyPipe {
) )
.await?; .await?;
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(Error::from( util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| {
ExtractionError::InvalidData( Error::from(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 {
@ -532,11 +533,11 @@ impl RustyPipe {
) )
.await?; .await?;
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(Error::from( util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| {
ExtractionError::InvalidData( Error::from(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 {
@ -561,9 +562,11 @@ impl RustyPipe {
) )
.await?; .await?;
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(Error::from( util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| {
ExtractionError::InvalidData("Could not find music client version in sw.js".into()), Error::from(ExtractionError::InvalidData(
"Could not find music client version in sw.js".into(),
)) ))
})
}; };
let from_html = async { let from_html = async {
@ -577,11 +580,11 @@ impl RustyPipe {
) )
.await?; .await?;
util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(Error::from( util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| {
ExtractionError::InvalidData( Error::from(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 {
@ -991,12 +994,14 @@ impl RustyPipeQuery {
if !mapres.warnings.is_empty() { if !mapres.warnings.is_empty() {
create_report( create_report(
Level::WRN, Level::WRN,
Some(ExtractionError::Warnings.to_string()), Some(ExtractionError::DeserializationWarnings.to_string()),
mapres.warnings, mapres.warnings,
); );
if self.opts.strict { if self.opts.strict {
return Err(Error::Extraction(ExtractionError::Warnings)); return Err(Error::Extraction(
ExtractionError::DeserializationWarnings,
));
} }
} else if self.opts.report { } else if self.opts.report {
create_report(Level::DBG, None, vec![]); create_report(Level::DBG, None, vec![]);

View file

@ -1,7 +1,7 @@
use crate::error::Result; use crate::error::Result;
use crate::model::{ use crate::model::{
ChannelPlaylist, ChannelVideo, Comment, Paginator, PlaylistVideo, RecommendedVideo, ChannelPlaylist, ChannelVideo, Comment, Paginator, PlaylistVideo, RecommendedVideo, SearchItem,
}; };
use super::RustyPipeQuery; use super::RustyPipeQuery;
@ -225,3 +225,47 @@ 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(())
}
}

View file

@ -26,7 +26,7 @@ use super::{
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
struct QPlayer { struct QPlayer<'a> {
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 {
#[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: String, video_id: &'a str,
/// Set to true to allow extraction of streams with sensitive content /// Set to true to allow extraction of streams with sensitive content
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.to_owned(), video_id,
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.to_owned(), video_id,
content_check_ok: true, content_check_ok: true,
racy_check_ok: true, racy_check_ok: true,
} }

View file

@ -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.to_owned(), continuation: ctoken,
}; };
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 u32, None => videos.len() as u64,
}; };
let playlist_id = self.header.playlist_header_renderer.playlist_id; let playlist_id = self.header.playlist_header_renderer.playlist_id;

View file

@ -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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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, Clone, Debug, Deserialize)] #[derive(Default, 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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UrlEndpoint { pub struct UrlEndpoint {
pub url: String, pub url: String,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AppendAction { pub struct AppendAction {
#[serde_as(as = "VecLogError<_>")] #[serde_as(as = "VecLogError<_>")]

View file

@ -3,7 +3,7 @@ use serde::Deserialize;
use super::Thumbnail; use super::Thumbnail;
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Rating { pub struct Rating {
pub count: u32, pub count: u64,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Statistics { pub struct Statistics {
pub views: u64, pub views: u64,
} }

View file

@ -2,6 +2,7 @@ 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;
@ -10,6 +11,8 @@ 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;
@ -27,20 +30,20 @@ use crate::serializer::{
text::{Text, TextComponent}, text::{Text, TextComponent},
}; };
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ThumbnailsWrap { pub struct ThumbnailsWrap {
#[serde(default)] #[serde(default)]
@ -49,14 +52,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, Clone, Debug, Deserialize)] #[derive(Default, 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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Thumbnail { pub struct Thumbnail {
pub url: String, pub url: String,
@ -64,13 +67,17 @@ pub struct Thumbnail {
pub height: u32, pub height: u32,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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
@ -90,7 +97,7 @@ pub enum VideoListItem {
/// Video displayed on a channel page /// Video displayed on a channel page
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GridVideoRenderer { pub struct GridVideoRenderer {
pub video_id: String, pub video_id: String,
@ -111,7 +118,7 @@ pub struct GridVideoRenderer {
/// Video displayed in recommendations /// Video displayed in recommendations
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CompactVideoRenderer { pub struct CompactVideoRenderer {
pub video_id: String, pub video_id: String,
@ -145,7 +152,7 @@ pub struct CompactVideoRenderer {
/// Video displayed in a playlist /// Video displayed in a playlist
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PlaylistVideoRenderer { pub struct PlaylistVideoRenderer {
pub video_id: String, pub video_id: String,
@ -160,7 +167,7 @@ pub struct PlaylistVideoRenderer {
/// Playlist displayed on a channel page /// Playlist displayed on a channel page
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct GridPlaylistRenderer { pub struct GridPlaylistRenderer {
pub playlist_id: String, pub playlist_id: String,
@ -172,7 +179,7 @@ pub struct GridPlaylistRenderer {
} }
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct UpcomingEventData { pub struct UpcomingEventData {
/// Unixtime in seconds /// Unixtime in seconds
@ -180,26 +187,26 @@ pub struct UpcomingEventData {
pub start_time: i64, pub start_time: i64,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Icon { pub struct Icon {
pub icon_type: IconType, pub icon_type: IconType,
@ -216,14 +223,14 @@ pub enum IconType {
Like, Like,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct VideoOwnerRenderer { pub struct VideoOwnerRenderer {
pub title: TextComponent, pub title: TextComponent,
@ -235,13 +242,13 @@ pub struct VideoOwnerRenderer {
pub badges: Vec<ChannelBadge>, pub badges: Vec<ChannelBadge>,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ChannelBadgeRenderer { pub struct ChannelBadgeRenderer {
pub style: ChannelBadgeStyle, pub style: ChannelBadgeStyle,
@ -254,19 +261,19 @@ pub enum ChannelBadgeStyle {
BadgeStyleTypeVerifiedArtist, BadgeStyleTypeVerifiedArtist,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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 /// Is `LIVE` in case of a livestream and `SHORTS` in case of a short video
#[serde_as(as = "Text")] #[serde_as(as = "Text")]
pub text: String, pub text: String,
#[serde(default)] #[serde(default)]
@ -274,7 +281,7 @@ pub struct TimeOverlayRenderer {
pub style: TimeOverlayStyle, pub style: TimeOverlayStyle,
} }
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)] #[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TimeOverlayStyle { pub enum TimeOverlayStyle {
#[default] #[default]
@ -285,7 +292,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(Clone, Debug, Deserialize)] #[derive(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,
@ -293,7 +300,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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct VideoBadgeRenderer { pub struct VideoBadgeRenderer {
pub style: VideoBadgeStyle, pub style: VideoBadgeStyle,
@ -309,7 +316,7 @@ pub enum VideoBadgeStyle {
// YouTube Music // YouTube Music
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MusicItem { pub struct MusicItem {
pub thumbnail: MusicThumbnailRenderer, pub thumbnail: MusicThumbnailRenderer,
@ -322,21 +329,21 @@ pub struct MusicItem {
pub fixed_columns: Vec<MusicColumn>, pub fixed_columns: Vec<MusicColumn>,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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>,
@ -344,7 +351,7 @@ pub struct MusicContentsRenderer<T> {
pub continuations: Option<Vec<MusicContinuation>>, pub continuations: Option<Vec<MusicContinuation>>,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct MusicColumn { pub struct MusicColumn {
#[serde( #[serde(
rename = "musicResponsiveListItemFlexColumnRenderer", rename = "musicResponsiveListItemFlexColumnRenderer",
@ -354,18 +361,18 @@ pub struct MusicColumn {
} }
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct MusicColumnRenderer { pub struct MusicColumnRenderer {
pub text: TextComponent, pub text: TextComponent,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MusicContinuationData { pub struct MusicContinuationData {
pub continuation: String, pub continuation: String,

View file

@ -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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct Empty {} pub struct Empty {}
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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, Clone, Debug, Deserialize)] #[derive(Default, 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, Clone, Debug, Deserialize)] #[derive(Default, 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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct PlayerMicroformatRenderer { pub struct PlayerMicroformatRenderer {
#[serde(alias = "familySafe")] #[serde(alias = "familySafe")]

View file

@ -7,7 +7,7 @@ use crate::serializer::{MapResult, VecLogError};
use super::{ContentRenderer, ContentsRenderer, ThumbnailsWrap, VideoListItem}; use super::{ContentRenderer, ContentsRenderer, ThumbnailsWrap, VideoListItem};
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AppendAction { pub struct AppendAction {
#[serde_as(as = "VecLogError<_>")] #[serde_as(as = "VecLogError<_>")]

View file

@ -9,33 +9,33 @@ use super::{
ContentRenderer, ContentsRenderer, MusicContentsRenderer, MusicContinuation, MusicItem, ContentRenderer, ContentsRenderer, MusicContentsRenderer, MusicContinuation, MusicItem,
}; };
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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")]

View file

@ -0,0 +1,215 @@
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,
}

View file

@ -4,12 +4,10 @@ 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}, text::{AccessibilityText, AttributedText, Text, TextComponents},
VecLogError, MapResult, VecLogError,
}; };
use super::{ use super::{
@ -22,26 +20,26 @@ use super::{
/// Video details response /// Video details response
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(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,
#[serde_as(as = "VecLogError<_>")]
/// Video chapters + comment section /// Video chapters + comment section
#[serde_as(as = "VecLogError<_>")]
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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct TwoColumnWatchNextResults { pub struct TwoColumnWatchNextResults {
/// Metadata about the video /// Metadata about the video
@ -53,7 +51,7 @@ pub struct TwoColumnWatchNextResults {
} }
/// Metadata about the video /// Metadata about the video
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct VideoResultsWrap { pub struct VideoResultsWrap {
pub results: VideoResults, pub results: VideoResults,
@ -61,7 +59,7 @@ pub struct VideoResultsWrap {
/// Video metadata items /// Video metadata items
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct VideoResults { pub struct VideoResults {
#[serde_as(as = "VecLogError<_>")] #[serde_as(as = "VecLogError<_>")]
@ -70,7 +68,7 @@ pub struct VideoResults {
/// Video metadata item /// Video metadata item
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum VideoResultsItem { pub enum VideoResultsItem {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -104,14 +102,14 @@ pub enum VideoResultsItem {
None, None,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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`)
@ -122,7 +120,7 @@ pub struct ViewCountRenderer {
} }
/// Like/Dislike buttons /// Like/Dislike buttons
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct VideoActions { pub struct VideoActions {
pub menu_renderer: VideoActionsMenu, pub menu_renderer: VideoActionsMenu,
@ -130,7 +128,7 @@ pub struct VideoActions {
/// Like/Dislike buttons /// Like/Dislike buttons
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct VideoActionsMenu { pub struct VideoActionsMenu {
#[serde_as(as = "VecSkipError<_>")] #[serde_as(as = "VecSkipError<_>")]
@ -143,7 +141,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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum TopLevelButton { pub enum TopLevelButton {
ToggleButtonRenderer(ToggleButton), ToggleButtonRenderer(ToggleButton),
@ -154,7 +152,7 @@ pub enum TopLevelButton {
} }
/// Like/Dislike button /// Like/Dislike button
#[derive(Clone, Debug, Deserialize)] #[derive(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,
@ -162,7 +160,7 @@ pub struct ToggleButtonWrap {
/// Like/Dislike button /// Like/Dislike button
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ToggleButton { pub struct ToggleButton {
/// Icon type: `LIKE` / `DISLIKE` /// Icon type: `LIKE` / `DISLIKE`
@ -176,7 +174,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(Clone, Debug, Deserialize)] #[derive(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,
@ -184,14 +182,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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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,
@ -199,7 +197,7 @@ pub struct MetadataRow {
/// Additional video metadata item (Creative Commons License) /// Additional video metadata item (Creative Commons License)
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MetadataRowRenderer { pub struct MetadataRowRenderer {
// `License` // `License`
@ -214,13 +212,13 @@ pub struct MetadataRowRenderer {
} }
/// Contains current video ID /// Contains current video ID
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CurrentVideoWatchEndpoint { pub struct CurrentVideoWatchEndpoint {
pub video_id: String, pub video_id: String,
@ -231,7 +229,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, Clone, Debug, Deserialize)] #[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "sectionIdentifier")] #[serde(rename_all = "kebab-case", tag = "sectionIdentifier")]
pub enum ItemSection { pub enum ItemSection {
CommentsEntryPoint { CommentsEntryPoint {
@ -247,7 +245,7 @@ pub enum ItemSection {
} }
/// Item section containing comment count /// Item section containing comment count
#[derive(Clone, Debug, Deserialize)] #[derive(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,
@ -255,7 +253,7 @@ pub struct ItemSectionCommentCount {
/// Renderer of item section containing comment count /// Renderer of item section containing comment count
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CommentsEntryPointHeaderRenderer { pub struct CommentsEntryPointHeaderRenderer {
#[serde_as(as = "Text")] #[serde_as(as = "Text")]
@ -263,14 +261,14 @@ pub struct CommentsEntryPointHeaderRenderer {
} }
/// Item section containing comments ctoken /// Item section containing comments ctoken
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RecommendationResultsWrap { pub struct RecommendationResultsWrap {
pub secondary_results: RecommendationResults, pub secondary_results: RecommendationResults,
@ -278,7 +276,7 @@ pub struct RecommendationResultsWrap {
/// Video recommendations /// Video recommendations
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(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
@ -288,7 +286,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(Clone, Debug, Deserialize)] #[derive(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,
@ -296,7 +294,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(Clone, Debug, Deserialize)] #[derive(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
@ -315,7 +313,7 @@ pub enum EngagementPanelRenderer {
} }
/// Chapter markers /// Chapter markers
#[derive(Clone, Debug, Deserialize)] #[derive(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,
@ -323,7 +321,7 @@ pub struct ChapterMarkersContent {
/// Chapter markers /// Chapter markers
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct MacroMarkersListRenderer { pub struct MacroMarkersListRenderer {
#[serde_as(as = "VecLogError<_>")] #[serde_as(as = "VecLogError<_>")]
@ -331,7 +329,7 @@ pub struct MacroMarkersListRenderer {
} }
/// Chapter marker /// Chapter marker
#[derive(Clone, Debug, Deserialize)] #[derive(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,
@ -339,7 +337,7 @@ pub struct MacroMarkersListItem {
/// Chapter marker /// Chapter marker
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(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
@ -352,13 +350,13 @@ pub struct MacroMarkersListItemRenderer {
} }
/// Contains chapter start time in seconds /// Contains chapter start time in seconds
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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
@ -367,7 +365,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(Clone, Debug, Deserialize)] #[derive(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,
@ -376,7 +374,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(Clone, Debug, Deserialize)] #[derive(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`)
@ -390,7 +388,7 @@ pub struct CommentItemSectionHeaderRenderer {
} }
/// Comment section menu /// Comment section menu
#[derive(Clone, Debug, Deserialize)] #[derive(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,
@ -401,14 +399,14 @@ pub struct CommentItemSectionHeaderMenu {
/// Items: /// Items:
/// - Top comments /// - Top comments
/// - Latest comments /// - Latest comments
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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
@ -420,14 +418,14 @@ pub struct CommentItemSectionHeaderMenuItem {
*/ */
/// Video recommendations continuation response /// Video recommendations continuation response
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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,
@ -435,7 +433,7 @@ pub struct RecommendationsContItem {
/// Video recommendations continuation /// Video recommendations continuation
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AppendRecommendations { pub struct AppendRecommendations {
#[serde_as(as = "VecLogError<_>")] #[serde_as(as = "VecLogError<_>")]
@ -448,7 +446,7 @@ pub struct AppendRecommendations {
/// Video comments continuation response /// Video comments continuation response
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct VideoComments { pub struct VideoComments {
/// - Initial response: 2*reloadContinuationItemsCommand /// - Initial response: 2*reloadContinuationItemsCommand
@ -466,7 +464,7 @@ pub struct VideoComments {
} }
/// Video comments continuation /// Video comments continuation
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CommentsContItem { pub struct CommentsContItem {
#[serde(alias = "reloadContinuationItemsCommand")] #[serde(alias = "reloadContinuationItemsCommand")]
@ -475,7 +473,7 @@ pub struct CommentsContItem {
/// Video comments continuation action /// Video comments continuation action
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AppendComments { pub struct AppendComments {
#[serde_as(as = "VecLogError<_>")] #[serde_as(as = "VecLogError<_>")]
@ -483,7 +481,7 @@ pub struct AppendComments {
} }
#[serde_as] #[serde_as]
#[derive(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum CommentListItem { pub enum CommentListItem {
/// Top-level comment /// Top-level comment
@ -513,14 +511,14 @@ pub enum CommentListItem {
}, },
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CommentRenderer { pub struct CommentRenderer {
/// Author name /// Author name
@ -546,24 +544,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: u32, pub reply_count: u64,
/// Buttons for comment interaction (Like/Dislike/Reply) /// Buttons for comment interaction (Like/Dislike/Reply)
pub action_buttons: CommentActionButtons, pub action_buttons: CommentActionButtons,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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, PartialOrd, Ord, Hash)] #[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")] #[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CommentPriority { pub enum CommentPriority {
/// Default rendering priority /// Default rendering priority
@ -575,7 +573,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, Clone, Debug, Deserialize)] #[derive(Default, 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,
@ -584,7 +582,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, Clone, Debug, Deserialize)] #[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct RepliesRenderer { pub struct RepliesRenderer {
#[serde_as(as = "VecSkipError<_>")] #[serde_as(as = "VecSkipError<_>")]
@ -593,7 +591,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(Clone, Debug, Deserialize)] #[derive(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,
@ -601,7 +599,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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CommentActionButtonsRenderer { pub struct CommentActionButtonsRenderer {
pub like_button: ToggleButtonWrap, pub like_button: ToggleButtonWrap,
@ -609,27 +607,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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(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(Clone, Debug, Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct AuthorCommentBadgeRenderer { pub struct AuthorCommentBadgeRenderer {
/// Verified: `CHECK` /// Verified: `CHECK`

265
src/client/search.rs Normal file
View file

@ -0,0 +1,265 @@
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);
}
}

View file

@ -19,10 +19,10 @@ use super::{
}; };
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
struct QVideo { struct QVideo<'a> {
context: YTContext, context: YTContext,
/// YouTube video ID /// YouTube video ID
video_id: String, video_id: &'a str,
/// Set to true to allow extraction of streams with sensitive content /// Set to true to allow extraction of streams with sensitive content
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.to_owned(), video_id,
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.to_owned(), continuation: ctoken,
}; };
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.to_owned(), continuation: ctoken,
}; };
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::<u32>( util::parse_large_numstr::<u64>(
&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::<u32>(&txt, &mut warnings) util::parse_numeric_or_warn::<u64>(&txt, &mut warnings)
}); });
} }
}); });

View file

@ -56,22 +56,12 @@ fn parse_cr_header(cr_header: &str) -> Result<(u64, u64)> {
); );
Ok(( Ok((
captures captures.get(2).unwrap().as_str().parse().map_err(|_| {
.get(2) DownloadError::Progressive("could not parse range header number".into())
.unwrap() })?,
.as_str() captures.get(3).unwrap().as_str().parse().map_err(|_| {
.parse() DownloadError::Progressive("could not parse range header number".into())
.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(),
)))?,
)) ))
} }
@ -96,7 +86,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).or_else(|e| Err(DownloadError::Other(e.to_string().into())))?; util::url_to_params(url).map_err(|e| 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());
@ -120,9 +110,9 @@ async fn download_single_file<P: Into<PathBuf>>(
)) ))
) )
.to_str() .to_str()
.or(Err(DownloadError::Progressive( .map_err(|_| {
"could not convert Content-Range header to string".into(), DownloadError::Progressive("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)?;
@ -207,9 +197,9 @@ async fn download_chunks_by_header(
)) ))
) )
.to_str() .to_str()
.or(Err(DownloadError::Progressive( .map_err(|_| {
"could not convert Content-Range header to string".into(), DownloadError::Progressive("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)?;

View file

@ -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")] #[error("Warnings during deserialization/mapping in strict mode")]
Warnings, DeserializationWarnings,
} }
/// Internal error /// Internal error

View file

@ -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: u32, pub video_count: u64,
/// 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: u32, pub reply_count: u64,
/// 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<u32>, pub video_count: Option<u64>,
} }
/// Additional channel metadata fetched from the "About" tab. /// Additional channel metadata fetched from the "About" tab.
@ -753,5 +753,98 @@ 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: u32, pub like_count: u64,
}
#[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,
} }

View file

@ -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<u32>, pub count: Option<u64>,
/// 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<u32>, items: Vec<T>, ctoken: Option<String>) -> Self { pub(crate) fn new(count: Option<u64>, items: Vec<T>, ctoken: Option<String>) -> Self {
Self { Self {
count: match ctoken { count: match ctoken {
Some(_) => count, Some(_) => count,

View file

@ -98,11 +98,8 @@ 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).or_else(|e| { serde_json::to_writer_pretty(&File::create(report_path)?, &report)
Err(Error::Other( .map_err(|e| Error::Other(format!("could not serialize report. err: {}", e).into()))?;
format!("could not serialize report. err: {}", e).into(),
))
})?;
Ok(()) Ok(())
} }
} }

View file

@ -305,9 +305,7 @@ 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);
loop { for c in chars.by_ref() {
match chars.next() {
Some(c) => {
buf.push(c); buf.push(c);
// is character on Basic Multilingual Plane -> 16bit in UTF-16, // is character on Basic Multilingual Plane -> 16bit in UTF-16,
@ -321,9 +319,6 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
break; break;
} }
} }
None => break,
}
}
buf buf
}; };
@ -339,7 +334,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, "");

View file

@ -43,11 +43,8 @@ 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).or_else(|e| { let mut parsed_url = Url::parse(url)
Err(Error::Other( .map_err(|e| Error::Other(format!("could not parse url `{}` err: {}", url, e).into()))?;
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()))