From 664b3c224348e88e71aa9cb81b00fb29c43ef1e9 Mon Sep 17 00:00:00 2001 From: SimolZimol <70102430+SimolZimol@users.noreply.github.com> Date: Tue, 28 Oct 2025 13:14:06 +0100 Subject: [PATCH] modified: Dockerfile modified: app.py --- Dockerfile | 2 ++ app.py | 64 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index f0f7e38..23e5c52 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,8 @@ RUN apt-get update && apt-get install -y \ COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt +# Ensure yt-dlp is up-to-date so extractor fixes are applied +RUN pip install --no-cache-dir -U yt-dlp COPY . . diff --git a/app.py b/app.py index 5e1ea87..df09842 100644 --- a/app.py +++ b/app.py @@ -444,24 +444,56 @@ async def _ensure_connected(ctx: commands.Context) -> Optional[discord.VoiceClie async def _ytdlp_extract(loop: asyncio.AbstractEventLoop, query: str) -> Optional[Dict]: if not ytdlp: return None - def _extract(): - with ytdlp.YoutubeDL(get_ytdl_opts()) as ytdl: - return ytdl.extract_info(query, download=False) - try: - info = await loop.run_in_executor(executor, _extract) - if info is None: + # Try extraction with multiple player_client hints if extraction fails due to player/nsig issues. + # Start with the configured client, then fall back to common alternatives. + preferred = os.getenv('YTDL_YT_CLIENT', 'android') + candidates = [preferred] + for c in ('web', 'tv', 'android_embedded', 'firetv'): + if c not in candidates: + candidates.append(c) + + last_exc = None + for client_hint in candidates: + def _extract_with_client(client=client_hint): + opts = get_ytdl_opts() + # Override player_client for this attempt + try: + opts['extractor_args']['youtube']['player_client'] = [client] + except Exception: + opts.setdefault('extractor_args', {}).setdefault('youtube', {})['player_client'] = [client] + with ytdlp.YoutubeDL(opts) as ytdl: + return ytdl.extract_info(query, download=False) + + try: + info = await loop.run_in_executor(executor, _extract_with_client) + if info is None: + continue + if 'entries' in info: + info = info['entries'][0] + return info + except Exception as e: + last_exc = e + msg = str(e).lower() + # If it's a cookies/sign-in issue, surface a helpful message and stop trying + if 'sign in to confirm' in msg or 'use --cookies' in msg or 'pass cookies' in msg: + print("❌ yt-dlp error: YouTube requires cookies to proceed.") + print(" Provide YTDL_COOKIES_FILE or YTDL_COOKIES_B64 (Netscape cookies.txt).") + traceback.print_exc() + return None + # If nsig/sabr or unsupported client warnings occurred, try next client hint + if 'nsig extraction failed' in msg or 'sabr' in msg or 'unsupported client' in msg: + print(f"⚠️ yt-dlp warning with client={client_hint}: {e}") + # continue to try other clients + continue + # Otherwise, log and stop trying + traceback.print_exc() return None - if 'entries' in info: - info = info['entries'][0] - return info - except Exception as e: - # Make YouTube cookie issues clearer in logs - msg = str(e) - if 'Sign in to confirm you’re not a bot' in msg or 'Use --cookies' in msg or 'pass cookies' in msg: - print("❌ yt-dlp error: YouTube requires cookies to proceed.") - print(" Provide YTDL_COOKIES_FILE or YTDL_COOKIES_B64 (Netscape cookies.txt).") + + # If we tried everything and failed, log last exception + if last_exc: + print(f"❌ All yt-dlp client attempts failed. Last error: {type(last_exc).__name__}: {last_exc}") traceback.print_exc() - return None + return None async def _create_audio_source(loop: asyncio.AbstractEventLoop, search: str, volume: float): # Accept either URL or search text; prepend ytsearch1: if not a URL