diff --git a/deno.json b/deno.json index 17eea29..9c8e3f6 100644 --- a/deno.json +++ b/deno.json @@ -12,7 +12,8 @@ "@preact/signals": "https://esm.sh/*@preact/signals@1.2.2", "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1", "twind": "https://esm.sh/twind@0.16.17", - "twind/": "https://esm.sh/twind@0.16.17/" + "twind/": "https://esm.sh/twind@0.16.17/", + "zod": "https://deno.land/x/zod@v3.22.4/mod.ts" }, "compilerOptions": { "jsx": "react-jsx", @@ -35,4 +36,4 @@ ] } } -} +} \ No newline at end of file diff --git a/lib/nrk.ts b/lib/nrk.ts index f22f691..a547aac 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -1,6 +1,7 @@ import { get, OK } from "https://deno.land/x/kall@v0.1.0/mod.ts"; import { components as searchComponents } from "./nrk-search.ts"; import { components as catalogComponents } from "./nrk-catalog.ts"; +import { z } from "zod"; type ArrayElement = A extends readonly (infer T)[] ? T : never; export type Serie = catalogComponents["schemas"]["SeriesViewModel"]; @@ -11,20 +12,22 @@ export type SearchResult = ArrayElement & { description?: string; }; -/* Incomplete manifest types */ -interface Manifest { - id: string; - playable: { - endSequenceStartTime: null; - duration: string; - assets: { - url: string; - format: string; - mimeType: string; - encrypted: boolean; - }[]; - }; -} +const manifestSchema = z.object({ + id: z.string(), + playable: z.object({ + endSequenceStartTime: z.null(), + duration: z.string(), + assets: z.array(z.object({ + url: z.string(), + format: z.string(), + mimeType: z.string(), + encrypted: z.boolean(), + })), + }), +}); + +/** Incomplete manifest types. */ +type Manifest = z.infer; const nrkAPI = `https://psapi.nrk.no`; @@ -32,10 +35,12 @@ async function withDownloadLink( episode: _OriginalEpisode, ): Promise { // getting stream link - const [playbackStatus, playbackResponse] = await get( + const [playbackStatus, playbackResponseRaw] = await get( `${nrkAPI}/playback/manifest/podcast/${episode.episodeId}`, ); + const playbackResponse = manifestSchema.parse(playbackResponseRaw); + if (playbackStatus === OK && playbackResponse) { return { ...episode, url: playbackResponse.playable.assets[0].url }; } else {