diff --git a/docs/provider-fix-todo.md b/docs/provider-fix-todo.md index 4885a55..9c7f487 100644 --- a/docs/provider-fix-todo.md +++ b/docs/provider-fix-todo.md @@ -43,8 +43,8 @@ Status: `[ ]` open, `[~]` in progress, `[x]` fixed and verified, `[-]` not repro | | Channel | Symptom | Notes | |---|---|---|---| -| [ ] | `fapello` | "girl" and "milf" both 0 | | -| [ ] | `chaturbate` | "girl" and "milf" both 0 | | +| [-] | `fapello` | "girl" and "milf" both 0 | by design, documented in the provider: the site's search returns photo models, not videos; no keyword video search exists, so queries are literal-filtered over the feed | +| [x] | `chaturbate` | "girl" and "milf" both 0 | format arguments swapped in the search URL (`girl/api/...?keywords=https://chaturbate.com`, a relative-URL builder error); fixed + unit test. Also stale-cache branch never refreshed. 90 results per query, yt-dlp resolves the live stream | | [x] | `cumlouder` | unknown tag takes 146s before returning 0 | a real 404 went through the requester's Jina/FlareSolverr fallback chain; now the raw response is read, 404 returns empty immediately (0s), 5xx/429 still retried. check.py passes | | [ ] | `vjav` | "girl" 0, "milf" 10 | may be term-specific | | [ ] | `xxxtik` | "girl" 0 (not re-run serially) | | diff --git a/src/providers/chaturbate.rs b/src/providers/chaturbate.rs index 92c740d..dac713c 100644 --- a/src/providers/chaturbate.rs +++ b/src/providers/chaturbate.rs @@ -138,6 +138,34 @@ impl ChaturbateProvider { Ok(video_items) } + /// `keywords=` search on the room list. (The base URL and query used to be + /// passed in swapped order, producing `milf/api/...?keywords=https://...`.) + fn build_search_url(base: &str, query: &str, page: u8) -> String { + let keywords = query + .trim() + .chars() + .map(|c| match c { + ' ' => "+".to_string(), + c if c.is_ascii_alphanumeric() || matches!(c, '-' | '_' | '.' | '~') => { + c.to_string() + } + c => { + let mut buf = [0u8; 4]; + c.encode_utf8(&mut buf) + .bytes() + .map(|b| format!("%{b:02X}")) + .collect::() + } + }) + .collect::(); + format!( + "{}/api/ts/roomlist/room-list/?keywords={}&limit=90&offset={}", + base, + keywords, + 90 * (page.max(1) as u32 - 1) + ) + } + async fn query( &self, cache: VideoCache, @@ -145,22 +173,15 @@ impl ChaturbateProvider { query: &str, options: ServerOptions, ) -> Result> { - let mut video_url = format!( - "{}/api/ts/roomlist/room-list/?keywords={}&limit=90&offset={}", - query, - self.url, - 90 * (page - 1) - ); - video_url = video_url.replace(" ", "+"); + let video_url = Self::build_search_url(&self.url, query, page); // Check our Video Cache. If the result is younger than 1 hour, we return it. let old_items = match cache.get(&video_url) { Some((time, items)) => { if time.elapsed().unwrap_or_default().as_secs() < 60 * 1 { return Ok(items.clone()); - } else { - let _ = cache.check().await; - return Ok(items.clone()); } + // Expired: refetch, keeping the stale list as the fallback. + items.clone() } None => { vec![] @@ -347,3 +368,18 @@ impl Provider for ChaturbateProvider { Some(self.build_channel(clientversion)) } } + +#[cfg(test)] +mod tests { + use super::ChaturbateProvider; + + #[test] + fn builds_search_url_with_base_first() { + assert_eq!( + ChaturbateProvider::build_search_url("https://chaturbate.com", "big tits", 2), + "https://chaturbate.com/api/ts/roomlist/room-list/?keywords=big+tits&limit=90&offset=90" + ); + assert!(ChaturbateProvider::build_search_url("https://chaturbate.com", "a&b", 1) + .contains("keywords=a%26b&")); + } +}