"""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, porntrex, 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]] = { # hqporner — CDN URLs IP-bound do VPS, force_proxy wymusza ruch przez VPS proxy. # 2026-05-20 (pre-public): bandwidth + anonimowość VPS > UX. Switch na WebView # fallback — mobile pobiera embed iframe z phone IP, FluidPlayer JS decoduje # mp4, ExoPlayer odtwarza direct z phone CDN session. **0 VPS bandwidth + VPS # IP nie ujawniony** (mobile nie łączy się z VPS proxy URL). # Trade-off: WebView ma 1 extra step (page → player JS) ale bez popup-ads jak # hqporner.com bo INJECTED_JS w PlayerScreen.tsx blokuje + scrape `.src`. "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 — 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