From fcd30449821763e9b5b57718dd02eff15d964d2b Mon Sep 17 00:00:00 2001 From: absidue <48293849+absidue@users.noreply.github.com> Date: Thu, 21 Dec 2023 23:02:19 +0100 Subject: [PATCH] feat(parser): Support new like and dislike nodes (#557) --- src/parser/classes/DislikeButtonView.ts | 16 ++++++ src/parser/classes/LikeButtonView.ts | 24 ++++++++ .../classes/SegmentedLikeDislikeButtonView.ts | 56 +++++++++++++++++++ src/parser/classes/ToggleButtonView.ts | 20 +++++++ src/parser/nodes.ts | 4 ++ src/parser/youtube/VideoInfo.ts | 12 ++++ 6 files changed, 132 insertions(+) create mode 100644 src/parser/classes/DislikeButtonView.ts create mode 100644 src/parser/classes/LikeButtonView.ts create mode 100644 src/parser/classes/SegmentedLikeDislikeButtonView.ts create mode 100644 src/parser/classes/ToggleButtonView.ts diff --git a/src/parser/classes/DislikeButtonView.ts b/src/parser/classes/DislikeButtonView.ts new file mode 100644 index 000000000..8656a72a0 --- /dev/null +++ b/src/parser/classes/DislikeButtonView.ts @@ -0,0 +1,16 @@ +import { YTNode } from '../helpers.js'; +import { Parser, type RawNode } from '../index.js'; +import ToggleButtonView from './ToggleButtonView.js'; + +export default class DislikeButtonView extends YTNode { + static type = 'DislikeButtonView'; + + toggle_button: ToggleButtonView | null; + dislike_entity_key: string; + + constructor(data: RawNode) { + super(); + this.toggle_button = Parser.parseItem(data.toggleButtonViewModel, ToggleButtonView); + this.dislike_entity_key = data.dislikeEntityKey; + } +} \ No newline at end of file diff --git a/src/parser/classes/LikeButtonView.ts b/src/parser/classes/LikeButtonView.ts new file mode 100644 index 000000000..e97bb8ace --- /dev/null +++ b/src/parser/classes/LikeButtonView.ts @@ -0,0 +1,24 @@ +import { YTNode } from '../helpers.js'; +import { Parser, type RawNode } from '../index.js'; +import ToggleButtonView from './ToggleButtonView.js'; + +export default class LikeButtonView extends YTNode { + static type = 'LikeButtonView'; + + toggle_button: ToggleButtonView | null; + like_status_entity_key: string; + like_status_entity: { + key: string, + like_status: string + }; + + constructor(data: RawNode) { + super(); + this.toggle_button = Parser.parseItem(data.toggleButtonViewModel, ToggleButtonView); + this.like_status_entity_key = data.likeStatusEntityKey; + this.like_status_entity = { + key: data.likeStatusEntity.key, + like_status: data.likeStatusEntity.likeStatus + }; + } +} \ No newline at end of file diff --git a/src/parser/classes/SegmentedLikeDislikeButtonView.ts b/src/parser/classes/SegmentedLikeDislikeButtonView.ts new file mode 100644 index 000000000..0ef577c2f --- /dev/null +++ b/src/parser/classes/SegmentedLikeDislikeButtonView.ts @@ -0,0 +1,56 @@ +import { YTNode } from '../helpers.js'; +import { Parser, type RawNode } from '../index.js'; +import LikeButtonView from './LikeButtonView.js'; +import DislikeButtonView from './DislikeButtonView.js'; + +export default class SegmentedLikeDislikeButtonView extends YTNode { + static type = 'SegmentedLikeDislikeButtonView'; + + like_button: LikeButtonView | null; + dislike_button: DislikeButtonView | null; + icon_type: string; + like_count_entity: { + key: string + }; + dynamic_like_count_update_data: { + update_status_key: string, + placeholder_like_count_values_key: string, + update_delay_loop_id: string, + update_delay_sec: number + }; + + like_count?: number; + short_like_count?: string; + + constructor(data: RawNode) { + super(); + this.like_button = Parser.parseItem(data.likeButtonViewModel, LikeButtonView); + this.dislike_button = Parser.parseItem(data.dislikeButtonViewModel, DislikeButtonView); + this.icon_type = data.iconType; + + if (this.like_button && this.like_button.toggle_button) { + const toggle_button = this.like_button.toggle_button; + + if (toggle_button.default_button) { + this.short_like_count = toggle_button.default_button.title; + + this.like_count = parseInt(toggle_button.default_button.accessibility_text.replace(/\D/g, '')); + } else if (toggle_button.toggled_button) { + this.short_like_count = toggle_button.toggled_button.title; + + this.like_count = parseInt(toggle_button.toggled_button.accessibility_text.replace(/\D/g, '')); + } + } + + this.like_count_entity = { + key: data.likeCountEntity.key + }; + + this.dynamic_like_count_update_data = { + update_status_key: data.dynamicLikeCountUpdateData.updateStatusKey, + placeholder_like_count_values_key: data.dynamicLikeCountUpdateData.placeholderLikeCountValuesKey, + update_delay_loop_id: data.dynamicLikeCountUpdateData.updateDelayLoopId, + update_delay_sec: data.dynamicLikeCountUpdateData.updateDelaySec + }; + } +} \ No newline at end of file diff --git a/src/parser/classes/ToggleButtonView.ts b/src/parser/classes/ToggleButtonView.ts new file mode 100644 index 000000000..d30306e3f --- /dev/null +++ b/src/parser/classes/ToggleButtonView.ts @@ -0,0 +1,20 @@ +import { YTNode } from '../helpers.js'; +import { Parser, type RawNode } from '../index.js'; +import ButtonView from './ButtonView.js'; + +export default class ToggleButtonView extends YTNode { + static type = 'ToggleButtonView'; + + default_button: ButtonView | null; + toggled_button: ButtonView | null; + identifier?: string; + is_toggling_disabled: boolean; + + constructor(data: RawNode) { + super(); + this.default_button = Parser.parseItem(data.defaultButtonViewModel, ButtonView); + this.toggled_button = Parser.parseItem(data.toggledButtonViewModel, ButtonView); + this.identifier = data.identifier; + this.is_toggling_disabled = data.isTogglingDisabled; + } +} \ No newline at end of file diff --git a/src/parser/nodes.ts b/src/parser/nodes.ts index 9a1cf53e4..88d7c3138 100644 --- a/src/parser/nodes.ts +++ b/src/parser/nodes.ts @@ -96,6 +96,7 @@ export { default as CreatePlaylistDialog } from './classes/CreatePlaylistDialog. export { default as DecoratedPlayerBar } from './classes/DecoratedPlayerBar.js'; export { default as DefaultPromoPanel } from './classes/DefaultPromoPanel.js'; export { default as DidYouMean } from './classes/DidYouMean.js'; +export { default as DislikeButtonView } from './classes/DislikeButtonView.js'; export { default as DownloadButton } from './classes/DownloadButton.js'; export { default as Dropdown } from './classes/Dropdown.js'; export { default as DropdownItem } from './classes/DropdownItem.js'; @@ -158,6 +159,7 @@ export { default as ItemSectionHeader } from './classes/ItemSectionHeader.js'; export { default as ItemSectionTab } from './classes/ItemSectionTab.js'; export { default as ItemSectionTabbedHeader } from './classes/ItemSectionTabbedHeader.js'; export { default as LikeButton } from './classes/LikeButton.js'; +export { default as LikeButtonView } from './classes/LikeButtonView.js'; export { default as LiveChat } from './classes/LiveChat.js'; export { default as AddBannerToLiveChatCommand } from './classes/livechat/AddBannerToLiveChatCommand.js'; export { default as AddChatItemAction } from './classes/livechat/AddChatItemAction.js'; @@ -328,6 +330,7 @@ export { default as SearchSuggestionsSection } from './classes/SearchSuggestions export { default as SecondarySearchContainer } from './classes/SecondarySearchContainer.js'; export { default as SectionList } from './classes/SectionList.js'; export { default as SegmentedLikeDislikeButton } from './classes/SegmentedLikeDislikeButton.js'; +export { default as SegmentedLikeDislikeButtonView } from './classes/SegmentedLikeDislikeButtonView.js'; export { default as SettingBoolean } from './classes/SettingBoolean.js'; export { default as SettingsCheckbox } from './classes/SettingsCheckbox.js'; export { default as SettingsOptions } from './classes/SettingsOptions.js'; @@ -373,6 +376,7 @@ export { default as ThumbnailOverlayToggleButton } from './classes/ThumbnailOver export { default as TimedMarkerDecoration } from './classes/TimedMarkerDecoration.js'; export { default as TitleAndButtonListHeader } from './classes/TitleAndButtonListHeader.js'; export { default as ToggleButton } from './classes/ToggleButton.js'; +export { default as ToggleButtonView } from './classes/ToggleButtonView.js'; export { default as ToggleMenuServiceItem } from './classes/ToggleMenuServiceItem.js'; export { default as Tooltip } from './classes/Tooltip.js'; export { default as TopicChannelDetails } from './classes/TopicChannelDetails.js'; diff --git a/src/parser/youtube/VideoInfo.ts b/src/parser/youtube/VideoInfo.ts index 5adb96fd8..7de58d529 100644 --- a/src/parser/youtube/VideoInfo.ts +++ b/src/parser/youtube/VideoInfo.ts @@ -12,6 +12,7 @@ import RelatedChipCloud from '../classes/RelatedChipCloud.js'; import RichMetadata from '../classes/RichMetadata.js'; import RichMetadataRow from '../classes/RichMetadataRow.js'; import SegmentedLikeDislikeButton from '../classes/SegmentedLikeDislikeButton.js'; +import SegmentedLikeDislikeButtonView from '../classes/SegmentedLikeDislikeButtonView.js'; import ToggleButton from '../classes/ToggleButton.js'; import TwoColumnWatchNextResults from '../classes/TwoColumnWatchNextResults.js'; import VideoPrimaryInfo from '../classes/VideoPrimaryInfo.js'; @@ -163,6 +164,17 @@ class VideoInfo extends MediaInfo { this.basic_info.is_disliked = segmented_like_dislike_button?.dislike_button?.is_toggled; } + const segmented_like_dislike_button_view = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButtonView); + if (segmented_like_dislike_button_view) { + this.basic_info.like_count = segmented_like_dislike_button_view.like_count; + + if (segmented_like_dislike_button_view.like_button) { + const like_status = segmented_like_dislike_button_view.like_button.like_status_entity.like_status; + this.basic_info.is_liked = like_status === 'LIKE'; + this.basic_info.is_disliked = like_status === 'DISLIKE'; + } + } + const comments_entry_point = results.get({ target_id: 'comments-entry-point' })?.as(ItemSection); this.comments_entry_point_header = comments_entry_point?.contents?.firstOfType(CommentsEntryPointHeader);