From 6d5771a0380f8b8e7d7567879006caaa65ee9fd0 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 24 Jun 2026 18:40:57 +0000 Subject: [PATCH] add hotbunny provider + fix domain cookie scoping in requester MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- build.rs | 5 + check.py | 21 ++- docs/provider-catalog.md | 2 + src/api.rs | 2 +- src/providers/hotbunny.rs | 355 +++++++++++++++++++++++++++++++++++ src/proxies/hotbunnythumb.rs | 52 +++++ src/proxies/mod.rs | 1 + src/proxy.rs | 5 + src/util/requester.rs | 31 ++- 9 files changed, 464 insertions(+), 10 deletions(-) create mode 100644 src/providers/hotbunny.rs create mode 100644 src/proxies/hotbunnythumb.rs diff --git a/build.rs b/build.rs index cba9fb3..9be97a5 100644 --- a/build.rs +++ b/build.rs @@ -391,6 +391,11 @@ const PROVIDERS: &[ProviderDef] = &[ module: "kwiky", ty: "KwikyProvider", }, + ProviderDef { + id: "hotbunny", + module: "hotbunny", + ty: "HotbunnyProvider", + }, ]; fn main() { diff --git a/check.py b/check.py index b9a365b..f90ef70 100644 --- a/check.py +++ b/check.py @@ -69,6 +69,7 @@ _BROWSER_UA = "Mozilla/5.0 (X11; Linux x86_64; rv:146.0) Gecko/20100101 Firefox/ _CF_PROTECTED_HOSTS = { "www.camsoda.com", "camsoda.com", + "assets.hotbunny.ai", } @@ -277,12 +278,20 @@ def check_video(video: dict, channel_id: str, results: Results, run_ytdlp: bool) continue ok, code = http_ok(furl, headers=fheaders) if not ok: - results.err( - channel_id, - f"{label} format[{j}]: unreachable HTTP={code}" - + (f" (headers={list(fheaders.keys())})" if fheaders else "") - + f": {furl}", - ) + if _is_cf_protected(furl): + results.warn( + channel_id, + f"{label} format[{j}]: unreachable HTTP={code} (CF-protected host, expected)" + + (f" (headers={list(fheaders.keys())})" if fheaders else "") + + f": {furl}", + ) + else: + results.err( + channel_id, + f"{label} format[{j}]: unreachable HTTP={code}" + + (f" (headers={list(fheaders.keys())})" if fheaders else "") + + f": {furl}", + ) else: results.info(channel_id, f"{label} format[{j}]: OK (HTTP {code})") diff --git a/docs/provider-catalog.md b/docs/provider-catalog.md index 4677f54..f29d239 100644 --- a/docs/provider-catalog.md +++ b/docs/provider-catalog.md @@ -24,6 +24,7 @@ This is the current implementation inventory as of this snapshot of the repo. Us | `hentaihaven` | `hentai-animation` | no | no | HTML scraper for hentaihaven.xxx (WordPress/Madara theme), Cloudflare-protected so the provider is gated behind `FLARE_URL` in `skip_reason_for_provider` (mod.rs); the shared requester clears CF directly (wreq Firefox136 emulation currently passes for the listing/search/watch/episode/`player.php` GETs) and falls back to Jina/FlareSolverr. Latest feed `/hentai/page/{N}/`, search `/?s={query}` (search is single-page — page>1 returns empty); listing/search cards link to series watch pages `https://hentaihaven.xxx/watch/{slug}/`. Per-series media resolution (the UUID exists nowhere in page HTML, so enrichment is unavoidable): watch page → episode links `…/watch/{slug}/episode-K` (in `manga-chapters-holder`) → episode page → `