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,
|
_vps_blocked_fallback,
|
||||||
_ytdlp,
|
_ytdlp,
|
||||||
eporner,
|
eporner,
|
||||||
|
freshporno,
|
||||||
hqporner,
|
hqporner,
|
||||||
latestpornvideo,
|
latestpornvideo,
|
||||||
paradisehill,
|
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
|
# trailer URLs `_preview*.mp4`), dedupe po filename. Get_file 302 → CDN, proxy
|
||||||
# follow_redirects=True wymagane (fix w stream_proxy.py).
|
# follow_redirects=True wymagane (fix w stream_proxy.py).
|
||||||
"pornhatcom": pornhat.extract,
|
"pornhatcom": pornhat.extract,
|
||||||
# Freshporno KVS — `cv=` HMAC signed token IP-bound do VPS. WebView fallback:
|
# Freshporno KVS (function/0 + license). 2026-06-04 DevTools + cross-IP re-test
|
||||||
# mobile fetchuje embed z phone IP, KVS player JS dekoduje video_url, ExoPlayer
|
# NAPRAWIA błąd z #20: finalny cdn4.freshporno.org/remote_control.php jest PORTABLE
|
||||||
# odtwarza direct z CDN. UX trade-off (page flicker przed video) vs bandwidth/
|
# (token time-bound nie IP-bound — VPS odtworzył token z residential → 206) ale
|
||||||
# anonimowość — public-app priorytet → WebView wygrywa.
|
# wymaga browser-TLS (curl_cffi chrome/ExoPlayer → 206; plain curl → 000). W #20
|
||||||
# (2026-05-28: krótko-żywy switch na freshporno.extract z force_proxy=True
|
# testowałem plain-curl-em poza sesją → 000 → błędnie „nieosiągalny" → WebView.
|
||||||
# cofnięty po feedbacku Jana "video proxy mnie nie interesuje, idziemy
|
# Teraz backend-resolve jak yespornvip/pornditt (_kvs używa curl_cffi chrome).
|
||||||
# publicznie".)
|
# Native, multi-quality, zero proxy/WebView. (zweryfikowane na emulatorze przed deploy)
|
||||||
# 2026-06-01 (task #20): _kvs.resolve_kvs dekoduje function/0 OK, ale finalny
|
"freshpornoorg": freshporno.extract,
|
||||||
# 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,
|
|
||||||
# porn00 — KVS engine. 2026-06-01 cross-IP re-test (task #20): get_file 302 →
|
# 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
|
# `fe.porn00.org/videos/.../<id>.mp4?token=&expires=` zwraca 403 z residential
|
||||||
# IP → token IP-bound do resolvera (VPS), NIE portable jak yespornvip/pornditt.
|
# 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 `video_url`/`video_alt_url`/`video_alt_url2` = `function/0/...get_file/...` +
|
||||||
flashvars JSON (`video_url: 'function/0/<URL>'`) i w `<a href="...?download=true">`
|
`license_code` (silnik identyczny z yespornvip/pornditt). Resolve server-side: decode +
|
||||||
linkach z labelem "MP4 720p" / "MP4 480p".
|
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
|
2026-06-04 (DevTools + cross-IP re-test, naprawia błędny wniosek z #20): finalny CDN url
|
||||||
ma tylko main+alt, max 2 jakości). `<a href="...get_file/...mp4/?download=true...">MP4 <q>p, ...`
|
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
|
||||||
Sidebar suggested videos używają `data-preview="...get_file/.../<id>_preview.mp4"` —
|
chrome / ExoPlayer/okhttp → 206; plain curl → connection 000). W #20 testowałem finalny
|
||||||
inny pattern (nie `<a href>`), więc anchor regex je naturalnie pomija.
|
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
|
||||||
CDN token IP-bound do VPS — mobile dostanie 403 na direct, fallback proxy działa.
|
do cdn4; mobile (ExoPlayer) gra direct. Native, multi-quality, zero WebView/proxy/reklam.
|
||||||
get_file 302 → `cdn4.freshporno.org/remote_control.php?...&file=<path>` direct mp4
|
|
||||||
(nie HLS). Type='mp4'.
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
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._models import StreamSource
|
||||||
|
from app.extractors.tubes import _kvs
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
_BASE = "https://freshporno.org"
|
||||||
|
|
||||||
# `<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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
|
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
|
||||||
html = fetch_tube_html(page_url, timeout=timeout)
|
return _kvs.resolve_kvs(page_url, base_url=_BASE, 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
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue