modified: app.py

This commit is contained in:
SimolZimol
2025-10-27 22:44:51 +01:00
parent d8067c8769
commit 14d41bd0cd

83
app.py
View File

@@ -11,6 +11,8 @@ from typing import Optional, List, Dict
from concurrent.futures import ThreadPoolExecutor
import functools
import traceback
import base64
from pathlib import Path
# Optional: YouTube extraction
try:
@@ -323,6 +325,52 @@ def get_team_emoji(ctx: commands.Context, team_name: str) -> str:
# Global music state per guild
MUSIC_STATE: Dict[int, Dict] = {}
# --- yt-dlp cookies and options support ---
# You can supply YouTube cookies to bypass bot checks/age gates:
# - YTDL_COOKIES_FILE: Path to a Netscape-format cookies.txt inside the container (e.g., mounted secret)
# - YTDL_COOKIES_B64: Base64-encoded content of a Netscape-format cookies.txt; will be written to app directory
# - YTDL_COOKIES_FROM_BROWSER: e.g., "chrome:Default" (only works on hosts with a real browser profile, usually not in containers)
# - YTDL_UA: Custom User-Agent string
# - YTDL_YT_CLIENT: youtube player client hint (e.g., android, web) to work around some restrictions (default: android)
YTDL_COOKIEFILE: Optional[str] = None
YTDL_COOKIESFROMBROWSER: Optional[tuple] = None
try:
# Priority 1: cookies from base64 env
_cookies_b64 = os.getenv('YTDL_COOKIES_B64')
if _cookies_b64:
try:
decoded = base64.b64decode(_cookies_b64)
cookie_path = Path(__file__).with_name('cookies.txt')
cookie_path.write_bytes(decoded)
YTDL_COOKIEFILE = str(cookie_path)
print(f"🍪 Wrote YouTube cookies to {YTDL_COOKIEFILE} (from YTDL_COOKIES_B64)")
except Exception as e:
print(f"⚠️ Failed to decode/write YTDL_COOKIES_B64: {e}")
# Priority 2: cookies from a file path
if not YTDL_COOKIEFILE:
_cookies_file = os.getenv('YTDL_COOKIES_FILE')
if _cookies_file and os.path.exists(_cookies_file):
YTDL_COOKIEFILE = _cookies_file
print(f"🍪 Using YouTube cookies file: {YTDL_COOKIEFILE}")
elif _cookies_file:
print(f"⚠️ YTDL_COOKIES_FILE set but not found: {_cookies_file}")
# Optional: cookies from browser (rarely usable in containers)
_cookies_from_browser = os.getenv('YTDL_COOKIES_FROM_BROWSER')
if _cookies_from_browser:
parts = _cookies_from_browser.split(':')
browser = parts[0].strip().lower() if parts else None
profile = parts[1].strip() if len(parts) > 1 else None
if browser:
# (browser, profile, keyring, container)
YTDL_COOKIESFROMBROWSER = (browser, profile, None, None)
print(f"🍪 Will try cookies from browser: {browser} profile={profile or 'default'}")
except Exception as e:
print(f"⚠️ Cookie configuration error: {e}")
YTDL_OPTS = {
'format': 'bestaudio/best',
'noplaylist': True,
@@ -331,6 +379,28 @@ YTDL_OPTS = {
'skip_download': True,
}
def get_ytdl_opts() -> Dict:
"""Build yt-dlp options dynamically, injecting cookies and headers if configured."""
opts = dict(YTDL_OPTS)
# UA and extractor tweaks
ua = os.getenv('YTDL_UA') or (
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/118.0 Safari/537.36'
)
yt_client = os.getenv('YTDL_YT_CLIENT', 'android')
opts['http_headers'] = {'User-Agent': ua}
# Extractor args can help avoid some player config checks
opts.setdefault('extractor_args', {})
opts['extractor_args'].setdefault('youtube', {})
# player_client hint (e.g., android)
opts['extractor_args']['youtube']['player_client'] = [yt_client]
# Use cookies if available
if YTDL_COOKIEFILE:
opts['cookiefile'] = YTDL_COOKIEFILE
elif YTDL_COOKIESFROMBROWSER:
opts['cookiesfrombrowser'] = YTDL_COOKIESFROMBROWSER
return opts
FFMPEG_BEFORE = "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5"
FFMPEG_OPTS = {
'before_options': FFMPEG_BEFORE,
@@ -375,7 +445,7 @@ async def _ytdlp_extract(loop: asyncio.AbstractEventLoop, query: str) -> Optiona
if not ytdlp:
return None
def _extract():
with ytdlp.YoutubeDL(YTDL_OPTS) as ytdl:
with ytdlp.YoutubeDL(get_ytdl_opts()) as ytdl:
return ytdl.extract_info(query, download=False)
try:
info = await loop.run_in_executor(executor, _extract)
@@ -384,7 +454,12 @@ async def _ytdlp_extract(loop: asyncio.AbstractEventLoop, query: str) -> Optiona
if 'entries' in info:
info = info['entries'][0]
return info
except Exception:
except Exception as e:
# Make YouTube cookie issues clearer in logs
msg = str(e)
if 'Sign in to confirm youre 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).")
traceback.print_exc()
return None
@@ -481,7 +556,9 @@ async def play(ctx: commands.Context, *, query: str):
await ctx.reply("🔎 Searching…")
item = await _create_audio_source(loop, query, state['volume'])
if not item:
await ctx.reply("❌ Couldn't get audio from that query.")
# Give a hint if cookies likely required
hint = "If this is YouTube, the server IP may be challenged. Provide YTDL_COOKIES_FILE or YTDL_COOKIES_B64."
await ctx.reply(f"❌ Couldn't get audio from that query.\n{hint}")
return
state['queue'].append(item)
if not vc.is_playing() and not vc.is_paused() and not state['now']: