Commit graph

56 commits

Author SHA1 Message Date
goon-foss
2a2c0d7e9d fix(api): bez planow ogolnych i szybkie listy serwisow, ktore stoja
Some checks failed
Backend tests / test (push) Has been cancelled
- db: prepare_threshold=None. psycopg przygotowywal zapytanie po 5 wykonaniach na
  polaczeniu, a Postgres przechodzil potem na plan ogolny. Lista duzego tagu na takim
  planie: "pov" 44 s zamiast 0,46 s, "masturbation" 46 s zamiast 0,18 s. Stad listy
  ladowaly sie raz szybko, raz minute, zaleznie od polaczenia z puli (tez GOON-73/75).
- Lista jednego serwisu bez frazy: przejscie od najnowszych z limitem 1 s, a po nim
  start od scen serwisu (do 150 tys.). Serwis, ktory stoi, mial sceny gleboko:
  superporn 72-92 s -> 1,7 s, yespornvip 15 s -> 1,5 s, hentaiplay 9 s -> 0,06 s.

Porownanie ze starym kodem na 88 listach serwisow: wyniki identyczne przy
zapytaniach puszczonych po sobie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 09:45:05 +02:00
goon-foss
3349700f72 fix(api): szukanie w serwisie i sortowanie po studiu z rzadka fraza
Some checks are pending
Backend tests / test (push) Waiting to run
- Szukanie w jednym serwisie do 60 tys. scen zaczyna od jego scen (scenes.id = ANY),
  gdy fraza jest popularna albo bez trigramu: "sel" w hqporner 3,4 s -> 0,1 s, "brat"
  w fullmoviesxxx 2,9 s -> 0,15 s. Rzadkie frazy dalej trigramami (GOON-75).
- Poprawka regresji z c08abf6: fraza bez trigramu przy zawezeniu po 3 s oddawala
  pusta liste ("ai" w fullmoviesxxx: 0 wynikow). Teraz od serwisu albo limit 10 s.
- sort=studio z rzadka fraza: JOIN ze studiami i sort samych trafien zamiast
  przejscia po 33 tys. studiow ("sunny leone" 928 s -> 0,26 s, "savanna" 20 s -> 0,17 s).

Porownanie ze starym kodem na 100 wyszukiwaniach w serwisach i 12 z sortem po studiu:
wyniki identyczne poza naprawionymi pustymi listami.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 17:13:39 +02:00
goon-foss
0da36d5c1e perf(tags): warianty pisowni tagow jednym zapytaniem
Some checks are pending
Backend tests / test (push) Waiting to run
enrich-tags wolal resolve_tag dla kazdej nazwy spoza slugow, a ten szukal po
alnum-kluczu i liczbie mnogiej osobnymi zapytaniami (N+1, GOON-5F). Nowe
resolve_tags_by_altkey robi to jednym IN po indeksie ix_tags_name_altkey;
_resolve_by_altkey dla jednej nazwy korzysta z niego (1 zapytanie zamiast 2).
Na 603 nazwach z produkcji wyniki identyczne, 0,77 s -> 0,03 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 13:24:18 +02:00
goon-foss
45ee7a81e1 fix(api): szybkie szukanie po tytule dla popularnych i krotkich fraz
Some checks are pending
Backend tests / test (push) Waiting to run
Planer szacowal kazda fraze na 427 trafien (histogram 100 probek, dolny limit), wiec
zawsze szedl indeksem trigramow i sortowal wszystkie trafienia: "vo" 44 s (dwie
litery nie maja trigramu, indeks oddaje 4,3 mln wierszy), "mis" 6,5 s, "free" 1,9 s
(GOON-73/75).

- 0046: histogram title_normalized 1000 probek, szacunki bliskie prawdy.
- list_scenes: przy q i sorcie po dacie najpierw przejscie po indeksie sortu z
  limitem 300 ms (bez bitmap, w SAVEPOINT wycofywanym zawsze), gdy probka tytulow
  z pg_stats mowi, ze wystarczy przejsc do 30 tys. scen; inaczej zwykly plan.
  Frazy bez trigramu zawsze przejsciem (limit 3 s, potem pusto).

115 prawdziwych wyszukiwan z logow: wyniki identyczne, najgorszy 44 s -> 0,8 s,
srednie frazy dotykaja 3-7x mniej stron ("bad" 122 tys. -> 16 tys.).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:00:11 +02:00
goon-foss
0edc85c05f feat(api): glowny feed bez archiwum i z limitem jednego zrodla na strone
"Newest ingest" bez filtrow pomija sceny z dociagania archiwum (backfill): deep-crawl
tubepornclassic dokladal 7,9 tys. scen z lat 2016-2018 dziennie, a xhamster/xnxx/xvideos/
sxyprn kolejne 3,3 tys., wszystkie na szczycie listy. Po odcieciu eporner (2 tys. anonimowych
uploadow dziennie) mial 121 ze 150 pozycji, wiec na pierwszych 8 stronach jedno zrodlo zajmuje
najwyzej 1/3 strony, dopoki sa inne; nadmiar zjezdza nizej, nic nie ginie. Pierwsze 3 strony:
eporner 51, xvideos 24, xnxx 20, youporn 20, pornxpph 15, fullporner 12.

Z filtrami i w sekcjach JAV/Anime bez zmian.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:00:11 +02:00
goon-foss
73853eb9ed perf(api): kilka tagow od najnowszych idzie po indeksie najrzadszego z nich
Z dwoma tagami (rough-sex + anal) lista szla stara droga po calym katalogu, 7,4 s na zimnym
cache. Teraz dolaczamy najrzadszy tag po ix_scene_tags_tag_sort, a reszta zostaje jako
EXISTS: 0,2 s. Dolaczona tabela ma alias, inaczej EXISTS po scene_tags korelowal sie z nia.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:00:11 +02:00
goon-foss
7f2fdfaed0 perf(api): lista scen jednego tagu od najnowszych po indeksie na scene_tags (GOON-64)
Ekran tagu (jeden tag, sort=release_date, 200 scen) sortowal po dacie z `scenes`, a filtrowal
po `scene_tags`. Postgres szedl od najnowszej sceny calego katalogu i kazda sprawdzal; przy
tagach rzedu 40-60 tys. scen (`rough-sex`) to 46 tys. scen na strone, 7-15 s na zimnym cache.
Dwie przebudowy samego zapytania nie pomogly (2x albo 30 s na duzych tagach), cache tez nie,
bo wejscia rozkladaja sie na setki tagow.

`scene_tags.sort_date` (0042) trzyma ta sama date co `_effective_date_desc()`, pilnowana przez
wyzwalacze w bazie, indeks (tag_id, sort_date DESC, scene_id DESC) w 0043. Wypelnianie 23 mln
wierszy: scripts/backfill_scene_tags_sort_date.py, partiami z pauza.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:00:11 +02:00
goon-foss
c8035c68fa fix: losowanie w obrebie wynikow filtra, widoczne pobieranie aktualizacji
Zgloszenia z apki:
- „random filter on and searching titles shows few results": przy sort=random braliśmy
  okno 2000 kolejnych id (0,05% katalogu) i dopiero na nim nakladalismy filtry, wiec
  szukanie w losowej kolejnosci trafialo na garstke scen. Teraz przy aktywnym filtrze
  (szukanie, tag, obsada, studio, strona, jakosc, data, dlugosc) losujemy WYNIKI:
  md5(id + seed) nad pula do 5000 wynikow filtra. Okno po PK zostaje tylko dla listy
  bez filtra, gdzie chronilo przed skanem 2,3 mln wierszy.
- „clicking install does nothing": pobieranie 87 MB szlo bez zadnego sladu w UI. Teraz
  nakladka z kreceleem na czas pobierania, „Download" obok „Install" w monicie i link
  do pliku w komunikacie bledu (na repo git APK nie ma, user szukal go tam).

Sprawdzone: szukanie „massage" z sort=random daje pelne strony (50 + kolejna), ten sam
seed powtarza kolejnosc, strony sie nie nakladaja, inny seed inna kolejnosc; czasy 120-800
ms (tag anal 1,1 s), lista bez filtra bez zmian. Na emulatorze pelna sciezka aktualizacji
z 0.4.0: monit z trzema przyciskami, Install, pobieranie, systemowy dialog, versionCode 15.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:00:10 +02:00
goon-foss
2cdc3d69da perf(api): tanszy odsiew stub-scen i zwarcie sprzecznego filtra playbacku
Some checks failed
Backend tests / test (push) Has been cancelled
Trzy zmiany wokol listy scen z filtrem originu, gdzie kandydatow sa dziesiatki tysiecy.

1. Kolejnosc czlonow OR w stub_exclusion_clause. Postgres liczy OR po kolei i przerywa na
pierwszym trafieniu, a sam czlonow nie przestawia. "Ma kanon" (jedyny czlon z JOIN-em do
sources) stal przed tanszym "ma performera", wiec placilismy za niego takze za sceny,
ktore performera mialy. Po zamianie ten czlon liczy sie 5146 razy zamiast 16 191.

2. Indeks scene_external_refs (scene_id, source_id), migracja 0041. Podzapytanie "ma
kanon" dociagalo source_id z heapu, bo stary indeks zna tylko scene_id. Teraz idzie
Index Only Scanem: Heap Fetches 16 191 -> 1241.

Razem: sort=title + origin=hqporner, strona 5 czyta 251 tys. blokow zamiast 294 tys.,
a na rozgrzanym cache trwa 305 ms zamiast 371 ms (mediana z 3 rund z rotowana
kolejnoscia). Zysk jest skromny. Pierwsze wykonanie na zimnym cache trwa 3-5 s i tak
zostaje, bo trzeba dotknac ~2 GB; sprawdzilem tez wariant z materializacja scen originu
i CTE, czyta wiecej blokow (302 tys.), wiec obecny plan zostaje.

3. has_playback=false RAZEM z filtrem originu to warunek sprzeczny, bo origin wymaga
zrodla z dead_at IS NULL. Wynik jest pusty z definicji, ale Postgres przemielal caly
katalog, zeby to stwierdzic: 148 s. Teraz zwarte na false(), 4,8 ms, dalej zero wynikow.
Przy has_playback=true z originem pomijamy z kolei nadmiarowe EXISTS o zywym playbacku,
bo origin sam go wymaga, a kosztowalo sonde na kazdym kandydacie.

Wyniki bez zmian: strony 1-5 dla origin=hqporner i dla glownego feedu porownane pozycja
po pozycji ze stara wersja daja zero roznic, a sam predykat stub zgadza sie na probce
200 tys. scen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 11:11:17 +02:00
goon-foss
b6a075b28a perf(db): kasuje ix_scenes_title_normalized, zbedny prefiks indeksu z 0039
ix_scenes_title_created_id_desc zaczyna sie od tej samej kolumny, wiec obsluguje wszystko,
co obslugiwal stary indeks: lookupy `title_normalized = X` z resolvera i skan zakresu przy
sortowaniu po tytule. Trzymanie obu to 367 MB na dysku i jeden indeks wiecej do utrzymania
przy kazdym zapisie do scenes, a ingest pisze tam caly czas.

Sprawdzone na produkcji przez BEGIN; DROP INDEX; EXPLAIN ANALYZE; ROLLBACK, zeby zobaczyc
wybor planera bez starego indeksu, a potem jeszcze raz po samym dropie:

  exact title lookup        1,76 ms -> 0,07 ms, Index Scan po nowym indeksie
  OR studio + exact title   ten sam ksztalt planu (BitmapOr), 3,7 ms
  OR data + exact title     Seq Scan przy szerokim oknie dat, ale to decyzja o release_date:
                            z enable_seqscan=off BitmapOr siega po nowy indeks
  trgm ILIKE                bez zmian, swoj indeks GIN

W wariancie z OR planer i tak siegal juz po nowy indeks, jeszcze zanim stary znikl.
Indeksy scenes: 3544 MB -> 3177 MB. Listy scen po dropie bez zmian (tytul 22-54 ms,
studio 38-73 ms na stronach 1-5).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 10:47:24 +02:00
goon-foss
470beb4aa6 perf(api): sortowanie scen po studiu i tytule bez sortowania calego katalogu
Obie listy chodzily na produkcji w dziesiatkach sekund, bo klucz sortu nie mial nic
wspolnego z filtrami widocznosci (zywy playback, odsiew wertykalow, odsiew stubow).

sort=studio: nazwa studia siedzi w dolaczonej tabeli, wiec zaden indeks scen jej nie
zna i planer materializowal caly przefiltrowany katalog (ponad 2 mln wierszy), po czym
sortowal go w calosci. Odwracamy zapytanie: idziemy po studiach w kolejnosci nazwy i dla
kazdego dobieramy jego sceny przez LATERAL po ix_scenes_studio_effective_date_desc.
Zewnetrzny skan jest posortowany, wiec LIMIT urywa go po kilkunastu studiach. Sceny bez
studia dobiera osobny ogon, tak jak wczesniej robilo NULLS LAST. Przy filtrze po studiach
zawezamy tez sam skan studiow, inaczej szedlby przez wszystkie 33 tys.

sort=title: nowy indeks na cale ORDER BY (title_normalized, created_at DESC, id DESC).
Sam title_normalized zostawial Incremental Sort, a ten przed oddaniem pierwszego wiersza
musi zebrac caly kubelek scen o tym samym tytule.

Pomiary na produkcji (2026-09-16, ten sam zestaw filtrow, po rozgrzewce):

  studio strona 1            483 s  ->   41 ms
  studio strona 5            ponad 5 min ->   38 ms
  studio + tag blowjob        71 s  ->  226 ms
  studio + min_duration       48 s  ->   42 ms
  studio + studio_slugs      255 ms ->   44 ms
  tytul strona 1             709 ms ->   12 ms  (81 wpisow indeksu zamiast 3064)
  tytul + tag blowjob        ponad 90 s -> 1,5 s

Wynik jest identyczny co do wiersza: pierwsze 251 pozycji pelnego katalogu porownane
pozycja po pozycji ze starym zapytaniem daje zero roznic, tak samo dwa okna alfabetyczne
studiow (w tym Brazzers, zeby sprawdzic wewnetrzny LIMIT).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 10:37:26 +02:00
goon-foss
37e6b7f03c perf(api): liczniki +N ulubionych z cache i indeksem, podobne sceny z najrzadszych tagow
- /favorites i /favorites/studios: liczniki w cache procesu (swiezy 5 min, potem
  odswiezanie w tle), mark_seen zeruje od razu; parametr counts=false dla ekranow,
  ktore chca tylko stan gwiazdki
- licznik studiow idzie indeksem ix_scenes_studio_effective_date_desc (migracja 0038)
  i konczy po 200 scenach na studio: 10-25 s -> kilkanascie ms
- okno licznika sortuje tak jak lista (data premiery albo dzien dodania), wczesniej
  rozjechane od 0033
- similar_scenes: pula z 4 najrzadszych tagow zamiast grupowania po wszystkich
  (do 5 s -> ponizej 0,4 s)
- enrich-tags: tagi jednym zapytaniem po slugach i jeden INSERT
- /scene-favorites: serializer wsadowy

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 15:05:43 +02:00
goon-foss
747d0cec3b perf(api): historia ogladania wsadowo, wyszukiwanie w obrebie strony po rownosci originu
- /watch/recent: _build_scenes_out_batch(light) zamiast 5-7 zapytan na scene (N+1 w Sentry)
- /scenes?origin=tube❌ rownosc zamiast ILIKE '%...%', 4,6 s -> 1,1 s na hdporngg z q

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 14:06:01 +02:00
goon-foss
be6c18b3b4 feat(scenes): sortowanie losowe (seed, okno po PK) i po dlugosci (indeks partial), zgloszenie ae6a045b
- random: md5(seed) -> uuid startu, okno 2000 id (8000 dla wertykalow) po pk_scenes,
  filtry na oknie; bez okna planer robil Merge Anti Join po 2,3 mln wierszy (7,6 s)
- duration_desc/asc: bez NULL, do 10 h, indeks ix_scenes_duration (migracja 0035)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-14 15:13:13 +02:00
goon-foss
fa7fb668b6 fix(sites): sortuj po dacie premiery z fallbackiem na date dodania + napraw miniaturki
Zgloszenie 1df50de3 ("hqporner: brak nowych scen ze strony, brak miniaturek").
Oba objawy mialy inne przyczyny i oba sie potwierdzily.

1. Kolejnosc. Sceny byly i ingest dzialal (91 nowych w 7 dni, ostatnia tego dnia),
ale tuby daty premiery zwykle NIE podaja: xhamster 99,2% scen bez daty, xnxx 97,7%,
porntrex 95,8%, hqporner 93,5%, sxyprn 85,7%. Przy release_date DESC NULLS LAST
wszystkie bezdatowe ladowaly ZA datowanymi, wiec 82 z tych 91 scen siedzialy za
3,4 tys. starszych pozycji. Teraz sortujemy po dacie premiery, a gdy jej brak, po
dniu trafienia do katalogu; sceny z prawdziwa data (kanon) bez zmian. Wyrazenie jest
immutable (jawne AT TIME ZONE 'UTC'), wiec wchodzi do indeksu, migracja 0033.

2. Miniaturki. Kafelki bez obrazka byly podpisane FULLPORNER, nie HQPORNER. Picker
bral pierwsze zrodlo alfabetycznie, a tube:fullporner < tube:hqpornercom. Dwie wady
naraz: czesc miniaturek fullporner to 153-bajtowa atrapa oddawana z kodem 200, a
czesc wskazuje CDN hqporner, ktory odrzuca Referer fullporner.com (ten sam plik:
Referer fullporner.com 403, wlasny host obrazka 200, 30 kB). Wiec: przegladany
serwis wygrywa wybor miniaturki, a proxy obrazkow po 403 ponawia raz z wlasnym
originem obrazka.

Pomiar na 40 kafelkach hqporner: przed 38 ladowalo i 1 nie, po 39 ladowalo i 0 nie,
wszystkie podpisane wlasciwym serwisem. Glowny feed bez zmian.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:25:53 +02:00
goon-foss
34276afb71 fix(scenes): min_duration_sec=0 znaczy bez progu, nie duration_sec >= 0
Zgloszenie 2ec95be5 ("Hentaimama dalej 0 scen") bylo trafne, mimo ze drill-downy
juz wysylaly min_duration_sec=0. Backend tlumaczyl to na duration_sec >= 0, a w SQL
NULL nie przechodzi ZADNEGO porownania, wiec scena o nieznanej dlugosci wypadala
dokladnie tak samo jak dziesieciosekundowy smiec. Serwisy, ktore dlugosci nie podaja
w ogole (hentaimama, javflix, supjav, hentaigasm, hentaiplay, xanimeporn to 100%
NULL), oddawaly PUSTA liste.

Teraz 0 znaczy brak progu i filtr w ogole sie nie naklada, a prog > 0 dalej odcina
NULL-e swiadomie, bo tam chodzi o smieci w glownym feedzie. To samo rozroznienie w
scieszce "pure default", zeby jawne 0 nie gubilo taniego liczenia.

Zweryfikowane na produkcji: hentaimama, javflix, supjav, hentaigasm i xanimeporn
oddaja sceny, glowny feed z progiem 60 bez zmian.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:03:51 +02:00
goon-foss
bbc2059728 feat(ui): badge jakosci i nazwa serwisu na kafelku sceny
Kierunek A z makiet (miniaturki niosa ekran, chrome cichy) + jeden element z C:
jakosc i zrodlo widoczne wprost na kafelku.

Backend musial to najpierw WYSTAWIC, bo lista jedzie w trybie `light`, ktory okrawa
playback_sources do samej miniatury:
  - `SceneOut.max_quality_p` (kolumna z migracji 0029, dotad uzywana tylko jako filtr),
  - slim wpis niesie PRAWDZIWY `origin` zrodla wybranej miniatury zamiast "".

Kafelek: badge jakosci w LEWYM dolnym rogu (czas zostaje w prawym, ten sam ciezar
wizualny), a w linii meta studio + przygaszona nazwa serwisu. Badge pokazujemy TYLKO
gdy jakosc jest znana (6,5% scen), bo ma byc faktem, nie zgadywaniem.

Zweryfikowane na emulatorze na PRAWDZIWYCH danych: zeby to bylo mozliwe, zbudowalem
build debugowy podpisany kluczem release (przechodzi kontrole X-App-Signature, a jako
debug nie ma FLAG_SECURE, wiec da sie zrobic zrzut). Widac "4K" i "1280p" w rogu oraz
"ONLY FANS  SXYPRN" w linii meta. Tymczasowa zmiana podpisu w build.gradle cofnieta.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:41:16 +02:00
goon-foss
0fed952c5c feat(filtr): minimalna jakosc w filtrze scen (720p/1080p/4K)
Prosba uzytkownika (zgloszenie 17376139). Backend mial juz parametr `min_quality_p`,
ale w praktyce byl NIEUZYWALNY i to zajelo wiekszosc pracy.

Wydajnosc, mierzona na produkcji przez EXPLAIN ANALYZE, prog 1080p:

  EXISTS na playback_sources (stan zastany)   32 s   3 556 612 odwiedzonych scen
  + indeks funkcyjny per-scena                24 s
  JOIN z DISTINCT po jakosci                   1,1 s W IZOLACJI
  ten sam JOIN + reszta filtrow feedu         52 s   planer wraca do skanu po dacie
  kolumna scenes.max_quality_p (0029)          2,0-2,6 s
  + indeksy czesciowe pod progi (0030)         0,09-0,11 s

Wniosek, ktory kosztowal najwiecej: ksztaltem zapytania sie tego NIE dalo zalatwic.
Koszt to LICZBA odwiedzonych scen, nie pojedynczy test, a przy `ORDER BY created_at
DESC LIMIT 24` planer i tak wracal do przechodzenia scen po dacie, gdy tylko doszly
pozostale filtry (bramka wertykalow, heurystyka szkieletow, prog dlugosci). Stad
denormalizacja + indeksy czesciowe z predykatem progu, ktore daja gotowe sortowanie.

Semantyka SUROWA, swiadomie: `quality` zapisujemy oportunistycznie, zna ja 6,5% scen
(210 302 z 3 236 368), najlepsze zrodlo ma 26% pokrycia. Wariant lagodny, przepuszczajacy
sceny o nieznanej jakosci, odrzucony po zmierzeniu: odsiewalby 88 647 z 3,2 mln, czyli
nie realizowalby intencji "nie chce ogladac ziemniaka". Filtr pokazuje wiec tylko jakosc
POTWIERDZONA, a UI mowi to wprost podpisem pod przelacznikiem, bo inaczej gwaltowny
spadek liczby wynikow wyglada na zepsuta aplikacje.

Warianty tekstowe 4K/UHD usuniete z warunku: 0 wystapien na 238 452 zywe zrodla,
a `OR ilike` blokowal uzycie indeksu.

Odswiezanie w taxonomy_counts (co 3h), z czyszczeniem do NULL gdy scena traci zrodlo
z jakoscia. Backfill: 210 302 sceny w 2m56s.

Zweryfikowane na emulatorze: sekcja MIN QUALITY renderuje sie z podpisem ostrzegawczym.
Lista wyszla pusta, ale to build debugowy dostajacy 403 (podpis debug spoza allowlisty),
nie blad filtra: ten sam URL z poprawnym podpisem oddaje 50 wynikow w 0,12 s.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 11:06:34 +02:00
goon-foss
a1f604a7a4 fix(verticals): przenos wertykal przy przejsciu do tagu/studia/performera
Zgloszenia 195ae42e i e9de414c: klikniecie studia w anime nic nie robilo, a tag
kliniety w anime pokazywal sceny spoza anime.

Jedna przyczyna: TagScenes/StudioScenes/PerformerScenes wolaly listScenes BEZ
parametru vertical, a backend bez niego wyklucza wszystkie wertykaly. Studio
animacyjne mialo wiec zero scen (pusty ekran = wyglada na martwy klik), a tag
zwracal wylacznie sceny glownego katalogu.

SceneOut dostaje pole "vertical" liczone z originow zrodel, czyli jedno zrodlo
prawdy w app/verticals.py zamiast dublowania listy originow w apce. W trybie
light dociagamy origin z tego samego wiersza co miniaturka, wiec zero dodatkowych
zapytan. SceneDetail przekazuje wartosc dalej w paramach nawigacji.

Dotyczy tak samo sekcji JAV, ktora miala ten sam blad.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 11:42:47 +02:00
goon-foss
dde21f2e6e fix(thumbs): degraduj chwiejne hosty miniaturek zamiast zerowac
Zgloszenie "Jakis problem z miniaturkami z fullporner" (36c33200).
fullporner NIE jest winowajca: pomiar 2026-08-13 dal 40/40 poprawnych
JPEG-ow i 0 z 68 414 zrodel bez URL-a. Winna byla KOLEJNOSC WYBORU.

Miniaturke wygrywal pierwszy origin alfabetycznie, a `tube:fullmoviesxxx`
sortuje sie PRZED `tube:fullporner`, wiec chwiejny host przykrywal zdrowy.
Pomiar (20-25 losowych URL-i na host):

    img.fullmovies.xxx    12/20 ok  (5x ReadTimeout, 3x 502)  54 019 zrodel
    img.hdporn.gg         16/20 ok  (3x 502, 1x ReadTimeout)  54 267 zrodel
    imgs.xiaoshenke.net   20/20 ok  (fullporner)              68 406 zrodel
    freshporno.org        20/20 ok

