mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-13 09:32:12 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e3846259f | ||
|
|
222dfce6bb |
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## [5.2.1](https://github.com/LuanRT/YouTube.js/compare/v5.2.0...v5.2.1) (2023-07-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* incorrect node parser implementations ([#428](https://github.com/LuanRT/YouTube.js/issues/428)) ([222dfce](https://github.com/LuanRT/YouTube.js/commit/222dfce6bbd13b2cd80ae11540cbc0edd9053fc5))
|
||||
|
||||
## [5.2.0](https://github.com/LuanRT/YouTube.js/compare/v5.1.0...v5.2.0) (2023-06-28)
|
||||
|
||||
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "youtubei.js",
|
||||
"version": "5.2.0",
|
||||
"version": "5.2.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "youtubei.js",
|
||||
"version": "5.2.0",
|
||||
"version": "5.2.1",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/LuanRT"
|
||||
],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "youtubei.js",
|
||||
"version": "5.2.0",
|
||||
"version": "5.2.1",
|
||||
"description": "A wrapper around YouTube's private API. Supports YouTube, YouTube Music, YouTube Kids and YouTube Studio (WIP).",
|
||||
"type": "module",
|
||||
"types": "./dist/src/platform/lib.d.ts",
|
||||
|
||||
@@ -7,14 +7,11 @@ export default class CarouselLockup extends YTNode {
|
||||
static type = 'CarouselLockup';
|
||||
|
||||
info_rows: ObservedArray<InfoRow>;
|
||||
video_lockup?: CompactVideo;
|
||||
video_lockup?: CompactVideo | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.info_rows = Parser.parseArray(data.infoRows, InfoRow);
|
||||
const video_lockup = Parser.parseItem(data.videoLockup, CompactVideo);
|
||||
if (video_lockup != null) {
|
||||
this.video_lockup = video_lockup;
|
||||
}
|
||||
this.video_lockup = Parser.parseItem(data.videoLockup, CompactVideo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import Parser, { type RawNode } from '../index.js';
|
||||
import ContinuationItem from './ContinuationItem.js';
|
||||
import EngagementPanelTitleHeader from './EngagementPanelTitleHeader.js';
|
||||
import SectionList from './SectionList.js';
|
||||
import StructuredDescriptionContent from './StructuredDescriptionContent.js';
|
||||
|
||||
export default class EngagementPanelSectionList extends YTNode {
|
||||
static type = 'EngagementPanelSectionList';
|
||||
|
||||
target_id: String;
|
||||
content?: SectionList|ContinuationItem|StructuredDescriptionContent;
|
||||
header: EngagementPanelTitleHeader | null;
|
||||
content: SectionList | ContinuationItem | StructuredDescriptionContent | null;
|
||||
target_id?: string;
|
||||
panel_identifier?: string;
|
||||
visibility?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.header = Parser.parseItem(data.header, EngagementPanelTitleHeader);
|
||||
this.content = Parser.parseItem(data.content, [ SectionList, ContinuationItem, StructuredDescriptionContent ]);
|
||||
this.panel_identifier = data.panelIdentifier;
|
||||
this.target_id = data.targetId;
|
||||
const content = Parser.parseItem(data.content, [ SectionList, ContinuationItem, StructuredDescriptionContent ]);
|
||||
if (content !== null) {
|
||||
this.content = content;
|
||||
}
|
||||
this.visibility = data.visibility;
|
||||
}
|
||||
}
|
||||
|
||||
17
src/parser/classes/EngagementPanelTitleHeader.ts
Normal file
17
src/parser/classes/EngagementPanelTitleHeader.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import { Parser, type RawNode } from '../index.js';
|
||||
import Text from './misc/Text.js';
|
||||
import Button from './Button.js';
|
||||
|
||||
export default class EngagementPanelTitleHeader extends YTNode {
|
||||
static type = 'EngagementPanelTitleHeader';
|
||||
|
||||
title: Text;
|
||||
visibility_button: Button | null;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.title = new Text(data.title);
|
||||
this.visibility_button = Parser.parseItem(data.visibilityButton, Button);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import { type RawNode } from '../index.js';
|
||||
import { Text } from '../misc.js';
|
||||
|
||||
import type { RawNode } from '../index.js';
|
||||
|
||||
export default class ExpandableVideoDescriptionBody extends YTNode {
|
||||
static type = 'ExpandableVideoDescriptionBody';
|
||||
|
||||
show_more_text: Text;
|
||||
show_less_text: Text;
|
||||
attributed_description_body_text: {
|
||||
content: String
|
||||
};
|
||||
attributed_description_body_text?: string;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.show_more_text = new Text(data.showMoreText);
|
||||
this.show_less_text = new Text(data.showLessText);
|
||||
this.attributed_description_body_text = {
|
||||
content: data.attributedDescriptionBodyText.content
|
||||
};
|
||||
|
||||
if (Reflect.has(data, 'attributedDescriptionBodyText')) {
|
||||
this.attributed_description_body_text = data.attributedDescriptionBodyText?.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Text } from '../misc.js';
|
||||
|
||||
export default class Factoid extends YTNode {
|
||||
static type = 'Factoid';
|
||||
|
||||
label: Text;
|
||||
value: Text;
|
||||
accessibility_text: String;
|
||||
|
||||
@@ -1,38 +1,29 @@
|
||||
import { YTNode } from '../helpers.js';
|
||||
import Parser, { type RawNode } from '../index.js';
|
||||
import { Text } from '../misc.js';
|
||||
import NavigationEndpoint from './NavigationEndpoint.js';
|
||||
import type { RawNode } from '../index.js';
|
||||
|
||||
export default class InfoRow extends YTNode {
|
||||
static type = 'InfoRow';
|
||||
metadata_text?: Text;
|
||||
metadata_endpoint?: NavigationEndpoint;
|
||||
info_row_expand_status_key: String;
|
||||
|
||||
title: Text;
|
||||
default_metadata?: Text;
|
||||
expanded_metadata?: Text;
|
||||
info_row_expand_status_key?: String;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
if ('defaultMetadata' in data && 'runs' in data.defaultMetadata) {
|
||||
const runs = data.defaultMetadata.runs;
|
||||
if (runs.length > 0) {
|
||||
const run = runs[0];
|
||||
this.metadata_text = run?.text;
|
||||
if ('navigationEndpoint' in run) {
|
||||
this.metadata_endpoint = Parser.parseItem({ navigationEndpoint: run.navigationEndpoint }, NavigationEndpoint) || undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ('expandedMetadata' in data && 'runs' in data.expandedMetadata) {
|
||||
this.metadata_text = new Text(data.expandedMetadata);
|
||||
}
|
||||
if (this.metadata_text === undefined) {
|
||||
this.metadata_text = data.expandedMetadata?.simpleText
|
||||
? new Text(data.expandedMetadata)
|
||||
: data.defaultMetadata?.simpleText
|
||||
? new Text(data.defaultMetadata)
|
||||
: undefined;
|
||||
}
|
||||
this.info_row_expand_status_key = data.infoRowExpandStatusKey;
|
||||
this.title = new Text(data.title);
|
||||
|
||||
if (Reflect.has(data, 'defaultMetadata')) {
|
||||
this.default_metadata = new Text(data.defaultMetadata);
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'expandedMetadata')) {
|
||||
this.expanded_metadata = new Text(data.expandedMetadata);
|
||||
}
|
||||
|
||||
if (Reflect.has(data, 'infoRowExpandStatusKey')) {
|
||||
this.info_row_expand_status_key = data.infoRowExpandStatusKey;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
import { type ObservedArray, YTNode } from '../helpers.js';
|
||||
import { YTNode, type ObservedArray } from '../helpers.js';
|
||||
import Parser, { type RawNode } from '../index.js';
|
||||
import ExpandableVideoDescriptionBody from './ExpandableVideoDescriptionBody.js';
|
||||
import VideoDescriptionHeader from './VideoDescriptionHeader.js';
|
||||
import VideoDescriptionInfocardsSection from './VideoDescriptionInfocardsSection.js';
|
||||
import VideoDescriptionMusicSection from './VideoDescriptionMusicSection.js';
|
||||
|
||||
export default class StructuredDescriptionContent extends YTNode {
|
||||
static type = 'StructuredDescriptionContent';
|
||||
|
||||
items: ObservedArray<VideoDescriptionHeader|ExpandableVideoDescriptionBody|VideoDescriptionMusicSection>;
|
||||
items: ObservedArray<VideoDescriptionHeader | ExpandableVideoDescriptionBody | VideoDescriptionMusicSection | VideoDescriptionInfocardsSection>;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.items = Parser.parseArray(data.items, [ VideoDescriptionHeader, ExpandableVideoDescriptionBody, VideoDescriptionMusicSection ]);
|
||||
this.items = Parser.parseArray(data.items, [ VideoDescriptionHeader, ExpandableVideoDescriptionBody, VideoDescriptionMusicSection, VideoDescriptionInfocardsSection ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { type ObservedArray, YTNode } from '../helpers.js';
|
||||
import { YTNode, type ObservedArray } from '../helpers.js';
|
||||
import Parser, { type RawNode } from '../index.js';
|
||||
import { Text } from '../misc.js';
|
||||
import type { Thumbnail } from '../misc.js';
|
||||
import { Text, Thumbnail } from '../misc.js';
|
||||
import Factoid from './Factoid.js';
|
||||
import NavigationEndpoint from './NavigationEndpoint.js';
|
||||
|
||||
@@ -10,7 +9,7 @@ export default class VideoDescriptionHeader extends YTNode {
|
||||
|
||||
channel: Text;
|
||||
channel_navigation_endpoint?: NavigationEndpoint;
|
||||
channel_thumbnails: String[];
|
||||
channel_thumbnail: Thumbnail[];
|
||||
factoids: ObservedArray<Factoid>;
|
||||
publish_date: Text;
|
||||
title: Text;
|
||||
@@ -20,8 +19,8 @@ export default class VideoDescriptionHeader extends YTNode {
|
||||
super();
|
||||
this.title = new Text(data.title);
|
||||
this.channel = new Text(data.channel);
|
||||
this.channel_navigation_endpoint = Parser.parseItem(data.channelNavigationEndpoint, NavigationEndpoint) || undefined;
|
||||
this.channel_thumbnails = data.channelThumbnail.thumbnails.map((thumbnail: Thumbnail) => thumbnail.url);
|
||||
this.channel_navigation_endpoint = new NavigationEndpoint(data.channelNavigationEndpoint);
|
||||
this.channel_thumbnail = Thumbnail.fromResponse(data.channelThumbnail);
|
||||
this.publish_date = new Text(data.publishDate);
|
||||
this.views = new Text(data.views);
|
||||
this.factoids = Parser.parseArray(data.factoid, Factoid);
|
||||
|
||||
28
src/parser/classes/VideoDescriptionInfocardsSection.ts
Normal file
28
src/parser/classes/VideoDescriptionInfocardsSection.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import Parser, { type RawNode } from '../index.js';
|
||||
|
||||
import { YTNode } from '../helpers.js';
|
||||
import Text from './misc/Text.js';
|
||||
import Thumbnail from './misc/Thumbnail.js';
|
||||
import Button from './Button.js';
|
||||
import NavigationEndpoint from './NavigationEndpoint.js';
|
||||
|
||||
export default class VideoDescriptionInfocardsSection extends YTNode {
|
||||
static type = 'VideoDescriptionInfocardsSection';
|
||||
|
||||
section_title: Text;
|
||||
creator_videos_button: Button | null;
|
||||
creator_about_button: Button | null;
|
||||
section_subtitle: Text;
|
||||
channel_avatar: Thumbnail[];
|
||||
channel_endpoint: NavigationEndpoint;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.section_title = new Text(data.sectionTitle);
|
||||
this.creator_videos_button = Parser.parseItem(data.creatorVideosButton, Button);
|
||||
this.creator_about_button = Parser.parseItem(data.creatorAboutButton, Button);
|
||||
this.section_subtitle = new Text(data.sectionSubtitle);
|
||||
this.channel_avatar = Thumbnail.fromResponse(data.channelAvatar);
|
||||
this.channel_endpoint = new NavigationEndpoint(data.channelEndpoint);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import { type ObservedArray, YTNode } from '../helpers.js';
|
||||
import CarouselLockup from './CarouselLockup.js';
|
||||
import { YTNode, type ObservedArray } from '../helpers.js';
|
||||
import Parser, { type RawNode } from '../index.js';
|
||||
import { Text } from '../misc.js';
|
||||
import CarouselLockup from './CarouselLockup.js';
|
||||
|
||||
export default class VideoDescriptionMusicSection extends YTNode {
|
||||
static type = 'VideoDescriptionMusicSection';
|
||||
|
||||
carousel_lockups: ObservedArray<CarouselLockup>;
|
||||
section_title: Text;
|
||||
|
||||
constructor(data: RawNode) {
|
||||
super();
|
||||
this.carousel_lockups = Parser.parseArray(data.carouselLockups, CarouselLockup);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-cond-assign */
|
||||
import { Platform } from '../utils/Utils.js';
|
||||
import { InnertubeError, 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';
|
||||
@@ -62,7 +62,9 @@ export class YTNodeGenerator {
|
||||
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)}`);
|
||||
console.warn(
|
||||
new InnertubeError(`${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)}`);
|
||||
@@ -460,15 +462,15 @@ export class YTNodeGenerator {
|
||||
* @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 ]);
|
||||
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;
|
||||
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;
|
||||
return should_optional ? Parser.parse(this.#accessDataFromKeyPath({ data }, [ ...key_path, key ]), true, inference_type.renderers.map((type) => Parser.getParserByName(type))) : undefined;
|
||||
}
|
||||
case 'object':
|
||||
{
|
||||
@@ -482,25 +484,25 @@ export class YTNodeGenerator {
|
||||
case 'misc':
|
||||
switch (inference_type.misc_type) {
|
||||
case 'NavigationEndpoint':
|
||||
return should_optional ? new NavigationEndpoint(this.#accessDataFromKeyPath({data}, [ ...key_path, key ])) : undefined;
|
||||
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;
|
||||
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;
|
||||
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] ]);
|
||||
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] ]),
|
||||
this.#accessDataFromKeyPath({ data }, [ ...key_path, inference_type.params[0] ]),
|
||||
inference_type.params[1] ?
|
||||
this.#accessDataFromKeyPath({data}, [ ...key_path, inference_type.params[1] ]) : undefined
|
||||
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 ]);
|
||||
return this.#accessDataFromKeyPath({ data }, [ ...key_path, key ]);
|
||||
}
|
||||
}
|
||||
static #passOne(classdata: any) {
|
||||
|
||||
@@ -90,6 +90,7 @@ 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 EngagementPanelSectionList } from './classes/EngagementPanelSectionList.js';
|
||||
export { default as EngagementPanelTitleHeader } from './classes/EngagementPanelTitleHeader.js';
|
||||
export { default as ExpandableMetadata } from './classes/ExpandableMetadata.js';
|
||||
export { default as ExpandableTab } from './classes/ExpandableTab.js';
|
||||
export { default as ExpandableVideoDescriptionBody } from './classes/ExpandableVideoDescriptionBody.js';
|
||||
@@ -342,6 +343,7 @@ export { default as VerticalWatchCardList } from './classes/VerticalWatchCardLis
|
||||
export { default as Video } from './classes/Video.js';
|
||||
export { default as VideoCard } from './classes/VideoCard.js';
|
||||
export { default as VideoDescriptionHeader } from './classes/VideoDescriptionHeader.js';
|
||||
export { default as VideoDescriptionInfocardsSection } from './classes/VideoDescriptionInfocardsSection.js';
|
||||
export { default as VideoDescriptionMusicSection } from './classes/VideoDescriptionMusicSection.js';
|
||||
export { default as VideoInfoCardContent } from './classes/VideoInfoCardContent.js';
|
||||
export { default as VideoOwner } from './classes/VideoOwner.js';
|
||||
|
||||
@@ -339,36 +339,45 @@ class VideoInfo extends MediaInfo {
|
||||
* Get songs used in the video.
|
||||
*/
|
||||
get music_tracks() {
|
||||
// @TODO: Refactor this.
|
||||
const description_content = this.page[1]?.engagement_panels?.filter((panel) => panel.content?.is(StructuredDescriptionContent));
|
||||
if (description_content !== undefined && description_content.length > 0) {
|
||||
const music_section = (description_content[0].content as StructuredDescriptionContent)?.items.filter((item: YTNode) => item?.is(VideoDescriptionMusicSection)) as VideoDescriptionMusicSection[];
|
||||
const music_section = description_content[0].content?.as(StructuredDescriptionContent)?.items?.filterType(VideoDescriptionMusicSection);
|
||||
if (music_section !== undefined && music_section.length > 0) {
|
||||
return music_section[0].carousel_lockups?.map((lookup) => {
|
||||
let song, artist, album, license, videoId, channelId;
|
||||
let song: string | undefined;
|
||||
let artist: string | undefined;
|
||||
let album: string | undefined;
|
||||
let license: string | undefined;
|
||||
let videoId: string | undefined;
|
||||
let channelId: string | undefined;
|
||||
|
||||
// If the song isn't in the video_lockup, it should be in the info_rows
|
||||
song = lookup.video_lockup?.title;
|
||||
song = lookup.video_lockup?.title?.toString();
|
||||
// If the video id isn't in the video_lockup, it should be in the info_rows
|
||||
videoId = lookup.video_lockup?.endpoint.payload.videoId;
|
||||
for (let i = 0; i < lookup.info_rows.length; i++) {
|
||||
const info_row = lookup.info_rows[i];
|
||||
if (info_row.info_row_expand_status_key === undefined) {
|
||||
if (song === undefined) {
|
||||
song = info_row.metadata_text;
|
||||
song = info_row.default_metadata?.toString() || info_row.expanded_metadata?.toString();
|
||||
if (videoId === undefined) {
|
||||
videoId = info_row.metadata_endpoint?.payload;
|
||||
const endpoint = info_row.default_metadata?.endpoint || info_row.expanded_metadata?.endpoint;
|
||||
videoId = endpoint?.payload?.videoId;
|
||||
}
|
||||
} else {
|
||||
album = info_row.metadata_text;
|
||||
album = info_row.default_metadata?.toString() || info_row.expanded_metadata?.toString();
|
||||
}
|
||||
} else {
|
||||
if (info_row.info_row_expand_status_key?.indexOf('structured-description-music-section-artists-row-state-id') !== -1) {
|
||||
artist = info_row.metadata_text;
|
||||
artist = info_row.default_metadata?.toString() || info_row.expanded_metadata?.toString();
|
||||
if (channelId === undefined) {
|
||||
channelId = info_row.metadata_endpoint?.payload?.browseId;
|
||||
const endpoint = info_row.default_metadata?.endpoint || info_row.expanded_metadata?.endpoint;
|
||||
channelId = endpoint?.payload?.browseId;
|
||||
}
|
||||
}
|
||||
if (info_row.info_row_expand_status_key?.indexOf('structured-description-music-section-licenses-row-state-id') !== -1) {
|
||||
license = info_row.metadata_text;
|
||||
license = info_row.default_metadata?.toString() || info_row.expanded_metadata?.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user