feat(perverzija): natywny HLS zamiast WebView z reklamami
Some checks failed
Backend tests / test (push) Has been cancelled

Bug-reports 40ec88cc / 92e9133e ('zacina sie na pierwszych sekundach i robi loop').
Trace CDP wykazal: iframe pervl4.xtremestream.xyz/player/index.php?data=<md5> ma w
HTML WYLACZNIE preroll/midroll (opencdn.b-cdn.net) - to one lecialy w petli. Wideo
player dociaga dopiero w runtime:
  GET /player/xs1.php?data=<md5>&q=<480|720>  ->  GOTOWY manifest HLS (#EXTM3U, VOD,
  segmenty ~4,17s z PELNYMI URL-ami do pervl4.xspcdn{01..10}.sa.com/cdn/down/...).
Segmenty maja rozszerzenie .html (kamuflaz), ale to zwykly HLS - nic nie trzeba
deszyfrowac ani skladac.

Nowy extractor zwraca oba warianty jako type='m3u8' z referer=index.php?data=<md5>
(taki Referer wysyla przegladarka; bez niego ryzyko 403). Zweryfikowane na prodzie:
2 zrodla, oba HTTP 200 i #EXTM3U (44245 / 41142 B).

Uwaga: sciezka manifestu nie konczy sie na .m3u8, wiec musi isc przez /proxy/hls
passthrough (casus pornhat) - segmenty i tak leca direct z CDN.
This commit is contained in:
goon-foss 2026-07-28 12:16:18 +02:00
parent c1ba536607
commit 580e4db535
2 changed files with 78 additions and 1 deletions

View file

@ -34,6 +34,7 @@ from app.extractors.tubes import (
galaxyporn,
hdporngg,
hqfap,
perverzija,
hqporner,
fullporner,
kporner8,
@ -188,7 +189,11 @@ _REGISTRY: dict[str, Callable[[str], list[StreamSource] | None]] = {
# 0dayxx + pornditt + pornhat — USUNIĘTE CAŁKOWICIE 2026-06-22 (user request): orphan
# factories (00.2% canonical match), zastępujemy lepszymi źródłami. Dane skasowane.
# CF-protected tube — curl_cffi w fetch_tube_html bypassa JA3, embed-iframe pattern.
"perverzijacom": _embed_iframe.extract,
# perverzija — natywny HLS (2026-07-28). Player xtremestream ma w HTML tylko
# preroll/midroll (grały w pętli, bug 40ec88cc/92e9133e), a wideo dociąga runtime:
# xs1.php?data=<md5>&q=<480|720> zwraca GOTOWY manifest HLS z pełnymi URL-ami
# segmentów. Zamiast WebView oddajemy m3u8 wprost.
"perverzijacom": perverzija.extract,
# Special: WebView-only (Yii2 session-bound player).
"paradisehillcc": paradisehill.extract,
# PornDoe — dołączony 2026-05-21 (theporndude audit). Stream URL nie inline w

View file

@ -0,0 +1,72 @@
"""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 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:
log.info("perverzija: brak iframe xtremestream na %s", page_url)
return None
host, vid = m.group("host"), m.group("id")
player_url = f"https://{host}/player/index.php?data={vid}"
out: list[StreamSource] = []
for q in _QUALITIES:
out.append(
StreamSource(
link=f"https://{host}/player/xs1.php?data={vid}&q={q}",
quality=f"{q}p",
type="m3u8",
referer=player_url,
raw={"extractor": "perverzija", "player": player_url, "q": q},
)
)
return out or None