From 71466b21bd79bec38da3c534fc5e1dd56983e3aa Mon Sep 17 00:00:00 2001 From: Luna Date: Mon, 1 Jun 2026 08:47:14 -0700 Subject: [PATCH] Classify YouTube captcha wall as rate-limited, not "not found" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The captcha/bot-challenge error reads: Video unavailable. YouTube is requiring a captcha challenge before playback Because it contains "Video unavailable", the NotFound rule grabbed it and showed the misleading "removed by the uploader — nothing to download" hint. But the upload isn't gone; it's a bot-detection wall. Add captcha / "not a bot" fingerprints to the RateLimited rule, which runs before NotFound so it wins the "Video unavailable" overlap. Also broadened the RateLimited hint to mention captchas + the POT provider as remedies alongside cookies + waiting. New test pins the captcha line → RateLimited; the genuine "removed by the uploader" line still maps to NotFound. 85 pass. Co-Authored-By: Claude Opus 4.7 --- src/error_class.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/error_class.rs b/src/error_class.rs index 4b3ae79..e709f4e 100644 --- a/src/error_class.rs +++ b/src/error_class.rs @@ -64,7 +64,7 @@ impl ErrorClass { pub fn hint(self) -> &'static str { match self { ErrorClass::RateLimited => - "YouTube is rate-limiting you. Add cookies from a logged-in browser session (Settings → Cookies), or wait 10–60 minutes and retry.", + "YouTube is rate-limiting you or demanding a captcha (bot detection). Refresh your cookies.txt from a logged-in browser session (Settings → Cookies), enable the POT token provider, and/or wait 10–60 minutes before retrying.", ErrorClass::MembersOnly => "This video requires a logged-in session with access (members-only, private, or paid). Update cookies.txt from an account that can view it.", ErrorClass::Geoblocked => @@ -102,12 +102,18 @@ where let l = line.to_ascii_lowercase(); // ── Rate limits / bot challenges ───────────────────────────── + // Checked first so a bot-challenge phrasing that also contains + // "video unavailable" (e.g. YouTube's captcha wall) is classified + // as rate-limited rather than NotFound below. if l.contains("http error 429") || l.contains("too many requests") || l.contains("sign in to confirm you") || l.contains("sign in to confirm your age") || l.contains("ratelimit") || l.contains("rate limit") + || l.contains("captcha") + || l.contains("requiring a captcha challenge") + || l.contains("not a bot") { return ErrorClass::RateLimited; } @@ -229,6 +235,18 @@ mod tests { ); } + #[test] + fn captcha_wall_is_rate_limited_not_not_found() { + // The captcha line also contains "Video unavailable", which the + // NotFound rule would otherwise grab. The RateLimited rule must + // win because the fix is "refresh cookies / wait", not "the + // upload is gone". + assert_eq!( + classify_str("ERROR: [youtube] tfoprUBw0H0: Video unavailable. YouTube is requiring a captcha challenge before playback"), + ErrorClass::RateLimited + ); + } + #[test] fn detects_geo_block() { assert_eq!(