Reverse-engineered the migrated 4k69 player: jwplayer now serves OK.ru CDN (okcdn.ru) mp4s. The static page (SSR behind Cloudflare, fetched via proxy) carries "file"+"label" pairs for every quality. okcdn's srcIp param is NOT enforced (cross-IP test 2026-06-14: 206 video/mp4 from a residential IP != srcIp), so the URL plays from any IP. Parse the okcdn sources server-side and return them mobile_direct_ok — the phone plays the direct video, no WebView, no VAST preroll, no age-gate, zero VPS proxy. Skips 4K/2K. Reverts the brief _vps_blocked_fallback routing (WebView grabbed the preroll ad, not content). Verified on emulator: native player streams the actual scene (report 5de3fbc5). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
68 lines
2.5 KiB
Python
68 lines
2.5 KiB
Python
"""4k69.com — okcdn.ru (OK.ru CDN) direct stream extractor.
|
|
|
|
2026-06-14: 4k69 zmigrowało player z get_file (4kporno.xxx) na jwplayer + okcdn.ru
|
|
(OK.ru video CDN). Strona (SSR za Cloudflare → curl_cffi/proxy) ma w inline jwplayer
|
|
setupie pary `"file": "<okcdn url>", "label": "<jakość>"` na WSZYSTKIE jakości
|
|
(4K/2K/1080p/720p/480p/360p/240p). To samo w LD-JSON `contentUrl` (jeden, niższy).
|
|
|
|
okcdn URL ma `expires=` (time-bound), `srcIp=` (IP edge Cloudflare który frontował
|
|
fetch) i `sig=` per jakość. KLUCZOWE (reverse-engineer + cross-IP test 2026-06-14):
|
|
`srcIp` NIE jest egzekwowane — URL gra z dowolnego IP (206 video/mp4 z residential IP
|
|
≠ srcIp). Więc resolwujemy server-side i oddajemy `mobile_direct_ok` → telefon gra
|
|
DIRECT, zero VPS proxy, zero WebView/reklam (VAST preroll jest runtime-only, nie ma go
|
|
w statycznym HTML, więc parsując HTML omijamy go całkiem).
|
|
|
|
Pomijamy 4K/2K (jak wcześniej 2160/1440 — za duże na mobile).
|
|
"""
|
|
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__)
|
|
|
|
# Pary file+label z jwplayer setupu: "file":"<okcdn>","label":"1080p". Label bierzemy
|
|
# wprost ze strony (pewniejsze niż mapowanie OK.ru type=N).
|
|
_OKCDN_FILE_RE = re.compile(
|
|
r'"file"\s*:\s*"(https?://[^"]*okcdn[^"]+)"\s*,\s*"label"\s*:\s*"([^"]+)"',
|
|
re.IGNORECASE,
|
|
)
|
|
# Za duże na mobile (jak stary skip 2160/1440).
|
|
_SKIP_LABEL_RE = re.compile(r"^(4k|2k|2160|1440)", re.IGNORECASE)
|
|
|
|
|
|
def _quality_num(label: str) -> int:
|
|
m = re.match(r"(\d+)", label or "")
|
|
return int(m.group(1)) if m else 0
|
|
|
|
|
|
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
|
|
html = fetch_tube_html(page_url, timeout=timeout)
|
|
|
|
seen: set[str] = set()
|
|
out: list[StreamSource] = []
|
|
for m in _OKCDN_FILE_RE.finditer(html):
|
|
url = m.group(1).replace("&", "&")
|
|
label = m.group(2).strip()
|
|
if url in seen:
|
|
continue
|
|
seen.add(url)
|
|
if _SKIP_LABEL_RE.match(label):
|
|
continue
|
|
out.append(StreamSource(
|
|
link=url,
|
|
quality=label,
|
|
type="mp4",
|
|
referer="https://4k69.com/",
|
|
# srcIp nieegzekwowane (cross-IP test 2026-06-14) → telefon gra direct.
|
|
raw={"mobile_direct_ok": True},
|
|
))
|
|
|
|
if not out:
|
|
log.info("4k69: no okcdn sources on %s", page_url)
|
|
return None
|
|
out.sort(key=lambda s: _quality_num(s.quality or ""), reverse=True)
|
|
return out
|