'use strict'; const ResultsParser = require('../parser/contents'); const { InnertubeError } = require('../utils/Utils'); // TODO: add a way subdivide into sections and return subfeeds? class Feed { #page; /** @type {import('../parser/contents/classes/ContinuationItem')[]} */ #continuation; /** @type {import('../core/Actions')} */ #actions; memo; constructor(actions, data, already_parsed = false) { if (data.on_response_received_actions || data.on_response_received_endpoints || already_parsed) { this.#page = data; } else { this.#page = ResultsParser.parseResponse(data); } this.memo = this.#page.on_response_received_commands ? this.#page.on_response_received_commands_memo: this.#page.on_response_received_actions ? this.#page.on_response_received_actions_memo: this.#page.on_response_received_endpoints ? this.#page.on_response_received_endpoints_memo: this.#page.contents_memo; this.#actions = actions; } /** * Get the original page data */ get page() { return this.#page; } get actions() { return this.#actions; } /** * Get all videos on a given page via memo * * @param {Map} memo * @returns {Array} */ static getVideosFromMemo(memo) { const videos = memo.get('Video') || []; const grid_videos = memo.get('GridVideo') || []; const compact_videos = memo.get('CompactVideo') || []; const playlist_videos = memo.get('PlaylistVideo') || []; const playlist_panel_videos = memo.get('PlaylistPanelVideo') || []; const watch_card_compact_videos = memo.get('WatchCardCompactVideo') || []; return [ ...videos, ...grid_videos, ...compact_videos, ...playlist_videos, ...playlist_panel_videos, ...watch_card_compact_videos ]; } /** * Get all playlists on a given page via memo * * @param {Map} memo * @returns {Array} */ static getPlaylistsFromMemo(memo) { const playlists = memo.get('Playlist') || []; const grid_playlists = memo.get('GridPlaylist') || []; return [...playlists, ...grid_playlists]; } /** * Get all the videos in the feed */ get videos() { return Feed.getVideosFromMemo(this.memo); } /** * Get all playlists in the feed * * @returns {Array} */ get playlists() { return Feed.getPlaylistsFromPage(this.memo); } /** * Get all the community posts in the feed * * @returns {import('../parser/contents/classes/BackstagePost')[]} */ get backstage_posts() { return this.memo.get('BackstagePost'); } /** * Get all the channels in the feed * * @returns {Array} */ get channels() { const channels = this.memo.get('Channel') || []; const grid_channels = this.memo.get('GridChannel') || []; return [...channels, ...grid_channels]; } get has_continuation() { return (this.memo.get('ContinuationItem') || []).length > 0; } async getContinuationData() { if (this.#continuation) { if (this.#continuation.length > 1) throw new InnertubeError('There are too many continuations, you\'ll need to find the correct one yourself in this.page'); if (this.#continuation.length === 0) throw new InnertubeError('There are no continuations'); const response = await this.#continuation[0].endpoint.call(this.#actions); return response; } this.#continuation = this.memo.get('ContinuationItem'); if (this.#continuation) return this.getContinuationData(); return null; } get shelves() { return this.#page.contents_memo.get('Shelf'); } getShelf(title) { return this.shelves.find(shelf => shelf.title.toString() === title); } get shelf_content() { return this.shelves.map(shelf => ({ title: shelf.title.toString(), content: shelf.content.contents, })); } async getContinuation() { const continuation_data = await this.getContinuationData(); return new Feed(this.actions, continuation_data, true); } } module.exports = Feed;