fix(extractors): 4k69 direct okcdn extraction (replaces WebView fallback)
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>
This commit is contained in:
parent
2a9445fe4a
commit
81d617efc2
2 changed files with 40 additions and 30 deletions
|
|
@ -181,14 +181,12 @@ _REGISTRY: dict[str, Callable[[str], list[StreamSource] | None]] = {
|
|||
# Cross-IP test 2026-06-10: oba CDN-y portable (`ip=`/`srcIp=` nie egzekwowane),
|
||||
# tokeny time-bound → on-demand fetch daje świeży URL. Mobile direct, zero proxy.
|
||||
"hqfapcom": hqfap.extract,
|
||||
# 4k69 — 2026-06-14 PRZEPIĘTE na _vps_blocked_fallback (WebView). Strona zmigrowała
|
||||
# player z get_file (4kporno.xxx) na jwplayer + okcdn.ru z `srcIp=` w tokenie =
|
||||
# IP-bound; plus 4k69 jest za Cloudflare (VPS fetch tylko przez proxy). Native
|
||||
# extractor (get_file regex) zwracał None → "host problem" (zgłoszenie 5de3fbc5).
|
||||
# WebView na telefonie: residential IP przechodzi CF, okcdn token bound do IP
|
||||
# telefonu, INJECTED_JS łapie jwplayer video.src → ExoPlayer gra. fourk69.extract
|
||||
# zostaje w module gdyby strona wróciła do get_file.
|
||||
"4k69com": _vps_blocked_fallback.extract,
|
||||
# 4k69 — 2026-06-14 player zmigrowany na jwplayer + okcdn.ru (OK.ru CDN). Natywny
|
||||
# fourk69.extract parsuje okcdn `file`+`label` ze strony (SSR za CF → proxy). okcdn
|
||||
# srcIp NIE egzekwowane (cross-IP test) → mobile_direct_ok, telefon gra direct.
|
||||
# Pełny reverse-engineer w fourk69.py (zgłoszenie 5de3fbc5). [Krótko był na
|
||||
# _vps_blocked_fallback/WebView, ale to łapało VAST preroll zamiast contentu.]
|
||||
"4k69com": fourk69.extract,
|
||||
# neporn — KVS function/0 + license (jak freshporno). Server-side _kvs resolve →
|
||||
# data001.neporn.com/remote_control.php portable (cross-IP 206, 2026-06-10).
|
||||
"neporncom": neporn.extract,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
"""4k69.com — get_file stream extractor (platforma jak fullmovies/hdporngg).
|
||||
"""4k69.com — okcdn.ru (OK.ru CDN) direct stream extractor.
|
||||
|
||||
Scene page (SSR za Cloudflare → curl_cffi) ma 3 get_file URL-e na www.4kporno.xxx
|
||||
(`..._2160m.mp4` / `_720m` / `_480m`) — w JSON-LD contentUrl i w JS playera, NIE
|
||||
w `<source>` tagach (dlatego nie _source_getfile, tylko skan całej strony).
|
||||
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).
|
||||
|
||||
Jak fpvcdn (fullmovies, ta sama rodzina `/get_file/8512/`): get_file binduje CDN
|
||||
do IP fetchera, jest stateless i ważny ≥90s → oddajemy NIEZRESOLWOWANE z
|
||||
mobile_direct_ok — telefon follow-uje 302 z własnym IP (cross-IP test 2026-06-10:
|
||||
lokalny ISP 206 video/mp4). 2160p pomijamy (CDN time-out ~30s, jak fpvcdn).
|
||||
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
|
||||
|
||||
|
|
@ -19,9 +24,19 @@ from app.extractors._models import StreamSource
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_GET_FILE_RE = re.compile(r"https://[a-z0-9.\-]+/get_file/[^\s\"'\\]+\.mp4/?", re.IGNORECASE)
|
||||
_QUALITY_RE = re.compile(r"_(\d{3,4})[mp]?\.mp4", re.IGNORECASE)
|
||||
_SKIP_QUALITY_RE = re.compile(r"^(2160|1440)$")
|
||||
# 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:
|
||||
|
|
@ -29,28 +44,25 @@ def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | Non
|
|||
|
||||
seen: set[str] = set()
|
||||
out: list[StreamSource] = []
|
||||
for m in _GET_FILE_RE.finditer(html):
|
||||
url = m.group(0)
|
||||
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)
|
||||
qm = _QUALITY_RE.search(url)
|
||||
quality_num = qm.group(1) if qm else None
|
||||
if quality_num and _SKIP_QUALITY_RE.match(quality_num):
|
||||
continue
|
||||
# `_preview.mp4` itp. bez liczby jakości — pomiń (trailer, nie scena).
|
||||
if not quality_num:
|
||||
if _SKIP_LABEL_RE.match(label):
|
||||
continue
|
||||
out.append(StreamSource(
|
||||
link=url,
|
||||
quality=f"{quality_num}p",
|
||||
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 get_file URLs on %s", page_url)
|
||||
log.info("4k69: no okcdn sources on %s", page_url)
|
||||
return None
|
||||
out.sort(key=lambda s: int((s.quality or "0p")[:-1]), reverse=True)
|
||||
out.sort(key=lambda s: _quality_num(s.quality or ""), reverse=True)
|
||||
return out
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue