SiteScenes passes the tube as origin/name (strings), not UUIDs, so the existing UUID-only auto-context loop dropped them. Reports like 'ingest of this site has been stuck 2 days' (14f3a655) arrived without any site identifier. Add a second loop for known string identity params (origin/name/sitetag/tag/q), length-capped, so per-site/per-performer reports become actionable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .github/workflows | ||
| alembic | ||
| app | ||
| landing | ||
| mobile | ||
| scripts | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| alembic.ini | ||
| CLAUDE.md | ||
| 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.