40 lines
2.4 KiB
JavaScript
40 lines
2.4 KiB
JavaScript
// Bounded client fallbacks shared by metadata probes and actual downloads.
|
|
export const BOT_CHECK_RE = /Sign in to confirm|not a bot|confirm you.{0,3}re not a bot/i;
|
|
export const DEFAULT_FALLBACK_CLIENTS = ['web_embedded', 'web_safari', 'tv_simply', 'android_vr', 'mweb'];
|
|
|
|
export function canTryAnotherClient(error) {
|
|
const message = String(error?.message || error || '');
|
|
// Explicit access restrictions are not fixed by selecting a different client.
|
|
if (/private video|members.only|join this channel|not available in your country|geo.?restricted|removed by|video has been removed|copyright claim|age.restricted|confirm your age/i.test(message)) return false;
|
|
return BOT_CHECK_RE.test(message) || /video unavailable|requested format (?:is )?not available|no video formats found/i.test(message);
|
|
}
|
|
|
|
export function createResilientYtdlp({ run, withCookies = args => args, clients = DEFAULT_FALLBACK_CLIENTS, log = console }) {
|
|
return async function runResilient(args, opts = {}) {
|
|
const explicitClient = args.some(arg => String(arg).includes('player_client=') || String(arg).includes('player-client='));
|
|
if (opts.signal?.aborted) throw opts.signal.reason || new Error('save cancelled');
|
|
try {
|
|
return await run(withCookies(args), opts);
|
|
} catch (initial) {
|
|
if (explicitClient || opts.signal?.aborted || !canTryAnotherClient(initial)) throw initial;
|
|
const failures = [{ client: 'default', error: initial }];
|
|
for (const client of [...new Set(clients)]) {
|
|
if (opts.signal?.aborted) throw opts.signal.reason || initial;
|
|
try {
|
|
const out = await run(withCookies(['--extractor-args', `youtube:player_client=${client}`, ...args]), opts);
|
|
log.warn?.(`[ytplayer] YouTube fallback succeeded via player_client=${client}`);
|
|
return out;
|
|
} catch (error) {
|
|
failures.push({ client, error });
|
|
if (opts.signal?.aborted || !canTryAnotherClient(error)) throw error;
|
|
}
|
|
}
|
|
// Keep the original useful diagnosis, not the final client's weaker
|
|
// "no formats" error. Retain the bounded attempt trail for diagnostics.
|
|
const error = new Error(`${initial.message}\nYouTube clients tried: ${failures.map(f => f.client).join(', ')}`, { cause: initial });
|
|
error.attempts = failures.map(({ client, error }) => ({ client, detail: String(error.message || error) }));
|
|
throw error;
|
|
}
|
|
};
|
|
}
|