fix(reap): dodaj brakujacy modul sondy do repo

`.gitignore` ma regule na `_*.py` (pliki robocze), przez co wspolny modul sondy wypadl z
poprzedniego commita i zostaly w nim dwa skrypty importujace nieistniejacy modul.
This commit is contained in:
goon-foss 2026-08-27 15:31:03 +02:00
parent f10d999410
commit 4df82b0bbe

127
scripts/_reap_probe.py Normal file
View file

@ -0,0 +1,127 @@
"""Wspolna sonda dla pomiaru i przemialu martwych zrodel.
Trzymamy to w jednym miejscu, zeby bramka w `tube_reap_dead.py` sprawdzala DOKLADNIE ta
sama sonde, ktora potem mieli baze. Gdyby to byly dwie kopie, bramka moglaby przepuscic
przemial oparty na innym zachowaniu niz to, ktore przetestowala.
"""
from __future__ import annotations
import re
import time
from urllib.parse import urlsplit, urlunsplit
from app.extractors import try_extract
from app.extractors._models import HosterDead, TubePageError
ALIVE, DEAD, UNKNOWN = "alive", "dead", "unknown"
_SPACING_SEC = 0.4
# Ciag cyfr dosc dlugi, zeby byl numerem sceny, a nie fragmentem tytulu.
_NUM_RE = re.compile(r"\d{4,}")
# Segment wygladajacy na identyfikator, a nie na tytul: bez mysinikow i dosc dlugi,
# za to z cyframi (np. eporner `000goU7Teov`, xnxx `video-10003hb8`).
_ID_SEG_RE = re.compile(r"^[A-Za-z0-9_.-]{6,}$")
_ZNACZNIK = "99899989"
def bogus_url(url: str) -> str | None:
"""Prawdziwy adres -> adres sceny, ktora na pewno nie istnieje.
Sluzy do kontroli negatywnej: jesli taki adres wraca ZYWY, to znaczy, ze dla tego
tuba w ogole nie widzimy smierci i jego wynik trzeba odrzucic, a nie czytac jako
"brak trupow".
Pierwsza wersja podmieniala po prostu ostatni ciag cyfr, przez co nie budowala nic
dla tubow BEZ numeru sceny w adresie (`hentaigasm.com/1-funkan-dake-.../`,
`mypornerleak.com/1st-double-vag-.../`). Takie tuby wychodzily w raporcie jako "0/0",
czyli niezmierzone, a bylo ich okolo 950 tys. zrodel. Dlatego kolejno:
1. numer w parametrach zapytania (`siska.video/video.php?videoID=100007`),
2. segment sciezki bedacy identyfikatorem (`/video/39776/`, `/386900.html`,
`/hd-porn/000goU7Teov/`, `/video-10003hb8/`),
3. gdy identyfikatora nie ma, tub routuje po samym slugu, wiec psujemy slug.
Kolejnosc ma znaczenie: slug psujemy DOPIERO na koncu, bo czesc tubow routuje po
samym numerze i sluga nie sprawdza, wiec zepsuty slug oddalby 200 i falszywie
wygladal na slepote sondy.
"""
parts = urlsplit(url)
if parts.query and _NUM_RE.search(parts.query):
q = _NUM_RE.sub(_ZNACZNIK, parts.query, count=1)
return urlunsplit((parts.scheme, parts.netloc, parts.path, q, parts.fragment))
segs = parts.path.split("/")
idx = [i for i, s in enumerate(segs) if s]
if not idx:
return None
def _rozbij(seg: str) -> tuple[str, str]:
rdzen, _, ext = seg.rpartition(".")
return (rdzen, ext) if rdzen else (seg, "")
def _zloz(i: int, nowy: str, ext: str) -> str:
segs[i] = f"{nowy}.{ext}" if ext else nowy
return urlunsplit(
(parts.scheme, parts.netloc, "/".join(segs), parts.query, parts.fragment)
)
# Przebieg 1: segment bedacy CALY identyfikatorem (`/video/39776/`, `/386900.html`,
# `/hd-porn/000goU7Teov/`). Segmenty czysto literowe pomijamy, bo to nazwa trasy
# (`videos`, `episodes`, `watch`). Podmiana trasy sprawdzalaby, czy tub obsluguje
# dany adres, a nie czy istnieje scena, wiec tub oddajacy 404 na zla trase i 200 na
# nieistniejaca scene FALSZYWIE zdalby bramke. To jedyny kierunek bledu, ktory boli.
for i in reversed(idx):
rdzen, ext = _rozbij(segs[i])
if "-" in rdzen or not _ID_SEG_RE.match(rdzen):
continue
if rdzen.isdigit() or (rdzen.isalnum() and any(c.isdigit() for c in rdzen)):
nowy = (
_NUM_RE.sub(_ZNACZNIK, rdzen, count=1)
if _NUM_RE.search(rdzen)
else rdzen[:-2] + "zq"
)
return _zloz(i, nowy, ext)
# Przebieg 2: identyfikator wtopiony w slug (`/video-10003hb8/`,
# `/videos/01082023-a-bj-to-remember/`, `..._17800085.html`).
for i in reversed(idx):
rdzen, ext = _rozbij(segs[i])
if _NUM_RE.search(rdzen):
return _zloz(i, _NUM_RE.sub(_ZNACZNIK, rdzen, count=1), ext)
# Przebieg 3: identyfikatora nie ma, tub routuje po samym slugu, wiec psujemy slug.
last = idx[-1]
rdzen, _, ext = segs[last].rpartition(".")
if rdzen:
segs[last] = f"{rdzen}-zqx9nieistnieje.{ext}"
else:
segs[last] = f"{segs[last]}-zqx9nieistnieje"
return urlunsplit((parts.scheme, parts.netloc, "/".join(segs), parts.query, parts.fragment))
def probe(sitetag: str, url: str) -> tuple[str, str]:
"""(werdykt, szczegol) dla jednego adresu. Szczegol sluzy tylko do raportu.
Idzie przez `try_extract`, czyli ten sam kod, ktorego uzywa aplikacja przy
odtwarzaniu, wiec sonda nie moze "widziec" czegos innego niz user.
DEAD pada TYLKO na twardych sygnalach: HTTP 404/410 albo `HosterDead` (soft-404, np.
sxyprn "Post Not Found"). Wszystko inne, lacznie z pustym wynikiem ekstraktora, to
UNKNOWN i zrodla nikt nie rusza. Odwrotne ustawienie schowaloby zywe sceny po cichej
zmianie markupu, tak jak przy porntrex (6479 scen).
"""
time.sleep(_SPACING_SEC)
try:
out = try_extract(sitetag, url)
except TubePageError as e:
if e.status_code in (404, 410):
return DEAD, f"HTTP {e.status_code}"
# 403, 5xx, rate limit: NIE jest dowodem na nic.
return UNKNOWN, f"HTTP {e.status_code}"
except HosterDead:
return DEAD, "soft-404"
except Exception as e:
return UNKNOWN, type(e).__name__
return (ALIVE, "") if out else (UNKNOWN, "ekstraktor zwrocil pusto")