Capture the durable Goon facts (phone-side resolve for IP-bound/Turnstile hosters, DoodStream/playmogo pass_md5, _embed_iframe vs _vps_blocked_fallback, stable image proxy tokens, paradisehill multipart, dedup/merge) and a local-debugging section (prod psql/worker patterns, Windows real-Python gotcha, Android emulator AVD `goon` + FLAG_SECURE-off screencap + 2x OTA apply, Chrome DevTools port 9223 + CDP-blanking + hoster network signatures). No secrets/IPs/usernames — env-var forms only. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.3 KiB
Goon
Self-hosted aggregator of adult-content scene metadata. Multi-source ingest (TPDB, StashDB, 30+ tubes) → dedup → on-demand stream resolution (yt-dlp + JWPlayer unpacker) → API + Expo mobile client.
Stack: Python 3.12 (FastAPI + SQLAlchemy + Alembic), Postgres 16, React Native (Expo), Docker Compose.
Git: github.com/goon-foss/goon (remote: goonfoss). Public OSS repo. 18+ — see DISCLAIMER.md.
Zmiany mobilne (Expo) → publikuj przez OTA, nie zostawiaj w gicie
Klient mobile/ (React Native + Expo) aktualizuje się przez self-hosted Expo Updates (OTA), nie przez sklep ani rebuild APK. Zmiany JS (ekrany, komponenty, api.ts) lecą silent.
KRYTYCZNE: git commit NIE publikuje OTA. Sam commit (ani push) nie zmienia NIC na telefonie. Po zmianie w mobile/ trzeba zbudować bundle i opublikować:
# z roota repo; konkretne wartości env w .env.local / prywatnej pamięci (NIE hardcoduj IP tutaj)
GOON_VPS_SSH=<root@vps> GOON_VPS_UPDATES_DIR=/root/goon/app/static/expo-updates \
GOON_PUBLIC_UPDATES_URL=https://api.goon-foss.org/expo-updates/asset \
python scripts/publish_update.py --runtime 1.1
Skrypt: expo export (build) → scp bundla na VPS pod app/static/expo-updates/<runtime>/<update_id>/ → przełącza current.json (endpoint GET /expo-updates/manifest serwuje aktywny). Apka pobiera przy następnym launchu.
Reguły:
- runtime =
1.1(==runtimeVersionwapp.json/APK). Bump TYLKO przy native change (wtedy też nowy APK przez PackageInstaller).RUNTIME_DEFAULTw skrypcie = 1.1. - Apply dwustopniowy: 1. launch pobiera w tle, 2. launch aplikuje → po publish restart apki 2×.
- Publish odpala AI (nie człowiek, nie git-hook). Skrypt działa one-shot też na Windows git-bash (fix 2026-06-02: scp source
.+cwd=DIST,MSYS_NO_PATHCONV, defensywne odkręcanie zmanglowanej ścieżki). - Backend (
app/) to OSOBNY deploy — scpapp/→VPS +docker compose restart(worker/api). NIE przez OTA../appi./scriptssą wolumenami w compose, więc restart wystarcza (bez rebuildu obrazu).
Resolve / playback (kluczowe ustalenia)
Stream resolve jest on-demand, per-source. Nic nie jest pre-resolvowane do DB poza miniaturkami i metadanymi.
- Registry ekstraktorów:
app/extractors/__init__.py_REGISTRYmapujesitetagna funkcję. Natywny ekstraktor zwracalist[StreamSource]._vps_blocked_fallback.extract= sygnał "resolve tylko w WebView na telefonie" (sprawdzajis_vps_blocked_fallback(sitetag)). - IP-bound / CAPTCHA hosterzy resolwują się PO STRONIE TELEFONU, nie na VPS. CDN tokeny i Cloudflare Turnstile wiążą się z IP requestera, więc VPS (Hetzner) dostaje 403 albo Turnstile gate, a residential IP telefonu przechodzi. Resolvery phone-side:
mobile/src/lib/{doodstream,packerHoster,filemoonHoster,getfileResolver,pornxpResolver}.ts.PlayerScreen.tsxroutuje URLtype='hoster':isDoodStream→resolveDoodStream,isPackerHoster→resolvePackerHoster,isFilemoonHoster→resolveFilemoonHoster. - DoodStream i klony (playmogo, doply, dood.*): protokół
pass_md5+ infradoodcdn.io+ niewidzialny Cloudflare Turnstile (przechodzi automatycznie w WebView z residential IP, to NIE interaktywny CAPTCHA). Tube który embeduje playmogo (sxyland, xmoviesforyou, siska) ma iść przez_embed_iframe.extract(wyłuskuje iframe, oddajetype='hoster', telefon dood-resolwuje), NIE przez_vps_blocked_fallback. - Image proxy: WSZYSTKIE miniaturki idą przez
/proxy/img/{token}/...(backend dokłada Referer, którego expo-image nie wysyła → CDN by zwracał 403). Token MUSI być stabilny (make_token(..., stable_bucket_sec=...)), inaczej URL zmienia się co request → expo-image cache miss → re-download co fetch/launch. - Movies multipart (paradisehill): backend parsuje
var videoListi zwraca wszystkie części jako osobneStreamLink; mobile pokazuje je w scrollowalnym Modalu (nieAlert.alert— Androidowy AlertDialog renderuje max 3 buttony, stąd dawne "max 3 z 35"). - Dedup:
app/scheduler/bulk_dedup.py(cross-source performer-shared +phash_exact).merge_scenes(app/resolve/scene_merge.py) przenosi refs/performers/tags/fingerprints/playback_sources i kasuje drop. Missing-merge bez phash:scripts/merge_exact_title_duration.py(ten sam performer + identyczny znormalizowany tytuł + długość co do sekundy). Over-attribution performerów bierze się z luźnego tube-searcha (hqporner wymaga WSZYSTKICH tokenów query w slug). - Bug reports z apki: tabela
bug_reportsna prodzie (screen_name,scene_id,movie_id,message,screenshot_b64,resolved). To główne źródło triage'u. Screenshot dekodujesz z base64 i oglądasz.
Lokalny debugging
Hosta VPS, klucze API i wartości env trzymaj w prywatnej pamięci / .env.local. NIE commituj ich tu (repo jest publiczne).
- DB / worker na prodzie (przez ssh na VPS):
docker compose exec -T db psql -U goon -d goon -c "..."(pamiętaj-U goon, bez tego "role root does not exist"). Python w workerze:docker compose exec -T worker python3 -c "..."albo heredoc.from app.db import session_scope(NIEapp.db.session). - Deploy backendu:
scp app/...na VPS +docker compose restart worker api. Wolumeny./appi./scripts→ restart wystarcza, bez rebuildu. - Windows: prawdziwy Python.
python/python3na PATH to STUB ze Sklepu Microsoft (wypisuje "Python was not found" i wychodzi). Użyj realnego interpretera:%LOCALAPPDATA%\Programs\Python\Python3XX\python.exe(uruchamiaj przez PowerShell).publish_update.pypo sukcesie kończy się czysto (UTF-8 stdout wymuszony 2026-06-08, koniec mylącego exit-1 na polskich znakach). - Emulator Android (podgląd odtwarzania scen i UI):
- adb:
%ANDROID_HOME%\platform-tools\adb.exe(ANDROID_HOMEustawiony; SDK NIE w domyślnym%LOCALAPPDATA%\Android\Sdk). AVD:goon. - Start:
emulator -avd goon -no-snapshot-load -no-boot-anim -gpu swiftshader_indirect(w tle). Czekaj na boot:adb wait-for-device+ polladb shell getprop sys.boot_completed==1. - Pakiet apki:
com.goon.mobile. Launch:adb shell monkey -p com.goon.mobile -c android.intent.category.LAUNCHER 1. - Screencap działa bo dev/emulator build ma
FLAG_SECUREWYŁĄCZONE. Release build ustawiaFLAG_SECURE(blokuje screenshoty/nagrywanie ekranu treści NSFW) — w buildzie emulatora zostawiamy off, żeby AI mógładb exec-out screencap -p > out.pngi zweryfikować odtwarzanie/UI. - OTA na emulatorze: po publish force-stop + launch 2× (1. pobiera bundle w tle, 2. aplikuje).
- Gesty przez adb: tap
adb shell input tap X Y(współrzędne device, sprawdźwm size, zwykle 1080x2400). Long-press:adb shell input swipe X Y X Y 700(ten sam punkt, >300ms =delayLongPress). Bounds elementów:adb shell uiautomator dump(RN czasem nie eksponuje tekstu, natywne Alert/dialogi tak).
- adb:
- Chrome DevTools (diagnoza hosterów/playbacku — sprawdzaj, nie zgaduj): odpal Chrome
--remote-debugging-port=9223 --user-data-dir=<temp>; podłącza sięchrome-devtoolsMCP. KVS/dood tubes blankują się na wykrycie CDP (strona → about:blank) → używajlist_network_requests(includePreservedRequests=true)zamiast DOM. Sygnatury w trace:pass_md5+doodcdn.io= DoodStream;kt_player(+license_code= KVS;cdn-cgi/challenge-platform+turnstile= Cloudflare (sprawdź czy invisible).
Kanban (auto-aggregation)
Ten workspace ma własny Kanban (custom tracker type kanban w .nimbalyst/trackers/kanban.yaml) z kolumnami:
Backlog → Następne → W trakcie → Czeka → Zrobione
Reguła auto-agregacji — WAŻNE
Kiedy podczas sesji pojawi się nowy temat / bug / pomysł / blocker, na który nie ma teraz czasu lub jest poboczny względem aktualnego zadania — utwórz tracker item zamiast tylko wspomnieć o nim w czacie:
mcp__nimbalyst-mcp__tracker_create(
type: "kanban",
title: "<krótki tytuł>",
status: "backlog",
description: "<1-2 zdania kontekstu + ścieżka pliku jeśli dotyczy>"
)
Powód: Jan często skupia się na pierwszej rzeczy z planu, a pozostałe znikają. Kanban jest gwarancją, że nic nie wypadnie. Jeśli wspomnisz coś w czacie i nie założysz ticketu — to się zgubi.
Status next = "wybrane na najbliższe dni" (3-5 max). waiting = zablokowane na zewnętrzne. done ustawia użytkownik, nigdy AI.