automatically quickmatch new authors

This commit is contained in:
MxMarx 2023-10-10 21:49:25 -07:00
parent 753ae3d7dc
commit 060cfe6e86
4 changed files with 97 additions and 58 deletions

View file

@ -8,6 +8,7 @@ const Database = require('../Database')
const CacheManager = require('../managers/CacheManager') const CacheManager = require('../managers/CacheManager')
const CoverManager = require('../managers/CoverManager') const CoverManager = require('../managers/CoverManager')
const AuthorFinder = require('../finders/AuthorFinder') const AuthorFinder = require('../finders/AuthorFinder')
const Scanner = require('../scanner/Scanner')
const { reqSupportsWebp } = require('../utils/index') const { reqSupportsWebp } = require('../utils/index')
@ -191,54 +192,21 @@ class AuthorController {
res.sendStatus(200) res.sendStatus(200)
} }
// POST api/authors/:id/match
async match(req, res) { async match(req, res) {
let authorData = null var author = req.author
const region = req.body.region || 'us' var options = req.body || {}
if (req.body.asin) { var matchResult = await Scanner.quickMatchAuthor(author, options)
authorData = await AuthorFinder.findAuthorByASIN(req.body.asin, region) if (!matchResult) {
} else {
authorData = await AuthorFinder.findAuthorByName(req.body.q, region)
}
if (!authorData) {
return res.status(404).send('Author not found') return res.status(404).send('Author not found')
} }
Logger.debug(`[AuthorController] match author with "${req.body.q || req.body.asin}"`, authorData) if (matchResult.updated) {
matchResult.author.updatedAt = Date.now()
let hasUpdates = false await Database.updateAuthor(matchResult.author)
if (authorData.asin && req.author.asin !== authorData.asin) { const numBooks = await Database.libraryItemModel.getForAuthor(matchResult.author).length
req.author.asin = authorData.asin SocketAuthority.emitter('author_updated', matchResult.author.toJSONExpanded(numBooks))
hasUpdates = true
} }
res.json(matchResult)
// Only updates image if there was no image before or the author ASIN was updated
if (authorData.image && (!req.author.imagePath || hasUpdates)) {
await CacheManager.purgeImageCache(req.author.id)
const imageData = await AuthorFinder.saveAuthorImage(req.author.id, authorData.image)
if (imageData) {
req.author.imagePath = imageData.path
hasUpdates = true
}
}
if (authorData.description && req.author.description !== authorData.description) {
req.author.description = authorData.description
hasUpdates = true
}
if (hasUpdates) {
req.author.updatedAt = Date.now()
await Database.updateAuthor(req.author)
const numBooks = await Database.libraryItemModel.getForAuthor(req.author).length
SocketAuthority.emitter('author_updated', req.author.toJSONExpanded(numBooks))
}
res.json({
updated: hasUpdates,
author: req.author
})
} }
// GET api/authors/:id/image // GET api/authors/:id/image

View file

@ -1,4 +1,5 @@
const { DataTypes, Model, where, fn, col } = require('sequelize') const { DataTypes, Model, where, fn, col } = require('sequelize')
const { nameToLastFirst } = require('../utils/parsers/parseNameString')
const oldAuthor = require('../objects/entities/Author') const oldAuthor = require('../objects/entities/Author')
@ -166,6 +167,12 @@ class Author extends Model {
onDelete: 'CASCADE' onDelete: 'CASCADE'
}) })
Author.belongsTo(library) Author.belongsTo(library)
Author.addHook('beforeCreate', async (author) => {
if (!author || author.lastFirst) return
author.lastFirst = nameToLastFirst(author.name)
})
} }
} }
module.exports = Author module.exports = Author

View file

