From 1fd1a750e390eb6f83eaf2fe1f9f9fda3c20ed16 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Thu, 27 Aug 2026 11:53:45 +0200 Subject: [PATCH] chore(seekplayer): wygaszona cala rodzina dla filmow + reaper zgodny z resolverem Na prosbe Jana wygaszone hurtem 338 904 zrodla rodziny seekplayer (easyvidplayer, embedseek, seekplayer, upns, player4me, rpmplay) na 26 248 filmach. Dowody, ktore to uzasadnily, trzy niezalezne: 1. Swieze identyfikatory wziete PROSTO ze strony serwisu tego samego dnia zachowuja sie tak samo jak nasze z bazy: brak adresu albo 404. Czyli to NIE jest nasza staroc, tylko hoster nie serwuje (przy galaxyporn ten sam test wychodzil odwrotnie). 2. Pomiar per marka, po 6 probek na kazda: 0 grajacych na 36. 3. Rozkodowany ich wlasny player: komunikat "Media source is not playable" pada dopiero po wyczerpaniu WSZYSTKICH czterech sciezek CDN (Ce.shift() az do pustej listy). Czyli ich klient tez sie poddaje. Zabezpieczenie: NIE wygaszono zrodel filmu, ktory nie ma nic innego (4 zrodla, 1 film), zeby nic nie zniknelo z katalogu. Po operacji 0 filmow stracilo wszystkie zrodla, a te, ktore mialy ta rodzine, maja srednio 10,6 zywych zrodel. Odwracalne: ustawiamy dead_at + czytelny dead_reason, nie kasujemy wierszy. Skrypt: `_probe` deleguje teraz do `seekplayer_engine.extract`, zeby werdykt byl TEN SAM co u resolvera aplikacji. Wczesniej mial wlasna, luzniejsza regule (sa pola = zywe) i uznawal za zywe zrodla z adresem do usunietego pliku, na ktorych CDN oddaje 502. Usuniete nieuzywane importy i akapit docstringu, ktory po tej zmianie mowil cos przeciwnego niz kod. --- scripts/seekplayer_reap.py | 79 ++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 46 deletions(-) diff --git a/scripts/seekplayer_reap.py b/scripts/seekplayer_reap.py index 617d6f0..ef03b7e 100644 --- a/scripts/seekplayer_reap.py +++ b/scripts/seekplayer_reap.py @@ -7,14 +7,12 @@ w odpowiedzi nie ma pola `source`. Ich własny player w tej sytuacji pisze wpros „Video is not ready yet", co potwierdziłem w przeglądarce z łącza domowego, i to samo widać z VPS oraz z brytyjskiego proxy, więc nie chodzi o blokadę po IP ani po kraju. -Skala (próbka 45 losowych źródeł, 2026-08-24): 26 razy HTTP 404, 18 razy odpowiedź bez -strumienia, 1 raz działający strumień. Czyli około 2% katalogu na tym silniku jeszcze -gra, reszty hoster już nie ma. Sam protokół jest bez zmian, dlatego NIE kasujemy tych -źródeł hurtem, tylko pytamy o każde z osobna i zostawiamy te, które odpowiadają. +Skala (próbka 45 źródeł, 2026-08-24): 26 razy HTTP 404, 18 razy odpowiedź bez strumienia, +1 raz działający strumień. Powtórka 2026-08-27 na sześciu markach: 0 grających na 36. -Bezpieczeństwo: filmów zależnych WYŁĄCZNIE od tej rodziny jest w bazie dokładnie -jeden, pozostałe 26 223 mają inne źródło. Sprzątanie nie chowa więc katalogu, tylko -przestaje podsuwać wybór, który i tak kończy się czarnym ekranem. +Bezpieczeństwo: filmów zależnych WYŁĄCZNIE od tej rodziny jest w bazie jeden, pozostałe +mają inne źródło. Sprzątanie nie chowa więc katalogu, tylko przestaje podsuwać wybór, +który i tak kończy się czarnym ekranem. Oszczędność: domeny bliźniacze (`p.` i `vip.easyvidplayer.com`, `my.` i `vip.player4me`) trzymają ten sam identyfikator wideo, więc pytamy raz na parę @@ -23,6 +21,13 @@ Oszczędność: domeny bliźniacze (`p.` i `vip.easyvidplayer.com`, `my.` i Wznawianie za darmo: bierzemy tylko żywe źródła, więc kolejne odpalenie samo pomija to, co już oznaczone. Żadnego pliku stanu. +STAN NA 2026-08-27: cała rodzina została wygaszona hurtowo dla FILMÓW (338 904 źródeł), +bo dowody były jednoznaczne: świeże identyfikatory prosto ze strony serwisu też nic nie +zwracają, 0 grających na 36 próbek w sześciu markach, a ich własny player kończy +komunikatem „Media source is not playable", który pada dopiero po wyczerpaniu WSZYSTKICH +czterech ścieżek CDN. Zostawiono 4 źródła jednego filmu, który nie miał nic innego, żeby +nic nie zniknęło z katalogu. Ten skrypt zostaje do punktowego sprawdzania i do scen. + Użycie (w kontenerze workera): python3 /srv/scripts/seekplayer_reap.py --limit 300 --dry-run python3 /srv/scripts/seekplayer_reap.py @@ -31,7 +36,6 @@ from __future__ import annotations import argparse import collections -import json import logging import re import sys @@ -42,8 +46,7 @@ from urllib.parse import urlparse from sqlalchemy import text from app.db import session_scope -from app.extractors._fetch import _DEFAULT_UA, browser_get -from app.extractors.hosters.seekplayer_engine import _HOST_RE, _decrypt +from app.extractors.hosters.seekplayer_engine import _HOST_RE log = logging.getLogger("seekplayer-reap") @@ -52,12 +55,6 @@ _FAMILY_SQL = ( "easyvidplayer|embedseek|seekplayer|upns|player4me|rpmplay" "|4meplayer|seekplays|ezplayer|seeks" ) -# Pola, w których silnik podaje adres strumienia. Nazwy wzięte z ich własnego bundla -# (`{Cloudflare: cf, Tiktok: hlsVideoTiktok, Google: hlsVideoGoogle, "In-House": source}`), -# a warunek „nie ma żadnego z nich" to dokładnie ten sam warunek, po którym ich player -# pokazuje „Video is not ready yet". -_STREAM_FIELDS = ("source", "cf", "hlsVideoGoogle", "hlsVideoTiktok", "cfNative", "p2pStream") - _CONCURRENCY = 3 # Odstęp przed każdym pobraniem. Bez niego przy pięciu wątkach 38% odpowiedzi to było # HTTP 429, a limit nie jest dowodem na nic, więc takie źródło trzeba zostawić i wrócić @@ -75,38 +72,28 @@ def _brand(hostname: str) -> str: def _probe(item: tuple) -> tuple: - """((brand, hash), verdict, detail). Sieć i parsowanie, bez dotykania bazy.""" + """((brand, hash), verdict, detail). Sieć i parsowanie, bez dotykania bazy. + + Deleguje do `seekplayer_engine.extract`, ŻEBY WERDYKT BYŁ TEN SAM co u resolvera, + z którego korzysta aplikacja. Wcześniej skrypt miał własną, luźniejszą regułę + (są pola = żywe), przez co uznawał za żywe źródła z adresem do usuniętego pliku, + na których CDN oddaje 502. Silnik sprawdza teraz każdą ścieżkę naprawdę. + """ key, url = item time.sleep(_SPACING_SEC) - p = urlparse(url) - host = f"{p.scheme}://{p.hostname}" - api = f"{host}/api/v1/video?id={p.fragment}&w=1920&h=1080&r=" - headers = {"User-Agent": _DEFAULT_UA, "Accept": "*/*", "Referer": host + "/"} - for attempt in (1, 2): - try: - r = browser_get(api, headers=headers, timeout=_TIMEOUT) - except Exception as e: - return (key, _UNKNOWN, f"{type(e).__name__}") - if r.status_code in (404, 410): - return (key, _DEAD, f"HTTP {r.status_code}") - if r.status_code == 429: - # Rate limit nie jest dowodem na cokolwiek, więc jedna przerwa i koniec. - if attempt == 1: - time.sleep(5) - continue - return (key, _UNKNOWN, "HTTP 429") - if r.status_code != 200 or not r.text: - return (key, _UNKNOWN, f"HTTP {r.status_code}") - try: - data = json.loads(_decrypt(r.text)) - except Exception as e: - return (key, _UNKNOWN, f"dekod {type(e).__name__}") - if data.get("error"): - return (key, _DEAD, str(data["error"])[:80]) - if any(data.get(k) for k in _STREAM_FIELDS): - return (key, _ALIVE, "") - return (key, _DEAD, "brak pola source (Video is not ready yet)") - return (key, _UNKNOWN, "429") + from app.extractors._models import HosterDead + from app.extractors.hosters import seekplayer_engine + + try: + out = seekplayer_engine.extract(url, timeout=_TIMEOUT) + except HosterDead as e: + return (key, _DEAD, str(e)[-80:]) + except Exception as e: + # Rate limit i błędy sieci NIE są dowodem na nic: zostawiamy nietknięte. + return (key, _UNKNOWN, type(e).__name__) + if out: + return (key, _ALIVE, (out[0].raw or {}).get("sciezka") or "") + return (key, _UNKNOWN, "brak zrodla bez wyjatku") def _load_work() -> tuple[dict, dict, dict]: