refactor(Parser)!: general refactoring of parsers (#344)

* refactor: move common info into MediaInfo

* refactor: better inference on Memo

* refactor: improved typesafety in parser methods

* refactor: remove PlaylistAuthor in favor of Author

* refactor: cleanup live chat parsers

- Replace non standard author type with Author class
- Remove redundant code

* fix: new errors due to changes

* fix: pass actions to FormatUtils#toDash

* refactor!: merge NavigatableText and Text into single class
This commit is contained in:
Daniel Wykerd
2023-03-15 23:25:12 +02:00
committed by GitHub
parent 3d3436472f
commit b13bf6e992
78 changed files with 400 additions and 737 deletions

View File

@@ -39,7 +39,7 @@ class AccountItemSection extends YTNode {
constructor(data: RawNode) {
super();
this.contents = data.contents.map((ac: any) => new AccountItem(ac.accountItem));
this.header = Parser.parseItem<AccountItemSectionHeader>(data.header, AccountItemSectionHeader);
this.header = Parser.parseItem(data.header, AccountItemSectionHeader);
}
}

View File

@@ -12,8 +12,8 @@ class AccountSectionList extends YTNode {
constructor(data: RawNode) {
super();
this.contents = Parser.parseItem<AccountItemSection>(data.contents[0], AccountItemSection);
this.footers = Parser.parseItem<AccountChannel>(data.footers[0], AccountChannel);
this.contents = Parser.parseItem(data.contents[0], AccountItemSection);
this.footers = Parser.parseItem(data.footers[0], AccountChannel);
}
}

View File

@@ -2,8 +2,8 @@ import Parser from '../index.js';
import Author from './misc/Author.js';
import Text from './misc/Text.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import type CommentActionButtons from './comments/CommentActionButtons.js';
import type Menu from './menus/Menu.js';
import CommentActionButtons from './comments/CommentActionButtons.js';
import Menu from './menus/Menu.js';
import { YTNode } from '../helpers.js';
@@ -49,15 +49,15 @@ class BackstagePost extends YTNode {
}
if (data.actionMenu) {
this.menu = Parser.parseItem<Menu>(data.actionMenu);
this.menu = Parser.parseItem(data.actionMenu, Menu);
}
if (data.actionButtons) {
this.action_buttons = Parser.parseItem<CommentActionButtons>(data.actionButtons);
this.action_buttons = Parser.parseItem(data.actionButtons, CommentActionButtons);
}
if (data.voteButton) {
this.vote_button = Parser.parseItem(data.voteButton);
this.vote_button = Parser.parseItem(data.voteButton, CommentActionButtons);
}
if (data.navigationEndpoint) {

View File

@@ -3,9 +3,9 @@ import Author from './misc/Author.js';
import Text from './misc/Text.js';
import Thumbnail from './misc/Thumbnail.js';
import type Button from './Button.js';
import type ChannelHeaderLinks from './ChannelHeaderLinks.js';
import type SubscribeButton from './SubscribeButton.js';
import Button from './Button.js';
import ChannelHeaderLinks from './ChannelHeaderLinks.js';
import SubscribeButton from './SubscribeButton.js';
import { YTNode } from '../helpers.js';
@@ -19,7 +19,7 @@ class C4TabbedHeader extends YTNode {
subscribers?: Text;
videos_count?: Text;
sponsor_button?: Button | null;
subscribe_button?: SubscribeButton | null;
subscribe_button?: SubscribeButton | Button | null;
header_links?: ChannelHeaderLinks | null;
channel_handle?: Text;
channel_id?: string;
@@ -52,15 +52,15 @@ class C4TabbedHeader extends YTNode {
}
if (data.sponsorButton) {
this.sponsor_button = Parser.parseItem<Button>(data.sponsorButton);
this.sponsor_button = Parser.parseItem(data.sponsorButton, Button);
}
if (data.subscribeButton) {
this.subscribe_button = Parser.parseItem<SubscribeButton>(data.subscribeButton);
this.subscribe_button = Parser.parseItem(data.subscribeButton, [ SubscribeButton, Button ]);
}
if (data.headerLinks) {
this.header_links = Parser.parseItem<ChannelHeaderLinks>(data.headerLinks);
this.header_links = Parser.parseItem(data.headerLinks, ChannelHeaderLinks);
}
if (data.channelHandleText) {

View File

@@ -4,7 +4,7 @@ import Text from './misc/Text.js';
import Author from './misc/Author.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import type SubscribeButton from './SubscribeButton.js';
import SubscribeButton from './SubscribeButton.js';
import { YTNode } from '../helpers.js';
@@ -36,7 +36,7 @@ class Channel extends YTNode {
this.long_byline = new Text(data.longBylineText);
this.short_byline = new Text(data.shortBylineText);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.subscribe_button = Parser.parseItem<SubscribeButton>(data.subscribeButton);
this.subscribe_button = Parser.parseItem(data.subscribeButton, SubscribeButton);
this.description_snippet = new Text(data.descriptionSnippet);
}
}

View File

@@ -4,7 +4,7 @@ import Text from './misc/Text.js';
import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import type Button from './Button.js';
import Button from './Button.js';
import { YTNode } from '../helpers.js';
@@ -49,7 +49,7 @@ class ChannelAboutFullMetadata extends YTNode {
this.email_reveal = new NavigationEndpoint(data.onBusinessEmailRevealClickCommand);
this.can_reveal_email = !data.signInForBusinessEmail;
this.country = new Text(data.country);
this.buttons = Parser.parseArray<Button>(data.actionButtons);
this.buttons = Parser.parseArray(data.actionButtons, Button);
}
}

View File

@@ -22,7 +22,7 @@ class ChannelAgeGate extends YTNode {
this.avatar = Thumbnail.fromResponse(data.avatar);
this.header = new Text(data.header);
this.main_text = new Text(data.mainText);
this.sign_in_button = Parser.parseItem<Button>(data.signInButton, Button);
this.sign_in_button = Parser.parseItem(data.signInButton, Button);
this.secondary_text = new Text(data.secondaryText);
}
}

View File

@@ -15,9 +15,9 @@ class ChipCloud extends YTNode {
constructor(data: any) {
super();
// TODO: check this assumption that chipcloudchip is always returned
this.chips = Parser.parseArray<ChipCloudChip>(data.chips, ChipCloudChip);
this.next_button = Parser.parseItem<Button>(data.nextButton, Button);
this.previous_button = Parser.parseItem<Button>(data.previousButton, Button);
this.chips = Parser.parseArray(data.chips, ChipCloudChip);
this.next_button = Parser.parseItem(data.nextButton, Button);
this.previous_button = Parser.parseItem(data.previousButton, Button);
this.horizontal_scrollable = data.horizontalScrollable;
}
}

View File

@@ -2,7 +2,7 @@ import Parser from '../index.js';
import Text from './misc/Text.js';
import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import type Menu from './menus/Menu.js';
import Menu from './menus/Menu.js';
import { YTNode } from '../helpers.js';
class CompactChannel extends YTNode {
@@ -28,7 +28,7 @@ class CompactChannel extends YTNode {
this.subscriber_count = new Text(data.subscriberCountText);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.tv_banner = Thumbnail.fromResponse(data.tvBanner);
this.menu = Parser.parseItem<Menu>(data.menu);
this.menu = Parser.parseItem(data.menu, Menu);
}
}

View File

@@ -15,8 +15,8 @@ class ConfirmDialog extends YTNode {
constructor (data: any) {
super();
this.title = new Text(data.title);
this.confirm_button = Parser.parseItem<Button>(data.confirmButton, Button);
this.cancel_button = Parser.parseItem<Button>(data.cancelButton, Button);
this.confirm_button = Parser.parseItem(data.confirmButton, Button);
this.cancel_button = Parser.parseItem(data.cancelButton, Button);
this.dialog_messages = data.dialogMessages.map((txt: any) => new Text(txt));
}
}

View File

@@ -9,7 +9,7 @@ class ConversationBar extends YTNode {
constructor(data: RawNode) {
super();
this.availability_message = Parser.parseItem<Message>(data.availabilityMessage, Message);
this.availability_message = Parser.parseItem(data.availabilityMessage, Message);
}
}

View File

@@ -11,7 +11,7 @@ class CopyLink extends YTNode {
constructor(data: any) {
super();
this.copy_button = Parser.parseItem<Button>(data.copyButton, Button);
this.copy_button = Parser.parseItem(data.copyButton, Button);
this.short_url = data.shortUrl;
this.style = data.style;
}

View File

@@ -19,8 +19,8 @@ class CreatePlaylistDialog extends YTNode {
this.title = new Text(data.dialogTitle).toString();
this.title_placeholder = data.titlePlaceholder || '';
this.privacy_option = Parser.parseItem(data.privacyOption, Dropdown)?.entries || null;
this.create_button = Parser.parseItem(data.cancelButton);
this.cancel_button = Parser.parseItem(data.cancelButton);
this.create_button = Parser.parseItem(data.cancelButton, Button);
this.cancel_button = Parser.parseItem(data.cancelButton, Button);
}
}

View File

@@ -1,7 +1,7 @@
import Parser from '../index.js';
import { YTNode } from '../helpers.js';
import type Button from './Button.js';
import type MultiMarkersPlayerBar from './MultiMarkersPlayerBar.js';
import Button from './Button.js';
import MultiMarkersPlayerBar from './MultiMarkersPlayerBar.js';
import type { RawNode } from '../index.js';
class DecoratedPlayerBar extends YTNode {
@@ -12,8 +12,8 @@ class DecoratedPlayerBar extends YTNode {
constructor(data: RawNode) {
super();
this.player_bar = Parser.parseItem<MultiMarkersPlayerBar>(data.playerBar);
this.player_bar_action_button = Parser.parseItem<Button>(data.playerBarActionButton);
this.player_bar = Parser.parseItem(data.playerBar, MultiMarkersPlayerBar);
this.player_bar_action_button = Parser.parseItem(data.playerBarActionButton, Button);
}
}

View File

@@ -32,9 +32,9 @@ class ExpandableMetadata extends YTNode {
expanded_title: new Text(data.header.expandedTitle)
};
this.expanded_content = Parser.parseItem<HorizontalCardList>(data.expandedContent);
this.expand_button = Parser.parseItem<Button>(data.expandButton);
this.collapse_button = Parser.parseItem<Button>(data.collapseButton);
this.expanded_content = Parser.parseItem(data.expandedContent, HorizontalCardList);
this.expand_button = Parser.parseItem(data.expandButton, Button);
this.collapse_button = Parser.parseItem(data.collapseButton, Button);
}
}

View File

