From 12c0305a4b4617d8e3ff3691269eb08c419dd869 Mon Sep 17 00:00:00 2001 From: kikootwo Date: Wed, 28 Jan 2026 13:06:41 -0500 Subject: [PATCH] Fix ABS metadata matching to respect Audible region Updated Audiobookshelf metadata matching to use the user's configured Audible region instead of always defaulting to 'audible' (US). Introduced mapRegionToABSProvider to map region codes to the correct provider value, and added tests for all supported regions (US, CA, UK, AU, IN). --- documentation/integrations/audible.md | 7 ++ src/lib/services/audiobookshelf/api.ts | 16 +++- tests/services/audiobookshelf-api.test.ts | 93 ++++++++++++++++++++++- 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/documentation/integrations/audible.md b/documentation/integrations/audible.md index 91814d8..2a99641 100644 --- a/documentation/integrations/audible.md +++ b/documentation/integrations/audible.md @@ -218,3 +218,10 @@ interface EnrichedAudibleAudiobook extends AudibleAudiobook { - **Fix:** Updated ASIN regex to match both `/pd/` and `/ac/` URL patterns: `/\/(?:pd|ac)\/[^\/]+\/([A-Z0-9]{10})/` - **Location:** `src/lib/integrations/audible.service.ts:75, 161, 240` - **Affects:** `getPopularAudiobooks()`, `getNewReleases()`, `search()` methods + +**Audiobookshelf metadata matching not respecting configured region (2026-01-28)** +- **Problem:** `triggerABSItemMatch()` hardcoded `'audible'` provider (audible.com) instead of respecting user's configured Audible region +- **Impact:** Users with non-US regions (CA, UK, AU, IN) had incorrect metadata matching in Audiobookshelf, causing wrong ASINs and poor search results +- **Fix:** Added `mapRegionToABSProvider()` to convert RMAB region codes to AudiobookShelf provider values. US → `'audible'`, others → `'audible.{region}'` (e.g., `'audible.ca'`, `'audible.uk'`) +- **Location:** `src/lib/services/audiobookshelf/api.ts:14, 147` +- **Affects:** All Audiobookshelf metadata matching operations diff --git a/src/lib/services/audiobookshelf/api.ts b/src/lib/services/audiobookshelf/api.ts index 7fdea66..eacd939 100644 --- a/src/lib/services/audiobookshelf/api.ts +++ b/src/lib/services/audiobookshelf/api.ts @@ -5,9 +5,18 @@ import { getConfigService } from '../config.service'; import { RMABLogger } from '@/lib/utils/logger'; +import { AudibleRegion } from '@/lib/types/audible'; const logger = RMABLogger.create('Audiobookshelf'); +/** + * Map RMAB Audible region to Audiobookshelf provider value + */ +function mapRegionToABSProvider(region: AudibleRegion): string { + // US uses 'audible' (audible.com), all others use 'audible.{region}' + return region === 'us' ? 'audible' : `audible.${region}`; +} + interface ABSRequestOptions { method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; body?: any; @@ -133,8 +142,13 @@ export async function triggerABSScan(libraryId: string) { */ export async function triggerABSItemMatch(itemId: string, asin?: string) { try { + // Get configured Audible region to use correct ABS provider + const configService = getConfigService(); + const region = await configService.getAudibleRegion(); + const provider = mapRegionToABSProvider(region); + const body: any = { - provider: 'audible', // Use Audible as the metadata provider + provider, // Use region-specific Audible provider (e.g., 'audible.ca' for Canada) }; // If we have an ASIN, we can do a direct match with 100% confidence diff --git a/tests/services/audiobookshelf-api.test.ts b/tests/services/audiobookshelf-api.test.ts index 70f4f98..19d9338 100644 --- a/tests/services/audiobookshelf-api.test.ts +++ b/tests/services/audiobookshelf-api.test.ts @@ -18,6 +18,7 @@ import { const configServiceMock = vi.hoisted(() => ({ get: vi.fn(), + getAudibleRegion: vi.fn(), })); const fetchMock = vi.hoisted(() => vi.fn()); @@ -139,12 +140,13 @@ describe('Audiobookshelf API client', () => { })); }); - it('includes ASIN overrides in metadata match requests', async () => { + it('includes ASIN overrides in metadata match requests with US region', async () => { configServiceMock.get.mockImplementation(async (key: string) => { if (key === 'audiobookshelf.server_url') return 'http://abs'; if (key === 'audiobookshelf.api_token') return 'token'; return null; }); + configServiceMock.getAudibleRegion.mockResolvedValue('us'); fetchMock.mockResolvedValue({ ok: true, json: async () => ({}), @@ -154,18 +156,105 @@ describe('Audiobookshelf API client', () => { const body = JSON.parse(fetchMock.mock.calls[0][1].body); expect(body).toEqual({ - provider: 'audible', + provider: 'audible', // US uses 'audible' asin: 'ASIN123', overrideDefaults: true, }); }); + it('uses region-specific provider for Canada', async () => { + configServiceMock.get.mockImplementation(async (key: string) => { + if (key === 'audiobookshelf.server_url') return 'http://abs'; + if (key === 'audiobookshelf.api_token') return 'token'; + return null; + }); + configServiceMock.getAudibleRegion.mockResolvedValue('ca'); + fetchMock.mockResolvedValue({ + ok: true, + json: async () => ({}), + }); + + await triggerABSItemMatch('item-1', 'ASIN123'); + + const body = JSON.parse(fetchMock.mock.calls[0][1].body); + expect(body).toEqual({ + provider: 'audible.ca', + asin: 'ASIN123', + overrideDefaults: true, + }); + }); + + it('uses region-specific provider for UK', async () => { + configServiceMock.get.mockImplementation(async (key: string) => { + if (key === 'audiobookshelf.server_url') return 'http://abs'; + if (key === 'audiobookshelf.api_token') return 'token'; + return null; + }); + configServiceMock.getAudibleRegion.mockResolvedValue('uk'); + fetchMock.mockResolvedValue({ + ok: true, + json: async () => ({}), + }); + + await triggerABSItemMatch('item-1'); + + const body = JSON.parse(fetchMock.mock.calls[0][1].body); + expect(body).toEqual({ + provider: 'audible.uk', + }); + }); + + it('uses region-specific provider for Australia', async () => { + configServiceMock.get.mockImplementation(async (key: string) => { + if (key === 'audiobookshelf.server_url') return 'http://abs'; + if (key === 'audiobookshelf.api_token') return 'token'; + return null; + }); + configServiceMock.getAudibleRegion.mockResolvedValue('au'); + fetchMock.mockResolvedValue({ + ok: true, + json: async () => ({}), + }); + + await triggerABSItemMatch('item-1', 'ASIN456'); + + const body = JSON.parse(fetchMock.mock.calls[0][1].body); + expect(body).toEqual({ + provider: 'audible.au', + asin: 'ASIN456', + overrideDefaults: true, + }); + }); + + it('uses region-specific provider for India', async () => { + configServiceMock.get.mockImplementation(async (key: string) => { + if (key === 'audiobookshelf.server_url') return 'http://abs'; + if (key === 'audiobookshelf.api_token') return 'token'; + return null; + }); + configServiceMock.getAudibleRegion.mockResolvedValue('in'); + fetchMock.mockResolvedValue({ + ok: true, + json: async () => ({}), + }); + + await triggerABSItemMatch('item-1', 'ASIN789'); + + const body = JSON.parse(fetchMock.mock.calls[0][1].body); + expect(body).toEqual({ + provider: 'audible.in', + asin: 'ASIN789', + overrideDefaults: true, + }); + }); + it('suppresses errors when metadata match fails', async () => { configServiceMock.get.mockImplementation(async (key: string) => { if (key === 'audiobookshelf.server_url') return 'http://abs'; if (key === 'audiobookshelf.api_token') return 'token'; return null; }); + configServiceMock.getAudibleRegion.mockResolvedValue('us'); fetchMock.mockResolvedValue({ ok: false, status: 500,