import "pkg:/source/api/sdk.bs"
import "pkg:/source/api/sdk.v1.bs"
import "pkg:/source/api/sdk.v2.bs"
import "pkg:/source/utils/misc.bs"
' Note: getApiVersionFromGlobal() is imported from misc.bs and serves as the
' single source of truth for API version detection across the entire app.
' ApiClient provides a centralized, stateful wrapper around the Jellyfin API.
' It automatically injects image parameters and version-specific fields for consistent,
' bulletproof item fetching across all Jellyfin server versions (10.7.0+).
'
' Usage:
' api = GetApi()
' data = api.GetItem(itemId, { fields: "Overview" })
'
' Or inline:
' data = GetApi().GetItem(itemId, { fields: "Overview" })
'
' The singleton pattern ensures one instance is shared across the entire app.
class ApiClient
' Cache global reference so m.global works inside class methods
private global = invalid
sub new()
m.global = GetGlobalAA().global
end sub
' Default image parameters for all item endpoints
' These ensure backdrop, logo, and thumb images are always requested
private imageDefaults = {
EnableImageTypes: "Primary,Backdrop,Logo,Thumb",
ImageTypeLimit: 1
}
' Get current user ID from global state
private function getUserId() as string
return m.global.user.id
end function
' Get API version from global state via helper
' Uses getApiVersionFromGlobal() as single source of truth for safe API version reading
private function getApiVersion() as integer
return getApiVersionFromGlobal()
end function
' ═══════════════════════════════════════════════════════════════════════════
' USER ENDPOINTS - Version-aware (V1/V2 routing)
' When adding V3 support, add else-if branch in each method below
' ═══════════════════════════════════════════════════════════════════════════
' Get a single item by ID with automatic image and version field injection
' @param itemId - The Jellyfin item ID
' @param params - Optional query parameters (fields, etc.)
' @returns API response or invalid on error
function GetItem(itemId as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetItem(userId, itemId, m.injectDefaults(params))
end if
return sdkV1.users.GetItem(userId, itemId, m.injectDefaults(params))
end function
' Get items by query with automatic image and version field injection
' @param params - Query parameters (limit, sortBy, filters, etc.)
' @returns API response or invalid on error
function GetItemsByQuery(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetItemsByQuery(userId, m.injectDefaults(params))
end if
return sdkV1.users.GetItemsByQuery(userId, m.injectDefaults(params))
end function
' Get resume items (Continue Watching) with automatic image injection
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetResumeItems(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetResumeItemsByQuery(userId, m.injectDefaults(params))
end if
return sdkV1.users.GetResumeItemsByQuery(userId, m.injectDefaults(params))
end function
' Get suggestions with automatic image injection
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetSuggestions(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetSuggestions(userId, m.injectDefaults(params))
end if
return sdkV1.users.GetSuggestions(userId, m.injectDefaults(params))
end function
' Get latest media with automatic image injection
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetLatestMedia(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetLatestMedia(userId, m.injectDefaults(params))
end if
return sdkV1.users.GetLatestMedia(userId, m.injectDefaults(params))
end function
' ═══════════════════════════════════════════════════════════════════════════
' RAW ACCESS - No image injection (for playback/internal use)
' ═══════════════════════════════════════════════════════════════════════════
' Get item without image parameters - use for playback info only
' @param itemId - The Jellyfin item ID
' @param params - Query parameters (passed through as-is)
' @returns API response or invalid on error
function GetItemRaw(itemId as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetItem(userId, itemId, params)
end if
return sdkV1.users.GetItem(userId, itemId, params)
end function
' Get items by query without image parameters
' @param params - Query parameters (passed through as-is)
' @returns API response or invalid on error
function GetItemsByQueryRaw(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetItemsByQuery(userId, params)
end if
return sdkV1.users.GetItemsByQuery(userId, params)
end function
' ═══════════════════════════════════════════════════════════════════════════
' SHOWS ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get seasons for a TV series with automatic image injection
' @param seriesId - The series ID
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetSeasons(seriesId as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = m.injectDefaults(params)
mergedParams.UserId = userId
return sdk.shows.GetSeasons(seriesId, mergedParams)
end function
' Get episodes for a TV season with automatic image injection
' @param seriesId - The series ID
' @param params - Optional query parameters (season number, etc.)
' @returns API response or invalid on error
function GetEpisodes(seriesId as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = m.injectDefaults(params)
mergedParams.UserId = userId
return sdk.shows.GetEpisodes(seriesId, mergedParams)
end function
' ═══════════════════════════════════════════════════════════════════════════
' ARTIST ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get artist by name
' @param name - Artist name
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetArtistByName(name as string, params = {} as object) as dynamic
return sdk.artists.GetByName(name, params)
end function
' Get all artists with automatic image injection
' @param params - Query parameters
' @returns API response or invalid on error
function GetArtists(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = m.injectDefaults(params)
mergedParams.UserId = userId
return sdk.artists.Get(mergedParams)
end function
' Get all album artists with automatic image injection
' @param params - Query parameters
' @returns API response or invalid on error
function GetAlbumArtists(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = m.injectDefaults(params)
mergedParams.UserId = userId
return sdk.artists.GetAlbumArtists(mergedParams)
end function
' ═══════════════════════════════════════════════════════════════════════════
' PLAYLIST ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get playlist items
' @param playlistId - The playlist ID
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetPlaylistItems(playlistId as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = {}
mergedParams.append(params)
mergedParams.UserId = userId
return sdk.playlists.GetItems(playlistId, mergedParams)
end function
' ═══════════════════════════════════════════════════════════════════════════
' ITEMS ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get instant mix based on item
' @param itemId - The item ID
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetInstantMix(itemId as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = {}
mergedParams.append(params)
mergedParams.UserId = userId
return sdk.items.GetInstantMix(itemId, mergedParams)
end function
' Get intros for an item
' @param itemId - The item ID
' @returns API response or invalid on error
function GetIntros(itemId as string) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetIntros(userId, itemId)
end if
return sdkV1.users.GetIntros(userId, itemId)
end function
' ═══════════════════════════════════════════════════════════════════════════
' THIN WRAPPERS - Pass-through to SDK for consistent UX
' These don't modify params but provide consistent api.* interface
' ═══════════════════════════════════════════════════════════════════════════
' Post playback info (used for starting playback)
' @param itemId - The item ID
' @param postData - Request body data
' @returns API response or invalid on error
function PostPlaybackInfo(itemId as string, postData as object) as dynamic
return sdk.items.PostPlayBackInfo(itemId, postData)
end function
' Get similar artists
' @param id - Artist ID
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetSimilarArtists(id as string, params = {} as object) as dynamic
return sdk.artists.GetSimilar(id, params)
end function
' Get similar albums
' @param id - Album ID
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetSimilarAlbums(id as string, params = {} as object) as dynamic
return sdk.albums.GetSimilar(id, params)
end function
' Get album instant mix
' @param id - Album ID
' @param params - Optional query parameters
' @returns API response or invalid on error
function GetAlbumInstantMix(id as string, params = {} as object) as dynamic
return sdk.albums.GetInstantMix(id, params)
end function
' Get local trailers for an item
' @param itemId - The item ID
' @returns API response or invalid on error
function GetLocalTrailers(itemId as string) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetLocalTrailers(userId, itemId)
end if
return sdkV1.users.GetLocalTrailers(userId, itemId)
end function
' Get special features for an item
' @param itemId - The item ID
' @returns API response or invalid on error
function GetSpecialFeatures(itemId as string) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetSpecialFeatures(userId, itemId)
end if
return sdkV1.users.GetSpecialFeatures(userId, itemId)
end function
' Mark item as favorite
' @param itemId - The item ID
' @returns API response or invalid on error
function MarkFavorite(itemId as string) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.MarkFavorite(userId, itemId)
end if
return sdkV1.users.MarkFavorite(userId, itemId)
end function
' Unmark item as favorite
' @param itemId - The item ID
' @returns API response or invalid on error
function UnmarkFavorite(itemId as string) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.UnmarkFavorite(userId, itemId)
end if
return sdkV1.users.UnmarkFavorite(userId, itemId)
end function
' Mark item as played
' @param itemId - The item ID
' @param params - Optional parameters (DatePlayed, etc.)
' @returns API response or invalid on error
function MarkPlayed(itemId as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.MarkPlayed(userId, itemId, params)
end if
return sdkV1.users.MarkPlayed(userId, itemId, params)
end function
' Mark item as unplayed
' @param itemId - The item ID
' @returns API response or invalid on error
function UnmarkPlayed(itemId as string) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.UnmarkPlayed(userId, itemId)
end if
return sdkV1.users.UnmarkPlayed(userId, itemId)
end function
' Update user rating
' @param itemId - The item ID
' @param params - Rating parameters
' @returns API response or invalid on error
function UpdateRating(itemId as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.UpdateRating(userId, itemId, params)
end if
return sdkV1.users.UpdateRating(userId, itemId, params)
end function
' Delete user rating
' @param itemId - The item ID
' @returns API response or invalid on error
function DeleteRating(itemId as string) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.DeleteRating(userId, itemId)
end if
return sdkV1.users.DeleteRating(userId, itemId)
end function
' Delete an item
' @param itemId - The item ID to delete
' @returns API response or invalid on error
function DeleteItem(itemId as string) as dynamic
return sdk.items.DeleteByID(itemId)
end function
' ═══════════════════════════════════════════════════════════════════════════
' SESSION ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Report playback started
' @param params - Playback parameters
' @returns API response or invalid on error
function Playing(params as object) as dynamic
return sdk.sessions.Playing(params)
end function
' Report playback stopped
' @param params - Playback parameters
' @returns API response or invalid on error
function PostStopped(params as object) as dynamic
return sdk.sessions.PostStopped(params)
end function
' Report playback progress
' @param params - Playback parameters
' @returns API response or invalid on error
function PostProgress(params as object) as dynamic
return sdk.sessions.PostProgress(params)
end function
' Get active sessions
' @param params - Query parameters
' @returns API response or invalid on error
function GetSessions(params = {} as object) as dynamic
return sdk.sessions.Get(params)
end function
' ═══════════════════════════════════════════════════════════════════════════
' SYSTEM ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get configuration by name
' @param name - Configuration name
' @returns API response or invalid on error
function GetConfigurationByName(name as string) as dynamic
return sdk.system.GetConfigurationByName(name)
end function
' ═══════════════════════════════════════════════════════════════════════════
' BRANDING ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get branding configuration
' @returns API response or invalid on error
function GetBrandingConfiguration() as dynamic
return sdk.branding.GetConfiguration()
end function
' ═══════════════════════════════════════════════════════════════════════════
' DISPLAY PREFERENCES ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get display preferences
' @param id - Preference ID
' @param params - Query parameters
' @returns API response or invalid on error
function GetDisplayPreferences(id as string, params = {} as object) as dynamic
return sdk.displayPreferences.Get(id, params)
end function
' ═══════════════════════════════════════════════════════════════════════════
' AUTHENTICATION ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Authenticate user by username and password
' @param username - User's username
' @param password - User's password
' @returns API response with auth token or invalid on error
function AuthenticateByName(username as string, password as string) as dynamic
return sdk.users.AuthenticateByName(username, password)
end function
' Get user by ID
' @param userId - The user ID
' @returns API response or invalid on error
function GetUser(userId as string) as dynamic
return sdk.users.Get(userId)
end function
' Get public users
' @returns API response or invalid on error
function GetPublicUsers() as dynamic
return sdk.users.GetPublic()
end function
' Authenticate via Quick Connect
' @param secret - The Quick Connect secret
' @returns API response or invalid on error
function AuthenticateWithQuickConnect(secret as string) as dynamic
return sdk.users.AuthenticateWithQuickConnect(secret)
end function
' ═══════════════════════════════════════════════════════════════════════════
' QUICK CONNECT ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Initiate Quick Connect
' @returns API response with secret or invalid on error
function InitiateQuickConnect() as dynamic
return sdk.quickConnect.Initiate()
end function
' Connect via Quick Connect
' @param secret - The Quick Connect secret
' @returns API response or invalid on error
function ConnectQuickConnect(secret as string) as dynamic
return sdk.quickConnect.Connect(secret)
end function
' ═══════════════════════════════════════════════════════════════════════════
' VIEWS ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get user views
' @returns API response or invalid on error
function GetViews() as dynamic
userId = m.getUserId()
if userId = "" then return invalid
if m.getApiVersion() >= 2
return sdkV2.users.GetViews(userId)
end if
return sdkV1.users.GetViews(userId)
end function
' ═══════════════════════════════════════════════════════════════════════════
' NEXT UP ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get next up episodes with automatic image injection
' @param params - Query parameters
' @returns API response or invalid on error
function GetNextUp(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = m.injectDefaults(params)
mergedParams.UserId = userId
return sdk.shows.GetNextUp(mergedParams)
end function
' Get upcoming episodes with automatic image injection
' @param params - Query parameters
' @returns API response or invalid on error
function GetUpcoming(params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = m.injectDefaults(params)
mergedParams.UserId = userId
return sdk.shows.GetUpcoming(mergedParams)
end function
' Get similar shows with automatic image injection
' @param id - Show ID
' @param params - Query parameters
' @returns API response or invalid on error
function GetSimilarShows(id as string, params = {} as object) as dynamic
userId = m.getUserId()
if userId = "" then return invalid
mergedParams = m.injectDefaults(params)
mergedParams.UserId = userId
return sdk.shows.GetSimilar(id, mergedParams)
end function
' ═══════════════════════════════════════════════════════════════════════════
' VIDEOS ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get additional parts for a video
' @param itemId - The video item ID
' @returns API response or invalid on error
function GetAdditionalParts(itemId as string) as dynamic
return sdk.videos.GetAdditionalParts(itemId)
end function
' ═══════════════════════════════════════════════════════════════════════════
' FILTERS ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get filters
' @param params - Query parameters
' @returns API response or invalid on error
function GetFilters(params = {} as object) as dynamic
return sdk.items.GetFilters(params)
end function
' Get items by query (alternative endpoint)
' @param params - Query parameters
' @returns API response or invalid on error
function GetByQuery(params = {} as object) as dynamic
return sdk.items.GetByQuery(params)
end function
' ═══════════════════════════════════════════════════════════════════════════
' IMAGE ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get image URL for an item by ID (HEAD request helper)
' @param id - Item ID
' @param imageType - Type of image
' @param imageIndex - Image index (default 0)
' @param params - Optional parameters
' @returns API response or invalid on error
function HeadImageURLByName(id as string, imageType as string, imageIndex = 0 as integer, params = {} as object) as dynamic
return sdk.items.HeadImageURLByName(id, imageType, imageIndex, params)
end function
' Get image URL
' @param id - Item ID
' @param imageType - Type of image
' @param imageIndex - Image index
' @param params - Optional parameters
' @returns API response or invalid on error
function GetImageURL(id as string, imageType as string, imageIndex = 0 as integer, params = {} as object) as dynamic
return sdk.items.GetImageURL(id, imageType, imageIndex, params)
end function
' Get user image URL
' @param id - User ID
' @param imageType - Type of image
' @param imageIndex - Image index
' @param params - Optional parameters
' @returns API response or invalid on error
function GetUserImageURL(id as string, imageType as string, imageIndex = 0 as integer, params = {} as object) as dynamic
if m.getApiVersion() >= 2
return sdkV2.users.GetImageURL(id, imageType, imageIndex, params)
end if
return sdkV1.users.GetImageURL(id, imageType, imageIndex, params)
end function
' ═══════════════════════════════════════════════════════════════════════════
' LIVETV ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get Live TV channels
' @param params - Query parameters
' @returns API response or invalid on error
function GetLiveTVChannels(params = {} as object) as dynamic
return sdk.liveTV.GetChannels(params)
end function
' ═══════════════════════════════════════════════════════════════════════════
' STUDIOS ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get studios
' @param params - Query parameters
' @returns API response or invalid on error
function GetStudios(params = {} as object) as dynamic
return sdk.studios.Get(params)
end function
' ═══════════════════════════════════════════════════════════════════════════
' GENRES ENDPOINTS
' ═══════════════════════════════════════════════════════════════════════════
' Get genres
' @param params - Query parameters
' @returns API response or invalid on error
function GetGenres(params = {} as object) as dynamic
return sdk.genres.Get(params)
end function
' ═══════════════════════════════════════════════════════════════════════════
' PRIVATE HELPERS
' ═══════════════════════════════════════════════════════════════════════════
' Inject default image parameters and version-specific fields into params
' Delegates to injectApiParams() pure function for testability
' @param params - Original parameters object
' @returns New params object with defaults merged in
private function injectDefaults(params as object) as object
return injectApiParams(params, m.getApiVersion(), m.imageDefaults)
end function
end class
' ═══════════════════════════════════════════════════════════════════════════
' SINGLETON FACTORY
' ═══════════════════════════════════════════════════════════════════════════
' Get the singleton ApiClient instance
' Creates the instance on first call, returns cached instance thereafter
' Usage: api = GetApi() or data = GetApi().GetItem(id)
' @returns ApiClient singleton instance
function GetApi() as ApiClient
globalAA = GetGlobalAA()
if not isValid(globalAA.apiClient)
globalAA.apiClient = new ApiClient()
end if
return globalAA.apiClient
end function