From 2addcc2e7c6cb9c99f5793f95ade1861c72269a9 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 10 Oct 2026 10:28:57 +0000 Subject: [PATCH] Deliver ytdlpCommand for an allowlist of verified channels decorate_channel ignored every provider-declared ytdlpCommand except pimpbunny's. Send the declared command for freepornvideosxxx and missav, where plain yt-dlp is verified to fail; other channels stay unchanged. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi --- docs/provider-fix-todo.md | 15 +++++++++------ src/providers/mod.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/docs/provider-fix-todo.md b/docs/provider-fix-todo.md index dce3135..896a979 100644 --- a/docs/provider-fix-todo.md +++ b/docs/provider-fix-todo.md @@ -64,13 +64,16 @@ Status: `[ ]` open, `[~]` in progress, `[x]` fixed and verified, `[-]` not repro |---|---|---| | [x] | `check.py` "url points to hottub server" | contradicted the hottub skill | proxied `video.url` is now verified to resolve (redirect target reachable, CF hosts -> warning) instead of hard-failing. Affected: spankbang, hqporner, pornhd3x, pimpbunny, sxyprn, noodlemagazine | -## Open decision (needs the owner) +## Decision: `ytdlpCommand` delivery (agreed: targeted rollout) -`ytdlpCommand` set on providers (`Channel.ytdlpCommand`, ~90 channels, e.g. `yt-dlp --impersonate chrome-120`) is **never sent to clients**: -`decorate_channel` in `src/providers/mod.rs` builds `ChannelView.ytdlpCommand` from a hardcoded `match` (only `pimpbunny`), and -`decorates_channel_with_group_and_tags` pins `hsex` to `None`. `/api/status` therefore reports `null` for all of them. Either deliver -`channel.ytdlpCommand` (clients then need yt-dlp impersonation support) or drop the dead declarations. Channels that only fail -under plain yt-dlp TLS (freepornvideosxxx, missav, ...) depend on this. `check.py` now applies whatever `/api/status` exposes. +Providers declare `Channel.ytdlpCommand` (~90 channels), but `/api/status` used to send it only for `pimpbunny`. +Delivering all of them at once risks breaking working channels if the app's yt-dlp lacks impersonation support, so +`decorate_channel` now delivers a provider's declared command **only for channels in `YTDLP_COMMAND_CHANNELS`** +(`src/providers/mod.rs`): `freepornvideosxxx`, `missav`. Add a channel there once plain yt-dlp is verified to fail for it. + +Still to do with the app side: confirm the app applies `ytdlpCommand` for non-pimpbunny channels and that its yt-dlp has an +impersonation target; if a minimum client version is needed, gate on `ClientVersion` (not done, the minimum is unknown). +`check.py` applies whatever `/api/status` exposes, so it verifies each addition. ## Found while fixing (pre-existing, unrelated) diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 4e1926a..69b6430 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -778,11 +778,20 @@ fn sanitize_status_options(options: Vec) -> Vec { .collect() } +/// Channels whose own declared `ytdlpCommand` is delivered in `/api/status`. +/// Verified: `freepornvideosxxx` (plain yt-dlp gets 502 on its pages, fine with +/// `--impersonate chrome-120`) and `missav` (Cloudflare 403 without it). +const YTDLP_COMMAND_CHANNELS: &[&str] = &["freepornvideosxxx", "missav"]; + pub fn decorate_channel(channel: Channel) -> ChannelView { let metadata = channel_metadata_for(&channel.id); let runtime_status = current_provider_channel_status(&channel.id); let ytdlp_command = match channel.id.as_str() { "pimpbunny" => Some("yt-dlp --compat-options allow-unsafe-ext".to_string()), + // Providers declare a `ytdlpCommand`, but it is only sent to clients + // for channels where plain yt-dlp was verified to fail (TLS-fingerprint + // 403/502 that real clients hit). Widen this list one channel at a time. + id if YTDLP_COMMAND_CHANNELS.contains(&id) => channel.ytdlpCommand.clone(), _ => None, }; ChannelView { @@ -1372,6 +1381,25 @@ mod tests { assert_eq!(groups[2].id, "jav"); } + #[test] + fn delivers_declared_ytdlp_command_only_for_allowlisted_channels() { + for id in ["freepornvideosxxx", "missav"] { + PROVIDER_RUNTIME_STATUS.remove(id); + assert_eq!( + decorate_channel(base_channel(id)).ytdlpCommand.as_deref(), + Some("yt-dlp --impersonate chrome-120"), + "{id} should deliver its declared command" + ); + } + // Declares the same command but is not allowlisted. + PROVIDER_RUNTIME_STATUS.remove("hsex"); + assert_eq!(decorate_channel(base_channel("hsex")).ytdlpCommand, None); + // An allowlisted id that declares nothing stays None. + let mut undeclared = base_channel("missav"); + undeclared.ytdlpCommand = None; + assert_eq!(decorate_channel(undeclared).ytdlpCommand, None); + } + #[test] fn decorates_pimpbunny_with_ytdlp_command() { PROVIDER_RUNTIME_STATUS.remove("pimpbunny");