diff --git a/deno.lock b/deno.lock index 3541790..c71d9fb 100644 --- a/deno.lock +++ b/deno.lock @@ -55,6 +55,16 @@ "https://deno.land/std@0.152.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239", "https://deno.land/std@0.152.0/async/tee.ts": "bcfae0017ebb718cf4eef9e2420e8675d91cb1bcc0ed9b668681af6e6caad846", "https://deno.land/std@0.152.0/http/server.ts": "0b0a9f3abfcfecead944b31ee9098a0c11a59b0495bf873ee200eb80e7441483", + "https://deno.land/std@0.180.0/datetime/_common.ts": "f5c1cb784c616151a3d8198a4ab29f65b7fe5c20a105d8979bde9558c7b52910", + "https://deno.land/std@0.180.0/datetime/constants.ts": "0e91a7019814e6df5ebb710b09aceb79f52cbc618bb06b34431f4f8b89d79505", + "https://deno.land/std@0.180.0/datetime/day_of_year.ts": "f9b9c8f1958e843fa072510fbb36e455431a3cc74ab8f39757bd0a59f4aaad10", + "https://deno.land/std@0.180.0/datetime/difference.ts": "556a5edb807f36f0ca334f235180650ccf05dfeb94aa2573642ef20314ef101c", + "https://deno.land/std@0.180.0/datetime/format.ts": "416807ca1971c5432bfa7d1e429b7c6360cb81fee598e8510dde6e7b0e819a57", + "https://deno.land/std@0.180.0/datetime/is_leap.ts": "cc74a158e029ffe1dfcbdd911107a6de428ee64585b1071d8fd4e1a997670d30", + "https://deno.land/std@0.180.0/datetime/mod.ts": "858d0d695b3a391a9c5faa561e46813e346feaef65bd6ddadb7ee2e65ac4e5a8", + "https://deno.land/std@0.180.0/datetime/parse.ts": "b0af5e272382e516f34dddf9dd1eda31f1197bdf8ee2ff7d93505073e97bd76b", + "https://deno.land/std@0.180.0/datetime/to_imf.ts": "851d072a1c86c7ec34c753e61b1951ceda50c9da805b170a0ecaea3b024dd266", + "https://deno.land/std@0.180.0/datetime/week_of_year.ts": "00ce4203f1efc69e2c0902ff4a4451a49d8a87a561e941b692f3b5f056f1f441", "https://deno.land/x/code_block_writer@11.0.3/mod.ts": "2c3448060e47c9d08604c8f40dee34343f553f33edcdfebbf648442be33205e5", "https://deno.land/x/code_block_writer@11.0.3/utils/string_utils.ts": "60cb4ec8bd335bf241ef785ccec51e809d576ff8e8d29da43d2273b69ce2a6ff", "https://deno.land/x/denoflate@1.2.1/mod.ts": "f5628e44b80b3d80ed525afa2ba0f12408e3849db817d47a883b801f9ce69dd6", @@ -110,6 +120,7 @@ "https://deno.land/x/ts_morph@16.0.0/ts_morph.d.ts": "38668b0e3780282a56a805425494490b0045d1928bd040c47a94095749dab8c3", "https://deno.land/x/ts_morph@16.0.0/ts_morph.js": "9fc0f3d6a3997c2df023fabc4e529d2117d214ffd4fd04247ca2f56c4e9cd470", "https://esm.sh/*preact-render-to-string@5.2.4": "5c965103e5039039a89c5de1f551a690aecd3918afe53b5d967ce5d96d939584", + "https://esm.sh/iso8601-duration@2.1.1": "111246e7e1d01ec7d70bf0f84be58d00d09cb1b88c2136f98d05046aea8376a2", "https://esm.sh/preact@10.11.0": "e888b244446037c56f1881173fb51d1f5fa7aae5599e6c5154619346a6a5094e", "https://esm.sh/preact@10.11.0/hooks": "2b8ec155eb8b87501663f074acff1d55a9114fa7d88f0b39da06c940af1ff736", "https://esm.sh/preact@10.11.0/jsx-runtime": "5c123264f19799ab243211132dded45f6d42d594b5c78dd585f947d07bf20eae", @@ -119,6 +130,8 @@ "https://esm.sh/twind@0.16.17": "519a5a4d20ff4f797ddf747af70a5600b3581c50a6edf38a4d77e8d7b4caff74", "https://esm.sh/twind@0.16.17/sheets": "5a2b275c294ceffb1b09f2cc367b386172b6ccaa14273dbde473865d4d3f6721", "https://esm.sh/v111/csstype@3.1.1/index.d.ts": "1c29793071152b207c01ea1954e343be9a44d85234447b2b236acae9e709a383", + "https://esm.sh/v111/iso8601-duration@2.1.1/deno/iso8601-duration.js": "5acc3e5498178f378a2b9e724c6493bd92310f369056293dbc49cc90adb8e3b6", + "https://esm.sh/v111/iso8601-duration@2.1.1/lib/index.d.ts": "0765e891e321959e930ffd832d20e7937eb8b279e1fc9cf15b35b0db0c7b8541", "https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/deno/preact-render-to-string.js": "7651121e4bb5ef2a48f840dba3f0c13087293ca2eb825aa23ffea3a87bd463b3", "https://esm.sh/v111/preact-render-to-string@5.2.4/X-ZS8q/src/index.d.ts": "b1d73703252c8570fdf2952475805f5808ba3511fefbd93a3e7bd8406de7dcd0", "https://esm.sh/v111/preact@10.11.0/hooks/src/index.d.ts": "5c29febb624fc25d71cb0e125848c9b711e233337a08f7eacfade38fd4c14cc3", @@ -129,6 +142,7 @@ "https://esm.sh/v111/twind@0.16.17/deno/sheets.js": "c504a460f5df2954f7334a821ef3ac9bedfc94dcc3f0dbf60af8d22f000391db", "https://esm.sh/v111/twind@0.16.17/deno/twind.js": "076dfc344b9507b3f5ee8a20d0b1e6eeb2ea3c6688649026ed0e13e4de158ff2", "https://esm.sh/v111/twind@0.16.17/sheets/sheets.d.ts": "103ecfb19311b86eb5f7a4899f6a3b1192a84cfb73b6ff9a8b84d78b053e6704", - "https://esm.sh/v111/twind@0.16.17/twind.d.ts": "35492f009a6d8695748d121d0b3b210b0819a663eafc77d68296d3110d6ec27f" + "https://esm.sh/v111/twind@0.16.17/twind.d.ts": "35492f009a6d8695748d121d0b3b210b0819a663eafc77d68296d3110d6ec27f", + "https://raw.githubusercontent.com/olaven/serialize-xml/v0.4.0/mod.ts": "0d8a42635bd6e0130b248d9bae27ab36a40076cfa7a9be0f743fa51d7f64eb9f" } } diff --git a/fresh.gen.ts b/fresh.gen.ts index 93cf232..4d62744 100644 --- a/fresh.gen.ts +++ b/fresh.gen.ts @@ -4,13 +4,15 @@ import config from "./deno.json" assert { type: "json" }; import * as $0 from "./routes/api/feeds/[seriesId].ts"; -import * as $1 from "./routes/index.tsx"; +import * as $1 from "./routes/api/feeds/[seriesId]/[episodeId]/chapters.ts"; +import * as $2 from "./routes/index.tsx"; import * as $$0 from "./islands/CopyButton.tsx"; const manifest = { routes: { "./routes/api/feeds/[seriesId].ts": $0, - "./routes/index.tsx": $1, + "./routes/api/feeds/[seriesId]/[episodeId]/chapters.ts": $1, + "./routes/index.tsx": $2, }, islands: { "./islands/CopyButton.tsx": $$0, diff --git a/lib/nrk.ts b/lib/nrk.ts index 5c1f28f..1a97606 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -6,7 +6,7 @@ import { components as catalogComponents } from './nrk-catalog.ts'; type ArrayElement = A extends readonly (infer T)[] ? T : never export type Serie = catalogComponents["schemas"]["SeriesViewModel"] type OriginalEpisode = catalogComponents["schemas"]["EpisodeHalResource"]; -export type Episode = OriginalEpisode & { url: string; } +export type OriginalEpisode = OriginalEpisode & { url: string; } export type SearchResultList = searchComponents["schemas"]["seriesResult"]["results"]; export type SearchResult = ArrayElement & { description?: string } @@ -25,7 +25,7 @@ interface Manifest { }; } -async function withDownloadLink(episode: OriginalEpisode): Promise { +async function withDownloadLink(episode: OriginalEpisode): Promise { // getting stream link const [playbackStatus, playbackResponse] = await get(`https://psapi.nrk.no/playback/manifest/podcast/${episode.episodeId}`) @@ -67,5 +67,16 @@ export const nrkRadio = { throw `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}` } + }, + getEpisode: async (seriesId: string, episodeId: string): Promise => { + + + const url = `https://psapi.nrk.no/radio/catalog/podcast/${seriesId}/episodes/${episodeId}` + const [status, episode] = await get(url); + if (status !== OK) { + throw `Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`; + } + + return episode; } } diff --git a/routes/api/feeds/[seriesId].ts b/routes/api/feeds/[seriesId].ts index 3772feb..c59fa29 100644 --- a/routes/api/feeds/[seriesId].ts +++ b/routes/api/feeds/[seriesId].ts @@ -2,8 +2,10 @@ import { nrkRadio, Episode } from "../../../lib/nrk.ts" import { HandlerContext } from "$fresh/server.ts"; import { serialize, tag, declaration } from "https://raw.githubusercontent.com/olaven/serialize-xml/v0.4.0/mod.ts"; +import { getHostName } from "../../../utils.ts"; + +function toItemTag(seriesId: string, episode: Episode) { -function toItemTag(episode: Episode) { const description = episode.titles.subtitle || ""; return tag("item", [ tag("title", episode.titles.title), @@ -13,6 +15,10 @@ function toItemTag(episode: Episode) { tag("guid", episode.id, [["isPermaLink", "false"]]), tag("pubDate", new Date(episode.date).toUTCString()), tag("itunes:duration", episode.durationInSeconds.toString()), + tag("podcast:chapters", "", [ + ["url", `${getHostName()}/api/feeds/${seriesId}/${episode.episodeId}/chapters`], + ["type", "application/json+chapters"], + ]), tag("enclosure", "", [ ["url", episode.url], ["length", episode.durationInSeconds.toString()], @@ -66,13 +72,14 @@ async function buildFeed(seriesId: string) { tag("link", linkValue), ]) ] : []), - ...serie.episodes.map(toItemTag), + ...serie.episodes.map(episode => toItemTag(seriesId, episode)), ]), ], [ ["version", "2.0"], ["xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd"], ["xmlns:content", "http://purl.org/rss/1.0/modules/content/"], + ["xmlns:podcast", "https://podcastindex.org/namespace/1.0"], ] ) ); @@ -84,7 +91,7 @@ export const handler = async (req: Request, _ctx: HandlerContext): Promise ({ + title: indexPoint.title, + startTime: toSeconds(parse(indexPoint.startPoint)) + })) +} +export const handler = async (req: Request, _ctx: HandlerContext): Promise => { + const seriesId = _ctx.params.seriesId; + const episodeId = _ctx.params.episodeId; + + console.log("going to get ep for ", episodeId); + const episode = await nrkRadio.getEpisode(seriesId, episodeId); + const chapters = toChapters(episode); + + return new Response(JSON.stringify(chapters), { + headers: { + "Content-Type": "application/json+chapters" + } + }); +}; diff --git a/utils.ts b/utils.ts new file mode 100644 index 0000000..5df399d --- /dev/null +++ b/utils.ts @@ -0,0 +1,11 @@ +export function getHostName() { + const deploymentId = Deno.env.get("DENO_DEPLOYMENT_ID"); + if (deploymentId) { + return `https://nrss-${deploymentId}.deno.dev`; + } else { + // assume env + return 'http://localhost:8000'; + } +} + +