Some checks failed
Backend tests / test (push) Has been cancelled
Fourth JAV vertical source (origin tube:supjav, gated to JAV tab via JAV_ORIGINS). Browse: CF-blocks datacenter IPs, so listing goes through the Bright Data ISP proxy with retry (the proxy rotates IPs and CF only lets some through). Homepage-latest only (~66 newest); deeper /page/N/ stays CF-403 even via proxy. Metadata parsed from the listing cards (title, thumbnail, JAV code, year/month) rather than per-post details, which are flaky and slow through the proxy. Stream: supjav hides the real hoster behind a per-server data-link (hex). base.js loads lk1.supremejav.com/supjav.php?l=<data-link>, which reverses the hex string and fetches ?c=<reversed>, 302-ing to the hoster (TV->turbovid, FST->fc2stream, ST->streamtape, VOE->voe). The extractor reproduces that: fetch detail via proxy, reverse each data-link, resolve through lk1 (reachable direct from the server), return the hosters as type=hoster so the phone resolves them (dood/filemoon native, the rest via the WebView fallback on the residential IP). lk1 needs no proxy; only the detail fetch does. Backend-only, no mobile change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
112 lines
4.4 KiB
Python
112 lines
4.4 KiB
Python
"""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=<data-link>&bg=`.
|
|
supjav.php robi `OLID = data-link.reverse()` (odwrócenie stringa hex) i ładuje
|
|
`supjav.php?c=<OLID>`, 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 `/<id>.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=<rev>` (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=`/`<h1>` 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 "<h1>" 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@<code>` (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
|
|
out.append(
|
|
StreamSource(
|
|
link=hoster,
|
|
type="hoster",
|
|
quality=host,
|
|
referer="https://supjav.com/",
|
|
)
|
|
)
|
|
if not out:
|
|
log.info("supjav: żaden data-link nie rozwiązał się na hoster (%s)", page_url)
|
|
return None
|
|
return out
|