Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add possibility to get continuation of YTMusic UpNext Tab #770

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion src/parser/types/ParsedResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export interface IStreamingData {
}

export type IPlayerResponse = Pick<IParsedResponse, 'captions' | 'cards' | 'endscreen' | 'microformat' | 'annotations' | 'playability_status' | 'streaming_data' | 'player_config' | 'playback_tracking' | 'storyboards' | 'video_details'>;
export type INextResponse = Pick<IParsedResponse, 'contents' | 'contents_memo' | 'current_video_endpoint' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'player_overlays' | 'engagement_panels'>
export type INextResponse = Pick<IParsedResponse, 'contents' | 'contents_memo' | 'continuation_contents' | 'continuation_contents_memo' | 'current_video_endpoint' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'player_overlays' | 'engagement_panels'>
export type IBrowseResponse = Pick<IParsedResponse, 'background' | 'continuation_contents' | 'continuation_contents_memo' | 'on_response_received_actions' | 'on_response_received_actions_memo' | 'on_response_received_endpoints' | 'on_response_received_endpoints_memo' | 'contents' | 'contents_memo' | 'header' | 'header_memo' | 'metadata' | 'microformat' | 'alerts' | 'sidebar' | 'sidebar_memo'>
export type ISearchResponse = Pick<IParsedResponse, 'header' | 'header_memo' | 'contents' | 'contents_memo' | 'on_response_received_commands' | 'continuation_contents' | 'continuation_contents_memo' | 'refinements' | 'estimated_results'>;
export type IResolveURLResponse = Pick<IParsedResponse, 'endpoint'>;
Expand Down
25 changes: 24 additions & 1 deletion src/parser/ytmusic/TrackInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import WatchNextTabbedResults from '../classes/WatchNextTabbedResults.js';
import type RichGrid from '../classes/RichGrid.js';
import type MusicQueue from '../classes/MusicQueue.js';
import type MusicCarouselShelf from '../classes/MusicCarouselShelf.js';
import type NavigationEndpoint from '../classes/NavigationEndpoint.js';
import NavigationEndpoint from '../classes/NavigationEndpoint.js';
import type { ObservedArray, YTNode } from '../helpers.js';
import type { Actions, ApiResponse } from '../../core/index.js';
import { PlaylistPanelContinuation } from '../continuations.js';

class TrackInfo extends MediaInfo {
public tabs?: ObservedArray<Tab>;
Expand Down Expand Up @@ -100,6 +101,28 @@ class TrackInfo extends MediaInfo {
return playlist_panel;
}

/**
* Retrieves up next continuation relative to current TrackInfo.
*/
async getUpNextContinuation(playlistPanel: PlaylistPanel | PlaylistPanelContinuation): Promise<PlaylistPanelContinuation> {
if (!this.current_video_endpoint)
throw new InnertubeError('Current Video Endpoint was not defined.', this.current_video_endpoint);

if (playlistPanel instanceof PlaylistPanel && playlistPanel.playlist_id !== this.current_video_endpoint.payload.playlistId) {
throw new InnertubeError('PlaylistId from TrackInfo does not match with PlaylistPanel');
}

const watch_next_endpoint = new NavigationEndpoint({ watchNextEndpoint: { ...this.current_video_endpoint.payload, continuation: playlistPanel.continuation } });
const response = await watch_next_endpoint.call(this.actions, { ...this.current_video_endpoint.payload, continuation: playlistPanel.continuation, client: 'YTMUSIC', parse: true });

const playlistCont = response.continuation_contents?.as(PlaylistPanelContinuation);

if (!playlistCont)
throw new InnertubeError('No PlaylistPanel Continuation available.', response);

return playlistCont;
}

/**
* Retrieves related content.
*/
Expand Down
21 changes: 21 additions & 0 deletions test/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,27 @@ describe('YouTube.js Tests', () => {
expect(upNext.playlist_id).toBe("PLQxo8OvVvJ1WI_Bp67F2wdIl_R2Rc_1-u");
});

test('Innertube#music.getInfo.NavEndpoint.getUpNextContinuation', async () => {
// Fetch some video from Homepage
const home = await innertube.music.getHomeFeed()
const homeItemFirst = home.sections!.first().as(YTNodes.MusicCarouselShelf).contents[0].as(YTNodes.MusicResponsiveListItem)

const info = await innertube.music.getInfo(homeItemFirst.id ?? homeItemFirst.endpoint!)
expect(info).toBeDefined();

const upNext = await info.getUpNext();

const endpoint = upNext.contents.filterType(YTNodes.PlaylistPanelVideo)[1].endpoint;

const info2 = await innertube.music.getInfo(endpoint)
const upNext2 = await info2.getUpNextContinuation(upNext)
expect(upNext2.contents?.length).toBeGreaterThan(0);

const upNext3 = await info2.getUpNextContinuation(upNext2)
expect(upNext3.contents?.length).toBeGreaterThan(0);

});

describe('Innertube#music.search', () => {
let search: YTMusic.Search;

Expand Down
Loading