@np-dev/youtubei-js
Version:
A wrapper around YouTube's private API. Supports YouTube, YouTube Music, YouTube Kids and YouTube Studio (WIP).
344 lines • 25.1 kB
JavaScript
var _VideoInfo_watch_next_continuation;
import { __awaiter, __classPrivateFieldGet, __classPrivateFieldSet } from "tslib";
import ChipCloud from '../classes/ChipCloud.js';
import ChipCloudChip from '../classes/ChipCloudChip.js';
import CommentsEntryPointHeader from '../classes/comments/CommentsEntryPointHeader.js';
import ContinuationItem from '../classes/ContinuationItem.js';
import ItemSection from '../classes/ItemSection.js';
import LiveChat from '../classes/LiveChat.js';
import MerchandiseShelf from '../classes/MerchandiseShelf.js';
import MicroformatData from '../classes/MicroformatData.js';
import PlayerMicroformat from '../classes/PlayerMicroformat.js';
import PlayerOverlay from '../classes/PlayerOverlay.js';
import RelatedChipCloud from '../classes/RelatedChipCloud.js';
import RichMetadata from '../classes/RichMetadata.js';
import RichMetadataRow from '../classes/RichMetadataRow.js';
import SegmentedLikeDislikeButton from '../classes/SegmentedLikeDislikeButton.js';
import ToggleButton from '../classes/ToggleButton.js';
import TwoColumnWatchNextResults from '../classes/TwoColumnWatchNextResults.js';
import VideoPrimaryInfo from '../classes/VideoPrimaryInfo.js';
import VideoSecondaryInfo from '../classes/VideoSecondaryInfo.js';
import LiveChatWrap from './LiveChat.js';
import PlayerLegacyDesktopYpcTrailer from '../classes/PlayerLegacyDesktopYpcTrailer.js';
import { InnertubeError } from '../../utils/Utils.js';
import { MediaInfo } from '../../core/mixins/index.js';
import StructuredDescriptionContent from '../classes/StructuredDescriptionContent.js';
import { VideoDescriptionMusicSection } from '../nodes.js';
class VideoInfo extends MediaInfo {
/**
* @param data - API response.
* @param actions - Actions instance.
* @param player - Player instance.
* @param cpn - Client Playback Nonce.
*/
constructor(data, actions, cpn) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17;
super(data, actions, cpn);
_VideoInfo_watch_next_continuation.set(this, void 0);
const [info, next] = this.page;
if (info.microformat && !((_a = info.microformat) === null || _a === void 0 ? void 0 : _a.is(PlayerMicroformat, MicroformatData)))
throw new InnertubeError('Invalid microformat', info.microformat);
this.basic_info = Object.assign(Object.assign(Object.assign({}, info.video_details), {
embed: ((_b = info.microformat) === null || _b === void 0 ? void 0 : _b.is(PlayerMicroformat)) ? (_c = info.microformat) === null || _c === void 0 ? void 0 : _c.embed : null,
channel: ((_d = info.microformat) === null || _d === void 0 ? void 0 : _d.is(PlayerMicroformat)) ? (_e = info.microformat) === null || _e === void 0 ? void 0 : _e.channel : null,
is_unlisted: (_f = info.microformat) === null || _f === void 0 ? void 0 : _f.is_unlisted,
is_family_safe: (_g = info.microformat) === null || _g === void 0 ? void 0 : _g.is_family_safe,
category: ((_h = info.microformat) === null || _h === void 0 ? void 0 : _h.is(PlayerMicroformat)) ? (_j = info.microformat) === null || _j === void 0 ? void 0 : _j.category : null,
has_ypc_metadata: ((_k = info.microformat) === null || _k === void 0 ? void 0 : _k.is(PlayerMicroformat)) ? (_l = info.microformat) === null || _l === void 0 ? void 0 : _l.has_ypc_metadata : null,
start_timestamp: ((_m = info.microformat) === null || _m === void 0 ? void 0 : _m.is(PlayerMicroformat)) ? info.microformat.start_timestamp : null,
view_count: ((_o = info.microformat) === null || _o === void 0 ? void 0 : _o.is(PlayerMicroformat)) && isNaN((_p = info.video_details) === null || _p === void 0 ? void 0 : _p.view_count) ? info.microformat.view_count : (_q = info.video_details) === null || _q === void 0 ? void 0 : _q.view_count
}), { like_count: undefined, is_liked: undefined, is_disliked: undefined });
this.annotations = info.annotations;
this.storyboards = info.storyboards;
this.endscreen = info.endscreen;
this.captions = info.captions;
this.cards = info.cards;
if (this.streaming_data) {
const default_audio_track = this.streaming_data.adaptive_formats.find((format) => { var _a; return (_a = format.audio_track) === null || _a === void 0 ? void 0 : _a.audio_is_default; });
if (default_audio_track) {
// The combined formats only exist for the default language, even for videos with multiple audio tracks
// So we can copy the language from the default audio track to the combined formats
this.streaming_data.formats.forEach((format) => format.language = default_audio_track.language);
}
else if (typeof ((_r = this.captions) === null || _r === void 0 ? void 0 : _r.default_audio_track_index) !== 'undefined' && ((_s = this.captions) === null || _s === void 0 ? void 0 : _s.audio_tracks) && this.captions.caption_tracks) {
// For videos with a single audio track and captions, we can use the captions to figure out the language of the audio and combined formats
const audioTrack = this.captions.audio_tracks[this.captions.default_audio_track_index];
const index = audioTrack.default_caption_track_index || 0;
const language_code = this.captions.caption_tracks[index].language_code;
this.streaming_data.adaptive_formats.forEach((format) => {
if (format.has_audio) {
format.language = language_code;
}
});
this.streaming_data.formats.forEach((format) => format.language = language_code);
}
}
const two_col = (_t = next === null || next === void 0 ? void 0 : next.contents) === null || _t === void 0 ? void 0 : _t.item().as(TwoColumnWatchNextResults);
const results = two_col === null || two_col === void 0 ? void 0 : two_col.results;
const secondary_results = two_col === null || two_col === void 0 ? void 0 : two_col.secondary_results;
if (results && secondary_results) {
if (((_u = info.microformat) === null || _u === void 0 ? void 0 : _u.is(PlayerMicroformat)) && ((_v = info.microformat) === null || _v === void 0 ? void 0 : _v.category) === 'Gaming') {
const row = (_y = (_x = (_w = results.firstOfType(VideoSecondaryInfo)) === null || _w === void 0 ? void 0 : _w.metadata) === null || _x === void 0 ? void 0 : _x.rows) === null || _y === void 0 ? void 0 : _y.firstOfType(RichMetadataRow);
if (row === null || row === void 0 ? void 0 : row.is(RichMetadataRow)) {
this.game_info = {
title: (_0 = (_z = row === null || row === void 0 ? void 0 : row.contents) === null || _z === void 0 ? void 0 : _z.firstOfType(RichMetadata)) === null || _0 === void 0 ? void 0 : _0.title,
release_year: (_2 = (_1 = row === null || row === void 0 ? void 0 : row.contents) === null || _1 === void 0 ? void 0 : _1.firstOfType(RichMetadata)) === null || _2 === void 0 ? void 0 : _2.subtitle
};
}
}
this.primary_info = results.firstOfType(VideoPrimaryInfo);
this.secondary_info = results.firstOfType(VideoSecondaryInfo);
this.merchandise = results.firstOfType(MerchandiseShelf);
this.related_chip_cloud = (_3 = secondary_results.firstOfType(RelatedChipCloud)) === null || _3 === void 0 ? void 0 : _3.content.as(ChipCloud);
if (two_col === null || two_col === void 0 ? void 0 : two_col.playlist) {
this.playlist = two_col.playlist;
}
this.watch_next_feed = ((_4 = secondary_results.firstOfType(ItemSection)) === null || _4 === void 0 ? void 0 : _4.contents) || secondary_results;
if (this.watch_next_feed && Array.isArray(this.watch_next_feed) && ((_5 = this.watch_next_feed.at(-1)) === null || _5 === void 0 ? void 0 : _5.is(ContinuationItem)))
__classPrivateFieldSet(this, _VideoInfo_watch_next_continuation, (_6 = this.watch_next_feed.pop()) === null || _6 === void 0 ? void 0 : _6.as(ContinuationItem), "f");
this.player_overlays = (_7 = next === null || next === void 0 ? void 0 : next.player_overlays) === null || _7 === void 0 ? void 0 : _7.item().as(PlayerOverlay);
if (two_col === null || two_col === void 0 ? void 0 : two_col.autoplay) {
this.autoplay = two_col.autoplay;
}
const segmented_like_dislike_button = (_9 = (_8 = this.primary_info) === null || _8 === void 0 ? void 0 : _8.menu) === null || _9 === void 0 ? void 0 : _9.top_level_buttons.firstOfType(SegmentedLikeDislikeButton);
if (((_10 = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.like_button) === null || _10 === void 0 ? void 0 : _10.is(ToggleButton)) && ((_11 = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.dislike_button) === null || _11 === void 0 ? void 0 : _11.is(ToggleButton))) {
this.basic_info.like_count = (_12 = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.like_button) === null || _12 === void 0 ? void 0 : _12.like_count;
this.basic_info.is_liked = (_13 = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.like_button) === null || _13 === void 0 ? void 0 : _13.is_toggled;
this.basic_info.is_disliked = (_14 = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.dislike_button) === null || _14 === void 0 ? void 0 : _14.is_toggled;
}
const comments_entry_point = (_15 = results.get({ target_id: 'comments-entry-point' })) === null || _15 === void 0 ? void 0 : _15.as(ItemSection);
this.comments_entry_point_header = (_16 = comments_entry_point === null || comments_entry_point === void 0 ? void 0 : comments_entry_point.contents) === null || _16 === void 0 ? void 0 : _16.firstOfType(CommentsEntryPointHeader);
this.livechat = (_17 = next === null || next === void 0 ? void 0 : next.contents_memo) === null || _17 === void 0 ? void 0 : _17.getType(LiveChat).first();
}
}
/**
* Applies given filter to the watch next feed. Use {@link filters} to get available filters.
* @param target_filter - Filter to apply.
*/
selectFilter(target_filter) {
var _a, _b, _c, _d;
return __awaiter(this, void 0, void 0, function* () {
if (!this.related_chip_cloud)
throw new InnertubeError('Chip cloud not found, cannot apply filter');
let cloud_chip;
if (typeof target_filter === 'string') {
const filter = (_b = (_a = this.related_chip_cloud) === null || _a === void 0 ? void 0 : _a.chips) === null || _b === void 0 ? void 0 : _b.get({ text: target_filter });
if (!filter)
throw new InnertubeError('Invalid filter', { available_filters: this.filters });
cloud_chip = filter;
}
else if (target_filter === null || target_filter === void 0 ? void 0 : target_filter.is(ChipCloudChip)) {
cloud_chip = target_filter;
}
else {
throw new InnertubeError('Invalid cloud chip', target_filter);
}
if (cloud_chip.is_selected)
return this;
const response = yield ((_c = cloud_chip.endpoint) === null || _c === void 0 ? void 0 : _c.call(this.actions, { parse: true }));
const data = (_d = response === null || response === void 0 ? void 0 : response.on_response_received_endpoints) === null || _d === void 0 ? void 0 : _d.get({ target_id: 'watch-next-feed' });
this.watch_next_feed = data === null || data === void 0 ? void 0 : data.contents;
return this;
});
}
/**
* Adds video to the watch history.
*/
addToWatchHistory() {
const _super = Object.create(null, {
addToWatchHistory: { get: () => super.addToWatchHistory }
});
return __awaiter(this, void 0, void 0, function* () {
return _super.addToWatchHistory.call(this);
});
}
/**
* Retrieves watch next feed continuation.
*/
getWatchNextContinuation() {
var _a, _b, _c, _d, _e;
return __awaiter(this, void 0, void 0, function* () {
if (!__classPrivateFieldGet(this, _VideoInfo_watch_next_continuation, "f"))
throw new InnertubeError('Watch next feed continuation not found');
const response = yield ((_a = __classPrivateFieldGet(this, _VideoInfo_watch_next_continuation, "f")) === null || _a === void 0 ? void 0 : _a.endpoint.call(this.actions, { parse: true }));
const data = (_b = response === null || response === void 0 ? void 0 : response.on_response_received_endpoints) === null || _b === void 0 ? void 0 : _b.get({ type: 'appendContinuationItemsAction' });
if (!data)
throw new InnertubeError('AppendContinuationItemsAction not found');
this.watch_next_feed = data === null || data === void 0 ? void 0 : data.contents;
if ((_d = (_c = this.watch_next_feed) === null || _c === void 0 ? void 0 : _c.at(-1)) === null || _d === void 0 ? void 0 : _d.is(ContinuationItem)) {
__classPrivateFieldSet(this, _VideoInfo_watch_next_continuation, (_e = this.watch_next_feed.pop()) === null || _e === void 0 ? void 0 : _e.as(ContinuationItem), "f");
}
else {
__classPrivateFieldSet(this, _VideoInfo_watch_next_continuation, undefined, "f");
}
return this;
});
}
/**
* Likes the video.
*/
like() {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const segmented_like_dislike_button = (_b = (_a = this.primary_info) === null || _a === void 0 ? void 0 : _a.menu) === null || _b === void 0 ? void 0 : _b.top_level_buttons.firstOfType(SegmentedLikeDislikeButton);
const button = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.like_button;
if (!button)
throw new InnertubeError('Like button not found', { video_id: this.basic_info.id });
if (!button.is(ToggleButton))
throw new InnertubeError('Like button is not a toggle button. This action is likely disabled for this video.', { video_id: this.basic_info.id });
if (button.is_toggled)
throw new InnertubeError('This video is already liked', { video_id: this.basic_info.id });
const response = yield button.endpoint.call(this.actions);
return response;
});
}
/**
* Dislikes the video.
*/
dislike() {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
const segmented_like_dislike_button = (_b = (_a = this.primary_info) === null || _a === void 0 ? void 0 : _a.menu) === null || _b === void 0 ? void 0 : _b.top_level_buttons.firstOfType(SegmentedLikeDislikeButton);
const button = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.dislike_button;
if (!button)
throw new InnertubeError('Dislike button not found', { video_id: this.basic_info.id });
if (!button.is(ToggleButton))
throw new InnertubeError('Dislike button is not a toggle button. This action is likely disabled for this video.', { video_id: this.basic_info.id });
if (button.is_toggled)
throw new InnertubeError('This video is already disliked', { video_id: this.basic_info.id });
const response = yield button.endpoint.call(this.actions);
return response;
});
}
/**
* Removes like/dislike.
*/
removeRating() {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
let button;
const segmented_like_dislike_button = (_b = (_a = this.primary_info) === null || _a === void 0 ? void 0 : _a.menu) === null || _b === void 0 ? void 0 : _b.top_level_buttons.firstOfType(SegmentedLikeDislikeButton);
const like_button = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.like_button;
const dislike_button = segmented_like_dislike_button === null || segmented_like_dislike_button === void 0 ? void 0 : segmented_like_dislike_button.dislike_button;
if (!(like_button === null || like_button === void 0 ? void 0 : like_button.is(ToggleButton)) || !(dislike_button === null || dislike_button === void 0 ? void 0 : dislike_button.is(ToggleButton)))
throw new InnertubeError('Like/Dislike button is not a toggle button. This action is likely disabled for this video.', { video_id: this.basic_info.id });
if (like_button === null || like_button === void 0 ? void 0 : like_button.is_toggled) {
button = like_button;
}
else if (dislike_button === null || dislike_button === void 0 ? void 0 : dislike_button.is_toggled) {
button = dislike_button;
}
if (!button)
throw new InnertubeError('This video is not liked/disliked', { video_id: this.basic_info.id });
const response = yield button.toggled_endpoint.call(this.actions);
return response;
});
}
/**
* Retrieves Live Chat if available.
*/
getLiveChat() {
if (!this.livechat)
throw new InnertubeError('Live Chat is not available', { video_id: this.basic_info.id });
return new LiveChatWrap(this);
}
/**
* Retrieves trailer info if available (typically for non-purchased movies or films).
* @returns `VideoInfo` for the trailer, or `null` if none.
*/
getTrailerInfo() {
var _a, _b;
if (this.has_trailer) {
const player_response = (_b = (_a = this.playability_status.error_screen) === null || _a === void 0 ? void 0 : _a.as(PlayerLegacyDesktopYpcTrailer).trailer) === null || _b === void 0 ? void 0 : _b.player_response;
if (player_response) {
return new VideoInfo([{ data: player_response }], this.actions, this.cpn);
}
}
return null;
}
/**
* Watch next feed filters.
*/
get filters() {
var _a, _b;
return ((_b = (_a = this.related_chip_cloud) === null || _a === void 0 ? void 0 : _a.chips) === null || _b === void 0 ? void 0 : _b.map((chip) => { var _a; return (_a = chip.text) === null || _a === void 0 ? void 0 : _a.toString(); })) || [];
}
/**
* Checks if continuation is available for the watch next feed.
*/
get wn_has_continuation() {
return !!__classPrivateFieldGet(this, _VideoInfo_watch_next_continuation, "f");
}
/**
* Gets the endpoint of the autoplay video
*/
get autoplay_video_endpoint() {
var _a, _b, _c;
return ((_c = (_b = (_a = this.autoplay) === null || _a === void 0 ? void 0 : _a.sets) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.autoplay_video) || null;
}
/**
* Checks if trailer is available.
*/
get has_trailer() {
var _a;
return !!((_a = this.playability_status.error_screen) === null || _a === void 0 ? void 0 : _a.is(PlayerLegacyDesktopYpcTrailer));
}
/**
* Get songs used in the video.
*/
get music_tracks() {
var _a, _b, _c, _d, _e, _f;
// @TODO: Refactor this.
const description_content = (_b = (_a = this.page[1]) === null || _a === void 0 ? void 0 : _a.engagement_panels) === null || _b === void 0 ? void 0 : _b.filter((panel) => { var _a; return (_a = panel.content) === null || _a === void 0 ? void 0 : _a.is(StructuredDescriptionContent); });
if (description_content !== undefined && description_content.length > 0) {
const music_section = (_e = (_d = (_c = description_content[0].content) === null || _c === void 0 ? void 0 : _c.as(StructuredDescriptionContent)) === null || _d === void 0 ? void 0 : _d.items) === null || _e === void 0 ? void 0 : _e.filterType(VideoDescriptionMusicSection);
if (music_section !== undefined && music_section.length > 0) {
return (_f = music_section[0].carousel_lockups) === null || _f === void 0 ? void 0 : _f.map((lookup) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
let song;
let artist;
let album;
let license;
let videoId;
let channelId;
// If the song isn't in the video_lockup, it should be in the info_rows
song = (_b = (_a = lookup.video_lockup) === null || _a === void 0 ? void 0 : _a.title) === null || _b === void 0 ? void 0 : _b.toString();
// If the video id isn't in the video_lockup, it should be in the info_rows
videoId = (_c = lookup.video_lockup) === null || _c === void 0 ? void 0 : _c.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 = ((_d = info_row.default_metadata) === null || _d === void 0 ? void 0 : _d.toString()) || ((_e = info_row.expanded_metadata) === null || _e === void 0 ? void 0 : _e.toString());
if (videoId === undefined) {
const endpoint = ((_f = info_row.default_metadata) === null || _f === void 0 ? void 0 : _f.endpoint) || ((_g = info_row.expanded_metadata) === null || _g === void 0 ? void 0 : _g.endpoint);
videoId = (_h = endpoint === null || endpoint === void 0 ? void 0 : endpoint.payload) === null || _h === void 0 ? void 0 : _h.videoId;
}
}
else {
album = ((_j = info_row.default_metadata) === null || _j === void 0 ? void 0 : _j.toString()) || ((_k = info_row.expanded_metadata) === null || _k === void 0 ? void 0 : _k.toString());
}
}
else {
if (((_l = info_row.info_row_expand_status_key) === null || _l === void 0 ? void 0 : _l.indexOf('structured-description-music-section-artists-row-state-id')) !== -1) {
artist = ((_m = info_row.default_metadata) === null || _m === void 0 ? void 0 : _m.toString()) || ((_o = info_row.expanded_metadata) === null || _o === void 0 ? void 0 : _o.toString());
if (channelId === undefined) {
const endpoint = ((_p = info_row.default_metadata) === null || _p === void 0 ? void 0 : _p.endpoint) || ((_q = info_row.expanded_metadata) === null || _q === void 0 ? void 0 : _q.endpoint);
channelId = (_r = endpoint === null || endpoint === void 0 ? void 0 : endpoint.payload) === null || _r === void 0 ? void 0 : _r.browseId;
}
}
if (((_s = info_row.info_row_expand_status_key) === null || _s === void 0 ? void 0 : _s.indexOf('structured-description-music-section-licenses-row-state-id')) !== -1) {
license = ((_t = info_row.default_metadata) === null || _t === void 0 ? void 0 : _t.toString()) || ((_u = info_row.expanded_metadata) === null || _u === void 0 ? void 0 : _u.toString());
}
}
}
return { song, artist, album, license, videoId, channelId };
});
}
}
return [];
}
}
_VideoInfo_watch_next_continuation = new WeakMap();
export default VideoInfo;
//# sourceMappingURL=VideoInfo.js.map