"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LibraryMixin = void 0;
const helpers_1 = require("../helpers");
const parsers_1 = require("../parsers");
const browsing_1 = require("../parsers/browsing");
const library_1 = require("../parsers/library");
const playlists_1 = require("../parsers/playlists");
const utils_1 = require("../parsers/utils");
/**
* @module Library
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const LibraryMixin = (Base) => {
return class LibraryMixin extends Base {
/**
* Retrieves the playlists in the user's library.
* @param {number} [limit = 25] Number of playlists to retrieve.
* @return Array of owned playlists.
* @example <caption>Each item is in the following format</caption>
* {
* 'playlistId': 'PLQwVIlKxHM6rz0fDJVv_0UlXGEWf-bFys',
* 'title': 'Playlist title',
* 'thumbnails: [...],
* 'count': 5
* }
*/
async getLibraryPlaylists(limit = 25) {
this._checkAuth();
const body = { browseId: 'FEmusic_liked_playlists' };
const endpoint = 'browse';
const response = await this._sendRequest(endpoint, body);
let results = (0, utils_1.findObjectByKey)((0, utils_1.nav)(response, [...parsers_1.SINGLE_COLUMN_TAB, ...parsers_1.SECTION_LIST]), 'itemSectionRenderer');
results = (0, utils_1.nav)(results, [...parsers_1.ITEM_SECTION, ...parsers_1.GRID]);
let playlists = (0, browsing_1.parseContentList)(results['items'].slice(1), browsing_1.parsePlaylist);
if ('continuations' in results) {
const requestFunc = async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams);
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const parseFunc = (contents) => (0, browsing_1.parseContentList)(contents, browsing_1.parsePlaylist);
playlists = [
...playlists,
...(await (0, utils_1.getContinuations)(results, 'gridContinuation', limit - playlists.length, requestFunc, parseFunc)),
];
}
return playlists;
}
/**
* Gets the songs in the user's library (liked videos are not included).
* To get liked songs and videos, use `getLikedSongs`.
* @param {Object} [options=]
* @param {number} [options.limit = 25] Limit number of songs to retrieve.
* @param {boolean} [options.validateResponse = false] Flag indicating if responses from YTM should be validated and retried in case when some songs are missing.
* @param {lt.Order} [options.order=] Order of songs to return. Allowed values: 'a_to_z', 'z_to_a', 'recently_added'.
* @return List of songs. Same format as `getPlaylist`
*/
async getLibrarySongs(options) {
this._checkAuth();
const { limit = 25, validateResponse = false, order } = options ?? {};
const body = { browseId: 'FEmusic_liked_videos' };
(0, helpers_1.validateOrderParameters)(order);
if (order) {
body['params'] = (0, helpers_1.prepareOrderParams)(order);
}
const endpoint = 'browse';
const perPage = 25;
const requestFunc = async (_additionalParams) => await this._sendRequest(endpoint, body); //additionalParams doesnt do anything? @codyduong PR this.
const parseFunc = (rawResponse) => (0, library_1.parseLibrarySongs)(rawResponse);
let response;
if (validateResponse) {
const validateFunc = (parsed) => (0, utils_1.validateResponse)(parsed, perPage, limit, 0);
response = await (0, utils_1.resendRequestUntilParsedResponseIsValid)(requestFunc, null, parseFunc, validateFunc, 3);
}
else {
response = parseFunc(await requestFunc(null));
}
const results = response['results'];
let songs = response['parsed'];
if ('continuations' in results) {
const requestContinuationsFunc = async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams);
const parseContinuationsFunc = (contents) => (0, playlists_1.parsePlaylistItems)(contents);
if (validateResponse) {
songs = [
...songs,
...(await (0, utils_1.getValidatedContinuations)(results, 'musicShelfContinuation', limit - songs.length, perPage, requestContinuationsFunc, parseContinuationsFunc)),
];
}
else {
songs = [
...songs,
...(await (0, utils_1.getContinuations)(results, 'musicShelfContinuation', limit - songs.length, requestContinuationsFunc, parseContinuationsFunc)),
];
}
}
return songs;
}
/**
* Gets the albums in the user's library.
* @param {Object} [options=]
* @param {number} [options.limit = 25] Number of albums to return.
* @param {lt.Order} [options.order=] Order of albums to return. Allowed values: 'a_to_z', 'z_to_a', 'recently_added'.
* @return List of albums.
* @example <caption>Each item is in the following format</caption>
* {
* "browseId": "MPREb_G8AiyN7RvFg",
* "title": "Beautiful",
* "type": "Album",
* "thumbnails": [...],
* "artists": [{
* "name": "Project 46",
* "id": "UCXFv36m62USAN5rnVct9B4g"
* }],
* "year": "2015"
* }
*/
async getLibraryAlbums(options) {
this._checkAuth();
const { limit = 25, order } = options ?? {};
const body = { browseId: 'FEmusic_liked_albums' };
if (order) {
body['params'] = (0, helpers_1.prepareOrderParams)(order);
}
const endpoint = 'browse';
const response = await this._sendRequest(endpoint, body);
return await (0, library_1.parseLibraryAlbums)(response, async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams), limit);
}
/**
* Gets the artists of the songs in the user's library.
* @param {Object} [options=]
* @param {number} [options.limit = 25] Number of artists to return.
* @param {lt.Order} [options.order=] Order of artists to return.
* @return List of artists.
* @example <caption>Each item is in the following format</caption>
* {
* "browseId": "UCxEqaQWosMHaTih-tgzDqug",
* "artist": "WildVibes",
* "subscribers": "2.91K",
* "thumbnails": [...]
* }
*/
async getLibraryArtists(options) {
this._checkAuth();
const body = { browseId: 'FEmusic_library_corpus_track_artists' };
const { limit = 25, order } = options ?? {};
(0, helpers_1.validateOrderParameters)(order);
const endpoint = 'browse';
const response = await this._sendRequest(endpoint, body);
return await (0, library_1.parseLibraryArtists)(response, async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams), limit);
}
/**
* Gets the artists the user has subscribed to.
* @param options
* @param {number} [options.limit=25] Number of artists to return.
* @param {lt.Order} [options.order=] Order of artists to return. Allowed values: 'a_to_z', 'z_to_a', 'recently_added'.
* @return List of artists. Same format as `getLibraryArtists`
*/
async getLibrarySubscriptions(options) {
this._checkAuth();
const { limit = 25, order } = options ?? {};
const body = {
browseId: 'FEmusic_library_corpus_artists',
};
(0, helpers_1.validateOrderParameters)(order);
if (order) {
body['params'] = (0, helpers_1.prepareOrderParams)(order);
}
const endpoint = 'browse';
const response = await this._sendRequest(endpoint, body);
return (0, library_1.parseLibraryArtists)(response, async (additionalParams) => await this._sendRequest(endpoint, body, additionalParams), limit);
}
/**
* Gets playlist items for the 'Liked Songs' playlist.
* @param {number} [limit=100] How many items to return.
* @return List of playlistItem dictionaries. See `getPlaylist`
*/
async getLikedSongs(limit = 100) {
return await this.getPlaylist('LM', limit);
}
/**
* Gets your play history in reverse chronological order.
*
* @return List of playlistItems, see `getPlaylist`.
*
* The additional property ``played`` indicates when the playlistItem was played.
*
* The additional property ``feedbackToken`` can be used to remove items with `removeHistoryItems`.
*/
async getHistory() {
this._checkAuth();
const body = { browseId: 'FEmusic_history' };
const endpoint = 'browse';
const response = await this._sendRequest(endpoint, body);
const results = (0, utils_1.nav)(response, [...parsers_1.SINGLE_COLUMN_TAB, ...parsers_1.SECTION_LIST]);
let songs = [];
for (const content of results) {
const data = (0, utils_1.nav)(content, [...parsers_1.MUSIC_SHELF, 'contents'], true);
if (!data) {
const error = (0, utils_1.nav)(content, ['musicNotifierShelfRenderer', ...parsers_1.TITLE], true);
throw new Error(error);
}
const menuEntries = [[-1, ...parsers_1.MENU_SERVICE, ...parsers_1.FEEDBACK_TOKEN]];
const songlist = (0, playlists_1.parsePlaylistItems)(data, menuEntries);
for (const song of songlist) {
song['played'] = (0, utils_1.nav)(content['musicShelfRenderer'], parsers_1.TITLE_TEXT);
}
songs = [...songs, ...songlist];
}
return songs;
}
/**
* Remove an item from the account's history. This method does currently not work with brand accounts.
* @param feedbackTokens Token to identify the item to remove, obtained from `getHistory`.
* @return Full response.
*/
async removeHistoryItems(feedbackTokens) {
this._checkAuth();
const body = { feedbackTokens };
const endpoint = 'feedback';
const response = await this._sendRequest(endpoint, body);
return response;
}
/**
* Rates a song ("thumbs up"/"thumbs down" interactions on YouTube Music).
* @param {string} [videoId] Video id.
* @param {string} [rating='INDIFFERENT'] One of 'LIKE', 'DISLIKE', 'INDIFFERENT'.
* 'INDIFFERENT' removes the playlist/album from the library
* @return Full response.
*/
async rateSong(videoId, rating = 'INDIFFERENT') {
this._checkAuth();
const body = { target: { videoId } };
const endpoint = (0, helpers_1.prepareLikeEndpoint)(rating);
if (!endpoint) {
throw new Error('Invalid rating provided');
}
return await this._sendRequest(endpoint, body);
}
/**
* Adds or removes a song from your library depending on the token provided.
* @param {string[]} [feedbackTokens] List of feedbackTokens obtained from authenticated requests
* to endpoints that return songs (i.e. `get_album`).
* @return Full response.
*/
async editSongLibraryStatus(feedbackTokens) {
this._checkAuth();
const body = { feedbackTokens };
const endpoint = 'feedback';
return await this._sendRequest(endpoint, body);
}
/**
* Rates a playlist/album ("Add to library"/"Remove from library" interactions on YouTube Music)
* You can also dislike a playlist/album, which has an effect on your recommendations
* @param {string} [videoId] Playlist id.
* @param {string} [rating='INDIFFERENT'] One of 'LIKE', 'DISLIKE', 'INDIFFERENT'.
* 'INDIFFERENT' removes the playlist/album from the library.
* @return Full response.
*/
async ratePlaylist(playlistId, rating = 'INDIFFERENT') {
this._checkAuth();
const body = { target: { playlistId } };
const endpoint = (0, helpers_1.prepareLikeEndpoint)(rating);
if (!endpoint) {
throw new Error('Invalid rating provided');
}
return await this._sendRequest(endpoint, body);
}
/**
* Subscribe to artists. Adds the artists to your library.
* @param channelIds Artist channel ids.
* @return Full response.
*/
async subscribeArtists(channelIds) {
this._checkAuth();
const body = { channelIds };
const endpoint = 'subscription/subscribe';
return await this._sendRequest(endpoint, body);
}
/**
* Unsubscribe to artists. Removes the artists to your library.
* @param channelIds Artist channel ids.
* @return Full response.
*/
async unsubscribeArtists(channelIds) {
this._checkAuth();
const body = { channelIds: channelIds };
const endpoint = 'subscription/unsubscribe';
return await this._sendRequest(endpoint, body);
}
};
};
exports.LibraryMixin = LibraryMixin;