fix(downloads): a site the downloader no longer understands says so, and offers a retry

yt-dlp asks for a bug report on an extraction failure it did not expect: the site answered in a way its extractor does not read (measured on a TikTok link: fails on every attempt under yt-dlp 2026.07.04, downloads under 2026.08.19). That failure read as "Something went wrong" with no retry; it is now its own category, site_changed, with a Retry in the failure window. A contract test holds every failure category to its copy in both languages.
This commit is contained in:
2026-10-12 20:54:47 +02:00
parent f6bce23a94
commit 132fb56428
7 changed files with 106 additions and 5 deletions
+25 -4
View File
@@ -5,6 +5,14 @@ from app.downloads import errors as dlerrors
from app.downloads.errors import classify
# Verbatim from dev job 128 (2026-10-09, yt-dlp 2026.07.04); the same link downloads under 2026.08.19.
_TIKTOK_UNEXPECTED = (
"ERROR: [TikTok] 7418778475890232609: Unexpected response from webpage request; please report "
"this issue on https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue "
"template. Confirm you are on the latest version using yt-dlp -U"
)
def test_real_ytdlp_messages_map_to_categories():
# Verbatim-shaped yt-dlp / ffmpeg output → the category the UI explains.
cases = {
@@ -32,6 +40,7 @@ def test_real_ytdlp_messages_map_to_categories():
"The source file is missing.": "source_missing",
"ERROR: unable to download video data: HTTP Error 403: Forbidden": "network",
"ERROR: Unable to download webpage: <urlopen error timed out>": "network",
_TIKTOK_UNEXPECTED: "site_changed",
}
for msg, expected in cases.items():
assert classify(msg) == expected, f"{msg!r} → {classify(msg)!r}, expected {expected!r}"
@@ -211,13 +220,25 @@ def test_our_post_without_a_video_verdict_names_the_group_link():
def test_a_catalog_entry_never_leaves_its_host():
# "Cannot parse data" is yt-dlp's wording for a broken extractor on every site.
# "Cannot parse data" is yt-dlp's wording for a broken extractor on every site: elsewhere it is
# only the category — the site changed under the extractor.
other = _FB_PARSE.replace("[facebook]", "[vimeo]")
assert dlerrors.diagnose(other, url="https://vimeo.com/1") == ("unknown", None)
assert dlerrors.diagnose(other, url="https://vimeo.com/1") == ("site_changed", None)
# The Facebook text for a link that is not Facebook's (a look-alike host) is not the Facebook wall.
assert dlerrors.diagnose(_FB_PARSE, url="https://facebook.com.evil/reel/1") == ("unknown", None)
assert dlerrors.diagnose(_FB_PARSE, url="https://facebook.com.evil/reel/1") == (
"site_changed",
None,
)
# No link known: the category only.
assert dlerrors.diagnose(_FB_PARSE) == ("unknown", None)
assert dlerrors.diagnose(_FB_PARSE) == ("site_changed", None)
def test_a_bug_report_request_keeps_the_more_specific_category():
# yt-dlp asks for the bug report on these too; what they say about the media wins.
assert classify(_FB_NO_FORMATS) == "format_unavailable"
assert classify("ERROR: [x] 1: Unable to extract title; please report this issue") == (
"format_unavailable"
)
def test_diagnose_falls_back_to_the_category():