Fix chaturbate search URL (swapped format args) and stale cache branch

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:21:02 +00:00
parent 2fccea0fc2
commit 0824941c23
2 changed files with 48 additions and 12 deletions

View File

@@ -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) | |

View File

@@ -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::<String>()
}
})
.collect::<String>();
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<Vec<VideoItem>> {
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&"));
}
}