moved parse function to nrk.ts

This commit is contained in:
olaven 2024-04-07 19:12:01 +02:00
parent ae50117ce1
commit f7813da81f
5 changed files with 52 additions and 58 deletions

View File

@ -1,27 +1,22 @@
import { nrkRadio } from "./nrk/nrk.ts"; import { nrkRadio } from "./nrk/nrk.ts";
import { parse } from "./parse.ts";
import { Series, storage } from "./storage.ts"; import { Series, storage } from "./storage.ts";
import * as datetime from "datetime"; import * as datetime from "datetime";
const SYNC_INTERVAL_HOURS = 1; const SYNC_INTERVAL_HOURS = 1;
async function initialFetch(options: { id: string }) { async function initialFetch(options: { id: string }) {
const fromNrk = await nrkRadio.getSerieData(options.id); const series = await nrkRadio.getSeries(options.id);
const parsed = parse.series(fromNrk); const stored = storage.write(series);
const stored = storage.write(parsed);
if (!stored) { if (!stored) {
throw new Error(`Failed to store series ${options.id}`); throw new Error(`Failed to store series ${options.id}`);
} }
return parsed; return series;
} }
async function updateFetch(existingSeries: Series) { async function updateFetch(existingSeries: Series) {
const fromNrk = await nrkRadio.getSerieData(existingSeries.id); const series = await nrkRadio.getSeries(existingSeries.id);
const parsed = parse.series(fromNrk); const newEpisodes = series.episodes.filter((episode) => {
const newEpisodes = parsed.episodes.filter((episode) => {
return !existingSeries.episodes.find((serieEpisode) => serieEpisode.id === episode.id); return !existingSeries.episodes.find((serieEpisode) => serieEpisode.id === episode.id);
}); });

View File

@ -2,28 +2,54 @@ 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 searchComponents } from "./nrk-search.ts";
import { components as catalogComponents } from "./nrk-catalog.ts"; import { components as catalogComponents } from "./nrk-catalog.ts";
import { external as playbackComponents } from "./nrk-playback.ts"; import { external as playbackComponents } from "./nrk-playback.ts";
import { Series } from "../storage.ts";
type ArrayElement<A> = A extends readonly (infer T)[] ? T : never; type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
type PodcastEpisodes = catalogComponents["schemas"]["EpisodesHalResource"]; type PodcastEpisodes = catalogComponents["schemas"]["EpisodesHalResource"];
type Podcast = catalogComponents["schemas"]["SeriesHalResource"]; type Podcast = catalogComponents["schemas"]["SeriesHalResource"];
type PodcastEpisodesSingle = catalogComponents["schemas"]["EpisodeHalResource"]; type PodcastEpisodesSingle = catalogComponents["schemas"]["EpisodeHalResource"];
export type Serie = catalogComponents["schemas"]["SeriesViewModel"]; export type NrkSerie = catalogComponents["schemas"]["SeriesViewModel"];
export type OriginalEpisode = PodcastEpisodesSingle & { url: string }; export type NrkOriginalEpisode = PodcastEpisodesSingle & { url: string };
export type PodcastEpisode = catalogComponents["schemas"]["PodcastEpisodeHalResource"]; export type NrkPodcastEpisode = catalogComponents["schemas"]["PodcastEpisodeHalResource"];
export type SearchResultList = searchComponents["schemas"]["seriesResult"]["results"]; export type NrkSearchResultList = searchComponents["schemas"]["seriesResult"]["results"];
export type SearchResult = ArrayElement<SearchResultList> & { export type SearchResult = ArrayElement<NrkSearchResultList> & {
description?: string; description?: string;
}; };
type Manifest = playbackComponents["schemas/playback-channel.json"]["components"]["schemas"]["PlayableManifest"]; type Manifest = playbackComponents["schemas/playback-channel.json"]["components"]["schemas"]["PlayableManifest"];
type NrkSerieData = Awaited<ReturnType<typeof nrkRadio.getSeries>>;
function parseSeries(nrkSeries: NrkSerieData): Series {
const imageUrl = nrkSeries.squareImage?.at(-1)?.url ?? "";
return {
id: nrkSeries.id,
title: nrkSeries.titles.title,
subtitle: nrkSeries.titles.subtitle ?? null,
link: `https://radio.nrk.no/podkast/${nrkSeries.id}`,
imageUrl: imageUrl,
lastFetchedAt: new Date(),
episodes: nrkSeries.episodes.map((episode) => {
return {
id: episode.id,
title: episode.titles.title,
subtitle: episode.titles.subtitle ?? null,
url: episode.url,
shareLink: episode._links.share?.href ?? "",
date: new Date(episode.date),
durationInSeconds: episode.durationInSeconds,
};
}),
};
}
const nrkAPI = `https://psapi.nrk.no`; const nrkAPI = `https://psapi.nrk.no`;
async function withDownloadLink( async function withDownloadLink(
episode: PodcastEpisodesSingle, episode: PodcastEpisodesSingle,
type: catalogComponents["schemas"]["Type"], type: catalogComponents["schemas"]["Type"],
): Promise<OriginalEpisode> { ): Promise<NrkOriginalEpisode> {
// getting stream link // getting stream link
let [playbackStatus, playbackResponse] = await get<Manifest>( let [playbackStatus, playbackResponse] = await get<Manifest>(
`${nrkAPI}/playback/manifest/podcast/${episode.episodeId}`, `${nrkAPI}/playback/manifest/podcast/${episode.episodeId}`,
@ -43,7 +69,7 @@ async function withDownloadLink(
} }
export const nrkRadio = { export const nrkRadio = {
search: async (query: string): Promise<SearchResultList> => { search: async (query: string): Promise<NrkSearchResultList> => {
const [status, response] = await get< const [status, response] = await get<
searchComponents["schemas"]["searchresult"] searchComponents["schemas"]["searchresult"]
>(`${nrkAPI}/radio/search/search?q=${query}`); >(`${nrkAPI}/radio/search/search?q=${query}`);
@ -52,7 +78,7 @@ export const nrkRadio = {
} }
throw `Something went wrong with ${query} - got status ${status}`; throw `Something went wrong with ${query} - got status ${status}`;
}, },
getSerieData: async (seriesId: string) => { getSeries: async (seriesId: string) => {
let [ let [
[episodeStatus, episodeResponse], [episodeStatus, episodeResponse],
[seriesStatus, serieResponse], [seriesStatus, serieResponse],
@ -86,16 +112,20 @@ export const nrkRadio = {
const episodes = await Promise.all( const episodes = await Promise.all(
episodeResponse._embedded.episodes.map((episode) => withDownloadLink(episode, serieResponse.type)), episodeResponse._embedded.episodes.map((episode) => withDownloadLink(episode, serieResponse.type)),
); );
return {
const serieData = {
...serieResponse.series, ...serieResponse.series,
episodes, episodes,
}; };
const parsedSeries = parseSeries(serieData);
return parsedSeries;
} }
throw `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`; throw `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`;
}, },
getEpisode: async (seriesId: string, episodeId: string): Promise<PodcastEpisode | null> => { getEpisode: async (seriesId: string, episodeId: string): Promise<NrkPodcastEpisode | null> => {
const url = `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`; const url = `${nrkAPI}/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`;
const [status, episode] = await get<PodcastEpisode>(url); const [status, episode] = await get<NrkPodcastEpisode>(url);
if (status === OK && episode) { if (status === OK && episode) {
return episode; return episode;
} }

View File

@ -1,31 +0,0 @@
import { nrkRadio } from "./nrk/nrk.ts";
import { Series } from "./storage.ts";
type NrkSerieData = Awaited<ReturnType<typeof nrkRadio.getSerieData>>;
function series(nrkSeries: NrkSerieData): Series {
const imageUrl = nrkSeries.squareImage?.at(-1)?.url ?? "";
return {
id: nrkSeries.id,
title: nrkSeries.titles.title,
subtitle: nrkSeries.titles.subtitle ?? null,
link: `https://radio.nrk.no/podkast/${nrkSeries.id}`,
imageUrl: imageUrl,
lastFetchedAt: new Date(),
episodes: nrkSeries.episodes.map((episode) => {
return {
id: episode.id,
title: episode.titles.title,
subtitle: episode.titles.subtitle ?? null,
url: episode.url,
shareLink: episode._links.share?.href ?? "",
date: new Date(episode.date),
durationInSeconds: episode.durationInSeconds,
};
}),
};
}
export const parse = {
series,
};

View File

@ -1,13 +1,13 @@
import { FreshContext } from "$fresh/server.ts"; import { FreshContext } from "$fresh/server.ts";
import { parse, toSeconds } from "https://esm.sh/iso8601-duration@2.1.1"; import { parse, toSeconds } from "https://esm.sh/iso8601-duration@2.1.1";
import { nrkRadio, PodcastEpisode } from "../../../../../lib/nrk/nrk.ts"; import { NrkPodcastEpisode, nrkRadio } from "../../../../../lib/nrk/nrk.ts";
type Chapter = { type Chapter = {
title: string | undefined; title: string | undefined;
startTime: number | undefined; startTime: number | undefined;
}; };
function toChapters(episode: PodcastEpisode): Chapter[] | null { function toChapters(episode: NrkPodcastEpisode): Chapter[] | null {
if (!episode.indexPoints) { if (!episode.indexPoints) {
return null; return null;
} }

View File

@ -5,20 +5,20 @@ import Header from "../components/Header.tsx";
import Search from "../components/Search.tsx"; import Search from "../components/Search.tsx";
import SeriesCard from "../components/SeriesCard.tsx"; import SeriesCard from "../components/SeriesCard.tsx";
import { CSS, render } from "$gfm"; import { CSS, render } from "$gfm";
import { nrkRadio, SearchResultList } from "../lib/nrk/nrk.ts"; import { nrkRadio, NrkSearchResultList } from "../lib/nrk/nrk.ts";
interface HandlerData { interface HandlerData {
query: string; query: string;
origin: string; origin: string;
rawMarkdown: string; rawMarkdown: string;
result?: SearchResultList; result?: NrkSearchResultList;
} }
export const handler: Handlers<HandlerData> = { export const handler: Handlers<HandlerData> = {
async GET(request, ctx) { async GET(request, ctx) {
const url = new URL(request.url); const url = new URL(request.url);
const query = url.searchParams.get("query"); const query = url.searchParams.get("query");
let result: SearchResultList | undefined; let result: NrkSearchResultList | undefined;
if (query) { if (query) {
result = await nrkRadio.search(query); result = await nrkRadio.search(query);
} }
@ -57,7 +57,7 @@ export default function Home({ data }: PageProps<HandlerData>) {
); );
} }
function SearchResult({ result, origin }: { result: SearchResultList; origin: string }) { function SearchResult({ result, origin }: { result: NrkSearchResultList; origin: string }) {
if (!result) { if (!result) {
return null; return null;
} }