add hotbunny provider + fix domain cookie scoping in requester

New channel: hotbunny (AI-generated hentai, hotbunny.ai JSON API).
Includes thumbnail proxy (/proxy/hotbunny-thumb/) since assets.hotbunny.ai
is CF bot-managed. check.py updated to treat CF-protected format URLs as
warnings rather than errors.

requester.rs: store_response_cookies now honours the Domain attribute in
Set-Cookie headers — cookies scoped to .domain.com are registered against
the parent domain so the wreq Jar returns them for sub.domain.com requests
automatically, without per-provider workarounds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Simon
2026-06-24 18:40:57 +00:00
parent 8373d49593
commit 6d5771a038
9 changed files with 464 additions and 10 deletions

View File

@@ -61,15 +61,40 @@ impl Requester {
}
fn store_response_cookies(&self, url: &str, response: &Response) {
let Some(origin) = Self::origin_url_for_cookie_scope(url) else {
let Some(default_origin) = Self::origin_url_for_cookie_scope(url) else {
return;
};
for value in response.headers().get_all(SET_COOKIE).iter() {
if let Ok(cookie) = value.to_str() {
self.cookie_jar.add_cookie_str(cookie, &origin);
let Ok(cookie) = value.to_str() else {
continue;
};
// Honour the Domain attribute: if Set-Cookie specifies Domain=.foo.com,
// register the cookie against https://foo.com/ so the Jar returns it for
// sub.foo.com requests as well.
let origin = Self::cookie_domain_origin(cookie, &default_origin);
self.cookie_jar.add_cookie_str(cookie, &origin);
}
}
/// Derive the registration origin for a cookie string.
/// If the string contains `Domain=<d>`, strips the leading dot and builds a
/// URL from `<d>` so the jar scopes the cookie to the whole domain tree.
/// Falls back to `default` when no Domain attribute is present.
fn cookie_domain_origin(cookie_str: &str, default: &url::Url) -> url::Url {
for attr in cookie_str.split(';').skip(1) {
let attr = attr.trim();
if attr.len() >= 7 && attr[..7].eq_ignore_ascii_case("domain=") {
let domain = attr[7..].trim().trim_start_matches('.');
if !domain.is_empty() {
let scheme = default.scheme();
if let Ok(u) = url::Url::parse(&format!("{scheme}://{domain}/")) {
return u;
}
}
}
}
default.clone()
}
fn store_flaresolverr_cookies(