"""Współdzielony resolver dla tubów z `` + IP-bound CDN (hdporn.gg, fullmovies.xxx, ta sama platforma, `/get_file/8512/`). 2026-06-04 (DevTools + cross-IP investigation, naprawia „hdporngg loading forever"): get_file 302-redirectuje do `fpvcdn.com` z **IP fetchera wbitym w URL** (`ip=`), więc finalny CDN jest IP-bound do tego kto resolvuje. Dlatego oddajemy get_file URL **NIEZRESOLWOWANY** (mobile_direct), ExoPlayer na telefonie sam follow-uje 302, fpvcdn bindje się do IP telefonu, gra. (Resolve na VPS → bind do IP VPS → mobile 403.) Zweryfikowane: get_file jest STATELESS (świeża sesja działa) + ważny ≥90s, więc telefon ma czas (resolve→picker→tap = sekundy). Źródło **2160p/4K konsekwentnie time-outuje na fpvcdn (~30s)** → POMIJAMY je (to była przyczyna „loading forever", player ładował 4K pierwsze); 720p/480p/1080p resolvują w ~1s. """ from __future__ import annotations import logging import re from app.extractors._fetch import _DEFAULT_IMPERSONATE, _DEFAULT_UA, _HAS_CURL_CFFI from app.extractors._models import StreamSource log = logging.getLogger(__name__) _SOURCE_RE = re.compile( r"]*?(?:title|label)=['\"]?([^'\">]*)", re.IGNORECASE, ) # fpvcdn nie serwuje 4K (30s timeout), skip żeby player nie wisiał na nim. _SKIP_QUALITY_RE = re.compile(r"2160|1440|4k", re.IGNORECASE) def resolve( page_url: str, base_url: str, *, timeout: float = 30.0, sitetag: str | None = None ) -> list[StreamSource] | None: if not _HAS_CURL_CFFI: log.info("source_getfile: curl_cffi unavailable — %s", page_url) return None from curl_cffi import requests as cf try: html = cf.get( page_url, impersonate=_DEFAULT_IMPERSONATE, headers={"User-Agent": _DEFAULT_UA, "Accept": "text/html,application/xhtml+xml"}, timeout=timeout, ).text except Exception as e: log.info("source_getfile: page fetch failed %s: %s", page_url, e) return None seen: set[str] = set() out: list[StreamSource] = [] for m in _SOURCE_RE.finditer(html): url = m.group(1).strip() quality = (m.group(2) or "").strip() if url.startswith("//"): url = "https:" + url if url in seen: continue seen.add(url) if _SKIP_QUALITY_RE.search(quality): log.info("source_getfile: skip broken-CDN quality %r on %s", quality, page_url) continue raw: dict = {"mobile_direct_ok": True} if sitetag: # get_file jest przypiety do IP tego, kto zaladowal strone, i bywa jednorazowy, # wiec proxy (zapas, gdy resolver na telefonie zawiedzie) po 403/410 pobiera # strone na nowo zamiast oddac blad (stream_proxy._fresh_tube_link, 2026-10-02). raw["refetch_url"] = page_url raw["refetch_hoster"] = f"tube:{sitetag}" out.append(StreamSource( link=url, type="mp4", quality=quality or None, referer=base_url + "/", raw=raw, )) if not out: log.info("source_getfile: no playable /get_file on %s", page_url) return None def _rank(s: StreamSource) -> int: mm = re.search(r"(\d{3,4})", s.quality or "") return int(mm.group(1)) if mm else -1 out.sort(key=_rank, reverse=True) return out