From e3ef51d141da4d4e8175883912d7bef05e3d13bc Mon Sep 17 00:00:00 2001 From: Josue Zamudio Date: Tue, 29 Sep 2026 17:33:54 +0000 Subject: [PATCH] Stop spamming upstream: drop video download from probe, kill re-rolls - CDN probe no longer downloads 64KB of video (or even a variant playlist): one small master-playlist fetch proves reachability. State stays OK/DEGRADED, kbps is no longer measured, and the pill/ panel only show a rate when one exists. - hianime: 5x getSourcesNew re-roll -> 1 attempt. The enc payload is deterministic per data-id (observed identical across calls), so re-rolling only hammered the provider. - anikoto: 2x re-roll -> 1 attempt per server, same reason. Kept: idle-gated + 30-min degraded backoff from the previous commit, the single _get retry (protects real user playback from CF blips), and the origin page check (the 'down' signal). Net upstream traffic is now what an actual viewer would generate, nothing more. --- server/app.py | 34 +++++++++++----------------------- server/providers/anikoto.py | 19 ++++++++++--------- server/providers/hianime.py | 29 +++++++++++++++-------------- static/app.js | 14 +++++++------- 4 files changed, 43 insertions(+), 53 deletions(-) diff --git a/server/app.py b/server/app.py index b94b5bb..e8f66de 100644 --- a/server/app.py +++ b/server/app.py @@ -61,23 +61,19 @@ _DEGRADED_RETRY = 1800 # origin (primary provider pages/API) health — separate host, separate failure # mode (observed 2026-09-03: CDN fine while origin 503'd "Under Maintenance") _ORIGIN = {"state": "unknown", "msg": "", "ts": 0.0, "hist": []} -# ponytail: fixed threshold; if the CDN's normal throughput drifts below it -# the pill sits on "slow" — tune via ANIWEB_CDN_KBPS, not a percentile. +# kept for the /api/cdn response shape (the panel shows it only when a rate +# was actually measured; the probe now checks reachability, not throughput) _CDN_THRESHOLD = float(os.environ.get("ANIWEB_CDN_KBPS", "40")) def _cdn_probe(): - """Background probe: pull 64KB from the CDN and log state flips, so - server.log shows when a degraded window ends (CDN RECOVERED). Skipped + """Background probe: fetch ONE small playlist and log state flips, so + server.log shows when a degraded window ends (CDN RECOVERED). No video + is downloaded just to light a status dot. Skipped while the app is idle and backed off while DEGRADED (see _LAST_PLAY). Probe target: ep_id 1 = One Piece E1 on the primary provider. ponytail: single fixed episode; if it ever disappears, the probe logs DEGRADED forever — swap the ID, don't build a picker.""" - def first(pl, base): - l = next(x.strip() for x in pl.splitlines() - if x.strip() and not x.startswith("#")) - return l if l.startswith("http") else base + l - state = None while True: if time.time() - _LAST_PLAY > _IDLE_SECS: @@ -89,23 +85,15 @@ def _cdn_probe(): raise RuntimeError("no master m3u8") # hianime's CDN 403s without the embed-origin Referer (anidb needs none) hdrs = upstream.stream_meta(m) or {} - base = m.rsplit("/", 1)[0] + "/" - v = first(upstream._get(m, timeout=10, headers=hdrs).text, base) - # hianime's variant playlists use URIs relative to THEIR OWN path - vbase = v.rsplit("/", 1)[0] + "/" - seg = first(upstream._get(v, timeout=10, headers=hdrs).text, vbase) - t = time.time() - r = upstream._session().get(seg, headers={**hdrs, "Range": "bytes=0-65535"}, - impersonate="chrome", timeout=10) - kbps = len(r.content) / 1024 / max(time.time() - t, 0.01) + upstream._get(m, timeout=10, headers=hdrs) + now = "OK" except Exception: - kbps = 0.0 - now = "OK" if kbps >= _CDN_THRESHOLD else "DEGRADED" + now = "DEGRADED" if now != state: - print(f"[probe] CDN {now} ({kbps:.0f} KB/s)", flush=True) + print(f"[probe] CDN {now}", flush=True) state = now - _CDN.update(state=now, kbps=round(kbps), ts=time.time()) - _CDN["hist"].append({"t": time.time(), "kbps": round(kbps)}) + _CDN.update(state=now, kbps=0, ts=time.time()) + _CDN["hist"].append({"t": time.time(), "kbps": 0}) del _CDN["hist"][:-20] _origin_probe() time.sleep(_DEGRADED_RETRY if now == "DEGRADED" else 180) diff --git a/server/providers/anikoto.py b/server/providers/anikoto.py index 819f5b1..e374481 100644 --- a/server/providers/anikoto.py +++ b/server/providers/anikoto.py @@ -117,15 +117,16 @@ def _master_m3u8(ep_id, lang="eng"): url = (d.get("result") or {}).get("url") if not url: continue - for _ in range(2): # re-roll: CDN domains rotate, most 403 our IP - try: - f = _player_file(url) - if not f: - break - _get(f, timeout=10) # prove this CDN serves us before committing - return f - except Exception as ex: - errs.append(str(ex)[:60]) + # ponytail: one attempt per server — the enc payload is deterministic, + # re-rolling would just spam the provider + try: + f = _player_file(url) + if not f: + break + _get(f, timeout=10) # prove this CDN serves us before committing + return f + except Exception as ex: + errs.append(str(ex)[:60]) raise RuntimeError("anikoto: no working server: " + "; ".join(errs[:3])) diff --git a/server/providers/hianime.py b/server/providers/hianime.py index 019746e..6960e8f 100644 --- a/server/providers/hianime.py +++ b/server/providers/hianime.py @@ -105,21 +105,22 @@ def _embed_payload(embed): if not mid: return None, [], None host = re.match(r'https?://([^/]+)', embed).group(1) - for _ in range(5): - d = megaplay_sources(_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: + # ponytail: one attempt — the enc payload is deterministic per data-id, + # so re-rolling getSourcesNew would just spam the provider + d = megaplay_sources(_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: + return None, [], None + 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 - 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 diff --git a/static/app.js b/static/app.js index c13ad2f..75ce0d1 100644 --- a/static/app.js +++ b/static/app.js @@ -957,12 +957,12 @@ function renderPill(d) { p.querySelector(".d").className = "d" + (st ? " " + st : ""); p.querySelector("span").textContent = !st ? "…" : st === "down" ? "down" - : st === "slow" ? "slow · " + d.kbps + " KB/s" : "up"; + : st === "slow" ? "slow" + (d.kbps ? " · " + d.kbps + " KB/s" : "") : "up"; p.title = "upstream site: " + d.origin.state + (d.origin.msg ? " — " + d.origin.msg : "") - + " · video CDN: " + d.state + " — " + d.kbps + " KB/s"; + + " · video CDN: " + d.state + (d.kbps ? " — " + d.kbps + " KB/s" : ""); p.setAttribute("aria-label", "status: " + (!st ? "checking" : st === "down" ? "down — upstream " + d.origin.state - : st === "slow" ? "slow — CDN " + d.state + ", " + d.kbps + " KB/s" + : st === "slow" ? "slow — CDN " + d.state + (d.kbps ? ", " + d.kbps + " KB/s" : "") : "up")); } @@ -982,12 +982,12 @@ function renderStatusPanel(d) { w.replaceChildren(); w.append(el("div", "sp-row", "upstream: " + d.origin.state + (d.origin.msg ? " (" + d.origin.msg + ")" : ""))); - w.append(el("div", "sp-row", "CDN: " + d.state + " — " + d.kbps + " KB/s")); + w.append(el("div", "sp-row", "CDN: " + d.state + (d.kbps ? " — " + d.kbps + " KB/s" : ""))); const ch = d.hist || []; if (ch.length > 1) { - w.append(sparkline(ch.map(s => s.kbps), d.threshold)); - w.append(el("div", "sp-cap", "CDN, last " + _mins(ch) - + " min (dashed = slow threshold " + Math.round(d.threshold) + " KB/s)")); + w.append(sparkline(ch.map(s => s.kbps), d.kbps ? d.threshold : null)); + w.append(el("div", "sp-cap", "CDN, last " + _mins(ch) + " min" + + (d.kbps ? " (dashed = slow threshold " + Math.round(d.threshold) + " KB/s)" : ""))); } const oh = d.origin.hist || []; if (oh.length > 1) {