mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-14 18:12:10 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ec1609974 | ||
|
|
25fd979363 | ||
|
|
0c319aacde | ||
|
|
69d42b291c | ||
|
|
9a9bb76a92 | ||
|
|
0b2b0da957 | ||
|
|
eeaae6209f | ||
|
|
13e796123b | ||
|
|
5f233ae34e | ||
|
|
c1de097ce1 | ||
|
|
67f13fffac | ||
|
|
c8173c88e0 |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,5 +1,24 @@
|
||||
# Changelog
|
||||
|
||||
## [12.2.0](https://github.com/LuanRT/YouTube.js/compare/v12.1.0...v12.2.0) (2024-12-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Actions:** Allow auth check to be skipped ([67f13ff](https://github.com/LuanRT/YouTube.js/commit/67f13fffacec2c655a03d66c6d8016620d9abcf9))
|
||||
* add `VideoMetadataCarouselView` ([#839](https://github.com/LuanRT/YouTube.js/issues/839)) ([9a9bb76](https://github.com/LuanRT/YouTube.js/commit/9a9bb76a928594c5c5f3e828c86081bf79c2562d))
|
||||
* **parser:** Add `ActiveAccountHeader` ([5f233ae](https://github.com/LuanRT/YouTube.js/commit/5f233ae34e278e7f7a0c48e4ba762d9bac9e312f))
|
||||
* **parser:** Add `ButtonCardView` ([#834](https://github.com/LuanRT/YouTube.js/issues/834)) ([eeaae62](https://github.com/LuanRT/YouTube.js/commit/eeaae6209f238b838b9b7fdd9bbef89f4f858fa3))
|
||||
* **parser:** Add `ClientSideToggleMenuItem` ([#835](https://github.com/LuanRT/YouTube.js/issues/835)) ([0b2b0da](https://github.com/LuanRT/YouTube.js/commit/0b2b0da9577f8d6ad19393700071ea9f26d4da10))
|
||||
* **parser:** Add `PlaylistThumbnailOverlay` ([c8173c8](https://github.com/LuanRT/YouTube.js/commit/c8173c88e0e17ec4bb4e93af1867c55d07611cc0))
|
||||
* **parser:** Update `LiveChatBanner` ([#840](https://github.com/LuanRT/YouTube.js/issues/840)) ([69d42b2](https://github.com/LuanRT/YouTube.js/commit/69d42b291c927abb9d84f97ed03518c4ddd4506e))
|
||||
* **parser:** Update `LiveChatMembershipItem` ([#836](https://github.com/LuanRT/YouTube.js/issues/836)) ([0c319aa](https://github.com/LuanRT/YouTube.js/commit/0c319aacdeba106d84b7f44505a7a296a154f97a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Player:** Fix signature algorithm extraction again ([#837](https://github.com/LuanRT/YouTube.js/issues/837)) ([13e7961](https://github.com/LuanRT/YouTube.js/commit/13e796123b87136f2d5d3b3c9b3ed079a014bf46))
|
||||
|
||||
## [12.1.0](https://github.com/LuanRT/YouTube.js/compare/v12.0.0...v12.1.0) (2024-12-10)
|
||||
|
||||
|
||||
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "youtubei.js",
|
||||
"version": "12.1.0",
|
||||
"version": "12.2.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "youtubei.js",
|
||||
"version": "12.1.0",
|
||||
"version": "12.2.0",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/LuanRT"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0",
|
||||
"jintr": "^3.1.0",
|
||||
"jintr": "^3.2.0",
|
||||
"tslib": "^2.5.0",
|
||||
"undici": "^5.19.1"
|
||||
},
|
||||
@@ -6155,9 +6155,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jintr": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jintr/-/jintr-3.1.0.tgz",
|
||||
"integrity": "sha512-azhCHApkRfBH8INpiUCwKBYaNCdB5G+x3NApsI2MxQXSlgFAx7rap3YwE3JAkN08GO8f3ilZsGB0Yvc+412ntQ==",
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jintr/-/jintr-3.2.0.tgz",
|
||||
"integrity": "sha512-psD1yf05kMKDNsUdW1l5YhO59pHScQ6OIHHb8W5SKSM2dCOFPsqolmIuSHgVA8+3Dc47NJR181CXZ4alCAPTkA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/LuanRT"
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "youtubei.js",
|
||||
"version": "12.1.0",
|
||||
"version": "12.2.0",
|
||||
"description": "A JavaScript client for YouTube's private API, known as InnerTube.",
|
||||
"type": "module",
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
@@ -107,7 +107,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^2.0.0",
|
||||
"jintr": "^3.1.0",
|
||||
"jintr": "^3.2.0",
|
||||
"tslib": "^2.5.0",
|
||||
"undici": "^5.19.1"
|
||||
},
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import type {
|
||||
IBrowseResponse, IGetNotificationsMenuResponse, INextResponse,
|
||||
IParsedResponse, IPlayerResponse, IRawResponse,
|
||||
IResolveURLResponse, ISearchResponse, IUpdatedMetadataResponse
|
||||
IBrowseResponse,
|
||||
IGetNotificationsMenuResponse,
|
||||
INextResponse,
|
||||
IParsedResponse,
|
||||
IPlayerResponse,
|
||||
IRawResponse,
|
||||
IResolveURLResponse,
|
||||
ISearchResponse,
|
||||
IUpdatedMetadataResponse
|
||||
} from '../parser/index.js';
|
||||
|
||||
import { NavigateAction, Parser } from '../parser/index.js';
|
||||
import { InnertubeError } from '../utils/Utils.js';
|
||||
|
||||
@@ -15,17 +20,25 @@ export interface ApiResponse {
|
||||
data: IRawResponse;
|
||||
}
|
||||
|
||||
export type InnertubeEndpoint = '/player' | '/search' | '/browse' | '/next' | '/reel' | '/updated_metadata' | '/notification/get_notification_menu' | string;
|
||||
export type InnertubeEndpoint =
|
||||
'/player'
|
||||
| '/search'
|
||||
| '/browse'
|
||||
| '/next'
|
||||
| '/reel'
|
||||
| '/updated_metadata'
|
||||
| '/notification/get_notification_menu'
|
||||
| string;
|
||||
|
||||
export type ParsedResponse<T> =
|
||||
T extends '/player' ? IPlayerResponse :
|
||||
T extends '/search' ? ISearchResponse :
|
||||
T extends '/browse' ? IBrowseResponse :
|
||||
T extends '/next' ? INextResponse :
|
||||
T extends '/updated_metadata' ? IUpdatedMetadataResponse :
|
||||
T extends '/navigation/resolve_url' ? IResolveURLResponse :
|
||||
T extends '/notification/get_notification_menu' ? IGetNotificationsMenuResponse :
|
||||
IParsedResponse;
|
||||
T extends '/search' ? ISearchResponse :
|
||||
T extends '/browse' ? IBrowseResponse :
|
||||
T extends '/next' ? INextResponse :
|
||||
T extends '/updated_metadata' ? IUpdatedMetadataResponse :
|
||||
T extends '/navigation/resolve_url' ? IResolveURLResponse :
|
||||
T extends '/notification/get_notification_menu' ? IGetNotificationsMenuResponse :
|
||||
IParsedResponse;
|
||||
|
||||
export default class Actions {
|
||||
public session: Session;
|
||||
@@ -52,7 +65,9 @@ export default class Actions {
|
||||
* @param client - The client to use.
|
||||
* @param params - Call parameters.
|
||||
*/
|
||||
async stats(url: string, client: { client_name: string; client_version: string }, params: { [key: string]: any }): Promise<Response> {
|
||||
async stats(url: string, client: { client_name: string; client_version: string }, params: {
|
||||
[key: string]: any
|
||||
}): Promise<Response> {
|
||||
const s_url = new URL(url);
|
||||
|
||||
s_url.searchParams.set('ver', '2');
|
||||
@@ -72,18 +87,39 @@ export default class Actions {
|
||||
* @param endpoint - The endpoint to call.
|
||||
* @param args - Call arguments
|
||||
*/
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args: { [key: string]: any; parse: true; protobuf?: false; serialized_data?: any }): Promise<ParsedResponse<T>>;
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args?: { [key: string]: any; parse?: false; protobuf?: true; serialized_data?: any }): Promise<ApiResponse>;
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args?: { [key: string]: any; parse?: boolean; protobuf?: boolean; serialized_data?: any }): Promise<ParsedResponse<T> | ApiResponse> {
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args: {
|
||||
[key: string]: any;
|
||||
parse: true;
|
||||
protobuf?: false;
|
||||
serialized_data?: any;
|
||||
skip_auth_check?: boolean
|
||||
}): Promise<ParsedResponse<T>>;
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args?: {
|
||||
[key: string]: any;
|
||||
parse?: false;
|
||||
protobuf?: true;
|
||||
serialized_data?: any;
|
||||
skip_auth_check?: boolean
|
||||
}): Promise<ApiResponse>;
|
||||
async execute<T extends InnertubeEndpoint>(endpoint: T, args?: {
|
||||
[key: string]: any;
|
||||
parse?: boolean;
|
||||
protobuf?: boolean;
|
||||
serialized_data?: any;
|
||||
skip_auth_check?: boolean
|
||||
}): Promise<ParsedResponse<T> | ApiResponse> {
|
||||
let data;
|
||||
|
||||
if (args && !args.protobuf) {
|
||||
data = { ...args };
|
||||
|
||||
if (Reflect.has(data, 'browseId')) {
|
||||
if (Reflect.has(data, 'browseId') && !args.skip_auth_check) {
|
||||
if (this.#needsLogin(data.browseId) && !this.session.logged_in)
|
||||
throw new InnertubeError('You must be signed in to perform this operation.');
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'skip_auth_check'))
|
||||
delete data.skip_auth_check;
|
||||
|
||||
if (Reflect.has(data, 'override_endpoint'))
|
||||
delete data.override_endpoint;
|
||||
|
||||
@@ -223,22 +223,23 @@ export default class Player {
|
||||
return parseInt(getStringBetweenStrings(data, 'signatureTimestamp:', ',') || '0');
|
||||
}
|
||||
|
||||
static extractSigSourceCode(data: string): string {
|
||||
let calls = getStringBetweenStrings(data, 'function(a){a=a.split("")', 'return a.join("")}');
|
||||
let var_name = 'a';
|
||||
static extractSigSourceCode(data: string): string | undefined {
|
||||
const match = data.match(/function\(([A-Za-z_0-9]+)\)\{([A-Za-z_0-9]+=[A-Za-z_0-9]+\.split\(""\)(.+?)\.join\(""\))\}/);
|
||||
|
||||
if (!calls) {
|
||||
calls = getStringBetweenStrings(data, 'function(J){J=J.split("")', 'return J.join("")}');
|
||||
var_name = 'J';
|
||||
if (!match) {
|
||||
Log.warn(TAG, 'Failed to extract signature decipher algorithm.');
|
||||
return;
|
||||
}
|
||||
|
||||
const obj_name = calls?.split(/\.|\[/)?.[0]?.replace(';', '')?.trim();
|
||||
const var_name = match[1];
|
||||
|
||||
const obj_name = match[3].split(/\.|\[/)[0]?.replace(';', '').trim();
|
||||
const functions = getStringBetweenStrings(data, `var ${obj_name}={`, '};');
|
||||
|
||||
if (!functions || !calls)
|
||||
if (!functions || !var_name)
|
||||
Log.warn(TAG, 'Failed to extract signature decipher algorithm.');
|
||||
|
||||
return `function descramble_sig(${var_name}) { ${var_name} = ${var_name}.split(""); let ${obj_name}={${functions}}${calls} return ${var_name}.join("") } descramble_sig(sig);`;
|
||||
return `function descramble_sig(${var_name}) { let ${obj_name}={${functions}}; ${match[2]} } descramble_sig(sig);`;
|
||||
}
|
||||
|
||||
static extractNSigSourceCode(data: string): string | undefined {
|
||||
@@ -262,6 +263,6 @@ export default class Player {
|
||||
}
|
||||
|
||||
static get LIBRARY_VERSION(): number {
|
||||
return 12;
|
||||
return 13;
|
||||
}
|
||||
}
|
||||
24
src/parser/classes/ActiveAccountHeader.ts
Normal file
24
src/parser/classes/ActiveAccountHeader.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import { type RawNode } from '../index.js';
|
||||
import Text from './misc/Text.js';
|
||||
import Thumbnail from './misc/Thumbnail.js';
|
||||
import NavigationEndpoint from './NavigationEndpoint.js';
|
||||
|
||||
export default class ActiveAccountHeader extends YTNode {
|
||||
static type = 'ActiveAccountHeader';
|
||||
|
||||
public account_name: Text;
|
||||
public account_photo: Thumbnail[];
|
||||
public endpoint: NavigationEndpoint;
|
||||
public manage_account_title: Text;
|
||||
public channel_handle: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.account_name = new Text(data.accountName);
|
||||
this.account_photo = Thumbnail.fromResponse(data.accountPhoto);
|
||||
this.endpoint = new NavigationEndpoint(data.serviceEndpoint);
|
||||
this.manage_account_title = new Text(data.manageAccountTitle);
|
||||
this.channel_handle = new Text(data.channelHandle);
|
||||
}
|
||||
}
|
||||
25
src/parser/classes/BackgroundPromo.ts
Normal file
25
src/parser/classes/BackgroundPromo.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import { Parser, type RawNode } from '../index.js';
|
||||
import Text from './misc/Text.js';
|
||||
import Button from './Button.js';
|
||||
import ButtonView from './ButtonView.js';
|
||||
|
||||
export default class BackgroundPromo extends YTNode {
|
||||
static type = 'BackgroundPromo';
|
||||
|
||||
public body_text?: Text;
|
||||
public cta_button?: Button | ButtonView | null;
|
||||
public icon_type?: string;
|
||||
public title?: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.body_text = new Text(data.bodyText);
|
||||
this.cta_button = Parser.parseItem(data.ctaButton, [ Button, ButtonView ]);
|
||||
|
||||
if (Reflect.has(data, 'icon'))
|
||||
this.icon_type = data.icon.iconType;
|
||||
|
||||
this.title = new Text(data.title);
|
||||
}
|
||||
}
|
||||
18
src/parser/classes/ButtonCardView.ts
Normal file
18
src/parser/classes/ButtonCardView.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import NavigationEndpoint from './NavigationEndpoint.js';
|
||||
import { YTNode } from '../helpers.js';
|
||||
import type { RawNode } from '../index.js';
|
||||
|
||||
export default class ButtonCardView extends YTNode {
|
||||
static type = 'ButtonCardView';
|
||||
|
||||
title: string;
|
||||
icon_name: string;
|
||||
on_tap_endpoint: NavigationEndpoint;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = data.title;
|
||||
this.icon_name = data.icon.sources[0].clientResource.imageName;
|
||||
this.on_tap_endpoint = new NavigationEndpoint(data.rendererContext.commandContext.onTap);
|
||||
}
|
||||
}
|
||||
16
src/parser/classes/CarouselItemView.ts
Normal file
16
src/parser/classes/CarouselItemView.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import { Parser, type RawNode } from '../index.js';
|
||||
import TextCarouselItemView from './TextCarouselItemView.js';
|
||||
|
||||
export default class CarouselItemView extends YTNode {
|
||||
static type = 'CarouselItemView';
|
||||
|
||||
item_type: string;
|
||||
carousel_item: TextCarouselItemView | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.item_type = data.itemType;
|
||||
this.carousel_item = Parser.parseItem(data.carouselItem, TextCarouselItemView);
|
||||
}
|
||||
}
|
||||
18
src/parser/classes/CarouselTitleView.ts
Normal file
18
src/parser/classes/CarouselTitleView.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import { Parser, type RawNode } from '../index.js';
|
||||
import ButtonView from './ButtonView.js';
|
||||
|
||||
export default class CarouselTitleView extends YTNode {
|
||||
static type = 'CarouselTitleView';
|
||||
|
||||
title: string;
|
||||
previous_button: ButtonView | null;
|
||||
next_button: ButtonView | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = data.title;
|
||||
this.previous_button = Parser.parseItem(data.previousButton, ButtonView);
|
||||
this.next_button = Parser.parseItem(data.nextButton, ButtonView);
|
||||
}
|
||||
}
|
||||
46
src/parser/classes/ClientSideToggleMenuItem.ts
Normal file
46
src/parser/classes/ClientSideToggleMenuItem.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import type { RawNode } from '../index.js';
|
||||
import NavigationEndpoint from './NavigationEndpoint.js';
|
||||
import Text from './misc/Text.js';
|
||||
|
||||
export default class ClientSideToggleMenuItem extends YTNode {
|
||||
static type = 'ClientSideToggleMenuItem';
|
||||
|
||||
text: Text;
|
||||
icon_type: string;
|
||||
toggled_text: Text;
|
||||
toggled_icon_type: string;
|
||||
is_toggled?: boolean;
|
||||
menu_item_identifier: string;
|
||||
endpoint: NavigationEndpoint;
|
||||
logging_directives?: {
|
||||
visibility: {
|
||||
types: string;
|
||||
},
|
||||
enable_displaylogger_experiment: boolean;
|
||||
};
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.text = new Text(data.defaultText);
|
||||
this.icon_type = data.defaultIcon.iconType;
|
||||
this.toggled_text = new Text(data.toggledText);
|
||||
this.toggled_icon_type = data.toggledIcon.iconType;
|
||||
|
||||
if (Reflect.has(data, 'isToggled')) {
|
||||
this.is_toggled = data.isToggled;
|
||||
}
|
||||
|
||||
this.menu_item_identifier = data.menuItemIdentifier;
|
||||
this.endpoint = new NavigationEndpoint(data.command);
|
||||
|
||||
if (Reflect.has(data, 'loggingDirectives')) {
|
||||
this.logging_directives = {
|
||||
visibility: {
|
||||
types: data.loggingDirectives.visibility.types
|
||||
},
|
||||
enable_displaylogger_experiment: data.loggingDirectives.enableDisplayloggerExperiment
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/parser/classes/PlaylistThumbnailOverlay.ts
Normal file
17
src/parser/classes/PlaylistThumbnailOverlay.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import { type RawNode } from '../index.js';
|
||||
import Text from './misc/Text.js';
|
||||
|
||||
export default class PlaylistThumbnailOverlay extends YTNode {
|
||||
static type = 'PlaylistThumbnailOverlay';
|
||||
|
||||
public icon_type?: string;
|
||||
public text: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
if (Reflect.has(data, 'icon'))
|
||||
this.icon_type = data.icon.iconType;
|
||||
this.text = new Text(data.text);
|
||||
}
|
||||
}
|
||||
22
src/parser/classes/TextCarouselItemView.ts
Normal file
22
src/parser/classes/TextCarouselItemView.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import { Parser, type RawNode } from '../index.js';
|
||||
import { Text } from '../misc.js';
|
||||
import ButtonView from './ButtonView.js';
|
||||
import NavigationEndpoint from './NavigationEndpoint.js';
|
||||
|
||||
export default class TextCarouselItemView extends YTNode {
|
||||
static type = 'TextCarouselItemView';
|
||||
|
||||
icon_name: string;
|
||||
text: Text;
|
||||
on_tap_endpoint: NavigationEndpoint;
|
||||
button: ButtonView | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.icon_name = data.iconName;
|
||||
this.text = Text.fromAttributed(data.text);
|
||||
this.on_tap_endpoint = new NavigationEndpoint(data.onTap);
|
||||
this.button = Parser.parseItem(data.button, ButtonView);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ export default class Video extends YTNode {
|
||||
show_action_menu: boolean;
|
||||
is_watched: boolean;
|
||||
menu: Menu | null;
|
||||
byline_text?: Text;
|
||||
search_video_result_entity_key?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
@@ -91,6 +92,10 @@ export default class Video extends YTNode {
|
||||
if (Reflect.has(data, 'searchVideoResultEntityKey')) {
|
||||
this.search_video_result_entity_key = data.searchVideoResultEntityKey;
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'bylineText')) {
|
||||
this.byline_text = new Text(data.bylineText);
|
||||
}
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
|
||||
@@ -5,16 +5,20 @@ import Video from './Video.js';
|
||||
|
||||
export default class VideoCard extends Video {
|
||||
static type = 'VideoCard';
|
||||
|
||||
public metadata_text?: Text;
|
||||
public byline_text?: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super(data);
|
||||
if (Reflect.has(data, 'metadataText')) {
|
||||
const metadata = new Text(data.metadataText);
|
||||
if (metadata.text) {
|
||||
this.short_view_count = new Text({ simpleText: metadata.text.split('·')[0].trim() } as RawNode);
|
||||
this.published = new Text({ simpleText: metadata.text.split('·')[1].trim() } as RawNode);
|
||||
this.metadata_text = new Text(data.metadataText);
|
||||
if (this.metadata_text.text) {
|
||||
this.short_view_count = new Text({ simpleText: this.metadata_text.text.split('·')[0]?.trim() } as RawNode);
|
||||
this.published = new Text({ simpleText: this.metadata_text.text.split('·')[1]?.trim() } as RawNode);
|
||||
}
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'bylineText')) {
|
||||
this.author = new Author(data.bylineText, data.ownerBadges, data.channelThumbnailSupportedRenderers?.channelThumbnailWithLinkRenderer?.thumbnail);
|
||||
}
|
||||
|
||||
17
src/parser/classes/VideoMetadataCarouselView.ts
Normal file
17
src/parser/classes/VideoMetadataCarouselView.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { YTNode, type ObservedArray } from '../helpers.js';
|
||||
import { Parser, type RawNode } from '../index.js';
|
||||
import CarouselItemView from './CarouselItemView.js';
|
||||
import CarouselTitleView from './CarouselTitleView.js';
|
||||
|
||||
export default class VideoMetadataCarouselView extends YTNode {
|
||||
static type = 'VideoMetadataCarouselView';
|
||||
|
||||
carousel_titles: ObservedArray<CarouselTitleView> | null;
|
||||
carousel_items: ObservedArray<CarouselItemView> | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.carousel_titles = Parser.parse(data.carouselTitles, true, CarouselTitleView);
|
||||
this.carousel_items = Parser.parse(data.carouselItems, true, CarouselItemView);
|
||||
}
|
||||
}
|
||||
@@ -9,19 +9,46 @@ export default class LiveChatBanner extends YTNode {
|
||||
header: LiveChatBannerHeader | null;
|
||||
contents: YTNode;
|
||||
action_id: string;
|
||||
viewer_is_creator: boolean;
|
||||
viewer_is_creator?: boolean;
|
||||
target_id: string;
|
||||
is_stackable: boolean;
|
||||
background_type: string;
|
||||
background_type?: string;
|
||||
banner_type: string;
|
||||
banner_properties_is_ephemeral?: boolean;
|
||||
banner_properties_auto_collapse_delay_seconds?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.header = Parser.parseItem(data.header, LiveChatBannerHeader);
|
||||
this.contents = Parser.parseItem(data.contents);
|
||||
this.action_id = data.actionId;
|
||||
this.viewer_is_creator = data.viewerIsCreator;
|
||||
|
||||
if (Reflect.has(data, 'viewerIsCreator')) {
|
||||
this.viewer_is_creator = data.viewerIsCreator;
|
||||
}
|
||||
|
||||
this.target_id = data.targetId;
|
||||
this.is_stackable = data.isStackable;
|
||||
this.background_type = data.backgroundType;
|
||||
|
||||
if (Reflect.has(data, 'backgroundType')) {
|
||||
this.background_type = data.backgroundType;
|
||||
}
|
||||
|
||||
this.banner_type = data.bannerType;
|
||||
|
||||
if (
|
||||
Reflect.has(data, 'bannerProperties') &&
|
||||
Reflect.has(data.bannerProperties, 'isEphemeral')
|
||||
) {
|
||||
this.banner_properties_is_ephemeral = Boolean(data.bannerProperties.isEphemeral);
|
||||
}
|
||||
|
||||
if (
|
||||
Reflect.has(data, 'bannerProperties') &&
|
||||
Reflect.has(data.bannerProperties, 'autoCollapseDelay') &&
|
||||
Reflect.has(data.bannerProperties.autoCollapseDelay, 'seconds')
|
||||
) {
|
||||
this.banner_properties_auto_collapse_delay_seconds = data.bannerProperties.autoCollapseDelay.seconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,16 +9,37 @@ export default class LiveChatMembershipItem extends YTNode {
|
||||
|
||||
id: string;
|
||||
timestamp: number;
|
||||
timestamp_usec: string;
|
||||
timestamp_text?: Text;
|
||||
header_primary_text?: Text;
|
||||
header_subtext: Text;
|
||||
message?: Text;
|
||||
author: Author;
|
||||
menu_endpoint: NavigationEndpoint;
|
||||
context_menu_accessibility_label: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.id = data.id;
|
||||
this.timestamp = Math.floor(parseInt(data.timestampUsec) / 1000);
|
||||
this.timestamp_usec = data.timestampUsec;
|
||||
|
||||
if (Reflect.has(data, 'timestampText')) {
|
||||
this.timestamp_text = new Text(data.timestampText);
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'headerPrimaryText')) {
|
||||
this.header_primary_text = new Text(data.headerPrimaryText);
|
||||
}
|
||||
|
||||
this.header_subtext = new Text(data.headerSubtext);
|
||||
|
||||
if (Reflect.has(data, 'message')) {
|
||||
this.message = new Text(data.message);
|
||||
}
|
||||
|
||||
this.author = new Author(data.authorName, data.authorBadges, data.authorPhoto, data.authorExternalChannelId);
|
||||
this.menu_endpoint = new NavigationEndpoint(data.contextMenuEndpoint);
|
||||
this.context_menu_accessibility_label = data.contextMenuAccessibility.accessibilityData.label;
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,21 @@ import SegmentedLikeDislikeButtonView from '../SegmentedLikeDislikeButtonView.js
|
||||
import MenuFlexibleItem from './MenuFlexibleItem.js';
|
||||
import LikeButton from '../LikeButton.js';
|
||||
import ToggleButton from '../ToggleButton.js';
|
||||
import FlexibleActionsView from '../FlexibleActionsView.js';
|
||||
|
||||
export default class Menu extends YTNode {
|
||||
static type = 'Menu';
|
||||
|
||||
public items: ObservedArray<YTNode>;
|
||||
public flexible_items: ObservedArray<MenuFlexibleItem>;
|
||||
public top_level_buttons: ObservedArray<ToggleButton | LikeButton | Button |ButtonView | SegmentedLikeDislikeButtonView>;
|
||||
public top_level_buttons: ObservedArray<ToggleButton | LikeButton | Button |ButtonView | SegmentedLikeDislikeButtonView | FlexibleActionsView>;
|
||||
public label?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.items = Parser.parseArray(data.items);
|
||||
this.flexible_items = Parser.parseArray(data.flexibleItems, MenuFlexibleItem);
|
||||
this.top_level_buttons = Parser.parseArray(data.topLevelButtons, [ ToggleButton, LikeButton, Button, ButtonView, SegmentedLikeDislikeButtonView ]);
|
||||
this.top_level_buttons = Parser.parseArray(data.topLevelButtons, [ ToggleButton, LikeButton, Button, ButtonView, SegmentedLikeDislikeButtonView, FlexibleActionsView ]);
|
||||
|
||||
if (Reflect.has(data, 'accessibility') && Reflect.has(data.accessibility, 'accessibilityData')) {
|
||||
this.label = data.accessibility.accessibilityData.label;
|
||||
|
||||
@@ -17,6 +17,7 @@ export { default as SignalAction } from './classes/actions/SignalAction.js';
|
||||
export { default as UpdateChannelSwitcherPageAction } from './classes/actions/UpdateChannelSwitcherPageAction.js';
|
||||
export { default as UpdateEngagementPanelAction } from './classes/actions/UpdateEngagementPanelAction.js';
|
||||
export { default as UpdateSubscribeButtonAction } from './classes/actions/UpdateSubscribeButtonAction.js';
|
||||
export { default as ActiveAccountHeader } from './classes/ActiveAccountHeader.js';
|
||||
export { default as AddToPlaylist } from './classes/AddToPlaylist.js';
|
||||
export { default as Alert } from './classes/Alert.js';
|
||||
export { default as AlertWithButton } from './classes/AlertWithButton.js';
|
||||
@@ -24,6 +25,7 @@ export { default as AttributionView } from './classes/AttributionView.js';
|
||||
export { default as AudioOnlyPlayability } from './classes/AudioOnlyPlayability.js';
|
||||
export { default as AutomixPreviewVideo } from './classes/AutomixPreviewVideo.js';
|
||||
export { default as AvatarView } from './classes/AvatarView.js';
|
||||
export { default as BackgroundPromo } from './classes/BackgroundPromo.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';
|
||||
@@ -31,6 +33,7 @@ export { default as BadgeView } from './classes/BadgeView.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 ButtonCardView } from './classes/ButtonCardView.js';
|
||||
export { default as ButtonView } from './classes/ButtonView.js';
|
||||
export { default as C4TabbedHeader } from './classes/C4TabbedHeader.js';
|
||||
export { default as CallToActionButton } from './classes/CallToActionButton.js';
|
||||
@@ -38,7 +41,9 @@ 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 CarouselItemView } from './classes/CarouselItemView.js';
|
||||
export { default as CarouselLockup } from './classes/CarouselLockup.js';
|
||||
export { default as CarouselTitleView } from './classes/CarouselTitleView.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';
|
||||
@@ -62,6 +67,7 @@ export { default as ChipBarView } from './classes/ChipBarView.js';
|
||||
export { default as ChipCloud } from './classes/ChipCloud.js';
|
||||
export { default as ChipCloudChip } from './classes/ChipCloudChip.js';
|
||||
export { default as ChipView } from './classes/ChipView.js';
|
||||
export { default as ClientSideToggleMenuItem } from './classes/ClientSideToggleMenuItem.js';
|
||||
export { default as ClipAdState } from './classes/ClipAdState.js';
|
||||
export { default as ClipCreation } from './classes/ClipCreation.js';
|
||||
export { default as ClipCreationScrubber } from './classes/ClipCreationScrubber.js';
|
||||
@@ -361,6 +367,7 @@ export { default as PlaylistPanelVideoWrapper } from './classes/PlaylistPanelVid
|
||||
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 PlaylistThumbnailOverlay } from './classes/PlaylistThumbnailOverlay.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';
|
||||
@@ -436,6 +443,7 @@ export { default as SubscriptionNotificationToggleButton } from './classes/Subsc
|
||||
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 TextCarouselItemView } from './classes/TextCarouselItemView.js';
|
||||
export { default as TextFieldView } from './classes/TextFieldView.js';
|
||||
export { default as TextHeader } from './classes/TextHeader.js';
|
||||
export { default as ThirdPartyShareTargetSection } from './classes/ThirdPartyShareTargetSection.js';
|
||||
@@ -489,6 +497,7 @@ export { default as VideoDescriptionInfocardsSection } from './classes/VideoDesc
|
||||
export { default as VideoDescriptionMusicSection } from './classes/VideoDescriptionMusicSection.js';
|
||||
export { default as VideoDescriptionTranscriptSection } from './classes/VideoDescriptionTranscriptSection.js';
|
||||
export { default as VideoInfoCardContent } from './classes/VideoInfoCardContent.js';
|
||||
export { default as VideoMetadataCarouselView } from './classes/VideoMetadataCarouselView.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';
|
||||
|
||||
@@ -76,7 +76,6 @@ const IGNORED_LIST = new Set([
|
||||
'SearchPyv',
|
||||
'MealbarPromo',
|
||||
'PrimetimePromo',
|
||||
'BackgroundPromo',
|
||||
'PromotedSparklesWeb',
|
||||
'CompactPromotedVideo',
|
||||
'BrandVideoShelf',
|
||||
|
||||
Reference in New Issue
Block a user