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:
parent
c18ed24330
commit
6e3ad870a7
2 changed files with 23 additions and 72 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 są 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)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue