diff --git a/CHANGELOG.md b/CHANGELOG.md index d3f740500..4b561d0ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## [3.2.0](https://github.com/LuanRT/YouTube.js/compare/v3.1.1...v3.2.0) (2023-03-08) + + +### Features + +* Add support for descriptive audio tracks ([#338](https://github.com/LuanRT/YouTube.js/issues/338)) ([574b67a](https://github.com/LuanRT/YouTube.js/commit/574b67a1f707a32378586dd2fe7b2f36f4ab6ddb)) +* export `FormatUtils`' types ([2d774e2](https://github.com/LuanRT/YouTube.js/commit/2d774e26aae79f3d1b115e0e85c148ae80985529)) +* **parser:** add `banner` to `PlaylistHeader` ([#337](https://github.com/LuanRT/YouTube.js/issues/337)) ([95033e7](https://github.com/LuanRT/YouTube.js/commit/95033e723ef912706e4d176de6b2760f017184e1)) +* **parser:** SharedPost ([#332](https://github.com/LuanRT/YouTube.js/issues/332)) ([ce53ac1](https://github.com/LuanRT/YouTube.js/commit/ce53ac18435cbcb20d6d4c4ab52fd156091e7592)) +* **VideoInfo:** add `game_info` and `category` ([#333](https://github.com/LuanRT/YouTube.js/issues/333)) ([214aa14](https://github.com/LuanRT/YouTube.js/commit/214aa147ce6306e37a6bf860a7bed5635db4797e)) +* **YouTube/Search:** add `SearchSubMenu` node ([#340](https://github.com/LuanRT/YouTube.js/issues/340)) ([a511608](https://github.com/LuanRT/YouTube.js/commit/a511608f18b37b0d9f2c7958ed5128330fabcfa0)) +* **yt:** add `getGuide()` ([#335](https://github.com/LuanRT/YouTube.js/issues/335)) ([2cc7b8b](https://github.com/LuanRT/YouTube.js/commit/2cc7b8bcd6938c7fb3af4f854a1d78b86d153873)) + + +### Bug Fixes + +* **SegmentedLikeDislikeButton:** like/dislike buttons can also be a simple `Button` ([9b2738f](https://github.com/LuanRT/YouTube.js/commit/9b2738f1285b278c3e83541857651be9a6248288)) +* **YouTube:** fix warnings when retrieving members-only content ([#341](https://github.com/LuanRT/YouTube.js/issues/341)) ([95f1d40](https://github.com/LuanRT/YouTube.js/commit/95f1d4077ff3775f36967dca786139a09e2830a2)) +* **ytmusic:** export search filters type ([cf8a33c](https://github.com/LuanRT/YouTube.js/commit/cf8a33c79f5432136b865d535fd0ecedc2393382)) + ## [3.1.1](https://github.com/LuanRT/YouTube.js/compare/v3.1.0...v3.1.1) (2023-03-01) diff --git a/README.md b/README.md index 000ec116b..a3c77b054 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,7 @@ const yt = await Innertube.create({ * [.getSearchSuggestions(query)](#getsearchsuggestions) * [.getComments(video_id, sort_by?)](#getcomments) * [.getHomeFeed()](#gethomefeed) + * [.getGuide()](#getguide) * [.getLibrary()](#getlibrary) * [.getHistory()](#gethistory) * [.getTrending()](#gettrending) @@ -426,6 +427,12 @@ Retrieves YouTube's home feed.

