feat: add video details mapping

- TODO: fix fetching comment count
This commit is contained in:
ThetaDev 2022-09-20 16:14:57 +02:00
parent df6543d62e
commit e800e16c68
13 changed files with 38081 additions and 179 deletions

View file

@ -68,6 +68,9 @@ pub enum VideoListItem<T> {
continuation_endpoint: ContinuationEndpoint,
},
/// No video list item (e.g. ad)
///
/// Note that there are sometimes playlists among the recommended
/// videos. They are currently ignored.
#[serde(other, deserialize_with = "ignore_any")]
None,
}
@ -84,10 +87,22 @@ pub struct ContinuationCommand {
pub token: String,
}
#[serde_as]
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Icon {
pub icon_type: String,
pub icon_type: IconType,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum IconType {
/// Checkmark for verified channels
Check,
/// Music note for verified artists
OfficialArtistBadge,
/// Like button
Like,
}
#[derive(Clone, Debug, Deserialize)]
@ -107,24 +122,24 @@ pub struct VideoOwnerRenderer {
pub subscriber_count_text: Option<String>,
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub badges: Vec<UserBadge>,
pub badges: Vec<ChannelBadge>,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserBadge {
pub metadata_badge_renderer: UserBadgeRenderer,
pub struct ChannelBadge {
pub metadata_badge_renderer: ChannelBadgeRenderer,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UserBadgeRenderer {
pub style: UserBadgeStyle,
pub struct ChannelBadgeRenderer {
pub style: ChannelBadgeStyle,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum UserBadgeStyle {
pub enum ChannelBadgeStyle {
BadgeStyleTypeVerified,
BadgeStyleTypeVerifiedArtist,
}
@ -155,6 +170,29 @@ pub enum TimeOverlayStyle {
Shorts,
}
/// 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 {
pub style: VideoBadgeStyle,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum VideoBadgeStyle {
/// Active livestream
BadgeStyleTypeLiveNow,
}
// YouTube Music
#[serde_as]
@ -247,3 +285,36 @@ impl From<Thumbnails> for Vec<crate::model::Thumbnail> {
.collect()
}
}
impl From<Vec<ChannelBadge>> for crate::model::Verification {
fn from(badges: Vec<ChannelBadge>) -> Self {
badges.get(0).map_or(crate::model::Verification::None, |b| {
match b.metadata_badge_renderer.style {
ChannelBadgeStyle::BadgeStyleTypeVerified => Self::Verified,
ChannelBadgeStyle::BadgeStyleTypeVerifiedArtist => Self::Artist,
}
})
}
}
impl From<Icon> for crate::model::Verification {
fn from(icon: Icon) -> Self {
match icon.icon_type {
IconType::Check => Self::Verified,
IconType::OfficialArtistBadge => Self::Artist,
_ => Self::None,
}
}
}
pub trait IsLive {
fn is_live(&self) -> bool;
}
impl IsLive for Vec<VideoBadge> {
fn is_live(&self) -> bool {
self.iter().any(|badge| {
badge.metadata_badge_renderer.style == VideoBadgeStyle::BadgeStyleTypeLiveNow
})
}
}

View file

@ -11,7 +11,10 @@ use crate::serializer::{
VecLogError,
};
use super::{ContentsRenderer, ContinuationEndpoint, Icon, Thumbnails, VideoListItem, VideoOwner};
use super::{
ChannelBadge, ContentsRenderer, ContinuationEndpoint, Icon, Thumbnails, VideoBadge,
VideoListItem, VideoOwner,
};
/*
#VIDEO DETAILS
@ -24,6 +27,8 @@ use super::{ContentsRenderer, ContinuationEndpoint, Icon, Thumbnails, VideoListI
pub struct VideoDetails {
/// Video metadata + recommended videos
pub contents: Contents,
/// Video ID
pub current_video_endpoint: CurrentVideoEndpoint,
#[serde_as(as = "VecLogError<_>")]
/// Video chapters + comment section
pub engagement_panels: MapResult<Vec<EngagementPanel>>,
@ -113,6 +118,7 @@ pub struct ViewCount {
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ViewCountRenderer {
/// View count (`232,975,196 views`)
#[serde_as(as = "Text")]
pub view_count: String,
}
@ -147,9 +153,9 @@ pub struct ToggleButtonWrap {
pub struct ToggleButton {
/// Icon type: `LIKE` / `DISLIKE`
pub default_icon: Icon,
/// Number of likes (`4,010,157 likes`)
/// Number of likes (`like this video along with 4,010,156 other people`)
#[serde_as(as = "AccessibilityText")]
pub default_text: String,
pub accessibility_data: String,
}
/// Shows additional video metadata. Its only known use is for
@ -192,22 +198,18 @@ pub struct MetadataRowRenderer {
pub contents: Vec<Vec<TextLink>>,
}
/*
#[serde_as]
/// Contains current video ID
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ItemSection {
#[serde(rename_all = "camelCase")]
CommentsEntryPointHeaderRenderer {
#[serde_as(as = "Text")]
comment_count: String,
},
#[serde(rename_all = "camelCase")]
ContinuationItemRenderer {
continuation_endpoint: ContinuationEndpoint,
},
pub struct CurrentVideoEndpoint {
pub watch_endpoint: CurrentVideoWatchEndpoint,
}
/// Contains current video ID
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CurrentVideoWatchEndpoint {
pub video_id: String,
}
*/
/// Video recommendations
#[derive(Clone, Debug, Deserialize)]
@ -237,40 +239,25 @@ pub struct RecommendedVideo {
#[serde(rename = "shortBylineText")]
#[serde_as(as = "TextLink")]
pub channel: TextLink,
pub channel_thumbnail: Thumbnails,
/// Channel verification badge
#[serde(default)]
#[serde_as(as = "VecSkipError<_>")]
pub owner_badges: Vec<ChannelBadge>,
#[serde_as(as = "Option<Text>")]
pub length_text: Option<String>,
/// (e.g. `11 months ago`)
#[serde_as(as = "Option<Text>")]
pub published_time_text: Option<String>,
#[serde_as(as = "Text")]
pub view_count_text: String,
/// Badges are displayed on the video thumbnail and
/// show certain video properties (e.g. active livestream)
#[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 {
pub style: VideoBadgeStyle,
}
#[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)]
@ -282,13 +269,13 @@ pub struct EngagementPanel {
/// The engagement panels are displayed below the video and contain chapter markers
/// and the comment section.
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "panelIdentifier")]
#[serde(rename_all = "kebab-case", tag = "targetId")]
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 },
EngagementPanelCommentsSection { header: CommentItemSectionHeader },
/// Ignored items:
/// - `engagement-panel-ads`
/// - `engagement-panel-structured-description`
@ -486,8 +473,9 @@ pub enum CommentListItem {
/// Header of the comment section (contains number of comments)
#[serde(rename_all = "camelCase")]
CommentsHeaderRenderer {
#[serde_as(as = "Text")]
count_text: Vec<String>
/// `4,238,993 Comments`
#[serde_as(as = "Option<Text>")]
count_text: Option<String>,
},
}
@ -520,11 +508,12 @@ pub struct CommentRenderer {
pub published_time_text: String,
pub comment_id: String,
pub author_is_channel_owner: bool,
#[serde_as(as = "Option<Text>")]
pub vote_count: Option<String>,
// #[serde_as(as = "Option<Text>")]
// pub vote_count: Option<String>,
pub author_comment_badge: Option<AuthorCommentBadge>,
#[serde(default)]
pub reply_count: u32,
/// Buttons for comment interaction (Like/Dislike/Reply)
pub action_buttons: CommentActionButtons,
}
@ -568,17 +557,20 @@ pub struct RepliesRenderer {
pub contents: Vec<CommentListItem>,
}
/// These are the buttons for comment interaction. Contains the CreatorHeart.
/// These are the buttons for comment interaction (Like/Dislike/Reply).
/// 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.
/// These are the buttons for comment interaction (Like/Dislike/Reply).
/// Contains the CreatorHeart.
#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommentActionButtonsRenderer {
pub like_button: ToggleButtonWrap,
pub creator_heart: Option<CreatorHeart>,
}
@ -607,5 +599,7 @@ pub struct AuthorCommentBadge {
#[serde(rename_all = "camelCase")]
pub struct AuthorCommentBadgeRenderer {
/// Verified: `CHECK`
///
/// Artist: `OFFICIAL_ARTIST_BADGE`
pub icon: Icon,
}