feat: add video details response model
- add paginator, impl for playlist items - small model refactor - add ignore_any deserializer - removed unnecessary clones in response mapping
This commit is contained in:
parent
17b6844eb0
commit
972288d810
32 changed files with 61791 additions and 5316 deletions
|
|
@ -2,21 +2,24 @@ pub mod channel;
|
|||
pub mod player;
|
||||
pub mod playlist;
|
||||
pub mod playlist_music;
|
||||
pub mod video;
|
||||
pub mod video_details;
|
||||
|
||||
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;
|
||||
pub use video_details::VideoComments;
|
||||
pub use video_details::VideoDetails;
|
||||
pub use video_details::VideoRecommendations;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_with::{serde_as, DefaultOnError, VecSkipError};
|
||||
|
||||
use crate::serializer::text::{Text, TextLink, TextLinks};
|
||||
use crate::serializer::{
|
||||
ignore_any,
|
||||
text::{Text, TextLink, TextLinks},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -64,6 +67,9 @@ pub enum VideoListItem<T> {
|
|||
ContinuationItemRenderer {
|
||||
continuation_endpoint: ContinuationEndpoint,
|
||||
},
|
||||
/// No video list item (e.g. ad)
|
||||
#[serde(other, deserialize_with = "ignore_any")]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
|
@ -215,6 +221,10 @@ pub struct MusicContinuationData {
|
|||
pub continuation: String,
|
||||
}
|
||||
|
||||
/*
|
||||
#MAPPING
|
||||
*/
|
||||
|
||||
impl From<Thumbnail> for crate::model::Thumbnail {
|
||||
fn from(tn: Thumbnail) -> Self {
|
||||
crate::model::Thumbnail {
|
||||
|
|
@ -227,10 +237,13 @@ impl From<Thumbnail> for crate::model::Thumbnail {
|
|||
|
||||
impl From<Thumbnails> for Vec<crate::model::Thumbnail> {
|
||||
fn from(ts: Thumbnails) -> Self {
|
||||
let mut thumbnails = vec![];
|
||||
for t in ts.thumbnails {
|
||||
thumbnails.push(t.into());
|
||||
}
|
||||
thumbnails
|
||||
ts.thumbnails
|
||||
.into_iter()
|
||||
.map(|t| crate::model::Thumbnail {
|
||||
url: t.url,
|
||||
width: t.width,
|
||||
height: t.height,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,9 +148,9 @@ pub struct SidebarItemPrimary {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SidebarPrimaryInfoRenderer {
|
||||
pub thumbnail_renderer: PlaylistThumbnailRenderer,
|
||||
// - `"495", " videos"`
|
||||
// - `"3,310,996 views"`
|
||||
// - `"Last updated on ", "Aug 7, 2022"`
|
||||
/// - `"495", " videos"`
|
||||
/// - `"3,310,996 views"`
|
||||
/// - `"Last updated on ", "Aug 7, 2022"`
|
||||
#[serde_as(as = "Vec<Text>")]
|
||||
pub stats: Vec<String>,
|
||||
}
|
||||
|
|
@ -175,5 +175,4 @@ pub struct OnResponseReceivedAction {
|
|||
pub struct AppendAction {
|
||||
#[serde_as(as = "VecLogError<_>")]
|
||||
pub continuation_items: MapResult<Vec<VideoListItem<PlaylistVideo>>>,
|
||||
pub target_id: String,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,36 +4,32 @@ use serde::Deserialize;
|
|||
use serde_with::serde_as;
|
||||
use serde_with::{DefaultOnError, VecSkipError};
|
||||
|
||||
use crate::serializer::text::TextLink;
|
||||
use crate::serializer::MapResult;
|
||||
use crate::serializer::{
|
||||
ignore_any,
|
||||
text::{AccessibilityText, Text, TextLink, TextLinks},
|
||||
VecLogError,
|
||||
};
|
||||
|
||||
use super::{ContinuationEndpoint, Icon, Thumbnails, VideoListItem, VideoOwner};
|
||||
use super::{ContentsRenderer, ContinuationEndpoint, Icon, Thumbnails, VideoListItem, VideoOwner};
|
||||
|
||||
/// Video info response
|
||||
/*
|
||||
#VIDEO DETAILS
|
||||
*/
|
||||
|
||||
/// Video details response
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Video {
|
||||
pub struct VideoDetails {
|
||||
/// Video metadata + recommended videos
|
||||
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>,
|
||||
#[serde_as(as = "VecLogError<_>")]
|
||||
/// Video chapters + comment section
|
||||
pub engagement_panels: MapResult<Vec<EngagementPanel>>,
|
||||
}
|
||||
|
||||
/// Video details main object, contains video metadata and recommended videos
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Contents {
|
||||
|
|
@ -43,74 +39,92 @@ pub struct Contents {
|
|||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TwoColumnWatchNextResults {
|
||||
/// Metadata about the video
|
||||
pub results: VideoResultsWrap,
|
||||
/// Video recommendations
|
||||
pub secondary_results: RecommendationResultsWrap,
|
||||
}
|
||||
|
||||
/// Metadata about the video
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VideoResultsWrap {
|
||||
pub results: VideoResults,
|
||||
}
|
||||
|
||||
/// Video metadata items
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VideoResults {
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub contents: Vec<VideoResultsItem>,
|
||||
#[serde_as(as = "VecLogError<_>")]
|
||||
pub contents: MapResult<Vec<VideoResultsItem>>,
|
||||
}
|
||||
|
||||
/// Video metadata item
|
||||
#[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")]
|
||||
#[serde_as(as = "Text")]
|
||||
title: String,
|
||||
view_count: ViewCountWrap,
|
||||
view_count: ViewCount,
|
||||
/// Like/Dislike button
|
||||
video_actions: VideoActions,
|
||||
#[serde_as(as = "crate::serializer::text::Text")]
|
||||
/// Absolute textual date (e.g. `Dec 29, 2019`)
|
||||
#[serde_as(as = "Text")]
|
||||
date_text: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
VideoSecondaryInfoRenderer {
|
||||
owner: VideoOwner,
|
||||
#[serde_as(as = "crate::serializer::text::Text")]
|
||||
#[serde_as(as = "Text")]
|
||||
description: String,
|
||||
/// Additional metadata (e.g. Creative Commons License)
|
||||
#[serde(default)]
|
||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||
metadata_row_container: Option<MetadataRowContainer>,
|
||||
},
|
||||
/*
|
||||
/// The comment section consists of 2 ItemSectionRenderers:
|
||||
///
|
||||
/// 1. sectionIdentifier: "comments-entry-point", contains number of comments
|
||||
/// 2. sectionIdentifier: "comment-item-section", contains continuation token
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ItemSectionRenderer {
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
contents: Vec<ItemSection>,
|
||||
section_identifier: String,
|
||||
},
|
||||
*/
|
||||
#[serde(other, deserialize_with = "ignore_any")]
|
||||
None,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ViewCountWrap {
|
||||
pub video_view_count_renderer: ViewCount,
|
||||
pub struct ViewCount {
|
||||
pub video_view_count_renderer: ViewCountRenderer,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ViewCount {
|
||||
#[serde_as(as = "crate::serializer::text::Text")]
|
||||
pub struct ViewCountRenderer {
|
||||
#[serde_as(as = "Text")]
|
||||
pub view_count: String,
|
||||
}
|
||||
|
||||
/// Like/Dislike buttons
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VideoActions {
|
||||
pub menu_renderer: VideoActionsMenu,
|
||||
}
|
||||
|
||||
/// Like/Dislike buttons
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -119,58 +133,73 @@ pub struct VideoActionsMenu {
|
|||
pub top_level_buttons: Vec<ToggleButtonWrap>,
|
||||
}
|
||||
|
||||
/// Like/Dislike button
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ToggleButtonWrap {
|
||||
pub toggle_button_renderer: ToggleButton,
|
||||
}
|
||||
|
||||
/// Like/Dislike button
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ToggleButton {
|
||||
/// Icon type: `LIKE` / `DISLIKE`
|
||||
pub default_icon: Icon,
|
||||
#[serde_as(as = "crate::serializer::text::Text")]
|
||||
/// Number of likes (`4,010,157 likes`)
|
||||
#[serde_as(as = "AccessibilityText")]
|
||||
pub default_text: String,
|
||||
}
|
||||
|
||||
/// Shows additional video metadata. Its only known use is for
|
||||
/// the Creative Commonse License.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetadataRowContainer {
|
||||
pub metadata_row_container_renderer: MetadataRowContainerRenderer,
|
||||
}
|
||||
|
||||
/// Shows additional video metadata. Its only known use is for
|
||||
/// the Creative Commonse License.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetadataRowContainerRenderer {
|
||||
pub rows: Vec<MetadataRow>,
|
||||
}
|
||||
|
||||
/// Additional video metadata item (Creative Commons License)
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MetadataRow {
|
||||
pub metadata_row_renderer: MetadataRowRenderer,
|
||||
}
|
||||
|
||||
/// Additional video metadata item (Creative Commons License)
|
||||
#[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>")]
|
||||
// `License`
|
||||
// #[serde_as(as = "Text")]
|
||||
// pub title: String,
|
||||
/// Creative commons license:
|
||||
///
|
||||
/// Text (en): `Creative Commons Attribution license (reuse allowed)`
|
||||
///
|
||||
/// URL: `https://www.youtube.com/t/creative_commons`
|
||||
#[serde_as(as = "Vec<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")]
|
||||
#[serde_as(as = "Text")]
|
||||
comment_count: String,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -178,49 +207,57 @@ pub enum ItemSection {
|
|||
continuation_endpoint: ContinuationEndpoint,
|
||||
},
|
||||
}
|
||||
*/
|
||||
|
||||
/// Video recommendations
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RecommendationResultsWrap {
|
||||
pub secondary_results: RecommendationResults,
|
||||
}
|
||||
|
||||
/// Video recommendations
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RecommendationResults {
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub results: Vec<VideoListItem<RecommendedVideo>>,
|
||||
#[serde_as(as = "VecLogError<_>")]
|
||||
pub results: MapResult<Vec<VideoListItem<RecommendedVideo>>>,
|
||||
}
|
||||
|
||||
/// Video recommendation item
|
||||
#[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")]
|
||||
#[serde_as(as = "Text")]
|
||||
pub title: String,
|
||||
#[serde(rename = "shortBylineText")]
|
||||
#[serde_as(as = "crate::serializer::text::TextLink")]
|
||||
#[serde_as(as = "TextLink")]
|
||||
pub channel: TextLink,
|
||||
#[serde_as(as = "Option<crate::serializer::text::Text>")]
|
||||
#[serde_as(as = "Option<Text>")]
|
||||
pub length_text: Option<String>,
|
||||
#[serde_as(as = "Option<crate::serializer::text::Text>")]
|
||||
#[serde_as(as = "Option<Text>")]
|
||||
pub published_time_text: Option<String>,
|
||||
#[serde_as(as = "crate::serializer::text::Text")]
|
||||
#[serde_as(as = "Text")]
|
||||
pub view_count_text: String,
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "VecSkipError<_>")]
|
||||
pub badges: Vec<VideoBadge>,
|
||||
}
|
||||
|
||||
/// Badges are displayed on the video thumbnail and
|
||||
/// show certain video properties (e.g. active livestream)
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VideoBadge {
|
||||
pub metadata_badge_renderer: VideoBadgeRenderer,
|
||||
}
|
||||
|
||||
/// Badges are displayed on the video thumbnail and
|
||||
/// show certain video properties (e.g. active livestream)
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VideoBadgeRenderer {
|
||||
|
|
@ -230,63 +267,181 @@ pub struct VideoBadgeRenderer {
|
|||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum VideoBadgeStyle {
|
||||
/// Active livestream
|
||||
BadgeStyleTypeLiveNow,
|
||||
}
|
||||
|
||||
/// The engagement panels are displayed below the video and contain chapter markers
|
||||
/// and the comment section.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EngagementPanel {
|
||||
pub engagement_panel_section_list_renderer: EngagementPanelRenderer,
|
||||
}
|
||||
|
||||
/// The engagement panels are displayed below the video and contain chapter markers
|
||||
/// and the comment section.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EngagementPanelRenderer {
|
||||
pub header: EngagementPanelHeader,
|
||||
#[serde(rename_all = "kebab-case", tag = "panelIdentifier")]
|
||||
pub enum EngagementPanelRenderer {
|
||||
/// Chapter markers
|
||||
EngagementPanelMacroMarkersDescriptionChapters { content: ChapterMarkersContent },
|
||||
/// Comment section (contains no comments, but the
|
||||
/// continuation tokens for fetching top/latest comments)
|
||||
CommentItemSection { header: CommentItemSectionHeader },
|
||||
/// Ignored items:
|
||||
/// - `engagement-panel-ads`
|
||||
/// - `engagement-panel-structured-description`
|
||||
/// (Desctiption already included in `VideoSecondaryInfoRenderer`)
|
||||
/// - `engagement-panel-searchable-transcript`
|
||||
/// (basically video subtitles in a different format)
|
||||
#[serde(other, deserialize_with = "ignore_any")]
|
||||
None,
|
||||
}
|
||||
|
||||
/// Chapter markers
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EngagementPanelHeader {
|
||||
pub engagement_panel_title_header_renderer: EngagementPanelHeaderRenderer,
|
||||
pub struct ChapterMarkersContent {
|
||||
pub macro_markers_list_renderer: ContentsRenderer<MacroMarkersListItem>,
|
||||
}
|
||||
|
||||
/// Chapter marker
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EngagementPanelHeaderRenderer {
|
||||
pub menu: EngagementPanelMenu,
|
||||
pub struct MacroMarkersListItem {
|
||||
pub macro_markers_list_item_renderer: MacroMarkersListItemRenderer,
|
||||
}
|
||||
|
||||
/// Chapter marker
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EngagementPanelMenu {
|
||||
pub sort_filter_sub_menu_renderer: EngagementPanelMenuRenderer,
|
||||
pub struct MacroMarkersListItemRenderer {
|
||||
/// Contains chapter start time in seconds
|
||||
pub on_tap: MacroMarkersListItemOnTap,
|
||||
pub thumbnail: Thumbnails,
|
||||
/// Textual time (`1:42`)
|
||||
#[serde_as(as = "Text")]
|
||||
pub time_description: String,
|
||||
/// Chapter title
|
||||
#[serde_as(as = "Text")]
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
/// Contains chapter start time in seconds
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EngagementPanelMenuRenderer {
|
||||
pub sub_menu_items: Vec<EngagementPanelMenuItem>,
|
||||
pub struct MacroMarkersListItemOnTap {
|
||||
pub watch_endpoint: MacroMarkersListItemWatchEndpoint,
|
||||
}
|
||||
/// Contains chapter start time in seconds
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct MacroMarkersListItemWatchEndpoint {
|
||||
/// Chapter start time in seconds
|
||||
pub start_time_seconds: u32,
|
||||
}
|
||||
|
||||
/// Comment section header
|
||||
/// (contains continuation tokens for fetching top/latest comments)
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EngagementPanelMenuItem {
|
||||
pub struct CommentItemSectionHeader {
|
||||
pub engagement_panel_title_header_renderer: CommentItemSectionHeaderRenderer,
|
||||
}
|
||||
|
||||
/// Comment section header
|
||||
/// (contains continuation tokens for fetching top/latest comments)
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommentItemSectionHeaderRenderer {
|
||||
/// Average comment count (e.g. `81`, `2.2K`, `705K`)
|
||||
///
|
||||
/// The accurate count is included in the first comment response.
|
||||
#[serde_as(as = "Text")]
|
||||
pub contextual_info: String,
|
||||
pub menu: CommentItemSectionHeaderMenu,
|
||||
}
|
||||
|
||||
/// Comment section menu
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommentItemSectionHeaderMenu {
|
||||
pub sort_filter_sub_menu_renderer: CommentItemSectionHeaderMenuRenderer,
|
||||
}
|
||||
|
||||
/// Comment section menu
|
||||
///
|
||||
/// Items:
|
||||
/// - Top comments
|
||||
/// - Latest comments
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommentItemSectionHeaderMenuRenderer {
|
||||
pub sub_menu_items: Vec<CommentItemSectionHeaderMenuItem>,
|
||||
}
|
||||
|
||||
/// Comment section menu item
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommentItemSectionHeaderMenuItem {
|
||||
/// Continuation token for fetching comments
|
||||
pub service_endpoint: ContinuationEndpoint,
|
||||
}
|
||||
|
||||
/*
|
||||
#RECOMMENDATIONS CONTINUATION
|
||||
*/
|
||||
|
||||
/// Video recommendations continuation response
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VideoRecommendations {
|
||||
pub on_response_received_endpoints: Vec<RecommendationsContItem>,
|
||||
}
|
||||
|
||||
/// Video recommendations continuation
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RecommendationsContItem {
|
||||
pub append_continuation_items_action: AppendRecommendations,
|
||||
}
|
||||
|
||||
/// Video recommendations continuation
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AppendRecommendations {
|
||||
pub continuation_items: Vec<VideoListItem<RecommendedVideo>>,
|
||||
#[serde_as(as = "VecLogError<_>")]
|
||||
pub continuation_items: MapResult<Vec<VideoListItem<RecommendedVideo>>>,
|
||||
}
|
||||
|
||||
/*
|
||||
#COMMENTS CONTINUATION
|
||||
*/
|
||||
|
||||
/// Video comments continuation response
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VideoComments {
|
||||
/// - Initial response: 2*reloadContinuationItemsCommand
|
||||
/// - 1*commentsHeaderRenderer: number of comments
|
||||
/// - n*commentThreadRenderer, continuationItemRenderer:
|
||||
/// comments + continuation
|
||||
/// - Continuation response: appendContinuationItemsAction
|
||||
/// - n*commentThreadRenderer, continuationItemRenderer:
|
||||
/// comments + continuation
|
||||
/// - Comment replies: appendContinuationItemsAction
|
||||
/// - n*commentRenderer, continuationItemRenderer:
|
||||
/// replies + continuation
|
||||
#[serde_as(as = "VecLogError<_>")]
|
||||
pub on_response_received_endpoints: MapResult<Vec<CommentsContItem>>,
|
||||
}
|
||||
|
||||
/// Video comments continuation
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommentsContItem {
|
||||
|
|
@ -294,40 +449,46 @@ pub struct CommentsContItem {
|
|||
pub append_continuation_items_action: AppendComments,
|
||||
}
|
||||
|
||||
/// Video comments continuation action
|
||||
#[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(as = "VecLogError<_>")]
|
||||
pub continuation_items: MapResult<Vec<CommentListItem>>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum CommentListItem {
|
||||
/// Top-level comment
|
||||
#[serde(rename_all = "camelCase")]
|
||||
CommentThreadRenderer {
|
||||
comment: Comment,
|
||||
/// Continuation token to fetch replies
|
||||
#[serde(default)]
|
||||
replies: Replies,
|
||||
#[serde(default)]
|
||||
#[serde_as(deserialize_as = "DefaultOnError")]
|
||||
rendering_priority: CommentPriority,
|
||||
},
|
||||
/// Reply comment
|
||||
CommentRenderer {
|
||||
#[serde(flatten)]
|
||||
comment: CommentRenderer,
|
||||
},
|
||||
/// Continuation token to fetch more comments
|
||||
#[serde(rename_all = "camelCase")]
|
||||
ContinuationItemRenderer {
|
||||
continuation_endpoint: ContinuationEndpoint,
|
||||
},
|
||||
|
||||
// TODO: TMP
|
||||
/// Header of the comment section (contains number of comments)
|
||||
#[serde(rename_all = "camelCase")]
|
||||
CommentsHeaderRenderer { count_text: Option<String> },
|
||||
CommentsHeaderRenderer {
|
||||
#[serde_as(as = "Text")]
|
||||
count_text: Vec<String>
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
|
@ -340,22 +501,26 @@ pub struct Comment {
|
|||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommentRenderer {
|
||||
// There may be comments with missing authors (possibly deleted users?)
|
||||
/// Author name
|
||||
///
|
||||
/// There may be comments with missing authors (possibly deleted users?)
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "DefaultOnError<Option<crate::serializer::text::Text>>")]
|
||||
#[serde_as(as = "DefaultOnError<Option<Text>>")]
|
||||
pub author_text: Option<String>,
|
||||
pub author_thumbnail: Thumbnails,
|
||||
#[serde(default)]
|
||||
/// ID of the author's channel
|
||||
#[serde_as(as = "DefaultOnError")]
|
||||
pub author_endpoint: Option<AuthorEndpoint>,
|
||||
|
||||
#[serde_as(as = "crate::serializer::text::Text")]
|
||||
/// Comment text
|
||||
#[serde_as(as = "Text")]
|
||||
pub content_text: String,
|
||||
#[serde_as(as = "crate::serializer::text::Text")]
|
||||
/// Textual publish date (e.g. `15 minutes ago`, `2 days ago`)
|
||||
#[serde_as(as = "Text")]
|
||||
pub published_time_text: String,
|
||||
pub comment_id: String,
|
||||
pub author_is_channel_owner: bool,
|
||||
#[serde_as(as = "Option<crate::serializer::text::Text>")]
|
||||
#[serde_as(as = "Option<Text>")]
|
||||
pub vote_count: Option<String>,
|
||||
pub author_comment_badge: Option<AuthorCommentBadge>,
|
||||
#[serde(default)]
|
||||
|
|
@ -378,17 +543,23 @@ pub struct BrowseEndpoint {
|
|||
#[derive(Default, Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum CommentPriority {
|
||||
/// Default rendering priority
|
||||
#[default]
|
||||
RenderingPriorityUnknown,
|
||||
/// Comment pinned by the creator
|
||||
RenderingPriorityPinnedComment,
|
||||
}
|
||||
|
||||
/// Does not contain replies directly but a continuation token
|
||||
/// for fetching them.
|
||||
#[derive(Default, Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Replies {
|
||||
pub comment_replies_renderer: RepliesRenderer,
|
||||
}
|
||||
|
||||
/// Does not contain replies directly but a continuation token
|
||||
/// for fetching them.
|
||||
#[serde_as]
|
||||
#[derive(Default, Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -397,24 +568,28 @@ pub struct RepliesRenderer {
|
|||
pub contents: Vec<CommentListItem>,
|
||||
}
|
||||
|
||||
/// These are the buttons for comment interaction. Contains the CreatorHeart.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommentActionButtons {
|
||||
pub comment_action_buttons_renderer: CommentActionButtonsRenderer,
|
||||
}
|
||||
|
||||
/// These are the buttons for comment interaction. Contains the CreatorHeart.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommentActionButtonsRenderer {
|
||||
pub creator_heart: Option<CreatorHeart>,
|
||||
}
|
||||
|
||||
/// Video creators can endorse comments by marking them with a ❤️.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreatorHeart {
|
||||
pub creator_heart_renderer: CreatorHeartRenderer,
|
||||
}
|
||||
|
||||
/// Video creators can endorse comments by marking them with a ❤️.
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CreatorHeartRenderer {
|
||||
|
|
@ -427,8 +602,10 @@ pub struct AuthorCommentBadge {
|
|||
pub author_comment_badge_renderer: AuthorCommentBadgeRenderer,
|
||||
}
|
||||
|
||||
/// YouTube channel badge (verified) of the comment author
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthorCommentBadgeRenderer {
|
||||
/// Verified: `CHECK`
|
||||
pub icon: Icon,
|
||||
}
|
||||
Reference in a new issue