+ +### getGuide() +Retrieves YouTube's content guide. + +**Returns**: `Promise` + ### getLibrary() Retrieves the account's library. diff --git a/docs/updating-the-parser.md b/docs/updating-the-parser.md index b392f7123..45a04ad89 100644 --- a/docs/updating-the-parser.md +++ b/docs/updating-the-parser.md @@ -1,8 +1,9 @@ # Updating the parser -YouTube is constantly changing, so it is not rare to see YouTube crawlers/scrapers breaking every now and then. +YouTube is constantly changing, so it is not rare to see YouTube crawlers/scrapers breaking every now and then. + +Our parser, on the other hand, was written so that it behaves similarly to an official client, parsing and mapping renderers (a.k.a YTNodes) dynamically without hard-coding their path in the response. This way, whenever a new renderer pops up (e.g; YouTube adds a new feature / minor UI changes) the library will print a warning similar to this: -Our parser, on the other hand, was written so that it behaves similarly to an official client, parsing and mapping renderers (a.k.a YTNodes) dynamically without hard-coding their path in the response. This way, whenever a new renderer pops up (e.g; YouTube adds a new feature / minor UI changes) the library will print a warning similar to this: ``` InnertubeError: SomeRenderer not found! This is a bug, want to help us fix it? Follow the instructions at https://github.com/LuanRT/YouTube.js/blob/main/docs/updating-the-parser.md or report it at https://github.com/LuanRT/YouTube.js/issues! @@ -26,17 +27,19 @@ Thanks to the modularity of the parser, a renderer can be implemented by simply For example, say we found a new renderer named `verticalListRenderer`, to let the parser know it exists we would have to create a file with the following structure: > `../classes/VerticalList.ts` + ```ts import Parser from '..'; import { YTNode } from '../helpers'; +import type { RawNode } from '../index.js'; class VerticalList extends YTNode { static type = 'VerticalList'; - + header; contents; - constructor(data: any) { + constructor(data: RawNode) { // parse the data here, ex; this.header = Parser.parseItem(data.header); this.contents = Parser.parseArray(data.contents); @@ -47,8 +50,9 @@ export default VerticalList; ``` Then update the parser map: + ```bash npm run build:parser-map ``` -And that's it! \ No newline at end of file +And that's it! diff --git a/package-lock.json b/package-lock.json index 72defcf9a..d8725eba4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "youtubei.js", - "version": "3.1.1", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "youtubei.js", - "version": "3.1.1", + "version": "3.2.0", "funding": [ "https://github.com/sponsors/LuanRT" ], diff --git a/package.json b/package.json index a45ff3b66..0bc68aa8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "youtubei.js", - "version": "3.1.1", + "version": "3.2.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/src/Innertube.ts b/src/Innertube.ts index 6bb0ac3e2..18fd36c4e 100644 --- a/src/Innertube.ts +++ b/src/Innertube.ts @@ -21,6 +21,7 @@ import PlaylistManager from './core/PlaylistManager.js'; import YTStudio from './core/Studio.js'; import TabbedFeed from './core/TabbedFeed.js'; import HomeFeed from './parser/youtube/HomeFeed.js'; +import Guide from './parser/youtube/Guide.js'; import Proto from './proto/index.js'; import Constants from './utils/Constants.js'; @@ -170,6 +171,14 @@ class Innertube { return new HomeFeed(this.actions, response); } + /** + * Retrieves YouTube's content guide. + */ + async getGuide(): Promise { + const response = await this.actions.execute('/guide'); + return new Guide(response.data); + } + /** * Returns the account's library. */ diff --git a/src/core/Feed.ts b/src/core/Feed.ts index cde6e0913..0a1013145 100644 --- a/src/core/Feed.ts +++ b/src/core/Feed.ts @@ -4,6 +4,7 @@ import { concatMemos, InnertubeError } from '../utils/Utils.js'; import type Actions from './Actions.js'; import BackstagePost from '../parser/classes/BackstagePost.js'; +import SharedPost from '../parser/classes/SharedPost.js'; import Channel from '../parser/classes/Channel.js'; import CompactVideo from '../parser/classes/CompactVideo.js'; import GridChannel from '../parser/classes/GridChannel.js'; @@ -100,7 +101,7 @@ class Feed { * Get all the community posts in the feed */ get posts() { - return this.#memo.getType([ BackstagePost, Post ]); + return this.#memo.getType([ BackstagePost, Post, SharedPost ]); } /** diff --git a/src/core/Music.ts b/src/core/Music.ts index f43f7f347..934eeb987 100644 --- a/src/core/Music.ts +++ b/src/core/Music.ts @@ -28,6 +28,10 @@ import type { ObservedArray, YTNode } from '../parser/helpers.js'; import type Actions from './Actions.js'; import type Session from './Session.js'; +export type SearchFilters = { + type?: 'all' | 'song' | 'video' | 'album' | 'playlist' | 'artist'; +}; + class Music { #session: Session; #actions: Actions; @@ -108,9 +112,7 @@ class Music { * @param query - Search query. * @param filters - Search filters. */ - async search(query: string, filters: { - type?: 'all' | 'song' | 'video' | 'album' | 'playlist' | 'artist'; - } = {}): Promise { + async search(query: string, filters: SearchFilters = {}): Promise { throwIfMissing({ query }); const payload: { diff --git a/src/parser/classes/AccountChannel.ts b/src/parser/classes/AccountChannel.ts index cec2b19fa..6c3af8471 100644 --- a/src/parser/classes/AccountChannel.ts +++ b/src/parser/classes/AccountChannel.ts @@ -1,6 +1,7 @@ import Text from './misc/Text.js'; import NavigationEndpoint from './NavigationEndpoint.js'; import { YTNode } from '../helpers.js'; +import type { RawNode } from '../index.js'; class AccountChannel extends YTNode { static type = 'AccountChannel'; @@ -8,7 +9,7 @@ class AccountChannel extends YTNode { title: Text; endpoint: NavigationEndpoint; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = new Text(data.title); this.endpoint = new NavigationEndpoint(data.navigationEndpoint); diff --git a/src/parser/classes/AccountItemSection.ts b/src/parser/classes/AccountItemSection.ts index 40daceb1b..89aa7d5bf 100644 --- a/src/parser/classes/AccountItemSection.ts +++ b/src/parser/classes/AccountItemSection.ts @@ -6,6 +6,7 @@ import NavigationEndpoint from './NavigationEndpoint.js'; import AccountItemSectionHeader from './AccountItemSectionHeader.js'; import { YTNode } from '../helpers.js'; +import type { RawNode } from '../index.js'; class AccountItem { static type = 'AccountItem'; @@ -18,7 +19,7 @@ class AccountItem { endpoint: NavigationEndpoint; account_byline: Text; - constructor(data: any) { + constructor(data: RawNode) { this.account_name = new Text(data.accountName); this.account_photo = Thumbnail.fromResponse(data.accountPhoto); this.is_selected = data.isSelected; @@ -35,7 +36,7 @@ class AccountItemSection extends YTNode { contents; header; - constructor(data: any) { + constructor(data: RawNode) { super(); this.contents = data.contents.map((ac: any) => new AccountItem(ac.accountItem)); this.header = Parser.parseItem(data.header, AccountItemSectionHeader); diff --git a/src/parser/classes/AccountItemSectionHeader.ts b/src/parser/classes/AccountItemSectionHeader.ts index 91d40edb7..1653392a8 100644 --- a/src/parser/classes/AccountItemSectionHeader.ts +++ b/src/parser/classes/AccountItemSectionHeader.ts @@ -1,12 +1,12 @@ import Text from './misc/Text.js'; import { YTNode } from '../helpers.js'; - +import type { RawNode } from '../index.js'; class AccountItemSectionHeader extends YTNode { static type = 'AccountItemSectionHeader'; title: Text; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = new Text(data.title); } diff --git a/src/parser/classes/AccountSectionList.ts b/src/parser/classes/AccountSectionList.ts index 03bc27cc4..31f54efbe 100644 --- a/src/parser/classes/AccountSectionList.ts +++ b/src/parser/classes/AccountSectionList.ts @@ -3,14 +3,14 @@ import AccountChannel from './AccountChannel.js'; import AccountItemSection from './AccountItemSection.js'; import { YTNode } from '../helpers.js'; - +import type { RawNode } from '../index.js'; class AccountSectionList extends YTNode { static type = 'AccountSectionList'; contents; footers; - constructor(data: any) { + constructor(data: RawNode) { super(); this.contents = Parser.parseItem(data.contents[0], AccountItemSection); this.footers = Parser.parseItem(data.footers[0], AccountChannel); diff --git a/src/parser/classes/Alert.ts b/src/parser/classes/Alert.ts index d095bee2a..e080d24cc 100644 --- a/src/parser/classes/Alert.ts +++ b/src/parser/classes/Alert.ts @@ -1,13 +1,13 @@ import Text from './misc/Text.js'; import { YTNode } from '../helpers.js'; - +import type { RawNode } from '../index.js'; class Alert extends YTNode { static type = 'Alert'; text: Text; alert_type: string; - constructor(data: any) { + constructor(data: RawNode) { super(); this.text = new Text(data.text); this.alert_type = data.type; diff --git a/src/parser/classes/AudioOnlyPlayability.ts b/src/parser/classes/AudioOnlyPlayability.ts index f1d4ed3a0..f5f6daaaa 100644 --- a/src/parser/classes/AudioOnlyPlayability.ts +++ b/src/parser/classes/AudioOnlyPlayability.ts @@ -1,11 +1,11 @@ import { YTNode } from '../helpers.js'; - +import type { RawNode } from '../index.js'; class AudioOnlyPlayability extends YTNode { static type = 'AudioOnlyPlayability'; audio_only_availability: string; - constructor (data: any) { + constructor (data: RawNode) { super(); this.audio_only_availability = data.audioOnlyAvailability; } diff --git a/src/parser/classes/AutomixPreviewVideo.ts b/src/parser/classes/AutomixPreviewVideo.ts index e697106f4..0759f3fa3 100644 --- a/src/parser/classes/AutomixPreviewVideo.ts +++ b/src/parser/classes/AutomixPreviewVideo.ts @@ -1,12 +1,12 @@ import { YTNode } from '../helpers.js'; import NavigationEndpoint from './NavigationEndpoint.js'; - +import type { RawNode } from '../index.js'; class AutomixPreviewVideo extends YTNode { static type = 'AutomixPreviewVideo'; playlist_video?: { endpoint: NavigationEndpoint }; - constructor(data: any) { + constructor(data: RawNode) { super(); if (data?.content?.automixPlaylistVideoRenderer?.navigationEndpoint) { this.playlist_video = { diff --git a/src/parser/classes/GuideCollapsibleEntry.ts b/src/parser/classes/GuideCollapsibleEntry.ts new file mode 100644 index 000000000..cece67e62 --- /dev/null +++ b/src/parser/classes/GuideCollapsibleEntry.ts @@ -0,0 +1,35 @@ +import Text from './misc/Text.js'; +import { YTNode } from '../helpers.js'; +import Parser from '../parser.js'; + +class GuideCollapsibleEntry extends YTNode { + static type = 'GuideCollapsibleEntry'; + + expander_item: { + title: string, + icon_type: string + }; + collapser_item: { + title: string, + icon_type: string + }; + expandable_items; + + constructor(data: any) { + super(); + + this.expander_item = { + title: new Text(data.expanderItem.guideEntryRenderer.formattedTitle).toString(), + icon_type: data.expanderItem.guideEntryRenderer.icon.iconType + }; + + this.collapser_item = { + title: new Text(data.collapserItem.guideEntryRenderer.formattedTitle).toString(), + icon_type: data.collapserItem.guideEntryRenderer.icon.iconType + }; + + this.expandable_items = Parser.parseArray(data.expandableItems); + } +} + +export default GuideCollapsibleEntry; \ No newline at end of file diff --git a/src/parser/classes/GuideCollapsibleSectionEntry.ts b/src/parser/classes/GuideCollapsibleSectionEntry.ts new file mode 100644 index 000000000..aaab66ab9 --- /dev/null +++ b/src/parser/classes/GuideCollapsibleSectionEntry.ts @@ -0,0 +1,23 @@ +import { YTNode } from '../helpers.js'; +import Parser from '../parser.js'; + +class GuideCollapsibleSectionEntry extends YTNode { + static type = 'GuideCollapsibleSectionEntry'; + + header_entry; + expander_icon: string; + collapser_icon: string; + section_items; + + constructor(data: any) { + super(); + + this.header_entry = Parser.parseItem(data.headerEntry); + this.expander_icon = data.expanderIcon.iconType; + this.collapser_icon = data.collapserIcon.iconType; + this.section_items = Parser.parseArray(data.sectionItems); + + } +} + +export default GuideCollapsibleSectionEntry; \ No newline at end of file diff --git a/src/parser/classes/GuideDownloadsEntry.ts b/src/parser/classes/GuideDownloadsEntry.ts new file mode 100644 index 000000000..8f06a1063 --- /dev/null +++ b/src/parser/classes/GuideDownloadsEntry.ts @@ -0,0 +1,14 @@ +import GuideEntry from './GuideEntry.js'; + +class GuideDownloadsEntry extends GuideEntry { + static type = 'GuideDownloadsEntry'; + + always_show: boolean; + + constructor(data: any) { + super(data.entryRenderer.guideEntryRenderer); + this.always_show = !!data.alwaysShow; + } +} + +export default GuideDownloadsEntry; \ No newline at end of file diff --git a/src/parser/classes/GuideEntry.ts b/src/parser/classes/GuideEntry.ts new file mode 100644 index 000000000..593740111 --- /dev/null +++ b/src/parser/classes/GuideEntry.ts @@ -0,0 +1,33 @@ +import Text from './misc/Text.js'; +import NavigationEndpoint from './NavigationEndpoint.js'; +import { YTNode } from '../helpers.js'; +import Thumbnail from './misc/Thumbnail.js'; + +class GuideEntry extends YTNode { + static type = 'GuideEntry'; + + title: Text; + endpoint: NavigationEndpoint; + icon_type?: string; + thumbnails?: Thumbnail[]; + badges?: any; + is_primary: boolean; + + constructor(data: any) { + super(); + this.title = new Text(data.formattedTitle); + this.endpoint = new NavigationEndpoint(data.navigationEndpoint || data.serviceEndpoint); + if (data.icon?.iconType) { + this.icon_type = data.icon.iconType; + } + if (data.thumbnail) { + this.thumbnails = Thumbnail.fromResponse(data.thumbnail); + } + if (data.badges) { + this.badges = data.badges; + } + this.is_primary = !!data.isPrimary; + } +} + +export default GuideEntry; \ No newline at end of file diff --git a/src/parser/classes/GuideSection.ts b/src/parser/classes/GuideSection.ts new file mode 100644 index 000000000..2bb0f4d08 --- /dev/null +++ b/src/parser/classes/GuideSection.ts @@ -0,0 +1,20 @@ +import Text from './misc/Text.js'; +import { YTNode } from '../helpers.js'; +import Parser from '../parser.js'; + +class GuideSection extends YTNode { + static type = 'GuideSection'; + + title?: Text; + items; + + constructor(data: any) { + super(); + if (data.formattedTitle) { + this.title = new Text(data.formattedTitle); + } + this.items = Parser.parseArray(data.items); + } +} + +export default GuideSection; \ No newline at end of file diff --git a/src/parser/classes/GuideSubscriptionsSection.ts b/src/parser/classes/GuideSubscriptionsSection.ts new file mode 100644 index 000000000..6cd053f4d --- /dev/null +++ b/src/parser/classes/GuideSubscriptionsSection.ts @@ -0,0 +1,7 @@ +import GuideSection from './GuideSection.js'; + +class GuideSubscriptionsSection extends GuideSection { + static type = 'GuideSubscriptionsSection'; +} + +export default GuideSubscriptionsSection; \ No newline at end of file diff --git a/src/parser/classes/HeroPlaylistThumbnail.ts b/src/parser/classes/HeroPlaylistThumbnail.ts new file mode 100644 index 000000000..1d83b2af0 --- /dev/null +++ b/src/parser/classes/HeroPlaylistThumbnail.ts @@ -0,0 +1,19 @@ +import { YTNode } from '../helpers.js'; +import NavigationEndpoint from './NavigationEndpoint.js'; +import Thumbnail from './misc/Thumbnail.js'; + +class HeroPlaylistThumbnail extends YTNode { + static type = 'HeroPlaylistThumbnail'; + + thumbnails: Thumbnail[]; + on_tap_endpoint: NavigationEndpoint; + + constructor(data: any) { + super(); + + this.thumbnails = Thumbnail.fromResponse(data.thumbnail); + this.on_tap_endpoint = new NavigationEndpoint(data.onTap); + } +} + +export default HeroPlaylistThumbnail; \ No newline at end of file diff --git a/src/parser/classes/PlayerLegacyDesktopYpcOffer.ts b/src/parser/classes/PlayerLegacyDesktopYpcOffer.ts new file mode 100644 index 000000000..dc847300f --- /dev/null +++ b/src/parser/classes/PlayerLegacyDesktopYpcOffer.ts @@ -0,0 +1,21 @@ +import { YTNode } from '../helpers.js'; +import type { RawNode } from '../index.js'; + +class PlayerLegacyDesktopYpcOffer extends YTNode { + static type = 'PlayerLegacyDesktopYpcOffer'; + + title: string; + thumbnail: string; + offer_description: string; + offer_id: string; + + constructor(data: RawNode) { + super(); + this.title = data.itemTitle; + this.thumbnail = data.itemThumbnail; + this.offer_description = data.offerDescription; + this.offer_id = data.offerId; + } +} + +export default PlayerLegacyDesktopYpcOffer; \ No newline at end of file diff --git a/src/parser/classes/PlaylistHeader.ts b/src/parser/classes/PlaylistHeader.ts index a670538d4..18c5a4c2a 100644 --- a/src/parser/classes/PlaylistHeader.ts +++ b/src/parser/classes/PlaylistHeader.ts @@ -21,6 +21,7 @@ class PlaylistHeader extends YTNode { save_button; shuffle_play_button; menu; + banner; constructor(data: any) { super(); @@ -39,6 +40,7 @@ class PlaylistHeader extends YTNode { this.save_button = Parser.parse(data.saveButton); this.shuffle_play_button = Parser.parse(data.shufflePlayButton); this.menu = Parser.parse(data.moreActionsMenu); + this.banner = Parser.parseItem(data.playlistHeaderBanner); } } diff --git a/src/parser/classes/RichMetadata.ts b/src/parser/classes/RichMetadata.ts new file mode 100644 index 000000000..76dc7b1e2 --- /dev/null +++ b/src/parser/classes/RichMetadata.ts @@ -0,0 +1,32 @@ +import Text from './misc/Text.js'; +import Thumbnail from './misc/Thumbnail.js'; +import NavigationEndpoint from './NavigationEndpoint.js'; +import { YTNode } from '../helpers.js'; + +class RichMetadata extends YTNode { + static type = 'RichMetadata'; + + thumbnail: Thumbnail[]; + title: Text; + subtitle?: Text; + call_to_action: Text; + icon_type?: string; + endpoint: NavigationEndpoint; + + constructor(data: any) { + super(); + + this.thumbnail = Thumbnail.fromResponse(data.thumbnail); + this.title = new Text(data.title); + this.subtitle = new Text(data.subtitle); + this.call_to_action = new Text(data.callToAction); + + if (data.callToActionIcon?.iconType) { + this.icon_type = data.callToActionIcon?.iconType; + } + + this.endpoint = new NavigationEndpoint(data.endpoint); + } +} + +export default RichMetadata; \ No newline at end of file diff --git a/src/parser/classes/RichMetadataRow.ts b/src/parser/classes/RichMetadataRow.ts new file mode 100644 index 000000000..189b8f4cc --- /dev/null +++ b/src/parser/classes/RichMetadataRow.ts @@ -0,0 +1,15 @@ +import Parser from '../index.js'; +import { YTNode } from '../helpers.js'; + +class RichMetadataRow extends YTNode { + static type = 'RichMetadataRow'; + + contents; + + constructor(data: any) { + super(); + this.contents = Parser.parseArray(data.contents); + } +} + +export default RichMetadataRow; \ No newline at end of file diff --git a/src/parser/classes/SearchFilter.ts b/src/parser/classes/SearchFilter.ts new file mode 100644 index 000000000..cea552ac3 --- /dev/null +++ b/src/parser/classes/SearchFilter.ts @@ -0,0 +1,22 @@ +import { YTNode } from '../helpers.js'; +import type { RawNode } from '../index.js'; +import Text from './misc/Text.js'; +import NavigationEndpoint from './NavigationEndpoint.js'; + +class SearchFilter extends YTNode { + static type = 'SearchFilter'; + + label: Text; + endpoint: NavigationEndpoint; + tooltip: string; + + constructor(data: RawNode) { + super(); + + this.label = new Text(data.label); + this.endpoint = new NavigationEndpoint(data.endpoint); + this.tooltip = data.tooltip; + } +} + +export default SearchFilter; \ No newline at end of file diff --git a/src/parser/classes/SearchFilterGroup.ts b/src/parser/classes/SearchFilterGroup.ts new file mode 100644 index 000000000..5e7c6aa10 --- /dev/null +++ b/src/parser/classes/SearchFilterGroup.ts @@ -0,0 +1,21 @@ +import { ObservedArray, YTNode } from '../helpers.js'; +import type { RawNode } from '../index.js'; +import { Parser } from '../index.js'; +import Text from './misc/Text.js'; +import SearchFilter from './SearchFilter.js'; + +class SearchFilterGroup extends YTNode { + static type = 'SearchFilterGroup'; + + title: Text; + filters: ObservedArray | null; + + constructor(data: RawNode) { + super(); + + this.title = new Text(data.title); + this.filters = Parser.parseArray(data.filters, SearchFilter); + } +} + +export default SearchFilterGroup; \ No newline at end of file diff --git a/src/parser/classes/SearchSubMenu.ts b/src/parser/classes/SearchSubMenu.ts new file mode 100644 index 000000000..48ae81fd6 --- /dev/null +++ b/src/parser/classes/SearchSubMenu.ts @@ -0,0 +1,23 @@ +import { ObservedArray, YTNode } from '../helpers.js'; +import type { RawNode } from '../index.js'; +import Parser from '../index.js'; +import Text from './misc/Text.js'; +import SearchFilterGroup from './SearchFilterGroup.js'; +import ToggleButton from './ToggleButton.js'; + +class SearchSubMenu extends YTNode { + static type = 'SearchSubMenu'; + + title: Text; + groups: ObservedArray | null; + 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); + } +} + +export default SearchSubMenu; \ No newline at end of file diff --git a/src/parser/classes/SegmentedLikeDislikeButton.ts b/src/parser/classes/SegmentedLikeDislikeButton.ts index 1dd446146..adc756fc2 100644 --- a/src/parser/classes/SegmentedLikeDislikeButton.ts +++ b/src/parser/classes/SegmentedLikeDislikeButton.ts @@ -1,17 +1,19 @@ +import { YTNode } from '../helpers.js'; +import type { RawNode } from '../index.js'; import Parser from '../index.js'; +import Button from './Button.js'; import ToggleButton from './ToggleButton.js'; -import { YTNode } from '../helpers.js'; class SegmentedLikeDislikeButton extends YTNode { static type = 'SegmentedLikeDislikeButton'; - like_button: ToggleButton | null; - dislike_button: ToggleButton | null; + like_button: ToggleButton | Button | null; + dislike_button: ToggleButton | Button | null; - constructor (data: any) { + constructor (data: RawNode) { super(); - this.like_button = Parser.parseItem(data.likeButton, ToggleButton); - this.dislike_button = Parser.parseItem(data.dislikeButton, ToggleButton); + this.like_button = Parser.parseItem(data.likeButton, [ ToggleButton, Button ]); + this.dislike_button = Parser.parseItem(data.dislikeButton, [ ToggleButton, Button ]); } } diff --git a/src/parser/classes/SharedPost.ts b/src/parser/classes/SharedPost.ts new file mode 100644 index 000000000..5782cfa76 --- /dev/null +++ b/src/parser/classes/SharedPost.ts @@ -0,0 +1,38 @@ +import { YTNode } from '../helpers.js'; +import { Menu } from '../map.js'; +import Parser from '../parser.js'; +import BackstagePost from './BackstagePost.js'; +import Button from './Button.js'; +import Author from './misc/Author.js'; +import Text from './misc/Text.js'; +import Thumbnail from './misc/Thumbnail.js'; +import NavigationEndpoint from './NavigationEndpoint.js'; + +class SharedPost extends YTNode { + static type = 'SharedPost'; + + thumbnail: Thumbnail[]; + content: Text; + published: Text; + menu: Menu | null; + original_post: BackstagePost | null; + id: string; + endpoint: NavigationEndpoint; + expand_button: Button | null; + author: Author; + + constructor(data: any) { + super(); + this.thumbnail = Thumbnail.fromResponse(data.thumbnail); + this.content = new Text(data.content); + this.published = new Text(data.publishedTimeText); + this.menu = Parser.parseItem(data.actionMenu, Menu); + this.original_post = Parser.parseItem(data.originalPost, BackstagePost); + this.id = data.postId; + this.endpoint = new NavigationEndpoint(data.navigationEndpoint); + this.expand_button = Parser.parseItem(data.expandButton, Button); + this.author = new Author(data.displayName, undefined); + } +} + +export default SharedPost; \ No newline at end of file diff --git a/src/parser/classes/VideoPrimaryInfo.ts b/src/parser/classes/VideoPrimaryInfo.ts index d3d0557e3..7ee9cac76 100644 --- a/src/parser/classes/VideoPrimaryInfo.ts +++ b/src/parser/classes/VideoPrimaryInfo.ts @@ -1,6 +1,9 @@ +import { YTNode } from '../helpers.js'; +import type { RawNode } from '../index.js'; +import type { ObservedArray } from '../helpers.js'; import Parser from '../index.js'; import Text from './misc/Text.js'; -import { YTNode } from '../helpers.js'; +import MetadataBadge from './MetadataBadge.js'; import Menu from './menus/Menu.js'; class VideoPrimaryInfo extends YTNode { @@ -10,16 +13,20 @@ class VideoPrimaryInfo extends YTNode { super_title_link: Text; view_count: Text; short_view_count: Text; + badges: ObservedArray; published: Text; - menu; + relative_date: Text; + menu: Menu | null; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = new Text(data.title); this.super_title_link = new Text(data.superTitleLink); - this.view_count = new Text(data.viewCount.videoViewCountRenderer.viewCount); - this.short_view_count = new Text(data.viewCount.videoViewCountRenderer.shortViewCount); + this.view_count = new Text(data.viewCount?.videoViewCountRenderer?.viewCount); + this.short_view_count = new Text(data.viewCount?.videoViewCountRenderer?.shortViewCount); + this.badges = Parser.parseArray(data.badges, MetadataBadge); this.published = new Text(data.dateText); + this.relative_date = new Text(data.relativeDateText); this.menu = Parser.parseItem(data.videoActions, Menu); } } diff --git a/src/parser/classes/actions/AppendContinuationItemsAction.ts b/src/parser/classes/actions/AppendContinuationItemsAction.ts index a9fa65411..5ca0cc004 100644 --- a/src/parser/classes/actions/AppendContinuationItemsAction.ts +++ b/src/parser/classes/actions/AppendContinuationItemsAction.ts @@ -1,5 +1,6 @@ import Parser from '../../index.js'; import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class AppendContinuationItemsAction extends YTNode { static type = 'AppendContinuationItemsAction'; @@ -7,7 +8,7 @@ class AppendContinuationItemsAction extends YTNode { items; target: string; - constructor(data: any) { + constructor(data: RawNode) { super(); this.items = Parser.parse(data.continuationItems); this.target = data.target; diff --git a/src/parser/classes/actions/OpenPopupAction.ts b/src/parser/classes/actions/OpenPopupAction.ts index 9286139e7..75c4701e6 100644 --- a/src/parser/classes/actions/OpenPopupAction.ts +++ b/src/parser/classes/actions/OpenPopupAction.ts @@ -1,5 +1,6 @@ import Parser from '../../index.js'; import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class OpenPopupAction extends YTNode { static type = 'OpenPopupAction'; @@ -7,7 +8,7 @@ class OpenPopupAction extends YTNode { popup; popup_type; - constructor(data: any) { + constructor(data: RawNode) { super(); this.popup = Parser.parse(data.popup); this.popup_type = data.popupType; diff --git a/src/parser/classes/analytics/AnalyticsMainAppKeyMetrics.ts b/src/parser/classes/analytics/AnalyticsMainAppKeyMetrics.ts index 31715ed51..526de8e2c 100644 --- a/src/parser/classes/analytics/AnalyticsMainAppKeyMetrics.ts +++ b/src/parser/classes/analytics/AnalyticsMainAppKeyMetrics.ts @@ -1,5 +1,6 @@ import DataModelSection from './DataModelSection.js'; import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class AnalyticsMainAppKeyMetrics extends YTNode { static type = 'AnalyticsMainAppKeyMetrics'; @@ -7,7 +8,7 @@ class AnalyticsMainAppKeyMetrics extends YTNode { period: string; sections: DataModelSection[]; - constructor(data: any) { + constructor(data: RawNode) { super(); this.period = data.cardData.periodLabel; const metrics_data = data.cardData.sections[0].analyticsKeyMetricsData; diff --git a/src/parser/classes/analytics/AnalyticsRoot.ts b/src/parser/classes/analytics/AnalyticsRoot.ts index a4bb3c9f3..bd3849b4a 100644 --- a/src/parser/classes/analytics/AnalyticsRoot.ts +++ b/src/parser/classes/analytics/AnalyticsRoot.ts @@ -1,4 +1,5 @@ import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class AnalyticsRoot extends YTNode { static type = 'AnalyticsRoot'; @@ -19,7 +20,7 @@ class AnalyticsRoot extends YTNode { }[]; }[]; - constructor(data: any) { + constructor(data: RawNode) { super(); const cards = data.analyticsTableCarouselData.data.tableCards; diff --git a/src/parser/classes/analytics/AnalyticsShortsCarouselCard.ts b/src/parser/classes/analytics/AnalyticsShortsCarouselCard.ts index 8ea3048dc..ce169ce55 100644 --- a/src/parser/classes/analytics/AnalyticsShortsCarouselCard.ts +++ b/src/parser/classes/analytics/AnalyticsShortsCarouselCard.ts @@ -1,4 +1,5 @@ import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; import NavigationEndpoint from '../NavigationEndpoint.js'; class AnalyticsShortsCarouselCard extends YTNode { @@ -11,7 +12,7 @@ class AnalyticsShortsCarouselCard extends YTNode { endpoint: NavigationEndpoint; }[]; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = data.title; this.shorts = data.shortsCarouselData.shorts.map((short: any) => ({ diff --git a/src/parser/classes/analytics/AnalyticsVideo.ts b/src/parser/classes/analytics/AnalyticsVideo.ts index b17da865f..09b9334d6 100644 --- a/src/parser/classes/analytics/AnalyticsVideo.ts +++ b/src/parser/classes/analytics/AnalyticsVideo.ts @@ -1,5 +1,6 @@ import Thumbnail from '../misc/Thumbnail.js'; import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class AnalyticsVideo extends YTNode { static type = 'AnalyticsVideo'; @@ -13,7 +14,7 @@ class AnalyticsVideo extends YTNode { is_short: boolean; }; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = data.videoTitle; diff --git a/src/parser/classes/analytics/AnalyticsVodCarouselCard.ts b/src/parser/classes/analytics/AnalyticsVodCarouselCard.ts index 27a98a199..a77e54d74 100644 --- a/src/parser/classes/analytics/AnalyticsVodCarouselCard.ts +++ b/src/parser/classes/analytics/AnalyticsVodCarouselCard.ts @@ -1,5 +1,6 @@ import Video from './AnalyticsVideo.js'; import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class AnalyticsVodCarouselCard extends YTNode { static type = 'AnalyticsVodCarouselCard'; @@ -8,7 +9,7 @@ class AnalyticsVodCarouselCard extends YTNode { videos: Video[] | null; no_data_message?: string; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = data.title; diff --git a/src/parser/classes/analytics/CtaGoToCreatorStudio.ts b/src/parser/classes/analytics/CtaGoToCreatorStudio.ts index 3d2595572..f2378edc4 100644 --- a/src/parser/classes/analytics/CtaGoToCreatorStudio.ts +++ b/src/parser/classes/analytics/CtaGoToCreatorStudio.ts @@ -1,4 +1,5 @@ import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class CtaGoToCreatorStudio extends YTNode { static type = 'CtaGoToCreatorStudio'; @@ -6,7 +7,7 @@ class CtaGoToCreatorStudio extends YTNode { title: string; use_new_specs: boolean; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = data.buttonLabel; this.use_new_specs = data.useNewSpecs; diff --git a/src/parser/classes/analytics/DataModelSection.ts b/src/parser/classes/analytics/DataModelSection.ts index d0967343f..cbc716449 100644 --- a/src/parser/classes/analytics/DataModelSection.ts +++ b/src/parser/classes/analytics/DataModelSection.ts @@ -1,4 +1,5 @@ import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class DataModelSection extends YTNode { static type = 'DataModelSection'; @@ -36,7 +37,7 @@ class DataModelSection extends YTNode { } }; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = data.title; diff --git a/src/parser/classes/analytics/StatRow.ts b/src/parser/classes/analytics/StatRow.ts index d0bf0f224..cea7e738e 100644 --- a/src/parser/classes/analytics/StatRow.ts +++ b/src/parser/classes/analytics/StatRow.ts @@ -1,6 +1,7 @@ import Text from '../misc/Text.js'; import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class StatRow extends YTNode { static type = 'StatRow'; @@ -8,7 +9,7 @@ class StatRow extends YTNode { title: Text; contents: Text; - constructor(data: any) { + constructor(data: RawNode) { super(); this.title = new Text(data.title); this.contents = new Text(data.contents); diff --git a/src/parser/classes/comments/AuthorCommentBadge.ts b/src/parser/classes/comments/AuthorCommentBadge.ts index f15098b41..6d04a88b7 100644 --- a/src/parser/classes/comments/AuthorCommentBadge.ts +++ b/src/parser/classes/comments/AuthorCommentBadge.ts @@ -1,4 +1,5 @@ import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class AuthorCommentBadge extends YTNode { static type = 'AuthorCommentBadge'; @@ -9,7 +10,7 @@ class AuthorCommentBadge extends YTNode { tooltip: string; style?: string; - constructor(data: any) { + constructor(data: RawNode) { super(); this.icon_type = data.icon?.iconType || null; diff --git a/src/parser/classes/comments/Comment.ts b/src/parser/classes/comments/Comment.ts index 3a787593a..d9ea0be29 100644 --- a/src/parser/classes/comments/Comment.ts +++ b/src/parser/classes/comments/Comment.ts @@ -16,6 +16,7 @@ import type Actions from '../../../core/Actions.js'; import Proto from '../../../proto/index.js'; import { InnertubeError } from '../../../utils/Utils.js'; import { YTNode, SuperParsedResult } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class Comment extends YTNode { static type = 'Comment'; @@ -44,7 +45,7 @@ class Comment extends YTNode { is_pinned: boolean; is_member: boolean; - constructor(data: any) { + constructor(data: RawNode) { super(); this.content = new Text(data.contentText); this.published = new Text(data.publishedTimeText); diff --git a/src/parser/classes/comments/CommentActionButtons.ts b/src/parser/classes/comments/CommentActionButtons.ts index 94732f86d..5e77be749 100644 --- a/src/parser/classes/comments/CommentActionButtons.ts +++ b/src/parser/classes/comments/CommentActionButtons.ts @@ -3,6 +3,7 @@ import type Button from '../Button.js'; import type ToggleButton from '../ToggleButton.js'; import type CreatorHeart from './CreatorHeart.js'; import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class CommentActionButtons extends YTNode { static type = 'CommentActionButtons'; @@ -12,7 +13,7 @@ class CommentActionButtons extends YTNode { reply_button; creator_heart; - constructor(data: any) { + constructor(data: RawNode) { super(); this.like_button = Parser.parseItem(data.likeButton); this.dislike_button = Parser.parseItem(data.dislikeButton); diff --git a/src/parser/classes/comments/CommentDialog.ts b/src/parser/classes/comments/CommentDialog.ts index 6144c6b26..c0b7459bb 100644 --- a/src/parser/classes/comments/CommentDialog.ts +++ b/src/parser/classes/comments/CommentDialog.ts @@ -4,6 +4,7 @@ import Thumbnail from '../misc/Thumbnail.js'; import type Button from '../Button.js'; import type EmojiPicker from './EmojiPicker.js'; import { YTNode } from '../../helpers.js'; +import type { RawNode } from '../../index.js'; class CommentDialog extends YTNode { static type = 'CommentDialog'; @@ -16,7 +17,7 @@ class CommentDialog extends YTNode { emoji_button: Button | null; emoji_picker: any | null; - constructor(data: any) { + constructor(data: RawNode) { super(); this.editable_text = new Text(data.editableText); this.author_thumbnail = Thumbnail.fromResponse(data.authorThumbnail); diff --git a/src/parser/classes/comments/CommentReplies.ts b/src/parser/classes/comments/CommentReplies.ts index 24bccd40b..d80849423 100644 --- a/src/parser/classes/comments/CommentReplies.ts +++ b/src/parser/classes/comments/CommentReplies.ts @@ -2,7 +2,7 @@ import Parser from '../../index.js'; import Thumbnail from '../misc/Thumbnail.js'; import type Button from '../Button.js'; import { YTNode } from '../../helpers.js'; - +import type { RawNode } from '../../index.js'; class CommentReplies extends YTNode { static type = 'CommentReplies'; @@ -12,7 +12,7 @@ class CommentReplies extends YTNode { view_replies_creator_thumbnail: Thumbnail[]; has_channel_owner_replied: boolean; - constructor(data: any) { + constructor(data: RawNode) { super(); this.contents = Parser.parseArray(data.contents); this.view_replies = Parser.parseItem