"""vjav.com, TXXX-network JAV tube. HLS stream extractor. vjav to SPA na silniku TXXX (ktk_player + MSE/blob src). Stream NIE jest w HTML strony (player renderuje się JS-owo). Zamiast tego AJAX endpoint zwraca zaciemniony URL pliku: GET /api/videofile.php?video_id=&lifetime=8640000 -> [{"format":"_hq.mp4","video_url":"","is_default":1, ...}] `video_url` to base64 w DWÓCH warstwach zaciemnienia: (1) wielkie łacińskie litery podmienione na cyrylicę-homoglif (М->M, С->C, А->A, Е->E), (2) custom alfabet base64 gdzie `,`->`/`, `~`->`=`, `-`->`+`. Po odkręceniu obu + b64decode dostajemy get_file: # stare wideo (shared txxx pool), URL absolutny: https://videotxxx.com/ext/get_file/9////_hq.mp4/?d=..&br=..&ti=.. # nowe wideo (własna infra vjav, server N), URL RELATYWNY (prepend https://vjav.com): /get_file/3////_hq.mp4/?d=..&br=..&ti=.. Dopisanie `&f=video.m3u8` -> 302 chain -> finalny HLS na *.ahcdn.com (`key=..,end=..,limit=3`, time-bound, NIE IP-bound, `referer=none` na whiteliscie -> portable, gra z residential IP telefonu bez sesji vjav). Zwracamy get_file (`?f=video.m3u8`) jako type='m3u8' + `mobile_direct_ok` -> playback.py owija w `/proxy/hls//play.m3u8` (passthrough manifestu ~1KB przez VPS, segmenty direct z telefonu; patrz stream_proxy.proxy_hls_manifest). Media id w get_file (318577) != page id (5197), dlatego MUSIMY przejść przez videofile.php. RE 2026-07-10. Wyzwanie 429 (ciasteczko w ciele odpowiedzi, od 2026-08-21) i sam tor są w `_txxx.extract_hls`, wspólnym z tubepornclassic. """ from __future__ import annotations import logging import re from app.extractors._models import StreamSource from app.extractors.tubes import _txxx log = logging.getLogger(__name__) _BASE = "https://vjav.com" _VIDEO_ID_RE = re.compile(r"/videos/(\d+)/") # Dekoder i cały tor videofile.php → get_file → HLS siedzą w `_txxx.py`, bo od 2026-09-07 # ten sam silnik obsługuje też tubepornclassic. Alias zachowany dla ewentualnych importów. _decode_video_url = _txxx.decode_video_url def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None: m = _VIDEO_ID_RE.search(page_url) if not m: log.info("vjav: brak video id w %s", page_url) return None return _txxx.extract_hls(_BASE, page_url, m.group(1), tag="vjav", timeout=timeout)