From 5098d142f91fe3e600854e9386ee49a843644a60 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 10 Oct 2026 07:43:48 +0000 Subject: [PATCH] Fix pornmz: match contentUrl meta case-insensitively, bound enrichment Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi --- docs/provider-fix-todo.md | 2 +- src/providers/pornmz.rs | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/provider-fix-todo.md b/docs/provider-fix-todo.md index 18c98a5..ce255f0 100644 --- a/docs/provider-fix-todo.md +++ b/docs/provider-fix-todo.md @@ -22,7 +22,7 @@ Status: `[ ]` open, `[~]` in progress, `[x]` fixed and verified, `[-]` not repro | | Channel | Symptom | Notes | |---|---|---|---| | [x] | `thaiporntv` | page URL "Unsupported URL", no `formats` | player attribute renamed `data-enc` -> `data-video-sources` (reversed base64 JSON `{quality,url}`); decoder handles both. 3 formats/item, yt-dlp OK, check.py passes | -| [ ] | `pornmz` | page URL "Unsupported URL", no `formats` | populate formats from detail page | +| [x] | `pornmz` | page URL "Unsupported URL", no `formats` | site changed `itemprop="contentURL"` -> `contentUrl`; selector now case-insensitive. Enrichment concurrency 10 + 25s per-item timeout. Origin is slow (15s+/detail) so first feed 25-50s. check.py passes | | [ ] | `coomer` | page URL "Unsupported URL"; media host CF-gated here | check whether `formats` are populated for videos | | [ ] | `tube8` | page URL 404; `/proxy/tube8/{id}` 502 | | | [ ] | `hypnotube` | page URLs 404 on all sampled items | URL construction | diff --git a/src/providers/pornmz.rs b/src/providers/pornmz.rs index 039ed5f..13fb039 100644 --- a/src/providers/pornmz.rs +++ b/src/providers/pornmz.rs @@ -835,7 +835,8 @@ impl PornmzProvider { } fn meta_itemprop(document: &Html, itemprop: &str) -> Option { - let selector = Self::selector(&format!("meta[itemprop=\"{itemprop}\"]")).ok()?; + // The site mixes `contentURL` / `contentUrl`; match the value case-insensitively. + let selector = Self::selector(&format!("meta[itemprop=\"{itemprop}\" i]")).ok()?; document .select(&selector) .next() @@ -1016,9 +1017,21 @@ impl PornmzProvider { let enriched = stream::iter(items.into_iter().map(|item| { let provider = self.clone(); let options = options.clone(); - async move { provider.enrich_item(item, &options).await } + async move { + // Detail pages are slow (~15s); don't let one hung page stall the list. + let fallback = item.clone(); + match tokio::time::timeout( + std::time::Duration::from_secs(25), + provider.enrich_item(item, &options), + ) + .await + { + Ok(enriched) => enriched, + Err(_) => fallback, + } + } })) - .buffer_unordered(6) + .buffer_unordered(10) .collect::>() .await; @@ -1239,6 +1252,23 @@ mod tests { assert!(item.uploadedAt.is_some()); } + #[test] + fn reads_lowercase_content_url_meta() { + let provider = PornmzProvider::new(); + let item = VideoItem::new( + "pm1".to_string(), + "T".to_string(), + "https://pornmz.com/video/id=pm1".to_string(), + CHANNEL_ID.to_string(), + String::new(), + 0, + ); + let html = r#"
"#; + let item = provider.apply_detail_video(item, html).expect("detail"); + assert_eq!(item.url, "https://video.twimg.com/x/pl/a.m3u8"); + assert_eq!(item.formats.as_ref().map(|f| f.len()), Some(1)); + } + #[test] fn derives_mp4_media_extension() { assert_eq!(