diff --git a/server/models/Book.js b/server/models/Book.js index 19923750e..df2014c4e 100644 --- a/server/models/Book.js +++ b/server/models/Book.js @@ -439,6 +439,7 @@ class Book extends Model { */ async updateAuthorsFromRequest(authors, libraryId) { if (!Array.isArray(authors)) return null + authors = parseNameString.dedupeNames(authors) if (!this.authors) { throw new Error(`[Book] Cannot update authors because authors are not loaded for book ${this.id}`) @@ -466,6 +467,8 @@ class Book extends Model { const authorsAdded = [] for (const authorName of newAuthorNames) { const { author, created } = await authorModel.findOrCreateByNameAndLibrary(authorName, libraryId) + // findOrCreate matches case-insensitively, so a second spelling can resolve to an author just linked + if (this.authors.some((au) => au.id === author.id)) continue await bookAuthorModel.create({ bookId: this.id, authorId: author.id }) if (created) { SocketAuthority.emitter('author_added', author.toOldJSON()) diff --git a/server/scanner/BookScanner.js b/server/scanner/BookScanner.js index 2b106cdc8..f8762a1cf 100644 --- a/server/scanner/BookScanner.js +++ b/server/scanner/BookScanner.js @@ -703,6 +703,9 @@ class BookScanner { bookMetadata.description = htmlSanitizer.sanitize(bookMetadata.description) } + // One dedupe after every metadata source. A repeated name inserts the same bookAuthor twice and aborts the scan. + bookMetadata.authors = parseNameString.dedupeNames(bookMetadata.authors) + return bookMetadata }