fix(extractors): vjav przechodzi wyzwanie 429, galaxyporn oddaje vikingfile telefonowi
Znalezione w nocnym smoke tescie (9 ostrzezen, w tym trzy ekstraktory po 0/3). vjav: /api/videofile.php oddaje z serwerowni 429 i 343-bajtowa strone, ktora w JS ustawia JEDNO ciasteczko i sie przeladowuje. Wartosc jest wprost w tej odpowiedzi, wiec czytamy ja i powtarzamy zapytanie, bez wykonywania JS. Sprawdzone: druga proba oddaje 200 i poprawny JSON. Bylo 0/3, jest 5/5. galaxyporn: serwis przenosi NOWE wgrania na vikingfile.com. Probka: 12/12 najnowszych scen na vikingfile, 12/12 najstarszych dalej na rodzinie seekplayera. Vikingfile chowa wideo za RENDEROWANYM Cloudflare Turnstile (turnstile.render, bez trybu invisible), a <video> jest puste i ukryte, dopoki wyzwanie nie padnie. Serwerowo nie do obejscia, wiec oddajemy embed jako type=hoster i telefon otwiera go w WebView z domowego IP. Rozroznienie po hoscie, bo seekplayer jako SPA w WebView pokazuje pusty player. Najnowsze sceny: bylo 0/3, jest 3/3. ZOSTAJE OTWARTE: rodzina seekplayera zmienila format odpowiedzi /api/v1/video. Stary klucz AES dalej odszyfrowuje poprawnie, ale w miejsce sources sa teraz pola pk, streamingConfig i hlsVideoTiktok ze SCIEZKA wzgledna. Zlozenie jej z hostem playera daje 404, a z domenami z streamingConfig 403, wiec URL jest podpisywany po stronie klienta z pola pk. Dotyczy starego katalogu galaxyporn ORAZ pornbusy (0/5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
1e6be1926e
commit
ae5f2aa7ac
2 changed files with 39 additions and 5 deletions
|
|
@ -25,6 +25,10 @@ _BASE = "https://galaxyporn.net"
|
||||||
_IFRAME_RE = re.compile(r'<iframe[^>]+src="([^"]+)"', re.IGNORECASE)
|
_IFRAME_RE = re.compile(r'<iframe[^>]+src="([^"]+)"', re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
#: Hostery, ktorych NIE resolwujemy serwerowo, tylko oddajemy telefonowi do WebView.
|
||||||
|
_NEEDS_WEBVIEW_RE = re.compile(r"vikingfile\.", re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
|
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
|
||||||
html_text = fetch_tube_html(page_url, timeout=timeout)
|
html_text = fetch_tube_html(page_url, timeout=timeout)
|
||||||
m = _IFRAME_RE.search(html_text)
|
m = _IFRAME_RE.search(html_text)
|
||||||
|
|
@ -39,6 +43,25 @@ def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | Non
|
||||||
# weryfikuje wynikowy URL, a HLS galaxyporn jest hotlink-guarded (403 bez
|
# weryfikuje wynikowy URL, a HLS galaxyporn jest hotlink-guarded (403 bez
|
||||||
# Referera) → wrapper kasował poprawnie zdekodowany stream (sprawdzone: engine
|
# Referera) → wrapper kasował poprawnie zdekodowany stream (sprawdzone: engine
|
||||||
# zwracał m3u8, wrapper None dla tych samych scen).
|
# zwracał m3u8, wrapper None dla tych samych scen).
|
||||||
|
# Od sierpnia 2026 serwis przenosi NOWE wgrania na vikingfile.com: probka
|
||||||
|
# 2026-08-21 dala 12/12 najnowszych scen na vikingfile i 12/12 najstarszych dalej
|
||||||
|
# na rodzinie seekplayera. Vikingfile chowa wideo za RENDEROWANYM Cloudflare
|
||||||
|
# Turnstile (`turnstile.render('#captcha', ...)`, bez trybu invisible), a element
|
||||||
|
# <video> jest pusty i ukryty, dopoki wyzwanie nie zostanie rozwiazane. Serwerowo
|
||||||
|
# nie da sie tego obejsc, wiec oddajemy embed telefonowi: w WebView z domowego IP
|
||||||
|
# user zobaczy widget i przejdzie dalej. To NIE dotyczy seekplayera, ktory jako SPA
|
||||||
|
# w WebView pokazuje pusty player, dlatego rozroznienie jest po hoscie.
|
||||||
|
if _NEEDS_WEBVIEW_RE.search(iframe_src):
|
||||||
|
return [
|
||||||
|
StreamSource(
|
||||||
|
link=iframe_src,
|
||||||
|
quality="hoster",
|
||||||
|
type="hoster",
|
||||||
|
referer=page_url,
|
||||||
|
raw={"extractor": "galaxyporn", "hoster": "vikingfile"},
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
sources = seekplayer_engine.extract(iframe_src, timeout=timeout)
|
sources = seekplayer_engine.extract(iframe_src, timeout=timeout)
|
||||||
if not sources:
|
if not sources:
|
||||||
# Nie oddajemy iframe'a jako type='hoster', seekplayer to SPA, w WebView
|
# Nie oddajemy iframe'a jako type='hoster', seekplayer to SPA, w WebView
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,10 @@ _VIDEO_ID_RE = re.compile(r"/videos/(\d+)/")
|
||||||
_decode_video_url = _txxx.decode_video_url
|
_decode_video_url = _txxx.decode_video_url
|
||||||
|
|
||||||
|
|
||||||
|
#: Wyzwanie anty-botowe: `document.cookie = 'NAZWA=WARTOSC; max-age=...'` w ciele 429.
|
||||||
|
_CHALLENGE_COOKIE_RE = re.compile(r"document\.cookie\s*=\s*'([^=]+)=([^;']+)")
|
||||||
|
|
||||||
|
|
||||||
def _quality_label(fmt: str | None) -> str | None:
|
def _quality_label(fmt: str | None) -> str | None:
|
||||||
"""`_hq.mp4` -> 'HD', `_sd.mp4` -> 'SD'. Trailer (`_tr`) filtrowany osobno."""
|
"""`_hq.mp4` -> 'HD', `_sd.mp4` -> 'SD'. Trailer (`_tr`) filtrowany osobno."""
|
||||||
if not fmt:
|
if not fmt:
|
||||||
|
|
@ -61,12 +65,19 @@ def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | Non
|
||||||
return None
|
return None
|
||||||
vid = m.group(1)
|
vid = m.group(1)
|
||||||
api = f"{_BASE}/api/videofile.php?video_id={vid}&lifetime=8640000"
|
api = f"{_BASE}/api/videofile.php?video_id={vid}&lifetime=8640000"
|
||||||
|
hdr = {"Referer": page_url, "X-Requested-With": "XMLHttpRequest"}
|
||||||
try:
|
try:
|
||||||
res = browser_get(
|
res = browser_get(api, timeout=timeout, headers=hdr)
|
||||||
api,
|
body = res.text if hasattr(res, "text") else res
|
||||||
timeout=timeout,
|
# Serwis dlawi ruch z serwerowni: zamiast JSON-a oddaje 429 i mikroskopijna
|
||||||
headers={"Referer": page_url, "X-Requested-With": "XMLHttpRequest"},
|
# strone, ktora w JS ustawia jedno ciasteczko i przeladowuje sie. Wartosc jest
|
||||||
)
|
# WPROST w tej odpowiedzi, wiec wystarczy ja odczytac i powtorzyc zapytanie,
|
||||||
|
# bez zadnego wykonywania JS. Sprawdzone 2026-08-21: pierwsza proba 429 i 343
|
||||||
|
# bajty, druga z ciasteczkiem 200 i poprawny JSON z video_url. Bez tego caly
|
||||||
|
# vjav resolwowal sie do zera (smoke test: 0/3 OK, "extractor None").
|
||||||
|
if (ck := _CHALLENGE_COOKIE_RE.search(body)) is not None:
|
||||||
|
hdr = dict(hdr, Cookie=f"{ck.group(1)}={ck.group(2)}")
|
||||||
|
res = browser_get(api, timeout=timeout, headers=hdr)
|
||||||
body = res.text if hasattr(res, "text") else res
|
body = res.text if hasattr(res, "text") else res
|
||||||
data = json.loads(body)
|
data = json.loads(body)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue