From 6997982cf2db87edf4929e9a77e2690e7b630d3d Mon Sep 17 00:00:00 2001 From: LuanRT Date: Mon, 24 Jul 2023 20:26:05 -0300 Subject: [PATCH] feat(parser): Add `SearchHeader` We may want to remove the old SearchSubMenu node in the future but YouTube still uses it sometimes, so we will keep it for now. Closes #452 --- src/parser/classes/Button.ts | 16 +++++----------- src/parser/classes/NavigationEndpoint.ts | 12 +++++++++--- .../classes/SearchFilterOptionsDialog.ts | 18 ++++++++++++++++++ src/parser/classes/SearchHeader.ts | 18 ++++++++++++++++++ src/parser/classes/SearchSubMenu.ts | 17 +++++++++++------ src/parser/nodes.ts | 2 ++ src/parser/youtube/Search.ts | 6 +++++- 7 files changed, 68 insertions(+), 21 deletions(-) create mode 100644 src/parser/classes/SearchFilterOptionsDialog.ts create mode 100644 src/parser/classes/SearchHeader.ts diff --git a/src/parser/classes/Button.ts b/src/parser/classes/Button.ts index 3d83207a2..46e37c92c 100644 --- a/src/parser/classes/Button.ts +++ b/src/parser/classes/Button.ts @@ -15,26 +15,20 @@ export default class Button extends YTNode { constructor(data: RawNode) { super(); - - if (Reflect.has(data, 'text')) { + if (Reflect.has(data, 'text')) this.text = new Text(data.text).toString(); - } - if (Reflect.has(data, 'accessibility') && Reflect.has(data.accessibility, 'label')) { + if (Reflect.has(data, 'accessibility') && Reflect.has(data.accessibility, 'label')) this.label = data.accessibility.label; - } - if (Reflect.has(data, 'tooltip')) { + if (Reflect.has(data, 'tooltip')) this.tooltip = data.tooltip; - } - if (Reflect.has(data, 'icon') && Reflect.has(data.icon, 'iconType')) { + if (Reflect.has(data, 'icon') && Reflect.has(data.icon, 'iconType')) this.icon_type = data.icon.iconType; - } - if (Reflect.has(data, 'isDisabled')) { + if (Reflect.has(data, 'isDisabled')) this.is_disabled = data.isDisabled; - } this.endpoint = new NavigationEndpoint(data.navigationEndpoint || data.serviceEndpoint || data.command); } diff --git a/src/parser/classes/NavigationEndpoint.ts b/src/parser/classes/NavigationEndpoint.ts index 05226c373..793f28826 100644 --- a/src/parser/classes/NavigationEndpoint.ts +++ b/src/parser/classes/NavigationEndpoint.ts @@ -4,12 +4,14 @@ import { YTNode } from '../helpers.js'; import Parser, { type RawNode } from '../index.js'; import type { IParsedResponse } from '../types/ParsedResponse.js'; import CreatePlaylistDialog from './CreatePlaylistDialog.js'; +import OpenPopupAction from './actions/OpenPopupAction.js'; export default class NavigationEndpoint extends YTNode { static type = 'NavigationEndpoint'; payload; dialog?: CreatePlaylistDialog | YTNode | null; + open_popup?: OpenPopupAction | null; metadata: { url?: string; @@ -24,6 +26,9 @@ export default class NavigationEndpoint extends YTNode { if (Reflect.has(data || {}, 'innertubeCommand')) data = data.innertubeCommand; + if (Reflect.has(data || {}, 'openPopupAction')) + this.open_popup = new OpenPopupAction(data.openPopupAction); + const name = Object.keys(data || {}) .find((item) => item.endsWith('Endpoint') || @@ -36,6 +41,7 @@ export default class NavigationEndpoint extends YTNode { this.dialog = Parser.parseItem(this.payload.dialog || this.payload.content); } + if (data?.serviceEndpoint) { data = data.serviceEndpoint; } @@ -85,9 +91,9 @@ export default class NavigationEndpoint extends YTNode { } } - call(actions: Actions, args: { [ key: string ]: any; parse: true }): Promise; - call(actions: Actions, args?: { [ key: string ]: any; parse?: false }): Promise; - call(actions: Actions, args?: { [ key: string ]: any; parse?: boolean }): Promise { + call(actions: Actions, args: { [key: string]: any; parse: true }): Promise; + call(actions: Actions, args?: { [key: string]: any; parse?: false }): Promise; + call(actions: Actions, args?: { [key: string]: any; parse?: boolean }): Promise { if (!actions) throw new Error('An active caller must be provided'); if (!this.metadata.api_url) diff --git a/src/parser/classes/SearchFilterOptionsDialog.ts b/src/parser/classes/SearchFilterOptionsDialog.ts new file mode 100644 index 000000000..915999b20 --- /dev/null +++ b/src/parser/classes/SearchFilterOptionsDialog.ts @@ -0,0 +1,18 @@ +import type { ObservedArray } from '../helpers.js'; +import { YTNode } from '../helpers.js'; +import { Parser, type RawNode } from '../index.js'; +import SearchFilterGroup from './SearchFilterGroup.js'; +import Text from './misc/Text.js'; + +export default class SearchFilterOptionsDialog extends YTNode { + static type = 'SearchFilterOptionsDialog'; + + title: Text; + groups: ObservedArray; + + constructor(data: RawNode) { + super(); + this.title = new Text(data.title); + this.groups = Parser.parseArray(data.groups, SearchFilterGroup); + } +} \ No newline at end of file diff --git a/src/parser/classes/SearchHeader.ts b/src/parser/classes/SearchHeader.ts new file mode 100644 index 000000000..1b431ad1f --- /dev/null +++ b/src/parser/classes/SearchHeader.ts @@ -0,0 +1,18 @@ +import { YTNode } from '../helpers.js'; +import { Parser, type RawNode } from '../index.js'; +import Button from './Button.js'; +import ChipCloud from './ChipCloud.js'; + +export default class SearchHeader extends YTNode { + static type = 'SearchHeader'; + + chip_bar: ChipCloud | null; + search_filter_button: Button | null; + + constructor(data: RawNode) { + super(); + this.chip_bar = Parser.parseItem(data.chipBar, ChipCloud); + this.search_filter_button = Parser.parseItem(data.searchFilterButton, Button); + console.log(this.search_filter_button?.endpoint.open_popup); + } +} \ No newline at end of file diff --git a/src/parser/classes/SearchSubMenu.ts b/src/parser/classes/SearchSubMenu.ts index 67b2882cf..6a88035a1 100644 --- a/src/parser/classes/SearchSubMenu.ts +++ b/src/parser/classes/SearchSubMenu.ts @@ -8,14 +8,19 @@ import ToggleButton from './ToggleButton.js'; export default class SearchSubMenu extends YTNode { static type = 'SearchSubMenu'; - title: Text; - groups: ObservedArray; - button: ToggleButton | null; + title?: Text; + groups?: ObservedArray; + button?: ToggleButton | null; constructor(data: RawNode) { super(); - this.title = new Text(data.title); - this.groups = Parser.parseArray(data.groups, SearchFilterGroup); - this.button = Parser.parseItem(data.button, ToggleButton); + if (Reflect.has(data, 'title')) + this.title = new Text(data.title); + + if (!Reflect.has(data, 'groups')) + this.groups = Parser.parseArray(data.groups, SearchFilterGroup); + + if (Reflect.has(data, 'button')) + this.button = Parser.parseItem(data.button, ToggleButton); } } \ No newline at end of file diff --git a/src/parser/nodes.ts b/src/parser/nodes.ts index 2a6ac79be..b800a06a0 100644 --- a/src/parser/nodes.ts +++ b/src/parser/nodes.ts @@ -289,6 +289,8 @@ export { default as RichShelf } from './classes/RichShelf.js'; export { default as SearchBox } from './classes/SearchBox.js'; export { default as SearchFilter } from './classes/SearchFilter.js'; export { default as SearchFilterGroup } from './classes/SearchFilterGroup.js'; +export { default as SearchFilterOptionsDialog } from './classes/SearchFilterOptionsDialog.js'; +export { default as SearchHeader } from './classes/SearchHeader.js'; export { default as SearchRefinementCard } from './classes/SearchRefinementCard.js'; export { default as SearchSubMenu } from './classes/SearchSubMenu.js'; export { default as SearchSuggestion } from './classes/SearchSuggestion.js'; diff --git a/src/parser/youtube/Search.ts b/src/parser/youtube/Search.ts index 5999c46c4..f083323c2 100644 --- a/src/parser/youtube/Search.ts +++ b/src/parser/youtube/Search.ts @@ -2,6 +2,7 @@ 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'; @@ -11,8 +12,8 @@ import type Actions from '../../core/Actions.js'; import type { ApiResponse } from '../../core/Actions.js'; import type { ObservedArray, YTNode } from '../helpers.js'; import type { ISearchResponse } from '../types/ParsedResponse.js'; - class Search extends Feed { + header?: SearchHeader; results?: ObservedArray | null; refinements: string[]; estimated_results: number; @@ -30,6 +31,9 @@ class Search extends Feed { 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 = contents.find((content) => content.is(ItemSection) && content.contents && content.contents.length > 0)?.as(ItemSection).contents; this.refinements = this.page.refinements || [];