get_file jest przypiety do IP i sesji ladujacej strone i bywa jednorazowy. Gdy resolver na telefonie zawiodl, proxy dostawalo ten sam zuzyty adres i oddawalo 410/503: 14 urzadzen w tydzien mialo na tych serwisach same porazki (inne serwisy graly im bez problemu). Teraz po 403/404/410 albo trwalym 503 proxy pobiera strone na nowo (try_extract) i bierze adres w tej samej jakosci. Test: adres pobrany z innego IP przez proxy 410 -> z odswiezaniem 206 video/mp4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
85 lines
3.4 KiB
Python
85 lines
3.4 KiB
Python
"""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, sitetag: str | None = None
|
|
) -> 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
|
|
raw: dict = {"mobile_direct_ok": True}
|
|
if sitetag:
|
|
# get_file jest przypiety do IP tego, kto zaladowal strone, i bywa jednorazowy,
|
|
# wiec proxy (zapas, gdy resolver na telefonie zawiedzie) po 403/410 pobiera
|
|
# strone na nowo zamiast oddac blad (stream_proxy._fresh_tube_link, 2026-10-02).
|
|
raw["refetch_url"] = page_url
|
|
raw["refetch_hoster"] = f"tube:{sitetag}"
|
|
out.append(StreamSource(
|
|
link=url, type="mp4", quality=quality or None,
|
|
referer=base_url + "/", raw=raw,
|
|
))
|
|
|
|
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
|