Files
shelfless/src/types/abs.ts
Scarriffle 83d8b7b99d Initial commit: Shelfless – alternative Audiobookshelf frontend
React + Vite + TypeScript SPA covering the full ABS feature set (library
browsing, item detail, metadata/cover editing, podcasts, player with session
sync, admin: users/libraries/scanner/server settings). Dev uses a dynamic
CORS proxy; production is served by server/index.mjs (static + reverse proxy
to ABS_URL). Includes systemd unit and installer under deploy/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 20:23:04 +02:00

335 lines
7.9 KiB
TypeScript

/**
* 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<string, unknown>
source?: string
}
export interface AuthorizeResponse {
user: AbsUser
userDefaultLibraryId?: string
}
export function isAdminUser(user: Pick<AbsUser, 'type'> | 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
}