fix(freshporno): backend KVS resolve (portable CDN) — corrects #20

Re-investigated with the proper method (Chrome DevTools network capture + cross-IP
test via Bright Data residential proxy + curl_cffi browser-TLS) instead of guessing.
freshporno's real flow is get_file -> 302 -> cdn4.freshporno.org/remote_control.php
-> 206 video/mp4. The CDN URL is PORTABLE cross-IP (a token generated from one
residential IP replays fine from the VPS and from a different Bright Data residential
IP), it only rejects non-browser TLS fingerprints (plain curl -> 000, curl_cffi
chrome / ExoPlayer -> 206).

In #20 I tested the final URL with a standalone plain curl, got 000, and wrongly
concluded "unreachable from residential" -> kept it on the WebView fallback, which
barely worked (ad-heavy page, flaky). That false negative is the regression the user
reported. freshporno is function/0 KVS, so _kvs.resolve_kvs (which uses curl_cffi
chrome) already decodes + resolves it to a portable mp4 — switch to backend resolve
like yespornvip/pornditt: native, multi-quality, no proxy, no WebView.

Verified: backend resolve returns 3x mp4 (1080/720/480, mobile_direct) + cdn 206;
user confirmed native playback on device.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jtrzupek 2026-06-05 21:12:17 +02:00
parent c18ed24330
commit 6e3ad870a7
2 changed files with 23 additions and 72 deletions

View file

