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