From 3900db14d3d31ca845c8035fd2c6ce126e1d8f24 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 7 Oct 2022 17:18:28 -0500 Subject: [PATCH] Add:Multi-region audible & audnexus support #731 --- client/components/modals/item/tabs/Cover.vue | 2 +- client/components/modals/item/tabs/Match.vue | 4 +- client/store/scanners.js | 42 +++++++++++++++++-- server/finders/BookFinder.js | 9 ++-- server/providers/Audible.js | 44 +++++++++++++++----- server/providers/Audnexus.js | 4 +- 6 files changed, 82 insertions(+), 23 deletions(-) diff --git a/client/components/modals/item/tabs/Cover.vue b/client/components/modals/item/tabs/Cover.vue index e3bf910f..f8f62332 100644 --- a/client/components/modals/item/tabs/Cover.vue +++ b/client/components/modals/item/tabs/Cover.vue @@ -125,7 +125,7 @@ export default { return this.$store.state.scanners.providers }, searchTitleLabel() { - if (this.provider == 'audible') return 'Search Title or ASIN' + if (this.provider.startsWith('audible')) return 'Search Title or ASIN' else if (this.provider == 'itunes') return 'Search Term' return 'Search Title' }, diff --git a/client/components/modals/item/tabs/Match.vue b/client/components/modals/item/tabs/Match.vue index dffbaa33..3bc48528 100644 --- a/client/components/modals/item/tabs/Match.vue +++ b/client/components/modals/item/tabs/Match.vue @@ -258,7 +258,7 @@ export default { return this.$store.state.scanners.providers }, searchTitleLabel() { - if (this.provider == 'audible') return 'Search Title or ASIN' + if (this.provider.startsWith('audible')) return 'Search Title or ASIN' else if (this.provider == 'itunes') return 'Search Term' return 'Search Title' }, @@ -312,7 +312,7 @@ export default { this.isProcessing = true this.lastSearch = searchQuery var searchEntity = this.isPodcast ? 'podcast' : 'books' - var results = await this.$axios.$get(`/api/search/${searchEntity}?${searchQuery}`, { timeout: 10000 }).catch((error) => { + var results = await this.$axios.$get(`/api/search/${searchEntity}?${searchQuery}`, { timeout: 20000 }).catch((error) => { console.error('Failed', error) return [] }) diff --git a/client/store/scanners.js b/client/store/scanners.js index f98f8fd4..e475566c 100644 --- a/client/store/scanners.js +++ b/client/store/scanners.js @@ -10,12 +10,48 @@ export const state = () => ({ value: 'openlibrary' }, { - text: 'Audible', + text: 'iTunes', + value: 'itunes' + }, + { + text: 'Audible.com', value: 'audible' }, { - text: 'iTunes', - value: 'itunes' + text: 'Audible.ca', + value: 'audible.ca' + }, + { + text: 'Audible.co.uk', + value: 'audible.uk' + }, + { + text: 'Audible.co.au', + value: 'audible.au' + }, + { + text: 'Audible.fr', + value: 'audible.fr' + }, + { + text: 'Audible.de', + value: 'audible.de' + }, + { + text: 'Audible.co.jp', + value: 'audible.jp' + }, + { + text: 'Audible.it', + value: 'audible.it' + }, + { + text: 'Audible.co.in', + value: 'audible.in' + }, + { + text: 'Audible.es', + value: 'audible.es' } ], podcastProviders: [ diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 2a5660ff..08b9aaef 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -150,8 +150,9 @@ class BookFinder { return this.iTunesApi.searchAudiobooks(title) } - async getAudibleResults(title, author, asin) { - var books = await this.audible.search(title, author, asin); + async getAudibleResults(title, author, asin, provider) { + const region = provider.includes('.') ? provider.split('.').pop() : '' + const books = await this.audible.search(title, author, asin, region) if (this.verbose) Logger.debug(`Audible Book Search Results: ${books.length || 0}`) if (!books) return [] return books @@ -165,8 +166,8 @@ class BookFinder { if (provider === 'google') { books = await this.getGoogleBooksResults(title, author) - } else if (provider === 'audible') { - books = await this.getAudibleResults(title, author, asin) + } else if (provider.startsWith('audible')) { + books = await this.getAudibleResults(title, author, asin, provider) } else if (provider === 'itunes') { books = await this.getiTunesAudiobooksResults(title, author) } else if (provider === 'openlibrary') { diff --git a/server/providers/Audible.js b/server/providers/Audible.js index b669eff0..83d94803 100644 --- a/server/providers/Audible.js +++ b/server/providers/Audible.js @@ -3,7 +3,20 @@ const htmlSanitizer = require('../utils/htmlSanitizer') const Logger = require('../Logger') class Audible { - constructor() { } + constructor() { + this.regionMap = { + 'us': '.com', + 'ca': '.ca', + 'uk': '.co.uk', + 'au': '.co.au', + 'fr': '.fr', + 'de': '.de', + 'jp': '.co.jp', + 'it': '.it', + 'in': '.co.in', + 'es': '.es' + } + } cleanResult(item) { var { title, subtitle, asin, authors, narrators, publisherName, summary, releaseDate, image, genres, seriesPrimary, seriesSecondary, language, runtimeLengthMin } = item @@ -29,7 +42,9 @@ class Audible { tags: tagsFiltered.length ? tagsFiltered.join(', ') : null, series: series != [] ? series.map(({ name, position }) => ({ series: name, sequence: position })) : null, language: language ? language.charAt(0).toUpperCase() + language.slice(1) : null, - duration: runtimeLengthMin && !isNaN(runtimeLengthMin) ? Number(runtimeLengthMin) : 0 + duration: runtimeLengthMin && !isNaN(runtimeLengthMin) ? Number(runtimeLengthMin) : 0, + region: item.region || null, + rating: item.rating || null } } @@ -37,9 +52,10 @@ class Audible { return /^[0-9A-Z]{10}$/.test(title) } - asinSearch(asin) { + asinSearch(asin, region) { asin = encodeURIComponent(asin); - var url = `https://api.audnex.us/books/${asin}` + var regionQuery = region ? `?region=${region}` : '' + var url = `https://api.audnex.us/books/${asin}${regionQuery}` Logger.debug(`[Audible] ASIN url: ${url}`) return axios.get(url).then((res) => { if (!res || !res.data || !res.data.asin) return null @@ -50,14 +66,19 @@ class Audible { }) } - async search(title, author, asin) { + async search(title, author, asin, region) { + if (region && !this.regionMap[region]) { + Logger.error(`[Audible] search: Invalid region ${region}`) + region = '' + } + var items if (asin) { - items = [await this.asinSearch(asin)] + items = [await this.asinSearch(asin, region)] } if (!items && this.isProbablyAsin(title)) { - items = [await this.asinSearch(title)] + items = [await this.asinSearch(title, region)] } if (!items) { @@ -65,14 +86,15 @@ class Audible { num_results: '10', products_sort_by: 'Relevance', title: title - }; + } if (author) queryObj.author = author - var queryString = (new URLSearchParams(queryObj)).toString(); - var url = `https://api.audible.com/1.0/catalog/products?${queryString}` + const queryString = (new URLSearchParams(queryObj)).toString() + const tld = region ? this.regionMap[region] : '.com' + const url = `https://api.audible${tld}/1.0/catalog/products?${queryString}` Logger.debug(`[Audible] Search url: ${url}`) items = await axios.get(url).then((res) => { if (!res || !res.data || !res.data.products) return null - return Promise.all(res.data.products.map(result => this.asinSearch(result.asin))) + return Promise.all(res.data.products.map(result => this.asinSearch(result.asin, region))) }).catch(error => { Logger.error('[Audible] query search error', error) return [] diff --git a/server/providers/Audnexus.js b/server/providers/Audnexus.js index 10518ec8..34651e1a 100644 --- a/server/providers/Audnexus.js +++ b/server/providers/Audnexus.js @@ -35,7 +35,7 @@ class Audnexus { return { asin: author.asin, description: author.description, - image: author.image, + image: author.image || null, name: author.name } } @@ -54,7 +54,7 @@ class Audnexus { return { asin: author.asin, description: author.description, - image: author.image, + image: author.image || null, name: author.name } }