Nowy predykat `_is_flaky_thumb`: degraduje do tieru fallback w LISCIE, ale
w odroznieniu od `_is_rotting_thumb` NIE zeruje w SceneDetail. Zerowanie
powtorzyloby wpadke z sierpnia, gdzie sceny majace tylko fullporner
tracily obrazek calkowicie.

Dwie iteracje, obie z pomiaru, nie z zalozenia:

1. Degradacja samego fullmovies przesunela wybor na hdporn (44 z 60),
   czyli PRZENIOSLA problem. Stad hdporn dopisany.
2. Po zdegradowaniu obu fullmovies WROCIL (38 z 60): sceny majace tylko
   te dwa hosty laduja w fallbacku, a `setdefault` bral pierwszy
   alfabetycznie, czyli gorszy z dwoch. Stad `_FLAKY_RANK` — w obrebie
   fallbacku wybieramy mniejsze zlo.

Po zmianie w dwoch niezaleznych losowaniach fullmovies nie wygrywa nigdzie,
a sceny majace WYLACZNIE chwiejne hosty dostaja hdporn (40/40, zero bez
miniatury), wiec nic nie traci obrazka.
2026-08-13 15:34:40 +02:00
goon-foss
ff898b0783 feat(verticals): animacja odgrodzona od glownego feedu + filmy na ekranie studia
1. WERTYKALY. Hentai i 3D szly do glownego feedu wymieszane z zachodnim
katalogiem, mimo ze argument za odgrodzeniem byl ten sam co przy JAV.
Zamiast drugiej kopii mechanizmu JAV wprowadzam wspolne pojecie wertykalu
(app/verticals.py): mapa nazwa -> originy, jeden predykat, `?vertical=`.

Brama po originie jest DOKLADNA, zmierzone: 9507 scen animowanych, ZERO
z originem spoza listy (nie maja sie z czym zdeduplikowac). Dlatego osobna
baza nie jest potrzebna, rozdzielam logicznie.

Tagi to samodzielny powod: z 3116 tagow przy scenach animowanych tylko 452
wystepuja tez poza animacja, czyli 85% to slownictwo wylacznie animacyjne.
`/tags?vertical=` liczy je NA ZYWO (male zbiory, zero migracji), a
zdenormalizowany Tag.scene_count opisuje juz WYLACZNIE glowny feed.

Przy okazji naprawione zasmiecenie JAV-em istniejace od lipca:
taxonomy_counts wliczal wertykaly do licznikow, ktore opisuja feed ich
nie pokazujacy. Refresh: 1476 tagow, 1402 performerow, 154 studia.

Stary `?jav=true` DZIALA dalej (instalacje sprzed sierpnia go wysylaja).

2. FILMY NA EKRANIE STUDIA. Zgloszenie: polubione studio od filmow nie
pokazuje nowych filmow i nie ma "+1". Backend liczyl je poprawnie
(_merged_new_counts scala sceny i filmy od 2026-07-28). Blad byl w apce:
tap na ulubione studio ZEROWAL licznik (markStudioSeen) i wchodzil na
ekran wylacznie ze scenami, wiec "+N" z nowych filmow znikalo, a filmy
nie mialy sie gdzie pokazac. StudioMoviesScreen istnial, ale wchodzilo
sie do niego tylko z detalu filmu. StudioScenes dostaje zakladki
Scenes/Movies, jak PerformerScenes.

Pasek zakladek w headerze przewija sie w poziomie, bo piata pozycja
(Anime) nie miescila sie na waskich telefonach.
2026-08-13 10:31:54 +02:00
goon-foss
a4486980a4 style: myslniki em/en z komentarzy i docstringow (calosc repo)
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>
2026-08-11 11:51:57 +02:00
goon-foss
72b7b561e5 fix(thumbs): odblokuj miniaturki fullporner (atrapa byla przejsciowa)
Pomiar na 300 losowych URL-ach z bazy: 298 prawdziwych JPEG-ow (5-25 kB),
tak samo z VPS jak i z lacza domowego. Wpis w _is_rotting_thumb zerowal
miniature w SceneDetail, wiec sceny majace tylko fullporner traciły ja
calkowicie (53 tys. zrodel).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 09:10:13 +02:00
goon-foss
4fd94de72d fix: cofnij obietnice keep-awake (modul nie jest w APK) + martwe miniatury fastpic
Some checks are pending
Backend tests / test (push) Waiting to run
1) Changelog obiecywal 'screen no longer turns off', ale to NIE dziala. Audyt APK
v0.2.1: nazwa 'ExpoKeepAwake' NIE wystepuje w zadnym classes*.dex - jest tylko
KeepAwakeManager z expo-modules-core (wewnetrzny interfejs). Czyli expo-keep-awake
nie zostal wkompilowany, requireNativeModule rzuca, a moj try/catch cicho polyka
blad. Blad w moim wczesniejszym rozumowaniu: obecnosc w node_modules/expo/
node_modules NIE oznacza, ze autolinking wciagnal modul do konkretnego builda.
Keep-awake WYMAGA nowego APK (OTA dowozi tylko JS), wiec wpis usuniety zamiast
obiecywac cos, czego nie ma. Zgloszenie 9d5618dd.

2) fastpic.org (galaxyporn) zrywa polaczenie zamiast oddac obraz -> dopisany do
_is_rotting_thumb. Bonus: scena bez miniatury odpala w apce auto-enrich, ktory
sciaga swieza ze strony tube'a. Zgloszenie c2f1dd41, 240 z 20213 zrodel.
2026-08-05 21:58:06 +02:00
goon-foss
c1e6c7ad49 fix(thumbs): odsiej zrodla oddajace atrapy zamiast miniatur (znikaly po merge)
Some checks are pending
Backend tests / test (push) Waiting to run
Zgloszenia a01dc891 / 2fbba822 / b437d5f7 ('3 zrodla live, a miniaturki nie ma',
'jakby przy merge scen cos ginelo'). Diagnoza: WSZYSTKIE zrodla mialy
thumbnail_url w bazie, wiec problem byl w wyborze. Apka bierze PIERWSZE zrodlo z
ustawionym URL-em, a po scaleniu na czolo listy wchodzilo zrodlo ktore formalnie
'ma miniature', ale realnie oddaje smiec - dzialajaca lezala nizej i nigdy nie
byla uzyta. Przy jednym zrodle bralo sie to dobre, stad objaw dopiero po merge.

