/** * Audiobookshelf API types. Only fields Shelfless uses are typed; unknown extras are * tolerated. See memory: abs-api-corrections. * * ABS returns two shapes for media: * - "minified" (library item lists): metadata uses flattened strings * (authorName, narratorName, seriesName). * - "expanded" (single item with ?expanded=1): metadata uses arrays of objects * (authors[], narrators[], series[]). * We model both with optional fields and read defensively via helpers in lib/media. */ // ── Auth ────────────────────────────────────────────────────────────────── export type UserType = 'root' | 'admin' | 'user' | 'guest' export interface UserPermissions { download: boolean update: boolean delete: boolean upload: boolean accessAllLibraries: boolean accessAllTags: boolean accessExplicitContent: boolean [key: string]: boolean | undefined } export interface AbsUser { id: string username: string email?: string | null type: UserType token: string isActive?: boolean isLocked?: boolean lastSeen?: number | null createdAt?: number permissions: UserPermissions librariesAccessible?: string[] itemTagsAccessible?: string[] } export interface LoginResponse { user: AbsUser userDefaultLibraryId?: string serverSettings?: Record source?: string } export interface AuthorizeResponse { user: AbsUser userDefaultLibraryId?: string } export function isAdminUser(user: Pick | null | undefined): boolean { return user?.type === 'root' || user?.type === 'admin' } // ── Libraries ─────────────────────────────────────────────────────────────── export type MediaType = 'book' | 'podcast' export interface LibraryFolder { id: string fullPath: string libraryId?: string addedAt?: number } export interface LibrarySettings { coverAspectRatio?: number disableWatcher?: boolean autoScanCronExpression?: string | null [key: string]: unknown } export interface Library { id: string name: string folders: LibraryFolder[] displayOrder: number icon: string mediaType: MediaType provider: string settings?: LibrarySettings createdAt: number lastUpdate: number } export interface LibrariesResponse { libraries: Library[] } export interface LibraryStats { totalItems?: number totalAuthors?: number totalGenres?: number totalDuration?: number totalSize?: number numAudioTracks?: number [key: string]: unknown } // ── Metadata / Media ───────────────────────────────────────────────────────── export interface AuthorRef { id?: string name: string } export interface SeriesRef { id?: string name: string sequence?: string | null } export interface BookMetadata { title: string | null titleIgnorePrefix?: string subtitle?: string | null // expanded authors?: AuthorRef[] narrators?: string[] series?: SeriesRef[] // minified authorName?: string narratorName?: string seriesName?: string genres?: string[] publishedYear?: string | null publishedDate?: string | null publisher?: string | null description?: string | null isbn?: string | null asin?: string | null language?: string | null explicit?: boolean } export interface Chapter { id: number start: number end: number title: string } export interface AudioFileMeta { filename: string ext: string path: string relPath?: string size: number } export interface AudioFile { index: number ino: string metadata: AudioFileMeta duration: number bitRate?: number codec?: string channels?: number mimeType?: string } export interface Book { id?: string libraryItemId?: string metadata: BookMetadata coverPath: string | null tags?: string[] audioFiles?: AudioFile[] chapters?: Chapter[] duration?: number size?: number numTracks?: number numChapters?: number numAudioFiles?: number ebookFormat?: string | null } export interface PodcastMetadata { title: string | null author?: string | null description?: string | null releaseDate?: string | null genres?: string[] feedUrl?: string | null imageUrl?: string | null itunesId?: string | null itunesArtistId?: string | null language?: string | null explicit?: boolean type?: string | null } export interface PodcastEpisode { id: string index?: number episode?: string | null season?: string | null title: string subtitle?: string | null description?: string | null pubDate?: string | null publishedAt?: number | null audioFile?: AudioFile duration?: number size?: number } export interface Podcast { id?: string libraryItemId?: string metadata: PodcastMetadata coverPath: string | null tags?: string[] episodes?: PodcastEpisode[] numEpisodes?: number autoDownloadEpisodes?: boolean } export type Media = Book | Podcast // ── Library items ───────────────────────────────────────────────────────── export interface LibraryFile { ino: string metadata: AudioFileMeta fileType?: string addedAt?: number updatedAt?: number } export interface LibraryItem { id: string ino?: string libraryId: string folderId?: string path?: string relPath?: string isFile?: boolean mtimeMs?: number ctimeMs?: number birthtimeMs?: number addedAt: number updatedAt: number isMissing?: boolean isInvalid?: boolean mediaType: MediaType media: Media libraryFiles?: LibraryFile[] numFiles?: number size?: number // present on some list/expanded responses collapsedSeries?: unknown recentEpisode?: PodcastEpisode progress?: MediaProgress } export interface LibraryItemsResponse { results: LibraryItem[] total: number limit: number page: number sortBy?: string sortDesc?: boolean mediaType?: MediaType minified?: boolean } export interface LibrarySearchResult { book?: { libraryItem: LibraryItem; matchKey?: string; matchText?: string }[] podcast?: { libraryItem: LibraryItem }[] authors?: { id: string; name: string }[] series?: { series: SeriesRef; books: LibraryItem[] }[] tags?: string[] } // ── Progress & sessions ───────────────────────────────────────────────────── export interface MediaProgress { id: string libraryItemId: string episodeId?: string | null duration: number progress: number // 0..1 currentTime: number isFinished: boolean hideFromContinueListening?: boolean lastUpdate?: number startedAt?: number finishedAt?: number | null } export interface ItemsInProgressResponse { libraryItems: LibraryItem[] } export interface AudioTrack { index: number startOffset: number duration: number title?: string contentUrl: string mimeType: string codec?: string metadata?: AudioFileMeta } export interface PlaybackSession { id: string userId: string libraryId?: string libraryItemId: string episodeId?: string | null mediaType: MediaType displayTitle?: string displayAuthor?: string coverPath?: string | null duration: number playMethod?: number mediaPlayer?: string audioTracks: AudioTrack[] chapters?: Chapter[] currentTime: number startedAt?: number updatedAt?: number } // ── Server / admin ────────────────────────────────────────────────────────── export interface BackupInfo { id: string backupMetadataCovers?: boolean fileName: string path?: string serverVersion?: string createdAt: number size?: number }