10 KiB
Audible Integration
Status: ✅ Implemented (Audnexus API + Web Scraping)
Audiobook metadata from Audnexus API (primary) and Audible.com scraping (fallback) for discovery, search, and detail pages.
Detail Page Strategy
Primary: Audnexus API
- Endpoint:
https://api.audnex.us/books/{asin} - Structured JSON response (no parsing needed)
- Provides: title, authors, narrators, description, duration, rating, genres, cover art
- Free, no API key required
- ~95% success rate for popular audiobooks
Fallback: Audible Scraping
- Used when Audnexus returns 404
- Parse Audible HTML with Cheerio
- Multiple selector strategies with promotional text filtering
- Extract JSON-LD structured data when available
Region Configuration
Status: ✅ Implemented
Configurable Audible region for accurate metadata matching across different international Audible stores.
Supported Regions:
- United States (
us) -audible.com(default, English) - Canada (
ca) -audible.ca(English) - United Kingdom (
uk) -audible.co.uk(English) - Australia (
au) -audible.com.au(English) - India (
in) -audible.in(English) - Germany (
de) -audible.de(non-English) - Spain (
es) -audible.es(non-English) - French (
fr) -audible.fr(non-English)
isEnglish Flag:
- Each region has
isEnglish: booleaninAudibleRegionConfig - Non-English regions (
isEnglish: false) display an amber warning in all region dropdowns (setup wizard + admin settings) - Warning text: "Many features such as search, discovery, and metadata matching are not yet fully supported for non-English regions."
- Dropdown options for non-English regions show
*suffix (e.g., "Germany *")
Why Regions Matter:
- Each Audible region uses different ASINs for the same audiobook
- Metadata engines (Audnexus/Audible Agent) in Plex/Audiobookshelf must match RMAB's region
- Mismatched regions cause poor search results and failed metadata matching
Configuration:
- Key:
audible.region(stored in database) - Default:
us - Set during: Setup wizard (Backend Selection step) or Admin Settings (Library tab)
- Help text instructs users to match their metadata engine region
Implementation:
AudibleServiceloads region from config on initialization- Dynamically builds base URL:
AUDIBLE_REGIONS[region].baseUrl - Audnexus API calls include region parameter:
?region={code} - IP redirect prevention:
?ipRedirectOverride=trueon all Audible requests (region only) - Locale enforcement:
?language=englishquery parameter on all Audible requests (forces English content regardless of server IP geolocation) - Configuration service helper:
getAudibleRegion()returns configured region - Auto-detection of region changes: Service checks config before each request and re-initializes if region changed
- Cache clearing: When region changes, ConfigService cache and AudibleService initialization are cleared
- Automatic refresh: Changing region automatically triggers
audible_refreshjob to fetch new data
Files:
- Types:
src/lib/types/audible.ts - Service:
src/lib/integrations/audible.service.ts - Config:
src/lib/services/config.service.ts - API:
src/app/api/admin/settings/audible/route.ts
Discovery Strategy (Popular/New/Search)
- Parse Audible HTML with Cheerio
- Multi-page scraping (20 items/page)
- Rate limit: max 10 req/min, 1.5s delay between pages
- Cache results in database (24hr TTL)
Data Sources
URLs dynamically built based on configured region:
- Best Sellers:
{baseUrl}/adblbestsellers - New Releases:
{baseUrl}/newreleases - Search:
{baseUrl}/search?keywords={query}&ipRedirectOverride=true - Detail Page:
{baseUrl}/pd/{asin}?ipRedirectOverride=true - Audnexus API:
https://api.audnex.us/books/{asin}?region={code}
Where {baseUrl} is determined by configured region (e.g., https://www.audible.co.uk for UK).
Metadata Extracted
- ASIN (Audible ID)
- Title, author, narrator
- Duration (minutes), release date, rating
- Description, cover art URL
- Genres/categories
Unified Matching (audiobook-matcher.ts)
Status: ✅ Production Ready (ASIN-Only Matching)
Single matching algorithm used everywhere (search, popular, new-releases, jobs).
Process (Library Availability Checks):
- Query DB directly by ASIN (indexed O(1) lookup)
- Check ASIN in dedicated field (100% confidence)
- Check ASIN in plexGuid (backward compatibility)
- Return match or null (no fuzzy fallback)
Match Priority:
findPlexMatch(): ASIN (field) → ASIN (GUID) → nullmatchAudiobook(): ASIN → ISBN → null
Benefits:
- Real-time matching at query time (not pre-matched)
- 100% confidence matches only (eliminates false positives)
- O(1) indexed lookups (faster than fuzzy matching)
- Solves race condition with Audiobookshelf ASIN population
- Used by all APIs for consistency
Note: Fuzzy matching (70% threshold) is preserved in ranking-algorithm.ts for Prowlarr torrent ranking, where it's needed to score multiple release candidates. Library availability checks require exact ASIN matches only.
Database-First Approach
Status: ✅ Implemented
Discovery APIs serve cached data from DB with real-time matching.
Flow:
audible_refreshjob runs daily → fetches 200 popular + 200 new releases- Downloads and caches cover thumbnails locally (reduces Audible load)
- Stores in DB with flags (
isPopular,isNewRelease) and rankings - Cleans up unused thumbnails after sync
- API routes query DB → apply real-time matching → return enriched results
- Homepage loads instantly (no Audible API hits)
Thumbnail Caching
Status: ✅ Implemented
Cover images cached locally to reduce external requests and improve performance.
Features:
- Downloads covers during
audible_refreshjob - Stores in
/app/cache/thumbnails(Docker volume) - Serves via
/api/cache/thumbnails/[filename] - Auto-cleanup of unused thumbnails
- Falls back to original URL if cache fails
- 24-hour browser cache headers
Implementation:
- Service:
src/lib/services/thumbnail-cache.service.ts - API Route:
src/app/api/cache/thumbnails/[filename]/route.ts - Storage: Docker volume
cachemounted at/app/cache - Filename:
{asin}.{ext}(e.g.,B08G9PRS1K.jpg)
API Endpoints:
GET /api/audiobooks/popular?page=1&limit=20 GET /api/audiobooks/new-releases?page=1&limit=20
Response:
{
success: boolean;
audiobooks: EnrichedAudibleAudiobook[];
count: number;
totalCount: number;
page: number;
totalPages: number;
hasMore: boolean;
lastSync: string | null; // ISO timestamp
message?: string; // if no data
}
Data Models
interface AudibleAudiobook {
asin: string;
title: string;
author: string;
narrator?: string;
description?: string;
coverArtUrl?: string;
durationMinutes?: number;
releaseDate?: string;
rating?: number;
genres?: string[];
}
interface EnrichedAudibleAudiobook extends AudibleAudiobook {
availabilityStatus: 'available' | 'requested' | 'unknown';
isAvailable: boolean;
plexGuid: string | null;
dbId: string;
}
Tech Stack
- axios (HTTP)
- cheerio (HTML parsing)
- Redis (caching, optional)
- Database (PostgreSQL)
- string-similarity (matching)
Fixed Issues
Search returning empty results (2026-01-07)
- Problem: Audible changed HTML structure for search results from
.productListItemto.s-result-item - Impact: All search queries returned 0 results
- Fix: Updated
search()method to support both.s-result-item(current) and.productListItem(legacy) - Selectors updated:
- Main:
.s-result-item, .productListItem - Title:
h2(new) orh3 a(legacy) - Author:
a[href*="/author/"](new) or.authorLabel(legacy) - Narrator:
a[href*="searchNarrator="](new) or.narratorLabel(legacy) - Runtime:
span:contains("Length:")(new) or.runtimeLabel(legacy) - Rating:
.a-icon-star span(new) or.ratingsLabel(legacy)
- Main:
- Location:
src/lib/integrations/audible.service.ts:235
Some audiobooks missing from search results (2026-01-07)
- Problem: ASIN extraction only matched
/pd/URLs but some audiobooks use/ac/URLs - Impact: Books like "Beatitude" by DJ Krimmer (ASIN: B0DVH7XL36) were skipped
- 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
Non-English locale pages served to users outside US (2026-02-05)
- Problem: Audible uses IP geolocation to serve locale-specific pages (e.g., Spanish content for Dominican Republic IPs).
ipRedirectOverride=trueonly prevents region redirects (audible.com → audible.co.uk), NOT language/locale changes. - Impact: Users self-hosting from non-English-speaking countries got non-English bestsellers/new releases on their homepage.
- Fix: Added
language=englishquery parameter to all Audible requests via axios default params. Audible respects this parameter and serves English content regardless of IP geolocation. Fails gracefully for regions where English isn't available. - Location:
src/lib/integrations/audible.service.ts—initialize()(axios default params) - Affects: All Audible scraping: popular, new releases, search, detail pages