UI/UX: Adiciona campo opcional para colagem manual de transcrição no VideoMind

This commit is contained in:
2026-06-05 22:19:33 +00:00
parent 281005a8bc
commit 3781c848da
2395 changed files with 242184 additions and 20 deletions
+9
View File
@@ -0,0 +1,9 @@
import type { ApiResponse } from '../../core/index.js';
import type { IParsedResponse } from '../types/index.js';
import type AccountItemSection from '../classes/AccountItemSection.js';
export default class AccountInfo {
#private;
contents: AccountItemSection | null;
constructor(response: ApiResponse);
get page(): IParsedResponse;
}
+20
View File
@@ -0,0 +1,20 @@
import { Parser } from '../index.js';
import { InnertubeError } from '../../utils/Utils.js';
import AccountSectionList from '../classes/AccountSectionList.js';
export default class AccountInfo {
#page;
contents;
constructor(response) {
this.#page = Parser.parseResponse(response.data);
if (!this.#page.contents)
throw new InnertubeError('Page contents not found');
const account_section_list = this.#page.contents.array().as(AccountSectionList)[0];
if (!account_section_list)
throw new InnertubeError('Account section list not found');
this.contents = account_section_list.contents[0];
}
get page() {
return this.#page;
}
}
//# sourceMappingURL=AccountInfo.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"AccountInfo.js","sourceRoot":"","sources":["../../../../src/parser/youtube/AccountInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,kBAAkB,MAAM,kCAAkC,CAAC;AAMlE,MAAM,CAAC,OAAO,OAAO,WAAW;IACrB,KAAK,CAAkB;IAEhC,QAAQ,CAA4B;IAEpC,YAAY,QAAqB;QAC/B,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEjD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ;YACtB,MAAM,IAAI,cAAc,CAAC,yBAAyB,CAAC,CAAC;QAEtD,MAAM,oBAAoB,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC;QAEnF,IAAI,CAAC,oBAAoB;YACvB,MAAM,IAAI,cAAc,CAAC,gCAAgC,CAAC,CAAC;QAE7D,IAAI,CAAC,QAAQ,GAAG,oBAAoB,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF"}
+123
View File
@@ -0,0 +1,123 @@
import Feed from '../../core/mixins/Feed.js';
import FilterableFeed from '../../core/mixins/FilterableFeed.js';
import TabbedFeed from '../../core/mixins/TabbedFeed.js';
import C4TabbedHeader from '../classes/C4TabbedHeader.js';
import CarouselHeader from '../classes/CarouselHeader.js';
import ChannelAboutFullMetadata from '../classes/ChannelAboutFullMetadata.js';
import AboutChannel from '../classes/AboutChannel.js';
import InteractiveTabbedHeader from '../classes/InteractiveTabbedHeader.js';
import SubscribeButton from '../classes/SubscribeButton.js';
import ExpandableTab from '../classes/ExpandableTab.js';
import type Tab from '../classes/Tab.js';
import PageHeader from '../classes/PageHeader.js';
import ChipCloudChip from '../classes/ChipCloudChip.js';
import type { AppendContinuationItemsAction, NavigateAction, ReloadContinuationItemsCommand, ShowMiniplayerCommand } from '../index.js';
import type { ApiResponse, Actions } from '../../core/index.js';
import type { IBrowseResponse } from '../types/index.js';
import type OpenPopupAction from '../classes/actions/OpenPopupAction.js';
export default class Channel extends TabbedFeed<IBrowseResponse> {
header?: C4TabbedHeader | CarouselHeader | InteractiveTabbedHeader | PageHeader;
metadata: {
url_canonical?: string | undefined;
title?: string | undefined;
description?: string | undefined;
thumbnail?: import("../misc.js").Thumbnail[] | undefined;
site_name?: string | undefined;
app_name?: string | undefined;
android_package?: string | undefined;
ios_app_store_id?: string | undefined;
ios_app_arguments?: string | undefined;
og_type?: string | undefined;
url_applinks_web?: string | undefined;
url_applinks_ios?: string | undefined;
url_applinks_android?: string | undefined;
url_twitter_ios?: string | undefined;
url_twitter_android?: string | undefined;
twitter_card_type?: string | undefined;
twitter_site_handle?: string | undefined;
schema_dot_org_type?: string | undefined;
noindex?: string | undefined;
is_unlisted?: boolean | undefined;
is_family_safe?: boolean | undefined;
tags?: string[] | undefined;
available_countries?: string[] | undefined;
type?: string | undefined;
url?: string | undefined;
rss_url?: string | undefined;
vanity_channel_url?: string | undefined;
external_id?: string | undefined;
keywords?: string[] | undefined;
avatar?: import("../misc.js").Thumbnail[] | undefined;
music_artist_name?: string;
android_deep_link?: string | undefined;
android_appindexing_link?: string | undefined;
ios_appindexing_link?: string | undefined;
};
subscribe_button?: SubscribeButton;
current_tab?: Tab | ExpandableTab;
constructor(actions: Actions, data: ApiResponse | IBrowseResponse, already_parsed?: boolean);
/**
* Applies given filter to the list. Use {@link filters} to get available filters.
* @param filter - The filter to apply
*/
applyFilter(filter: string | ChipCloudChip): Promise<FilteredChannelList>;
/**
* Applies given sort filter to the list. Use {@link sort_filters} to get available filters.
* @param sort - The sort filter to apply
*/
applySort(sort: string): Promise<Channel>;
/**
* Applies given content type filter to the list. Use {@link content_type_filters} to get available filters.
* @param content_type_filter - The content type filter to apply
*/
applyContentTypeFilter(content_type_filter: string): Promise<Channel>;
get filters(): string[];
get sort_filters(): string[];
get content_type_filters(): string[];
getHome(): Promise<Channel>;
getVideos(): Promise<Channel>;
getShorts(): Promise<Channel>;
getLiveStreams(): Promise<Channel>;
getReleases(): Promise<Channel>;
getPodcasts(): Promise<Channel>;
getCourses(): Promise<Channel>;
getPlaylists(): Promise<Channel>;
getCommunity(): Promise<Channel>;
/**
* Retrieves the about page.
* Note that this does not return a new {@link Channel} object.
*/
getAbout(): Promise<ChannelAboutFullMetadata | AboutChannel>;
/**
* Searches within the channel.
*/
search(query: string): Promise<Channel>;
get has_home(): boolean;
get has_videos(): boolean;
get has_shorts(): boolean;
get has_live_streams(): boolean;
get has_releases(): boolean;
get has_podcasts(): boolean;
get has_courses(): boolean;
get has_playlists(): boolean;
get has_community(): boolean;
get has_about(): boolean;
get has_search(): boolean;
getContinuation(): Promise<ChannelListContinuation>;
}
export declare class ChannelListContinuation extends Feed<IBrowseResponse> {
contents?: AppendContinuationItemsAction | OpenPopupAction | NavigateAction | ShowMiniplayerCommand | ReloadContinuationItemsCommand;
constructor(actions: Actions, data: ApiResponse | IBrowseResponse, already_parsed?: boolean);
getContinuation(): Promise<ChannelListContinuation>;
}
export declare class FilteredChannelList extends FilterableFeed<IBrowseResponse> {
applied_filter?: ChipCloudChip;
contents?: AppendContinuationItemsAction | OpenPopupAction | NavigateAction | ShowMiniplayerCommand | ReloadContinuationItemsCommand;
constructor(actions: Actions, data: ApiResponse | IBrowseResponse, already_parsed?: boolean);
/**
* Applies given filter to the list.
* @param filter - The filter to apply
*/
applyFilter(filter: string | ChipCloudChip): Promise<FilteredChannelList>;
getContinuation(): Promise<FilteredChannelList>;
}
+274
View File
@@ -0,0 +1,274 @@
import Feed from '../../core/mixins/Feed.js';
import FilterableFeed from '../../core/mixins/FilterableFeed.js';
import { ChannelError, InnertubeError } from '../../utils/Utils.js';
import TabbedFeed from '../../core/mixins/TabbedFeed.js';
import C4TabbedHeader from '../classes/C4TabbedHeader.js';
import CarouselHeader from '../classes/CarouselHeader.js';
import ChannelAboutFullMetadata from '../classes/ChannelAboutFullMetadata.js';
import AboutChannel from '../classes/AboutChannel.js';
import ChannelMetadata from '../classes/ChannelMetadata.js';
import InteractiveTabbedHeader from '../classes/InteractiveTabbedHeader.js';
import MicroformatData from '../classes/MicroformatData.js';
import SubscribeButton from '../classes/SubscribeButton.js';
import ExpandableTab from '../classes/ExpandableTab.js';
import SectionList from '../classes/SectionList.js';
import PageHeader from '../classes/PageHeader.js';
import TwoColumnBrowseResults from '../classes/TwoColumnBrowseResults.js';
import ChipCloudChip from '../classes/ChipCloudChip.js';
import FeedFilterChipBar from '../classes/FeedFilterChipBar.js';
import ChannelSubMenu from '../classes/ChannelSubMenu.js';
import SortFilterSubMenu from '../classes/SortFilterSubMenu.js';
import ContinuationItem from '../classes/ContinuationItem.js';
import NavigationEndpoint from '../classes/NavigationEndpoint.js';
export default class Channel extends TabbedFeed {
header;
metadata;
subscribe_button;
current_tab;
constructor(actions, data, already_parsed = false) {
super(actions, data, already_parsed);
this.header = this.page.header?.item()?.as(C4TabbedHeader, CarouselHeader, InteractiveTabbedHeader, PageHeader);
const metadata = this.page.metadata?.item().as(ChannelMetadata);
const microformat = this.page.microformat?.as(MicroformatData);
if (this.page.alerts) {
const alert = this.page.alerts[0];
if (alert?.alert_type === 'ERROR') {
throw new ChannelError(alert.text.toString());
}
}
if (!metadata && !this.page.contents)
throw new InnertubeError('Invalid channel', this);
this.metadata = { ...metadata, ...(microformat || {}) };
this.subscribe_button = this.page.header_memo?.getType(SubscribeButton)[0];
if (this.page.contents)
this.current_tab = this.page.contents.item().as(TwoColumnBrowseResults).tabs.find((tab) => tab.selected);
}
/**
* Applies given filter to the list. Use {@link filters} to get available filters.
* @param filter - The filter to apply
*/
async applyFilter(filter) {
let target_filter;
const filter_chipbar = this.memo.getType(FeedFilterChipBar)[0];
if (typeof filter === 'string') {
target_filter = filter_chipbar?.contents.find((chip) => chip.text === filter);
if (!target_filter)
throw new InnertubeError(`Filter ${filter} not found`, { available_filters: this.filters });
}
else {
target_filter = filter;
}
if (!target_filter.endpoint)
throw new InnertubeError('Invalid filter', filter);
const page = await target_filter.endpoint.call(this.actions, { parse: true });
if (!page)
throw new InnertubeError('No page returned', { filter: target_filter });
return new FilteredChannelList(this.actions, page, true);
}
/**
* Applies given sort filter to the list. Use {@link sort_filters} to get available filters.
* @param sort - The sort filter to apply
*/
async applySort(sort) {
const sort_filter_sub_menu = this.memo.getType(SortFilterSubMenu)[0];
if (!sort_filter_sub_menu || !sort_filter_sub_menu.sub_menu_items)
throw new InnertubeError('No sort filter sub menu found');
const target_sort = sort_filter_sub_menu.sub_menu_items.find((item) => item.title === sort);
if (!target_sort)
throw new InnertubeError(`Sort filter ${sort} not found`, { available_sort_filters: this.sort_filters });
if (target_sort.selected)
return this;
const page = await target_sort.endpoint.call(this.actions, { parse: true });
return new Channel(this.actions, page, true);
}
/**
* Applies given content type filter to the list. Use {@link content_type_filters} to get available filters.
* @param content_type_filter - The content type filter to apply
*/
async applyContentTypeFilter(content_type_filter) {
const sub_menu = this.current_tab?.content?.as(SectionList).sub_menu?.as(ChannelSubMenu);
if (!sub_menu)
throw new InnertubeError('Sub menu not found');
const item = sub_menu.content_type_sub_menu_items.find((item) => item.title === content_type_filter);
if (!item)
throw new InnertubeError(`Sub menu item ${content_type_filter} not found`, { available_filters: this.content_type_filters });
if (item.selected)
return this;
const page = await item.endpoint.call(this.actions, { parse: true });
return new Channel(this.actions, page, true);
}
get filters() {
return this.memo.getType(FeedFilterChipBar)?.[0]?.contents.filterType(ChipCloudChip).map((chip) => chip.text) || [];
}
get sort_filters() {
const sort_filter_sub_menu = this.memo.getType(SortFilterSubMenu)[0];
return sort_filter_sub_menu?.sub_menu_items?.map((item) => item.title) || [];
}
get content_type_filters() {
const sub_menu = this.current_tab?.content?.as(SectionList).sub_menu?.as(ChannelSubMenu);
return sub_menu?.content_type_sub_menu_items.map((item) => item.title) || [];
}
async getHome() {
const tab = await this.getTabByURL('featured');
return new Channel(this.actions, tab.page, true);
}
async getVideos() {
const tab = await this.getTabByURL('videos');
return new Channel(this.actions, tab.page, true);
}
async getShorts() {
const tab = await this.getTabByURL('shorts');
return new Channel(this.actions, tab.page, true);
}
async getLiveStreams() {
const tab = await this.getTabByURL('streams');
return new Channel(this.actions, tab.page, true);
}
async getReleases() {
const tab = await this.getTabByURL('releases');
return new Channel(this.actions, tab.page, true);
}
async getPodcasts() {
const tab = await this.getTabByURL('podcasts');
return new Channel(this.actions, tab.page, true);
}
async getCourses() {
const tab = await this.getTabByURL('courses');
return new Channel(this.actions, tab.page, true);
}
async getPlaylists() {
const tab = await this.getTabByURL('playlists');
return new Channel(this.actions, tab.page, true);
}
async getCommunity() {
const tab = await this.getTabByURL('posts');
return new Channel(this.actions, tab.page, true);
}
/**
* Retrieves the about page.
* Note that this does not return a new {@link Channel} object.
*/
async getAbout() {
if (this.hasTabWithURL('about')) {
const tab = await this.getTabByURL('about');
return tab.memo.getType(ChannelAboutFullMetadata)[0];
}
const tagline = this.header?.is(C4TabbedHeader) && this.header.tagline;
if (tagline || this.header?.is(PageHeader) && this.header.content?.description) {
if (tagline && tagline.more_endpoint instanceof NavigationEndpoint) {
const response = await tagline.more_endpoint.call(this.actions);
const tab = new TabbedFeed(this.actions, response, false);
return tab.memo.getType(ChannelAboutFullMetadata)[0];
}
const endpoint = this.page.header_memo?.getType(ContinuationItem)[0]?.endpoint;
if (!endpoint) {
throw new InnertubeError('Failed to extract continuation to get channel about');
}
const response = await endpoint.call(this.actions, { parse: true });
if (!response.on_response_received_endpoints_memo) {
throw new InnertubeError('Unexpected response while fetching channel about', { response });
}
return response.on_response_received_endpoints_memo.getType(AboutChannel)[0];
}
throw new InnertubeError('About not found');
}
/**
* Searches within the channel.
*/
async search(query) {
const tab = this.memo.getType(ExpandableTab)?.[0];
if (!tab)
throw new InnertubeError('Search tab not found', this);
const page = await tab.endpoint.call(this.actions, { query, parse: true });
return new Channel(this.actions, page, true);
}
get has_home() {
return this.hasTabWithURL('featured');
}
get has_videos() {
return this.hasTabWithURL('videos');
}
get has_shorts() {
return this.hasTabWithURL('shorts');
}
get has_live_streams() {
return this.hasTabWithURL('streams');
}
get has_releases() {
return this.hasTabWithURL('releases');
}
get has_podcasts() {
return this.hasTabWithURL('podcasts');
}
get has_courses() {
return this.hasTabWithURL('courses');
}
get has_playlists() {
return this.hasTabWithURL('playlists');
}
get has_community() {
return this.hasTabWithURL('posts');
}
get has_about() {
// Game topic channels still have an about tab, user channels have switched to the popup
return this.hasTabWithURL('about') ||
!!(this.header?.is(C4TabbedHeader) && this.header.tagline?.more_endpoint) ||
!!(this.header?.is(PageHeader) && this.header.content?.description?.more_endpoint);
}
get has_search() {
return this.memo.getType(ExpandableTab)?.length > 0;
}
async getContinuation() {
const page = await super.getContinuationData();
if (!page)
throw new InnertubeError('Could not get continuation data');
return new ChannelListContinuation(this.actions, page, true);
}
}
export class ChannelListContinuation extends Feed {
contents;
constructor(actions, data, already_parsed = false) {
super(actions, data, already_parsed);
this.contents =
this.page.on_response_received_actions?.[0] ||
this.page.on_response_received_endpoints?.[0];
}
async getContinuation() {
const page = await super.getContinuationData();
if (!page)
throw new InnertubeError('Could not get continuation data');
return new ChannelListContinuation(this.actions, page, true);
}
}
export class FilteredChannelList extends FilterableFeed {
applied_filter;
contents;
constructor(actions, data, already_parsed = false) {
super(actions, data, already_parsed);
this.applied_filter = this.memo.getType(ChipCloudChip).find((chip) => chip.is_selected);
// Removes the filter chipbar from the actions list
if (this.page.on_response_received_actions &&
this.page.on_response_received_actions.length > 1) {
this.page.on_response_received_actions.shift();
}
this.contents = this.page.on_response_received_actions?.[0];
}
/**
* Applies given filter to the list.
* @param filter - The filter to apply
*/
async applyFilter(filter) {
const feed = await super.getFilteredFeed(filter);
return new FilteredChannelList(this.actions, feed.page, true);
}
async getContinuation() {
const page = await super.getContinuationData();
if (!page?.on_response_received_actions_memo)
throw new InnertubeError('Unexpected continuation data', page);
// Keep the filters
page.on_response_received_actions_memo.set('FeedFilterChipBar', this.memo.getType(FeedFilterChipBar));
page.on_response_received_actions_memo.set('ChipCloudChip', this.memo.getType(ChipCloudChip));
return new FilteredChannelList(this.actions, page, true);
}
}
//# sourceMappingURL=Channel.js.map
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
import type { ObservedArray } from '../helpers.js';
import CommentsHeader from '../classes/comments/CommentsHeader.js';
import CommentThread from '../classes/comments/CommentThread.js';
import type { Actions, ApiResponse } from '../../core/index.js';
import type { INextResponse } from '../types/index.js';
export default class Comments {
#private;
header?: CommentsHeader;
contents: ObservedArray<CommentThread>;
constructor(actions: Actions, data: any, already_parsed?: boolean);
/**
* Applies given sort option to the comments.
* @param sort - Sort type.
*/
applySort(sort: 'TOP_COMMENTS' | 'NEWEST_FIRST'): Promise<Comments>;
/**
* Creates a top-level comment.
* @param text - Comment text.
*/
createComment(text: string): Promise<ApiResponse>;
/**
* Retrieves next batch of comments.
*/
getContinuation(): Promise<Comments>;
get has_continuation(): boolean;
get page(): INextResponse;
}
+92
View File
@@ -0,0 +1,92 @@
import { Parser } from '../index.js';
import { InnertubeError } from '../../utils/Utils.js';
import { observe } from '../helpers.js';
import CommentsHeader from '../classes/comments/CommentsHeader.js';
import CommentSimplebox from '../classes/comments/CommentSimplebox.js';
import CommentThread from '../classes/comments/CommentThread.js';
import ContinuationItem from '../classes/ContinuationItem.js';
import { ReloadContinuationItemsCommand } from '../index.js';
import AppendContinuationItemsAction from '../classes/actions/AppendContinuationItemsAction.js';
export default class Comments {
#page;
#actions;
#continuation;
header;
contents;
constructor(actions, data, already_parsed = false) {
this.#page = already_parsed ? data : Parser.parseResponse(data);
this.#actions = actions;
const contents = this.#page.on_response_received_endpoints;
if (!contents)
throw new InnertubeError('Comments page did not have any content.');
const header_node = contents.at(0)?.as(AppendContinuationItemsAction, ReloadContinuationItemsCommand);
const body_node = contents.at(1)?.as(AppendContinuationItemsAction, ReloadContinuationItemsCommand);
this.header = header_node?.contents?.firstOfType(CommentsHeader);
const threads = body_node?.contents?.filterType(CommentThread) || [];
this.contents = observe(threads.map((thread) => {
if (thread.comment)
thread.comment.setActions(this.#actions);
thread.setActions(this.#actions);
return thread;
}));
this.#continuation = body_node?.contents?.firstOfType(ContinuationItem);
}
/**
* Applies given sort option to the comments.
* @param sort - Sort type.
*/
async applySort(sort) {
if (!this.header)
throw new InnertubeError('Page header is missing. Cannot apply sort option.');
let button;
if (sort === 'TOP_COMMENTS') {
button = this.header.sort_menu?.sub_menu_items?.at(0);
}
else if (sort === 'NEWEST_FIRST') {
button = this.header.sort_menu?.sub_menu_items?.at(1);
}
if (!button)
throw new InnertubeError('Could not find target button.');
if (button.selected)
return this;
const response = await button.endpoint.call(this.#actions, { parse: true });
return new Comments(this.#actions, response, true);
}
/**
* Creates a top-level comment.
* @param text - Comment text.
*/
async createComment(text) {
if (!this.header)
throw new InnertubeError('Page header is missing. Cannot create comment.');
const button = this.header.create_renderer?.as(CommentSimplebox).submit_button;
if (!button)
throw new InnertubeError('Could not find target button. You are probably not logged in.');
if (!button.endpoint)
throw new InnertubeError('Button does not have an endpoint.');
return await button.endpoint.call(this.#actions, { commentText: text });
}
/**
* Retrieves next batch of comments.
*/
async getContinuation() {
if (!this.#continuation)
throw new InnertubeError('Continuation not found');
const data = await this.#continuation.endpoint.call(this.#actions, { parse: true });
// Copy the previous page so we can keep the header.
const page = Object.assign({}, this.#page);
if (!page.on_response_received_endpoints || !data.on_response_received_endpoints)
throw new InnertubeError('Invalid reponse format, missing on_response_received_endpoints.');
// Remove previous items and append the continuation.
page.on_response_received_endpoints.pop();
page.on_response_received_endpoints.push(data.on_response_received_endpoints[0]);
return new Comments(this.#actions, page, true);
}
get has_continuation() {
return !!this.#continuation;
}
get page() {
return this.#page;
}
}
//# sourceMappingURL=Comments.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Comments.js","sourceRoot":"","sources":["../../../../src/parser/youtube/Comments.ts"],"names":[],"mappings":"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"}
+11
View File
@@ -0,0 +1,11 @@
import GuideSection from '../classes/GuideSection.js';
import GuideSubscriptionsSection from '../classes/GuideSubscriptionsSection.js';
import type { ObservedArray } from '../helpers.js';
import type { IGuideResponse } from '../types/index.js';
import type { IRawResponse } from '../index.js';
export default class Guide {
#private;
contents?: ObservedArray<GuideSection | GuideSubscriptionsSection>;
constructor(data: IRawResponse);
get page(): IGuideResponse;
}
+16
View File
@@ -0,0 +1,16 @@
import { Parser } from '../index.js';
import GuideSection from '../classes/GuideSection.js';
import GuideSubscriptionsSection from '../classes/GuideSubscriptionsSection.js';
export default class Guide {
#page;
contents;
constructor(data) {
this.#page = Parser.parseResponse(data);
if (this.#page.items)
this.contents = this.#page.items.array().as(GuideSection, GuideSubscriptionsSection);
}
get page() {
return this.#page;
}
}
//# sourceMappingURL=Guide.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Guide.js","sourceRoot":"","sources":["../../../../src/parser/youtube/Guide.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,YAAY,MAAM,4BAA4B,CAAC;AACtD,OAAO,yBAAyB,MAAM,yCAAyC,CAAC;AAMhF,MAAM,CAAC,OAAO,OAAO,KAAK;IACf,KAAK,CAAiB;IACxB,QAAQ,CAA2D;IAE1E,YAAY,IAAkB;QAC5B,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,aAAa,CAAiB,IAAI,CAAC,CAAC;QACxD,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK;YAClB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,yBAAyB,CAAC,CAAC;IACzF,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF"}
+17
View File
@@ -0,0 +1,17 @@
import FilterableFeed from '../../core/mixins/FilterableFeed.js';
import HashtagHeader from '../classes/HashtagHeader.js';
import RichGrid from '../classes/RichGrid.js';
import PageHeader from '../classes/PageHeader.js';
import type { Actions, ApiResponse } from '../../core/index.js';
import type { IBrowseResponse } from '../index.js';
import type ChipCloudChip from '../classes/ChipCloudChip.js';
export default class HashtagFeed extends FilterableFeed<IBrowseResponse> {
header?: HashtagHeader | PageHeader;
contents: RichGrid;
constructor(actions: Actions, response: IBrowseResponse | ApiResponse);
/**
* Applies given filter and returns a new {@link HashtagFeed} object. Use {@link HashtagFeed.filters} to get available filters.
* @param filter - Filter to apply.
*/
applyFilter(filter: string | ChipCloudChip): Promise<HashtagFeed>;
}
+31
View File
@@ -0,0 +1,31 @@
import { InnertubeError } from '../../utils/Utils.js';
import FilterableFeed from '../../core/mixins/FilterableFeed.js';
import HashtagHeader from '../classes/HashtagHeader.js';
import RichGrid from '../classes/RichGrid.js';
import PageHeader from '../classes/PageHeader.js';
import Tab from '../classes/Tab.js';
export default class HashtagFeed extends FilterableFeed {
header;
contents;
constructor(actions, response) {
super(actions, response);
if (!this.page.contents_memo)
throw new InnertubeError('Unexpected response', this.page);
const tab = this.page.contents_memo.getType(Tab)[0];
if (!tab.content)
throw new InnertubeError('Content tab has no content', tab);
if (this.page.header) {
this.header = this.page.header.item().as(HashtagHeader, PageHeader);
}
this.contents = tab.content.as(RichGrid);
}
/**
* Applies given filter and returns a new {@link HashtagFeed} object. Use {@link HashtagFeed.filters} to get available filters.
* @param filter - Filter to apply.
*/
async applyFilter(filter) {
const response = await super.getFilteredFeed(filter);
return new HashtagFeed(this.actions, response.page);
}
}
//# sourceMappingURL=HashtagFeed.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"HashtagFeed.js","sourceRoot":"","sources":["../../../../src/parser/youtube/HashtagFeed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,cAAc,MAAM,qCAAqC,CAAC;AACjE,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,UAAU,MAAM,0BAA0B,CAAC;AAClD,OAAO,GAAG,MAAM,mBAAmB,CAAC;AAMpC,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,cAA+B;IAC/D,MAAM,CAA8B;IACpC,QAAQ,CAAW;IAE1B,YAAY,OAAgB,EAAE,QAAuC;QACnE,KAAK,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAEzB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa;YAC1B,MAAM,IAAI,cAAc,CAAC,qBAAqB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAE7D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpD,IAAI,CAAC,GAAG,CAAC,OAAO;YACd,MAAM,IAAI,cAAc,CAAC,4BAA4B,EAAE,GAAG,CAAC,CAAC;QAE9D,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YACrB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;IAC3C,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,MAA8B;QAC9C,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACrD,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtD,CAAC;CACF"}
+18
View File
@@ -0,0 +1,18 @@
import Feed from '../../core/mixins/Feed.js';
import ItemSection from '../classes/ItemSection.js';
import BrowseFeedActions from '../classes/BrowseFeedActions.js';
import type { Actions, ApiResponse } from '../../core/index.js';
import type { IBrowseResponse } from '../types/index.js';
export default class History extends Feed<IBrowseResponse> {
sections: ItemSection[];
feed_actions: BrowseFeedActions;
constructor(actions: Actions, data: ApiResponse | IBrowseResponse, already_parsed?: boolean);
/**
* Retrieves next batch of contents.
*/
getContinuation(): Promise<History>;
/**
* Removes a video from watch history.
*/
removeVideo(video_id: string, pages_to_load?: number): Promise<boolean>;
}
+77
View File
@@ -0,0 +1,77 @@
import Feed from '../../core/mixins/Feed.js';
import ItemSection from '../classes/ItemSection.js';
import BrowseFeedActions from '../classes/BrowseFeedActions.js';
import Button from '../classes/Button.js';
import Video from '../classes/Video.js';
import LockupView from '../classes/LockupView.js';
// TODO: make feed actions usable
export default class History extends Feed {
sections;
feed_actions;
constructor(actions, data, already_parsed = false) {
super(actions, data, already_parsed);
this.sections = this.memo.getType(ItemSection);
this.feed_actions = this.memo.getType(BrowseFeedActions)[0];
}
/**
* Retrieves next batch of contents.
*/
async getContinuation() {
const response = await this.getContinuationData();
if (!response)
throw new Error('No continuation data found');
return new History(this.actions, response, true);
}
/**
* Removes a video from watch history.
*/
async removeVideo(video_id, pages_to_load = 1) {
let pagesToLoad = pages_to_load;
while (pagesToLoad > 0) {
let feedbackToken;
for (const section of this.sections) {
for (const content of section.contents) {
if (content.is(Video)) {
if (content.video_id === video_id && content.menu) {
feedbackToken = content.menu.top_level_buttons[0].as(Button).endpoint.payload.feedbackToken;
break;
}
}
else if (content.is(LockupView)) {
if (content.content_id === video_id) {
const listItems = content.metadata?.menu_button?.on_tap?.payload.panelLoadingStrategy.inlineContent.sheetViewModel.content.listViewModel.listItems;
const listItem = listItems.find((video) => video.listItemViewModel?.title.content === 'Remove from watch history');
feedbackToken = listItem.listItemViewModel.rendererContext.commandContext.onTap.innertubeCommand.feedbackEndpoint.feedbackToken;
break;
}
}
}
if (feedbackToken) {
break;
}
}
if (feedbackToken) {
const body = { feedbackTokens: [feedbackToken] };
const response = await this.actions.execute('/feedback', body);
const data = response.data;
if (!data.feedbackResponses[0].isProcessed) {
throw new Error('Failed to remove video from watch history');
}
return true;
}
if (--pagesToLoad > 0) {
try {
Object.assign(this, await this.getContinuation());
}
catch {
throw new Error('Unable to find video in watch history');
}
}
else {
throw new Error('Unable to find video in watch history');
}
}
return false;
}
}
//# sourceMappingURL=History.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"History.js","sourceRoot":"","sources":["../../../../src/parser/youtube/History.ts"],"names":[],"mappings":"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"}
+21
View File
@@ -0,0 +1,21 @@
import FilterableFeed from '../../core/mixins/FilterableFeed.js';
import FeedTabbedHeader from '../classes/FeedTabbedHeader.js';
import RichGrid from '../classes/RichGrid.js';
import type { IBrowseResponse } from '../types/index.js';
import type { AppendContinuationItemsAction, ReloadContinuationItemsCommand } from '../index.js';
import type { ApiResponse, Actions } from '../../core/index.js';
import type ChipCloudChip from '../classes/ChipCloudChip.js';
export default class HomeFeed extends FilterableFeed<IBrowseResponse> {
contents?: RichGrid | AppendContinuationItemsAction | ReloadContinuationItemsCommand;
header?: FeedTabbedHeader;
constructor(actions: Actions, data: ApiResponse | IBrowseResponse, already_parsed?: boolean);
/**
* Applies given filter to the feed. Use {@link filters} to get available filters.
* @param filter - Filter to apply.
*/
applyFilter(filter: string | ChipCloudChip): Promise<HomeFeed>;
/**
* Retrieves next batch of contents.
*/
getContinuation(): Promise<HomeFeed>;
}
+32
View File
@@ -0,0 +1,32 @@
import FilterableFeed from '../../core/mixins/FilterableFeed.js';
import FeedTabbedHeader from '../classes/FeedTabbedHeader.js';
import RichGrid from '../classes/RichGrid.js';
export default class HomeFeed extends FilterableFeed {
contents;
header;
constructor(actions, data, already_parsed = false) {
super(actions, data, already_parsed);
this.header = this.memo.getType(FeedTabbedHeader)[0];
this.contents = this.memo.getType(RichGrid)[0] || this.page.on_response_received_actions?.[0];
}
/**
* Applies given filter to the feed. Use {@link filters} to get available filters.
* @param filter - Filter to apply.
*/
async applyFilter(filter) {
const feed = await super.getFilteredFeed(filter);
return new HomeFeed(this.actions, feed.page, true);
}
/**
* Retrieves next batch of contents.
*/
async getContinuation() {
const feed = await super.getContinuation();
// Keep the page header
feed.page.header = this.page.header;
if (this.header)
feed.page.header_memo?.set(this.header.type, [this.header]);
return new HomeFeed(this.actions, feed.page, true);
}
}
//# sourceMappingURL=HomeFeed.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"HomeFeed.js","sourceRoot":"","sources":["../../../../src/parser/youtube/HomeFeed.ts"],"names":[],"mappings":"AAAA,OAAO,cAAc,MAAM,qCAAqC,CAAC;AACjE,OAAO,gBAAgB,MAAM,gCAAgC,CAAC;AAC9D,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAO9C,MAAM,CAAC,OAAO,OAAO,QAAS,SAAQ,cAA+B;IAC5D,QAAQ,CAA6E;IACrF,MAAM,CAAoB;IAEjC,YAAY,OAAgB,EAAE,IAAmC,EAAE,cAAc,GAAG,KAAK;QACvF,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,4BAA4B,EAAE,CAAC,CAAC,CAAC,CAAC;IAChG,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,WAAW,CAAC,MAA8B;QAC9C,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;QACjD,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,eAAe,EAAE,CAAC;QAE3C,uBAAuB;QACvB,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAEpC,IAAI,IAAI,CAAC,MAAM;YACb,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAE,IAAI,CAAC,MAAM,CAAE,CAAC,CAAC;QAEhE,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACrD,CAAC;CACF"}
+12
View File
@@ -0,0 +1,12 @@
import Button from '../classes/Button.js';
import type { Actions } from '../../core/index.js';
import type { ObservedArray, YTNode } from '../helpers.js';
import type { IParsedResponse } from '../types/index.js';
export default class ItemMenu {
#private;
constructor(data: IParsedResponse, actions: Actions);
selectItem(icon_type: string): Promise<IParsedResponse>;
selectItem(button: Button): Promise<IParsedResponse>;
items(): ObservedArray<YTNode>;
page(): IParsedResponse;
}
+47
View File
@@ -0,0 +1,47 @@
import Menu from '../classes/menus/Menu.js';
import Button from '../classes/Button.js';
import MenuServiceItem from '../classes/menus/MenuServiceItem.js';
import { InnertubeError } from '../../utils/Utils.js';
export default class ItemMenu {
#page;
#actions;
#items;
constructor(data, actions) {
this.#page = data;
this.#actions = actions;
const menu = data?.live_chat_item_context_menu_supported_renderers;
if (!menu || !menu.is(Menu))
throw new InnertubeError('Response did not have a "live_chat_item_context_menu_supported_renderers" property. The call may have failed.');
this.#items = menu.as(Menu).items;
}
async selectItem(item) {
let endpoint;
if (item instanceof Button) {
if (!item.endpoint)
throw new InnertubeError('Item does not have an endpoint.');
endpoint = item.endpoint;
}
else {
const button = this.#items.find((button) => {
if (!button.is(MenuServiceItem)) {
return false;
}
const menuServiceItem = button.as(MenuServiceItem);
return menuServiceItem.icon_type === item;
});
if (!button || !button.is(MenuServiceItem))
throw new InnertubeError(`Button "${item}" not found.`);
endpoint = button.endpoint;
}
if (!endpoint)
throw new InnertubeError('Target button does not have an endpoint.');
return await endpoint.call(this.#actions, { parse: true });
}
items() {
return this.#items;
}
page() {
return this.#page;
}
}
//# sourceMappingURL=ItemMenu.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"ItemMenu.js","sourceRoot":"","sources":["../../../../src/parser/youtube/ItemMenu.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,0BAA0B,CAAC;AAC5C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAKtD,MAAM,CAAC,OAAO,OAAO,QAAQ;IAClB,KAAK,CAAkB;IACvB,QAAQ,CAAU;IAClB,MAAM,CAAwB;IAEvC,YAAY,IAAqB,EAAE,OAAgB;QACjD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QAExB,MAAM,IAAI,GAAG,IAAI,EAAE,+CAA+C,CAAC;QAEnE,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;YACzB,MAAM,IAAI,cAAc,CAAC,+GAA+G,CAAC,CAAC;QAE5I,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;IACpC,CAAC;IAID,KAAK,CAAC,UAAU,CAAC,IAAqB;QACpC,IAAI,QAAwC,CAAC;QAE7C,IAAI,IAAI,YAAY,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,QAAQ;gBAChB,MAAM,IAAI,cAAc,CAAC,iCAAiC,CAAC,CAAC;YAE9D,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC3B,CAAC;aAAM,CAAC;YACN,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;gBACzC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC;oBAChC,OAAO,KAAK,CAAC;gBACf,CAAC;gBACD,MAAM,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC;gBACnD,OAAO,eAAe,CAAC,SAAS,KAAK,IAAI,CAAC;YAC5C,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,eAAe,CAAC;gBACxC,MAAM,IAAI,cAAc,CAAC,WAAW,IAAI,cAAc,CAAC,CAAC;YAE1D,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC,QAAQ;YACX,MAAM,IAAI,cAAc,CAAC,0CAA0C,CAAC,CAAC;QAEvE,OAAO,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IAED,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF"}
+47
View File
@@ -0,0 +1,47 @@
import Feed from '../../core/mixins/Feed.js';
import History from './History.js';
import Playlist from './Playlist.js';
import PageHeader from '../classes/PageHeader.js';
import type { Actions, ApiResponse } from '../../core/index.js';
import type { IBrowseResponse } from '../types/index.js';
export default class Library extends Feed<IBrowseResponse> {
#private;
header: PageHeader | null;
sections: {
type: string | undefined;
title: import("../misc.js").Text;
contents: any[];
getAll: () => Promise<Feed<IBrowseResponse> | History | Playlist>;
}[];
constructor(actions: Actions, data: ApiResponse | IBrowseResponse);
get history(): {
type: string | undefined;
title: import("../misc.js").Text;
contents: any[];
getAll: () => Promise<Feed<IBrowseResponse> | History | Playlist>;
} | undefined;
get watch_later(): {
type: string | undefined;
title: import("../misc.js").Text;
contents: any[];
getAll: () => Promise<Feed<IBrowseResponse> | History | Playlist>;
} | undefined;
get liked_videos(): {
type: string | undefined;
title: import("../misc.js").Text;
contents: any[];
getAll: () => Promise<Feed<IBrowseResponse> | History | Playlist>;
} | undefined;
get playlists_section(): {
type: string | undefined;
title: import("../misc.js").Text;
contents: any[];
getAll: () => Promise<Feed<IBrowseResponse> | History | Playlist>;
} | undefined;
get clips(): {
type: string | undefined;
title: import("../misc.js").Text;
contents: any[];
getAll: () => Promise<Feed<IBrowseResponse> | History | Playlist>;
} | undefined;
}
+60
View File
@@ -0,0 +1,60 @@
import { InnertubeError } from '../../utils/Utils.js';
import Feed from '../../core/mixins/Feed.js';
import History from './History.js';
import Playlist from './Playlist.js';
import Menu from '../classes/menus/Menu.js';
import Shelf from '../classes/Shelf.js';
import Button from '../classes/Button.js';
import PageHeader from '../classes/PageHeader.js';
export default class Library extends Feed {
header;
sections;
constructor(actions, data) {
super(actions, data);
if (!this.page.contents_memo)
throw new InnertubeError('Page contents not found');
this.header = this.memo.getType(PageHeader)[0];
const shelves = this.page.contents_memo.getType(Shelf);
this.sections = shelves.map((shelf) => ({
type: shelf.icon_type,
title: shelf.title,
contents: shelf.content?.key('items').array() || [],
getAll: () => this.#getAll(shelf)
}));
}
async #getAll(shelf) {
if (!shelf.menu?.as(Menu).top_level_buttons)
throw new InnertubeError(`The ${shelf.title.text} shelf doesn't have more items`);
const button = shelf.menu.as(Menu).top_level_buttons.firstOfType(Button);
if (!button)
throw new InnertubeError('Did not find target button.');
const page = await button.as(Button).endpoint.call(this.actions, { parse: true });
switch (shelf.icon_type) {
case 'LIKE':
case 'WATCH_LATER':
return new Playlist(this.actions, page, true);
case 'WATCH_HISTORY':
return new History(this.actions, page, true);
case 'CONTENT_CUT':
return new Feed(this.actions, page, true);
default:
throw new InnertubeError('Target shelf not implemented.');
}
}
get history() {
return this.sections.find((section) => section.type === 'WATCH_HISTORY');
}
get watch_later() {
return this.sections.find((section) => section.type === 'WATCH_LATER');
}
get liked_videos() {
return this.sections.find((section) => section.type === 'LIKE');
}
get playlists_section() {
return this.sections.find((section) => section.type === 'PLAYLISTS');
}
get clips() {
return this.sections.find((section) => section.type === 'CONTENT_CUT');
}
}
//# sourceMappingURL=Library.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Library.js","sourceRoot":"","sources":["../../../../src/parser/youtube/Library.ts"],"names":[],"mappings":"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"}
+78
View File
@@ -0,0 +1,78 @@
import { EventEmitter } from '../../utils/index.js';
import { LiveChatContinuation } from '../index.js';
import SmoothedQueue from './SmoothedQueue.js';
import RunAttestationCommand from '../classes/commands/RunAttestationCommand.js';
import AddChatItemAction from '../classes/livechat/AddChatItemAction.js';
import UpdateDateTextAction from '../classes/livechat/UpdateDateTextAction.js';
import UpdateDescriptionAction from '../classes/livechat/UpdateDescriptionAction.js';
import UpdateTitleAction from '../classes/livechat/UpdateTitleAction.js';
import UpdateToggleButtonTextAction from '../classes/livechat/UpdateToggleButtonTextAction.js';
import UpdateViewershipAction from '../classes/livechat/UpdateViewershipAction.js';
import ItemMenu from './ItemMenu.js';
import type { ObservedArray } from '../helpers.js';
import type VideoInfo from './VideoInfo.js';
import type AddBannerToLiveChatCommand from '../classes/livechat/AddBannerToLiveChatCommand.js';
import type RemoveBannerForLiveChatCommand from '../classes/livechat/RemoveBannerForLiveChatCommand.js';
import type ShowLiveChatTooltipCommand from '../classes/livechat/ShowLiveChatTooltipCommand.js';
import type LiveChatAutoModMessage from '../classes/livechat/items/LiveChatAutoModMessage.js';
import type LiveChatMembershipItem from '../classes/livechat/items/LiveChatMembershipItem.js';
import type LiveChatPaidMessage from '../classes/livechat/items/LiveChatPaidMessage.js';
import type LiveChatPaidSticker from '../classes/livechat/items/LiveChatPaidSticker.js';
import type LiveChatTextMessage from '../classes/livechat/items/LiveChatTextMessage.js';
import type LiveChatViewerEngagementMessage from '../classes/livechat/items/LiveChatViewerEngagementMessage.js';
import type AddLiveChatTickerItemAction from '../classes/livechat/AddLiveChatTickerItemAction.js';
import type MarkChatItemAsDeletedAction from '../classes/livechat/MarkChatItemAsDeletedAction.js';
import type MarkChatItemsByAuthorAsDeletedAction from '../classes/livechat/MarkChatItemsByAuthorAsDeletedAction.js';
import type ReplaceChatItemAction from '../classes/livechat/ReplaceChatItemAction.js';
import type ReplayChatItemAction from '../classes/livechat/ReplayChatItemAction.js';
import type ShowLiveChatActionPanelAction from '../classes/livechat/ShowLiveChatActionPanelAction.js';
import type Button from '../classes/Button.js';
import type { IParsedResponse } from '../types/index.js';
export type ChatAction = AddChatItemAction | AddBannerToLiveChatCommand | AddLiveChatTickerItemAction | MarkChatItemAsDeletedAction | MarkChatItemsByAuthorAsDeletedAction | RemoveBannerForLiveChatCommand | ReplaceChatItemAction | ReplayChatItemAction | ShowLiveChatActionPanelAction | ShowLiveChatTooltipCommand;
export type ChatItemWithMenu = LiveChatAutoModMessage | LiveChatMembershipItem | LiveChatPaidMessage | LiveChatPaidSticker | LiveChatTextMessage | LiveChatViewerEngagementMessage;
export type LiveMetadata = {
title?: UpdateTitleAction;
description?: UpdateDescriptionAction;
views?: UpdateViewershipAction;
likes?: UpdateToggleButtonTextAction;
date?: UpdateDateTextAction;
};
export default class LiveChat extends EventEmitter {
#private;
smoothed_queue: SmoothedQueue;
initial_info?: LiveChatContinuation;
metadata?: LiveMetadata;
running: boolean;
is_replay: boolean;
constructor(video_info: VideoInfo);
on(type: 'start', listener: (initial_data: LiveChatContinuation) => void): void;
on(type: 'chat-update', listener: (action: ChatAction) => void): void;
on(type: 'metadata-update', listener: (metadata: LiveMetadata) => void): void;
on(type: 'error', listener: (err: Error) => void): void;
on(type: 'end', listener: () => void): void;
once(type: 'start', listener: (initial_data: LiveChatContinuation) => void): void;
once(type: 'chat-update', listener: (action: ChatAction) => void): void;
once(type: 'metadata-update', listener: (metadata: LiveMetadata) => void): void;
once(type: 'error', listener: (err: Error) => void): void;
once(type: 'end', listener: () => void): void;
start(): void;
stop(): void;
/**
* Sends a message.
* @param text - Text to send.
*/
sendMessage(text: string): Promise<ObservedArray<AddChatItemAction | RunAttestationCommand>>;
/**
* Applies given filter to the live chat.
* @param filter - Filter to apply.
*/
applyFilter(filter: 'TOP_CHAT' | 'LIVE_CHAT'): void;
/**
* Retrieves given chat item's menu.
*/
getItemMenu(item: ChatItemWithMenu): Promise<ItemMenu>;
/**
* Equivalent to "clicking" a button.
*/
selectButton(button: Button): Promise<IParsedResponse>;
}
+233
View File
@@ -0,0 +1,233 @@
// noinspection ES6MissingAwait
import { EventEmitter } from '../../utils/index.js';
import { InnertubeError, Platform, u8ToBase64 } from '../../utils/Utils.js';
import { LiveChatContinuation, Parser } from '../index.js';
import SmoothedQueue from './SmoothedQueue.js';
import RunAttestationCommand from '../classes/commands/RunAttestationCommand.js';
import AddChatItemAction from '../classes/livechat/AddChatItemAction.js';
import UpdateDateTextAction from '../classes/livechat/UpdateDateTextAction.js';
import UpdateDescriptionAction from '../classes/livechat/UpdateDescriptionAction.js';
import UpdateTitleAction from '../classes/livechat/UpdateTitleAction.js';
import UpdateToggleButtonTextAction from '../classes/livechat/UpdateToggleButtonTextAction.js';
import UpdateViewershipAction from '../classes/livechat/UpdateViewershipAction.js';
import NavigationEndpoint from '../classes/NavigationEndpoint.js';
import ItemMenu from './ItemMenu.js';
import { LiveMessageParams } from '../../../protos/generated/misc/params.js';
export default class LiveChat extends EventEmitter {
#actions;
#video_id;
#channel_id;
#continuation;
#mcontinuation;
#retry_count = 0;
smoothed_queue;
initial_info;
metadata;
running = false;
is_replay = false;
constructor(video_info) {
super();
this.#video_id = video_info.basic_info.id;
this.#channel_id = video_info.basic_info.channel_id;
this.#actions = video_info.actions;
this.#continuation = video_info.livechat?.continuation;
this.is_replay = video_info.livechat?.is_replay || false;
this.smoothed_queue = new SmoothedQueue();
this.smoothed_queue.callback = async (actions) => {
if (!actions.length) {
// Wait 2 seconds before requesting an incremental continuation if the action group is empty.
await this.#wait(2000);
}
else if (actions.length < 10) {
// If there are less than 10 actions, wait until all of them are emitted.
await this.#emitSmoothedActions(actions);
}
else if (this.is_replay) {
/**
* NOTE: Live chat replays require data from the video player for actions to be emitted timely
* and as we don't have that, this ends up being quite innacurate.
*/
this.#emitSmoothedActions(actions);
await this.#wait(2000);
}
else {
// There are more than 10 actions, emit them asynchronously so we can request the next incremental continuation.
this.#emitSmoothedActions(actions);
}
if (this.running) {
this.#pollLivechat();
}
};
}
on(type, listener) {
super.on(type, listener);
}
once(type, listener) {
super.once(type, listener);
}
start() {
if (!this.running) {
this.running = true;
this.#pollLivechat();
this.#pollMetadata();
}
}
stop() {
this.smoothed_queue.clear();
this.running = false;
}
#pollLivechat() {
(async () => {
try {
const response = await this.#actions.execute(this.is_replay ? 'live_chat/get_live_chat_replay' : 'live_chat/get_live_chat', { continuation: this.#continuation, parse: true });
const contents = response.continuation_contents;
if (!contents) {
this.emit('error', new InnertubeError('Unexpected live chat incremental continuation response', response));
this.emit('end');
this.stop();
}
if (!(contents instanceof LiveChatContinuation)) {
this.stop();
this.emit('end');
return;
}
this.#continuation = contents.continuation.token;
// Header only exists in the first request
if (contents.header) {
this.initial_info = contents;
this.emit('start', contents);
if (this.running)
this.#pollLivechat();
}
else {
this.smoothed_queue.enqueueActionGroup(contents.actions);
}
this.#retry_count = 0;
}
catch (err) {
this.emit('error', err);
if (this.#retry_count++ < 10) {
await this.#wait(2000);
this.#pollLivechat();
}
else {
this.emit('error', new InnertubeError('Reached retry limit for incremental continuation requests', err));
this.emit('end');
this.stop();
}
}
})();
}
/**
* Ensures actions are emitted at the right speed.
* This and {@link SmoothedQueue} were based off of YouTube's own implementation.
*/
async #emitSmoothedActions(action_queue) {
const base = 1E4;
let delay = action_queue.length < base / 80 ? 1 : Math.ceil(action_queue.length / (base / 80));
const emit_delay_ms = delay == 1 ? (delay = base / action_queue.length,
delay *= Math.random() + 0.5,
delay = Math.min(1E3, delay),
delay = Math.max(80, delay)) : delay = 80;
for (const action of action_queue) {
await this.#wait(emit_delay_ms);
this.emit('chat-update', action);
}
}
#pollMetadata() {
(async () => {
try {
const payload = { videoId: this.#video_id };
if (this.#mcontinuation) {
payload.continuation = this.#mcontinuation;
}
const response = await this.#actions.execute('/updated_metadata', payload);
const data = Parser.parseResponse(response.data);
this.#mcontinuation = data.continuation?.token;
this.metadata = {
title: data.actions?.array().firstOfType(UpdateTitleAction) || this.metadata?.title,
description: data.actions?.array().firstOfType(UpdateDescriptionAction) || this.metadata?.description,
views: data.actions?.array().firstOfType(UpdateViewershipAction) || this.metadata?.views,
likes: data.actions?.array().firstOfType(UpdateToggleButtonTextAction) || this.metadata?.likes,
date: data.actions?.array().firstOfType(UpdateDateTextAction) || this.metadata?.date
};
this.emit('metadata-update', this.metadata);
await this.#wait(5000);
if (this.running)
this.#pollMetadata();
}
catch {
await this.#wait(2000);
if (this.running)
this.#pollMetadata();
}
})();
}
/**
* Sends a message.
* @param text - Text to send.
*/
async sendMessage(text) {
const writer = LiveMessageParams.encode({
params: {
ids: {
videoId: this.#video_id,
channelId: this.#channel_id
}
},
number0: 1,
number1: 4
});
const params = btoa(encodeURIComponent(u8ToBase64(writer.finish())));
const response = await this.#actions.execute('/live_chat/send_message', {
richMessage: { textSegments: [{ text }] },
clientMessageId: Platform.shim.uuidv4(),
client: 'WEB',
parse: true,
params
});
if (!response.actions)
throw new InnertubeError('Unexpected response from send_message', response);
return response.actions.array().as(AddChatItemAction, RunAttestationCommand);
}
/**
* Applies given filter to the live chat.
* @param filter - Filter to apply.
*/
applyFilter(filter) {
if (!this.initial_info)
throw new InnertubeError('Cannot apply filter before initial info is retrieved.');
const menu_items = this.initial_info?.header?.view_selector?.sub_menu_items;
if (filter === 'TOP_CHAT') {
if (menu_items?.at(0)?.selected)
return;
this.#continuation = menu_items?.at(0)?.continuation;
}
else {
if (menu_items?.at(1)?.selected)
return;
this.#continuation = menu_items?.at(1)?.continuation;
}
}
/**
* Retrieves given chat item's menu.
*/
async getItemMenu(item) {
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.key('menu_endpoint').instanceof(NavigationEndpoint).call(this.#actions, { parse: true });
if (!response)
throw new InnertubeError('Could not retrieve item menu.', item);
return new ItemMenu(response, this.#actions);
}
/**
* Equivalent to "clicking" a button.
*/
async selectButton(button) {
return await button.endpoint.call(this.#actions, { parse: true });
}
async #wait(ms) {
return new Promise((resolve) => setTimeout(() => resolve(), ms));
}
}
//# sourceMappingURL=LiveChat.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
import SimpleMenuHeader from '../classes/menus/SimpleMenuHeader.js';
import Notification from '../classes/Notification.js';
import type { ApiResponse, Actions } from '../../core/index.js';
import type { IGetNotificationsMenuResponse } from '../types/index.js';
export default class NotificationsMenu {
#private;
header: SimpleMenuHeader;
contents: Notification[];
constructor(actions: Actions, response: ApiResponse);
getContinuation(): Promise<NotificationsMenu>;
get page(): IGetNotificationsMenuResponse;
}
+30
View File
@@ -0,0 +1,30 @@
import { Parser } from '../index.js';
import { InnertubeError } from '../../utils/Utils.js';
import ContinuationItem from '../classes/ContinuationItem.js';
import SimpleMenuHeader from '../classes/menus/SimpleMenuHeader.js';
import Notification from '../classes/Notification.js';
export default class NotificationsMenu {
#page;
#actions;
header;
contents;
constructor(actions, response) {
this.#actions = actions;
this.#page = Parser.parseResponse(response.data);
if (!this.#page.actions_memo)
throw new InnertubeError('Page actions not found');
this.header = this.#page.actions_memo.getType(SimpleMenuHeader)[0];
this.contents = this.#page.actions_memo.getType(Notification);
}
async getContinuation() {
const continuation = this.#page.actions_memo?.getType(ContinuationItem)[0];
if (!continuation)
throw new InnertubeError('Continuation not found');
const response = await continuation.endpoint.call(this.#actions, { parse: false });
return new NotificationsMenu(this.#actions, response);
}
get page() {
return this.#page;
}
}
//# sourceMappingURL=NotificationsMenu.js.map
@@ -0,0 +1 @@
{"version":3,"file":"NotificationsMenu.js","sourceRoot":"","sources":["../../../../src/parser/youtube/NotificationsMenu.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,gBAAgB,MAAM,gCAAgC,CAAC;AAC9D,OAAO,gBAAgB,MAAM,sCAAsC,CAAC;AACpE,OAAO,YAAY,MAAM,4BAA4B,CAAC;AAKtD,MAAM,CAAC,OAAO,OAAO,iBAAiB;IAC3B,KAAK,CAAgC;IACrC,QAAQ,CAAU;IAEpB,MAAM,CAAmB;IACzB,QAAQ,CAAiB;IAEhC,YAAY,OAAgB,EAAE,QAAqB;QACjD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,aAAa,CAAgC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAEhF,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY;YAC1B,MAAM,IAAI,cAAc,CAAC,wBAAwB,CAAC,CAAC;QAErD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;QAE3E,IAAI,CAAC,YAAY;YACf,MAAM,IAAI,cAAc,CAAC,wBAAwB,CAAC,CAAC;QAErD,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAEnF,OAAO,IAAI,iBAAiB,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF"}
+37
View File
@@ -0,0 +1,37 @@
import Feed from '../../core/mixins/Feed.js';
import Message from '../classes/Message.js';
import ReelItem from '../classes/ReelItem.js';
import ShortsLockupView from '../classes/ShortsLockupView.js';
import PlaylistVideo from '../classes/PlaylistVideo.js';
import { type ObservedArray, type YTNode } from '../helpers.js';
import type { Actions, ApiResponse } from '../../core/index.js';
import type { IBrowseResponse } from '../types/index.js';
import type Thumbnail from '../classes/misc/Thumbnail.js';
import type NavigationEndpoint from '../classes/NavigationEndpoint.js';
export default class Playlist extends Feed<IBrowseResponse> {
#private;
info: {
subtitle: import("../misc.js").Text | null;
author: import("../misc.js").Author;
thumbnails: Thumbnail[];
total_items: string;
views: string;
last_updated: string;
can_share: boolean;
can_delete: boolean;
can_reorder: boolean;
is_editable: boolean;
privacy: string;
title?: string | undefined;
description?: string | undefined;
type?: string | undefined;
};
menu: YTNode;
endpoint?: NavigationEndpoint;
messages: ObservedArray<Message>;
constructor(actions: Actions, data: ApiResponse | IBrowseResponse, already_parsed?: boolean);
get items(): ObservedArray<PlaylistVideo | ReelItem | ShortsLockupView>;
get has_continuation(): boolean;
getContinuationData(): Promise<IBrowseResponse | undefined>;
getContinuation(): Promise<Playlist>;
}
+90
View File
@@ -0,0 +1,90 @@
import { InnertubeError } from '../../utils/Utils.js';
import Feed from '../../core/mixins/Feed.js';
import Message from '../classes/Message.js';
import PlaylistCustomThumbnail from '../classes/PlaylistCustomThumbnail.js';
import PlaylistHeader from '../classes/PlaylistHeader.js';
import PlaylistMetadata from '../classes/PlaylistMetadata.js';
import PlaylistSidebarPrimaryInfo from '../classes/PlaylistSidebarPrimaryInfo.js';
import PlaylistSidebarSecondaryInfo from '../classes/PlaylistSidebarSecondaryInfo.js';
import PlaylistVideoList from '../classes/PlaylistVideoList.js';
import PlaylistVideoThumbnail from '../classes/PlaylistVideoThumbnail.js';
import ReelItem from '../classes/ReelItem.js';
import ShortsLockupView from '../classes/ShortsLockupView.js';
import VideoOwner from '../classes/VideoOwner.js';
import Alert from '../classes/Alert.js';
import ContinuationItem from '../classes/ContinuationItem.js';
import PlaylistVideo from '../classes/PlaylistVideo.js';
import SectionList from '../classes/SectionList.js';
import { observe } from '../helpers.js';
export default class Playlist extends Feed {
info;
menu;
endpoint;
messages;
constructor(actions, data, already_parsed = false) {
super(actions, data, already_parsed);
const header = this.memo.getType(PlaylistHeader)[0];
const primary_info = this.memo.getType(PlaylistSidebarPrimaryInfo)[0];
const secondary_info = this.memo.getType(PlaylistSidebarSecondaryInfo)[0];
const video_list = this.memo.getType(PlaylistVideoList)[0];
const alert = this.page.alerts?.firstOfType(Alert);
if (alert && alert.alert_type === 'ERROR')
throw new InnertubeError(alert.text.toString(), alert);
if (!primary_info && !secondary_info && Object.keys(this.page).length === 0)
throw new InnertubeError('Got empty continuation response. This is likely the end of the playlist.');
this.info = {
...this.page.metadata?.item().as(PlaylistMetadata),
...{
subtitle: header ? header.subtitle : null,
author: secondary_info?.owner?.as(VideoOwner).author ?? header?.author,
thumbnails: primary_info?.thumbnail_renderer?.as(PlaylistVideoThumbnail, PlaylistCustomThumbnail).thumbnail,
total_items: this.#getStat(0, primary_info),
views: this.#getStat(1, primary_info),
last_updated: this.#getStat(2, primary_info),
can_share: header?.can_share,
can_delete: header?.can_delete,
can_reorder: video_list?.can_reorder,
is_editable: video_list?.is_editable,
privacy: header?.privacy
}
};
this.menu = primary_info?.menu;
this.endpoint = primary_info?.endpoint;
this.messages = this.memo.getType(Message);
}
get items() {
return observe(this.videos.as(PlaylistVideo, ReelItem, ShortsLockupView).filter((video) => video.style !== 'PLAYLIST_VIDEO_RENDERER_STYLE_RECOMMENDED_VIDEO'));
}
get has_continuation() {
const section_list = this.memo.getType(SectionList)[0];
if (!section_list)
return super.has_continuation;
return !!this.memo.getType(ContinuationItem).find((node) => !section_list.contents.includes(node));
}
async getContinuationData() {
const section_list = this.memo.getType(SectionList)[0];
/**
* No section list means there can't be additional continuation nodes here,
* so no need to check.
*/
if (!section_list)
return await super.getContinuationData();
const playlist_contents_continuation = this.memo.getType(ContinuationItem)
.find((node) => !section_list.contents.includes(node));
if (!playlist_contents_continuation)
throw new InnertubeError('There are no continuations.');
return await playlist_contents_continuation.endpoint.call(this.actions, { parse: true });
}
async getContinuation() {
const page = await this.getContinuationData();
if (!page)
throw new InnertubeError('Could not get continuation data');
return new Playlist(this.actions, page, true);
}
#getStat(index, primary_info) {
if (!primary_info || !primary_info.stats)
return 'N/A';
return primary_info.stats[index]?.toString() || 'N/A';
}
}
//# sourceMappingURL=Playlist.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Playlist.js","sourceRoot":"","sources":["../../../../src/parser/youtube/Playlist.ts"],"names":[],"mappings":"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"}
+31
View File
@@ -0,0 +1,31 @@
import Feed from '../../core/mixins/Feed.js';
import HorizontalCardList from '../classes/HorizontalCardList.js';
import SearchHeader from '../classes/SearchHeader.js';
import SearchRefinementCard from '../classes/SearchRefinementCard.js';
import SearchSubMenu from '../classes/SearchSubMenu.js';
import UniversalWatchCard from '../classes/UniversalWatchCard.js';
import type { ApiResponse, Actions } from '../../core/index.js';
import type { ObservedArray, YTNode } from '../helpers.js';
import type { ISearchResponse } from '../types/index.js';
export default class Search extends Feed<ISearchResponse> {
header?: SearchHeader;
results: ObservedArray<YTNode>;
refinements: string[];
estimated_results: number;
sub_menu?: SearchSubMenu;
watch_card?: UniversalWatchCard;
refinement_cards?: HorizontalCardList | null;
constructor(actions: Actions, data: ApiResponse | ISearchResponse, already_parsed?: boolean);
/**
* Applies given refinement card and returns a new {@link Search} object. Use {@link refinement_card_queries} to get a list of available refinement cards.
*/
selectRefinementCard(card: SearchRefinementCard | string): Promise<Search>;
/**
* Returns a list of refinement card queries.
*/
get refinement_card_queries(): string[];
/**
* Retrieves next batch of search results.
*/
getContinuation(): Promise<Search>;
}
+77
View File
@@ -0,0 +1,77 @@
import Feed from '../../core/mixins/Feed.js';
import { InnertubeError } from '../../utils/Utils.js';
import HorizontalCardList from '../classes/HorizontalCardList.js';
import ItemSection from '../classes/ItemSection.js';
import SearchHeader from '../classes/SearchHeader.js';
import SearchRefinementCard from '../classes/SearchRefinementCard.js';
import SearchSubMenu from '../classes/SearchSubMenu.js';
import SectionList from '../classes/SectionList.js';
import UniversalWatchCard from '../classes/UniversalWatchCard.js';
import { observe } from '../helpers.js';
import { ReloadContinuationItemsCommand } from '../index.js';
import AppendContinuationItemsAction from '../classes/actions/AppendContinuationItemsAction.js';
export default class Search extends Feed {
header;
results;
refinements;
estimated_results;
sub_menu;
watch_card;
refinement_cards;
constructor(actions, data, already_parsed = false) {
super(actions, data, already_parsed);
const contents = this.page.contents_memo?.getType(SectionList)[0].contents ||
this.page.on_response_received_commands?.[0].as(AppendContinuationItemsAction, ReloadContinuationItemsCommand).contents;
if (!contents)
throw new InnertubeError('No contents found in search response');
if (this.page.header)
this.header = this.page.header.item().as(SearchHeader);
this.results = observe(contents.filterType(ItemSection).flatMap((section) => section.contents));
this.refinements = this.page.refinements || [];
this.estimated_results = this.page.estimated_results || 0;
if (this.page.contents_memo) {
this.sub_menu = this.page.contents_memo.getType(SearchSubMenu)[0];
this.watch_card = this.page.contents_memo.getType(UniversalWatchCard)[0];
}
this.refinement_cards = this.results?.firstOfType(HorizontalCardList);
}
/**
* Applies given refinement card and returns a new {@link Search} object. Use {@link refinement_card_queries} to get a list of available refinement cards.
*/
async selectRefinementCard(card) {
let target_card;
if (typeof card === 'string') {
if (!this.refinement_cards)
throw new InnertubeError('No refinement cards found.');
target_card = this.refinement_cards?.cards.find((refinement_card) => {
return refinement_card.is(SearchRefinementCard) && refinement_card.query === card;
});
if (!target_card)
throw new InnertubeError(`Refinement card "${card}" not found`, { available_cards: this.refinement_card_queries });
}
else if (card.type === 'SearchRefinementCard') {
target_card = card;
}
else {
throw new InnertubeError('Invalid refinement card!');
}
const page = await target_card.endpoint.call(this.actions, { parse: true });
return new Search(this.actions, page, true);
}
/**
* Returns a list of refinement card queries.
*/
get refinement_card_queries() {
return this.refinement_cards?.cards.as(SearchRefinementCard).map((card) => card.query) || [];
}
/**
* Retrieves next batch of search results.
*/
async getContinuation() {
const response = await this.getContinuationData();
if (!response)
throw new InnertubeError('Could not get continuation data');
return new Search(this.actions, response, true);
}
}
//# sourceMappingURL=Search.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Search.js","sourceRoot":"","sources":["../../../../src/parser/youtube/Search.ts"],"names":[],"mappings":"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"}
+33
View File
@@ -0,0 +1,33 @@
import CompactLink from '../classes/CompactLink.js';
import PageIntroduction from '../classes/PageIntroduction.js';
import SettingsSidebar from '../classes/SettingsSidebar.js';
import SettingsSwitch from '../classes/SettingsSwitch.js';
import type { ApiResponse, Actions } from '../../core/index.js';
import type { IBrowseResponse } from '../types/index.js';
export default class Settings {
#private;
sidebar?: SettingsSidebar;
introduction?: PageIntroduction;
sections: {
title: string | null;
contents: import("../helpers.js").ObservedArray<import("../helpers.js").YTNode>;
}[] | undefined;
constructor(actions: Actions, response: ApiResponse);
/**
* Selects an item from the sidebar menu. Use {@link sidebar_items} to see available items.
*/
selectSidebarItem(target_item: string | CompactLink): Promise<Settings>;
/**
* Finds a setting by name and returns it. Use {@link setting_options} to see available options.
*/
getSettingOption(name: string): SettingsSwitch;
/**
* Returns settings available in the page.
*/
get setting_options(): string[];
/**
* Returns options available in the sidebar.
*/
get sidebar_items(): string[];
get page(): IBrowseResponse;
}
+108
View File
@@ -0,0 +1,108 @@
import { Parser } from '../index.js';
import { InnertubeError } from '../../utils/Utils.js';
import CompactLink from '../classes/CompactLink.js';
import ItemSection from '../classes/ItemSection.js';
import PageIntroduction from '../classes/PageIntroduction.js';
import SectionList from '../classes/SectionList.js';
import SettingsOptions from '../classes/SettingsOptions.js';
import SettingsSidebar from '../classes/SettingsSidebar.js';
import SettingsSwitch from '../classes/SettingsSwitch.js';
import CommentsHeader from '../classes/comments/CommentsHeader.js';
import ItemSectionHeader from '../classes/ItemSectionHeader.js';
import ItemSectionTabbedHeader from '../classes/ItemSectionTabbedHeader.js';
import TwoColumnBrowseResults from '../classes/TwoColumnBrowseResults.js';
export default class Settings {
#page;
#actions;
sidebar;
introduction;
sections;
constructor(actions, response) {
this.#actions = actions;
this.#page = Parser.parseResponse(response.data);
this.sidebar = this.#page.sidebar?.as(SettingsSidebar);
if (!this.#page.contents)
throw new InnertubeError('Page contents not found');
const tab = this.#page.contents.item().as(TwoColumnBrowseResults).tabs.find((tab) => tab.selected);
if (!tab)
throw new InnertubeError('Target tab not found');
const contents = tab.content?.as(SectionList).contents.as(ItemSection);
this.introduction = contents?.shift()?.contents?.firstOfType(PageIntroduction);
this.sections = contents?.map((el) => ({
title: el.header?.is(CommentsHeader, ItemSectionHeader, ItemSectionTabbedHeader) ? el.header.title.toString() : null,
contents: el.contents
}));
}
/**
* Selects an item from the sidebar menu. Use {@link sidebar_items} to see available items.
*/
async selectSidebarItem(target_item) {
if (!this.sidebar)
throw new InnertubeError('Sidebar not available');
let item;
if (typeof target_item === 'string') {
item = this.sidebar.items.find((link) => link.title === target_item);
if (!item)
throw new InnertubeError(`Item "${target_item}" not found`, { available_items: this.sidebar_items });
}
else if (target_item?.is(CompactLink)) {
item = target_item;
}
else {
throw new InnertubeError('Invalid item', { target_item });
}
const response = await item.endpoint.call(this.#actions, { parse: false });
return new Settings(this.#actions, response);
}
/**
* Finds a setting by name and returns it. Use {@link setting_options} to see available options.
*/
getSettingOption(name) {
if (!this.sections)
throw new InnertubeError('Sections not available');
for (const section of this.sections) {
if (!section.contents)
continue;
for (const el of section.contents) {
const options = el.as(SettingsOptions).options;
if (options) {
for (const option of options) {
if (option.is(SettingsSwitch) &&
option.title?.toString() === name)
return option;
}
}
}
}
throw new InnertubeError(`Option "${name}" not found`, { available_options: this.setting_options });
}
/**
* Returns settings available in the page.
*/
get setting_options() {
if (!this.sections)
throw new InnertubeError('Sections not available');
let options = [];
for (const section of this.sections) {
if (!section.contents)
continue;
for (const el of section.contents) {
if (el.as(SettingsOptions).options)
options = options.concat(el.as(SettingsOptions).options);
}
}
return options.map((opt) => opt.title?.toString()).filter((el) => el);
}
/**
* Returns options available in the sidebar.
*/
get sidebar_items() {
if (!this.sidebar)
throw new InnertubeError('Sidebar not available');
return this.sidebar.items.map((item) => item.title.toString());
}
get page() {
return this.#page;
}
}
//# sourceMappingURL=Settings.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"Settings.js","sourceRoot":"","sources":["../../../../src/parser/youtube/Settings.ts"],"names":[],"mappings":"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"}
+24
View File
@@ -0,0 +1,24 @@
import type { YTNode } from '../helpers.js';
declare class DelayQueue {
front: number[];
back: number[];
constructor();
isEmpty(): boolean;
clear(): void;
getValues(): number[];
}
export default class SmoothedQueue {
#private;
constructor();
enqueueActionGroup(group: YTNode[]): void;
emitSmoothedActions(): void;
clear(): void;
set callback(cb: ((actions: YTNode[]) => void) | null);
get callback(): ((actions: YTNode[]) => void) | null;
get action_queue(): YTNode[][];
get estimated_update_interval(): number | null;
get last_update_time(): number | null;
get next_update_id(): any;
get poll_response_delay_queue(): DelayQueue;
}
export {};
+127
View File
@@ -0,0 +1,127 @@
/**
* Flattens the given queue.
* @param queue - The queue to flatten.
*/
function flattenQueue(queue) {
const nodes = [];
for (const group of queue) {
if (Array.isArray(group)) {
for (const node of group) {
nodes.push(node);
}
}
else {
nodes.push(group);
}
}
return nodes;
}
class DelayQueue {
front;
back;
constructor() {
this.front = [];
this.back = [];
}
isEmpty() {
return !this.front.length && !this.back.length;
}
clear() {
this.front = [];
this.back = [];
}
getValues() {
return this.front.concat(this.back.reverse());
}
}
export default class SmoothedQueue {
#last_update_time;
#estimated_update_interval;
#callback;
#action_queue;
#next_update_id;
#poll_response_delay_queue;
constructor() {
this.#last_update_time = null;
this.#estimated_update_interval = null;
this.#callback = null;
this.#action_queue = [];
this.#next_update_id = null;
this.#poll_response_delay_queue = new DelayQueue();
}
enqueueActionGroup(group) {
if (this.#last_update_time !== null) {
const delay = Date.now() - this.#last_update_time;
this.#poll_response_delay_queue.back.push(delay);
if (5 < (this.#poll_response_delay_queue.front.length + this.#poll_response_delay_queue.back.length)) {
if (!this.#poll_response_delay_queue.front.length) {
this.#poll_response_delay_queue.front = this.#poll_response_delay_queue.back;
this.#poll_response_delay_queue.front.reverse();
this.#poll_response_delay_queue.back = [];
}
this.#poll_response_delay_queue.front.pop();
}
this.#estimated_update_interval = Math.max(...this.#poll_response_delay_queue.getValues());
}
this.#last_update_time = Date.now();
this.#action_queue.push(group);
if (this.#next_update_id === null) {
this.#next_update_id = setTimeout(this.emitSmoothedActions.bind(this));
}
}
emitSmoothedActions() {
this.#next_update_id = null;
if (this.#action_queue.length) {
let delay = 1E4;
if (this.#estimated_update_interval !== null && this.#last_update_time !== null) {
delay = this.#estimated_update_interval - Date.now() + this.#last_update_time;
}
delay = this.#action_queue.length < delay / 80 ? 1 : Math.ceil(this.#action_queue.length / (delay / 80));
const actions = flattenQueue(this.#action_queue.splice(0, delay));
if (this.#callback) {
this.#callback(actions);
}
if (this.#action_queue !== null) {
if (delay == 1) {
delay = this.#estimated_update_interval / this.#action_queue.length;
delay *= Math.random() + 0.5;
delay = Math.min(1E3, delay);
delay = Math.max(80, delay);
}
else {
delay = 80;
}
this.#next_update_id = setTimeout(this.emitSmoothedActions.bind(this), delay);
}
}
}
clear() {
if (this.#next_update_id !== null) {
clearTimeout(this.#next_update_id);
this.#next_update_id = null;
}
this.#action_queue = [];
}
set callback(cb) {
this.#callback = cb;
}
get callback() {
return this.#callback;
}
get action_queue() {
return this.#action_queue;
}
get estimated_update_interval() {
return this.#estimated_update_interval;
}
get last_update_time() {
return this.#last_update_time;
}
get next_update_id() {
return this.#next_update_id;
}
get poll_response_delay_queue() {
return this.#poll_response_delay_queue;
}
}
//# sourceMappingURL=SmoothedQueue.js.map
@@ -0,0 +1 @@
{"version":3,"file":"SmoothedQueue.js","sourceRoot":"","sources":["../../../../src/parser/youtube/SmoothedQueue.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,SAAS,YAAY,CAAC,KAAiB;IACrC,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU;IACd,KAAK,CAAW;IAChB,IAAI,CAAW;IAEf;QACE,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;IACjB,CAAC;IAEM,OAAO;QACZ,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;IACjD,CAAC;IAEM,KAAK;QACV,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;IACjB,CAAC;IAEM,SAAS;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAChD,CAAC;CACF;AAED,MAAM,CAAC,OAAO,OAAO,aAAa;IAChC,iBAAiB,CAAgB;IACjC,0BAA0B,CAAgB;IAC1C,SAAS,CAAuC;IAChD,aAAa,CAAa;IAC1B,eAAe,CAAM;IAEZ,0BAA0B,CAAa;IAEhD;QACE,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC;QACvC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,0BAA0B,GAAG,IAAI,UAAU,EAAE,CAAC;IACrD,CAAC;IAEM,kBAAkB,CAAC,KAAe;QACvC,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;YACpC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,iBAAiB,CAAC;YAElD,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAEjD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;gBACrG,IAAI,CAAC,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;oBAClD,IAAI,CAAC,0BAA0B,CAAC,KAAK,GAAG,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC;oBAC7E,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;oBAChD,IAAI,CAAC,0BAA0B,CAAC,IAAI,GAAG,EAAE,CAAC;gBAC5C,CAAC;gBAED,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YAC9C,CAAC;YAED,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,0BAA0B,CAAC,SAAS,EAAE,CAAC,CAAC;QAC7F,CAAC;QAED,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEpC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE/B,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;YAClC,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAEM,mBAAmB;QACxB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAE5B,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,CAAC;YAC9B,IAAI,KAAK,GAAG,GAAG,CAAC;YAEhB,IAAI,IAAI,CAAC,0BAA0B,KAAK,IAAI,IAAI,IAAI,CAAC,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBAChF,KAAK,GAAG,IAAI,CAAC,0BAA0B,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,iBAAiB,CAAC;YAChF,CAAC;YAED,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;YAEzG,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;YAElE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACnB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC1B,CAAC;YAED,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI,EAAE,CAAC;gBAChC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;oBACf,KAAK,GAAG,IAAI,CAAC,0BAAoC,GAAG,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC;oBAC9E,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC;oBAC7B,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;oBAC7B,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;gBAC9B,CAAC;qBAAM,CAAC;oBACN,KAAK,GAAG,EAAE,CAAC;gBACb,CAAC;gBAED,IAAI,CAAC,eAAe,GAAG,UAAU,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;YAChF,CAAC;QACH,CAAC;IACH,CAAC;IAEM,KAAK;QACV,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,EAAE,CAAC;YAClC,YAAY,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACnC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;QAC9B,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;IAC1B,CAAC;IAED,IAAI,QAAQ,CAAC,EAAwC;QACnD,IAAI,CAAC,SAAS,GAAG,EAAE,CAAC;IACtB,CAAC;IAED,IAAI,QAAQ;QACV,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,IAAI,yBAAyB;QAC3B,OAAO,IAAI,CAAC,0BAA0B,CAAC;IACzC,CAAC;IAED,IAAI,gBAAgB;QAClB,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAChC,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED,IAAI,yBAAyB;QAC3B,OAAO,IAAI,CAAC,0BAA0B,CAAC;IACzC,CAAC;CACF"}
+22
View File
@@ -0,0 +1,22 @@
import Transcript from '../classes/Transcript.js';
import type { ApiResponse, Actions } from '../../core/index.js';
import type { IGetTranscriptResponse } from '../index.js';
export default class TranscriptInfo {
#private;
transcript: Transcript;
constructor(actions: Actions, response: ApiResponse);
/**
* Selects a language from the language menu and returns the updated transcript.
* @param language - Language to select.
*/
selectLanguage(language: string): Promise<TranscriptInfo>;
/**
* Returns available languages.
*/
get languages(): string[];
/**
* Returns the currently selected language.
*/
get selectedLanguage(): string;
get page(): IGetTranscriptResponse;
}
+45
View File
@@ -0,0 +1,45 @@
import { Parser } from '../index.js';
import Transcript from '../classes/Transcript.js';
export default class TranscriptInfo {
#page;
#actions;
transcript;
constructor(actions, response) {
this.#page = Parser.parseResponse(response.data);
this.#actions = actions;
if (!this.#page.actions_memo)
throw new Error('Page actions not found');
this.transcript = this.#page.actions_memo.getType(Transcript)[0];
}
/**
* Selects a language from the language menu and returns the updated transcript.
* @param language - Language to select.
*/
async selectLanguage(language) {
const target_menu_item = this.transcript.content?.footer?.language_menu?.sub_menu_items?.find((item) => item.title.toString() === language);
if (!target_menu_item)
throw new Error(`Language not found: ${language}`);
if (target_menu_item.selected)
return this;
const response = await this.#actions.execute('/get_transcript', {
params: target_menu_item.continuation
});
return new TranscriptInfo(this.#actions, response);
}
/**
* Returns available languages.
*/
get languages() {
return this.transcript.content?.footer?.language_menu?.sub_menu_items?.map((item) => item.title.toString()) || [];
}
/**
* Returns the currently selected language.
*/
get selectedLanguage() {
return this.transcript.content?.footer?.language_menu?.sub_menu_items?.find((item) => item.selected)?.title.toString() || '';
}
get page() {
return this.#page;
}
}
//# sourceMappingURL=TranscriptInfo.js.map
@@ -0,0 +1 @@
{"version":3,"file":"TranscriptInfo.js","sourceRoot":"","sources":["../../../../src/parser/youtube/TranscriptInfo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,UAAU,MAAM,0BAA0B,CAAC;AAKlD,MAAM,CAAC,OAAO,OAAO,cAAc;IACxB,KAAK,CAAyB;IAC9B,QAAQ,CAAU;IACpB,UAAU,CAAa;IAE9B,YAAY,OAAgB,EAAE,QAAqB;QACjD,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QACjD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QAExB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY;YAC1B,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAE5C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,cAAc,CAAC,QAAgB;QACnC,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,QAAQ,CAAC,CAAC;QAE5I,IAAI,CAAC,gBAAgB;YACnB,MAAM,IAAI,KAAK,CAAC,uBAAuB,QAAQ,EAAE,CAAC,CAAC;QAErD,IAAI,gBAAgB,CAAC,QAAQ;YAC3B,OAAO,IAAI,CAAC;QAEd,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,iBAAiB,EAAE;YAC9D,MAAM,EAAE,gBAAgB,CAAC,YAAY;SACtC,CAAC,CAAC;QAEH,OAAO,IAAI,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACrD,CAAC;IAED;;OAEG;IACH,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;IACpH,CAAC;IAED;;OAEG;IACH,IAAI,gBAAgB;QAClB,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC/H,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF"}
+99
View File
@@ -0,0 +1,99 @@
import { MediaInfo } from '../../core/mixins/index.js';
import ChipCloud from '../classes/ChipCloud.js';
import ChipCloudChip from '../classes/ChipCloudChip.js';
import CommentsEntryPointHeader from '../classes/comments/CommentsEntryPointHeader.js';
import LiveChat from '../classes/LiveChat.js';
import MerchandiseShelf from '../classes/MerchandiseShelf.js';
import PlayerOverlay from '../classes/PlayerOverlay.js';
import TwoColumnWatchNextResults from '../classes/TwoColumnWatchNextResults.js';
import VideoPrimaryInfo from '../classes/VideoPrimaryInfo.js';
import VideoSecondaryInfo from '../classes/VideoSecondaryInfo.js';
import NavigationEndpoint from '../classes/NavigationEndpoint.js';
import LiveChatWrap from './LiveChat.js';
import type { Actions, ApiResponse } from '../../core/index.js';
import type { ObservedArray, YTNode } from '../helpers.js';
import type Heatmap from '../classes/Heatmap.js';
export default class VideoInfo extends MediaInfo {
#private;
primary_info?: VideoPrimaryInfo | null;
secondary_info?: VideoSecondaryInfo | null;
playlist?: TwoColumnWatchNextResults['playlist'];
game_info?: {
title: import("../misc.js").Text | undefined;
release_year: import("../misc.js").Text | undefined;
} | undefined;
merchandise?: MerchandiseShelf | null;
related_chip_cloud?: ChipCloud | null;
watch_next_feed?: ObservedArray<YTNode> | null;
player_overlays?: PlayerOverlay | null;
comments_entry_point_header?: CommentsEntryPointHeader | null;
livechat?: LiveChat | null;
autoplay?: TwoColumnWatchNextResults['autoplay'];
heat_map?: Heatmap | null;
constructor(data: [ApiResponse, ApiResponse?], actions: Actions, cpn: string);
/**
* Applies given filter to the watch next feed. Use {@link filters} to get available filters.
* @param target_filter - Filter to apply.
*/
selectFilter(target_filter: string | ChipCloudChip | undefined): Promise<VideoInfo>;
/**
* Adds video to the watch history.
*/
addToWatchHistory(): Promise<Response>;
/**
* Updates watch time for the video.
*/
updateWatchTime(startTime: number): Promise<Response>;
/**
* Retrieves watch next feed continuation.
*/
getWatchNextContinuation(): Promise<VideoInfo>;
/**
* Likes the video.
*/
like(): Promise<ApiResponse>;
/**
* Dislikes the video.
*/
dislike(): Promise<ApiResponse>;
/**
* Removes like/dislike.
*/
removeRating(): Promise<ApiResponse>;
/**
* Retrieves Live Chat if available.
*/
getLiveChat(): LiveChatWrap;
/**
* Retrieves trailer info if available (typically for non-purchased movies or films).
* @returns `VideoInfo` for the trailer, or `null` if none.
*/
getTrailerInfo(): VideoInfo | null;
/**
* Watch next feed filters.
*/
get filters(): string[];
/**
* Checks if continuation is available for the watch next feed.
*/
get wn_has_continuation(): boolean;
/**
* Gets the endpoint of the autoplay video
*/
get autoplay_video_endpoint(): NavigationEndpoint | null;
/**
* Checks if trailer is available.
*/
get has_trailer(): boolean;
/**
* Get songs used in the video.
*/
get music_tracks(): {
song: string | undefined;
artist: string | undefined;
album: string | undefined;
license: string | undefined;
videoId: string | undefined;
channelId: string | undefined;
}[];
}
+388
View File
@@ -0,0 +1,388 @@
import { InnertubeError } from '../../utils/Utils.js';
import { MediaInfo } from '../../core/mixins/index.js';
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 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 SegmentedLikeDislikeButtonView from '../classes/SegmentedLikeDislikeButtonView.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 NavigationEndpoint from '../classes/NavigationEndpoint.js';
import PlayerLegacyDesktopYpcTrailer from '../classes/PlayerLegacyDesktopYpcTrailer.js';
import YpcTrailer from '../classes/YpcTrailer.js';
import StructuredDescriptionContent from '../classes/StructuredDescriptionContent.js';
import VideoDescriptionMusicSection from '../classes/VideoDescriptionMusicSection.js';
import LiveChatWrap from './LiveChat.js';
import MacroMarkersListEntity from '../classes/MacroMarkersListEntity.js';
import { ReloadContinuationItemsCommand } from '../index.js';
import AppendContinuationItemsAction from '../classes/actions/AppendContinuationItemsAction.js';
export default class VideoInfo extends MediaInfo {
primary_info;
secondary_info;
playlist;
game_info;
merchandise;
related_chip_cloud;
watch_next_feed;
player_overlays;
comments_entry_point_header;
livechat;
autoplay;
heat_map;
#watch_next_continuation;
constructor(data, actions, cpn) {
super(data, actions, cpn);
const [info, next] = this.page;
if (this.streaming_data) {
const default_audio_track = this.streaming_data.adaptive_formats.find((format) => format.audio_track?.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 (this.captions?.caption_tracks && this.captions?.caption_tracks.length > 0) {
// 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 auto_generated_caption_track = this.captions.caption_tracks.find((caption) => caption.kind === 'asr');
const language_code = auto_generated_caption_track?.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 = next?.contents?.item().as(TwoColumnWatchNextResults);
const results = two_col?.results;
const secondary_results = two_col?.secondary_results;
if (results && secondary_results) {
if (info.microformat?.is(PlayerMicroformat) && info.microformat?.category === 'Gaming') {
const row = results.firstOfType(VideoSecondaryInfo)?.metadata?.rows?.firstOfType(RichMetadataRow);
if (row?.is(RichMetadataRow)) {
this.game_info = {
title: row?.contents?.firstOfType(RichMetadata)?.title,
release_year: row?.contents?.firstOfType(RichMetadata)?.subtitle
};
}
}
this.primary_info = results.firstOfType(VideoPrimaryInfo);
this.secondary_info = results.firstOfType(VideoSecondaryInfo);
this.merchandise = results.firstOfType(MerchandiseShelf);
this.related_chip_cloud = secondary_results.firstOfType(RelatedChipCloud)?.content.as(ChipCloud);
if (two_col?.playlist) {
this.playlist = two_col.playlist;
}
this.watch_next_feed = secondary_results.firstOfType(ItemSection)?.contents || secondary_results;
if (this.watch_next_feed && Array.isArray(this.watch_next_feed) && this.watch_next_feed.at(-1)?.is(ContinuationItem))
this.#watch_next_continuation = this.watch_next_feed.pop()?.as(ContinuationItem);
this.player_overlays = next?.player_overlays?.item().as(PlayerOverlay);
if (two_col?.autoplay) {
this.autoplay = two_col.autoplay;
}
const segmented_like_dislike_button = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButton);
if (segmented_like_dislike_button?.like_button?.is(ToggleButton) && segmented_like_dislike_button?.dislike_button?.is(ToggleButton)) {
this.basic_info.like_count = segmented_like_dislike_button?.like_button?.like_count;
this.basic_info.is_liked = segmented_like_dislike_button?.like_button?.is_toggled;
this.basic_info.is_disliked = segmented_like_dislike_button?.dislike_button?.is_toggled;
}
const segmented_like_dislike_button_view = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButtonView);
if (segmented_like_dislike_button_view) {
this.basic_info.like_count = segmented_like_dislike_button_view.like_count;
if (segmented_like_dislike_button_view.like_button) {
const like_status = segmented_like_dislike_button_view.like_button.like_status_entity.like_status;
this.basic_info.is_liked = like_status === 'LIKE';
this.basic_info.is_disliked = like_status === 'DISLIKE';
}
}
const comments_entry_point = results.find((node) => {
return node.is(ItemSection) && node.target_id === 'comments-entry-point';
});
this.comments_entry_point_header = comments_entry_point?.contents?.firstOfType(CommentsEntryPointHeader);
this.livechat = next?.contents_memo?.getType(LiveChat)[0];
const macro_markers_list_for_heatmap = this.page[1]?.contents_memo?.getType(MacroMarkersListEntity);
let calculated_heat_map = null;
if (macro_markers_list_for_heatmap) {
const heatmap_markers_entity = macro_markers_list_for_heatmap.find((markers) => markers.isHeatmap());
if (heatmap_markers_entity) {
try {
calculated_heat_map = heatmap_markers_entity.toHeatmap();
}
catch { /** NO-OP */ }
}
}
this.heat_map = calculated_heat_map;
}
}
/**
* Applies given filter to the watch next feed. Use {@link filters} to get available filters.
* @param target_filter - Filter to apply.
*/
async selectFilter(target_filter) {
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 = this.related_chip_cloud?.chips?.find((chip) => chip.text === target_filter);
if (!filter)
throw new InnertubeError('Invalid filter', { available_filters: this.filters });
cloud_chip = filter;
}
else if (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 = await cloud_chip.endpoint?.call(this.actions, { parse: true });
const data = response?.on_response_received_endpoints?.find((endpoint) => {
return endpoint.is(ReloadContinuationItemsCommand) && endpoint.target_id === 'watch-next-feed';
});
this.watch_next_feed = data?.contents;
return this;
}
/**
* Adds video to the watch history.
*/
async addToWatchHistory() {
return super.addToWatchHistory();
}
/**
* Updates watch time for the video.
*/
async updateWatchTime(startTime) {
return super.updateWatchTime(startTime);
}
/**
* Retrieves watch next feed continuation.
*/
async getWatchNextContinuation() {
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 data = response?.on_response_received_endpoints?.firstOfType(AppendContinuationItemsAction);
if (!data)
throw new InnertubeError('AppendContinuationItemsAction not found');
this.watch_next_feed = data?.contents;
if (this.watch_next_feed?.at(-1)?.is(ContinuationItem)) {
this.#watch_next_continuation = this.watch_next_feed.pop()?.as(ContinuationItem);
}
else {
this.#watch_next_continuation = undefined;
}
return this;
}
/**
* Likes the video.
*/
async like() {
const segmented_like_dislike_button_view = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButtonView);
if (segmented_like_dislike_button_view) {
const button = segmented_like_dislike_button_view?.like_button?.toggle_button;
if (!button || !button.default_button || !segmented_like_dislike_button_view.like_button)
throw new InnertubeError('Like button not found', { video_id: this.basic_info.id });
const like_status = segmented_like_dislike_button_view.like_button.like_status_entity.like_status;
if (like_status === 'LIKE')
throw new InnertubeError('This video is already liked', { video_id: this.basic_info.id });
if (!button.default_button.on_tap)
throw new InnertubeError('onTap command not found', { video_id: this.basic_info.id });
const endpoint = new NavigationEndpoint(button.default_button.on_tap.payload.commands.find((cmd) => cmd.innertubeCommand));
return await endpoint.call(this.actions);
}
const segmented_like_dislike_button = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButton);
const button = 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 });
return await button.endpoint.call(this.actions);
}
/**
* Dislikes the video.
*/
async dislike() {
const segmented_like_dislike_button_view = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButtonView);
if (segmented_like_dislike_button_view) {
const button = segmented_like_dislike_button_view?.dislike_button?.toggle_button;
if (!button || !button.default_button || !segmented_like_dislike_button_view.dislike_button || !segmented_like_dislike_button_view.like_button)
throw new InnertubeError('Dislike button not found', { video_id: this.basic_info.id });
const like_status = segmented_like_dislike_button_view.like_button.like_status_entity.like_status;
if (like_status === 'DISLIKE')
throw new InnertubeError('This video is already disliked', { video_id: this.basic_info.id });
if (!button.default_button.on_tap)
throw new InnertubeError('onTap command not found', { video_id: this.basic_info.id });
const endpoint = new NavigationEndpoint(button.default_button.on_tap.payload.commands.find((cmd) => cmd.innertubeCommand));
return await endpoint.call(this.actions);
}
const segmented_like_dislike_button = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButton);
const button = 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 });
return await button.endpoint.call(this.actions);
}
/**
* Removes like/dislike.
*/
async removeRating() {
let button;
const segmented_like_dislike_button_view = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButtonView);
if (segmented_like_dislike_button_view) {
const toggle_button = segmented_like_dislike_button_view?.like_button?.toggle_button;
if (!toggle_button || !toggle_button.default_button || !segmented_like_dislike_button_view.like_button)
throw new InnertubeError('Like button not found', { video_id: this.basic_info.id });
const like_status = segmented_like_dislike_button_view.like_button.like_status_entity.like_status;
if (like_status === 'LIKE') {
button = segmented_like_dislike_button_view?.like_button?.toggle_button;
}
else if (like_status === 'DISLIKE') {
button = segmented_like_dislike_button_view?.dislike_button?.toggle_button;
}
else {
throw new InnertubeError('This video is not liked/disliked', { video_id: this.basic_info.id });
}
if (!button || !button.toggled_button)
throw new InnertubeError('Like/Dislike button not found', { video_id: this.basic_info.id });
if (!button.toggled_button.on_tap)
throw new InnertubeError('onTap command not found', { video_id: this.basic_info.id });
const endpoint = new NavigationEndpoint(button.toggled_button.on_tap.payload.commands.find((cmd) => cmd.innertubeCommand));
return await endpoint.call(this.actions);
}
const segmented_like_dislike_button = this.primary_info?.menu?.top_level_buttons.firstOfType(SegmentedLikeDislikeButton);
const like_button = segmented_like_dislike_button?.like_button;
const dislike_button = segmented_like_dislike_button?.dislike_button;
if (!like_button?.is(ToggleButton) || !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?.is_toggled) {
button = like_button;
}
else if (dislike_button?.is_toggled) {
button = dislike_button;
}
if (!button)
throw new InnertubeError('This video is not liked/disliked', { video_id: this.basic_info.id });
return await button.toggled_endpoint.call(this.actions);
}
/**
* 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() {
if (this.has_trailer && this.playability_status?.error_screen) {
let player_response;
if (this.playability_status.error_screen.is(PlayerLegacyDesktopYpcTrailer)) {
player_response = this.playability_status.error_screen.trailer?.player_response;
}
else if (this.playability_status.error_screen.is(YpcTrailer)) {
player_response = this.playability_status.error_screen.player_response;
}
if (player_response) {
return new VideoInfo([{ data: player_response }], this.actions, this.cpn);
}
}
return null;
}
/**
* Watch next feed filters.
*/
get filters() {
return this.related_chip_cloud?.chips?.map((chip) => chip.text?.toString()) || [];
}
/**
* Checks if continuation is available for the watch next feed.
*/
get wn_has_continuation() {
return !!this.#watch_next_continuation;
}
/**
* Gets the endpoint of the autoplay video
*/
get autoplay_video_endpoint() {
return this.autoplay?.sets?.[0]?.autoplay_video || null;
}
/**
* Checks if trailer is available.
*/
get has_trailer() {
return !!this.playability_status?.error_screen?.is(PlayerLegacyDesktopYpcTrailer, YpcTrailer);
}
/**
* 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?.filterType(VideoDescriptionMusicSection);
if (music_section !== undefined && music_section.length > 0) {
return music_section[0].carousel_lockups?.map((lookup) => {
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 = 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.default_metadata?.toString() || info_row.expanded_metadata?.toString();
if (videoId === undefined) {
const endpoint = info_row.default_metadata?.endpoint || info_row.expanded_metadata?.endpoint;
videoId = endpoint?.payload?.videoId;
}
}
else {
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.default_metadata?.toString() || info_row.expanded_metadata?.toString();
if (channelId === undefined) {
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.default_metadata?.toString() || info_row.expanded_metadata?.toString();
}
}
}
return { song, artist, album, license, videoId, channelId };
});
}
}
return [];
}
}
//# sourceMappingURL=VideoInfo.js.map
File diff suppressed because one or more lines are too long
+18
View File
@@ -0,0 +1,18 @@
export { default as AccountInfo } from './AccountInfo.js';
export { default as Channel } from './Channel.js';
export * from './Channel.js';
export { default as Comments } from './Comments.js';
export { default as Guide } from './Guide.js';
export { default as History } from './History.js';
export { default as HomeFeed } from './HomeFeed.js';
export { default as HashtagFeed } from './HashtagFeed.js';
export { default as ItemMenu } from './ItemMenu.js';
export { default as Library } from './Library.js';
export { default as LiveChat } from './LiveChat.js';
export { default as NotificationsMenu } from './NotificationsMenu.js';
export { default as Playlist } from './Playlist.js';
export { default as Search } from './Search.js';
export { default as Settings } from './Settings.js';
export { default as SmoothedQueue } from './SmoothedQueue.js';
export { default as VideoInfo } from './VideoInfo.js';
export { default as TranscriptInfo } from './TranscriptInfo.js';
+19
View File
@@ -0,0 +1,19 @@
export { default as AccountInfo } from './AccountInfo.js';
export { default as Channel } from './Channel.js';
export * from './Channel.js';
export { default as Comments } from './Comments.js';
export { default as Guide } from './Guide.js';
export { default as History } from './History.js';
export { default as HomeFeed } from './HomeFeed.js';
export { default as HashtagFeed } from './HashtagFeed.js';
export { default as ItemMenu } from './ItemMenu.js';
export { default as Library } from './Library.js';
export { default as LiveChat } from './LiveChat.js';
export { default as NotificationsMenu } from './NotificationsMenu.js';
export { default as Playlist } from './Playlist.js';
export { default as Search } from './Search.js';
export { default as Settings } from './Settings.js';
export { default as SmoothedQueue } from './SmoothedQueue.js';
export { default as VideoInfo } from './VideoInfo.js';
export { default as TranscriptInfo } from './TranscriptInfo.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/parser/youtube/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,qBAAqB,CAAC"}