galaxyporn.net carries paysite rips with a dedicated <div id="video-actors"> cast block (no sidebar pollution), studio + date in the title prefix, healthy /page/N/ pagination. Measured orphan risk is low: 92% of a 75-performer sample already have a tpdb/stashdb ref in our DB and 79% of titles match scenes we already hold. seekplayer_engine host regex widened to cover seekplays|4meplayer|ezplayer| seeks|upn and the pro|cloud|one TLDs. This is the same engine (identical AES key/IV and /api/v1/video endpoint), just newer domains; the whitelist is additive so existing hosts keep matching (verified, including that upns.evil.com is still rejected). Unlocks all four galaxyporn player hosts and the same family on other sites. Two things the extractor had to get right: - Call seekplayer_engine directly rather than via extract_stream_from_hoster: the wrapper verifies the resulting URL and the hotlink-guarded HLS 403s, so it discarded correctly decoded streams. - Referer must be the PLAYER origin, not galaxyporn.net. With the site referer the manifest 403s; with the player origin it returns 200. Verified end to end: manifest 200 -> variant 200 (570 segments) -> segment 206. Duration is absent from the HTML, JSON-LD and player payload, and a NULL duration would silently hide scenes behind the min_duration_sec filter (the porntrex incident), so it is computed from the player's thumbnail.vtt last cue. The player API throttles bursts, hence the retry/backoff: that took missing durations from 9/21 down to 3/21. The value runs ~2% short of the true length (one thumbnail interval), which beats having none. Pilot: 21 scenes/page with studio 21/21 and clean cast (max 3), ingest of 2 pages = 42 seen, 34 new, 8 merged, 0 errors. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|---|---|---|
| .github/workflows | ||
| alembic | ||
| app | ||
| landing | ||
| mobile | ||
| scripts | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| alembic.ini | ||
| CONTRIBUTING.md | ||
| DISCLAIMER.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| streamtape_sample.md | ||
| theporndude_coverage.json | ||
| theporndude_dump.json | ||
| theporndude_final_report.md | ||
| theporndude_free_tubes.json | ||
| theporndude_full_audit_report.md | ||
| theporndude_movies.json | ||
| theporndude_movies_scorecard.json | ||
| theporndude_resolved.json | ||
| theporndude_scorecard.json | ||
| theporndude_triage.json | ||
Goon
Self-hosted aggregator for adult-content scene metadata. Indexes scenes from TheporndB, StashDB, and 30+ public adult tube sites; deduplicates across sources; serves an API + mobile (React Native) client for browsing and linking out to playback.
18+ ONLY · Self-hosted only · See DISCLAIMER.md before hosting an instance.
What it does
- Multi-source ingest: pulls canonical scene/performer/studio metadata from TPDB and StashDB on a delta cron, merges duplicates by performer + title + date heuristics (perceptual hash + Levenshtein title distance).
- Tube discovery: per-performer search across 30+ public adult tube sites (mainstream + aggregators). Each tube is scraped directly via HTTP — no proprietary API dependencies.
- Stream resolution on demand: when a user clicks Watch, the API extracts a fresh m3u8/mp4 URL from the tube's page (or falls back to embed link for WebView playback). Mainstream tubes use yt-dlp; aggregator tubes use a generic P.A.C.K.E.R. unpacker for JWPlayer-based hosters (StreamWish/doodporn/mixdrop/...).
- Mobile client (Expo / React Native): scene grid, performer pages, watch history, favorites, hold-to-preview animated thumbnails.
- Performer-driven backfill: a continuous worker walks performers ordered
by
last_searched_at NULLS FIRSTand back-fills tube scenes for the longest-stale performer first.
What it doesn't do
- Host or store any media. Scene metadata + thumbnail URLs only.
- Bypass paywalls, authentication, geo-blocks, or DRM.
- Provide age verification, ToS gating, or moderation for public deployments. See DISCLAIMER.md.
- Phone home. Sentry telemetry is opt-in (env var, empty by default).
Quick start
1. Run the backend (Docker)
git clone <repo-url> goon
cd goon
cp .env.example .env
# Edit .env:
# - TPDB_API_TOKEN (theporndb.net account → API tokens)
# - STASHDB_API_KEY (stashdb.org account → API keys)
# - API_KEYS (generate one: python -c "import secrets; print(secrets.token_urlsafe(32))")
docker compose up -d
Three services come up: db (Postgres 16), api (FastAPI on :8000,
auto-applies migrations on startup), worker (APScheduler running TPDB/StashDB
delta + performer-driven backfill).
Verify: curl localhost:8000/health → {"status":"ok"}.
2. Install the mobile app (Android)
Download the latest debug APK from
GitHub Releases → goon-vX.Y.Z-debug.apk, install on
your Android device (allow "Install from unknown sources" for the browser /
file manager you used to download).
On first launch the app shows the age-gate disclaimer (must be accepted), then a login screen. Enter:
- Backend URL:
http://<your-backend-host>:8000(e.g. your LAN IP, orhttp://localhost:8000if running on the device — uncommon) - API key: one of the values you put in
API_KEYSin.env
That's it.
Local Python (no Docker)
python -m venv .venv && . .venv/bin/activate # or .\.venv\Scripts\activate on Windows
pip install -e .[dev]
cp .env.example .env # edit creds
alembic upgrade head
uvicorn app.main:app --port 8000
Worker (manual one-shot ingest)
# Foreground APScheduler with all jobs
python -m app.scheduler.worker
# One-shot:
python -m app.scheduler.worker --once --source=tpdb --limit=200
python -m app.scheduler.worker --once --strategy=performer-driven --top-n=20
python -m app.scheduler.worker --once --strategy=performer-driven \
--performers="Lola Noir,Mia Malkova"
Building the APK locally
cd mobile
npm install
cd android
./gradlew assembleDebug
# output: mobile/android/app/build/outputs/apk/debug/app-debug.apk
Or just push a v* tag — GitHub Actions builds and attaches the APK to the
Release (.github/workflows/build-apk.yml).
Sentry telemetry (optional)
Default behavior: no telemetry. Sentry only initializes when a DSN is present at runtime/build time.
To enable Sentry for your instance (errors only, no PII, no replay):
- Backend: set
SENTRY_DSN=https://...in.env(gitignored). OptionallySENTRY_ENVIRONMENT=productionandSENTRY_TRACES_SAMPLE_RATE=0.1. - Mobile (local builds): create
mobile/.env(gitignored) withEXPO_PUBLIC_SENTRY_DSN=https://.... Expo SDK 49+ auto-inlinesEXPO_PUBLIC_*vars into the JS bundle at build time. - Mobile (CI builds): add a GitHub repository secret named
SENTRY_DSN. The APK workflow exports it asEXPO_PUBLIC_SENTRY_DSNto gradle. Without the secret, the APK ships with telemetry disabled (forks of this repo don't inherit your DSN).
Sentry init is gated by if (SENTRY_DSN) { Sentry.init(...) } — empty DSN
means the SDK is loaded as dead code but never sends a single request.
Configuration
All runtime config is environment variables (see .env.example for the full list). Highlights:
| Var | Default | Required? | Notes |
|---|---|---|---|
DATABASE_URL |
postgresql+psycopg://goon:goon@localhost:5432/goon |
Yes | Postgres 14+ |
TPDB_API_TOKEN |
empty | For TPDB ingest | Get from theporndb.net account |
STASHDB_API_KEY |
empty | For StashDB ingest | Get from stashdb.org account |
API_KEYS |
empty | Recommended | CSV of allowed API keys; empty = no auth (localhost-only) |
SENTRY_DSN |
empty | No | Empty = no telemetry. Use your own DSN if you want crash reports. |
LOG_LEVEL |
INFO |
No | DEBUG for verbose tube scraping logs |
Scheduler tuning (set to 0 to disable a job):
| Var | Default | Description |
|---|---|---|
GOON_SCHED_TPDB_HOURS |
6 |
TPDB delta interval |
GOON_SCHED_STASHDB_HOURS |
6 |
StashDB delta interval |
GOON_SCHED_PERFORMER_DRIVEN_HOURS |
12 |
Top-N performer ingest |
GOON_SCHED_PERFORMER_CONTINUOUS_SECONDS |
15 |
Continuous backfill tick |
Architecture (high level)
┌──────────┐ delta cron ┌────────────┐
│ TPDB │────────────────▶│ │
└──────────┘ │ │
┌──────────┐ │ ingest │
│ StashDB │────────────────▶│ pipeline │──┐
└──────────┘ │ │ │ cross-source
┌──────────┐ performer- │ │ ▼ dedup +
│ ~25 tube │ driven ┌───▶│ │ ┌─────────┐
│ sites │ search │ └────────────┘ │ Postgres│
└──────────┘────────────┘ └─────────┘
│
┌─────────────────────────────────────────────┤
▼ ▼
┌──────────────┐ ┌──────────────┐
│ FastAPI │ │ Worker │
│ /scenes │◀────── on Watch click ───│ scheduler │
│ /performers │ resolve stream URL │ (APScheduler)│
│ /playback │ (yt-dlp / hoster └──────────────┘
└──────────────┘ packer)
│
▼
┌──────────────┐
│ Expo mobile │
│ (Android) │
└──────────────┘
Key modules:
app/connectors/— TPDB, StashDB, dooplay (movies), paradisehill (movies),direct_scrapers/(25 tube discovery scrapers).app/extractors/— stream URL resolution per tube. yt-dlp wrapper + custom + generic embed-iframe + P.A.C.K.E.R. unpacker.app/resolve/— cross-source scene merging (phash, title similarity, performer overlap, release date window).app/scheduler/— APScheduler jobs +performer_driven.py(the core ingest strategy: completeness > recency).mobile/— Expo / React Native client.
Tube coverage
Discovery + stream resolution registered for ~33 sources:
Mainstream tubes: pornhub, redtube, xhamster, xvideos, xnxx, youporn, eporner, hqporner, sxyprn, porntrex, pornhat.
Aggregators / mirrors: xmoviesforyou, watchporn, siska, porn4days, porndish, xxxfreewatch, latestleaks, latestpornvideo, mypornerleak, porndittcom, hdporn92, sxyland, 0dayxx, perverzija, fpoxxx, porn00, pornxp, hdporngg, fullmovies, freshporno, shyfap.
Movie sites: paradisehill (primary) + dooplay mirrors (mangoporn, streamporn, pandamovies).
If you want to add another tube, see CONTRIBUTING.md.
Support the project
Goon is free, open-source, and ad-free. It stays that way because donations cover the VPS, the TPDB/StashDB tokens, and the time. Crypto only — mainstream processors refuse adult projects, even FOSS tooling.
In-app: Scenes → ♥ opens a screen with QR codes for Monero, Bitcoin, and USDT (TRC-20).
Addresses are hardcoded in
mobile/src/lib/donate.ts so a compromised
server cannot swap them mid-donation. Verify the value on-screen against the
copy in this repo before sending.
Roadmap
Near-term:
- Browse-by-performer + sort-by-studio
- Multi-tag filter (AND / OR)
- Continue-watching rail (position sync across devices)
- Stash local-server bridge — sync favorites/watchlist with a self-hosted Stash
- iOS sideload via TestFlight invite
Mid-term:
- Web companion (read-only browser frontend over the same API)
- BTCPay Server invoicing for one-time / recurring donations
- Performer-alert notifications (server push when a favorited performer drops a new scene)
License
MIT — see LICENSE.