import logger from '@server/logger'; import ServarrBase from './base'; export interface ReadarrBookOptions { title: string; qualityProfileId: number; metadataProfileId: number; tags: number[]; rootFolderPath: string; foreignBookId: string; // GoodReads/Edition ID authorId?: number; monitored?: boolean; searchNow?: boolean; } export interface ReadarrAuthor { id: number; authorName: string; foreignAuthorId: string; monitored: boolean; path: string; qualityProfileId: number; metadataProfileId: number; rootFolderPath: string; tags: number[]; added: string; status: string; ended: boolean; images: { coverType: string; url: string; remoteUrl: string; }[]; genres: string[]; statistics?: { bookFileCount: number; bookCount: number; totalBookCount: number; sizeOnDisk: number; percentOfBooks: number; }; } export interface ReadarrBook { id: number; title: string; foreignBookId: string; authorId: number; monitored: boolean; releaseDate: string; genres: string[]; images: { coverType: string; url: string; remoteUrl: string; }[]; author: ReadarrAuthor; overview: string; pageCount: number; statistics?: { bookFileCount: number; sizeOnDisk: number; }; } export interface ReadarrMetadataProfile { id: number; name: string; } class ReadarrAPI extends ServarrBase<{ authorId: number }> { constructor({ url, apiKey }: { url: string; apiKey: string }) { super({ url, apiKey, cacheName: 'readarr', apiName: 'Readarr' }); } public getAuthors = async (): Promise => { try { const response = await this.axios.get('/author'); return response.data; } catch (e) { throw new Error(`[Readarr] Failed to retrieve authors: ${e.message}`, { cause: e, }); } }; public getAuthor = async ({ id, }: { id: number; }): Promise => { try { const response = await this.axios.get(`/author/${id}`); return response.data; } catch (e) { throw new Error(`[Readarr] Failed to retrieve author: ${e.message}`, { cause: e, }); } }; public searchBook = async (term: string): Promise => { try { const response = await this.axios.get('/book/lookup', { params: { term }, }); return response.data; } catch (e) { throw new Error(`[Readarr] Failed to search books: ${e.message}`, { cause: e, }); } }; public searchAuthor = async (term: string): Promise => { try { const response = await this.axios.get( '/author/lookup', { params: { term } } ); return response.data; } catch (e) { throw new Error(`[Readarr] Failed to search authors: ${e.message}`, { cause: e, }); } }; public getBooks = async (authorId: number): Promise => { try { const response = await this.axios.get('/book', { params: { authorId }, }); return response.data; } catch (e) { throw new Error(`[Readarr] Failed to retrieve books: ${e.message}`, { cause: e, }); } }; public getBook = async ({ id }: { id: number }): Promise => { try { const response = await this.axios.get(`/book/${id}`); return response.data; } catch (e) { throw new Error(`[Readarr] Failed to retrieve book: ${e.message}`, { cause: e, }); } }; public addBook = async ( options: ReadarrBookOptions ): Promise => { try { const lookupResults = await this.searchBook( `readarr:${options.foreignBookId}` ); const lookupBook = lookupResults[0]; if (!lookupBook) { throw new Error('Book not found in lookup'); } const response = await this.axios.post('/book', { ...lookupBook, qualityProfileId: options.qualityProfileId, metadataProfileId: options.metadataProfileId, rootFolderPath: options.rootFolderPath, monitored: options.monitored ?? true, tags: options.tags, addOptions: { searchForNewBook: options.searchNow ?? true, }, }); if (response.data.id) { logger.info('Readarr accepted request', { label: 'Readarr', bookId: response.data.id, title: response.data.title, }); } return response.data; } catch (e) { logger.error('Failed to add book to Readarr', { label: 'Readarr', errorMessage: e.message, options, }); throw new Error('Failed to add book to Readarr', { cause: e }); } }; public getMetadataProfiles = async (): Promise< ReadarrMetadataProfile[] > => { try { const response = await this.axios.get('/metadataprofile'); return response.data; } catch (e) { throw new Error( `[Readarr] Failed to retrieve metadata profiles: ${e.message}`, { cause: e } ); } }; public removeBook = async (bookId: number): Promise => { try { await this.axios.delete(`/book/${bookId}`, { params: { deleteFiles: true, addImportListExclusion: false }, }); logger.info(`[Readarr] Removed book ${bookId}`); } catch (e) { throw new Error(`[Readarr] Failed to remove book: ${e.message}`, { cause: e, }); } }; } export default ReadarrAPI;