perf: decode Zoko configuration in place
This commit is contained in:
@@ -16,6 +16,7 @@ enum ZokoError: String, LocalizedError {
|
|||||||
enum ZokoResolver {
|
enum ZokoResolver {
|
||||||
static let bodyLimit = 2 * 1024 * 1024
|
static let bodyLimit = 2 * 1024 * 1024
|
||||||
static let referer = "Referer: https://zokoanime.video/\r\n"
|
static let referer = "Referer: https://zokoanime.video/\r\n"
|
||||||
|
private static let codecKey = Array("otaku-embed-v1".utf8)
|
||||||
|
|
||||||
static func validResource(_ url: URL, playlist: Bool = false) -> Bool {
|
static func validResource(_ url: URL, playlist: Bool = false) -> Bool {
|
||||||
guard ResolvedSource.validURL(url.absoluteString), url.scheme == "https",
|
guard ResolvedSource.validURL(url.absoluteString), url.scheme == "https",
|
||||||
@@ -47,6 +48,7 @@ enum ZokoResolver {
|
|||||||
if response.statusCode == 403 || response.statusCode == 429 || response.statusCode == 503 { throw ZokoError.protection }
|
if response.statusCode == 403 || response.statusCode == 429 || response.statusCode == 503 { throw ZokoError.protection }
|
||||||
guard response.statusCode == 200 else { throw ZokoError.transport }
|
guard response.statusCode == 200 else { throw ZokoError.transport }
|
||||||
var data = Data()
|
var data = Data()
|
||||||
|
if response.expectedContentLength > 0 { data.reserveCapacity(Int(response.expectedContentLength)) }
|
||||||
for try await byte in bytes {
|
for try await byte in bytes {
|
||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
guard data.count < bodyLimit else { throw ZokoError.malformed }
|
guard data.count < bodyLimit else { throw ZokoError.malformed }
|
||||||
@@ -88,17 +90,18 @@ enum ZokoResolver {
|
|||||||
lower.contains("captcha") || lower.contains("sorry, you have been blocked") { throw ZokoError.protection }
|
lower.contains("captcha") || lower.contains("sorry, you have been blocked") { throw ZokoError.protection }
|
||||||
throw ZokoError.malformed
|
throw ZokoError.malformed
|
||||||
}
|
}
|
||||||
guard let encoded = Data(base64Encoded: String(page[range])), !encoded.isEmpty else { throw ZokoError.malformed }
|
guard var decoded = Data(base64Encoded: String(page[range])), !decoded.isEmpty else { throw ZokoError.malformed }
|
||||||
let key = Array("otaku-embed-v1".utf8)
|
decoded.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) in
|
||||||
var decoded = Array(encoded)
|
let bytes = buffer.bindMemory(to: UInt8.self)
|
||||||
for i in decoded.indices { decoded[i] ^= key[i % key.count] }
|
for i in bytes.indices { bytes[i] ^= codecKey[i % codecKey.count] }
|
||||||
|
}
|
||||||
struct Config: Decodable {
|
struct Config: Decodable {
|
||||||
struct Subtitle: Decodable { let src: String; let lang: String; let label: String; let `default`: Bool? }
|
struct Subtitle: Decodable { let src: String; let lang: String; let label: String; let `default`: Bool? }
|
||||||
let src: String
|
let src: String
|
||||||
let subtitles: [Subtitle]
|
let subtitles: [Subtitle]
|
||||||
}
|
}
|
||||||
let config: Config
|
let config: Config
|
||||||
do { config = try JSONDecoder().decode(Config.self, from: Data(decoded)) }
|
do { config = try JSONDecoder().decode(Config.self, from: decoded) }
|
||||||
catch { throw ZokoError.malformed }
|
catch { throw ZokoError.malformed }
|
||||||
guard let url = URL(string: config.src), validResource(url, playlist: true), url.path.hasSuffix(".m3u8"), config.subtitles.count <= 32 else { throw ZokoError.malformed }
|
guard let url = URL(string: config.src), validResource(url, playlist: true), url.path.hasSuffix(".m3u8"), config.subtitles.count <= 32 else { throw ZokoError.malformed }
|
||||||
let subtitles = try config.subtitles.map { row -> ResolvedSubtitle in
|
let subtitles = try config.subtitles.map { row -> ResolvedSubtitle in
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ Terebi resolves bounded static `window.__P` data over ordinary verified HTTPS, t
|
|||||||
|
|
||||||
Use stable `anilist:<ID>:<episode>` for resume and report registration. Queue/enqueue documents keep their existing version-1 shape and use the watch URL. Version-2 reports keep exactly the existing event/position/duration/eligibility fields, substituting `media_identity: {"provider":"anilist","media_id":"1","episode":1}`. Validate against the exact expected provider/ID/episode; never reinterpret an old `miruro` identity as AniList. Explicit external source documents also accept the `anilist` identity namespace with decimal IDs, retaining the existing strict native same-origin resource policy.
|
Use stable `anilist:<ID>:<episode>` for resume and report registration. Queue/enqueue documents keep their existing version-1 shape and use the watch URL. Version-2 reports keep exactly the existing event/position/duration/eligibility fields, substituting `media_identity: {"provider":"anilist","media_id":"1","episode":1}`. Validate against the exact expected provider/ID/episode; never reinterpret an old `miruro` identity as AniList. Explicit external source documents also accept the `anilist` identity namespace with decimal IDs, retaining the existing strict native same-origin resource policy.
|
||||||
|
|
||||||
`make check-zoko check-reporting check-cli check-stream check-progress` is the focused non-GUI regression command. Optional `make check-zoko-live` uses the real native decoder (paused frame stepping, ten-minute seek/decode, restored position), VTT text at acknowledged positions and actual version-2 progress. It does not start GUI/audio playback or verify visible rendering/full episodes; Core Audio initializes a device. These commands are provided for integrated verification, not a claim they have been exercised.
|
`make check-zoko check-reporting check-cli check-stream check-progress` is the focused non-GUI regression command. Optional `make check-zoko-live` uses the real native decoder (paused frame stepping, ten-minute seek/decode, restored position), VTT text at acknowledged positions and actual version-2 progress. It does not start GUI/audio playback or verify visible rendering/full episodes; Core Audio initializes a device.
|
||||||
|
|
||||||
|
Integrated verification on 2026-10-25 passed all six targets above, including live fresh AniList 1 episode 1 resolution and actual native frame/seek/VTT/resume/version-2 reporting. The live run emitted redacted FFmpeg warnings/errors despite completing its assertions; it was not warning-free. The deployed static assignment omits its semicolon, which the parser explicitly supports without evaluating JavaScript. No GUI/browser test, audio-output test or full-episode verification was performed.
|
||||||
|
|
||||||
## Launch contract
|
## Launch contract
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user