UNPKG

@imput/youtubei.js

Version:

A JavaScript client for YouTube's private API, known as InnerTube. Fork of youtubei.js

267 lines 12.2 kB
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 { 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.get({ selected: true }); } /** * 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.get({ 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('community'); 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('community'); } 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 { 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 { constructor(actions, data, already_parsed = false) { super(actions, data, already_parsed); this.applied_filter = this.memo.getType(ChipCloudChip).get({ is_selected: true }); // 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