diff --git a/build.rs b/build.rs index c257507..160c78d 100644 --- a/build.rs +++ b/build.rs @@ -286,6 +286,11 @@ const PROVIDERS: &[ProviderDef] = &[ module: "sextb", ty: "SextbProvider", }, + ProviderDef { + id: "notfans", + module: "notfans", + ty: "NotfansProvider", + }, ProviderDef { id: "hentaihaven", module: "hentaihaven", @@ -296,6 +301,11 @@ const PROVIDERS: &[ProviderDef] = &[ module: "fikfap", ty: "FikfapProvider", }, + ProviderDef { + id: "hdporn92", + module: "hdporn92", + ty: "Hdporn92Provider", + }, ProviderDef { id: "chaturbate", module: "chaturbate", @@ -371,6 +381,11 @@ const PROVIDERS: &[ProviderDef] = &[ module: "xgroovy", ty: "XgroovyProvider", }, + ProviderDef { + id: "borntobefuck", + module: "borntobefuck", + ty: "BorntobefuckProvider", + }, ProviderDef { id: "xvideos", module: "xvideos", @@ -416,6 +431,16 @@ const PROVIDERS: &[ProviderDef] = &[ module: "redgifs", ty: "RedgifsProvider", }, + ProviderDef { + id: "hornyleak", + module: "hornyleak", + ty: "HornyleakProvider", + }, + ProviderDef { + id: "fapello", + module: "fapello", + ty: "FapelloProvider", + }, ]; fn main() { diff --git a/check.py b/check.py index 1e21259..a8608da 100644 --- a/check.py +++ b/check.py @@ -74,6 +74,14 @@ _CF_PROTECTED_HOSTS = { # plain requests/curl always see the "Just a moment" challenge. "hentaihaven.xxx", "www.hentaihaven.xxx", + # Cloudflare-gated; the shared requester uses the Jina mirror fallback. + # `video.url` is the detail page so it will always 403 to a plain request. + "fapello.com", + "www.fapello.com", + "cdn.fapello.com", + # hdplayer.gives returns 200 to plain curl but 404 to curl_cffi's JA3 — the + # HLS list/enc... endpoint is request-bound to the embed-page TLS context. + "hdplayer.gives", } diff --git a/docs/README.md b/docs/README.md index a2fa405..6cef05b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,7 +5,7 @@ This folder is the fastest handoff path for anyone adding or repairing a channel Start here: 1. Read `architecture.md` for the server flow, request lifecycle, and core types. -2. Read `provider-playbook.md` for the exact process to add a new provider or proxy. +2. Read `provider-playbook.md` — its top **"Best Practices (Living)"** section is the cross-cutting rule set to internalize first, then follow the rest of the playbook for the exact process to add a new provider or proxy. 3. Use `provider-catalog.md` to find the closest existing implementation to copy. 4. Use `docs/hottubapp/*.html` when you need the client-facing API contract for status, videos, or uploaders. 5. Only then touch `prompts/new-channel.md`; it assumes the docs above exist. diff --git a/docs/provider-catalog.md b/docs/provider-catalog.md index 208cf59..9c66c36 100644 --- a/docs/provider-catalog.md +++ b/docs/provider-catalog.md @@ -15,16 +15,19 @@ This is the current implementation inventory as of this snapshot of the repo. Us | `chaturbate` | `live-cams` | no | no | Live cam channel. | | `clapdat` | `amateur-homemade` | no | yes | Svelte/JSON-hydrated provider using home/recent/trending routes, Meilisearch keyword search, and `/proxy/clapdat/...` redirect playback resolution. | | `erome` | `amateur-homemade` | no | no | HTML album scraper with hot/new feeds, keyword search, and uploader-slug shortcuts (`uploader:`). | +| `fapello` | `onlyfans` | no | no | HTML + embedded-JSON provider for fapello.com (OnlyFans-style photo/video leak aggregator; the site is fundamentally a *photo* model archive — `/`, `/new/`, `/hot/`, `/trending/`, `/tags/`, `/search_v2/` — and the much smaller `/videos/` section is what this channel surfaces). Cloudflare-gated; the shared requester's Jina mirror fallback resolves every listing and detail page (`r.jina.ai/http://fapello.com/...`, `X-Return-Format: html`). Latest feed `GET /videos/` (page 1, ~6-8 cards), `GET /ajax/videos/page-{N}/` (page 2+, ~4 cards) — cards expose the numeric id, the `fapello.com/content/.../{model}_{NNNN}_300px.jpg` thumbnail, and the `/{model_slug}/` uploader link. Each detail page `GET /video/new/{id}/` embeds a complete `var BOOT = { items: [...], next_page, page_size }` JSON block (preserved by the Jina mirror) — every BOOT item carries the resolved `video_url` (`https://cdn.fapello.com/content/.../{N}/{model}_{NNNN}.mp4`), `poster_url` (the matching `.jpg`, no `_300px` suffix — provider swaps that in for the full-size thumb), `duration` ("HH:MM:SS" → seconds), `width`/`height`/`aspect`/`orientation`, `count_likes` (→ `views`), `name`/`model_url` (uploader), and `tags[]`. Per-card BOOT enrichment is bounded via `buffer_unordered(4)` with a 20s timeout — listing-only metadata (id, thumb, uploader) is preserved on timeout so a Jina-throttled page still produces usable cards. The Jina mirror does NOT trigger JS, so the `/popular_videos/{twelve_hours,month,all_time}/` routes and `/search_v2/?type=...` are empty (search returns *photo models*, not videos; the site has no native "videos matching keyword" search) — those filters are deliberately not exposed; only `sort:new` is offered. `video.url` is the detail page (not yt-dlp-resolvable — no extractor exists), so `formats[0]` carries the direct `cdn.fapello.com` mp4 with `Referer: https://fapello.com/` (the CDN serves direct without it, but the header matches the on-site context). Media and thumbnails (`fapello.com/content/.../*.jpg`, `cdn.fapello.com/content/.../*.mp4`) are range-served, no auth/cookies/JA3 emulation required, so no proxy route is needed. `check.py`'s `_CF_PROTECTED_HOSTS` allowlist includes `fapello.com` and `cdn.fapello.com` so its plain-requests 403s for `video.url` and the format URL downgrade to warnings instead of errors. No `/api/uploaders` (fapello has stable model slugs but no dedicated "videos by this uploader" feed — only the mixed photo+video model page). | | `fikfap` | `tiktok` | yes | yes (thumbs only) | JSON-API provider for fikfap.com (TikTok-style swipe short clips); anonymous auth via a client-generated `Authorization-Anonymous` UUID header (no real login needed); listing via `GET api.fikfap.com/posts?sort=new\|trending\|random&amount=N&afterId=` (cursor pagination — page N costs N sequential requests); search via `GET search?q=` (single fixed-size batch, no pagination — page 2+ returns empty); hashtag feeds via `GET hashtags/label/{label}/posts` and creator feeds via `GET profile/username/{user}/posts`, both also cursor-paginated; `tag:`/`hashtag:`/`#` and `user:`/`uploader:` query prefixes route directly; `categories` option exposes a small curated static hashtag list (no full catalog endpoint exists anonymously); `video.url` is the `fikfap.com/post/{id}` page (a client-rendered SPA, not yt-dlp-resolvable on its own); `videoStreamUrl` from the JSON response is sent directly as `formats[0].url` (signed Bunny CDN HLS `.m3u8`, ~24h token expiry) with `httpHeaders: {Referer: https://fikfap.com/}` — Hot Tub clients apply a format's `http_headers` across the whole HLS playback session (manifest, sub-playlists, and segments), so no proxying of the media itself is needed; thumbnails have no per-field header mechanism, so they're proxied via `/proxy/fikfap-thumb/...` to inject the same Referer; `get_uploader` implemented (`fikfap:` IDs) using `GET profile/username/{user}`. | | `freepornvideosxxx` | `studio-network` | no | no | Studio-style scraper. | | `fyptt` | `tiktok` | no | no | HTML scraper for fyptt.to (Beaver Builder/WordPress short-form TikTok-style vertical porn); card selector `.fl-post-grid-post[class*="post-ID"]` with `category-{slug}` CSS class doubling as both listing tag and category-archive route; latest feed `/` (page N: `/page/N/`), search `/?s=query` (page N: `/page/N/?s=query`), category archives at bare top-level slugs like `/tiktok-ass/` (12 hardcoded categories exposed via the `categories` filter option, or via an explicit `cat:`/`category:` query prefix — bare keyword queries always go to WordPress search, never a category archive, because the category names ("sexy", "ass", "tiktok", "live", ...) are also the most common search terms); per-item enrichment fetches the detail page for the JSON-LD `embedURL` (one of three on-site player endpoints: `fypttstr.php`, `fypttjwstr.php`, or `fypttjwstrhls.php`) and `datePublished`, then fetches that embed URL to extract the actual signed `stream.fyptt.to` mp4 or `/hls/*.m3u8` URL (token expires ~2h, no Referer required) for `formats`; thumbnails (`fyptt.to/wp-content/uploads/...webp`) need no proxy; no duration metadata available on listing or detail pages (set to 0); no real uploader/model identity (the `girl-{slug}` CSS class is cosmetic only, not a linkable archive) so `/api/uploaders` is not implemented; `video.url` is the detail page URL (not yt-dlp resolvable directly — the player is sandboxed-iframe-only) so `formats` are populated instead; no proxy needed. | | `freeuseporn` | `fetish-kink` | no | no | Fetish archive pattern. | | `hanime` | `hentai-animation` | no | yes | Uses proxied CDN/thumb handling. | | `heavyfetish` | `fetish-kink` | no | no | Direct media handling. | +| `hdporn92` | `mainstream-tube` | no | no | WordPress retrotube-theme aggregator for hdporn92.com (network studio releases — MyPervyFamily, MYLF, StepSiblings, …). Latest feed `/` (page 1) and `/page/N/` (page N≥2); search `/?s={query}` (page 1) and `/page/N/?s={query}` (page N≥2) — the path with the `s` parameter must come after the `page/N` segment, not before. Pagination is by `article.loop-video` cards; the post id comes from the `post-{N}` CSS class. Two-step enrichment (page URL + Referer-locked m3u8 in `formats`): detail page → `