From 6251ec1bd956b95c40c22c034bd5709e6e0c28ea Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Sun, 9 Oct 2022 14:52:22 +0200 Subject: [PATCH 1/3] fix: remove clone from response models --- src/client/mod.rs | 6 +- src/client/response/channel.rs | 42 +++++----- src/client/response/channel_rss.rs | 12 +-- src/client/response/mod.rs | 62 +++++++------- src/client/response/player.rs | 26 +++--- src/client/response/playlist.rs | 40 ++++----- src/client/response/playlist_music.rs | 18 ++-- src/client/response/video_details.rs | 116 +++++++++++++------------- src/error.rs | 2 +- src/model/mod.rs | 26 +++--- 10 files changed, 176 insertions(+), 174 deletions(-) diff --git a/src/client/mod.rs b/src/client/mod.rs index 4c5a549..4de9d87 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -991,12 +991,14 @@ impl RustyPipeQuery { if !mapres.warnings.is_empty() { create_report( Level::WRN, - Some(ExtractionError::Warnings.to_string()), + Some(ExtractionError::DeserializationWarnings.to_string()), mapres.warnings, ); if self.opts.strict { - return Err(Error::Extraction(ExtractionError::Warnings)); + return Err(Error::Extraction( + ExtractionError::DeserializationWarnings, + )); } } else if self.opts.report { create_report(Level::DBG, None, vec![]); diff --git a/src/client/response/channel.rs b/src/client/response/channel.rs index f98d502..3f37ca3 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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Channel { pub header: Header, @@ -19,13 +19,13 @@ pub struct Channel { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChannelCont { pub on_response_received_actions: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TabsRenderer { #[serde_as(as = "VecSkipError<_>")] pub tabs: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TabRendererWrap { pub tab_renderer: ContentRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SectionListRendererWrap { pub section_list_renderer: SectionListRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SectionListRenderer { pub contents: Vec, @@ -63,14 +63,14 @@ pub struct SectionListRenderer { pub target_id: Option, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ItemSectionRendererWrap { pub item_section_renderer: ContentsRenderer, } #[serde_as] -#[derive(Default, Clone, Debug, Deserialize)] +#[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ChannelContent { GridRenderer { @@ -83,14 +83,14 @@ pub enum ChannelContent { None, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Header { pub c4_tabbed_header_renderer: HeaderRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Metadata { pub channel_metadata_renderer: ChannelMetadataRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChannelMetadataRenderer { pub description: String, pub vanity_channel_url: Option, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Microformat { pub microformat_data_renderer: MicroformatDataRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MicroformatDataRenderer { #[serde(default)] @@ -141,7 +141,7 @@ pub struct MicroformatDataRenderer { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChannelFullMetadata { #[serde_as(as = "Text")] @@ -153,7 +153,7 @@ pub struct ChannelFullMetadata { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NavigationEndpoint { pub url_endpoint: UrlEndpoint, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UrlEndpoint { pub url: String, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OnResponseReceivedAction { pub append_continuation_items_action: AppendAction, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(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 b054832..e5eff72 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(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] pub struct MediaGroup { #[serde(rename = "$unflatten=media:thumbnail")] pub thumbnail: Thumbnail, @@ -38,7 +38,7 @@ pub struct MediaGroup { pub community: Community, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] pub struct Community { #[serde(rename = "$unflatten=media:starRating")] pub rating: Rating, @@ -46,12 +46,12 @@ pub struct Community { pub statistics: Statistics, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] pub struct Rating { pub count: u32, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] pub struct Statistics { pub views: u64, } diff --git a/src/client/response/mod.rs b/src/client/response/mod.rs index 3b43b17..8ee2c6a 100644 --- a/src/client/response/mod.rs +++ b/src/client/response/mod.rs @@ -27,20 +27,20 @@ use crate::serializer::{ text::{Text, TextComponent}, }; -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ContentRenderer { pub content: T, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ContentsRenderer { #[serde(alias = "tabs")] pub contents: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ThumbnailsWrap { #[serde(default)] @@ -49,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, Clone, Debug, Deserialize)] +#[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Thumbnails { #[serde(default)] pub thumbnails: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Thumbnail { pub url: String, @@ -64,7 +64,7 @@ pub struct Thumbnail { pub height: u32, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub enum VideoListItem { GridVideoRenderer(GridVideoRenderer), @@ -90,7 +90,7 @@ pub enum VideoListItem { /// Video displayed on a channel page #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GridVideoRenderer { pub video_id: String, @@ -111,7 +111,7 @@ pub struct GridVideoRenderer { /// Video displayed in recommendations #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CompactVideoRenderer { pub video_id: String, @@ -145,7 +145,7 @@ pub struct CompactVideoRenderer { /// Video displayed in a playlist #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistVideoRenderer { pub video_id: String, @@ -160,7 +160,7 @@ pub struct PlaylistVideoRenderer { /// Playlist displayed on a channel page #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct GridPlaylistRenderer { pub playlist_id: String, @@ -172,7 +172,7 @@ pub struct GridPlaylistRenderer { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UpcomingEventData { /// Unixtime in seconds @@ -180,26 +180,26 @@ pub struct UpcomingEventData { pub start_time: i64, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ContinuationItemRenderer { pub continuation_endpoint: ContinuationEndpoint, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ContinuationEndpoint { pub continuation_command: ContinuationCommand, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ContinuationCommand { pub token: String, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Icon { pub icon_type: IconType, @@ -216,14 +216,14 @@ pub enum IconType { Like, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoOwner { pub video_owner_renderer: VideoOwnerRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoOwnerRenderer { pub title: TextComponent, @@ -235,13 +235,13 @@ pub struct VideoOwnerRenderer { pub badges: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChannelBadge { pub metadata_badge_renderer: ChannelBadgeRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChannelBadgeRenderer { pub style: ChannelBadgeStyle, @@ -254,14 +254,14 @@ pub enum ChannelBadgeStyle { BadgeStyleTypeVerifiedArtist, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TimeOverlay { pub thumbnail_overlay_time_status_renderer: TimeOverlayRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TimeOverlayRenderer { /// `29:54` @@ -285,7 +285,7 @@ pub enum TimeOverlayStyle { /// Badges are displayed on the video thumbnail and /// show certain video properties (e.g. active livestream) -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoBadge { pub metadata_badge_renderer: VideoBadgeRenderer, @@ -293,7 +293,7 @@ pub struct VideoBadge { /// Badges are displayed on the video thumbnail and /// show certain video properties (e.g. active livestream) -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoBadgeRenderer { pub style: VideoBadgeStyle, @@ -309,7 +309,7 @@ pub enum VideoBadgeStyle { // YouTube Music #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MusicItem { pub thumbnail: MusicThumbnailRenderer, @@ -322,21 +322,21 @@ pub struct MusicItem { pub fixed_columns: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MusicThumbnailRenderer { #[serde(alias = "croppedSquareThumbnailRenderer")] pub music_thumbnail_renderer: ThumbnailsWrap, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistItemData { pub video_id: String, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MusicContentsRenderer { pub contents: Vec, @@ -344,7 +344,7 @@ pub struct MusicContentsRenderer { pub continuations: Option>, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] pub struct MusicColumn { #[serde( rename = "musicResponsiveListItemFlexColumnRenderer", @@ -354,18 +354,18 @@ pub struct MusicColumn { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] pub struct MusicColumnRenderer { pub text: TextComponent, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MusicContinuation { pub next_continuation_data: MusicContinuationData, } -#[derive(Clone, Debug, Deserialize)] +#[derive(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 55c3a1c..ee8fce6 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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Player { pub playability_status: PlayabilityStatus, @@ -18,7 +18,7 @@ pub struct Player { pub microformat: Option, } -#[derive(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] pub struct Empty {} #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct StreamingData { #[serde_as(as = "JsonString")] @@ -58,7 +58,7 @@ pub struct StreamingData { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Format { pub itag: u32, @@ -152,7 +152,7 @@ pub enum FormatType { FormatStreamTypeOtf, } -#[derive(Default, Clone, Debug, Deserialize)] +#[derive(Default, Debug, Deserialize)] #[serde(default, rename_all = "camelCase")] pub struct ColorInfo { pub primaries: Primaries, @@ -166,7 +166,7 @@ pub enum Primaries { ColorPrimariesBt2020, } -#[derive(Default, Clone, Debug, Deserialize)] +#[derive(Default, 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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Captions { pub player_captions_tracklist_renderer: PlayerCaptionsTracklistRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlayerCaptionsTracklistRenderer { pub caption_tracks: Vec, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CaptionTrack { pub base_url: String, @@ -197,7 +197,7 @@ pub struct CaptionTrack { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Microformat { #[serde(alias = "microformatDataRenderer")] @@ -223,7 +223,7 @@ pub struct Microformat { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(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 5c90ba6..7fe0d31 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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Playlist { pub contents: Contents, @@ -16,59 +16,59 @@ pub struct Playlist { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistCont { #[serde_as(as = "VecSkipError<_>")] pub on_response_received_actions: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Contents { pub two_column_browse_results_renderer: ContentsRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Tab { pub tab_renderer: ContentRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SectionList { pub section_list_renderer: ContentsRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ItemSection { pub item_section_renderer: ContentsRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistVideoListRenderer { pub playlist_video_list_renderer: PlaylistVideoList, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistVideoList { #[serde_as(as = "VecLogError<_>")] pub contents: MapResult>, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Header { pub playlist_header_renderer: HeaderRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HeaderRenderer { pub playlist_id: String, @@ -87,48 +87,48 @@ pub struct HeaderRenderer { pub byline: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistHeaderBanner { pub hero_playlist_thumbnail_renderer: ThumbnailsWrap, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Byline { pub playlist_byline_renderer: BylineRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BylineRenderer { #[serde_as(as = "Text")] pub text: String, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Sidebar { pub playlist_sidebar_renderer: SidebarRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SidebarRenderer { #[serde_as(as = "VecSkipError<_>")] pub items: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SidebarItemPrimary { pub playlist_sidebar_primary_info_renderer: SidebarPrimaryInfoRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SidebarPrimaryInfoRenderer { pub thumbnail_renderer: PlaylistThumbnailRenderer, @@ -139,7 +139,7 @@ pub struct SidebarPrimaryInfoRenderer { pub stats: Vec, } -#[derive(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OnResponseReceivedAction { pub append_continuation_items_action: AppendAction, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(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 86e854c..85ad5ff 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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistMusic { pub contents: Contents, pub header: Header, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Contents { pub single_column_browse_results_renderer: ContentsRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Tab { pub tab_renderer: ContentRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SectionList { /// Includes a continuation token for fetching recommendations pub section_list_renderer: MusicContentsRenderer, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ItemSection { #[serde(alias = "musicPlaylistShelfRenderer")] @@ -43,7 +43,7 @@ pub struct ItemSection { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct PlaylistMusicItem { pub music_responsive_list_item_renderer: MusicItem, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Header { pub music_detail_header_renderer: HeaderRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct HeaderRenderer { #[serde_as(as = "crate::serializer::text::Text")] diff --git a/src/client/response/video_details.rs b/src/client/response/video_details.rs index fc23707..4b9b6f1 100644 --- a/src/client/response/video_details.rs +++ b/src/client/response/video_details.rs @@ -22,7 +22,7 @@ use super::{ /// Video details response #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoDetails { /// Video metadata + recommended videos @@ -35,13 +35,13 @@ pub struct VideoDetails { } /// Video details main object, contains video metadata and recommended videos -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Contents { pub two_column_watch_next_results: TwoColumnWatchNextResults, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct TwoColumnWatchNextResults { /// Metadata about the video @@ -53,7 +53,7 @@ pub struct TwoColumnWatchNextResults { } /// Metadata about the video -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoResultsWrap { pub results: VideoResults, @@ -61,7 +61,7 @@ pub struct VideoResultsWrap { /// Video metadata items #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoResults { #[serde_as(as = "VecLogError<_>")] @@ -70,7 +70,7 @@ pub struct VideoResults { /// Video metadata item #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub enum VideoResultsItem { #[serde(rename_all = "camelCase")] @@ -104,14 +104,14 @@ pub enum VideoResultsItem { None, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ViewCount { pub video_view_count_renderer: ViewCountRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ViewCountRenderer { /// View count (`232,975,196 views`) @@ -122,7 +122,7 @@ pub struct ViewCountRenderer { } /// Like/Dislike buttons -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoActions { pub menu_renderer: VideoActionsMenu, @@ -130,7 +130,7 @@ pub struct VideoActions { /// Like/Dislike buttons #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoActionsMenu { #[serde_as(as = "VecSkipError<_>")] @@ -143,7 +143,7 @@ pub struct VideoActionsMenu { /// /// See: https://github.com/TeamNewPipe/NewPipeExtractor/pull/926 #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TopLevelButton { ToggleButtonRenderer(ToggleButton), @@ -154,7 +154,7 @@ pub enum TopLevelButton { } /// Like/Dislike button -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToggleButtonWrap { pub toggle_button_renderer: ToggleButton, @@ -162,7 +162,7 @@ pub struct ToggleButtonWrap { /// Like/Dislike button #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ToggleButton { /// Icon type: `LIKE` / `DISLIKE` @@ -176,7 +176,7 @@ pub struct ToggleButton { /// Shows additional video metadata. Its only known use is for /// the Creative Commonse License. -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MetadataRowContainer { pub metadata_row_container_renderer: MetadataRowContainerRenderer, @@ -184,14 +184,14 @@ pub struct MetadataRowContainer { /// Shows additional video metadata. Its only known use is for /// the Creative Commonse License. -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MetadataRowContainerRenderer { pub rows: Vec, } /// Additional video metadata item (Creative Commons License) -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MetadataRow { pub metadata_row_renderer: MetadataRowRenderer, @@ -199,7 +199,7 @@ pub struct MetadataRow { /// Additional video metadata item (Creative Commons License) #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MetadataRowRenderer { // `License` @@ -214,13 +214,13 @@ pub struct MetadataRowRenderer { } /// Contains current video ID -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CurrentVideoEndpoint { pub watch_endpoint: CurrentVideoWatchEndpoint, } /// Contains current video ID -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CurrentVideoWatchEndpoint { pub video_id: String, @@ -231,7 +231,7 @@ pub struct CurrentVideoWatchEndpoint { /// 1. CommentsEntryPointHeaderRenderer: contains number of comments /// 2. ContinuationItemRenderer: contains continuation token #[serde_as] -#[derive(Default, Clone, Debug, Deserialize)] +#[derive(Default, Debug, Deserialize)] #[serde(rename_all = "kebab-case", tag = "sectionIdentifier")] pub enum ItemSection { CommentsEntryPoint { @@ -247,7 +247,7 @@ pub enum ItemSection { } /// Item section containing comment count -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ItemSectionCommentCount { pub comments_entry_point_header_renderer: CommentsEntryPointHeaderRenderer, @@ -255,7 +255,7 @@ pub struct ItemSectionCommentCount { /// Renderer of item section containing comment count #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentsEntryPointHeaderRenderer { #[serde_as(as = "Text")] @@ -263,14 +263,14 @@ pub struct CommentsEntryPointHeaderRenderer { } /// Item section containing comments ctoken -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ItemSectionComments { pub continuation_item_renderer: ContinuationItemRenderer, } /// Video recommendations -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RecommendationResultsWrap { pub secondary_results: RecommendationResults, @@ -278,7 +278,7 @@ pub struct RecommendationResultsWrap { /// Video recommendations #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RecommendationResults { /// Can be `None` for age-restricted videos @@ -288,7 +288,7 @@ pub struct RecommendationResults { /// The engagement panels are displayed below the video and contain chapter markers /// and the comment section. -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EngagementPanel { pub engagement_panel_section_list_renderer: EngagementPanelRenderer, @@ -296,7 +296,7 @@ pub struct EngagementPanel { /// The engagement panels are displayed below the video and contain chapter markers /// and the comment section. -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "kebab-case", tag = "targetId")] pub enum EngagementPanelRenderer { /// Chapter markers @@ -315,7 +315,7 @@ pub enum EngagementPanelRenderer { } /// Chapter markers -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChapterMarkersContent { pub macro_markers_list_renderer: MacroMarkersListRenderer, @@ -323,7 +323,7 @@ pub struct ChapterMarkersContent { /// Chapter markers #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MacroMarkersListRenderer { #[serde_as(as = "VecLogError<_>")] @@ -331,7 +331,7 @@ pub struct MacroMarkersListRenderer { } /// Chapter marker -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MacroMarkersListItem { pub macro_markers_list_item_renderer: MacroMarkersListItemRenderer, @@ -339,7 +339,7 @@ pub struct MacroMarkersListItem { /// Chapter marker #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MacroMarkersListItemRenderer { /// Contains chapter start time in seconds @@ -352,13 +352,13 @@ pub struct MacroMarkersListItemRenderer { } /// Contains chapter start time in seconds -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MacroMarkersListItemOnTap { pub watch_endpoint: MacroMarkersListItemWatchEndpoint, } /// Contains chapter start time in seconds -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct MacroMarkersListItemWatchEndpoint { /// Chapter start time in seconds @@ -367,7 +367,7 @@ pub struct MacroMarkersListItemWatchEndpoint { /// Comment section header /// (contains continuation tokens for fetching top/latest comments) -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentItemSectionHeader { pub engagement_panel_title_header_renderer: CommentItemSectionHeaderRenderer, @@ -376,7 +376,7 @@ pub struct CommentItemSectionHeader { /// Comment section header /// (contains continuation tokens for fetching top/latest comments) #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentItemSectionHeaderRenderer { /// Approximate comment count (e.g. `81`, `2.2K`, `705K`) @@ -390,7 +390,7 @@ pub struct CommentItemSectionHeaderRenderer { } /// Comment section menu -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentItemSectionHeaderMenu { pub sort_filter_sub_menu_renderer: CommentItemSectionHeaderMenuRenderer, @@ -401,14 +401,14 @@ pub struct CommentItemSectionHeaderMenu { /// Items: /// - Top comments /// - Latest comments -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentItemSectionHeaderMenuRenderer { pub sub_menu_items: Vec, } /// Comment section menu item -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentItemSectionHeaderMenuItem { /// Continuation token for fetching comments @@ -420,14 +420,14 @@ pub struct CommentItemSectionHeaderMenuItem { */ /// Video recommendations continuation response -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoRecommendations { pub on_response_received_endpoints: Vec, } /// Video recommendations continuation -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RecommendationsContItem { pub append_continuation_items_action: AppendRecommendations, @@ -435,7 +435,7 @@ pub struct RecommendationsContItem { /// Video recommendations continuation #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AppendRecommendations { #[serde_as(as = "VecLogError<_>")] @@ -448,7 +448,7 @@ pub struct AppendRecommendations { /// Video comments continuation response #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct VideoComments { /// - Initial response: 2*reloadContinuationItemsCommand @@ -466,7 +466,7 @@ pub struct VideoComments { } /// Video comments continuation -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentsContItem { #[serde(alias = "reloadContinuationItemsCommand")] @@ -475,7 +475,7 @@ pub struct CommentsContItem { /// Video comments continuation action #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AppendComments { #[serde_as(as = "VecLogError<_>")] @@ -483,7 +483,7 @@ pub struct AppendComments { } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub enum CommentListItem { /// Top-level comment @@ -513,14 +513,14 @@ pub enum CommentListItem { }, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Comment { pub comment_renderer: CommentRenderer, } #[serde_as] -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentRenderer { /// Author name @@ -551,19 +551,19 @@ pub struct CommentRenderer { pub action_buttons: CommentActionButtons, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AuthorEndpoint { pub browse_endpoint: BrowseEndpoint, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BrowseEndpoint { 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")] pub enum CommentPriority { /// Default rendering priority @@ -575,7 +575,7 @@ pub enum CommentPriority { /// Does not contain replies directly but a continuation token /// for fetching them. -#[derive(Default, Clone, Debug, Deserialize)] +#[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Replies { pub comment_replies_renderer: RepliesRenderer, @@ -584,7 +584,7 @@ pub struct Replies { /// Does not contain replies directly but a continuation token /// for fetching them. #[serde_as] -#[derive(Default, Clone, Debug, Deserialize)] +#[derive(Default, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RepliesRenderer { #[serde_as(as = "VecSkipError<_>")] @@ -593,7 +593,7 @@ pub struct RepliesRenderer { /// These are the buttons for comment interaction (Like/Dislike/Reply). /// Contains the CreatorHeart. -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentActionButtons { pub comment_action_buttons_renderer: CommentActionButtonsRenderer, @@ -601,7 +601,7 @@ pub struct CommentActionButtons { /// These are the buttons for comment interaction (Like/Dislike/Reply). /// Contains the CreatorHeart. -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommentActionButtonsRenderer { pub like_button: ToggleButtonWrap, @@ -609,27 +609,27 @@ pub struct CommentActionButtonsRenderer { } /// Video creators can endorse comments by marking them with a ❤️. -#[derive(Clone, Debug, Deserialize)] +#[derive(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(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CreatorHeartRenderer { pub is_hearted: bool, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AuthorCommentBadge { pub author_comment_badge_renderer: AuthorCommentBadgeRenderer, } /// YouTube channel badge (verified) of the comment author -#[derive(Clone, Debug, Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AuthorCommentBadgeRenderer { /// Verified: `CHECK` diff --git a/src/error.rs b/src/error.rs index 97b061c..572fd9e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -82,7 +82,7 @@ pub enum ExtractionError { #[error("got wrong result from YT: {0}")] WrongResult(String), #[error("Warnings during deserialization/mapping")] - Warnings, + DeserializationWarnings, } /// Internal error diff --git a/src/model/mod.rs b/src/model/mod.rs index 8096227..3d0e9c3 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -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,7 +600,7 @@ 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, @@ -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, From ecb84e32e1639e5ff6585e06393cd64fc22fa1d2 Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Mon, 10 Oct 2022 01:09:13 +0200 Subject: [PATCH 2/3] feat: add search --- src/client/channel.rs | 18 +- src/client/channel_rss.rs | 2 +- src/client/mod.rs | 37 ++-- src/client/pagination.rs | 46 ++++- src/client/player.rs | 8 +- src/client/playlist.rs | 4 +- src/client/response/channel_rss.rs | 2 +- src/client/response/mod.rs | 11 +- src/client/response/search.rs | 215 ++++++++++++++++++++++ src/client/response/video_details.rs | 10 +- src/client/search.rs | 265 +++++++++++++++++++++++++++ src/client/video_details.rs | 14 +- src/download.rs | 36 ++-- src/error.rs | 2 +- src/model/mod.rs | 101 +++++++++- src/model/paginator.rs | 4 +- src/report.rs | 7 +- src/serializer/text.rs | 27 ++- src/{util.rs => util/mod.rs} | 7 +- 19 files changed, 710 insertions(+), 106 deletions(-) create mode 100644 src/client/response/search.rs create mode 100644 src/client/search.rs rename src/{util.rs => util/mod.rs} (98%) diff --git a/src/client/channel.rs b/src/client/channel.rs index 4ea2c4f..7b74532 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 { +struct QChannel<'a> { context: YTContext, - browse_id: String, + browse_id: &'a str, 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.to_owned(), + browse_id: channel_id, 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.to_owned(), + continuation: ctoken, }; 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.to_owned(), + browse_id: channel_id, 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.to_owned(), + continuation: ctoken, }; 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.to_owned(), + browse_id: channel_id, 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 33c6e6a..44fa26b 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 4de9d87..e93ade5 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -5,6 +5,7 @@ mod pagination; mod player; mod playlist; mod response; +mod search; mod video_details; #[cfg(feature = "rss")] @@ -124,9 +125,9 @@ struct ThirdParty { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -struct QContinuation { +struct QContinuation<'a> { 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"; @@ -514,11 +515,11 @@ impl RustyPipe { ) .await?; - util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or(Error::from( - ExtractionError::InvalidData( + util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &swjs, 1).ok_or_else(|| { + Error::from(ExtractionError::InvalidData( "Could not find desktop client version in sw.js".into(), - ), - )) + )) + }) }; let from_html = async { @@ -532,11 +533,11 @@ impl RustyPipe { ) .await?; - util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(Error::from( - ExtractionError::InvalidData( + util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| { + Error::from(ExtractionError::InvalidData( "Could not find desktop client version in sw.js".into(), - ), - )) + )) + }) }; match from_swjs.await { @@ -561,9 +562,11 @@ impl RustyPipe { ) .await?; - 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()), - )) + 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(), + )) + }) }; let from_html = async { @@ -577,11 +580,11 @@ impl RustyPipe { ) .await?; - util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or(Error::from( - ExtractionError::InvalidData( + util::get_cg_from_regexes(CLIENT_VERSION_REGEXES.iter(), &html, 1).ok_or_else(|| { + Error::from(ExtractionError::InvalidData( "Could not find music client version on html page".into(), - ), - )) + )) + }) }; match from_swjs.await { diff --git a/src/client/pagination.rs b/src/client/pagination.rs index ae32195..3ebd019 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, + ChannelPlaylist, ChannelVideo, Comment, Paginator, PlaylistVideo, RecommendedVideo, SearchItem, }; use super::RustyPipeQuery; @@ -225,3 +225,47 @@ 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 457bdd2..3a0a584 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 { +struct QPlayer<'a> { context: YTContext, /// Website playback context #[serde(skip_serializing_if = "Option::is_none")] @@ -35,7 +35,7 @@ struct QPlayer { #[serde(skip_serializing_if = "Option::is_none")] cpn: Option, /// YouTube video ID - video_id: String, + video_id: &'a str, /// 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.to_owned(), + video_id, 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.to_owned(), + video_id, content_check_ok: true, racy_check_ok: true, } diff --git a/src/client/playlist.rs b/src/client/playlist.rs index c9e6095..5dea1b1 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.to_owned(), + continuation: ctoken, }; self.execute_request::( @@ -143,7 +143,7 @@ impl MapResponse for response::Playlist { 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; diff --git a/src/client/response/channel_rss.rs b/src/client/response/channel_rss.rs index e5eff72..205f103 100644 --- a/src/client/response/channel_rss.rs +++ b/src/client/response/channel_rss.rs @@ -48,7 +48,7 @@ pub struct Community { #[derive(Debug, Deserialize)] pub struct Rating { - pub count: u32, + pub count: u64, } #[derive(Debug, Deserialize)] diff --git a/src/client/response/mod.rs b/src/client/response/mod.rs index 8ee2c6a..e84fb96 100644 --- a/src/client/response/mod.rs +++ b/src/client/response/mod.rs @@ -2,6 +2,7 @@ pub mod channel; pub mod player; pub mod playlist; pub mod playlist_music; +pub mod search; pub mod video_details; pub use channel::Channel; @@ -10,6 +11,8 @@ 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; @@ -67,10 +70,14 @@ pub struct Thumbnail { #[derive(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 @@ -266,7 +273,7 @@ pub struct TimeOverlay { pub struct TimeOverlayRenderer { /// `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")] pub text: String, #[serde(default)] @@ -274,7 +281,7 @@ pub struct TimeOverlayRenderer { 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")] pub enum TimeOverlayStyle { #[default] diff --git a/src/client/response/search.rs b/src/client/response/search.rs new file mode 100644 index 0000000..fe7509b --- /dev/null +++ b/src/client/response/search.rs @@ -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")] + 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 4b9b6f1..f453c56 100644 --- a/src/client/response/video_details.rs +++ b/src/client/response/video_details.rs @@ -4,12 +4,10 @@ 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}, - VecLogError, + text::{AccessibilityText, AttributedText, Text, TextComponents}, + MapResult, VecLogError, }; use super::{ @@ -29,8 +27,8 @@ pub struct VideoDetails { pub contents: Contents, /// Video ID pub current_video_endpoint: CurrentVideoEndpoint, - #[serde_as(as = "VecLogError<_>")] /// Video chapters + comment section + #[serde_as(as = "VecLogError<_>")] pub engagement_panels: MapResult>, } @@ -546,7 +544,7 @@ pub struct CommentRenderer { // pub vote_count: Option, pub author_comment_badge: Option, #[serde(default)] - pub reply_count: u32, + pub reply_count: u64, /// Buttons for comment interaction (Like/Dislike/Reply) pub action_buttons: CommentActionButtons, } diff --git a/src/client/search.rs b/src/client/search.rs new file mode 100644 index 0000000..2846037 --- /dev/null +++ b/src/client/search.rs @@ -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, +} + +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 808f759..64a4e55 100644 --- a/src/client/video_details.rs +++ b/src/client/video_details.rs @@ -19,10 +19,10 @@ use super::{ }; #[derive(Debug, Serialize)] -struct QVideo { +struct QVideo<'a> { context: YTContext, /// YouTube video ID - video_id: String, + video_id: &'a str, /// 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.to_owned(), + video_id, 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.to_owned(), + continuation: ctoken, }; 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.to_owned(), + continuation: ctoken, }; 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 a9ce92f..7ba3fbf 100644 --- a/src/download.rs +++ b/src/download.rs @@ -56,22 +56,12 @@ fn parse_cr_header(cr_header: &str) -> Result<(u64, u64)> { ); Ok(( - 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(), - )))?, + 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()) + })?, )) } @@ -96,7 +86,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).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"); if is_gvideo { size = url_params.get("clen").and_then(|s| s.parse::().ok()); @@ -120,9 +110,9 @@ async fn download_single_file>( )) ) .to_str() - .or(Err(DownloadError::Progressive( - "could not convert Content-Range header to string".into(), - )))?; + .map_err(|_| { + DownloadError::Progressive("could not convert Content-Range header to string".into()) + })?; let (_, original_size) = parse_cr_header(cr_header)?; @@ -207,9 +197,9 @@ async fn download_chunks_by_header( )) ) .to_str() - .or(Err(DownloadError::Progressive( - "could not convert Content-Range header to string".into(), - )))?; + .map_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 572fd9e..35875a5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -81,7 +81,7 @@ pub enum ExtractionError { InvalidData(Cow<'static, str>), #[error("got wrong result from YT: {0}")] WrongResult(String), - #[error("Warnings during deserialization/mapping")] + #[error("Warnings during deserialization/mapping in strict mode")] DeserializationWarnings, } diff --git a/src/model/mod.rs b/src/model/mod.rs index 3d0e9c3..5cce258 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: u32, + pub video_count: u64, /// Playlist thumbnail pub thumbnail: Vec, /// Playlist description in plaintext format @@ -607,7 +607,7 @@ pub struct Comment { /// Number of comment likes pub like_count: Option, /// Number of replies - pub reply_count: u32, + pub reply_count: u64, /// Paginator to fetch comment replies pub replies: Paginator, /// Is the comment from the channel owner? @@ -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,5 +753,98 @@ pub struct ChannelRssVideo { /// Number of likes /// /// 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, + 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, } diff --git a/src/model/paginator.rs b/src/model/paginator.rs index cc7766d..090e799 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 87e9d13..cf29592 100644 --- a/src/report.rs +++ b/src/report.rs @@ -98,11 +98,8 @@ 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).or_else(|e| { - Err(Error::Other( - format!("could not serialize report. err: {}", e).into(), - )) - })?; + serde_json::to_writer_pretty(&File::create(report_path)?, &report) + .map_err(|e| Error::Other(format!("could not serialize report. err: {}", e).into()))?; Ok(()) } } diff --git a/src/serializer/text.rs b/src/serializer/text.rs index 3406836..dfa5321 100644 --- a/src/serializer/text.rs +++ b/src/serializer/text.rs @@ -305,23 +305,18 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText { } let mut buf = String::with_capacity(until - i_utf16); - loop { - match chars.next() { - Some(c) => { - buf.push(c); + for c in chars.by_ref() { + 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; - } - } - None => break, + if i_utf16 >= until { + break; } } buf @@ -339,7 +334,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.rs b/src/util/mod.rs similarity index 98% rename from src/util.rs rename to src/util/mod.rs index 67980a2..2928f63 100644 --- a/src/util.rs +++ b/src/util/mod.rs @@ -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}` pub fn url_to_params(url: &str) -> Result<(String, BTreeMap)> { - let mut parsed_url = Url::parse(url).or_else(|e| { - Err(Error::Other( - format!("could not parse url `{}` err: {}", url, e).into(), - )) - })?; + let mut parsed_url = Url::parse(url) + .map_err(|e| 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())) From db6a479bcfab96deed27be9b6c3a00f41ab3ef0b Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Mon, 10 Oct 2022 01:12:28 +0200 Subject: [PATCH 3/3] chore(pre-commit): error on clippy warnings --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d7459e..001e8bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,3 +11,4 @@ repos: - id: cargo-fmt - id: cargo-check - id: cargo-clippy + args: ["--", "-D", "warnings"]