Try alternate YouTube clients for unavailable save sources
This commit is contained in:
39
server/ytdlp-resilience.js
Normal file
39
server/ytdlp-resilience.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user