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.
This commit is contained in:
goon-foss 2026-08-27 19:23:55 +02:00
parent 4df82b0bbe
commit b84ff5bdbc

View file

@ -695,6 +695,28 @@ async def _produce_manifest(producer: str, embed_url: str) -> str | None:
return 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}") @router.get("/hls/{token}/{_basename:path}")
async def proxy_hls_manifest(token: str, _basename: str) -> Response: async def proxy_hls_manifest(token: str, _basename: str) -> Response:
"""Direct-HLS manifest passthrough dla time-bound (mobile_direct_ok) m3u8 hosterów. """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: async with httpx.AsyncClient(follow_redirects=True, timeout=20.0) as client:
try: try:
r = await client.get(target, headers=headers) 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: except httpx.HTTPError as e:
log.info("proxy-hls fetch failed %s: %s", target, e) log.info("proxy-hls fetch failed %s: %s", target, e)
raise HTTPException(status_code=502, detail="manifest fetch failed") from e raise HTTPException(status_code=502, detail="manifest fetch failed") from e