Dwa hosty dopisane do _is_rotting_thumb (istniejacy mechanizm zerowania martwych):
- imgs.xiaoshenke.net (fullporner): HTTP 200 z ~153-bajtowa ATRAPA zamiast obrazu,
- tn.sextu.com (fullvideosporn): 429 dla IP datacenter, wiec nasze image-proxy tej
  miniatury NIGDY nie pobierze (znany problem tego zrodla).

Zweryfikowane na wszystkich trzech zgloszonych scenach: bylo 153B/404/429,
teraz 200 i realne obrazy (22287 / 21633 / 32820 B).
2026-08-02 16:30:21 +02:00
goon-foss
55da508f9c fix: keep-screen-on podczas odtwarzania + on-demand miniaturki youporn
Some checks failed
Backend tests / test (push) Has been cancelled
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
&amp; 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.
2026-07-26 17:00:31 +02:00
goon-foss
2d1800a461 fix(scenes): sxyprn detail thumbnail + stronger WebView pre-play mute
Some checks are pending
Backend tests / test (push) Waiting to run
1) _build_scene_out (detal) nie stosował _sxyprn_thumb_url jak lista → detal
pokazywał martwy trafficdeposit URL, refresh nie pomagał (sxyprn IP-bound). Teraz
detal też podmienia na żywy /proxy/sxyprn-thumb/<id>. Backend-only.

2) Mute WebView-scrape: attribute-autoplay (<video autoplay>) omijał override play()
i grał unmuted. Dokładam proaktywne ciszenie na play/loadeddata/volumechange +
poll 40ms do gestu (property-lock trzyma między tickami). Web Audio i tak gated
autoplay-policy. Raport usera: dalej głos przed ExoPlayerem.
2026-07-22 11:16:44 +02:00
goon-foss
f97369a764 fix(scenes): filter junk 'performers' from similar-scene matching
Some checks are pending
Backend tests / test (push) Waiting to run
Kategorie/studia podszywające się pod performera (over-attribution z luźnego
tube-searcha: 'Big Tits', 'Brazzers', 'Ball Licking') zaśmiecały ranking similar
— klastrował po nich zamiast po treści. Odsiew: slug performera pokrywa się z
tagiem I brak kanonicznego refa (tpdb/stashdb). Prawdziwi performerzy z tym samym
tagiem mają kanoniczny ref → zostają (Jax Slayher/Angela White OK). Scena z samymi
śmieciowymi performerami spada na tag-matching (i pusto gdy ma tylko mega-tag).
2026-07-21 15:50:49 +02:00
goon-foss
45459b414b feat(scenes): similar-scenes rail on detail (shared performers/tags)
Some checks are pending
Backend tests / test (push) Waiting to run
Backend GET /scenes/{id}/similar: ranking po WSPÓLNYCH performerach (waga 3) i
tagach (waga 1). Performerzy selektywni → mała pula kandydatów (cap 1500); tagi
liczone tylko w tej puli. Fill z tagów (bounded 400, HAVING>=2 wspólne) tylko gdy
performerów mało/brak (anonimowe tube sceny) — inaczej szeroki tag skanowałby
setki tysięcy scen. Filtr widoczności jak lista: grywalne + brama JAV + blacklist
+ nie-stub + nie-self. Ranking/sort w Pythonie nad ograniczonymi zbiorami.
Zmierzone: 210-944ms wg liczby performerów seeda.

Mobile: SimilarRail (poziomy) na dole SceneDetail, lazy przez react-query, znika
gdy brak dopasowań, tap → nav.push kolejnego SceneDetail.
2026-07-21 15:26:55 +02:00
goon-foss
db1bd2a3d4 fix(scenes): JAV sites showed "No videos" when browsed directly
Some checks are pending
Backend tests / test (push) Waiting to run
The JAV vertical gate excluded JAV origins whenever jav=false, including when the user
browsed a specific JAV site (Sites -> javflix/vjav/supjav -> SiteScenes passes
origin=... without jav=true), so those pages showed "No videos" (3 reports 07-19).

Only apply the exclusion to the main feed (origin is None). Browsing a specific origin
shows it regardless; the JAV tab (jav=true) still shows only JAV. Verified: javflix/vjav/
supjav now return scenes via origin filter, main feed still gates JAV out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 15:18:01 +02:00
goon-foss
4df1e01b31 feat(jav): separate JAV section (tab + feed gating) + enable javflix ingest
JAV is a distinct vertical (user decision): Asian codes/titles that do not dedup
against the western catalog, so they must not flood the main feed.

Backend (scenes.py): JAV_ORIGINS = {tube:javflix, tube:javguru, tube:vjav,
tube:supjav}; list_scenes gains a `jav` param. Default (jav=false) excludes any
scene with a live JAV-origin source; jav=true returns only those. The cached
default-count and _is_pure_default also exclude JAV so the main feed count matches.
JavflixScraper is now registered in ALL_BROWSE_SCRAPERS (scheduled ingest lands in
the JAV section, gated). Scraper hardened: requires a real server button
(class="myLink") so static pages (Terms/FAQ) are skipped, and unescapes HTML
entities in the title.

Mobile: a "JAV" top tab reuses ScenesScreen with { jav: true } (route param ->
listScenes jav=true). The 60s minimum-duration default is disabled in the JAV tab
because javflix does not expose duration (NULL >= 60 would hide the whole section).

Verified on prod: 16 javflix scenes appear only in the JAV feed and are excluded
from the 2.29M main feed; playback resolves to voe/doodstream/emturbovid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 11:16:31 +02:00
goon-foss
e30f6830f4 fix(mobile): stop Scenes feed repeating on scroll + mute audio during 2x hold
- Scenes feed repeated the same videos on scroll (report 304c9258): OFFSET
  pagination + continuous ingest means new scenes shift the offset, so later pages
  re-include earlier rows. Backend now appends Scene.id as a unique final tiebreaker
  to every sort (deterministic order), and the mobile Scenes list dedups by scene id
  when concatenating pages so a drifted overlap can never show a duplicate.
- Player: holding to fast-forward at 2x now mutes the sped-up (chipmunk) audio for
  the duration of the hold and restores the user's real mute preference on release
  (report 35bbf428).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:59:28 +02:00
goon-foss
cb22174657 fix(thumbnails): watchporn thumbs at ingest + backfill; SceneDetail uses live sxyprn thumb
Two bug-report clusters about missing thumbnails:

