From 4ef5a3c7c57240cbbe3a94ce007c8fe04100fc36 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 25 May 2024 06:10:19 +0500 Subject: [PATCH 1/3] style: ran prettier on snap yaml file (#774) --- .github/workflows/snap.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 650c339d74d4fe85ef7f76184901e86f4eeada85 Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sat, 25 May 2024 15:44:36 +0500 Subject: [PATCH 2/3] fix(jellyfinapi): use external api class for jellyfin api requests (#762) * refactor(jellyfinapi): use the external api class for jellyfin api requests refactors jellyfin api requests to be handled by the external api to be consistent with how other external api requests are made related #728, related #387 * style: prettier formatted * refactor(jellyfinapi): rename device in auth header as jellyseerr * refactor(error): rename api error code generic to unknown * refactor(errorcodes): consistent casing of error code enums --- server/api/jellyfin.ts | 118 +++++++++++++++++++++----------------- server/constants/error.ts | 2 + 2 files changed, 68 insertions(+), 52 deletions(-) diff --git a/server/api/jellyfin.ts b/server/api/jellyfin.ts index bab7ea72..f23e9ace 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,31 +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(jellyfinHost: string, authToken?: string, deviceId?: string) { - this.jellyfinHost = jellyfinHost; - this.authToken = authToken; - - let authHeaderVal = ''; - if (this.authToken) { - authHeaderVal = `MediaBrowser Client="Jellyseerr", Device="Jellyseerr", DeviceId="${deviceId}", Version="10.8.0", Token="${authToken}"`; + 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( @@ -130,7 +132,8 @@ class JellyfinAPI { 'X-Forwarded-For': ClientIP, } : {}; - const account = await this.axios.post( + + const authResponse = await this.post( '/Users/AuthenticateByName', { Username: Username, @@ -141,7 +144,7 @@ class JellyfinAPI { } ); - return account.data; + return authResponse; } catch (e) { const status = e.response?.status; @@ -177,66 +180,72 @@ class JellyfinAPI { 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 { - 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 []; } } @@ -270,11 +279,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) { @@ -282,23 +291,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); } } @@ -306,36 +317,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); } } @@ -344,11 +357,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) { @@ -356,7 +369,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 87e37e4c..22b9ad60 100644 --- a/server/constants/error.ts +++ b/server/constants/error.ts @@ -1,5 +1,7 @@ export enum ApiErrorCode { InvalidUrl = 'INVALID_URL', InvalidCredentials = 'INVALID_CREDENTIALS', + InvalidAuthToken = 'INVALID_AUTH_TOKEN', NotAdmin = 'NOT_ADMIN', + Unknown = 'UNKNOWN', } From 7a5e8d69bf620c8e7bf5f284840b1a5fe757ae5f Mon Sep 17 00:00:00 2001 From: Fallenbagel <98979876+Fallenbagel@users.noreply.github.com> Date: Sun, 26 May 2024 18:21:14 +0500 Subject: [PATCH 3/3] feat(settings): stores jellyfin/emby server name in the settings (#763) Stores jellyfin/emby(?) server name in the settings file. This might come in handy in the future once simultaneous multi-server sync is implemented. --- server/routes/auth.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/routes/auth.ts b/server/routes/auth.ts index c0f789a1..f64d5c12 100644 --- a/server/routes/auth.ts +++ b/server/routes/auth.ts @@ -314,6 +314,9 @@ authRoutes.post('/jellyfin', async (req, res, next) => { userType: UserType.JELLYFIN, }); + const serverName = await jellyfinserver.getServerName(); + + settings.jellyfin.name = serverName; settings.jellyfin.hostname = body.hostname ?? ''; settings.jellyfin.serverId = account.User.ServerId; settings.save();