diff --git a/deno/package.json b/deno/package.json index 33168a1e..02d7be13 100644 --- a/deno/package.json +++ b/deno/package.json @@ -1,6 +1,6 @@ { "name": "youtubei.js", - "version": "5.6.0", + "version": "5.7.0", "description": "A wrapper around YouTube's private API. Supports YouTube, YouTube Music, YouTube Kids and YouTube Studio (WIP).", "type": "module", "types": "./dist/src/platform/lib.d.ts", diff --git a/deno/src/parser/classes/Button.ts b/deno/src/parser/classes/Button.ts index 31af5b1e..b605c143 100644 --- a/deno/src/parser/classes/Button.ts +++ b/deno/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/deno/src/parser/classes/ContentPreviewImageView.ts b/deno/src/parser/classes/ContentPreviewImageView.ts new file mode 100644 index 00000000..fbbcd639 --- /dev/null +++ b/deno/src/parser/classes/ContentPreviewImageView.ts @@ -0,0 +1,16 @@ +import { YTNode } from '../helpers.ts'; +import type { RawNode } from '../index.ts'; +import Thumbnail from './misc/Thumbnail.ts'; + +export default class ContentPreviewImageView extends YTNode { + static type = 'ContentPreviewImageView'; + + image: Thumbnail[]; + style: string; + + constructor(data: RawNode) { + super(); + this.image = data.image.sources.map((x: any) => new Thumbnail(x)).sort((a: Thumbnail, b: Thumbnail) => b.width - a.width); + this.style = data.style; + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/DynamicTextView.ts b/deno/src/parser/classes/DynamicTextView.ts new file mode 100644 index 00000000..be212500 --- /dev/null +++ b/deno/src/parser/classes/DynamicTextView.ts @@ -0,0 +1,13 @@ +import { YTNode } from '../helpers.ts'; +import type { RawNode } from '../index.ts'; + +export default class DynamicTextView extends YTNode { + static type = 'DynamicTextView'; + + text: string; + + constructor(data: RawNode) { + super(); + this.text = data.text.content; + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/NavigationEndpoint.ts b/deno/src/parser/classes/NavigationEndpoint.ts index 50677b5c..ce98a877 100644 --- a/deno/src/parser/classes/NavigationEndpoint.ts +++ b/deno/src/parser/classes/NavigationEndpoint.ts @@ -4,12 +4,14 @@ import { YTNode } from '../helpers.ts'; import Parser, { type RawNode } from '../index.ts'; import type { IParsedResponse } from '../types/ParsedResponse.ts'; import CreatePlaylistDialog from './CreatePlaylistDialog.ts'; +import OpenPopupAction from './actions/OpenPopupAction.ts'; 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/deno/src/parser/classes/PageHeader.ts b/deno/src/parser/classes/PageHeader.ts new file mode 100644 index 00000000..4fae0f05 --- /dev/null +++ b/deno/src/parser/classes/PageHeader.ts @@ -0,0 +1,16 @@ +import { YTNode } from '../helpers.ts'; +import Parser, { type RawNode } from '../index.ts'; +import PageHeaderView from './PageHeaderView.ts'; + +export default class PageHeader extends YTNode { + static type = 'PageHeader'; + + page_title: string; + content: PageHeaderView | null; + + constructor(data: RawNode) { + super(); + this.page_title = data.pageTitle; + this.content = Parser.parseItem(data.content, PageHeaderView); + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/PageHeaderView.ts b/deno/src/parser/classes/PageHeaderView.ts new file mode 100644 index 00000000..62aa9d9b --- /dev/null +++ b/deno/src/parser/classes/PageHeaderView.ts @@ -0,0 +1,17 @@ +import { YTNode } from '../helpers.ts'; +import Parser, { type RawNode } from '../index.ts'; +import ContentPreviewImageView from './ContentPreviewImageView.ts'; +import DynamicTextView from './DynamicTextView.ts'; + +export default class PageHeaderView extends YTNode { + static type = 'PageHeaderView'; + + image: ContentPreviewImageView | null; + title: DynamicTextView | null; + + constructor(data: RawNode) { + super(); + this.image = Parser.parseItem(data.image, ContentPreviewImageView); + this.title = Parser.parseItem(data.title, DynamicTextView); + } +} \ No newline at end of file diff --git a/deno/src/parser/classes/SearchFilterOptionsDialog.ts b/deno/src/parser/classes/SearchFilterOptionsDialog.ts new file mode 100644 index 00000000..d0905034 --- /dev/null +++ b/deno/src/parser/classes/SearchFilterOptionsDialog.ts @@ -0,0 +1,18 @@ +import type { ObservedArray } from '../helpers.ts'; +import { YTNode } from '../helpers.ts'; +import { Parser, type RawNode } from '../index.ts'; +import SearchFilterGroup from './SearchFilterGroup.ts'; +import Text from './misc/Text.ts'; + +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/deno/src/parser/classes/SearchHeader.ts b/deno/src/parser/classes/SearchHeader.ts new file mode 100644 index 00000000..c63b3c53 --- /dev/null +++ b/deno/src/parser/classes/SearchHeader.ts @@ -0,0 +1,18 @@ +import { YTNode } from '../helpers.ts'; +import { Parser, type RawNode } from '../index.ts'; +import Button from './Button.ts'; +import ChipCloud from './ChipCloud.ts'; + +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/deno/src/parser/classes/SearchSubMenu.ts b/deno/src/parser/classes/SearchSubMenu.ts index 461ac744..21886f26 100644 --- a/deno/src/parser/classes/SearchSubMenu.ts +++ b/deno/src/parser/classes/SearchSubMenu.ts @@ -8,14 +8,19 @@ import ToggleButton from './ToggleButton.ts'; 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/deno/src/parser/nodes.ts b/deno/src/parser/nodes.ts index 54552670..b0992caf 100644 --- a/deno/src/parser/nodes.ts +++ b/deno/src/parser/nodes.ts @@ -71,6 +71,7 @@ export { default as CompactPlaylist } from './classes/CompactPlaylist.ts'; export { default as CompactStation } from './classes/CompactStation.ts'; export { default as CompactVideo } from './classes/CompactVideo.ts'; export { default as ConfirmDialog } from './classes/ConfirmDialog.ts'; +export { default as ContentPreviewImageView } from './classes/ContentPreviewImageView.ts'; export { default as ContinuationItem } from './classes/ContinuationItem.ts'; export { default as ConversationBar } from './classes/ConversationBar.ts'; export { default as CopyLink } from './classes/CopyLink.ts'; @@ -81,6 +82,7 @@ export { default as DidYouMean } from './classes/DidYouMean.ts'; export { default as DownloadButton } from './classes/DownloadButton.ts'; export { default as Dropdown } from './classes/Dropdown.ts'; export { default as DropdownItem } from './classes/DropdownItem.ts'; +export { default as DynamicTextView } from './classes/DynamicTextView.ts'; export { default as Element } from './classes/Element.ts'; export { default as EmergencyOnebox } from './classes/EmergencyOnebox.ts'; export { default as EmojiPickerCategory } from './classes/EmojiPickerCategory.ts'; @@ -238,6 +240,8 @@ export { default as MusicTwoRowItem } from './classes/MusicTwoRowItem.ts'; export { default as MusicVisualHeader } from './classes/MusicVisualHeader.ts'; export { default as NavigationEndpoint } from './classes/NavigationEndpoint.ts'; export { default as Notification } from './classes/Notification.ts'; +export { default as PageHeader } from './classes/PageHeader.ts'; +export { default as PageHeaderView } from './classes/PageHeaderView.ts'; export { default as PageIntroduction } from './classes/PageIntroduction.ts'; export { default as PlayerAnnotationsExpanded } from './classes/PlayerAnnotationsExpanded.ts'; export { default as PlayerCaptionsTracklist } from './classes/PlayerCaptionsTracklist.ts'; @@ -285,6 +289,8 @@ export { default as RichShelf } from './classes/RichShelf.ts'; export { default as SearchBox } from './classes/SearchBox.ts'; export { default as SearchFilter } from './classes/SearchFilter.ts'; export { default as SearchFilterGroup } from './classes/SearchFilterGroup.ts'; +export { default as SearchFilterOptionsDialog } from './classes/SearchFilterOptionsDialog.ts'; +export { default as SearchHeader } from './classes/SearchHeader.ts'; export { default as SearchRefinementCard } from './classes/SearchRefinementCard.ts'; export { default as SearchSubMenu } from './classes/SearchSubMenu.ts'; export { default as SearchSuggestion } from './classes/SearchSuggestion.ts'; diff --git a/deno/src/parser/youtube/Channel.ts b/deno/src/parser/youtube/Channel.ts index 2a71c50f..c49e2e84 100644 --- a/deno/src/parser/youtube/Channel.ts +++ b/deno/src/parser/youtube/Channel.ts @@ -9,6 +9,7 @@ import SubscribeButton from '../classes/SubscribeButton.ts'; import ExpandableTab from '../classes/ExpandableTab.ts'; import SectionList from '../classes/SectionList.ts'; import Tab from '../classes/Tab.ts'; +import PageHeader from '../classes/PageHeader.ts'; import Feed from '../../core/mixins/Feed.ts'; import FilterableFeed from '../../core/mixins/FilterableFeed.ts'; @@ -25,7 +26,7 @@ import type { ApiResponse } from '../../core/Actions.ts'; import type { IBrowseResponse } from '../types/index.ts'; export default class Channel extends TabbedFeed { - header?: C4TabbedHeader | CarouselHeader | InteractiveTabbedHeader; + header?: C4TabbedHeader | CarouselHeader | InteractiveTabbedHeader | PageHeader; metadata; subscribe_button?: SubscribeButton; current_tab?: Tab | ExpandableTab; @@ -33,7 +34,7 @@ export default class Channel extends TabbedFeed { constructor(actions: Actions, data: ApiResponse | IBrowseResponse, already_parsed = false) { super(actions, data, already_parsed); - this.header = this.page.header?.item()?.as(C4TabbedHeader, CarouselHeader, InteractiveTabbedHeader); + 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); diff --git a/deno/src/parser/youtube/Search.ts b/deno/src/parser/youtube/Search.ts index 7a3c43d4..c5a2ac66 100644 --- a/deno/src/parser/youtube/Search.ts +++ b/deno/src/parser/youtube/Search.ts @@ -2,6 +2,7 @@ import Feed from '../../core/mixins/Feed.ts'; import { InnertubeError } from '../../utils/Utils.ts'; import HorizontalCardList from '../classes/HorizontalCardList.ts'; import ItemSection from '../classes/ItemSection.ts'; +import SearchHeader from '../classes/SearchHeader.ts'; import SearchRefinementCard from '../classes/SearchRefinementCard.ts'; import SearchSubMenu from '../classes/SearchSubMenu.ts'; import SectionList from '../classes/SectionList.ts'; @@ -11,8 +12,8 @@ import type Actions from '../../core/Actions.ts'; import type { ApiResponse } from '../../core/Actions.ts'; import type { ObservedArray, YTNode } from '../helpers.ts'; import type { ISearchResponse } from '../types/ParsedResponse.ts'; - 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 || [];