@ -15,10 +15,12 @@ const LibraryFile = require('../objects/files/LibraryFile')
const SocketAuthority = require('../SocketAuthority') const SocketAuthority = require('../SocketAuthority')
const fsExtra = require("../libs/fsExtra") const fsExtra = require("../libs/fsExtra")
const BookFinder = require('../finders/BookFinder') const BookFinder = require('../finders/BookFinder')
const Author = require('../objects/entities/Author')
const LibraryScan = require("./LibraryScan") const LibraryScan = require("./LibraryScan")
const OpfFileScanner = require('./OpfFileScanner') const OpfFileScanner = require('./OpfFileScanner')
const AbsMetadataFileScanner = require('./AbsMetadataFileScanner') const AbsMetadataFileScanner = require('./AbsMetadataFileScanner')
const Scanner = require('../scanner/Scanner')
/** /**
* Metadata for books pulled from files * Metadata for books pulled from files
@ -192,11 +194,15 @@ class BookScanner {
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" added author "${authorName}"`) libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" added author "${authorName}"`)
authorsUpdated = true authorsUpdated = true
} else { } else {
const newAuthor = await Database.authorModel.create({ let author = new Author()
name: authorName, author.setData({ name: authorName }, libraryItemData.libraryId)
lastFirst: parseNameString.nameToLastFirst(authorName), let authorOptions = {}
libraryId: libraryItemData.libraryId authorOptions.region = libraryScan.library.provider.includes('.') ? libraryScan.library.provider.split('.').pop() : 'us'
}) let authorData = await Scanner.quickMatchAuthor(author, authorOptions)
if (authorData) {
author = authorData.author
}
const newAuthor = await Database.authorModel.create(author)
await media.addAuthor(newAuthor) await media.addAuthor(newAuthor)
Database.addAuthorToFilterData(libraryItemData.libraryId, newAuthor.name, newAuthor.id) Database.addAuthorToFilterData(libraryItemData.libraryId, newAuthor.name, newAuthor.id)
libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" added new author "${authorName}"`) libraryScan.addLog(LogLevel.DEBUG, `Updating book "${bookMetadata.title}" added new author "${authorName}"`)
@ -416,12 +422,16 @@ class BookScanner {
}) })
} else { } else {
// New author // New author
bookObject.bookAuthors.push({ let author = new Author()
author: { author.setData({ name: authorName }, libraryItemData.libraryId)
libraryId: libraryItemData.libraryId, let authorOptions = {}
name: authorName, authorOptions.region = libraryScan.library.provider.includes('.') ? libraryScan.library.provider.split('.').pop() : 'us'
lastFirst: parseNameString.nameToLastFirst(authorName) let authorData = await Scanner.quickMatchAuthor(author, authorOptions)
if (authorData) {
author = authorData.author
} }
bookObject.bookAuthors.push({
author: author.toJSON()
}) })
} }
} }

View file

@ -12,12 +12,14 @@ const Author = require('../objects/entities/Author')
const Series = require('../objects/entities/Series') const Series = require('../objects/entities/Series')
const LibraryScanner = require('./LibraryScanner') const LibraryScanner = require('./LibraryScanner')
const CoverManager = require('../managers/CoverManager') const CoverManager = require('../managers/CoverManager')
const CacheManager = require('../managers/CacheManager')
const AuthorFinder = require('../finders/AuthorFinder')
class Scanner { class Scanner {
constructor() { } constructor() { }
async quickMatchLibraryItem(libraryItem, options = {}) { async quickMatchLibraryItem(libraryItem, options = {}) {
var provider = options.provider || 'google' options.provider = options.provider || 'google'
var searchTitle = options.title || libraryItem.media.metadata.title var searchTitle = options.title || libraryItem.media.metadata.title
var searchAuthor = options.author || libraryItem.media.metadata.authorName var searchAuthor = options.author || libraryItem.media.metadata.authorName
var overrideDefaults = options.overrideDefaults || false var overrideDefaults = options.overrideDefaults || false
@ -36,10 +38,10 @@ class Scanner {
var searchISBN = options.isbn || libraryItem.media.metadata.isbn var searchISBN = options.isbn || libraryItem.media.metadata.isbn
var searchASIN = options.asin || libraryItem.media.metadata.asin var searchASIN = options.asin || libraryItem.media.metadata.asin
var results = await BookFinder.search(provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 }) var results = await BookFinder.search(options.provider, searchTitle, searchAuthor, searchISBN, searchASIN, { maxFuzzySearches: 2 })
if (!results.length) { if (!results.length) {
return { return {
warning: `No ${provider} match found` warning: `No ${options.provider} match found`
} }
} }
var matchData = results[0] var matchData = results[0]
@ -60,7 +62,7 @@ class Scanner {
var results = await PodcastFinder.search(searchTitle) var results = await PodcastFinder.search(searchTitle)
if (!results.length) { if (!results.length) {
return { return {
warning: `No ${provider} match found` warning: `No ${options.provider} match found`
} }
} }
var matchData = results[0] var matchData = results[0]
@ -189,6 +191,13 @@ class Scanner {
if (!author) { if (!author) {
author = new Author() author = new Author()
author.setData({ name: authorName }, libraryItem.libraryId) author.setData({ name: authorName }, libraryItem.libraryId)
let authorOptions = {}
authorOptions.region = options.provider.includes('.') ? options.provider.split('.').pop() : 'us'
let authorData = await this.quickMatchAuthor(author, authorOptions)
if (authorData) {
author = authorData.author
}
await Database.createAuthor(author) await Database.createAuthor(author)
SocketAuthority.emitter('author_added', author.toJSON()) SocketAuthority.emitter('author_added', author.toJSON())
// Update filter data // Update filter data
@ -362,5 +371,50 @@ class Scanner {
LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter(ls => ls.id !== library.id) LibraryScanner.librariesScanning = LibraryScanner.librariesScanning.filter(ls => ls.id !== library.id)
SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData) SocketAuthority.emitter('scan_complete', libraryScan.getScanEmitData)
} }
/**
* Search providers for author
* @param {Author} author
* @param {{region:string, asin:string, q:string}} options
*/
async quickMatchAuthor(author, options = {}) {
let authorData = null
const region = options.region || 'us'
if (options.asin) {
authorData = await AuthorFinder.findAuthorByASIN(options.asin, region)
} else {
authorData = await AuthorFinder.findAuthorByName(options.q || author.name, region)
}
if (!authorData) {
return null
}
Logger.debug(`[Scanner] match author from with "${options.q || options.asin}"`, authorData)
let hasUpdated = false;
['asin', 'name', 'description'].forEach(key => {
if (authorData[key] && author[key] !== authorData[key]) {
author[key] = authorData[key];
hasUpdated = true;
}
});
// Only updates image if there was no image before or the author ASIN was updated
if (authorData.image && (!author.imagePath || (authorData.asin && author.asin !== authorData.asin))) {
await CacheManager.purgeImageCache(author.id)
const imageData = await AuthorFinder.saveAuthorImage(author.id, authorData.image)
if (imageData) {
author.imagePath = imageData.path
hasUpdated = true
}
}
return {
updated: hasUpdated,
author: author
}
}
} }
module.exports = new Scanner() module.exports = new Scanner()