feat: enrich Miruro URL playback metadata and timeline

This commit is contained in:
2026-10-22 11:39:56 +02:00
parent b9c7d7f73a
commit a1c8314391
7 changed files with 798 additions and 37 deletions
+244
View File
@@ -0,0 +1,244 @@
import Foundation
@main
struct MiruroMetadataChecks {
struct Failure: Error { let message: String }
struct Promise { let id: Int }
static let mediaID = "OERe_a0YS4h-DlsBDOeXwX7HOO6316tw"
static let canonical = "/watch/\(mediaID)/the-apothecary-diaries-season-3?ep=1"
static func require(_ condition: @autoclosure () -> Bool, _ message: String) throws {
if !condition() { throw Failure(message: message) }
}
static func reject(_ message: String, _ operation: () throws -> Void) throws {
do { try operation() }
catch {
try require(!error.localizedDescription.contains("secret"), "errors do not expose untrusted URLs")
return
}
throw Failure(message: "Accepted \(message)")
}
// Sanitized observed watch schema; every envelope deliberately owns a fresh devalue table.
static func table(_ root: Any) -> [Any] {
var result: [Any] = []
func append(_ value: Any) -> Int {
let index = result.count
result.append(NSNull())
if let promise = value as? Promise {
let reference = append(promise.id)
result[index] = ["Promise", reference] as [Any]
} else if let object = value as? [String: Any] {
var references: [String: Int] = [:]
for key in object.keys.sorted() { references[key] = append(object[key]!) }
result[index] = references
} else if let array = value as? [Any] {
let references = array.map { append($0) }
result[index] = references
} else {
result[index] = value
}
return index
}
_ = append(root)
return result
}
static func wire(_ envelopes: [[String: Any]]) throws -> Data {
var body = Data()
for envelope in envelopes {
body.append(try JSONSerialization.data(withJSONObject: envelope, options: [.sortedKeys]))
body.append(10)
}
return body
}
static func initial(media: Any = ["id": mediaID, "title": ["english": "The Apothecary Diaries Season 3"]],
episodes: Any = Promise(id: 23)) -> [String: Any] {
["type": "data", "nodes": [NSNull(), ["type": "data", "data": table([
"media": media, "episodeNumber": 1, "episodesData": episodes,
"sourcesData": Promise(id: 99), "seasonsData": Promise(id: 8)
])]]]
}
static func chunk(_ payload: Any, id: Int = 23) -> [String: Any] {
["type": "chunk", "id": id, "data": table(payload)]
}
static var rows: [[String: Any]] { [
["episode": 1, "type": "op", "start": 63.313, "end": 153.237],
["episode": 1, "type": "ed", "start": 1335.252, "end": 1425.384]
] }
static func payload(_ intervals: Any = rows) -> [String: Any] {
["currentEpisode": ["number": 1, "title": "Locusts"],
"episodes": [["number": 2, "title": "Not selected"]], "aniskip": intervals]
}
static func decode(_ envelopes: [[String: Any]]) throws -> MiruroMetadata {
try MiruroMetadataResolver.decode(wire(envelopes), mediaID: mediaID, episode: 1)
}
static func checks() async throws {
let unrelated: [String: Any] = ["type": "chunk", "id": 99,
"data": [["streams": 1], ["UnsupportedSourceTag", 999999]]]
let fixtures = [initial(), chunk(payload()), unrelated, chunk(["ignored": "season"], id: 8)]
let metadata = try decode(fixtures)
try require(metadata.title == "The Apothecary Diaries Season 3 — Episode 1: Locusts", "descriptive media and selected episode title")
try require(metadata.intervals.count == 2, "observed fractional opening and ending decoded")
try require(metadata.intervals[0].start == 63.313 && metadata.intervals[1].end == 1425.384, "fractional seconds preserved")
try require(metadata.intervals.allSatisfy { $0.votes == nil && $0.provider == nil && $0.referenceLength == nil }, "absent provenance is not invented")
let reordered = try decode([unrelated, chunk(payload()), initial()])
try require(reordered.title == metadata.title && reordered.intervals == metadata.intervals, "independent and out-of-order promise IDs")
let inline = try decode([initial(episodes: payload())])
try require(inline.intervals == metadata.intervals, "inline episode metadata")
let titleOnly = try decode([initial()])
try require(titleOnly.title == "The Apothecary Diaries Season 3 — Episode 1" && titleOnly.intervals.isEmpty, "unresolved optional episode promise preserves series title")
let rejected = try decode([initial(), ["type": "chunk", "id": 23, "error": ["message": "secret"]]])
try require(rejected.title == titleOnly.title && rejected.intervals.isEmpty, "rejected episode promise preserves title")
let duplicated = try decode([initial(), chunk(payload()), chunk(payload())])
try require(duplicated.title == titleOnly.title && duplicated.intervals.isEmpty, "ambiguous duplicate promise is not selected")
let segmentsOnly = try decode([initial(media: NSNull()), chunk(["aniskip": rows])])
try require(segmentsOnly.title == nil && segmentsOnly.intervals.count == 2, "interval enrichment does not require title")
let episodeOnly = try decode([initial(media: NSNull()), chunk(payload())])
try require(episodeOnly.title == "Episode 1: Locusts", "episode title without media title")
let fallbackTitle = try decode([initial(media: ["title": ["english": " ", "romaji": "Kusuriya"]]), chunk(payload())])
try require(fallbackTitle.title?.hasPrefix("Kusuriya —") == true, "title language fallback")
let selected = try decode([initial(), chunk(["currentEpisode": ["number": 2, "title": "Wrong"],
"episodes": [["number": 2, "title": "Wrong"], ["number": 1, "title": "Correct"]]])])
try require(selected.title?.hasSuffix(": Correct") == true, "episode titles are selected by returned number")
let mixedRows: [Any] = [NSNull(), "bad", ["episode": 1, "type": "op", "start": "bad", "end": 100],
["episode": true, "type": "op", "start": 0, "end": 100],
["episode": 1, "type": "recap", "start": 0, "end": 100]] + rows
let partial = try decode([initial(), chunk(payload(mixedRows))])
try require(partial.title == metadata.title && partial.intervals == metadata.intervals, "malformed rows and unknown kinds do not poison usable metadata")
try reject("wrong returned media identity") {
_ = try decode([initial(media: ["id": "different", "title": "Wrong"]), chunk(payload())])
}
try reject("oversized body") {
_ = try MiruroMetadataResolver.decode(Data(repeating: 32, count: MiruroMetadataResolver.bodyLimit + 1), mediaID: mediaID, episode: 1)
}
try reject("deep JSON") {
_ = try MiruroMetadataResolver.decode(Data((String(repeating: "[", count: 65) + "0" + String(repeating: "]", count: 65)).utf8), mediaID: mediaID, episode: 1)
}
try reject("malformed JSON") {
_ = try MiruroMetadataResolver.decode(Data("{broken".utf8), mediaID: mediaID, episode: 1)
}
let cycle: [String: Any] = ["type": "chunk", "id": 23, "data": [["Promise", 1], 23]]
let cyclic = try decode([initial(), cycle])
try require(cyclic.title == titleOnly.title && cyclic.intervals.isEmpty, "promise cycles are depth bounded and optional")
let amplified: [String: Any] = ["type": "chunk", "id": 23,
"data": [["aniskip": 1], Array(repeating: 2, count: 70000), ["type": 3], "op"]]
let bounded = try decode([initial(), amplified])
try require(bounded.title == titleOnly.title && bounded.intervals.isEmpty, "repeated-reference expansion is bounded")
try normalizationChecks()
try redirectChecks()
let responseURL = URL(string: "https://barelystarted.miruro.tv\(canonical)")!
for mime in ["application/json", "text/sveltekit-data"] {
try MiruroMetadataResolver.validateResponse(HTTPURLResponse(url: responseURL, statusCode: 200,
httpVersion: nil, headerFields: ["Content-Type": mime])!)
}
for (status, type, size) in [(403, "text/html", "12"), (302, "application/json", "12"),
(200, "text/html", "12"), (200, "application/json", "2097153")] {
try reject("protection, redirect or oversized response") {
try MiruroMetadataResolver.validateResponse(HTTPURLResponse(url: responseURL, statusCode: status,
httpVersion: nil, headerFields: ["Content-Type": type, "Content-Length": size])!)
}
}
let cancelled = Task {
withUnsafeCurrentTask { $0?.cancel() }
return try await MiruroMetadataResolver.resolve(mediaID: mediaID, episode: 1)
}
do { _ = try await cancelled.value; throw Failure(message: "Cancellation accepted") }
catch is CancellationError {}
print("PASS: native optional metadata protocol, independent promises, safe redirects, bounded decoding and interval normalization")
}
static func normalizationChecks() throws {
func interval(_ kind: MiruroInterval.Kind = .intro, _ start: Double = 0, _ end: Double = 10,
episode: Int = 1, votes: Double? = nil, provider: String? = nil,
length: Double? = nil) -> MiruroInterval {
MiruroInterval(kind: kind, start: start, end: end, episode: episode,
provider: provider, votes: votes, referenceLength: length)
}
let valid = interval(.intro, 10, 100)
for duration in [0, -1, Double.nan, .infinity] {
try require(MiruroMetadata(title: nil, intervals: [valid]).visibleIntervals(episode: 1, duration: duration).isEmpty, "wait for finite positive actual duration")
}
let invalid = [interval(.intro, -1, 10), interval(.intro, 10, 10), interval(.intro, 20, 10),
interval(.intro, .nan, 10), interval(.intro, 0, .infinity), interval(.intro, 0, 101),
interval(episode: 2), interval(votes: -1), interval(votes: .nan),
interval(provider: "SomeCrunchyrollProvider"), interval(length: 106)]
try require(MiruroMetadata(title: nil, intervals: invalid).visibleIntervals(episode: 1, duration: 100).isEmpty, "bounds, episode, votes, provenance and reference-length rejection")
let metadata = MiruroMetadata(title: nil, intervals: [valid])
try require(metadata.visibleIntervals(episode: 1, duration: 100) == [valid], "end equal to duration allowed")
try require(metadata.visibleIntervals(episode: 1, duration: 99).isEmpty, "duration shrink revalidates without trimming")
try require(metadata.visibleIntervals(episode: 1, duration: 120) == [valid], "duration growth revalidates without scaling")
let first = interval(.intro, 5, 15, votes: 4), tied = interval(.intro, 20, 30, votes: 4)
let ending = interval(.outro, 12, 40, votes: 2)
let ranked = MiruroMetadata(title: nil, intervals: [interval(), first, tied, ending, interval(.outro, 50, 60, votes: 1)])
try require(ranked.visibleIntervals(episode: 1, duration: 100) == [first, ending], "descending votes, stable ties, intro then outro, no merging overlap")
let optional = [interval(length: 105), interval(.outro, 80, 100, length: 0)]
try require(MiruroMetadata(title: nil, intervals: optional).visibleIntervals(episode: 1, duration: 100) == optional, "five-percent equality and absent/nonpositive length handling")
let raw = try decode([initial(), chunk(payload([
["episode": 1, "type": "mixed-op", "start": 1, "end": 10, "votes": 3, "provider": "aniskip", "episode_length": 100],
["episode": 1, "type": "mixed-ed", "start": 90, "end": 100]
]))])
try require(raw.intervals.count == 2 && raw.intervals[0].rawKind == "mixed-op" && raw.intervals[0].votes == 3 && raw.intervals[0].provider == "aniskip" && raw.intervals[0].referenceLength == 100, "evidenced mixed kinds and optional fields preserved")
}
static func redirectChecks() throws {
let envelope = try wire([["type": "redirect", "location": canonical]])
guard case .redirect(let location) = try MiruroMetadataResolver.decodeRoute(envelope, mediaID: mediaID, episode: 1) else {
throw Failure(message: "HTTP-200 route redirect not recognized")
}
var route = try MiruroMetadataResolver.Route(mediaID: mediaID, episode: 1)
try require(route.url.path.hasSuffix("/episode/__data.json"), "placeholder data route")
try route.follow(location)
try require(route.url.path.hasSuffix("/the-apothecary-diaries-season-3/__data.json") && route.url.query == "ep=1", "canonical data route preserves episode")
try reject("redirect loop") { try route.follow(canonical) }
for unsafe in ["https://evil.example\(canonical)", "//evil.example\(canonical)",
"http://barelystarted.miruro.tv\(canonical)", "https://user:secret@barelystarted.miruro.tv\(canonical)",
canonical.replacingOccurrences(of: mediaID, with: "other"), canonical.replacingOccurrences(of: "ep=1", with: "ep=2"),
canonical + "&ep=1", canonical + "#secret", canonical.replacingOccurrences(of: "ep=1", with: "ep=%31"),
canonical.replacingOccurrences(of: "/watch/", with: "/info/"), "/", canonical + "\n",
canonical.replacingOccurrences(of: "/the-apothecary", with: "/%74he-apothecary")] {
try reject("unsafe canonical redirect") {
var route = try MiruroMetadataResolver.Route(mediaID: mediaID, episode: 1)
try route.follow(unsafe)
}
}
var bounded = try MiruroMetadataResolver.Route(mediaID: mediaID, episode: 1)
for index in 0..<MiruroMetadataResolver.redirectLimit { try bounded.follow("/watch/\(mediaID)/slug-\(index)?ep=1") }
try reject("too many redirects") { try bounded.follow("/watch/\(mediaID)/last?ep=1") }
}
static func main() async {
do {
let args = Array(CommandLine.arguments.dropFirst())
if args.count == 2, args[0] == "--live" {
guard case .miruro(let id, let episode) = try StreamURL.parse(args[1]) else {
throw Failure(message: "--live requires a Miruro episode URL")
}
let metadata = try await MiruroMetadataResolver.resolve(mediaID: id, episode: episode)
// JSON escaping prevents terminal control injection; source payloads/URLs are never printed.
let projection: [String: Any] = ["title": metadata.title as Any? ?? NSNull(),
"intervals": metadata.intervals.map { row -> [String: Any] in
["kind": row.kind == .intro ? "intro" : "outro", "episode": row.episode, "start": row.start, "end": row.end]
}]
let data = try JSONSerialization.data(withJSONObject: projection, options: [.sortedKeys])
print(String(decoding: data, as: UTF8.self))
} else if args.isEmpty {
try await checks()
} else {
throw Failure(message: "Usage: miruro-metadata-check [--live EPISODE_URL]")
}
} catch {
let message = (error as? Failure)?.message ?? "Miruro optional metadata check failed."
FileHandle.standardError.write(Data("FAIL: \(message)\n".utf8))
exit(1)
}
}
}