fix: resolve AniList playback using exact MAL mappings
This commit is contained in:
@@ -62,15 +62,19 @@ def main():
|
||||
require(result.returncode != 0 and not result.stdout, "invalid syntax/unsupported URL must fail without JSON or launch")
|
||||
require(not (root / secret).exists(), "invalid source check must not create progress file")
|
||||
if live:
|
||||
for episode, expected in [(1, "available"), (27, "unavailable")]:
|
||||
result = invoke(["--check-source", f"https://zokoanime.video/stream/ani/1/{episode}/sub"])
|
||||
for media_id, episode, expected in [
|
||||
(1, 1, "available"), (1, 27, "unavailable"),
|
||||
(97940, 1, "available"), (97940, 170, "available"),
|
||||
(195604, 1, "available"),
|
||||
]:
|
||||
result = invoke(["--check-source", f"https://zokoanime.video/stream/ani/{media_id}/{episode}/sub"])
|
||||
require(result.returncode == 0, "completed provider checks exit zero")
|
||||
require(result.stdout.endswith(b"\n") and result.stdout.count(b"\n") == 1, "exactly one JSON line")
|
||||
row = json.loads(result.stdout)
|
||||
require(row == {"version": 1, "provider": "zokoanime", "track": "sub", "status": expected},
|
||||
f"live episode {episode} expected {expected}; result is redacted")
|
||||
f"live AniList {media_id} episode {episode} expected {expected}; result is redacted")
|
||||
require(not result.stderr, "successful source checks have no error logs")
|
||||
print("PASS: actual CLI resolves episode 1/root manifest and explicit missing episode 27 headlessly, without state writes")
|
||||
print("PASS: actual CLI resolves exact MAL mappings for Cowboy Bebop and both Black Clover entries; explicit missing remains unavailable; no state writes")
|
||||
else:
|
||||
print("PASS: actual CLI rejects hostile URLs/flags privately, without configuration reads, state writes or bundle launch")
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ struct SourceCheckChecks {
|
||||
throw Failure()
|
||||
}
|
||||
// Exercise error classification, not fake provider availability. Live proof uses the actual CLI below.
|
||||
for category in [ZokoError.unavailable, .transport, .protection, .malformed] {
|
||||
for category in [ZokoError.unavailable, .transport, .protection, .malformed, .mapping] {
|
||||
let result = await SourceCheck.run(mediaID: "1", episode: 1, resolve: { _, _ in throw category },
|
||||
validate: { _ in throw Failure() })
|
||||
try require(result == (category == .unavailable ? .unavailable : .failed), "only explicit missing episode is unavailable")
|
||||
|
||||
+41
-5
@@ -36,6 +36,41 @@ struct ZokoCheck {
|
||||
var config: [String: Any] = ["src": "https://hls.dramahot.top/master.m3u8?token=fixture",
|
||||
"subtitles": [["src": "https://hls.dramahot.top/en.vtt", "lang": "en", "label": "English", "default": true]]]
|
||||
let source = try ZokoResolver.parse(page: page(config), mediaID: "1", episode: 1)
|
||||
let mapped = try await ZokoResolver.resolve(mediaID: "97940", episode: 1, fetchResponse: { request in
|
||||
let data: Data
|
||||
let mime: String
|
||||
if request.url?.absoluteString == "https://graphql.anilist.co/" {
|
||||
let body = try JSONSerialization.jsonObject(with: request.httpBody!) as! [String: Any]
|
||||
try require((body["variables"] as? [String: Int])?["id"] == 97940, "mapping uses the original AniList ID")
|
||||
data = Data(#"{"data":{"Media":{"id":97940,"idMal":34572,"title":{"english":"Black Clover"}}}}"#.utf8)
|
||||
mime = "application/json"
|
||||
} else {
|
||||
try require(request.url?.absoluteString == "https://zokoanime.video/stream/mal/34572/1/sub", "provider lookup uses the exact mapped MAL ID, not AniList ID")
|
||||
data = Data(try page(["src": "https://hls.dramahot.top/master.m3u8", "subtitles": []]).utf8)
|
||||
mime = "text/html"
|
||||
}
|
||||
return (data, HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: ["Content-Type": mime])!)
|
||||
})
|
||||
try require(mapped.identity == "anilist:97940:1" && mapped.title == "Black Clover · Episode 1 (sub)",
|
||||
"mapped playback preserves AniList identity and catalog title")
|
||||
for bad in [
|
||||
#"{"data":{"Media":{"id":34572,"idMal":97940}}}"#,
|
||||
#"{"data":{"Media":{"id":97940,"idMal":0}}}"#,
|
||||
#"{"data":{"Media":{"id":97940,"idMal":-1}}}"#,
|
||||
#"{"data":{"Media":{"id":97940,"idMal":2147483648}}}"#,
|
||||
#"{"data":{"Media":{"id":97940,"idMal":true}}}"#,
|
||||
#"{"data":{"Media":{"id":97940,"idMal":34572}},"errors":[{"message":"partial failure"}]}"#
|
||||
] {
|
||||
try expect(.malformed) { _ = try ZokoResolver.decodeCatalog(Data(bad.utf8), mediaID: "97940") }
|
||||
}
|
||||
do {
|
||||
_ = try await ZokoResolver.resolve(mediaID: "97940", episode: 1, enrichTitle: false, fetchResponse: { request in
|
||||
try require(request.url?.host == "graphql.anilist.co", "missing mapping must not guess a provider ID")
|
||||
return (Data(#"{"data":{"Media":{"id":97940,"idMal":null}}}"#.utf8),
|
||||
HTTPURLResponse(url: request.url!, statusCode: 200, httpVersion: nil, headerFields: ["Content-Type": "application/json"])!)
|
||||
})
|
||||
throw Failure(message: "missing mapping accepted")
|
||||
} catch ZokoError.mapping {}
|
||||
try require(source.identity == "anilist:1:1" && source.requirements.zoko, "native scoped transport selected")
|
||||
try require(source.title == "AniList 1 · Episode 1 (sub)" && source.subtitles.count == 1 && source.subtitles[0].isDefault,
|
||||
"honest fallback title and VTT metadata")
|
||||
@@ -97,10 +132,10 @@ struct ZokoCheck {
|
||||
throw Failure(message: "Native player acknowledgement timed out")
|
||||
}
|
||||
|
||||
static func live() async throws {
|
||||
let source = try await MiruroTransport.prepare(ZokoResolver.resolve(mediaID: "1", episode: 1))
|
||||
static func live(mediaID: String = "1", episode: Int = 1) async throws {
|
||||
let source = try await MiruroTransport.prepare(ZokoResolver.resolve(mediaID: mediaID, episode: episode))
|
||||
defer { _fixLifetime(source) }
|
||||
try require(source.identity == "anilist:1:1" && source.headers.isEmpty, "native relay preserves identity without upstream headers")
|
||||
try require(source.identity == "anilist:\(mediaID):\(episode)" && source.headers.isEmpty, "native relay preserves identity without upstream headers")
|
||||
guard let track = source.subtitles.first(where: \.isDefault) else { throw Failure(message: "Live source has no default subtitle") }
|
||||
let document = try await WebVTTDocument.load(url: track.url, headers: track.headers)
|
||||
guard let cue = document.cues.first(where: { !$0.text.isEmpty && $0.end > $0.start }) else { throw Failure(message: "Live VTT contains no active cues") }
|
||||
@@ -152,16 +187,17 @@ struct ZokoCheck {
|
||||
try require(!rows.isEmpty, "Native progress records emitted")
|
||||
for row in rows {
|
||||
let identity = row["media_identity"] as? [String: Any]
|
||||
try require(row["version"] as? Int == 2 && row["media_url"] == nil && identity?["provider"] as? String == "anilist" && identity?["media_id"] as? String == "1" && identity?["episode"] as? Int == 1,
|
||||
try require(row["version"] as? Int == 2 && row["media_url"] == nil && identity?["provider"] as? String == "anilist" && identity?["media_id"] as? String == mediaID && identity?["episode"] as? Int == episode,
|
||||
"Native progress uses AniList v2 identity with no media URL")
|
||||
}
|
||||
try require(rows.contains { ($0["position_seconds"] as? Double ?? 0) >= 600 }, "Acknowledged seek/resume position reported")
|
||||
print("PASS: fresh AniList 1 episode 1 resolves through scoped relay; actual native video decode/frame step, tracks, 600s seek/decode, VTT text, restored position and v2 progress (no GUI/audio playback)")
|
||||
print("PASS: fresh AniList \(mediaID) episode \(episode) mapped via MAL; actual native decode/frame step, 600s seek/decode, VTT text, restored position and AniList v2 progress (no GUI/audio playback)")
|
||||
}
|
||||
static func main() async {
|
||||
do {
|
||||
try await checks()
|
||||
if CommandLine.arguments.contains("--live") { try await live() }
|
||||
if CommandLine.arguments.contains("--live-black-clover") { try await live(mediaID: "97940") }
|
||||
} catch {
|
||||
FileHandle.standardError.write(Data("FAIL: Zoko check: \(error.localizedDescription)\n".utf8))
|
||||
exit(1)
|
||||
|
||||
Reference in New Issue
Block a user