"""xanimeporn.com — mp4 z endpointu pobierania. Dodany 2026-08-04. Player to Fluid Player z **pustym ``** — źródło wstrzykiwane jest w runtime, a skrypt inicjalizujący obsługuje wyłącznie fullscreen i analitykę. W statycznym HTML nie ma ani jednego linku do wideo, więc wygląda to na ślepy zaułek. Wyjściem jest zakładka „Downloads", która niesie: https://videos.xanimeporn.com/download.php?id=&quality=1080p https://videos.xanimeporn.com/download.php?id=&quality=720p `postId` siedzi w klasie `` (`postid-69951`), więc nie trzeba parsować zakładki — składamy URL sami dla obu jakości. **Oddajemy `download.php`, a NIE docelowego pliku.** Endpoint przekierowuje na `download.xanimeporn.com/...?s=&e=`, czyli URL z tokenem i wygasaniem. Zwracając `download.php` zostawiamy generowanie tokenu na moment odtwarzania: telefon idzie za przekierowaniem i dostaje świeży podpis, więc nic nie wietrzeje. To ta sama lekcja co przy watchporn, gdzie zapisany statyczny URL po czasie padał. Zweryfikowane z VPS-a: 1080p 222 MB i 720p 84 MB, oba 206 na Range z `ftypisom`/`ftypmp42` w pierwszym KB (faststart). Wymaga Referera i UA, oba dokłada `playback.py`. Bez Cloudflare i bez proxy, w odróżnieniu od hentaimamy. """ from __future__ import annotations import logging import re from app.extractors._fetch import fetch_tube_html from app.extractors._models import StreamSource log = logging.getLogger(__name__) _BASE = "https://xanimeporn.com" _POST_ID_RE = re.compile(r"\bpostid-(\d+)\b") #: Serwis publikuje dokładnie te dwie jakości; kolejność = preferencja. _QUALITIES = ("1080p", "720p") def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None: html_text = fetch_tube_html(page_url, timeout=timeout) m = _POST_ID_RE.search(html_text) if not m: log.info("xanimeporn: brak postid w klasie body na %s", page_url) return None post_id = m.group(1) return [ StreamSource( link=f"https://videos.xanimeporn.com/download.php?id={post_id}&quality={q}", type="mp4", quality=q, referer=_BASE + "/", # Zweryfikowane 206 + faststart z VPS-a; telefon sam podąża za 302 i # dostaje własny, świeży token. raw={"mobile_direct_ok": True}, ) for q in _QUALITIES ]