feat(Parser): just-in-time YTNode generation (#310)

* refactor: merge NavigatableText into Text

* fix(Text): data might not be object

* refactor: remove GetParserByName from map

* feat(Parser): just-in-time YTNode generation

* refactor: cleanup YTNodeGenerator

* fix: YTNode map imports

* feat(YTNodeGenerator): primative types

Add support for inferring primatives types

* fix(YTNodeGenerator): NavigationEndpoint detection

* fix(YTNodeGenerator): fix generated typescript

Correct types and linting for generated typescript class

* chore: update parsers after merge

* feat: add support for object type inference

* fix: object type def

* docs: basic YTNodeGenerator explanation

* docs: tsdoc for YTNodeGenerator

* docs: update parser updating guide

* fix: apply suggested changes

* docs: accessing generated nodes
This commit is contained in:
Daniel Wykerd
2023-03-15 08:39:36 +02:00
committed by GitHub
parent ffd7d79308
commit 2cee59024c
20 changed files with 1201 additions and 1169 deletions

View File

@@ -3,7 +3,6 @@ import Parser from '../index.js';
import Thumbnail from './misc/Thumbnail.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import NavigatableText from './misc/NavigatableText.js';
import { YTNode } from '../helpers.js';
class GridPlaylist extends YTNode {
@@ -14,7 +13,7 @@ class GridPlaylist extends YTNode {
author?: PlaylistAuthor;
badges;
endpoint: NavigationEndpoint;
view_playlist: NavigatableText;
view_playlist: Text;
thumbnails: Thumbnail[];
thumbnail_renderer;
sidebar_thumbnails: Thumbnail[] | null;
@@ -32,7 +31,7 @@ class GridPlaylist extends YTNode {
this.badges = Parser.parse(data.ownerBadges);
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
this.view_playlist = new NavigatableText(data.viewPlaylistText);
this.view_playlist = new Text(data.viewPlaylistText);
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.thumbnail_renderer = Parser.parse(data.thumbnailRenderer);
this.sidebar_thumbnails = [].concat(...data.sidebarThumbnails?.map((thumbnail: any) => Thumbnail.fromResponse(thumbnail)) || []) || null;

View File

@@ -43,8 +43,8 @@ class Movie extends YTNode {
this.author = new Author(data.longBylineText, data.ownerBadges, data.channelThumbnailSupportedRenderers?.channelThumbnailWithLinkRenderer?.thumbnail);
this.duration = {
text: data.lengthText ? new Text(data.lengthText).text : new Text(overlay_time_status).text,
seconds: timeToSeconds(data.lengthText ? new Text(data.lengthText).text : new Text(overlay_time_status).text)
text: data.lengthText ? new Text(data.lengthText).toString() : new Text(overlay_time_status).toString(),
seconds: timeToSeconds(data.lengthText ? new Text(data.lengthText).toString() : new Text(overlay_time_status).toString())
};
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);

View File

@@ -14,7 +14,7 @@ class MusicSortFilterButton extends YTNode {
constructor(data: any) {
super();
this.title = new Text(data.title).text;
this.title = new Text(data.title).toString();
this.icon_type = data.icon?.icon_type || null;
this.menu = Parser.parseItem(data.menu, MusicMultiSelectMenu);
}

View File

@@ -4,7 +4,6 @@ import Thumbnail from './misc/Thumbnail.js';
import NavigationEndpoint from './NavigationEndpoint.js';
import PlaylistAuthor from './misc/PlaylistAuthor.js';
import { YTNode } from '../helpers.js';
import NavigatableText from './misc/NavigatableText.js';
class Playlist extends YTNode {
static type = 'Playlist';
@@ -21,7 +20,7 @@ class Playlist extends YTNode {
badges;
endpoint: NavigationEndpoint;
thumbnail_overlays;
view_playlist?: NavigatableText;
view_playlist?: Text;
constructor(data: any) {
super();
@@ -43,7 +42,7 @@ class Playlist extends YTNode {
this.thumbnail_overlays = Parser.parseArray(data.thumbnailOverlays);
if (data.viewPlaylistText) {
this.view_playlist = new NavigatableText(data.viewPlaylistText);
this.view_playlist = new Text(data.viewPlaylistText);
}
}
}

View File

@@ -47,7 +47,7 @@ class PlaylistVideo extends YTNode {
}
this.duration = {
text: new Text(data.lengthText).text,
text: new Text(data.lengthText).toString(),
seconds: parseInt(data.lengthSeconds)
};
}

View File

@@ -77,8 +77,8 @@ class Video extends YTNode {
}
this.duration = {
text: data.lengthText ? new Text(data.lengthText).text : new Text(overlay_time_status).text,
seconds: timeToSeconds(data.lengthText ? new Text(data.lengthText).text : new Text(overlay_time_status).text)
text: data.lengthText ? new Text(data.lengthText).toString() : new Text(overlay_time_status).toString(),
seconds: timeToSeconds(data.lengthText ? new Text(data.lengthText).toString() : new Text(overlay_time_status).toString())
};
this.show_action_menu = data.showActionMenu;

View File

@@ -13,7 +13,7 @@ class MusicMultiSelectMenu extends YTNode {
constructor(data: RawNode) {
super();
this.title = new Text(data.title.musicMenuTitleRenderer?.primaryText).text;
this.title = new Text(data.title.musicMenuTitleRenderer?.primaryText).toString();
this.options = Parser.parseArray(data.options, [ MusicMultiSelectMenuItem, MusicMenuItemDivider ]);
}
}

View File

@@ -14,7 +14,7 @@ class MusicMultiSelectMenuItem extends YTNode {
constructor(data: RawNode) {
super();
this.title = new Text(data.title).text;
this.title = new Text(data.title).toString();
this.form_item_entity_key = data.formItemEntityKey;
this.selected_icon_type = data.selectedIcon?.iconType || null;
this.endpoint = data.selectedCommand ? new NavigationEndpoint(data.selectedCommand) : null;

View File

@@ -1,9 +1,9 @@
import Parser from '../../index.js';
import NavigatableText from './NavigatableText.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';
class Author {
#nav_text;
@@ -11,14 +11,14 @@ class Author {
id: string;
name: string;
thumbnails: Thumbnail[];
endpoint: NavigationEndpoint | null;
endpoint?: NavigationEndpoint;
badges?: any;
is_verified?: boolean | null;
is_verified_artist?: boolean | null;
url: string | null;
constructor(item: any, badges?: any, thumbs?: any) {
this.#nav_text = new NavigatableText(item);
this.#nav_text = new Text(item);
this.id =
(this.#nav_text.runs?.[0] as TextRun)?.endpoint?.payload?.browseId ||

View File

@@ -1,27 +0,0 @@
import Text from './Text.js';
import NavigationEndpoint from '../NavigationEndpoint.js';
import type { RawNode } from '../../index.js';
class NavigatableText extends Text {
static type = 'NavigatableText';
endpoint: NavigationEndpoint | null;
constructor(node: RawNode) {
super(node);
// TODO: is this needed? Text now supports this itself
this.endpoint =
node?.runs?.[0]?.navigationEndpoint ?
new NavigationEndpoint(node?.runs[0].navigationEndpoint) :
node?.navigationEndpoint ?
new NavigationEndpoint(node?.navigationEndpoint) :
node?.titleNavigationEndpoint ?
new NavigationEndpoint(node?.titleNavigationEndpoint) : null;
}
toJSON(): NavigatableText {
return this;
}
}
export default NavigatableText;

View File

@@ -1,5 +1,6 @@
import TextRun from './TextRun.js';
import EmojiRun from './EmojiRun.js';
import NavigationEndpoint from '../NavigationEndpoint.js';
import type { RawNode } from '../../index.js';
export interface Run {
@@ -18,8 +19,9 @@ export function escape(text: string) {
}
class Text {
text: string;
text?: string;
runs;
endpoint?: NavigationEndpoint;
constructor(data: RawNode) {
if (data?.hasOwnProperty('runs') && Array.isArray(data.runs)) {
@@ -29,16 +31,28 @@ class Text {
);
this.text = this.runs.map((run) => run.text).join('');
} else {
this.text = data?.simpleText || 'N/A';
this.text = data?.simpleText;
}
if (typeof data === 'object' && data !== null && Reflect.has(data, 'navigationEndpoint')) {
this.endpoint = new NavigationEndpoint(data.navigationEndpoint);
}
if (typeof data === 'object' && data !== null && Reflect.has(data, 'titleNavigationEndpoint')) {
this.endpoint = new NavigationEndpoint(data.titleNavigationEndpoint);
}
if (!this.endpoint)
this.endpoint = (this.runs?.[0] as TextRun)?.endpoint;
}
toHTML() {
return this.runs ? this.runs.map((run) => run.toHTML()).join('') : this.text;
}
isEmpty() {
return this.text === undefined;
}
toString() {
return this.text;
return this.text || 'N/A';
}
}