diff --git a/docs/updating-the-parser.md b/docs/updating-the-parser.md index 45a04ad89..c996b8372 100644 --- a/docs/updating-the-parser.md +++ b/docs/updating-the-parser.md @@ -5,16 +5,18 @@ YouTube is constantly changing, so it is not rare to see YouTube crawlers/scrape Our parser, on the other hand, was written so that it behaves similarly to an official client, parsing and mapping renderers (a.k.a YTNodes) dynamically without hard-coding their path in the response. This way, whenever a new renderer pops up (e.g; YouTube adds a new feature / minor UI changes) the library will print a warning similar to this: ``` -InnertubeError: SomeRenderer not found! +SomeRenderer not found! This is a bug, want to help us fix it? Follow the instructions at https://github.com/LuanRT/YouTube.js/blob/main/docs/updating-the-parser.md or report it at https://github.com/LuanRT/YouTube.js/issues! - at Parser.printError (...) - at Parser.parseItem (...) - at Parser.parseArray (...) { - info: { - // renderer data, can be used as a reference to implement the renderer parser - }, - date: 2022-05-22T22:16:06.831Z, - version: '2.2.3' +Introspected and JIT generated this class in the meantime: +class SomeRenderer extends YTNode { + static type = 'SomeRenderer'; + + // ... + + constructor(data: RawNode) { + super(); + // ... + } } ``` @@ -24,7 +26,7 @@ This warning **does not** throw an error. The parser itself will continue workin Thanks to the modularity of the parser, a renderer can be implemented by simply adding a new file anywhere in the [classes directory](../src/parser/classes)! -For example, say we found a new renderer named `verticalListRenderer`, to let the parser know it exists we would have to create a file with the following structure: +For example, say we found a new renderer named `verticalListRenderer`, to let the parser know it exists at compile-time we would have to create a file with the following structure: > `../classes/VerticalList.ts` @@ -49,6 +51,8 @@ class VerticalList extends YTNode { export default VerticalList; ``` +You may use the parser's generated class for the new renderer as a starting point for your own implementation. + Then update the parser map: ```bash diff --git a/scripts/build-parser-map.cjs b/scripts/build-parser-map.cjs index d49a92f54..7b1e3e315 100644 --- a/scripts/build-parser-map.cjs +++ b/scripts/build-parser-map.cjs @@ -3,9 +3,7 @@ const fs = require('fs'); const path = require('path'); const import_list = []; - -const json = []; -const misc_exports = []; +const misc_imports = []; glob.sync('../src/parser/classes/**/*.{js,ts}', { cwd: __dirname }) .forEach((file) => { @@ -16,44 +14,26 @@ glob.sync('../src/parser/classes/**/*.{js,ts}', { cwd: __dirname }) if (is_misc) { const class_name = file.split('/').pop().replace('.js', '').replace('.ts', ''); - import_list.push(`import { default as ${class_name} } from './classes/${file}.js';`); - misc_exports.push(class_name); + misc_imports.push(`export { default as ${class_name} } from './classes/${file}.js';`); } else { - import_list.push(`import { default as ${import_name} } from './classes/${file}.js'; -export { ${import_name} };`); - json.push(import_name); + import_list.push(`export { default as ${import_name} } from './classes/${file}.js';`); } }); fs.writeFileSync( - path.resolve(__dirname, '../src/parser/map.ts'), + path.resolve(__dirname, '../src/parser/nodes.ts'), `// This file was auto generated, do not edit. // See ./scripts/build-parser-map.js -import { YTNodeConstructor } from './helpers.js'; ${import_list.join('\n')} +` +); -const map: Record = { - ${json.join(',\n ')} -}; - -export const Misc = { - ${misc_exports.join(',\n ')} -}; - -/** - * @param name - Name of the node to be parsed - */ -export default function GetParserByName(name: string) { - const ParserConstructor = map[name]; - - if (!ParserConstructor) { - const error = new Error(\`Module not found: \${name}\`); - (error as any).code = 'MODULE_NOT_FOUND'; - throw error; - } +fs.writeFileSync( + path.resolve(__dirname, '../src/parser/misc.ts'), + `// This file was auto generated, do not edit. +// See ./scripts/build-parser-map.js - return ParserConstructor; -} +${misc_imports.join('\n')} ` ); \ No newline at end of file diff --git a/src/parser/README.md b/src/parser/README.md index 2f49c9bc5..ec161ebb5 100644 --- a/src/parser/README.md +++ b/src/parser/README.md @@ -256,6 +256,65 @@ const videos = response.contents_memo.getType(Video); ## Adding new nodes Instructions can be found [here](https://github.com/LuanRT/YouTube.js/blob/main/docs/updating-the-parser.md). +## Generating nodes at runtime +YouTube constantly updates their client, and sometimes they add new nodes to the response. The parser needs to know about these new nodes in order to parse them correctly. Once a new node is dicovered by the parser, it will attempt to generate a new node class for it. + +Using the existing `YTNode` class, you may interact with these new nodes in a type-safe way. However, you will not be able to cast them to the node's specific type, as this requires the node to be defined at compile-time. + +The current implementation recognises the following values: +- Renderers +- Renderer arrays +- Text +- Navigation endpoints +- Author (does not currently detect the author thumbnails) +- Thumbnails +- Objects (key-value pairs) +- Primatives (string, number, boolean, etc.) + +This may be expanded in the future. + +At runtime, these JIT-generated nodes will revalidate themselves when constructed so that when the types change, the node will be re-generated. + +To access these nodes that have been generated at runtime, you may use the `Parser.getParserByName(name: string)` method. You may also check if a parser has been generated for a node by using the `Parser.hasParser(name: string)` method. + +```ts +import { Parser } from "youtubei.js"; + +// We may check if we have a parser for a node. +if (Parser.hasParser('Example')) { + // Then retrieve it. + const Example = Parser.getParserByName('Example'); + // We may then use the parser as normal. + const example = new Example(data); +} +``` + +You may also generate your own nodes ahead of time, given you have an example of one of the nodes. + +```ts +import { Generator } from "youtubei.js"; + +// Provided you have an example of the node `Example` +const example_data = { + "title": { + "runs": [ + { + "text": "Example" + } + ] + } +} + +// The first argument is the name of the class, the second is the data you have for the node. +// It will return a class that extends YTNode. +const Example = Generator.YTNodeGenerator.generateRuntimeClass('Example', example_data); + +// You may now use this class as you would any other node. +const example = new Example(example_data); + +const title = example.key('title').instanceof(Text).toString(); +``` + ## How it works If you decompile a YouTube client and analyze it, it becomes apparent that it uses classes such as `../youtube/api/innertube/MusicItemRenderer` and `../youtube/api/innertube/SectionListRenderer` to parse objects from the response, map them into models, and generate the UI. The website operates similarly, but instead uses plain JSON. You can think of renderers as components in a web framework. diff --git a/src/parser/classes/GridPlaylist.ts b/src/parser/classes/GridPlaylist.ts index 4996e32c4..2ba18c8da 100644 --- a/src/parser/classes/GridPlaylist.ts +++ b/src/parser/classes/GridPlaylist.ts @@ -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; diff --git a/src/parser/classes/Movie.ts b/src/parser/classes/Movie.ts index 927547519..bec15e79d 100644 --- a/src/parser/classes/Movie.ts +++ b/src/parser/classes/Movie.ts @@ -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); diff --git a/src/parser/classes/MusicSortFilterButton.ts b/src/parser/classes/MusicSortFilterButton.ts index 0ff6b0134..ec16e01f4 100644 --- a/src/parser/classes/MusicSortFilterButton.ts +++ b/src/parser/classes/MusicSortFilterButton.ts @@ -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); } diff --git a/src/parser/classes/Playlist.ts b/src/parser/classes/Playlist.ts index 2c603d1e1..77160628b 100644 --- a/src/parser/classes/Playlist.ts +++ b/src/parser/classes/Playlist.ts @@ -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); } } } diff --git a/src/parser/classes/PlaylistVideo.ts b/src/parser/classes/PlaylistVideo.ts index 83573d79f..d437d2e05 100644 --- a/src/parser/classes/PlaylistVideo.ts +++ b/src/parser/classes/PlaylistVideo.ts @@ -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) }; } diff --git a/src/parser/classes/Video.ts b/src/parser/classes/Video.ts index c92b42044..591903612 100644 --- a/src/parser/classes/Video.ts +++ b/src/parser/classes/Video.ts @@ -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; diff --git a/src/parser/classes/menus/MusicMultiSelectMenu.ts b/src/parser/classes/menus/MusicMultiSelectMenu.ts index 69d678e77..3fe901216 100644 --- a/src/parser/classes/menus/MusicMultiSelectMenu.ts +++ b/src/parser/classes/menus/MusicMultiSelectMenu.ts @@ -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 ]); } } diff --git a/src/parser/classes/menus/MusicMultiSelectMenuItem.ts b/src/parser/classes/menus/MusicMultiSelectMenuItem.ts index 1e2b7e2af..de0091d2f 100644 --- a/src/parser/classes/menus/MusicMultiSelectMenuItem.ts +++ b/src/parser/classes/menus/MusicMultiSelectMenuItem.ts @@ -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; diff --git a/src/parser/classes/misc/Author.ts b/src/parser/classes/misc/Author.ts index 37ca931ef..22bd0cfbb 100644 --- a/src/parser/classes/misc/Author.ts +++ b/src/parser/classes/misc/Author.ts @@ -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 || diff --git a/src/parser/classes/misc/NavigatableText.ts b/src/parser/classes/misc/NavigatableText.ts deleted file mode 100644 index 3d5aac489..000000000 --- a/src/parser/classes/misc/NavigatableText.ts +++ /dev/null @@ -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; \ No newline at end of file diff --git a/src/parser/classes/misc/Text.ts b/src/parser/classes/misc/Text.ts index 1e95b7a0d..282484950 100644 --- a/src/parser/classes/misc/Text.ts +++ b/src/parser/classes/misc/Text.ts @@ -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'; } } diff --git a/src/parser/generator.ts b/src/parser/generator.ts new file mode 100644 index 000000000..8156cb805 --- /dev/null +++ b/src/parser/generator.ts @@ -0,0 +1,690 @@ +/* eslint-disable no-cond-assign */ +import { Platform } from '../utils/Utils.js'; +import Author from './classes/misc/Author.js'; +import Text from './classes/misc/Text.js'; +import Thumbnail from './classes/misc/Thumbnail.js'; +import NavigationEndpoint from './classes/NavigationEndpoint.js'; +import { YTNode, YTNodeConstructor } from './helpers.js'; +import Parser from './parser.js'; + +export type MiscInferenceType = { + type: 'misc', + misc_type: 'NavigationEndpoint', + optional: boolean, + endpoint: NavigationEndpoint +} | { + type: 'misc', + misc_type: 'Text', + optional: boolean, + text: string, + endpoint?: NavigationEndpoint +} | { + type: 'misc', + misc_type: 'Thumbnail', + optional: boolean, +} | { + type: 'misc', + misc_type: 'Author', + optional: boolean, + params: [string, string?], +} + +export type InferenceType = { + type: 'renderer', + renderers: string[], + optional: boolean, +} | { + type: 'renderer_list', + renderers: string[], + optional: boolean, +} | MiscInferenceType | { + type: 'object', + keys: KeyInfo, + optional: boolean, +} | { + type: 'primative', + typeof: ('string' | 'number' | 'boolean' | 'bigint' | 'symbol' | 'undefined' | 'function')[], + optional: boolean, +} | { + type: 'unknown', + optional: boolean, +} + +export type KeyInfo = (readonly [string, InferenceType])[]; + +export class YTNodeGenerator { + static #ignored_keys = new Set([ + 'trackingParams', 'accessibility', 'accessibilityData' + ]); + static #renderers_examples: Record = {}; + static #camelToSnake(str: string) { + return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); + } + static #logNewClass(classname: string, key_info: KeyInfo) { + console.warn(`${classname} not found!\nThis is a bug, want to help us fix it? Follow the instructions at ${Platform.shim.info.repo_url}/blob/main/docs/updating-the-parser.md or report it at ${Platform.shim.info.bugs_url}!\nIntrospected and JIT generated this class in the meantime:\n${this.generateTypescriptClass(classname, key_info)}`); + } + static #logChangedKeys(classname: string, key_info: KeyInfo, changed_keys: KeyInfo) { + console.warn(`${classname} changed!\nThe following keys where altered: ${changed_keys.map(([ key ]) => this.#camelToSnake(key)).join(', ')}\nThe class has changed to:\n${this.generateTypescriptClass(classname, key_info)}`); + } + /** + * Is this key ignored by the parser? + * @param key - The key to check + * @returns Whether or not the key is ignored + */ + static isIgnoredKey(key: string | symbol) { + return typeof key === 'string' && this.#ignored_keys.has(key); + } + /** + * Merges two sets of key info, resolving any conflicts + * @param key_info - The current key info + * @param new_key_info - The new key info + * @returns The merged key info + */ + static mergeKeyInfo(key_info: KeyInfo, new_key_info: KeyInfo) { + const changed_keys = new Map(); + const current_keys = new Set(key_info.map(([ key ]) => key)); + const new_keys = new Set(new_key_info.map(([ key ]) => key)); + + const added_keys = new_key_info.filter(([ key ]) => !current_keys.has(key)); + const removed_keys = key_info.filter(([ key ]) => !new_keys.has(key)); + + const common_keys = key_info.filter(([ key ]) => new_keys.has(key)); + + const new_key_map = new Map(new_key_info); + + for (const [ key, type ] of common_keys) { + const new_type = new_key_map.get(key); + if (!new_type) continue; + if (type.type !== new_type.type) { + // We've got a type mismatch, this is unknown, we do not resolve unions + changed_keys.set(key, { + type: 'unknown', + optional: true + }); + continue; + } + // We've got the same type, so we can now resolve the changes + switch (type.type) { + case 'object': + { + if (new_type.type !== 'object') continue; + const { resolved_key_info } = this.mergeKeyInfo(type.keys, new_type.keys); + const resolved_key: InferenceType = { + type: 'object', + keys: resolved_key_info, + optional: type.optional || new_type.optional + }; + const did_change = JSON.stringify(resolved_key) !== JSON.stringify(type); + if (did_change) changed_keys.set(key, resolved_key); + } + break; + case 'renderer': + { + if (new_type.type !== 'renderer') continue; + const union_map = { + ...type.renderers, + ...new_type.renderers + }; + const either_optional = type.optional || new_type.optional; + const resolved_key: InferenceType = { + type: 'renderer', + renderers: union_map, + optional: either_optional + }; + const did_change = JSON.stringify({ + ...resolved_key, + renderers: Object.keys(resolved_key.renderers) + }) !== JSON.stringify({ + ...type, + renderers: Object.keys(type.renderers) + }); + if (did_change) changed_keys.set(key, resolved_key); + } + break; + case 'renderer_list': + { + if (new_type.type !== 'renderer_list') continue; + const union_map = { + ...type.renderers, + ...new_type.renderers + }; + const either_optional = type.optional || new_type.optional; + const resolved_key: InferenceType = { + type: 'renderer_list', + renderers: union_map, + optional: either_optional + }; + const did_change = JSON.stringify({ + ...resolved_key, + renderers: Object.keys(resolved_key.renderers) + }) !== JSON.stringify({ + ...type, + renderers: Object.keys(type.renderers) + }); + if (did_change) changed_keys.set(key, resolved_key); + } + break; + case 'misc': + { + if (new_type.type !== 'misc') continue; + if (type.misc_type !== new_type.misc_type) { + // We've got a type mismatch, this is unknown, we do not resolve unions + changed_keys.set(key, { + type: 'unknown', + optional: true + }); + } + switch (type.misc_type) { + case 'Author': + { + if (new_type.misc_type !== 'Author') break; + const had_optional_param = type.params[1] || new_type.params[1]; + const either_optional = type.optional || new_type.optional; + const resolved_key: MiscInferenceType = { + type: 'misc', + misc_type: 'Author', + optional: either_optional, + params: [ new_type.params[0], had_optional_param ] + }; + const did_change = JSON.stringify(resolved_key) !== JSON.stringify(type); + if (did_change) changed_keys.set(key, resolved_key); + } + break; + // Other cases can not change + } + } + break; + case 'primative': + { + if (new_type.type !== 'primative') continue; + const resolved_key: InferenceType = { + type: 'primative', + typeof: Array.from(new Set([ ...new_type.typeof, ...type.typeof ])), + optional: type.optional || new_type.optional + }; + const did_change = JSON.stringify(resolved_key) !== JSON.stringify(type); + if (did_change) changed_keys.set(key, resolved_key); + } + break; + } + } + + for (const [ key, type ] of added_keys) { + changed_keys.set(key, { + ...type, + optional: true + }); + } + + for (const [ key, type ] of removed_keys) { + changed_keys.set(key, { + ...type, + optional: true + }); + } + + const unchanged_keys = key_info.filter(([ key ]) => !changed_keys.has(key)); + + const resolved_key_info_map = new Map([ ...unchanged_keys, ...changed_keys ]); + const resolved_key_info = [ ...resolved_key_info_map.entries() ]; + + return { + resolved_key_info, + changed_keys: [ ...changed_keys.entries() ] + }; + } + /** + * Given a classname and its resolved key info, create a new class + * @param classname - The name of the class + * @param key_info - The resolved key info + * @returns Class based on the key info extending YTNode + */ + static createRuntimeClass(classname: string, key_info: KeyInfo): YTNodeConstructor { + this.#logNewClass(classname, key_info); + const node = class extends YTNode { + static type = classname; + static #key_info = new Map(); + static set key_info(key_info: KeyInfo) { + this.#key_info = new Map(key_info); + } + static get key_info() { + return [ ...this.#key_info.entries() ]; + } + constructor(data: any) { + super(); + const { + key_info, + unimplemented_dependencies + } = YTNodeGenerator.introspect(data); + + const { + resolved_key_info, + changed_keys + } = YTNodeGenerator.mergeKeyInfo(node.key_info, key_info); + + const did_change = changed_keys.length > 0; + + if (did_change) { + node.key_info = resolved_key_info; + YTNodeGenerator.#logChangedKeys(classname, node.key_info, changed_keys); + } + + for (const [ name, data ] of unimplemented_dependencies) + YTNodeGenerator.generateRuntimeClass(name, data); + + for (const [ key, value ] of key_info) { + let snake_key = YTNodeGenerator.#camelToSnake(key); + if (value.type === 'misc' && value.misc_type === 'NavigationEndpoint') + snake_key = 'endpoint'; + Reflect.set(this, snake_key, YTNodeGenerator.parse(key, value, data)); + } + } + }; + node.key_info = key_info; + Object.defineProperty(node, 'name', { value: classname, writable: false }); + return node; + } + /** + * Introspect an example of a class in order to determine its key info and dependencies + * @param classdata - The example of the class + * @returns The key info and any unimplemented dependencies + */ + static introspect(classdata: string) { + const key_info = this.#introspect(classdata); + const dependencies = new Map(); + for (const [ , value ] of key_info) { + if (value.type === 'renderer' || value.type === 'renderer_list') + for (const renderer of value.renderers) { + const example = this.#renderers_examples[renderer]; + if (example) + dependencies.set(renderer, example); + } + } + const unimplemented_dependencies = Array.from(dependencies).filter(([ classname ]) => !Parser.hasParser(classname)); + + return { + key_info, + unimplemented_dependencies + }; + } + /** + * Given example data for a class, introspect, implement dependencies, and create a new class + * @param classname - The name of the class + * @param classdata - The example of the class + * @returns Class based on the example classdata extending YTNode + */ + static generateRuntimeClass(classname: string, classdata: any) { + const { + key_info, + unimplemented_dependencies + } = this.introspect(classdata); + + const JITNode = this.createRuntimeClass(classname, key_info); + Parser.addRuntimeParser(classname, JITNode); + + for (const [ name, data ] of unimplemented_dependencies) + this.generateRuntimeClass(name, data); + + return JITNode; + } + /** + * Generate a typescript class based on the key info + * @param classname - The name of the class + * @param key_info - The key info, as returned by {@link YTNodeGenerator.introspect} + * @returns Typescript class file + */ + static generateTypescriptClass(classname: string, key_info: KeyInfo) { + const props: string[] = []; + const constructor_lines = [ + 'super();' + ]; + for (const [ key, value ] of key_info) { + let snake_key = this.#camelToSnake(key); + if (value.type === 'misc' && value.misc_type === 'NavigationEndpoint') + snake_key = 'endpoint'; + props.push(`${snake_key}${value.optional ? '?' : ''}: ${this.toTypeDeclaration(value)};`); + constructor_lines.push(`this.${snake_key} = ${this.toParser(key, value)};`); + } + return `class ${classname} extends YTNode {\n static type = '${classname}';\n\n ${props.join('\n ')}\n\n constructor(data: RawNode) {\n ${constructor_lines.join('\n ')}\n }\n}\n`; + } + /** + * For a given inference type, get the typescript type declaration + * @param inference_type - The inference type to get the declaration for + * @param indentation - The indentation level (used for objects) + * @returns Typescript type declaration + */ + static toTypeDeclaration(inference_type: InferenceType, indentation = 0): string { + switch (inference_type.type) { + case 'renderer': + { + return `${inference_type.renderers.map((type) => `YTNodes.${type}`).join(' | ')} | null`; + } + case 'renderer_list': + { + return `ObservedArray<${inference_type.renderers.map((type) => `YTNodes.${type}`).join(' | ')}> | null`; + } + case 'object': + { + return `{\n${inference_type.keys.map(([ key, value ]) => `${' '.repeat((indentation + 2) * 2)}${this.#camelToSnake(key)}${value.optional ? '?' : ''}: ${this.toTypeDeclaration(value, indentation + 1)}`).join(',\n')}\n${' '.repeat((indentation + 1) * 2)}}`; + } + case 'misc': + switch (inference_type.misc_type) { + case 'Thumbnail': + return 'Thumbnail[]'; + default: + return inference_type.misc_type; + } + case 'primative': + return inference_type.typeof.join(' | '); + case 'unknown': + return '/* TODO: determine correct type */ unknown'; + } + } + /** + * Generate statements to parse a given inference type + * @param key - The key to parse + * @param inference_type - The inference type to parse + * @param key_path - The path to the key (excluding the key itself) + * @param indentation - The indentation level (used for objects) + * @returns Statement to parse the given key + */ + static toParser(key: string, inference_type: InferenceType, key_path: string[] = [ 'data' ], indentation = 1) { + let parser = 'undefined'; + switch (inference_type.type) { + case 'renderer': + { + parser = `Parser.parseItem(${key_path.join('.')}.${key}, [ ${inference_type.renderers.map((type) => `YTNodes.${type}`).join(', ')} ])`; + } + break; + case 'renderer_list': + { + parser = `Parser.parse(${key_path.join('.')}.${key}, true, [ ${inference_type.renderers.map((type) => `YTNodes.${type}`).join(', ')} ])`; + } + break; + case 'object': + { + const new_keypath = [ ...key_path, key ]; + parser = `{\n${inference_type.keys.map(([ key, value ]) => `${' '.repeat((indentation + 2) * 2)}${this.#camelToSnake(key)}: ${this.toParser(key, value, new_keypath, indentation + 1)}`).join(',\n')}\n${' '.repeat((indentation + 1) * 2)}}`; + } + break; + case 'misc': + switch (inference_type.misc_type) { + case 'Thumbnail': + parser = `Thumbnail.fromResponse(${key_path.join('.')}.${key})`; + break; + case 'Author': + { + const author_parser = `new Author(${key_path.join('.')}.${inference_type.params[0]}, ${inference_type.params[1] ? `${key_path.join('.')}.${inference_type.params[1]}` : 'undefined'})`; + if (inference_type.optional) + return `Reflect.has(${key_path.join('.')}, '${inference_type.params[0]}') ? ${author_parser} : undefined`; + return author_parser; + } + default: + parser = `new ${inference_type.misc_type}(${key_path.join('.')}.${key})`; + break; + } + if (parser === 'undefined') + throw new Error('Unreachable code reached! Switch missing case!'); + break; + case 'primative': + case 'unknown': + parser = `${key_path.join('.')}.${key}`; + break; + } + if (inference_type.optional) + return `Reflect.has(${key_path.join('.')}, '${key}') ? ${parser} : undefined`; + return parser; + } + static #accessDataFromKeyPath(root: any, key_path: string[]) { + let data = root; + for (const key of key_path) + data = data[key]; + return data; + } + static #hasDataFromKeyPath(root: any, key_path: string[]) { + let data = root; + for (const key of key_path) + if (!Reflect.has(data, key)) + return false; + else + data = data[key]; + return true; + } + /** + * Parse a value from a given key path using the given inference type + * @param key - The key to parse + * @param inference_type - The inference type to parse + * @param data - The data to parse from + * @param key_path - The path to the key (excluding the key itself) + * @returns The parsed value + */ + static parse(key: string, inference_type: InferenceType, data: any, key_path: string[] = [ 'data' ]) { + const should_optional = !inference_type.optional || this.#hasDataFromKeyPath({data}, [ ...key_path, key ]); + switch (inference_type.type) { + case 'renderer': + { + return should_optional ? Parser.parseItem(this.#accessDataFromKeyPath({data}, [ ...key_path, key ]), inference_type.renderers.map((type) => Parser.getParserByName(type))) : undefined; + } + case 'renderer_list': + { + return should_optional ? Parser.parse(this.#accessDataFromKeyPath({data}, [ ...key_path, key ]), true, inference_type.renderers.map((type) => Parser.getParserByName(type))) : undefined; + } + case 'object': + { + const obj: any = {}; + const new_key_path = [ ...key_path, key ]; + for (const [ key, value ] of inference_type.keys) { + obj[key] = should_optional ? this.parse(key, value, data, new_key_path) : undefined; + } + return obj; + } + case 'misc': + switch (inference_type.misc_type) { + case 'NavigationEndpoint': + return should_optional ? new NavigationEndpoint(this.#accessDataFromKeyPath({data}, [ ...key_path, key ])) : undefined; + case 'Text': + return should_optional ? new Text(this.#accessDataFromKeyPath({data}, [ ...key_path, key ])) : undefined; + case 'Thumbnail': + return should_optional ? Thumbnail.fromResponse(this.#accessDataFromKeyPath({data}, [ ...key_path, key ])) : undefined; + case 'Author': + { + const author_should_optional = !inference_type.optional || this.#hasDataFromKeyPath({data}, [ ...key_path, inference_type.params[0] ]); + return author_should_optional ? new Author( + this.#accessDataFromKeyPath({data}, [ ...key_path, inference_type.params[0] ]), + inference_type.params[1] ? + this.#accessDataFromKeyPath({data}, [ ...key_path, inference_type.params[1] ]) : undefined + ) : undefined; + } + } + throw new Error('Unreachable code reached! Switch missing case!'); + case 'primative': + case 'unknown': + return this.#accessDataFromKeyPath({data}, [ ...key_path, key ]); + } + } + static #passOne(classdata: any) { + const keys = Reflect.ownKeys(classdata).filter((key) => !this.isIgnoredKey(key)).filter((key) => typeof key === 'string') as string[]; + const key_info = keys.map((key) => { + const value = classdata[key]; + const inferred_type = this.inferType(key as string, value); + return [ + key, + inferred_type + ] as const; + }); + return key_info; + } + static #passTwo(key_info: KeyInfo) { + // The second pass will detect Author + const channel_nav = key_info.filter(([ , value ]) => { + if (value.type !== 'misc') return false; + if (!(value.misc_type === 'NavigationEndpoint' || value.misc_type === 'Text')) return false; + return value.endpoint?.metadata.page_type === 'WEB_PAGE_TYPE_CHANNEL'; + }); + + // Whichever one has the longest text is the most probable match + const most_probable_match = channel_nav.sort(([ , a ], [ , b ]) => { + if (a.type !== 'misc' || b.type !== 'misc') return 0; + if (a.misc_type !== 'Text' || b.misc_type !== 'Text') return 0; + return b.text.length - a.text.length; + }); + + const excluded_keys = new Set(); + + const cannonical_channel_nav = most_probable_match[0]; + + let author: MiscInferenceType | undefined; + // We've found an author + if (cannonical_channel_nav) { + excluded_keys.add(cannonical_channel_nav[0]); + // Now to locate its metadata + // We'll first get all the keys in the classdata + const keys = key_info.map(([ key ]) => key); + // Check for anything ending in 'Badges' equals 'badges' + const badges = keys.filter((key) => key.endsWith('Badges') || key === 'badges'); + // The likely candidate is the one with some prefix (owner, author) + const likely_badges = badges.filter((key) => key.startsWith('owner') || key.startsWith('author')); + // If we have a likely candidate, we'll use that + const cannonical_badges = likely_badges[0] ?? badges[0]; + // Now we have the author and its badges + // Verify that its actually badges + const badge_key_info = key_info.find(([ key ]) => key === cannonical_badges); + const is_badges = badge_key_info ? + badge_key_info[1].type === 'renderer_list' && Reflect.has(badge_key_info[1].renderers, 'MetadataBadge') : + false; + + if (is_badges && cannonical_badges) excluded_keys.add(cannonical_badges); + // TODO: next we check for the author's thumbnail + author = { + type: 'misc', + misc_type: 'Author', + optional: false, + params: [ + cannonical_channel_nav[0], + is_badges ? cannonical_badges : undefined + ] + }; + } + + if (author) { + key_info.push([ 'author', author ]); + } + + return key_info.filter(([ key ]) => !excluded_keys.has(key)); + } + static #introspect(classdata: any) { + const key_info = this.#passOne(classdata); + return this.#passTwo(key_info); + } + /** + * Infer the type of a key given its value + * @param key - The key to infer the type of + * @param value - The value of the key + * @returns The inferred type + */ + static inferType(key: string, value: any): InferenceType { + let return_value: string | Record | boolean | MiscInferenceType = false; + if (return_value = this.isRenderer(value)) { + this.#renderers_examples[return_value] = value[Reflect.ownKeys(value)[0]]; + return { + type: 'renderer', + renderers: [ return_value ], + optional: false + }; + } + if (return_value = this.isRendererList(value)) { + for (const [ key, value ] of Object.entries(return_value)) { + this.#renderers_examples[key] = value; + } + return { + type: 'renderer_list', + renderers: Object.keys(return_value), + optional: false + }; + } + if (return_value = this.isMiscType(key, value)) { + return return_value as MiscInferenceType; + } + const primative_type = typeof value; + if (primative_type === 'object') + return { + type: 'object', + keys: Object.entries(value).map(([ key, value ]) => [ key, this.inferType(key, value) ]), + optional: false + }; + return { + type: 'primative', + typeof: [ primative_type ], + optional: false + }; + } + /** + * Checks if the given value is an array of renderers + * @param value - The value to check + * @returns If it is a renderer list, return an object with keys being the classnames, and values being an example of that class. + * Otherwise, return false. + */ + static isRendererList(value: any) { + const arr = Array.isArray(value); + const is_list = arr && value.every((item) => this.isRenderer(item)); + return ( + is_list ? + Object.fromEntries(value.map((item) => { + const key = Reflect.ownKeys(item)[0].toString(); + return [ Parser.sanitizeClassName(key), item[key] ]; + })) : + false + ); + } + /** + * Check if the given value is a misc type. + * @param key - The key of the value + * @param value - The value to check + * @returns If it is a misc type, return the InferenceType. Otherwise, return false. + */ + static isMiscType(key: string, value: any): MiscInferenceType | false { + // NavigationEndpoint + if ((key.endsWith('Endpoint') || key.endsWith('Command') || key === 'endpoint') && typeof value === 'object') { + return { + type: 'misc', + endpoint: new NavigationEndpoint(value), + optional: false, + misc_type: 'NavigationEndpoint' + }; + } + // Text + if (typeof value === 'object' && (Reflect.has(value, 'simpleText') || Reflect.has(value, 'runs'))) { + const textNode = new Text(value); + return { + type: 'misc', + misc_type: 'Text', + optional: false, + endpoint: textNode.endpoint, + text: textNode.toString() + }; + } + // Thumbnail + if (typeof value === 'object' && Reflect.has(value, 'thumbnails') && Array.isArray(value.thumbnails)) { + return { + type: 'misc', + misc_type: 'Thumbnail', + optional: false + }; + } + return false; + } + /** + * Check if the given value is a renderer + * @param value - The value to check + * @returns If it is a renderer, return the class name. Otherwise, return false. + */ + static isRenderer(value: any) { + const is_object = typeof value === 'object'; + if (!is_object) return false; + const keys = Reflect.ownKeys(value); + if (keys.length === 1 && keys[0].toString().includes('Renderer')) { + return Parser.sanitizeClassName(keys[0].toString()); + } + return false; + } +} \ No newline at end of file diff --git a/src/parser/index.ts b/src/parser/index.ts index af45a97cd..2b06e605a 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -1,11 +1,12 @@ export { default as Parser } from './parser.js'; export * from './parser.js'; export * from './types/index.js'; -export { Misc } from '../parser/map.js'; -export * as YTNodes from '../parser/map.js'; +export * as Misc from './misc.js'; +export * as YTNodes from './nodes.js'; export * as YT from './youtube/index.js'; export * as YTMusic from './ytmusic/index.js'; export * as YTKids from './ytkids/index.js'; export * as Helpers from './helpers.js'; +export * as Generator from './generator.js'; import Parser from './parser.js'; export default Parser; \ No newline at end of file diff --git a/src/parser/map.ts b/src/parser/map.ts deleted file mode 100644 index 4d5c419e8..000000000 --- a/src/parser/map.ts +++ /dev/null @@ -1,1077 +0,0 @@ -// This file was auto generated, do not edit. -// See ./scripts/build-parser-map.js -import { YTNodeConstructor } from './helpers.js'; - -import { default as AccountChannel } from './classes/AccountChannel.js'; -export { AccountChannel }; -import { default as AccountItemSection } from './classes/AccountItemSection.js'; -export { AccountItemSection }; -import { default as AccountItemSectionHeader } from './classes/AccountItemSectionHeader.js'; -export { AccountItemSectionHeader }; -import { default as AccountSectionList } from './classes/AccountSectionList.js'; -export { AccountSectionList }; -import { default as AppendContinuationItemsAction } from './classes/actions/AppendContinuationItemsAction.js'; -export { AppendContinuationItemsAction }; -import { default as OpenPopupAction } from './classes/actions/OpenPopupAction.js'; -export { OpenPopupAction }; -import { default as Alert } from './classes/Alert.js'; -export { Alert }; -import { default as AnalyticsMainAppKeyMetrics } from './classes/analytics/AnalyticsMainAppKeyMetrics.js'; -export { AnalyticsMainAppKeyMetrics }; -import { default as AnalyticsRoot } from './classes/analytics/AnalyticsRoot.js'; -export { AnalyticsRoot }; -import { default as AnalyticsShortsCarouselCard } from './classes/analytics/AnalyticsShortsCarouselCard.js'; -export { AnalyticsShortsCarouselCard }; -import { default as AnalyticsVideo } from './classes/analytics/AnalyticsVideo.js'; -export { AnalyticsVideo }; -import { default as AnalyticsVodCarouselCard } from './classes/analytics/AnalyticsVodCarouselCard.js'; -export { AnalyticsVodCarouselCard }; -import { default as CtaGoToCreatorStudio } from './classes/analytics/CtaGoToCreatorStudio.js'; -export { CtaGoToCreatorStudio }; -import { default as DataModelSection } from './classes/analytics/DataModelSection.js'; -export { DataModelSection }; -import { default as StatRow } from './classes/analytics/StatRow.js'; -export { StatRow }; -import { default as AudioOnlyPlayability } from './classes/AudioOnlyPlayability.js'; -export { AudioOnlyPlayability }; -import { default as AutomixPreviewVideo } from './classes/AutomixPreviewVideo.js'; -export { AutomixPreviewVideo }; -import { default as BackstageImage } from './classes/BackstageImage.js'; -export { BackstageImage }; -import { default as BackstagePost } from './classes/BackstagePost.js'; -export { BackstagePost }; -import { default as BackstagePostThread } from './classes/BackstagePostThread.js'; -export { BackstagePostThread }; -import { default as BrowseFeedActions } from './classes/BrowseFeedActions.js'; -export { BrowseFeedActions }; -import { default as BrowserMediaSession } from './classes/BrowserMediaSession.js'; -export { BrowserMediaSession }; -import { default as Button } from './classes/Button.js'; -export { Button }; -import { default as C4TabbedHeader } from './classes/C4TabbedHeader.js'; -export { C4TabbedHeader }; -import { default as CallToActionButton } from './classes/CallToActionButton.js'; -export { CallToActionButton }; -import { default as Card } from './classes/Card.js'; -export { Card }; -import { default as CardCollection } from './classes/CardCollection.js'; -export { CardCollection }; -import { default as CarouselHeader } from './classes/CarouselHeader.js'; -export { CarouselHeader }; -import { default as CarouselItem } from './classes/CarouselItem.js'; -export { CarouselItem }; -import { default as Channel } from './classes/Channel.js'; -export { Channel }; -import { default as ChannelAboutFullMetadata } from './classes/ChannelAboutFullMetadata.js'; -export { ChannelAboutFullMetadata }; -import { default as ChannelAgeGate } from './classes/ChannelAgeGate.js'; -export { ChannelAgeGate }; -import { default as ChannelFeaturedContent } from './classes/ChannelFeaturedContent.js'; -export { ChannelFeaturedContent }; -import { default as ChannelHeaderLinks } from './classes/ChannelHeaderLinks.js'; -export { ChannelHeaderLinks }; -import { default as ChannelMetadata } from './classes/ChannelMetadata.js'; -export { ChannelMetadata }; -import { default as ChannelMobileHeader } from './classes/ChannelMobileHeader.js'; -export { ChannelMobileHeader }; -import { default as ChannelOptions } from './classes/ChannelOptions.js'; -export { ChannelOptions }; -import { default as ChannelSubMenu } from './classes/ChannelSubMenu.js'; -export { ChannelSubMenu }; -import { default as ChannelThumbnailWithLink } from './classes/ChannelThumbnailWithLink.js'; -export { ChannelThumbnailWithLink }; -import { default as ChannelVideoPlayer } from './classes/ChannelVideoPlayer.js'; -export { ChannelVideoPlayer }; -import { default as Chapter } from './classes/Chapter.js'; -export { Chapter }; -import { default as ChildVideo } from './classes/ChildVideo.js'; -export { ChildVideo }; -import { default as ChipCloud } from './classes/ChipCloud.js'; -export { ChipCloud }; -import { default as ChipCloudChip } from './classes/ChipCloudChip.js'; -export { ChipCloudChip }; -import { default as CollaboratorInfoCardContent } from './classes/CollaboratorInfoCardContent.js'; -export { CollaboratorInfoCardContent }; -import { default as CollageHeroImage } from './classes/CollageHeroImage.js'; -export { CollageHeroImage }; -import { default as AuthorCommentBadge } from './classes/comments/AuthorCommentBadge.js'; -export { AuthorCommentBadge }; -import { default as Comment } from './classes/comments/Comment.js'; -export { Comment }; -import { default as CommentActionButtons } from './classes/comments/CommentActionButtons.js'; -export { CommentActionButtons }; -import { default as CommentDialog } from './classes/comments/CommentDialog.js'; -export { CommentDialog }; -import { default as CommentReplies } from './classes/comments/CommentReplies.js'; -export { CommentReplies }; -import { default as CommentReplyDialog } from './classes/comments/CommentReplyDialog.js'; -export { CommentReplyDialog }; -import { default as CommentsEntryPointHeader } from './classes/comments/CommentsEntryPointHeader.js'; -export { CommentsEntryPointHeader }; -import { default as CommentsHeader } from './classes/comments/CommentsHeader.js'; -export { CommentsHeader }; -import { default as CommentSimplebox } from './classes/comments/CommentSimplebox.js'; -export { CommentSimplebox }; -import { default as CommentThread } from './classes/comments/CommentThread.js'; -export { CommentThread }; -import { default as CreatorHeart } from './classes/comments/CreatorHeart.js'; -export { CreatorHeart }; -import { default as EmojiPicker } from './classes/comments/EmojiPicker.js'; -export { EmojiPicker }; -import { default as PdgCommentChip } from './classes/comments/PdgCommentChip.js'; -export { PdgCommentChip }; -import { default as SponsorCommentBadge } from './classes/comments/SponsorCommentBadge.js'; -export { SponsorCommentBadge }; -import { default as CompactChannel } from './classes/CompactChannel.js'; -export { CompactChannel }; -import { default as CompactLink } from './classes/CompactLink.js'; -export { CompactLink }; -import { default as CompactMix } from './classes/CompactMix.js'; -export { CompactMix }; -import { default as CompactPlaylist } from './classes/CompactPlaylist.js'; -export { CompactPlaylist }; -import { default as CompactStation } from './classes/CompactStation.js'; -export { CompactStation }; -import { default as CompactVideo } from './classes/CompactVideo.js'; -export { CompactVideo }; -import { default as ConfirmDialog } from './classes/ConfirmDialog.js'; -export { ConfirmDialog }; -import { default as ContinuationItem } from './classes/ContinuationItem.js'; -export { ContinuationItem }; -import { default as ConversationBar } from './classes/ConversationBar.js'; -export { ConversationBar }; -import { default as CopyLink } from './classes/CopyLink.js'; -export { CopyLink }; -import { default as CreatePlaylistDialog } from './classes/CreatePlaylistDialog.js'; -export { CreatePlaylistDialog }; -import { default as DecoratedPlayerBar } from './classes/DecoratedPlayerBar.js'; -export { DecoratedPlayerBar }; -import { default as DefaultPromoPanel } from './classes/DefaultPromoPanel.js'; -export { DefaultPromoPanel }; -import { default as DidYouMean } from './classes/DidYouMean.js'; -export { DidYouMean }; -import { default as DownloadButton } from './classes/DownloadButton.js'; -export { DownloadButton }; -import { default as Dropdown } from './classes/Dropdown.js'; -export { Dropdown }; -import { default as DropdownItem } from './classes/DropdownItem.js'; -export { DropdownItem }; -import { default as Element } from './classes/Element.js'; -export { Element }; -import { default as EmergencyOnebox } from './classes/EmergencyOnebox.js'; -export { EmergencyOnebox }; -import { default as EmojiPickerCategory } from './classes/EmojiPickerCategory.js'; -export { EmojiPickerCategory }; -import { default as EmojiPickerCategoryButton } from './classes/EmojiPickerCategoryButton.js'; -export { EmojiPickerCategoryButton }; -import { default as EmojiPickerUpsellCategory } from './classes/EmojiPickerUpsellCategory.js'; -export { EmojiPickerUpsellCategory }; -import { default as Endscreen } from './classes/Endscreen.js'; -export { Endscreen }; -import { default as EndscreenElement } from './classes/EndscreenElement.js'; -export { EndscreenElement }; -import { default as EndScreenPlaylist } from './classes/EndScreenPlaylist.js'; -export { EndScreenPlaylist }; -import { default as EndScreenVideo } from './classes/EndScreenVideo.js'; -export { EndScreenVideo }; -import { default as ExpandableMetadata } from './classes/ExpandableMetadata.js'; -export { ExpandableMetadata }; -import { default as ExpandableTab } from './classes/ExpandableTab.js'; -export { ExpandableTab }; -import { default as ExpandedShelfContents } from './classes/ExpandedShelfContents.js'; -export { ExpandedShelfContents }; -import { default as FeedFilterChipBar } from './classes/FeedFilterChipBar.js'; -export { FeedFilterChipBar }; -import { default as FeedTabbedHeader } from './classes/FeedTabbedHeader.js'; -export { FeedTabbedHeader }; -import { default as GameCard } from './classes/GameCard.js'; -export { GameCard }; -import { default as GameDetails } from './classes/GameDetails.js'; -export { GameDetails }; -import { default as Grid } from './classes/Grid.js'; -export { Grid }; -import { default as GridChannel } from './classes/GridChannel.js'; -export { GridChannel }; -import { default as GridHeader } from './classes/GridHeader.js'; -export { GridHeader }; -import { default as GridMix } from './classes/GridMix.js'; -export { GridMix }; -import { default as GridMovie } from './classes/GridMovie.js'; -export { GridMovie }; -import { default as GridPlaylist } from './classes/GridPlaylist.js'; -export { GridPlaylist }; -import { default as GridVideo } from './classes/GridVideo.js'; -export { GridVideo }; -import { default as GuideCollapsibleEntry } from './classes/GuideCollapsibleEntry.js'; -export { GuideCollapsibleEntry }; -import { default as GuideCollapsibleSectionEntry } from './classes/GuideCollapsibleSectionEntry.js'; -export { GuideCollapsibleSectionEntry }; -import { default as GuideDownloadsEntry } from './classes/GuideDownloadsEntry.js'; -export { GuideDownloadsEntry }; -import { default as GuideEntry } from './classes/GuideEntry.js'; -export { GuideEntry }; -import { default as GuideSection } from './classes/GuideSection.js'; -export { GuideSection }; -import { default as GuideSubscriptionsSection } from './classes/GuideSubscriptionsSection.js'; -export { GuideSubscriptionsSection }; -import { default as HashtagHeader } from './classes/HashtagHeader.js'; -export { HashtagHeader }; -import { default as Heatmap } from './classes/Heatmap.js'; -export { Heatmap }; -import { default as HeatMarker } from './classes/HeatMarker.js'; -export { HeatMarker }; -import { default as HeroPlaylistThumbnail } from './classes/HeroPlaylistThumbnail.js'; -export { HeroPlaylistThumbnail }; -import { default as HighlightsCarousel } from './classes/HighlightsCarousel.js'; -export { HighlightsCarousel }; -import { default as HistorySuggestion } from './classes/HistorySuggestion.js'; -export { HistorySuggestion }; -import { default as HorizontalCardList } from './classes/HorizontalCardList.js'; -export { HorizontalCardList }; -import { default as HorizontalList } from './classes/HorizontalList.js'; -export { HorizontalList }; -import { default as HorizontalMovieList } from './classes/HorizontalMovieList.js'; -export { HorizontalMovieList }; -import { default as IconLink } from './classes/IconLink.js'; -export { IconLink }; -import { default as InfoPanelContainer } from './classes/InfoPanelContainer.js'; -export { InfoPanelContainer }; -import { default as InfoPanelContent } from './classes/InfoPanelContent.js'; -export { InfoPanelContent }; -import { default as InteractiveTabbedHeader } from './classes/InteractiveTabbedHeader.js'; -export { InteractiveTabbedHeader }; -import { default as ItemSection } from './classes/ItemSection.js'; -export { ItemSection }; -import { default as ItemSectionHeader } from './classes/ItemSectionHeader.js'; -export { ItemSectionHeader }; -import { default as ItemSectionTab } from './classes/ItemSectionTab.js'; -export { ItemSectionTab }; -import { default as ItemSectionTabbedHeader } from './classes/ItemSectionTabbedHeader.js'; -export { ItemSectionTabbedHeader }; -import { default as LikeButton } from './classes/LikeButton.js'; -export { LikeButton }; -import { default as LiveChat } from './classes/LiveChat.js'; -export { LiveChat }; -import { default as AddBannerToLiveChatCommand } from './classes/livechat/AddBannerToLiveChatCommand.js'; -export { AddBannerToLiveChatCommand }; -import { default as AddChatItemAction } from './classes/livechat/AddChatItemAction.js'; -export { AddChatItemAction }; -import { default as AddLiveChatTickerItemAction } from './classes/livechat/AddLiveChatTickerItemAction.js'; -export { AddLiveChatTickerItemAction }; -import { default as DimChatItemAction } from './classes/livechat/DimChatItemAction.js'; -export { DimChatItemAction }; -import { default as LiveChatAutoModMessage } from './classes/livechat/items/LiveChatAutoModMessage.js'; -export { LiveChatAutoModMessage }; -import { default as LiveChatBanner } from './classes/livechat/items/LiveChatBanner.js'; -export { LiveChatBanner }; -import { default as LiveChatBannerHeader } from './classes/livechat/items/LiveChatBannerHeader.js'; -export { LiveChatBannerHeader }; -import { default as LiveChatBannerPoll } from './classes/livechat/items/LiveChatBannerPoll.js'; -export { LiveChatBannerPoll }; -import { default as LiveChatMembershipItem } from './classes/livechat/items/LiveChatMembershipItem.js'; -export { LiveChatMembershipItem }; -import { default as LiveChatPaidMessage } from './classes/livechat/items/LiveChatPaidMessage.js'; -export { LiveChatPaidMessage }; -import { default as LiveChatPaidSticker } from './classes/livechat/items/LiveChatPaidSticker.js'; -export { LiveChatPaidSticker }; -import { default as LiveChatPlaceholderItem } from './classes/livechat/items/LiveChatPlaceholderItem.js'; -export { LiveChatPlaceholderItem }; -import { default as LiveChatProductItem } from './classes/livechat/items/LiveChatProductItem.js'; -export { LiveChatProductItem }; -import { default as LiveChatRestrictedParticipation } from './classes/livechat/items/LiveChatRestrictedParticipation.js'; -export { LiveChatRestrictedParticipation }; -import { default as LiveChatTextMessage } from './classes/livechat/items/LiveChatTextMessage.js'; -export { LiveChatTextMessage }; -import { default as LiveChatTickerPaidMessageItem } from './classes/livechat/items/LiveChatTickerPaidMessageItem.js'; -export { LiveChatTickerPaidMessageItem }; -import { default as LiveChatTickerPaidStickerItem } from './classes/livechat/items/LiveChatTickerPaidStickerItem.js'; -export { LiveChatTickerPaidStickerItem }; -import { default as LiveChatTickerSponsorItem } from './classes/livechat/items/LiveChatTickerSponsorItem.js'; -export { LiveChatTickerSponsorItem }; -import { default as LiveChatViewerEngagementMessage } from './classes/livechat/items/LiveChatViewerEngagementMessage.js'; -export { LiveChatViewerEngagementMessage }; -import { default as PollHeader } from './classes/livechat/items/PollHeader.js'; -export { PollHeader }; -import { default as LiveChatActionPanel } from './classes/livechat/LiveChatActionPanel.js'; -export { LiveChatActionPanel }; -import { default as MarkChatItemAsDeletedAction } from './classes/livechat/MarkChatItemAsDeletedAction.js'; -export { MarkChatItemAsDeletedAction }; -import { default as MarkChatItemsByAuthorAsDeletedAction } from './classes/livechat/MarkChatItemsByAuthorAsDeletedAction.js'; -export { MarkChatItemsByAuthorAsDeletedAction }; -import { default as RemoveBannerForLiveChatCommand } from './classes/livechat/RemoveBannerForLiveChatCommand.js'; -export { RemoveBannerForLiveChatCommand }; -import { default as RemoveChatItemAction } from './classes/livechat/RemoveChatItemAction.js'; -export { RemoveChatItemAction }; -import { default as RemoveChatItemByAuthorAction } from './classes/livechat/RemoveChatItemByAuthorAction.js'; -export { RemoveChatItemByAuthorAction }; -import { default as ReplaceChatItemAction } from './classes/livechat/ReplaceChatItemAction.js'; -export { ReplaceChatItemAction }; -import { default as ReplayChatItemAction } from './classes/livechat/ReplayChatItemAction.js'; -export { ReplayChatItemAction }; -import { default as ShowLiveChatActionPanelAction } from './classes/livechat/ShowLiveChatActionPanelAction.js'; -export { ShowLiveChatActionPanelAction }; -import { default as ShowLiveChatDialogAction } from './classes/livechat/ShowLiveChatDialogAction.js'; -export { ShowLiveChatDialogAction }; -import { default as ShowLiveChatTooltipCommand } from './classes/livechat/ShowLiveChatTooltipCommand.js'; -export { ShowLiveChatTooltipCommand }; -import { default as UpdateDateTextAction } from './classes/livechat/UpdateDateTextAction.js'; -export { UpdateDateTextAction }; -import { default as UpdateDescriptionAction } from './classes/livechat/UpdateDescriptionAction.js'; -export { UpdateDescriptionAction }; -import { default as UpdateLiveChatPollAction } from './classes/livechat/UpdateLiveChatPollAction.js'; -export { UpdateLiveChatPollAction }; -import { default as UpdateTitleAction } from './classes/livechat/UpdateTitleAction.js'; -export { UpdateTitleAction }; -import { default as UpdateToggleButtonTextAction } from './classes/livechat/UpdateToggleButtonTextAction.js'; -export { UpdateToggleButtonTextAction }; -import { default as UpdateViewershipAction } from './classes/livechat/UpdateViewershipAction.js'; -export { UpdateViewershipAction }; -import { default as LiveChatAuthorBadge } from './classes/LiveChatAuthorBadge.js'; -export { LiveChatAuthorBadge }; -import { default as LiveChatDialog } from './classes/LiveChatDialog.js'; -export { LiveChatDialog }; -import { default as LiveChatHeader } from './classes/LiveChatHeader.js'; -export { LiveChatHeader }; -import { default as LiveChatItemList } from './classes/LiveChatItemList.js'; -export { LiveChatItemList }; -import { default as LiveChatMessageInput } from './classes/LiveChatMessageInput.js'; -export { LiveChatMessageInput }; -import { default as LiveChatParticipant } from './classes/LiveChatParticipant.js'; -export { LiveChatParticipant }; -import { default as LiveChatParticipantsList } from './classes/LiveChatParticipantsList.js'; -export { LiveChatParticipantsList }; -import { default as MacroMarkersListItem } from './classes/MacroMarkersListItem.js'; -export { MacroMarkersListItem }; -import { default as Menu } from './classes/menus/Menu.js'; -export { Menu }; -import { default as MenuNavigationItem } from './classes/menus/MenuNavigationItem.js'; -export { MenuNavigationItem }; -import { default as MenuServiceItem } from './classes/menus/MenuServiceItem.js'; -export { MenuServiceItem }; -import { default as MenuServiceItemDownload } from './classes/menus/MenuServiceItemDownload.js'; -export { MenuServiceItemDownload }; -import { default as MultiPageMenu } from './classes/menus/MultiPageMenu.js'; -export { MultiPageMenu }; -import { default as MultiPageMenuNotificationSection } from './classes/menus/MultiPageMenuNotificationSection.js'; -export { MultiPageMenuNotificationSection }; -import { default as MusicMenuItemDivider } from './classes/menus/MusicMenuItemDivider.js'; -export { MusicMenuItemDivider }; -import { default as MusicMultiSelectMenu } from './classes/menus/MusicMultiSelectMenu.js'; -export { MusicMultiSelectMenu }; -import { default as MusicMultiSelectMenuItem } from './classes/menus/MusicMultiSelectMenuItem.js'; -export { MusicMultiSelectMenuItem }; -import { default as SimpleMenuHeader } from './classes/menus/SimpleMenuHeader.js'; -export { SimpleMenuHeader }; -import { default as MerchandiseItem } from './classes/MerchandiseItem.js'; -export { MerchandiseItem }; -import { default as MerchandiseShelf } from './classes/MerchandiseShelf.js'; -export { MerchandiseShelf }; -import { default as Message } from './classes/Message.js'; -export { Message }; -import { default as MetadataBadge } from './classes/MetadataBadge.js'; -export { MetadataBadge }; -import { default as MetadataRow } from './classes/MetadataRow.js'; -export { MetadataRow }; -import { default as MetadataRowContainer } from './classes/MetadataRowContainer.js'; -export { MetadataRowContainer }; -import { default as MetadataRowHeader } from './classes/MetadataRowHeader.js'; -export { MetadataRowHeader }; -import { default as MetadataScreen } from './classes/MetadataScreen.js'; -export { MetadataScreen }; -import { default as MicroformatData } from './classes/MicroformatData.js'; -export { MicroformatData }; -import { default as Author } from './classes/misc/Author.js'; -import { default as ChildElement } from './classes/misc/ChildElement.js'; -import { default as EmojiRun } from './classes/misc/EmojiRun.js'; -import { default as Format } from './classes/misc/Format.js'; -import { default as NavigatableText } from './classes/misc/NavigatableText.js'; -import { default as PlaylistAuthor } from './classes/misc/PlaylistAuthor.js'; -import { default as Text } from './classes/misc/Text.js'; -import { default as TextRun } from './classes/misc/TextRun.js'; -import { default as Thumbnail } from './classes/misc/Thumbnail.js'; -import { default as VideoDetails } from './classes/misc/VideoDetails.js'; -import { default as Mix } from './classes/Mix.js'; -export { Mix }; -import { default as Movie } from './classes/Movie.js'; -export { Movie }; -import { default as MovingThumbnail } from './classes/MovingThumbnail.js'; -export { MovingThumbnail }; -import { default as MultiMarkersPlayerBar } from './classes/MultiMarkersPlayerBar.js'; -export { MultiMarkersPlayerBar }; -import { default as MusicCardShelf } from './classes/MusicCardShelf.js'; -export { MusicCardShelf }; -import { default as MusicCardShelfHeaderBasic } from './classes/MusicCardShelfHeaderBasic.js'; -export { MusicCardShelfHeaderBasic }; -import { default as MusicCarouselShelf } from './classes/MusicCarouselShelf.js'; -export { MusicCarouselShelf }; -import { default as MusicCarouselShelfBasicHeader } from './classes/MusicCarouselShelfBasicHeader.js'; -export { MusicCarouselShelfBasicHeader }; -import { default as MusicDescriptionShelf } from './classes/MusicDescriptionShelf.js'; -export { MusicDescriptionShelf }; -import { default as MusicDetailHeader } from './classes/MusicDetailHeader.js'; -export { MusicDetailHeader }; -import { default as MusicDownloadStateBadge } from './classes/MusicDownloadStateBadge.js'; -export { MusicDownloadStateBadge }; -import { default as MusicEditablePlaylistDetailHeader } from './classes/MusicEditablePlaylistDetailHeader.js'; -export { MusicEditablePlaylistDetailHeader }; -import { default as MusicElementHeader } from './classes/MusicElementHeader.js'; -export { MusicElementHeader }; -import { default as MusicHeader } from './classes/MusicHeader.js'; -export { MusicHeader }; -import { default as MusicImmersiveHeader } from './classes/MusicImmersiveHeader.js'; -export { MusicImmersiveHeader }; -import { default as MusicInlineBadge } from './classes/MusicInlineBadge.js'; -export { MusicInlineBadge }; -import { default as MusicItemThumbnailOverlay } from './classes/MusicItemThumbnailOverlay.js'; -export { MusicItemThumbnailOverlay }; -import { default as MusicLargeCardItemCarousel } from './classes/MusicLargeCardItemCarousel.js'; -export { MusicLargeCardItemCarousel }; -import { default as MusicNavigationButton } from './classes/MusicNavigationButton.js'; -export { MusicNavigationButton }; -import { default as MusicPlayButton } from './classes/MusicPlayButton.js'; -export { MusicPlayButton }; -import { default as MusicPlaylistShelf } from './classes/MusicPlaylistShelf.js'; -export { MusicPlaylistShelf }; -import { default as MusicQueue } from './classes/MusicQueue.js'; -export { MusicQueue }; -import { default as MusicResponsiveListItem } from './classes/MusicResponsiveListItem.js'; -export { MusicResponsiveListItem }; -import { default as MusicResponsiveListItemFixedColumn } from './classes/MusicResponsiveListItemFixedColumn.js'; -export { MusicResponsiveListItemFixedColumn }; -import { default as MusicResponsiveListItemFlexColumn } from './classes/MusicResponsiveListItemFlexColumn.js'; -export { MusicResponsiveListItemFlexColumn }; -import { default as MusicShelf } from './classes/MusicShelf.js'; -export { MusicShelf }; -import { default as MusicSideAlignedItem } from './classes/MusicSideAlignedItem.js'; -export { MusicSideAlignedItem }; -import { default as MusicSortFilterButton } from './classes/MusicSortFilterButton.js'; -export { MusicSortFilterButton }; -import { default as MusicThumbnail } from './classes/MusicThumbnail.js'; -export { MusicThumbnail }; -import { default as MusicTwoRowItem } from './classes/MusicTwoRowItem.js'; -export { MusicTwoRowItem }; -import { default as MusicVisualHeader } from './classes/MusicVisualHeader.js'; -export { MusicVisualHeader }; -import { default as NavigationEndpoint } from './classes/NavigationEndpoint.js'; -export { NavigationEndpoint }; -import { default as Notification } from './classes/Notification.js'; -export { Notification }; -import { default as PageIntroduction } from './classes/PageIntroduction.js'; -export { PageIntroduction }; -import { default as PlayerAnnotationsExpanded } from './classes/PlayerAnnotationsExpanded.js'; -export { PlayerAnnotationsExpanded }; -import { default as PlayerCaptionsTracklist } from './classes/PlayerCaptionsTracklist.js'; -export { PlayerCaptionsTracklist }; -import { default as PlayerErrorMessage } from './classes/PlayerErrorMessage.js'; -export { PlayerErrorMessage }; -import { default as PlayerLegacyDesktopYpcOffer } from './classes/PlayerLegacyDesktopYpcOffer.js'; -export { PlayerLegacyDesktopYpcOffer }; -import { default as PlayerLegacyDesktopYpcTrailer } from './classes/PlayerLegacyDesktopYpcTrailer.js'; -export { PlayerLegacyDesktopYpcTrailer }; -import { default as PlayerLiveStoryboardSpec } from './classes/PlayerLiveStoryboardSpec.js'; -export { PlayerLiveStoryboardSpec }; -import { default as PlayerMicroformat } from './classes/PlayerMicroformat.js'; -export { PlayerMicroformat }; -import { default as PlayerOverlay } from './classes/PlayerOverlay.js'; -export { PlayerOverlay }; -import { default as PlayerOverlayAutoplay } from './classes/PlayerOverlayAutoplay.js'; -export { PlayerOverlayAutoplay }; -import { default as PlayerStoryboardSpec } from './classes/PlayerStoryboardSpec.js'; -export { PlayerStoryboardSpec }; -import { default as Playlist } from './classes/Playlist.js'; -export { Playlist }; -import { default as PlaylistCustomThumbnail } from './classes/PlaylistCustomThumbnail.js'; -export { PlaylistCustomThumbnail }; -import { default as PlaylistHeader } from './classes/PlaylistHeader.js'; -export { PlaylistHeader }; -import { default as PlaylistInfoCardContent } from './classes/PlaylistInfoCardContent.js'; -export { PlaylistInfoCardContent }; -import { default as PlaylistMetadata } from './classes/PlaylistMetadata.js'; -export { PlaylistMetadata }; -import { default as PlaylistPanel } from './classes/PlaylistPanel.js'; -export { PlaylistPanel }; -import { default as PlaylistPanelVideo } from './classes/PlaylistPanelVideo.js'; -export { PlaylistPanelVideo }; -import { default as PlaylistPanelVideoWrapper } from './classes/PlaylistPanelVideoWrapper.js'; -export { PlaylistPanelVideoWrapper }; -import { default as PlaylistSidebar } from './classes/PlaylistSidebar.js'; -export { PlaylistSidebar }; -import { default as PlaylistSidebarPrimaryInfo } from './classes/PlaylistSidebarPrimaryInfo.js'; -export { PlaylistSidebarPrimaryInfo }; -import { default as PlaylistSidebarSecondaryInfo } from './classes/PlaylistSidebarSecondaryInfo.js'; -export { PlaylistSidebarSecondaryInfo }; -import { default as PlaylistVideo } from './classes/PlaylistVideo.js'; -export { PlaylistVideo }; -import { default as PlaylistVideoList } from './classes/PlaylistVideoList.js'; -export { PlaylistVideoList }; -import { default as PlaylistVideoThumbnail } from './classes/PlaylistVideoThumbnail.js'; -export { PlaylistVideoThumbnail }; -import { default as Poll } from './classes/Poll.js'; -export { Poll }; -import { default as Post } from './classes/Post.js'; -export { Post }; -import { default as PostMultiImage } from './classes/PostMultiImage.js'; -export { PostMultiImage }; -import { default as ProfileColumn } from './classes/ProfileColumn.js'; -export { ProfileColumn }; -import { default as ProfileColumnStats } from './classes/ProfileColumnStats.js'; -export { ProfileColumnStats }; -import { default as ProfileColumnStatsEntry } from './classes/ProfileColumnStatsEntry.js'; -export { ProfileColumnStatsEntry }; -import { default as ProfileColumnUserInfo } from './classes/ProfileColumnUserInfo.js'; -export { ProfileColumnUserInfo }; -import { default as RecognitionShelf } from './classes/RecognitionShelf.js'; -export { RecognitionShelf }; -import { default as ReelItem } from './classes/ReelItem.js'; -export { ReelItem }; -import { default as ReelShelf } from './classes/ReelShelf.js'; -export { ReelShelf }; -import { default as RelatedChipCloud } from './classes/RelatedChipCloud.js'; -export { RelatedChipCloud }; -import { default as RichGrid } from './classes/RichGrid.js'; -export { RichGrid }; -import { default as RichItem } from './classes/RichItem.js'; -export { RichItem }; -import { default as RichListHeader } from './classes/RichListHeader.js'; -export { RichListHeader }; -import { default as RichMetadata } from './classes/RichMetadata.js'; -export { RichMetadata }; -import { default as RichMetadataRow } from './classes/RichMetadataRow.js'; -export { RichMetadataRow }; -import { default as RichSection } from './classes/RichSection.js'; -export { RichSection }; -import { default as RichShelf } from './classes/RichShelf.js'; -export { RichShelf }; -import { default as SearchBox } from './classes/SearchBox.js'; -export { SearchBox }; -import { default as SearchFilter } from './classes/SearchFilter.js'; -export { SearchFilter }; -import { default as SearchFilterGroup } from './classes/SearchFilterGroup.js'; -export { SearchFilterGroup }; -import { default as SearchRefinementCard } from './classes/SearchRefinementCard.js'; -export { SearchRefinementCard }; -import { default as SearchSubMenu } from './classes/SearchSubMenu.js'; -export { SearchSubMenu }; -import { default as SearchSuggestion } from './classes/SearchSuggestion.js'; -export { SearchSuggestion }; -import { default as SearchSuggestionsSection } from './classes/SearchSuggestionsSection.js'; -export { SearchSuggestionsSection }; -import { default as SecondarySearchContainer } from './classes/SecondarySearchContainer.js'; -export { SecondarySearchContainer }; -import { default as SectionList } from './classes/SectionList.js'; -export { SectionList }; -import { default as SegmentedLikeDislikeButton } from './classes/SegmentedLikeDislikeButton.js'; -export { SegmentedLikeDislikeButton }; -import { default as SettingBoolean } from './classes/SettingBoolean.js'; -export { SettingBoolean }; -import { default as SettingsCheckbox } from './classes/SettingsCheckbox.js'; -export { SettingsCheckbox }; -import { default as SettingsOptions } from './classes/SettingsOptions.js'; -export { SettingsOptions }; -import { default as SettingsSidebar } from './classes/SettingsSidebar.js'; -export { SettingsSidebar }; -import { default as SettingsSwitch } from './classes/SettingsSwitch.js'; -export { SettingsSwitch }; -import { default as SharedPost } from './classes/SharedPost.js'; -export { SharedPost }; -import { default as Shelf } from './classes/Shelf.js'; -export { Shelf }; -import { default as ShowingResultsFor } from './classes/ShowingResultsFor.js'; -export { ShowingResultsFor }; -import { default as SimpleCardContent } from './classes/SimpleCardContent.js'; -export { SimpleCardContent }; -import { default as SimpleCardTeaser } from './classes/SimpleCardTeaser.js'; -export { SimpleCardTeaser }; -import { default as SimpleTextSection } from './classes/SimpleTextSection.js'; -export { SimpleTextSection }; -import { default as SingleActionEmergencySupport } from './classes/SingleActionEmergencySupport.js'; -export { SingleActionEmergencySupport }; -import { default as SingleColumnBrowseResults } from './classes/SingleColumnBrowseResults.js'; -export { SingleColumnBrowseResults }; -import { default as SingleColumnMusicWatchNextResults } from './classes/SingleColumnMusicWatchNextResults.js'; -export { SingleColumnMusicWatchNextResults }; -import { default as SingleHeroImage } from './classes/SingleHeroImage.js'; -export { SingleHeroImage }; -import { default as SlimOwner } from './classes/SlimOwner.js'; -export { SlimOwner }; -import { default as SlimVideoMetadata } from './classes/SlimVideoMetadata.js'; -export { SlimVideoMetadata }; -import { default as SortFilterSubMenu } from './classes/SortFilterSubMenu.js'; -export { SortFilterSubMenu }; -import { default as SubFeedOption } from './classes/SubFeedOption.js'; -export { SubFeedOption }; -import { default as SubFeedSelector } from './classes/SubFeedSelector.js'; -export { SubFeedSelector }; -import { default as SubscribeButton } from './classes/SubscribeButton.js'; -export { SubscribeButton }; -import { default as SubscriptionNotificationToggleButton } from './classes/SubscriptionNotificationToggleButton.js'; -export { SubscriptionNotificationToggleButton }; -import { default as Tab } from './classes/Tab.js'; -export { Tab }; -import { default as Tabbed } from './classes/Tabbed.js'; -export { Tabbed }; -import { default as TabbedSearchResults } from './classes/TabbedSearchResults.js'; -export { TabbedSearchResults }; -import { default as TextHeader } from './classes/TextHeader.js'; -export { TextHeader }; -import { default as ThumbnailLandscapePortrait } from './classes/ThumbnailLandscapePortrait.js'; -export { ThumbnailLandscapePortrait }; -import { default as ThumbnailOverlayBottomPanel } from './classes/ThumbnailOverlayBottomPanel.js'; -export { ThumbnailOverlayBottomPanel }; -import { default as ThumbnailOverlayEndorsement } from './classes/ThumbnailOverlayEndorsement.js'; -export { ThumbnailOverlayEndorsement }; -import { default as ThumbnailOverlayHoverText } from './classes/ThumbnailOverlayHoverText.js'; -export { ThumbnailOverlayHoverText }; -import { default as ThumbnailOverlayInlineUnplayable } from './classes/ThumbnailOverlayInlineUnplayable.js'; -export { ThumbnailOverlayInlineUnplayable }; -import { default as ThumbnailOverlayLoadingPreview } from './classes/ThumbnailOverlayLoadingPreview.js'; -export { ThumbnailOverlayLoadingPreview }; -import { default as ThumbnailOverlayNowPlaying } from './classes/ThumbnailOverlayNowPlaying.js'; -export { ThumbnailOverlayNowPlaying }; -import { default as ThumbnailOverlayPinking } from './classes/ThumbnailOverlayPinking.js'; -export { ThumbnailOverlayPinking }; -import { default as ThumbnailOverlayPlaybackStatus } from './classes/ThumbnailOverlayPlaybackStatus.js'; -export { ThumbnailOverlayPlaybackStatus }; -import { default as ThumbnailOverlayResumePlayback } from './classes/ThumbnailOverlayResumePlayback.js'; -export { ThumbnailOverlayResumePlayback }; -import { default as ThumbnailOverlaySidePanel } from './classes/ThumbnailOverlaySidePanel.js'; -export { ThumbnailOverlaySidePanel }; -import { default as ThumbnailOverlayTimeStatus } from './classes/ThumbnailOverlayTimeStatus.js'; -export { ThumbnailOverlayTimeStatus }; -import { default as ThumbnailOverlayToggleButton } from './classes/ThumbnailOverlayToggleButton.js'; -export { ThumbnailOverlayToggleButton }; -import { default as TimedMarkerDecoration } from './classes/TimedMarkerDecoration.js'; -export { TimedMarkerDecoration }; -import { default as TitleAndButtonListHeader } from './classes/TitleAndButtonListHeader.js'; -export { TitleAndButtonListHeader }; -import { default as ToggleButton } from './classes/ToggleButton.js'; -export { ToggleButton }; -import { default as ToggleMenuServiceItem } from './classes/ToggleMenuServiceItem.js'; -export { ToggleMenuServiceItem }; -import { default as Tooltip } from './classes/Tooltip.js'; -export { Tooltip }; -import { default as TopicChannelDetails } from './classes/TopicChannelDetails.js'; -export { TopicChannelDetails }; -import { default as TwoColumnBrowseResults } from './classes/TwoColumnBrowseResults.js'; -export { TwoColumnBrowseResults }; -import { default as TwoColumnSearchResults } from './classes/TwoColumnSearchResults.js'; -export { TwoColumnSearchResults }; -import { default as TwoColumnWatchNextResults } from './classes/TwoColumnWatchNextResults.js'; -export { TwoColumnWatchNextResults }; -import { default as UniversalWatchCard } from './classes/UniversalWatchCard.js'; -export { UniversalWatchCard }; -import { default as UpsellDialog } from './classes/UpsellDialog.js'; -export { UpsellDialog }; -import { default as VerticalList } from './classes/VerticalList.js'; -export { VerticalList }; -import { default as VerticalWatchCardList } from './classes/VerticalWatchCardList.js'; -export { VerticalWatchCardList }; -import { default as Video } from './classes/Video.js'; -export { Video }; -import { default as VideoCard } from './classes/VideoCard.js'; -export { VideoCard }; -import { default as VideoInfoCardContent } from './classes/VideoInfoCardContent.js'; -export { VideoInfoCardContent }; -import { default as VideoOwner } from './classes/VideoOwner.js'; -export { VideoOwner }; -import { default as VideoPrimaryInfo } from './classes/VideoPrimaryInfo.js'; -export { VideoPrimaryInfo }; -import { default as VideoSecondaryInfo } from './classes/VideoSecondaryInfo.js'; -export { VideoSecondaryInfo }; -import { default as WatchCardCompactVideo } from './classes/WatchCardCompactVideo.js'; -export { WatchCardCompactVideo }; -import { default as WatchCardHeroVideo } from './classes/WatchCardHeroVideo.js'; -export { WatchCardHeroVideo }; -import { default as WatchCardRichHeader } from './classes/WatchCardRichHeader.js'; -export { WatchCardRichHeader }; -import { default as WatchCardSectionSequence } from './classes/WatchCardSectionSequence.js'; -export { WatchCardSectionSequence }; -import { default as WatchNextEndScreen } from './classes/WatchNextEndScreen.js'; -export { WatchNextEndScreen }; -import { default as WatchNextTabbedResults } from './classes/WatchNextTabbedResults.js'; -export { WatchNextTabbedResults }; -import { default as YpcTrailer } from './classes/YpcTrailer.js'; -export { YpcTrailer }; -import { default as AnchoredSection } from './classes/ytkids/AnchoredSection.js'; -export { AnchoredSection }; -import { default as KidsCategoriesHeader } from './classes/ytkids/KidsCategoriesHeader.js'; -export { KidsCategoriesHeader }; -import { default as KidsCategoryTab } from './classes/ytkids/KidsCategoryTab.js'; -export { KidsCategoryTab }; -import { default as KidsHomeScreen } from './classes/ytkids/KidsHomeScreen.js'; -export { KidsHomeScreen }; - -const map: Record = { - AccountChannel, - AccountItemSection, - AccountItemSectionHeader, - AccountSectionList, - AppendContinuationItemsAction, - OpenPopupAction, - Alert, - AnalyticsMainAppKeyMetrics, - AnalyticsRoot, - AnalyticsShortsCarouselCard, - AnalyticsVideo, - AnalyticsVodCarouselCard, - CtaGoToCreatorStudio, - DataModelSection, - StatRow, - AudioOnlyPlayability, - AutomixPreviewVideo, - BackstageImage, - BackstagePost, - BackstagePostThread, - BrowseFeedActions, - BrowserMediaSession, - Button, - C4TabbedHeader, - CallToActionButton, - Card, - CardCollection, - CarouselHeader, - CarouselItem, - Channel, - ChannelAboutFullMetadata, - ChannelAgeGate, - ChannelFeaturedContent, - ChannelHeaderLinks, - ChannelMetadata, - ChannelMobileHeader, - ChannelOptions, - ChannelSubMenu, - ChannelThumbnailWithLink, - ChannelVideoPlayer, - Chapter, - ChildVideo, - ChipCloud, - ChipCloudChip, - CollaboratorInfoCardContent, - CollageHeroImage, - AuthorCommentBadge, - Comment, - CommentActionButtons, - CommentDialog, - CommentReplies, - CommentReplyDialog, - CommentsEntryPointHeader, - CommentsHeader, - CommentSimplebox, - CommentThread, - CreatorHeart, - EmojiPicker, - PdgCommentChip, - SponsorCommentBadge, - CompactChannel, - CompactLink, - CompactMix, - CompactPlaylist, - CompactStation, - CompactVideo, - ConfirmDialog, - ContinuationItem, - ConversationBar, - CopyLink, - CreatePlaylistDialog, - DecoratedPlayerBar, - DefaultPromoPanel, - DidYouMean, - DownloadButton, - Dropdown, - DropdownItem, - Element, - EmergencyOnebox, - EmojiPickerCategory, - EmojiPickerCategoryButton, - EmojiPickerUpsellCategory, - Endscreen, - EndscreenElement, - EndScreenPlaylist, - EndScreenVideo, - ExpandableMetadata, - ExpandableTab, - ExpandedShelfContents, - FeedFilterChipBar, - FeedTabbedHeader, - GameCard, - GameDetails, - Grid, - GridChannel, - GridHeader, - GridMix, - GridMovie, - GridPlaylist, - GridVideo, - GuideCollapsibleEntry, - GuideCollapsibleSectionEntry, - GuideDownloadsEntry, - GuideEntry, - GuideSection, - GuideSubscriptionsSection, - HashtagHeader, - Heatmap, - HeatMarker, - HeroPlaylistThumbnail, - HighlightsCarousel, - HistorySuggestion, - HorizontalCardList, - HorizontalList, - HorizontalMovieList, - IconLink, - InfoPanelContainer, - InfoPanelContent, - InteractiveTabbedHeader, - ItemSection, - ItemSectionHeader, - ItemSectionTab, - ItemSectionTabbedHeader, - LikeButton, - LiveChat, - AddBannerToLiveChatCommand, - AddChatItemAction, - AddLiveChatTickerItemAction, - DimChatItemAction, - LiveChatAutoModMessage, - LiveChatBanner, - LiveChatBannerHeader, - LiveChatBannerPoll, - LiveChatMembershipItem, - LiveChatPaidMessage, - LiveChatPaidSticker, - LiveChatPlaceholderItem, - LiveChatProductItem, - LiveChatRestrictedParticipation, - LiveChatTextMessage, - LiveChatTickerPaidMessageItem, - LiveChatTickerPaidStickerItem, - LiveChatTickerSponsorItem, - LiveChatViewerEngagementMessage, - PollHeader, - LiveChatActionPanel, - MarkChatItemAsDeletedAction, - MarkChatItemsByAuthorAsDeletedAction, - RemoveBannerForLiveChatCommand, - RemoveChatItemAction, - RemoveChatItemByAuthorAction, - ReplaceChatItemAction, - ReplayChatItemAction, - ShowLiveChatActionPanelAction, - ShowLiveChatDialogAction, - ShowLiveChatTooltipCommand, - UpdateDateTextAction, - UpdateDescriptionAction, - UpdateLiveChatPollAction, - UpdateTitleAction, - UpdateToggleButtonTextAction, - UpdateViewershipAction, - LiveChatAuthorBadge, - LiveChatDialog, - LiveChatHeader, - LiveChatItemList, - LiveChatMessageInput, - LiveChatParticipant, - LiveChatParticipantsList, - MacroMarkersListItem, - Menu, - MenuNavigationItem, - MenuServiceItem, - MenuServiceItemDownload, - MultiPageMenu, - MultiPageMenuNotificationSection, - MusicMenuItemDivider, - MusicMultiSelectMenu, - MusicMultiSelectMenuItem, - SimpleMenuHeader, - MerchandiseItem, - MerchandiseShelf, - Message, - MetadataBadge, - MetadataRow, - MetadataRowContainer, - MetadataRowHeader, - MetadataScreen, - MicroformatData, - Mix, - Movie, - MovingThumbnail, - MultiMarkersPlayerBar, - MusicCardShelf, - MusicCardShelfHeaderBasic, - MusicCarouselShelf, - MusicCarouselShelfBasicHeader, - MusicDescriptionShelf, - MusicDetailHeader, - MusicDownloadStateBadge, - MusicEditablePlaylistDetailHeader, - MusicElementHeader, - MusicHeader, - MusicImmersiveHeader, - MusicInlineBadge, - MusicItemThumbnailOverlay, - MusicLargeCardItemCarousel, - MusicNavigationButton, - MusicPlayButton, - MusicPlaylistShelf, - MusicQueue, - MusicResponsiveListItem, - MusicResponsiveListItemFixedColumn, - MusicResponsiveListItemFlexColumn, - MusicShelf, - MusicSideAlignedItem, - MusicSortFilterButton, - MusicThumbnail, - MusicTwoRowItem, - MusicVisualHeader, - NavigationEndpoint, - Notification, - PageIntroduction, - PlayerAnnotationsExpanded, - PlayerCaptionsTracklist, - PlayerErrorMessage, - PlayerLegacyDesktopYpcOffer, - PlayerLegacyDesktopYpcTrailer, - PlayerLiveStoryboardSpec, - PlayerMicroformat, - PlayerOverlay, - PlayerOverlayAutoplay, - PlayerStoryboardSpec, - Playlist, - PlaylistCustomThumbnail, - PlaylistHeader, - PlaylistInfoCardContent, - PlaylistMetadata, - PlaylistPanel, - PlaylistPanelVideo, - PlaylistPanelVideoWrapper, - PlaylistSidebar, - PlaylistSidebarPrimaryInfo, - PlaylistSidebarSecondaryInfo, - PlaylistVideo, - PlaylistVideoList, - PlaylistVideoThumbnail, - Poll, - Post, - PostMultiImage, - ProfileColumn, - ProfileColumnStats, - ProfileColumnStatsEntry, - ProfileColumnUserInfo, - RecognitionShelf, - ReelItem, - ReelShelf, - RelatedChipCloud, - RichGrid, - RichItem, - RichListHeader, - RichMetadata, - RichMetadataRow, - RichSection, - RichShelf, - SearchBox, - SearchFilter, - SearchFilterGroup, - SearchRefinementCard, - SearchSubMenu, - SearchSuggestion, - SearchSuggestionsSection, - SecondarySearchContainer, - SectionList, - SegmentedLikeDislikeButton, - SettingBoolean, - SettingsCheckbox, - SettingsOptions, - SettingsSidebar, - SettingsSwitch, - SharedPost, - Shelf, - ShowingResultsFor, - SimpleCardContent, - SimpleCardTeaser, - SimpleTextSection, - SingleActionEmergencySupport, - SingleColumnBrowseResults, - SingleColumnMusicWatchNextResults, - SingleHeroImage, - SlimOwner, - SlimVideoMetadata, - SortFilterSubMenu, - SubFeedOption, - SubFeedSelector, - SubscribeButton, - SubscriptionNotificationToggleButton, - Tab, - Tabbed, - TabbedSearchResults, - TextHeader, - ThumbnailLandscapePortrait, - ThumbnailOverlayBottomPanel, - ThumbnailOverlayEndorsement, - ThumbnailOverlayHoverText, - ThumbnailOverlayInlineUnplayable, - ThumbnailOverlayLoadingPreview, - ThumbnailOverlayNowPlaying, - ThumbnailOverlayPinking, - ThumbnailOverlayPlaybackStatus, - ThumbnailOverlayResumePlayback, - ThumbnailOverlaySidePanel, - ThumbnailOverlayTimeStatus, - ThumbnailOverlayToggleButton, - TimedMarkerDecoration, - TitleAndButtonListHeader, - ToggleButton, - ToggleMenuServiceItem, - Tooltip, - TopicChannelDetails, - TwoColumnBrowseResults, - TwoColumnSearchResults, - TwoColumnWatchNextResults, - UniversalWatchCard, - UpsellDialog, - VerticalList, - VerticalWatchCardList, - Video, - VideoCard, - VideoInfoCardContent, - VideoOwner, - VideoPrimaryInfo, - VideoSecondaryInfo, - WatchCardCompactVideo, - WatchCardHeroVideo, - WatchCardRichHeader, - WatchCardSectionSequence, - WatchNextEndScreen, - WatchNextTabbedResults, - YpcTrailer, - AnchoredSection, - KidsCategoriesHeader, - KidsCategoryTab, - KidsHomeScreen -}; - -export const Misc = { - Author, - ChildElement, - EmojiRun, - Format, - NavigatableText, - PlaylistAuthor, - Text, - TextRun, - Thumbnail, - VideoDetails -}; - -/** - * @param name - Name of the node to be parsed - */ -export default function GetParserByName(name: string) { - const ParserConstructor = map[name]; - - if (!ParserConstructor) { - const error = new Error(`Module not found: ${name}`); - (error as any).code = 'MODULE_NOT_FOUND'; - throw error; - } - - return ParserConstructor; -} diff --git a/src/parser/misc.ts b/src/parser/misc.ts new file mode 100644 index 000000000..d34d34126 --- /dev/null +++ b/src/parser/misc.ts @@ -0,0 +1,12 @@ +// This file was auto generated, do not edit. +// See ./scripts/build-parser-map.js + +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'; +export { default as VideoDetails } from './classes/misc/VideoDetails.js'; diff --git a/src/parser/nodes.ts b/src/parser/nodes.ts new file mode 100644 index 000000000..053d4b00e --- /dev/null +++ b/src/parser/nodes.ts @@ -0,0 +1,347 @@ +// This file was auto generated, do not edit. +// See ./scripts/build-parser-map.js + +export { default as AccountChannel } from './classes/AccountChannel.js'; +export { default as AccountItemSection } from './classes/AccountItemSection.js'; +export { default as AccountItemSectionHeader } from './classes/AccountItemSectionHeader.js'; +export { default as AccountSectionList } from './classes/AccountSectionList.js'; +export { default as AppendContinuationItemsAction } from './classes/actions/AppendContinuationItemsAction.js'; +export { default as OpenPopupAction } from './classes/actions/OpenPopupAction.js'; +export { default as Alert } from './classes/Alert.js'; +export { default as AnalyticsMainAppKeyMetrics } from './classes/analytics/AnalyticsMainAppKeyMetrics.js'; +export { default as AnalyticsRoot } from './classes/analytics/AnalyticsRoot.js'; +export { default as AnalyticsShortsCarouselCard } from './classes/analytics/AnalyticsShortsCarouselCard.js'; +export { default as AnalyticsVideo } from './classes/analytics/AnalyticsVideo.js'; +export { default as AnalyticsVodCarouselCard } from './classes/analytics/AnalyticsVodCarouselCard.js'; +export { default as CtaGoToCreatorStudio } from './classes/analytics/CtaGoToCreatorStudio.js'; +export { default as DataModelSection } from './classes/analytics/DataModelSection.js'; +export { default as StatRow } from './classes/analytics/StatRow.js'; +export { default as AudioOnlyPlayability } from './classes/AudioOnlyPlayability.js'; +export { default as AutomixPreviewVideo } from './classes/AutomixPreviewVideo.js'; +export { default as BackstageImage } from './classes/BackstageImage.js'; +export { default as BackstagePost } from './classes/BackstagePost.js'; +export { default as BackstagePostThread } from './classes/BackstagePostThread.js'; +export { default as BrowseFeedActions } from './classes/BrowseFeedActions.js'; +export { default as BrowserMediaSession } from './classes/BrowserMediaSession.js'; +export { default as Button } from './classes/Button.js'; +export { default as C4TabbedHeader } from './classes/C4TabbedHeader.js'; +export { default as CallToActionButton } from './classes/CallToActionButton.js'; +export { default as Card } from './classes/Card.js'; +export { default as CardCollection } from './classes/CardCollection.js'; +export { default as CarouselHeader } from './classes/CarouselHeader.js'; +export { default as CarouselItem } from './classes/CarouselItem.js'; +export { default as Channel } from './classes/Channel.js'; +export { default as ChannelAboutFullMetadata } from './classes/ChannelAboutFullMetadata.js'; +export { default as ChannelAgeGate } from './classes/ChannelAgeGate.js'; +export { default as ChannelFeaturedContent } from './classes/ChannelFeaturedContent.js'; +export { default as ChannelHeaderLinks } from './classes/ChannelHeaderLinks.js'; +export { default as ChannelMetadata } from './classes/ChannelMetadata.js'; +export { default as ChannelMobileHeader } from './classes/ChannelMobileHeader.js'; +export { default as ChannelOptions } from './classes/ChannelOptions.js'; +export { default as ChannelSubMenu } from './classes/ChannelSubMenu.js'; +export { default as ChannelThumbnailWithLink } from './classes/ChannelThumbnailWithLink.js'; +export { default as ChannelVideoPlayer } from './classes/ChannelVideoPlayer.js'; +export { default as Chapter } from './classes/Chapter.js'; +export { default as ChildVideo } from './classes/ChildVideo.js'; +export { default as ChipCloud } from './classes/ChipCloud.js'; +export { default as ChipCloudChip } from './classes/ChipCloudChip.js'; +export { default as CollaboratorInfoCardContent } from './classes/CollaboratorInfoCardContent.js'; +export { default as CollageHeroImage } from './classes/CollageHeroImage.js'; +export { default as AuthorCommentBadge } from './classes/comments/AuthorCommentBadge.js'; +export { default as Comment } from './classes/comments/Comment.js'; +export { default as CommentActionButtons } from './classes/comments/CommentActionButtons.js'; +export { default as CommentDialog } from './classes/comments/CommentDialog.js'; +export { default as CommentReplies } from './classes/comments/CommentReplies.js'; +export { default as CommentReplyDialog } from './classes/comments/CommentReplyDialog.js'; +export { default as CommentsEntryPointHeader } from './classes/comments/CommentsEntryPointHeader.js'; +export { default as CommentsHeader } from './classes/comments/CommentsHeader.js'; +export { default as CommentSimplebox } from './classes/comments/CommentSimplebox.js'; +export { default as CommentThread } from './classes/comments/CommentThread.js'; +export { default as CreatorHeart } from './classes/comments/CreatorHeart.js'; +export { default as EmojiPicker } from './classes/comments/EmojiPicker.js'; +export { default as PdgCommentChip } from './classes/comments/PdgCommentChip.js'; +export { default as SponsorCommentBadge } from './classes/comments/SponsorCommentBadge.js'; +export { default as CompactChannel } from './classes/CompactChannel.js'; +export { default as CompactLink } from './classes/CompactLink.js'; +export { default as CompactMix } from './classes/CompactMix.js'; +export { default as CompactPlaylist } from './classes/CompactPlaylist.js'; +export { default as CompactStation } from './classes/CompactStation.js'; +export { default as CompactVideo } from './classes/CompactVideo.js'; +export { default as ConfirmDialog } from './classes/ConfirmDialog.js'; +export { default as ContinuationItem } from './classes/ContinuationItem.js'; +export { default as ConversationBar } from './classes/ConversationBar.js'; +export { default as CopyLink } from './classes/CopyLink.js'; +export { default as CreatePlaylistDialog } from './classes/CreatePlaylistDialog.js'; +export { default as DecoratedPlayerBar } from './classes/DecoratedPlayerBar.js'; +export { default as DefaultPromoPanel } from './classes/DefaultPromoPanel.js'; +export { default as DidYouMean } from './classes/DidYouMean.js'; +export { default as DownloadButton } from './classes/DownloadButton.js'; +export { default as Dropdown } from './classes/Dropdown.js'; +export { default as DropdownItem } from './classes/DropdownItem.js'; +export { default as Element } from './classes/Element.js'; +export { default as EmergencyOnebox } from './classes/EmergencyOnebox.js'; +export { default as EmojiPickerCategory } from './classes/EmojiPickerCategory.js'; +export { default as EmojiPickerCategoryButton } from './classes/EmojiPickerCategoryButton.js'; +export { default as EmojiPickerUpsellCategory } from './classes/EmojiPickerUpsellCategory.js'; +export { default as Endscreen } from './classes/Endscreen.js'; +export { default as EndscreenElement } from './classes/EndscreenElement.js'; +export { default as EndScreenPlaylist } from './classes/EndScreenPlaylist.js'; +export { default as EndScreenVideo } from './classes/EndScreenVideo.js'; +export { default as ExpandableMetadata } from './classes/ExpandableMetadata.js'; +export { default as ExpandableTab } from './classes/ExpandableTab.js'; +export { default as ExpandedShelfContents } from './classes/ExpandedShelfContents.js'; +export { default as FeedFilterChipBar } from './classes/FeedFilterChipBar.js'; +export { default as FeedTabbedHeader } from './classes/FeedTabbedHeader.js'; +export { default as GameCard } from './classes/GameCard.js'; +export { default as GameDetails } from './classes/GameDetails.js'; +export { default as Grid } from './classes/Grid.js'; +export { default as GridChannel } from './classes/GridChannel.js'; +export { default as GridHeader } from './classes/GridHeader.js'; +export { default as GridMix } from './classes/GridMix.js'; +export { default as GridMovie } from './classes/GridMovie.js'; +export { default as GridPlaylist } from './classes/GridPlaylist.js'; +export { default as GridVideo } from './classes/GridVideo.js'; +export { default as GuideCollapsibleEntry } from './classes/GuideCollapsibleEntry.js'; +export { default as GuideCollapsibleSectionEntry } from './classes/GuideCollapsibleSectionEntry.js'; +export { default as GuideDownloadsEntry } from './classes/GuideDownloadsEntry.js'; +export { default as GuideEntry } from './classes/GuideEntry.js'; +export { default as GuideSection } from './classes/GuideSection.js'; +export { default as GuideSubscriptionsSection } from './classes/GuideSubscriptionsSection.js'; +export { default as HashtagHeader } from './classes/HashtagHeader.js'; +export { default as Heatmap } from './classes/Heatmap.js'; +export { default as HeatMarker } from './classes/HeatMarker.js'; +export { default as HeroPlaylistThumbnail } from './classes/HeroPlaylistThumbnail.js'; +export { default as HighlightsCarousel } from './classes/HighlightsCarousel.js'; +export { default as HistorySuggestion } from './classes/HistorySuggestion.js'; +export { default as HorizontalCardList } from './classes/HorizontalCardList.js'; +export { default as HorizontalList } from './classes/HorizontalList.js'; +export { default as HorizontalMovieList } from './classes/HorizontalMovieList.js'; +export { default as IconLink } from './classes/IconLink.js'; +export { default as InfoPanelContainer } from './classes/InfoPanelContainer.js'; +export { default as InfoPanelContent } from './classes/InfoPanelContent.js'; +export { default as InteractiveTabbedHeader } from './classes/InteractiveTabbedHeader.js'; +export { default as ItemSection } from './classes/ItemSection.js'; +export { default as ItemSectionHeader } from './classes/ItemSectionHeader.js'; +export { default as ItemSectionTab } from './classes/ItemSectionTab.js'; +export { default as ItemSectionTabbedHeader } from './classes/ItemSectionTabbedHeader.js'; +export { default as LikeButton } from './classes/LikeButton.js'; +export { default as LiveChat } from './classes/LiveChat.js'; +export { default as AddBannerToLiveChatCommand } from './classes/livechat/AddBannerToLiveChatCommand.js'; +export { default as AddChatItemAction } from './classes/livechat/AddChatItemAction.js'; +export { default as AddLiveChatTickerItemAction } from './classes/livechat/AddLiveChatTickerItemAction.js'; +export { default as DimChatItemAction } from './classes/livechat/DimChatItemAction.js'; +export { default as LiveChatAutoModMessage } from './classes/livechat/items/LiveChatAutoModMessage.js'; +export { default as LiveChatBanner } from './classes/livechat/items/LiveChatBanner.js'; +export { default as LiveChatBannerHeader } from './classes/livechat/items/LiveChatBannerHeader.js'; +export { default as LiveChatBannerPoll } from './classes/livechat/items/LiveChatBannerPoll.js'; +export { default as LiveChatMembershipItem } from './classes/livechat/items/LiveChatMembershipItem.js'; +export { default as LiveChatPaidMessage } from './classes/livechat/items/LiveChatPaidMessage.js'; +export { default as LiveChatPaidSticker } from './classes/livechat/items/LiveChatPaidSticker.js'; +export { default as LiveChatPlaceholderItem } from './classes/livechat/items/LiveChatPlaceholderItem.js'; +export { default as LiveChatProductItem } from './classes/livechat/items/LiveChatProductItem.js'; +export { default as LiveChatRestrictedParticipation } from './classes/livechat/items/LiveChatRestrictedParticipation.js'; +export { default as LiveChatTextMessage } from './classes/livechat/items/LiveChatTextMessage.js'; +export { default as LiveChatTickerPaidMessageItem } from './classes/livechat/items/LiveChatTickerPaidMessageItem.js'; +export { default as LiveChatTickerPaidStickerItem } from './classes/livechat/items/LiveChatTickerPaidStickerItem.js'; +export { default as LiveChatTickerSponsorItem } from './classes/livechat/items/LiveChatTickerSponsorItem.js'; +export { default as LiveChatViewerEngagementMessage } from './classes/livechat/items/LiveChatViewerEngagementMessage.js'; +export { default as PollHeader } from './classes/livechat/items/PollHeader.js'; +export { default as LiveChatActionPanel } from './classes/livechat/LiveChatActionPanel.js'; +export { default as MarkChatItemAsDeletedAction } from './classes/livechat/MarkChatItemAsDeletedAction.js'; +export { default as MarkChatItemsByAuthorAsDeletedAction } from './classes/livechat/MarkChatItemsByAuthorAsDeletedAction.js'; +export { default as RemoveBannerForLiveChatCommand } from './classes/livechat/RemoveBannerForLiveChatCommand.js'; +export { default as RemoveChatItemAction } from './classes/livechat/RemoveChatItemAction.js'; +export { default as RemoveChatItemByAuthorAction } from './classes/livechat/RemoveChatItemByAuthorAction.js'; +export { default as ReplaceChatItemAction } from './classes/livechat/ReplaceChatItemAction.js'; +export { default as ReplayChatItemAction } from './classes/livechat/ReplayChatItemAction.js'; +export { default as ShowLiveChatActionPanelAction } from './classes/livechat/ShowLiveChatActionPanelAction.js'; +export { default as ShowLiveChatDialogAction } from './classes/livechat/ShowLiveChatDialogAction.js'; +export { default as ShowLiveChatTooltipCommand } from './classes/livechat/ShowLiveChatTooltipCommand.js'; +export { default as UpdateDateTextAction } from './classes/livechat/UpdateDateTextAction.js'; +export { default as UpdateDescriptionAction } from './classes/livechat/UpdateDescriptionAction.js'; +export { default as UpdateLiveChatPollAction } from './classes/livechat/UpdateLiveChatPollAction.js'; +export { default as UpdateTitleAction } from './classes/livechat/UpdateTitleAction.js'; +export { default as UpdateToggleButtonTextAction } from './classes/livechat/UpdateToggleButtonTextAction.js'; +export { default as UpdateViewershipAction } from './classes/livechat/UpdateViewershipAction.js'; +export { default as LiveChatAuthorBadge } from './classes/LiveChatAuthorBadge.js'; +export { default as LiveChatDialog } from './classes/LiveChatDialog.js'; +export { default as LiveChatHeader } from './classes/LiveChatHeader.js'; +export { default as LiveChatItemList } from './classes/LiveChatItemList.js'; +export { default as LiveChatMessageInput } from './classes/LiveChatMessageInput.js'; +export { default as LiveChatParticipant } from './classes/LiveChatParticipant.js'; +export { default as LiveChatParticipantsList } from './classes/LiveChatParticipantsList.js'; +export { default as MacroMarkersListItem } from './classes/MacroMarkersListItem.js'; +export { default as Menu } from './classes/menus/Menu.js'; +export { default as MenuNavigationItem } from './classes/menus/MenuNavigationItem.js'; +export { default as MenuServiceItem } from './classes/menus/MenuServiceItem.js'; +export { default as MenuServiceItemDownload } from './classes/menus/MenuServiceItemDownload.js'; +export { default as MultiPageMenu } from './classes/menus/MultiPageMenu.js'; +export { default as MultiPageMenuNotificationSection } from './classes/menus/MultiPageMenuNotificationSection.js'; +export { default as MusicMenuItemDivider } from './classes/menus/MusicMenuItemDivider.js'; +export { default as MusicMultiSelectMenu } from './classes/menus/MusicMultiSelectMenu.js'; +export { default as MusicMultiSelectMenuItem } from './classes/menus/MusicMultiSelectMenuItem.js'; +export { default as SimpleMenuHeader } from './classes/menus/SimpleMenuHeader.js'; +export { default as MerchandiseItem } from './classes/MerchandiseItem.js'; +export { default as MerchandiseShelf } from './classes/MerchandiseShelf.js'; +export { default as Message } from './classes/Message.js'; +export { default as MetadataBadge } from './classes/MetadataBadge.js'; +export { default as MetadataRow } from './classes/MetadataRow.js'; +export { default as MetadataRowContainer } from './classes/MetadataRowContainer.js'; +export { default as MetadataRowHeader } from './classes/MetadataRowHeader.js'; +export { default as MetadataScreen } from './classes/MetadataScreen.js'; +export { default as MicroformatData } from './classes/MicroformatData.js'; +export { default as Mix } from './classes/Mix.js'; +export { default as Movie } from './classes/Movie.js'; +export { default as MovingThumbnail } from './classes/MovingThumbnail.js'; +export { default as MultiMarkersPlayerBar } from './classes/MultiMarkersPlayerBar.js'; +export { default as MusicCardShelf } from './classes/MusicCardShelf.js'; +export { default as MusicCardShelfHeaderBasic } from './classes/MusicCardShelfHeaderBasic.js'; +export { default as MusicCarouselShelf } from './classes/MusicCarouselShelf.js'; +export { default as MusicCarouselShelfBasicHeader } from './classes/MusicCarouselShelfBasicHeader.js'; +export { default as MusicDescriptionShelf } from './classes/MusicDescriptionShelf.js'; +export { default as MusicDetailHeader } from './classes/MusicDetailHeader.js'; +export { default as MusicDownloadStateBadge } from './classes/MusicDownloadStateBadge.js'; +export { default as MusicEditablePlaylistDetailHeader } from './classes/MusicEditablePlaylistDetailHeader.js'; +export { default as MusicElementHeader } from './classes/MusicElementHeader.js'; +export { default as MusicHeader } from './classes/MusicHeader.js'; +export { default as MusicImmersiveHeader } from './classes/MusicImmersiveHeader.js'; +export { default as MusicInlineBadge } from './classes/MusicInlineBadge.js'; +export { default as MusicItemThumbnailOverlay } from './classes/MusicItemThumbnailOverlay.js'; +export { default as MusicLargeCardItemCarousel } from './classes/MusicLargeCardItemCarousel.js'; +export { default as MusicNavigationButton } from './classes/MusicNavigationButton.js'; +export { default as MusicPlayButton } from './classes/MusicPlayButton.js'; +export { default as MusicPlaylistShelf } from './classes/MusicPlaylistShelf.js'; +export { default as MusicQueue } from './classes/MusicQueue.js'; +export { default as MusicResponsiveListItem } from './classes/MusicResponsiveListItem.js'; +export { default as MusicResponsiveListItemFixedColumn } from './classes/MusicResponsiveListItemFixedColumn.js'; +export { default as MusicResponsiveListItemFlexColumn } from './classes/MusicResponsiveListItemFlexColumn.js'; +export { default as MusicShelf } from './classes/MusicShelf.js'; +export { default as MusicSideAlignedItem } from './classes/MusicSideAlignedItem.js'; +export { default as MusicSortFilterButton } from './classes/MusicSortFilterButton.js'; +export { default as MusicThumbnail } from './classes/MusicThumbnail.js'; +export { default as MusicTwoRowItem } from './classes/MusicTwoRowItem.js'; +export { default as MusicVisualHeader } from './classes/MusicVisualHeader.js'; +export { default as NavigationEndpoint } from './classes/NavigationEndpoint.js'; +export { default as Notification } from './classes/Notification.js'; +export { default as PageIntroduction } from './classes/PageIntroduction.js'; +export { default as PlayerAnnotationsExpanded } from './classes/PlayerAnnotationsExpanded.js'; +export { default as PlayerCaptionsTracklist } from './classes/PlayerCaptionsTracklist.js'; +export { default as PlayerErrorMessage } from './classes/PlayerErrorMessage.js'; +export { default as PlayerLegacyDesktopYpcOffer } from './classes/PlayerLegacyDesktopYpcOffer.js'; +export { default as PlayerLegacyDesktopYpcTrailer } from './classes/PlayerLegacyDesktopYpcTrailer.js'; +export { default as PlayerLiveStoryboardSpec } from './classes/PlayerLiveStoryboardSpec.js'; +export { default as PlayerMicroformat } from './classes/PlayerMicroformat.js'; +export { default as PlayerOverlay } from './classes/PlayerOverlay.js'; +export { default as PlayerOverlayAutoplay } from './classes/PlayerOverlayAutoplay.js'; +export { default as PlayerStoryboardSpec } from './classes/PlayerStoryboardSpec.js'; +export { default as Playlist } from './classes/Playlist.js'; +export { default as PlaylistCustomThumbnail } from './classes/PlaylistCustomThumbnail.js'; +export { default as PlaylistHeader } from './classes/PlaylistHeader.js'; +export { default as PlaylistInfoCardContent } from './classes/PlaylistInfoCardContent.js'; +export { default as PlaylistMetadata } from './classes/PlaylistMetadata.js'; +export { default as PlaylistPanel } from './classes/PlaylistPanel.js'; +export { default as PlaylistPanelVideo } from './classes/PlaylistPanelVideo.js'; +export { default as PlaylistPanelVideoWrapper } from './classes/PlaylistPanelVideoWrapper.js'; +export { default as PlaylistSidebar } from './classes/PlaylistSidebar.js'; +export { default as PlaylistSidebarPrimaryInfo } from './classes/PlaylistSidebarPrimaryInfo.js'; +export { default as PlaylistSidebarSecondaryInfo } from './classes/PlaylistSidebarSecondaryInfo.js'; +export { default as PlaylistVideo } from './classes/PlaylistVideo.js'; +export { default as PlaylistVideoList } from './classes/PlaylistVideoList.js'; +export { default as PlaylistVideoThumbnail } from './classes/PlaylistVideoThumbnail.js'; +export { default as Poll } from './classes/Poll.js'; +export { default as Post } from './classes/Post.js'; +export { default as PostMultiImage } from './classes/PostMultiImage.js'; +export { default as ProfileColumn } from './classes/ProfileColumn.js'; +export { default as ProfileColumnStats } from './classes/ProfileColumnStats.js'; +export { default as ProfileColumnStatsEntry } from './classes/ProfileColumnStatsEntry.js'; +export { default as ProfileColumnUserInfo } from './classes/ProfileColumnUserInfo.js'; +export { default as RecognitionShelf } from './classes/RecognitionShelf.js'; +export { default as ReelItem } from './classes/ReelItem.js'; +export { default as ReelShelf } from './classes/ReelShelf.js'; +export { default as RelatedChipCloud } from './classes/RelatedChipCloud.js'; +export { default as RichGrid } from './classes/RichGrid.js'; +export { default as RichItem } from './classes/RichItem.js'; +export { default as RichListHeader } from './classes/RichListHeader.js'; +export { default as RichMetadata } from './classes/RichMetadata.js'; +export { default as RichMetadataRow } from './classes/RichMetadataRow.js'; +export { default as RichSection } from './classes/RichSection.js'; +export { default as RichShelf } from './classes/RichShelf.js'; +export { default as SearchBox } from './classes/SearchBox.js'; +export { default as SearchFilter } from './classes/SearchFilter.js'; +export { default as SearchFilterGroup } from './classes/SearchFilterGroup.js'; +export { default as SearchRefinementCard } from './classes/SearchRefinementCard.js'; +export { default as SearchSubMenu } from './classes/SearchSubMenu.js'; +export { default as SearchSuggestion } from './classes/SearchSuggestion.js'; +export { default as SearchSuggestionsSection } from './classes/SearchSuggestionsSection.js'; +export { default as SecondarySearchContainer } from './classes/SecondarySearchContainer.js'; +export { default as SectionList } from './classes/SectionList.js'; +export { default as SegmentedLikeDislikeButton } from './classes/SegmentedLikeDislikeButton.js'; +export { default as SettingBoolean } from './classes/SettingBoolean.js'; +export { default as SettingsCheckbox } from './classes/SettingsCheckbox.js'; +export { default as SettingsOptions } from './classes/SettingsOptions.js'; +export { default as SettingsSidebar } from './classes/SettingsSidebar.js'; +export { default as SettingsSwitch } from './classes/SettingsSwitch.js'; +export { default as SharedPost } from './classes/SharedPost.js'; +export { default as Shelf } from './classes/Shelf.js'; +export { default as ShowingResultsFor } from './classes/ShowingResultsFor.js'; +export { default as SimpleCardContent } from './classes/SimpleCardContent.js'; +export { default as SimpleCardTeaser } from './classes/SimpleCardTeaser.js'; +export { default as SimpleTextSection } from './classes/SimpleTextSection.js'; +export { default as SingleActionEmergencySupport } from './classes/SingleActionEmergencySupport.js'; +export { default as SingleColumnBrowseResults } from './classes/SingleColumnBrowseResults.js'; +export { default as SingleColumnMusicWatchNextResults } from './classes/SingleColumnMusicWatchNextResults.js'; +export { default as SingleHeroImage } from './classes/SingleHeroImage.js'; +export { default as SlimOwner } from './classes/SlimOwner.js'; +export { default as SlimVideoMetadata } from './classes/SlimVideoMetadata.js'; +export { default as SortFilterSubMenu } from './classes/SortFilterSubMenu.js'; +export { default as SubFeedOption } from './classes/SubFeedOption.js'; +export { default as SubFeedSelector } from './classes/SubFeedSelector.js'; +export { default as SubscribeButton } from './classes/SubscribeButton.js'; +export { default as SubscriptionNotificationToggleButton } from './classes/SubscriptionNotificationToggleButton.js'; +export { default as Tab } from './classes/Tab.js'; +export { default as Tabbed } from './classes/Tabbed.js'; +export { default as TabbedSearchResults } from './classes/TabbedSearchResults.js'; +export { default as TextHeader } from './classes/TextHeader.js'; +export { default as ThumbnailLandscapePortrait } from './classes/ThumbnailLandscapePortrait.js'; +export { default as ThumbnailOverlayBottomPanel } from './classes/ThumbnailOverlayBottomPanel.js'; +export { default as ThumbnailOverlayEndorsement } from './classes/ThumbnailOverlayEndorsement.js'; +export { default as ThumbnailOverlayHoverText } from './classes/ThumbnailOverlayHoverText.js'; +export { default as ThumbnailOverlayInlineUnplayable } from './classes/ThumbnailOverlayInlineUnplayable.js'; +export { default as ThumbnailOverlayLoadingPreview } from './classes/ThumbnailOverlayLoadingPreview.js'; +export { default as ThumbnailOverlayNowPlaying } from './classes/ThumbnailOverlayNowPlaying.js'; +export { default as ThumbnailOverlayPinking } from './classes/ThumbnailOverlayPinking.js'; +export { default as ThumbnailOverlayPlaybackStatus } from './classes/ThumbnailOverlayPlaybackStatus.js'; +export { default as ThumbnailOverlayResumePlayback } from './classes/ThumbnailOverlayResumePlayback.js'; +export { default as ThumbnailOverlaySidePanel } from './classes/ThumbnailOverlaySidePanel.js'; +export { default as ThumbnailOverlayTimeStatus } from './classes/ThumbnailOverlayTimeStatus.js'; +export { default as ThumbnailOverlayToggleButton } from './classes/ThumbnailOverlayToggleButton.js'; +export { default as TimedMarkerDecoration } from './classes/TimedMarkerDecoration.js'; +export { default as TitleAndButtonListHeader } from './classes/TitleAndButtonListHeader.js'; +export { default as ToggleButton } from './classes/ToggleButton.js'; +export { default as ToggleMenuServiceItem } from './classes/ToggleMenuServiceItem.js'; +export { default as Tooltip } from './classes/Tooltip.js'; +export { default as TopicChannelDetails } from './classes/TopicChannelDetails.js'; +export { default as TwoColumnBrowseResults } from './classes/TwoColumnBrowseResults.js'; +export { default as TwoColumnSearchResults } from './classes/TwoColumnSearchResults.js'; +export { default as TwoColumnWatchNextResults } from './classes/TwoColumnWatchNextResults.js'; +export { default as UniversalWatchCard } from './classes/UniversalWatchCard.js'; +export { default as UpsellDialog } from './classes/UpsellDialog.js'; +export { default as VerticalList } from './classes/VerticalList.js'; +export { default as VerticalWatchCardList } from './classes/VerticalWatchCardList.js'; +export { default as Video } from './classes/Video.js'; +export { default as VideoCard } from './classes/VideoCard.js'; +export { default as VideoInfoCardContent } from './classes/VideoInfoCardContent.js'; +export { default as VideoOwner } from './classes/VideoOwner.js'; +export { default as VideoPrimaryInfo } from './classes/VideoPrimaryInfo.js'; +export { default as VideoSecondaryInfo } from './classes/VideoSecondaryInfo.js'; +export { default as WatchCardCompactVideo } from './classes/WatchCardCompactVideo.js'; +export { default as WatchCardHeroVideo } from './classes/WatchCardHeroVideo.js'; +export { default as WatchCardRichHeader } from './classes/WatchCardRichHeader.js'; +export { default as WatchCardSectionSequence } from './classes/WatchCardSectionSequence.js'; +export { default as WatchNextEndScreen } from './classes/WatchNextEndScreen.js'; +export { default as WatchNextTabbedResults } from './classes/WatchNextTabbedResults.js'; +export { default as YpcTrailer } from './classes/YpcTrailer.js'; +export { default as AnchoredSection } from './classes/ytkids/AnchoredSection.js'; +export { default as KidsCategoriesHeader } from './classes/ytkids/KidsCategoriesHeader.js'; +export { default as KidsCategoryTab } from './classes/ytkids/KidsCategoryTab.js'; +export { default as KidsHomeScreen } from './classes/ytkids/KidsHomeScreen.js'; diff --git a/src/parser/parser.ts b/src/parser/parser.ts index 2ded528ef..886b07750 100644 --- a/src/parser/parser.ts +++ b/src/parser/parser.ts @@ -21,7 +21,8 @@ import Thumbnail from './classes/misc/Thumbnail.js'; import { InnertubeError, ParsingError, Platform } from '../utils/Utils.js'; import { Memo, observe, ObservedArray, SuperParsedResult, YTNode, YTNodeConstructor } from './helpers.js'; -import GetParserByName from './map.js'; +import * as YTNodes from './nodes.js'; +import { YTNodeGenerator } from './generator.js'; export type ParserError = { classname: string, classdata: any, err: any }; export type ParserErrorHandler = (error: ParserError) => void; @@ -290,7 +291,9 @@ export default class Parser { if (!this.shouldIgnore(classname)) { try { - const TargetClass = GetParserByName(classname); + const has_target_class = this.hasParser(classname); + + const TargetClass = has_target_class ? this.getParserByName(classname) : YTNodeGenerator.generateRuntimeClass(classname, data[keys[0]]); if (validTypes) { if (Array.isArray(validTypes)) { @@ -495,6 +498,34 @@ export default class Parser { static shouldIgnore(classname: string) { return this.ignore_list.has(classname); } + + static #rt_nodes = new Map(Array.from(Object.entries(YTNodes))); + static #dynamic_nodes = new Map(); + + static getParserByName(classname: string) { + const ParserConstructor = this.#rt_nodes.get(classname); + + if (!ParserConstructor) { + const error = new Error(`Module not found: ${classname}`); + (error as any).code = 'MODULE_NOT_FOUND'; + throw error; + } + + return ParserConstructor; + } + + static hasParser(classname: string) { + return this.#rt_nodes.has(classname); + } + + static addRuntimeParser(classname: string, ParserConstructor: YTNodeConstructor) { + this.#rt_nodes.set(classname, ParserConstructor); + this.#dynamic_nodes.set(classname, ParserConstructor); + } + + static getDynamicParsers() { + return Object.fromEntries(this.#dynamic_nodes); + } } // Continuation