diff --git a/.github/workflows/snap.yaml b/.github/workflows/snap.yaml index c42ce71e..e3e6d819 100644 --- a/.github/workflows/snap.yaml +++ b/.github/workflows/snap.yaml @@ -2,7 +2,7 @@ name: Publish Snap # turn off edge snap builds temporarily and make it manual -# on: +# on: # push: # branches: # - develop diff --git a/server/api/jellyfin.ts b/server/api/jellyfin.ts index 163fbcdc..b14b8fea 100644 --- a/server/api/jellyfin.ts +++ b/server/api/jellyfin.ts @@ -1,10 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import ExternalAPI from '@server/api/externalapi'; import { ApiErrorCode } from '@server/constants/error'; import availabilitySync from '@server/lib/availabilitySync'; import logger from '@server/logger'; import { ApiError } from '@server/types/error'; -import type { AxiosInstance } from 'axios'; -import axios from 'axios'; +import { getAppVersion } from '@server/utils/appVersion'; export interface JellyfinUserResponse { Name: string; @@ -92,32 +92,33 @@ export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem { DateCreated?: string; } -class JellyfinAPI { +class JellyfinAPI extends ExternalAPI { private authToken?: string; private userId?: string; private jellyfinHost: string; - private axios: AxiosInstance; - constructor(hostname: string, authToken?: string, deviceId?: string) { - this.jellyfinHost = hostname; - - this.authToken = authToken; - - let authHeaderVal = ''; - if (this.authToken) { - authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="10.8.0", Token="${authToken}"`; + constructor(jellyfinHost: string, authToken?: string, deviceId?: string) { + let authHeaderVal: string; + if (authToken) { + authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="${getAppVersion()}", Token="${authToken}"`; } else { - authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="10.8.0"`; + authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="${getAppVersion()}"`; } - this.axios = axios.create({ - baseURL: this.jellyfinHost, - headers: { - 'X-Emby-Authorization': authHeaderVal, - 'Content-Type': 'application/json', - Accept: 'application/json', - }, - }); + super( + jellyfinHost, + {}, + { + headers: { + 'X-Emby-Authorization': authHeaderVal, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + } + ); + + this.jellyfinHost = jellyfinHost; + this.authToken = authToken; } public async login( @@ -131,7 +132,8 @@ class JellyfinAPI { 'X-Forwarded-For': ClientIP, } : {}; - const account = await this.axios.post( + + const authResponse = await this.post( '/Users/AuthenticateByName', { Username: Username, @@ -142,7 +144,7 @@ class JellyfinAPI { } ); - return account.data; + return authResponse; } catch (e) { const status = e.response?.status; @@ -178,79 +180,82 @@ class JellyfinAPI { public async getSystemInfo(): Promise { try { - // TODO: remove axios from here - const systemInfoResponse = await this.axios.get('/System/Info'); + const systemInfoResponse = await this.get('/System/Info'); return systemInfoResponse; } catch (e) { - //TODO: Use the api error codes - throw new Error('Invalid auth token'); + throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken); } } public async getServerName(): Promise { try { - const account = await this.axios.get( - "/System/Info/Public'}" + const serverResponse = await this.get( + '/System/Info/Public' ); - return account.data.ServerName; + + return serverResponse.ServerName; } catch (e) { logger.error( `Something went wrong while getting the server name from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); - throw new Error('girl idk'); + + throw new ApiError(e.response?.status, ApiErrorCode.Unknown); } } public async getUsers(): Promise { try { - const account = await this.axios.get(`/Users`); - return { users: account.data }; + const userReponse = await this.get(`/Users`); + + return { users: userReponse }; } catch (e) { logger.error( `Something went wrong while getting the account from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); - throw new Error('Invalid auth token'); + + throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken); } } public async getUser(): Promise { try { - const account = await this.axios.get( + const userReponse = await this.get( `/Users/${this.userId ?? 'Me'}` ); - return account.data; + return userReponse; } catch (e) { logger.error( `Something went wrong while getting the account from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); - throw new Error('Invalid auth token'); + + throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken); } } public async getLibraries(): Promise { try { - console.log('getting libraries with url', this.jellyfinHost); - const mediaFolders = await this.axios.get(`/Library/MediaFolders`); + const mediaFolderResponse = await this.get(`/Library/MediaFolders`); - return this.mapLibraries(mediaFolders.data.Items); - } catch (mediaFoldersError) { + return this.mapLibraries(mediaFolderResponse.Items); + } catch (mediaFoldersResponseError) { // fallback to user views to get libraries - // this only affects LDAP users + // this only and maybe/depending on factors affects LDAP users try { - const mediaFolders = await this.axios.get( + const mediaFolderResponse = await this.get( `/Users/${this.userId ?? 'Me'}/Views` ); - return this.mapLibraries(mediaFolders.data.Items); + return this.mapLibraries(mediaFolderResponse.Items); } catch (e) { logger.error( `Something went wrong while getting libraries from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); + return []; } } @@ -284,11 +289,11 @@ class JellyfinAPI { public async getLibraryContents(id: string): Promise { try { - const contents = await this.axios.get( + const libraryItemsResponse = await this.get( `/Users/${this.userId}/Items?SortBy=SortName&SortOrder=Ascending&IncludeItemTypes=Series,Movie,Others&Recursive=true&StartIndex=0&ParentId=${id}&collapseBoxSetItems=false` ); - return contents.data.Items.filter( + return libraryItemsResponse.Items.filter( (item: JellyfinLibraryItem) => item.LocationType !== 'Virtual' ); } catch (e) { @@ -296,23 +301,25 @@ class JellyfinAPI { `Something went wrong while getting library content from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); - throw new Error('Invalid auth token'); + + throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken); } } public async getRecentlyAdded(id: string): Promise { try { - const contents = await this.axios.get( + const itemResponse = await this.get( `/Users/${this.userId}/Items/Latest?Limit=12&ParentId=${id}` ); - return contents.data; + return itemResponse; } catch (e) { logger.error( `Something went wrong while getting library content from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); - throw new Error('Invalid auth token'); + + throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken); } } @@ -320,36 +327,38 @@ class JellyfinAPI { id: string ): Promise { try { - const contents = await this.axios.get( + const itemResponse = await this.get( `/Users/${this.userId}/Items/${id}` ); - return contents.data; + return itemResponse; } catch (e) { if (availabilitySync.running) { if (e.response && e.response.status === 500) { return undefined; } } + logger.error( `Something went wrong while getting library content from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); - throw new Error('Invalid auth token'); + throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken); } } public async getSeasons(seriesID: string): Promise { try { - const contents = await this.axios.get(`/Shows/${seriesID}/Seasons`); + const seasonResponse = await this.get(`/Shows/${seriesID}/Seasons`); - return contents.data.Items; + return seasonResponse.Items; } catch (e) { logger.error( `Something went wrong while getting the list of seasons from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); - throw new Error('Invalid auth token'); + + throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken); } } @@ -358,11 +367,11 @@ class JellyfinAPI { seasonID: string ): Promise { try { - const contents = await this.axios.get( + const episodeResponse = await this.get( `/Shows/${seriesID}/Episodes?seasonId=${seasonID}` ); - return contents.data.Items.filter( + return episodeResponse.Items.filter( (item: JellyfinLibraryItem) => item.LocationType !== 'Virtual' ); } catch (e) { @@ -370,7 +379,8 @@ class JellyfinAPI { `Something went wrong while getting the list of episodes from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); - throw new Error('Invalid auth token'); + + throw new ApiError(e.response?.status, ApiErrorCode.InvalidAuthToken); } } } diff --git a/server/constants/error.ts b/server/constants/error.ts index 2f06d52a..ac18c3ec 100644 --- a/server/constants/error.ts +++ b/server/constants/error.ts @@ -1,8 +1,9 @@ export enum ApiErrorCode { InvalidUrl = 'INVALID_URL', InvalidCredentials = 'INVALID_CREDENTIALS', + InvalidAuthToken = 'INVALID_AUTH_TOKEN', NotAdmin = 'NOT_ADMIN', SyncErrorGroupedFolders = 'SYNC_ERROR_GROUPED_FOLDERS', SyncErrorNoLibraries = 'SYNC_ERROR_NO_LIBRARIES', - Generic = 'GENERIC', + Unknown = 'UNKNOWN', } diff --git a/server/routes/auth.ts b/server/routes/auth.ts index dca58244..4c398060 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -321,11 +321,14 @@ authRoutes.post('/jellyfin', async (req, res, next) => { userType: UserType.JELLYFIN, }); + const serverName = await jellyfinserver.getServerName(); + + settings.jellyfin.name = serverName; + settings.jellyfin.serverId = account.User.ServerId; settings.jellyfin.ip = body.hostname ?? ''; settings.jellyfin.port = body.port ?? 8096; settings.jellyfin.urlBase = body.urlBase ?? ''; settings.jellyfin.useSsl = body.useSsl ?? false; - settings.jellyfin.serverId = account.User.ServerId; settings.save(); startJobs();