"""yesporn.vip — KVS engine direct stream extractor. User bug-report 2026-05-27: "Yespornvip dalej nie działa". Origin `tube:yespornvip` istniał w playback_sources ale brak wpisu w `_REGISTRY` → `try_extract()` zwracał None → mobile player no-source. Detail page sceny linkuje do `/embed/` w iframe. Embed page renderuje KVS player z `flashvars`: - `video_url: 'function/0/https://yesporn.vip/get_file/////.mp4/?embed=true'` - `event_reporting2: 'https://yesporn.vip/get_file/.../.mp4/'` (analytics ping URL, ale jest valid get_file) - `video_url_text: '480p'` — quality label dla video_url `function/0/` to KVS player JS dekoder prefix — dla type 0 to passthrough, URL po prefixie jest bezpośrednio użyteczny. Single-quality (480p) z embed bo wyższe (`video_alt_url`) to redirect URLs (`video_alt_url_redirect: '1'`), nie direct streamy. CDN time-bound signed, mobile gra direct. """ from __future__ import annotations import logging import re from app.extractors._fetch import fetch_tube_html from app.extractors._models import StreamSource log = logging.getLogger(__name__) _BASE = "https://yesporn.vip" # `video_url: 'function/0/https://.../get_file/...mp4/?embed=true'` lub bez prefixu _VIDEO_URL_RE = re.compile( r"video_url\s*:\s*'(?:function/0/)?(https?://[^']+/get_file/[^']+\.mp4/?[^']*)'", re.IGNORECASE, ) _QUALITY_RE = re.compile(r"video_url_text\s*:\s*'([^']*)'", re.IGNORECASE) def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None: if "/embed/" not in page_url: # Detail page → derive embed URL via /video/// → /embed/. m = re.search(r"/video/(\d+)/", page_url) if m: embed_url = f"{_BASE}/embed/{m.group(1)}" else: log.info("yespornvip: cannot derive embed from %s", page_url) return None else: embed_url = page_url html = fetch_tube_html(embed_url, timeout=timeout) m = _VIDEO_URL_RE.search(html) if not m: log.info("yespornvip: no video_url in flashvars on %s", embed_url) return None url = m.group(1) # `?embed=true` parametr — get_file z embed=true może zwracać HTML wrapper # zamiast 302 do CDN. Zostawiamy bo player tak go używa, ale jeśli 302 nie # wskoczy poprawnie to fallback usunie param. quality = None q_match = _QUALITY_RE.search(html) if q_match: quality = q_match.group(1).strip() or None return [ StreamSource( link=url, type="mp4", quality=quality, referer=_BASE + "/", raw={"mobile_direct_ok": True}, ) ]