import json, os, re, sys, urllib.parse, urllib.request, urllib.error PORT = os.environ.get('TEST_PORT', '5001') BASE = f'http://127.0.0.1:{PORT}' UA = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/126 Safari/537.36'} def req(url, data=None, timeout=60): body = json.dumps(data).encode() if data is not None else None headers = {**UA, 'Content-Type': 'application/json'} if data is not None else UA r = urllib.request.Request(url, data=body, headers=headers) try: with urllib.request.urlopen(r, timeout=timeout) as resp: return resp.status, dict(resp.headers), resp.read() except urllib.error.HTTPError as e: return e.code, dict(e.headers), e.read() except Exception as e: return 0, {}, str(e).encode() def test_sources(anime_id, ep, lang='sub', providers=None): providers = providers or ['anibd','anikoto','reanime','senshi','kaa','2dhive', 'animegg','animenosub','animedunya','anidbapp','anineko','mkissa'] ok = 0 for p in providers: st, hd, body = req(f'{BASE}/api/watch/sources', {'anime_id': anime_id, 'episode_number': ep, 'language': lang, 'provider': p}) try: d = json.loads(body) except Exception: print(f' {p:12s} st={st} RAW={body[:100]!r}') continue vl = d.get('video_link') or '' proxied = '/api/stream-proxy?' in vl n_hls = len(d.get('hls_sources') or []) n_emb = len(d.get('embed_sources') or []) n_trk = len(d.get('tracks') or []) usable = st == 200 and d.get('available') and (bool(vl) or n_hls or n_emb) if usable: ok += 1 print(f' {p:12s} st={st} avail={d.get("available")} type={d.get("source_type")} ' f'proxied={proxied} hls={n_hls} embeds={n_emb} tracks={n_trk}') if vl and not proxied: print(f' !! NOT PROXIED: {vl[:110]}') if not usable and st == 200: print(f' detail: {json.dumps(d)[:180]}') print(f' -> {ok}/{len(providers)} usable') return ok def fetch_proxied(manifest_text): """Return the first rewritten URL from a proxied manifest (or None).""" for ln in manifest_text.splitlines(): if '/api/stream-proxy' in ln: return ln if ln.startswith('http') else f'{BASE}{ln}' m = re.search(r'(/api/stream-proxy[^"\s]+)', manifest_text) return (m.group(1) if m and m.group(1).startswith('http') else (f'{BASE}{m.group(1)}' if m else None)) def proxy_roundtrip(url, ref, depth=3): """Walk master → variant → segment through /api/stream-proxy.""" q = urllib.parse.urlencode({'url': url, 'ref': ref}) st, hd, body = req(f'{BASE}/api/stream-proxy?{q}') if st != 200: print(f' PROXY {st}: {body[:200]!r}') return False text = body.decode('utf-8', 'replace') rew = [ln for ln in text.splitlines() if '/api/stream-proxy' in ln] print(f' master st={st} ctype={hd.get("Content-Type")} lines={len(text.splitlines())} rewritten={len(rew)}') print(' sample:', rew[0][:110] if rew else 'NONE') cur = text for i in range(depth): nxt = fetch_proxied(cur) if not nxt: print(' no more rewritten URLs — done') return True st2, hd2, body2 = req(nxt, timeout=120) ctype = hd2.get('Content-Type') or '' print(f' hop{i+1} st={st2} ctype={ctype} bytes={len(body2)}') if st2 != 200: print(f' BODY: {body2[:200]!r}') return False if len(body2) > 50000: print(f' hop{i+1} looks like a real segment ({len(body2)} bytes) — PLAYABLE') return True if 'mpegurl' not in ctype and 'dash' not in ctype: print(f' hop{i+1} is binary/short — treat as segment, PLAYABLE') return True cur = body2.decode('utf-8', 'replace') return True print(f'=== target: {BASE} ===') print('\n--- 1) FMA:B (5114) ep 1 sub — all providers ---') n1 = test_sources(5114, 1) print('\n--- 2) FMA:B (5114) ep 1 DUB ---') n2 = test_sources(5114, 1, lang='dub', providers=['anibd','anikoto','reanime','anidbapp']) print('\n--- 3) One Piece (21) ep 1 sub ---') n3 = test_sources(21, 1, providers=['anibd','reanime','anikoto','2dhive']) print('\n--- 4) Stream-proxy round-trip on a real anibd m3u8 ---') proxy_roundtrip('https://playeng.animeapps.top/r2/cachehd/5114ebd1/index.m3u8', 'https://playeng.animeapps.top/') print('\n--- 5) Embed source check (reanime, iframe-based) ---') st, hd, body = req(f'{BASE}/api/watch/sources', {'anime_id': 5114, 'episode_number': 1, 'language': 'sub', 'provider': 'reanime'}) if st == 200: d = json.loads(body) print(' type:', d.get('source_type'), '| embeds:', len(d.get('embed_sources') or [])) for e in (d.get('embed_sources') or [])[:2]: print(' embed:', str(e)[:150]) print('\n=== DONE ===')