Some checks are pending
Backend tests / test (push) Waiting to run
- xtremestream od 12.10 szyfruje segmenty (AES-128-CBC, IV = numer segmentu, klucz spod tokenu zwiazanego z IP i UA): producent manifestu bierze xs1.php, przepisuje sciezki na _enc i dokleja EXT-X-KEY; klucz oddaje /proxy/hls/<token>/key.bin na wlasny token - proxy: Accept-Encoding identity (skompresowany manifest bez naglowka konczyl na "Input does not start with the #EXTM3U header"), play.m3u8 z nazwy pliku jako manifest - ingest_watchdog: porndoe jako zrodlo zamrozone (nowe sceny przekierowuja z kazdego IP) - scripts/porntrex_duration_walk.py: dlugosci z listingu /latest-updates/N/, z dry-run
93 lines
3.9 KiB
Python
93 lines
3.9 KiB
Python
"""perverzija.com (tube.perverzija.com), natywny HLS zamiast WebView.
|
|
|
|
Ustalone trace'em CDP (2026-07-28, bug-reports 40ec88cc / 92e9133e "zacina się na
|
|
pierwszych sekundach i robi loop"):
|
|
|
|
Strona osadza iframe `pervl4.xtremestream.xyz/player/index.php?data=<md5>`. Samo źródło
|
|
tego iframe'a NIE zawiera streamu (jest tam wyłącznie preroll/midroll z opencdn.b-cdn.net
|
|
to one leciały w pętli). Player dociąga wideo dopiero w runtime:
|
|
|
|
GET /player/xs1.php?data=<md5>&q=<480|720> → GOTOWY manifest HLS (#EXTM3U, VOD,
|
|
segmenty po ~4,17 s z PEŁNYMI URL-ami do pervl4.xspcdn{01..10}.sa.com/cdn/down/...)
|
|
|
|
Segmenty mają rozszerzenie `.html` (kamuflaż), ale to zwykły HLS, nic nie trzeba
|
|
deszyfrować ani składać. Wystarczy oddać URL `xs1.php` jako `type='m3u8'`.
|
|
|
|
WAŻNE (mobile): ścieżka manifestu NIE kończy się na `.m3u8`, a expo-video bez contentType
|
|
potraktowałby ją jako progressive i padł, dokładnie casus pornhat. Dlatego manifest musi
|
|
iść przez `/proxy/hls` (passthrough); segmenty i tak lecą direct z CDN, więc przez VPS
|
|
przechodzi ~41 KB, nie wideo.
|
|
|
|
`referer` celowo wskazuje na `index.php?data=<md5>`, taki Referer wysyła przeglądarka
|
|
przy wywołaniu xs1.php i bez niego jest ryzyko 403.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import re
|
|
|
|
from app.extractors._models import StreamSource
|
|
from app.extractors import browser_get
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# iframe playera: https://pervl4.xtremestream.xyz/player/index.php?data=<32 hex>
|
|
_PLAYER_IFRAME_RE = re.compile(
|
|
r"https?://(?P<host>[a-z0-9\-]+\.xtremestream\.[a-z]+)/player/index\.php\?data=(?P<id>[0-9a-f]{16,64})",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
# Player wystawia te dwie jakości (trace: xs1.php?...&q=480 i &q=720). Kolejność =
|
|
# preferencja (najlepsza pierwsza), tak jak w pozostałych ekstraktorach.
|
|
_QUALITIES = ("720", "480")
|
|
|
|
|
|
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
|
|
try:
|
|
res = browser_get(page_url, timeout=timeout)
|
|
html = res.text if hasattr(res, "text") else str(res)
|
|
except Exception as e:
|
|
log.info("perverzija page fetch failed %s: %s", page_url, e)
|
|
return None
|
|
|
|
m = _PLAYER_IFRAME_RE.search(html)
|
|
if not m:
|
|
# ~10% katalogu siedzi na innym playerze niż xtremestream (próbka 30 stron
|
|
# 2026-08-20: 27 xtremestream, 1 mixdrop, 1 playhydrax, 1 nie do pobrania).
|
|
# Oddajemy je generycznemu embed-iframe: wychodzi type='hoster', czyli telefon
|
|
# resolwuje je po swojej stronie, zamiast żeby scena wyglądała na martwą.
|
|
log.info("perverzija: brak xtremestream na %s, probuje embed-iframe", page_url)
|
|
from app.extractors.tubes import _embed_iframe
|
|
|
|
try:
|
|
return _embed_iframe.extract(page_url, timeout=timeout)
|
|
except Exception as e:
|
|
log.info("perverzija: embed-iframe fallback tez padl %s: %s", page_url, e)
|
|
return None
|
|
|
|
host, vid = m.group("host"), m.group("id")
|
|
player_url = f"https://{host}/player/index.php?data={vid}"
|
|
|
|
# Od 2026-10-12 segmenty sa szyfrowane (AES-128, klucz spod tokenu zwiazanego z IP),
|
|
# wiec manifest sklada backend w `/proxy/hls` (producent `xtremestream`, patrz
|
|
# extractors/hosters/xtremestream.py). Link to adres playera z jakoscia w zapytaniu;
|
|
# producent bierze z niego id i `q`. `mobile_direct_ok` zostaje: segmenty NIE sa
|
|
# IP-bound (test z dwoch IP 2026-10-14), przez VPS leci manifest i 16 B klucza.
|
|
out: list[StreamSource] = []
|
|
for q in _QUALITIES:
|
|
out.append(
|
|
StreamSource(
|
|
link=f"{player_url}&q={q}",
|
|
quality=f"{q}p",
|
|
type="m3u8",
|
|
referer=player_url,
|
|
raw={
|
|
"extractor": "perverzija",
|
|
"player": player_url,
|
|
"q": q,
|
|
"manifest_producer": "xtremestream",
|
|
"mobile_direct_ok": True,
|
|
},
|
|
)
|
|
)
|
|
return out or None
|