App clients were refreshing (and thus swapping thumbnails) every 30
minutes, already more often than hourly. Lowered the app-client cache
TTL to 15 minutes in both fetch paths and matched the channel's
advisory cacheDuration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTqf6orbHZ9rFFpVpcgzcR
melonstube.com started fronting itself with Cloudflare bot management
(a real Turnstile challenge) since this provider was written, so the
shared Requester's Firefox151 emulation got blocked on every path and
the provider returned 0 items.
A/B-probed several browser TLS fingerprints live: Chrome120/124 pass
the plain listing pages but still 403 on /category/* and the
/searching/by-form search POST, while Safari18 passes everywhere.
Listing/search fetches now try a dedicated Safari18-emulated client
first and fall back to the shared Requester if Cloudflare's rules
change again (same pattern as lulustream.rs/doodstream.rs).
Also fixes get_videos ignoring perPage entirely (always returned the
full ~120-card page); it now truncates to the requested size.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTqf6orbHZ9rFFpVpcgzcR
ripnsfw.com serves its entire catalogue as a published-Google-Sheet CSV
with no native pagination/search API, so the provider fetches and
parses that CSV once (cached 180s) and does feed/search/pagination/sort
in memory. Each row's Doodstream/Lulustream embed links resolve to
formats[] via the existing (previously unused) doodstream/lulustream
redirect proxies.
Also fixes check.py's follow_proxy_redirect, which used HEAD even
though these redirect-proxy routes only accept GET/POST, so it never
actually resolved the redirect; extends the CF-protected host list
(suffix matching + ripnsfw.com's client-only-SPA 404 page, dood.video,
tnmr.org) so known sandbox/CDN-IP-reputation failures are reported as
warnings instead of errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTqf6orbHZ9rFFpVpcgzcR
item.formats was always null, leaving the client to open the bare
eporner.com page URL — which the site serves as a small
restricted/preview clip with an "only available on the website"
message instead of the real video.
Reverse-engineered the site's own player flow (matches yt-dlp's
EpornerIE extractor): derive calc_hash from the page's hash via
per-chunk base36 encoding, call the site's /xhr/video/{id} JSON API,
and populate formats[] with the real direct mp4 URLs across all
available resolutions, sorted highest first.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017haiuheHbREQdnh3B5rqgc
hornyleak: yt-dlp's generic extractor on the detail page picked up the
site's own 728x90 banner-ad <video> tag instead of following the real
/embed/{id}/ iframe, so every item streamed an ad. Now resolve the
actual HLS master playlist via the existing embed -> hdplayer.gives
chain and ship it as an explicit format.
hentaimama: search/genre/studio results are series cards, and
resolving them only followed the *last* episode link, so a 4-episode
series only ever surfaced 1 item. Now expands every series match into
all of its episodes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017haiuheHbREQdnh3B5rqgc
Three bugs prevented jable.tv from returning items:
1. FlaresolverrCookie deserialization (root cause): fields like `expires`,
`size`, `priority`, `sameParty`, `sourceScheme`, `sourcePort` are
omitted by FlareSolverr for session cookies, causing serde to fail with
"missing field" and the entire solve to return Err. Added #[serde(default)]
to all non-essential cookie fields.
2. Transport-error branch in get_with_headers: when direct fetch fails with
a transport error and Jina returns a CF challenge body, the old code
returned Ok(challenge_html) instead of falling through to FlareSolverr.
Now checks looks_like_cloudflare_challenge and breaks to FS.
3. Post-FlareSolverr retry: wreq's Firefox136 TLS fingerprint causes CF to
drop the connection even with FS cookies applied, so request.send()
returns Err. Was propagated with `?`; now catches and returns
res.solution.response (the headless-browser body) directly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
kwiky.com (ICF/StreamMate platform) short-form cam clips. Two-step API
(ID list → metadata batch), XSRF token auth cached with double-checked
RwLock, direct MP4 formats from media.icfcdn.com, 48 curated tags via
keywordSearch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bare keyword queries no longer hijack to a category archive when the
query matches a category name (sexy/ass/tiktok/...); only an explicit
cat:/category: prefix or the categories filter routes to an archive.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>