@alufi/ytsr
Version:
A ytsr fork. Made for Alufi Bot Utility
113 lines (90 loc) • 4.69 kB
JavaScript
const PARSE_ITEM = require('./parseItem.js');
const MINIGET = require('miniget');
const UTIL = require('./util.js');
const QS = require('querystring');
const BASE_SEARCH_URL = 'https://www.youtube.com/results?';
const BASE_API_URL = 'https://www.youtube.com/youtubei/v1/search?key=';
const CACHE = new Map();
// Save api key and client version for safeSearch
const saveCache = (parsed, opts) => {
if (parsed.apiKey) CACHE.set('apiKey', parsed.apiKey);
else if (CACHE.has('apiKey')) parsed.apiKey = CACHE.get('apiKey');
if (parsed.context) CACHE.set('clientVersion', parsed.context.client.clientVersion);
else if (CACHE.has('clientVersion')) parsed.context = UTIL.buildPostContext(CACHE.get('clientVersion'), opts);
};
// eslint-disable-next-line complexity
const main = module.exports = async (searchString, options, rt = 3) => {
if (rt === 2) {
CACHE.delete('apiKey');
CACHE.delete('clientVersion');
CACHE.delete('playlistParams');
}
if (rt === 0) throw new Error('Unable to find JSON!');
// Set default values
const opts = UTIL.checkArgs(searchString, options);
const ref = BASE_SEARCH_URL + QS.encode(opts.query);
let parsed = {};
if (!opts.safeSearch || !CACHE.has('apiKey') || !CACHE.has('clientVersion') || !CACHE.has('playlistParams')) {
const body = await MINIGET(ref, opts.requestOptions).text();
parsed = UTIL.parseBody(body, opts);
let plParams = UTIL.betweenFromRight(body, `"params":"`, '"}},"tooltip":"Search for Playlist"');
if (plParams) CACHE.set('playlistParams', plParams);
saveCache(parsed, opts);
}
if (opts.type === 'playlist') {
let params = 'EgIQAw%253D%253D';
if (CACHE.has('playlistParams')) params = CACHE.get('playlistParams');
parsed.json = await UTIL.doPost(BASE_API_URL + parsed.apiKey, opts, {
context: parsed.context,
params,
query: searchString,
}).catch(() => null);
if (!parsed.json) throw new Error('Cannot searching for Playlist!');
} else if (opts.safeSearch || !parsed.json) {
try {
if (!parsed.apiKey || !parsed.context.client.clientVersion) throw new Error('Missing api key');
const context = parsed.context;
parsed.json = await UTIL.doPost(BASE_API_URL + parsed.apiKey, opts, { context, query: searchString });
} catch (Error) { if (rt === 1) throw Error }
}
if (!parsed.json) return main(searchString, options, rt - 1);
const resp = { query: opts.search };
try {
// Parse items
const { rawItems, continuation } = UTIL.parseWrapper(parsed.json.contents.twoColumnSearchResultsRenderer.primaryContents);
// Parse items
resp.data = rawItems.map(a => PARSE_ITEM(a, resp)).filter(r => r && r.type === opts.type).filter((_, index) => index < opts.limit);
// Adjust tracker
opts.limit -= resp.data.length;
// Get amount of results
resp.results = Number(parsed.json.estimatedResults) || 0;
let token = null;
if (continuation) token = continuation.continuationItemRenderer.continuationEndpoint.continuationCommand.token;
// We're already on last page or hit the limit
if (!token || opts.limit < 1) return resp;
// Recursively fetch more items
const nestedResp = await parsePage2(parsed.apiKey, token, parsed.context, opts);
// Merge the responses
resp.data.push(...nestedResp);
return resp;
} catch (error) { throw new Error('No result!'); }
};
const parsePage2 = async (apiKey, token, context, opts) => {
const json = await UTIL.doPost(BASE_API_URL + apiKey, opts.requestOptions, { context, continuation: token });
if (!Array.isArray(json.onResponseReceivedCommands)) {
// No more content
return [];
}
const { rawItems, continuation } = UTIL.parsePage2Wrapper(json.onResponseReceivedCommands[0].appendContinuationItemsAction.continuationItems);
const parsedItems = rawItems.map(PARSE_ITEM).filter(r => r && r.type === opts.type).filter((_, index) => index < opts.limit);
// Adjust tracker
opts.limit -= parsedItems.length;
let nextToken = null;
if (continuation) nextToken = continuation.continuationItemRenderer.continuationEndpoint.continuationCommand.token;
// We're already on last page or hit the limit
if (!nextToken || opts.limit < 1) return parsedItems;
// Recursively fetch more items
const nestedResp = await parsePage2(apiKey, nextToken, context, opts);
parsedItems.push(...nestedResp);
return parsedItems;
};