@@ -1,6 +1,6 @@
import { YTNode } from '../helpers.js';
import Parser, { RawNode } from '../index.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import Author from './misc/Author.js';
import Text from './misc/Text.js';
import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
@@ -10,7 +10,7 @@ class GridPlaylist extends YTNode {
id: string;
title: Text;
author?: PlaylistAuthor;
author?: Author;
badges;
endpoint: NavigationEndpoint;
view_playlist: Text;
@@ -26,7 +26,7 @@ class GridPlaylist extends YTNode {
this.title = new Text(data.title);
if (data.shortBylineText) {
this.author = new PlaylistAuthor(data.shortBylineText, data.ownerBadges);
this.author = new Author(data.shortBylineText, data.ownerBadges);
}
this.badges = Parser.parseArray(data.ownerBadges);

View File

@@ -4,7 +4,7 @@ import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import Author from './misc/Author.js';
import type Menu from './menus/Menu.js';
import Menu from './menus/Menu.js';
import { YTNode } from '../helpers.js';
@@ -38,7 +38,7 @@ class GridVideo extends YTNode {
this.views = new Text(data.viewCountText);
this.short_view_count = new Text(data.shortViewCountText);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.menu = Parser.parseItem<Menu>(data.menu);
this.menu = Parser.parseItem(data.menu, Menu);
}
}

View File

@@ -1,5 +1,5 @@
import Parser from '../index.js';
import type HeatMarker from './HeatMarker.js';
import HeatMarker from './HeatMarker.js';
import { YTNode } from '../helpers.js';
@@ -17,7 +17,7 @@ class Heatmap extends YTNode {
this.max_height_dp = data.maxHeightDp;
this.min_height_dp = data.minHeightDp;
this.show_hide_animation_duration_millis = data.showHideAnimationDurationMillis;
this.heat_markers = Parser.parseArray<HeatMarker>(data.heatMarkers);
this.heat_markers = Parser.parseArray(data.heatMarkers, HeatMarker);
this.heat_markers_decorations = Parser.parseArray(data.heatMarkersDecorations);
}
}

View File

@@ -3,6 +3,8 @@ import { YTNode } from '../helpers.js';
import SearchRefinementCard from './SearchRefinementCard.js';
import Button from './Button.js';
import MacroMarkersListItem from './MacroMarkersListItem.js';
import GameCard from './GameCard.js';
import VideoCard from './VideoCard.js';
class HorizontalCardList extends YTNode {
static type = 'HorizontalCardList';
@@ -14,10 +16,10 @@ class HorizontalCardList extends YTNode {
constructor(data: any) {
super();
this.cards = Parser.parseArray<SearchRefinementCard | MacroMarkersListItem>(data.cards);
this.cards = Parser.parseArray(data.cards, [ SearchRefinementCard, MacroMarkersListItem, GameCard, VideoCard ]);
this.header = Parser.parseItem(data.header);
this.previous_button = Parser.parseItem<Button>(data.previousButton, Button);
this.next_button = Parser.parseItem<Button>(data.nextButton, Button);
this.previous_button = Parser.parseItem(data.previousButton, Button);
this.next_button = Parser.parseItem(data.nextButton, Button);
}
}

View File

@@ -30,7 +30,7 @@ class InteractiveTabbedHeader extends YTNode {
this.badges = Parser.parseArray<MetadataBadge>(data.badges, MetadataBadge);
this.box_art = Thumbnail.fromResponse(data.boxArt);
this.banner = Thumbnail.fromResponse(data.banner);
this.buttons = Parser.parseArray<SubscribeButton | Button>(data.buttons, [ SubscribeButton, Button ]);
this.buttons = Parser.parseArray(data.buttons, [ SubscribeButton, Button ]);
this.auto_generated = new Text(data.autoGenerated);
}
}

View File

