refactored client, added reports
This commit is contained in:
parent
bb015561c1
commit
6cc927031a
22 changed files with 9091 additions and 1 deletions
74
src/client2/response/channel.rs
Normal file
74
src/client2/response/channel.rs
Normal 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
216
src/client2/response/mod.rs
Normal 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,
|
||||
}
|
||||
231
src/client2/response/player.rs
Normal file
231
src/client2/response/player.rs
Normal 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>>,
|
||||
}
|
||||
179
src/client2/response/playlist.rs
Normal file
179
src/client2/response/playlist.rs
Normal 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,
|
||||
}
|
||||
95
src/client2/response/playlist_music.rs
Normal file
95
src/client2/response/playlist_music.rs
Normal 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>,
|
||||
}
|
||||
432
src/client2/response/video.rs
Normal file
432
src/client2/response/video.rs
Normal 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,
|
||||
}
|
||||
Reference in a new issue