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 <noreply@anthropic.com>
This commit is contained in:
goon-foss 2026-10-15 14:55:13 +02:00
parent 6da817dc79
commit e0744da8c7
2 changed files with 128 additions and 0 deletions

View file

@ -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.

View file

@ -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())