modified: app.py
This commit is contained in:
83
app.py
83
app.py
@@ -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 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).")
|
||||
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']:
|
||||
|
||||
Reference in New Issue
Block a user