1. watchporn scenes had no thumbnail on the list, only appearing after opening
   SceneDetail (which auto-enriches og:image). Coverage was 1.3% (463/36,915).
   The browse scraper never captured a thumbnail. KVS stores the poster at a fixed
   derivable path (contents/videos_screenshots/<id//1000*1000>/<id>/preview.jpg,
   verified 8/8 loading). Scraper now sets thumbnail_url (og:image, else derived);
   backfilled 36,687 existing rows -> 100% coverage.

2. SceneDetail showed no thumb where the list showed one: the mobile detail picks
   the first source with a thumbnail_url (origin ASC often puts sxyprncom first),
   and sxyprn/trafficdeposit stored thumbs rot to 404. The list already swaps those
   for a live resolver (/proxy/sxyprn-thumb/), but the detail builder did not. It
   now applies the same live-resolver swap and nulls other rotting thumbs so the
   detail lands on a working image.

Both backend-only, no OTA needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:21:59 +02:00
goon-foss
cb0b843f48 refactor(review): dedup favorites/list visibility + SQL-aggregate count, share mobile isNew
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>
2026-07-02 09:41:37 +02:00
goon-foss
07cc3fdfba fix(favorites): exclude bulk-backfill scenes from "+N new" (tube fake dates)
Browse scrapers backfilling old catalogs stamp the tube's import/post date as
release_date, so old content (e.g. 83 MissaX classics via perverzija, ~3600/3 days
across eporner/youporn/etc.) fake-ranked as newest and flooded the favorites "+N".
NULLing the dates was a non-starter — the stub filter would hide 251k performer-less
scenes. Instead: a Scene.backfill flag marks bulk catalog imports; they stay visible but
never count as "new".

- scenes.backfill column (+ index, migration 0026); resolve_scene/_process_scene thread it.
- deep_crawl tags scenes from pages beyond the "latest" threshold (>2) as backfill;
  latest pages + TPDB/StashDB delta stay genuine. Cursor reset re-sweeps page 1 so real
  new content is always caught fresh.
- favorites +N (performers + studios) excludes backfill within the top-200 window.
- SceneOut exposes `backfill`; mobile NEW badge + NEW-first re-sort skip it (badge==count).
- Retroactive: tagged 439k existing scenes in bulk (>10 non-canonical / studio / day)
  clusters. Device check: favorite-studios +N 11626 (naive) -> 236.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:04:01 +02:00
goon-foss
9a82709bd9 fix(api): cap list_scenes filter sizes to prevent DB OOM (Fixes GOON-1M)
A single request with 194 studio_slugs + 23 tag filters (each tag = a correlated
EXISTS) plus an ILIKE search built a query heavy enough that the OOM killer killed the
Postgres backend, triggering a full crash-recovery (~1s prod-wide outage, all in-flight
connections dropped). Any user could do this with a big enough filter. Cap studios to
50, tags to 15, performers to 15 (far above any real UI usage) and return 422 instead
of executing — bounding query complexity regardless of the planner's choice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 16:25:29 +02:00
goon-foss
b5c3c6c7ff feat(sources): remove 0dayxx + pornditt + pornhat entirely
Three orphan-factory tubes (0–0.2% canonical match — auto-screenshot thumbs and
slug titles that never match TPDB/StashDB) — to be replaced by better sources.
Removed scrapers (files + imports), extractors (registry + modules), the pornhat
entry from tag-enrichment priority lists and the 0dayxx display override, and purged
the DB (19,003 playback_sources + 9,904 solo-orphan scenes; shared mirror scenes keep
their other sources). The pornhat-based enrich_studio endpoint stays as a graceful
no-op (no pornhat sources → returns no studio).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 12:23:29 +02:00
goon-foss
2d73fc66d9 feat(sources): remove pornhub + redtube entirely
Both scrapers were disabled since 2026-05-12 (~0.4% canonical match — mostly short
amateur clips that never match studio content); their data sat frozen. Removed for
good: deleted the extractor registry entries, scraper files and imports, dropped them
from the tag-enrichment priority lists, and purged the DB (17,906 playback_sources +
122 scenes that had no other source; mirror scenes shared with other tubes just lost
the ph/rt link).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 11:55:08 +02:00
goon-foss
97efd37936 feat(scenes): force-refresh thumbnail via enrich-thumbnail ?force
enrich-thumbnail was fill-only (skipped scenes that already had a thumbnail), so a
broken or stale preview (rotting sxyprn/trafficdeposit) could not be refreshed. Add a
force flag that re-fetches the source page and overwrites the existing thumbnail.
Backs the new "Refresh thumbnail" button (report d3376a71).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 19:04:10 +02:00
goon-foss
6e1f8fdc47 feat(sxyprn): on-demand thumbnail resolver (live posters, ~1h-TTL workaround)
trafficdeposit poster tokens live ~1h (hour-bucketed), so stored URLs can't persist.
New GET /proxy/sxyprn-thumb/{post_id}: resolves the current og:image from the live
/post/<id> page (cache resolved poster URL ~40min), streams bytes with Referer +
long client Cache-Control (URL is stable per post_id → client disk-caches the image,
backend fetches each post ~once). Deleted posts ("Post Not Found") → 404.

Scene grid now emits /proxy/sxyprn-thumb/<id> for sxyprn sources (derived from
page_url) instead of the dead stored trafficdeposit URL. Verified: live post → 200
image, deleted → 404, grid emits resolver URL. Backend-only, no OTA.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 15:02:49 +02:00
goon-foss
7b5bd5e4e0 fix(api): de-prioritize rotting sxyprn/trafficdeposit thumbnails
sxyprn thumbnails are time-signed on trafficdeposit CDN and ROT — the signed asset
404s after ~weeks and can't be re-signed/refreshed server-side (bug 2026-06-10,
~15k sxyprn-only scenes showed broken thumbs). In the light-list slim-thumbnail pick,
prefer a thumbnail from any non-trafficdeposit source; fall back to sxyprn only when
it's the scene's sole thumbnail (recent ones still load; dead ones now render a clean
placeholder client-side instead of a broken image).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 10:11:10 +02:00
goon-foss
fe842b3603 feat(api): device-scope user state (favorites/progress/blacklists)
Public instance has no accounts, so all user state was GLOBAL in DB — new users
saw/overwrote each other's (and Jan's) favorites, watched badges and blacklists
(bug 2026-06-10). Add device_id (VARCHAR 64) to 9 state tables with composite PK
(device_id, entity_id); app sends X-Device-Id header (get_device_id dep). All
favorites/scene-favorites/blacklist/watch + scene&movie list/detail (is_favorite,
watched, blacklist-hide) now filter by device. Existing rows backfilled to
'legacy-shared'; POST /me/adopt-legacy reassigns them to the caller once. Old
clients (no header) map to legacy-shared so they keep working until OTA updates.

Migration 0022: add col, backfill, composite PK. Verified on prod: 967 progress
rows preserved, device isolation holds (new device sees none of legacy state).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 08:58:01 +02:00
goon-foss
e5dabd13ef chore(api): drop unused has_animated_thumbnail scene filter
The hold-to-preview gesture is being removed (did nothing useful), and no client
sends this filter. Remove the Query param, its EXISTS filter, and the pure-default
count guard reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 09:52:15 +02:00
goon-foss
62d6878359 feat(api): scene hide + merge-duplicate endpoints for long-press actions
POST /scenes/{id}/hide — marks all playback_sources dead so the scene drops out
of has_playback lists (reversible via dead_at; row kept for dedup/refs).
POST /scenes/{keep_id}/merge/{drop_id} — merges drop into keep via scene_merge
(moves refs/performers/tags/fingerprints/playback). Backs the new tile long-press
menu (hide / mark-duplicate) replacing the dead animated-preview gesture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 09:47:16 +02:00
goon-foss
cd0f618693 fix(proxy): stable image-proxy URLs so expo-image actually caches thumbnails
make_token embedded the current timestamp in the expiry, so every /scenes fetch
produced a DIFFERENT proxied URL for the same thumbnail → expo-image (keyed by URI)
cache-missed and re-downloaded every list load / app launch. Add stable_bucket_sec:
quantize the expiry base to a window so the URL is identical across requests.
_wrap_image_proxy uses a 7-day bucket → thumbnails disk-cache for a week instead of
re-fetching constantly. Answers "czy miniatury są cache'owane" — now yes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 09:45:22 +02:00
goon-foss
c6a133ba19 fix(api): collapse same-origin playback sources on scene detail
A merged scene often aggregates several uploads from ONE tube (re-encodes / 4K
dups). bug-report aa79a995 "why 2 links, both porntrex?" = same scene std + 4K
(porntrex 2591377 + 2593449 "...in 4K"). In the UI these are indistinguishable
links to one hoster (same extractor). Keep one best per origin: prefer duration
matching the scene → any duration → first (origin-asc stable). Dead already filtered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:50:45 +02:00
goon-foss
1a4e1c17ca perf(scenes): literal tag_id in filter — 4-12s tag lists -> ~20ms
Tag-filtered scene lists (e.g. blowjob + has_playback) took 4-12s. Root cause:
the filter joined scene_tags->tags on slug, so the actual tag_id was opaque to
the planner at plan time. It fell back to average per-tag cardinality
(8.4M/11541 ≈ 726) instead of the real 273k, chose to materialize ALL matching
scene_tags + check playback per row, then top-N sort.

Fix: resolve slug->tag_id in the app and filter on a LITERAL tag_id (no slug
join). With a constant, the planner uses MCV stats, knows the tag is huge, and
walks ix_scenes_created_at_desc probing scene_tags/playback per scene, stopping
at the page limit. Verified: blowjob list 3300ms -> 18ms (EXPLAIN), HTTP 4-12s ->
47ms. Unknown slug short-circuits to empty. (Pairs with the raised tag_id
statistics target so mid-tier tags also get correct estimates.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:10:31 +02:00
goon-foss
5d1f665b80 perf(scenes): light list payload — drop tags/refs, slim playback to thumbnail
Scene list returned the full SceneOut per item (nested tags/external_refs + all
playback_sources with page_url/embed/stream/quality) though SceneTile only reads
the thumbnail + title/duration/performer/studio, and SceneDetail re-fetches the
full scene via /scenes/{id}. Added light=True to _build_scenes_out_batch: skip the
tags + external_refs queries entirely and collapse playback_sources to one slim
entry (thumbnail_url + animated_thumbnail_url only).

Result: default list payload 78KB->48KB (-38%), ~28ms cached, less DB work per
list. Verified on emulator: grid thumbnails/durations/titles render unchanged.
No mobile change (tile reads the same fields); server-side, no OTA.

NOTE: the separate slow path — common-tag-filtered lists (4-12s; query expands all
matching scene_tags before sort/limit) — is structural (needs a denormalized
(tag_id, created_at) index) and deferred. VACUUM ANALYZE + raised tag_id stats
applied but the planner still can't avoid the materialization.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 21:03:26 +02:00
goon-foss
4d2e46000b fix(playback): mark deleted sxyprn posts dead + rank native sources first
Two bug-report fixes (2026-06-07):
- sxyprn returns HTTP 200 "Post Not Found" for deleted posts (soft-404), so the
  extractor returned None → resolve treated it as transient and never marked the
  source dead, leaving a dead link offered forever. Now raise HosterDead on the
  marker so resolve marks it dead.
- Scene playback sources were ordered alphabetically by origin, so a WebView-
  fallback hoster (fpoxxx, IP-bound + ad-heavy) ranked above a working native
  source (freshporno) on the same scene. Add is_vps_blocked_fallback() and sort
  native-resolve origins ahead of WebView-fallback ones.

Verified on prod: sxyprn dead URL → HosterDead; scene sources reorder
freshpornoorg before fpoxxx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 14:09:01 +02:00
goon-foss
9d7e0eca94 perf(movies+scenes): direct-play #hash movie hosters; skip empty blacklist filters
Movies: the seekplayer-engine family (easyvidplayer/player4me/seekplayer/
embedseek/upns, ~322k sources) returns a time-bound master.m3u8 on a CDN with a
valid IP-SAN cert that plays cross-IP. Mark it mobile_direct in resolve, and make
MovieDetailScreen prefer direct_url with a proxy fallback (mirrors the scene
path) — previously every movie streamed through the VPS proxy. Paradisehill
multipart parts now go direct too. Device-verified: ExoPlayer plays the raw CDN
direct, zero proxy traffic, no flicker.

Scenes: the three blacklist NOT EXISTS clauses were appended to every filtered
list and evaluated per-row even when all blacklist tables are empty (~3.4s tax on
a deep mega-tag walk). Skip them when the tables are empty (cached check) —
mega-tag list 6.7s -> 3.3s, and every filtered list benefits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:44:41 +02:00