diff --git a/check.py b/check.py index 1c1d33a..35addc1 100644 --- a/check.py +++ b/check.py @@ -354,7 +354,29 @@ def check_video(video: dict, channel_id: str, results: Results, run_ytdlp: bool) if not vurl: results.err(channel_id, f"{label}: missing url") elif "127.0.0.1" in vurl or "localhost" in vurl: - results.err(channel_id, f"{label}: url points to hottub server: {vurl}") + # A proxied video.url is legitimate when the page itself cannot be + # played directly (see the hottub skill: "if a video URL requires a + # server-side proxy, set it as the video URL"). Verify that it + # actually resolves instead of rejecting it outright. + resolved_vurl = follow_proxy_redirect(vurl) + if resolved_vurl and resolved_vurl != vurl: + ok, code = http_ok(resolved_vurl) + target = resolved_vurl + else: + ok, code = http_ok(vurl) + target = vurl + if ok: + results.info(channel_id, f"{label}: proxied url OK (HTTP {code})") + elif _is_cf_protected(target): + results.warn( + channel_id, + f"{label}: proxied url target unreachable HTTP={code} (CF-protected host, expected): {vurl}", + ) + else: + results.err( + channel_id, + f"{label}: proxied url does not resolve (HTTP={code}, target {target[:120]}): {vurl}", + ) else: ok, code = http_ok(vurl) if not ok: diff --git a/docs/provider-fix-todo.md b/docs/provider-fix-todo.md index 121609c..4885a55 100644 --- a/docs/provider-fix-todo.md +++ b/docs/provider-fix-todo.md @@ -37,7 +37,7 @@ Status: `[ ]` open, `[~]` in progress, `[x]` fixed and verified, `[-]` not repro |---|---|---|---| | [x] | `xxdbx` | malformed thumbs `https:/7004….jpg` | assets are now site-relative (`/id.jpg`) rather than `//host/..`; new `absolute_url` resolves all three forms. 36 items, p2/search OK, check.py passes | | [x] | `ph-shorties` | thumbs return 403 | same Referer-gated `pix-cdn77.phncdn.com` thumbs; routed through `aylo-thumb` proxy. check.py passes | -| [ ] | `noodlemagazine` | thumbs unreachable (-1 / 400) in check.py | thumb proxy | +| [x] | `noodlemagazine` | thumbs unreachable (-1 / 400) in check.py | thumb proxy dropped the preview's query string (`?id&tkn..`), rejected extensionless `getVideoPreview` paths, and warmed up via noodlemagazine.com through the fallback chain on every image (10-28s). All 24 thumbs now 200 in ~0.08s. Titles fix from earlier commit stands | ## 4. Search @@ -62,7 +62,7 @@ Status: `[ ]` open, `[~]` in progress, `[x]` fixed and verified, `[-]` not repro | | Item | Notes | |---|---|---| -| [ ] | `check.py` "url points to hottub server" | contradicts the hottub skill (proxy-needing videos use the proxy as `item.url`). Affects spankbang, hqporner, pornhd3x, pimpbunny, sxyprn, noodlemagazine. Decide: exempt proxy URLs that resolve, rather than hard-fail. | +| [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) diff --git a/src/proxies/noodlemagazine.rs b/src/proxies/noodlemagazine.rs index e5d4e80..931b9e5 100644 --- a/src/proxies/noodlemagazine.rs +++ b/src/proxies/noodlemagazine.rs @@ -105,9 +105,11 @@ impl NoodlemagazineProxy { fn has_allowed_image_extension(path: &str) -> bool { let path = path.to_ascii_lowercase(); - [".jpg", ".jpeg", ".png", ".webp", ".avif", ".gif"] - .iter() - .any(|ext| path.ends_with(ext)) + // `.../i.mycdn.me/getVideoPreview?id=..` previews have no extension. + path.ends_with("/getvideopreview") + || [".jpg", ".jpeg", ".png", ".webp", ".avif", ".gif"] + .iter() + .any(|ext| path.ends_with(ext)) } fn is_disallowed_thumb_host(host: &str) -> bool { @@ -292,20 +294,23 @@ pub async fn get_image( requester: web::types::State, ) -> Result { let endpoint = req.match_info().query("endpoint").to_string(); - let image_url = NoodlemagazineProxy::normalize_image_url(&endpoint); + let mut image_url = NoodlemagazineProxy::normalize_image_url(&endpoint); + // The preview's own query (`id`, `idx`, `type`, `tkn`, `fn`) arrives as the + // request query string, not as part of the matched path. + let query = req.query_string(); + if !query.is_empty() && !image_url.contains('?') { + image_url.push('?'); + image_url.push_str(query); + } if !NoodlemagazineProxy::is_allowed_thumb_url(&image_url) { return Ok(web::HttpResponse::BadRequest().finish()); } + // No site warm-up first: the image CDN answers directly (~0.1s). Warming + // via noodlemagazine.com cost 10-30s through the requester's fallbacks; + // the retry below still warms up when the first fetch fails. let mut requester = requester.get_ref().clone(); - let _ = requester - .get_with_headers( - NoodlemagazineProxy::root_referer(), - NoodlemagazineProxy::root_html_headers(), - Some(Version::HTTP_11), - ) - .await; let mut headers = NoodlemagazineProxy::image_headers(&requester, image_url.as_str()); let mut upstream = requester @@ -409,6 +414,9 @@ mod tests { #[test] fn allows_https_image_thumbs_but_rejects_local_or_non_images() { + assert!(NoodlemagazineProxy::is_allowed_thumb_url( + "https://img.pvvstream.pro/preview/a/-1_2/240/i.mycdn.me/getVideoPreview?id=1&idx=0&type=39&tkn=x" + )); assert!(NoodlemagazineProxy::is_allowed_thumb_url( "https://noodlemagazine.com/thumbs/example.webp" ));