"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ExploreMixin = void 0;
const parsers_1 = require("../parsers");
const browsing_1 = require("../parsers/browsing");
const explore_1 = require("../parsers/explore");
const utils_1 = require("../parsers/utils");
/**
* @module Explore
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const ExploreMixin = (Base) => {
return class ExploreMixin extends Base {
/**
* Fetch "Moods & Genres" categories from YouTube Music.
*
* @return Object of sections and categories
* @example
* {
* 'For you': [
* {
* 'params': 'ggMPOg1uX1ZwN0pHT2NBT1Fk',
* 'title': '1980s'
* },
* {
* 'params': 'ggMPOg1uXzZQbDB5eThLRTQ3',
* 'title': 'Feel Good'
* },
* ...
* ],
* 'Genres': [
* {
* 'params': 'ggMPOg1uXzVLbmZnaWI4STNs',
* 'title': 'Dance & Electronic'
* },
* {
* 'params': 'ggMPOg1uX3NjZllsNGVEMkZo',
* 'title': 'Decades'
* },
* ...
* ],
* 'Moods & moments': [
* {
* 'params': 'ggMPOg1uXzVuc0dnZlhpV3Ba',
* 'title': 'Chill'
* },
* {
* 'params': 'ggMPOg1uX2ozUHlwbWM3ajNq',
* 'title': 'Commute'
* },
* ...
* ],
* }
*/
async getMoodCategories() {
const sections = {};
const response = await this._sendRequest('browse', { browseId: 'FEmusic_moods_and_genres' });
const naved = (0, utils_1.nav)(response, [
...parsers_1.SINGLE_COLUMN_TAB,
...parsers_1.SECTION_LIST,
]);
for (const section of naved) {
const title = (0, utils_1.nav)(section, [
...parsers_1.GRID,
'header',
'gridHeaderRenderer',
...parsers_1.TITLE_TEXT,
]);
sections[title] = [];
for (const category of (0, utils_1.nav)(section, parsers_1.GRID_ITEMS)) {
sections[title].push({
title: (0, utils_1.nav)(category, parsers_1.CATEGORY_TITLE),
params: (0, utils_1.nav)(category, parsers_1.CATEGORY_PARAMS),
});
}
}
return sections;
}
/**
* Retrieve a list of playlists for a given "Moods & Genres" category.
* @param params params obtained by `getMoodCategories`
* @returns List of playlists in the format of `getLibraryPlaylists`
*/
async getMoodPlaylists(params) {
let playlists = [];
const response = await this._sendRequest('browse', {
browseId: 'FEmusic_moods_and_genres_category',
params: params,
});
for (const section of (0, utils_1.nav)(response, [...parsers_1.SINGLE_COLUMN_TAB, ...parsers_1.SECTION_LIST])) {
let path = [];
if ('gridRenderer' in section) {
path = parsers_1.GRID_ITEMS;
}
else if ('musicCarouselShelfRenderer' in section) {
path = parsers_1.CAROUSEL_CONTENTS;
}
else if ('musicImmersiveCarouselShelfRenderer' in section) {
path = ['musicImmersiveCarouselShelfRenderer', 'contents'];
}
if (path.length) {
const results = (0, utils_1.nav)(section, path);
playlists = [
...playlists,
...(0, browsing_1.parseContentList)(results, browsing_1.parsePlaylist),
];
}
}
return playlists;
}
/**
* Get latest charts data from YouTube Music: Top songs, top videos, top artists and top trending videos.
* Global charts have no Trending section, US charts have an extra Genres section with some Genre charts.
* @param {string} [country = 'ZZ'] ISO 3166-1 Alpha-2 country code.
* @returns Dictionary containing chart songs (only if authenticated), chart videos, chart artists and trending videos.
* @example
* {
* "countries": {
* "selected": {
* "text": "United States"
* },
* "options": ["DE",
* "ZZ",
* "ZW"]
* },
* "songs": {
* "playlist": "VLPL4fGSI1pDJn6O1LS0XSdF3RyO0Rq_LDeI",
* "items": [
* {
* "title": "Outside (Better Days)",
* "videoId": "oT79YlRtXDg",
* "artists": [
* {
* "name": "MO3",
* "id": "UCdFt4Cvhr7Okaxo6hZg5K8g"
* },
* {
* "name": "OG Bobby Billions",
* "id": "UCLusb4T2tW3gOpJS1fJ-A9g"
* }
* ],
* "thumbnails": [...],
* "isExplicit": true,
* "album": {
* "name": "Outside (Better Days)",
* "id": "MPREb_fX4Yv8frUNv"
* },
* "rank": "1",
* "trend": "up"
* }
* ]
* },
* "videos": {
* "playlist": "VLPL4fGSI1pDJn69On1f-8NAvX_CYlx7QyZc",
* "items": [
* {
* "title": "EVERY CHANCE I GET (Official Music Video) (feat. Lil Baby & Lil Durk)",
* "videoId": "BTivsHlVcGU",
* "playlistId": "PL4fGSI1pDJn69On1f-8NAvX_CYlx7QyZc",
* "thumbnails": [],
* "views": "46M"
* }
* ]
* },
* "artists": {
* "playlist": null,
* "items": [
* {
* "title": "YoungBoy Never Broke Again",
* "browseId": "UCR28YDxjDE3ogQROaNdnRbQ",
* "subscribers": "9.62M",
* "thumbnails": [],
* "rank": "1",
* "trend": "neutral"
* }
* ]
* },
* "genres": [
* {
* "title": "Top 50 Pop Music Videos United States",
* "playlistId": "PL4fGSI1pDJn77aK7sAW2AT0oOzo5inWY8",
* "thumbnails": []
* }
* ],
* "trending": {
* "playlist": "VLPLrEnWoR732-DtKgaDdnPkezM_nDidBU9H",
* "items": [
* {
* "title": "Permission to Dance",
* "videoId": "CuklIb9d3fI",
* "playlistId": "PLrEnWoR732-DtKgaDdnPkezM_nDidBU9H",
* "artists": [
* {
* "name": "BTS",
* "id": "UC9vrvNSL3xcWGSkV86REBSg"
* }
* ],
* "thumbnails": [],
* "views": "108M"
* }
* ]
* }
* }
*/
async getCharts(country = 'ZZ') {
const body = { browseId: 'FEmusic_charts' };
if (country) {
body['formData'] = { selectedValues: [country] };
}
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]);
const charts = {
countries: {},
};
const menu = (0, utils_1.nav)(results[0], [
...parsers_1.MUSIC_SHELF,
'subheaders',
0,
'musicSideAlignedItemRenderer',
'startItems',
0,
'musicSortFilterButtonRenderer',
]);
charts['countries']['selected'] = (0, utils_1.nav)(menu, parsers_1.TITLE);
charts['countries']['options'] = (0, utils_1.nav)(response, parsers_1.FRAMEWORK_MUTATIONS)
.map((m) => (0, utils_1.nav)(m, ['payload', 'musicFormBooleanChoice', 'opaqueToken'], true))
.filter((x) => x);
const chartsCategories = ['videos', 'artists'];
const hasSongs = !!this.getAuth();
const hasGenres = country == 'US';
const hasTrending = country != 'ZZ';
if (hasSongs) {
chartsCategories.splice(0, 0, 'songs');
}
if (hasGenres) {
chartsCategories.push('genres');
}
if (hasTrending) {
chartsCategories.push('trending');
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
const parseChart = (i, parseFunc, key) => {
return (0, browsing_1.parseContentList)((0, utils_1.nav)(results[i + (hasSongs ? 1 : 0)], parsers_1.CAROUSEL_CONTENTS, true), parseFunc, key).filter((x) => x);
};
for (const [i, c] of chartsCategories.entries()) {
//@ts-expect-error, we'll set the items later...
charts[c] = {
playlist: (0, utils_1.nav)(results[1 + i], [...parsers_1.CAROUSEL, ...parsers_1.CAROUSEL_TITLE, ...parsers_1.NAVIGATION_BROWSE_ID], true),
};
}
if (hasSongs) {
charts['songs'] = {
...charts['songs'],
...{
items: parseChart(0, explore_1.parseChartSong, parsers_1.MRLIR),
},
};
}
charts['videos']['items'] = parseChart(1, browsing_1.parseVideo, parsers_1.MTRIR);
charts['artists']['items'] = parseChart(2, explore_1.parseChartArtist, parsers_1.MRLIR);
if (hasGenres) {
//@ts-expect-error: TS didn't detect this control flow discrimination...
charts['genres'] = parseChart(3, browsing_1.parsePlaylist, parsers_1.MTRIR);
}
if (hasTrending) {
charts['trending']['items'] = parseChart(3 + (hasGenres ? 1 : 0), explore_1.parseChartTrending, parsers_1.MRLIR);
}
return charts;
}
};
};
exports.ExploreMixin = ExploreMixin;