refactored client, added reports

This commit is contained in:
ThetaDev 2022-09-12 16:26:09 +02:00
parent bb015561c1
commit 6cc927031a
22 changed files with 9091 additions and 1 deletions

View file

@ -0,0 +1,74 @@
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::VecSkipError;
use super::TimeOverlay;
use super::{ContentRenderer, ContentsRenderer, Thumbnails, VideoListItem};
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Channel {
pub contents: Contents,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Contents {
pub two_column_browse_results_renderer: TabsRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TabsRenderer {
#[serde_as(as = "VecSkipError<_>")]
pub tabs: Vec<TabRendererWrap>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TabRendererWrap {
pub tab_renderer: ContentRenderer<SectionListRendererWrap>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SectionListRendererWrap {
pub section_list_renderer: ContentsRenderer<ItemSectionRendererWrap>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemSectionRendererWrap {
pub item_section_renderer: ContentsRenderer<GridRendererWrap>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridRendererWrap {
pub grid_renderer: GridRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GridRenderer {
#[serde_as(as = "VecSkipError<_>")]
pub items: Vec<VideoListItem<ChannelVideo>>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChannelVideo {
pub video_id: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "crate::serializer::text::Text")]
pub title: String,
#[serde_as(as = "Option<crate::serializer::text::Text>")]
pub published_time_text: Option<String>,
#[serde_as(as = "crate::serializer::text::Text")]
pub view_count_text: String,
#[serde_as(as = "VecSkipError<_>")]
pub thumbnail_overlays: Vec<TimeOverlay>,
}

216
src/client2/response/mod.rs Normal file
View file

@ -0,0 +1,216 @@
pub mod channel;
pub mod player;
pub mod playlist;
pub mod playlist_music;
pub mod video;
pub use channel::Channel;
pub use player::Player;
pub use playlist::Playlist;
pub use playlist::PlaylistCont;
pub use playlist_music::PlaylistMusic;
pub use video::Video;
pub use video::VideoComments;
pub use video::VideoRecommendations;
use serde::Deserialize;
use serde_with::{serde_as, DefaultOnError, VecSkipError};
use crate::serializer::text::TextLink;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContentRenderer<T> {
pub content: T,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContentsRenderer<T> {
#[serde(alias = "tabs")]
pub contents: Vec<T>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ThumbnailsWrap {
pub thumbnail: Thumbnails,
}
#[derive(Default, Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Thumbnails {
pub thumbnails: Vec<Thumbnail>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Thumbnail {
pub url: String,
pub width: u32,
pub height: u32,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum VideoListItem<T> {
#[serde(alias = "playlistVideoRenderer", alias = "compactVideoRenderer")]
GridVideoRenderer {
#[serde(flatten)]
video: T,
},
#[serde(rename_all = "camelCase")]
ContinuationItemRenderer {
continuation_endpoint: ContinuationEndpoint,
},
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContinuationEndpoint {
pub continuation_command: ContinuationCommand,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ContinuationCommand {
pub token: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Icon {
pub icon_type: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoOwner {
pub video_owner_renderer: VideoOwnerRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoOwnerRenderer {
#[serde_as(as = "crate::serializer::text::TextLink")]
pub title: TextLink,
pub thumbnail: Thumbnails,
#[serde_as(as = "Option<crate::serializer::text::Text>")]
pub subscriber_count_text: Option<String>,
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub badges: Vec<UserBadge>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserBadge {
pub metadata_badge_renderer: UserBadgeRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserBadgeRenderer {
pub style: UserBadgeStyle,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum UserBadgeStyle {
BadgeStyleTypeVerified,
BadgeStyleTypeVerifiedArtist,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TimeOverlay {
pub thumbnail_overlay_time_status_renderer: TimeOverlayRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TimeOverlayRenderer {
#[serde_as(as = "crate::serializer::text::Text")]
pub text: String,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
pub style: TimeOverlayStyle,
}
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TimeOverlayStyle {
#[default]
Default,
Live,
Shorts,
}
// YouTube Music
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MusicItem {
pub thumbnail: MusicThumbnailRenderer,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
pub playlist_item_data: Option<PlaylistItemData>,
#[serde_as(as = "VecSkipError<_>")]
pub flex_columns: Vec<MusicColumn>,
#[serde_as(as = "VecSkipError<_>")]
pub fixed_columns: Vec<MusicColumn>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MusicThumbnailRenderer {
#[serde(alias = "croppedSquareThumbnailRenderer")]
pub music_thumbnail_renderer: ThumbnailsWrap,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistItemData {
pub video_id: String,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MusicContentsRenderer<T> {
pub contents: Vec<T>,
#[serde_as(as = "Option<VecSkipError<_>>")]
pub continuations: Option<Vec<MusicContinuation>>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct MusicColumn {
#[serde(
rename = "musicResponsiveListItemFlexColumnRenderer",
alias = "musicResponsiveListItemFixedColumnRenderer"
)]
pub renderer: MusicColumnRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
pub struct MusicColumnRenderer {
#[serde_as(as = "crate::serializer::text::TextLinks")]
pub text: Vec<TextLink>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MusicContinuation {
pub next_continuation_data: MusicContinuationData,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MusicContinuationData {
pub continuation: String,
}

View file

@ -0,0 +1,231 @@
use std::ops::Range;
use chrono::NaiveDate;
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::{json::JsonString, DefaultOnError, VecSkipError};
use super::Thumbnails;
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Player {
pub playability_status: PlayabilityStatus,
pub streaming_data: Option<StreamingData>,
pub captions: Option<Captions>,
pub video_details: Option<VideoDetails>,
pub microformat: Option<Microformat>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE")]
pub enum PlayabilityStatus {
#[serde(rename_all = "camelCase")]
Ok { live_streamability: Option<Empty> },
/// Video cant be played because of DRM / Geoblock
#[serde(rename_all = "camelCase")]
Unplayable { reason: String },
/// Age limit / Private video
#[serde(rename_all = "camelCase")]
LoginRequired { reason: String },
#[serde(rename_all = "camelCase")]
LiveStreamOffline { reason: String },
/// Video was censored / deleted
#[serde(rename_all = "camelCase")]
Error { reason: String },
}
#[derive(Clone, Debug, Deserialize)]
pub struct Empty {}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StreamingData {
#[serde_as(as = "JsonString")]
pub expires_in_seconds: u32,
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub formats: Vec<Format>,
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub adaptive_formats: Vec<Format>,
/// Only on livestreams
pub dash_manifest_url: Option<String>,
/// Only on livestreams
pub hls_manifest_url: Option<String>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Format {
pub itag: u32,
pub url: Option<String>,
#[serde(default, rename = "type")]
pub format_type: FormatType,
pub mime_type: String,
pub bitrate: u32,
pub width: Option<u32>,
pub height: Option<u32>,
#[serde_as(as = "Option<crate::serializer::range::Range>")]
pub index_range: Option<Range<u32>>,
#[serde_as(as = "Option<crate::serializer::range::Range>")]
pub init_range: Option<Range<u32>>,
#[serde_as(as = "JsonString")]
pub content_length: u64,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
pub quality: Option<Quality>,
pub fps: Option<u8>,
pub quality_label: Option<String>,
pub average_bitrate: u32,
pub color_info: Option<ColorInfo>,
// Audio only
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
pub audio_quality: Option<AudioQuality>,
#[serde_as(as = "Option<JsonString>")]
pub audio_sample_rate: Option<u32>,
pub audio_channels: Option<u8>,
pub loudness_db: Option<f64>,
pub audio_track: Option<AudioTrack>,
pub signature_cipher: Option<String>,
}
impl Format {
pub fn is_audio(&self) -> bool {
self.audio_quality.is_some() && self.audio_sample_rate.is_some()
}
pub fn is_video(&self) -> bool {
self.quality.is_some()
&& self.quality_label.is_some()
&& self.fps.is_some()
&& self.height.is_some()
&& self.width.is_some()
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "lowercase")]
pub enum Quality {
Tiny,
Small,
Medium,
Large,
Highres,
Hd720,
Hd1080,
Hd1440,
Hd2160,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AudioQuality {
#[serde(rename = "AUDIO_QUALITY_LOW", alias = "low")]
Low,
#[serde(rename = "AUDIO_QUALITY_MEDIUM", alias = "medium")]
Medium,
#[serde(rename = "AUDIO_QUALITY_HIGH", alias = "high")]
High,
}
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum FormatType {
#[default]
Default,
/// This stream only works via DASH and not via progressive HTTP.
FormatStreamTypeOtf,
}
#[derive(Default, Clone, Debug, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ColorInfo {
pub primaries: Primaries,
}
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Primaries {
#[default]
ColorPrimariesBt709,
ColorPrimariesBt2020,
}
#[derive(Default, Clone, Debug, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct AudioTrack {
pub id: String,
pub display_name: String,
pub audio_is_default: bool,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Captions {
pub player_captions_tracklist_renderer: PlayerCaptionsTracklistRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlayerCaptionsTracklistRenderer {
pub caption_tracks: Vec<CaptionTrack>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CaptionTrack {
pub base_url: String,
#[serde_as(as = "crate::serializer::text::Text")]
pub name: String,
pub language_code: String,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoDetails {
pub video_id: String,
pub title: String,
#[serde_as(as = "JsonString")]
pub length_seconds: u32,
pub keywords: Option<Vec<String>>,
pub channel_id: String,
pub short_description: Option<String>,
pub thumbnail: Option<Thumbnails>,
#[serde_as(as = "JsonString")]
pub view_count: u64,
pub author: String,
pub is_live_content: bool,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Microformat {
#[serde(alias = "microformatDataRenderer")]
pub player_microformat_renderer: PlayerMicroformatRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlayerMicroformatRenderer {
#[serde(alias = "familySafe")]
pub is_family_safe: bool,
pub category: String,
pub publish_date: NaiveDate,
// Only on YT Music
pub tags: Option<Vec<String>>,
}

View file

@ -0,0 +1,179 @@
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::{json::JsonString, DefaultOnError, VecSkipError};
use crate::client2::MapResult;
use crate::serializer::text::TextLink;
use super::{ContentRenderer, ContentsRenderer, Thumbnails, ThumbnailsWrap, VideoListItem};
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Playlist {
pub contents: Contents,
pub header: Header,
pub sidebar: Option<Sidebar>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistCont {
#[serde_as(as = "VecSkipError<_>")]
pub on_response_received_actions: Vec<OnResponseReceivedAction>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Contents {
pub two_column_browse_results_renderer: ContentsRenderer<Tab>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Tab {
pub tab_renderer: ContentRenderer<SectionList>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SectionList {
pub section_list_renderer: ContentsRenderer<ItemSection>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemSection {
pub item_section_renderer: ContentsRenderer<PlaylistVideoListRenderer>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistVideoListRenderer {
pub playlist_video_list_renderer: PlaylistVideoList,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistVideoList {
#[serde_as(as = "crate::serializer::VecLogError<_>")]
pub contents: MapResult<Vec<VideoListItem<PlaylistVideo>>>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistVideo {
pub video_id: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "crate::serializer::text::Text")]
pub title: String,
#[serde(rename = "shortBylineText")]
#[serde_as(as = "crate::serializer::text::TextLink")]
pub channel: TextLink,
#[serde_as(as = "JsonString")]
pub length_seconds: u32,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Header {
pub playlist_header_renderer: HeaderRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HeaderRenderer {
pub playlist_id: String,
#[serde_as(as = "crate::serializer::text::Text")]
pub title: String,
#[serde(default)]
#[serde_as(as = "DefaultOnError<Option<crate::serializer::text::Text>>")]
pub description_text: Option<String>,
#[serde_as(as = "crate::serializer::text::Text")]
pub num_videos_text: String,
#[serde_as(as = "Option<crate::serializer::text::TextLink>")]
pub owner_text: Option<TextLink>,
// Alternative layout
pub playlist_header_banner: Option<PlaylistHeaderBanner>,
#[serde(default)]
pub byline: Vec<Byline>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistHeaderBanner {
pub hero_playlist_thumbnail_renderer: ThumbnailsWrap,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Byline {
pub playlist_byline_renderer: BylineRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BylineRenderer {
#[serde_as(as = "crate::serializer::text::Text")]
pub text: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Sidebar {
pub playlist_sidebar_renderer: SidebarRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SidebarRenderer {
#[serde_as(as = "VecSkipError<_>")]
pub items: Vec<SidebarItemPrimary>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SidebarItemPrimary {
pub playlist_sidebar_primary_info_renderer: SidebarPrimaryInfoRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SidebarPrimaryInfoRenderer {
pub thumbnail_renderer: PlaylistThumbnailRenderer,
// - `"495", " videos"`
// - `"3,310,996 views"`
// - `"Last updated on ", "Aug 7, 2022"`
#[serde_as(as = "Vec<crate::serializer::text::Text>")]
pub stats: Vec<String>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistThumbnailRenderer {
// the alternative field name is used by YTM playlists
#[serde(alias = "playlistCustomThumbnailRenderer")]
pub playlist_video_thumbnail_renderer: ThumbnailsWrap,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OnResponseReceivedAction {
pub append_continuation_items_action: AppendAction,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppendAction {
#[serde_as(as = "crate::serializer::VecLogError<_>")]
pub continuation_items: MapResult<Vec<VideoListItem<PlaylistVideo>>>,
pub target_id: String,
}

View file

@ -0,0 +1,95 @@
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::VecSkipError;
use crate::serializer::text::Text;
use super::MusicThumbnailRenderer;
use super::{
ContentRenderer, ContentsRenderer, MusicContentsRenderer, MusicContinuation, MusicItem,
};
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistMusic {
pub contents: Contents,
pub header: Header,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Contents {
pub single_column_browse_results_renderer: ContentsRenderer<Tab>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Tab {
pub tab_renderer: ContentRenderer<SectionList>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SectionList {
/// Includes a continuation token for fetching recommendations
pub section_list_renderer: MusicContentsRenderer<ItemSection>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ItemSection {
#[serde(alias = "musicPlaylistShelfRenderer")]
pub music_shelf_renderer: MusicShelf,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MusicShelf {
/// Playlist ID (only for playlists)
pub playlist_id: Option<String>,
#[serde_as(as = "VecSkipError<_>")]
pub contents: Vec<PlaylistMusicItem>,
/// Continuation token for fetching more (>100) playlist items
#[serde_as(as = "Option<VecSkipError<_>>")]
pub continuations: Option<Vec<MusicContinuation>>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlaylistMusicItem {
pub music_responsive_list_item_renderer: MusicItem,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Header {
pub music_detail_header_renderer: HeaderRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HeaderRenderer {
#[serde_as(as = "crate::serializer::text::Text")]
pub title: String,
/// Content type + Channel/Artist + Year.
/// Missing on artist_tracks view.
///
/// `"Playlist", " • ", <"Best Music">, " • ", "2022"`
///
/// `"Album", " • ", <"Helene Fischer">, " • ", "2021"`
pub subtitle: Option<Text>,
/// Playlist description. May contain hashtags which are
/// displayed as search links on the YouTube website.
#[serde_as(as = "Option<crate::serializer::text::Text>")]
pub description: Option<String>,
/// Playlist thumbnail / album cover.
/// Missing on artist_tracks view.
pub thumbnail: Option<MusicThumbnailRenderer>,
/// Number of tracks + playtime.
/// Missing on artist_tracks view.
///
/// `"64 songs", " • ", "3 hours, 40 minutes"`
pub second_subtitle: Option<Text>,
}

View file

@ -0,0 +1,432 @@
use serde::Deserialize;
use serde_with::serde_as;
use serde_with::{DefaultOnError, VecSkipError};
use crate::serializer::text::TextLink;
use super::{ContinuationEndpoint, Icon, Thumbnails, VideoListItem, VideoOwner};
/// Video info response
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Video {
pub contents: Contents,
#[serde_as(as = "VecSkipError<_>")]
pub engagement_panels: Vec<EngagementPanel>,
}
/// Video recommendations response
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoRecommendations {
pub on_response_received_endpoints: Vec<RecommendationsContItem>,
}
/// Video comments response
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoComments {
#[serde_as(as = "VecSkipError<_>")]
pub on_response_received_endpoints: Vec<CommentsContItem>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Contents {
pub two_column_watch_next_results: TwoColumnWatchNextResults,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TwoColumnWatchNextResults {
pub results: VideoResultsWrap,
pub secondary_results: RecommendationResultsWrap,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoResultsWrap {
pub results: VideoResults,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoResults {
#[serde_as(as = "VecSkipError<_>")]
pub contents: Vec<VideoResultsItem>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum VideoResultsItem {
#[serde(rename_all = "camelCase")]
VideoPrimaryInfoRenderer {
#[serde_as(as = "crate::serializer::text::Text")]
title: String,
view_count: ViewCountWrap,
video_actions: VideoActions,
#[serde_as(as = "crate::serializer::text::Text")]
date_text: String,
},
#[serde(rename_all = "camelCase")]
VideoSecondaryInfoRenderer {
owner: VideoOwner,
#[serde_as(as = "crate::serializer::text::Text")]
description: String,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
metadata_row_container: Option<MetadataRowContainer>,
},
#[serde(rename_all = "camelCase")]
ItemSectionRenderer {
#[serde_as(as = "VecSkipError<_>")]
contents: Vec<ItemSection>,
section_identifier: String,
},
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ViewCountWrap {
pub video_view_count_renderer: ViewCount,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ViewCount {
#[serde_as(as = "crate::serializer::text::Text")]
pub view_count: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoActions {
pub menu_renderer: VideoActionsMenu,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoActionsMenu {
#[serde_as(as = "VecSkipError<_>")]
pub top_level_buttons: Vec<ToggleButtonWrap>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToggleButtonWrap {
pub toggle_button_renderer: ToggleButton,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToggleButton {
pub default_icon: Icon,
#[serde_as(as = "crate::serializer::text::Text")]
pub default_text: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataRowContainer {
pub metadata_row_container_renderer: MetadataRowContainerRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataRowContainerRenderer {
pub rows: Vec<MetadataRow>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataRow {
pub metadata_row_renderer: MetadataRowRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MetadataRowRenderer {
#[serde_as(as = "crate::serializer::text::Text")]
pub title: String,
#[serde_as(as = "Vec<crate::serializer::text::TextLinks>")]
pub contents: Vec<Vec<TextLink>>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ItemSection {
#[serde(rename_all = "camelCase")]
CommentsEntryPointHeaderRenderer {
#[serde_as(as = "crate::serializer::text::Text")]
header_text: String,
#[serde_as(as = "crate::serializer::text::Text")]
comment_count: String,
},
#[serde(rename_all = "camelCase")]
ContinuationItemRenderer {
continuation_endpoint: ContinuationEndpoint,
},
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecommendationResultsWrap {
pub secondary_results: RecommendationResults,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecommendationResults {
#[serde_as(as = "VecSkipError<_>")]
pub results: Vec<VideoListItem<RecommendedVideo>>,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecommendedVideo {
pub video_id: String,
pub thumbnail: Thumbnails,
#[serde_as(as = "crate::serializer::text::Text")]
pub title: String,
#[serde(rename = "shortBylineText")]
#[serde_as(as = "crate::serializer::text::TextLink")]
pub channel: TextLink,
#[serde_as(as = "Option<crate::serializer::text::Text>")]
pub length_text: Option<String>,
#[serde_as(as = "Option<crate::serializer::text::Text>")]
pub published_time_text: Option<String>,
#[serde_as(as = "crate::serializer::text::Text")]
pub view_count_text: String,
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub badges: Vec<VideoBadge>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoBadge {
pub metadata_badge_renderer: VideoBadgeRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VideoBadgeRenderer {
pub style: VideoBadgeStyle,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum VideoBadgeStyle {
BadgeStyleTypeLiveNow,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EngagementPanel {
pub engagement_panel_section_list_renderer: EngagementPanelRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EngagementPanelRenderer {
pub header: EngagementPanelHeader,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EngagementPanelHeader {
pub engagement_panel_title_header_renderer: EngagementPanelHeaderRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EngagementPanelHeaderRenderer {
pub menu: EngagementPanelMenu,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EngagementPanelMenu {
pub sort_filter_sub_menu_renderer: EngagementPanelMenuRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EngagementPanelMenuRenderer {
pub sub_menu_items: Vec<EngagementPanelMenuItem>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EngagementPanelMenuItem {
pub service_endpoint: ContinuationEndpoint,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RecommendationsContItem {
pub append_continuation_items_action: AppendRecommendations,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppendRecommendations {
pub continuation_items: Vec<VideoListItem<RecommendedVideo>>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommentsContItem {
#[serde(alias = "reloadContinuationItemsCommand")]
pub append_continuation_items_action: AppendComments,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AppendComments {
#[serde_as(as = "VecSkipError<_>")]
pub continuation_items: Vec<CommentListItem>,
pub target_id: String,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum CommentListItem {
#[serde(rename_all = "camelCase")]
CommentThreadRenderer {
comment: Comment,
#[serde(default)]
replies: Replies,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
rendering_priority: CommentPriority,
},
CommentRenderer {
#[serde(flatten)]
comment: CommentRenderer,
},
#[serde(rename_all = "camelCase")]
ContinuationItemRenderer {
continuation_endpoint: ContinuationEndpoint,
},
// TODO: TMP
#[serde(rename_all = "camelCase")]
CommentsHeaderRenderer { count_text: Option<String> },
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Comment {
pub comment_renderer: CommentRenderer,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommentRenderer {
// There may be comments with missing authors (possibly deleted users?)
#[serde(default)]
#[serde_as(as = "DefaultOnError<Option<crate::serializer::text::Text>>")]
pub author_text: Option<String>,
pub author_thumbnail: Thumbnails,
#[serde(default)]
#[serde_as(as = "DefaultOnError")]
pub author_endpoint: Option<AuthorEndpoint>,
#[serde_as(as = "crate::serializer::text::Text")]
pub content_text: String,
#[serde_as(as = "crate::serializer::text::Text")]
pub published_time_text: String,
pub comment_id: String,
pub author_is_channel_owner: bool,
#[serde_as(as = "Option<crate::serializer::text::Text>")]
pub vote_count: Option<String>,
pub author_comment_badge: Option<AuthorCommentBadge>,
#[serde(default)]
pub reply_count: u32,
pub action_buttons: CommentActionButtons,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthorEndpoint {
pub browse_endpoint: BrowseEndpoint,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BrowseEndpoint {
pub browse_id: String,
}
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum CommentPriority {
#[default]
RenderingPriorityUnknown,
RenderingPriorityPinnedComment,
}
#[derive(Default, Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Replies {
pub comment_replies_renderer: RepliesRenderer,
}
#[serde_as]
#[derive(Default, Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RepliesRenderer {
#[serde_as(as = "VecSkipError<_>")]
pub contents: Vec<CommentListItem>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommentActionButtons {
pub comment_action_buttons_renderer: CommentActionButtonsRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommentActionButtonsRenderer {
pub creator_heart: Option<CreatorHeart>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatorHeart {
pub creator_heart_renderer: CreatorHeartRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreatorHeartRenderer {
pub is_hearted: bool,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthorCommentBadge {
pub author_comment_badge_renderer: AuthorCommentBadgeRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthorCommentBadgeRenderer {
pub icon: Icon,
}