From b84ff5bdbc4efeeb6371ec71c850ce854ed383ca Mon Sep 17 00:00:00 2001 From: goon-foss Date: Thu, 27 Aug 2026 19:23:55 +0200 Subject: [PATCH] fix(vjav): powtorz pobranie manifestu z ciasteczkiem wyzwania (100% bledow -> gra) vjav mial 587 bledow odtwarzania i ZERO udanych, przez caly czas, odkad zbieramy dla niego zdarzenia. Kazdy blad to ten sam komunikat odtwarzacza: "Source error Response code: 429". Dotyczylo to 58 779 zrodel, czyli tub nigdy ani razu nie zagral. Siec TXXX 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 dokladnie to od 2026-08-21, ale TYLKO przy `videofile.php`. Adres manifestu oddawany odtwarzaczowi tej sztuczki nie dostawal, a `/proxy/hls` pobiera go z VPS przy KAZDYM odtworzeniu. Stad komplet bledow. To dlatego bug byl niewidoczny z zewnatrz: `try_extract` konczyl sie sukcesem, wiec w kazdym pomiarze zrodla vjav wychodzily jako zywe (149 na 150), podczas gdy u userow nie dzialalo nic. "Zywe" znaczy "ekstraktor zwrocil adres", a nie "to gra". Sprawdzone na prodzie: przed poprawka /proxy/hls oddawal 429, po poprawce 200 i poprawny #EXTM3U z segmentami na vjav0.ahcdn.com. Ponowienie odpala sie wylacznie przy 429 z odpowiedzia ponizej 4 KB, wiec nie dotyka pozostalych hosterow. Osobno potwierdzone, ze blokada jest po adresie IP: ta sama strona z VPS oddaje 429, a przez proxy z innym IP 200 i 72 KB tresci. --- app/api/stream_proxy.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) 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