Keep hianime alive across server rotation: try every listed server
hianime's HD-1 slot rotated to a megaplay chain whose CDN (norami.top)
is Cloudflare-blocked for us, which killed stream resolution (CDN probe
sat at 0 KB/s). The ZokoAnime server still serves the working
zokoanime chain. _master_m3u8 now walks every listed server of the
wanted type; _embed_payload handles both chains (window.__P blob and
megaplay data-id/getSourcesNew with re-roll). stream_meta returns {}
when the host is ours but no referer is needed, so the /stream
allowlist still holds.
This commit is contained in:
1 parent
65da3e848f
commit
7444e29592
2 files changed
+79
-37
No files matched your search
+72
-33
@@ -72,9 +72,9 @@ def _servers(ep_id):
|
||||
def _languages(ep_id):
|
||||
t = _servers(ep_id)
|
||||
out = []
|
||||
if re.search(r'data-type="sub"[\s\S]*?data-server-name="HD-1"', t):
|
||||
if 'data-type="sub"' in t:
|
||||
out.append({"code": "jpn", "name": "Sub", "embed_url": "sub"})
|
||||
if re.search(r'data-type="dub"[\s\S]*?data-server-name="HD-1"', t):
|
||||
if 'data-type="dub"' in t:
|
||||
out.append({"code": "eng", "name": "Dub", "embed_url": "dub"})
|
||||
return out
|
||||
|
||||
@@ -84,39 +84,76 @@ def _deobfuscate(blob):
|
||||
return json.loads(bytes(b ^ KEY[i % len(KEY)] for i, b in enumerate(raw)))
|
||||
|
||||
|
||||
def _embed_payload(embed):
|
||||
"""embed url -> (master_m3u8, [{label, src} subs], referer_or_None).
|
||||
|
||||
Two player chains have appeared: zokoanime (window.__P blob,
|
||||
base64+XOR) and megaplay (page needs the hianime referer; data-id ->
|
||||
getSourcesNew; CDN domains rotate and most 403 us, so re-roll until
|
||||
one serves)."""
|
||||
page = _get(embed, timeout=15).text
|
||||
blob = re.search(r'window\.__P="([^"]+)"', page)
|
||||
if blob:
|
||||
j = _deobfuscate(blob.group(1))
|
||||
subs = [{"label": (x or {}).get("label", ""), "src": (x or {}).get("src")}
|
||||
for x in j.get("subtitles") or []]
|
||||
return j.get("src"), subs, re.match(r'https?://[^/]+', embed).group(0) + "/"
|
||||
# megaplay: without the hianime referer the page is an error stub with no data-id
|
||||
if not re.search(r'data-id="\d+"', page):
|
||||
page = _get(embed, headers={"Referer": BASE + "/"}, timeout=15).text
|
||||
mid = re.search(r'data-id="(\d+)"', page)
|
||||
if not mid:
|
||||
return None, [], None
|
||||
host = re.match(r'https?://([^/]+)', embed).group(1)
|
||||
for _ in range(5):
|
||||
d = _get(f"https://{host}/stream/getSourcesNew?id={mid.group(1)}",
|
||||
headers={"Referer": embed, "X-Requested-With": "XMLHttpRequest"},
|
||||
timeout=15).json()
|
||||
src = (d.get("sources") or {}).get("file")
|
||||
if not src:
|
||||
continue
|
||||
subs = [{"label": (x or {}).get("label", ""), "src": (x or {}).get("file")}
|
||||
for x in d.get("tracks") or []]
|
||||
for cand, refr in (({"Referer": embed}, embed), ({}, None)):
|
||||
try:
|
||||
_get(src, headers=cand, timeout=10)
|
||||
return src, subs, refr
|
||||
except Exception:
|
||||
continue
|
||||
return None, [], None
|
||||
|
||||
|
||||
def _master_m3u8(ep_id, lang="eng"):
|
||||
global REFERER, STREAM_HOSTS
|
||||
t = _servers(ep_id)
|
||||
want = "sub" if lang in ("jpn", "sub") else "dub"
|
||||
# HD-1 has rotated between player chains (zokoanime blob, megaplay);
|
||||
# try every listed server of the wanted type until one resolves
|
||||
for typ in (want, "sub") if want == "dub" else (want,):
|
||||
m = re.search(rf'data-type="{typ}"[\s\S]*?data-server-name="HD-1"'
|
||||
r'[\s\S]*?data-hash="([^"]+)"', t)
|
||||
if not m:
|
||||
continue
|
||||
embed = base64.b64decode(m.group(1)).decode()
|
||||
REFERER = re.match(r'https?://[^/]+', embed).group(0) + "/"
|
||||
j = _deobfuscate(re.search(r'window\.__P="([^"]+)"',
|
||||
_get(embed, timeout=15).text).group(1))
|
||||
master = j.get("src")
|
||||
if not master:
|
||||
continue
|
||||
# prove the CDN serves us (it 403s without the referer) and register
|
||||
# the hosts so the /stream proxy allowlist survives CDN rotation
|
||||
r = _get(master, headers={"Referer": REFERER}, timeout=10)
|
||||
hosts = {urlparse(master).netloc}
|
||||
for line in r.text.splitlines():
|
||||
if line and not line.startswith("#"):
|
||||
hosts.add(urlparse(urljoin(master, line)).netloc)
|
||||
STREAM_HOSTS |= hosts
|
||||
subs = j.get("subtitles") or []
|
||||
# the 'lang' field is useless (everything says 'en'); the real language
|
||||
# is the label — and it isn't first (Chainsaw Man E1 leads with Arabic)
|
||||
pick = next((x for x in subs if (x or {}).get("label", "").lower() == "english"), None)
|
||||
if not pick and subs:
|
||||
pick = subs[0]
|
||||
if pick and pick.get("src"):
|
||||
SUBS[ep_id] = pick["src"]
|
||||
return master
|
||||
for _name, h in re.findall(
|
||||
rf'data-type="{typ}"[\s\S]*?data-server-name="([^"]+)"'
|
||||
rf'[\s\S]*?data-hash="([^"]+)"', t):
|
||||
embed = base64.b64decode(h).decode()
|
||||
master, subs, refr = _embed_payload(embed)
|
||||
if not master:
|
||||
continue
|
||||
REFERER = refr
|
||||
# prove the CDN serves us and register the hosts so the /stream proxy
|
||||
# allowlist survives CDN rotation
|
||||
r = _get(master, headers={"Referer": refr} if refr else {}, timeout=10)
|
||||
hosts = {urlparse(master).netloc}
|
||||
for line in r.text.splitlines():
|
||||
if line and not line.startswith("#"):
|
||||
hosts.add(urlparse(urljoin(master, line)).netloc)
|
||||
STREAM_HOSTS |= hosts
|
||||
# the 'lang' field is useless (everything says 'en'); the real language
|
||||
# is the label — and it isn't first (Chainsaw Man E1 leads with Arabic)
|
||||
pick = next((x for x in subs if (x or {}).get("label", "").lower() == "english"), None)
|
||||
if not pick and subs:
|
||||
pick = subs[0]
|
||||
if pick and pick.get("src"):
|
||||
SUBS[ep_id] = pick["src"]
|
||||
return master
|
||||
return None
|
||||
|
||||
|
||||
@@ -129,9 +166,11 @@ master_m3u8 = lambda e, l="eng": _cached(f"hianime:master:{e}:{l}", 3600, _maste
|
||||
|
||||
|
||||
def stream_meta(url):
|
||||
"""{Referer} for our own playlist/segment hosts, else None."""
|
||||
if REFERER and urlparse(url).netloc in STREAM_HOSTS:
|
||||
return {"Referer": REFERER}
|
||||
"""{Referer} (or {}) for our own playlist/segment hosts, else None —
|
||||
non-None doubles as the /stream allowlist; {} = host is ours but the
|
||||
CDN needs no referer."""
|
||||
if urlparse(url).netloc in STREAM_HOSTS:
|
||||
return {"Referer": REFERER} if REFERER else {}
|
||||
return None
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user