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