4k69.com (~65k scenes): same PlayTube CMS as hqfap - common logic moved to _playtube.py (sitemap catalog, JSON-LD, pills). Studio classified by matching category pills against the studios index page. Streams are get_file (fullmovies family) returned unresolved with mobile_direct, 2160p skipped. neporn.com: KVS engine, latest-updates listing, JSON-LD + video:duration meta, performers from models links with flashvars video_tags fallback for fresh uploads. Resolve via _kvs; final URL portable cross-IP. superporn.com rejected: Cloudflare 403 from VPS on all TLS impersonations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
56 lines
2 KiB
Python
56 lines
2 KiB
Python
"""4k69.com — get_file stream extractor (platforma jak fullmovies/hdporngg).
|
|
|
|
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).
|
|
|
|
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).
|
|
"""
|
|
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__)
|
|
|
|
_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)$")
|
|
|
|
|
|
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 _GET_FILE_RE.finditer(html):
|
|
url = m.group(0)
|
|
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:
|
|
continue
|
|
out.append(StreamSource(
|
|
link=url,
|
|
quality=f"{quality_num}p",
|
|
type="mp4",
|
|
referer="https://4k69.com/",
|
|
raw={"mobile_direct_ok": True},
|
|
))
|
|
|
|
if not out:
|
|
log.info("4k69: no get_file URLs on %s", page_url)
|
|
return None
|
|
out.sort(key=lambda s: int((s.quality or "0p")[:-1]), reverse=True)
|
|
return out
|