fix(hdporngg+fullmovies): native get_file, skip broken 4K — "loading forever"
User: "hdporngg loading forever". DevTools + cross-IP investigation (not guessing): - site is alive (sample scenes 200; the one earlier 404 was a single removed video, not the site — my earlier "site dead" was a hasty generalization). - both are the same platform (<source src=.../get_file/8512/...mp4>), no function/0. - the get_file 302 is fast (~100ms) but the 2160p/4K source on fpvcdn.com TIMES OUT (~30s); 720p/480p resolve in ~1s. The player loading 4K first = the "loading forever". - the final fpvcdn URL embeds the requester IP (ip=<fetcher>) -> IP-bound to whoever resolves it; BUT the get_file itself is stateless (fresh session works) and valid >=90s, and binds fpvcdn to the fetcher. So a VPS resolve would bind to the VPS IP (mobile 403), but returning the get_file URL UNRESOLVED lets the phone follow the 302 itself -> fpvcdn binds to the phone IP -> plays. Fix: new _source_getfile resolver returns get_file URLs as mobile_direct (skip 4K), phone resolves the 302 in-session. Native, multi-quality, no WebView, no proxy. Replaces fullmovies' old force_proxy+4K extractor and the WebView fallback for both. Backend-verified: resolve -> 720/480 mobile_direct, get_file fresh fetch -> 206. Pending on-device confirmation (emulator unstable; same mechanism as porn00/freshporno which work). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c05bafb4c7
commit
e780e1ae6f
4 changed files with 115 additions and 68 deletions
|
|
@ -29,6 +29,8 @@ from app.extractors.tubes import (
|
|||
_ytdlp,
|
||||
eporner,
|
||||
freshporno,
|
||||
fullmovies,
|
||||
hdporngg,
|
||||
hqporner,
|
||||
latestpornvideo,
|
||||
paradisehill,
|
||||
|
|
@ -152,14 +154,15 @@ _REGISTRY: dict[str, Callable[[str], list[StreamSource] | None]] = {
|
|||
# 0 VPS bandwidth — zgodne z pre-public bandwidth/anonimowość priorytet.
|
||||
"porndoecom": _vps_blocked_fallback.extract,
|
||||
# fullmovies.xxx + hdporn.gg — BRAKOWAŁO extractora (try_extract→None→"no stream";
|
||||
# bug 19866e9e "problem z oboma hosterami" — scena mająca TYLKO te dwa źródła nie
|
||||
# grała w ogóle). fullmovies ma `<source src=...get_file...mp4>`, ale get_file
|
||||
# time-outuje z VPS (CDN nieosiągalny, jak freshporno) → backend-resolve odpada.
|
||||
# hdporn.gg sample-scena 404 (część contentu usunięta). Oba → WebView fallback:
|
||||
# telefon (residential IP) ładuje stronę, player JS/`<source>` gra, INJECTED_JS
|
||||
# scrape łapie URL. Lepsze niż brak ścieżki playbacku. (2026-06-03)
|
||||
"fullmoviesxxx": _vps_blocked_fallback.extract,
|
||||
"hdporngg": _vps_blocked_fallback.extract,
|
||||
# fullmovies.xxx + hdporn.gg — ta sama platforma (`<source>/get_file/8512/`).
|
||||
# 2026-06-04 (DevTools + cross-IP, naprawia „loading forever" + bug 19866e9e):
|
||||
# get_file binduje fpvcdn do IP FETCHERA + jest stateless + ważny ≥90s, więc
|
||||
# oddajemy get_file NIEZRESOLWOWANY (mobile_direct) — telefon follow-uje 302 →
|
||||
# fpvcdn z IP telefonu → gra. POMIJAMY 4K (time-out 30s na fpvcdn = przyczyna
|
||||
# „loading forever"; 720/480p gra ~1s). Native, multi-quality, ZERO proxy/WebView.
|
||||
# (#19866e9e wcześniej źle: założyłem „get_file 403 IP-bound" testem plain-curl.)
|
||||
"fullmoviesxxx": fullmovies.extract,
|
||||
"hdporngg": hdporngg.extract,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
76
app/extractors/tubes/_source_getfile.py
Normal file
76
app/extractors/tubes/_source_getfile.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
"""Współdzielony resolver dla tubów z `<source src=.../get_file/...mp4>` + IP-bound CDN
|
||||
(hdporn.gg, fullmovies.xxx — ta sama platforma, `/get_file/8512/`).
|
||||
|
||||
2026-06-04 (DevTools + cross-IP investigation — naprawia „hdporngg loading forever"):
|
||||
get_file 302-redirectuje do `fpvcdn.com` z **IP fetchera wbitym w URL** (`ip=<kto-fetchnął>`),
|
||||
więc finalny CDN jest IP-bound do tego kto resolvuje. Dlatego oddajemy get_file URL
|
||||
**NIEZRESOLWOWANY** (mobile_direct) — ExoPlayer na telefonie sam follow-uje 302, fpvcdn
|
||||
bindje się do IP telefonu, gra. (Resolve na VPS → bind do IP VPS → mobile 403.)
|
||||
|
||||
Zweryfikowane: get_file jest STATELESS (świeża sesja działa) + ważny ≥90s, więc telefon
|
||||
ma czas (resolve→picker→tap = sekundy). Źródło **2160p/4K konsekwentnie time-outuje na
|
||||
fpvcdn (~30s)** → POMIJAMY je (to była przyczyna „loading forever" — player ładował 4K
|
||||
pierwsze); 720p/480p/1080p resolvują w ~1s.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from app.extractors._fetch import _DEFAULT_IMPERSONATE, _DEFAULT_UA, _HAS_CURL_CFFI
|
||||
from app.extractors._models import StreamSource
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_SOURCE_RE = re.compile(
|
||||
r"<source\s+src=['\"]([^'\"]+/get_file/[^'\"]+\.mp4[^'\"]*)['\"]"
|
||||
r"[^>]*?(?:title|label)=['\"]?([^'\">]*)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
# fpvcdn nie serwuje 4K (30s timeout) — skip żeby player nie wisiał na nim.
|
||||
_SKIP_QUALITY_RE = re.compile(r"2160|1440|4k", re.IGNORECASE)
|
||||
|
||||
|
||||
def resolve(page_url: str, base_url: str, *, timeout: float = 30.0) -> list[StreamSource] | None:
|
||||
if not _HAS_CURL_CFFI:
|
||||
log.info("source_getfile: curl_cffi unavailable — %s", page_url)
|
||||
return None
|
||||
from curl_cffi import requests as cf
|
||||
try:
|
||||
html = cf.get(
|
||||
page_url, impersonate=_DEFAULT_IMPERSONATE,
|
||||
headers={"User-Agent": _DEFAULT_UA, "Accept": "text/html,application/xhtml+xml"},
|
||||
timeout=timeout,
|
||||
).text
|
||||
except Exception as e:
|
||||
log.info("source_getfile: page fetch failed %s: %s", page_url, e)
|
||||
return None
|
||||
|
||||
seen: set[str] = set()
|
||||
out: list[StreamSource] = []
|
||||
for m in _SOURCE_RE.finditer(html):
|
||||
url = m.group(1).strip()
|
||||
quality = (m.group(2) or "").strip()
|
||||
if url.startswith("//"):
|
||||
url = "https:" + url
|
||||
if url in seen:
|
||||
continue
|
||||
seen.add(url)
|
||||
if _SKIP_QUALITY_RE.search(quality):
|
||||
log.info("source_getfile: skip broken-CDN quality %r on %s", quality, page_url)
|
||||
continue
|
||||
out.append(StreamSource(
|
||||
link=url, type="mp4", quality=quality or None,
|
||||
referer=base_url + "/", raw={"mobile_direct_ok": True},
|
||||
))
|
||||
|
||||
if not out:
|
||||
log.info("source_getfile: no playable <source>/get_file on %s", page_url)
|
||||
return None
|
||||
|
||||
def _rank(s: StreamSource) -> int:
|
||||
mm = re.search(r"(\d{3,4})", s.quality or "")
|
||||
return int(mm.group(1)) if mm else -1
|
||||
|
||||
out.sort(key=_rank, reverse=True)
|
||||
return out
|
||||
|
|
@ -1,68 +1,19 @@
|
|||
"""fullmovies.xxx — direct mp4 sources extractor.
|
||||
"""fullmovies.xxx — `<source>/get_file` tube z IP-bound fpvcdn. Patrz _source_getfile.py.
|
||||
|
||||
Detail page ma `<video class="video-js">` z multiple `<source>` (per quality):
|
||||
`<source src='https://www.fullmovies.xxx/get_file/<token>/<dir>/<id>/<id>_2160m.mp4/' type='video/mp4' label="2160p" selected="true">`
|
||||
`<source src='.../<id>_720m.mp4/' type='video/mp4' label="720p">`
|
||||
`<source src='.../<id>_480m.mp4/' type='video/mp4' label="480p">`
|
||||
|
||||
URL pattern: `https://www.fullmovies.xxx/get_file/<signed_token>/<dir>/<id>/<id>_<q>m.mp4/`
|
||||
- Trailing slash — server odsyła 302 na CDN.
|
||||
- `<signed_token>` IP-bound do requester (jak HQPorner /get_file/). Mobile direct = 403.
|
||||
- force_proxy=True wymusza wszystko przez goon proxy (proxy follows redirect na CDN).
|
||||
|
||||
Quality labels: 2160p / 1080p / 720p / 480p / 360p.
|
||||
Ta sama platforma co hdporn.gg (`/get_file/8512/`). 2026-06-04 (DevTools + cross-IP):
|
||||
stary extractor zakładał „get_file IP-bound → 403 → force_proxy". Faktycznie get_file
|
||||
binduje fpvcdn do IP **fetchera** (mobile), więc oddajemy get_file NIEZRESOLWOWANY
|
||||
(mobile_direct, ZERO proxy) — telefon follow-uje 302 → fpvcdn z IP telefonu, gra. Pomijamy
|
||||
4K/2160p (konsekwentnie time-outuje na fpvcdn ~30s; reszta gra ~1s). Bug 19866e9e: scena z
|
||||
TYLKO fullmovies+hdporngg nie grała (extractor nie był zarejestrowany).
|
||||
"""
|
||||
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 _source_getfile
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Single-quoted attribute (apostrophes inside src=). Quality from `label="<q>"`.
|
||||
_SOURCE_RE = re.compile(
|
||||
r"""<source\s+src=['"](?P<url>https?://[^'"]+\.mp4/?)['"]"""
|
||||
r"""\s+type=['"]video/mp4['"]"""
|
||||
r"""\s+label=['"](?P<q>[^'"]+)['"]""",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
_BASE = "https://www.fullmovies.xxx"
|
||||
|
||||
|
||||
def extract(page_url: str, *, timeout: float = 60.0) -> list[StreamSource] | None:
|
||||
html = fetch_tube_html(page_url, timeout=timeout)
|
||||
seen: set[str] = set()
|
||||
result: list[StreamSource] = []
|
||||
# fullmovies /get_file/ URL ma signed token IP-bound do requester. Bez force_proxy
|
||||
# mobile dostaje 403. Proxy follows 302 na CDN.
|
||||
proxy_flag = {"force_proxy": True}
|
||||
for m in _SOURCE_RE.finditer(html):
|
||||
url = m.group("url")
|
||||
if url in seen:
|
||||
continue
|
||||
seen.add(url)
|
||||
result.append(
|
||||
StreamSource(
|
||||
link=url,
|
||||
type="mp4",
|
||||
quality=m.group("q"),
|
||||
referer=f"{page_url}",
|
||||
raw=proxy_flag,
|
||||
)
|
||||
)
|
||||
|
||||
if not result:
|
||||
log.info("fullmovies: no <source> tags on %s", page_url)
|
||||
return None
|
||||
|
||||
# Sort by quality desc (2160p > 1080p > 720p > 480p > 360p)
|
||||
def _q(s: StreamSource) -> int:
|
||||
try:
|
||||
return int((s.quality or "0").rstrip("p"))
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
result.sort(key=_q, reverse=True)
|
||||
return result
|
||||
def extract(page_url: str, *, timeout: float = 30.0) -> list[StreamSource] | None:
|
||||
return _source_getfile.resolve(page_url, _BASE, timeout=timeout)
|
||||
|
|
|
|||
17
app/extractors/tubes/hdporngg.py
Normal file
17
app/extractors/tubes/hdporngg.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
"""hdporn.gg — `<source>/get_file` tube z IP-bound fpvcdn. Patrz _source_getfile.py.
|
||||
|
||||
Bug: „hdporngg loading który trwa nie wiadomo ile". Przyczyna (DevTools 2026-06-04):
|
||||
player ładował 2160p/4K jako pierwsze, a 4K source na fpvcdn time-outuje (~30s); 720/480p
|
||||
grają w ~1s. Oddajemy niezresolwowany get_file (mobile sam follow-uje 302 → fpvcdn z IP
|
||||
telefonu, bo CDN jest IP-bound do fetchera) i POMIJAMY 4K. Native, multi-quality, bez WebView.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from app.extractors._models import StreamSource
|
||||
from app.extractors.tubes import _source_getfile
|
||||
|
||||
_BASE = "https://www.hdporn.gg"
|
||||
|
||||
|
||||
def extract(page_url: str, *, timeout: float = 30.0) -> list[StreamSource] | None:
|
||||
return _source_getfile.resolve(page_url, _BASE, timeout=timeout)
|
||||
Loading…
Add table
Reference in a new issue