modified: app.py
This commit is contained in:
64
app.py
64
app.py
@@ -381,20 +381,23 @@ YTDL_OPTS = {
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_ytdl_opts() -> Dict:
|
def get_ytdl_opts(client_hint: Optional[str] = None) -> Dict:
|
||||||
"""Build yt-dlp options dynamically, injecting cookies and headers if configured."""
|
"""Build yt-dlp options dynamically, injecting cookies and headers if configured.
|
||||||
|
client_hint: preferred YouTube client (e.g., 'android', 'web', 'ios', 'tv').
|
||||||
|
"""
|
||||||
opts = dict(YTDL_OPTS)
|
opts = dict(YTDL_OPTS)
|
||||||
# UA and extractor tweaks
|
# UA and extractor tweaks
|
||||||
ua = os.getenv('YTDL_UA') or (
|
ua = os.getenv('YTDL_UA') or (
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
|
||||||
'(KHTML, like Gecko) Chrome/118.0 Safari/537.36'
|
'(KHTML, like Gecko) Chrome/118.0 Safari/537.36'
|
||||||
)
|
)
|
||||||
yt_client = os.getenv('YTDL_YT_CLIENT', 'android')
|
yt_client_env = os.getenv('YTDL_YT_CLIENT', 'android')
|
||||||
|
yt_client = client_hint or yt_client_env
|
||||||
opts['http_headers'] = {'User-Agent': ua}
|
opts['http_headers'] = {'User-Agent': ua}
|
||||||
# Extractor args can help avoid some player config checks
|
# Extractor args can help avoid some player config checks
|
||||||
opts.setdefault('extractor_args', {})
|
opts.setdefault('extractor_args', {})
|
||||||
opts['extractor_args'].setdefault('youtube', {})
|
opts['extractor_args'].setdefault('youtube', {})
|
||||||
# player_client hint (e.g., android)
|
# player_client hint
|
||||||
opts['extractor_args']['youtube']['player_client'] = [yt_client]
|
opts['extractor_args']['youtube']['player_client'] = [yt_client]
|
||||||
# Use cookies if available
|
# Use cookies if available
|
||||||
if YTDL_COOKIEFILE:
|
if YTDL_COOKIEFILE:
|
||||||
@@ -446,24 +449,45 @@ async def _ensure_connected(ctx: commands.Context) -> Optional[discord.VoiceClie
|
|||||||
async def _ytdlp_extract(loop: asyncio.AbstractEventLoop, query: str) -> Optional[Dict]:
|
async def _ytdlp_extract(loop: asyncio.AbstractEventLoop, query: str) -> Optional[Dict]:
|
||||||
if not ytdlp:
|
if not ytdlp:
|
||||||
return None
|
return None
|
||||||
def _extract():
|
|
||||||
with ytdlp.YoutubeDL(get_ytdl_opts()) as ytdl:
|
# Try multiple YouTube client profiles to bypass some restrictions
|
||||||
return ytdl.extract_info(query, download=False)
|
env_client = os.getenv('YTDL_YT_CLIENT', 'android')
|
||||||
try:
|
fallback_clients = ['web', 'android', 'ios', 'tv']
|
||||||
info = await loop.run_in_executor(executor, _extract)
|
clients_to_try: List[str] = []
|
||||||
if info is None:
|
for c in [env_client] + fallback_clients:
|
||||||
return None
|
if c not in clients_to_try:
|
||||||
if 'entries' in info:
|
clients_to_try.append(c)
|
||||||
info = info['entries'][0]
|
|
||||||
return info
|
last_error: Optional[Exception] = None
|
||||||
except Exception as e:
|
for client in clients_to_try:
|
||||||
# Make YouTube cookie issues clearer in logs
|
def _extract_with_client():
|
||||||
msg = str(e)
|
with ytdlp.YoutubeDL(get_ytdl_opts(client)) as ytdl:
|
||||||
if 'Sign in to confirm you’re not a bot' in msg or 'Use --cookies' in msg or 'pass cookies' in msg:
|
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_error = e
|
||||||
|
msg = str(e)
|
||||||
|
print(f"⚠️ yt-dlp attempt failed with client='{client}': {msg[:200]}")
|
||||||
|
# If it's clearly a bot-check/cookies issue, try next client
|
||||||
|
if ('Sign in to confirm' in msg) or ('Use --cookies' in msg) or ('pass cookies' in msg) or ('HTTP Error 403' in msg):
|
||||||
|
continue
|
||||||
|
# Otherwise, break early for other errors
|
||||||
|
break
|
||||||
|
|
||||||
|
# All attempts failed
|
||||||
|
if last_error:
|
||||||
|
msg = str(last_error)
|
||||||
|
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("❌ yt-dlp error: YouTube requires cookies to proceed.")
|
||||||
print(" Provide YTDL_COOKIES_FILE or YTDL_COOKIES_B64 (Netscape cookies.txt).")
|
print(" Provide YTDL_COOKIES_FILE or YTDL_COOKIES_B64 (Netscape cookies.txt).")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _create_audio_source(loop: asyncio.AbstractEventLoop, search: str, volume: float):
|
async def _create_audio_source(loop: asyncio.AbstractEventLoop, search: str, volume: float):
|
||||||
# Accept either URL or search text; prepend ytsearch1: if not a URL
|
# Accept either URL or search text; prepend ytsearch1: if not a URL
|
||||||
@@ -559,7 +583,7 @@ async def play(ctx: commands.Context, *, query: str):
|
|||||||
item = await _create_audio_source(loop, query, state['volume'])
|
item = await _create_audio_source(loop, query, state['volume'])
|
||||||
if not item:
|
if not item:
|
||||||
# Give a hint if cookies likely required
|
# 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."
|
hint = "If this is YouTube, try setting YTDL_YT_CLIENT=web and/or provide cookies via YTDL_COOKIES_FILE or YTDL_COOKIES_B64."
|
||||||
await ctx.reply(f"❌ Couldn't get audio from that query.\n{hint}")
|
await ctx.reply(f"❌ Couldn't get audio from that query.\n{hint}")
|
||||||
return
|
return
|
||||||
state['queue'].append(item)
|
state['queue'].append(item)
|
||||||
|
|||||||
Reference in New Issue
Block a user