Compare commits

...

3 Commits

Author SHA1 Message Date
kikootwo 4c1d1c89e8 Audible regions: isEnglish flag + UI warnings
Add an isEnglish flag to AUDIBLE_REGIONS and update region handling across the app. UI: populate Audible region selects from AUDIBLE_REGIONS and mark non-English regions with a '*' and an amber warning explaining limited feature support. Service: set axios default param language=english on Audible requests (simplifies/fixes locale handling) and remove the previous locale-correction flow. API: validate regions dynamically from AUDIBLE_REGIONS. Also bump package version to 1.0.2. These changes make region metadata explicit and inform users about limited support for non-English regions while forcing English content where supported.
2026-02-06 11:48:00 -05:00
kikootwo d25d93680e Merge pull request #36 from aronjanosch/feature/german-audible-region-and-regional-title
Add German Audible region
2026-02-06 11:10:14 -05:00
Aron Wiederkehr 312421a96b Add German Audible region 2026-02-05 20:09:21 +01:00
8 changed files with 138 additions and 143 deletions
+18 -15
View File
@@ -26,11 +26,18 @@ Audiobook metadata from Audnexus API (primary) and Audible.com scraping (fallbac
Configurable Audible region for accurate metadata matching across different international Audible stores. Configurable Audible region for accurate metadata matching across different international Audible stores.
**Supported Regions:** **Supported Regions:**
- United States (`us`) - `audible.com` (default) - United States (`us`) - `audible.com` (default, English)
- Canada (`ca`) - `audible.ca` - Canada (`ca`) - `audible.ca` (English)
- United Kingdom (`uk`) - `audible.co.uk` - United Kingdom (`uk`) - `audible.co.uk` (English)
- Australia (`au`) - `audible.com.au` - Australia (`au`) - `audible.com.au` (English)
- India (`in`) - `audible.in` - India (`in`) - `audible.in` (English)
- Germany (`de`) - `audible.de` (non-English)
**`isEnglish` Flag:**
- Each region has `isEnglish: boolean` in `AudibleRegionConfig`
- 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:** **Why Regions Matter:**
- Each Audible region uses different ASINs for the same audiobook - Each Audible region uses different ASINs for the same audiobook
@@ -48,7 +55,7 @@ Configurable Audible region for accurate metadata matching across different inte
- Dynamically builds base URL: `AUDIBLE_REGIONS[region].baseUrl` - Dynamically builds base URL: `AUDIBLE_REGIONS[region].baseUrl`
- Audnexus API calls include region parameter: `?region={code}` - Audnexus API calls include region parameter: `?region={code}`
- IP redirect prevention: `?ipRedirectOverride=true` on all Audible requests (region only) - IP redirect prevention: `?ipRedirectOverride=true` on all Audible requests (region only)
- **Locale enforcement:** Cookie `lc-acbus=en_US` + `handleLocaleRedirect()` detects non-English culture codes in response URLs and re-requests using the English URL from Audible's locale picker - **Locale enforcement:** `?language=english` query parameter on all Audible requests (forces English content regardless of server IP geolocation)
- Configuration service helper: `getAudibleRegion()` returns configured region - Configuration service helper: `getAudibleRegion()` returns configured region
- **Auto-detection of region changes**: Service checks config before each request and re-initializes if region changed - **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 - **Cache clearing**: When region changes, ConfigService cache and AudibleService initialization are cleared
@@ -228,12 +235,8 @@ interface EnrichedAudibleAudiobook extends AudibleAudiobook {
- **Affects:** All Audiobookshelf metadata matching operations - **Affects:** All Audiobookshelf metadata matching operations
**Non-English locale pages served to users outside US (2026-02-05)** **Non-English locale pages served to users outside US (2026-02-05)**
- **Problem:** Audible uses IP geolocation to add culture codes (e.g., `es_US`, `fr_CA`) to URLs, serving locale-specific pages. `ipRedirectOverride=true` only prevents region redirects (audible.com → audible.co.uk), NOT language/locale redirects within the same region. - **Problem:** Audible uses IP geolocation to serve locale-specific pages (e.g., Spanish content for Dominican Republic IPs). `ipRedirectOverride=true` only prevents region redirects (audible.com → audible.co.uk), NOT language/locale changes.
- **Impact:** Users self-hosting from non-English-speaking countries (e.g., Dominican Republic) got Spanish bestsellers/new releases on their homepage because the `audible_refresh` job scraped locale-redirected pages. - **Impact:** Users self-hosting from non-English-speaking countries got non-English bestsellers/new releases on their homepage.
- **Fix:** Three-layer defense in `AudibleService`: - **Fix:** Added `language=english` query 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.
1. **Cookie:** `lc-acbus=en_US` header hints English locale preference - **Location:** `src/lib/integrations/audible.service.ts``initialize()` (axios default params)
2. **Locale picker detection (primary):** After every request, checks response URL for non-`en_*` culture codes (`xx_YY` pattern). If found, parses page HTML for Audible's `<adbl-toggle-chip>` locale picker, extracts the English option's `data-value` URL, and re-requests. Data-driven — uses Audible's own English URL rather than guessing. - **Affects:** All Audible scraping: popular, new releases, search, detail pages
3. **Fallback URL rewrite:** If no locale picker found, strips the culture code from the path and adds `language=en_US` query param (mirrors picker pattern).
- **Verification:** After correction, validates the response URL no longer contains a non-English culture code and logs success/failure.
- **Location:** `src/lib/integrations/audible.service.ts``handleLocaleRedirect()`, `initialize()`
- **Affects:** All Audible scraping: popular, new releases, search, detail pages (via `fetchWithRetry`)
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "readmeabook", "name": "readmeabook",
"version": "1.0.1", "version": "1.0.2",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
@@ -6,6 +6,7 @@
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Settings, ABSLibrary } from '../../lib/types'; import { Settings, ABSLibrary } from '../../lib/types';
import { AUDIBLE_REGIONS } from '@/lib/types/audible';
interface AudiobookshelfSectionProps { interface AudiobookshelfSectionProps {
settings: Settings; settings: Settings;
@@ -161,12 +162,39 @@ export function AudiobookshelfSection({
onChange={(e) => handleAudibleRegionChange(e.target.value)} onChange={(e) => handleAudibleRegionChange(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
> >
<option value="us">United States</option> {Object.values(AUDIBLE_REGIONS).map((region) => (
<option value="ca">Canada</option> <option key={region.code} value={region.code}>
<option value="uk">United Kingdom</option> {region.name}{!region.isEnglish ? ' *' : ''}
<option value="au">Australia</option> </option>
<option value="in">India</option> ))}
</select> </select>
{AUDIBLE_REGIONS[settings.audibleRegion as keyof typeof AUDIBLE_REGIONS]?.isEnglish === false && (
<div className="bg-amber-50 dark:bg-amber-900/20 rounded-lg p-4 border border-amber-200 dark:border-amber-800 mt-2">
<div className="flex gap-3">
<svg
className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
<div>
<p className="text-sm font-medium text-amber-900 dark:text-amber-100">
Non-English Region
</p>
<p className="text-sm text-amber-700 dark:text-amber-300 mt-1">
Many features such as search, discovery, and metadata matching are not yet fully
supported for non-English regions. You may still proceed, but expect limited
functionality.
</p>
</div>
</div>
</div>
)}
<p className="text-sm text-gray-500 dark:text-gray-400"> <p className="text-sm text-gray-500 dark:text-gray-400">
Select the Audible region that matches your metadata engine (Audnexus/Audible Agent) Select the Audible region that matches your metadata engine (Audnexus/Audible Agent)
configuration in Audiobookshelf. This ensures accurate book matching and metadata. configuration in Audiobookshelf. This ensures accurate book matching and metadata.
@@ -6,6 +6,7 @@
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Settings, PlexLibrary } from '../../lib/types'; import { Settings, PlexLibrary } from '../../lib/types';
import { AUDIBLE_REGIONS } from '@/lib/types/audible';
interface PlexSectionProps { interface PlexSectionProps {
settings: Settings; settings: Settings;
@@ -161,12 +162,39 @@ export function PlexSection({
onChange={(e) => handleAudibleRegionChange(e.target.value)} onChange={(e) => handleAudibleRegionChange(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
> >
<option value="us">United States</option> {Object.values(AUDIBLE_REGIONS).map((region) => (
<option value="ca">Canada</option> <option key={region.code} value={region.code}>
<option value="uk">United Kingdom</option> {region.name}{!region.isEnglish ? ' *' : ''}
<option value="au">Australia</option> </option>
<option value="in">India</option> ))}
</select> </select>
{AUDIBLE_REGIONS[settings.audibleRegion as keyof typeof AUDIBLE_REGIONS]?.isEnglish === false && (
<div className="bg-amber-50 dark:bg-amber-900/20 rounded-lg p-4 border border-amber-200 dark:border-amber-800 mt-2">
<div className="flex gap-3">
<svg
className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
<div>
<p className="text-sm font-medium text-amber-900 dark:text-amber-100">
Non-English Region
</p>
<p className="text-sm text-amber-700 dark:text-amber-300 mt-1">
Many features such as search, discovery, and metadata matching are not yet fully
supported for non-English regions. You may still proceed, but expect limited
functionality.
</p>
</div>
</div>
</div>
)}
<p className="text-sm text-gray-500 dark:text-gray-400"> <p className="text-sm text-gray-500 dark:text-gray-400">
Select the Audible region that matches your metadata engine (Audnexus/Audible Agent) Select the Audible region that matches your metadata engine (Audnexus/Audible Agent)
configuration in Plex. This ensures accurate book matching and metadata. configuration in Plex. This ensures accurate book matching and metadata.
+3 -2
View File
@@ -8,11 +8,12 @@ import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middlewar
import { getConfigService } from '@/lib/services/config.service'; import { getConfigService } from '@/lib/services/config.service';
import { getAudibleService } from '@/lib/integrations/audible.service'; import { getAudibleService } from '@/lib/integrations/audible.service';
import { getJobQueueService } from '@/lib/services/job-queue.service'; import { getJobQueueService } from '@/lib/services/job-queue.service';
import { AUDIBLE_REGIONS } from '@/lib/types/audible';
import { RMABLogger } from '@/lib/utils/logger'; import { RMABLogger } from '@/lib/utils/logger';
const logger = RMABLogger.create('API.Admin.Settings.Audible'); const logger = RMABLogger.create('API.Admin.Settings.Audible');
const VALID_REGIONS = ['us', 'ca', 'uk', 'au', 'in']; const VALID_REGIONS = Object.keys(AUDIBLE_REGIONS);
export async function PUT(request: NextRequest) { export async function PUT(request: NextRequest) {
return requireAuth(request, async (req: AuthenticatedRequest) => { return requireAuth(request, async (req: AuthenticatedRequest) => {
@@ -24,7 +25,7 @@ export async function PUT(request: NextRequest) {
if (!region || !VALID_REGIONS.includes(region)) { if (!region || !VALID_REGIONS.includes(region)) {
logger.warn('Invalid region provided', { region }); logger.warn('Invalid region provided', { region });
return NextResponse.json( return NextResponse.json(
{ success: false, error: 'Invalid Audible region. Must be one of: us, ca, uk, au, in' }, { success: false, error: `Invalid Audible region. Must be one of: ${VALID_REGIONS.join(', ')}` },
{ status: 400 } { status: 400 }
); );
} }
+33 -6
View File
@@ -6,7 +6,7 @@
'use client'; 'use client';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { AudibleRegion } from '@/lib/types/audible'; import { AudibleRegion, AUDIBLE_REGIONS } from '@/lib/types/audible';
interface BackendSelectionStepProps { interface BackendSelectionStepProps {
value: 'plex' | 'audiobookshelf'; value: 'plex' | 'audiobookshelf';
@@ -113,12 +113,39 @@ export function BackendSelectionStep({
onChange={(e) => onAudibleRegionChange(e.target.value as AudibleRegion)} onChange={(e) => onAudibleRegionChange(e.target.value as AudibleRegion)}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
> >
<option value="us">United States</option> {Object.values(AUDIBLE_REGIONS).map((region) => (
<option value="ca">Canada</option> <option key={region.code} value={region.code}>
<option value="uk">United Kingdom</option> {region.name}{!region.isEnglish ? ' *' : ''}
<option value="au">Australia</option> </option>
<option value="in">India</option> ))}
</select> </select>
{AUDIBLE_REGIONS[audibleRegion]?.isEnglish === false && (
<div className="bg-amber-50 dark:bg-amber-900/20 rounded-lg p-4 border border-amber-200 dark:border-amber-800 mt-2">
<div className="flex gap-3">
<svg
className="w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
clipRule="evenodd"
/>
</svg>
<div>
<p className="text-sm font-medium text-amber-900 dark:text-amber-100">
Non-English Region
</p>
<p className="text-sm text-amber-700 dark:text-amber-300 mt-1">
Many features such as search, discovery, and metadata matching are not yet fully
supported for non-English regions. You may still proceed, but expect limited
functionality.
</p>
</div>
</div>
</div>
)}
<p className="text-sm text-gray-600 dark:text-gray-400"> <p className="text-sm text-gray-600 dark:text-gray-400">
Select the Audible region that matches your metadata engine (Audnexus/Audible Agent) Select the Audible region that matches your metadata engine (Audnexus/Audible Agent)
configuration in {value === 'plex' ? 'Plex' : 'Audiobookshelf'}. This ensures accurate book matching and metadata. configuration in {value === 'plex' ? 'Plex' : 'Audiobookshelf'}. This ensures accurate book matching and metadata.
+3 -108
View File
@@ -88,10 +88,10 @@ export class AudibleService {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9', 'Accept-Language': 'en-US,en;q=0.9',
'Cookie': 'lc-acbus=en_US', // Force English locale (prevents IP-based language redirect for non-US IPs)
}, },
params: { params: {
ipRedirectOverride: 'true', // Prevent IP-based region redirects ipRedirectOverride: 'true', // Prevent IP-based region redirects
language: 'english', // Force English locale (prevents IP-based language serving for non-English IPs)
}, },
}); });
@@ -108,118 +108,16 @@ export class AudibleService {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9', 'Accept-Language': 'en-US,en;q=0.9',
'Cookie': 'lc-acbus=en_US', // Force English locale
}, },
params: { params: {
ipRedirectOverride: 'true', ipRedirectOverride: 'true',
language: 'english',
}, },
}); });
this.initialized = true; this.initialized = true;
} }
} }
/**
* Detect and correct non-English locale pages from Audible.
*
* Audible uses IP geolocation to serve locale-specific pages by adding culture
* codes to URLs (e.g., /adblbestsellers → /es_US/charts/best for Spanish-speaking IPs).
* ipRedirectOverride only prevents region redirects (audible.com → audible.co.uk),
* NOT language/locale redirects within the same region.
*
* Strategy (data-driven):
* 1. Check response URL for any non-English culture code (xx_YY where xx != 'en')
* 2. Parse the page's locale picker (adbl-toggle-chip elements) to find the English URL
* 3. Re-request using Audible's own English URL (from the picker's data-value attribute)
* 4. Fallback: strip culture code from URL + add language=en_US param if no picker found
*
* Returns corrected response, or null if no correction needed.
*/
private async handleLocaleRedirect(response: any): Promise<any | null> {
try {
// Extract final URL after all redirects (Node.js http internals)
const finalUrl: string = response.request?.res?.responseUrl ||
response.request?._redirectable?._currentUrl || '';
if (!finalUrl) return null;
// Check for non-English culture code in URL path
// Culture codes: xx_YY (e.g., es_US, fr_CA, pt_BR, de_DE, ja_JP)
// Match in path segment: must follow a / and be followed by / or end-of-path or query string
const localeMatch = finalUrl.match(/\/([a-z]{2}_[A-Z]{2})(\/|$|\?)/);
if (!localeMatch || localeMatch[1].startsWith('en')) {
return null; // No culture code found, or already English
}
const detectedLocale = localeMatch[1];
logger.warn(`Detected non-English locale (${detectedLocale}) in Audible response URL: ${finalUrl}`);
// --- Primary strategy: parse the locale picker from the page HTML ---
// Audible pages include a locale picker with <adbl-toggle-chip> web components:
// <adbl-toggle-chip data-locale="en_CA" data-value="/charts/best?language=en_CA">English</adbl-toggle-chip>
// <adbl-toggle-chip data-locale="fr_CA" data-value="/fr_CA/charts/best?language=fr_CA">Français</adbl-toggle-chip>
// The English option's data-value gives us the exact correct English URL for this page.
const $ = cheerio.load(response.data);
const englishChip = $('adbl-toggle-chip[data-locale^="en"]').first();
if (englishChip.length > 0) {
const englishPath = englishChip.attr('data-value');
const englishLocale = englishChip.attr('data-locale');
if (englishPath) {
logger.info(`Found English option (${englishLocale}) in locale picker: ${englishPath}`);
// Re-request using the English URL from the picker
// data-value is a relative path (e.g., "/charts/best?language=en_CA")
// Client defaults add ipRedirectOverride=true automatically
const correctedResponse = await this.client.get(englishPath);
// Verify the correction actually resolved to English
const correctedUrl: string = correctedResponse.request?.res?.responseUrl ||
correctedResponse.request?._redirectable?._currentUrl || '';
if (correctedUrl) {
const verifyMatch = correctedUrl.match(/\/([a-z]{2}_[A-Z]{2})(\/|$|\?)/);
if (verifyMatch && !verifyMatch[1].startsWith('en')) {
logger.warn(`Locale correction incomplete — corrected URL still contains non-English locale (${verifyMatch[1]}): ${correctedUrl}`);
} else {
logger.info(`Locale correction successful (${detectedLocale}${englishLocale})`);
}
}
return correctedResponse;
}
logger.warn('English locale chip found but missing data-value attribute');
} else {
logger.warn('No locale picker found on page, attempting fallback URL rewrite');
}
// --- Fallback strategy: URL rewrite ---
// Strip the non-English culture code from the path and add language=en_US param.
// This mirrors the locale picker pattern: English URLs have no prefix + language param.
try {
const urlObj = new URL(finalUrl);
urlObj.pathname = urlObj.pathname.replace(`/${detectedLocale}`, '');
urlObj.searchParams.set('language', 'en_US');
// Build relative path (client will prepend baseURL)
const fallbackPath = urlObj.pathname + urlObj.search;
logger.info(`Fallback: re-requesting with URL rewrite: ${fallbackPath}`);
return await this.client.get(fallbackPath);
} catch (urlError) {
logger.warn('Fallback URL rewrite failed', {
error: urlError instanceof Error ? urlError.message : String(urlError),
});
}
} catch (error) {
logger.debug('Locale correction failed entirely, using original response', {
error: error instanceof Error ? error.message : String(error),
});
}
return null;
}
/** /**
* Fetch with retry logic and exponential backoff * Fetch with retry logic and exponential backoff
* Retries on network errors and rate limiting (503, 429) * Retries on network errors and rate limiting (503, 429)
@@ -233,10 +131,7 @@ export class AudibleService {
for (let attempt = 0; attempt <= maxRetries; attempt++) { for (let attempt = 0; attempt <= maxRetries; attempt++) {
try { try {
const response = await this.client.get(url, config); return await this.client.get(url, config);
// Check if redirected to non-English locale (e.g., /es_US/) and correct it
return await this.handleLocaleRedirect(response) || response;
} catch (error: any) { } catch (error: any) {
lastError = error; lastError = error;
const status = error.response?.status; const status = error.response?.status;
+14 -1
View File
@@ -3,13 +3,14 @@
* Documentation: documentation/integrations/audible.md * Documentation: documentation/integrations/audible.md
*/ */
export type AudibleRegion = 'us' | 'ca' | 'uk' | 'au' | 'in'; export type AudibleRegion = 'us' | 'ca' | 'uk' | 'au' | 'in' | 'de';
export interface AudibleRegionConfig { export interface AudibleRegionConfig {
code: AudibleRegion; code: AudibleRegion;
name: string; name: string;
baseUrl: string; baseUrl: string;
audnexusParam: string; audnexusParam: string;
isEnglish: boolean;
} }
export const AUDIBLE_REGIONS: Record<AudibleRegion, AudibleRegionConfig> = { export const AUDIBLE_REGIONS: Record<AudibleRegion, AudibleRegionConfig> = {
@@ -18,30 +19,42 @@ export const AUDIBLE_REGIONS: Record<AudibleRegion, AudibleRegionConfig> = {
name: 'United States', name: 'United States',
baseUrl: 'https://www.audible.com', baseUrl: 'https://www.audible.com',
audnexusParam: 'us', audnexusParam: 'us',
isEnglish: true,
}, },
ca: { ca: {
code: 'ca', code: 'ca',
name: 'Canada', name: 'Canada',
baseUrl: 'https://www.audible.ca', baseUrl: 'https://www.audible.ca',
audnexusParam: 'ca', audnexusParam: 'ca',
isEnglish: true,
}, },
uk: { uk: {
code: 'uk', code: 'uk',
name: 'United Kingdom', name: 'United Kingdom',
baseUrl: 'https://www.audible.co.uk', baseUrl: 'https://www.audible.co.uk',
audnexusParam: 'uk', audnexusParam: 'uk',
isEnglish: true,
}, },
au: { au: {
code: 'au', code: 'au',
name: 'Australia', name: 'Australia',
baseUrl: 'https://www.audible.com.au', baseUrl: 'https://www.audible.com.au',
audnexusParam: 'au', audnexusParam: 'au',
isEnglish: true,
}, },
in: { in: {
code: 'in', code: 'in',
name: 'India', name: 'India',
baseUrl: 'https://www.audible.in', baseUrl: 'https://www.audible.in',
audnexusParam: 'in', audnexusParam: 'in',
isEnglish: true,
},
de: {
code: 'de',
name: 'Germany',
baseUrl: 'https://www.audible.de',
audnexusParam: 'de',
isEnglish: false,
}, },
}; };