mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-07-04 12:47:04 +00:00
chore: v4.0.0 release
This commit is contained in:
@@ -9,7 +9,7 @@ class Analytics {
|
||||
|
||||
constructor(response: ApiResponse) {
|
||||
this.#page = Parser.parseResponse<IBrowseResponse>(response.data);
|
||||
this.sections = this.#page.contents_memo?.getType(Element).map((el) => el.model?.item()).flatMap((el) => !el ? [] : el);
|
||||
this.sections = this.#page.contents_memo?.getType(Element).map((el) => el.model).flatMap((el) => !el ? [] : el);
|
||||
}
|
||||
|
||||
get page(): IBrowseResponse {
|
||||
|
||||
@@ -36,6 +36,7 @@ import ItemMenu from './ItemMenu.ts';
|
||||
|
||||
import type Actions from '../../core/Actions.ts';
|
||||
import type { IParsedResponse, IUpdatedMetadataResponse } from '../types/ParsedResponse.ts';
|
||||
import { NavigationEndpoint } from '../nodes.ts';
|
||||
|
||||
export type ChatAction =
|
||||
AddChatItemAction | AddBannerToLiveChatCommand | AddLiveChatTickerItemAction |
|
||||
@@ -288,10 +289,10 @@ class LiveChat extends EventEmitter {
|
||||
* Retrieves given chat item's menu.
|
||||
*/
|
||||
async getItemMenu(item: ChatItemWithMenu): Promise<ItemMenu> {
|
||||
if (!item.menu_endpoint)
|
||||
if (!item.hasKey('menu_endpoint') || !item.key('menu_endpoint').isInstanceof(NavigationEndpoint))
|
||||
throw new InnertubeError('This item does not have a menu.', item);
|
||||
|
||||
const response = await item.menu_endpoint.call(this.#actions, { parse: true });
|
||||
const response = await item.key('menu_endpoint').instanceof(NavigationEndpoint).call(this.#actions, { parse: true });
|
||||
|
||||
if (!response)
|
||||
throw new InnertubeError('Could not retrieve item menu.', item);
|
||||
|
||||
@@ -36,8 +36,8 @@ class Playlist extends Feed<IBrowseResponse> {
|
||||
this.info = {
|
||||
...this.page.metadata?.item().as(PlaylistMetadata),
|
||||
...{
|
||||
author: secondary_info?.owner.item().as(VideoOwner).author ?? header?.author,
|
||||
thumbnails: primary_info?.thumbnail_renderer.item().as(PlaylistVideoThumbnail, PlaylistCustomThumbnail).thumbnail as Thumbnail[],
|
||||
author: secondary_info?.owner?.as(VideoOwner).author ?? header?.author,
|
||||
thumbnails: primary_info?.thumbnail_renderer?.as(PlaylistVideoThumbnail, PlaylistCustomThumbnail).thumbnail as Thumbnail[],
|
||||
total_items: this.#getStat(0, primary_info),
|
||||
views: this.#getStat(1, primary_info),
|
||||
last_updated: this.#getStat(2, primary_info),
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import Constants from '../../utils/Constants.ts';
|
||||
import Parser from '../index.ts';
|
||||
|
||||
import ChipCloud from '../classes/ChipCloud.ts';
|
||||
import ChipCloudChip from '../classes/ChipCloudChip.ts';
|
||||
import CommentsEntryPointHeader from '../classes/comments/CommentsEntryPointHeader.ts';
|
||||
@@ -21,10 +18,10 @@ import VideoPrimaryInfo from '../classes/VideoPrimaryInfo.ts';
|
||||
import VideoSecondaryInfo from '../classes/VideoSecondaryInfo.ts';
|
||||
import LiveChatWrap from './LiveChat.ts';
|
||||
import NavigationEndpoint from '../classes/NavigationEndpoint.ts';
|
||||
import PlayerLegacyDesktopYpcTrailer from '../classes/PlayerLegacyDesktopYpcTrailer.ts';
|
||||
|
||||
import type CardCollection from '../classes/CardCollection.ts';
|
||||
import type Endscreen from '../classes/Endscreen.ts';
|
||||
import type Format from '../classes/misc/Format.ts';
|
||||
import type PlayerAnnotationsExpanded from '../classes/PlayerAnnotationsExpanded.ts';
|
||||
import type PlayerCaptionsTracklist from '../classes/PlayerCaptionsTracklist.ts';
|
||||
import type PlayerLiveStoryboardSpec from '../classes/PlayerLiveStoryboardSpec.ts';
|
||||
@@ -32,33 +29,21 @@ import type PlayerStoryboardSpec from '../classes/PlayerStoryboardSpec.ts';
|
||||
|
||||
import type Actions from '../../core/Actions.ts';
|
||||
import type { ApiResponse } from '../../core/Actions.ts';
|
||||
import type Player from '../../core/Player.ts';
|
||||
import type { ObservedArray, YTNode } from '../helpers.ts';
|
||||
import type { INextResponse, IPlayerResponse } from '../types/ParsedResponse.ts';
|
||||
|
||||
import FormatUtils, { DownloadOptions, FormatFilter, FormatOptions, URLTransformer } from '../../utils/FormatUtils.ts';
|
||||
import { ObservedArray, YTNode } from '../helpers.ts';
|
||||
|
||||
import { InnertubeError } from '../../utils/Utils.ts';
|
||||
import { MediaInfo } from '../../core/MediaInfo.ts';
|
||||
|
||||
class VideoInfo {
|
||||
#page: [IPlayerResponse, INextResponse?];
|
||||
|
||||
#actions: Actions;
|
||||
#player?: Player;
|
||||
#cpn?: string;
|
||||
class VideoInfo extends MediaInfo {
|
||||
#watch_next_continuation?: ContinuationItem;
|
||||
|
||||
basic_info;
|
||||
streaming_data;
|
||||
playability_status;
|
||||
annotations?: ObservedArray<PlayerAnnotationsExpanded>;
|
||||
storyboards?: PlayerStoryboardSpec | PlayerLiveStoryboardSpec;
|
||||
endscreen?: Endscreen;
|
||||
captions?: PlayerCaptionsTracklist;
|
||||
cards?: CardCollection;
|
||||
|
||||
#playback_tracking;
|
||||
|
||||
primary_info?: VideoPrimaryInfo | null;
|
||||
secondary_info?: VideoSecondaryInfo | null;
|
||||
playlist?;
|
||||
@@ -77,18 +62,10 @@ class VideoInfo {
|
||||
* @param player - Player instance.
|
||||
* @param cpn - Client Playback Nonce.
|
||||
*/
|
||||
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, player?: Player, cpn?: string) {
|
||||
this.#actions = actions;
|
||||
this.#player = player;
|
||||
this.#cpn = cpn;
|
||||
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, cpn: string) {
|
||||
super(data, actions, cpn);
|
||||
|
||||
const info = Parser.parseResponse<IPlayerResponse>(data[0].data);
|
||||
const next = data?.[1]?.data ? Parser.parseResponse<INextResponse>(data[1].data) : undefined;
|
||||
|
||||
this.#page = [ info, next ];
|
||||
|
||||
if (info.playability_status?.status === 'ERROR')
|
||||
throw new InnertubeError('This video is unavailable', info.playability_status);
|
||||
const [ info, next ] = this.page;
|
||||
|
||||
if (info.microformat && !info.microformat?.is(PlayerMicroformat, MicroformatData))
|
||||
throw new InnertubeError('Invalid microformat', info.microformat);
|
||||
@@ -113,16 +90,12 @@ class VideoInfo {
|
||||
is_disliked: undefined as boolean | undefined
|
||||
};
|
||||
|
||||
this.streaming_data = info.streaming_data;
|
||||
this.playability_status = info.playability_status;
|
||||
this.annotations = info.annotations;
|
||||
this.storyboards = info.storyboards;
|
||||
this.endscreen = info.endscreen;
|
||||
this.captions = info.captions;
|
||||
this.cards = info.cards;
|
||||
|
||||
this.#playback_tracking = info.playback_tracking;
|
||||
|
||||
const two_col = next?.contents?.item().as(TwoColumnWatchNextResults);
|
||||
|
||||
const results = two_col?.results;
|
||||
@@ -199,7 +172,7 @@ class VideoInfo {
|
||||
|
||||
if (cloud_chip.is_selected) return this;
|
||||
|
||||
const response = await cloud_chip.endpoint?.call(this.#actions, { parse: true });
|
||||
const response = await cloud_chip.endpoint?.call(this.actions, { parse: true });
|
||||
const data = response?.on_response_received_endpoints?.get({ target_id: 'watch-next-feed' });
|
||||
|
||||
this.watch_next_feed = data?.contents;
|
||||
@@ -211,24 +184,7 @@ class VideoInfo {
|
||||
* Adds video to the watch history.
|
||||
*/
|
||||
async addToWatchHistory(): Promise<Response> {
|
||||
if (!this.#playback_tracking)
|
||||
throw new InnertubeError('Playback tracking not available');
|
||||
|
||||
const url_params = {
|
||||
cpn: this.#cpn,
|
||||
fmt: 251,
|
||||
rtn: 0,
|
||||
rt: 0
|
||||
};
|
||||
|
||||
const url = this.#playback_tracking.videostats_playback_url.replace('https://s.', 'https://www.');
|
||||
|
||||
const response = await this.#actions.stats(url, {
|
||||
client_name: Constants.CLIENTS.WEB.NAME,
|
||||
client_version: Constants.CLIENTS.WEB.VERSION
|
||||
}, url_params);
|
||||
|
||||
return response;
|
||||
return super.addToWatchHistory();
|
||||
}
|
||||
|
||||
|
||||
@@ -239,7 +195,7 @@ class VideoInfo {
|
||||
if (!this.#watch_next_continuation)
|
||||
throw new InnertubeError('Watch next feed continuation not found');
|
||||
|
||||
const response = await this.#watch_next_continuation?.endpoint.call(this.#actions, { parse: true });
|
||||
const response = await this.#watch_next_continuation?.endpoint.call(this.actions, { parse: true });
|
||||
const data = response?.on_response_received_endpoints?.get({ type: 'appendContinuationItemsAction' });
|
||||
|
||||
if (!data)
|
||||
@@ -271,7 +227,7 @@ class VideoInfo {
|
||||
if (button.is_toggled)
|
||||
throw new InnertubeError('This video is already liked', { video_id: this.basic_info.id });
|
||||
|
||||
const response = await button.endpoint.call(this.#actions);
|
||||
const response = await button.endpoint.call(this.actions);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -292,7 +248,7 @@ class VideoInfo {
|
||||
if (button.is_toggled)
|
||||
throw new InnertubeError('This video is already disliked', { video_id: this.basic_info.id });
|
||||
|
||||
const response = await button.endpoint.call(this.#actions);
|
||||
const response = await button.endpoint.call(this.actions);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -320,7 +276,7 @@ class VideoInfo {
|
||||
if (!button)
|
||||
throw new InnertubeError('This video is not liked/disliked', { video_id: this.basic_info.id });
|
||||
|
||||
const response = await button.toggled_endpoint.call(this.#actions);
|
||||
const response = await button.toggled_endpoint.call(this.actions);
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -335,29 +291,17 @@ class VideoInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the format that best matches the given options.
|
||||
* @param options - Options
|
||||
* Retrieves trailer info if available (typically for non-purchased movies or films).
|
||||
* @returns `VideoInfo` for the trailer, or `null` if none.
|
||||
*/
|
||||
chooseFormat(options: FormatOptions): Format {
|
||||
return FormatUtils.chooseFormat(options, this.streaming_data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a DASH manifest from the streaming data.
|
||||
* @param url_transformer - Function to transform the URLs.
|
||||
* @param format_filter - Function to filter the formats.
|
||||
* @returns DASH manifest
|
||||
*/
|
||||
toDash(url_transformer?: URLTransformer, format_filter?: FormatFilter): string {
|
||||
return FormatUtils.toDash(this.streaming_data, url_transformer, format_filter, this.#cpn, this.#player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the video.
|
||||
* @param options - Download options.
|
||||
*/
|
||||
async download(options: DownloadOptions = {}): Promise<ReadableStream<Uint8Array>> {
|
||||
return FormatUtils.download(options, this.#actions, this.playability_status, this.streaming_data, this.#actions.session.player, this.cpn);
|
||||
getTrailerInfo(): VideoInfo | null {
|
||||
if (this.has_trailer) {
|
||||
const player_response = this.playability_status.error_screen?.as(PlayerLegacyDesktopYpcTrailer).trailer?.player_response;
|
||||
if (player_response) {
|
||||
return new VideoInfo([ { data: player_response } as ApiResponse ], this.actions, this.cpn);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -367,20 +311,6 @@ class VideoInfo {
|
||||
return this.related_chip_cloud?.chips?.map((chip) => chip.text?.toString()) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions instance.
|
||||
*/
|
||||
get actions(): Actions {
|
||||
return this.#actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Content Playback Nonce.
|
||||
*/
|
||||
get cpn(): string | undefined {
|
||||
return this.#cpn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if continuation is available for the watch next feed.
|
||||
*/
|
||||
@@ -395,6 +325,13 @@ class VideoInfo {
|
||||
return this.autoplay?.sets?.[0]?.autoplay_video || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if trailer is available.
|
||||
*/
|
||||
get has_trailer(): boolean {
|
||||
return !!this.playability_status.error_screen?.is(PlayerLegacyDesktopYpcTrailer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get songs used in the video.
|
||||
*/
|
||||
@@ -433,13 +370,6 @@ class VideoInfo {
|
||||
*/
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Original parsed InnerTube response.
|
||||
*/
|
||||
get page(): [IPlayerResponse, INextResponse?] {
|
||||
return this.#page;
|
||||
}
|
||||
}
|
||||
|
||||
export default VideoInfo;
|
||||
Reference in New Issue
Block a user