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:
parent
fe229bc98d
commit
2f711604f0
6 changed files with 95 additions and 1 deletions
10
src/web.rs
10
src/web.rs
|
|
@ -367,6 +367,9 @@ struct SettingsPayload {
|
|||
subtitle_format: String,
|
||||
#[serde(default)]
|
||||
subtitle_langs: String,
|
||||
/// YouTube player clients (comma-separated). Empty = yt-dlp defaults.
|
||||
#[serde(default)]
|
||||
youtube_player_clients: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
|
|
@ -1220,6 +1223,7 @@ async fn get_settings(State(state): State<Arc<WebState>>) -> impl IntoResponse {
|
|||
let use_bundled_ytdlp = cfg.backup.use_bundled_ytdlp;
|
||||
let use_pot_provider = cfg.backup.use_pot_provider;
|
||||
let subs = cfg.subtitles.clone();
|
||||
let player_clients_out = cfg.backup.youtube_player_clients.clone();
|
||||
drop(cfg);
|
||||
|
||||
let scheduler_next_check_secs = if scheduler_enabled {
|
||||
|
|
@ -1256,6 +1260,7 @@ async fn get_settings(State(state): State<Arc<WebState>>) -> impl IntoResponse {
|
|||
subtitles_embed: subs.embed,
|
||||
subtitle_format: subs.format,
|
||||
subtitle_langs: subs.langs,
|
||||
youtube_player_clients: player_clients_out.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1295,6 +1300,7 @@ async fn post_settings(
|
|||
cfg.subtitles.embed = body.subtitles_embed;
|
||||
cfg.subtitles.format = body.subtitle_format.trim().to_string();
|
||||
cfg.subtitles.langs = body.subtitle_langs.trim().to_string();
|
||||
cfg.backup.youtube_player_clients = body.youtube_player_clients.trim().to_string();
|
||||
|
||||
if let Err(e) = cfg.save(&state.config_path) {
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, format!("save failed: {e}")).into_response();
|
||||
|
|
@ -1309,6 +1315,7 @@ async fn post_settings(
|
|||
let use_bundled_ytdlp = cfg.backup.use_bundled_ytdlp;
|
||||
let use_pot_provider = cfg.backup.use_pot_provider;
|
||||
let subs = cfg.subtitles.clone();
|
||||
let player_clients_out = cfg.backup.youtube_player_clients.clone();
|
||||
drop(cfg);
|
||||
|
||||
// Apply the new concurrency limit and binary choices to the live downloader.
|
||||
|
|
@ -1320,6 +1327,7 @@ async fn post_settings(
|
|||
dl.use_bundled_ytdlp = use_bundled_ytdlp;
|
||||
dl.use_pot_provider = use_pot_provider;
|
||||
dl.subtitle_defaults = subs.clone();
|
||||
dl.youtube_player_clients = player_clients_out.clone();
|
||||
}
|
||||
|
||||
if let Some(new_pwd) = &body.new_download_password {
|
||||
|
|
@ -1374,6 +1382,7 @@ async fn post_settings(
|
|||
subtitles_embed: subs.embed,
|
||||
subtitle_format: subs.format,
|
||||
subtitle_langs: subs.langs,
|
||||
youtube_player_clients: player_clients_out.clone(),
|
||||
}).into_response()
|
||||
}
|
||||
|
||||
|
|
@ -2344,6 +2353,7 @@ async fn serve(config: Config, shutdown_rx: std::sync::mpsc::Receiver<()>) {
|
|||
config.backup.use_pot_provider,
|
||||
);
|
||||
downloader.subtitle_defaults = config.subtitles.clone();
|
||||
downloader.youtube_player_clients = config.backup.youtube_player_clients.clone();
|
||||
let music_root = downloader.music_root();
|
||||
let _ = std::fs::create_dir_all(&music_root);
|
||||
let transcode = AtomicBool::new(config.web.transcode);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue