From 82e1f1b010e54fcaa3ac622e38a5feeb520bd878 Mon Sep 17 00:00:00 2001 From: hobokenchicken Date: Wed, 1 Jul 2026 08:21:48 -0400 Subject: [PATCH] fix: handle isbn as array from Listenarr search results --- backend/src/services/listenarr.ts | 95 +++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 backend/src/services/listenarr.ts diff --git a/backend/src/services/listenarr.ts b/backend/src/services/listenarr.ts new file mode 100644 index 0000000..7824535 --- /dev/null +++ b/backend/src/services/listenarr.ts @@ -0,0 +1,95 @@ +import axios from "axios"; + +const LISTENARR_URL = process.env.LISTENARR_URL || ""; +const LISTENARR_API_KEY = process.env.LISTENARR_API_KEY || ""; + +const listenarrApi = axios.create({ + baseURL: LISTENARR_URL, + timeout: 15000, + headers: { + "X-Api-Key": LISTENARR_API_KEY, + "Content-Type": "application/json", + }, +}); + +export async function searchAudiobooks( + query: string +): Promise { + const resp = await listenarrApi.get("/api/v1/search/audible", { + params: { query }, + }); + return resp.data?.results || []; +} + +export async function addAudiobook(options: { + asin: string; + title: string; + authors: string[]; + imageUrl?: string; + description?: string; + monitored?: boolean; + autoSearch?: boolean; + subtitle?: string; + narrators?: string[]; + publisher?: string; + language?: string; + releaseDate?: string; + runtimeLengthMin?: number; + genres?: string[]; + series?: string; + seriesNumber?: string; + isbn?: string; + explicit?: boolean; + abridged?: boolean; +}): Promise { + // Defensive: ensure series/seriesNumber are always strings, not arrays + const series = typeof options.series === "string" ? options.series : ""; + const seriesNumber = typeof options.seriesNumber === "string" ? options.seriesNumber : ""; + + const body: any = { + metadata: { + asin: options.asin, + title: options.title, + subtitle: options.subtitle || "", + source: "audible", + authors: options.authors || [], + narrators: options.narrators || [], + imageUrl: options.imageUrl || "", + description: options.description || "", + publisher: options.publisher || "", + language: options.language || "", + releaseDate: options.releaseDate || "", + runtime: options.runtimeLengthMin ? options.runtimeLengthMin * 60 : 0, + genres: options.genres || [], + series: series, + seriesNumber: seriesNumber, + isbn: Array.isArray(options.isbn) ? options.isbn : (typeof options.isbn === "string" && options.isbn ? options.isbn.split(",") : []), + explicit: options.explicit || false, + abridged: options.abridged || false, + }, + autoSearch: options.autoSearch !== false, + monitored: options.monitored !== false, + qualityProfileId: 1, + }; + + const resp = await listenarrApi.post("/api/v1/library/add", body); + return resp.data; +} + +export async function getListenarrStatus(): Promise<{ + connected: boolean; + data?: any; + error?: string; +}> { + try { + const resp = await listenarrApi.get("/api/v1/system/info", { + timeout: 5000, + }); + return { connected: true, data: resp.data }; + } catch (error: any) { + return { + connected: false, + error: error?.message || "Failed to connect to Listenarr", + }; + } +}