The redirect proxy from 6ba65f1 still played the one-minute "watch on
eporner.com" clip away from home: checked from foreign networks
(check-host.net nodes in BR/DE/HU), a freshly signed link answers 302 ->
static.eporner.com/na.mp4, while the IP that resolved it gets the file. A
redirect therefore only works for a player behind the server's public IP.
/proxy/eporner/{alnum_id}/{height}.mp4 now streams the mp4: resolves the
link (cached 90 min, re-resolved when the CDN answers with a redirect),
forwards Range, and returns a sized body so 206 responses keep
Content-Length. It is no longer a redirect proxy, so app clients keep the
URLs in formats. Verified locally: HEAD, 0-1, mid-file and open-ended ranges
return 206 with Content-Length/Content-Range and valid MP4 data.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi
Expired eporner mp4 links are answered with a 302 to static.eporner.com/
na.mp4, a one-minute "watch it on eporner.com" clip. The links expire
exactly two hours after the /xhr/video call, so anything played from a
favourite, history entry or a list left open past that hit the decoy.
New /proxy/eporner/{alnum_id}/{height} re-resolves the hash/xhr chain at
play time and 302s to the matching (else best) quality. The eporner
provider and melonstube's eporner destinations now emit these proxy URLs;
verified app clients get the best quality as video.url via the existing
redirect-proxy rule. Verified live: proxy -> fresh CDN link -> 206 video/mp4
for app and non-app clients.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUYdCh2nCVsQYgJAT7gwSi
- wowxxx: site migrated wow.xxx -> wowxxx.to; update BASE_URL and stop
pinning Version::HTTP_11, which mismatched the TLS handshake's h2 ALPN
and got scored as a bot (same root cause already fixed for jable.tv)
- porndish: recognize playmogo.com iframes as the same Doodstream-clone
embed protocol as myvidplay.com (confirmed live) instead of falling
through to a naive branch that pointed formats[0] at the unresolved,
Cloudflare-protected iframe URL; kept in sync in both providers/porndish.rs
and proxies/porndish.rs since single-provider builds can't share code
across that boundary
- noodlemagazine: entity-decode the thumb URL pulled from a raw
data-original="..." attribute - undecoded `&` in the query string
was getting the CDN to 400 the request
- requester.rs: cap the previously-unbounded HTTP 429 retry loop at 3
attempts before falling through to the existing Jina/FlareSolverr
fallback, instead of retrying forever with no bound
Also fixes check.py: the thumbnail check never consulted the
CF-protected-host allowlist (only the page-url check did), and adds
missav.ws/surrit.com, tik.porn/image-cdn.tik.porn, vrporn.com, and
www.wowxxx.to/img.wowxxx.to to that allowlist - all four providers
already work fine through the app's real client; the checker's plain
probes (and its yt-dlp invocation, which never applies a channel's
declared ytdlpCommand impersonation flag) just can't replicate that.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTqf6orbHZ9rFFpVpcgzcR
- shooshtime: update parse_list_videos selectors for the site's redesigned
markup (div.card / a.title / a.image img / div.duration / div.list div.box
/ div.name a.text)
- hdporn92: mirror the site's own links.hls4||hls3||hls2 fallback and scan
every P.A.C.K.E.R. block instead of assuming the first one is the player
block; prefer a genuine .m3u8 candidate over a decoy .txt one
- camsoda: replace naive first-`{`/last-`}` JSON extraction with a proper
balanced-brace scan so trailing wrapper markup can't corrupt the parse
("key must be a string")
- sextb: fix mirror_url() double-prefixing the scheme when building Jina
mirror URLs (produced a malformed https://r.jina.ai/http://https://... URL)
- supjav: switch curl_cffi impersonation from chrome to safari18_0 - Cloudflare
blocks the chrome/firefox profiles outright but passes Safari
- hentaimama: resolve every card on a listing page (not just the first
perPage) and truncate after filtering, so a bulk "Coming soon" placeholder
batch at the front of the feed no longer empties the whole page; report a
provider error if literally every candidate fails
Refreshes graphify's graph and docs/provider-catalog.md with the new
technical details for all six providers.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QTqf6orbHZ9rFFpVpcgzcR
melonstube is a meta-search aggregator that redirects out to ~120
third-party hosts. A live crawl of /new, /popular, and /rating
(~3,350 out-links) found the previous "assume yt-dlp can resolve it"
fallback was hiding real failures on the highest-traffic hosts:
- eporner.com is ~34% of all melonstube traffic by itself, and like
the eporner channel (fixed earlier), serves a restricted preview
clip without its hash/xhr dance.
- fapcat.com/momvids.com 403 a plain fetch (Cloudflare).
- Several others just needed real extraction instead of a guess.
Per-card enrichment now dispatches by destination host to:
- Eporner: reuses eporner's own hash/xhr + Referer resolution, moved
to src/util/eporner_resolve.rs so both providers' single-provider
builds still compile (HOT_TUB_PROVIDER=<id> only compiles the one
selected provider module; src/util/* always compiles).
- Kvs (pornfreenow, analdin, babepump, w1mp, fetishshrine, pervclips,
xozilla, ok.xxx, ebonypeek, xgroovy): a shared get_file extractor,
the same KVS backend several other Hot Tub providers already scrape.
- KvsCfGated (fapcat, momvids): same extractor via the Safari18
bypass client melonstube.com itself already needs.
- XhamsterMirror (xh.partners): a genuine xHamster mirror instance,
Referer built from the actual resolved host.
- SomePorn: HLS playlist URL built from the page's video id directly.
- Pornone: direct <source> tag extraction.
flirt4free.com (a live-cam page, not a VOD) moved into the dropped-
host bucket alongside the existing junk/join.* hosts.
Enrichment is bounded (concurrency, a 10s per-card timeout, and
capped to the requested perPage rather than the full ~240-card page)
so a slow or rate-limited destination can't stall the response.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root cause found live: eporner's CDN mp4 URLs enforce a Referer check
independent of the hash/xhr resolution — a request with no Referer or
one pointing at eporner.com gets the real file, but any other Referer
gets a 302 to a static .../na.mp4 ("not available") decoy. Since our
VideoFormat entries declared no http_headers at all, playback fell
through to whatever default Referer the client happened to send,
occasionally tripping this check and showing the short "this video is
only available at eporner.com" clip.
Verified against a live video: fetching a resolved format URL with an
unrelated Referer reproduces the exact na.mp4 redirect; with the
correct Referer it serves the full file. Each format now explicitly
declares Referer: https://www.eporner.com/ so client-side defaults
can't trip the check.
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
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>