Fix xxdbx site-relative thumbs; cumlouder answers 404 immediately
xxdbx assets are now /id.jpg, not //host/..; prefixing https: produced https:/id.jpg. cumlouder's unknown-tag 404 went through the requester's mirror/FlareSolverr fallbacks (146s); read the raw response and return an empty list at once while still retrying 5xx. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi
This commit is contained in:
@@ -357,22 +357,33 @@ impl CumlouderProvider {
|
||||
let headers = vec![("Referer".to_string(), format!("{BASE_URL}/"))];
|
||||
|
||||
// The origin sporadically answers its first uncached request with a
|
||||
// Cloudflare 522; a single retry succeeds.
|
||||
// Cloudflare 522; a retry succeeds. Use the raw response so a real
|
||||
// 404 (unknown tag/category) answers immediately instead of
|
||||
// escalating through the requester's mirror/FlareSolverr fallbacks,
|
||||
// which cost minutes.
|
||||
let mut last_err = String::new();
|
||||
let mut html = None;
|
||||
for _ in 0..3 {
|
||||
match requester
|
||||
.get_with_headers(&url, headers.clone(), None)
|
||||
.get_raw_with_headers(&url, headers.clone())
|
||||
.await
|
||||
{
|
||||
Ok(body) if body.contains("muestra-escena") => {
|
||||
html = Some(body);
|
||||
break;
|
||||
}
|
||||
Ok(body) => {
|
||||
Ok(response) => {
|
||||
let status = response.status().as_u16();
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
if status == 404 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
if body.contains("muestra-escena") {
|
||||
html = Some(body);
|
||||
break;
|
||||
}
|
||||
if status >= 500 || status == 429 {
|
||||
last_err = format!("HTTP {status}");
|
||||
continue;
|
||||
}
|
||||
// A page with no cards is a real empty archive.
|
||||
last_err = format!("no cards in {} bytes", body.len());
|
||||
// A page with no cards is a real empty archive (404 body
|
||||
// included); only keep retrying on transport errors.
|
||||
html = Some(body);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,23 @@ impl XxdbxProvider {
|
||||
provider
|
||||
}
|
||||
|
||||
/// Resolves the card's asset URLs, which the site serves protocol-relative
|
||||
/// (`//prev.xxdbx.com/..`) or, now, site-relative (`/1234.jpg`).
|
||||
fn absolute_url(raw: &str) -> String {
|
||||
let raw = raw.trim();
|
||||
if raw.is_empty() {
|
||||
String::new()
|
||||
} else if raw.starts_with("http://") || raw.starts_with("https://") {
|
||||
raw.to_string()
|
||||
} else if let Some(rest) = raw.strip_prefix("//") {
|
||||
format!("https://{rest}")
|
||||
} else if raw.starts_with('/') {
|
||||
format!("https://xxdbx.com{raw}")
|
||||
} else {
|
||||
format!("https://xxdbx.com/{raw}")
|
||||
}
|
||||
}
|
||||
|
||||
fn build_channel(&self, clientversion: ClientVersion) -> Channel {
|
||||
let _ = clientversion;
|
||||
Channel {
|
||||
@@ -272,26 +289,24 @@ impl XxdbxProvider {
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
|
||||
let thumb = format!(
|
||||
"https:{}",
|
||||
video_segment
|
||||
.split("<img ")
|
||||
.collect::<Vec<&str>>()
|
||||
.get(1)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.split("src=\"")
|
||||
.collect::<Vec<&str>>()
|
||||
.last()
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.split("\"")
|
||||
.collect::<Vec<&str>>()
|
||||
.get(0)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
);
|
||||
let thumb_raw = video_segment
|
||||
.split("<img ")
|
||||
.collect::<Vec<&str>>()
|
||||
.get(1)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.split("src=\"")
|
||||
.collect::<Vec<&str>>()
|
||||
.last()
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.split("\"")
|
||||
.collect::<Vec<&str>>()
|
||||
.get(0)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let thumb = Self::absolute_url(&thumb_raw);
|
||||
let raw_duration = video_segment
|
||||
.split("<div class=\"v_dur\">")
|
||||
.collect::<Vec<&str>>()
|
||||
@@ -306,21 +321,19 @@ impl XxdbxProvider {
|
||||
.to_string();
|
||||
let duration = parse_time_to_seconds(raw_duration.as_str()).unwrap_or(0) as u32;
|
||||
|
||||
let preview = format!(
|
||||
"https:{}",
|
||||
video_segment
|
||||
.split("data-preview=\"")
|
||||
.collect::<Vec<&str>>()
|
||||
.get(1)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.split("\"")
|
||||
.collect::<Vec<&str>>()
|
||||
.get(0)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
);
|
||||
let preview_raw = video_segment
|
||||
.split("data-preview=\"")
|
||||
.collect::<Vec<&str>>()
|
||||
.get(1)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.split("\"")
|
||||
.collect::<Vec<&str>>()
|
||||
.get(0)
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
let preview = Self::absolute_url(&preview_raw);
|
||||
let tags = video_segment
|
||||
.split("<div class=\"v_tags\">")
|
||||
.collect::<Vec<&str>>()
|
||||
@@ -439,3 +452,25 @@ impl Provider for XxdbxProvider {
|
||||
Some(self.build_channel(clientversion))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::XxdbxProvider;
|
||||
|
||||
#[test]
|
||||
fn resolves_site_relative_protocol_relative_and_absolute_assets() {
|
||||
assert_eq!(
|
||||
XxdbxProvider::absolute_url("/7004779764736.jpg"),
|
||||
"https://xxdbx.com/7004779764736.jpg"
|
||||
);
|
||||
assert_eq!(
|
||||
XxdbxProvider::absolute_url("//prev.xxdbx.com/700477973230.mp4"),
|
||||
"https://prev.xxdbx.com/700477973230.mp4"
|
||||
);
|
||||
assert_eq!(
|
||||
XxdbxProvider::absolute_url("https://cdn.example/a.jpg"),
|
||||
"https://cdn.example/a.jpg"
|
||||
);
|
||||
assert_eq!(XxdbxProvider::absolute_url(""), "");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user