diff --git a/README.md b/README.md
index a3c77b054..b30944ff5 100644
--- a/README.md
+++ b/README.md
@@ -248,7 +248,7 @@ const yt = await Innertube.create({
Methods
- * [.getInfo(video_id, client?)](#getinfo)
+ * [.getInfo(target, client?)](#getinfo)
* [.getBasicInfo(video_id, client?)](#getbasicinfo)
* [.search(query, filters?)](#search)
* [.getSearchSuggestions(query)](#getsearchsuggestions)
@@ -273,7 +273,7 @@ const yt = await Innertube.create({
-### getInfo(video_id, client?)
+### getInfo(target, client?)
Retrieves video info, including playback data and even layout elements such as menus, buttons, etc — all nicely parsed.
@@ -281,7 +281,7 @@ Retrieves video info, including playback data and even layout elements such as m
| Param | Type | Description |
| --- | --- | --- |
-| video_id | `string` | The id of the video |
+| target | `string` \| `NavigationEndpoint` | If `string`, the id of the video. If `NavigationEndpoint`, the endpoint of watchable elements such as `Video`, `Mix` and `Playlist`. To clarify, valid endpoints have payloads containing at least `videoId` and optionally `playlistId`, `params` and `index`. |
| client? | `InnerTubeClient` | `WEB`, `ANDROID`, `YTMUSIC`, `YTMUSIC_ANDROID` or `TV_EMBEDDED` |
@@ -321,6 +321,9 @@ Retrieves video info, including playback data and even layout elements such as m
- `#addToWatchHistory()`
- Adds the video to the watch history.
+- `#autoplay_video_endpoint`
+ - Returns the endpoint of the video for Autoplay.
+
- `#page`
- Returns original InnerTube response (sanitized).
diff --git a/src/Innertube.ts b/src/Innertube.ts
index 18fd36c4e..7bbcf7dcb 100644
--- a/src/Innertube.ts
+++ b/src/Innertube.ts
@@ -31,7 +31,7 @@ import type Format from './parser/classes/misc/Format.js';
import type { ApiResponse } from './core/Actions.js';
import type { IBrowseResponse, IParsedResponse } from './parser/types/index.js';
import type { DownloadOptions, FormatOptions } from './utils/FormatUtils.js';
-import { generateRandomString, throwIfMissing } from './utils/Utils.js';
+import { generateRandomString, InnertubeError, throwIfMissing } from './utils/Utils.js';
export type InnertubeConfig = SessionOptions;
@@ -72,16 +72,48 @@ class Innertube {
/**
* Retrieves video info.
- * @param video_id - The video id.
+ * @param target - The video id or `NavigationEndpoint`.
* @param client - The client to use.
*/
- async getInfo(video_id: string, client?: InnerTubeClient): Promise {
- throwIfMissing({ video_id });
+ async getInfo(target: string | NavigationEndpoint, client?: InnerTubeClient): Promise {
+ throwIfMissing({ target });
+
+ let payload: {
+ videoId: string,
+ playlistId?: string,
+ params?: string,
+ playlistIndex?: number
+ };
+
+ if (target instanceof NavigationEndpoint) {
+ const video_id = target.payload?.videoId;
+ if (!video_id) {
+ throw new InnertubeError('Missing video id in endpoint payload.', target);
+ }
+ payload = {
+ videoId: video_id
+ };
+ if (target.payload.playlistId) {
+ payload.playlistId = target.payload.playlistId;
+ }
+ if (target.payload.params) {
+ payload.params = target.payload.params;
+ }
+ if (target.payload.index) {
+ payload.playlistIndex = target.payload.index;
+ }
+ } else if (typeof target === 'string') {
+ payload = {
+ videoId: target
+ };
+ } else {
+ throw new InnertubeError('Invalid target, expected either a video id or a valid NavigationEndpoint', target);
+ }
const cpn = generateRandomString(16);
- const initial_info = this.actions.getVideoInfo(video_id, cpn, client);
- const continuation = this.actions.execute('/next', { videoId: video_id });
+ const initial_info = this.actions.getVideoInfo(payload.videoId, cpn, client);
+ const continuation = this.actions.execute('/next', payload);
const response = await Promise.all([ initial_info, continuation ]);
return new VideoInfo(response, this.actions, this.session.player, cpn);
diff --git a/src/parser/classes/TwoColumnWatchNextResults.ts b/src/parser/classes/TwoColumnWatchNextResults.ts
index 875d83e05..bc4167100 100644
--- a/src/parser/classes/TwoColumnWatchNextResults.ts
+++ b/src/parser/classes/TwoColumnWatchNextResults.ts
@@ -1,5 +1,15 @@
import Parser from '../index.js';
import { YTNode } from '../helpers.js';
+import Text from './misc/Text.js';
+import PlaylistAuthor from './misc/PlaylistAuthor.js';
+import NavigationEndpoint from './NavigationEndpoint.js';
+
+import type Menu from './menus/Menu.js';
+
+type AutoplaySet = {
+ autoplay_video: NavigationEndpoint,
+ next_button_video?: NavigationEndpoint
+};
class TwoColumnWatchNextResults extends YTNode {
static type = 'TwoColumnWatchNextResults';
@@ -7,12 +17,66 @@ class TwoColumnWatchNextResults extends YTNode {
results;
secondary_results;
conversation_bar;
+ playlist?: {
+ id: string,
+ title: string,
+ author: Text | PlaylistAuthor,
+ contents: YTNode[],
+ current_index: number,
+ is_infinite: boolean,
+ menu: Menu | null
+ };
+ autoplay?: {
+ sets: AutoplaySet[],
+ modified_sets?: AutoplaySet[],
+ count_down_secs?: number
+ };
constructor(data: any) {
super();
this.results = Parser.parseArray(data.results?.results.contents);
this.secondary_results = Parser.parseArray(data.secondaryResults?.secondaryResults.results);
this.conversation_bar = Parser.parseItem(data?.conversationBar);
+
+ const playlistData = data.playlist?.playlist;
+ if (playlistData) {
+ this.playlist = {
+ id: playlistData.playlistId,
+ title: playlistData.title,
+ author: playlistData.shortBylineText?.simpleText ?
+ new Text(playlistData.shortBylineText) :
+ new PlaylistAuthor(playlistData.longBylineText),
+ contents: Parser.parseArray(playlistData.contents),
+ current_index: playlistData.currentIndex,
+ is_infinite: !!playlistData.isInfinite,
+ menu: Parser.parseItem