Fix coomer: bogus video formats on image posts, and its CDN reachability in check.py

- coomer.rs: only emit a `formats` entry for actual video extensions.
  Image-only posts (very common - many creators post photos, not just
  videos) were getting a `formats[0]` pointing at a static jpg/png, which
  a player would try and fail to stream as video; the preview URL already
  covers showing the image.
- coomer.rs tests: fix stale fixtures that baked a `/data/` prefix into
  `file.path`, which doesn't match the real API's path shape (the code
  prepends `/data/` itself) and was doubling it into `/data/data/...`.
  This was masking the formats bug above until the fixture was corrected.
- check.py: coomer.st/data/... 302s to a load-balanced nN.coomer.st CDN
  node; every node tested (n1/n2/n3) is a hard connect-timeout from this
  sandbox's egress while coomer.st itself is a normal 200 - the same
  IP-reputation-style CDN block already documented for ripnsfw's
  doodstream/lulustream edges. Added `.coomer.st` to the CF-protected
  suffix allowlist, and made the url/thumb checks follow redirects before
  testing it (previously only the format-url check did).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTqf6orbHZ9rFFpVpcgzcR
This commit is contained in:
Simon
2026-09-23 07:42:21 +00:00
parent 2adde22514
commit 2c456dff59
2 changed files with 27 additions and 13 deletions

View File

@@ -127,6 +127,12 @@ _CF_PROTECTED_SUFFIXES = (
# lulustream's signed HLS edge (reached via ripnsfw's /proxy/lulustream/...). # lulustream's signed HLS edge (reached via ripnsfw's /proxy/lulustream/...).
# Returns 403/522 to datacenter IPs regardless of UA/TLS impersonation. # Returns 403/522 to datacenter IPs regardless of UA/TLS impersonation.
".tnmr.org", ".tnmr.org",
# coomer's file CDN: coomer.st/data/... 302s to a load-balanced nN.coomer.st
# edge node (n1, n2, n3, n4, ...); every node is a hard connect-timeout from
# this sandbox's egress (verified: n1/n2/n3 all time out while coomer.st
# itself is a normal 200) — an IP-reputation-style CDN block, not a code
# defect. The leading dot means bare `coomer.st` itself is unaffected.
".coomer.st",
) )
@@ -325,7 +331,8 @@ def check_video(video: dict, channel_id: str, results: Results, run_ytdlp: bool)
else: else:
ok, code = http_ok(vurl) ok, code = http_ok(vurl)
if not ok: if not ok:
if _is_cf_protected(vurl): resolved_vurl = follow_proxy_redirect(vurl)
if _is_cf_protected(vurl) or _is_cf_protected(resolved_vurl):
results.warn(channel_id, f"{label}: url unreachable HTTP={code} (CF-protected host, expected): {vurl}") results.warn(channel_id, f"{label}: url unreachable HTTP={code} (CF-protected host, expected): {vurl}")
else: else:
results.err(channel_id, f"{label}: url unreachable HTTP={code}: {vurl}") results.err(channel_id, f"{label}: url unreachable HTTP={code}: {vurl}")
@@ -336,7 +343,8 @@ def check_video(video: dict, channel_id: str, results: Results, run_ytdlp: bool)
if thumb: if thumb:
ok, code = http_ok(thumb) ok, code = http_ok(thumb)
if not ok: if not ok:
if _is_cf_protected(thumb): resolved_thumb = follow_proxy_redirect(thumb)
if _is_cf_protected(thumb) or _is_cf_protected(resolved_thumb):
results.warn(channel_id, f"{label}: thumb unreachable HTTP={code} (CF-protected host, expected): {thumb}") results.warn(channel_id, f"{label}: thumb unreachable HTTP={code} (CF-protected host, expected): {thumb}")
else: else:
results.err(channel_id, f"{label}: thumb unreachable HTTP={code}: {thumb}") results.err(channel_id, f"{label}: thumb unreachable HTTP={code}: {thumb}")

View File

@@ -462,18 +462,24 @@ impl CoomerProvider {
if let Some(ref url) = format_url { if let Some(ref url) = format_url {
let ext = ext_hint.unwrap_or_else(|| "mp4".to_string()); let ext = ext_hint.unwrap_or_else(|| "mp4".to_string());
let is_video = matches!(ext.as_str(), "mp4" | "webm" | "mov" | "mkv"); let is_video = matches!(ext.as_str(), "mp4" | "webm" | "mov" | "mkv");
let mut format = VideoFormat::new(url.clone(), "original".to_string(), ext.clone()); // An image-only post (very common - many creators post photos,
// The /data/... path 302s to the n*.coomer.st CDN; that CDN will // not just videos) has nothing playable; emitting a "format"
// serve the bytes without a Referer requirement, but the *initial* // pointing at a static jpg/png just gives players a broken
// redirect to the CDN does require a valid coomer.st session cookie // pseudo-video to try to stream. The preview URL already covers
// and Referer. Set the Referer so the 302 actually returns the // showing the image.
// Location header.
format = format.http_header("Referer".to_string(), REFERER.to_string());
format = format.http_header("User-Agent".to_string(), BROWSER_UA.to_string());
if is_video { if is_video {
let mut format =
VideoFormat::new(url.clone(), "original".to_string(), ext.clone());
// The /data/... path 302s to the n*.coomer.st CDN; that CDN will
// serve the bytes without a Referer requirement, but the *initial*
// redirect to the CDN does require a valid coomer.st session cookie
// and Referer. Set the Referer so the 302 actually returns the
// Location header.
format = format.http_header("Referer".to_string(), REFERER.to_string());
format = format.http_header("User-Agent".to_string(), BROWSER_UA.to_string());
format = format.video_ext(ext); format = format.video_ext(ext);
item.formats = Some(vec![format]);
} }
item.formats = Some(vec![format]);
} }
// Drop the auto-generated preview URL if it duplicates the format URL. // Drop the auto-generated preview URL if it duplicates the format URL.
@@ -793,7 +799,7 @@ mod tests {
"service": "onlyfans", "service": "onlyfans",
"title": "First post", "title": "First post",
"published": "2025-01-02T03:04:05Z", "published": "2025-01-02T03:04:05Z",
"file": {"name": "clip.mp4", "path": "/data/abc/clip.mp4"}, "file": {"name": "clip.mp4", "path": "/abc/clip.mp4"},
"attachments": [] "attachments": []
}"#; }"#;
let post: ApiPost = serde_json::from_str(json).expect("parse"); let post: ApiPost = serde_json::from_str(json).expect("parse");
@@ -826,7 +832,7 @@ mod tests {
"service": "fansly", "service": "fansly",
"title": "", "title": "",
"substring": "<p>Hello there friends</p>", "substring": "<p>Hello there friends</p>",
"file": {"name": "p.jpg", "path": "/data/x/p.jpg"} "file": {"name": "p.jpg", "path": "/x/p.jpg"}
}"#; }"#;
let post: ApiPost = serde_json::from_str(json).expect("parse"); let post: ApiPost = serde_json::from_str(json).expect("parse");
let item = CoomerProvider::build_video_item(post).expect("item"); let item = CoomerProvider::build_video_item(post).expect("item");