diff --git a/app/extractors/__init__.py b/app/extractors/__init__.py index 1a7a1a4..7d3a173 100644 --- a/app/extractors/__init__.py +++ b/app/extractors/__init__.py @@ -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/.../.mp4?token=&expires=` zwraca 403 z residential # IP → token IP-bound do resolvera (VPS), NIE portable jak yespornvip/pornditt. diff --git a/app/extractors/tubes/freshporno.py b/app/extractors/tubes/freshporno.py index d6467b1..a1d904b 100644 --- a/app/extractors/tubes/freshporno.py +++ b/app/extractors/tubes/freshporno.py @@ -1,71 +1,24 @@ -"""freshporno.org — KVS engine, BEZ `` 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/'`) i w `` -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). `MP4 p, ...` - -Sidebar suggested videos używają `data-preview="...get_file/.../_preview.mp4"` — -inny pattern (nie ``), 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=` 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__) - -# `MP4 p, ` — main + alt streams. -_ANCHOR_QUALITY_RE = re.compile( - r']*href="(?Phttps?://[^"]+/get_file/[^"]+\.mp4/)\?download=true[^"]*"' - r'[^>]*>\s*MP4\s+(?P\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)