#![allow(clippy::enum_variant_names)]
use serde::Deserialize;
use serde_with::{rust::deserialize_ignore_any, serde_as, DefaultOnError, VecSkipError};
use crate::serializer::{
text::{AccessibilityText, AttributedText, Text, TextComponent, TextComponents},
MapResult,
};
use super::{
url_endpoint::BrowseEndpointWrap, ContinuationEndpoint, ContinuationItemRenderer, Icon,
MusicContinuationData, Thumbnails,
};
use super::{
ChannelBadge, ContentsRendererLogged, FrameworkUpdates, ImageView, ResponseContext,
YouTubeListItem,
};
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VideoDetails {
pub contents: Option<Contents>,
pub current_video_endpoint: Option<CurrentVideoEndpoint>,
pub engagement_panels: MapResult<Vec<EngagementPanel>>,
pub response_context: ResponseContext,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Contents {
pub two_column_watch_next_results: TwoColumnWatchNextResults,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct TwoColumnWatchNextResults {
pub results: VideoResultsWrap,
pub secondary_results: Option<RecommendationResultsWrap>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VideoResultsWrap {
pub results: VideoResults,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VideoResults {
pub contents: Option<MapResult<Vec<VideoResultsItem>>>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) enum VideoResultsItem {
#[serde(rename_all = "camelCase")]
VideoPrimaryInfoRenderer {
#[serde_as(as = "Text")]
title: String,
view_count: Option<ViewCount>,
video_actions: VideoActions,
#[serde_as(as = "Option<Text>")]
date_text: Option<String>,
},
#[serde(rename_all = "camelCase")]
VideoSecondaryInfoRenderer {
owner: VideoOwner,
description: Option<TextComponents>,
#[serde_as(as = "Option<AttributedText>")]
attributed_description: Option<TextComponents>,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
metadata_row_container: Option<MetadataRowContainer>,
},
ItemSectionRenderer(#[serde_as(deserialize_as = "DefaultOnError")] ItemSection),
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ViewCount {
pub video_view_count_renderer: ViewCountRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ViewCountRenderer {
#[serde_as(as = "Text")]
pub view_count: String,
#[serde(default)]
pub is_live: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VideoActions {
pub menu_renderer: VideoActionsMenu,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VideoActionsMenu {
#[serde_as(as = "VecSkipError<_>")]
pub top_level_buttons: Vec<TopLevelButton>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) enum TopLevelButton {
ToggleButtonRenderer(ToggleButton),
#[serde(rename_all = "camelCase")]
SegmentedLikeDislikeButtonRenderer {
like_button: ToggleButtonWrap,
},
#[serde(rename_all = "camelCase")]
SegmentedLikeDislikeButtonViewModel {
like_button_view_model: LikeButtonViewModelWrap,
},
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LikeButtonViewModelWrap {
pub like_button_view_model: LikeButtonViewModel,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct LikeButtonViewModel {
pub toggle_button_view_model: ToggleButtonViewModelWrap,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ToggleButtonViewModelWrap {
pub toggle_button_view_model: ToggleButtonViewModel,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ToggleButtonViewModel {
pub default_button_view_model: ButtonViewModelWrap,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ButtonViewModelWrap {
pub button_view_model: ButtonViewModel,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ButtonViewModel {
pub accessibility_text: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ToggleButtonWrap {
pub toggle_button_renderer: ToggleButton,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ToggleButton {
pub default_icon: Icon,
#[serde_as(as = "AccessibilityText")]
pub accessibility_data: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VideoOwner {
pub video_owner_renderer: VideoOwnerRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VideoOwnerRenderer {
pub title: TextComponent,
pub thumbnail: Thumbnails,
#[serde_as(as = "Option<Text>")]
pub subscriber_count_text: Option<String>,
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub badges: Vec<ChannelBadge>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MetadataRowContainer {
pub metadata_row_container_renderer: MetadataRowContainerRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MetadataRowContainerRenderer {
pub rows: Vec<MetadataRow>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MetadataRow {
pub metadata_row_renderer: MetadataRowRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MetadataRowRenderer {
pub contents: Vec<TextComponents>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CurrentVideoEndpoint {
pub watch_endpoint: CurrentVideoWatchEndpoint,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CurrentVideoWatchEndpoint {
pub video_id: String,
}
#[serde_as]
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "sectionIdentifier")]
pub(crate) enum ItemSection {
CommentsEntryPoint {
#[serde_as(as = "VecSkipError<_>")]
contents: Vec<ItemSectionCommentCount>,
},
CommentItemSection {
#[serde_as(as = "VecSkipError<_>")]
contents: Vec<ItemSectionComments>,
},
#[default]
None,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ItemSectionCommentCount {
pub comments_entry_point_header_renderer: CommentsEntryPointHeaderRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentsEntryPointHeaderRenderer {
#[serde_as(as = "Text")]
pub comment_count: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ItemSectionComments {
pub continuation_item_renderer: ContinuationItemRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RecommendationResultsWrap {
pub secondary_results: RecommendationResults,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RecommendationResults {
pub results: Option<MapResult<Vec<YouTubeListItem>>>,
#[serde_as(as = "Option<VecSkipError<_>>")]
pub continuations: Option<Vec<MusicContinuationData>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct EngagementPanel {
pub engagement_panel_section_list_renderer: EngagementPanelRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "targetId")]
pub(crate) enum EngagementPanelRenderer {
EngagementPanelMacroMarkersDescriptionChapters { content: ChapterMarkersContent },
EngagementPanelCommentsSection { header: CommentItemSectionHeader },
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ChapterMarkersContent {
pub macro_markers_list_renderer: ContentsRendererLogged<MacroMarkersListItem>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MacroMarkersListItem {
pub macro_markers_list_item_renderer: MacroMarkersListItemRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MacroMarkersListItemRenderer {
pub on_tap: MacroMarkersListItemOnTap,
#[serde(default)]
pub thumbnail: Thumbnails,
#[serde_as(as = "Text")]
pub title: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MacroMarkersListItemOnTap {
pub watch_endpoint: MacroMarkersListItemWatchEndpoint,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct MacroMarkersListItemWatchEndpoint {
pub start_time_seconds: u32,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentItemSectionHeader {
pub engagement_panel_title_header_renderer: CommentItemSectionHeaderRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentItemSectionHeaderRenderer {
pub menu: CommentItemSectionHeaderMenu,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentItemSectionHeaderMenu {
pub sort_filter_sub_menu_renderer: CommentItemSectionHeaderMenuRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentItemSectionHeaderMenuRenderer {
pub sub_menu_items: Vec<CommentItemSectionHeaderMenuItem>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentItemSectionHeaderMenuItem {
pub service_endpoint: ContinuationEndpoint,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VideoComments {
pub on_response_received_endpoints: MapResult<Vec<CommentsContItem>>,
pub framework_updates: Option<FrameworkUpdates<Payload>>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentsContItem {
#[serde(alias = "reloadContinuationItemsCommand")]
pub append_continuation_items_action: AppendComments,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AppendComments {
pub continuation_items: MapResult<Vec<CommentListItem>>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) enum CommentListItem {
CommentThreadRenderer(CommentThreadRenderer),
CommentRenderer(CommentRenderer),
CommentViewModel(CommentViewModel),
ContinuationItemRenderer(ContinuationItemVariants),
#[serde(rename_all = "camelCase")]
CommentsHeaderRenderer {
#[serde_as(as = "Option<Text>")]
count_text: Option<String>,
},
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub(crate) enum ContinuationItemVariants {
#[serde(rename_all = "camelCase")]
Ep {
continuation_endpoint: ContinuationEndpoint,
},
Btn {
button: ContinuationButton,
},
}
impl ContinuationItemVariants {
pub fn into_token(self) -> Option<String> {
match self {
ContinuationItemVariants::Ep {
continuation_endpoint,
} => continuation_endpoint,
ContinuationItemVariants::Btn { button } => button.button_renderer.command,
}
.into_token()
}
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentThreadRenderer {
pub comment: Option<Comment>,
pub comment_view_model: Option<CommentViewModelWrap>,
#[serde(default)]
pub replies: Replies,
#[serde(default)]
#[serde_as(deserialize_as = "DefaultOnError")]
pub rendering_priority: CommentPriority,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Comment {
pub comment_renderer: CommentRenderer,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentRenderer {
#[serde(default)]
#[serde_as(as = "DefaultOnError<Option<Text>>")]
pub author_text: Option<String>,
#[serde(default)]
pub author_thumbnail: Thumbnails,
#[serde(default)]
#[serde_as(as = "DefaultOnError")]
pub author_endpoint: Option<BrowseEndpointWrap>,
pub content_text: TextComponents,
#[serde_as(as = "Text")]
pub published_time_text: String,
pub comment_id: String,
pub author_is_channel_owner: bool,
pub author_comment_badge: Option<AuthorCommentBadge>,
#[serde(default)]
pub reply_count: u64,
#[serde_as(as = "Option<Text>")]
pub vote_count: Option<String>,
pub action_buttons: CommentActionButtons,
}
#[derive(Default, Clone, Copy, Debug, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub(crate) enum CommentPriority {
#[default]
RenderingPriorityUnknown,
RenderingPriorityPinnedComment,
}
impl From<CommentPriority> for bool {
fn from(value: CommentPriority) -> Self {
matches!(value, CommentPriority::RenderingPriorityPinnedComment)
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentViewModelWrap {
pub comment_view_model: CommentViewModel,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentViewModel {
pub comment_id: String,
pub comment_key: String,
pub comment_surface_key: String,
pub toolbar_state_key: String,
}
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Replies {
pub comment_replies_renderer: RepliesRenderer,
}
#[serde_as]
#[derive(Default, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct RepliesRenderer {
#[serde_as(as = "VecSkipError<_>")]
pub contents: Vec<CommentListItem>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentActionButtons {
pub comment_action_buttons_renderer: CommentActionButtonsRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentActionButtonsRenderer {
pub creator_heart: Option<CreatorHeart>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CreatorHeart {
pub creator_heart_renderer: CreatorHeartRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CreatorHeartRenderer {
pub is_hearted: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AuthorCommentBadge {
pub author_comment_badge_renderer: AuthorCommentBadgeRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AuthorCommentBadgeRenderer {
pub icon: Icon,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) enum Payload {
CommentEntityPayload(CommentEntityPayload),
CommentSurfaceEntityPayload(CommentSurfaceEntityPayload),
#[serde(rename_all = "camelCase")]
EngagementToolbarStateEntityPayload {
heart_state: HeartState,
},
#[serde(other, deserialize_with = "deserialize_ignore_any")]
None,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentEntityPayload {
pub properties: CommentProperties,
#[serde(default)]
#[serde_as(as = "DefaultOnError")]
pub author: Option<CommentAuthor>,
pub toolbar: CommentToolbar,
#[serde(default)]
pub avatar: ImageView,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentSurfaceEntityPayload {
pub voice_reply_container_view_model: Option<VoiceReplyContainer>,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentProperties {
#[serde_as(as = "AttributedText")]
pub content: TextComponents,
pub published_time: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentAuthor {
pub channel_id: String,
pub display_name: String,
#[serde(default)]
pub is_verified: bool,
#[serde(default)]
pub is_artist: bool,
#[serde(default)]
pub is_creator: bool,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CommentToolbar {
pub like_count_notliked: String,
pub reply_count: String,
}
#[derive(Debug, Copy, Clone, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub(crate) enum HeartState {
ToolbarHeartStateUnhearted,
ToolbarHeartStateHearted,
}
impl From<HeartState> for bool {
fn from(value: HeartState) -> Self {
match value {
HeartState::ToolbarHeartStateUnhearted => false,
HeartState::ToolbarHeartStateHearted => true,
}
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContinuationButton {
pub button_renderer: ContinuationButtonRenderer,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContinuationButtonRenderer {
pub command: ContinuationEndpoint,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VoiceReplyContainer {
pub voice_reply_container_view_model: VoiceReplyContainer2,
}
#[serde_as]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct VoiceReplyContainer2 {
#[serde_as(as = "AttributedText")]
pub transcript_text: TextComponents,
}