From ba53f872d210ebd726745ee56fd06bded2854ea2 Mon Sep 17 00:00:00 2001 From: goon-foss Date: Fri, 2 Oct 2026 14:16:14 +0200 Subject: [PATCH] fix(proxy): hdporn.gg i fullmovies, swiezy adres zamiast 410 w zapasowym proxy 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 --- app/api/stream_proxy.py | 51 +++++++++++++++++++++++++ app/extractors/tubes/_source_getfile.py | 13 ++++++- app/extractors/tubes/fullmovies.py | 2 +- app/extractors/tubes/hdporngg.py | 2 +- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/app/api/stream_proxy.py b/app/api/stream_proxy.py index ccc83d3..32c3f03 100644 --- a/app/api/stream_proxy.py +++ b/app/api/stream_proxy.py @@ -585,6 +585,40 @@ async def sxyprn_thumb(post_id: str) -> Response: ) +#: Po tych statusach proxy pobiera strone tube na nowo (gdy token ma rf + rh "tube:..."). +#: 503 dopiero po nieudanym ponowieniu: nowy token bywa kierowany na inny wezel CDN. +_TUBE_REFRESH_STATUSES = frozenset({403, 404, 410, 503}) +_QUALITY_IN_URL_RE = re.compile(r"_(\d{3,4})[a-z]?\.mp4", re.IGNORECASE) + + +async def _fresh_tube_link(sitetag: str, page_url: str, old_target: str) -> str | None: + """Swiezy adres pliku z ekstraktora tube, w tej samej jakosci co `old_target`. + + hdporn.gg / fullmovies.xxx: get_file jest przypiety do IP ladujacego strone i bywa + jednorazowy. Telefon, ktoremu nie wyszedl wlasny resolver, dostawal od proxy ten sam + zuzyty adres i konczyl na 410/503 (14 urzadzen w tydzien z samymi porazkami, 2026-10-02). + """ + import anyio + + from app.extractors import try_extract + + try: + sources = await anyio.to_thread.run_sync(lambda: try_extract(sitetag, page_url)) + except Exception as e: # noqa: BLE001 (HosterDead/TubePageError: zostaje stary blad) + log.info("proxy: odswiezenie %s nie wyszlo: %s", page_url, e) + return None + links = [s.link for s in (sources or []) if s.link] + if not links: + return None + want = _QUALITY_IN_URL_RE.search(old_target) + if want: + for link in links: + m = _QUALITY_IN_URL_RE.search(link) + if m and m.group(1) == want.group(1): + return link + return links[0] + + async def _refetch_mixdrop_url(session: "AsyncSession", embed_url: str) -> str | None: """Re-fetch mixdrop embed, decode P.A.C.K.E.R., extract fresh MDCore.wurl. Cookies persist w session, użytkowane potem do mp4 GET (same-session bind). @@ -870,6 +904,23 @@ async def proxy_stream( stream=True, follow_redirects=True, ) + if ( + upstream.status_code in _TUBE_REFRESH_STATUSES + and refetch_url + and (refetch_hoster or "").startswith("tube:") + and method.upper() in ("GET", "HEAD") + ): + fresh = await _fresh_tube_link(refetch_hoster[5:], refetch_url, target) + if fresh: + log.info("proxy: upstream %d, swiezy adres z %s", upstream.status_code, refetch_url[:70]) + await upstream.aclose() + target = fresh + cached_target = None # nowy cel: po sukcesie cache dostanie jego koncowy adres + upstream = await client.send( + client.build_request(method, target, headers=headers), + stream=True, + follow_redirects=True, + ) if upstream.status_code >= 400: status = upstream.status_code ups_headers = dict(upstream.headers) diff --git a/app/extractors/tubes/_source_getfile.py b/app/extractors/tubes/_source_getfile.py index c756f15..2935f42 100644 --- a/app/extractors/tubes/_source_getfile.py +++ b/app/extractors/tubes/_source_getfile.py @@ -31,7 +31,9 @@ _SOURCE_RE = re.compile( _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: +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 @@ -59,9 +61,16 @@ def resolve(page_url: str, base_url: str, *, timeout: float = 30.0) -> list[Stre 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={"mobile_direct_ok": True}, + referer=base_url + "/", raw=raw, )) if not out: diff --git a/app/extractors/tubes/fullmovies.py b/app/extractors/tubes/fullmovies.py index 985c624..70e7377 100644 --- a/app/extractors/tubes/fullmovies.py +++ b/app/extractors/tubes/fullmovies.py @@ -16,4 +16,4 @@ _BASE = "https://www.fullmovies.xxx" def extract(page_url: str, *, timeout: float = 30.0) -> list[StreamSource] | None: - return _source_getfile.resolve(page_url, _BASE, timeout=timeout) + return _source_getfile.resolve(page_url, _BASE, timeout=timeout, sitetag="fullmoviesxxx") diff --git a/app/extractors/tubes/hdporngg.py b/app/extractors/tubes/hdporngg.py index 7f6d56d..8ebc7a4 100644 --- a/app/extractors/tubes/hdporngg.py +++ b/app/extractors/tubes/hdporngg.py @@ -14,4 +14,4 @@ _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) + return _source_getfile.resolve(page_url, _BASE, timeout=timeout, sitetag="hdporngg")