From d990e5b9094e13408651204589bd8886e1a90a5b Mon Sep 17 00:00:00 2001 From: mikiher Date: Sun, 12 Nov 2023 13:30:23 +0000 Subject: [PATCH 01/17] Add NFO metadata source --- .../components/modals/libraries/EditModal.vue | 2 +- .../libraries/LibraryScannerSettings.vue | 5 + server/objects/settings/LibrarySettings.js | 4 +- server/scanner/BookScanner.js | 11 ++- server/scanner/LibraryItemScanData.js | 5 + server/scanner/NfoFileScanner.js | 48 ++++++++++ server/utils/parsers/parseNfoMetadata.js | 94 +++++++++++++++++++ 7 files changed, 165 insertions(+), 4 deletions(-) create mode 100644 server/scanner/NfoFileScanner.js create mode 100644 server/utils/parsers/parseNfoMetadata.js diff --git a/client/components/modals/libraries/EditModal.vue b/client/components/modals/libraries/EditModal.vue index 5bcdabed..2a68dd63 100644 --- a/client/components/modals/libraries/EditModal.vue +++ b/client/components/modals/libraries/EditModal.vue @@ -127,7 +127,7 @@ export default { skipMatchingMediaWithIsbn: false, autoScanCronExpression: null, hideSingleBookSeries: false, - metadataPrecedence: ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata'] + metadataPrecedence: ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] } } }, diff --git a/client/components/modals/libraries/LibraryScannerSettings.vue b/client/components/modals/libraries/LibraryScannerSettings.vue index 215f79b5..253d4e6b 100644 --- a/client/components/modals/libraries/LibraryScannerSettings.vue +++ b/client/components/modals/libraries/LibraryScannerSettings.vue @@ -64,6 +64,11 @@ export default { name: 'Audio file meta tags', include: true }, + nfoFile: { + id: 'nfoFile', + name: 'NFO file', + include: true + }, txtFiles: { id: 'txtFiles', name: 'desc.txt & reader.txt files', diff --git a/server/objects/settings/LibrarySettings.js b/server/objects/settings/LibrarySettings.js index b734b6bf..10ee19e0 100644 --- a/server/objects/settings/LibrarySettings.js +++ b/server/objects/settings/LibrarySettings.js @@ -9,7 +9,7 @@ class LibrarySettings { this.autoScanCronExpression = null this.audiobooksOnly = false this.hideSingleBookSeries = false // Do not show series that only have 1 book - this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata'] + this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] if (settings) { this.construct(settings) @@ -28,7 +28,7 @@ class LibrarySettings { this.metadataPrecedence = [...settings.metadataPrecedence] } else { // Added in v2.4.5 - this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata'] + this.metadataPrecedence = ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] } } diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js index 282155f2..48e8529a 100644 --- a/server/scanner/BookScanner.js +++ b/server/scanner/BookScanner.js @@ -18,6 +18,7 @@ const BookFinder = require('../finders/BookFinder') const LibraryScan = require("./LibraryScan") const OpfFileScanner = require('./OpfFileScanner') +const NfoFileScanner = require('./NfoFileScanner') const AbsMetadataFileScanner = require('./AbsMetadataFileScanner') /** @@ -593,7 +594,7 @@ class BookScanner { } const bookMetadataSourceHandler = new BookScanner.BookMetadataSourceHandler(bookMetadata, audioFiles, libraryItemData, libraryScan, existingLibraryItemId) - const metadataPrecedence = librarySettings.metadataPrecedence || ['folderStructure', 'audioMetatags', 'txtFiles', 'opfFile', 'absMetadata'] + const metadataPrecedence = librarySettings.metadataPrecedence || ['folderStructure', 'audioMetatags', 'nfoFile', 'txtFiles', 'opfFile', 'absMetadata'] libraryScan.addLog(LogLevel.DEBUG, `"${bookMetadata.title}" Getting metadata with precedence [${metadataPrecedence.join(', ')}]`) for (const metadataSource of metadataPrecedence) { if (bookMetadataSourceHandler[metadataSource]) { @@ -649,6 +650,14 @@ class BookScanner { AudioFileScanner.setBookMetadataFromAudioMetaTags(bookTitle, this.audioFiles, this.bookMetadata, this.libraryScan) } + /** + * Metadata from .nfo file + */ + async nfoFile() { + if (!this.libraryItemData.metadataNfoLibraryFile) return + await NfoFileScanner.scanBookNfoFile(this.libraryItemData.metadataNfoLibraryFile, this.bookMetadata) + } + /** * Description from desc.txt and narrator from reader.txt */ diff --git a/server/scanner/LibraryItemScanData.js b/server/scanner/LibraryItemScanData.js index 576280c8..b604e4d7 100644 --- a/server/scanner/LibraryItemScanData.js +++ b/server/scanner/LibraryItemScanData.js @@ -132,6 +132,11 @@ class LibraryItemScanData { return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.opf') } + /** @type {LibraryItem.LibraryFileObject} */ + get metadataNfoLibraryFile() { + return this.libraryFiles.find(lf => lf.metadata.ext.toLowerCase() === '.nfo') + } + /** * * @param {LibraryItem} existingLibraryItem diff --git a/server/scanner/NfoFileScanner.js b/server/scanner/NfoFileScanner.js new file mode 100644 index 00000000..e450b5c3 --- /dev/null +++ b/server/scanner/NfoFileScanner.js @@ -0,0 +1,48 @@ +const { parseNfoMetadata } = require('../utils/parsers/parseNfoMetadata') +const { readTextFile } = require('../utils/fileUtils') + +class NfoFileScanner { + constructor() { } + + /** + * Parse metadata from .nfo file found in library scan and update bookMetadata + * + * @param {import('../models/LibraryItem').LibraryFileObject} nfoLibraryFileObj + * @param {Object} bookMetadata + */ + async scanBookNfoFile(nfoLibraryFileObj, bookMetadata) { + const nfoText = await readTextFile(nfoLibraryFileObj.metadata.path) + const nfoMetadata = nfoText ? await parseNfoMetadata(nfoText) : null + if (nfoMetadata) { + for (const key in nfoMetadata) { + if (key === 'tags') { // Add tags only if tags are empty + if (nfoMetadata.tags.length) { + bookMetadata.tags = nfoMetadata.tags + } + } else if (key === 'genres') { // Add genres only if genres are empty + if (nfoMetadata.genres.length) { + bookMetadata.genres = nfoMetadata.genres + } + } else if (key === 'authors') { + if (nfoMetadata.authors?.length) { + bookMetadata.authors = nfoMetadata.authors + } + } else if (key === 'narrators') { + if (nfoMetadata.narrators?.length) { + bookMetadata.narrators = nfoMetadata.narrators + } + } else if (key === 'series') { + if (nfoMetadata.series) { + bookMetadata.series = [{ + name: nfoMetadata.series, + sequence: nfoMetadata.sequence || null + }] + } + } else if (nfoMetadata[key] && key !== 'sequence') { + bookMetadata[key] = nfoMetadata[key] + } + } + } + } +} +module.exports = new NfoFileScanner() \ No newline at end of file diff --git a/server/utils/parsers/parseNfoMetadata.js b/server/utils/parsers/parseNfoMetadata.js new file mode 100644 index 00000000..a7fbbceb --- /dev/null +++ b/server/utils/parsers/parseNfoMetadata.js @@ -0,0 +1,94 @@ +function parseNfoMetadata(nfoText) { + if (!nfoText) return null + const lines = nfoText.split(/\r?\n/) + const metadata = {} + let insideBookDescription = false + lines.forEach(line => { + if (line.search(/^\s*book description\s*$/i) !== -1) { + insideBookDescription = true + return + } + if (insideBookDescription) { + if (line.search(/^\s*=+\s*$/i) !== -1) return + metadata.description = metadata.description || '' + metadata.description += line + '\n' + return + } + const match = line.match(/^(.*?):(.*)$/) + if (match) { + const key = match[1].toLowerCase().trim() + const value = match[2].trim() + if (!value) return + switch (key) { + case 'title': + { + const titleMatch = value.match(/^(.*?):(.*)$/) + if (titleMatch) { + metadata.title = titleMatch[1].trim() + metadata.subtitle = titleMatch[2].trim() + } else { + metadata.title = value + } + } + break + case 'author': + metadata.authors = value.split(/\s*,\s*/) + break + case 'narrator': + case 'read by': + metadata.narrators = value.split(/\s*,\s*/) + break + case 'series name': + metadata.series = value + break + case 'genre': + metadata.genres = value.split(/\s*,\s*/) + break + case 'tags': + metadata.tags = value.split(/\s*,\s*/) + break + case 'copyright': + case 'audible.com release': + case 'audiobook copyright': + case 'book copyright': + case 'recording copyright': + case 'release date': + case 'date': + { + const year = extractYear(value) + if (year) { + metadata.publishedYear = year + } + } + break; + case 'position in series': + metadata.sequence = value + break + case 'unabridged': + metadata.abridged = value.toLowerCase() === 'yes' ? false : true + break + case 'abridged': + metadata.abridged = value.toLowerCase() === 'no' ? false : true + break + case 'publisher': + metadata.publisher = value + break + case 'asin': + metadata.asin = value + break + case 'isbn': + case 'isbn-10': + case 'isbn-13': + metadata.isbn = value + break + } + } + }) + return metadata +} +module.exports = { parseNfoMetadata } + +function extractYear(str) { + const match = str.match(/\d{4}/g) + return match ? match[match.length-1] : null +} \ No newline at end of file From 7b6aa3ba5a720a893d22bbc558137f9b0fd6bb5b Mon Sep 17 00:00:00 2001 From: Lars Kiesow Date: Sun, 19 Nov 2023 21:00:54 +0100 Subject: [PATCH 02/17] Allow enabling dev logs This patch allows users to enable dev logs on production systems by setting the `HIDE_DEV_LOGS` environment variable. Before, you could only use this on a non-production environment. On production, the logs would be disabled. This patch changes the behavior and uses the `NODE_ENV` only as default. On production they are disabled if `HIDE_DEV_LOGS` is undefined but can be enabled by setting `HIDE_DEV_LOGS=0` on dev, they are enabled if undefined, but can be disabled by setting `HIDE_DEV_LOGS=1`. --- server/Logger.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/Logger.js b/server/Logger.js index 19e657b4..9909778f 100644 --- a/server/Logger.js +++ b/server/Logger.js @@ -5,6 +5,7 @@ class Logger { constructor() { this.isDev = process.env.NODE_ENV !== 'production' this.logLevel = !this.isDev ? LogLevel.INFO : LogLevel.TRACE + this.hideDevLogs = process.env.HIDE_DEV_LOGS === undefined ? !this.isDev : process.env.HIDE_DEV_LOGS === '1' this.socketListeners = [] this.logManager = null @@ -92,7 +93,7 @@ class Logger { * @param {...any} args */ dev(...args) { - if (!this.isDev || process.env.HIDE_DEV_LOGS === '1') return + if (this.hideDevLogs) return console.log(`[${this.timestamp}] DEV:`, ...args) } From 781d4f570f2617f28fa53a4fb0a05d77bc2fd494 Mon Sep 17 00:00:00 2001 From: mikiher Date: Tue, 21 Nov 2023 09:11:06 +0200 Subject: [PATCH 03/17] Add test for parseNfoMetadata --- .../utils/parsers/parseNfoMetadata.test.js | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 test/server/utils/parsers/parseNfoMetadata.test.js diff --git a/test/server/utils/parsers/parseNfoMetadata.test.js b/test/server/utils/parsers/parseNfoMetadata.test.js new file mode 100644 index 00000000..91141335 --- /dev/null +++ b/test/server/utils/parsers/parseNfoMetadata.test.js @@ -0,0 +1,123 @@ +const chai = require('chai') +const expect = chai.expect +const { parseNfoMetadata } = require('../../../../server/utils/parsers/parseNfoMetadata') + +describe('parseNfoMetadata', () => { + it('returns null if nfoText is empty', () => { + const result = parseNfoMetadata('') + expect(result).to.be.null + }) + + it('parses title', () => { + const nfoText = 'Title: The Great Gatsby' + const result = parseNfoMetadata(nfoText) + expect(result.title).to.equal('The Great Gatsby') + }) + + it('parses title with subtitle', () => { + const nfoText = 'Title: The Great Gatsby: A Novel' + const result = parseNfoMetadata(nfoText) + expect(result.title).to.equal('The Great Gatsby') + expect(result.subtitle).to.equal('A Novel') + }) + + it('parses authors', () => { + const nfoText = 'Author: F. Scott Fitzgerald' + const result = parseNfoMetadata(nfoText) + expect(result.authors).to.deep.equal(['F. Scott Fitzgerald']) + }) + + it('parses multiple authors', () => { + const nfoText = 'Author: John Steinbeck, Ernest Hemingway' + const result = parseNfoMetadata(nfoText) + expect(result.authors).to.deep.equal(['John Steinbeck', 'Ernest Hemingway']) + }) + + it('parses narrators', () => { + const nfoText = 'Read by: Jake Gyllenhaal' + const result = parseNfoMetadata(nfoText) + expect(result.narrators).to.deep.equal(['Jake Gyllenhaal']) + }) + + it('parses multiple narrators', () => { + const nfoText = 'Read by: Jake Gyllenhaal, Kate Winslet' + const result = parseNfoMetadata(nfoText) + expect(result.narrators).to.deep.equal(['Jake Gyllenhaal', 'Kate Winslet']) + }) + + it('parses series name', () => { + const nfoText = 'Series Name: Harry Potter' + const result = parseNfoMetadata(nfoText) + expect(result.series).to.equal('Harry Potter') + }) + + it('parses genre', () => { + const nfoText = 'Genre: Fiction' + const result = parseNfoMetadata(nfoText) + expect(result.genres).to.deep.equal(['Fiction']) + }) + + it('parses multiple genres', () => { + const nfoText = 'Genre: Fiction, Historical' + const result = parseNfoMetadata(nfoText) + expect(result.genres).to.deep.equal(['Fiction', 'Historical']) + }) + + it('parses tags', () => { + const nfoText = 'Tags: mystery, thriller' + const result = parseNfoMetadata(nfoText) + expect(result.tags).to.deep.equal(['mystery', 'thriller']) + }) + + it('parses year from various date fields', () => { + const nfoText = 'Release Date: 2021-05-01\nBook Copyright: 2021\nRecording Copyright: 2021' + const result = parseNfoMetadata(nfoText) + expect(result.publishedYear).to.equal('2021') + }) + + it('parses position in series', () => { + const nfoText = 'Position in Series: 2' + const result = parseNfoMetadata(nfoText) + expect(result.sequence).to.equal('2') + }) + + it('parses abridged flag', () => { + const nfoText = 'Abridged: No' + const result = parseNfoMetadata(nfoText) + expect(result.abridged).to.be.false + + const nfoText2 = 'Unabridged: Yes' + const result2 = parseNfoMetadata(nfoText2) + expect(result2.abridged).to.be.false + }) + + it('parses publisher', () => { + const nfoText = 'Publisher: Penguin Random House' + const result = parseNfoMetadata(nfoText) + expect(result.publisher).to.equal('Penguin Random House') + }) + + it('parses ASIN', () => { + const nfoText = 'ASIN: B08X5JZJLH' + const result = parseNfoMetadata(nfoText) + expect(result.asin).to.equal('B08X5JZJLH') + }) + + it('parses description', () => { + const nfoText = 'Book Description\n=========\nThis is a book.\n It\'s good' + const result = parseNfoMetadata(nfoText) + expect(result.description).to.equal('This is a book.\n It\'s good\n') + }) + + it('no value', () => { + const nfoText = 'Title:' + const result = parseNfoMetadata(nfoText) + expect(result.title).to.be.undefined + }) + + it('no year value', () => { + const nfoText = "Date:0" + const result = parseNfoMetadata(nfoText) + expect(result.publishedYear).to.be.undefined + }) +}) \ No newline at end of file From 0d61e29ecf6dfe2056aa2d44cfe813850c74bcbb Mon Sep 17 00:00:00 2001 From: JBlond Date: Tue, 21 Nov 2023 20:30:48 +0100 Subject: [PATCH 04/17] de language translation follow up for 27497451d9847fc57b7e67b8676f35532e299b2d --- client/strings/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/strings/de.json b/client/strings/de.json index f7cf8b68..2b1db3ae 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -181,11 +181,11 @@ "LabelAddToCollectionBatch": "Füge {0} Hörbüch(er)/Podcast(s) der Sammlung hinzu", "LabelAddToPlaylist": "Zur Wiedergabeliste hinzufügen", "LabelAddToPlaylistBatch": "Füge {0} Hörbüch(er)/Podcast(s) der Wiedergabeliste hinzu", - "LabelAdminUsersOnly": "Admin users only", + "LabelAdminUsersOnly": "Nur Admin Benutzer", "LabelAll": "Alle", "LabelAllUsers": "Alle Benutzer", - "LabelAllUsersExcludingGuests": "All users excluding guests", - "LabelAllUsersIncludingGuests": "All users including guests", + "LabelAllUsersExcludingGuests": "Alle Benutzer außer Gästen", + "LabelAllUsersIncludingGuests": "All Benutzer und Gäste", "LabelAlreadyInYourLibrary": "In der Bibliothek vorhanden", "LabelAppend": "Anhängen", "LabelAuthor": "Autor", @@ -232,7 +232,7 @@ "LabelDeselectAll": "Alles abwählen", "LabelDevice": "Gerät", "LabelDeviceInfo": "Geräteinformationen", - "LabelDeviceIsAvailableTo": "Device is available to...", + "LabelDeviceIsAvailableTo": "Dem Geärt ist es möglich zu ...", "LabelDirectory": "Verzeichnis", "LabelDiscFromFilename": "CD aus dem Dateinamen", "LabelDiscFromMetadata": "CD aus den Metadaten", @@ -398,7 +398,7 @@ "LabelSeason": "Staffel", "LabelSelectAllEpisodes": "Alle Episoden auswählen", "LabelSelectEpisodesShowing": "{0} ausgewählte Episoden werden angezeigt", - "LabelSelectUsers": "Select users", + "LabelSelectUsers": "Benutzer auswählen", "LabelSendEbookToDevice": "E-Book senden an...", "LabelSequence": "Reihenfolge", "LabelSeries": "Serien", From 9d257ebecd2995dfcd9cc0ce17168c453da9fde5 Mon Sep 17 00:00:00 2001 From: advplyr Date: Fri, 24 Nov 2023 15:36:42 -0600 Subject: [PATCH 05/17] Update:Home page shelf bulk items added socket event only adds new items to the recently added shelf instead of refreshing all shelves #2323 --- client/components/app/BookShelfCategorized.vue | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index fbed11be..15f34867 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -338,9 +338,15 @@ export default { libraryItemsAdded(libraryItems) { console.log('libraryItems added', libraryItems) - const isThisLibrary = !libraryItems.some((li) => li.libraryId !== this.currentLibraryId) - if (!this.search && isThisLibrary) { - this.fetchCategories() + const recentlyAddedShelf = this.shelves.find((shelf) => shelf.id === 'recently-added') + if (!recentlyAddedShelf) return + + // Add new library item to the recently added shelf + for (const libraryItem of libraryItems) { + if (libraryItem.libraryId === this.currentLibraryId && !recentlyAddedShelf.entities.some((ent) => ent.id === libraryItem.id)) { + // Add to front of array + recentlyAddedShelf.entities.unshift(libraryItem) + } } }, libraryItemsUpdated(items) { From 0fac9e367d927a66400af53949a87c3a08d15cb5 Mon Sep 17 00:00:00 2001 From: JBlond Date: Sat, 25 Nov 2023 19:10:26 +0100 Subject: [PATCH 06/17] de translation follow up for 2e06ae01a1ec4f670d51eaf8a7a3e5a5b256bdf0 --- 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 fcb1cd33..6e86b3ac 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -92,7 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen", "HeaderAudiobookTools": "Hörbuch-Dateiverwaltungstools", "HeaderAudioTracks": "Audiodateien", - "HeaderAuthentication": "Authentication", + "HeaderAuthentication": "Authentifizierung", "HeaderBackups": "Sicherungen", "HeaderChangePassword": "Passwort ändern", "HeaderChapters": "Kapitel", @@ -132,10 +132,10 @@ "HeaderNewAccount": "Neues Konto", "HeaderNewLibrary": "Neue Bibliothek", "HeaderNotifications": "Benachrichtigungen", - "HeaderOpenIDConnectAuthentication": "OpenID Connect Authentication", + "HeaderOpenIDConnectAuthentication": "OpenID Connect Authentifizierung", "HeaderOpenRSSFeed": "RSS-Feed öffnen", "HeaderOtherFiles": "Sonstige Dateien", - "HeaderPasswordAuthentication": "Password Authentication", + "HeaderPasswordAuthentication": "Password Authentifizierung", "HeaderPermissions": "Berechtigungen", "HeaderPlayerQueue": "Spieler Warteschlange", "HeaderPlaylist": "Wiedergabeliste", @@ -196,10 +196,10 @@ "LabelAuthorLastFirst": "Autor (Nachname, Vorname)", "LabelAuthors": "Autoren", "LabelAutoDownloadEpisodes": "Episoden automatisch herunterladen", - "LabelAutoLaunch": "Auto Launch", - "LabelAutoLaunchDescription": "Redirect to the auth provider automatically when navigating to the login page (manual override path /login?autoLaunch=0)", - "LabelAutoRegister": "Auto Register", - "LabelAutoRegisterDescription": "Automatically create new users after logging in", + "LabelAutoLaunch": "Automatischer Start", + "LabelAutoLaunchDescription": "Automatische Weiterleitung zum Authentifizierungsanbieter beim Navigieren zur Anmeldeseite (manueller Überschreibungspfad /login?autoLaunch=0)", + "LabelAutoRegister": "Automatische Registrierung", + "LabelAutoRegisterDescription": "Automatische neue Neutzer anlegen nach dem Einloggen", "LabelBackToUser": "Zurück zum Benutzer", "LabelBackupLocation": "Backup-Ort", "LabelBackupsEnableAutomaticBackups": "Automatische Sicherung aktivieren", @@ -324,11 +324,11 @@ "LabelLogLevelInfo": "Informationen", "LabelLogLevelWarn": "Warnungen", "LabelLookForNewEpisodesAfterDate": "Suchen nach neuen Episoden nach diesem Datum", - "LabelMatchExistingUsersBy": "Match existing users by", - "LabelMatchExistingUsersByDescription": "Used for connecting existing users. Once connected, users will be matched by a unique id from your SSO provider", + "LabelMatchExistingUsersBy": "Zuordnen existierender Benutzer mit", + "LabelMatchExistingUsersByDescription": "Wird zum Verbinden vorhandener Benutzer verwendet. Sobald die Verbindung hergestellt ist, wird den Benutzern eine eindeutige ID von Ihrem SSO-Anbieter zugeordnet", "LabelMediaPlayer": "Mediaplayer", "LabelMediaType": "Medientyp", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "1 ist die niedrigste Priorität, 5 ist die höhste Priorität", "LabelMetadataProvider": "Metadatenanbieter", "LabelMetaTag": "Meta Schlagwort", "LabelMetaTags": "Meta Tags", From 5e69b54eb0ee39f118221a077802b093ea190b30 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 26 Nov 2023 13:45:43 -0600 Subject: [PATCH 07/17] Reverse order of metadata precedence in UI, add translations --- .../libraries/LibraryScannerSettings.vue | 27 +++++++++++++++---- client/strings/cs.json | 7 +++-- client/strings/da.json | 5 +++- client/strings/de.json | 5 +++- client/strings/en-us.json | 4 ++- client/strings/es.json | 5 +++- client/strings/fr.json | 5 +++- client/strings/gu.json | 5 +++- client/strings/hi.json | 5 +++- client/strings/hr.json | 5 +++- client/strings/it.json | 7 +++-- client/strings/lt.json | 5 +++- client/strings/nl.json | 5 +++- client/strings/no.json | 5 +++- client/strings/pl.json | 5 +++- client/strings/ru.json | 5 +++- client/strings/sv.json | 5 +++- client/strings/zh-cn.json | 5 +++- 18 files changed, 91 insertions(+), 24 deletions(-) diff --git a/client/components/modals/libraries/LibraryScannerSettings.vue b/client/components/modals/libraries/LibraryScannerSettings.vue index 253d4e6b..8ec73dd0 100644 --- a/client/components/modals/libraries/LibraryScannerSettings.vue +++ b/client/components/modals/libraries/LibraryScannerSettings.vue @@ -19,9 +19,11 @@
  • reorder
    - {{ source.include ? index + 1 : '' }} + {{ source.include ? getSourceIndex(source.id) : '' }} +
    +
    + {{ source.name }} {{ index === firstActiveSourceIndex ? $strings.LabelHighestPriority : $strings.LabelLowestPriority }}
    -
    {{ source.name }}
    @@ -97,20 +99,34 @@ export default { }, isBookLibrary() { return this.mediaType === 'book' + }, + firstActiveSourceIndex() { + return this.metadataSourceMapped.findIndex((source) => source.include) + }, + lastActiveSourceIndex() { + return this.metadataSourceMapped.findLastIndex((source) => source.include) } }, methods: { + getSourceIndex(source) { + const activeSources = (this.librarySettings.metadataPrecedence || []).map((s) => s).reverse() + return activeSources.findIndex((s) => s === source) + 1 + }, resetToDefault() { this.metadataSourceMapped = [] for (const key in this.metadataSourceData) { this.metadataSourceMapped.push({ ...this.metadataSourceData[key] }) } + this.metadataSourceMapped.reverse() + this.$emit('update', this.getLibraryData()) }, getLibraryData() { + const metadataSourceIds = this.metadataSourceMapped.map((source) => (source.include ? source.id : null)).filter((s) => s) + metadataSourceIds.reverse() return { settings: { - metadataPrecedence: this.metadataSourceMapped.map((source) => (source.include ? source.id : null)).filter((s) => s) + metadataPrecedence: metadataSourceIds } } }, @@ -125,15 +141,16 @@ export default { }, init() { const metadataPrecedence = this.librarySettings.metadataPrecedence || [] - this.metadataSourceMapped = metadataPrecedence.map((source) => this.metadataSourceData[source]).filter((s) => s) for (const sourceKey in this.metadataSourceData) { if (!metadataPrecedence.includes(sourceKey)) { const unusedSourceData = { ...this.metadataSourceData[sourceKey], include: false } - this.metadataSourceMapped.push(unusedSourceData) + this.metadataSourceMapped.unshift(unusedSourceData) } } + + this.metadataSourceMapped.reverse() } }, mounted() { diff --git a/client/strings/cs.json b/client/strings/cs.json index 07f3d4f7..71d06dca 100644 --- a/client/strings/cs.json +++ b/client/strings/cs.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Nastavení oznámení Apprise", "HeaderAudiobookTools": "Nástroje pro správu souborů audioknih", "HeaderAudioTracks": "Zvukové stopy", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Zálohy", "HeaderChangePassword": "Změnit heslo", "HeaderChapters": "Kapitoly", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Trvale smazat soubor", "LabelHasEbook": "Obsahuje elektronickou knihu", "LabelHasSupplementaryEbook": "Obsahuje doplňkovou elektronickou knihu", + "LabelHighestPriority": "Highest priority", "LabelHost": "Hostitel", "LabelHour": "Hodina", "LabelIcon": "Ikona", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Informace", "LabelLogLevelWarn": "Varovat", "LabelLookForNewEpisodesAfterDate": "Hledat nové epizody po tomto datu", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Přehrávač médií", "LabelMediaType": "Typ média", - "LabelMetadataOrderOfPrecedenceDescription": "1 je nejnižší priorita, 5 je nejvyšší priorita", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Poskytovatel metadat", "LabelMetaTag": "Metaznačka", "LabelMetaTags": "Metaznačky", @@ -726,4 +729,4 @@ "ToastSocketFailedToConnect": "Socket se nepodařilo připojit", "ToastUserDeleteFailed": "Nepodařilo se smazat uživatele", "ToastUserDeleteSuccess": "Uživatel smazán" -} +} \ No newline at end of file diff --git a/client/strings/da.json b/client/strings/da.json index 768bb724..3fafae9f 100644 --- a/client/strings/da.json +++ b/client/strings/da.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise Notifikationsindstillinger", "HeaderAudiobookTools": "Audiobog Filhåndteringsværktøjer", "HeaderAudioTracks": "Lydspor", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Sikkerhedskopier", "HeaderChangePassword": "Skift Adgangskode", "HeaderChapters": "Kapitler", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Permanent slet fil", "LabelHasEbook": "Har e-bog", "LabelHasSupplementaryEbook": "Har supplerende e-bog", + "LabelHighestPriority": "Highest priority", "LabelHost": "Vært", "LabelHour": "Time", "LabelIcon": "Ikon", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Information", "LabelLogLevelWarn": "Advarsel", "LabelLookForNewEpisodesAfterDate": "Søg efter nye episoder efter denne dato", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Medieafspiller", "LabelMediaType": "Medietype", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadataudbyder", "LabelMetaTag": "Meta-tag", "LabelMetaTags": "Meta-tags", diff --git a/client/strings/de.json b/client/strings/de.json index f7cf8b68..f957ca99 100644 --- a/client/strings/de.json +++ b/client/strings/de.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise Benachrichtigungseinstellungen", "HeaderAudiobookTools": "Hörbuch-Dateiverwaltungstools", "HeaderAudioTracks": "Audiodateien", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Sicherungen", "HeaderChangePassword": "Passwort ändern", "HeaderChapters": "Kapitel", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Datei dauerhaft löschen", "LabelHasEbook": "mit E-Book", "LabelHasSupplementaryEbook": "mit zusätlichem E-Book", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Stunde", "LabelIcon": "Symbol", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Informationen", "LabelLogLevelWarn": "Warnungen", "LabelLookForNewEpisodesAfterDate": "Suchen nach neuen Episoden nach diesem Datum", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Mediaplayer", "LabelMediaType": "Medientyp", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadatenanbieter", "LabelMetaTag": "Meta Schlagwort", "LabelMetaTags": "Meta Tags", diff --git a/client/strings/en-us.json b/client/strings/en-us.json index 6f06ca77..8d9a22c8 100644 --- a/client/strings/en-us.json +++ b/client/strings/en-us.json @@ -276,6 +276,7 @@ "LabelHardDeleteFile": "Hard delete file", "LabelHasEbook": "Has ebook", "LabelHasSupplementaryEbook": "Has supplementary ebook", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Hour", "LabelIcon": "Icon", @@ -317,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", "LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Media Player", "LabelMediaType": "Media Type", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadata Provider", "LabelMetaTag": "Meta Tag", "LabelMetaTags": "Meta Tags", diff --git a/client/strings/es.json b/client/strings/es.json index 0ac0a960..d4752c1a 100644 --- a/client/strings/es.json +++ b/client/strings/es.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Ajustes de Notificaciones de Apprise", "HeaderAudiobookTools": "Herramientas de Gestión de Archivos de Audiolibro", "HeaderAudioTracks": "Pistas de Audio", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Respaldos", "HeaderChangePassword": "Cambiar Contraseña", "HeaderChapters": "Capítulos", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Eliminar Definitivamente", "LabelHasEbook": "Tiene Ebook", "LabelHasSupplementaryEbook": "Tiene Ebook Suplementario", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Hora", "LabelIcon": "Icono", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Información", "LabelLogLevelWarn": "Advertencia", "LabelLookForNewEpisodesAfterDate": "Buscar Nuevos Episodios a partir de esta Fecha", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Reproductor de Medios", "LabelMediaType": "Tipo de Multimedia", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Proveedor de Metadata", "LabelMetaTag": "Meta Tag", "LabelMetaTags": "Meta Tags", diff --git a/client/strings/fr.json b/client/strings/fr.json index 5ad80723..56f214a2 100644 --- a/client/strings/fr.json +++ b/client/strings/fr.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Configuration des Notifications Apprise", "HeaderAudiobookTools": "Outils de Gestion de Fichier Audiobook", "HeaderAudioTracks": "Pistes audio", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Sauvegardes", "HeaderChangePassword": "Modifier le mot de passe", "HeaderChapters": "Chapitres", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Suppression du fichier", "LabelHasEbook": "Dispose d’un livre numérique", "LabelHasSupplementaryEbook": "Dispose d’un livre numérique supplémentaire", + "LabelHighestPriority": "Highest priority", "LabelHost": "Hôte", "LabelHour": "Heure", "LabelIcon": "Icone", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", "LabelLookForNewEpisodesAfterDate": "Chercher de nouveaux épisode après cette date", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Lecteur multimédia", "LabelMediaType": "Type de média", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Fournisseur de métadonnées", "LabelMetaTag": "Etiquette de métadonnée", "LabelMetaTags": "Etiquettes de métadonnée", diff --git a/client/strings/gu.json b/client/strings/gu.json index d71c9f17..6e11a221 100644 --- a/client/strings/gu.json +++ b/client/strings/gu.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise સૂચના સેટિંગ્સ", "HeaderAudiobookTools": "Audiobook File Management Tools", "HeaderAudioTracks": "Audio Tracks", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Backups", "HeaderChangePassword": "Change Password", "HeaderChapters": "Chapters", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Hard delete file", "LabelHasEbook": "Has ebook", "LabelHasSupplementaryEbook": "Has supplementary ebook", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Hour", "LabelIcon": "Icon", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", "LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Media Player", "LabelMediaType": "Media Type", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadata Provider", "LabelMetaTag": "Meta Tag", "LabelMetaTags": "Meta Tags", diff --git a/client/strings/hi.json b/client/strings/hi.json index 51b2e762..6e228c89 100644 --- a/client/strings/hi.json +++ b/client/strings/hi.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise अधिसूचना सेटिंग्स", "HeaderAudiobookTools": "Audiobook File Management Tools", "HeaderAudioTracks": "Audio Tracks", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Backups", "HeaderChangePassword": "Change Password", "HeaderChapters": "Chapters", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Hard delete file", "LabelHasEbook": "Has ebook", "LabelHasSupplementaryEbook": "Has supplementary ebook", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Hour", "LabelIcon": "Icon", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", "LabelLookForNewEpisodesAfterDate": "Look for new episodes after this date", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Media Player", "LabelMediaType": "Media Type", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadata Provider", "LabelMetaTag": "Meta Tag", "LabelMetaTags": "Meta Tags", diff --git a/client/strings/hr.json b/client/strings/hr.json index e04343a0..6c24b1bd 100644 --- a/client/strings/hr.json +++ b/client/strings/hr.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise Notification Settings", "HeaderAudiobookTools": "Audiobook File Management alati", "HeaderAudioTracks": "Audio Tracks", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Backups", "HeaderChangePassword": "Promijeni lozinku", "HeaderChapters": "Poglavlja", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Obriši datoteku zauvijek", "LabelHasEbook": "Has ebook", "LabelHasSupplementaryEbook": "Has supplementary ebook", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Sat", "LabelIcon": "Ikona", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", "LabelLookForNewEpisodesAfterDate": "Traži nove epizode nakon ovog datuma", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Media Player", "LabelMediaType": "Media Type", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Poslužitelj metapodataka ", "LabelMetaTag": "Meta Tag", "LabelMetaTags": "Meta Tags", diff --git a/client/strings/it.json b/client/strings/it.json index c893212e..d5cee73c 100644 --- a/client/strings/it.json +++ b/client/strings/it.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprendi le impostazioni di Notifica", "HeaderAudiobookTools": "Utilità Audiobook File Management", "HeaderAudioTracks": "Tracce Audio", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Backup", "HeaderChangePassword": "Cambia Password", "HeaderChapters": "Capitoli", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Elimina Definitivamente", "LabelHasEbook": "Un ebook", "LabelHasSupplementaryEbook": "Un ebook Supplementare", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Ora", "LabelIcon": "Icona", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Allarme", "LabelLookForNewEpisodesAfterDate": "Cerca nuovi episodi dopo questa data", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Media Player", "LabelMediaType": "Tipo Media", - "LabelMetadataOrderOfPrecedenceDescription": "1 e bassa priorità, 5 è alta priorità", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadata Provider", "LabelMetaTag": "Meta Tag", "LabelMetaTags": "Meta Tags", @@ -726,4 +729,4 @@ "ToastSocketFailedToConnect": "Socket non riesce a connettersi", "ToastUserDeleteFailed": "Errore eliminazione utente", "ToastUserDeleteSuccess": "Utente eliminato" -} +} \ No newline at end of file diff --git a/client/strings/lt.json b/client/strings/lt.json index ebc6b558..520aaa74 100644 --- a/client/strings/lt.json +++ b/client/strings/lt.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise pranešimo nustatymai", "HeaderAudiobookTools": "Audioknygų failų valdymo įrankiai", "HeaderAudioTracks": "Garso takeliai", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Atsarginės kopijos", "HeaderChangePassword": "Pakeisti slaptažodį", "HeaderChapters": "Skyriai", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Galutinai ištrinti failą", "LabelHasEbook": "Turi e-knygą", "LabelHasSupplementaryEbook": "Turi papildomą e-knygą", + "LabelHighestPriority": "Highest priority", "LabelHost": "Serveris", "LabelHour": "Valanda", "LabelIcon": "Piktograma", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", "LabelLookForNewEpisodesAfterDate": "Ieškoti naujų epizodų po šios datos", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Grotuvas", "LabelMediaType": "Medijos tipas", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metaduomenų tiekėjas", "LabelMetaTag": "Meta žymė", "LabelMetaTags": "Meta žymos", diff --git a/client/strings/nl.json b/client/strings/nl.json index 06aed904..e05db32b 100644 --- a/client/strings/nl.json +++ b/client/strings/nl.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise-notificatie instellingen", "HeaderAudiobookTools": "Audioboekbestandbeheer tools", "HeaderAudioTracks": "Audiotracks", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Back-ups", "HeaderChangePassword": "Wachtwoord wijzigen", "HeaderChapters": "Hoofdstukken", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Hard-delete bestand", "LabelHasEbook": "Heeft ebook", "LabelHasSupplementaryEbook": "Heeft supplementair ebook", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Uur", "LabelIcon": "Icoon", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Waarschuwing", "LabelLookForNewEpisodesAfterDate": "Zoek naar nieuwe afleveringen na deze datum", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Mediaspeler", "LabelMediaType": "Mediatype", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadatabron", "LabelMetaTag": "Meta-tag", "LabelMetaTags": "Meta-tags", diff --git a/client/strings/no.json b/client/strings/no.json index 7fcd1c96..b36a0026 100644 --- a/client/strings/no.json +++ b/client/strings/no.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise notifikasjonsinstillinger", "HeaderAudiobookTools": "Lydbok Filbehandlingsverktøy", "HeaderAudioTracks": "Lydspor", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Sikkerhetskopier", "HeaderChangePassword": "Bytt passord", "HeaderChapters": "Kapittel", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Tving sletting av fil", "LabelHasEbook": "Har ebok", "LabelHasSupplementaryEbook": "Har supplerende ebok", + "LabelHighestPriority": "Highest priority", "LabelHost": "Tjener", "LabelHour": "Time", "LabelIcon": "Ikon", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", "LabelLookForNewEpisodesAfterDate": "Se etter nye episoder etter denne datoen", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Mediespiller", "LabelMediaType": "Medie type", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadata Leverandør", "LabelMetaTag": "Meta Tag", "LabelMetaTags": "Meta Tags", diff --git a/client/strings/pl.json b/client/strings/pl.json index dd3c1d4a..e3729a6b 100644 --- a/client/strings/pl.json +++ b/client/strings/pl.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Ustawienia powiadomień Apprise", "HeaderAudiobookTools": "Narzędzia do zarządzania audiobookami", "HeaderAudioTracks": "Ścieżki audio", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Kopie zapasowe", "HeaderChangePassword": "Zmień hasło", "HeaderChapters": "Rozdziały", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Usuń trwale plik", "LabelHasEbook": "Has ebook", "LabelHasSupplementaryEbook": "Has supplementary ebook", + "LabelHighestPriority": "Highest priority", "LabelHost": "Host", "LabelHour": "Godzina", "LabelIcon": "Ikona", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Informacja", "LabelLogLevelWarn": "Ostrzeżenie", "LabelLookForNewEpisodesAfterDate": "Szukaj nowych odcinków po dacie", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Odtwarzacz", "LabelMediaType": "Typ mediów", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Dostawca metadanych", "LabelMetaTag": "Tag", "LabelMetaTags": "Meta Tags", diff --git a/client/strings/ru.json b/client/strings/ru.json index 832ffe8b..9caabb72 100644 --- a/client/strings/ru.json +++ b/client/strings/ru.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Настройки оповещений", "HeaderAudiobookTools": "Инструменты файлов аудиокниг", "HeaderAudioTracks": "Аудио треки", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Бэкапы", "HeaderChangePassword": "Изменить пароль", "HeaderChapters": "Главы", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Жесткое удаление файла", "LabelHasEbook": "Есть e-книга", "LabelHasSupplementaryEbook": "Есть дополнительная e-книга", + "LabelHighestPriority": "Highest priority", "LabelHost": "Хост", "LabelHour": "Часы", "LabelIcon": "Иконка", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Info", "LabelLogLevelWarn": "Warn", "LabelLookForNewEpisodesAfterDate": "Искать новые эпизоды после этой даты", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Медиа проигрыватель", "LabelMediaType": "Тип медиа", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Провайдер", "LabelMetaTag": "Мета тег", "LabelMetaTags": "Мета теги", diff --git a/client/strings/sv.json b/client/strings/sv.json index 23d489d0..ab20e40b 100644 --- a/client/strings/sv.json +++ b/client/strings/sv.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "Apprise Meddelandeinställningar", "HeaderAudiobookTools": "Ljudbokshantering", "HeaderAudioTracks": "Ljudspår", + "HeaderAuthentication": "Authentication", "HeaderBackups": "Säkerhetskopior", "HeaderChangePassword": "Ändra lösenord", "HeaderChapters": "Kapitel", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "Hård radering av fil", "LabelHasEbook": "Har e-bok", "LabelHasSupplementaryEbook": "Har kompletterande e-bok", + "LabelHighestPriority": "Highest priority", "LabelHost": "Värd", "LabelHour": "Timme", "LabelIcon": "Ikon", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "Felsökningsnivå: Information", "LabelLogLevelWarn": "Felsökningsnivå: Varning", "LabelLookForNewEpisodesAfterDate": "Sök efter nya avsnitt efter detta datum", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "Mediaspelare", "LabelMediaType": "Mediatyp", - "LabelMetadataOrderOfPrecedenceDescription": "1 är lägsta prioritet, 5 är högsta prioritet", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "Metadataleverantör", "LabelMetaTag": "Metamärke", "LabelMetaTags": "Metamärken", diff --git a/client/strings/zh-cn.json b/client/strings/zh-cn.json index 5d3de27a..616e4f6c 100644 --- a/client/strings/zh-cn.json +++ b/client/strings/zh-cn.json @@ -92,6 +92,7 @@ "HeaderAppriseNotificationSettings": "测试通知设置", "HeaderAudiobookTools": "有声读物文件管理工具", "HeaderAudioTracks": "音轨", + "HeaderAuthentication": "Authentication", "HeaderBackups": "备份", "HeaderChangePassword": "更改密码", "HeaderChapters": "章节", @@ -275,6 +276,7 @@ "LabelHardDeleteFile": "完全删除文件", "LabelHasEbook": "有电子书", "LabelHasSupplementaryEbook": "有补充电子书", + "LabelHighestPriority": "Highest priority", "LabelHost": "主机", "LabelHour": "小时", "LabelIcon": "图标", @@ -316,9 +318,10 @@ "LabelLogLevelInfo": "信息", "LabelLogLevelWarn": "警告", "LabelLookForNewEpisodesAfterDate": "在此日期后查找新剧集", + "LabelLowestPriority": "Lowest Priority", "LabelMediaPlayer": "媒体播放器", "LabelMediaType": "媒体类型", - "LabelMetadataOrderOfPrecedenceDescription": "1 is lowest priority, 5 is highest priority", + "LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources", "LabelMetadataProvider": "元数据提供者", "LabelMetaTag": "元数据标签", "LabelMetaTags": "元标签", From d9584174ffb153d812cda70af530492253ec1c1c Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 26 Nov 2023 14:33:35 -0600 Subject: [PATCH 08/17] Parse NFO trim final parsed description --- server/utils/parsers/parseNfoMetadata.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/server/utils/parsers/parseNfoMetadata.js b/server/utils/parsers/parseNfoMetadata.js index a7fbbceb..ac41cfe6 100644 --- a/server/utils/parsers/parseNfoMetadata.js +++ b/server/utils/parsers/parseNfoMetadata.js @@ -60,7 +60,7 @@ function parseNfoMetadata(nfoText) { metadata.publishedYear = year } } - break; + break case 'position in series': metadata.sequence = value break @@ -76,19 +76,25 @@ function parseNfoMetadata(nfoText) { case 'asin': metadata.asin = value break - case 'isbn': - case 'isbn-10': - case 'isbn-13': + case 'isbn': + case 'isbn-10': + case 'isbn-13': metadata.isbn = value break - } + } } }) + + // Trim leading/trailing whitespace for description + if (metadata.description) { + metadata.description = metadata.description.trim() + } + return metadata } module.exports = { parseNfoMetadata } function extractYear(str) { const match = str.match(/\d{4}/g) - return match ? match[match.length-1] : null + return match ? match[match.length - 1] : null } \ No newline at end of file From b4c14fc78d032f896b5ffd9f0223b205c0510a43 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 26 Nov 2023 14:38:25 -0600 Subject: [PATCH 09/17] Parse NFO comma separated strings remove empty strings --- server/utils/parsers/parseNfoMetadata.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/utils/parsers/parseNfoMetadata.js b/server/utils/parsers/parseNfoMetadata.js index ac41cfe6..56e9400a 100644 --- a/server/utils/parsers/parseNfoMetadata.js +++ b/server/utils/parsers/parseNfoMetadata.js @@ -32,20 +32,20 @@ function parseNfoMetadata(nfoText) { } break case 'author': - metadata.authors = value.split(/\s*,\s*/) + metadata.authors = value.split(/\s*,\s*/).filter(v => v) break case 'narrator': case 'read by': - metadata.narrators = value.split(/\s*,\s*/) + metadata.narrators = value.split(/\s*,\s*/).filter(v => v) break case 'series name': metadata.series = value break case 'genre': - metadata.genres = value.split(/\s*,\s*/) + metadata.genres = value.split(/\s*,\s*/).filter(v => v) break case 'tags': - metadata.tags = value.split(/\s*,\s*/) + metadata.tags = value.split(/\s*,\s*/).filter(v => v) break case 'copyright': case 'audible.com release': From 3d468339b38eef5dba72dd097f4d9eb4e9a99dd4 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 26 Nov 2023 14:41:19 -0600 Subject: [PATCH 10/17] Update parse nfo metadata test for description --- test/server/utils/parsers/parseNfoMetadata.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/server/utils/parsers/parseNfoMetadata.test.js b/test/server/utils/parsers/parseNfoMetadata.test.js index 91141335..70e6a096 100644 --- a/test/server/utils/parsers/parseNfoMetadata.test.js +++ b/test/server/utils/parsers/parseNfoMetadata.test.js @@ -106,7 +106,7 @@ describe('parseNfoMetadata', () => { it('parses description', () => { const nfoText = 'Book Description\n=========\nThis is a book.\n It\'s good' const result = parseNfoMetadata(nfoText) - expect(result.description).to.equal('This is a book.\n It\'s good\n') + expect(result.description).to.equal('This is a book.\n It\'s good') }) it('no value', () => { From f243ad14e09725ef5d632d956fa13033d232abb3 Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 27 Nov 2023 17:10:31 -0600 Subject: [PATCH 11/17] Add help link to oidc guide --- client/pages/config/authentication.vue | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/pages/config/authentication.vue b/client/pages/config/authentication.vue index e2f6d678..e645569e 100644 --- a/client/pages/config/authentication.vue +++ b/client/pages/config/authentication.vue @@ -11,6 +11,11 @@

    {{ $strings.HeaderOpenIDConnectAuthentication }}

    + + + help_outline + +
    From 086954fb9cf6ffaf2b51cab547615562f5341b4e Mon Sep 17 00:00:00 2001 From: advplyr Date: Mon, 27 Nov 2023 17:41:47 -0600 Subject: [PATCH 12/17] Version bump v2.6.0 --- client/package-lock.json | 4 ++-- client/package.json | 2 +- package-lock.json | 6 +++--- package.json | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 1dc72e4c..16adf9db 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf-client", - "version": "2.5.0", + "version": "2.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "audiobookshelf-client", - "version": "2.5.0", + "version": "2.6.0", "license": "ISC", "dependencies": { "@nuxtjs/axios": "^5.13.6", diff --git a/client/package.json b/client/package.json index c815d388..a13b5815 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf-client", - "version": "2.5.0", + "version": "2.6.0", "buildNumber": 1, "description": "Self-hosted audiobook and podcast client", "main": "index.js", diff --git a/package-lock.json b/package-lock.json index e1a5f266..9df54fdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "audiobookshelf", - "version": "2.5.0", + "version": "2.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "audiobookshelf", - "version": "2.5.0", + "version": "2.6.0", "license": "GPL-3.0", "dependencies": { "axios": "^0.27.2", @@ -9487,4 +9487,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 477f62af..061e2a7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "audiobookshelf", - "version": "2.5.0", + "version": "2.6.0", "buildNumber": 1, "description": "Self-hosted audiobook and podcast server", "main": "index.js", @@ -61,4 +61,4 @@ "nyc": "^15.1.0", "sinon": "^17.0.1" } -} +} \ No newline at end of file From ad53894ea1e57ab49c633bed177f512d976c2fdb Mon Sep 17 00:00:00 2001 From: Denis Arnst Date: Tue, 28 Nov 2023 17:29:22 +0100 Subject: [PATCH 13/17] SSO/OpenID: Provide detailed error messages --- server/Auth.js | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/server/Auth.js b/server/Auth.js index dedf32f0..af2b4289 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -363,12 +363,48 @@ class Auth { req.session[sessionKey].code_verifier = req.query.code_verifier } + function handleAuthError(isMobile, errorCode, errorMessage, logMessage, logMessageDetail) { + Logger.error(logMessage) + if (logMessageDetail) { + Logger.debug(logMessageDetail.toString()) + } + + if (isMobile) { + return res.status(errorCode).send(errorMessage) + } else { + return res.redirect(`/login?error=${encodeURIComponent(errorMessage)}&autoLaunch=0`) + } + } + + function passportCallback(req, res, next) { + return (err, user, info) => { + const isMobile = req.session[sessionKey]?.mobile === true + if (err) { + return handleAuthError(isMobile, 500, 'Error in callback', `[Auth] Error in openid callback - ${err}`, err?.response?.body) + } + + if (!user) { + // Info usually contains the error message from the SSO provider + // Depending on the error, it can also have a body + return handleAuthError(isMobile, 401, 'Unauthorized', `[Auth] No user in openid callback - ${info}`, info?.response?.body) + } + + req.logIn(user, (loginError) => { + if (loginError) { + return handleAuthError(isMobile, 500, 'Error during login', `[Auth] Error in openid callback: ${loginError}`) + } + next() + }) + } + } + + // While not required by the standard, the passport plugin re-sends the original redirect_uri in the token request // We need to set it correctly, as some SSO providers (e.g. keycloak) check that parameter when it is provided if (req.session[sessionKey].mobile) { - return passport.authenticate('openid-client', { redirect_uri: 'audiobookshelf://oauth' })(req, res, next) + return passport.authenticate('openid-client', { redirect_uri: 'audiobookshelf://oauth' }, passportCallback(req, res, next))(req, res, next) } else { - return passport.authenticate('openid-client', { failureRedirect: '/login?error=Unauthorized&autoLaunch=0' })(req, res, next) + return passport.authenticate('openid-client', passportCallback(req, res, next))(req, res, next) } }, // on a successfull login: read the cookies and react like the client requested (callback or json) From 618028503bcd4033ef664a0747abd4a80a594635 Mon Sep 17 00:00:00 2001 From: Denis Arnst Date: Tue, 28 Nov 2023 20:07:49 +0100 Subject: [PATCH 14/17] SSO/OpenID: Also Log token header --- server/Auth.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/server/Auth.js b/server/Auth.js index af2b4289..74ccf240 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -363,10 +363,13 @@ class Auth { req.session[sessionKey].code_verifier = req.query.code_verifier } - function handleAuthError(isMobile, errorCode, errorMessage, logMessage, logMessageDetail) { + function handleAuthError(isMobile, errorCode, errorMessage, logMessage, response) { Logger.error(logMessage) - if (logMessageDetail) { - Logger.debug(logMessageDetail.toString()) + if (response) { + // Depending on the error, it can also have a body + // We also log the request header the passport plugin sents for the URL + const header = response.req?._header.replace(/Authorization: [^\r\n]*/i, 'Authorization: REDACTED') + Logger.debug(header + '\n' + response.body?.toString()) } if (isMobile) { @@ -380,13 +383,12 @@ class Auth { return (err, user, info) => { const isMobile = req.session[sessionKey]?.mobile === true if (err) { - return handleAuthError(isMobile, 500, 'Error in callback', `[Auth] Error in openid callback - ${err}`, err?.response?.body) + return handleAuthError(isMobile, 500, 'Error in callback', `[Auth] Error in openid callback - ${err}`, err?.response) } if (!user) { // Info usually contains the error message from the SSO provider - // Depending on the error, it can also have a body - return handleAuthError(isMobile, 401, 'Unauthorized', `[Auth] No user in openid callback - ${info}`, info?.response?.body) + return handleAuthError(isMobile, 401, 'Unauthorized', `[Auth] No user in openid callback - ${info}`, info?.response) } req.logIn(user, (loginError) => { From 36599a2984c539c3d22d7f058c3101a328427572 Mon Sep 17 00:00:00 2001 From: Denis Arnst Date: Tue, 28 Nov 2023 21:16:39 +0100 Subject: [PATCH 15/17] SSO/OpenID: Rename probably misleading message --- server/Auth.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/Auth.js b/server/Auth.js index 74ccf240..96a2bc9e 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -388,7 +388,7 @@ class Auth { if (!user) { // Info usually contains the error message from the SSO provider - return handleAuthError(isMobile, 401, 'Unauthorized', `[Auth] No user in openid callback - ${info}`, info?.response) + return handleAuthError(isMobile, 401, 'Unauthorized', `[Auth] No data in openid callback - ${info}`, info?.response) } req.logIn(user, (loginError) => { From a719065b8d8d24c0d5b7c1ce10ea3e112b6e1c39 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 28 Nov 2023 16:37:19 -0600 Subject: [PATCH 16/17] Auto formatting --- server/Auth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/Auth.js b/server/Auth.js index 96a2bc9e..57792177 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -379,7 +379,7 @@ class Auth { } } - function passportCallback(req, res, next) { + function passportCallback(req, res, next) { return (err, user, info) => { const isMobile = req.session[sessionKey]?.mobile === true if (err) { @@ -390,7 +390,7 @@ class Auth { // Info usually contains the error message from the SSO provider return handleAuthError(isMobile, 401, 'Unauthorized', `[Auth] No data in openid callback - ${info}`, info?.response) } - + req.logIn(user, (loginError) => { if (loginError) { return handleAuthError(isMobile, 500, 'Error during login', `[Auth] Error in openid callback: ${loginError}`) From 166477ae27e9b645ce34d62eff6d69c9d97bbe49 Mon Sep 17 00:00:00 2001 From: advplyr Date: Tue, 28 Nov 2023 16:39:52 -0600 Subject: [PATCH 17/17] Fix:Narrators page 404 on reload #2359 --- server/Server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server/Server.js b/server/Server.js index 4883fb71..5e8cab76 100644 --- a/server/Server.js +++ b/server/Server.js @@ -231,6 +231,7 @@ class Server { '/library/:library/search', '/library/:library/bookshelf/:id?', '/library/:library/authors', + '/library/:library/narrators', '/library/:library/series/:id?', '/library/:library/podcast/search', '/library/:library/podcast/latest',