From a3e63e03d2d9ff1f1b11a23f802d9b7667bfcf87 Mon Sep 17 00:00:00 2001 From: lukeIam <2lukeiam@gmail.com> Date: Sun, 16 Apr 2023 13:36:50 +0200 Subject: [PATCH 01/36] Use region for author queries --- server/providers/Audnexus.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/providers/Audnexus.js b/server/providers/Audnexus.js index 43dfb665..5935508e 100644 --- a/server/providers/Audnexus.js +++ b/server/providers/Audnexus.js @@ -9,7 +9,8 @@ class Audnexus { authorASINsRequest(name) { name = encodeURIComponent(name); - return axios.get(`${this.baseUrl}/authors?name=${name}`).then((res) => { + var regionQuery = region ? `®ion=${region}` : '' + return axios.get(`${this.baseUrl}/authors?name=${name}${regionQuery}`).then((res) => { return res.data || [] }).catch((error) => { Logger.error(`[Audnexus] Author ASIN request failed for ${name}`, error) @@ -19,7 +20,8 @@ class Audnexus { authorRequest(asin) { asin = encodeURIComponent(asin); - return axios.get(`${this.baseUrl}/authors/${asin}`).then((res) => { + var regionQuery = region ? `?region=${region}` : '' + return axios.get(`${this.baseUrl}/authors/${asin}${regionQuery}`).then((res) => { return res.data }).catch((error) => { Logger.error(`[Audnexus] Author request failed for ${asin}`, error) From f5009f76f4c3a1668574a57335fab8a3bb08f603 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 16 Apr 2023 15:21:04 -0500 Subject: [PATCH 02/36] Update proper lockfile settings #1326 --- server/Db.js | 57 +++++++++++++--------- server/libs/properLockfile/lib/lockfile.js | 3 ++ 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/server/Db.js b/server/Db.js index d836ef79..b6bff04f 100644 --- a/server/Db.js +++ b/server/Db.js @@ -27,17 +27,16 @@ class Db { this.SeriesPath = Path.join(global.ConfigPath, 'series') this.FeedsPath = Path.join(global.ConfigPath, 'feeds') - const staleTime = 1000 * 60 * 2 - this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath, { lockoptions: { stale: staleTime } }) - this.usersDb = new njodb.Database(this.UsersPath, { lockoptions: { stale: staleTime } }) - this.sessionsDb = new njodb.Database(this.SessionsPath, { lockoptions: { stale: staleTime } }) - this.librariesDb = new njodb.Database(this.LibrariesPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.settingsDb = new njodb.Database(this.SettingsPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.collectionsDb = new njodb.Database(this.CollectionsPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.playlistsDb = new njodb.Database(this.PlaylistsPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.authorsDb = new njodb.Database(this.AuthorsPath, { lockoptions: { stale: staleTime } }) - this.seriesDb = new njodb.Database(this.SeriesPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.feedsDb = new njodb.Database(this.FeedsPath, { datastores: 2, lockoptions: { stale: staleTime } }) + this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath, this.getNjodbOptions()) + this.usersDb = new njodb.Database(this.UsersPath, this.getNjodbOptions()) + this.sessionsDb = new njodb.Database(this.SessionsPath, this.getNjodbOptions()) + this.librariesDb = new njodb.Database(this.LibrariesPath, this.getNjodbOptions()) + this.settingsDb = new njodb.Database(this.SettingsPath, this.getNjodbOptions()) + this.collectionsDb = new njodb.Database(this.CollectionsPath, this.getNjodbOptions()) + this.playlistsDb = new njodb.Database(this.PlaylistsPath, this.getNjodbOptions()) + this.authorsDb = new njodb.Database(this.AuthorsPath, this.getNjodbOptions()) + this.seriesDb = new njodb.Database(this.SeriesPath, this.getNjodbOptions()) + this.feedsDb = new njodb.Database(this.FeedsPath, this.getNjodbOptions()) this.libraryItems = [] this.users = [] @@ -59,6 +58,21 @@ class Db { return this.users.some(u => u.id === 'root') } + getNjodbOptions() { + return { + lockoptions: { + stale: 1000 * 20, // 20 seconds + update: 2500, + retries: { + retries: 20, + minTimeout: 250, + maxTimeout: 5000, + factor: 1 + } + } + } + } + getEntityDb(entityName) { if (entityName === 'user') return this.usersDb else if (entityName === 'session') return this.sessionsDb @@ -88,17 +102,16 @@ class Db { } reinit() { - const staleTime = 1000 * 60 * 2 - this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath, { lockoptions: { stale: staleTime } }) - this.usersDb = new njodb.Database(this.UsersPath, { lockoptions: { stale: staleTime } }) - this.sessionsDb = new njodb.Database(this.SessionsPath, { lockoptions: { stale: staleTime } }) - this.librariesDb = new njodb.Database(this.LibrariesPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.settingsDb = new njodb.Database(this.SettingsPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.collectionsDb = new njodb.Database(this.CollectionsPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.playlistsDb = new njodb.Database(this.PlaylistsPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.authorsDb = new njodb.Database(this.AuthorsPath, { lockoptions: { stale: staleTime } }) - this.seriesDb = new njodb.Database(this.SeriesPath, { datastores: 2, lockoptions: { stale: staleTime } }) - this.feedsDb = new njodb.Database(this.FeedsPath, { datastores: 2, lockoptions: { stale: staleTime } }) + this.libraryItemsDb = new njodb.Database(this.LibraryItemsPath, this.getNjodbOptions()) + this.usersDb = new njodb.Database(this.UsersPath, this.getNjodbOptions()) + this.sessionsDb = new njodb.Database(this.SessionsPath, this.getNjodbOptions()) + this.librariesDb = new njodb.Database(this.LibrariesPath, this.getNjodbOptions()) + this.settingsDb = new njodb.Database(this.SettingsPath, this.getNjodbOptions()) + this.collectionsDb = new njodb.Database(this.CollectionsPath, this.getNjodbOptions()) + this.playlistsDb = new njodb.Database(this.PlaylistsPath, this.getNjodbOptions()) + this.authorsDb = new njodb.Database(this.AuthorsPath, this.getNjodbOptions()) + this.seriesDb = new njodb.Database(this.SeriesPath, this.getNjodbOptions()) + this.feedsDb = new njodb.Database(this.FeedsPath, this.getNjodbOptions()) return this.init() } diff --git a/server/libs/properLockfile/lib/lockfile.js b/server/libs/properLockfile/lib/lockfile.js index d981a00c..6e680d3a 100644 --- a/server/libs/properLockfile/lib/lockfile.js +++ b/server/libs/properLockfile/lib/lockfile.js @@ -118,6 +118,7 @@ function updateLock(file, options) { // the lockfile was deleted or we are over the threshold if (err) { if (err.code === 'ENOENT' || isOverThreshold) { + console.error(`lockfile "${file}" compromised. stat code=${err.code}, isOverThreshold=${isOverThreshold}`) return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' })); } @@ -129,6 +130,7 @@ function updateLock(file, options) { const isMtimeOurs = lock.mtime.getTime() === stat.mtime.getTime(); if (!isMtimeOurs) { + console.error(`lockfile "${file}" compromised. mtime is not ours`) return setLockAsCompromised( file, lock, @@ -152,6 +154,7 @@ function updateLock(file, options) { // the lockfile was deleted or we are over the threshold if (err) { if (err.code === 'ENOENT' || isOverThreshold) { + console.error(`lockfile "${file}" compromised. utimes code=${err.code}, isOverThreshold=${isOverThreshold}`) return setLockAsCompromised(file, lock, Object.assign(err, { code: 'ECOMPROMISED' })); } From e271e89835b413834da5c5231e0d36cfa9d4216c Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 16 Apr 2023 15:53:46 -0500 Subject: [PATCH 03/36] Author API requests to use region from library provider --- client/components/app/BookShelfToolbar.vue | 9 +++++- client/components/cards/AuthorCard.vue | 11 +++++++ .../components/modals/authors/EditModal.vue | 11 +++++++ server/controllers/AuthorController.js | 13 ++++---- server/finders/AuthorFinder.js | 8 ++--- server/providers/Audnexus.js | 32 +++++++++++-------- 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index a3879279..57599877 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -189,6 +189,9 @@ export default { currentLibraryId() { return this.$store.state.libraries.currentLibraryId }, + libraryProvider() { + return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google' + }, currentLibraryMediaType() { return this.$store.getters['libraries/getCurrentLibraryMediaType'] }, @@ -323,7 +326,11 @@ export default { const payload = {} if (author.asin) payload.asin = author.asin else payload.q = author.name - console.log('Payload', payload, 'author', author) + + payload.region = 'us' + if (this.libraryProvider.startsWith('audible.')) { + payload.region = this.libraryProvider.split('.').pop() || 'us' + } this.$eventBus.$emit(`searching-author-${author.id}`, true) diff --git a/client/components/cards/AuthorCard.vue b/client/components/cards/AuthorCard.vue index ce4f0d3b..db4e7e9a 100644 --- a/client/components/cards/AuthorCard.vue +++ b/client/components/cards/AuthorCard.vue @@ -77,6 +77,12 @@ export default { }, userCanUpdate() { return this.$store.getters['user/getUserCanUpdate'] + }, + currentLibraryId() { + return this.$store.state.libraries.currentLibraryId + }, + libraryProvider() { + return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google' } }, methods: { @@ -92,6 +98,11 @@ export default { if (this.asin) payload.asin = this.asin else payload.q = this.name + payload.region = 'us' + if (this.libraryProvider.startsWith('audible.')) { + payload.region = this.libraryProvider.split('.').pop() || 'us' + } + var response = await this.$axios.$post(`/api/authors/${this.authorId}/match`, payload).catch((error) => { console.error('Failed', error) return null diff --git a/client/components/modals/authors/EditModal.vue b/client/components/modals/authors/EditModal.vue index f2430390..40292dca 100644 --- a/client/components/modals/authors/EditModal.vue +++ b/client/components/modals/authors/EditModal.vue @@ -85,6 +85,12 @@ export default { }, title() { return this.$strings.HeaderUpdateAuthor + }, + currentLibraryId() { + return this.$store.state.libraries.currentLibraryId + }, + libraryProvider() { + return this.$store.getters['libraries/getLibraryProvider'](this.currentLibraryId) || 'google' } }, methods: { @@ -151,6 +157,11 @@ export default { if (this.authorCopy.asin) payload.asin = this.authorCopy.asin else payload.q = this.authorCopy.name + payload.region = 'us' + if (this.libraryProvider.startsWith('audible.')) { + payload.region = this.libraryProvider.split('.').pop() || 'us' + } + var response = await this.$axios.$post(`/api/authors/${this.authorId}/match`, payload).catch((error) => { console.error('Failed', error) return null diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index d3d72f7c..106734a4 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -167,18 +167,19 @@ class AuthorController { } async match(req, res) { - var authorData = null + let authorData = null + const region = req.body.region || 'us' if (req.body.asin) { - authorData = await this.authorFinder.findAuthorByASIN(req.body.asin) + authorData = await this.authorFinder.findAuthorByASIN(req.body.asin, region) } else { - authorData = await this.authorFinder.findAuthorByName(req.body.q) + authorData = await this.authorFinder.findAuthorByName(req.body.q, region) } if (!authorData) { return res.status(404).send('Author not found') } Logger.debug(`[AuthorController] match author with "${req.body.q || req.body.asin}"`, authorData) - var hasUpdates = false + let hasUpdates = false if (authorData.asin && req.author.asin !== authorData.asin) { req.author.asin = authorData.asin hasUpdates = true @@ -188,7 +189,7 @@ class AuthorController { if (authorData.image && (!req.author.imagePath || hasUpdates)) { this.cacheManager.purgeImageCache(req.author.id) - var imageData = await this.authorFinder.saveAuthorImage(req.author.id, authorData.image) + const imageData = await this.authorFinder.saveAuthorImage(req.author.id, authorData.image) if (imageData) { req.author.imagePath = imageData.path hasUpdates = true @@ -204,7 +205,7 @@ class AuthorController { req.author.updatedAt = Date.now() await this.db.updateEntity('author', req.author) - var numBooks = this.db.libraryItems.filter(li => { + const numBooks = this.db.libraryItems.filter(li => { return li.media.metadata.hasAuthor && li.media.metadata.hasAuthor(req.author.id) }).length SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks)) diff --git a/server/finders/AuthorFinder.js b/server/finders/AuthorFinder.js index 1f8b970b..18fb2223 100644 --- a/server/finders/AuthorFinder.js +++ b/server/finders/AuthorFinder.js @@ -20,16 +20,16 @@ class AuthorFinder { }) } - findAuthorByASIN(asin) { + findAuthorByASIN(asin, region) { if (!asin) return null - return this.audnexus.findAuthorByASIN(asin) + return this.audnexus.findAuthorByASIN(asin, region) } - async findAuthorByName(name, options = {}) { + async findAuthorByName(name, region, options = {}) { if (!name) return null const maxLevenshtein = !isNaN(options.maxLevenshtein) ? Number(options.maxLevenshtein) : 3 - var author = await this.audnexus.findAuthorByName(name, maxLevenshtein) + const author = await this.audnexus.findAuthorByName(name, region, maxLevenshtein) if (!author || !author.name) { return null } diff --git a/server/providers/Audnexus.js b/server/providers/Audnexus.js index 5935508e..b74d1d13 100644 --- a/server/providers/Audnexus.js +++ b/server/providers/Audnexus.js @@ -7,10 +7,12 @@ class Audnexus { this.baseUrl = 'https://api.audnex.us' } - authorASINsRequest(name) { - name = encodeURIComponent(name); - var regionQuery = region ? `®ion=${region}` : '' - return axios.get(`${this.baseUrl}/authors?name=${name}${regionQuery}`).then((res) => { + authorASINsRequest(name, region) { + name = encodeURIComponent(name) + const regionQuery = region ? `®ion=${region}` : '' + const authorRequestUrl = `${this.baseUrl}/authors?name=${name}${regionQuery}` + Logger.info(`[Audnexus] Searching for author "${authorRequestUrl}"`) + return axios.get(authorRequestUrl).then((res) => { return res.data || [] }).catch((error) => { Logger.error(`[Audnexus] Author ASIN request failed for ${name}`, error) @@ -18,10 +20,12 @@ class Audnexus { }) } - authorRequest(asin) { - asin = encodeURIComponent(asin); - var regionQuery = region ? `?region=${region}` : '' - return axios.get(`${this.baseUrl}/authors/${asin}${regionQuery}`).then((res) => { + authorRequest(asin, region) { + asin = encodeURIComponent(asin) + const regionQuery = region ? `?region=${region}` : '' + const authorRequestUrl = `${this.baseUrl}/authors/${asin}${regionQuery}` + Logger.info(`[Audnexus] Searching for author "${authorRequestUrl}"`) + return axios.get(authorRequestUrl).then((res) => { return res.data }).catch((error) => { Logger.error(`[Audnexus] Author request failed for ${asin}`, error) @@ -29,8 +33,8 @@ class Audnexus { }) } - async findAuthorByASIN(asin) { - var author = await this.authorRequest(asin) + async findAuthorByASIN(asin, region) { + const author = await this.authorRequest(asin, region) if (!author) { return null } @@ -42,14 +46,14 @@ class Audnexus { } } - async findAuthorByName(name, maxLevenshtein = 3) { + async findAuthorByName(name, region, maxLevenshtein = 3) { Logger.debug(`[Audnexus] Looking up author by name ${name}`) - var asins = await this.authorASINsRequest(name) - var matchingAsin = asins.find(obj => levenshteinDistance(obj.name, name) <= maxLevenshtein) + const asins = await this.authorASINsRequest(name, region) + const matchingAsin = asins.find(obj => levenshteinDistance(obj.name, name) <= maxLevenshtein) if (!matchingAsin) { return null } - var author = await this.authorRequest(matchingAsin.asin) + const author = await this.authorRequest(matchingAsin.asin) if (!author) { return null } From e4961feffbca36012b005f8b9f6d0e5285ca3ea9 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 16 Apr 2023 16:23:13 -0500 Subject: [PATCH 04/36] Update:Remove item metadata path when removing item #1561 --- server/routers/ApiRouter.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 19483367..1b2df155 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -410,6 +410,12 @@ class ApiRouter { await this.cacheManager.purgeCoverCache(libraryItem.id) } + const itemMetadataPath = Path.join(global.MetadataPath, 'items', libraryItem.id) + if (await fs.pathExists(itemMetadataPath)) { + Logger.debug(`[ApiRouter] Removing item metadata path "${itemMetadataPath}"`) + await fs.remove(itemMetadataPath) + } + await this.db.removeLibraryItem(libraryItem.id) SocketAuthority.emitter('item_removed', libraryItem.toJSONExpanded()) } From 1b5ab6c37853801300489799702425d5d6881d6e Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 16 Apr 2023 16:33:28 -0500 Subject: [PATCH 05/36] Update xml2js 0.5.0 --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3529c256..b2917178 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "htmlparser2": "^8.0.1", "node-tone": "^1.0.1", "socket.io": "^4.5.4", - "xml2js": "^0.4.23" + "xml2js": "^0.5.0" }, "bin": { "audiobookshelf": "prod.js" @@ -1329,9 +1329,9 @@ } }, "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -2300,9 +2300,9 @@ "requires": {} }, "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -2314,4 +2314,4 @@ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 296271b3..5ea715ac 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "htmlparser2": "^8.0.1", "node-tone": "^1.0.1", "socket.io": "^4.5.4", - "xml2js": "^0.4.23" + "xml2js": "^0.5.0" }, "devDependencies": { "nodemon": "^2.0.20" From 128c554543bf3b37f08c139ed16f2bf76072bf54 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 16 Apr 2023 16:34:09 -0500 Subject: [PATCH 06/36] Version bump 2.2.19 --- client/package-lock.json | 4 ++-- client/package.json | 2 +- package-lock.json | 6 +++--- package.json | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 0e890c9c..b995afe0 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.2.18", + "version": "2.2.19", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.2.18", + "version": "2.2.19", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index dcacf68f..25579ab7 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.2.18", + "version": "2.2.19", "description": "Self-hosted audiobook and podcast client", "main": "index.js", "scripts": { diff --git a/package-lock.json b/package-lock.json index b2917178..b7676698 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf", - "version": "2.2.18", + "version": "2.2.19", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "audiobookshelf", - "version": "2.2.18", + "version": "2.2.19", "license": "GPL-3.0", "dependencies": { "axios": "^0.27.2", @@ -2314,4 +2314,4 @@ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 5ea715ac..cb86b94b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "2.2.18", + "version": "2.2.19", "description": "Self-hosted audiobook and podcast server", "main": "index.js", "scripts": { From 1b96297cc7d241420db23136613df153e6d78c90 Mon Sep 17 00:00:00 2001 From: Shawn Salat <31082405+ThinkSalat@users.noreply.github.com> Date: Mon, 17 Apr 2023 08:25:20 -0600 Subject: [PATCH 07/36] blur the url input when clicking submit to add info currently in input --- .../components/modals/notification/NotificationEditModal.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/components/modals/notification/NotificationEditModal.vue b/client/components/modals/notification/NotificationEditModal.vue index 55264f03..9693f292 100644 --- a/client/components/modals/notification/NotificationEditModal.vue +++ b/client/components/modals/notification/NotificationEditModal.vue @@ -10,7 +10,7 @@
- + @@ -103,6 +103,8 @@ export default { if (this.$refs.modal) this.$refs.modal.setHide() }, submitForm() { + this.$ref.urlsInput.blur() + if (!this.newNotification.urls.length) { this.$toast.error('Must enter an Apprise URL') return From 730d60575ed942865327002f9bdb2e4b46f0099b Mon Sep 17 00:00:00 2001 From: Machou Date: Mon, 17 Apr 2023 22:42:48 +0200 Subject: [PATCH 08/36] Update fr.json --- client/strings/fr.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/strings/fr.json b/client/strings/fr.json index 2488a00f..61649d15 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -20,7 +20,7 @@ "ButtonCreate": "Créer", "ButtonCreateBackup": "Créer une sauvegarde", "ButtonDelete": "Effacer", - "ButtonDownloadQueue": "Queue de téléchargement", + "ButtonDownloadQueue": "File d’attente de téléchargement", "ButtonEdit": "Modifier", "ButtonEditChapters": "Modifier les chapitres", "ButtonEditPodcast": "Modifier les podcasts", @@ -217,7 +217,7 @@ "LabelDuration": "Durée", "LabelDurationFound": "Durée trouvée :", "LabelEdit": "Modifier", - "LabelEmbeddedCover": "Embedded Cover", + "LabelEmbeddedCover": "Couverture du livre intégrée", "LabelEnable": "Activer", "LabelEnd": "Fin", "LabelEpisode": "Épisode", @@ -290,7 +290,7 @@ "LabelNewestAuthors": "Nouveaux auteurs", "LabelNewestEpisodes": "Derniers épisodes", "LabelNewPassword": "Nouveau mot de passe", - "LabelNextBackupDate": "Prochaine date de sauvegarde", + "LabelNextBackupDate": "Date de la prochaine sauvegarde", "LabelNextScheduledRun": "Prochain lancement prévu", "LabelNotes": "Notes", "LabelNotFinished": "Non terminé(e)", @@ -339,7 +339,7 @@ "LabelRSSFeedCustomOwnerEmail": "Email propriétaire personnalisé", "LabelRSSFeedCustomOwnerName": "Nom propriétaire personnalisé", "LabelRSSFeedOpen": "Flux RSS ouvert", - "LabelRSSFeedPreventIndexing": "Empêcher l'indexation", + "LabelRSSFeedPreventIndexing": "Empêcher l’indexation", "LabelRSSFeedSlug": "Identificateur d’adresse du Flux RSS ", "LabelRSSFeedURL": "Adresse du flux RSS", "LabelSearchTerm": "Terme de recherche", @@ -651,4 +651,4 @@ "ToastSocketFailedToConnect": "Échec de la connexion WebSocket", "ToastUserDeleteFailed": "Échec de la suppression de l’utilisateur", "ToastUserDeleteSuccess": "Utilisateur supprimé" -} \ No newline at end of file +} From cc1b41995df82ef347eae2010b495c5be49b70c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannik=20Ku=CC=88hnemundt?= Date: Mon, 17 Apr 2023 23:17:40 +0200 Subject: [PATCH 09/36] Fixed german translation --- client/strings/de.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/client/strings/de.json b/client/strings/de.json index ae80a73c..a5bb2a4f 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -20,7 +20,7 @@ "ButtonCreate": "Erstellen", "ButtonCreateBackup": "Sicherung erstellen", "ButtonDelete": "Löschen", - "ButtonDownloadQueue": "Queue", + "ButtonDownloadQueue": "Download Warteschlange", "ButtonEdit": "Bearbeiten", "ButtonEditChapters": "Kapitel bearbeiten", "ButtonEditPodcast": "Podcast bearbeiten", @@ -93,9 +93,9 @@ "HeaderCollection": "Sammlungen", "HeaderCollectionItems": "Sammlungseinträge", "HeaderCover": "Titelbild", - "HeaderCurrentDownloads": "Current Downloads", + "HeaderCurrentDownloads": "Aktuelle Downloads", "HeaderDetails": "Details", - "HeaderDownloadQueue": "Download Queue", + "HeaderDownloadQueue": "Download Warteschlange", "HeaderEpisodes": "Episoden", "HeaderFiles": "Dateien", "HeaderFindChapters": "Kapitel suchen", @@ -290,8 +290,8 @@ "LabelNewestAuthors": "Neuste Autoren", "LabelNewestEpisodes": "Neueste Episoden", "LabelNewPassword": "Neues Passwort", - "LabelNextBackupDate": "Next backup date", - "LabelNextScheduledRun": "Next scheduled run", + "LabelNextBackupDate": "Nächstes Sicherungsdatum", + "LabelNextScheduledRun": "Nächster planmäßiger Durchlauf", "LabelNotes": "Hinweise", "LabelNotFinished": "nicht beendet", "LabelNotificationAppriseURL": "Apprise URL(s)", @@ -336,10 +336,10 @@ "LabelRegion": "Region", "LabelReleaseDate": "Veröffentlichungsdatum", "LabelRemoveCover": "Lösche Titelbild", - "LabelRSSFeedCustomOwnerEmail": "Custom owner Email", - "LabelRSSFeedCustomOwnerName": "Custom owner Name", + "LabelRSSFeedCustomOwnerEmail": "Benutzerdefinierte Eigentümer-E-Mail", + "LabelRSSFeedCustomOwnerName": "Benutzerdefinierter Name des Eigentümers", "LabelRSSFeedOpen": "RSS Feed Offen", - "LabelRSSFeedPreventIndexing": "Prevent Indexing", + "LabelRSSFeedPreventIndexing": "Indizierung verhindern", "LabelRSSFeedSlug": "RSS Feed Schlagwort", "LabelRSSFeedURL": "RSS Feed URL", "LabelSearchTerm": "Begriff suchen", @@ -549,7 +549,7 @@ "MessageRemoveAllItemsWarning": "WARNUNG! Bei dieser Aktion werden alle Bibliotheksobjekte aus der Datenbank entfernt, einschließlich aller Aktualisierungen oder Online-Abgleichs, die Sie vorgenommen haben. Ihre eigentlichen Dateien bleiben davon unberührt. Sind Sie sicher?", "MessageRemoveChapter": "Kapitel löschen", "MessageRemoveEpisodes": "Entferne {0} Episode(n)", - "MessageRemoveFromPlayerQueue": "Aus der Abspielwarteliste löschen Remove from player queue", + "MessageRemoveFromPlayerQueue": "Aus der Abspielwarteliste löschen", "MessageRemoveUserWarning": "Sind Sie sicher, dass Sie den Benutzer \"{0}\" dauerhaft löschen wollen?", "MessageReportBugsAndContribute": "Fehler melden, Funktionen anfordern und Beiträge leisten auf", "MessageResetChaptersConfirm": "Sind Sie sicher, dass Sie die Kapitel zurücksetzen und die vorgenommenen Änderungen rückgängig machen wollen?", @@ -581,7 +581,7 @@ "PlaceholderNewFolderPath": "Neuer Ordnerpfad", "PlaceholderNewPlaylist": "Neuer Wiedergabelistenname", "PlaceholderSearch": "Suche...", - "PlaceholderSearchEpisode": "Search episode...", + "PlaceholderSearchEpisode": "Suche Episode...", "ToastAccountUpdateFailed": "Aktualisierung des Kontos fehlgeschlagen", "ToastAccountUpdateSuccess": "Konto aktualisiert", "ToastAuthorImageRemoveFailed": "Bild konnte nicht entfernt werden", From 0bb1cf002d18746b167c177c5a814fa0f5645b6c Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 17 Apr 2023 17:03:58 -0500 Subject: [PATCH 10/36] Fix:Crash when podcasts put empty spaces with episode file path in RSS feed #1650 --- server/utils/podcastUtils.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/utils/podcastUtils.js b/server/utils/podcastUtils.js index 553ad7d1..58200349 100644 --- a/server/utils/podcastUtils.js +++ b/server/utils/podcastUtils.js @@ -85,6 +85,8 @@ function extractEpisodeData(item) { } } + episode.enclosure.url = episode.enclosure.url.trim() + // Full description with html if (item['content:encoded']) { const rawDescription = (extractFirstArrayItem(item, 'content:encoded') || '').trim() From 3b97e2146d3ab84100da8f2ec9ab9badd914cf3d Mon Sep 17 00:00:00 2001 From: Divyang Joshi Date: Mon, 17 Apr 2023 20:09:59 -0500 Subject: [PATCH 11/36] fix: Make sure all existing genres also show up on the match tab --- client/components/modals/item/tabs/Match.vue | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/components/modals/item/tabs/Match.vue b/client/components/modals/item/tabs/Match.vue index 8cf49bbd..58c52df8 100644 --- a/client/components/modals/item/tabs/Match.vue +++ b/client/components/modals/item/tabs/Match.vue @@ -115,7 +115,7 @@
- +

{{ $strings.LabelCurrently }} {{ mediaMetadata.genres.join(', ') }}

@@ -300,6 +300,12 @@ export default { }, isPodcast() { return this.mediaType == 'podcast' + }, + genres() { + const filterData = this.$store.state.libraries.filterData || {} + const currentGenres = filterData.genres || [] + const selectedMatchGenres = this.selectedMatch.genres || [] + return [...new Set([...currentGenres ,...selectedMatchGenres])] } }, methods: { From 8790166ac18f806addb902cad0516cb9dd5289c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannik=20Ku=CC=88hnemundt?= Date: Tue, 18 Apr 2023 16:37:34 +0200 Subject: [PATCH 12/36] Fix further translation --- client/strings/de.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/client/strings/de.json b/client/strings/de.json index a5bb2a4f..58246580 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -186,7 +186,7 @@ "LabelBitrate": "Bitrate", "LabelBooks": "Bücher", "LabelChangePassword": "Passwort ändern", - "LabelChannels": "Channels", + "LabelChannels": "Kanäle", "LabelChapters": "Chapters", "LabelChaptersFound": "gefundene Kapitel", "LabelChapterTitle": "Kapitelüberschrift", @@ -204,7 +204,7 @@ "LabelCronExpression": "Cron Ausdruck", "LabelCurrent": "Aktuell", "LabelCurrently": "Aktuell:", - "LabelCustomCronExpression": "Custom Cron Expression:", + "LabelCustomCronExpression": "Benutzerdefinierter Cron-Ausdruck", "LabelDatetime": "Datum & Uhrzeit", "LabelDescription": "Beschreibung", "LabelDeselectAll": "Alles abwählen", @@ -217,7 +217,7 @@ "LabelDuration": "Laufzeit", "LabelDurationFound": "Gefundene Laufzeit:", "LabelEdit": "Bearbeiten", - "LabelEmbeddedCover": "Embedded Cover", + "LabelEmbeddedCover": "Eingebettetes Cover", "LabelEnable": "Aktivieren", "LabelEnd": "Ende", "LabelEpisode": "Episode", @@ -324,7 +324,7 @@ "LabelPodcasts": "Podcasts", "LabelPodcastType": "Podcast Type", "LabelPrefixesToIgnore": "Zu ignorierende(s) Vorwort(e) (Groß- und Kleinschreibung wird nicht berücksichtigt)", - "LabelPreventIndexing": "Prevent your feed from being indexed by iTunes and Google podcast directories", + "LabelPreventIndexing": "Verhindere, dass dein Feed von iTunes- und Google-Podcast-Verzeichnissen indiziert wird", "LabelProgress": "Fortschritt", "LabelProvider": "Anbieter", "LabelPubDate": "Veröffentlichungsdatum", @@ -332,7 +332,7 @@ "LabelPublishYear": "Jahr", "LabelRecentlyAdded": "Kürzlich hinzugefügt", "LabelRecentSeries": "Aktuelle Serien", - "LabelRecommended": "Recommended", + "LabelRecommended": "Empfohlen", "LabelRegion": "Region", "LabelReleaseDate": "Veröffentlichungsdatum", "LabelRemoveCover": "Lösche Titelbild", @@ -468,7 +468,7 @@ "MessageChapterEndIsAfter": "Ungültige Kapitelendzeit: Kapitelende > Mediumende (Kapitelende liegt nach dem Ende des Mediums)", "MessageChapterErrorFirstNotZero": "Ungültige Kapitelstartzeit: Das erste Kapitel muss bei 0 beginnen", "MessageChapterErrorStartGteDuration": "Ungültige Kapitelstartzeit: Kapitelanfang > Mediumlänge (Kapitelanfang liegt zeitlich nach dem Ende des Mediums -> Lösung: Kapitelanfang < Mediumlänge)", - "MessageChapterErrorStartLtPrev": "Ungültige Kapitelstartzeit: Kapitelanfang < Kapitelanfang vorheriges Kapitel (Kapitelanfang liegt zeitlich vor dem Beginn des vorherigen Kapitels -> Lösung: Kapitelanfang >= Startzeit des vorherigen Kapitels)", + "MessageChapterErrorStartLtPrev": "Ungültige Kapitelstartzeit: Kapitelanfang < Kapitelanfang vorheriges Kapitel (Kapitelanfang liegt zeitlich vor dem Beginn des vorherigen Kapitels -> Lösung: Kapitelanfang >= Startzeit des vorherigen Kapitels)", "MessageChapterStartIsAfter": "Ungültige Kapitelstartzeit: Kapitelanfang > Mediumende (Kapitelanfang liegt nach dem Ende des Mediums)", "MessageCheckingCron": "Überprüfe cron...", "MessageConfirmDeleteBackup": "Sind Sie sicher, dass Sie die Sicherung für {0} löschen wollen?", @@ -517,8 +517,8 @@ "MessageNoCollections": "Keine Sammlungen", "MessageNoCoversFound": "Keine Titelbilder gefunden", "MessageNoDescription": "Keine Beschreibung", - "MessageNoDownloadsInProgress": "No downloads currently in progress", - "MessageNoDownloadsQueued": "No downloads queued", + "MessageNoDownloadsInProgress": "Derzeit keine Downloads in Arbeit", + "MessageNoDownloadsQueued": "Keine Downloads in der Warteschlange", "MessageNoEpisodeMatchesFound": "Keine Episodenübereinstimmungen gefunden", "MessageNoEpisodes": "Keine Episoden", "MessageNoFoldersAvailable": "Keine Ordner verfügbar", @@ -535,7 +535,7 @@ "MessageNoSearchResultsFor": "Keine Suchergebnisse für \"{0}\"", "MessageNoSeries": "Keine Serien", "MessageNoTags": "Keine Tags", - "MessageNoTasksRunning": "No Tasks Running", + "MessageNoTasksRunning": "Keine laufenden Aufgaben", "MessageNotYetImplemented": "Noch nicht implementiert", "MessageNoUpdateNecessary": "Keine Aktualisierung erforderlich", "MessageNoUpdatesWereNecessary": "Keine Aktualisierungen waren notwendig", From 6cb9dfaa85e67a00c6c70c9a6d3695a033b587a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannik=20K=C3=BChnemundt?= Date: Tue, 18 Apr 2023 17:55:59 +0200 Subject: [PATCH 13/36] Update de.json --- client/strings/de.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/strings/de.json b/client/strings/de.json index 58246580..4b9188e5 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -20,7 +20,7 @@ "ButtonCreate": "Erstellen", "ButtonCreateBackup": "Sicherung erstellen", "ButtonDelete": "Löschen", - "ButtonDownloadQueue": "Download Warteschlange", + "ButtonDownloadQueue": "Warteschlange", "ButtonEdit": "Bearbeiten", "ButtonEditChapters": "Kapitel bearbeiten", "ButtonEditPodcast": "Podcast bearbeiten", From cc5e92ec8e6ae8b16dada95402d37bedb408cb81 Mon Sep 17 00:00:00 2001 From: advplyr <67830747+advplyr@users.noreply.github.com> Date: Tue, 18 Apr 2023 18:06:22 -0500 Subject: [PATCH 14/36] Update client/components/modals/notification/NotificationEditModal.vue --- client/components/modals/notification/NotificationEditModal.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/modals/notification/NotificationEditModal.vue b/client/components/modals/notification/NotificationEditModal.vue index 9693f292..2fc10ce6 100644 --- a/client/components/modals/notification/NotificationEditModal.vue +++ b/client/components/modals/notification/NotificationEditModal.vue @@ -103,7 +103,7 @@ export default { if (this.$refs.modal) this.$refs.modal.setHide() }, submitForm() { - this.$ref.urlsInput.blur() + this.$refs.urlsInput?.forceBlur() if (!this.newNotification.urls.length) { this.$toast.error('Must enter an Apprise URL') From c7b4b3bd3e7e9c85b511c002ca2f587cdae42985 Mon Sep 17 00:00:00 2001 From: Alex Jackson Date: Wed, 19 Apr 2023 16:22:08 -0500 Subject: [PATCH 15/36] Update:Bump tone version Addresses #1703. Paths with quotations were not handled by tone Date: Wed, 19 Apr 2023 16:40:46 -0500 Subject: [PATCH 16/36] Update:Bump tone version in devcontainer to v0.1.5 --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 766273e7..968e97c4 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -12,4 +12,4 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* # Move tone executable to appropriate directory -COPY --from=sandreas/tone:v0.1.2 /usr/local/bin/tone /usr/local/bin/ +COPY --from=sandreas/tone:v0.1.5 /usr/local/bin/tone /usr/local/bin/ From e2dd66d450ad154fd4ae166c40ef0739f007aab1 Mon Sep 17 00:00:00 2001 From: coldshouldermedia <54368516+coldshouldermedia@users.noreply.github.com> Date: Wed, 19 Apr 2023 16:19:29 -0600 Subject: [PATCH 17/36] Updated SWAG Reverse Proxy guide --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a3508126..4146df20 100644 --- a/readme.md +++ b/readme.md @@ -147,7 +147,7 @@ For this to work you must enable at least the following mods using `a2enmod`: ### SWAG Reverse Proxy -[See this solution](https://forums.unraid.net/topic/112698-support-audiobookshelf/?do=findComment&comment=1049637) +LinuxServer.io now ships SWAG with reverse proxy [subdomain](https://github.com/linuxserver/reverse-proxy-confs/blob/master/audiobookshelf.subdomain.conf.sample) and [subfolder](https://github.com/linuxserver/reverse-proxy-confs/blob/master/airsonic.subfolder.conf.sample) config samples for audiobookshelf. ### Synology Reverse Proxy From 3b6fa73ac0c444d52bc0b9b0da0a35ce61136e5b Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 19 Apr 2023 17:22:25 -0500 Subject: [PATCH 18/36] Update tone in debian package to v0.1.5 --- build/debian/DEBIAN/preinst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/debian/DEBIAN/preinst b/build/debian/DEBIAN/preinst index be2d8b79..ad6657ef 100644 --- a/build/debian/DEBIAN/preinst +++ b/build/debian/DEBIAN/preinst @@ -50,7 +50,7 @@ install_ffmpeg() { echo "Starting FFMPEG Install" WGET="wget https://johnvansickle.com/ffmpeg/builds/ffmpeg-git-amd64-static.tar.xz --output-document=ffmpeg-git-amd64-static.tar.xz" - WGET_TONE="wget https://github.com/sandreas/tone/releases/download/v0.1.2/tone-0.1.2-linux-x64.tar.gz --output-document=tone-0.1.2-linux-x64.tar.gz" + WGET_TONE="wget https://github.com/sandreas/tone/releases/download/v0.1.5/tone-0.1.5-linux-x64.tar.gz --output-document=tone-0.1.5-linux-x64.tar.gz" if ! cd "$FFMPEG_INSTALL_DIR"; then echo "Creating ffmpeg install dir at $FFMPEG_INSTALL_DIR" @@ -66,8 +66,8 @@ install_ffmpeg() { # Temp downloading tone library to the ffmpeg dir echo "Getting tone.." $WGET_TONE - tar xvf tone-0.1.2-linux-x64.tar.gz --strip-components=1 - rm tone-0.1.2-linux-x64.tar.gz + tar xvf tone-0.1.5-linux-x64.tar.gz --strip-components=1 + rm tone-0.1.5-linux-x64.tar.gz echo "Good to go on Ffmpeg (& tone)... hopefully" } From 6ae3ad508e915efbb21ac28a9d180f91f3e3e1af Mon Sep 17 00:00:00 2001 From: advplyr Date: Wed, 19 Apr 2023 18:03:43 -0500 Subject: [PATCH 19/36] Readme update --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 4146df20..8077edae 100644 --- a/readme.md +++ b/readme.md @@ -147,7 +147,7 @@ For this to work you must enable at least the following mods using `a2enmod`: ### SWAG Reverse Proxy -LinuxServer.io now ships SWAG with reverse proxy [subdomain](https://github.com/linuxserver/reverse-proxy-confs/blob/master/audiobookshelf.subdomain.conf.sample) and [subfolder](https://github.com/linuxserver/reverse-proxy-confs/blob/master/airsonic.subfolder.conf.sample) config samples for audiobookshelf. +[See LinuxServer.io config sample](https://github.com/linuxserver/reverse-proxy-confs/blob/master/audiobookshelf.subdomain.conf.sample) ### Synology Reverse Proxy From f9ed412e4e47a781b7febdbfa1c2b250b4414f12 Mon Sep 17 00:00:00 2001 From: Spenser Bushey Date: Wed, 19 Apr 2023 22:13:52 -0700 Subject: [PATCH 20/36] Add AudiobookCovers.com metadata provider AudiobookCovers.com acts as a cover-only metadata provider, therefore will only show up in the covers selector. --- client/components/modals/item/tabs/Cover.vue | 6 ++--- client/store/scanners.js | 6 +++++ server/finders/BookFinder.js | 16 ++++++++++++ server/providers/AudiobookCovers.js | 26 ++++++++++++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 server/providers/AudiobookCovers.js diff --git a/client/components/modals/item/tabs/Cover.vue b/client/components/modals/item/tabs/Cover.vue index 3cbad218..42931744 100644 --- a/client/components/modals/item/tabs/Cover.vue +++ b/client/components/modals/item/tabs/Cover.vue @@ -49,13 +49,13 @@
-
+
-
+
{{ $strings.ButtonSearch }} @@ -128,7 +128,7 @@ export default { }, providers() { if (this.isPodcast) return this.$store.state.scanners.podcastProviders - return this.$store.state.scanners.providers + return [...this.$store.state.scanners.providers, ...this.$store.state.scanners.coverOnlyProviders] }, searchTitleLabel() { if (this.provider.startsWith('audible')) return this.$strings.LabelSearchTitleOrASIN diff --git a/client/store/scanners.js b/client/store/scanners.js index 0a339a03..9a330f4f 100644 --- a/client/store/scanners.js +++ b/client/store/scanners.js @@ -63,6 +63,12 @@ export const state = () => ({ text: 'iTunes', value: 'itunes' } + ], + coverOnlyProviders: [ + { + text: 'AudiobookCovers.com', + value: 'audiobookcovers' + } ] }) diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index a7d5b730..7716301f 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -4,6 +4,7 @@ const Audible = require('../providers/Audible') const iTunes = require('../providers/iTunes') const Audnexus = require('../providers/Audnexus') const FantLab = require('../providers/FantLab') +const AudiobookCovers = require('../providers/AudiobookCovers') const Logger = require('../Logger') const { levenshteinDistance } = require('../utils/index') @@ -15,6 +16,7 @@ class BookFinder { this.iTunesApi = new iTunes() this.audnexus = new Audnexus() this.fantLab = new FantLab() + this.audiobookCovers = new AudiobookCovers() this.verbose = false } @@ -159,6 +161,16 @@ class BookFinder { return books } + async getAudiobookCoversResults(search) { + const covers = await this.audiobookCovers.search(search) + if (this.verbose) Logger.debug(`AudiobookCovers Book Search Results: ${books.length || 0}`) + if (covers.errorCode) { + Logger.error(`AusiobookCovers Search Error ${books.errorCode}`) + return [] + } + return covers + } + async getiTunesAudiobooksResults(title, author) { return this.iTunesApi.searchAudiobooks(title) } @@ -187,11 +199,15 @@ class BookFinder { books = await this.getOpenLibResults(title, author, maxTitleDistance, maxAuthorDistance) } else if (provider === 'fantlab') { books = await this.getFantLabResults(title, author) + } else if (provider === 'audiobookcovers') { + books = await this.getAudiobookCoversResults(title) } else { books = await this.getGoogleBooksResults(title, author) } + console.log(books) + if (!books.length && !options.currentlyTryingCleaned) { var cleanedTitle = this.cleanTitleForCompares(title) var cleanedAuthor = this.cleanAuthorForCompares(author) diff --git a/server/providers/AudiobookCovers.js b/server/providers/AudiobookCovers.js new file mode 100644 index 00000000..b969fba3 --- /dev/null +++ b/server/providers/AudiobookCovers.js @@ -0,0 +1,26 @@ +const axios = require('axios') +const Logger = require('../Logger') + +class AudiobookCovers { + constructor() { } + + async search(search) { + const url = `https://api.audiobookcovers.com/cover/bytext/` + const params = new URLSearchParams([['q', search]]) + const items = await axios.get(url, { params }).then((res) => { + if (!res || !res.data) return [] + return res.data + }).catch(error => { + Logger.error('[AudiobookCovers] Cover search error', error) + return [] + }) + // console.log(items) + // return items as an array of objects, each object contains: + // cover: item.filename + return items.map(item => { return { cover: item.filename } }) + } +} + + + +module.exports = AudiobookCovers From c32efb8db8a94d165e1f82f6f87a148a03b17657 Mon Sep 17 00:00:00 2001 From: advplyr Date: Thu, 20 Apr 2023 17:51:06 -0500 Subject: [PATCH 21/36] Fix:Podcast episode search modal search filter #1699 --- .../components/modals/podcast/EpisodeFeed.vue | 43 +++++++++++-------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/client/components/modals/podcast/EpisodeFeed.vue b/client/components/modals/podcast/EpisodeFeed.vue index 5092d8b1..9c199325 100644 --- a/client/components/modals/podcast/EpisodeFeed.vue +++ b/client/components/modals/podcast/EpisodeFeed.vue @@ -6,7 +6,7 @@
-
+
@@ -16,12 +16,12 @@ v-for="(episode, index) in episodesList" :key="index" class="relative" - :class="itemEpisodeMap[episode.enclosure.url?.split('?')[0]] ? 'bg-primary bg-opacity-40' : selectedEpisodes[String(index)] ? 'cursor-pointer bg-success bg-opacity-10' : index % 2 == 0 ? 'cursor-pointer bg-primary bg-opacity-25 hover:bg-opacity-40' : 'cursor-pointer bg-primary bg-opacity-5 hover:bg-opacity-25'" - @click="toggleSelectEpisode(index, episode)" + :class="itemEpisodeMap[episode.cleanUrl] ? 'bg-primary bg-opacity-40' : selectedEpisodes[episode.cleanUrl] ? 'cursor-pointer bg-success bg-opacity-10' : index % 2 == 0 ? 'cursor-pointer bg-primary bg-opacity-25 hover:bg-opacity-40' : 'cursor-pointer bg-primary bg-opacity-5 hover:bg-opacity-25'" + @click="toggleSelectEpisode(episode)" >
- download_done - + download_done +
@@ -63,6 +63,7 @@ export default { data() { return { processing: false, + episodesCleaned: [], selectedEpisodes: {}, selectAll: false, search: null, @@ -92,7 +93,7 @@ export default { return this.libraryItem.media.metadata.title || 'Unknown' }, allDownloaded() { - return !this.episodes.some((episode) => !this.itemEpisodeMap[episode.enclosure.url?.split('?')[0]]) + return !this.episodesCleaned.some((episode) => !this.itemEpisodeMap[episode.cleanUrl]) }, episodesSelected() { return Object.keys(this.selectedEpisodes).filter((key) => !!this.selectedEpisodes[key]) @@ -113,7 +114,7 @@ export default { return map }, episodesList() { - return this.episodes.filter((episode) => { + return this.episodesCleaned.filter((episode) => { if (!this.searchText) return true return (episode.title && episode.title.toLowerCase().includes(this.searchText)) || (episode.subtitle && episode.subtitle.toLowerCase().includes(this.searchText)) }) @@ -131,31 +132,29 @@ export default { }, 500) }, toggleSelectAll(val) { - for (let i = 0; i < this.episodes.length; i++) { - const episode = this.episodes[i] - if (this.itemEpisodeMap[episode.enclosure.url?.split('?')[0]]) this.selectedEpisodes[String(i)] = false - else this.$set(this.selectedEpisodes, String(i), val) + for (const episode of this.episodesCleaned) { + if (this.itemEpisodeMap[episode.cleanUrl]) this.selectedEpisodes[episode.cleanUrl] = false + else this.$set(this.selectedEpisodes, episode.cleanUrl, val) } }, checkSetIsSelectedAll() { - for (let i = 0; i < this.episodes.length; i++) { - const episode = this.episodes[i] - if (!this.itemEpisodeMap[episode.enclosure.url?.split('?')[0]] && !this.selectedEpisodes[String(i)]) { + for (const episode of this.episodesCleaned) { + if (!this.itemEpisodeMap[episode.cleanUrl] && !this.selectedEpisodes[episode.cleanUrl]) { this.selectAll = false return } } this.selectAll = true }, - toggleSelectEpisode(index, episode) { + toggleSelectEpisode(episode) { if (this.itemEpisodeMap[episode.enclosure.url?.split('?')[0]]) return - this.$set(this.selectedEpisodes, String(index), !this.selectedEpisodes[String(index)]) + this.$set(this.selectedEpisodes, episode.cleanUrl, !this.selectedEpisodes[episode.cleanUrl]) this.checkSetIsSelectedAll() }, submit() { var episodesToDownload = [] if (this.episodesSelected.length) { - episodesToDownload = this.episodesSelected.map((episodeIndex) => this.episodes[Number(episodeIndex)]) + episodesToDownload = this.episodesSelected.map((cleanUrl) => this.episodesCleaned.find((ep) => ep.cleanUrl == cleanUrl)) } var payloadSize = JSON.stringify(episodesToDownload).length @@ -185,7 +184,15 @@ export default { }) }, init() { - this.episodes.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1)) + this.episodesCleaned = this.episodes + .filter((ep) => ep.enclosure?.url) + .map((_ep) => { + return { + ..._ep, + cleanUrl: _ep.enclosure.url.split('?')[0] + } + }) + this.episodesCleaned.sort((a, b) => (a.publishedAt < b.publishedAt ? 1 : -1)) this.selectAll = false this.selectedEpisodes = {} } From 134289785833548c5468643990f95542a9d8d57a Mon Sep 17 00:00:00 2001 From: Spenser Bushey Date: Thu, 20 Apr 2023 16:39:04 -0700 Subject: [PATCH 22/36] Removed useless comments --- server/providers/AudiobookCovers.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/providers/AudiobookCovers.js b/server/providers/AudiobookCovers.js index b969fba3..ea03a9d2 100644 --- a/server/providers/AudiobookCovers.js +++ b/server/providers/AudiobookCovers.js @@ -14,9 +14,6 @@ class AudiobookCovers { Logger.error('[AudiobookCovers] Cover search error', error) return [] }) - // console.log(items) - // return items as an array of objects, each object contains: - // cover: item.filename return items.map(item => { return { cover: item.filename } }) } } From 84839bea44e85fdaecdd3e6b56bf9d7ff37f1066 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 21 Apr 2023 16:17:52 -0500 Subject: [PATCH 23/36] Cleanup audiobookcovers.com addition --- client/components/modals/item/tabs/Cover.vue | 2 +- server/finders/BookFinder.js | 20 ++++++++------------ server/providers/AudiobookCovers.js | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/client/components/modals/item/tabs/Cover.vue b/client/components/modals/item/tabs/Cover.vue index 42931744..ab63c7e8 100644 --- a/client/components/modals/item/tabs/Cover.vue +++ b/client/components/modals/item/tabs/Cover.vue @@ -49,7 +49,7 @@
-
+
diff --git a/server/finders/BookFinder.js b/server/finders/BookFinder.js index 7716301f..94981d14 100644 --- a/server/finders/BookFinder.js +++ b/server/finders/BookFinder.js @@ -163,12 +163,8 @@ class BookFinder { async getAudiobookCoversResults(search) { const covers = await this.audiobookCovers.search(search) - if (this.verbose) Logger.debug(`AudiobookCovers Book Search Results: ${books.length || 0}`) - if (covers.errorCode) { - Logger.error(`AusiobookCovers Search Error ${books.errorCode}`) - return [] - } - return covers + if (this.verbose) Logger.debug(`AudiobookCovers Search Results: ${covers.length || 0}`) + return covers || [] } async getiTunesAudiobooksResults(title, author) { @@ -206,8 +202,6 @@ class BookFinder { books = await this.getGoogleBooksResults(title, author) } - console.log(books) - if (!books.length && !options.currentlyTryingCleaned) { var cleanedTitle = this.cleanTitleForCompares(title) var cleanedAuthor = this.cleanAuthorForCompares(author) @@ -218,11 +212,13 @@ class BookFinder { return this.search(provider, cleanedTitle, cleanedAuthor, isbn, asin, options) } - if (["google", "audible", "itunes", 'fantlab'].includes(provider)) return books + if (provider === 'openlibrary') { + books.sort((a, b) => { + return a.totalDistance - b.totalDistance + }) + } - return books.sort((a, b) => { - return a.totalDistance - b.totalDistance - }) + return books } async findCovers(provider, title, author, options = {}) { diff --git a/server/providers/AudiobookCovers.js b/server/providers/AudiobookCovers.js index ea03a9d2..edb1b199 100644 --- a/server/providers/AudiobookCovers.js +++ b/server/providers/AudiobookCovers.js @@ -14,7 +14,7 @@ class AudiobookCovers { Logger.error('[AudiobookCovers] Cover search error', error) return [] }) - return items.map(item => { return { cover: item.filename } }) + return items.map(item => ({ cover: item.filename })) } } From dadd41cb5c221c8c79f1a26a0bebd3d740b51539 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 21 Apr 2023 17:49:25 -0500 Subject: [PATCH 24/36] Fix:Podcast episode quick match crash #1711 --- client/components/modals/podcast/tabs/EpisodeDetails.vue | 7 ++++--- server/utils/areEquivalent.js | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/components/modals/podcast/tabs/EpisodeDetails.vue b/client/components/modals/podcast/tabs/EpisodeDetails.vue index debf9155..d7130eba 100644 --- a/client/components/modals/podcast/tabs/EpisodeDetails.vue +++ b/client/components/modals/podcast/tabs/EpisodeDetails.vue @@ -31,9 +31,10 @@ {{ $strings.ButtonSave }}
-
-

Episode URL from RSS feed

- {{ enclosureUrl }} +
+ + +

Episode not linked to RSS feed episode

diff --git a/server/utils/areEquivalent.js b/server/utils/areEquivalent.js index fb10463c..94a1901e 100644 --- a/server/utils/areEquivalent.js +++ b/server/utils/areEquivalent.js @@ -25,6 +25,11 @@ module.exports = function areEquivalent(value1, value2, stack = []) { return true; } + // Truthy check to handle value1=null, value2=Object + if ((value1 && !value2) || (!value1 && value2)) { + return false + } + const type1 = typeof value1; // Ensure types match From 33f20d54cccc5e5e919715d144e50633b7da2a48 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 23 Apr 2023 17:08:36 -0500 Subject: [PATCH 25/36] Updated docker template xml --- docker-template.xml | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/docker-template.xml b/docker-template.xml index 229f2774..cdd72b10 100644 --- a/docker-template.xml +++ b/docker-template.xml @@ -48,21 +48,9 @@ rw - - - 99 - AUDIOBOOKSHELF_UID - - - - 100 - AUDIOBOOKSHELF_GID - - - /mnt/user/appdata/audiobookshelf/config/ /mnt/user/appdata/audiobookshelf/metadata/ 13378 - + \ No newline at end of file From a5627a1b52b942d15444dfd0bd53c40a441f87c4 Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 24 Apr 2023 18:25:30 -0500 Subject: [PATCH 26/36] Add:Search for narrators #1495 --- .../components/app/BookShelfCategorized.vue | 29 +++-- client/components/app/BookShelfRow.vue | 6 ++ client/components/cards/ItemSearchCard.vue | 9 +- client/components/cards/NarratorCard.vue | 50 +++++++++ .../components/cards/NarratorSearchCard.vue | 34 ++++++ client/components/controls/GlobalSearch.vue | 14 ++- client/components/widgets/NarratorsSlider.vue | 100 ++++++++++++++++++ client/pages/library/_library/search.vue | 33 +++--- server/controllers/LibraryController.js | 19 +++- server/objects/mediaTypes/Book.js | 7 +- server/objects/metadata/BookMetadata.js | 3 + 11 files changed, 271 insertions(+), 33 deletions(-) create mode 100644 client/components/cards/NarratorCard.vue create mode 100644 client/components/cards/NarratorSearchCard.vue create mode 100644 client/components/widgets/NarratorsSlider.vue diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index fe1d5df5..ca070e18 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -28,6 +28,9 @@

{{ $strings[shelf.labelStringKey] }}

+ +

{{ $strings[shelf.labelStringKey] }}

+
@@ -185,8 +188,8 @@ export default { this.shelves = categories }, async setShelvesFromSearch() { - var shelves = [] - if (this.results.books && this.results.books.length) { + const shelves = [] + if (this.results.books?.length) { shelves.push({ id: 'books', label: 'Books', @@ -196,7 +199,7 @@ export default { }) } - if (this.results.podcasts && this.results.podcasts.length) { + if (this.results.podcasts?.length) { shelves.push({ id: 'podcasts', label: 'Podcasts', @@ -206,7 +209,7 @@ export default { }) } - if (this.results.series && this.results.series.length) { + if (this.results.series?.length) { shelves.push({ id: 'series', label: 'Series', @@ -221,7 +224,7 @@ export default { }) }) } - if (this.results.tags && this.results.tags.length) { + if (this.results.tags?.length) { shelves.push({ id: 'tags', label: 'Tags', @@ -236,7 +239,7 @@ export default { }) }) } - if (this.results.authors && this.results.authors.length) { + if (this.results.authors?.length) { shelves.push({ id: 'authors', label: 'Authors', @@ -250,6 +253,20 @@ export default { }) }) } + if (this.results.narrators?.length) { + shelves.push({ + id: 'narrators', + label: 'Narrators', + labelStringKey: 'LabelNarrators', + type: 'narrators', + entities: this.results.narrators.map((n) => { + return { + ...n, + type: 'narrator' + } + }) + }) + } this.shelves = shelves }, scan() { diff --git a/client/components/app/BookShelfRow.vue b/client/components/app/BookShelfRow.vue index bd97d56a..60632db7 100644 --- a/client/components/app/BookShelfRow.vue +++ b/client/components/app/BookShelfRow.vue @@ -41,6 +41,11 @@
+
+ +
@@ -88,6 +93,7 @@ export default { return this.bookCoverWidth * this.bookCoverAspectRatio }, shelfHeight() { + if (this.shelf.type === 'narrators') return 148 return this.bookCoverHeight + 48 }, paddingLeft() { diff --git a/client/components/cards/ItemSearchCard.vue b/client/components/cards/ItemSearchCard.vue index f29888e5..1784db0f 100644 --- a/client/components/cards/ItemSearchCard.vue +++ b/client/components/cards/ItemSearchCard.vue @@ -10,7 +10,7 @@

by {{ authorName }}

-

+
@@ -67,12 +67,13 @@ export default { // but with removing commas periods etc this is no longer plausible const html = this.matchText - if (this.matchKey === 'episode') return `

Episode: ${html}

` - if (this.matchKey === 'tags') return `

Tags: ${html}

` + if (this.matchKey === 'episode') return `

${this.$strings.LabelEpisode}: ${html}

` + if (this.matchKey === 'tags') return `

${this.$strings.LabelTags}: ${html}

` if (this.matchKey === 'authors') return `by ${html}` if (this.matchKey === 'isbn') return `

ISBN: ${html}

` if (this.matchKey === 'asin') return `

ASIN: ${html}

` - if (this.matchKey === 'series') return `

Series: ${html}

` + if (this.matchKey === 'series') return `

${this.$strings.LabelSeries}: ${html}

` + if (this.matchKey === 'narrators') return `

${this.$strings.LabelNarrator}: ${html}

` return `${html}` } }, diff --git a/client/components/cards/NarratorCard.vue b/client/components/cards/NarratorCard.vue new file mode 100644 index 00000000..9d3e5a30 --- /dev/null +++ b/client/components/cards/NarratorCard.vue @@ -0,0 +1,50 @@ + + + \ No newline at end of file diff --git a/client/components/cards/NarratorSearchCard.vue b/client/components/cards/NarratorSearchCard.vue new file mode 100644 index 00000000..e147e079 --- /dev/null +++ b/client/components/cards/NarratorSearchCard.vue @@ -0,0 +1,34 @@ + + + + + \ No newline at end of file diff --git a/client/components/controls/GlobalSearch.vue b/client/components/controls/GlobalSearch.vue index 670eb6e7..3dbebf1c 100644 --- a/client/components/controls/GlobalSearch.vue +++ b/client/components/controls/GlobalSearch.vue @@ -63,6 +63,15 @@ + +

{{ $strings.LabelNarrators }}

+
@@ -84,6 +93,7 @@ export default { authorResults: [], seriesResults: [], tagResults: [], + narratorResults: [], searchTimeout: null, lastSearch: null } @@ -114,6 +124,7 @@ export default { this.authorResults = [] this.seriesResults = [] this.tagResults = [] + this.narratorResults = [] this.showMenu = false this.isFetching = false this.isTyping = false @@ -142,7 +153,7 @@ export default { } this.isFetching = true - var searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${value}&limit=3`).catch((error) => { + const searchResults = await this.$axios.$get(`/api/libraries/${this.currentLibraryId}/search?q=${value}&limit=3`).catch((error) => { console.error('Search error', error) return [] }) @@ -155,6 +166,7 @@ export default { this.authorResults = searchResults.authors || [] this.seriesResults = searchResults.series || [] this.tagResults = searchResults.tags || [] + this.narratorResults = searchResults.narrators || [] this.isFetching = false if (!this.showMenu) { diff --git a/client/components/widgets/NarratorsSlider.vue b/client/components/widgets/NarratorsSlider.vue new file mode 100644 index 00000000..77302f6f --- /dev/null +++ b/client/components/widgets/NarratorsSlider.vue @@ -0,0 +1,100 @@ + + + \ No newline at end of file diff --git a/client/pages/library/_library/search.vue b/client/pages/library/_library/search.vue index b338323f..2f43d202 100644 --- a/client/pages/library/_library/search.vue +++ b/client/pages/library/_library/search.vue @@ -11,27 +11,27 @@