Intro skip: parse Jikan v4's actual response shape

Jikan /anime/{id}/episodes returns the episode list DIRECTLY under
'data' (flat array), not {'episodes': [...]}. The old code called
.get() on that list -> AttributeError, swallowed by the endpoint's
except -> [] -> the skip button could never appear, even with Jikan
up. Accept both shapes now.

Verified end-to-end with a mocked /api/intros response: the button is
wired into the player, visible exactly while currentTime is inside the
intro window (111-188s test), hidden outside it.

The live button still awaits Jikan/MAL recovery (504ing since this
feature landed); failures are not cached, so it activates on the first
successful fetch.

Skipped: MAL-direct fallback — official API needs a client key
(403 without), and the keyless HTML page carries no intro data.
This commit is contained in:
jozamudi committed 2026-08-31 00:24:37 +00:00
1 parent eb5082bd3b
commit 27de918cb0
2 files changed
+36 -2

No files matched your search

+6 -2
View File
@@ -132,9 +132,13 @@ def _parse_intros(eps):
def _intros(mal_id): def _intros(mal_id):
"""Jikan (keyless MAL mirror): per-episode intro start/length, seconds""" """Jikan (keyless MAL mirror): per-episode intro start/length, seconds."""
# Jikan v4 returns the list directly under 'data'; tolerate a wrapped
# shape too, so a format change doesn't silently kill the button
r = _get("https://api.jikan.moe/v4/anime/{}".format(mal_id) + "/episodes") r = _get("https://api.jikan.moe/v4/anime/{}".format(mal_id) + "/episodes")
return _parse_intros(r.json().get("data", {}).get("episodes", [])) d = r.json().get("data")
eps = d if isinstance(d, list) else (d or {}).get("episodes", [])
return _parse_intros(eps)
def _master_m3u8(ep_id, lang="eng"): def _master_m3u8(ep_id, lang="eng"):
+30
View File
@@ -312,3 +312,33 @@ def test_parse_intros():
None, None,
None, None,
] ]
def test_intros_parses_jikan_shapes():
# _intros against a stubbed HTTP layer: Jikan v4 returns the episode
# list directly under 'data' (not {'episodes': [...]}) — a regression
# here would silently disable the skip button whenever Jikan is up.
import server.upstream as u
class R:
def json(self):
return {"data": [
{"title": "Episode 1", "intro": {"start": 111, "length": 77}},
{"title": "Episode 2", "intro": {"start": 0, "length": 0}},
]}
u._CACHE.clear()
u._get = lambda url: R() # stub the network
assert u._intros(92573) == [
{"start": 111, "length": 77},
{"start": 0, "length": 0},
]
# wrapped shape still works
class W:
def json(self):
return {"data": {"episodes": [
{"intro": {"start": 90, "length": 60}},
]}}
u._get = lambda url: W()
assert u._intros(92573)[0]["start"] == 90
u._CACHE.clear()