From 6a93c0c084be297b6211772d3fcbaf434ef2fda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Thu, 4 Apr 2024 22:04:17 +0200 Subject: [PATCH 01/29] test: Add initial tests for `nrkRadio` #18 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- deno.json | 5 +++-- lib/nrk.ts | 7 ++++++- lib/nrk_test.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 lib/nrk_test.ts diff --git a/deno.json b/deno.json index e8e12b5..e2e26bd 100644 --- a/deno.json +++ b/deno.json @@ -16,7 +16,8 @@ "@preact/signals": "https://esm.sh/*@preact/signals@1.2.2", "@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1", "$gfm": "https://deno.land/x/gfm@0.6.0/mod.ts", - "openapi-typescript": "npm:openapi-typescript@6.7.5" + "openapi-typescript": "npm:openapi-typescript@6.7.5", + "$std/": "https://deno.land/std@0.221.0/" }, "compilerOptions": { "jsx": "react-jsx", @@ -39,4 +40,4 @@ ] } } -} +} \ No newline at end of file diff --git a/lib/nrk.ts b/lib/nrk.ts index 75328b7..552f916 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -42,8 +42,12 @@ async function withDownloadLink( } } +// TODO: Replace throws with `console.error` and return `null` if bad response. export const nrkRadio = { search: async (query: string): Promise => { + if (query === "") { + throw "Empty search query."; + } const [status, response] = await get< searchComponents["schemas"]["searchresult"] >(`${nrkAPI}/radio/search/search?q=${query}`); @@ -99,6 +103,7 @@ export const nrkRadio = { if (status === OK && episode) { return episode; } - throw `Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`; + console.error(`Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`); + return null; }, }; diff --git a/lib/nrk_test.ts b/lib/nrk_test.ts new file mode 100644 index 0000000..0d67a9f --- /dev/null +++ b/lib/nrk_test.ts @@ -0,0 +1,41 @@ +import { assertEquals } from "$std/assert/assert_equals.ts"; +import { assertRejects } from "$std/assert/assert_rejects.ts"; +import { nrkRadio } from "./nrk.ts"; +import { assertGreaterOrEqual } from "$std/assert/assert_greater_or_equal.ts"; +import { assertExists } from "https://deno.land/std@0.216.0/assert/assert_exists.ts"; + +Deno.test("Verify search query `trygd` returns one result: 'Trygdekontoret'", async () => { + const result = await nrkRadio.search("trygd"); + + assertExists(result); + assertEquals(result.length, 1); + assertEquals(result[0].seriesId, "trygdekontoret"); +}); + +Deno.test("Verify empty search query yields error", async () => { + await assertRejects(async () => await nrkRadio.search("")); +}); + +Deno.test("Verify getting series data for 'trygdekontoret' works", async () => { + const result = await nrkRadio.getSerieData("trygdekontoret"); + + assertGreaterOrEqual(result.episodes.length, 1); +}); + +// TODO: This doesn't work because there is no try-catch system, it seems. +// Deno.test("Verify getting series data for 'trygd' does not works", async () => { +// await assertThrows(async () => await nrkRadio.getSerieData("trygd")); +// }); + +Deno.test("Verify getting episodeId 'l_0bc5e55a-46b5-48a5-85e5-5a46b5d8a562' for 'trygdekontoret' works", async () => { + const result = await nrkRadio.getEpisode("trygdekontoret", "l_0bc5e55a-46b5-48a5-85e5-5a46b5d8a562"); + + assertExists(result); + assertEquals(result.duration.seconds, 4152); +}); + +Deno.test("Verify getting episodeId 'null' for 'trygdekontoret' fails", async () => { + const result = await nrkRadio.getEpisode("trygdekontoret", "null"); + + assertEquals(result, null); +}); -- 2.40.1 From ae7821d31ea6222702908678d850f10a9fb76551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Fri, 5 Apr 2024 17:30:10 +0200 Subject: [PATCH 02/29] chore: Prepare for updated version of `kall` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- lib/nrk.ts | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/lib/nrk.ts b/lib/nrk.ts index 552f916..5e4819b 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -1,4 +1,5 @@ -import { get, OK } from "https://deno.land/x/kall@v0.1.0/mod.ts"; +// FIXME: Remember to replace this with actual version +import { get, STATUS_CODE } from "../../kall/mod.ts"; import { components as searchComponents } from "./nrk-search.ts"; import { components as catalogComponents } from "./nrk-catalog.ts"; import { external as playbackComponents } from "./nrk-playback.ts"; @@ -25,17 +26,19 @@ async function withDownloadLink( type: catalogComponents["schemas"]["Type"], ): Promise { // getting stream link - let [playbackStatus, playbackResponse] = await get( + let { status: playbackStatus, body: playbackResponse } = await get( `${nrkAPI}/playback/manifest/podcast/${episode.episodeId}`, ); if (type === "series") { - [playbackStatus, playbackResponse] = await get( + const { status, body } = await get( `${nrkAPI}/playback/manifest/program/${episode.episodeId}`, ); + playbackStatus = status; + playbackResponse = body; } - if (playbackStatus === OK && playbackResponse) { + if (playbackStatus === STATUS_CODE.OK && playbackResponse) { return { ...episode, url: playbackResponse.playable.assets[0].url }; } else { throw `Error getting downloadLink for ${episode.episodeId}, serie: ${episode.originalTitle}. Status: ${playbackStatus}`; @@ -48,18 +51,18 @@ export const nrkRadio = { if (query === "") { throw "Empty search query."; } - const [status, response] = await get< + const { status, body } = await get< searchComponents["schemas"]["searchresult"] >(`${nrkAPI}/radio/search/search?q=${query}`); - if (status === OK && response) { - return response.results.series?.results; + if (status === STATUS_CODE.OK && body) { + return body.results.series?.results; } throw `Something went wrong with ${query} - got status ${status}`; }, getSerieData: async (seriesId: string) => { let [ - [episodeStatus, episodeResponse], - [seriesStatus, serieResponse], + { status: episodeStatus, body: episodeResponse }, + { status: seriesStatus, body: serieResponse }, ] = await Promise.all([ get( `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes`, @@ -69,10 +72,10 @@ export const nrkRadio = { ), ]); - if (episodeStatus !== OK || seriesStatus !== OK) { + if (episodeStatus !== STATUS_CODE.OK || seriesStatus !== STATUS_CODE.OK) { [ - [episodeStatus, episodeResponse], - [seriesStatus, serieResponse], + { status: episodeStatus, body: episodeResponse }, + { status: seriesStatus, body: serieResponse }, ] = await Promise.all([ get( `https://psapi.nrk.no/radio/catalog/series/${seriesId}/episodes`, @@ -84,7 +87,7 @@ export const nrkRadio = { } if ( - episodeStatus === OK && seriesStatus === OK && + episodeStatus === STATUS_CODE.OK && seriesStatus === STATUS_CODE.OK && serieResponse?.series && episodeResponse?._embedded.episodes?.length ) { const episodes = await Promise.all( @@ -99,8 +102,8 @@ export const nrkRadio = { }, getEpisode: async (seriesId: string, episodeId: string): Promise => { const url = `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`; - const [status, episode] = await get(url); - if (status === OK && episode) { + const { status, body: episode } = await get(url); + if (status === STATUS_CODE.OK && episode) { return episode; } console.error(`Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`); -- 2.40.1 From 93d11a56b18e7c154a51a63663d71864d772047e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sat, 6 Apr 2024 23:48:55 +0200 Subject: [PATCH 03/29] refactor: Returns `null` instead of throwing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- lib/nrk.ts | 10 ++++++---- lib/nrk_test.ts | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/nrk.ts b/lib/nrk.ts index 5e4819b..fec43c5 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -45,11 +45,11 @@ async function withDownloadLink( } } -// TODO: Replace throws with `console.error` and return `null` if bad response. export const nrkRadio = { - search: async (query: string): Promise => { + search: async (query: string): Promise => { if (query === "") { - throw "Empty search query."; + console.error("Empty search query."); + return null; } const { status, body } = await get< searchComponents["schemas"]["searchresult"] @@ -57,7 +57,9 @@ export const nrkRadio = { if (status === STATUS_CODE.OK && body) { return body.results.series?.results; } - throw `Something went wrong with ${query} - got status ${status}`; + + console.error(`Something went wrong with ${query} - got status ${status}`); + return null; }, getSerieData: async (seriesId: string) => { let [ diff --git a/lib/nrk_test.ts b/lib/nrk_test.ts index 0d67a9f..cb4eb03 100644 --- a/lib/nrk_test.ts +++ b/lib/nrk_test.ts @@ -1,5 +1,4 @@ import { assertEquals } from "$std/assert/assert_equals.ts"; -import { assertRejects } from "$std/assert/assert_rejects.ts"; import { nrkRadio } from "./nrk.ts"; import { assertGreaterOrEqual } from "$std/assert/assert_greater_or_equal.ts"; import { assertExists } from "https://deno.land/std@0.216.0/assert/assert_exists.ts"; @@ -12,8 +11,9 @@ Deno.test("Verify search query `trygd` returns one result: 'Trygdekontoret'", as assertEquals(result[0].seriesId, "trygdekontoret"); }); -Deno.test("Verify empty search query yields error", async () => { - await assertRejects(async () => await nrkRadio.search("")); +Deno.test("Verify empty search query yields `null`", async () => { + const result = await nrkRadio.search(""); + assertEquals(result, null); }); Deno.test("Verify getting series data for 'trygdekontoret' works", async () => { -- 2.40.1 From 23ce66684721af4c8b47a3384d00b8e839f294c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sat, 6 Apr 2024 23:56:35 +0200 Subject: [PATCH 04/29] refactor: Returns `null` instead of throwing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- lib/nrk.ts | 15 +++++++++++---- lib/nrk_test.ts | 9 +++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/nrk.ts b/lib/nrk.ts index fec43c5..55ff674 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -8,6 +8,8 @@ type ArrayElement = A extends readonly (infer T)[] ? T : never; type PodcastEpisodes = catalogComponents["schemas"]["EpisodesHalResource"]; type Podcast = catalogComponents["schemas"]["SeriesHalResource"]; type PodcastEpisodesSingle = catalogComponents["schemas"]["EpisodeHalResource"]; +type RadioSeriesEpisode = catalogComponents["schemas"]["EpisodesHalResource"]; +type RadioSeries = catalogComponents["schemas"]["SeriesHalResource"]; export type Serie = catalogComponents["schemas"]["SeriesViewModel"]; export type OriginalEpisode = PodcastEpisodesSingle & { url: string }; @@ -61,7 +63,9 @@ export const nrkRadio = { console.error(`Something went wrong with ${query} - got status ${status}`); return null; }, - getSerieData: async (seriesId: string) => { + getSerieData: async ( + seriesId: string, + ): Promise<{ episodes: OriginalEpisode[] } & (RadioSeries["series"] | Podcast["series"]) | null> => { let [ { status: episodeStatus, body: episodeResponse }, { status: seriesStatus, body: serieResponse }, @@ -79,10 +83,10 @@ export const nrkRadio = { { status: episodeStatus, body: episodeResponse }, { status: seriesStatus, body: serieResponse }, ] = await Promise.all([ - get( + get( `https://psapi.nrk.no/radio/catalog/series/${seriesId}/episodes`, ), - get( + get( `https://psapi.nrk.no/radio/catalog/series/${seriesId}`, ), ]); @@ -100,7 +104,10 @@ export const nrkRadio = { episodes, }; } - throw `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`; + console.error( + `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`, + ); + return null; }, getEpisode: async (seriesId: string, episodeId: string): Promise => { const url = `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`; diff --git a/lib/nrk_test.ts b/lib/nrk_test.ts index cb4eb03..d31cdb1 100644 --- a/lib/nrk_test.ts +++ b/lib/nrk_test.ts @@ -19,13 +19,14 @@ Deno.test("Verify empty search query yields `null`", async () => { Deno.test("Verify getting series data for 'trygdekontoret' works", async () => { const result = await nrkRadio.getSerieData("trygdekontoret"); + assertExists(result); assertGreaterOrEqual(result.episodes.length, 1); }); -// TODO: This doesn't work because there is no try-catch system, it seems. -// Deno.test("Verify getting series data for 'trygd' does not works", async () => { -// await assertThrows(async () => await nrkRadio.getSerieData("trygd")); -// }); +Deno.test("Verify getting series data for 'trygd' does not works", async () => { + const result = await nrkRadio.getSerieData("trygd"); + assertEquals(result, null); +}); Deno.test("Verify getting episodeId 'l_0bc5e55a-46b5-48a5-85e5-5a46b5d8a562' for 'trygdekontoret' works", async () => { const result = await nrkRadio.getEpisode("trygdekontoret", "l_0bc5e55a-46b5-48a5-85e5-5a46b5d8a562"); -- 2.40.1 From 0982cb002c8f3c72c31e105d2e64b5cba8dde7cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sat, 6 Apr 2024 23:59:59 +0200 Subject: [PATCH 05/29] refactor: Less nesting, easier to read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- lib/nrk.ts | 130 ++++++++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 62 deletions(-) diff --git a/lib/nrk.ts b/lib/nrk.ts index 55ff674..b087086 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -47,75 +47,81 @@ async function withDownloadLink( } } -export const nrkRadio = { - search: async (query: string): Promise => { - if (query === "") { - console.error("Empty search query."); - return null; - } - const { status, body } = await get< - searchComponents["schemas"]["searchresult"] - >(`${nrkAPI}/radio/search/search?q=${query}`); - if (status === STATUS_CODE.OK && body) { - return body.results.series?.results; - } - - console.error(`Something went wrong with ${query} - got status ${status}`); +async function search(query: string): Promise { + if (query === "") { + console.error("Empty search query."); return null; - }, - getSerieData: async ( - seriesId: string, - ): Promise<{ episodes: OriginalEpisode[] } & (RadioSeries["series"] | Podcast["series"]) | null> => { - let [ + } + const { status, body } = await get< + searchComponents["schemas"]["searchresult"] + >(`${nrkAPI}/radio/search/search?q=${query}`); + if (status === STATUS_CODE.OK && body) { + return body.results.series?.results; + } + + console.error(`Something went wrong with ${query} - got status ${status}`); + return null; +} + +async function getSerieData( + seriesId: string, +): Promise<{ episodes: OriginalEpisode[] } & (RadioSeries["series"] | Podcast["series"]) | null> { + let [ + { status: episodeStatus, body: episodeResponse }, + { status: seriesStatus, body: serieResponse }, + ] = await Promise.all([ + get( + `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes`, + ), + get( + `${nrkAPI}/radio/catalog/podcast/${seriesId}`, + ), + ]); + + if (episodeStatus !== STATUS_CODE.OK || seriesStatus !== STATUS_CODE.OK) { + [ { status: episodeStatus, body: episodeResponse }, { status: seriesStatus, body: serieResponse }, ] = await Promise.all([ - get( - `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes`, + get( + `https://psapi.nrk.no/radio/catalog/series/${seriesId}/episodes`, ), - get( - `${nrkAPI}/radio/catalog/podcast/${seriesId}`, + get( + `https://psapi.nrk.no/radio/catalog/series/${seriesId}`, ), ]); + } - if (episodeStatus !== STATUS_CODE.OK || seriesStatus !== STATUS_CODE.OK) { - [ - { status: episodeStatus, body: episodeResponse }, - { status: seriesStatus, body: serieResponse }, - ] = await Promise.all([ - get( - `https://psapi.nrk.no/radio/catalog/series/${seriesId}/episodes`, - ), - get( - `https://psapi.nrk.no/radio/catalog/series/${seriesId}`, - ), - ]); - } - - if ( - episodeStatus === STATUS_CODE.OK && seriesStatus === STATUS_CODE.OK && - serieResponse?.series && episodeResponse?._embedded.episodes?.length - ) { - const episodes = await Promise.all( - episodeResponse._embedded.episodes.map((episode) => withDownloadLink(episode, serieResponse.type)), - ); - return { - ...serieResponse.series, - episodes, - }; - } - console.error( - `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`, + if ( + episodeStatus === STATUS_CODE.OK && seriesStatus === STATUS_CODE.OK && + serieResponse?.series && episodeResponse?._embedded.episodes?.length + ) { + const episodes = await Promise.all( + episodeResponse._embedded.episodes.map((episode) => withDownloadLink(episode, serieResponse.type)), ); - return null; - }, - getEpisode: async (seriesId: string, episodeId: string): Promise => { - const url = `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`; - const { status, body: episode } = await get(url); - if (status === STATUS_CODE.OK && episode) { - return episode; - } - console.error(`Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`); - return null; - }, + return { + ...serieResponse.series, + episodes, + }; + } + console.error( + `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`, + ); + return null; +} + +async function getEpisode(seriesId: string, episodeId: string): Promise { + const url = `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`; + const { status, body: episode } = await get(url); + if (status === STATUS_CODE.OK && episode) { + return episode; + } + console.error(`Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`); + return null; +} + +export const nrkRadio = { + search, + getSerieData, + getEpisode, }; -- 2.40.1 From 52b7cf7a1358afe39c7437a8ccaebdb99f461cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sun, 7 Apr 2024 00:01:04 +0200 Subject: [PATCH 06/29] chore: Update test-name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- lib/nrk_test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nrk_test.ts b/lib/nrk_test.ts index d31cdb1..12eb645 100644 --- a/lib/nrk_test.ts +++ b/lib/nrk_test.ts @@ -35,7 +35,7 @@ Deno.test("Verify getting episodeId 'l_0bc5e55a-46b5-48a5-85e5-5a46b5d8a562' for assertEquals(result.duration.seconds, 4152); }); -Deno.test("Verify getting episodeId 'null' for 'trygdekontoret' fails", async () => { +Deno.test("Verify getting episodeId 'null' for 'trygdekontoret' yields `null`", async () => { const result = await nrkRadio.getEpisode("trygdekontoret", "null"); assertEquals(result, null); -- 2.40.1 From 2c1d6a3dca413f385375bc93946cf177c914fd3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sun, 7 Apr 2024 00:03:27 +0200 Subject: [PATCH 07/29] refactor: Rename function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- lib/nrk.ts | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/nrk.ts b/lib/nrk.ts index b087086..f25bb27 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -19,34 +19,8 @@ export type SearchResult = ArrayElement & { description?: string; }; -type Manifest = playbackComponents["schemas/playback-channel.json"]["components"]["schemas"]["PlayableManifest"]; - const nrkAPI = `https://psapi.nrk.no`; -async function withDownloadLink( - episode: PodcastEpisodesSingle, - type: catalogComponents["schemas"]["Type"], -): Promise { - // getting stream link - let { status: playbackStatus, body: playbackResponse } = await get( - `${nrkAPI}/playback/manifest/podcast/${episode.episodeId}`, - ); - - if (type === "series") { - const { status, body } = await get( - `${nrkAPI}/playback/manifest/program/${episode.episodeId}`, - ); - playbackStatus = status; - playbackResponse = body; - } - - if (playbackStatus === STATUS_CODE.OK && playbackResponse) { - return { ...episode, url: playbackResponse.playable.assets[0].url }; - } else { - throw `Error getting downloadLink for ${episode.episodeId}, serie: ${episode.originalTitle}. Status: ${playbackStatus}`; - } -} - async function search(query: string): Promise { if (query === "") { console.error("Empty search query."); @@ -97,7 +71,7 @@ async function getSerieData( serieResponse?.series && episodeResponse?._embedded.episodes?.length ) { const episodes = await Promise.all( - episodeResponse._embedded.episodes.map((episode) => withDownloadLink(episode, serieResponse.type)), + episodeResponse._embedded.episodes.map((episode) => getEpisodeWithDownloadLink(episode, serieResponse.type)), ); return { ...serieResponse.series, @@ -120,6 +94,32 @@ async function getEpisode(seriesId: string, episodeId: string): Promise { + // getting stream link + let { status: playbackStatus, body: playbackResponse } = await get( + `${nrkAPI}/playback/manifest/podcast/${episode.episodeId}`, + ); + + if (type === "series") { + const { status, body } = await get( + `${nrkAPI}/playback/manifest/program/${episode.episodeId}`, + ); + playbackStatus = status; + playbackResponse = body; + } + + if (playbackStatus === STATUS_CODE.OK && playbackResponse) { + return { ...episode, url: playbackResponse.playable.assets[0].url }; + } else { + throw `Error getting downloadLink for ${episode.episodeId}, serie: ${episode.originalTitle}. Status: ${playbackStatus}`; + } +} + export const nrkRadio = { search, getSerieData, -- 2.40.1 From f30497a76b28c677cbddcbcc4afb40c01f8a94a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sun, 7 Apr 2024 00:07:44 +0200 Subject: [PATCH 08/29] test: Add test for getEpisodeWithDownloadLink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- lib/nrk.ts | 4 ++++ lib/nrk_test.ts | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/nrk.ts b/lib/nrk.ts index f25bb27..f751cec 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -125,3 +125,7 @@ export const nrkRadio = { getSerieData, getEpisode, }; + +export const forTestingOnly = { + getEpisodeWithDownloadLink, +}; diff --git a/lib/nrk_test.ts b/lib/nrk_test.ts index 12eb645..26297bc 100644 --- a/lib/nrk_test.ts +++ b/lib/nrk_test.ts @@ -2,6 +2,7 @@ import { assertEquals } from "$std/assert/assert_equals.ts"; import { nrkRadio } from "./nrk.ts"; import { assertGreaterOrEqual } from "$std/assert/assert_greater_or_equal.ts"; import { assertExists } from "https://deno.land/std@0.216.0/assert/assert_exists.ts"; +import { forTestingOnly } from "./nrk.ts"; Deno.test("Verify search query `trygd` returns one result: 'Trygdekontoret'", async () => { const result = await nrkRadio.search("trygd"); @@ -40,3 +41,11 @@ Deno.test("Verify getting episodeId 'null' for 'trygdekontoret' yields `null`", assertEquals(result, null); }); + +Deno.test("Verify getting episode, 'l_0bc5e55a-46b5-48a5-85e5-5a46b5d8a562' with download link works`", async () => { + const series = await nrkRadio.getSerieData("trygdekontoret"); + assertExists(series); + + const result = await forTestingOnly.getEpisodeWithDownloadLink(series.episodes[0], "podcast"); + assertExists(result); +}); -- 2.40.1 From da4036e65383c935b4f1c0e94ea21875da0c11a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sun, 7 Apr 2024 00:18:33 +0200 Subject: [PATCH 09/29] chore: Make compatiable with not throwing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- routes/index.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/routes/index.tsx b/routes/index.tsx index 1d04993..288810d 100644 --- a/routes/index.tsx +++ b/routes/index.tsx @@ -7,18 +7,18 @@ import SeriesCard from "../components/SeriesCard.tsx"; import { nrkRadio, SearchResultList } from "../lib/nrk.ts"; import { CSS, render } from "$gfm"; -interface HandlerData { +type Props = { query: string; origin: string; rawMarkdown: string; - result?: SearchResultList; -} + result?: SearchResultList | null; +}; -export const handler: Handlers = { +export const handler: Handlers = { async GET(request, ctx) { const url = new URL(request.url); const query = url.searchParams.get("query"); - let result: SearchResultList | undefined; + let result: Props["result"]; if (query) { result = await nrkRadio.search(query); } @@ -27,7 +27,7 @@ export const handler: Handlers = { }, }; -export default function Home({ data }: PageProps) { +export default function Home({ data }: PageProps) { return ( <> @@ -57,7 +57,7 @@ export default function Home({ data }: PageProps) { ); } -function SearchResult({ result, origin }: { result: SearchResultList; origin: string }) { +function SearchResult({ result, origin }: { result: Props["result"]; origin: string }) { if (!result) { return null; } -- 2.40.1 From bac665a57a8c5b220ac1c572a9c04d39ab8af413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sun, 7 Apr 2024 00:18:46 +0200 Subject: [PATCH 10/29] chore: Formatting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index e2e26bd..865a2af 100644 --- a/deno.json +++ b/deno.json @@ -40,4 +40,4 @@ ] } } -} \ No newline at end of file +} -- 2.40.1 From 6c10e738ec532407b2083b0e3286c999bd4a06c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sun, 7 Apr 2024 00:19:09 +0200 Subject: [PATCH 11/29] refactor: Move responsibility of where fetch should happen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- lib/nrk.ts | 5 ++--- routes/api/feeds/[seriesId].ts | 22 +++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/nrk.ts b/lib/nrk.ts index f751cec..09100ae 100644 --- a/lib/nrk.ts +++ b/lib/nrk.ts @@ -37,9 +37,8 @@ async function search(query: string): Promise { return null; } -async function getSerieData( - seriesId: string, -): Promise<{ episodes: OriginalEpisode[] } & (RadioSeries["series"] | Podcast["series"]) | null> { +export type SeriesData = { episodes: OriginalEpisode[] } & (RadioSeries["series"] | Podcast["series"]); +async function getSerieData(seriesId: string): Promise { let [ { status: episodeStatus, body: episodeResponse }, { status: seriesStatus, body: serieResponse }, diff --git a/routes/api/feeds/[seriesId].ts b/routes/api/feeds/[seriesId].ts index 8729249..9a66ffd 100644 --- a/routes/api/feeds/[seriesId].ts +++ b/routes/api/feeds/[seriesId].ts @@ -2,6 +2,7 @@ import { nrkRadio, OriginalEpisode } from "../../../lib/nrk.ts"; import { FreshContext } from "$fresh/server.ts"; import { declaration, serialize, tag } from "https://raw.githubusercontent.com/olaven/serialize-xml/v0.4.0/mod.ts"; import { getHostName } from "../../../utils.ts"; +import { SeriesData } from "../../../lib/nrk.ts"; function toItemTag(seriesId: string, episode: OriginalEpisode) { const description = episode.titles.subtitle || ""; @@ -28,10 +29,9 @@ function toItemTag(seriesId: string, episode: OriginalEpisode) { ]); } -async function buildFeed(seriesId: string) { - const serie = await nrkRadio.getSerieData(seriesId); - const imageUrl = serie.squareImage?.at(-1)?.url ?? ""; - const linkValue = `https://radio.nrk.no/podkast/${serie.id}`; +function buildFeed(series: SeriesData) { + const imageUrl = series.squareImage?.at(-1)?.url ?? ""; + const linkValue = `https://radio.nrk.no/podkast/${series.id}`; // Quickly adapted from https://raw.githubusercontent.com/olaven/paperpod/1cde9abd3174b26e126aa74fc5a3b63fd078c0fd/packages/converter/src/rss.ts return serialize( @@ -43,7 +43,7 @@ async function buildFeed(seriesId: string) { "rss", [ tag("channel", [ - tag("title", serie.titles.title), + tag("title", series.titles.title), tag("link", linkValue), tag("itunes:author", "NRK"), /* serie.category.id does not overlap with Apple's supported categories.. @@ -58,7 +58,7 @@ async function buildFeed(seriesId: string) { ]), tag( "description", - serie.titles.subtitle || "", + series.titles.subtitle || "", ), tag("ttl", "60"), //60 minutes ...(imageUrl @@ -68,12 +68,12 @@ async function buildFeed(seriesId: string) { ]), tag("image", [ tag("url", imageUrl), - tag("title", serie.titles.title), + tag("title", series.titles.title), tag("link", linkValue), ]), ] : []), - ...serie.episodes.map((episode) => toItemTag(seriesId, episode)), + ...series.episodes.map((episode) => toItemTag(series.id, episode)), ]), ], [ @@ -88,8 +88,12 @@ async function buildFeed(seriesId: string) { export const handler = async (_req: Request, ctx: FreshContext): Promise => { const seriesId = ctx.params.seriesId; + const series = await nrkRadio.getSerieData(seriesId); + if (!series) { + return new Response(`Couldn't find series with seriesId: ${seriesId}`, { status: 404 }); + } try { - const feedContent = await buildFeed(seriesId); + const feedContent = buildFeed(series); return new Response(feedContent, { headers: { -- 2.40.1 From ded707687439c003ed6902031f5dc6332fd57e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20H=C3=A5rek=20Andreassen?= Date: Sun, 7 Apr 2024 00:21:08 +0200 Subject: [PATCH 12/29] chore: Use status codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- routes/api/feeds/[seriesId].ts | 6 +++--- routes/api/feeds/[seriesId]/[episodeId]/chapters.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/routes/api/feeds/[seriesId].ts b/routes/api/feeds/[seriesId].ts index 9a66ffd..4e78e02 100644 --- a/routes/api/feeds/[seriesId].ts +++ b/routes/api/feeds/[seriesId].ts @@ -1,5 +1,5 @@ import { nrkRadio, OriginalEpisode } from "../../../lib/nrk.ts"; -import { FreshContext } from "$fresh/server.ts"; +import { FreshContext, STATUS_CODE } from "$fresh/server.ts"; import { declaration, serialize, tag } from "https://raw.githubusercontent.com/olaven/serialize-xml/v0.4.0/mod.ts"; import { getHostName } from "../../../utils.ts"; import { SeriesData } from "../../../lib/nrk.ts"; @@ -90,7 +90,7 @@ export const handler = async (_req: Request, ctx: FreshContext): Promise Date: Sun, 7 Apr 2024 00:23:18 +0200 Subject: [PATCH 13/29] refactor: Clean up types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tim Hårek Andreassen --- components/Search.tsx | 4 ++-- routes/index.tsx | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/components/Search.tsx b/components/Search.tsx index e264d49..da0863a 100644 --- a/components/Search.tsx +++ b/components/Search.tsx @@ -1,4 +1,4 @@ -export default function Search(props: { defaultValue: string }) { +export default function Search(props: { defaultValue: string | null }) { return (
@@ -8,7 +8,7 @@ export default function Search(props: { defaultValue: string }) { className="border-2 rounded-md px-2" name="query" id="query" - defaultValue={props.defaultValue} + defaultValue={props.defaultValue ?? ""} />