Files
YouTube.js/lib/core/Music.js
LuanRT 1d62e469a9 refactor: rewrite Comments Section logic (#88)
* feat: add core comments section classes

* chore: update type declarations

* chore: fix linter warnings

* style: fix linter

* chore: update tests

* chore(tests): fix typo

* chore(tests): fix typo x2

* fix(tests): `getReplies()` method is only present in `CommentThread` and not `Comment`

* chore(tests): fix comment id path

* chore(tests): remove outdated code

* chore(tests): fix results path

* chore: enforce code style

* chore: update type declarations

* docs: add examples and documentation

* chore(docs): fix paths

* chore(docs): fix more paths

* chore(docs): fix `Comments.js` path

* chore(docs): fix typo

* chore(docs): mention example file

* chore(examples): fix imports

* chore(examples): fix typo
2022-07-02 19:55:33 -03:00

172 lines
5.0 KiB
JavaScript

'use strict';
const Parser = require('../parser/contents');
const Search = require('../parser/ytmusic/Search');
const HomeFeed = require('../parser/ytmusic/HomeFeed');
const Explore = require('../parser/ytmusic/Explore');
const Library = require('../parser/ytmusic/Library');
const Artist = require('../parser/ytmusic/Artist');
const Album = require('../parser/ytmusic/Album');
const { InnertubeError, observe } = require('../utils/Utils');
/** @namespace */
class Music {
#session;
#actions;
/**
* @param {import('../Innertube')} session
*/
constructor(session) {
this.#session = session;
this.#actions = session.actions;
}
/**
* Searches on YouTube Music.
*
* @param {string} query
* @param {object} filters - search filters
* @param {string} [filters.type] - all | song | video | album | playlist | artist
* @returns {Promise.<Search>}
*/
async search(query, filters) {
const response = await this.#actions.search({ query, filters, client: 'YTMUSIC' });
return new Search(response, this.#actions, { is_filtered: filters?.hasOwnProperty('type') && filters.type !== 'all' });
}
/**
* Retrieves the home feed.
*
* @returns {Promise.<HomeFeed>}
*/
async getHomeFeed() {
const response = await this.#actions.browse('FEmusic_home', { client: 'YTMUSIC' });
return new HomeFeed(response, this.#actions);
}
/**
* Retrieves the Explore feed.
*
* @returns {Promise.<Explore>}
*/
async getExplore() {
const response = await this.#actions.browse('FEmusic_explore', { client: 'YTMUSIC' });
return new Explore(response, this.#actions);
}
/**
* Retrieves the Library.
*
* @returns {Promise.<Library>}
*/
async getLibrary() {
const response = await this.#actions.browse('FEmusic_liked_albums', { client: 'YTMUSIC' });
return new Library(response, this.#actions);
}
/**
* Retrieves artist's info & content.
*
* @param {string} artist_id
* @returns {Promise.<Artist>}
*/
async getArtist(artist_id) {
if (!artist_id.startsWith('UC')) throw new InnertubeError('Invalid artist id', artist_id);
const response = await this.#actions.browse(artist_id, { client: 'YTMUSIC' });
return new Artist(response, this.#actions);
}
/**
* Retrieves album.
*
* @param {string} album_id
* @returns {Promise.<Album>}
*/
async getAlbum(album_id) {
if (!album_id.startsWith('MPR')) throw new InnertubeError('Invalid album id', album_id);
const response = await this.#actions.browse(album_id, { client: 'YTMUSIC' });
return new Album(response, this.#actions);
}
/**
* Retrieves song lyrics.
*
* @param {string} video_id
*/
async getLyrics(video_id) {
const response = await this.#actions.next({ video_id, client: 'YTMUSIC' });
const data = Parser.parseResponse(response.data);
const tab = data.contents.tabs.get({ title: 'Lyrics' });
const page = await tab.endpoint.call(this.#actions, 'YTMUSIC');
if (!page) throw new InnertubeError('Invalid video id');
if (page.contents.constructor.name === 'Message')
throw new InnertubeError(page.contents.text, video_id);
const description_shelf = page.contents.contents.get({ type: 'MusicDescriptionShelf' });
return {
/** @type {string} */
text: description_shelf.description.toString(),
/** @type {import('../parser/contents/classes/Text')} */
footer: description_shelf.footer
};
}
/**
* Retrieves up next.
*
* @param {string} video_id
*/
async getUpNext(video_id) {
const response = await this.#actions.next({ video_id, client: 'YTMUSIC' });
const data = Parser.parseResponse(response.data);
const tab = data.contents.tabs.get({ title: 'Up next' });
const upnext_content = tab.content.content;
if (!upnext_content) throw new InnertubeError('Invalid id', video_id);
return {
/** @type {string} */
id: upnext_content.playlist_id,
/** @type {string} */
title: upnext_content.title,
/** @type {boolean} */
is_editable: upnext_content.is_editable,
/** @type {import('../parser/contents/classes/PlaylistPanelVideo')[]} */
contents: observe(upnext_content.contents)
};
}
/**
* Retrieves related content.
*
* @param {string} video_id
*/
async getRelated(video_id) {
const response = await this.#actions.next({ video_id, client: 'YTMUSIC' });
const data = Parser.parseResponse(response.data);
const tab = data.contents.tabs.get({ title: 'Related' });
const page = await tab.endpoint.call(this.#actions, 'YTMUSIC');
if (!page) throw new InnertubeError('Invalid video id');
const shelves = page.contents.contents.findAll({ type: 'MusicCarouselShelf' });
const info = page.contents.contents.get({ type: 'MusicDescriptionShelf' });
return {
/** @type {import('../parser/contents/classes/MusicCarouselShelf')[]} */
sections: shelves,
/** @type {string} */
info: info?.description.toString() || ''
};
}
}
module.exports = Music;