@@ -15,7 +15,7 @@ class ItemSection extends YTNode {
constructor(data: any) {
super();
this.header = Parser.parseItem<CommentsHeader | ItemSectionHeader | ItemSectionTabbedHeader>(data.header);
this.header = Parser.parseItem(data.header, [ CommentsHeader, ItemSectionHeader, ItemSectionTabbedHeader ]);
this.contents = Parser.parse(data.contents, true);
if (data.targetId || data.sectionIdentifier) {

View File

@@ -13,7 +13,7 @@ class ItemSectionTabbedHeader extends YTNode {
constructor(data: any) {
super();
this.title = new Text(data.title);
this.tabs = Parser.parseArray<ItemSectionTab>(data.tabs, ItemSectionTab);
this.tabs = Parser.parseArray(data.tabs, ItemSectionTab);
if (data.endItems) {
this.end_items = Parser.parseArray(data.endItems);
}

View File

@@ -1,7 +1,7 @@
import Parser from '../index.js';
import type Menu from './menus/Menu.js';
import type Button from './Button.js';
import type SortFilterSubMenu from './SortFilterSubMenu.js';
import Menu from './menus/Menu.js';
import Button from './Button.js';
import SortFilterSubMenu from './SortFilterSubMenu.js';
import { YTNode } from '../helpers.js';
class LiveChatHeader extends YTNode {
@@ -13,9 +13,9 @@ class LiveChatHeader extends YTNode {
constructor(data: any) {
super();
this.overflow_menu = Parser.parseItem<Menu>(data.overflowMenu);
this.collapse_button = Parser.parseItem<Button>(data.collapseButton);
this.view_selector = Parser.parseItem<SortFilterSubMenu>(data.viewSelector);
this.overflow_menu = Parser.parseItem(data.overflowMenu, Menu);
this.collapse_button = Parser.parseItem(data.collapseButton, Button);
this.view_selector = Parser.parseItem(data.viewSelector, SortFilterSubMenu);
}
}

View File

@@ -1,6 +1,6 @@
import Parser from '../index.js';
import { YTNode } from '../helpers.js';
import type Button from './Button.js';
import Button from './Button.js';
class LiveChatItemList extends YTNode {
static type = 'LiveChatItemList';
@@ -11,7 +11,7 @@ class LiveChatItemList extends YTNode {
constructor(data: any) {
super();
this.max_items_to_display = data.maxItemsToDisplay;
this.more_comments_below_button = Parser.parseItem<Button>(data.moreCommentsBelowButton);
this.more_comments_below_button = Parser.parseItem(data.moreCommentsBelowButton, Button);
}
}

View File

@@ -1,7 +1,7 @@
import Text from './misc/Text.js';
import Parser from '../index.js';
import Thumbnail from './misc/Thumbnail.js';
import type Button from './Button.js';
import Button from './Button.js';
import { YTNode } from '../helpers.js';
class LiveChatMessageInput extends YTNode {
@@ -16,7 +16,7 @@ class LiveChatMessageInput extends YTNode {
super();
this.author_name = new Text(data.authorName);
this.author_photo = Thumbnail.fromResponse(data.authorPhoto);
this.send_button = Parser.parseItem<Button>(data.sendButton);
this.send_button = Parser.parseItem(data.sendButton, Button);
this.target_id = data.targetId;
}
}

View File

@@ -1,7 +1,7 @@
import Parser from '../index.js';
import Text from './misc/Text.js';
import { ObservedArray, YTNode } from '../helpers.js';
import type LiveChatParticipant from './LiveChatParticipant.js';
import LiveChatParticipant from './LiveChatParticipant.js';
class LiveChatParticipantsList extends YTNode {
static type = 'LiveChatParticipantsList';
@@ -12,7 +12,7 @@ class LiveChatParticipantsList extends YTNode {
constructor(data: any) {
super();
this.title = new Text(data.title);
this.participants = Parser.parseArray<LiveChatParticipant>(data.participants);
this.participants = Parser.parseArray(data.participants, LiveChatParticipant);
}
}

View File

@@ -1,6 +1,6 @@
import Parser from '../index.js';
import type Chapter from './Chapter.js';
import type Heatmap from './Heatmap.js';
import Chapter from './Chapter.js';
import Heatmap from './Heatmap.js';
import type { RawNode } from '../index.js';
import { observe, ObservedArray, YTNode } from '../helpers.js';
@@ -21,11 +21,11 @@ class Marker extends YTNode {
this.value = {};
if (data.value.heatmap) {
this.value.heatmap = Parser.parseItem<Heatmap>(data.value.heatmap);
this.value.heatmap = Parser.parseItem(data.value.heatmap, Heatmap);
}
if (data.value.chapters) {
this.value.chapters = Parser.parseArray<Chapter>(data.value.chapters);
this.value.chapters = Parser.parseArray(data.value.chapters, Chapter);
}
}
}

View File

@@ -5,19 +5,19 @@ import MusicResponsiveListItem from './MusicResponsiveListItem.js';
import MusicCarouselShelfBasicHeader from './MusicCarouselShelfBasicHeader.js';
import MusicNavigationButton from './MusicNavigationButton.js';
import { YTNode } from '../helpers.js';
import { ObservedArray, YTNode } from '../helpers.js';
class MusicCarouselShelf extends YTNode {
static type = 'MusicCarouselShelf';
header: MusicCarouselShelfBasicHeader | null;
contents: Array<MusicTwoRowItem | MusicResponsiveListItem | MusicNavigationButton>;
contents: ObservedArray<MusicTwoRowItem | MusicResponsiveListItem | MusicNavigationButton>;
num_items_per_column: number | null;
constructor(data: any) {
super();
this.header = Parser.parseItem<MusicCarouselShelfBasicHeader>(data.header, MusicCarouselShelfBasicHeader);
this.contents = Parser.parseArray<MusicTwoRowItem | MusicResponsiveListItem | MusicNavigationButton>(data.contents, [ MusicTwoRowItem, MusicResponsiveListItem, MusicNavigationButton ]);
this.contents = Parser.parseArray(data.contents, [ MusicTwoRowItem, MusicResponsiveListItem, MusicNavigationButton ]);
this.num_items_per_column = Reflect.has(data, 'numItemsPerColumn') ? parseInt(data.numItemsPerColumn) : null;
}
}

View File

@@ -37,7 +37,7 @@ class MusicShelf extends YTNode {
}
if (data.bottomButton) {
this.bottom_button = Parser.parseItem<Button>(data.bottomButton);
this.bottom_button = Parser.parseItem(data.bottomButton, Button);
}
if (data.subheaders) {

View File

@@ -2,7 +2,7 @@ import Text from './misc/Text.js';
import Parser from '../index.js';
import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import Author from './misc/Author.js';
import { YTNode } from '../helpers.js';
class Playlist extends YTNode {
@@ -10,7 +10,7 @@ class Playlist extends YTNode {
id: string;
title: Text;
author: Text | PlaylistAuthor;
author: Text | Author;
thumbnails: Thumbnail[];
video_count: Text;
video_count_short: Text;
@@ -29,7 +29,7 @@ class Playlist extends YTNode {
this.author = data.shortBylineText?.simpleText ?
new Text(data.shortBylineText) :
new PlaylistAuthor(data.longBylineText, data.ownerBadges, null);
new Author(data.longBylineText, data.ownerBadges, null);
this.thumbnails = Thumbnail.fromResponse(data.thumbnail || { thumbnails: data.thumbnails.map((th: any) => th.thumbnails).flat(1) });
this.video_count = new Text(data.thumbnailText);

View File

@@ -1,5 +1,5 @@
import Text from './misc/Text.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import Author from './misc/Author.js';
import Parser, { RawNode } from '../index.js';
import { YTNode } from '../helpers.js';
@@ -10,7 +10,7 @@ class PlaylistHeader extends YTNode {
title: Text;
stats: Text[];
brief_stats: Text[];
author: PlaylistAuthor;
author: Author;
description: Text;
num_videos: Text;
view_count: Text;
@@ -29,7 +29,7 @@ class PlaylistHeader extends YTNode {
this.title = new Text(data.title);
this.stats = data.stats.map((stat: any) => new Text(stat));
this.brief_stats = data.briefStats.map((stat: any) => new Text(stat));
this.author = new PlaylistAuthor({ ...data.ownerText, navigationEndpoint: data.ownerEndpoint }, data.ownerBadges, null);
this.author = new Author({ ...data.ownerText, navigationEndpoint: data.ownerEndpoint }, data.ownerBadges, null);
this.description = new Text(data.descriptionText);
this.num_videos = new Text(data.numVideosText);
this.view_count = new Text(data.viewCountText);

View File

@@ -23,7 +23,7 @@ class PlaylistPanel extends YTNode {
super();
this.title = data.title;
this.title_text = new Text(data.titleText);
this.contents = Parser.parseArray<PlaylistPanelVideoWrapper | PlaylistPanelVideo | AutomixPreviewVideo>(data.contents);
this.contents = Parser.parseArray(data.contents, [ PlaylistPanelVideoWrapper, PlaylistPanelVideo, AutomixPreviewVideo ]);
this.playlist_id = data.playlistId;
this.is_infinite = data.isInfinite;
this.continuation = data.continuations?.[0]?.nextRadioContinuationData?.continuation || data.continuations?.[0]?.nextContinuationData?.continuation;

View File

@@ -10,8 +10,8 @@ class PlaylistPanelVideoWrapper extends YTNode {
constructor(data: any) {
super();
this.primary = Parser.parseItem<PlaylistPanelVideo>(data.primaryRenderer);
this.counterpart = data.counterpart?.map((item: any) => Parser.parseItem<PlaylistPanelVideo>(item.counterpartRenderer)) || [];
this.primary = Parser.parseItem(data.primaryRenderer, PlaylistPanelVideo);
this.counterpart = data.counterpart?.map((item: any) => Parser.parseItem(item.counterpartRenderer, PlaylistPanelVideo)) || [];
}
}

View File

@@ -1,10 +1,10 @@
import Text from './misc/Text.js';
import Parser from '../index.js';
import Thumbnail from './misc/Thumbnail.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import Author from './misc/Author.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import ThumbnailOverlayTimeStatus from './ThumbnailOverlayTimeStatus.js';
import type Menu from './menus/Menu.js';
import Menu from './menus/Menu.js';
import { YTNode } from '../helpers.js';
@@ -14,7 +14,7 @@ class PlaylistVideo extends YTNode {
id: string;
index: Text;
title: Text;
author: PlaylistAuthor;
author: Author;
thumbnails: Thumbnail[];
thumbnail_overlays;
set_video_id: string | undefined;
@@ -33,13 +33,13 @@ class PlaylistVideo extends YTNode {
this.id = data.videoId;
this.index = new Text(data.index);
this.title = new Text(data.title);
this.author = new PlaylistAuthor(data.shortBylineText);
this.author = new Author(data.shortBylineText);
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
this.set_video_id = data?.setVideoId;
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.is_playable = data.isPlayable;
this.menu = Parser.parseItem<Menu>(data.menu);
this.menu = Parser.parseItem(data.menu, Menu);
const upcoming = data.upcomingEventData && Number(`${data.upcomingEventData.startTime}000`);
if (upcoming) {

View File

@@ -12,8 +12,8 @@ class SegmentedLikeDislikeButton extends YTNode {
constructor (data: RawNode) {
super();
this.like_button = Parser.parseItem<ToggleButton | Button>(data.likeButton, [ ToggleButton, Button ]);
this.dislike_button = Parser.parseItem<ToggleButton | Button>(data.dislikeButton, [ ToggleButton, Button ]);
this.like_button = Parser.parseItem(data.likeButton, [ ToggleButton, Button ]);
this.dislike_button = Parser.parseItem(data.dislikeButton, [ ToggleButton, Button ]);
}
}

View File

@@ -25,7 +25,7 @@ class SettingsOptions extends YTNode {
}
if (Reflect.has(data, 'options')) {
this.options = Parser.parseArray<SettingsSwitch | Dropdown | CopyLink | SettingsCheckbox | ChannelOptions>(data.options, [
this.options = Parser.parseArray(data.options, [
SettingsSwitch, Dropdown, CopyLink,
SettingsCheckbox, ChannelOptions
]);

View File

@@ -33,7 +33,7 @@ class Shelf extends YTNode {
}
if (data.playAllButton) {
this.play_all_button = Parser.parseItem<Button>(data.playAllButton);
this.play_all_button = Parser.parseItem(data.playAllButton, Button);
}
}
}

View File

@@ -19,7 +19,7 @@ class Tab extends YTNode {
this.title = data.title || 'N/A';
this.selected = data.selected || false;
this.endpoint = new NavigationEndpoint(data.endpoint);
this.content = Parser.parseItem<SectionList | MusicQueue | RichGrid>(data.content, [ SectionList, MusicQueue, RichGrid ]);
this.content = Parser.parseItem(data.content, [ SectionList, MusicQueue, RichGrid ]);
}
}

View File

@@ -1,10 +1,10 @@
import Parser from '../index.js';
import { YTNode } from '../helpers.js';
import Text from './misc/Text.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import Author from './misc/Author.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import type Menu from './menus/Menu.js';
import Menu from './menus/Menu.js';
type AutoplaySet = {
autoplay_video: NavigationEndpoint,
@@ -20,7 +20,7 @@ class TwoColumnWatchNextResults extends YTNode {
playlist?: {
id: string,
title: string,
author: Text | PlaylistAuthor,
author: Text | Author,
contents: YTNode[],
current_index: number,
is_infinite: boolean,
@@ -45,11 +45,11 @@ class TwoColumnWatchNextResults extends YTNode {
title: playlistData.title,
author: playlistData.shortBylineText?.simpleText ?
new Text(playlistData.shortBylineText) :
new PlaylistAuthor(playlistData.longBylineText),
new Author(playlistData.longBylineText),
contents: Parser.parseArray(playlistData.contents),
current_index: playlistData.currentIndex,
is_infinite: !!playlistData.isInfinite,
menu: Parser.parseItem<Menu>(playlistData.menu)
menu: Parser.parseItem(playlistData.menu, Menu)
};
}

View File

@@ -1,6 +1,6 @@
import Parser from '../index.js';
import { YTNode } from '../helpers.js';
import type Button from './Button.js';
import Button from './Button.js';
import Text from './misc/Text.js';
class UpsellDialog extends YTNode {
@@ -16,8 +16,8 @@ class UpsellDialog extends YTNode {
super();
this.message_title = new Text(data.dialogMessageTitle);
this.message_text = new Text(data.dialogMessageText);
this.action_button = Parser.parseItem<Button>(data.actionButton);
this.dismiss_button = Parser.parseItem<Button>(data.dismissButton);
this.action_button = Parser.parseItem(data.actionButton, Button);
this.dismiss_button = Parser.parseItem(data.dismissButton, Button);
this.is_visible = data.isVisible;
}
}

View File

@@ -59,7 +59,7 @@ class Video extends YTNode {
hover_text: new Text(snippet.snippetHoverText)
})) || [];
this.expandable_metadata = Parser.parseItem<ExpandableMetadata>(data.expandableMetadata);
this.expandable_metadata = Parser.parseItem(data.expandableMetadata, ExpandableMetadata);
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);

View File

@@ -20,14 +20,14 @@ class VideoSecondaryInfo extends YTNode {
constructor(data: RawNode) {
super();
this.owner = Parser.parseItem<VideoOwner>(data.owner);
this.owner = Parser.parseItem(data.owner, VideoOwner);
this.description = new Text(data.description);
if (Reflect.has(data, 'attributedDescription')) {
this.description = new Text(this.#convertAttributedDescriptionToRuns(data.attributedDescription));
}
this.subscribe_button = Parser.parseItem<SubscribeButton | Button>(data.subscribeButton, [ SubscribeButton, Button ]);
this.subscribe_button = Parser.parseItem(data.subscribeButton, [ SubscribeButton, Button ]);
this.metadata = Parser.parseItem<MetadataRowContainer>(data.metadataRowContainer, MetadataRowContainer);
this.show_more_text = data.showMoreText;
this.show_less_text = data.showLessText;

View File

@@ -12,7 +12,7 @@ class WatchNextEndScreen extends YTNode {
constructor(data: any) {
super();
this.results = Parser.parseArray<EndScreenVideo | EndScreenPlaylist>(data.results, [ EndScreenVideo, EndScreenPlaylist ]);
this.results = Parser.parseArray(data.results, [ EndScreenVideo, EndScreenPlaylist ]);
this.title = new Text(data.title).toString();
}
}

View File

@@ -6,10 +6,10 @@ import CommentReplyDialog from './CommentReplyDialog.js';
import AuthorCommentBadge from './AuthorCommentBadge.js';
import Author from '../misc/Author.js';
import type Menu from '../menus/Menu.js';
import type CommentActionButtons from './CommentActionButtons.js';
import type SponsorCommentBadge from './SponsorCommentBadge.js';
import type PdgCommentChip from './PdgCommentChip.js';
import Menu from '../menus/Menu.js';
import CommentActionButtons from './CommentActionButtons.js';
import SponsorCommentBadge from './SponsorCommentBadge.js';
import PdgCommentChip from './PdgCommentChip.js';
import type { ApiResponse } from '../../../core/Actions.js';
import type Actions from '../../../core/Actions.js';
@@ -51,9 +51,9 @@ class Comment extends YTNode {
this.published = new Text(data.publishedTimeText);
this.author_is_channel_owner = data.authorIsChannelOwner;
this.current_user_reply_thumbnail = Thumbnail.fromResponse(data.currentUserReplyThumbnail);
this.sponsor_comment_badge = Parser.parseItem<SponsorCommentBadge>(data.sponsorCommentBadge);
this.paid_comment_chip = Parser.parseItem<PdgCommentChip>(data.paidCommentChipRenderer);
this.author_badge = Parser.parseItem<AuthorCommentBadge>(data.authorCommentBadge, AuthorCommentBadge);
this.sponsor_comment_badge = Parser.parseItem(data.sponsorCommentBadge, SponsorCommentBadge);
this.paid_comment_chip = Parser.parseItem(data.paidCommentChipRenderer, PdgCommentChip);
this.author_badge = Parser.parseItem(data.authorCommentBadge, AuthorCommentBadge);
this.author = new Author({
...data.authorText,
@@ -62,8 +62,8 @@ class Comment extends YTNode {
metadataBadgeRenderer: this.author_badge?.orig_badge
} ] : null, data.authorThumbnail);
this.action_menu = Parser.parseItem<Menu>(data.actionMenu);
this.action_buttons = Parser.parseItem<CommentActionButtons>(data.actionButtons);
this.action_menu = Parser.parseItem(data.actionMenu, Menu);
this.action_buttons = Parser.parseItem(data.actionButtons, CommentActionButtons);
this.comment_id = data.commentId;
this.vote_status = data.voteStatus;

View File

@@ -1,7 +1,7 @@
import Parser from '../../index.js';
import type Button from '../Button.js';
import type ToggleButton from '../ToggleButton.js';
import type CreatorHeart from './CreatorHeart.js';
import Button from '../Button.js';
import ToggleButton from '../ToggleButton.js';
import CreatorHeart from './CreatorHeart.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
@@ -15,10 +15,10 @@ class CommentActionButtons extends YTNode {
constructor(data: RawNode) {
super();
this.like_button = Parser.parseItem<ToggleButton>(data.likeButton);
this.dislike_button = Parser.parseItem<ToggleButton>(data.dislikeButton);
this.reply_button = Parser.parseItem<Button>(data.replyButton);
this.creator_heart = Parser.parseItem<CreatorHeart>(data.creatorHeart);
this.like_button = Parser.parseItem(data.likeButton, ToggleButton);
this.dislike_button = Parser.parseItem(data.dislikeButton, ToggleButton);
this.reply_button = Parser.parseItem(data.replyButton, Button);
this.creator_heart = Parser.parseItem(data.creatorHeart, CreatorHeart);
}
}

View File

@@ -1,8 +1,8 @@
import Parser from '../../index.js';
import Text from '../misc/Text.js';
import Thumbnail from '../misc/Thumbnail.js';
import type Button from '../Button.js';
import type EmojiPicker from './EmojiPicker.js';
import Button from '../Button.js';
import EmojiPicker from './EmojiPicker.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
@@ -21,11 +21,11 @@ class CommentDialog extends YTNode {
super();
this.editable_text = new Text(data.editableText);
this.author_thumbnail = Thumbnail.fromResponse(data.authorThumbnail);
this.submit_button = Parser.parseItem<Button>(data.submitButton);
this.cancel_button = Parser.parseItem<Button>(data.cancelButton);
this.submit_button = Parser.parseItem(data.submitButton, Button);
this.cancel_button = Parser.parseItem(data.cancelButton, Button);
this.placeholder = new Text(data.placeholderText);
this.emoji_button = Parser.parseItem<Button>(data.emojiButton);
this.emoji_picker = Parser.parseItem<EmojiPicker>(data.emojiPicker);
this.emoji_button = Parser.parseItem(data.emojiButton, Button);
this.emoji_picker = Parser.parseItem(data.emojiPicker, EmojiPicker);
}
}

View File

@@ -1,6 +1,6 @@
import Parser from '../../index.js';
import Thumbnail from '../misc/Thumbnail.js';
import type Button from '../Button.js';
import Button from '../Button.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
class CommentReplies extends YTNode {
@@ -15,8 +15,8 @@ class CommentReplies extends YTNode {
constructor(data: RawNode) {
super();
this.contents = Parser.parseArray(data.contents);
this.view_replies = Parser.parseItem<Button>(data.viewReplies);
this.hide_replies = Parser.parseItem<Button>(data.hideReplies);
this.view_replies = Parser.parseItem(data.viewReplies, Button);
this.hide_replies = Parser.parseItem(data.hideReplies, Button);
this.view_replies_creator_thumbnail = Thumbnail.fromResponse(data.viewRepliesCreatorThumbnail);
this.has_channel_owner_replied = !!data.viewRepliesCreatorThumbnail;
}

View File

@@ -1,7 +1,7 @@
import Parser from '../../index.js';
import Thumbnail from '../misc/Thumbnail.js';
import Text from '../misc/Text.js';
import type Button from '../Button.js';
import Button from '../Button.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
@@ -16,8 +16,8 @@ class CommentReplyDialog extends YTNode {
constructor(data: RawNode) {
super();
this.reply_button = Parser.parseItem<Button>(data.replyButton);
this.cancel_button = Parser.parseItem<Button>(data.cancelButton);
this.reply_button = Parser.parseItem(data.replyButton, Button);
this.cancel_button = Parser.parseItem(data.cancelButton, Button);
this.author_thumbnail = Thumbnail.fromResponse(data.authorThumbnail);
this.placeholder = new Text(data.placeholderText);
this.error_message = new Text(data.errorMessage);

View File

@@ -1,7 +1,7 @@
import Parser from '../../index.js';
import Thumbnail from '../misc/Thumbnail.js';
import Text from '../misc/Text.js';
import type Button from '../Button.js';
import Button from '../Button.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
@@ -16,8 +16,8 @@ class CommentSimplebox extends YTNode {
constructor(data: RawNode) {
super();
this.submit_button = Parser.parseItem<Button>(data.submitButton);
this.cancel_button = Parser.parseItem<Button>(data.cancelButton);
this.submit_button = Parser.parseItem(data.submitButton, Button);
this.cancel_button = Parser.parseItem(data.cancelButton, Button);
this.author_thumbnails = Thumbnail.fromResponse(data.authorThumbnail);
this.placeholder = new Text(data.placeholderText);
this.avatar_size = data.avatarSize;

View File

@@ -24,7 +24,7 @@ class CommentThread extends YTNode {
constructor(data: RawNode) {
super();
this.comment = Parser.parseItem<Comment>(data.comment, Comment);
this.comment_replies_data = Parser.parseItem<CommentReplies>(data.replies);
this.comment_replies_data = Parser.parseItem(data.replies, CommentReplies);
this.is_moderated_elq_comment = data.isModeratedElqComment;
this.has_replies = !!this.comment_replies_data;
}

View File

@@ -1,7 +1,7 @@
import Parser from '../../index.js';
import Text from '../misc/Text.js';
import Thumbnail from '../misc/Thumbnail.js';
import type SortFilterSubMenu from '../SortFilterSubMenu.js';
import SortFilterSubMenu from '../SortFilterSubMenu.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
@@ -28,7 +28,7 @@ class CommentsHeader extends YTNode {
this.count = new Text(data.countText);
this.comments_count = new Text(data.commentsCount);
this.create_renderer = Parser.parseItem(data.createRenderer);
this.sort_menu = Parser.parseItem<SortFilterSubMenu>(data.sortMenu);
this.sort_menu = Parser.parseItem(data.sortMenu, SortFilterSubMenu);
this.custom_emojis = data.customEmojis?.map((emoji: any) => ({
emoji_id: emoji.emojiId,

View File

@@ -1,6 +1,6 @@
import Parser from '../../index.js';
import { YTNode } from '../../helpers.js';
import type LiveChatBanner from './items/LiveChatBanner.js';
import LiveChatBanner from './items/LiveChatBanner.js';
import type { RawNode } from '../../index.js';
class AddBannerToLiveChatCommand extends YTNode {
@@ -10,7 +10,7 @@ class AddBannerToLiveChatCommand extends YTNode {
constructor(data: RawNode) {
super();
this.banner = Parser.parseItem<LiveChatBanner>(data.bannerRenderer);
this.banner = Parser.parseItem(data.bannerRenderer, LiveChatBanner);
}
}

View File

@@ -19,7 +19,7 @@ class LiveChatAutoModMessage extends YTNode {
constructor(data: RawNode) {
super();
this.menu_endpoint = new NavigationEndpoint(data.contextMenuEndpoint);
this.moderation_buttons = Parser.parseArray<Button>(data.moderationButtons, [ Button ]);
this.moderation_buttons = Parser.parseArray(data.moderationButtons, [ Button ]);
this.auto_moderated_item = Parser.parseItem(data.autoModeratedItem);
this.header_text = new Text(data.headerText);
this.timestamp = Math.floor(parseInt(data.timestampUsec) / 1000);

View File

@@ -1,6 +1,6 @@
import { YTNode } from '../../../helpers.js';
import Parser from '../../../index.js';
import type LiveChatBannerHeader from './LiveChatBannerHeader.js';
import LiveChatBannerHeader from './LiveChatBannerHeader.js';
import type { RawNode } from '../../../index.js';
class LiveChatBanner extends YTNode {
@@ -16,7 +16,7 @@ class LiveChatBanner extends YTNode {
constructor(data: RawNode) {
super();
this.header = Parser.parseItem<LiveChatBannerHeader>(data.header);
this.header = Parser.parseItem(data.header, LiveChatBannerHeader);
this.contents = Parser.parseItem(data.contents);
this.action_id = data.actionId;
this.viewer_is_creator = data.viewerIsCreator;

View File

@@ -1,6 +1,6 @@
import { YTNode } from '../../../helpers.js';
import Parser from '../../../index.js';
import type Button from '../../Button.js';
import Button from '../../Button.js';
import Text from '../../misc/Text.js';
import type { RawNode } from '../../../index.js';
@@ -15,7 +15,7 @@ class LiveChatBannerHeader extends YTNode {
super();
this.text = new Text(data.text).toString();
this.icon_type = data.icon?.iconType;
this.context_menu_button = Parser.parseItem<Button>(data.contextMenuButton);
this.context_menu_button = Parser.parseItem(data.contextMenuButton, Button);
}
}

View File

@@ -1,11 +1,8 @@
import { observe, ObservedArray, YTNode } from '../../../helpers.js';
import Parser from '../../../index.js';
import LiveChatAuthorBadge from '../../LiveChatAuthorBadge.js';
import MetadataBadge from '../../MetadataBadge.js';
import { YTNode } from '../../../helpers.js';
import Text from '../../misc/Text.js';
import Thumbnail from '../../misc/Thumbnail.js';
import NavigationEndpoint from '../../NavigationEndpoint.js';
import type { RawNode } from '../../../index.js';
import Author from '../../misc/Author.js';
class LiveChatMembershipItem extends YTNode {
static type = 'LiveChatMembershipItem';
@@ -13,15 +10,7 @@ class LiveChatMembershipItem extends YTNode {
timestamp: number;
header_subtext: Text;
author: {
id: string;
name: Text;
thumbnails: Thumbnail[];
badges: ObservedArray<LiveChatAuthorBadge | MetadataBadge>;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
};
author: Author;
menu_endpoint: NavigationEndpoint;
@@ -31,22 +20,7 @@ class LiveChatMembershipItem extends YTNode {
this.timestamp = Math.floor(parseInt(data.timestampUsec) / 1000);
this.header_subtext = new Text(data.headerSubtext);
this.author = {
id: data.authorExternalChannelId,
name: new Text(data?.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
};
const badges = Parser.parseArray<LiveChatAuthorBadge | MetadataBadge>(data.authorBadges);
this.author.badges = badges;
this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
this.author = new Author(data.authorName, data.authorBadges, data.authorPhoto, data.authorExternalChannelId);
this.menu_endpoint = new NavigationEndpoint(data.contextMenuEndpoint);
}

View File

@@ -1,25 +1,15 @@
import { observe, ObservedArray, YTNode } from '../../../helpers.js';
import Parser from '../../../index.js';
import LiveChatAuthorBadge from '../../LiveChatAuthorBadge.js';
import MetadataBadge from '../../MetadataBadge.js';
import { YTNode } from '../../../helpers.js';
import Text from '../../misc/Text.js';
import Thumbnail from '../../misc/Thumbnail.js';
import NavigationEndpoint from '../../NavigationEndpoint.js';
import type { RawNode } from '../../../index.js';
import Author from '../../misc/Author.js';
class LiveChatPaidMessage extends YTNode {
static type = 'LiveChatPaidMessage';
message: Text;
author: {
id: string;
name: Text;
thumbnails: Thumbnail[];
badges: ObservedArray<LiveChatAuthorBadge | MetadataBadge>;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
};
author: Author;
header_background_color: number;
header_text_color: number;
@@ -35,22 +25,7 @@ class LiveChatPaidMessage extends YTNode {
super();
this.message = new Text(data.message);
this.author = {
id: data.authorExternalChannelId,
name: new Text(data.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
};
const badges = Parser.parseArray<LiveChatAuthorBadge | MetadataBadge>(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
this.author = new Author(data.authorName, data.authorBadges, data.authorPhoto, data.authorExternalChannelId);
this.header_background_color = data.headerBackgroundColor;
this.header_text_color = data.headerTextColor;

View File

@@ -1,26 +1,16 @@
import { observe, ObservedArray, YTNode } from '../../../helpers.js';
import Parser from '../../../index.js';
import LiveChatAuthorBadge from '../../LiveChatAuthorBadge.js';
import MetadataBadge from '../../MetadataBadge.js';
import { YTNode } from '../../../helpers.js';
import Text from '../../misc/Text.js';
import Thumbnail from '../../misc/Thumbnail.js';
import NavigationEndpoint from '../../NavigationEndpoint.js';
import type { RawNode } from '../../../index.js';
import Author from '../../misc/Author.js';
class LiveChatPaidSticker extends YTNode {
static type = 'LiveChatPaidSticker';
id: string;
author: {
id: string;
name: Text;
thumbnails: Thumbnail[];
badges: ObservedArray<LiveChatAuthorBadge | MetadataBadge>;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
};
author: Author;
money_chip_background_color: number;
money_chip_text_color: number;
@@ -36,22 +26,7 @@ class LiveChatPaidSticker extends YTNode {
super();
this.id = data.id;
this.author = {
id: data.authorExternalChannelId,
name: new Text(data.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
};
const badges = Parser.parseArray<LiveChatAuthorBadge | MetadataBadge>(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
this.author = new Author(data.authorName, data.authorBadges, data.authorPhoto, data.authorExternalChannelId);
this.money_chip_background_color = data.moneyChipBackgroundColor;
this.money_chip_text_color = data.moneyChipTextColor;

View File

@@ -1,28 +1,16 @@
import { observe, ObservedArray, YTNode } from '../../../helpers.js';
import { ObservedArray, YTNode } from '../../../helpers.js';
import Parser from '../../../index.js';
import Button from '../../Button.js';
import LiveChatAuthorBadge from '../../LiveChatAuthorBadge.js';
import MetadataBadge from '../../MetadataBadge.js';
import Text from '../../misc/Text.js';
import Thumbnail from '../../misc/Thumbnail.js';
import NavigationEndpoint from '../../NavigationEndpoint.js';
import type { RawNode } from '../../../index.js';
import Author from '../../misc/Author.js';
class LiveChatTextMessage extends YTNode {
static type = 'LiveChatTextMessage';
export class LiveChatMessageBase extends YTNode {
static type = 'LiveChatMessageBase';
message: Text;
author?: {
id: string;
name: Text;
thumbnails: Thumbnail[];
badges: ObservedArray<LiveChatAuthorBadge | MetadataBadge>;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
};
menu_endpoint?: NavigationEndpoint;
inline_action_buttons: ObservedArray<Button>;
timestamp: number;
id: string;
@@ -31,28 +19,26 @@ class LiveChatTextMessage extends YTNode {
super();
this.message = new Text(data.message);
this.author = {
id: data.authorExternalChannelId,
name: new Text(data.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
};
const badges = Parser.parseArray<LiveChatAuthorBadge | MetadataBadge>(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
this.menu_endpoint = new NavigationEndpoint(data.contextMenuEndpoint);
this.inline_action_buttons = Parser.parseArray<Button>(data.inlineActionButtons, [ Button ]);
this.inline_action_buttons = Parser.parseArray(data.inlineActionButtons, [ Button ]);
this.timestamp = Math.floor(parseInt(data.timestampUsec) / 1000);
this.id = data.id;
}
}
class LiveChatTextMessage extends LiveChatMessageBase {
static type = 'LiveChatTextMessage';
author: Author;
menu_endpoint?: NavigationEndpoint;
constructor(data: RawNode) {
super(data);
this.author = new Author(data.authorName, data.authorBadges, data.authorPhoto, data.authorExternalChannelId);
this.menu_endpoint = new NavigationEndpoint(data.contextMenuEndpoint);
}
}
export default LiveChatTextMessage;

View File

@@ -1,25 +1,15 @@
import Parser from '../../../index.js';
import LiveChatAuthorBadge from '../../LiveChatAuthorBadge.js';
import MetadataBadge from '../../MetadataBadge.js';
import Text from '../../misc/Text.js';
import Thumbnail from '../../misc/Thumbnail.js';
import NavigationEndpoint from '../../NavigationEndpoint.js';
import type { RawNode } from '../../../index.js';
import { observe, ObservedArray, YTNode } from '../../../helpers.js';
import { YTNode } from '../../../helpers.js';
import Author from '../../misc/Author.js';
class LiveChatTickerPaidMessageItem extends YTNode {
static type = 'LiveChatTickerPaidMessageItem';
author: {
id: string;
name: Text;
thumbnails: Thumbnail[];
badges: ObservedArray<LiveChatAuthorBadge | MetadataBadge>;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
};
author: Author;
amount: Text;
duration_sec: string; // Or number?
@@ -31,27 +21,12 @@ class LiveChatTickerPaidMessageItem extends YTNode {
constructor(data: RawNode) {
super();
this.author = {
id: data.authorExternalChannelId,
name: new Text(data?.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
};
const badges = Parser.parseArray<LiveChatAuthorBadge | MetadataBadge>(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
this.author = new Author(data.authorName, data.authorBadges, data.authorPhoto, data.authorExternalChannelId);
this.amount = new Text(data.amount);
this.duration_sec = data.durationSec;
this.full_duration_sec = data.fullDurationSec;
this.show_item = Parser.parse(data.showItemEndpoint?.showLiveChatItemEndpoint?.renderer);
this.show_item = Parser.parseItem(data.showItemEndpoint?.showLiveChatItemEndpoint?.renderer);
this.show_item_endpoint = new NavigationEndpoint(data.showItemEndpoint);
this.id = data.id;
}

View File

@@ -1,60 +1,7 @@
import Parser from '../../../index.js';
import LiveChatAuthorBadge from '../../LiveChatAuthorBadge.js';
import MetadataBadge from '../../MetadataBadge.js';
import Text from '../../misc/Text.js';
import Thumbnail from '../../misc/Thumbnail.js';
import NavigationEndpoint from '../../NavigationEndpoint.js';
import type { RawNode } from '../../../index.js';
import LiveChatTickerPaidMessageItem from './LiveChatTickerPaidMessageItem.js';
import { observe, ObservedArray, YTNode } from '../../../helpers.js';
class LiveChatTickerPaidStickerItem extends YTNode {
class LiveChatTickerPaidStickerItem extends LiveChatTickerPaidMessageItem {
static type = 'LiveChatTickerPaidStickerItem';
author: {
id: string;
name: Text;
thumbnails: Thumbnail[];
badges: ObservedArray<LiveChatAuthorBadge | MetadataBadge>;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
};
amount: Text;
duration_sec: string; // Or number?
full_duration_sec: string;
show_item;
show_item_endpoint: NavigationEndpoint;
id: string;
constructor(data: RawNode) {
super();
this.author = {
id: data.authorExternalChannelId,
name: new Text(data?.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
};
const badges = Parser.parseArray<LiveChatAuthorBadge | MetadataBadge>(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
this.amount = new Text(data.amount);
this.duration_sec = data.durationSec;
this.full_duration_sec = data.fullDurationSec;
this.show_item = Parser.parseItem(data.showItemEndpoint?.showLiveChatItemEndpoint?.renderer);
this.show_item_endpoint = new NavigationEndpoint(data.showItemEndpoint);
this.id = data.id;
}
}
export default LiveChatTickerPaidStickerItem;

View File

@@ -1,25 +1,14 @@
import Parser from '../../../index.js';
import { observe, ObservedArray, YTNode } from '../../../helpers.js';
import LiveChatAuthorBadge from '../../LiveChatAuthorBadge.js';
import MetadataBadge from '../../MetadataBadge.js';
import { YTNode } from '../../../helpers.js';
import Text from '../../misc/Text.js';
import Thumbnail from '../../misc/Thumbnail.js';
import type { RawNode } from '../../../index.js';
import Author from '../../misc/Author.js';
class LiveChatTickerSponsorItem extends YTNode {
static type = 'LiveChatTickerSponsorItem';
id: string;
detail: Text;
author: {
id: string;
name: Text;
thumbnails: Thumbnail[];
badges: ObservedArray<LiveChatAuthorBadge | MetadataBadge>;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
};
author: Author;
duration_sec: string;
@@ -28,22 +17,7 @@ class LiveChatTickerSponsorItem extends YTNode {
this.id = data.id;
this.detail = new Text(data.detailText);
this.author = {
id: data.authorExternalChannelId,
name: new Text(data?.authorName),
thumbnails: Thumbnail.fromResponse(data.sponsorPhoto),
badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
};
const badges = Parser.parseArray<LiveChatAuthorBadge | MetadataBadge>(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
this.author = new Author(data.authorName, data.authorBadges, data.sponsorPhoto, data.authorExternalChannelId);
this.duration_sec = data.durationSec;
// TODO: finish this

View File

@@ -1,8 +1,9 @@
import Parser from '../../../index.js';
import LiveChatTextMessage from './LiveChatTextMessage.js';
import { LiveChatMessageBase } from './LiveChatTextMessage.js';
import type { RawNode } from '../../../index.js';
class LiveChatViewerEngagementMessage extends LiveChatTextMessage {
class LiveChatViewerEngagementMessage extends LiveChatMessageBase {
static type = 'LiveChatViewerEngagementMessage';
icon_type: string;
@@ -10,8 +11,6 @@ class LiveChatViewerEngagementMessage extends LiveChatTextMessage {
constructor(data: RawNode) {
super(data);
delete this.author;
delete this.menu_endpoint;
this.icon_type = data.icon.iconType;
this.action_button = Parser.parseItem(data.actionButton);
}

View File

@@ -1,9 +1,10 @@
import Parser from '../../index.js';
import Parser, { RawNode } from '../../index.js';
import Text from './Text.js';
import NavigationEndpoint from '../NavigationEndpoint.js';
import TextRun from './TextRun.js';
import Thumbnail from './Thumbnail.js';
import Constants from '../../../utils/Constants.js';
import Text from './Text.js';
import { observe, ObservedArray, YTNode } from '../../helpers.js';
class Author {
#nav_text;
@@ -11,23 +12,26 @@ class Author {
id: string;
name: string;
thumbnails: Thumbnail[];
endpoint?: NavigationEndpoint;
badges?: any;
endpoint: NavigationEndpoint | null;
badges: ObservedArray<YTNode>;
is_moderator?: boolean | null;
is_verified?: boolean | null;
is_verified_artist?: boolean | null;
url: string | null;
constructor(item: any, badges?: any, thumbs?: any) {
constructor(item: RawNode, badges?: any, thumbs?: any, id?: string) {
this.#nav_text = new Text(item);
this.id =
(this.#nav_text.runs?.[0] as TextRun)?.endpoint?.payload?.browseId ||
id ||
(this.#nav_text?.runs?.[0] as TextRun)?.endpoint?.payload?.browseId ||
this.#nav_text?.endpoint?.payload?.browseId || 'N/A';
this.name = this.#nav_text.text || 'N/A';
this.name = this.#nav_text?.text || 'N/A';
this.thumbnails = thumbs ? Thumbnail.fromResponse(thumbs) : [];
this.endpoint = ((this.#nav_text.runs?.[0] as TextRun) as TextRun)?.endpoint || this.#nav_text.endpoint;
this.badges = Array.isArray(badges) ? Parser.parseArray(badges) : [];
this.endpoint = ((this.#nav_text?.runs?.[0] as TextRun) as TextRun)?.endpoint || this.#nav_text?.endpoint || null;
this.badges = Array.isArray(badges) ? Parser.parseArray(badges) : observe([] as YTNode[]);
this.is_moderator = this.badges?.some((badge: any) => badge.icon_type == 'MODERATOR') || null;
this.is_verified = this.badges?.some((badge: any) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') || null;
this.is_verified_artist = this.badges?.some((badge: any) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') || null;

View File

@@ -1,12 +0,0 @@
import Author from './Author.js';
class PlaylistAuthor extends Author {
constructor(item: any, badges?: any, thumbs?: any) {
super(item, badges, thumbs);
delete this.badges;
delete this.is_verified;
delete this.is_verified_artist;
}
}
export default PlaylistAuthor;

View File

@@ -1,6 +1,6 @@
import Parser from '../../index.js';
import NavigationEndpoint from '../NavigationEndpoint.js';
import type SectionList from '../SectionList.js';
import SectionList from '../SectionList.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
@@ -19,7 +19,7 @@ class AnchoredSection extends YTNode {
constructor(data: RawNode) {
super();
this.title = data.title;
this.content = Parser.parseItem<SectionList>(data.content);
this.content = Parser.parseItem(data.content, SectionList);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.category_assets = {
asset_key: data.categoryAssets?.assetKey,

View File

@@ -1,6 +1,6 @@
import Parser from '../../index.js';
import type Button from '../Button.js';
import type KidsCategoryTab from './KidsCategoryTab.js';
import Button from '../Button.js';
import KidsCategoryTab from './KidsCategoryTab.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
@@ -12,8 +12,8 @@ class KidsCategoriesHeader extends YTNode {
constructor(data: RawNode) {
super();
this.category_tabs = Parser.parseArray<KidsCategoryTab>(data.categoryTabs);
this.privacy_button = Parser.parseItem<Button>(data.privacyButtonRenderer);
this.category_tabs = Parser.parseArray(data.categoryTabs, KidsCategoryTab);
this.privacy_button = Parser.parseItem(data.privacyButtonRenderer, Button);
}
}

View File

@@ -1,5 +1,5 @@
import Parser from '../../index.js';
import type AnchoredSection from './AnchoredSection.js';
import AnchoredSection from './AnchoredSection.js';
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
@@ -10,7 +10,7 @@ class KidsHomeScreen extends YTNode {
constructor(data: RawNode) {
super();
this.anchors = Parser.parseArray<AnchoredSection>(data.anchors);
this.anchors = Parser.parseArray(data.anchors, AnchoredSection);
}
}

View File

@@ -474,9 +474,10 @@ export function observe<T extends YTNode>(obj: Array<T>): ObservedArray<T> {
}
export class Memo extends Map<string, YTNode[]> {
getType<T extends YTNode>(type: YTNodeConstructor<T> | YTNodeConstructor<T>[]) {
if (Array.isArray(type))
return observe(type.flatMap((type) => (this.get(type.type) || []) as T[]));
return observe((this.get(type.type) || []) as T[]);
getType<T extends YTNode, K extends YTNodeConstructor<T>[]>(types: K): ObservedArray<InstanceType<K[number]>>;
getType<T extends YTNode, K extends YTNodeConstructor<T>[]>(...types: K): ObservedArray<InstanceType<K[number]>>
getType(...types: YTNodeConstructor<YTNode>[] | YTNodeConstructor<YTNode>[][]) {
types = types.flat();
return observe(types.flatMap((type) => (this.get(type.type) || []) as YTNode[]));
}
}

View File

@@ -5,7 +5,6 @@ export { default as Author } from './classes/misc/Author.js';
export { default as ChildElement } from './classes/misc/ChildElement.js';
export { default as EmojiRun } from './classes/misc/EmojiRun.js';
export { default as Format } from './classes/misc/Format.js';
export { default as PlaylistAuthor } from './classes/misc/PlaylistAuthor.js';
export { default as Text } from './classes/misc/Text.js';
export { default as TextRun } from './classes/misc/TextRun.js';
export { default as Thumbnail } from './classes/misc/Thumbnail.js';

View File

@@ -1,15 +1,15 @@
import type AudioOnlyPlayability from './classes/AudioOnlyPlayability.js';
import type CardCollection from './classes/CardCollection.js';
import type Endscreen from './classes/Endscreen.js';
import type PlayerAnnotationsExpanded from './classes/PlayerAnnotationsExpanded.js';
import type PlayerCaptionsTracklist from './classes/PlayerCaptionsTracklist.js';
import type PlayerLiveStoryboardSpec from './classes/PlayerLiveStoryboardSpec.js';
import type PlayerStoryboardSpec from './classes/PlayerStoryboardSpec.js';
import type Message from './classes/Message.js';
import type LiveChatParticipantsList from './classes/LiveChatParticipantsList.js';
import type LiveChatHeader from './classes/LiveChatHeader.js';
import type LiveChatItemList from './classes/LiveChatItemList.js';
import type Alert from './classes/Alert.js';
import AudioOnlyPlayability from './classes/AudioOnlyPlayability.js';
import CardCollection from './classes/CardCollection.js';
import Endscreen from './classes/Endscreen.js';
import PlayerAnnotationsExpanded from './classes/PlayerAnnotationsExpanded.js';
import PlayerCaptionsTracklist from './classes/PlayerCaptionsTracklist.js';
import PlayerLiveStoryboardSpec from './classes/PlayerLiveStoryboardSpec.js';
import PlayerStoryboardSpec from './classes/PlayerStoryboardSpec.js';
import Message from './classes/Message.js';
import LiveChatParticipantsList from './classes/LiveChatParticipantsList.js';
import LiveChatHeader from './classes/LiveChatHeader.js';
import LiveChatItemList from './classes/LiveChatItemList.js';
import Alert from './classes/Alert.js';
import type { IParsedResponse, IRawResponse, RawData, RawNode } from './types/index.js';
@@ -170,7 +170,7 @@ export default class Parser {
parsed_data.overlay = overlay;
}
const alerts = this.parseArray<Alert>(data.alerts);
const alerts = this.parseArray(data.alerts, Alert);
if (alerts.length) {
parsed_data.alerts = alerts;
}
@@ -203,7 +203,7 @@ export default class Parser {
status: data.playabilityStatus.status,
reason: data.playabilityStatus.reason || '',
embeddable: !!data.playabilityStatus.playableInEmbed || false,
audio_only_playablility: this.parseItem<AudioOnlyPlayability>(data.playabilityStatus.audioOnlyPlayability),
audio_only_playablility: this.parseItem(data.playabilityStatus.audioOnlyPlayability, AudioOnlyPlayability),
error_screen: this.parseItem(data.playabilityStatus.errorScreen)
} : null;
@@ -233,7 +233,7 @@ export default class Parser {
parsed_data.endpoint = endpoint;
}
const captions = this.parseItem<PlayerCaptionsTracklist>(data.captions);
const captions = this.parseItem(data.captions, PlayerCaptionsTracklist);
if (captions) {
parsed_data.captions = captions;
}
@@ -243,22 +243,22 @@ export default class Parser {
parsed_data.video_details = video_details;
}
const annotations = this.parseArray<PlayerAnnotationsExpanded>(data.annotations);
const annotations = this.parseArray(data.annotations, PlayerAnnotationsExpanded);
if (annotations.length) {
parsed_data.annotations = annotations;
}
const storyboards = this.parseItem<PlayerStoryboardSpec | PlayerLiveStoryboardSpec>(data.storyboards);
const storyboards = this.parseItem(data.storyboards, [ PlayerStoryboardSpec, PlayerLiveStoryboardSpec ]);
if (storyboards) {
parsed_data.storyboards = storyboards;
}
const endscreen = this.parseItem<Endscreen>(data.endscreen);
const endscreen = this.parseItem(data.endscreen, Endscreen);
if (endscreen) {
parsed_data.endscreen = endscreen;
}
const cards = this.parseItem<CardCollection>(data.cards);
const cards = this.parseItem(data.cards, CardCollection);
if (cards) {
parsed_data.cards = cards;
}
@@ -279,7 +279,10 @@ export default class Parser {
* @param data - The data to parse.
* @param validTypes - YTNode types that are allowed to be parsed.
*/
static parseItem<T extends YTNode = YTNode>(data?: RawNode, validTypes?: YTNodeConstructor<T> | YTNodeConstructor<T>[]) {
static parseItem<T extends YTNode, K extends YTNodeConstructor<T>[]>(data: RawNode | undefined, validTypes: K): InstanceType<K[number]> | null;
static parseItem<T extends YTNode>(data: RawNode | undefined, validTypes: YTNodeConstructor<T>): T | null;
static parseItem(data?: RawNode) : YTNode;
static parseItem(data?: RawNode, validTypes?: YTNodeConstructor | YTNodeConstructor[]) {
if (!data) return null;
const keys = Object.keys(data);
@@ -306,7 +309,7 @@ export default class Parser {
const result = new TargetClass(data[keys[0]]);
this.#addToMemo(classname, result);
return result as T;
return result;
} catch (err) {
this.#errorHandler({ classname, classdata: data[keys[0]], err });
return null;
@@ -316,17 +319,24 @@ export default class Parser {
return null;
}
extra() {
Parser.parseItem({}, [ MusicMultiSelectMenuItem, MusicMultiSelectMenuItem ]);
}
/**
* Parses an array of items.
* @param data - The data to parse.
* @param validTypes - YTNode types that are allowed to be parsed.
*/
static parseArray<T extends YTNode = YTNode>(data?: RawNode[], validTypes?: YTNodeConstructor<T> | YTNodeConstructor<T>[]) {
static parseArray<T extends YTNode, K extends YTNodeConstructor<T>[]>(data: RawNode[] | undefined, validTypes: K): ObservedArray<InstanceType<K[number]>>;
static parseArray<T extends YTNode = YTNode>(data: RawNode[] | undefined, validType: YTNodeConstructor<T>): ObservedArray<T>;
static parseArray(data: RawNode[] | undefined): ObservedArray<YTNode>;
static parseArray(data?: RawNode[], validTypes?: YTNodeConstructor | YTNodeConstructor[]) {
if (Array.isArray(data)) {
const results: T[] = [];
const results: YTNode[] = [];
for (const item of data) {
const result = this.parseItem(item, validTypes);
const result = this.parseItem(item, validTypes as YTNodeConstructor);
if (result) {
results.push(result);
}
@@ -334,7 +344,7 @@ export default class Parser {
return observe(results);
} else if (!data) {
return observe([] as T[]);
return observe([] as YTNode[]);
}
throw new ParsingError('Expected array but got a single item');
}
@@ -345,7 +355,7 @@ export default class Parser {
* @param requireArray - Whether the data should be parsed as an array.
* @param validTypes - YTNode types that are allowed to be parsed.
*/
static parse<T extends YTNode = YTNode>(data: RawData, requireArray: true, validTypes?: YTNodeConstructor<T> | YTNodeConstructor<T>[]): ObservedArray<T> | null;
static parse<T extends YTNode, K extends YTNodeConstructor<T>[]>(data: RawData, requireArray: true, validTypes?: K): ObservedArray<InstanceType<K[number]>> | null;
static parse<T extends YTNode = YTNode>(data?: RawData, requireArray?: false | undefined, validTypes?: YTNodeConstructor<T> | YTNodeConstructor<T>[]): SuperParsedResult<T>;
static parse<T extends YTNode = YTNode>(data?: RawData, requireArray?: boolean, validTypes?: YTNodeConstructor<T> | YTNodeConstructor<T>[]) {
if (!data) return null;
@@ -354,7 +364,7 @@ export default class Parser {
const results: T[] = [];
for (const item of data) {
const result = this.parseItem(item, validTypes);
const result = this.parseItem(item, validTypes as YTNodeConstructor<T>);
if (result) {
results.push(result);
}
@@ -367,7 +377,7 @@ export default class Parser {
throw new ParsingError('Expected array but got a single item');
}
return new SuperParsedResult(this.parseItem(data, validTypes));
return new SuperParsedResult(this.parseItem(data, validTypes as YTNodeConstructor<T>));
}
static parseC(data: RawNode) {
@@ -699,10 +709,10 @@ export class LiveChatContinuation extends YTNode {
}), true) || observe<YTNode>([]);
this.action_panel = Parser.parseItem(data.actionPanel);
this.item_list = Parser.parseItem<LiveChatItemList>(data.itemList);
this.header = Parser.parseItem<LiveChatHeader>(data.header);
this.participants_list = Parser.parseItem<LiveChatParticipantsList>(data.participantsList);
this.popout_message = Parser.parseItem<Message>(data.popoutMessage);
this.item_list = Parser.parseItem(data.itemList, LiveChatItemList);
this.header = Parser.parseItem(data.header, LiveChatHeader);
this.participants_list = Parser.parseItem(data.participantsList, LiveChatParticipantsList);
this.popout_message = Parser.parseItem(data.popoutMessage, Message);
this.emojis = data.emojis?.map((emoji: any) => ({
emoji_id: emoji.emojiId,

View File

@@ -36,6 +36,7 @@ import ItemMenu from './ItemMenu.js';
import type Actions from '../../core/Actions.js';
import type { IParsedResponse, IUpdatedMetadataResponse } from '../types/ParsedResponse.js';
import { NavigationEndpoint } from '../nodes.js';
export type ChatAction =
AddChatItemAction | AddBannerToLiveChatCommand | AddLiveChatTickerItemAction |
@@ -288,10 +289,10 @@ class LiveChat extends EventEmitter {
* Retrieves given chat item's menu.
*/
async getItemMenu(item: ChatItemWithMenu): Promise<ItemMenu> {
if (!item.menu_endpoint)
if (!item.hasKey('menu_endpoint') || !item.key('menu_endpoint').isInstanceof(NavigationEndpoint))
throw new InnertubeError('This item does not have a menu.', item);
const response = await item.menu_endpoint.call(this.#actions, { parse: true });
const response = await item.key('menu_endpoint').instanceof(NavigationEndpoint).call(this.#actions, { parse: true });
if (!response)
throw new InnertubeError('Could not retrieve item menu.', item);

View File

@@ -1,6 +1,3 @@
import Constants from '../../utils/Constants.js';
import Parser from '../index.js';
import ChipCloud from '../classes/ChipCloud.js';
import ChipCloudChip from '../classes/ChipCloudChip.js';
import CommentsEntryPointHeader from '../classes/comments/CommentsEntryPointHeader.js';
@@ -25,7 +22,6 @@ import PlayerLegacyDesktopYpcTrailer from '../classes/PlayerLegacyDesktopYpcTrai
import type CardCollection from '../classes/CardCollection.js';
import type Endscreen from '../classes/Endscreen.js';
import type Format from '../classes/misc/Format.js';
import type PlayerAnnotationsExpanded from '../classes/PlayerAnnotationsExpanded.js';
import type PlayerCaptionsTracklist from '../classes/PlayerCaptionsTracklist.js';
import type PlayerLiveStoryboardSpec from '../classes/PlayerLiveStoryboardSpec.js';
@@ -33,33 +29,21 @@ import type PlayerStoryboardSpec from '../classes/PlayerStoryboardSpec.js';
import type Actions from '../../core/Actions.js';
import type { ApiResponse } from '../../core/Actions.js';
import type Player from '../../core/Player.js';
import type { ObservedArray, YTNode } from '../helpers.js';
import type { INextResponse, IPlayerResponse } from '../types/ParsedResponse.js';
import FormatUtils, { DownloadOptions, FormatFilter, FormatOptions, URLTransformer } from '../../utils/FormatUtils.js';
import { ObservedArray, YTNode } from '../helpers.js';
import { InnertubeError } from '../../utils/Utils.js';
import { MediaInfo } from '../../core/MediaInfo.js';
class VideoInfo {
#page: [IPlayerResponse, INextResponse?];
#actions: Actions;
#player?: Player;
#cpn?: string;
class VideoInfo extends MediaInfo {
#watch_next_continuation?: ContinuationItem;
basic_info;
streaming_data;
playability_status;
annotations?: ObservedArray<PlayerAnnotationsExpanded>;
storyboards?: PlayerStoryboardSpec | PlayerLiveStoryboardSpec;
endscreen?: Endscreen;
captions?: PlayerCaptionsTracklist;
cards?: CardCollection;
#playback_tracking;
primary_info?: VideoPrimaryInfo | null;
secondary_info?: VideoSecondaryInfo | null;
playlist?;
@@ -78,18 +62,10 @@ class VideoInfo {
* @param player - Player instance.
* @param cpn - Client Playback Nonce.
*/
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, player?: Player, cpn?: string) {
this.#actions = actions;
this.#player = player;
this.#cpn = cpn;
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, cpn: string) {
super(data, actions, cpn);
const info = Parser.parseResponse<IPlayerResponse>(data[0].data);
const next = data?.[1]?.data ? Parser.parseResponse<INextResponse>(data[1].data) : undefined;
this.#page = [ info, next ];
if (info.playability_status?.status === 'ERROR')
throw new InnertubeError('This video is unavailable', info.playability_status);
const [ info, next ] = this.page;
if (info.microformat && !info.microformat?.is(PlayerMicroformat, MicroformatData))
throw new InnertubeError('Invalid microformat', info.microformat);
@@ -114,16 +90,12 @@ class VideoInfo {
is_disliked: undefined as boolean | undefined
};
this.streaming_data = info.streaming_data;
this.playability_status = info.playability_status;
this.annotations = info.annotations;
this.storyboards = info.storyboards;
this.endscreen = info.endscreen;
this.captions = info.captions;
this.cards = info.cards;
this.#playback_tracking = info.playback_tracking;
const two_col = next?.contents?.item().as(TwoColumnWatchNextResults);
const results = two_col?.results;
@@ -200,7 +172,7 @@ class VideoInfo {
if (cloud_chip.is_selected) return this;
const response = await cloud_chip.endpoint?.call(this.#actions, { parse: true });
const response = await cloud_chip.endpoint?.call(this.actions, { parse: true });
const data = response?.on_response_received_endpoints?.get({ target_id: 'watch-next-feed' });
this.watch_next_feed = data?.contents;
@@ -212,24 +184,7 @@ class VideoInfo {
* Adds video to the watch history.
*/
async addToWatchHistory(): Promise<Response> {
if (!this.#playback_tracking)
throw new InnertubeError('Playback tracking not available');
const url_params = {
cpn: this.#cpn,
fmt: 251,
rtn: 0,
rt: 0
};
const url = this.#playback_tracking.videostats_playback_url.replace('https://s.', 'https://www.');
const response = await this.#actions.stats(url, {
client_name: Constants.CLIENTS.WEB.NAME,
client_version: Constants.CLIENTS.WEB.VERSION
}, url_params);
return response;
return super.addToWatchHistory();
}
@@ -240,7 +195,7 @@ class VideoInfo {
if (!this.#watch_next_continuation)
throw new InnertubeError('Watch next feed continuation not found');
const response = await this.#watch_next_continuation?.endpoint.call(this.#actions, { parse: true });
const response = await this.#watch_next_continuation?.endpoint.call(this.actions, { parse: true });
const data = response?.on_response_received_endpoints?.get({ type: 'appendContinuationItemsAction' });
if (!data)
@@ -272,7 +227,7 @@ class VideoInfo {
if (button.is_toggled)
throw new InnertubeError('This video is already liked', { video_id: this.basic_info.id });
const response = await button.endpoint.call(this.#actions);
const response = await button.endpoint.call(this.actions);
return response;
}
@@ -293,7 +248,7 @@ class VideoInfo {
if (button.is_toggled)
throw new InnertubeError('This video is already disliked', { video_id: this.basic_info.id });
const response = await button.endpoint.call(this.#actions);
const response = await button.endpoint.call(this.actions);
return response;
}
@@ -321,7 +276,7 @@ class VideoInfo {
if (!button)
throw new InnertubeError('This video is not liked/disliked', { video_id: this.basic_info.id });
const response = await button.toggled_endpoint.call(this.#actions);
const response = await button.toggled_endpoint.call(this.actions);
return response;
}
@@ -343,38 +298,12 @@ class VideoInfo {
if (this.has_trailer) {
const player_response = this.playability_status.error_screen?.as(PlayerLegacyDesktopYpcTrailer).trailer?.player_response;
if (player_response) {
return new VideoInfo([ { data: player_response } as ApiResponse ], this.#actions, this.#player, this.#cpn);
return new VideoInfo([ { data: player_response } as ApiResponse ], this.actions, this.cpn);
}
}
return null;
}
/**
* Selects the format that best matches the given options.
* @param options - Options
*/
chooseFormat(options: FormatOptions): Format {
return FormatUtils.chooseFormat(options, this.streaming_data);
}
/**
* Generates a DASH manifest from the streaming data.
* @param url_transformer - Function to transform the URLs.
* @param format_filter - Function to filter the formats.
* @returns DASH manifest
*/
async toDash(url_transformer?: URLTransformer, format_filter?: FormatFilter): Promise<string> {
return await FormatUtils.toDash(this.streaming_data, url_transformer, format_filter, this.#cpn, this.#player, this.#actions);
}
/**
* Downloads the video.
* @param options - Download options.
*/
async download(options: DownloadOptions = {}): Promise<ReadableStream<Uint8Array>> {
return FormatUtils.download(options, this.#actions, this.playability_status, this.streaming_data, this.#actions.session.player, this.cpn);
}
/**
* Watch next feed filters.
*/
@@ -382,20 +311,6 @@ class VideoInfo {
return this.related_chip_cloud?.chips?.map((chip) => chip.text?.toString()) || [];
}
/**
* Actions instance.
*/
get actions(): Actions {
return this.#actions;
}
/**
* Content Playback Nonce.
*/
get cpn(): string | undefined {
return this.#cpn;
}
/**
* Checks if continuation is available for the watch next feed.
*/
@@ -455,13 +370,6 @@ class VideoInfo {
*/
return [];
}
/**
* Original parsed InnerTube response.
*/
get page(): [IPlayerResponse, INextResponse?] {
return this.#page;
}
}
export default VideoInfo;

View File

@@ -1,59 +1,32 @@
import Parser from '../index.js';
import ItemSection from '../classes/ItemSection.js';
import NavigationEndpoint from '../classes/NavigationEndpoint.js';
import PlayerOverlay from '../classes/PlayerOverlay.js';
import SlimVideoMetadata from '../classes/SlimVideoMetadata.js';
import TwoColumnWatchNextResults from '../classes/TwoColumnWatchNextResults.js';
import type Format from '../classes/misc/Format.js';
import type Actions from '../../core/Actions.js';
import type { ApiResponse } from '../../core/Actions.js';
import type { ObservedArray, YTNode } from '../helpers.js';
import type { INextResponse, IPlayerResponse } from '../types/ParsedResponse.js';
import { Constants } from '../../utils/index.js';
import { InnertubeError } from '../../utils/Utils.js';
import FormatUtils, { DownloadOptions, FormatFilter, FormatOptions, URLTransformer } from '../../utils/FormatUtils.js';
class VideoInfo {
#page: [IPlayerResponse, INextResponse?];
#actions: Actions;
#cpn: string;
import { MediaInfo } from '../../core/MediaInfo.js';
class VideoInfo extends MediaInfo {
basic_info;
streaming_data;
playability_status;
captions;
#playback_tracking;
slim_video_metadata?: SlimVideoMetadata;
watch_next_feed?: ObservedArray<YTNode>;
current_video_endpoint?: NavigationEndpoint;
player_overlays?: PlayerOverlay;
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, cpn: string) {
this.#actions = actions;
super(data, actions, cpn);
const info = Parser.parseResponse<IPlayerResponse>(data[0].data);
const next = data?.[1]?.data ? Parser.parseResponse<INextResponse>(data[1].data) : undefined;
this.#page = [ info, next ];
this.#cpn = cpn;
if (info.playability_status?.status === 'ERROR')
throw new InnertubeError('This video is unavailable', info.playability_status);
const [ info, next ] = this.page;
this.basic_info = info.video_details;
this.streaming_data = info.streaming_data;
this.playability_status = info.playability_status;
this.captions = info.captions;
this.#playback_tracking = info.playback_tracking;
const two_col = next?.contents?.item().as(TwoColumnWatchNextResults);
const results = two_col?.results;
@@ -67,72 +40,11 @@ class VideoInfo {
}
}
/**
* Generates a DASH manifest from the streaming data.
* @param url_transformer - Function to transform the URLs.
* @param format_filter - Function to filter the formats.
* @returns DASH manifest
*/
async toDash(url_transformer?: URLTransformer, format_filter?: FormatFilter): Promise<string> {
return await FormatUtils.toDash(this.streaming_data, url_transformer, format_filter, this.#cpn, this.#actions.session.player, this.#actions);
}
/**
* Selects the format that best matches the given options.
* @param options - Options
*/
chooseFormat(options: FormatOptions): Format {
return FormatUtils.chooseFormat(options, this.streaming_data);
}
/**
* Downloads the video.
* @param options - Download options.
*/
async download(options: DownloadOptions = {}): Promise<ReadableStream<Uint8Array>> {
return FormatUtils.download(options, this.#actions, this.playability_status, this.streaming_data, this.#actions.session.player, this.cpn);
}
/**
* Adds video to the watch history.
*/
async addToWatchHistory(): Promise<Response> {
if (!this.#playback_tracking)
throw new InnertubeError('Playback tracking not available');
const url_params = {
cpn: this.#cpn,
fmt: 251,
rtn: 0,
rt: 0
};
const url = this.#playback_tracking.videostats_playback_url.replace('https://s.', 'https://www.');
const response = await this.#actions.stats(url, {
client_name: Constants.CLIENTS.WEB.NAME,
client_version: Constants.CLIENTS.WEB.VERSION
}, url_params);
return response;
}
/**
* Actions instance.
*/
get actions(): Actions {
return this.#actions;
}
/**
* Content Playback Nonce.
*/
get cpn(): string | undefined {
return this.#cpn;
}
get page(): [IPlayerResponse, INextResponse?] {
return this.#page;
return super.addToWatchHistory();
}
}

View File

@@ -1,4 +1,3 @@
import Parser from '../index.js';
import type Actions from '../../core/Actions.js';
import type { ApiResponse } from '../../core/Actions.js';
@@ -19,43 +18,25 @@ import SectionList from '../classes/SectionList.js';
import Tab from '../classes/Tab.js';
import WatchNextTabbedResults from '../classes/WatchNextTabbedResults.js';
import type Format from '../classes/misc/Format.js';
import type NavigationEndpoint from '../classes/NavigationEndpoint.js';
import type PlayerLiveStoryboardSpec from '../classes/PlayerLiveStoryboardSpec.js';
import type PlayerStoryboardSpec from '../classes/PlayerStoryboardSpec.js';
import type { ObservedArray, YTNode } from '../helpers.js';
import type { INextResponse, IPlayerResponse } from '../types/ParsedResponse.js';
import FormatUtils, { DownloadOptions, FormatFilter, FormatOptions, URLTransformer } from '../../utils/FormatUtils.js';
class TrackInfo {
#page: [ IPlayerResponse, INextResponse? ];
#actions: Actions;
#cpn: string;
import { MediaInfo } from '../../core/MediaInfo.js';
class TrackInfo extends MediaInfo {
basic_info;
streaming_data;
playability_status;
storyboards?: PlayerStoryboardSpec | PlayerLiveStoryboardSpec;
endscreen?: Endscreen;
#playback_tracking;
tabs?: ObservedArray<Tab>;
current_video_endpoint?: NavigationEndpoint;
player_overlays?: PlayerOverlay;
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, cpn: string) {
this.#actions = actions;
super(data, actions, cpn);
const info = Parser.parseResponse<IPlayerResponse>(data[0].data);
const next = data?.[1]?.data ? Parser.parseResponse<INextResponse>(data[1].data) : undefined;
this.#page = [ info, next ];
this.#cpn = cpn;
if (info.playability_status?.status === 'ERROR')
throw new InnertubeError('This video is unavailable', info.playability_status);
const [ info, next ] = this.page;
if (!info.microformat?.is(MicroformatData))
throw new InnertubeError('Invalid microformat', info.microformat);
@@ -71,13 +52,9 @@ class TrackInfo {
}
};
this.streaming_data = info.streaming_data;
this.playability_status = info.playability_status;
this.storyboards = info.storyboards;
this.endscreen = info.endscreen;
this.#playback_tracking = info.playback_tracking;
if (next) {
const tabbed_results = next.contents_memo?.getType(WatchNextTabbedResults)?.[0];
@@ -89,32 +66,6 @@ class TrackInfo {
}
}
/**
* Generates a DASH manifest from the streaming data.
* @param url_transformer - Function to transform the URLs.
* @param format_filter - Function to filter the formats.
* @returns DASH manifest
*/
async toDash(url_transformer?: URLTransformer, format_filter?: FormatFilter): Promise<string> {
return await FormatUtils.toDash(this.streaming_data, url_transformer, format_filter, this.#cpn, this.#actions.session.player, this.#actions);
}
/**
* Selects the format that best matches the given options.
* @param options - Options
*/
chooseFormat(options: FormatOptions): Format {
return FormatUtils.chooseFormat(options, this.streaming_data);
}
/**
* Downloads the video.
* @param options - Download options.
*/
async download(options: DownloadOptions = {}): Promise<ReadableStream<Uint8Array>> {
return FormatUtils.download(options, this.#actions, this.playability_status, this.streaming_data, this.#actions.session.player);
}
/**
* Retrieves contents of the given tab.
*/
@@ -133,7 +84,7 @@ class TrackInfo {
if (target_tab.content)
return target_tab.content;
const page = await target_tab.endpoint.call(this.#actions, { client: 'YTMUSIC', parse: true });
const page = await target_tab.endpoint.call(this.actions, { client: 'YTMUSIC', parse: true });
if (page.contents?.item().key('type').string() === 'Message')
return page.contents.item().as(Message);
@@ -161,7 +112,7 @@ class TrackInfo {
if (!automix_preview_video)
throw new InnertubeError('Automix item not found');
const page = await automix_preview_video.playlist_video?.endpoint.call(this.#actions, {
const page = await automix_preview_video.playlist_video?.endpoint.call(this.actions, {
videoId: this.basic_info.id,
client: 'YTMUSIC',
parse: true
@@ -196,33 +147,12 @@ class TrackInfo {
* Adds the song to the watch history.
*/
async addToWatchHistory(): Promise<Response> {
if (!this.#playback_tracking)
throw new InnertubeError('Playback tracking not available');
const url_params = {
cpn: this.#cpn,
fmt: 251,
rtn: 0,
rt: 0
};
const url = this.#playback_tracking.videostats_playback_url.replace('https://s.', 'https://music.');
const response = await this.#actions.stats(url, {
client_name: Constants.CLIENTS.YTMUSIC.NAME,
client_version: Constants.CLIENTS.YTMUSIC.VERSION
}, url_params);
return response;
return super.addToWatchHistory(Constants.CLIENTS.YTMUSIC.NAME, Constants.CLIENTS.YTMUSIC.VERSION, 'https://music.');
}
get available_tabs(): string[] {
return this.tabs ? this.tabs.map((tab) => tab.title) : [];
}
get page(): [IPlayerResponse, INextResponse?] {
return this.#page;
}
}
export default TrackInfo;