goon/README.md
goon-foss ad0284585b Initial commit
Goon — self-hosted aggregator for adult-content scene metadata.

Indexes scenes from TPDB, StashDB, and 30+ public adult tube sites.
Cross-source deduplication via perceptual hash + Levenshtein distance.
FastAPI backend + APScheduler worker + React Native (Expo) mobile client.

FOSS, ad-free, donation-funded. See README for details.
2026-05-20 10:10:22 +02:00

261 lines
10 KiB
Markdown

# 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 <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](../../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://<your-backend-host>: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).