Fix noodlemagazine thumb proxy; check.py verifies proxied urls

The thumb proxy dropped the preview query string, rejected extensionless
getVideoPreview paths and warmed up through the slow fallback chain on
every image. check.py now verifies that a proxied video.url resolves
instead of failing it outright.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi
This commit is contained in:
Simon
2026-10-10 09:17:30 +00:00
parent e4c5cb2f0a
commit 2fccea0fc2
3 changed files with 44 additions and 14 deletions

View File

@@ -354,7 +354,29 @@ def check_video(video: dict, channel_id: str, results: Results, run_ytdlp: bool)
if not vurl: if not vurl:
results.err(channel_id, f"{label}: missing url") results.err(channel_id, f"{label}: missing url")
elif "127.0.0.1" in vurl or "localhost" in vurl: 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: else:
ok, code = http_ok(vurl) ok, code = http_ok(vurl)
if not ok: if not ok:

View File

@@ -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] | `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 | | [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 ## 4. Search
@@ -62,7 +62,7 @@ Status: `[ ]` open, `[~]` in progress, `[x]` fixed and verified, `[-]` not repro
| | Item | Notes | | | 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) ## Open decision (needs the owner)

View File

@@ -105,9 +105,11 @@ impl NoodlemagazineProxy {
fn has_allowed_image_extension(path: &str) -> bool { fn has_allowed_image_extension(path: &str) -> bool {
let path = path.to_ascii_lowercase(); let path = path.to_ascii_lowercase();
[".jpg", ".jpeg", ".png", ".webp", ".avif", ".gif"] // `.../i.mycdn.me/getVideoPreview?id=..` previews have no extension.
.iter() path.ends_with("/getvideopreview")
.any(|ext| path.ends_with(ext)) || [".jpg", ".jpeg", ".png", ".webp", ".avif", ".gif"]
.iter()
.any(|ext| path.ends_with(ext))
} }
fn is_disallowed_thumb_host(host: &str) -> bool { fn is_disallowed_thumb_host(host: &str) -> bool {
@@ -292,20 +294,23 @@ pub async fn get_image(
requester: web::types::State<Requester>, requester: web::types::State<Requester>,
) -> Result<impl web::Responder, web::Error> { ) -> Result<impl web::Responder, web::Error> {
let endpoint = req.match_info().query("endpoint").to_string(); 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) { if !NoodlemagazineProxy::is_allowed_thumb_url(&image_url) {
return Ok(web::HttpResponse::BadRequest().finish()); 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 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 headers = NoodlemagazineProxy::image_headers(&requester, image_url.as_str());
let mut upstream = requester let mut upstream = requester
@@ -409,6 +414,9 @@ mod tests {
#[test] #[test]
fn allows_https_image_thumbs_but_rejects_local_or_non_images() { 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( assert!(NoodlemagazineProxy::is_allowed_thumb_url(
"https://noodlemagazine.com/thumbs/example.webp" "https://noodlemagazine.com/thumbs/example.webp"
)); ));