"""Stream URL extractors per-tube. Public API: - `try_extract(sitetag, page_url) -> list[StreamSource] | None` - `StreamSource` (dataclass) - `HosterDead` (exception) - `extract_stream_from_hoster(iframe_url, *, referer)` — generic packer-based hoster extract - `fetch_tube_html(url)` — Chrome TLS fingerprint fetch (curl_cffi) - `browser_get(url)` — low-level Architektura: każdy tube ma osobny moduł `app.extractors.tubes.` który eksportuje `extract(page_url) -> list[StreamSource] | None`. Registry niżej mapuje sitetag → modułowy extractor. `try_extract()` to thin wrapper z exception handlingiem. Po removalu porn-app dependency, ten moduł jest jedynym mechanizmem rozwiązywania streamów — playback.py nie wpada już do porn-app /stream API. """ from __future__ import annotations import logging from collections.abc import Callable from app.extractors._fetch import browser_get, fetch_tube_html from app.extractors._models import HosterDead, StreamSource, TubePageError from app.extractors.hoster import extract_stream_from_hoster, unpack_packer from app.extractors.tubes import ( _embed_iframe, _vps_blocked_fallback, _ytdlp, eporner, freshporno, hqporner, latestpornvideo, paradisehill, porn00, pornhat, pornxp, sxyprn, ) log = logging.getLogger(__name__) # Sitetag → extractor function. Sitetag pasuje do format'u z origin: `pornapp:` # (lub po Fazie 2 migracji: `tube:`). # # Mainstream tubes (pornhub/xvideos/xnxx/xhamster/redtube/youporn/porntrex) używają # yt-dlp jako extractor — battle-tested, aktualizowane przez upstream przy zmianach # HTML. Aggregator tubes (xmoviesforyou/watchporn/siska/...) używają generic # embed-iframe extractor (page → /e/ iframe → P.A.C.K.E.R. unpack). Custom kod # tylko tam gdzie tube ma niestandardowy schemat (eporner XHR, sxyprn URL transform). _REGISTRY: dict[str, Callable[[str], list[StreamSource] | None]] = { # Custom (zoptymalizowane / niestandardowy player) # hqporner — CDN URL (bigcdn.cc, video.flyflv.com z `ip=` parametrem) IP-bound do # requestera. VPS resolve daje 200 ale mobile direct = 404/403. Switch na WebView # fallback: mobile pobiera embed iframe (mydaddy.cc/hqwo.cc) z phone IP, FluidPlayer # JS decoduje mp4 URL z mobile session. Plus INJECTED_JS skanuje `.src`. # ~32k scen (drugi po porntrex największy single saving). Verified 2026-05-18. "hqpornercom": _vps_blocked_fallback.extract, "epornercom": eporner.extract, "sxyprncom": sxyprn.extract, # Mainstream tubes — yt-dlp # NB: 2026-05-18 cross-IP test potwierdził że xvideos/xnxx/pornhub/youporn/redtube # CDN URLs są **time-bound** (nie IP-bound) — mobile_direct_ok auto-detect w # playback.py daje mobile direct fetch, zero VPS bandwidth. "pornhubcom": _ytdlp.extract, "redtubecom": _ytdlp.extract, "xvideoscom": _ytdlp.extract, "xnxxcom": _ytdlp.extract, "youporncom": _ytdlp.extract, # porntrex KVS get_file — `kt_ips=` cookie + single-use token (410 po reuse). # CDN IP-bound do VPS, mobile direct = 403. Switch na _vps_blocked_fallback: # mobile WebView z phone IP → KVS player JS dekoduje video.src → INJECTED_JS scrape. # 137k scen oszczędzone z VPS bandwidth (largest single saving). "porntrexcom": _vps_blocked_fallback.extract, # VPS-blocked tubes — KVS / Cloudflare blokuje Hetzner IP, ale działają z residential # IP (potwierdzone Chrome DevTools MCP 2026-05-15). Mobile WebView + INJECTED_JS # (PlayerScreen.tsx:805) skanuje