mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Update:Validate ASIN for author, chapter and match requests
This commit is contained in:
		
							parent
							
								
									ee501f70ed
								
							
						
					
					
						commit
						a018374d26
					
				| @ -9,7 +9,7 @@ const CacheManager = require('../managers/CacheManager') | |||||||
| const CoverManager = require('../managers/CoverManager') | const CoverManager = require('../managers/CoverManager') | ||||||
| const AuthorFinder = require('../finders/AuthorFinder') | const AuthorFinder = require('../finders/AuthorFinder') | ||||||
| 
 | 
 | ||||||
| const { reqSupportsWebp } = require('../utils/index') | const { reqSupportsWebp, isValidASIN } = require('../utils/index') | ||||||
| 
 | 
 | ||||||
| const naturalSort = createNewSortInstance({ | const naturalSort = createNewSortInstance({ | ||||||
|   comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare |   comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare | ||||||
| @ -252,7 +252,7 @@ class AuthorController { | |||||||
|   async match(req, res) { |   async match(req, res) { | ||||||
|     let authorData = null |     let authorData = null | ||||||
|     const region = req.body.region || 'us' |     const region = req.body.region || 'us' | ||||||
|     if (req.body.asin) { |     if (req.body.asin && isValidASIN(req.body.asin.toUpperCase?.())) { | ||||||
|       authorData = await AuthorFinder.findAuthorByASIN(req.body.asin, region) |       authorData = await AuthorFinder.findAuthorByASIN(req.body.asin, region) | ||||||
|     } else { |     } else { | ||||||
|       authorData = await AuthorFinder.findAuthorByName(req.body.q, region) |       authorData = await AuthorFinder.findAuthorByName(req.body.q, region) | ||||||
|  | |||||||
| @ -1,12 +1,13 @@ | |||||||
| const Logger = require("../Logger") | const Logger = require('../Logger') | ||||||
| const BookFinder = require('../finders/BookFinder') | const BookFinder = require('../finders/BookFinder') | ||||||
| const PodcastFinder = require('../finders/PodcastFinder') | const PodcastFinder = require('../finders/PodcastFinder') | ||||||
| const AuthorFinder = require('../finders/AuthorFinder') | const AuthorFinder = require('../finders/AuthorFinder') | ||||||
| const MusicFinder = require('../finders/MusicFinder') | const MusicFinder = require('../finders/MusicFinder') | ||||||
| const Database = require("../Database") | const Database = require('../Database') | ||||||
|  | const { isValidASIN } = require('../utils') | ||||||
| 
 | 
 | ||||||
| class SearchController { | class SearchController { | ||||||
|   constructor() { } |   constructor() {} | ||||||
| 
 | 
 | ||||||
|   async findBooks(req, res) { |   async findBooks(req, res) { | ||||||
|     const id = req.query.id |     const id = req.query.id | ||||||
| @ -63,6 +64,9 @@ class SearchController { | |||||||
| 
 | 
 | ||||||
|   async findChapters(req, res) { |   async findChapters(req, res) { | ||||||
|     const asin = req.query.asin |     const asin = req.query.asin | ||||||
|  |     if (!isValidASIN(asin.toUpperCase())) { | ||||||
|  |       return res.json({ error: 'Invalid ASIN' }) | ||||||
|  |     } | ||||||
|     const region = (req.query.region || 'us').toLowerCase() |     const region = (req.query.region || 'us').toLowerCase() | ||||||
|     const chapterData = await BookFinder.findChapters(asin, region) |     const chapterData = await BookFinder.findChapters(asin, region) | ||||||
|     if (!chapterData) { |     if (!chapterData) { | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| const axios = require('axios').default | const axios = require('axios').default | ||||||
| const htmlSanitizer = require('../utils/htmlSanitizer') | const htmlSanitizer = require('../utils/htmlSanitizer') | ||||||
| const Logger = require('../Logger') | const Logger = require('../Logger') | ||||||
|  | const { isValidASIN } = require('../utils/index') | ||||||
| 
 | 
 | ||||||
| class Audible { | class Audible { | ||||||
|   #responseTimeout = 30000 |   #responseTimeout = 30000 | ||||||
| @ -81,16 +82,6 @@ class Audible { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |  | ||||||
|    * Test if a search title matches an ASIN. Supports lowercase letters |  | ||||||
|    * |  | ||||||
|    * @param {string} title |  | ||||||
|    * @returns {boolean} |  | ||||||
|    */ |  | ||||||
|   isProbablyAsin(title) { |  | ||||||
|     return /^[0-9A-Za-z]{10}$/.test(title) |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** |   /** | ||||||
|    * |    * | ||||||
|    * @param {string} asin |    * @param {string} asin | ||||||
| @ -137,11 +128,11 @@ class Audible { | |||||||
|     if (!timeout || isNaN(timeout)) timeout = this.#responseTimeout |     if (!timeout || isNaN(timeout)) timeout = this.#responseTimeout | ||||||
| 
 | 
 | ||||||
|     let items |     let items | ||||||
|     if (asin) { |     if (asin && isValidASIN(asin.toUpperCase())) { | ||||||
|       items = [await this.asinSearch(asin, region, timeout)] |       items = [await this.asinSearch(asin, region, timeout)] | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     if (!items && this.isProbablyAsin(title)) { |     if (!items && isValidASIN(title.toUpperCase())) { | ||||||
|       items = [await this.asinSearch(title, region, timeout)] |       items = [await this.asinSearch(title, region, timeout)] | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| const axios = require('axios').default | const axios = require('axios').default | ||||||
| const { levenshteinDistance } = require('../utils/index') |  | ||||||
| const Logger = require('../Logger') |  | ||||||
| const Throttle = require('p-throttle') | const Throttle = require('p-throttle') | ||||||
|  | const Logger = require('../Logger') | ||||||
|  | const { levenshteinDistance } = require('../utils/index') | ||||||
|  | const { isValidASIN } = require('../utils/index') | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @typedef AuthorSearchObj |  * @typedef AuthorSearchObj | ||||||
| @ -66,13 +67,19 @@ class Audnexus { | |||||||
|    * @returns {Promise<AuthorSearchObj>} |    * @returns {Promise<AuthorSearchObj>} | ||||||
|    */ |    */ | ||||||
|   authorRequest(asin, region) { |   authorRequest(asin, region) { | ||||||
|     asin = encodeURIComponent(asin) |     if (!isValidASIN(asin?.toUpperCase?.())) { | ||||||
|     const regionQuery = region ? `?region=${region}` : '' |       Logger.error(`[Audnexus] Invalid ASIN ${asin}`) | ||||||
|     const authorRequestUrl = `${this.baseUrl}/authors/${asin}${regionQuery}` |       return null | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     asin = encodeURIComponent(asin.toUpperCase()) | ||||||
|  | 
 | ||||||
|  |     const authorRequestUrl = new URL(`${this.baseUrl}/authors/${asin}`) | ||||||
|  |     if (region) authorRequestUrl.searchParams.set('region', region) | ||||||
| 
 | 
 | ||||||
|     Logger.info(`[Audnexus] Searching for author "${authorRequestUrl}"`) |     Logger.info(`[Audnexus] Searching for author "${authorRequestUrl}"`) | ||||||
| 
 | 
 | ||||||
|     return this._processRequest(this.limiter(() => axios.get(authorRequestUrl))) |     return this._processRequest(this.limiter(() => axios.get(authorRequestUrl.toString()))) | ||||||
|       .then((res) => res.data) |       .then((res) => res.data) | ||||||
|       .catch((error) => { |       .catch((error) => { | ||||||
|         Logger.error(`[Audnexus] Author request failed for ${asin}`, error) |         Logger.error(`[Audnexus] Author request failed for ${asin}`, error) | ||||||
| @ -135,10 +142,20 @@ class Audnexus { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /** | ||||||
|  |    * | ||||||
|  |    * @param {string} asin | ||||||
|  |    * @param {string} region | ||||||
|  |    * @returns {Promise<Object>} | ||||||
|  |    */ | ||||||
|   getChaptersByASIN(asin, region) { |   getChaptersByASIN(asin, region) { | ||||||
|     Logger.debug(`[Audnexus] Get chapters for ASIN ${asin}/${region}`) |     Logger.debug(`[Audnexus] Get chapters for ASIN ${asin}/${region}`) | ||||||
| 
 | 
 | ||||||
|     return this._processRequest(this.limiter(() => axios.get(`${this.baseUrl}/books/${asin}/chapters?region=${region}`))) |     asin = encodeURIComponent(asin.toUpperCase()) | ||||||
|  |     const chaptersRequestUrl = new URL(`${this.baseUrl}/books/${asin}/chapters`) | ||||||
|  |     if (region) chaptersRequestUrl.searchParams.set('region', region) | ||||||
|  | 
 | ||||||
|  |     return this._processRequest(this.limiter(() => axios.get(chaptersRequestUrl.toString()))) | ||||||
|       .then((res) => res.data) |       .then((res) => res.data) | ||||||
|       .catch((error) => { |       .catch((error) => { | ||||||
|         Logger.error(`[Audnexus] Chapter ASIN request failed for ${asin}/${region}`, error) |         Logger.error(`[Audnexus] Chapter ASIN request failed for ${asin}/${region}`, error) | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| const Path = require('path') | const Path = require('path') | ||||||
| const uuid = require('uuid') | const uuid = require('uuid') | ||||||
| const Logger = require('../Logger') | const Logger = require('../Logger') | ||||||
| const { parseString } = require("xml2js") | const { parseString } = require('xml2js') | ||||||
| const areEquivalent = require('./areEquivalent') | const areEquivalent = require('./areEquivalent') | ||||||
| 
 | 
 | ||||||
| const levenshteinDistance = (str1, str2, caseSensitive = false) => { | const levenshteinDistance = (str1, str2, caseSensitive = false) => { | ||||||
| @ -11,8 +11,9 @@ const levenshteinDistance = (str1, str2, caseSensitive = false) => { | |||||||
|     str1 = str1.toLowerCase() |     str1 = str1.toLowerCase() | ||||||
|     str2 = str2.toLowerCase() |     str2 = str2.toLowerCase() | ||||||
|   } |   } | ||||||
|   const track = Array(str2.length + 1).fill(null).map(() => |   const track = Array(str2.length + 1) | ||||||
|     Array(str1.length + 1).fill(null)) |     .fill(null) | ||||||
|  |     .map(() => Array(str1.length + 1).fill(null)) | ||||||
|   for (let i = 0; i <= str1.length; i += 1) { |   for (let i = 0; i <= str1.length; i += 1) { | ||||||
|     track[0][i] = i |     track[0][i] = i | ||||||
|   } |   } | ||||||
| @ -25,7 +26,7 @@ const levenshteinDistance = (str1, str2, caseSensitive = false) => { | |||||||
|       track[j][i] = Math.min( |       track[j][i] = Math.min( | ||||||
|         track[j][i - 1] + 1, // deletion
 |         track[j][i - 1] + 1, // deletion
 | ||||||
|         track[j - 1][i] + 1, // insertion
 |         track[j - 1][i] + 1, // insertion
 | ||||||
|         track[j - 1][i - 1] + indicator, // substitution
 |         track[j - 1][i - 1] + indicator // substitution
 | ||||||
|       ) |       ) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @ -138,7 +139,10 @@ module.exports.toNumber = (val, fallback = 0) => { | |||||||
| module.exports.cleanStringForSearch = (str) => { | module.exports.cleanStringForSearch = (str) => { | ||||||
|   if (!str) return '' |   if (!str) return '' | ||||||
|   // Remove ' . ` " ,
 |   // Remove ' . ` " ,
 | ||||||
|   return str.toLowerCase().replace(/[\'\.\`\",]/g, '').trim() |   return str | ||||||
|  |     .toLowerCase() | ||||||
|  |     .replace(/[\'\.\`\",]/g, '') | ||||||
|  |     .trim() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const getTitleParts = (title) => { | const getTitleParts = (title) => { | ||||||
| @ -235,3 +239,14 @@ module.exports.isUUID = (str) => { | |||||||
|   if (!str || typeof str !== 'string') return false |   if (!str || typeof str !== 'string') return false | ||||||
|   return uuid.validate(str) |   return uuid.validate(str) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Check if a string is a valid ASIN | ||||||
|  |  * | ||||||
|  |  * @param {string} str | ||||||
|  |  * @returns {boolean} | ||||||
|  |  */ | ||||||
|  | module.exports.isValidASIN = (str) => { | ||||||
|  |   if (!str || typeof str !== 'string') return false | ||||||
|  |   return /^[A-Z0-9]{10}$/.test(str) | ||||||
|  | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user