From 40fc24b043ce030df788d3a9ceec24938494b132 Mon Sep 17 00:00:00 2001 From: LuanRT Date: Sun, 7 Aug 2022 06:15:55 -0300 Subject: [PATCH] refactor!: fix inconsistent use of `SuperParsedResult` --- src/core/Feed.ts | 2 +- src/core/Music.ts | 4 +- src/parser/classes/PlaylistPanel.ts | 4 +- src/parser/classes/Tab.ts | 6 +- src/parser/classes/misc/Author.js | 6 +- src/parser/youtube/Library.ts | 5 +- src/parser/ytmusic/Album.ts | 3 +- src/parser/ytmusic/Artist.ts | 2 +- src/parser/ytmusic/Explore.ts | 8 ++- src/parser/ytmusic/HomeFeed.ts | 29 +++++---- src/parser/ytmusic/Search.ts | 93 ++++++++++++++--------------- 11 files changed, 84 insertions(+), 78 deletions(-) diff --git a/src/core/Feed.ts b/src/core/Feed.ts index 04b6cd44..106bf167 100644 --- a/src/core/Feed.ts +++ b/src/core/Feed.ts @@ -119,7 +119,7 @@ class Feed { * Returns contents from the page. */ get contents() { - const tab_content = this.#memo.getType(Tab)?.[0]?.content.item(); + const tab_content = this.#memo.getType(Tab)?.[0]?.content; const reload_continuation_items = this.#memo.getType(ReloadContinuationItemsCommand)?.[0]; const append_continuation_items = this.#memo.getType(AppendContinuationItemsAction)?.[0]; diff --git a/src/core/Music.ts b/src/core/Music.ts index 4d01fa92..81a20cad 100644 --- a/src/core/Music.ts +++ b/src/core/Music.ts @@ -167,9 +167,9 @@ class Music { if (!tab) throw new InnertubeError('Could not find target tab.'); - const music_queue = tab.content.item().as(MusicQueue); + const music_queue = tab.content?.as(MusicQueue); - if (!music_queue.content) + if (!music_queue || !music_queue.content) throw new InnertubeError('Music queue was empty, the given id is probably invalid.', music_queue); const playlist_panel = music_queue.content.item().as(PlaylistPanel); diff --git a/src/parser/classes/PlaylistPanel.ts b/src/parser/classes/PlaylistPanel.ts index d61a1f4d..012f5532 100644 --- a/src/parser/classes/PlaylistPanel.ts +++ b/src/parser/classes/PlaylistPanel.ts @@ -1,5 +1,7 @@ import Parser from '../index'; import Text from './misc/Text'; +import PlaylistPanelVideo from './PlaylistPanelVideo'; + import { YTNode } from '../helpers'; class PlaylistPanel extends YTNode { @@ -19,7 +21,7 @@ class PlaylistPanel extends YTNode { super(); this.title = data.title; this.title_text = new Text(data.titleText); - this.contents = Parser.parse(data.contents); + this.contents = Parser.parseArray(data.contents, PlaylistPanelVideo); this.playlist_id = data.playlistId; this.is_infinite = data.isInfinite; this.continuation = data.continuations[0]?.nextRadioContinuationData?.continuation; diff --git a/src/parser/classes/Tab.ts b/src/parser/classes/Tab.ts index e2b36438..08f63822 100644 --- a/src/parser/classes/Tab.ts +++ b/src/parser/classes/Tab.ts @@ -1,5 +1,9 @@ import Parser from '../index'; import NavigationEndpoint from './NavigationEndpoint'; +import SectionList from './SectionList'; +import MusicQueue from './MusicQueue'; +import RichGrid from './RichGrid'; + import { YTNode } from '../helpers'; class Tab extends YTNode { @@ -15,7 +19,7 @@ class Tab extends YTNode { this.title = data.title || 'N/A'; this.selected = data.selected || false; this.endpoint = new NavigationEndpoint(data.endpoint); - this.content = Parser.parse(data.content); + this.content = Parser.parseItem(data.content, [ SectionList, MusicQueue, RichGrid ]); } } diff --git a/src/parser/classes/misc/Author.js b/src/parser/classes/misc/Author.js index ad31b645..99674d5a 100644 --- a/src/parser/classes/misc/Author.js +++ b/src/parser/classes/misc/Author.js @@ -10,7 +10,7 @@ class Author { this.#nav_text = new NavigatableText(item); this.id = - this.#nav_text.runs?.[0].endpoint.browse?.id || + this.#nav_text.runs?.[0].endpoint?.browse?.id || this.#nav_text.endpoint?.browse?.id || 'N/A'; this.name = this.#nav_text.text || 'N/A'; @@ -21,8 +21,8 @@ class Author { this.is_verified_artist = this.badges?.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') || null; this.url = - this.#nav_text.runs?.[0].endpoint.browse && - `${Constants.URLS.YT_BASE}${this.#nav_text.runs[0].endpoint.browse?.base_url || `/u/${this.#nav_text.runs[0].endpoint.browse?.id}`}` || + this.#nav_text.runs?.[0].endpoint?.browse && + `${Constants.URLS.YT_BASE}${this.#nav_text.runs[0].endpoint?.browse?.base_url || `/u/${this.#nav_text.runs[0].endpoint?.browse?.id}`}` || `${Constants.URLS.YT_BASE}${this.#nav_text.endpoint?.browse?.base_url || `/u/${this.#nav_text.endpoint?.browse?.id}`}` || null; } diff --git a/src/parser/youtube/Library.ts b/src/parser/youtube/Library.ts index e83f40df..b6c70180 100644 --- a/src/parser/youtube/Library.ts +++ b/src/parser/youtube/Library.ts @@ -44,7 +44,10 @@ class Library { this.profile = { stats, user_info }; - const shelves = tab.content.item().as(SectionList).contents.array().as(ItemSection).map((is: ItemSection) => is.contents?.firstOfType(Shelf)); + if (!tab.content) + throw new InnertubeError('Target tab did not have any content.'); + + const shelves = tab.content.as(SectionList).contents.array().as(ItemSection).map((is: ItemSection) => is.contents?.firstOfType(Shelf)); this.sections = shelves.map((shelf: any) => ({ type: shelf.icon_type, diff --git a/src/parser/ytmusic/Album.ts b/src/parser/ytmusic/Album.ts index fe3dbaf1..1863338c 100644 --- a/src/parser/ytmusic/Album.ts +++ b/src/parser/ytmusic/Album.ts @@ -4,7 +4,6 @@ import Actions, { AxioslikeResponse } from '../../core/Actions'; import MusicDetailHeader from '../classes/MusicDetailHeader'; import MicroformatData from '../classes/MicroformatData'; import MusicCarouselShelf from '../classes/MusicCarouselShelf'; -import MusicResponsiveListItem from '../classes/MusicResponsiveListItem'; import MusicShelf from '../classes/MusicShelf'; class Album { @@ -24,7 +23,7 @@ class Album { this.header = this.#page.header.item().as(MusicDetailHeader); this.url = this.#page.microformat?.as(MicroformatData).url_canonical || null; - this.contents = this.#page.contents_memo.get('MusicShelf')?.[0].as(MusicShelf).contents.array().as(MusicResponsiveListItem); + this.contents = this.#page.contents_memo.get('MusicShelf')?.[0].as(MusicShelf).contents; this.sections = this.#page.contents_memo.get('MusicCarouselShelf') as MusicCarouselShelf[] || ([] as MusicCarouselShelf[]); } diff --git a/src/parser/ytmusic/Artist.ts b/src/parser/ytmusic/Artist.ts index 8a8bb8bb..7491e983 100644 --- a/src/parser/ytmusic/Artist.ts +++ b/src/parser/ytmusic/Artist.ts @@ -32,7 +32,7 @@ class Artist { if (!music_shelves.length) throw new InnertubeError('Could not find any node of type MusicShelf.'); - const shelf = music_shelves.find((shelf) => shelf.title === 'Songs') as MusicShelf; + const shelf = music_shelves.find((shelf) => shelf.title.toString() === 'Songs') as MusicShelf; if (!shelf) throw new InnertubeError('Could not find target shelf (Songs).'); diff --git a/src/parser/ytmusic/Explore.ts b/src/parser/ytmusic/Explore.ts index 139d58c7..e33c4169 100644 --- a/src/parser/ytmusic/Explore.ts +++ b/src/parser/ytmusic/Explore.ts @@ -3,7 +3,6 @@ import Parser, { ParsedResponse } from '..'; import { InnertubeError } from '../../utils/Utils'; import { AxioslikeResponse } from '../../core/Actions'; -import Tab from '../classes/Tab'; import Grid from '../classes/Grid'; import SectionList from '../classes/SectionList'; import SingleColumnBrowseResults from '../classes/SingleColumnBrowseResults'; @@ -19,12 +18,15 @@ class Explore { constructor(response: AxioslikeResponse) { this.#page = Parser.parseResponse(response.data); - const tab = this.#page.contents.item().as(SingleColumnBrowseResults).tabs.array().as(Tab).get({ selected: true }); + const tab = this.#page.contents.item().as(SingleColumnBrowseResults).tabs.get({ selected: true }); if (!tab) throw new InnertubeError('Could not find target tab.'); - const section_list = tab.content.item().as(SectionList); + const section_list = tab.content?.as(SectionList); + + if (!section_list) + throw new InnertubeError('Target tab did not have any content.'); this.top_buttons = section_list.contents.array().firstOfType(Grid)?.items.array().as(MusicNavigationButton) || ([] as MusicNavigationButton[]); this.sections = section_list.contents.array().getAll({ type: 'MusicCarouselShelf' }) as MusicCarouselShelf[]; diff --git a/src/parser/ytmusic/HomeFeed.ts b/src/parser/ytmusic/HomeFeed.ts index 85abe32e..ad64ed88 100644 --- a/src/parser/ytmusic/HomeFeed.ts +++ b/src/parser/ytmusic/HomeFeed.ts @@ -1,9 +1,10 @@ -import Parser, { ParsedResponse } from '../index'; +import Parser, { ParsedResponse, SectionListContinuation } from '../index'; import Actions, { AxioslikeResponse } from '../../core/Actions'; import { InnertubeError } from '../../utils/Utils'; -import MusicCarouselShelfBasicHeader from '../classes/MusicCarouselShelfBasicHeader'; -import MusicTwoRowItem from '../classes/MusicTwoRowItem'; +import SectionList from '../classes/SectionList'; +import SingleColumnBrowseResults from '../classes/SingleColumnBrowseResults'; +import MusicCarouselShelf from '../classes/MusicCarouselShelf'; class HomeFeed { #page; @@ -16,25 +17,23 @@ class HomeFeed { this.#actions = actions; this.#page = Parser.parseResponse((response as AxioslikeResponse).data); - const tab = this.#page.contents.item().key('tabs').parsed().array().get({ selected: true }); + const tab = this.#page.contents.item().as(SingleColumnBrowseResults).tabs.get({ selected: true }); if (!tab) throw new InnertubeError('Could not get Home tab.'); - let contents; - if (tab.key('content').isNull()) { - this.#continuation = this.#page.continuation_contents?.key('continuation').string(); - contents = this.#page.continuation_contents?.key('contents').array(); - } else { - this.#continuation = tab.key('content').parsed().item().key('continuation').string(); - contents = tab.key('content').parsed().item().key('contents').parsed().array(); + if (!this.#page.continuation_contents) + throw new InnertubeError('Continuation did not have any content.'); + + this.#continuation = this.#page.continuation_contents.as(SectionListContinuation).continuation; + this.sections = this.#page.continuation_contents.as(SectionListContinuation).contents?.as(MusicCarouselShelf); + + return; } - this.sections = contents?.map((content) => ({ - header: content.header.item() as MusicCarouselShelfBasicHeader, - contents: content.contents.array() as Array - })); + this.#continuation = tab.content?.as(SectionList).continuation; + this.sections = tab.content?.as(SectionList).contents.array().as(MusicCarouselShelf); } /** diff --git a/src/parser/ytmusic/Search.ts b/src/parser/ytmusic/Search.ts index 857b8635..5591f6de 100644 --- a/src/parser/ytmusic/Search.ts +++ b/src/parser/ytmusic/Search.ts @@ -1,29 +1,32 @@ +import Parser, { ParsedResponse } from '../index'; +import Actions, { AxioslikeResponse } from '../../core/Actions'; + +import { InnertubeError } from '../../utils/Utils'; + +import SectionList from '../classes/SectionList'; +import TabbedSearchResults from '../classes/TabbedSearchResults'; + import DidYouMean from '../classes/DidYouMean'; import ShowingResultsFor from '../classes/ShowingResultsFor'; import MusicShelf from '../classes/MusicShelf'; import MusicResponsiveListItem from '../classes/MusicResponsiveListItem'; -import Shelf from '../classes/Shelf'; -import RichShelf from '../classes/RichShelf'; -import ReelShelf from '../classes/ReelShelf'; import ChipCloud from '../classes/ChipCloud'; import ChipCloudChip from '../classes/ChipCloudChip'; import ItemSection from '../classes/ItemSection'; import Message from '../classes/Message'; -import Parser, { ParsedResponse } from '../index'; -import { InnertubeError } from '../../utils/Utils'; -import Actions, { AxioslikeResponse } from '../../core/Actions'; - class Search { #page; #actions; #continuation; - #header; - did_you_mean; - showing_results_for; - message; + header; + + did_you_mean: DidYouMean | null; + showing_results_for: ShowingResultsFor | null; + message: Message | null; + results; sections; @@ -34,44 +37,39 @@ class Search { response as ParsedResponse : Parser.parseResponse((response as AxioslikeResponse).data); - const tab = this.#page.contents.item().key('tabs').parsed().array().get({ selected: true }); + const tab = this.#page.contents.item().as(TabbedSearchResults).tabs.get({ selected: true }); if (!tab) throw new InnertubeError('Could not find target tab.'); - const tab_content = tab.key('content').parsed().item(); + const tab_content = tab.content?.as(SectionList); - if (tab_content.hasKey('header')) { - this.#header = tab_content.key('header').parsed().item().as(ChipCloud); - } + if (!tab_content) + throw new InnertubeError('Target tab did not have any content.'); - const shelves = tab_content.key('contents').parsed().array(); + this.header = tab_content.hasKey('header') ? tab_content.header?.item().as(ChipCloud) : null; + + const shelves = tab_content.contents.array().as(MusicShelf, ItemSection); const item_section = shelves.firstOfType(ItemSection); this.did_you_mean = item_section?.contents?.firstOfType(DidYouMean) || null; this.showing_results_for = item_section?.contents?.firstOfType(ShowingResultsFor) || null; this.message = item_section?.contents?.firstOfType(Message) || null; - if (!!this.did_you_mean || !!this.showing_results_for || !!this.message) - shelves.shift(); - if (args.is_continuation || args.is_filtered) { - const shelf = shelves.firstOfType(MusicShelf); - this.results = shelf?.contents.array().as(MusicResponsiveListItem); - this.#continuation = shelf?.continuation; - return; + this.results = shelves.firstOfType(MusicShelf)?.contents; + this.#continuation = shelves.firstOfType(MusicShelf)?.continuation; + } else { + this.sections = shelves.filterType(MusicShelf); } - - this.sections = shelves.as(MusicShelf, Shelf, RichShelf, ReelShelf).map((shelf) => ({ - title: shelf.title.toString(), - contents: shelf.key('contents').parsed().array().as(MusicResponsiveListItem), - getMore: () => this.#getMore(shelf) - })); } - async #getMore(shelf: MusicShelf | Shelf | RichShelf | ReelShelf): Promise { - if (!shelf.endpoint) - throw new InnertubeError(`${shelf.title} doesn't have more items`); + /** + * Equivalent to clicking on the shelf to load more items. + */ + async getMore(shelf: MusicShelf | undefined): Promise { + if (!shelf || !shelf.endpoint) + throw new InnertubeError('Cannot retrieve more items for this shelf because it does not have an endpoint.'); const response = await shelf.endpoint.call(this.#actions, 'YTMUSIC', true); @@ -104,8 +102,7 @@ class Search { if (!this.filters?.includes(name)) throw new InnertubeError('Invalid filter', { available_filters: this.filters }); - // TODO: make sure this is a ChipCloudChip or use the property accessor helpers on YTNode - const filter = this.#header?.chips?.as(ChipCloudChip).get({ text: name }); + const filter = this.header?.chips?.as(ChipCloudChip).get({ text: name }); if (filter?.is_selected) return this; @@ -122,32 +119,32 @@ class Search { } get filters(): string[] | null { - return this.#header?.chips?.as(ChipCloudChip).map((chip) => chip.text) || null; + return this.header?.chips?.as(ChipCloudChip).map((chip) => chip.text) || null; } - get songs() { - return this.sections?.find((section) => section.title === 'Songs'); + get songs(): MusicShelf | undefined { + return this.sections?.find((section) => section.title.toString() === 'Songs'); } - get videos() { - return this.sections?.find((section) => section.title === 'Videos'); + get videos(): MusicShelf | undefined { + return this.sections?.find((section) => section.title.toString() === 'Videos'); } - get albums() { - return this.sections?.find((section) => section.title === 'Albums'); + get albums(): MusicShelf | undefined { + return this.sections?.find((section) => section.title.toString() === 'Albums'); } - get artists() { - return this.sections?.find((section) => section.title === 'Artists'); + get artists(): MusicShelf | undefined { + return this.sections?.find((section) => section.title.toString() === 'Artists'); } - get playlists() { - return this.sections?.find((section) => section.title === 'Community playlists'); + get playlists(): MusicShelf | undefined { + return this.sections?.find((section) => section.title.toString() === 'Community playlists'); } - get page() { + get page(): ParsedResponse { return this.#page; } } -export default Search; +export default Search; \ No newline at end of file