diff --git a/src/app.css b/src/app.css index 08ccc49..36b9ef7 100644 --- a/src/app.css +++ b/src/app.css @@ -18,6 +18,10 @@ a { @apply focus-visible:outline outline-2 outline-[#f0cd6dc2] outline-offset-2; } +.peer-selectable { + @apply peer-focus-visible:outline outline-2 outline-[#f0cd6dc2] outline-offset-2; +} + .selectable-explicit { @apply focus-within:outline outline-2 outline-[#f0cd6dc2] outline-offset-2; } diff --git a/src/lib/apis/jellyfin/jellyfinApi.ts b/src/lib/apis/jellyfin/jellyfinApi.ts index 9c1cb02..0c7d6d3 100644 --- a/src/lib/apis/jellyfin/jellyfinApi.ts +++ b/src/lib/apis/jellyfin/jellyfinApi.ts @@ -1,88 +1,72 @@ import type { components, paths } from '$lib/apis/jellyfin/jellyfin.generated'; import type { DeviceProfile } from '$lib/apis/jellyfin/playback-profiles'; -import { JELLYFIN_API_KEY, JELLYFIN_BASE_URL } from '$lib/constants'; +import { settings } from '$lib/stores/settings.store'; +import axios from 'axios'; import createClient from 'openapi-fetch'; +import { get } from 'svelte/store'; export type JellyfinItem = components['schemas']['BaseItemDto']; -export const jellyfinAvailable = !!JELLYFIN_BASE_URL && !!JELLYFIN_API_KEY; - export const JELLYFIN_DEVICE_ID = 'Reiverr Client'; -export const JellyfinApi = - JELLYFIN_BASE_URL && JELLYFIN_API_KEY - ? createClient({ - baseUrl: JELLYFIN_BASE_URL, - headers: { - Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${JELLYFIN_API_KEY}"` - } - }) - : undefined; +function getJellyfinApi() { + const baseUrl = get(settings)?.jellyfin.baseUrl; + const apiKey = get(settings)?.jellyfin.apiKey; + const userId = get(settings)?.jellyfin.userId; -let userId: string | undefined = undefined; -const getUserId = async () => { - if (userId) return userId; + if (!baseUrl || !apiKey || !userId) return undefined; - const user = JellyfinApi?.get('/Users', { - params: {}, + return createClient({ + baseUrl, headers: { - 'cache-control': 'max-age=3600' + Authorization: `MediaBrowser DeviceId="${JELLYFIN_DEVICE_ID}", Token="${apiKey}"` } - }).then((r) => r.data?.[0]?.Id || ''); - - userId = await user; - return user; -}; + }); +} export const getJellyfinContinueWatching = async (): Promise => - getUserId().then((userId) => - userId - ? JellyfinApi?.get('/Users/{userId}/Items/Resume', { - params: { - path: { - userId - }, - query: { - mediaTypes: ['Video'], - fields: ['ProviderIds'] - } - } - }).then((r) => r.data?.Items || []) - : undefined - ); + getJellyfinApi() + ?.get('/Users/{userId}/Items/Resume', { + params: { + path: { + userId: get(settings)?.jellyfin.userId || '' + }, + query: { + mediaTypes: ['Video'], + fields: ['ProviderIds'] + } + } + }) + .then((r) => r.data?.Items || []); export const getJellyfinNextUp = async () => - getUserId().then((userId) => - userId - ? JellyfinApi?.get('/Shows/NextUp', { - params: { - query: { - userId, - fields: ['ProviderIds'] - } - } - }).then((r) => r.data?.Items || []) - : undefined - ); + getJellyfinApi() + ?.get('/Shows/NextUp', { + params: { + query: { + userId: get(settings)?.jellyfin.userId || '', + fields: ['ProviderIds'] + } + } + }) + .then((r) => r.data?.Items || []); export const getJellyfinItems = async () => - getUserId().then((userId) => - userId - ? JellyfinApi?.get('/Users/{userId}/Items', { - params: { - path: { - userId - }, - query: { - hasTmdbId: true, - recursive: true, - includeItemTypes: ['Movie', 'Series'], - fields: ['ProviderIds'] - } - } - }).then((r) => r.data?.Items || []) - : undefined - ); + getJellyfinApi() + ?.get('/Users/{userId}/Items', { + params: { + path: { + userId: get(settings)?.jellyfin.userId || '' + }, + query: { + hasTmdbId: true, + recursive: true, + includeItemTypes: ['Movie', 'Series'], + fields: ['ProviderIds'] + } + } + }) + .then((r) => r.data?.Items || []); // export const getJellyfinSeries = () => // JellyfinApi.get('/Users/{userId}/Items', { @@ -99,24 +83,22 @@ export const getJellyfinItems = async () => // }).then((r) => r.data?.Items || []); export const getJellyfinEpisodes = async () => - getUserId().then((userId) => - userId - ? JellyfinApi?.get('/Users/{userId}/Items', { - params: { - path: { - userId - }, - query: { - recursive: true, - includeItemTypes: ['Episode'] - } - }, - headers: { - 'cache-control': 'max-age=10' - } - }).then((r) => r.data?.Items || []) - : undefined - ); + getJellyfinApi() + ?.get('/Users/{userId}/Items', { + params: { + path: { + userId: get(settings)?.jellyfin.userId || '' + }, + query: { + recursive: true, + includeItemTypes: ['Episode'] + } + }, + headers: { + 'cache-control': 'max-age=10' + } + }) + .then((r) => r.data?.Items || []); // export const getJellyfinEpisodesBySeries = (seriesId: string) => // getJellyfinEpisodes().then((items) => items?.filter((i) => i.SeriesId === seriesId) || []); @@ -125,18 +107,16 @@ export const getJellyfinEpisodes = async () => // getJellyfinItems().then((items) => items?.find((i) => i.ProviderIds?.Tmdb == tmdbId)); export const getJellyfinItem = async (itemId: string) => - getUserId().then((userId) => - userId - ? JellyfinApi?.get('/Users/{userId}/Items/{itemId}', { - params: { - path: { - itemId, - userId - } - } - }).then((r) => r.data) - : undefined - ); + getJellyfinApi() + ?.get('/Users/{userId}/Items/{itemId}', { + params: { + path: { + itemId, + userId: get(settings)?.jellyfin.userId || '' + } + } + }) + .then((r) => r.data); // export const requestJellyfinItemByTmdbId = () => // request((tmdbId: string) => getJellyfinItemByTmdbId(tmdbId)); @@ -147,35 +127,37 @@ export const getJellyfinPlaybackInfo = async ( startTimeTicks = 0, maxStreamingBitrate = 140000000 ) => - getUserId().then((userId) => - userId - ? JellyfinApi?.post('/Items/{itemId}/PlaybackInfo', { - params: { - path: { - itemId: itemId - }, - query: { - userId, - startTimeTicks, - autoOpenLiveStream: true, - maxStreamingBitrate - } - }, - body: { - DeviceProfile: playbackProfile - } - }).then((r) => ({ - playbackUri: - r.data?.MediaSources?.[0]?.TranscodingUrl || - `/Videos/${r.data?.MediaSources?.[0].Id}/stream.mp4?Static=true&mediaSourceId=${r.data?.MediaSources?.[0].Id}&deviceId=${JELLYFIN_DEVICE_ID}&api_key=${JELLYFIN_API_KEY}&Tag=${r.data?.MediaSources?.[0].ETag}`, - mediaSourceId: r.data?.MediaSources?.[0]?.Id, - playSessionId: r.data?.PlaySessionId, - directPlay: - !!r.data?.MediaSources?.[0]?.SupportsDirectPlay || - !!r.data?.MediaSources?.[0]?.SupportsDirectStream - })) - : undefined - ); + getJellyfinApi() + ?.post('/Items/{itemId}/PlaybackInfo', { + params: { + path: { + itemId: itemId + }, + query: { + userId: get(settings)?.jellyfin.userId || '', + startTimeTicks, + autoOpenLiveStream: true, + maxStreamingBitrate + } + }, + body: { + DeviceProfile: playbackProfile + } + }) + .then((r) => ({ + playbackUri: + r.data?.MediaSources?.[0]?.TranscodingUrl || + `/Videos/${r.data?.MediaSources?.[0].Id}/stream.mp4?Static=true&mediaSourceId=${ + r.data?.MediaSources?.[0].Id + }&deviceId=${JELLYFIN_DEVICE_ID}&api_key=${get(settings)?.jellyfin.apiKey}&Tag=${ + r.data?.MediaSources?.[0].ETag + }`, + mediaSourceId: r.data?.MediaSources?.[0]?.Id, + playSessionId: r.data?.PlaySessionId, + directPlay: + !!r.data?.MediaSources?.[0]?.SupportsDirectPlay || + !!r.data?.MediaSources?.[0]?.SupportsDirectStream + })); export const reportJellyfinPlaybackStarted = ( itemId: string, @@ -184,20 +166,16 @@ export const reportJellyfinPlaybackStarted = ( audioStreamIndex?: number, subtitleStreamIndex?: number ) => - getUserId().then((userId) => - userId - ? JellyfinApi?.post('/Sessions/Playing', { - body: { - CanSeek: true, - ItemId: itemId, - PlaySessionId: sessionId, - MediaSourceId: mediaSourceId, - AudioStreamIndex: 1, - SubtitleStreamIndex: -1 - } - }) - : undefined - ); + getJellyfinApi()?.post('/Sessions/Playing', { + body: { + CanSeek: true, + ItemId: itemId, + PlaySessionId: sessionId, + MediaSourceId: mediaSourceId, + AudioStreamIndex: 1, + SubtitleStreamIndex: -1 + } + }); export const reportJellyfinPlaybackProgress = ( itemId: string, @@ -205,7 +183,7 @@ export const reportJellyfinPlaybackProgress = ( isPaused: boolean, positionTicks: number ) => - JellyfinApi?.post('/Sessions/Playing/Progress', { + getJellyfinApi()?.post('/Sessions/Playing/Progress', { body: { ItemId: itemId, PlaySessionId: sessionId, @@ -221,7 +199,7 @@ export const reportJellyfinPlaybackStopped = ( sessionId: string, positionTicks: number ) => - JellyfinApi?.post('/Sessions/Playing/Stopped', { + getJellyfinApi()?.post('/Sessions/Playing/Stopped', { body: { ItemId: itemId, PlaySessionId: sessionId, @@ -231,7 +209,7 @@ export const reportJellyfinPlaybackStopped = ( }); export const delteActiveEncoding = (playSessionId: string) => - JellyfinApi?.del('/Videos/ActiveEncodings', { + getJellyfinApi()?.del('/Videos/ActiveEncodings', { params: { query: { deviceId: JELLYFIN_DEVICE_ID, @@ -241,32 +219,50 @@ export const delteActiveEncoding = (playSessionId: string) => }); export const setJellyfinItemWatched = async (jellyfinId: string) => - getUserId().then((userId) => - userId - ? JellyfinApi?.post('/Users/{userId}/PlayedItems/{itemId}', { - params: { - path: { - userId, - itemId: jellyfinId - }, - query: { - datePlayed: new Date().toISOString() - } - } - }) - : undefined - ); + getJellyfinApi()?.post('/Users/{userId}/PlayedItems/{itemId}', { + params: { + path: { + userId: get(settings)?.jellyfin.userId || '', + itemId: jellyfinId + }, + query: { + datePlayed: new Date().toISOString() + } + } + }); export const setJellyfinItemUnwatched = async (jellyfinId: string) => - getUserId().then((userId) => - userId - ? JellyfinApi?.del('/Users/{userId}/PlayedItems/{itemId}', { - params: { - path: { - userId, - itemId: jellyfinId - } - } - }) - : undefined - ); + getJellyfinApi()?.del('/Users/{userId}/PlayedItems/{itemId}', { + params: { + path: { + userId: get(settings)?.jellyfin.userId || '', + itemId: jellyfinId + } + } + }); + +export const getJellyfinHealth = async ( + baseUrl: string | undefined = undefined, + apiKey: string | undefined = undefined +) => + axios + .get((baseUrl || get(settings)?.jellyfin.baseUrl) + '/System/Info', { + headers: { + 'X-Emby-Token': apiKey || get(settings)?.jellyfin.apiKey + } + }) + .then((res) => res.status === 200) + .catch(() => false); + +export const getJellyfinUsers = async ( + baseUrl: string | undefined = undefined, + apiKey: string | undefined = undefined +): Promise => + axios + .get((baseUrl || get(settings)?.jellyfin.baseUrl) + '/Users', { + headers: { + 'X-Emby-Token': apiKey || get(settings)?.jellyfin.apiKey + } + }) + .then((res) => res.data || []) + .catch(() => []); diff --git a/src/lib/apis/radarr/radarrApi.ts b/src/lib/apis/radarr/radarrApi.ts index 037ffa8..35b76ad 100644 --- a/src/lib/apis/radarr/radarrApi.ts +++ b/src/lib/apis/radarr/radarrApi.ts @@ -1,8 +1,8 @@ import type { components, paths } from '$lib/apis/radarr/radarr.generated'; import { getTmdbMovie } from '$lib/apis/tmdb/tmdbApi'; -import { RADARR_API_KEY, RADARR_BASE_URL } from '$lib/constants'; import { settings } from '$lib/stores/settings.store'; import { log } from '$lib/utils'; +import axios from 'axios'; import createClient from 'openapi-fetch'; import { get } from 'svelte/store'; @@ -25,31 +25,39 @@ export interface RadarrMovieOptions { searchNow?: boolean; } -export const radarrAvailable = !!RADARR_BASE_URL && !!RADARR_API_KEY; +function getRadarrApi() { + const baseUrl = get(settings)?.radarr.baseUrl; + const apiKey = get(settings)?.radarr.apiKey; -export const RadarrApi = - RADARR_BASE_URL && RADARR_API_KEY - ? createClient({ - baseUrl: RADARR_BASE_URL, - headers: { - 'X-Api-Key': RADARR_API_KEY - } - }) - : undefined; + if (!baseUrl || !apiKey) return undefined; + + console.log(baseUrl, apiKey); + + return createClient({ + baseUrl, + headers: { + 'X-Api-Key': apiKey + } + }); +} export const getRadarrMovies = (): Promise => - RadarrApi?.get('/api/v3/movie', { - params: {} - }).then((r) => r.data || []) || Promise.resolve([]); + getRadarrApi() + ?.get('/api/v3/movie', { + params: {} + }) + .then((r) => r.data || []) || Promise.resolve([]); export const getRadarrMovieByTmdbId = (tmdbId: string): Promise => - RadarrApi?.get('/api/v3/movie', { - params: { - query: { - tmdbId: Number(tmdbId) + getRadarrApi() + ?.get('/api/v3/movie', { + params: { + query: { + tmdbId: Number(tmdbId) + } } - } - }).then((r) => r.data?.find((m) => (m.tmdbId as any) == tmdbId)) || Promise.resolve(undefined); + }) + .then((r) => r.data?.find((m) => (m.tmdbId as any) == tmdbId)) || Promise.resolve(undefined); export const addMovieToRadarr = async (tmdbId: number) => { const tmdbMovie = await getTmdbMovie(tmdbId); @@ -60,9 +68,9 @@ export const addMovieToRadarr = async (tmdbId: number) => { if (!tmdbMovie) throw new Error('Movie not found'); const options: RadarrMovieOptions = { - qualityProfileId: get(settings).radarr.qualityProfileId, - profileId: get(settings).radarr.profileId, - rootFolderPath: get(settings).radarr.rootFolderPath, + qualityProfileId: get(settings)?.radarr.qualityProfileId || 0, + profileId: get(settings)?.radarr.profileId || 0, + rootFolderPath: get(settings)?.radarr.rootFolderPath || '', minimumAvailability: 'announced', title: tmdbMovie.title || tmdbMovie.original_title || '', tmdbId: tmdbMovie.id || 0, @@ -73,60 +81,70 @@ export const addMovieToRadarr = async (tmdbId: number) => { }; return ( - RadarrApi?.post('/api/v3/movie', { - params: {}, - body: options - }).then((r) => r.data) || Promise.resolve(undefined) + getRadarrApi() + ?.post('/api/v3/movie', { + params: {}, + body: options + }) + .then((r) => r.data) || Promise.resolve(undefined) ); }; export const cancelDownloadRadarrMovie = async (downloadId: number) => { - const deleteResponse = await RadarrApi?.del('/api/v3/queue/{id}', { - params: { - path: { - id: downloadId - }, - query: { - blocklist: false, - removeFromClient: true + const deleteResponse = await getRadarrApi() + ?.del('/api/v3/queue/{id}', { + params: { + path: { + id: downloadId + }, + query: { + blocklist: false, + removeFromClient: true + } } - } - }).then((r) => log(r)); + }) + .then((r) => log(r)); return !!deleteResponse?.response.ok; }; export const fetchRadarrReleases = (movieId: number) => - RadarrApi?.get('/api/v3/release', { params: { query: { movieId: movieId } } }).then( - (r) => r.data || [] - ) || Promise.resolve([]); + getRadarrApi() + ?.get('/api/v3/release', { params: { query: { movieId: movieId } } }) + .then((r) => r.data || []) || Promise.resolve([]); export const downloadRadarrMovie = (guid: string, indexerId: number) => - RadarrApi?.post('/api/v3/release', { - params: {}, - body: { - indexerId, - guid - } - }).then((res) => res.response.ok) || Promise.resolve(false); + getRadarrApi() + ?.post('/api/v3/release', { + params: {}, + body: { + indexerId, + guid + } + }) + .then((res) => res.response.ok) || Promise.resolve(false); export const deleteRadarrMovie = (id: number) => - RadarrApi?.del('/api/v3/moviefile/{id}', { - params: { - path: { - id + getRadarrApi() + ?.del('/api/v3/moviefile/{id}', { + params: { + path: { + id + } } - } - }).then((res) => res.response.ok) || Promise.resolve(false); + }) + .then((res) => res.response.ok) || Promise.resolve(false); export const getRadarrDownloads = (): Promise => - RadarrApi?.get('/api/v3/queue', { - params: { - query: { - includeMovie: true + getRadarrApi() + ?.get('/api/v3/queue', { + params: { + query: { + includeMovie: true + } } - } - }).then((r) => (r.data?.records?.filter((record) => record.movie) as RadarrDownload[]) || []) || + }) + .then((r) => (r.data?.records?.filter((record) => record.movie) as RadarrDownload[]) || []) || Promise.resolve([]); export const getRadarrDownloadsById = (radarrId: number) => @@ -136,22 +154,41 @@ export const getRadarrDownloadsByTmdbId = (tmdbId: number) => getRadarrDownloads().then((downloads) => downloads.filter((d) => d.movie.tmdbId === tmdbId)); const lookupRadarrMovieByTmdbId = (tmdbId: number) => - RadarrApi?.get('/api/v3/movie/lookup/tmdb', { - params: { - query: { - tmdbId + getRadarrApi() + ?.get('/api/v3/movie/lookup/tmdb', { + params: { + query: { + tmdbId + } } - } - }).then((r) => r.data as any as RadarrMovie) || Promise.resolve(undefined); + }) + .then((r) => r.data as any as RadarrMovie) || Promise.resolve(undefined); export const getDiskSpace = (): Promise => - RadarrApi?.get('/api/v3/diskspace', {}).then((d) => d.data || []) || Promise.resolve([]); + getRadarrApi() + ?.get('/api/v3/diskspace', {}) + .then((d) => d.data || []) || Promise.resolve([]); export const removeFromRadarr = (id: number) => - RadarrApi?.del('/api/v3/movie/{id}', { - params: { - path: { - id + getRadarrApi() + ?.del('/api/v3/movie/{id}', { + params: { + path: { + id + } } - } - }).then((res) => res.response.ok) || Promise.resolve(false); + }) + .then((res) => res.response.ok) || Promise.resolve(false); + +export const getRadarrHealth = async ( + baseUrl: string | undefined = undefined, + apiKey: string | undefined = undefined +) => + axios + .get((baseUrl || get(settings)?.radarr.baseUrl) + '/api/v3/health', { + headers: { + 'X-Api-Key': apiKey || get(settings)?.radarr.apiKey + } + }) + .then((res) => res.status === 200) + .catch(() => false); diff --git a/src/lib/apis/sonarr/sonarrApi.ts b/src/lib/apis/sonarr/sonarrApi.ts index 613344a..adad45e 100644 --- a/src/lib/apis/sonarr/sonarrApi.ts +++ b/src/lib/apis/sonarr/sonarrApi.ts @@ -1,10 +1,10 @@ import type { components, paths } from '$lib/apis/sonarr/sonarr.generated'; -import { log } from '$lib/utils'; -import createClient from 'openapi-fetch'; -import { getTmdbSeries } from '../tmdb/tmdbApi'; -import { get } from 'svelte/store'; import { settings } from '$lib/stores/settings.store'; -import { SONARR_API_KEY, SONARR_BASE_URL } from '$lib/constants'; +import { log } from '$lib/utils'; +import axios from 'axios'; +import createClient from 'openapi-fetch'; +import { get } from 'svelte/store'; +import { getTmdbSeries } from '../tmdb/tmdbApi'; export type SonarrSeries = components['schemas']['SeriesResource']; export type SonarrReleaseResource = components['schemas']['ReleaseResource']; @@ -38,38 +38,42 @@ export interface SonarrSeriesOptions { }; } -export const sonarrAvailable = !!SONARR_BASE_URL && !!SONARR_API_KEY; +function getSonarrApi() { + const baseUrl = get(settings)?.sonarr.baseUrl; + const apiKey = get(settings)?.sonarr.apiKey; -export const SonarrApi = - SONARR_BASE_URL && SONARR_API_KEY - ? createClient({ - baseUrl: SONARR_BASE_URL, - headers: { - 'X-Api-Key': SONARR_API_KEY - } - }) - : undefined; + if (!baseUrl || !apiKey) return undefined; + + return createClient({ + baseUrl, + headers: { + 'X-Api-Key': apiKey + } + }); +} export const getSonarrSeries = (): Promise => - SonarrApi?.get('/api/v3/series', { - params: {} - }).then((r) => r.data || []) || Promise.resolve([]); + getSonarrApi() + ?.get('/api/v3/series', { + params: {} + }) + .then((r) => r.data || []) || Promise.resolve([]); export const getSonarrSeriesByTvdbId = (tvdbId: number): Promise => - SonarrApi?.get('/api/v3/series', { - params: { - query: { - tvdbId: tvdbId + getSonarrApi() + ?.get('/api/v3/series', { + params: { + query: { + tvdbId: tvdbId + } } - } - }).then((r) => r.data?.find((m) => m.tvdbId === tvdbId)) || Promise.resolve(undefined); - -export const getRadarrDownloadById = (sonarrId: number) => - getSonarrDownloads().then((downloads) => downloads.find((d) => d.series.id === sonarrId)) || - Promise.resolve(undefined); + }) + .then((r) => r.data?.find((m) => m.tvdbId === tvdbId)) || Promise.resolve(undefined); export const getDiskSpace = (): Promise => - SonarrApi?.get('/api/v3/diskspace', {}).then((d) => d.data || []) || Promise.resolve([]); + getSonarrApi() + ?.get('/api/v3/diskspace', {}) + .then((d) => d.data || []) || Promise.resolve([]); export const addSeriesToSonarr = async (tmdbId: number) => { const tmdbSeries = await getTmdbSeries(tmdbId); @@ -80,103 +84,120 @@ export const addSeriesToSonarr = async (tmdbId: number) => { const options: SonarrSeriesOptions = { title: tmdbSeries.name, tvdbId: tmdbSeries.external_ids.tvdb_id, - qualityProfileId: get(settings).sonarr.qualityProfileId, + qualityProfileId: get(settings)?.sonarr.qualityProfileId || 0, monitored: false, addOptions: { monitor: 'none', searchForMissingEpisodes: false, searchForCutoffUnmetEpisodes: false }, - rootFolderPath: get(settings).sonarr.rootFolderPath, - languageProfileId: get(settings).sonarr.languageProfileId, + rootFolderPath: get(settings)?.sonarr.rootFolderPath || '', + languageProfileId: get(settings)?.sonarr.languageProfileId || 0, seasonFolder: true }; - return SonarrApi?.post('/api/v3/series', { - params: {}, - body: options - }).then((r) => r.data); + return getSonarrApi() + ?.post('/api/v3/series', { + params: {}, + body: options + }) + .then((r) => r.data); }; export const cancelDownloadSonarrEpisode = async (downloadId: number) => { - const deleteResponse = await SonarrApi?.del('/api/v3/queue/{id}', { - params: { - path: { - id: downloadId - }, - query: { - blocklist: false, - removeFromClient: true + const deleteResponse = await getSonarrApi() + ?.del('/api/v3/queue/{id}', { + params: { + path: { + id: downloadId + }, + query: { + blocklist: false, + removeFromClient: true + } } - } - }).then((r) => log(r)); + }) + .then((r) => log(r)); return !!deleteResponse?.response.ok; }; export const downloadSonarrEpisode = (guid: string, indexerId: number) => - SonarrApi?.post('/api/v3/release', { - params: {}, - body: { - indexerId, - guid - } - }).then((res) => res.response.ok) || Promise.resolve(false); + getSonarrApi() + ?.post('/api/v3/release', { + params: {}, + body: { + indexerId, + guid + } + }) + .then((res) => res.response.ok) || Promise.resolve(false); export const deleteSonarrEpisode = (id: number) => - SonarrApi?.del('/api/v3/episodefile/{id}', { - params: { - path: { - id + getSonarrApi() + ?.del('/api/v3/episodefile/{id}', { + params: { + path: { + id + } } - } - }).then((res) => res.response.ok) || Promise.resolve(false); + }) + .then((res) => res.response.ok) || Promise.resolve(false); export const getSonarrDownloads = (): Promise => - SonarrApi?.get('/api/v3/queue', { - params: { - query: { - includeEpisode: true, - includeSeries: true + getSonarrApi() + ?.get('/api/v3/queue', { + params: { + query: { + includeEpisode: true, + includeSeries: true + } } - } - }).then( - (r) => - (r.data?.records?.filter((record) => record.episode && record.series) as SonarrDownload[]) || - [] - ) || Promise.resolve([]); + }) + .then( + (r) => + (r.data?.records?.filter( + (record) => record.episode && record.series + ) as SonarrDownload[]) || [] + ) || Promise.resolve([]); export const getSonarrDownloadsById = (sonarrId: number) => getSonarrDownloads().then((downloads) => downloads.filter((d) => d.seriesId === sonarrId)) || Promise.resolve([]); export const removeFromSonarr = (id: number): Promise => - SonarrApi?.del('/api/v3/series/{id}', { - params: { - path: { - id + getSonarrApi() + ?.del('/api/v3/series/{id}', { + params: { + path: { + id + } } - } - }).then((res) => res.response.ok) || Promise.resolve(false); + }) + .then((res) => res.response.ok) || Promise.resolve(false); export const getSonarrEpisodes = async (seriesId: number) => { const episodesPromise = - SonarrApi?.get('/api/v3/episode', { - params: { - query: { - seriesId + getSonarrApi() + ?.get('/api/v3/episode', { + params: { + query: { + seriesId + } } - } - }).then((r) => r.data || []) || Promise.resolve([]); + }) + .then((r) => r.data || []) || Promise.resolve([]); const episodeFilesPromise = - SonarrApi?.get('/api/v3/episodefile', { - params: { - query: { - seriesId + getSonarrApi() + ?.get('/api/v3/episodefile', { + params: { + query: { + seriesId + } } - } - }).then((r) => r.data || []) || Promise.resolve([]); + }) + .then((r) => r.data || []) || Promise.resolve([]); const episodes = await episodesPromise; const episodeFiles = await episodeFilesPromise; @@ -188,32 +209,51 @@ export const getSonarrEpisodes = async (seriesId: number) => { }; export const fetchSonarrReleases = async (episodeId: number) => - SonarrApi?.get('/api/v3/release', { - params: { - query: { - episodeId + getSonarrApi() + ?.get('/api/v3/release', { + params: { + query: { + episodeId + } } - } - }).then((r) => r.data || []) || Promise.resolve([]); + }) + .then((r) => r.data || []) || Promise.resolve([]); export const fetchSonarrSeasonReleases = async (seriesId: number, seasonNumber: number) => - SonarrApi?.get('/api/v3/release', { - params: { - query: { - seriesId, - seasonNumber + getSonarrApi() + ?.get('/api/v3/release', { + params: { + query: { + seriesId, + seasonNumber + } } - } - }).then((r) => r.data || []) || Promise.resolve([]); + }) + .then((r) => r.data || []) || Promise.resolve([]); export const fetchSonarrEpisodes = async (seriesId: number): Promise => { return ( - SonarrApi?.get('/api/v3/episode', { - params: { - query: { - seriesId + getSonarrApi() + ?.get('/api/v3/episode', { + params: { + query: { + seriesId + } } - } - }).then((r) => r.data || []) || Promise.resolve([]) + }) + .then((r) => r.data || []) || Promise.resolve([]) ); }; + +export const getSonarrHealth = async ( + baseUrl: string | undefined = undefined, + apiKey: string | undefined = undefined +) => + axios + .get((baseUrl || get(settings)?.sonarr.baseUrl) + '/api/v3/health', { + headers: { + 'X-Api-Key': apiKey || get(settings)?.sonarr.apiKey + } + }) + .then((res) => res.status === 200) + .catch(() => false); diff --git a/src/lib/apis/tmdb/tmdbApi.ts b/src/lib/apis/tmdb/tmdbApi.ts index 9d2d666..28529b2 100644 --- a/src/lib/apis/tmdb/tmdbApi.ts +++ b/src/lib/apis/tmdb/tmdbApi.ts @@ -70,7 +70,7 @@ export const getTmdbMovie = async (tmdbId: number) => }, query: { append_to_response: 'videos,credits,external_ids,images', - ...({ include_image_language: get(settings).language + ',en,null' } as any) + ...({ include_image_language: get(settings)?.language + ',en,null' } as any) } } }).then((res) => res.data as TmdbMovieFull2 | undefined); @@ -101,7 +101,7 @@ export const getTmdbSeries = async (tmdbId: number): Promise .then( (r) => ( - r?.backdrops?.find((b) => b.iso_639_1 === get(settings).language) || + r?.backdrops?.find((b) => b.iso_639_1 === get(settings)?.language) || r?.backdrops?.find((b) => b.iso_639_1 === 'en') || r?.backdrops?.find((b) => b.iso_639_1) || r?.backdrops?.[0] @@ -173,7 +173,7 @@ export const getTmdbMovieBackdrop = async (tmdbId: number) => .then( (r) => ( - r?.backdrops?.find((b) => b.iso_639_1 === get(settings).language) || + r?.backdrops?.find((b) => b.iso_639_1 === get(settings)?.language) || r?.backdrops?.find((b) => b.iso_639_1 === 'en') || r?.backdrops?.find((b) => b.iso_639_1) || r?.backdrops?.[0] @@ -193,8 +193,8 @@ export const getTmdbPopularMovies = () => TmdbApiOpen.get('/3/movie/popular', { params: { query: { - language: get(settings).language, - region: get(settings).region + language: get(settings)?.language, + region: get(settings)?.region } } }).then((res) => res.data?.results || []); @@ -203,7 +203,7 @@ export const getTmdbPopularSeries = () => TmdbApiOpen.get('/3/tv/popular', { params: { query: { - language: get(settings).language + language: get(settings)?.language } } }).then((res) => res.data?.results || []); @@ -215,7 +215,7 @@ export const getTmdbTrendingAll = () => time_window: 'day' }, query: { - language: get(settings).language + language: get(settings)?.language } } }).then((res) => res.data?.results || []); diff --git a/src/lib/components/Button.svelte b/src/lib/components/Button.svelte index 7ef9676..6d924ed 100644 --- a/src/lib/components/Button.svelte +++ b/src/lib/components/Button.svelte @@ -19,7 +19,7 @@ 'bg-white text-zinc-900 font-extrabold backdrop-blur-lg rounded-xl': type === 'primary', 'hover:bg-amber-400 focus-within:bg-amber-400 hover:border-amber-400 focus-within:border-amber-400': type === 'primary' && !disabled, - 'text-zinc-200 bg-zinc-400 bg-opacity-20 backdrop-blur-lg rounded-xl': type === 'secondary', + 'text-zinc-200 bg-zinc-600 bg-opacity-20 backdrop-blur-lg rounded-xl': type === 'secondary', 'focus-visible:bg-zinc-200 focus-visible:text-zinc-800 hover:bg-zinc-200 hover:text-zinc-800': (type === 'secondary' || type === 'tertiary') && !disabled, 'rounded-full': type === 'tertiary', diff --git a/src/lib/components/ContextMenu/LibraryItemContextItems.svelte b/src/lib/components/ContextMenu/LibraryItemContextItems.svelte index a85d8ae..c4e8cad 100644 --- a/src/lib/components/ContextMenu/LibraryItemContextItems.svelte +++ b/src/lib/components/ContextMenu/LibraryItemContextItems.svelte @@ -1,7 +1,7 @@ @@ -59,7 +61,7 @@ - window.open(RADARR_BASE_URL + '/movie/' + $itemStore.item?.radarrMovie?.tmdbId)} + window.open($settings.radarr.baseUrl + '/movie/' + $itemStore.item?.radarrMovie?.tmdbId)} > Open in Radarr @@ -67,7 +69,9 @@ - window.open(SONARR_BASE_URL + '/series/' + $itemStore.item?.sonarrSeries?.titleSlug)} + window.open( + $settings.sonarr.baseUrl + '/series/' + $itemStore.item?.sonarrSeries?.titleSlug + )} > Open in Sonarr diff --git a/src/lib/components/SourceStats/RadarrStats.svelte b/src/lib/components/SourceStats/RadarrStats.svelte index 1652659..f9e1eb1 100644 --- a/src/lib/components/SourceStats/RadarrStats.svelte +++ b/src/lib/components/SourceStats/RadarrStats.svelte @@ -1,7 +1,7 @@ + + diff --git a/src/lib/components/forms/Input.svelte b/src/lib/components/forms/Input.svelte new file mode 100644 index 0000000..99f5d2e --- /dev/null +++ b/src/lib/components/forms/Input.svelte @@ -0,0 +1,23 @@ + + +
+ {#if type === 'text'} + + {:else if type === 'number'} + + {/if} +
diff --git a/src/lib/components/forms/Select.svelte b/src/lib/components/forms/Select.svelte new file mode 100644 index 0000000..929e662 --- /dev/null +++ b/src/lib/components/forms/Select.svelte @@ -0,0 +1,29 @@ + + +
+ +
+ +
+
diff --git a/src/lib/components/forms/Toggle.svelte b/src/lib/components/forms/Toggle.svelte new file mode 100644 index 0000000..6001af1 --- /dev/null +++ b/src/lib/components/forms/Toggle.svelte @@ -0,0 +1,11 @@ + + +