From 71b70e5868c42e7e1708ee6defb1d0ee4202035d Mon Sep 17 00:00:00 2001 From: Aleksi Lassila Date: Thu, 28 Mar 2024 00:49:43 +0200 Subject: [PATCH] feat: Implemented jellyfin api --- .idea/reiverr.iml | 2 + src/lib/apis/api.interface.ts | 20 ++-- .../{jellyfinApi.ts => jellyfin-api.ts} | 96 ++++++++++++++----- src/lib/apis/reiverr/reiverr-api.ts | 6 +- .../EpisodeCard/EpisodeCard.svelte | 2 +- src/lib/components-old/SeriesPage.svelte | 2 +- .../VideoPlayer/VideoPlayer.svelte | 2 +- src/lib/components/Card/JellyfinCard.svelte | 30 ++++++ src/lib/components/CardGrid.svelte | 3 + src/lib/components/Carousel/Carousel.svelte | 12 +-- .../HeroShowcase/HeroShowcase.svelte | 2 +- .../components/VideoPlayer/VideoPlayer.svelte | 2 +- src/lib/pages/BrowseSeriesPage.svelte | 18 ++-- src/lib/pages/LibraryPage.svelte | 20 +++- src/lib/stores/data.store.ts | 4 +- 15 files changed, 157 insertions(+), 64 deletions(-) rename src/lib/apis/jellyfin/{jellyfinApi.ts => jellyfin-api.ts} (80%) create mode 100644 src/lib/components/Card/JellyfinCard.svelte create mode 100644 src/lib/components/CardGrid.svelte diff --git a/.idea/reiverr.iml b/.idea/reiverr.iml index fe7ecd0..f5282d0 100644 --- a/.idea/reiverr.iml +++ b/.idea/reiverr.iml @@ -9,6 +9,8 @@ + + diff --git a/src/lib/apis/api.interface.ts b/src/lib/apis/api.interface.ts index d97e42b..f196f53 100644 --- a/src/lib/apis/api.interface.ts +++ b/src/lib/apis/api.interface.ts @@ -1,11 +1,15 @@ import type createClient from 'openapi-fetch'; -export abstract class Api> { - protected abstract baseUrl: string; - protected abstract client: ReturnType>; - protected abstract isLoggedIn: boolean; - - getApi() { - return this.client; - } +export interface Api> { + getClient(): ReturnType>; } + +// export abstract class Api> { +// protected abstract baseUrl: string; +// protected abstract client: ReturnType>; +// protected abstract isLoggedIn: boolean; +// +// getApi() { +// return this.client; +// } +// } diff --git a/src/lib/apis/jellyfin/jellyfinApi.ts b/src/lib/apis/jellyfin/jellyfin-api.ts similarity index 80% rename from src/lib/apis/jellyfin/jellyfinApi.ts rename to src/lib/apis/jellyfin/jellyfin-api.ts index c54708c..e3334db 100644 --- a/src/lib/apis/jellyfin/jellyfinApi.ts +++ b/src/lib/apis/jellyfin/jellyfin-api.ts @@ -4,11 +4,82 @@ import { get } from 'svelte/store'; import type { components, paths } from './jellyfin.generated'; import { settings } from '../../stores/settings.store'; import type { DeviceProfile } from './playback-profiles'; +import type { Api } from '../api.interface'; +import { appState } from '../../stores/app-state.store'; export type JellyfinItem = components['schemas']['BaseItemDto']; export const JELLYFIN_DEVICE_ID = 'Reiverr Client'; +export class JellyfinApi implements Api { + getClient() { + const jellyfinSettings = get(appState).user?.settings.jellyfin; + const baseUrl = jellyfinSettings?.baseUrl; + const apiKey = jellyfinSettings?.apiKey; + + return createClient({ + baseUrl, + headers: { + Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${apiKey}"` + } + }); + } + + getUserId() { + return get(appState).user?.settings.jellyfin.userId || ''; + } + + async getContinueWatching(): Promise { + return this.getClient() + .GET('/Users/{userId}/Items/Resume', { + params: { + path: { + userId: this.getUserId() + }, + query: { + mediaTypes: ['Video'], + fields: ['ProviderIds', 'Genres'] + } + } + }) + .then((r) => r.data?.Items || []); + } + + async getLibraryItems() { + return ( + this.getClient() + .GET('/Users/{userId}/Items', { + params: { + path: { + userId: this.getUserId() + }, + query: { + hasTmdbId: true, + recursive: true, + includeItemTypes: ['Movie', 'Series'], + fields: ['ProviderIds', 'Genres', 'DateLastMediaAdded', 'DateCreated'] + } + } + }) + .then((r) => r.data?.Items || []) || Promise.resolve([]) + ); + } + + getPosterUrl(item: JellyfinItem, quality = 100, original = false) { + return item.ImageTags?.Primary + ? `${get(appState).user?.settings.jellyfin.baseUrl}/Items/${ + item?.Id + }/Images/Primary?quality=${quality}${original ? '' : '&fillWidth=432'}&tag=${ + item?.ImageTags?.Primary + }` + : ''; + } +} + +export const jellyfinApi = new JellyfinApi(); +export const getReiverrApiClient = jellyfinApi.getClient; + +/* function getJellyfinApi() { const baseUrl = get(settings)?.jellyfin.baseUrl; const apiKey = get(settings)?.jellyfin.apiKey; @@ -51,23 +122,6 @@ export const getJellyfinNextUp = async () => }) .then((r) => r.data?.Items || []); -export const getJellyfinItems = async () => - getJellyfinApi() - ?.GET('/Users/{userId}/Items', { - params: { - path: { - userId: get(settings)?.jellyfin.userId || '' - }, - query: { - hasTmdbId: true, - recursive: true, - includeItemTypes: ['Movie', 'Series'], - fields: ['ProviderIds', 'Genres', 'DateLastMediaAdded', 'DateCreated'] - } - } - }) - .then((r) => r.data?.Items || []) || Promise.resolve([]); - // export const getJellyfinSeries = () => // JellyfinApi.get('/Users/{userId}/Items', { // params: { @@ -285,13 +339,6 @@ export const getJellyfinUsers = async ( .then((res) => res.data || []) .catch(() => []); -export const getJellyfinPosterUrl = (item: JellyfinItem, quality = 100, original = false) => - item.ImageTags?.Primary - ? `${get(settings).jellyfin.baseUrl}/Items/${item?.Id}/Images/Primary?quality=${quality}${ - original ? '' : '&fillWidth=432' - }&tag=${item?.ImageTags?.Primary}` - : ''; - export const getJellyfinBackdrop = (item: JellyfinItem, quality = 100) => { if (item.BackdropImageTags?.length) { return `${get(settings).jellyfin.baseUrl}/Items/${ @@ -303,3 +350,4 @@ export const getJellyfinBackdrop = (item: JellyfinItem, quality = 100) => { }/Images/Primary?quality=${quality}&tag=${item?.ImageTags?.Primary}`; } }; +*/ diff --git a/src/lib/apis/reiverr/reiverr-api.ts b/src/lib/apis/reiverr/reiverr-api.ts index 06ff9ec..b3f5557 100644 --- a/src/lib/apis/reiverr/reiverr-api.ts +++ b/src/lib/apis/reiverr/reiverr-api.ts @@ -2,11 +2,9 @@ import createClient from 'openapi-fetch'; import type { paths } from './reiverr.generated'; import { get } from 'svelte/store'; import { appState } from '../../stores/app-state.store'; +import type { Api } from '../api.interface'; -interface ApiInterface> { - getClient(): ReturnType>; -} -export class ReiverrApi implements ApiInterface { +export class ReiverrApi implements Api { getClient(basePath?: string) { const token = get(appState).token; diff --git a/src/lib/components-old/EpisodeCard/EpisodeCard.svelte b/src/lib/components-old/EpisodeCard/EpisodeCard.svelte index 48d7742..7f25ef3 100644 --- a/src/lib/components-old/EpisodeCard/EpisodeCard.svelte +++ b/src/lib/components-old/EpisodeCard/EpisodeCard.svelte @@ -2,7 +2,7 @@ import { setJellyfinItemUnwatched, setJellyfinItemWatched - } from '../../../lib/apis/jellyfin/jellyfinApi'; + } from '../../apis/jellyfin/jellyfin-api'; import { jellyfinItemsStore } from '../../../lib/stores/data.store'; import classNames from 'classnames'; import { Check } from 'radix-icons-svelte'; diff --git a/src/lib/components-old/SeriesPage.svelte b/src/lib/components-old/SeriesPage.svelte index 7ef1089..6f49cc4 100644 --- a/src/lib/components-old/SeriesPage.svelte +++ b/src/lib/components-old/SeriesPage.svelte @@ -1,5 +1,5 @@ + + diff --git a/src/lib/components/CardGrid.svelte b/src/lib/components/CardGrid.svelte new file mode 100644 index 0000000..f1c1e9d --- /dev/null +++ b/src/lib/components/CardGrid.svelte @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/lib/components/Carousel/Carousel.svelte b/src/lib/components/Carousel/Carousel.svelte index 1e028b5..d6bd310 100644 --- a/src/lib/components/Carousel/Carousel.svelte +++ b/src/lib/components/Carousel/Carousel.svelte @@ -1,5 +1,4 @@ -
-
+
+
{heading}
{#if scrollX > 50}
{/if} {#if carousel && scrollX < carousel?.scrollWidth - carousel?.clientWidth - 50}
diff --git a/src/lib/components/HeroShowcase/HeroShowcase.svelte b/src/lib/components/HeroShowcase/HeroShowcase.svelte index a56a810..62ab7cd 100644 --- a/src/lib/components/HeroShowcase/HeroShowcase.svelte +++ b/src/lib/components/HeroShowcase/HeroShowcase.svelte @@ -125,7 +125,7 @@ {/await}
- + diff --git a/src/lib/components/VideoPlayer/VideoPlayer.svelte b/src/lib/components/VideoPlayer/VideoPlayer.svelte index 7a7cd5d..6a9bba7 100644 --- a/src/lib/components/VideoPlayer/VideoPlayer.svelte +++ b/src/lib/components/VideoPlayer/VideoPlayer.svelte @@ -6,7 +6,7 @@ reportJellyfinPlaybackProgress, reportJellyfinPlaybackStarted, reportJellyfinPlaybackStopped - } from '../../apis/jellyfin/jellyfinApi'; + } from '../../apis/jellyfin/jellyfin-api'; import getDeviceProfile from '../../apis/jellyfin/playback-profiles'; import { getQualities } from '../../apis/jellyfin/qualities'; import { settings } from '../../stores/settings.store'; diff --git a/src/lib/pages/BrowseSeriesPage.svelte b/src/lib/pages/BrowseSeriesPage.svelte index f7199f5..045fb3a 100644 --- a/src/lib/pages/BrowseSeriesPage.svelte +++ b/src/lib/pages/BrowseSeriesPage.svelte @@ -6,7 +6,7 @@ import type { TitleType } from '../types'; import type { ComponentProps } from 'svelte'; import Poster from '../components/Card/Card.svelte'; - import { getJellyfinItems, type JellyfinItem } from '../apis/jellyfin/jellyfinApi'; + import { type JellyfinItem } from '../apis/jellyfin/jellyfin-api'; import { jellyfinItemsStore } from '../stores/data.store'; import Carousel from '../components/Carousel/Carousel.svelte'; import { _ } from 'svelte-i18n'; @@ -62,12 +62,12 @@ const fetchPopularMovies = () => getTmdbPopularMovies(); - const fetchLibraryItems = async () => { - const items = await getJellyfinItems(); - const props = await fetchCardProps(items, 'series'); - console.log('JellyfinItems', items, props); - return props; - }; + // const fetchLibraryItems = async () => { + // const items = await getJellyfinItems(); + // const props = await fetchCardProps(items, 'series'); + // console.log('JellyfinItems', items, props); + // return props; + // }; function parseIncludedLanguages(includedLanguages: string) { return includedLanguages.replace(' ', '').split(',').join('|'); @@ -76,8 +76,8 @@ - -
+ +
{$_('discover.streamingNow')}
{#await fetchNowStreaming()} diff --git a/src/lib/pages/LibraryPage.svelte b/src/lib/pages/LibraryPage.svelte index 2538cd4..148d64b 100644 --- a/src/lib/pages/LibraryPage.svelte +++ b/src/lib/pages/LibraryPage.svelte @@ -1,9 +1,13 @@ - +
LibraryPage
- - - + + {#await libraryItemsP} + + {:then items} + {#each items as item} + + {/each} + {/await} +
diff --git a/src/lib/stores/data.store.ts b/src/lib/stores/data.store.ts index 353c7ea..56fa255 100644 --- a/src/lib/stores/data.store.ts +++ b/src/lib/stores/data.store.ts @@ -1,6 +1,6 @@ import { derived, writable } from 'svelte/store'; import { settings } from './settings.store'; -import { getJellyfinItems, type JellyfinItem } from '../apis/jellyfin/jellyfinApi'; +import { jellyfinApi, type JellyfinItem } from '../apis/jellyfin/jellyfin-api'; import { getSonarrDownloads, getSonarrSeries, @@ -59,7 +59,7 @@ function _createDataFetchStore(fn: () => Promise) { }; } -export const jellyfinItemsStore = _createDataFetchStore(getJellyfinItems); +export const jellyfinItemsStore = _createDataFetchStore(jellyfinApi.getLibraryItems); export function createJellyfinItemStore(tmdbId: number | Promise) { const store = writable<{ loading: boolean; item?: JellyfinItem }>({