goon/app/extractors/tubes/galaxyporn.py
goon-foss ae5f2aa7ac 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>
2026-08-21 11:35:05 +02:00

92 lines
4.1 KiB
Python

"""galaxyporn.net, iframe → seekplayer engine. Dodany 2026-07-26.
Scene page ma jeden `<iframe src="https://<host>/#<hash>">` rodziny seekplayer
(galaxy.upns.online / galaxy.4meplayer.pro / sport.seekplays.com / news.upns.pro,
100% próbki 50 scen z całej głębokości archiwum). Silnik mamy już w repo
(`hosters/seekplayer_engine.py`, ten sam AES key/IV + `/api/v1/video?id=`), więc
oddajemy iframe do generycznego `extract_stream_from_hoster` i nic nie dublujemy.
HLS jest hotlink-guarded na Referer (bez niego 403 na master i na segmentach),
a token podpisany pod IP fetchera → manifest i segmenty idą przez `/proxy/hls`.
"""
from __future__ import annotations
import logging
import re
from urllib.parse import urlparse
from app.extractors._fetch import fetch_tube_html
from app.extractors._models import StreamSource
from app.extractors.hosters import seekplayer_engine
log = logging.getLogger(__name__)
_BASE = "https://galaxyporn.net"
_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:
html_text = fetch_tube_html(page_url, timeout=timeout)
m = _IFRAME_RE.search(html_text)
if not m:
log.info("galaxyporn: no iframe on %s", page_url)
return None
iframe_src = m.group(1).strip()
if iframe_src.startswith("//"):
iframe_src = "https:" + iframe_src
# Wołamy silnik BEZPOŚREDNIO, nie przez `extract_stream_from_hoster`: wrapper
# weryfikuje wynikowy URL, a HLS galaxyporn jest hotlink-guarded (403 bez
# Referera) → wrapper kasował poprawnie zdekodowany stream (sprawdzone: engine
# 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)
if not sources:
# Nie oddajemy iframe'a jako type='hoster', seekplayer to SPA, w WebView
# user zobaczy pusty player. Lepiej None (źródło jako nierozwiązane).
log.info("galaxyporn: seekplayer resolve failed for %s", iframe_src)
return None
# Referer MUSI być hostem PLAYERA (np. https://galaxy.4meplayer.pro/), NIE
# galaxyporn.net, zweryfikowane: ten sam manifest z Refererem galaxyporn = 403,
# z Refererem playera = 200 + lista wariantów. Silnik ustawia go poprawnie, więc
# go zachowujemy; fallback liczymy z hosta iframe'a.
player_origin = f"https://{urlparse(iframe_src).hostname}/"
out: list[StreamSource] = []
for s in sources:
raw = dict(s.raw or {})
# Token HLS jest Referer+IP-bound → manifest+segmenty przez /proxy/hls
# (bez mobile_direct_ok, inaczej telefon dostanie 403).
raw["proxy_no_verify"] = True
out.append(
StreamSource(
link=s.link,
type=s.type or ("m3u8" if ".m3u8" in s.link.lower() else "mp4"),
quality=s.quality,
referer=s.referer or player_origin,
raw=raw,
)
)
return out