diff --git a/app/api/stream_proxy.py b/app/api/stream_proxy.py index 4ed0183..b41b349 100644 --- a/app/api/stream_proxy.py +++ b/app/api/stream_proxy.py @@ -695,6 +695,28 @@ async def _produce_manifest(producer: str, embed_url: str) -> str | None: return None +_CHALLENGE_COOKIE_RE = re.compile(r"document\.cookie\s*=\s*'([^=]+)=([^;']+)") + + +def _challenge_cookie(r: httpx.Response) -> str | None: + """Ciasteczko z odpowiedzi-wyzwania sieci TXXX (vjav i rodzina), albo None. + + Serwis dlawi ruch z serwerowni: zamiast tresci oddaje 429 i mikroskopijna strone, + ktora w JS ustawia jedno ciasteczko i przeladowuje sie. Wartosc stoi WPROST w tej + odpowiedzi, wiec wystarczy ja odczytac i powtorzyc zapytanie, bez wykonywania JS. + + Ekstraktor vjav robil to od 2026-08-21 przy `videofile.php`, ale adres manifestu + oddawany odtwarzaczowi tej sztuczki nie dostawal. Skutek: `try_extract` konczyl sie + sukcesem, wiec zrodlo wygladalo na zywe, a `/proxy/hls` przy kazdym odtworzeniu + dostawal 429 i oddawal blad. Stad 587 bledow i ZERO udanych odtworzen na vjav w + ciagu 14 dni, przy 58 779 zrodel. + """ + if r.status_code != 429 or len(r.content) > 4096: + return None + m = _CHALLENGE_COOKIE_RE.search(r.text) + return f"{m.group(1)}={m.group(2)}" if m else None + + @router.get("/hls/{token}/{_basename:path}") async def proxy_hls_manifest(token: str, _basename: str) -> Response: """Direct-HLS manifest passthrough dla time-bound (mobile_direct_ok) m3u8 hosterów. @@ -732,6 +754,8 @@ async def proxy_hls_manifest(token: str, _basename: str) -> Response: async with httpx.AsyncClient(follow_redirects=True, timeout=20.0) as client: try: r = await client.get(target, headers=headers) + if (ck := _challenge_cookie(r)) is not None: + r = await client.get(target, headers=dict(headers, Cookie=ck)) except httpx.HTTPError as e: log.info("proxy-hls fetch failed %s: %s", target, e) raise HTTPException(status_code=502, detail="manifest fetch failed") from e