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):
|
def _languages(ep_id):
|
||||||
t = _servers(ep_id)
|
t = _servers(ep_id)
|
||||||
out = []
|
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"})
|
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"})
|
out.append({"code": "eng", "name": "Dub", "embed_url": "dub"})
|
||||||
return out
|
return out
|
||||||
|
|
||||||
@@ -84,39 +84,76 @@ def _deobfuscate(blob):
|
|||||||
return json.loads(bytes(b ^ KEY[i % len(KEY)] for i, b in enumerate(raw)))
|
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"):
|
def _master_m3u8(ep_id, lang="eng"):
|
||||||
global REFERER, STREAM_HOSTS
|
global REFERER, STREAM_HOSTS
|
||||||
t = _servers(ep_id)
|
t = _servers(ep_id)
|
||||||
want = "sub" if lang in ("jpn", "sub") else "dub"
|
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,):
|
for typ in (want, "sub") if want == "dub" else (want,):
|
||||||
m = re.search(rf'data-type="{typ}"[\s\S]*?data-server-name="HD-1"'
|
for _name, h in re.findall(
|
||||||
r'[\s\S]*?data-hash="([^"]+)"', t)
|
rf'data-type="{typ}"[\s\S]*?data-server-name="([^"]+)"'
|
||||||
if not m:
|
rf'[\s\S]*?data-hash="([^"]+)"', t):
|
||||||
continue
|
embed = base64.b64decode(h).decode()
|
||||||
embed = base64.b64decode(m.group(1)).decode()
|
master, subs, refr = _embed_payload(embed)
|
||||||
REFERER = re.match(r'https?://[^/]+', embed).group(0) + "/"
|
if not master:
|
||||||
j = _deobfuscate(re.search(r'window\.__P="([^"]+)"',
|
continue
|
||||||
_get(embed, timeout=15).text).group(1))
|
REFERER = refr
|
||||||
master = j.get("src")
|
# prove the CDN serves us and register the hosts so the /stream proxy
|
||||||
if not master:
|
# allowlist survives CDN rotation
|
||||||
continue
|
r = _get(master, headers={"Referer": refr} if refr else {}, timeout=10)
|
||||||
# prove the CDN serves us (it 403s without the referer) and register
|
hosts = {urlparse(master).netloc}
|
||||||
# the hosts so the /stream proxy allowlist survives CDN rotation
|
for line in r.text.splitlines():
|
||||||
r = _get(master, headers={"Referer": REFERER}, timeout=10)
|
if line and not line.startswith("#"):
|
||||||
hosts = {urlparse(master).netloc}
|
hosts.add(urlparse(urljoin(master, line)).netloc)
|
||||||
for line in r.text.splitlines():
|
STREAM_HOSTS |= hosts
|
||||||
if line and not line.startswith("#"):
|
# the 'lang' field is useless (everything says 'en'); the real language
|
||||||
hosts.add(urlparse(urljoin(master, line)).netloc)
|
# is the label — and it isn't first (Chainsaw Man E1 leads with Arabic)
|
||||||
STREAM_HOSTS |= hosts
|
pick = next((x for x in subs if (x or {}).get("label", "").lower() == "english"), None)
|
||||||
subs = j.get("subtitles") or []
|
if not pick and subs:
|
||||||
# the 'lang' field is useless (everything says 'en'); the real language
|
pick = subs[0]
|
||||||
# is the label — and it isn't first (Chainsaw Man E1 leads with Arabic)
|
if pick and pick.get("src"):
|
||||||
pick = next((x for x in subs if (x or {}).get("label", "").lower() == "english"), None)
|
SUBS[ep_id] = pick["src"]
|
||||||
if not pick and subs:
|
return master
|
||||||
pick = subs[0]
|
|
||||||
if pick and pick.get("src"):
|
|
||||||
SUBS[ep_id] = pick["src"]
|
|
||||||
return master
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -129,9 +166,11 @@ master_m3u8 = lambda e, l="eng": _cached(f"hianime:master:{e}:{l}", 3600, _maste
|
|||||||
|
|
||||||
|
|
||||||
def stream_meta(url):
|
def stream_meta(url):
|
||||||
"""{Referer} for our own playlist/segment hosts, else None."""
|
"""{Referer} (or {}) for our own playlist/segment hosts, else None —
|
||||||
if REFERER and urlparse(url).netloc in STREAM_HOSTS:
|
non-None doubles as the /stream allowlist; {} = host is ours but the
|
||||||
return {"Referer": REFERER}
|
CDN needs no referer."""
|
||||||
|
if urlparse(url).netloc in STREAM_HOSTS:
|
||||||
|
return {"Referer": REFERER} if REFERER else {}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -196,12 +196,15 @@ master_m3u8 = lambda e, l="eng": _call("master_m3u8", e, l)
|
|||||||
|
|
||||||
def stream_meta(url):
|
def stream_meta(url):
|
||||||
"""Provider hook: extra upstream headers for a /stream URL (hianime's
|
"""Provider hook: extra upstream headers for a /stream URL (hianime's
|
||||||
CDN demands the embed origin as Referer), or None if no enabled
|
CDN may demand the embed origin as Referer), or None if no enabled
|
||||||
provider claims the host — that doubles as the allowlist."""
|
provider claims the host — that doubles as the allowlist (an empty
|
||||||
|
dict means: allowed, no extra headers)."""
|
||||||
for p in _providers():
|
for p in _providers():
|
||||||
fn = getattr(_prov(p), "stream_meta", None)
|
fn = getattr(_prov(p), "stream_meta", None)
|
||||||
if fn and fn(url):
|
if fn:
|
||||||
return fn(url)
|
v = fn(url)
|
||||||
|
if v is not None:
|
||||||
|
return v
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user