From ffcf88efb7a8a0b39bf944ea6a8a29e5922f9762 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Wed, 9 Sep 2026 00:15:25 +0300 Subject: [PATCH] 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 --- src/sources/ytdlp.ts | 30 +++++++++++++++++++++++++++++- web/src/components/SearchPanel.tsx | 11 +---------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/sources/ytdlp.ts b/src/sources/ytdlp.ts index 213a63c..64a1c63 100644 --- a/src/sources/ytdlp.ts +++ b/src/sources/ytdlp.ts @@ -103,13 +103,41 @@ function runYtDlp(args: string[], timeoutMs = 45_000): Promise { }); } +/** 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 { diff --git a/web/src/components/SearchPanel.tsx b/web/src/components/SearchPanel.tsx index 789fe9d..5bfe29d 100644 --- a/web/src/components/SearchPanel.tsx +++ b/web/src/components/SearchPanel.tsx @@ -12,6 +12,7 @@ const SOURCE_BADGE: Record = { interface Props { serverId: string; canControl: boolean; + /** Adds the local library to the source picker. */ localLibrary: boolean; onError(message: string | null): void; } @@ -126,16 +127,6 @@ export function SearchPanel({ serverId, canControl, localLibrary, onError }: Pro ))} )} - -

- Префиксы: sc: — искать в SoundCloud, yt: — в YouTube - {localLibrary ? ( - <> - , local: — в локальной медиатеке - - ) : null} - . -

); }