"""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, hqporner, latestpornvideo, paradisehill, pornditt, pornhat, porntrex, sxyprn, yespornvip, ) 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]] = { # hqporner — dedicated extractor zwraca multi-quality `` mp4 URLs # (bigcdn.cc / hqwo.cc / flyflv) z `force_proxy=True`. CDN URLs IP-bound do # VPS, więc playback.py routuje przez proxy — mobile dostaje quality picker # + natywny ExoPlayer, bez WebView. # Bug-report e8ddd8d4: WebView fallback (`_vps_blocked_fallback`) ładował # hqporner.com scene page w WebView, ale ta strona ma ad-iframes (adtng, # goaserv, mavrtracktor) + pop-under-triggery → user klikał i widział # reklamę zamiast video. INJECTED_JS w PlayerScreen.tsx nie chwytał # popupów dośc szybko. Powrót do natywnego = `` mp4 picker omija # tę ścieżkę całkowicie. "hqpornercom": hqporner.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. # pornhub — 2026-06-02: yt-dlp z VPS dostaje HTTP 403 (Pornhub blokuje Hetzner IP; # yt-dlp aktualny, inne yt-dlp tuby działają → blok specyficzny dla PH). WebView # fallback gra z residential IP telefonu (jak xhamster). Wcześniej `_ytdlp.extract` # zwracał 0 źródeł → "nie działa odtwarzanie". "pornhubcom": _vps_blocked_fallback.extract, "redtubecom": _ytdlp.extract, "xvideoscom": _ytdlp.extract, "xnxxcom": _ytdlp.extract, "youporncom": _ytdlp.extract, # porntrex KVS — 2026-05-22 VPS znów dociera (HTTP 200). Dedykowany extractor: # flashvars `video_url` → `get_file` 302 → CDN time-bound signed URL # (`expires`+`md5`, NIE IP-bound) → mobile gra direct, zero VPS bandwidth. "porntrexcom": porntrex.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