/* eslint-disable @typescript-eslint/no-explicit-any */ import logger from '@server/logger'; import type { AxiosInstance } from 'axios'; import axios from 'axios'; export interface JellyfinUserResponse { Name: string; ServerId: string; ServerName: string; Id: string; PrimaryImageTag?: string; } export interface JellyfinLoginResponse { User: JellyfinUserResponse; AccessToken: string; } export interface JellyfinUserListResponse { users: JellyfinUserResponse[]; } export interface JellyfinLibrary { type: 'show' | 'movie'; key: string; title: string; agent: string; } export interface JellyfinLibraryItem { Name: string; Id: string; HasSubtitles: boolean; Type: 'Movie' | 'Episode' | 'Season' | 'Series'; LocationType: 'FileSystem' | 'Offline' | 'Remote' | 'Virtual'; SeriesName?: string; SeriesId?: string; SeasonId?: string; SeasonName?: string; IndexNumber?: number; IndexNumberEnd?: number; ParentIndexNumber?: number; MediaType: string; } export interface JellyfinMediaStream { Codec: string; Type: 'Video' | 'Audio' | 'Subtitle'; Height?: number; Width?: number; AverageFrameRate?: number; RealFrameRate?: number; Language?: string; DisplayTitle: string; } export interface JellyfinMediaSource { Protocol: string; Id: string; Path: string; Type: string; VideoType: string; MediaStreams: JellyfinMediaStream[]; } export interface JellyfinLibraryItemExtended extends JellyfinLibraryItem { ProviderIds: { Tmdb?: string; Imdb?: string; Tvdb?: string; }; MediaSources?: JellyfinMediaSource[]; Width?: number; Height?: number; IsHD?: boolean; DateCreated?: string; } class JellyfinAPI { 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="Overseerr", Device="Axios", DeviceId="${deviceId}", Version="10.8.0", Token="${authToken}"`; } else { authHeaderVal = `MediaBrowser Client="Overseerr", Device="Axios", DeviceId="${deviceId}", Version="10.8.0"`; } this.axios = axios.create({ baseURL: this.jellyfinHost, headers: { 'X-Emby-Authorization': authHeaderVal, 'Content-Type': 'application/json', Accept: 'application/json', }, }); } public async login( Username?: string, Password?: string ): Promise { try { const account = await this.axios.post( '/Users/AuthenticateByName', { Username: Username, Pw: Password, } ); return account.data; } catch (e) { throw new Error('Unauthorized'); } } public setUserId(userId: string): void { this.userId = userId; return; } public async getServerName(): Promise { try { const account = await this.axios.get( "/System/Info/Public'}" ); return account.data.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'); } } public async getUsers(): Promise { try { const account = await this.axios.get(`/Users`); return { users: account.data }; } 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'); } } public async getUser(): Promise { try { const account = await this.axios.get( `/Users/${this.userId ?? 'Me'}` ); return account.data; } 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'); } } public async getLibraries(): Promise { try { // TODO: Try to fix automatic grouping without fucking up LDAP users // const libraries = await this.axios.get('/Library/VirtualFolders'); const account = await this.axios.get( `/Users/${this.userId ?? 'Me'}/Views` ); const response: JellyfinLibrary[] = account.data.Items.filter( (Item: any) => { return ( Item.Type === 'CollectionFolder' && Item.CollectionType !== 'music' && Item.CollectionType !== 'books' && Item.CollectionType !== 'musicvideos' && Item.CollectionType !== 'homevideos' ); } ).map((Item: any) => { return { key: Item.Id, title: Item.Name, type: Item.CollectionType === 'movies' ? 'movie' : 'show', agent: 'jellyfin', }; }); return response; } catch (e) { logger.error( `Something went wrong while getting libraries from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); return []; } } public async getLibraryContents(id: string): Promise { try { const contents = await this.axios.get( `/Users/${this.userId}/Items?SortBy=SortName&SortOrder=Ascending&IncludeItemTypes=Series,Movie,Others&Recursive=true&StartIndex=0&ParentId=${id}` ); return contents.data.Items.filter( (item: JellyfinLibraryItem) => item.LocationType !== 'Virtual' ); } 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'); } } public async getRecentlyAdded(id: string): Promise { try { const contents = await this.axios.get( `/Users/${this.userId}/Items/Latest?Limit=12&ParentId=${id}` ); return contents.data; } 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'); } } public async getItemData(id: string): Promise { try { const contents = await this.axios.get( `/Users/${this.userId}/Items/${id}` ); return contents.data; } 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'); } } public async getSeasons(seriesID: string): Promise { try { const contents = await this.axios.get(`/Shows/${seriesID}/Seasons`); return contents.data.Items.filter( (item: JellyfinLibraryItem) => item.LocationType !== 'Virtual' ); } 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'); } } public async getEpisodes( seriesID: string, seasonID: string ): Promise { try { const contents = await this.axios.get( `/Shows/${seriesID}/Episodes?seasonId=${seasonID}` ); return contents.data.Items.filter( (item: JellyfinLibraryItem) => item.LocationType !== 'Virtual' ); } catch (e) { logger.error( `Something went wrong while getting the list of episodes from the Jellyfin server: ${e.message}`, { label: 'Jellyfin API' } ); throw new Error('Invalid auth token'); } } } export default JellyfinAPI;