ytmusic_api_unofficial
Version:
A simple API to get music from YouTube Music
610 lines (609 loc) • 32.9 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectType = detectType;
exports.topResult = topResult;
exports.rankingResponse = rankingResponse;
exports.topResults = topResults;
exports.parseMenuPlaylists = parseMenuPlaylists;
exports.parseSearchResult = parseSearchResult;
exports.parseSearchResults = parseSearchResults;
exports.getFlexColumnItem = getFlexColumnItem;
exports.parseSongFromFollowList = parseSongFromFollowList;
exports.getItemText = getItemText;
exports.parseGetResult = parseGetResult;
exports.getYTIdFromText = getYTIdFromText;
exports.getTypeByID = getTypeByID;
exports.downloadYTDL = downloadYTDL;
exports.getPlayers_dv = getPlayers_dv;
exports.customThumbnailSize = customThumbnailSize;
exports.thumbnail_defaults_size = thumbnail_defaults_size;
const responseBuilder_1 = require("./responseBuilder");
const Music_1 = __importDefault(require("../classes/Music"));
const default_1 = require("./default");
const Artist_1 = __importDefault(require("../classes/Artist"));
const User_1 = __importDefault(require("../classes/User"));
const Album_1 = __importDefault(require("../classes/Album"));
const Playlist_1 = __importDefault(require("../classes/Playlist"));
const error_1 = require("./error");
const request_1 = __importDefault(require("./request"));
const decode_1 = require("./decode");
const StreamPlayer_1 = __importDefault(require("../classes/StreamPlayer"));
const Player_1 = __importDefault(require("../classes/Player"));
const Thumbnail_1 = require("../classes/Thumbnail");
function detectType(type, none_if_empty = false) {
var _a;
if (!type)
return null;
return (_a = default_1.all_TYPES.map(x => type.toLowerCase().split(" ")[0].startsWith(x) ? x : null).filter(x => x !== null)[0]) !== null && _a !== void 0 ? _a : (none_if_empty ? null : 'song');
}
function topResult(response) {
const type = detectType((0, responseBuilder_1.nav)(response, responseBuilder_1.SUBTITLE));
const search_result = { "category": (0, responseBuilder_1.nav)(response, responseBuilder_1.CARD_SHELF_TITLE), "type": type };
if (type === "album")
search_result["browseId"] = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.TITLE, ...responseBuilder_1.NAVIGATION_BROWSE_ID], true);
if (type === 'song' || type === 'video') {
if (response.onTap) {
search_result.videoId = (0, responseBuilder_1.nav)(response.onTap, responseBuilder_1.WATCH_VIDEO_ID);
search_result.videoType = (0, responseBuilder_1.nav)(response.onTap, responseBuilder_1.NAVIGATION_VIDEO_TYPE);
}
}
if (type === "song" || type === "video" || type === "album") {
search_result.videoId = (0, responseBuilder_1.nav)(response, ["onTap", ...responseBuilder_1.WATCH_VIDEO_ID], true);
search_result.videoType = (0, responseBuilder_1.nav)(response, ["onTap", ...responseBuilder_1.NAVIGATION_VIDEO_TYPE], true);
search_result.title = (0, responseBuilder_1.nav)(response, responseBuilder_1.TITLE_TEXT);
const runs = (0, responseBuilder_1.nav)(response, ["subtitle", "runs"]);
const songInfo = parseSongRuns(runs.slice(2));
Object.assign(search_result, songInfo);
}
}
function parseSongRuns(runs) {
const parsed = { artists: [] };
runs.forEach((run, index) => {
if (run.text.length === 3 && Object.keys(run).length === 1 && index % 2 !== 0)
return;
const text = run.text;
if ("navigationEndpoint" in run) {
// Artist or album
const item = {
name: text,
id: (0, responseBuilder_1.nav)(run, responseBuilder_1.NAVIGATION_BROWSE_ID, true),
};
if (item.id && (item.id.startsWith("MPRE") || item.id.includes("release_detail"))) {
parsed.album = item;
}
else {
parsed.artists.push(item);
}
}
else {
if (/^\d([^ ])* [^ ]*$/.test(text) && index > 0) {
parsed.views = text.split(" ")[0];
}
else if (/^(\d+:)*\d+:\d+$/.test(text)) {
parsed.duration = parseDuration(text);
}
else if (/^\d{4}$/.test(text)) {
parsed.year = Number(text);
}
}
});
if (parsed.artists.length === 0)
parsed.artists.push({
name: runs[0].text,
id: undefined,
isArtist: false
});
return parsed;
}
function parseDuration(duration) {
if (!duration || !duration.trim())
return duration;
const increments = [1, 60, 3600];
const timeParts = duration.split(":").reverse();
return timeParts.reduce((total, time, index) => {
return total + (parseInt(time) * increments[index]);
}, 0);
}
function countIdenticalWords(text) {
// Remove punctuation, convert to lowercase, and split text into words
let words = text.toLowerCase().replace(/[^\w\s]/g, '').split(/\s+/);
// Create an object to count identical words
let wordCount = {};
// Loop through words and count occurrences
words.forEach(word => {
if (wordCount[word]) {
wordCount[word]++;
}
else {
wordCount[word] = 1;
}
});
return wordCount;
}
function getWords(text) {
return text
.toLowerCase() // Convert to lowercase for case-insensitive comparison
.replace(/[^\w\s]/g, "") // Remove non-word characters like punctuation
.split(/\s+/); // Split text by whitespace
}
// Function to count identical words and rank them by length
function countIdenticalWordsAndRank(text1, text2) {
const words1 = getWords(text1);
const words2 = getWords(text2);
// Create a set of words from each text
const set1 = new Set(words1);
const set2 = new Set(words2);
// Find common words in both sets
const commonWords = [...set1].filter(word => set2.has(word));
// Sort common words by length in descending order and rank them
const rankedWords = commonWords
.sort((a, b) => b.length - a.length)
.map((word, index) => ({ word, rank: index + 1, length: word.length }));
return rankedWords.reduce((total, word) => total + word.length, 0);
}
function rankingResponse(data, query) {
for (const item of data) {
if (item.title === undefined)
continue;
let ranking = countIdenticalWordsAndRank(item.title + ' - ' + item.artists.toString(), query);
if (item.title.match("clip") || item.title.match("official") || item.title.match("video"))
ranking -= 10;
if (item.isTopResult)
ranking += 50;
else {
const topResult = data.find((x) => x.isTopResult);
if (topResult) {
if (Math.abs(item.duration.duration - topResult.duration.duration) <= 15)
ranking += 20;
if (topResult.artists.some((x) => item.artists.some((y) => x.id === y.id)))
ranking += 15;
}
}
if (item.isAudioOnly)
ranking += 20;
item.searchRanking = ranking;
}
return data.sort((a, b) => b.searchRanking - a.searchRanking);
}
function topResults(response) {
let searchResult = {};
searchResult.resultType = detectType((0, responseBuilder_1.nav)(response, responseBuilder_1.SUBTITLE), true);
searchResult.category = (0, responseBuilder_1.nav)(response, responseBuilder_1.CARD_SHELF_TITLE);
searchResult.thumbnails = (0, responseBuilder_1.nav)(response, responseBuilder_1.THUMBNAILS, true);
if (["song", "video"].includes(searchResult.resultType)) {
const onTap = response.onTap;
if (onTap) {
searchResult["videoId"] = (0, responseBuilder_1.nav)(onTap, responseBuilder_1.WATCH_VIDEO_ID);
searchResult["videoType"] = (0, responseBuilder_1.nav)(onTap, responseBuilder_1.NAVIGATION_VIDEO_TYPE);
}
searchResult["videoId"] = (0, responseBuilder_1.nav)(response, ["onTap", ...responseBuilder_1.WATCH_VIDEO_ID], true);
searchResult["videoType"] = (0, responseBuilder_1.nav)(response, ["onTap", ...responseBuilder_1.NAVIGATION_VIDEO_TYPE], true);
searchResult["title"] = (0, responseBuilder_1.nav)(response, responseBuilder_1.TITLE_TEXT);
const runs = (0, responseBuilder_1.nav)(response, ["subtitle", "runs"]);
const songInfo = parseSongRuns(runs.slice(2));
searchResult = Object.assign(Object.assign({}, searchResult), songInfo);
}
if (["artist"].includes(searchResult.resultType)) {
searchResult.name = (0, responseBuilder_1.nav)(response, responseBuilder_1.TITLE_TEXT);
searchResult.followers = (0, responseBuilder_1.nav)(response, ['subtitle', 'runs', 2, 'text'], true);
searchResult.id = (0, responseBuilder_1.nav)(response, ['onTap', "browseEndpoint", "browseId"], true);
if (searchResult.followers)
searchResult.followers = searchResult.followers.split(' ')[0].includes('K') ? Number(searchResult.followers.split(' ')[0].replace('K', '')) * 1000 : searchResult.followers.split(' ')[0].includes('M') ? Number(searchResult.followers.split(' ')[0].replace('M', '')) * 1000000 : Number(searchResult.followers.split(' ')[0]);
parseMenuPlaylists(response, searchResult);
}
switch (searchResult.resultType) {
/*
case "artist":
searchResult.artist = getItemText(response, 0);
//parse_menu_playlists(data, searchResult);
new Error("Not implemented")
break;
case "album":
searchResult.type = getItemText(response, 1);
break;
*/
case "song":
case "video":
searchResult.isTopResult = true;
return new Music_1.default(searchResult);
case "artist":
return new Artist_1.default(searchResult);
default:
if (process.env.YT_DEBUG_MODE === "true")
console.log("tpRs", "Unknown result type:", searchResult.resultType);
}
}
function parseMenuPlaylists(response, result) {
const menuItems = (0, responseBuilder_1.nav)(response, responseBuilder_1.MENU_ITEMS, true);
if (!menuItems)
return;
const watchMenu = (0, responseBuilder_1.findObjectsByKey)(menuItems, responseBuilder_1.MNIR);
for (const item of watchMenu.map((_x) => _x[responseBuilder_1.MNIR])) {
const icon = (0, responseBuilder_1.nav)(item, responseBuilder_1.ICON_TYPE);
let watchKey;
switch (icon) {
case "MUSIC_SHUFFLE":
watchKey = "shuffleId";
break;
case "MIX":
watchKey = "radioId";
break;
default:
continue;
}
let watchId = (0, responseBuilder_1.nav)(item, ["navigationEndpoint", "watchPlaylistEndpoint", "playlistId"], true);
if (!watchId)
watchId = (0, responseBuilder_1.nav)(item, ["navigationEndpoint", "watchEndpoint", "playlistId"], true);
else
result[watchKey] = watchId;
}
}
function parseSearchResult(response, category = "") {
var _a;
const videoType = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.PLAY_BUTTON, "playNavigationEndpoint", ...responseBuilder_1.NAVIGATION_VIDEO_TYPE], true);
let resultType = category ? category.toLowerCase() : "";
const browseID = (0, responseBuilder_1.nav)(response, responseBuilder_1.NAVIGATION_BROWSE_ID, true);
if (!resultType || !default_1.all_TYPES.includes(resultType)) {
if (browseID) {
let mapping = {
"VM": "playlist",
"RD": "playlist",
"VL": "playlist",
"MPLA": "artist",
"MPRE": "album",
"MPSP": "podcast",
"MPED": "episode",
"UC": "artist",
};
// Return artist if browseID starts with "UC", episode if it starts with "MPED", etc.
const key = Object.keys(mapping).find(key => browseID.startsWith(key));
if (key)
resultType = (_a = mapping[key]) !== null && _a !== void 0 ? _a : (videoType === "MUSIC_VIDEO_TYPE_ATV" ? "song" : "video");
}
else {
resultType = videoType === "MUSIC_VIDEO_TYPE_ATV" ? "song" : "video";
}
}
let searchResult = {};
searchResult.resultType = detectType(resultType, true) || "";
if (resultType !== "artist")
searchResult.title = getItemText(response, 0);
else {
((0, responseBuilder_1.nav)(getFlexColumnItem(response, 1), responseBuilder_1.TEXT_RUNS, true) || []).forEach((run) => {
if (run.text.includes('subscribers'))
searchResult.followers = run.text.split(' ')[0].includes('K') ? Number(run.text.split(' ')[0].replace('K', '')) * 1000 : run.text.split(' ')[0].includes('M') ? Number(run.text.split(' ')[0].replace('M', '')) * 1000000 : Number(run.text.split(' ')[0]);
});
searchResult.name = getItemText(response, 0);
parseMenuPlaylists(response, searchResult);
}
if (["profile"].includes(searchResult.resultType))
searchResult.name = getItemText(response, 1, 2, true);
if (["song", "video", "episode"].includes(searchResult.resultType)) {
searchResult.videoId = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.PLAY_BUTTON, "playNavigationEndpoint", "watchEndpoint", "videoId"], true) || (0, responseBuilder_1.nav)(response, ["playlistItemData", "videoId"], true);
searchResult.videoType = videoType;
}
if (["song", "video", "album"].includes(searchResult.resultType)) {
searchResult.duration = null;
searchResult.year = null;
const nav_ = (0, responseBuilder_1.nav)(getFlexColumnItem(response, 1), responseBuilder_1.TEXT_RUNS, true);
if (!nav_)
return null;
const nav_Offset = (nav_[0].length === 1 ? 2 : 0);
searchResult = Object.assign(Object.assign({}, searchResult), parseSongRuns(nav_.slice(nav_Offset)));
}
if (["song", "album"].includes(searchResult.resultType))
searchResult.isExplicit = (0, responseBuilder_1.nav)(response, responseBuilder_1.BADGE_LABEL, true) !== null;
if (["artist", "album", "playlist", "profile", "podcast"].includes(searchResult.resultType))
searchResult.id = (0, responseBuilder_1.nav)(response, responseBuilder_1.NAVIGATION_BROWSE_ID, true);
if (searchResult.resultType === "album") {
searchResult.browseId = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.TITLE, ...responseBuilder_1.NAVIGATION_BROWSE_ID], true);
}
searchResult.thumbnails = (0, responseBuilder_1.nav)(response, responseBuilder_1.THUMBNAILS, true);
switch (searchResult.resultType) {
case "profile":
searchResult.name = getItemText(response, 1, 2, true);
return new User_1.default(searchResult);
case "artist":
return new Artist_1.default(searchResult);
case "album":
return new Album_1.default(searchResult);
case "song":
case "video":
if (!searchResult.duration)
searchResult.duration = parseDuration((0, responseBuilder_1.nav)(response, ["fixedColumns", 0, "musicResponsiveListItemFixedColumnRenderer", ...responseBuilder_1.TEXT_RUN_TEXT], true) || 'NaN:NaN');
return new Music_1.default(searchResult);
case "":
break;
default:
if (process.env.YT_DEBUG_MODE === "true")
console.log("srRs", "Unknown result type:", searchResult.resultType);
}
return null;
}
function parseSearchResults(response, category) {
return response.map(result => parseSearchResult(result[responseBuilder_1.MRLIR], category));
}
function getFlexColumnItem(item, index) {
if (!(item === null || item === void 0 ? void 0 : item.flexColumns))
return null;
if (item.flexColumns.length <= index || !("text" in item.flexColumns[index].musicResponsiveListItemFlexColumnRenderer) || !("runs" in item.flexColumns[index].musicResponsiveListItemFlexColumnRenderer.text)) {
return null;
}
else
return item.flexColumns[index].musicResponsiveListItemFlexColumnRenderer;
}
function parseSongFromFollowList(response) {
var _a;
let music = {};
music.resultType = "song";
music.title = (0, responseBuilder_1.nav)(response, responseBuilder_1.TITLE_TEXT, true);
music.thumbnails = (0, responseBuilder_1.nav)(response, responseBuilder_1.THUMBNAIL, true);
music.videoId = response.videoId;
music.isExplicit = (0, responseBuilder_1.nav)(response, responseBuilder_1.BADGE_LABEL, true) !== null;
music.videoType = (0, responseBuilder_1.nav)(response, ['navigationEndpoint', ...responseBuilder_1.NAVIGATION_VIDEO_TYPE], true);
music = Object.assign(Object.assign({}, music), parseSongRuns((_a = response === null || response === void 0 ? void 0 : response.longBylineText) === null || _a === void 0 ? void 0 : _a.runs));
music.duration = parseDuration((0, responseBuilder_1.nav)(response, ["lengthText", "runs", 0, "text"], true) || 'NaN:NaN');
music.radioPlID = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.MENU_ITEMS, 0, 'menuNavigationItemRenderer', "navigationEndpoint", "watchEndpoint"], true);
return new Music_1.default(music);
}
function getItemText(item, index, run_index = 0, none_if_absent = false) {
const column = getFlexColumnItem(item, index);
if (!column) {
return null;
}
if (none_if_absent && column.text.runs.length < run_index + 1) {
return null;
}
return column.text.runs[run_index].text;
}
function parseGetResult(response, type) {
var _a;
let searchResult = {};
searchResult.resultType = type;
if (['song'].includes(type)) {
response = response.contents.singleColumnMusicWatchNextResultsRenderer.tabbedRenderer.watchNextTabbedResultsRenderer.tabs;
searchResult.browseID = (0, responseBuilder_1.nav)(response, ["0", "tabRenderer", "content", "musicQueueRenderer", "queue", "playlistId"], true) || (0, responseBuilder_1.nav)(response, [1, "tabRenderer", "endpoint", "browseEndpoint", "browseId"], true);
searchResult.relativeBrowseID = (0, responseBuilder_1.nav)(response, [2, "tabRenderer", "endpoint", "browseEndpoint", "browseId"], true);
searchResult.radioPlID = (0, responseBuilder_1.nav)(response[0].tabRenderer.content, ['musicQueueRenderer', 'content', 'playlistPanelRenderer', 'contents', 1, "automixPreviewVideoRenderer", "content", "automixPlaylistVideoRenderer", "navigationEndpoint", "watchPlaylistEndpoint"], true);
response = (0, responseBuilder_1.nav)(response[0].tabRenderer.content, ['musicQueueRenderer', 'content', 'playlistPanelRenderer', 'contents', 0, 'playlistPanelVideoRenderer'], true);
searchResult.videoType = (0, responseBuilder_1.nav)(response, ["navigationEndpoint", ...responseBuilder_1.NAVIGATION_VIDEO_TYPE], true);
searchResult.thumbnails = (0, responseBuilder_1.nav)(response, responseBuilder_1.THUMBNAIL, true);
}
if (["autoMix"].includes(type)) {
response = (0, responseBuilder_1.nav)(response, ['contents', 'singleColumnMusicWatchNextResultsRenderer', 'tabbedRenderer', 'watchNextTabbedResultsRenderer', 'tabs', 0, 'tabRenderer', 'content', 'musicQueueRenderer', 'content', 'playlistPanelRenderer'], true);
searchResult.name = response.title + " - AutoMix";
searchResult.musics = ((0, responseBuilder_1.nav)(response, ["contents"], true) || []).filter((e) => { var _a; return (_a = e.playlistPanelVideoRenderer) === null || _a === void 0 ? void 0 : _a.videoId; }).map((e) => parseSongFromFollowList(e.playlistPanelVideoRenderer));
searchResult.musics = searchResult.musics.filter((e) => e.title !== response.title);
}
if (["charts"].includes(type)) {
searchResult.musics = parseSearchResults((0, responseBuilder_1.nav)(response, responseBuilder_1.CHARTS_SHELF_RENDERER, true) || [], 'song');
searchResult.name = (0, responseBuilder_1.nav)(response, responseBuilder_1.HEADER_CHART_TITLE, true);
searchResult.thumbnails = (0, responseBuilder_1.nav)(response, responseBuilder_1.HEADER_CHART_THUMBNAIL, true);
searchResult.description = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.HEADER_CHART_DESCRIPTION], true);
searchResult.id = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.CHARTS_SHELF_PLAYLISTID], true);
searchResult.artists = [];
}
if (["playlist", "album"].includes(type)) {
searchResult.musics = parseSearchResults((0, responseBuilder_1.nav)(response, [...responseBuilder_1.PLAYLIST_SHELF_RENDERER, "contents"], true) || [], 'song');
searchResult.name = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.PLAYLIST_SHELF_HEADER_RENDERER, ...responseBuilder_1.TITLE, "text"], true);
searchResult.thumbnails = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.PLAYLIST_SHELF_HEADER_RENDERER, ...responseBuilder_1.THUMBNAILS], true);
searchResult.description = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.PLAYLIST_SHELF_HEADER_RENDERER, ...responseBuilder_1.DESCRIPTION], true);
searchResult.id = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.PLAYLIST_SHELF_RENDERER, ...responseBuilder_1.PLAYLIST_ID], true);
}
if (["album"].includes(type)) {
searchResult.musics = parseSearchResults((0, responseBuilder_1.nav)(response, [...responseBuilder_1.ALBUM_SHELF_RENDERER, "contents"], true) || [], 'song');
searchResult.description = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.ALBUM_SHELF_HEADER_RENDERER, "description", ...responseBuilder_1.DESCRIPTION_SHELF, ...responseBuilder_1.DESCRIPTION], true);
searchResult.id = (getYTIdFromText((0, responseBuilder_1.nav)(response, [...responseBuilder_1.ALBUM_LINK], true))).id;
searchResult.artists = (_a = (parseSongRuns((0, responseBuilder_1.nav)(response, [...responseBuilder_1.ALBUM_SHELF_HEADER_RENDERER, "straplineTextOne", "runs"], true)))) === null || _a === void 0 ? void 0 : _a.artists;
}
if (['artist'].includes(type)) {
searchResult.name = (0, responseBuilder_1.nav)(response, responseBuilder_1.HEADER_ARTIST_TITLE, true);
searchResult.thumbnails = (0, responseBuilder_1.nav)(response, responseBuilder_1.HEADER_ARTIST_THUMBNAIL, true);
searchResult.description = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.HEADER_ARTIST, ...responseBuilder_1.DESCRIPTION], true);
searchResult.id = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.HEADER_ARTIST, ...responseBuilder_1.SUBSCRIBE_BUTTON, "channelId"], true);
searchResult.followers = (0, responseBuilder_1.nav)(response, [...responseBuilder_1.HEADER_ARTIST, ...responseBuilder_1.SUBSCRIBE_BUTTON, "longSubscriberCountText", ...responseBuilder_1.RUN_TEXT], true);
if (searchResult.followers)
searchResult.followers = searchResult.followers.split(' ')[0].includes('K') ? Number(searchResult.followers.split(' ')[0].replace('K', '')) * 1000 : searchResult.followers.split(' ')[0].includes('M') ? Number(searchResult.followers.split(' ')[0].replace('M', '')) * 1000000 : Number(searchResult.followers.split(' ')[0]);
}
if (!searchResult.resultType) {
if (searchResult.browseID) {
let mapping = {
"VM": "playlist",
"RD": "playlist",
"VL": "playlist",
"MPLA": "artist",
"MPRE": "album",
"MPSP": "podcast",
"MPED": "episode",
"UC": "artist",
};
const key = Object.keys(mapping).find(key => searchResult.browseID.startsWith(key));
if (key)
searchResult.resultType = mapping[key];
else
searchResult.resultType = searchResult.videoType === "MUSIC_VIDEO_TYPE_ATV" ? "song" : "video";
}
else {
searchResult.resultType = searchResult.videoType === "MUSIC_VIDEO_TYPE_ATV" ? "song" : "video";
}
}
if (!["artist"].includes(searchResult.resultType))
searchResult.title = (0, responseBuilder_1.nav)(response, responseBuilder_1.TITLE_TEXT, true);
if (["song", "video", "episode"].includes(searchResult.resultType)) {
searchResult.videoId = (0, responseBuilder_1.nav)(response, ["videoId"], true);
}
if (["song", "video"].includes(searchResult.resultType)) {
searchResult.duration = null;
searchResult.year = null;
const nav_ = (0, responseBuilder_1.nav)(response, responseBuilder_1.LONGTEXT_RUNS, false);
const nav_Offset = (nav_[0].length === 1 ? 2 : 0);
searchResult = Object.assign(Object.assign({}, searchResult), parseSongRuns([...nav_.slice(nav_Offset), ...response.lengthText.runs]));
}
if (["song"].includes(searchResult.resultType)) {
searchResult.isExplicit = (0, responseBuilder_1.nav)(response, responseBuilder_1.BADGE_LABEL, true) !== null;
}
switch (searchResult.resultType) {
case "song":
case "video":
return new Music_1.default(searchResult);
case "artist":
return new Artist_1.default(searchResult);
case "charts":
case "playlist":
case "album":
case "autoMix":
return new Playlist_1.default(searchResult);
default:
console.log("gtRs", "Unknown result type: ", searchResult);
}
return null;
}
function getYTIdFromText(text, precise = false) {
const reg = /https?:\/\/(?:music\.|www\.)?(?:youtube\.com\/(?:watch\?v=|v\/|channel\/|playlist\?list=)|youtu\.be\/)([A-Za-z0-9_-]+)/m;
let id = null;
if (!text.includes(' '))
id = text.match(reg);
if (id) {
const type = getTypeByID(id[1]);
if (type === "playlist" && (id[1].startsWith("PL") || id[1].startsWith("OL")))
id[1] = 'VL' + id[1];
return { id: id[1], type: type, isValidId: true };
}
else
return { isValidId: false, id: text, type: getTypeByID(text) };
}
function getTypeByID(id = "") {
if (!id)
return null;
if (id.startsWith("MPLA"))
return "artist";
if (id.startsWith("MPRE"))
return "album";
if (id.startsWith("MPSP"))
return "podcast";
if (id.startsWith("MPED"))
return "episode";
if (id.startsWith("UC"))
return "artist";
if (id.startsWith("RD"))
return "playlist";
if (id.startsWith("VL"))
return "playlist";
if (id.startsWith("VM"))
return "playlist";
if (id.startsWith("PL"))
return "playlist";
if (id.startsWith("OL"))
return "playlist";
if (id.length === 11)
return "song";
return null;
}
function downloadYTDL(query, format = default_1.AvailableFormat[0], quality = default_1.AvailableQuality[0]) {
return new Promise((resolve, reject) => {
format = format === null || format === void 0 ? void 0 : format.toLowerCase();
quality = quality === null || quality === void 0 ? void 0 : quality.toLowerCase();
if (!default_1.AvailableFormat.includes(format))
return reject((0, error_1.error)(1003, `Available formats: ${default_1.AvailableFormat.join(", ")}`));
if (!default_1.AvailableQuality.includes(quality))
return reject((0, error_1.error)(1004, `Available qualities: ${default_1.AvailableQuality.join(", ")}`));
format = format.replace('mp3', 'mp4');
const id = getYTIdFromText(query, true);
if (!id)
return reject((0, error_1.error)(1005, query));
getPlayers_dv(id.id).then((res) => __awaiter(this, void 0, void 0, function* () {
let downloads = format === 'mp4' ? res.videos : res.audios;
if (quality === 'high') {
downloads = downloads.sort((a, b) => b.bitrate - a.bitrate)[0];
}
else if (quality === 'low') {
downloads = downloads.sort((a, b) => a.bitrate - b.bitrate)[0];
}
else if (quality === 'medium') {
downloads = downloads.sort((a, b) => b.bitrate - a.bitrate)[Math.round(downloads.length / 2) - 1];
}
if (!downloads)
return reject((0, error_1.error)(2006, `No download found for ${format} ${quality}`));
downloads.urlDecoded = yield downloads.url();
resolve(new Player_1.default(downloads));
})).catch(reject);
});
}
function getPlayers_dv(id) {
const date = new Date();
// client version: "clientVersion": "1." + date.getUTCFullYear() + (date.getUTCMonth() + 1).toString().padStart(2, '0') + date.getUTCDate().toString().padStart(2, '0') + ".01.00",
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
(0, request_1.default)('player?key=', {
videoId: id,
"context": {
"client": {
"clientName": "WEB_REMIX",
"clientVersion": "1." + date.getUTCFullYear() + (date.getUTCMonth() + 1).toString().padStart(2, '0') + date.getUTCDate().toString().padStart(2, '0') + ".01.00",
}
},
"playbackContext": {
"contentPlaybackContext": {
"referer": `https://music.youtube.com/watch?v=${id}`,
"signatureTimestamp": yield (0, decode_1.getSignatureTimestamp)(),
}
}
}).then((res) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!res.streamingData) {
if (process.env.YT_DEBUG_MODE === "true")
console.log('gP_dv', res);
return reject((0, error_1.error)(2004, (0, responseBuilder_1.nav)(res, ['playabilityStatus', 'status'], true) || (0, responseBuilder_1.nav)(res, ['playabilityStatus', 'messages', 0, 'message'], true) || 'Unknown error'));
}
const items = { videos: [], audios: [] };
//TODO: fix adaptative format signature
for (const item of res.streamingData.formats) {
if (item.audioQuality)
items.audios.push(item);
else
items.videos.push(item);
}
//Sort by bitrate
items.audios = items.audios.sort((a, b) => b.bitrate - a.bitrate);
items.videos = items.videos.sort((a, b) => b.bitrate - a.bitrate);
items.available = (!!items.audio || !!items.videos) && res.playabilityStatus.status === 'OK';
items.maxBitrate = Number((_b = (_a = res === null || res === void 0 ? void 0 : res.playerConfig) === null || _a === void 0 ? void 0 : _a.streamSelectionConfig) === null || _b === void 0 ? void 0 : _b.maxBitrate);
resolve(new StreamPlayer_1.default(items));
})).catch(reject);
}));
}
function customThumbnailSize(url, width, height) {
const match = url.match(/=w(\d+)-h(\d+)/);
if (match)
url = url.replace(match[0], `=w${match[1]}-h${match[2]}`);
return url;
}
const defaultsSize = [60, 120, 180, 226, 302, 480, 544, 720, 1080];
function thumbnail_defaults_size(url, thumbnails_defaults) {
if (!url)
return [];
let thumbnails = [];
for (const size of defaultsSize) {
if (new URL(url).origin.includes('googleusercontent'))
thumbnails.push(new Thumbnail_1.Thumbnail({
url: customThumbnailSize(url, size, size),
width: size,
height: size
}));
}
if (thumbnails.length === 0)
thumbnails = thumbnails_defaults || [];
return thumbnails;
}
//# sourceMappingURL=utils.js.map