Fix YouTube 403s by upgrading yt-dlp to 2026.8.19

Every YouTube download — /force and the normal clip path alike — was
failing at the data-transfer stage with "HTTP Error 403: Forbidden".
Extraction succeeded and a format was selected; only the media fetch
died, which is why it read as a per-video problem rather than a broken
extractor.

The cause was YouTube retiring the cookieless `android_vr` client that
yt-dlp 2026.6.9 pinned. Its media URLs now 403 for every video, so the
failure was global rather than specific to the video that surfaced it.
2026.8.19 re-points at `visionos` and downloads work again.

Refresh the cookie-withholding note, which named android_vr as the
client whose URLs work, and record the 403-on-data-transfer signature as
the tell for a retired client so the next occurrence goes straight to a
yt-dlp upgrade instead of the cookie or format logic.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QH4jMHfUTDhm41BrRtkPc3
This commit is contained in:
2026-08-22 01:10:40 -04:00
co-authored by Claude Opus 5
parent 1523bdd48d
commit 0a9af17246
2 changed files with 15 additions and 7 deletions
+12 -4
View File
@@ -62,10 +62,18 @@ _NODE22 = "/opt/node22/bin/node"
JS_RUNTIME = f"node:{_NODE22}" if os.path.exists(_NODE22) else "node" JS_RUNTIME = f"node:{_NODE22}" if os.path.exists(_NODE22) else "node"
# YouTube is the one host where we must NOT send cookies: with account cookies # YouTube is the one host where we must NOT send cookies: with account cookies
# present, yt-dlp skips the cookieless `android_vr`/`android` clients (whose media # present, yt-dlp skips the cookieless client it would otherwise pick (currently
# URLs work) and falls back to the `tv` client, whose URLs now 403 on the data # `visionos`, which offers the full ladder of split renditions) and falls back to
# download. A cookieless YouTube request just works. Cookies are still needed for # a `tv` client that only ever yields muxed 360p — and whose URLs have repeatedly
# Instagram/TikTok/X, so we withhold them per-URL rather than dropping the file. # 403'd on the data download. A cookieless YouTube request just works. Cookies are
# still needed for Instagram/TikTok/X, so we withhold them per-URL rather than
# dropping the file.
#
# Which cookieless client yt-dlp picks is not stable — YouTube retires them and
# yt-dlp re-points at a working one. When every YouTube download starts failing at
# the data stage with "HTTP Error 403: Forbidden" (extraction succeeds, the
# transfer does not), that is the signal that the pinned client has been retired:
# upgrade yt-dlp rather than touching the cookie or format logic here.
_YOUTUBE_HOST_RE = re.compile(r"(?:^|\.)(?:youtube\.com|youtu\.be|youtube-nocookie\.com)$", re.I) _YOUTUBE_HOST_RE = re.compile(r"(?:^|\.)(?:youtube\.com|youtu\.be|youtube-nocookie\.com)$", re.I)
def _is_youtube(url: str) -> bool: def _is_youtube(url: str) -> bool:
+3 -3
View File
@@ -1,8 +1,8 @@
signalbot==1.1.0 signalbot==1.1.0
# yt-dlp is kept current for site/extractor changes; bump the floor deliberately. # yt-dlp is kept current for site/extractor changes; bump the floor deliberately.
# 2026.6.9 fixes a YouTube 403-on-data-download regression; it also requires the # 2026.8.19 restores YouTube downloads: the cookieless client the older build
# JS challenge to run on Node >= 22 (see below). # pinned (android_vr) was retired and every data transfer 403'd.
yt-dlp>=2026.6.9 yt-dlp>=2026.8.19
# Needed for YouTube: yt-dlp wraps URLs in a JS "n-sig" challenge that a JS # Needed for YouTube: yt-dlp wraps URLs in a JS "n-sig" challenge that a JS
# runtime must solve. Requires Node >= 22 on PATH (bot.py points at a standalone # runtime must solve. Requires Node >= 22 on PATH (bot.py points at a standalone
# /opt/node22 since Debian 13 only ships Node 20) plus this package. # /opt/node22 since Debian 13 only ships Node 20) plus this package.