"""supjav.com — JAV embed-aggregator. Resolve data-link → hoster (phone-side). supjav chowa realny hoster za `data-link` (hex) na przyciskach serwerów (TV/FST/ST/VOE). base.js: klik serwera → iframe `src = lk1.supremejav.com/supjav.php?l=&bg=`. supjav.php robi `OLID = data-link.reverse()` (odwrócenie stringa hex) i ładuje `supjav.php?c=`, które 302-uje na realny hoster (RE 2026-07-10): TV → turbovidhls.com, FST → fc2stream.tv, ST → streamtape.com, VOE → voe.sx Flow ekstraktora (on-demand, play time): 1. fetch detail `/.html` PRZEZ proxy (Bright Data — supjav CF-blokuje VPS IP), 2. parse `data-link` z `.btn-server`, 3. reverse hex → GET `lk1.supremejav.com/supjav.php?c=` (direct z VPS, follow 302) → finalny hoster URL, 4. zwróć type='hoster' → telefon resolwuje (dood/filemoon natywnie, reszta WebView fallback z residential IP). lk1 osiągalny z VPS bez proxy; tylko detail wymaga proxy. """ from __future__ import annotations import logging import re import curl_cffi.requests as _rq from app.config import get_settings from app.extractors import browser_get from app.extractors._fetch import _DEFAULT_IMPERSONATE from app.extractors._models import StreamSource log = logging.getLogger(__name__) _LK = "https://lk1.supremejav.com/supjav.php" _DATA_LINK_RE = re.compile(r'class="btn-server[^"]*"\s+data-link="([0-9a-f]{16,})"', re.IGNORECASE) # reklama/tracker domeny które lk1 czasem zwraca zamiast hostera — odrzucamy. _AD_RE = re.compile(r"(snaptrckr|trackwilltrk|mayzaent|eix304|doppiocdn|/ad\?)", re.IGNORECASE) def fetch_supjav_html(url: str, *, proxy: str | None, timeout: float, tries: int = 6) -> str: """Fetch supjav przez proxy z retry. Bright Data rotuje IP per-request, a CF przepuszcza tylko część IP (reszta dostaje ~6KB challenge). Retry aż trafimy IP który przechodzi (realna strona ma `data-link=`/`

` i jest duża). Zwraca '' gdy wszystkie próby padły.""" last = "" for _ in range(max(1, tries)): try: res = browser_get(url, timeout=timeout, proxy=proxy) html = res.text if hasattr(res, "text") else res except Exception: html = "" last = html or last if html and len(html) > 15000 and ("data-link=" in html or "

" in html): return html return last def _resolve_hoster(data_link: str, timeout: float) -> str | None: """reverse hex → lk1 supjav.php?c= → follow 302 → finalny hoster URL.""" olid = data_link[::-1] try: s = _rq.Session(impersonate=_DEFAULT_IMPERSONATE) r = s.get( f"{_LK}?c={olid}", headers={"Referer": "https://lk1.supremejav.com/"}, allow_redirects=True, timeout=timeout, ) final = str(r.url) r.close() except Exception as e: log.info("supjav: lk1 resolve failed (%s): %s", data_link[:12], e) return None # supjav.php bez realnego hostera zostaje na lk1 (albo leci na ad) → odrzuć. if "supremejav.com" in final or _AD_RE.search(final): return None # lk1 dokleja fragment `#supjav.com@` (metadata) — hostery go ignorują, tniemy. return final.split("#", 1)[0] def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None: proxy = get_settings().brightdata_proxy_url if not proxy: log.info("supjav: brak proxy — nie mogę pobrać CF-blokowanego detalu %s", page_url) return None html = fetch_supjav_html(page_url, proxy=proxy, timeout=timeout) if not html: log.info("supjav: detail fetch failed (CF) %s", page_url) return None data_links = list(dict.fromkeys(_DATA_LINK_RE.findall(html))) if not data_links: log.info("supjav: brak data-link na %s", page_url) return None seen: set[str] = set() out: list[StreamSource] = [] for dl in data_links: hoster = _resolve_hoster(dl, timeout=min(timeout, 40.0)) if not hoster or hoster in seen: continue seen.add(hoster) host = hoster.split("/")[2] if "://" in hoster else hoster # Label serwera bez cyfr (host "fc2stream.tv" psułby _quality_to_int → "2" jako # jakość → zły `best`). Zostawiamy skróconą, bezcyfrową nazwę do pickera. label = re.sub(r"\.[a-z]+$", "", host.split(".")[0]) if host else None label = re.sub(r"\d+", "", label).upper() if label else None out.append( StreamSource( link=hoster, type="hoster", quality=label or None, referer="https://supjav.com/", ) ) if not out: log.info("supjav: żaden data-link nie rozwiązał się na hoster (%s)", page_url) return None # voe.sx PIERWSZY (sort po URL, niezależnie od label) — telefon resolwuje go do # bezpośredniego m3u8 (voeResolver.ts, phone-IP-bound → gra direct, zero WebView). # Reszta (turbovid/fc2stream/streamtape) bez phone-side resolvera → WebView, więc niżej. out.sort(key=lambda s: 0 if "//voe." in s.link or ".voe." in s.link else 1) return out