Upgrade Fresh and add a bunch of small QOL improvements (#16)

This commit is contained in:
Tim Hårek Andreassen 2024-03-24 13:41:18 +01:00 committed by GitHub
parent 3535097032
commit 77428d374a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 788 additions and 613 deletions

View File

@ -3,16 +3,19 @@
https://nrss.deno.dev/
## Local devlopment
install Deno
https://deno.land/manual/getting_started/installation
### Run the application
``` shell
```shell
deno task start
```
Open http://localhost:8000/ to view it in the browser
## Known Problems
- Some clients may not accept a feed hosted over HTTPS only. See @steinarb's workaround [here](https://github.com/olaven/NRSS/issues/5#issuecomment-1488840679) for a possible solution.
- The feeds only provide the latest episodes for a podcast, not the entire archive. I've not yet found any acceptable method of fetching all episodes without getting rate limited by NRK or introducing a storage layer. This is tracked in https://github.com/olaven/NRSS/issues/8.

View File

@ -1,9 +1,18 @@
export default function Search(props: { defaultValue: string }) {
return (
<form className="flex flex-col w-full lg:w-2/3 mx-auto my-4">
<label className="sr-only" htmlFor="query">Program</label>
<input placeholder="NRK-podcast" className="border-2 rounded-md px-2" name="query" id="query" defaultValue={props.defaultValue} />
<button type="submit" className="my-2 bg-gray-100 rounded-md">Søk</button>
</form>
);
return (
<form className="flex flex-col w-full lg:w-2/3 mx-auto my-4">
<label className="sr-only" htmlFor="query">Program</label>
<input
type="search"
placeholder="NRK-podcast"
className="border-2 rounded-md px-2"
name="query"
id="query"
defaultValue={props.defaultValue}
/>
<button type="submit" className="my-2 bg-gray-100 rounded-md">
Søk
</button>
</form>
);
}

View File

@ -1,19 +1,20 @@
import { SearchResult } from "../lib/nrk.ts"
import { SearchResult } from "../lib/nrk.ts";
import CopyButton from "../islands/CopyButton.tsx";
export function SerieCard(props: { serie: SearchResult, origin: string }) {
const feedUrl = new URL(`/api/feeds/${props.serie.seriesId}`, props.origin);
const image = props.serie.images[0];
return (
<div className="border-2 my-2 p-2">
<div className="mx-auto h-full w-full">
<h2 className="text-xl">{props.serie.title}</h2>
<p className="text-md">{props.serie.description}</p>
<img src={image.uri} width={image.width} />
<CopyButton text={feedUrl.toString()}>
Kopier URL
</CopyButton>
</div>
</div >
);
export default function SeriesCard(props: { serie: SearchResult; origin: string }) {
const feedUrl = new URL(`/api/feeds/${props.serie.seriesId}`, props.origin);
const image = props.serie.images[0];
return (
<div className="border-2 my-2 p-2">
<div className="mx-auto h-full w-full space-y-2">
<h2 className="text-xl">{props.serie.title}</h2>
<p className="text-md">{props.serie.description}</p>
<img src={image.uri} width={image.width} alt="" />
<code className="font-mono bg-black text-white select-all p-2">{feedUrl.toString()}</code>
<CopyButton text={feedUrl.toString()}>
Kopier URL
</CopyButton>
</div>
</div>
);
}

View File

@ -1,10 +1,38 @@
{
"lock": false,
"tasks": {
"start": "deno run -A --watch=static/,routes/ dev.ts"
"start": "deno run -A --watch=static/,routes/ dev.ts",
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx"
},
"imports": {
"$fresh/": "https://deno.land/x/fresh@1.6.8/",
"preact": "https://esm.sh/preact@10.19.6",
"preact/": "https://esm.sh/preact@10.19.6/",
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4",
"@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/"
},
"importMap": "./import_map.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
},
"fmt": {
"exclude": [
"output"
],
"useTabs": false,
"lineWidth": 120,
"indentWidth": 2,
"proseWrap": "preserve"
},
"lint": {
"rules": {
"tags": [
"fresh",
"recommended"
]
}
}
}

148
deno.lock
View File

@ -1,148 +0,0 @@
{
"version": "2",
"remote": {
"https://deno.land/std@0.140.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
"https://deno.land/std@0.140.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49",
"https://deno.land/std@0.140.0/fs/_util.ts": "0fb24eb4bfebc2c194fb1afdb42b9c3dda12e368f43e8f2321f84fc77d42cb0f",
"https://deno.land/std@0.140.0/fs/ensure_dir.ts": "9dc109c27df4098b9fc12d949612ae5c9c7169507660dcf9ad90631833209d9d",
"https://deno.land/std@0.140.0/fs/expand_glob.ts": "0c10130d67c9b02164b03df8e43c6d6defbf8e395cb69d09e84a8586e6d72ac3",
"https://deno.land/std@0.140.0/fs/walk.ts": "117403ccd21fd322febe56ba06053b1ad5064c802170f19b1ea43214088fe95f",
"https://deno.land/std@0.140.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3",
"https://deno.land/std@0.140.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09",
"https://deno.land/std@0.140.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b",
"https://deno.land/std@0.140.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633",
"https://deno.land/std@0.140.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee",
"https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d",
"https://deno.land/std@0.140.0/path/posix.ts": "293cdaec3ecccec0a9cc2b534302dfe308adb6f10861fa183275d6695faace44",
"https://deno.land/std@0.140.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
"https://deno.land/std@0.140.0/path/win32.ts": "31811536855e19ba37a999cd8d1b62078235548d67902ece4aa6b814596dd757",
"https://deno.land/std@0.150.0/_util/assert.ts": "e94f2eb37cebd7f199952e242c77654e43333c1ac4c5c700e929ea3aa5489f74",
"https://deno.land/std@0.150.0/_util/os.ts": "3b4c6e27febd119d36a416d7a97bd3b0251b77c88942c8f16ee5953ea13e2e49",
"https://deno.land/std@0.150.0/async/abortable.ts": "87aa7230be8360c24ad437212311c9e8d4328854baec27b4c7abb26e85515c06",
"https://deno.land/std@0.150.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0",
"https://deno.land/std@0.150.0/async/debounce.ts": "564273ef242bcfcda19a439132f940db8694173abffc159ea34f07d18fc42620",
"https://deno.land/std@0.150.0/async/deferred.ts": "bc18e28108252c9f67dfca2bbc4587c3cbf3aeb6e155f8c864ca8ecff992b98a",
"https://deno.land/std@0.150.0/async/delay.ts": "cbbdf1c87d1aed8edc7bae13592fb3e27e3106e0748f089c263390d4f49e5f6c",
"https://deno.land/std@0.150.0/async/mod.ts": "9852cd8ed897ab2d41a8fbee611d574e97898327db5c19d9d58e41126473f02c",
"https://deno.land/std@0.150.0/async/mux_async_iterator.ts": "5b4aca6781ad0f2e19ccdf1d1a1c092ccd3e00d52050d9c27c772658c8367256",
"https://deno.land/std@0.150.0/async/pool.ts": "ef9eb97b388543acbf0ac32647121e4dbe629236899586c4d4311a8770fbb239",
"https://deno.land/std@0.150.0/async/tee.ts": "bcfae0017ebb718cf4eef9e2420e8675d91cb1bcc0ed9b668681af6e6caad846",
"https://deno.land/std@0.150.0/flags/mod.ts": "594472736e24b2f2afd3451cf7ccd58a21706ce91006478a544fdfa056c69697",
"https://deno.land/std@0.150.0/fs/_util.ts": "2cf50bfb1081c2d5f2efec10ac19abbc2baf478e51cd1b057d0da2f30585b6ba",
"https://deno.land/std@0.150.0/fs/walk.ts": "6ce8d87fbaeda23383e979599ad27f3f94b3e5ff0c0cd976b5fc5c2aa0df7d92",
"https://deno.land/std@0.150.0/http/http_status.ts": "897575a7d6bc2b9123f6a38ecbc0f03d95a532c5d92029315dc9f508e12526b8",
"https://deno.land/std@0.150.0/http/server.ts": "0b0a9f3abfcfecead944b31ee9098a0c11a59b0495bf873ee200eb80e7441483",
"https://deno.land/std@0.150.0/media_types/_util.ts": "ce9b4fc4ba1c447dafab619055e20fd88236ca6bdd7834a21f98bd193c3fbfa1",
"https://deno.land/std@0.150.0/media_types/mod.ts": "2d4b6f32a087029272dc59e0a55ae3cc4d1b27b794ccf528e94b1925795b3118",
"https://deno.land/std@0.150.0/media_types/vendor/mime-db.v1.52.0.ts": "724cee25fa40f1a52d3937d6b4fbbfdd7791ff55e1b7ac08d9319d5632c7f5af",
"https://deno.land/std@0.150.0/path/_constants.ts": "df1db3ffa6dd6d1252cc9617e5d72165cd2483df90e93833e13580687b6083c3",
"https://deno.land/std@0.150.0/path/_interface.ts": "ee3b431a336b80cf445441109d089b70d87d5e248f4f90ff906820889ecf8d09",
"https://deno.land/std@0.150.0/path/_util.ts": "c1e9686d0164e29f7d880b2158971d805b6e0efc3110d0b3e24e4b8af2190d2b",
"https://deno.land/std@0.150.0/path/common.ts": "bee563630abd2d97f99d83c96c2fa0cca7cee103e8cb4e7699ec4d5db7bd2633",
"https://deno.land/std@0.150.0/path/glob.ts": "cb5255638de1048973c3e69e420c77dc04f75755524cb3b2e160fe9277d939ee",
"https://deno.land/std@0.150.0/path/mod.ts": "4945b430b759b0b3d98f2a278542cbcf95e0ad2bd8eaaed3c67322b306b2b346",
"https://deno.land/std@0.150.0/path/posix.ts": "c1f7afe274290ea0b51da07ee205653b2964bd74909a82deb07b69a6cc383aaa",
"https://deno.land/std@0.150.0/path/separator.ts": "fe1816cb765a8068afb3e8f13ad272351c85cbc739af56dacfc7d93d710fe0f9",
"https://deno.land/std@0.150.0/path/win32.ts": "bd7549042e37879c68ff2f8576a25950abbfca1d696d41d82c7bca0b7e6f452c",
"https://deno.land/std@0.150.0/semver/mod.ts": "4a5195fa81b4aede8875a386550a1119f01fb58d74aea899b2cfb136c05a7310",
"https://deno.land/std@0.152.0/async/abortable.ts": "87aa7230be8360c24ad437212311c9e8d4328854baec27b4c7abb26e85515c06",
"https://deno.land/std@0.152.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0",
"https://deno.land/std@0.152.0/async/debounce.ts": "564273ef242bcfcda19a439132f940db8694173abffc159ea34f07d18fc42620",
"https://deno.land/std@0.152.0/async/deferred.ts": "bc18e28108252c9f67dfca2bbc4587c3cbf3aeb6e155f8c864ca8ecff992b98a",
"https://deno.land/std@0.152.0/async/delay.ts": "cbbdf1c87d1aed8edc7bae13592fb3e27e3106e0748f089c263390d4f49e5f6c",
"https://deno.land/std@0.152.0/async/mod.ts": "dd0a8ed4f3984ffabe2fcca7c9f466b7932d57b1864ffee148a5d5388316db6b",
"https://deno.land/std@0.152.0/async/mux_async_iterator.ts": "5b4aca6781ad0f2e19ccdf1d1a1c092ccd3e00d52050d9c27c772658c8367256",
"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",
"https://deno.land/x/denoflate@1.2.1/pkg/denoflate.js": "b9f9ad9457d3f12f28b1fb35c555f57443427f74decb403113d67364e4f2caf4",
"https://deno.land/x/denoflate@1.2.1/pkg/denoflate_bg.wasm.js": "d581956245407a2115a3d7e8d85a9641c032940a8e810acbd59ca86afd34d44d",
"https://deno.land/x/esbuild@v0.14.51/mod.d.ts": "c142324d0383c39de0d7660cd207a7f7f52c7198a13d7d3281c0d636a070f441",
"https://deno.land/x/esbuild@v0.14.51/mod.js": "7432566c71fac77637822dc230319c7392a2d2fef51204c9d12c956d7093c279",
"https://deno.land/x/esbuild@v0.14.51/wasm.d.ts": "c142324d0383c39de0d7660cd207a7f7f52c7198a13d7d3281c0d636a070f441",
"https://deno.land/x/esbuild@v0.14.51/wasm.js": "afc1b6927543b664af60ce452c4929e5dc2bb9a0f4ed47b446a6431847c7598e",
"https://deno.land/x/esbuild_deno_loader@0.5.2/deps.ts": "bf83c27b7787b2f245fa0bc0b99f5041aa949c000a81c016cfe828d06b476d37",
"https://deno.land/x/esbuild_deno_loader@0.5.2/mod.ts": "bc111a68f323dbdb6edec68dd558ab732b27866d2ef304708872d763387b65d7",
"https://deno.land/x/esbuild_deno_loader@0.5.2/src/deno.ts": "0e83ccabbe2b004389288e38df2031b79eb347df2d139fce9394d8e88a11f259",
"https://deno.land/x/esbuild_deno_loader@0.5.2/src/native_loader.ts": "343854a566cf510cf25144f7c09fc0c1097780a31830305142a075d12bb697ba",
"https://deno.land/x/esbuild_deno_loader@0.5.2/src/portable_loader.ts": "35b6c526eed8c2c781a3256b23c30aa7cce69c0ef1d583c15528663287ba18a3",
"https://deno.land/x/esbuild_deno_loader@0.5.2/src/shared.ts": "b64749cd8c0f6252a11498bd8758ef1220003e46b2c9b68e16da63fd7e92b13a",
"https://deno.land/x/fresh@1.1.4/dev.ts": "a66c7d64be35bcd6a8e12eec9c27ae335044c70363a241f2e36ee776db468622",
"https://deno.land/x/fresh@1.1.4/plugins/twind.ts": "c0570d6010e29ba24ee5f43b9d3f1fe735f7fac76d9a3e680c9896373d669876",
"https://deno.land/x/fresh@1.1.4/plugins/twind/shared.ts": "023e0ffcd66668753b5049edab0de46e6d66194fb6026c679034b9bbf04ad6f3",
"https://deno.land/x/fresh@1.1.4/runtime.ts": "b02ec1e2e32cf73a33d262b7c9dcab9468ce16cd89fd424196c71003698a4ab0",
"https://deno.land/x/fresh@1.1.4/server.ts": "f379c9aad24471a71e58fb887fa57e5cc27ad9df035987eb260541c78df38e84",
"https://deno.land/x/fresh@1.1.4/src/dev/deps.ts": "de5470828c17839c0b52c328e6709f3477740b9800deaf724d6569b64b1d3872",
"https://deno.land/x/fresh@1.1.4/src/dev/error.ts": "21a38d240c00279662e6adde41367f1da0ae7e2836d993f818ea94aabab53e7b",
"https://deno.land/x/fresh@1.1.4/src/dev/mod.ts": "f5836b2eccd0efd7c0a726a121f174a974daefc22058f759f07d4df56c46e978",
"https://deno.land/x/fresh@1.1.4/src/runtime/csp.ts": "9ee900e9b0b786057b1009da5976298c202d1b86d1f1e4d2510bde5f06530ac9",
"https://deno.land/x/fresh@1.1.4/src/runtime/head.ts": "0f9932874497ab6e57ed1ba01d549e843523df4a5d36ef97460e7a43e3132fdc",
"https://deno.land/x/fresh@1.1.4/src/runtime/utils.ts": "8320a874a44bdd5905c7d4b87a0e7a14a6c50a2ed133800e72ae57341e4d4faa",
"https://deno.land/x/fresh@1.1.4/src/server/bundle.ts": "7fb20c084948894b8eca90984ef92c3f6d12a194910ecb9adee21ace9f1607bb",
"https://deno.land/x/fresh@1.1.4/src/server/constants.ts": "ad10dda1bc20c25c2926f6a8cfd79ef4368d70b4b03a645f65c04b3fa7d93a8c",
"https://deno.land/x/fresh@1.1.4/src/server/context.ts": "2773bf0f9b170f888a3135df5646b2cd5492add072eeb8569e73eb3cfcad963e",
"https://deno.land/x/fresh@1.1.4/src/server/default_error_page.ts": "9a1a595a1a2b31c9b724b04db82b8af256285536db272658d831ac9ef1d3d448",
"https://deno.land/x/fresh@1.1.4/src/server/deps.ts": "1b467e4d00109356b06117a9f2e5a21a2b48353a0e573b25cdbbfe7e9cfccb34",
"https://deno.land/x/fresh@1.1.4/src/server/htmlescape.ts": "834ac7d0caa9fc38dffd9b8613fb47aeecd4f22d5d70c51d4b20a310c085835c",
"https://deno.land/x/fresh@1.1.4/src/server/mod.ts": "72d213444334dd2e94c757a0eee0fc486c0919399ea9184d07ad042f34edd00d",
"https://deno.land/x/fresh@1.1.4/src/server/render.ts": "6f50707bd1f6e33ed84bb71ae3b0996d202b953cefc4285f5356524c7b21f01f",
"https://deno.land/x/fresh@1.1.4/src/server/types.ts": "dde992ab4ee635df71a7fc96fe4cd85943c1a9286ea8eb586563d5f5ca154955",
"https://deno.land/x/importmap@0.2.1/_util.ts": "ada9a9618b537e6c0316c048a898352396c882b9f2de38aba18fd3f2950ede89",
"https://deno.land/x/importmap@0.2.1/mod.ts": "ae3d1cd7eabd18c01a4960d57db471126b020f23b37ef14e1359bbb949227ade",
"https://deno.land/x/kall@v0.1.0/mod.ts": "7379f599251db0b86e08d25ed74965d7f00f55491c13ffbb524a4d42bd97f0fe",
"https://deno.land/x/kall@v0.1.0/source/codes.ts": "67f72d771a039f96e4d0ddd23a54b27caf2c28d207dd37a1fe2e66b69bb8a0aa",
"https://deno.land/x/kall@v0.1.0/source/filters.ts": "8fd499ca3e4cb3d7e40963107710fec04d2179771128434bb3e7419b2d204dc7",
"https://deno.land/x/kall@v0.1.0/source/methods.ts": "01f7d90f3509da9a46122f17ae9510d8ed14ec252082d3e61b971ad410b6caf2",
"https://deno.land/x/kall@v0.1.0/source/support.ts": "bb32b6c2274255d95413600e67f8705f53656b5e8bbad4165ab95c442c31543f",
"https://deno.land/x/rutt@0.0.14/mod.ts": "5027b8e8b12acca48b396a25aee74ad7ee94a25c24cda75571d7839cbd41113c",
"https://deno.land/x/serializexml@v0.1.0/mod.ts": "922e83b6897ae004a39e14c726e5f04d3056d58cdd2c206c4cd659e959400f17",
"https://deno.land/x/serializexml@v0.3.2/mod.ts": "eb29afa7c8a03e692086a58abb87047a91fa5e51841c4c757807a58eb04e0730",
"https://deno.land/x/ts_morph@16.0.0/common/DenoRuntime.ts": "537800e840d0994f9055164e11bf33eadf96419246af0d3c453793c3ae67bdb3",
"https://deno.land/x/ts_morph@16.0.0/common/mod.ts": "01985d2ee7da8d1caee318a9d07664774fbee4e31602bc2bb6bb62c3489555ed",
"https://deno.land/x/ts_morph@16.0.0/common/ts_morph_common.d.ts": "39f2ddefd4995e4344236c44c2bf296069149f45ef6f00440b56e7b32cb2b3bd",
"https://deno.land/x/ts_morph@16.0.0/common/ts_morph_common.js": "7d908bf4f416aa96de956dc11ecc83b585bed297e16418d496ca04a3481067e0",
"https://deno.land/x/ts_morph@16.0.0/common/typescript.d.ts": "df7dd83543f14081ca74918d5a80ff60f634f465746cf2aff8924b28bcc3b152",
"https://deno.land/x/ts_morph@16.0.0/common/typescript.js": "5c59651248a4c41b25fa7beee8e0d0d0fab5f439fa72d478e65abd8241aa533c",
"https://deno.land/x/ts_morph@16.0.0/mod.ts": "adba9b82f24865d15d2c78ef6074b9a7457011719056c9928c800f130a617c93",
"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",
"https://esm.sh/stable/preact@10.11.0/deno/hooks.js": "32a891b0e7abdfbf30dc7c274dbcbd46f2ea04d6637174451c50c41c12429805",
"https://esm.sh/stable/preact@10.11.0/deno/jsx-runtime.js": "0310ceb4c83643ee994baa3104675f08834300731a838ddecf5078853492e8d3",
"https://esm.sh/stable/preact@10.11.0/deno/preact.js": "08219fcb5b92ccdc709233a070ab22478c91521866c819aea679144847e4e87a",
"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",
"https://esm.sh/v111/preact@10.11.0/jsx-runtime/src/index.d.ts": "e153460ed2b3fe2ad8b93696ecd48fbf73cd628b0b0ea6692b71804a3af69dfd",
"https://esm.sh/v111/preact@10.11.0/src/index.d.ts": "1a5c331227be54be6515b0c92a469d352834fa413963ae84a39a05a3177111f6",
"https://esm.sh/v111/preact@10.11.0/src/jsx.d.ts": "c423715fd7992b2e1446fea11d2d04e8adbd66c1edca1ce5e85f90e0d26a2eb2",
"https://esm.sh/v111/style-vendorizer@2.2.3/deno/style-vendorizer.js": "4823723c1cd5b34a60b4a1dfdf272a821d367fc472c23aeed6c3bcd7af5b7a7b",
"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://raw.githubusercontent.com/olaven/serialize-xml/v0.4.0/mod.ts": "0d8a42635bd6e0130b248d9bae27ab36a40076cfa7a9be0f743fa51d7f64eb9f"
}
}

View File

@ -1,24 +1,25 @@
// DO NOT EDIT. This file is generated by fresh.
// DO NOT EDIT. This file is generated by Fresh.
// This file SHOULD be checked into source version control.
// This file is automatically updated during development when running `dev.ts`.
import config from "./deno.json" assert { type: "json" };
import * as $0 from "./routes/api/feeds/[seriesId].ts";
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";
import * as $_app from "./routes/_app.tsx";
import * as $api_feeds_seriesId_ from "./routes/api/feeds/[seriesId].ts";
import * as $api_feeds_seriesId_episodeId_chapters from "./routes/api/feeds/[seriesId]/[episodeId]/chapters.ts";
import * as $index from "./routes/index.tsx";
import * as $CopyButton from "./islands/CopyButton.tsx";
import { type Manifest } from "$fresh/server.ts";
const manifest = {
routes: {
"./routes/api/feeds/[seriesId].ts": $0,
"./routes/api/feeds/[seriesId]/[episodeId]/chapters.ts": $1,
"./routes/index.tsx": $2,
"./routes/_app.tsx": $_app,
"./routes/api/feeds/[seriesId].ts": $api_feeds_seriesId_,
"./routes/api/feeds/[seriesId]/[episodeId]/chapters.ts": $api_feeds_seriesId_episodeId_chapters,
"./routes/index.tsx": $index,
},
islands: {
"./islands/CopyButton.tsx": $$0,
"./islands/CopyButton.tsx": $CopyButton,
},
baseUrl: import.meta.url,
config,
};
} satisfies Manifest;
export default manifest;

View File

@ -1,12 +0,0 @@
{
"imports": {
"$fresh/": "https://deno.land/x/fresh@1.1.4/",
"preact": "https://esm.sh/preact@10.11.0",
"preact/": "https://esm.sh/preact@10.11.0/",
"preact-render-to-string": "https://esm.sh/*preact-render-to-string@5.2.4",
"@preact/signals": "https://esm.sh/*@preact/signals@1.0.3",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.0.1",
"twind": "https://esm.sh/twind@0.16.17",
"twind/": "https://esm.sh/twind@0.16.17/"
}
}

View File

@ -1,28 +1,33 @@
import Preact from "preact";
import { useState } from "preact/hooks"
import { useState } from "preact/hooks";
import { IS_BROWSER } from "$fresh/runtime.ts";
export default function CopyButton(props: { textBefore: string, textAfter: string } & Preact.PropsWithChildren) {
type Props = {
text: string;
children: Preact.ComponentChildren;
disabled?: boolean;
};
const [clicked, setClicked] = useState(false);
export default function CopyButton({ text, disabled = false, children }: Props) {
const [clicked, setClicked] = useState(false);
const startReset = () => {
setTimeout(() => {
setClicked(false);
}, 2_000);
}
const startReset = () => {
setTimeout(() => {
setClicked(false);
}, 2_000);
};
return (
<button
onClick={() => {
navigator.clipboard.writeText(props.text.toString());
setClicked(true);
startReset();
}}
disabled={!IS_BROWSER || props.disabled}
class="px-2 py-1 border(gray-100 2) hover:bg-gray-200 mx-auto"
>
{clicked ? "Kopiert!" : props.children}
</button>
);
return (
<button
onClick={() => {
navigator.clipboard.writeText(text.toString());
setClicked(true);
startReset();
}}
disabled={!IS_BROWSER || disabled}
class="px-2 py-1 border(gray-100 2) hover:bg-gray-200 mx-auto"
>
{clicked ? "Kopiert!" : children}
</button>
);
}

View File

@ -508,7 +508,9 @@ export interface components {
/** @description Aggregated info for clips */
ExtraMaterialClipsHalEmbedded: {
metadata?: {
displayAspectRatio: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DisplayAspectRatio"];
displayAspectRatio: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DisplayAspectRatio"];
/**
* @description Clip length. The string is formatted as https://en.wikipedia.org/wiki/ISO_8601#Durations
* @example PT2M0S
@ -517,7 +519,9 @@ export interface components {
id: string;
/** @enum {undefined} */
playability: "nonPlayable" | "playable";
preplay: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PreplayVm"];
preplay: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PreplayVm"];
/** @description Link to full metadata */
_links: {
self: components["schemas"]["HalLink"];
@ -886,36 +890,56 @@ export interface external {
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json": {
paths: {
"/playback/debug": {
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackDebug"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackDebug"];
};
"/playback/dnslist": {
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackDnsList"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackDnsList"];
};
"/playback/manifest/{id}": {
/** This endpoint is just for clients that doesn't know its context */
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackManifestRedirect"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackManifestRedirect"];
};
"/playback/manifest/program/{programId}": {
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackProgramManifest"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackProgramManifest"];
};
"/playback/manifest/channel/{channelId}": {
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackChannelManifest"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackChannelManifest"];
};
"/playback/manifest/clip/{clipId}": {
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackClipManifest"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackClipManifest"];
};
"/playback/metadata/{id}": {
/** This endpoint is just for clients that doesn't know its context */
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackMetadataRedirect"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackMetadataRedirect"];
};
"/playback/metadata/program/{programId}": {
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackProgramMetadata"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackProgramMetadata"];
};
"/playback/metadata/channel/{channelId}": {
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackChannelMetadata"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackChannelMetadata"];
};
"/playback/metadata/clip/{clipId}": {
get: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["operations"]["getPlaybackClipMetadata"];
get: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["operations"]["getPlaybackClipMetadata"];
};
};
components: {
@ -935,8 +959,12 @@ export interface external {
href: string;
};
};
playbackData: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlaybackDataVm"];
locationData: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LocationDataVm"];
playbackData: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlaybackDataVm"];
locationData: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LocationDataVm"];
};
PlaybackDataVm: {
maxBitrate: number;
@ -951,9 +979,15 @@ export interface external {
isBeta: boolean;
originalUa: string;
wurflId: string | null;
distributionFormat: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DistributionFormat"];
deviceGroupName: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DeviceGroupName"];
playerPlatform: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayerPlatform"];
distributionFormat: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DistributionFormat"];
deviceGroupName: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DeviceGroupName"];
playerPlatform: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayerPlatform"];
customManifestParameters: (
| "AudioOnly"
| "DolbyOnly"
@ -965,7 +999,9 @@ export interface external {
| "NoSignalDiscontinuities"
| "SignalDiscontinuities"
)[];
subtitlesDistribution: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SubtitlesDistribution"];
subtitlesDistribution: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SubtitlesDistribution"];
};
/** @enum {string} */
DistributionFormat:
@ -975,7 +1011,12 @@ export interface external {
| "ProgressiveDownload"
| "ProgressiveMp3";
/** @enum {string} */
DeviceGroupName: "Desktop" | "Mobile" | "SetTopUnit" | "Tablet" | "Tv";
DeviceGroupName:
| "Desktop"
| "Mobile"
| "SetTopUnit"
| "Tablet"
| "Tv";
/** @enum {string} */
PlayerPlatform:
| "Altibox"
@ -1021,28 +1062,51 @@ export interface external {
forwardedFor: string;
realIp: string;
};
ManifestResponse: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableManifest"]
> &
Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["NonPlayableManifest"]
ManifestResponse:
& Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableManifest"]
>
& Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["NonPlayableManifest"]
>;
PlayableManifest: {
/** @enum {string} */
playability: "playable";
playable: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableElementVm"];
playable: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableElementVm"];
statistics: {
scores: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ScoresStatisticsVm"];
ga: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["GoogleAnalyticsVm"];
conviva: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ConvivaStatisticsVm"];
luna: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LunaStatisticsVm"];
scores: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ScoresStatisticsVm"];
ga: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["GoogleAnalyticsVm"];
conviva: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ConvivaStatisticsVm"];
luna: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LunaStatisticsVm"];
};
nonPlayable: unknown | null;
id: string;
streamingMode: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableStreamingMode"];
availability: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["AvailabilityVm"];
displayAspectRatio: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DisplayAspectRatio"];
sourceMedium: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableSourceMedium"];
streamingMode: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableStreamingMode"];
availability: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["AvailabilityVm"];
displayAspectRatio: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DisplayAspectRatio"];
sourceMedium: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableSourceMedium"];
_links: {
self: {
href: string;
@ -1063,12 +1127,22 @@ export interface external {
ga: unknown | null;
luna: unknown | null;
};
nonPlayable: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["NonPlayableElementVm"];
nonPlayable: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["NonPlayableElementVm"];
id: string;
streamingMode: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableStreamingMode"];
availability: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["AvailabilityVm"];
displayAspectRatio: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DisplayAspectRatio"];
sourceMedium: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableSourceMedium"];
streamingMode: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableStreamingMode"];
availability: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["AvailabilityVm"];
displayAspectRatio: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DisplayAspectRatio"];
sourceMedium: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableSourceMedium"];
_links: {
self: {
href: string;
@ -1084,19 +1158,31 @@ export interface external {
endSequenceStartTime: string | null;
duration: string | null;
/** @description List of mediaAssets */
assets: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["MediaUrlVm"][];
assets: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["MediaUrlVm"][];
/** @description Livebuffer if element is live */
liveBuffer: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LiveBufferVm"]
> | null;
liveBuffer:
| Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LiveBufferVm"]
>
| null;
/** @description List of subtitles */
subtitles: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SubtitlesVm"][];
subtitles: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SubtitlesVm"][];
};
MediaUrlVm: {
/** @description The media URL itself. */
url: string;
format: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableFormat"];
mimeType: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableMimeType"];
format: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableFormat"];
mimeType: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableMimeType"];
/** @description True if program is marked as HDSecure, false otherwise */
encrypted: boolean;
};
@ -1120,7 +1206,9 @@ export interface external {
bufferStartTime: string | null;
/** @description Live buffer length */
bufferDuration: string;
bufferType: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LiveBufferType"];
bufferType: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LiveBufferType"];
};
/**
* @description Type of liveBuffer
@ -1128,9 +1216,15 @@ export interface external {
*/
LiveBufferType: "fixed" | "growing" | "none" | "sliding";
SubtitlesVm: {
type: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SubtitleType"];
language: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SubtitleLanguage"];
label: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SubtitleLabel"];
type: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SubtitleType"];
language: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SubtitleLanguage"];
label: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SubtitleLabel"];
defaultOn: boolean;
webVtt: string;
};
@ -1139,7 +1233,10 @@ export interface external {
/** @enum {string} */
SubtitleLanguage: "en" | "nb";
/** @enum {string} */
SubtitleLabel: "English" | "Norsk tekst" | "Tekstet for hørselshemmede";
SubtitleLabel:
| "English"
| "Norsk tekst"
| "Tekstet for hørselshemmede";
ScoresStatisticsVm: {
springStreamSite: string;
springStreamStream: string;
@ -1178,14 +1275,18 @@ export interface external {
* When duration is unknown, eg channels: -1
*/
duration: number;
streamType: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ConvivaStreamType"];
streamType: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ConvivaStreamType"];
/**
* @description The URL from where this video content will be downloaded. For
* manifest-based streaming protocols, it should be the URL of the top-level
* manifest.
*/
streamUrl: string;
custom: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ConvivaCustomProperties"];
custom: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ConvivaCustomProperties"];
};
/** @enum {string} */
ConvivaStreamType: "LIVE" | "VOD";
@ -1221,7 +1322,9 @@ export interface external {
* Example: underholdning
*/
category: string;
mediaType: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ConvivaMediaType"];
mediaType: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ConvivaMediaType"];
/**
* @description Name of selected CDN
* Example: Telenor-Cdn
@ -1239,7 +1342,9 @@ export interface external {
* Example: tv.nrk.no, nrk.no, nrksuper.no
*/
serviceName: string;
contentType: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ConvivaContentType"];
contentType: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ConvivaContentType"];
};
/**
* @description MediaType for current content
@ -1257,8 +1362,12 @@ export interface external {
| "Podcast"
| "Program";
LunaStatisticsVm: {
config: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LunaConfig"];
data: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LunaPayload"];
config: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LunaConfig"];
data: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LunaPayload"];
};
LunaConfig: {
beacon: string;
@ -1288,27 +1397,37 @@ export interface external {
* @description Only applicable for on-demand media elements. Will be null for live media
* elements.
*/
onDemand: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["OnDemandUsageRights"]
> | null;
onDemand:
| Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["OnDemandUsageRights"]
>
| null;
/**
* @description Only applicable for live media elements. Will be null for on-demand media
* elements.
*/
live:
| (Partial<{
| (
& Partial<{
/** @enum {string} */
type: "channel";
/** @enum {boolean} */
isOngoing: true;
transmissionInterval: unknown | null;
}> &
Partial<{
/** @enum {string} */
type: "transmission";
isOngoing: boolean;
transmissionInterval: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LiveTransmissionIntervalVm"];
}>)
}>
& Partial<{
/** @enum {string} */
type: "transmission";
isOngoing: boolean;
transmissionInterval: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"][
"LiveTransmissionIntervalVm"
];
}>
)
| null;
externalEmbeddingAllowed: boolean;
};
@ -1326,8 +1445,12 @@ export interface external {
/** @enum {string} */
PlayableSourceMedium: "audio" | "video";
NonPlayableElementVm: {
reason: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["NonPlayableReason"];
messageType: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["NonPlayableMessageType"];
reason: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["NonPlayableReason"];
messageType: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["NonPlayableMessageType"];
/**
* @description Standard message for end user describing why the media element is not
* playable.
@ -1389,7 +1512,9 @@ export interface external {
/** @description Image to show before play */
PosterVm: {
/** @description List of images in different sizes */
images: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PosterImageVm"][];
images: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PosterImageVm"][];
};
/** @description Title to show before play */
TitlesVm: {
@ -1397,12 +1522,18 @@ export interface external {
subtitle: string;
};
PreplayVm: {
titles: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["TitlesVm"];
titles: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["TitlesVm"];
/** @description Description to show before play */
description: string;
poster: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PosterVm"];
poster: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PosterVm"];
/** @description List of index points */
indexPoints: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["IndexPointVm"][];
indexPoints: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["IndexPointVm"][];
};
LegalAgeRating: {
code: string;
@ -1412,7 +1543,9 @@ export interface external {
LegalAgeBodyRated: {
/** @enum {string} */
status: "rated";
rating: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LegalAgeRating"];
rating: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LegalAgeRating"];
};
LegalAgeBodyExempt: {
/** @enum {string} */
@ -1420,11 +1553,16 @@ export interface external {
};
LegalAgeVm: {
legalReference: string;
body: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LegalAgeBodyExempt"]
> &
Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LegalAgeBodyRated"]
body:
& Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LegalAgeBodyExempt"]
>
& Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LegalAgeBodyRated"]
>;
};
PlayableMetadataVm: {
@ -1434,33 +1572,63 @@ export interface external {
PlayableMetadata: {
/** @enum {string} */
playability: "playable";
playable: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableMetadataVm"];
playable: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableMetadataVm"];
nonPlayable: unknown | null;
id: string;
streamingMode: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableStreamingMode"];
streamingMode: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableStreamingMode"];
duration: string | null;
availability: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["AvailabilityVm"];
legalAge: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LegalAgeVm"];
preplay: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PreplayVm"];
displayAspectRatio: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DisplayAspectRatio"];
skipDialogInfo: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SkipDialogInfoVm"]
> | null;
sourceMedium: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableSourceMedium"];
availability: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["AvailabilityVm"];
legalAge: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LegalAgeVm"];
preplay: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PreplayVm"];
displayAspectRatio: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DisplayAspectRatio"];
skipDialogInfo:
| Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SkipDialogInfoVm"]
>
| null;
sourceMedium: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableSourceMedium"];
_links: {
self: {
href: string;
};
manifests: Partial<
{
href: string;
name: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ManifestLinkName"];
}[]
> | null;
next: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["NextLink"]
> | null;
series?: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SeriesLink"];
manifests:
| Partial<
{
href: string;
name: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"][
"ManifestLinkName"
];
}[]
>
| null;
next:
| Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["NextLink"]
>
| null;
series?: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SeriesLink"];
progress?: {
href: string;
/** @enum {boolean} */
@ -1468,50 +1636,90 @@ export interface external {
};
};
_embedded: {
manifests: Partial<
{
id: string;
availabilityLabel: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["AvailabilityLabel"];
_links: {
self: {
href: string;
name: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ManifestLinkName"];
manifests:
| Partial<
{
id: string;
availabilityLabel: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"][
"AvailabilityLabel"
];
_links: {
self: {
href: string;
name: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"][
"ManifestLinkName"
];
};
};
};
}[]
> | null;
}[]
>
| null;
};
};
NonPlayableMetadata: {
/** @enum {string} */
playability: "nonPlayable";
playable: unknown | null;
nonPlayable: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["NonPlayableElementVm"];
nonPlayable: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["NonPlayableElementVm"];
id: string;
streamingMode: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableStreamingMode"];
streamingMode: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableStreamingMode"];
duration: string | null;
availability: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["AvailabilityVm"];
legalAge: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["LegalAgeVm"];
preplay: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PreplayVm"];
displayAspectRatio: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DisplayAspectRatio"];
skipDialogInfo: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SkipDialogInfoVm"]
> | null;
sourceMedium: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableSourceMedium"];
availability: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["AvailabilityVm"];
legalAge: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["LegalAgeVm"];
preplay: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PreplayVm"];
displayAspectRatio: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DisplayAspectRatio"];
skipDialogInfo:
| Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SkipDialogInfoVm"]
>
| null;
sourceMedium: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableSourceMedium"];
_links: {
self: {
href: string;
};
manifests: Partial<
{
href: string;
name: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ManifestLinkName"];
}[]
> | null;
next: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["NextLink"]
> | null;
series?: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["SeriesLink"];
manifests:
| Partial<
{
href: string;
name: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"][
"ManifestLinkName"
];
}[]
>
| null;
next:
| Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["NextLink"]
>
| null;
series?: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["SeriesLink"];
progress?: {
href: string;
/** @enum {boolean} */
@ -1519,25 +1727,40 @@ export interface external {
};
};
_embedded: {
manifests: Partial<
{
id: string;
availabilityLabel: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["AvailabilityLabel"];
_links: {
self: {
href: string;
name: external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ManifestLinkName"];
manifests:
| Partial<
{
id: string;
availabilityLabel: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"][
"AvailabilityLabel"
];
_links: {
self: {
href: string;
name: external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"][
"ManifestLinkName"
];
};
};
};
}[]
> | null;
}[]
>
| null;
};
};
MetadataResponse: Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["PlayableMetadata"]
> &
Partial<
external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["NonPlayableMetadata"]
MetadataResponse:
& Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["PlayableMetadata"]
>
& Partial<
external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["NonPlayableMetadata"]
>;
};
};
@ -1547,7 +1770,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DebugData"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DebugData"];
};
};
};
@ -1557,7 +1782,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["DnsList"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["DnsList"];
};
};
};
@ -1593,7 +1820,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ManifestResponse"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ManifestResponse"];
};
};
};
@ -1623,7 +1852,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ManifestResponse"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ManifestResponse"];
};
};
};
@ -1651,7 +1882,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ManifestResponse"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ManifestResponse"];
};
};
};
@ -1679,7 +1912,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["ManifestResponse"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["ManifestResponse"];
};
};
};
@ -1709,7 +1944,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["MetadataResponse"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["MetadataResponse"];
};
};
};
@ -1729,7 +1966,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["MetadataResponse"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["MetadataResponse"];
};
};
};
@ -1749,7 +1988,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["MetadataResponse"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["MetadataResponse"];
};
};
};
@ -1771,7 +2012,9 @@ export interface external {
/** OK */
200: {
content: {
"application/json": external["https://psapi.nrk.no/documentation/openapi/playback/openapi.json"]["components"]["schemas"]["MetadataResponse"];
"application/json": external[
"https://psapi.nrk.no/documentation/openapi/playback/openapi.json"
]["components"]["schemas"]["MetadataResponse"];
};
};
};

View File

@ -500,5 +500,3 @@ export interface operations {
};
};
}
export interface external {}

View File

@ -1,82 +1,92 @@
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 { components as searchComponents } from "./nrk-search.ts";
import { components as catalogComponents } from "./nrk-catalog.ts";
type ArrayElement<A> = A extends readonly (infer T)[] ? T : never
export type Serie = catalogComponents["schemas"]["SeriesViewModel"]
type OriginalEpisode = catalogComponents["schemas"]["EpisodeHalResource"];
export type OriginalEpisode = OriginalEpisode & { url: string; }
type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
export type Serie = catalogComponents["schemas"]["SeriesViewModel"];
type _OriginalEpisode = catalogComponents["schemas"]["EpisodeHalResource"];
export type OriginalEpisode = _OriginalEpisode & { url: string };
export type SearchResultList = searchComponents["schemas"]["seriesResult"]["results"];
export type SearchResult = ArrayElement<SearchResultList> & { description?: string }
export type SearchResult = ArrayElement<SearchResultList> & {
description?: string;
};
/* Incomplete manifest types */
interface Manifest {
id: string;
playable: {
endSequenceStartTime: null,
duration: string;
assets: {
url: string;
format: string;
mimeType: string;
encrypted: boolean;
}[];
};
id: string;
playable: {
endSequenceStartTime: null;
duration: string;
assets: {
url: string;
format: string;
mimeType: string;
encrypted: boolean;
}[];
};
}
async function withDownloadLink(episode: OriginalEpisode): Promise<OriginalEpisode> {
// getting stream link
const [playbackStatus, playbackResponse] = await get<Manifest>(`https://psapi.nrk.no/playback/manifest/podcast/${episode.episodeId}`)
async function withDownloadLink(
episode: _OriginalEpisode,
): Promise<OriginalEpisode> {
// getting stream link
const [playbackStatus, playbackResponse] = await get<Manifest>(
`https://psapi.nrk.no/playback/manifest/podcast/${episode.episodeId}`,
);
if (playbackStatus === OK && playbackResponse) {
return { ...episode, url: playbackResponse.playable.assets[0].url };
} else {
throw `Error getting downloadLink for ${episode.episodeId}, serie: ${episode.originalTitle}. Status: ${playbackStatus}`
}
if (playbackStatus === 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: async (query: string): Promise<SearchResultList> => {
const [status, response] = await get<searchComponents["schemas"]["searchresult"]>(`https://psapi.nrk.no/radio/search/search?q=${query}`);
if (status === OK && response) {
return response.results.series?.results;
} else {
throw `Something went wrong with ${query} - got status ${status}`
}
},
getSerieData: async (seriesId: string) => {
const [
[episodeStatus, episodeResponse],
[seriesStatus, serieResponse],
] = await Promise.all([
get<catalogComponents["schemas"]["EpisodesHalResource"]>(`https://psapi.nrk.no/radio/catalog/podcast/${seriesId}/episodes`),
get<catalogComponents["schemas"]["SeriesHalResource"]>(`https://psapi.nrk.no/radio/catalog/podcast/${seriesId}`)
]);
if (episodeStatus === OK && seriesStatus === OK && serieResponse?.series && episodeResponse?._embedded.episodes?.length) {
const episodes = await Promise.all(episodeResponse._embedded.episodes.map(episode => withDownloadLink(episode)));
return {
...serieResponse.series,
episodes,
}
} else {
throw `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`
}
},
getEpisode: async (seriesId: string, episodeId: string): Promise<OriginalEpisode> => {
const url = `https://psapi.nrk.no/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`
const [status, episode] = await get<OriginalEpisode>(url);
if (status !== OK) {
throw `Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`;
}
return episode;
search: async (query: string): Promise<SearchResultList> => {
const [status, response] = await get<
searchComponents["schemas"]["searchresult"]
>(`https://psapi.nrk.no/radio/search/search?q=${query}`);
if (status === OK && response) {
return response.results.series?.results;
} else {
throw `Something went wrong with ${query} - got status ${status}`;
}
}
},
getSerieData: async (seriesId: string) => {
const [
[episodeStatus, episodeResponse],
[seriesStatus, serieResponse],
] = await Promise.all([
get<catalogComponents["schemas"]["EpisodesHalResource"]>(
`https://psapi.nrk.no/radio/catalog/podcast/${seriesId}/episodes`,
),
get<catalogComponents["schemas"]["SeriesHalResource"]>(
`https://psapi.nrk.no/radio/catalog/podcast/${seriesId}`,
),
]);
if (
episodeStatus === OK && seriesStatus === OK &&
serieResponse?.series && episodeResponse?._embedded.episodes?.length
) {
const episodes = await Promise.all(
episodeResponse._embedded.episodes.map((episode) => withDownloadLink(episode)),
);
return {
...serieResponse.series,
episodes,
};
} else {
throw `Error getting episodes for ${seriesId}: EpisodeStatus: ${episodeStatus}. SerieStatus: ${seriesStatus}`;
}
},
getEpisode: async (seriesId: string, episodeId: string): Promise<OriginalEpisode | null> => {
const url = `https://psapi.nrk.no/radio/catalog/podcast/${seriesId}/episodes/${episodeId}`;
const [status, episode] = await get<OriginalEpisode>(url);
if (status !== OK) {
throw `Error getting episode ${episodeId}. Status: ${status}. Series: ${seriesId}`;
}
return episode;
},
};

View File

@ -1,9 +1,3 @@
/// <reference no-default-lib="true" />
/// <reference lib="dom" />
/// <reference lib="dom.iterable" />
/// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" />
import { start } from "$fresh/server.ts";
import manifest from "./fresh.gen.ts";

16
routes/_app.tsx Normal file
View File

@ -0,0 +1,16 @@
import { PageProps } from "$fresh/server.ts";
export default function App({ Component }: PageProps) {
return (
<html lang="no">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>NRSS</title>
</head>
<body>
<Component />
</body>
</html>
);
}

View File

@ -1,97 +1,98 @@
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 { 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";
function toItemTag(seriesId: string, episode: Episode) {
const description = episode.titles.subtitle || "";
return tag("item", [
tag("title", episode.titles.title),
tag("link", episode._links.share.href),
tag("description", description),
tag("itunes:summary", description),
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()],
["type", "audio/mpeg3"],
]),
])
function toItemTag(seriesId: string, episode: OriginalEpisode) {
const description = episode.titles.subtitle || "";
return tag("item", [
tag("title", episode.titles.title),
tag("link", episode._links.share?.href),
tag("description", description),
tag("itunes:summary", description),
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()],
["type", "audio/mpeg3"],
]),
]);
}
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}`;
const serie = await nrkRadio.getSerieData(seriesId);
const imageUrl = serie.squareImage.at(-1)?.url;
const linkValue = `https://radio.nrk.no/podkast/${serie.id}`;
// Quickly adapted from https://raw.githubusercontent.com/olaven/paperpod/1cde9abd3174b26e126aa74fc5a3b63fd078c0fd/packages/converter/src/rss.ts
return serialize(
declaration([
["version", "1.0"],
["encoding", "UTF-8"],
]),
tag(
"rss",
[
tag("channel", [
tag("title", serie.titles.title),
tag("link", linkValue),
tag("itunes:author", "NRK"),
/* serie.category.id does not overlap with Apple's supported categories..
// Quickly adapted from https://raw.githubusercontent.com/olaven/paperpod/1cde9abd3174b26e126aa74fc5a3b63fd078c0fd/packages/converter/src/rss.ts
return serialize(
declaration([
["version", "1.0"],
["encoding", "UTF-8"],
]),
tag(
"rss",
[
tag("channel", [
tag("title", serie.titles.title),
tag("link", linkValue),
tag("itunes:author", "NRK"),
/* serie.category.id does not overlap with Apple's supported categories..
These podcast feeds are not going to be indexed in itunes anyways, so
a static, valid category is fine. The point is simply to pass third party
podcast feed validation.
*/
tag("itunes:category", "", [["text", "Government"]]),
tag("itunes:owner",
[
tag("itunes:name", "NRK"),
tag("itunes:email", "nrkpodcast@nrk.no")
]
),
tag(
"description",
serie.titles.subtitle || ""
),
tag("ttl", "60"), //60 minutes
...(imageUrl ? [
tag("itunes:image", "", [
["href", imageUrl],
]),
tag("image", [
tag("url", imageUrl),
tag("title", serie.titles.title),
tag("link", linkValue),
])
] : []),
...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"],
tag("itunes:category", "", [["text", "Government"]]),
tag("itunes:owner", [
tag("itunes:name", "NRK"),
tag("itunes:email", "nrkpodcast@nrk.no"),
]),
tag(
"description",
serie.titles.subtitle || "",
),
tag("ttl", "60"), //60 minutes
...(imageUrl
? [
tag("itunes:image", "", [
["href", imageUrl],
]),
tag("image", [
tag("url", imageUrl),
tag("title", serie.titles.title),
tag("link", linkValue),
]),
]
)
);
: []),
...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"],
],
),
);
}
export const handler = async (req: Request, _ctx: HandlerContext): Promise<Response> => {
const seriesId = _ctx.params.seriesId;
const feedContent = await buildFeed(seriesId);
export const handler = async (_req: Request, ctx: FreshContext): Promise<Response> => {
const seriesId = ctx.params.seriesId;
const feedContent = await buildFeed(seriesId);
return new Response(feedContent, {
headers: {
"Content-Type": "application/xml"
}
});
return new Response(feedContent, {
headers: {
"Content-Type": "application/xml",
},
});
};

View File

@ -1,27 +1,39 @@
import { FreshContext } from "$fresh/server.ts";
import { parse, toSeconds } from "https://esm.sh/iso8601-duration@2.1.1";
import { nrkRadio, OriginalEpisode } from "../../../../../lib/nrk.ts";
// TODO: This type `OriginalEpisode` is missing stuff.
function toChapters(episode: OriginalEpisode) {
return episode.indexPoints.map(indexPoint => ({
title: indexPoint.title,
startTime: toSeconds(parse(indexPoint.startPoint))
}))
return episode.indexPoints.map((indexPoint) => ({
title: indexPoint.title,
startTime: toSeconds(parse(indexPoint.startPoint)),
}));
}
export const handler = async (req: Request, _ctx: HandlerContext): Promise<Response> => {
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);
const body = {
version: "1.2.0",
chapters,
}
export const handler = async (_req: Request, ctx: FreshContext): Promise<Response> => {
const seriesId = ctx.params.seriesId;
const episodeId = ctx.params.episodeId;
return new Response(JSON.stringify(body), {
headers: {
"Content-Type": "application/json"
}
console.log("going to get ep for ", episodeId);
const episode = await nrkRadio.getEpisode(seriesId, episodeId);
if (!episode) {
return new Response(JSON.stringify({ message: `Episode ${episodeId} is missing` }), {
headers: {
"Content-Type": "application/json",
},
status: 500,
});
}
const chapters = toChapters(episode);
const body = {
version: "1.2.0",
chapters,
};
return new Response(JSON.stringify(body), {
headers: {
"Content-Type": "application/json",
},
});
};

View File

@ -1,49 +1,76 @@
import { Head } from "$fresh/runtime.ts";
import { Handlers, PageProps } from "$fresh/server.ts";
import Search from "../components/Search.tsx"
import { SerieCard } from "../components/SeriesCard.tsx";
import { nrkRadio, SearchResult, SearchResultList, Serie } from "../lib/nrk.ts";
import Search from "../components/Search.tsx";
import SeriesCard from "../components/SeriesCard.tsx";
import { nrkRadio, SearchResultList } from "../lib/nrk.ts";
interface HandlerData {
query: string;
result?: SearchResultList;
origin: string;
query: string;
result?: SearchResultList;
origin: string;
}
export const handler: Handlers<HandlerData> = {
async GET(request, ctx) {
const url = new URL(request.url);
const query = url.searchParams.get("query");
let result: SearchResultList | undefined;
if (query) {
result = await nrkRadio.search(query);
}
return ctx.render({ query: query || "", result, origin: url.origin });
},
async GET(request, ctx) {
const url = new URL(request.url);
const query = url.searchParams.get("query");
let result: SearchResultList | undefined;
if (query) {
result = await nrkRadio.search(query);
}
return ctx.render({ query: query || "", result, origin: url.origin });
},
};
export default function Home({ data }: PageProps<HandlerData>) {
return (
<>
<Head>
<title>NRSS</title>
</Head>
<div className="p-4 mx-auto max-w-screen-md bg-blue-400">
<div className="text-center">
<h1 className="text-2xl lg:text-3xl">NRSS</h1>
<h2 className="lg:text-xl">NRK-podcast som RSS</h2>
</div>
<Search defaultValue={data.query} />
{data.result ? (
<div className="w-full mx-auto my-4">
{data.result.map(result => <SerieCard serie={result} origin={data.origin} />)}
</div>
) : null}
<h2 className="text-3xl">Hva er dette?</h2>
<p>Hei! Denne løsningen er laget som en reaksjon at statsfinansierte NRK lukker ned innholdet sitt til sin egen app fremfor å bygge oppunder åpne standarder som RSS. Denne siden er laget fort og gæli - den er ustabil og kommer til å krasje :) Sjekk gjerne ut <a href="https://github.com/olaven/NRSS/" className="underline">kildekoden</a>. </p>
<h2 className="text-3xl">Hvordan bruker jeg dette?</h2>
<p>Søk NRK-podcasten du vil høre . Kopier deretter URL-en under bildet. Lim denne inn i akkurat den podcastspilleren du måtte foretrekke! Se <a className="underline" href="https://help.omnystudio.com/en/articles/5222518-podcast-apps-that-support-add-rss-feed">her</a> for en oversikt over hvordan det gjøres i populære podcastspillere.</p>
return (
<>
<Head>
<title>NRSS</title>
</Head>
<div className="p-4 mx-auto max-w-screen-md bg-blue-400">
<div className="text-center">
<h1 className="text-2xl lg:text-3xl">NRSS</h1>
<h2 className="lg:text-xl">NRK-podcast som RSS</h2>
</div>
<Search defaultValue={data.query} />
{data.result
? (
<div className="w-full mx-auto my-4">
{data.result.map((result) => (
<SeriesCard
serie={result}
origin={data.origin}
/>
))}
</div>
</>
);
)
: null}
<h2 className="text-3xl">Hva er dette?</h2>
<p>
Hei! Denne løsningen er laget som en reaksjon at statsfinansierte NRK lukker ned innholdet sitt til sin
egen app fremfor å bygge oppunder åpne standarder som RSS. Denne siden er laget fort og gæli - den er ustabil
og kommer til å krasje :) Sjekk gjerne ut{" "}
<a
href="https://github.com/olaven/NRSS/"
className="underline"
>
kildekoden
</a>.
</p>
<h2 className="text-3xl">Hvordan bruker jeg dette?</h2>
<p>
Søk NRK-podcasten du vil høre . Kopier deretter URL-en under bildet. Lim denne inn i akkurat den
podcastspilleren du måtte foretrekke! Se{" "}
<a
className="underline"
href="https://help.omnystudio.com/en/articles/5222518-podcast-apps-that-support-add-rss-feed"
>
her
</a>{" "}
for en oversikt over hvordan det gjøres i populære podcastspillere.
</p>
</div>
</>
);
}

View File

@ -1,11 +0,0 @@
{
"compilerOptions": {
"plugins": [
{
"name": "typescript-deno-plugin",
"enable": true, // default is `true`
"importmap": "import_map.json"
}
]
}
}

View File

@ -1,11 +1,9 @@
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';
}
const deploymentId = Deno.env.get("DENO_DEPLOYMENT_ID");
if (deploymentId) {
return `https://nrss-${deploymentId}.deno.dev`;
} else {
// assume env
return "http://localhost:8000";
}
}