Translate yt-dlp errors and drop the redundant prefix hint

Errors reached people as raw tool output: "Unsupported URL:
https://...%D0%B0%D1%83..." says nothing to whoever pasted a link. The
common cases now map to plain descriptions with the next step where there
is one (age gate and bot checks point at YTDLP_COOKIES, a stale extractor
at updating yt-dlp), URLs are decoded, and anything unrecognised still
falls through verbatim.

The panel's prefix hint is gone now that the source picker does the same
job visibly; prefixes keep working and stay documented for chat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-09-09 00:15:25 +03:00
co-authored by Claude Opus 5
parent 971fd65b1f
commit ffcf88efb7
2 changed files with 30 additions and 11 deletions
+29 -1
View File
@@ -103,13 +103,41 @@ function runYtDlp(args: string[], timeoutMs = 45_000): Promise<YtDlpRun> {
});
}
/** yt-dlp messages people actually hit, in words that suggest what to do. */
const ERROR_PATTERNS: Array<[RegExp, string]> = [
[/unsupported url/i, "Не понимаю эту ссылку — проверьте, что она открывается в браузере"],
[/is not a valid url/i, "Это не похоже на ссылку"],
[/video unavailable|this video is not available/i, "Видео недоступно"],
[/private video/i, "Видео приватное"],
[/members[- ]only|join this channel/i, "Видео только для участников канала"],
[/confirm your age|age[- ]restricted/i, "Возрастное ограничение — нужны cookies аккаунта (YTDLP_COOKIES)"],
[/confirm you'?re not a bot|sign in to confirm/i, "YouTube требует вход — добавьте cookies.txt (YTDLP_COOKIES)"],
[/not available in your country|geo[- ]?restricted|blocked it in your country/i, "Недоступно в регионе сервера"],
[/page needs to be reloaded/i, "YouTube сменил API — обновите yt-dlp (см. README)"],
[/unable to download|network|timed out|connection/i, "Не удалось связаться с источником"],
];
/** Percent-encoded URLs are unreadable in a chat message. */
function decodeUrls(text: string): string {
return text.replace(/https?:\/\/\S+/g, (url) => {
try {
return decodeURI(url);
} catch {
return url;
}
});
}
function firstUsefulError(stderr: string): string {
const line = stderr
.split(/\r?\n/)
.map((l) => l.trim())
.find((l) => l.toUpperCase().startsWith("ERROR"));
if (!line) return "Не удалось получить трек";
return line.replace(/^ERROR:\s*/i, "").slice(0, 300);
const raw = decodeUrls(line.replace(/^ERROR:\s*/i, "")).slice(0, 300);
const known = ERROR_PATTERNS.find(([pattern]) => pattern.test(raw));
return known ? known[1] : raw;
}
function sourceOf(entry: YtDlpEntry): SourceKind {