# 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](./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 FIRST` and 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](./DISCLAIMER.md). - Phone home. Sentry telemetry is opt-in (env var, empty by default). --- ## Quick start ### 1. Run the backend (Docker) ```bash git clone 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](../../releases/latest) → `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://:8000` (e.g. your LAN IP, or `http://localhost:8000` if running on the device — uncommon) - **API key**: one of the values you put in `API_KEYS` in `.env` That's it. ### Local Python (no Docker) ```bash 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) ```bash # 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 ```bash 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](./.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). Optionally `SENTRY_ENVIRONMENT=production` and `SENTRY_TRACES_SAMPLE_RATE=0.1`. - **Mobile (local builds)**: create `mobile/.env` (gitignored) with `EXPO_PUBLIC_SENTRY_DSN=https://...`. Expo SDK 49+ auto-inlines `EXPO_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 as `EXPO_PUBLIC_SENTRY_DSN` to 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](./.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/`](./app/connectors/) — TPDB, StashDB, dooplay (movies), paradisehill (movies), [`direct_scrapers/`](./app/connectors/direct_scrapers/) (25 tube discovery scrapers). - [`app/extractors/`](./app/extractors/) — stream URL resolution per tube. yt-dlp wrapper + custom + generic embed-iframe + P.A.C.K.E.R. unpacker. - [`app/resolve/`](./app/resolve/) — cross-source scene merging (phash, title similarity, performer overlap, release date window). - [`app/scheduler/`](./app/scheduler/) — APScheduler jobs + [`performer_driven.py`](./app/scheduler/performer_driven.py) (the core ingest strategy: completeness > recency). - [`mobile/`](./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](./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`](./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](./LICENSE).