This commit is contained in:
LuanRT
2022-08-11 20:37:01 -03:00
6 changed files with 136 additions and 23 deletions

View File

@@ -8,6 +8,7 @@ import Explore from '../parser/ytmusic/Explore';
import Library from '../parser/ytmusic/Library';
import Artist from '../parser/ytmusic/Artist';
import Album from '../parser/ytmusic/Album';
import Playlist from '../parser/ytmusic/Playlist';
import Parser from '../parser/index';
import { observe, YTNode } from '../parser/helpers';
@@ -108,6 +109,16 @@ class Music {
return new Album(response, this.#actions);
}
/**
* Retrieves playlist.
*/
async getPlaylist(playlist_id: string) {
throwIfMissing({ playlist_id });
const response = await this.#actions.browse(`VL${playlist_id.replace(/VL/g, '')}`, { client: 'YTMUSIC' });
return new Playlist(response, this.#actions);
}
/**
* Retrieves song lyrics.
*/

View File

@@ -1,22 +0,0 @@
import Parser from '../index';
import { YTNode } from '../helpers';
class MusicPlaylistShelf extends YTNode {
static type = 'MusicPlaylistShelf';
#continuations;
constructor(data) {
super();
this.playlist_id = data.playlistId;
this.contents = Parser.parse(data.contents);
this.collapsed_item_count = data.collapsedItemCount;
this.#continuations = data.continuations;
}
get continuation() {
return this.#continuations?.[0]?.nextContinuationData;
}
}
export default MusicPlaylistShelf;

View File

@@ -0,0 +1,24 @@
import Parser from '../index';
import MusicResponsiveListItem from './MusicResponsiveListItem';
import { YTNode } from '../helpers';
class MusicPlaylistShelf extends YTNode {
static type = 'MusicPlaylistShelf';
playlist_id: string;
contents;
collapsed_item_count: number;
continuation: string | null;
constructor(data: any) {
super();
this.playlist_id = data.playlistId;
this.contents = Parser.parseArray<MusicResponsiveListItem>(data.contents, MusicResponsiveListItem);
this.collapsed_item_count = data.collapsedItemCount;
this.continuation = data.continuations?.[0]?.nextContinuationData?.continuation || null;
}
}
export default MusicPlaylistShelf;

View File

@@ -43,7 +43,20 @@ export class SectionListContinuation extends YTNode {
constructor(data: any) {
super();
this.contents = Parser.parse(data.contents, true);
this.continuation = data.continuations[0].nextContinuationData.continuation;
this.continuation = data.continuations?.[0].nextContinuationData.continuation || null;
}
}
export class MusicPlaylistShelfContinuation extends YTNode {
static readonly type = 'musicPlaylistShelfContinuation';
continuation: string;
contents: ObservedArray<YTNode> | null;
constructor(data: any) {
super();
this.contents = Parser.parse(data.contents, true);
this.continuation = data.continuations?.[0].nextContinuationData.continuation || null;
}
}
@@ -220,6 +233,8 @@ export default class Parser {
return new SectionListContinuation(data.sectionListContinuation);
if (data.liveChatContinuation)
return new LiveChatContinuation(data.liveChatContinuation);
if (data.musicPlaylistShelfContinuation)
return new MusicPlaylistShelfContinuation(data.musicPlaylistShelfContinuation);
}
static parseRR(actions: any[]) {

View File

@@ -0,0 +1,80 @@
import Parser, { MusicPlaylistShelfContinuation, ParsedResponse, SectionListContinuation } from '../index';
import Actions, { AxioslikeResponse } from '../../core/Actions';
import MusicDetailHeader from '../classes/MusicDetailHeader';
import MusicCarouselShelf from '../classes/MusicCarouselShelf';
import MusicPlaylistShelf from '../classes/MusicPlaylistShelf';
import SectionList from '../classes/SectionList';
import { InnertubeError } from '../../utils/Utils';
class Playlist {
#page;
#actions;
#continuation;
header;
items;
constructor(response: AxioslikeResponse, actions: Actions) {
this.#actions = actions;
this.#page = Parser.parseResponse(response.data);
this.#actions = actions;
if (this.#page.continuation_contents) {
const data = this.#page.continuation_contents?.as(MusicPlaylistShelfContinuation);
this.items = data.contents;
this.#continuation = data.continuation;
} else {
this.header = this.#page.header.item().as(MusicDetailHeader);
this.items = this.#page.contents_memo.get('MusicPlaylistShelf')?.[0].as(MusicPlaylistShelf).contents;
this.#continuation = this.#page.contents_memo.get('MusicPlaylistShelf')?.[0].as(MusicPlaylistShelf).continuation || null;
}
}
get page(): ParsedResponse {
return this.#page;
}
get has_continuation() {
return !!this.#continuation;
}
/**
* Retrieves playlist item continuation.
*/
async getContinuation() {
if (this.#continuation) {
const response = await this.#actions.browse(this.#continuation, { is_ctoken: true, client: 'YTMUSIC' });
return new Playlist(response, this.#actions);
}
throw new InnertubeError('Continuation not found.');
}
/**
* Retrieves related playlists
*/
async getRelated() {
let section_continuation = this.#page.contents_memo.get('SectionList')?.[0].as(SectionList).continuation;
while (section_continuation) {
const response = await this.#actions.browse(section_continuation, { is_ctoken: true, client: 'YTMUSIC' });
const data = Parser.parseResponse(response.data);
const section_list = data.continuation_contents?.as(SectionListContinuation);
const sections = section_list?.contents?.as(MusicCarouselShelf);
const related = sections?.filter((section) => section.header?.title === 'Related playlists')[0];
if (related) {
return related.contents || [];
}
section_continuation = section_list?.continuation;
}
return [];
}
}
export default Playlist;

View File

@@ -62,6 +62,11 @@ describe('YouTube.js Tests', () => {
const playlist = await this.session.getPlaylist('PLLw0AzOz95FU7w2juhPECP9NyGhbZmz_t', { client: 'YOUTUBE' });
expect(playlist.items.length).toBeLessThanOrEqual(100);
});
it('Should retrieve playlist with YouTube Music', async () => {
const playlist = await this.session.music.getPlaylist('PLVbEymL-83SyVXXqT7fYX5sEvELvyGjL7');
expect(playlist.items.length).toBeLessThanOrEqual(100);
});
it('Should retrieve home feed', async () => {
const homefeed = await this.session.getHomeFeed();