Anti-bot: configurable YouTube player clients (global + per-channel)

YouTube's bot-detection cracks down on different player clients over
time — the `web` client gets captcha-walled, then later `tv`/`mweb` are
the safe ones, etc. Let the user route around whichever client is
currently being targeted instead of being stuck with yt-dlp's pick.

- New backup.youtube_player_clients config (comma-separated, blank =
  yt-dlp default). Maps to --extractor-args youtube:player_client=…
- Per-channel DownloadOptions.youtube_player_clients override (None =
  defer to global). Resolved in the downloader's apply_player_client,
  which omits the flag entirely when empty so yt-dlp keeps its default
  client set (the recommended baseline).
- Threaded into the live downloader at construction + settings save
  (app + web).

UI: a "YouTube player clients" field in both global Settings (desktop +
web) and the per-channel options dialog, with hints pointing at
"tv,mweb" as the current least-bot-checked combo for captcha-prone
channels.

Verified the settings round-trip (global persists to config + echoes;
per-channel override saves + reads back). 87 tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Luna 2026-06-01 09:27:57 -07:00
parent fe229bc98d
commit 2f711604f0
6 changed files with 95 additions and 1 deletions

View file

@ -87,6 +87,14 @@ pub struct BackupSection {
/// install the plugin and run the server themselves.
#[serde(default)]
pub use_pot_provider: bool,
/// YouTube player clients to try, as a comma-separated list passed to
/// `--extractor-args youtube:player_client=…`. Empty = let yt-dlp pick
/// its defaults (recommended). YouTube's bot-detection cracks down on
/// different clients over time; setting e.g. `tv,mweb` routes around a
/// client that's currently being captcha-walled. Per-channel overrides
/// live in [`crate::download_options::DownloadOptions`].
#[serde(default)]
pub youtube_player_clients: String,
}
fn default_max_concurrent() -> usize { 3 }
@ -214,6 +222,7 @@ impl Config {
max_concurrent: default_max_concurrent(),
use_bundled_ytdlp: false,
use_pot_provider: false,
youtube_player_clients: String::new(),
},
player: PlayerSection::default(),
ui: UiSection::default(),