@ -28,6 +28,7 @@ from app.extractors.tubes import (
_vps_blocked_fallback,
_ytdlp,
eporner,
freshporno,
hqporner,
latestpornvideo,
paradisehill,
@ -107,17 +108,14 @@ _REGISTRY: dict[str, Callable[[str], list[StreamSource] | None]] = {
# trailer URLs `_preview*.mp4`), dedupe po filename. Get_file 302 → CDN, proxy
# follow_redirects=True wymagane (fix w stream_proxy.py).
"pornhatcom": pornhat.extract,
# Freshporno KVS — `cv=` HMAC signed token IP-bound do VPS. WebView fallback:
# mobile fetchuje embed z phone IP, KVS player JS dekoduje video_url, ExoPlayer
# odtwarza direct z CDN. UX trade-off (page flicker przed video) vs bandwidth/
# anonimowość — public-app priorytet → WebView wygrywa.
# (2026-05-28: krótko-żywy switch na freshporno.extract z force_proxy=True
# cofnięty po feedbacku Jana "video proxy mnie nie interesuje, idziemy
# publicznie".)
# 2026-06-01 (task #20): _kvs.resolve_kvs dekoduje function/0 OK, ale finalny
# cdn2.freshporno.org/remote_control.php?cv=... jest nieosiągalny z residential
# IP (connect 000) → backend-resolve bezużyteczny. WebView pozostaje.
"freshpornoorg": _vps_blocked_fallback.extract,
# Freshporno KVS (function/0 + license). 2026-06-04 DevTools + cross-IP re-test
# NAPRAWIA błąd z #20: finalny cdn4.freshporno.org/remote_control.php jest PORTABLE
# (token time-bound nie IP-bound — VPS odtworzył token z residential → 206) ale
# wymaga browser-TLS (curl_cffi chrome/ExoPlayer → 206; plain curl → 000). W #20
# testowałem plain-curl-em poza sesją → 000 → błędnie „nieosiągalny" → WebView.
# Teraz backend-resolve jak yespornvip/pornditt (_kvs używa curl_cffi chrome).
# Native, multi-quality, zero proxy/WebView. (zweryfikowane na emulatorze przed deploy)
"freshpornoorg": freshporno.extract,
# porn00 — KVS engine. 2026-06-01 cross-IP re-test (task #20): get_file 302 →
# `fe.porn00.org/videos/.../<id>.mp4?token=&expires=` zwraca 403 z residential
# IP → token IP-bound do resolvera (VPS), NIE portable jak yespornvip/pornditt.

View file

@ -1,71 +1,24 @@
"""freshporno.org — KVS engine, BEZ `<source>` tagów.
"""freshporno.org — KVS (kt_player) direct stream extractor. Patrz app/extractors/tubes/_kvs.py.
Page używa kt_player (KVS Flash + JS legacy player) URLs wewnątrz JavaScript
flashvars JSON (`video_url: 'function/0/<URL>'`) i w `<a href="...?download=true">`
linkach z labelem "MP4 720p" / "MP4 480p".
Flashvars `video_url`/`video_alt_url`/`video_alt_url2` = `function/0/...get_file/...` +
`license_code` (silnik identyczny z yespornvip/pornditt). Resolve server-side: decode +
follow 302 `cdn4.freshporno.org/remote_control.php?time=&cv=...` (206 video/mp4).
Bierzemy anchor pattern bo ma WSZYSTKIE quality z explicit labelem (vs flashvars
ma tylko main+alt, max 2 jakości). `<a href="...get_file/...mp4/?download=true...">MP4 <q>p, ...`
Sidebar suggested videos używają `data-preview="...get_file/.../<id>_preview.mp4"`
inny pattern (nie `<a href>`), więc anchor regex je naturalnie pomija.
CDN token IP-bound do VPS mobile dostanie 403 na direct, fallback proxy działa.
get_file 302 `cdn4.freshporno.org/remote_control.php?...&file=<path>` direct mp4
(nie HLS). Type='mp4'.
2026-06-04 (DevTools + cross-IP re-test, naprawia błędny wniosek z #20): finalny CDN url
jest **portable cross-IP** (token time-bound, NIE IP-bound VPS odtworzył token
wygenerowany z residential IP 206) ale wymaga **browser-podobnego TLS** (curl_cffi
chrome / ExoPlayer/okhttp 206; plain curl connection 000). W #20 testowałem finalny
URL plain-curl-em poza sesją 000 błędnie uznałem nieosiągalny z residential" i
zostawiłem na WebView. _kvs.resolve_kvs używa curl_cffi chrome impersonation, więc dociera
do cdn4; mobile (ExoPlayer) gra direct. Native, multi-quality, zero WebView/proxy/reklam.
"""
from __future__ import annotations
import logging
import re
from app.extractors._fetch import fetch_tube_html
from app.extractors._models import StreamSource
from app.extractors.tubes import _kvs
log = logging.getLogger(__name__)
# `<a href="<URL>?download=true...">MP4 <quality>p, <size>` — main + alt streams.
_ANCHOR_QUALITY_RE = re.compile(
r'<a\s+[^>]*href="(?P<url>https?://[^"]+/get_file/[^"]+\.mp4/)\?download=true[^"]*"'
r'[^>]*>\s*MP4\s+(?P<q>\d{3,4}p)',
re.IGNORECASE,
)
_BASE = "https://freshporno.org"
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
html = fetch_tube_html(page_url, timeout=timeout)
seen_keys: set[str] = set()
result: list[StreamSource] = []
for m in _ANCHOR_QUALITY_RE.finditer(html):
url = m.group("url")
quality = m.group("q")
# Dedupe po basename (path bez query string).
basename = url.rstrip("/").split("/")[-1]
if basename in seen_keys:
continue
seen_keys.add(basename)
# `force_proxy=True` (2026-05-20): freshporno get_file 302 → cdn4.freshporno.org
# IP-bound (cv= HMAC token). Mobile direct = 403/SSL fail → fallback proxy
# generuje "mrugnięcie" (user bug 743eefbf "najpierw strona potem video").
# Force_proxy wymusza mobile użycie proxied URL od razu — bez flickera +
# natywny ExoPlayer + quality picker zachowane.
result.append(StreamSource(
link=url, type="mp4", quality=quality,
raw={"force_proxy": True},
))
if not result:
log.info("freshporno: no MP4 anchor matches on %s", page_url)
return None
def _quality_key(s: StreamSource) -> int:
if not s.quality:
return -1
try:
return int(s.quality.rstrip("p"))
except ValueError:
return -1
result.sort(key=_quality_key, reverse=True)
return result
return _kvs.resolve_kvs(page_url, base_url=_BASE, timeout=timeout)