From e0744da8c739d398d41fc60859b9ae5392a6e35c Mon Sep 17 00:00:00 2001 From: goon-foss Date: Thu, 15 Oct 2026 14:55:13 +0200 Subject: [PATCH] fix(playback): przytrzymanie nie zabija ostatniego zrodla sceny bez potwierdzenia W 30 dni 490 scen zostalo przez "mark as broken" bez zadnego zrodla, glownie przez problemy sieci uzytkownika (porndoe 403, galaxyporn, perverzija). Ostatnie zrodlo znika tylko, gdy serwer widzi 404/410 albo HosterDead. Skrypt przywrocil 366 zrodel z pozytywnym potwierdzeniem, ze strona zyje. Co-Authored-By: Claude Opus 5.5 --- app/api/playback.py | 39 ++++++++++ scripts/revive_user_marked_last_sources.py | 89 ++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 scripts/revive_user_marked_last_sources.py diff --git a/app/api/playback.py b/app/api/playback.py index a2c29ac..1d3cb08 100644 --- a/app/api/playback.py +++ b/app/api/playback.py @@ -374,12 +374,51 @@ def mark_playback_dead( raise HTTPException(status_code=404, detail="playback source not found for scene") if pb.dead_at is None: tag = _requester_tag(request) + others_alive = session.scalar( + select(func.count()) + .select_from(PlaybackSource) + .where( + PlaybackSource.scene_id == scene_id, + PlaybackSource.id != pb.id, + PlaybackSource.dead_at.is_(None), + ) + ) + if not others_alive and not _scene_source_confirmed_dead(pb): + log.info( + "user mark-dead na ostatnim zrodle sceny %s odrzucone, serwer go nie potwierdzil (%s)", + pb.id, tag, + ) + return pb.dead_at = datetime.now(UTC) pb.dead_reason = f"user-marked dead (mobile long-press) {tag}"[:512] session.commit() log.info("user marked playback %s dead (origin=%s %s)", pb.id, pb.origin, tag) +def _scene_source_confirmed_dead(pb: PlaybackSource) -> bool: + """Czy serwer sam widzi, ze zrodlo sceny nie zyje. Uzywane, gdy przytrzymanie w apce + zabiloby OSTATNIE zywe zrodlo sceny (scena znika wtedy z list). + + W 30 dni do 2026-10-15 z 617 zrodel oznaczonych w apce 490 zostawilo scene bez zadnego + zrodla, glownie porndoe (403 z sieci uzytkownika), galaxyporn, perverzija, javflix + (zgloszenie cbb87c8e: strona zyje, ekstraktor daje voe i doodstream). Martwe tylko, + gdy strona daje 404/410 albo ekstraktor mowi wprost, ze film usuniety; kazda + niepewnosc zostawia zrodlo. + """ + origin = pb.origin or "" + if not origin.startswith(("tube:", "pornapp:")): + return False + try: + try_extract(origin.split(":", 1)[1], pb.page_url) + except HosterDead: + return True + except TubePageError as e: + return e.status_code in (404, 410) + except Exception: + return False + return False + + def _movie_source_confirmed_dead(pb: Any) -> bool: """Czy serwer sam widzi, ze link filmu nie zyje. Uzywane, gdy przytrzymanie w apce zabiloby OSTATNI link filmu. diff --git a/scripts/revive_user_marked_last_sources.py b/scripts/revive_user_marked_last_sources.py new file mode 100644 index 0000000..2433ff7 --- /dev/null +++ b/scripts/revive_user_marked_last_sources.py @@ -0,0 +1,89 @@ +"""Przywraca ostatnie zrodla scen zabite przytrzymaniem w apce, jesli strona zyje (2026-10-15). + +Do 15.10 przytrzymanie "mark as broken" zabijalo zrodlo bez sprawdzenia. W 30 dni 490 scen +zostalo przez to bez zadnego zrodla i zniknelo z list (porndoe: 403 z sieci uzytkownika, +galaxyporn, perverzija, javflix...). Od 15.10 serwer sprawdza ostatnie zrodlo przed +oznaczeniem; ten skrypt cofa wczesniejsze przypadki, ale tylko z POZYTYWNYM dowodem: +- ekstraktor zwraca zrodla (strona i film istnieja), +- dla serwisow grajacych tylko w WebView (ekstraktor nic nie pobiera): strona odpowiada 200. + + python scripts/revive_user_marked_last_sources.py # podglad + python scripts/revive_user_marked_last_sources.py --apply [--days 30] +""" +from __future__ import annotations + +import argparse +import logging +from collections import Counter + +import sqlalchemy as sa + +from app.db import session_scope +from app.extractors import is_vps_blocked_fallback, try_extract +from app.extractors._fetch import browser_get + +log = logging.getLogger(__name__) + + +def _alive(origin: str, page_url: str) -> bool: + if not origin.startswith(("tube:", "pornapp:")): + return False + sitetag = origin.split(":", 1)[1] + if is_vps_blocked_fallback(sitetag): + try: + return browser_get(page_url, timeout=20.0).status_code < 400 + except Exception: + return False + try: + return bool(try_extract(sitetag, page_url)) + except Exception: + return False + + +def run(apply: bool, days: int) -> dict: + with session_scope() as s: + rows = s.execute( + sa.text( + """ + SELECT p.id, p.origin, p.page_url + FROM playback_sources p + WHERE p.dead_reason LIKE 'user-marked dead%' + AND p.dead_at > now() - make_interval(days => :d) + AND NOT EXISTS ( + SELECT 1 FROM playback_sources o + WHERE o.scene_id = p.scene_id AND o.dead_at IS NULL + ) + """ + ), + {"d": days}, + ).all() + revive: list = [] + by_origin: Counter = Counter() + for pb_id, origin, url in rows: + if _alive(origin, url): + revive.append(pb_id) + by_origin[origin] += 1 + print({"candidates": len(rows), "alive": len(revive)}, by_origin.most_common(8)) + if apply and revive: + with session_scope() as s: + s.execute( + sa.text("UPDATE playback_sources SET dead_at = NULL, dead_reason = NULL WHERE id = ANY(:ids)"), + {"ids": revive}, + ) + s.commit() + print("revived", len(revive)) + return {"candidates": len(rows), "revived": len(revive) if apply else 0} + + +def main() -> int: + logging.basicConfig(level=logging.WARNING) + p = argparse.ArgumentParser() + p.add_argument("--apply", action="store_true") + p.add_argument("--days", type=int, default=30) + args = p.parse_args() + run(args.apply, args.days) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())