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.
This commit is contained in:
goon-foss 2026-08-27 11:53:45 +02:00
parent 38eee03902
commit 1fd1a750e3

View file

@ -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 „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. 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 Skala (próbka 45 źródeł, 2026-08-24): 26 razy HTTP 404, 18 razy odpowiedź bez strumienia,
strumienia, 1 raz działający strumień. Czyli około 2% katalogu na tym silniku jeszcze 1 raz działający strumień. Powtórka 2026-08-27 na sześciu markach: 0 grających na 36.
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ą.
Bezpieczeństwo: filmów zależnych WYŁĄCZNIE od tej rodziny jest w bazie dokładnie Bezpieczeństwo: filmów zależnych WYŁĄCZNIE od tej rodziny jest w bazie jeden, pozostałe
jeden, pozostałe 26 223 mają inne źródło. Sprzątanie nie chowa więc katalogu, tylko mają inne źródło. Sprzątanie nie chowa więc katalogu, tylko przestaje podsuwać wybór,
przestaje podsuwać wybór, który i tak kończy się czarnym ekranem. który i tak kończy się czarnym ekranem.
Oszczędność: domeny bliźniacze (`p.` i `vip.easyvidplayer.com`, `my.` i 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ę `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 Wznawianie za darmo: bierzemy tylko żywe źródła, więc kolejne odpalenie samo pomija
to, co już oznaczone. Żadnego pliku stanu. 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): Użycie (w kontenerze workera):
python3 /srv/scripts/seekplayer_reap.py --limit 300 --dry-run python3 /srv/scripts/seekplayer_reap.py --limit 300 --dry-run
python3 /srv/scripts/seekplayer_reap.py python3 /srv/scripts/seekplayer_reap.py
@ -31,7 +36,6 @@ from __future__ import annotations
import argparse import argparse
import collections import collections
import json
import logging import logging
import re import re
import sys import sys
@ -42,8 +46,7 @@ from urllib.parse import urlparse
from sqlalchemy import text from sqlalchemy import text
from app.db import session_scope from app.db import session_scope
from app.extractors._fetch import _DEFAULT_UA, browser_get from app.extractors.hosters.seekplayer_engine import _HOST_RE
from app.extractors.hosters.seekplayer_engine import _HOST_RE, _decrypt
log = logging.getLogger("seekplayer-reap") log = logging.getLogger("seekplayer-reap")
@ -52,12 +55,6 @@ _FAMILY_SQL = (
"easyvidplayer|embedseek|seekplayer|upns|player4me|rpmplay" "easyvidplayer|embedseek|seekplayer|upns|player4me|rpmplay"
"|4meplayer|seekplays|ezplayer|seeks" "|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 _CONCURRENCY = 3
# Odstęp przed każdym pobraniem. Bez niego przy pięciu wątkach 38% odpowiedzi to było # 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ć # 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: 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 key, url = item
time.sleep(_SPACING_SEC) time.sleep(_SPACING_SEC)
p = urlparse(url) from app.extractors._models import HosterDead
host = f"{p.scheme}://{p.hostname}" from app.extractors.hosters import seekplayer_engine
api = f"{host}/api/v1/video?id={p.fragment}&w=1920&h=1080&r="
headers = {"User-Agent": _DEFAULT_UA, "Accept": "*/*", "Referer": host + "/"} try:
for attempt in (1, 2): out = seekplayer_engine.extract(url, timeout=_TIMEOUT)
try: except HosterDead as e:
r = browser_get(api, headers=headers, timeout=_TIMEOUT) return (key, _DEAD, str(e)[-80:])
except Exception as e: except Exception as e:
return (key, _UNKNOWN, f"{type(e).__name__}") # Rate limit i błędy sieci NIE są dowodem na nic: zostawiamy nietknięte.
if r.status_code in (404, 410): return (key, _UNKNOWN, type(e).__name__)
return (key, _DEAD, f"HTTP {r.status_code}") if out:
if r.status_code == 429: return (key, _ALIVE, (out[0].raw or {}).get("sciezka") or "")
# Rate limit nie jest dowodem na cokolwiek, więc jedna przerwa i koniec. return (key, _UNKNOWN, "brak zrodla bez wyjatku")
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")
def _load_work() -> tuple[dict, dict, dict]: def _load_work() -> tuple[dict, dict, dict]: