- lib/bottomBar.ts: stale paska, szyna scroll-top, wartosc Animated do chowania
(hysteresis po przebytej drodze, strefy gora/dol), inset dla list
- BottomTabsBar: absolute nad trescia, translateY z native drivera, null przy klawiaturze
- Scenes: Performers/Tags/Favorites jako IconButton obok lejka i zebatki, chipsy
zapisanych wyszukiwan tylko przy fokusie albo wpisanym tekscie, Save jako chip
- changelog 2026-09-07d, OTA 3f7e21c5 na 1.3
Dwie prosby z apki 2026-09-02.
Sites: dlugie nacisniecie przypina albo odpina strone (gwiazdka i akcent na ramce),
przypiete ida na gore listy w tej samej kolejnosci co reszta, takze przy szukaniu i przy
sortowaniu "Recent". Stan po stronie serwera (/favorites/sites), wiec przechodzi kodem
synchronizacji jak inne ulubione.
Filtry: `usePersistedState` w SecureStore, osobny klucz dla Scenes, JAV, Anime
(`filters_<sekcja>`) i Movies. Zapis dopiero po wczytaniu, zeby domyslne z pierwszego
renderu nie nadpisaly zapisanych; odczyt scalany z domyslnymi, zeby nowe pole w kolejnej
wersji dostalo wartosc. Zapytanie o liste czeka na wczytanie (`enabled`), bez podwojnego
strzalu. SecureStore ma ~2 KB na klucz; przy bardzo dlugiej liscie tagow zapis moze sie
nie udac i wtedy filtr zyje tylko do zamkniecia apki, ale ekran sie nie wywraca.
tsc: 11 bledow przed i po (baza). Wpis w changelogu 2026-09-02b.
Zgloszone przez uzytkownika: po wydaniu 0.3.0 nie dostal propozycji aktualizacji.
Przyczyna jest moja. `APP_VERSION` czyta `Constants.expoConfig.version`, czyli app.json,
a app.json przyjezdza do aplikacji RAZEM Z PACZKA OTA. Podbilem wersje na 0.3.0 i
opublikowalem te paczke rowniez na stary kanal 1.2, wiec kazdy uzytkownik APK 0.2.2
zaczal raportowac 0.3.0 i sam siebie uznal za aktualnego. Skutek podwojny: nie dostal ani
monitu o nowym APK, ani wpisu w changelogu, ktory byl kierowany dokladnie do niego.
Poprzednia poprawka (dad4bbf, porownanie "nowsza" zamiast "rozna") tego NIE naprawiala:
przy dwoch identycznych numerach oba warunki daja falsz.
Jedyny numer odporny na OTA to runtime z AndroidManifest, bo expo-updates uzywa go do
sprawdzania zgodnosci paczek i nie moze brac go z paczki. Stad `NATIVE_RUNTIME`
w `src/lib/appVersion.ts`, a serwer oddaje teraz `runtime_version` obok `version`.
Porownanie leci po runtime, z zapasowym porownaniem wersji, gdy serwer go nie poda.
Bramka wpisow w changelogu przeszla z `onlyBelowVersion` na `onlyBelowRuntime`.
`expo-application` bylby czystszym zrodlem natywnej wersji, ale to modul natywny, wiec
nie wszedlby przez OTA do ludzi na starym APK, czyli dokladnie do tych, ktorych to
dotyczy. Runtime dziala na tym, co juz maja zainstalowane.
Przy okazji tresc dialogu: pokazywala "Server: 0.3.0, App: 0.3.0", czyli dwie identyczne
liczby, bo drugi numer byl z paczki, a nie z APK. Teraz nie pokazujemy numeru
zainstalowanej wersji, bo po OTA nie mamy wiarygodnego.
Zweryfikowane na emulatorze w obie strony, na podpisanych APK:
* APK 0.2.2 (runtime 1.2) po pobraniu paczki: monit jest, tresc czytelna.
* APK 0.3.0 (runtime 1.3): monitu nie ma.
Instalacja 0.3.0 na wierzch 0.2.2 przeszla ponownie.
Zgloszenie de93ba42 ("Can't stream to Chrome cast"). Cast to natywny SDK, wiec NIE da
sie go wyslac przez OTA: to nowe APK, ktore ludzie musza pobrac sami. Klucz podpisujacy
ten sam, wiec ALLOWED_APP_SIG_HASH bez zmian i aktualizacja wchodzi na wierzch starej.
Argument, ktory przewazyl: od czerwca release ma wlaczone FLAG_SECURE, wiec zwykle
mirrorowanie ekranu na telewizor jest zablokowane. Cast jest jedyna droga na duzy ekran.
Zasieg: 77% udanych odtworzen konczy sie bezposrednim adresem, plus czesc hosterow
filmowych oddajacych gotowy plik. Okolo 13% (porndoe, mypornerleak, superporn) idzie
przez WebView i castowac sie NIE DA, tam przycisku nie ma.
Wpiete recznie, bez `expo prebuild`, bo katalog android/ jest w repo i ma reczne zmiany,
ktore prebuild by przepisal. Cztery edycje, dokladnie to co robi config plugin:
manifest (OPTIONS_PROVIDER + expanded controller), MainActivity, castFrameworkVersion
w rootProject.ext oraz JAWNA zaleznosc play-services-cast-framework w app/build.gradle.
Ta ostatnia jest konieczna, bo biblioteka deklaruje ja jako `implementation`, co nie
jest przechodnie na sciezke kompilacji, i bez niej build pada na compileReleaseKotlin.
Identyfikator odbiornika CELOWO zaden: bez wpisu biblioteka bierze domyslny odbiornik
Google. Rejestracja wlasnego wiazalaby konto Google z Goonem, a przy tresci dla
doroslych latwo o cofniecie.
Urzadzenia bez Google Play Services zabezpieczone w DWOCH miejscach, bo mamy uzytkownika
na LineageOS (GOON-3B) i sideload na zegarku (GOON-51): `getSharedInstance` w try/catch
po stronie natywnej, a po stronie JS cala obsluga w osobnym `CastControl`, montowanym
dopiero gdy `useCastAvailable` potwierdzi Play Services. To nie jest ostroznosc na
wyrost: hooki biblioteki synchronicznie subskrybuja natywne zdarzenia i bez GMS wywracaja
ekran, a hooka nie da sie wywolac warunkowo.
Chromecast pobiera plik SAM i nie wysyla naszych naglowkow, wiec zrodla wymagajace
Referera ida przez /proxy (dokłada go po stronie serwera), a reszta bezposrednio, zeby
nie obciazac VPS. Do tego proxy przestaje przekazywac CORS hostera: mixdrop oddawal
`Access-Control-Allow-Origin: https://mixdrop.my`, co odbiornik odrzucal przy HLS.
Przy okazji GOON-3B: doszedl brakujacy wpis `com.google.android.gms.version`, ktory
wywracal apke na LineageOS, gdy Chromium probowal podpiac skaner kodow.
Zweryfikowane na emulatorze na podpisanym APK: certyfikat identyczny ze starym
(3294aba5...), instalacja NA WIERZCH 0.2.2 udana, wersja 13/0.3.0, runtime 1.3 w
manifescie, aplikacja wstaje bez crasha, ExoPlayer gra (MediaCodec dla video), a SDK
Casta startuje poprawnie: dynamite sie laduje i `MediaRouterProxy` czyta NASZ CastOptions.
Logika wyboru adresu przetestowana osobno na osmiu przypadkach.
CZEGO NIE ZWERYFIKOWALEM: realnego rzutu na telewizor. W sieci emulatora nie ma zadnego
Chromecasta, wiec `loadMedia` i odtwarzanie po stronie odbiornika sa niesprawdzone.
Build wymaga SENTRY_DISABLE_AUTO_UPLOAD=true, bo sentry.properties czeka na
SENTRY_ORG/SENTRY_PROJECT/SENTRY_AUTH_TOKEN ze srodowiska (Infisical, workspace
goon-app). Skutek: slady JS z 0.3.0 beda w Sentry nieczytelne do czasu przebudowy z tymi
zmiennymi.
Apka porownywala wersje z serwerem przez `!==`, a nie "nowsza niz moja". Serwer trzyma
numer najnowszego APK na sztywno w `app/main.py`, wiec w momencie, gdy aplikacja
wyprzedza serwer, KAZDY uzytkownik nowej wersji dostawal monit "Update available" z
propozycja zejscia w dol.
Zlapane na emulatorze przy weryfikacji 0.3.0: swiezo zainstalowana 0.3.0 kazala
zainstalowac 0.2.2. Bez tej weryfikacji poszloby to do ludzi.
Wspolny `isNewerVersion` w `src/lib/appVersion.ts`, uzywany teraz i przez monit o APK,
i przez bramkowanie wpisow w changelogu (byla tam druga kopia tej samej logiki).
Sprawdzone na przypadkach granicznych, w tym 0.3.10 wobec 0.3.9 i pusta odpowiedz
serwera.
Zgloszenie 2026-08-18: kropla "zrobila wiecej zlego niz dobrego". Trzy zarzuty,
wszystkie trafne:
- nie wygladala ladniej,
- rysunek SVG mial 44 px i wystawal ponad pasek, wiec naglowek spuchl i rozjechalo
sie wyrownanie wiersza do srodka,
- morfowanie ksztaltu nie przypominalo wzoru.
Research wzorcow z liderow (X/Twitter, YouTube, Reddit, App Store, Duolingo, apki
bankowe): gorne zakladki to wszedzie plaski, powsciagliwy pasek STALEJ WYSOKOSCI z
PRZESUWANYM wskaznikiem, nie morfowanym. Uzytkownik wybral podkreslenie (wzorzec
dominujacy na Androidzie).
TopTabsBar: aktywna zakladka = accentText + cienka kreska hugujaca tekst, przejezdza
w 220 ms ease-out (translateX + width z pomiarow onLayout). Wysokosc = wiersz tekstu,
wiec logo/zakladki/ikony znow sa w jednej linii. Naglowek wraca na theme.card (trik z
theme.bg byl tylko po to, zeby kropla byla widoczna).
Usuniete: LiquidTabs.tsx + lib/liquidTabPath.ts (caly aparat SVG-morph, martwy).
Zweryfikowane na emulatorze nagraniem klatka po klatce: kreska przejezdza Anime->Scenes
mijajac zakladki, kolory sie przelaczaja, laduje pod aktywna; wiersz wyrownany.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wzorowane na reelu _code_and_chill_ ("the bar melts around it"), ale
technika wzieta z jego drugiego zdania: "every socket is solved for
tangency, so the liquid never creases". Czyli sciezka LICZONA, nie
rozmywana.
Filtr "goo" (rozmycie + prog alfy) byl pierwszym pomyslem i zostal
ODRZUCONY po zajrzeniu w zrodlo react-native-svg. Filtry na Androidzie
dzialaja (GroupView extends RenderableView, ktory obsluguje mFilter), ale
RenderableView.java:376 przy KAZDYM rysowaniu robi Bitmap.createBitmap(
canvas.getWidth(), canvas.getHeight(), ARGB_8888), rysuje do niej element,
naklada programowy filtr i wrzuca wynik z powrotem. Przy animowanej kropli
to pelnoekranowa alokacja na klatke, w nagłowku KAZDEGO ekranu.
liquidTabPath: gniazdo to dwa luki styczne jednoczesnie do krawedzi paska
(srodek na y=top+k) i do kropli (odleglosc srodkow = r+k), stad
dx = sqrt((r+k)^2 - (top+k-cy)^2). Jedna <Path>, zero alokacji.
Weryfikacja liczbowa (nie wizualna): trzy niezmienniki stycznosci trzymaja
sie do 1e-9, piec przypadkow brzegowych OK. Test wylapal brakujacy warunek
`cy - r < top`: bez niego wzor ma rozwiazanie takze gdy kropla siedzi CALA
w pasku (top=26, cy=45, r=17, k=12 -> dx≈28,1) i powstawaloby gniazdo, w
ktore nie ma sie co wtopic.
Animacja: Animated.Value + listener przepisujacy cx do stanu, bo sciezka
to string i nie da sie jej podac Animated wprost (brak Reanimated w
projekcie). ~12 re-renderow SVG na 200 ms przejscia, poza tym komponent stoi.
useNativeDriver=false swiadomie: animujemy geometrie, nie transform.
Etykieta aktywnej zakladki siedzi na wypelnieniu akcentu — jasny tekst daje
tam 5,03:1, jedyny wariant tego koloru przechodzacy AA (patrz theme.ts).
Animacje SFM/Blender. Kanon nie istnieje tak samo jak przy hentai, wiec
kazda pozycja jest orphanem i to poprawne.
Playback: KVS z jawnymi flashvars (4 jakosci, bez license_code), ale
`v-acctoken` jest ZWIAZANY Z IP tego, kto pobral strone. Test krzyzowy:
URL wygenerowany przez proxy i pobrany z VPS = 403, ten sam URL z IP
generujacego = 206 ftypisom 70 MB. Po zdekodowaniu tokenu adresu nie
widac wprost (inaczej niz fpoxxx), wiec samo ogladanie nie wystarcza.
Dlatego bez mobile_direct_ok: telefon resolwuje sam, UPFRONT, istniejaca
sciezka isGetFileUrl()/resolveGetFilePage z SceneDetailScreen. Rozszerzam
getfileResolver o flashvars, bo dotad umial tylko <source> (hdporn.gg,
fullmovies.xxx). Zweryfikowane end-to-end z residential IP: strona ->
flashvars 720p -> 302 na boomio-cdn -> 206 ftypisom 39,8 MB.
Bramka dlugosci zestrojona z `min_ingest_duration_sec` (prod 180 s)
zamiast wlasnej liczby: pierwsza wersja miala 60 s i liczniki klamaly
(seen 57, new 31, skipped 26). Po zestrojeniu seen 20, new 20, skipped 0.
Krotka tresc wycina ~70% katalogu (mediana 68 s).
Tagi TYLKO z class=tag_item. `class=button` wyglada na wlasciwe, a jest
chmura "Top Tags": przy filmie o Lux siedzialy tam "cell (dbz)" i
"queen of pain (dota)". Autor animacji zapisany jako studio.
1745 wystapien w 291 plikach (backend, scripts, mobile). Dominowal jeden ksztalt,
' — ' laczace zdania, wiec poszedl przecinek; myslnik otwierajacy mysl zdjety;
zakresy 0–5 i A–Z na dywiz.
Podzial komentarz/kod robil skaner stanow, nie regex, zeby nie ruszyc myslnika
stojacego w stringu albo w wyrazeniu regularnym. Potwierdzone maszynowo: po wycieciu
komentarzy z wersji HEAD i z dysku obie sa identyczne we wszystkich 291 plikach,
czyli poza komentarzami nie zmienilo sie nic. 233 pliki .py parsuja sie bez bledu,
tsc ma te same 11 bledow w tych samych 3 plikach co przed zmiana.
Dwie rzeczy wymagaly poprawki po automacie: wyrownane listy definicji, gdzie myslnik
byl separatorem kolumny (37 miejsc, kolumna przywrocona), i trzy przeniesienia mysli
do nowej linii, gdzie zostawal wiszacy przecinek na poczatku (poprawione recznie).
Bundla nie republikuje: Hermes i tak nie niesie komentarzy, wiec dla uzytkownika
to zero zmiany.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
32 miejsca w 14 plikach: changelog, onboarding, ekrany Sites/Blacklist/Login/Player,
komunikaty bledow, placeholdery. Zamiast myslnika przecinek, dwukropek, kropka albo
nawias, zaleznie od tego co dane zdanie robi.
Zostawione swiadomie: pojedynczy glif U+2014 jako znacznik BRAKU WARTOSCI
(release_year ?? '—', nieocenione osie w SitesScreen, separator zakresu w filtrze
filmow). To element interfejsu, nie proza, a zwykly dywiz wyglada tam gorzej.
Zostawione tez komentarze w kodzie, w tym te wewnatrz INJECTED_JS.
Przy okazji: MigrationBanner dostal twardy warunek na runtime (needsManualMigration).
Wczesniej przed pokazaniem go nowej wersji chronilo TYLKO to, ze kanal OTA 1.2 byl
pusty. Pierwsza publikacja na 1.2 wyslalaby ten sam bundle i baner kazalby migrowac
z wersji, na ktora user wlasnie przeszedl.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Klucz podpisujacy stare buildy przepadl, wiec update in-place jest niemozliwy:
Android nadpisze aplikacje tylko buildem z tym samym podpisem. Odinstalowanie
czysci SecureStore, a w nim goon.device_id, czyli kod synchronizacji, bez ktorego
ulubione/postep zostaja na serwerze bez sposobu wskazania ich. Baner pokazuje kod
na wierzchu i prowadzi przez kroki (kopiuj kod, pobierz, odinstaluj, wklej kod).
expo-keep-awake: sprawdzenie stringow w dexach APK 0.2.1 potwierdzilo, ze modulu
ExpoKeepAwake tam nie ma (jest tylko interfejs z expo-modules-core), wiec ekranu
gasnacego w trakcie ogladania NIE dalo sie naprawic przez OTA. Teraz jawna
zaleznosc, autolinking ja widzi, wchodzi do APK 0.2.2 (runtime 1.2).
UWAGA przy publikacji banera na runtime 1.1: manifest bierze runtimeVersion z
argumentu --runtime, wiec 1.1 jest serwowane poprawnie mimo app.json=1.2, ale
extras nadal niosa app.json (APP_VERSION zaczyna raportowac 0.2.2). Dlatego
serwerowe /version trzeba bumpnac do 0.2.2 razem z wgraniem APK, inaczej stary
dialog Update available wyskoczy z mylacym Server 0.2.1 / App 0.2.2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zgloszenie b52470be ('Doply resolver chyba padl', err 'Source error None of the').
Diagnoza: protokol dziala (doply -> playmogo, pass_md5 obecny, CDN oddaje 206
video/mp4), ale strona jawnie mowi '<h1>Not Found</h1> video you are looking for is
not found' - wideo jest USUNIETE. Detekcja szukala doslownej frazy 'video not found',
ktora w tym wariancie NIE pada, wiec resolver uznawal usuniete wideo za sukces.
Playmogo mimo usuniecia dalej wystawia token pass_md5 i serwuje plik-zaslepke, stad
u usera 'Source error' zamiast czytelnego komunikatu.
Rozszerzone markery: 'is not found', 'no_video_' (obrazek-zaslepka
i.doodcdn.io/img/no_video_3.svg - najpewniejszy), 'video has been deleted',
'file was deleted'. Blad 'video_deleted' jest NIEretryowalny, wiec apka od razu
pokaze komunikat i pozwoli oznaczyc zrodlo jako martwe.
1) Duplikaty tagow wrocily, ale INNYM wzorcem niz w lipcu: alnum-klucz z natury nie
lapie liczby mnogiej ('blowjob' != 'blowjobs'). Audyt: 1944 pary, m.in.
Blowjob/Blowjobs (390728 vs 791), Teen/Teens (53096 vs 41294), Cumshot/Cumshots.
Scalone oba kierunki (kanoniczny = wiekszy scene_count): 1938 + 2063 tagow,
przeniesione 251251 + 26142 przypisan scen i 118522 + 392 filmow. Zostaje 581 par
(lancuchy i remisy) - celowo pominiete, wymagaja recznej decyzji.
2) Prewencja w _resolve_by_altkey: gdy alnum-klucz nie trafi, probujemy obu form
(+s / -s). Tylko proste 's' - form nieregularnych (panty/panties) NIE ruszamy, bo
zbyt latwo o falszywy zlew (glass/glasses, bra/bras). Zweryfikowane na prodzie:
Blowjobs->Blowjob, Teens->Teen, Cumshots->Cumshot, Lesbians->Lesbian.
3) UI: formatCount() skraca duze liczniki (181764 -> 181k, 1200 -> 1,2k) w tagach,
performerach, ulubionych i filtrach filmow - user-request, chipy przestaja puchnac.
1) Ekran gasl w trakcie ogladania (bug-reports fcbaa933 2026-07-18, c40fb79f
2026-07-23, dwoch userow). Android wygasza po timeoucie, bo dotyk nie pada, a samo
granie wideo nie trzyma wake-locka. expo-keep-awake NIE jest w top-level
node_modules (siedzi w node_modules/expo/node_modules), wiec metro nie rozwiaze
importu, ale natywny modul ExpoKeepAwake JEST w APK (expo go autolinkuje) -
siegamy po niego przez requireNativeModule z expo-modules-core (top-level).
Dzieki temu leci OTA, bez nowego APK. Wake-lock tylko gdy realnie gra (przy pauzie
ekran gasnie normalnie); WebView-player trzyma przez caly czas zycia ekranu, bo
tam nie ma sygnalu play/pause.
2) Miniaturki youporn (bug-report bebf8c92): youporn przeszedl dla czesci katalogu
na imgproxy z PODPISANYM URL-em (?hash=...&validto=<ts>), ktory WYGASA - zapisany
w bazie po czasie zwraca 410 (8666 z 62930 zrodel). Statyczne .../original/N.jpg
dzialaja bezterminowo. Nowy endpoint /proxy/youporn-thumb/<video_id> resolwuje
swiezy poster przy serwowaniu (jak sxyprn-thumb), z html.unescape bo og:image ma
& i psulo to podpis (403). Podmiana TYLKO dla podpisanych - statycznych nie
ruszamy, zero dodatkowych fetchy.
Zweryfikowane: 8/8 wczesniej-410 teraz 200 (57-142KB), statyczne bez regresji.
REGRESJA z 3355372 (diagnostyka sxyprn): wzorzec challenge sprawdzany PRZED
data-vnfo, a 'challenge-platform' to skrypt Cloudflare JSD wstrzykiwany do KAŻDEJ
strony za CF (bez challenge) → false positive na DOBRYCH stronach → return [] →
phone-resolve padał wszystkim → fallback backend/proxy → CAŁE wideo przez VPS
(potwierdzone: 49MB/chunk na trafficdeposit) + WebView z reklamami.
Fix: najpierw prawdziwy sygnał (data-vnfo), diagnoza powodu DOPIERO gdy go brak.
'challenge-platform' usunięty z listy (za szeroki). Diagnostyka znów czysto
obserwacyjna — nie przerywa resolve na heurystyce tekstowej.
Wykryte przez samą diagnostykę: sxyprn=challenge_197503b / challenge_75831b
(197KB/75KB = pełne strony, nie interstitiale).
'sxyprn ładuje reklamy' jest niereprodukowalny z naszych IP (strona zwraca
data-vnfo → resolver OK) → podejrzenie region/IP-gate u usera. resolveSxyprnPage
zapamiętuje powód ostatniego resolve (http_<status> / challenge_<len> /
no_vnfo_<len> / post_not_found / vnfo_parse_err / ok_N) i BugReportFAB dokleja go
jako sxyprn=<reason> do auto-contextu. Przy kolejnym zgłoszeniu zobaczymy CO sxyprn
realnie zwrócił temu userowi, bez pytania. Non-behavioral (nie rusza resolve).
Sentry ddec9d6b (fatal, Android): RuntimeException 'Adding window failed' /
DeadObjectException gdy RN Alert (natywny DialogFragment) pokazuje się przez
WindowManager.addView na oknie activity które leci w tło / jest ubijane (martwy
binder). Trigger: alert z async handlera (np. 'Update available' na boocie)
odpalony gdy user zbackgroundował apkę.
Fix: globalny patch Alert.alert (src/lib/alertGuard, side-effect import w App.tsx)
— no-op gdy AppState.currentState !== 'active'. Obejmuje wszystkie ~56 call-site
bez owijania każdego. Transientowe alerty w tle i tak bez sensu; update wróci przy
następnym foregroundzie.
Playback reports ("doesn't start", "audio lags") came in with no way to know the
device or which source/server was used, forcing a follow-up question. Now the bug
report auto-context also carries:
- device = phone model / Android version (Platform.constants, zero-dep)
- play = last-played origin/host, mode, position, and last player error
New in-memory lastPlayback store written by PlayerScreen on source/status change, read
by BugReportFAB when composing a report. Reset on app restart (current session only).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hqfap.com's Cloudflare blocks the VPS (Hetzner) IP — the server times out fetching
the page (0 bytes), so backend resolve returns None (503) or a stale/IP-bound URL that
fails on the phone (35 player_errors / 3 days, the top error source). But the page loads
fine from residential IPs (200, 0.7s) and its okcdn/vstor/cdnde mp4 is portable cross-IP
(srcIp not enforced, verified 206 from residential).
Fix: phone-side resolve, same pattern as sxyprn/eporner/fpoxxx. New hqfapResolver.ts
(fetch page on device -> JSON-LD contentUrl -> direct mp4, mirror of hqfap.py) wired into
the proactive phone-resolve chain in SceneDetail and the on-error re-resolve in
PlayerScreen; hqfapcom added to PHONE_RESOLVE_ORIGINS. Backend hqfap.extract stays as a
last-resort fallback.
Note: sxyprn/eporner were checked and are NOT broken (95-96% play success; their "gone"
events are real deletions), so no change there.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From user bug reports (8f8c10c0 freeomovie, c156e4b7/8db71220 watchporn scene):
freeomovie (app/connectors/freeomovie.py):
- Parse the detail-page poster (<img class="rmbd">); RawMovie was built with no
poster_url, so title-only orphans (no canonical match) rendered blank cards. Re-ingest
backfilled existing blanks 11 -> 1.
- _host_label now returns the registrable domain (parts[-2]) not the subdomain, so
video.player4me.xyz labels as 'player4me' (was garbage 'freeomovie:video').
- Skip player4me (JS-SPA, no known resolver); pruned 28 dead freeomovie:video sources.
- myvidplay.com added to mobile DOOD_HOSTS (doodstream.ts): it 301s to playmogo.com
(doodcdn clone), so it now routes to the native dood resolver instead of a dead WebView.
watchporn: the reported 4 duplicate/broken quality rows were the API process serving a
STALE extractor registry (never restarted after the re-enable deploy) -> _embed_iframe
scraped 2 tokenless get_file (403) + 2 preview trailers (404). The running api now serves
the native extractor (2 clean playing links, verified). Hardened _embed_iframe to skip
preview/videos_screenshots clips and tokenless get_file so this class can't recur.
Also: DonateScreen em-dashes -> commas/semicolon (no-em-dash rule).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up cleanup from the ultra-review (behaviour-preserving, verified equivalent):
- scenes.py: extract live_playback_exists / blacklist_clauses / stub_exclusion_clause;
list_scenes and favorites now share ONE definition of "visible scene" (was a hand-kept
copy in favorites -> the count-vs-list drift class). Verified identical: helper vs old
inline both count 1,853,327 scenes.
- favorites.py: replace the two copy-pasted count blocks with one _new_counts(kind=...)
that counts in SQL (count(*) FILTER over the windowed subquery) instead of streaming up
to N*200 rows to Python; joins the favorite table for per-row last_seen. Deployed
_new_counts verified == hand SQL (studios 364). Dropped now-unused imports.
- mobile: extract lib/newScenes.ts (isNewScene / sortNewFirst); SceneTile + Performer/
StudioScenes use it (was triplicated, already drifted once in the deleted FavoriteSceneRow).
- SceneDetail: fold tube:pornxpph into the phoneResolver map (was a verbatim-duplicated block).
- deep_crawl: persist the cursor after every completed page (was once at run end), so a
mid-page hard-kill past the soft budget can't lose progress (GOON-V hardening).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A 7-slide carousel shown once on first launch:
- the three tabs (Scenes/Movies/Sites)
- search, filters, saved searches, Performers/Tags/Favorites
- long-press actions (hide/duplicate a scene, remove a wrong performer, link diagnostics)
- player gestures (tap controls, double-tap ±15s, swipe to scrub, unmute)
- favorites, Hidden content, PIN lock, the ? report button, Sites ★ ratings
Gated by a SecureStore flag; replayable from Settings ⚙ → Replay tutorial (via a
tiny onboarding bus). Suppresses the What's-new popup for brand-new users (the tour
covers it) and marks the changelog seen on finish.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(1) Scroll jank/device load on long scene lists (report 5b7ca1e1): SceneTile is now React.memo'd so typing in search no longer re-renders every mounted tile, and sceneGridProps bounds the render window (windowSize 7 etc.) — required because removeClippedSubviews stays false to avoid thumbnail blanking. Applies to all scene grids. (2) fpoxxx played an ad instead of the video via the WebView fallback (reports f79beefb/cfa207c7). fpoxxx is KVS with an IP-bound + session-bound get_file token (cross-IP 403 confirmed), so it must resolve phone-side: new fpoxxxResolver fetches the page + follows get_file on the device (KVS real_url port for the function/0 case), wired into SceneDetailScreen like sxyprn/eporner. Verified from a residential IP: get_file -> CDN returns 206 video/mp4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These CDNs bind their signed video URL to the IP that fetched the page, so a
server-side resolve hands the phone a URL bound to the server IP -- the device then
gets a placeholder/403 and falls back through the proxy, streaming the whole video
through the server. Resolve on the device instead (token binds to the phone IP) so
playback goes direct with zero proxy bandwidth.
Ports of the existing backend extractors:
- sxyprnResolver.ts: data-vnfo + boo/ssut51 transform
- epornerResolver.ts: vid+hash -> /xhr/video mp4 sources
- voeResolver.ts: mirror redirect + 7-step payload decoder
Wired into SceneDetailScreen.onPress (sxyprn/eporner) and MovieDetailScreen.playVoe (voe).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mypornerleak embeds luluvids.top (+ cdnstream.top/cdnvids.top) which are
luluvid/streamwish forks on new TLDs, all confirmed P.A.C.K.E.R.-JWPlayer. They
were missing from PACKER_HOSTS, so isPackerHoster() returned false → the phone-
side packer resolver never ran → WebView fallback landed on luluvids.top's
"disable Adblock and enable popup" wall (bug-report 2026-06-07, scene 75aa3316).
filemoon variant (bysezoxexe.com) was already covered.
Verified on emulator (live OTA): mypornerleak source → luluvids.top resolves
phone-side → native ExoPlayer PLAYING (position advancing), no adblock wall.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pornxp.ph serves direct <source> mp4 (360/720/1080p) on st.pornxp.sh whose path
token is IP-bound to whoever fetched the PAGE (verified 2026-06-07: VPS-resolved
URL → 403 cross-IP). Backend resolve was therefore impossible, so pornxpph fell
to the WebView fallback which black-screened (bug-report fd06cd86).
Fix: resolve on-device (same pattern as getfileResolver/doodstream) — the phone
fetches the page, so tokens bind to the phone IP and play natively. New
pornxpResolver.ts extracts the <source> mp4s into multi-quality StreamLinks;
SceneDetail short-circuits tube:pornxpph to it before backend resolve, feeding
the existing quality-picker + native player.
Verified on emulator (live OTA): pornxpph scene → quality picker (1080/720/360)
→ native playback PLAYING (no WebView, no ads, no black screen).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
porndish scenes resolve only to playmogo.com embeds, which are DoodStream clones
(doodcdn.io + pass_md5 + Cloudflare Turnstile). The mobile resolver already
supported playmogo, but DoodStream is flaky from a single shot: the embed is
sometimes Turnstile-gated (no pass_md5), and the pass_md5 endpoint intermittently
returns the literal string "RELOAD" (stale/consumed token) instead of a base URL.
The old code built "RELOAD<suffix>?token=..." -> ExoPlayer "no extractors" ->
WebView -> loading forever (bug 62e78c9a).
Wrap resolveDoodStream in a 3-attempt retry that re-fetches the embed (fresh
token) on retryable failures (gate / RELOAD / empty / stale token), and reject a
non-http pass_md5 body as retryable instead of building a garbage URL. Verified
cross-IP that the pass_md5 -> base -> final flow yields 206 video/mp4 when not
gated; real carrier IPs are gated far less than the test proxy. Strict
improvement: worst case is the existing WebView fallback, best case native play.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Device logs (not assumptions) pinned the real cause of the hdporngg/fullmovies
flicker: the backend returns a get_file URL, but get_file is bound to the IP that
loaded the *page*. The backend (VPS) loads the page, so the get_file is VPS-bound;
the phone fetching that get_file gets HTTP 410 -> ExoPlayer errors -> falls back to
the proxy via nav.replace (the "flicker"), and ends up streaming through the proxy.
(My earlier "stateless/portable" test was from the VPS — same IP as the page load —
so it wrongly showed 206.)
Fix: when the direct_url is a get_file, the phone re-fetches the *page* itself
(resolveGetFilePage on source.page_url) so the get_file is bound to the phone IP,
picks the requested quality skipping 4K (dead on fpvcdn), follows to the CDN, and
hands ExoPlayer a working URL. On failure it keeps the original (proxy fallback).
Verified on device: [getfile] page-resolve -> get_file 206 -> ExoPlayer PLAYING,
position advancing, no error/proxy/flicker, real video frame rendered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
hdporn.gg/fullmovies.xxx return an unresolved get_file direct_url that 302-redirects
to fpvcdn.com with the requester IP baked in. The backend can't resolve it (would
bind fpvcdn to the VPS IP -> mobile 403), so the phone must follow the redirect. But
ExoPlayer errors on that cross-domain get_file->fpvcdn redirect (drops Referer / won't
complete it) -> the native player falls back to the proxy via nav.replace, which the
user sees as a screen-reload "flicker" before playback (and means it's actually playing
through the VPS proxy, not direct).
Fix: resolve the get_file 302 in JS on the phone (so fpvcdn binds to the phone IP)
before navigating to the player, and hand ExoPlayer the final fpvcdn URL directly —
no redirect, no error, no flicker, no proxy. Uses the same redirect:'manual' +
Location-header pattern as the doodstream resolver (works on RN Android). On resolve
failure it keeps the original get_file URL (current behaviour with proxy fallback).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tłumaczenie wszystkich user-facing stringów PL→EN (bug-report 2026-05-31
"dalej wszystko po polsku"). Alerty, przyciski, placeholdery, labelki w 12
ekranach/komponentach: BugReportFAB, AppLock(Screen/Settings/PinEntry),
applock biometric prompts, doodstream error msgs, MovieDetail, PlaybackQuality,
Player, SceneDetail, ScenesFilter, SiteScenes. Komentarze w kodzie zostają PL.
Zmiany były WIP drugiego okna (uncommitted); wjechały do bundla 0.2.1 przy
buildzie (były w working tree) — apka zainstalowana już ma EN. Ten commit
utrwala je w gicie żeby nie zginęły. Czysto stringi, zero zmian logiki.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
filemoon (+ mirrory kerapoxy/lvturbo/emturbovid/bysezoxexe/bysezejataos)
nie umarł — ~2026-05 zrobił rebrand na Vite SPA "Byse Frontend". Stary
P.A.C.K.E.R.-JWPlayer embed zniknął, więc backend uznał go za martwego i
wpisał na DEAD_HOSTER_RE. RE bundla index-ChwZgmXV.js (2026-05-22):
POST /api/videos/<code>/embed/playback body {"fingerprint":{}}
→ {"playback":{"key_parts":[..],"iv":..,"payload":..}}
→ key=concat(b64url(key_parts)); AES-256-GCM(key,iv,payload) → JSON
→ sources[*].url = HLS master.m3u8
Browser-attestation jest opcjonalny — pusty fingerprint wystarcza.
Stream URL jest IP-bound (token wiąże się z IP requestera), więc resolve
musi iść z urządzenia użytkownika (jak doodstream.ts / packerHoster.ts).
- mobile/src/lib/aesGcm.ts — pure-JS AES-256-GCM decrypt (RN/Hermes nie
ma Web Crypto); S-box liczony z GF(2^8), GHASH weryfikuje tag.
Zweryfikowane przeciw cryptography (Python) na 2 payloadach.
- mobile/src/lib/filemoonHoster.ts — resolver: POST playback → decrypt →
pick best source. E2E test: filemoon.to/e + /d + bysezoxexe.com mirror.
- PlayerScreen: filemoon w resolve useEffect obok doodstream/packer.
- backend: filemoon poza DEAD_HOSTER_RE; hoster.py early-return → przelot
jako type='hoster' do mobile resolvera (server-side resolve bezcelowy,
bo URL IP-bound do VPS).
- direct_scrapers: poprawiony błędny komentarz "filemoon shutdown".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.