feat: auto-populate series ASIN from Audible metadata

- Update Audible provider to return series ASIN in search results
- Pass series ASIN through Match.vue when selecting metadata
- Update Book.updateSeriesFromRequest to forward ASIN to Series model
- Update Scanner to use series ASIN during quick match

When using the Audible metadata provider, the series ASIN is now
automatically captured and stored with the series.
This commit is contained in:
Quentin King 2026-01-03 10:34:25 -06:00
parent edbd49c4c1
commit d5a2ea9feb
4 changed files with 38 additions and 7 deletions

View file

@ -544,7 +544,8 @@ export default {
id: `new-${Math.floor(Math.random() * 10000)}`,
displayName: se.sequence ? `${se.series} #${se.sequence}` : se.series,
name: se.series,
sequence: se.sequence || ''
sequence: se.sequence || '',
asin: se.asin || null
}
})
}
@ -580,7 +581,9 @@ export default {
seriesPayload.push({
id: seriesItem.id,
name: seriesItem.name,
sequence: seriesItem.sequence
sequence: seriesItem.sequence,
// Support both 'asin' (from provider) and 'audibleSeriesAsin' (from edit form)
asin: seriesItem.asin || seriesItem.audibleSeriesAsin || null
})
)
updatePayload.metadata.series = seriesPayload

View file

@ -524,8 +524,16 @@ class Book extends Model {
hasUpdates = true
Logger.debug(`[Book] "${this.title}" Updated series "${existingSeries.name}" sequence ${seriesObjSequence}`)
}
// Update series ASIN if provided and not already set
if (seriesObj.asin && !existingSeries.audibleSeriesAsin) {
existingSeries.audibleSeriesAsin = seriesObj.asin
await existingSeries.save()
const SocketAuthority = require('../SocketAuthority')
SocketAuthority.emitter('series_updated', existingSeries.toOldJSON())
Logger.debug(`[Book] "${this.title}" Updated series "${existingSeries.name}" ASIN ${seriesObj.asin}`)
}
} else {
const series = await seriesModel.findOrCreateByNameAndLibrary(seriesObj.name, libraryId)
const series = await seriesModel.findOrCreateByNameAndLibrary(seriesObj.name, libraryId, seriesObj.asin)
series.bookSeries = await bookSeriesModel.create({ bookId: this.id, seriesId: series.id, sequence: seriesObjSequence })
this.series.push(series)
seriesAdded.push(series)
@ -553,7 +561,7 @@ class Book extends Model {
*/
oldMetadataToJSON() {
const authors = this.authors.map((au) => ({ id: au.id, name: au.name }))
const series = this.series.map((se) => ({ id: se.id, name: se.name, sequence: se.bookSeries.sequence }))
const series = this.series.map((se) => ({ id: se.id, name: se.name, sequence: se.bookSeries.sequence, audibleSeriesAsin: se.audibleSeriesAsin }))
return {
title: this.title,
subtitle: this.subtitle,

View file

@ -47,13 +47,15 @@ class Audible {
if (seriesPrimary) {
series.push({
series: seriesPrimary.name,
sequence: this.cleanSeriesSequence(seriesPrimary.name, seriesPrimary.position || '')
sequence: this.cleanSeriesSequence(seriesPrimary.name, seriesPrimary.position || ''),
asin: seriesPrimary.asin || null
})
}
if (seriesSecondary) {
series.push({
series: seriesSecondary.name,
sequence: this.cleanSeriesSequence(seriesSecondary.name, seriesSecondary.position || '')
sequence: this.cleanSeriesSequence(seriesSecondary.name, seriesSecondary.position || ''),
asin: seriesSecondary.asin || null
})
}

View file

@ -303,17 +303,35 @@ class Scanner {
Logger.info(`[Scanner] quickMatchBookBuildUpdatePayload: Updated series sequence for "${existingSeries.name}" to ${seriesMatchItem.sequence} in "${libraryItem.media.title}"`)
hasSeriesUpdates = true
}
// Update series ASIN if provided and not already set
if (seriesMatchItem.asin && !existingSeries.audibleSeriesAsin) {
existingSeries.set({ audibleSeriesAsin: seriesMatchItem.asin })
if (existingSeries.changed()) {
await existingSeries.save()
SocketAuthority.emitter('series_updated', existingSeries.toOldJSON())
Logger.info(`[Scanner] quickMatchBookBuildUpdatePayload: Updated series "${existingSeries.name}" with ASIN ${seriesMatchItem.asin}`)
}
}
} else {
let seriesItem = await Database.seriesModel.getByNameAndLibrary(seriesMatchItem.series, libraryItem.libraryId)
if (!seriesItem) {
seriesItem = await Database.seriesModel.create({
name: seriesMatchItem.series,
nameIgnorePrefix: getTitleIgnorePrefix(seriesMatchItem.series),
libraryId: libraryItem.libraryId
libraryId: libraryItem.libraryId,
audibleSeriesAsin: seriesMatchItem.asin || null
})
// Update filter data
Database.addSeriesToFilterData(libraryItem.libraryId, seriesItem.name, seriesItem.id)
SocketAuthority.emitter('series_added', seriesItem.toOldJSON())
} else if (seriesMatchItem.asin && !seriesItem.audibleSeriesAsin) {
// Series exists but has no ASIN, update it
seriesItem.set({ audibleSeriesAsin: seriesMatchItem.asin })
if (seriesItem.changed()) {
await seriesItem.save()
SocketAuthority.emitter('series_updated', seriesItem.toOldJSON())
Logger.info(`[Scanner] quickMatchBookBuildUpdatePayload: Updated series "${seriesItem.name}" with ASIN ${seriesMatchItem.asin}`)
}
}
const bookSeries = await Database.bookSeriesModel.create({
seriesId: seriesItem.id,