Files
lintunes/tests/test_round59.py
T
travandClaude Opus 5 d448f64c2b v0.22.1: not a bot
Import from URL now gets past YouTube's "Sign in to confirm you're not a
bot": a run that downloads nothing for that reason retries once with
yt-dlp's --cookies-from-browser (Firefox first), and a browser that worked
is remembered in this machine's config.json so later imports send the
cookies from the start.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 00:43:01 -07:00

155 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Round 59: YouTube's "Sign in to confirm you're not a bot".
YouTube flags a network, not a program, so `song` hits it too. The cure is
the browser's YouTube cookies: a run that downloads nothing because of the
check is retried once with --cookies-from-browser, and the browser is then
remembered in this machine's config.json.
"""
import os
import stat
import sys
import pytest
from lintunes import config, url_import
from lintunes.url_import import UrlImportWorker, build_command, is_bot_check
BOT_LINE = ("[youtube] m-7NEY4p5s0: Sign in to confirm you’re not a bot. "
"Use --cookies-from-browser or --cookies for the authentication.")
# Refuses anonymous requests the way YouTube does; lets cookies through.
# Each run appends its argv to $FAKE_YTDLP_LOG so tests can count attempts.
FAKE_YTDLP = '''#!/usr/bin/env python3
import os, sys
args = sys.argv[1:]
with open(os.environ["FAKE_YTDLP_LOG"], "a") as log:
log.write(" ".join(args) + "\\n")
if "--cookies-from-browser" not in args or os.environ.get("FAKE_ALWAYS_BOT"):
print("ERROR: " + os.environ["FAKE_BOT_LINE"], file=sys.stderr, flush=True)
sys.exit(1)
dest = args[args.index("-P") + 1]
path = os.path.join(dest, "Song [m-7NEY4p5s0].mp3")
open(path, "wb").write(b"ID3fake")
print("LTSTART 1\\t1\\tSong", flush=True)
print(f"LTFILE {path}", flush=True)
'''
@pytest.fixture
def fake(tmp_path, monkeypatch):
bin_dir = tmp_path / "bin"
bin_dir.mkdir()
script = bin_dir / "yt-dlp"
script.write_text(FAKE_YTDLP.replace("/usr/bin/env python3", sys.executable))
script.chmod(script.stat().st_mode | stat.S_IEXEC)
log = tmp_path / "argv.log"
monkeypatch.setenv("PATH", f"{bin_dir}{os.pathsep}{os.environ['PATH']}")
monkeypatch.setenv("FAKE_YTDLP_LOG", str(log))
monkeypatch.setenv("FAKE_BOT_LINE", BOT_LINE)
monkeypatch.setattr(url_import, "default_cookies_browser",
lambda: "firefox")
dest = tmp_path / "dl"
dest.mkdir()
return dest, lambda: (log.read_text().splitlines()
if log.exists() else [])
def _run(dest, **kw):
worker = UrlImportWorker("https://www.youtube.com/watch?v=m-7NEY4p5s0",
**kw)
worker.temp_dir = dest
events = []
worker.downloaded.connect(lambda p: events.append(("file", p)))
worker.finished.connect(lambda d: events.append(("finished", d)))
worker.failed.connect(lambda m: events.append(("failed", m)))
worker.cookies_used.connect(lambda b: events.append(("cookies", b)))
worker._run()
return events
def test_cookies_go_before_the_url_and_only_when_asked(tmp_path):
plain = build_command("https://x.test/v", tmp_path)
assert "--cookies-from-browser" not in plain
cmd = build_command("https://x.test/v", tmp_path, "firefox")
i = cmd.index("--cookies-from-browser")
assert cmd[i + 1] == "firefox"
assert i < cmd.index("--") and cmd[-1] == "https://x.test/v"
assert cmd[1:4] == url_import.SONG_ARGS
def test_is_bot_check():
assert is_bot_check(BOT_LINE)
assert is_bot_check(BOT_LINE.replace("’", "'"))
assert not is_bot_check("[generic] Unsupported URL: https://x.test")
assert not is_bot_check("")
def test_a_bot_check_retries_with_the_browsers_cookies(fake):
dest, argvs = fake
events = _run(dest)
assert len(argvs()) == 2
assert "--cookies-from-browser" not in argvs()[0]
assert "--cookies-from-browser firefox" in argvs()[1]
assert events == [("file", str(dest / "Song [m-7NEY4p5s0].mp3")),
("cookies", "firefox"),
("finished", {"downloaded": 1, "errors": [],
"cancelled": False})]
def test_a_remembered_browser_is_used_from_the_start(fake):
dest, argvs = fake
events = _run(dest, cookies_browser="firefox")
assert len(argvs()) == 1
assert "--cookies-from-browser firefox" in argvs()[0]
assert ("cookies", "firefox") not in events # nothing new to remember
assert events[-1][0] == "finished"
def test_cookies_that_dont_help_fail_with_advice(fake, monkeypatch):
dest, argvs = fake
monkeypatch.setenv("FAKE_ALWAYS_BOT", "1")
events = _run(dest)
assert len(argvs()) == 2 # one retry, not a loop
[(kind, message)] = events
assert kind == "failed"
assert "Sign in to YouTube in Firefox" in message
assert BOT_LINE[:40] in message
def test_no_browser_means_no_retry(fake, monkeypatch):
dest, argvs = fake
monkeypatch.setattr(url_import, "default_cookies_browser", lambda: None)
events = _run(dest)
assert len(argvs()) == 1
assert events[0][0] == "failed"
assert "your browser" in events[0][1]
def test_a_cancelled_run_is_not_retried(fake):
dest, argvs = fake
worker = UrlImportWorker("https://x.test/v")
worker.temp_dir = dest
worker._cancel.set()
worker._run()
assert len(argvs()) <= 1
def test_default_browser_prefers_firefox(tmp_path, monkeypatch):
monkeypatch.setattr(url_import.Path, "home", lambda: tmp_path)
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / ".config"))
assert url_import.default_cookies_browser() is None
(tmp_path / ".config" / "chromium").mkdir(parents=True)
assert url_import.default_cookies_browser() == "chromium"
(tmp_path / ".mozilla" / "firefox").mkdir(parents=True)
assert url_import.default_cookies_browser() == "firefox"
def test_the_window_remembers_the_browser_in_config_json(
qapp, tmp_path, monkeypatch):
from lintunes.gui.main_window import MainWindow
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path / "cfg"))
config.save_config({"music_root": "/somewhere"})
MainWindow._remember_cookies_browser(None, "firefox")
assert config.load_config() == {"music_root": "/somewhere",
url_import.COOKIES_KEY: "firefox"}