diff --git a/scraper/service.js b/scraper/service.js index d741b0d..c6aef1b 100644 --- a/scraper/service.js +++ b/scraper/service.js @@ -549,6 +549,7 @@ class ScraperService { ...found.info, id: crypto.randomUUID(), seriesId: found.info.id, kind: input.inspect === true ? 'inspection' : 'download', title: String(input.title || found.info.title).trim().slice(0, 200), metadataSearch: String(input.metadataSearch || input.title || found.info.title).trim().slice(0, 200), + metadataSearchExplicit: !!String(input.metadataSearch || '').trim(), scope, scopeLabel: scopeLabel(scope), existingPolicy: policy, ...(input.libraryMangaId ? { libraryMangaId: String(input.libraryMangaId) } : {}), status: 'queued', order: this.state.queue.length, added: new Date().toISOString(), @@ -605,6 +606,18 @@ class ScraperService { if (action === 'confirm') { if (item.status !== 'ready' || item.kind !== 'inspection') throw new Error('Inspect the source first'); item.confirmedSourceTitle = item.sourceTitle; + // Prefer the inspected canonical title when metadata lookup was derived + // from the URL/title fallback. Keep an explicitly supplied search term. + const canonicalTitle = String(item.sourceTitle || '').trim(); + const search = String(item.metadataSearch || '').trim(); + const seriesId = String(item.seriesId || '').trim(); + const fallbackSearch = !item.metadataSearchExplicit + && (!search || search === seriesId || search === String(item.title || '').trim()); + if (canonicalTitle && fallbackSearch) { + item.metadataSearch = canonicalTitle; + item.metadataSearchExplicit = true; + } + if (canonicalTitle && String(item.title || '') === String(item.seriesId || '')) item.title = canonicalTitle; item.kind = 'download'; } else { if (!['unavailable', 'quarantined', 'complete', 'ready'].includes(item.status)) throw new Error('Only inactive finished, unavailable or quarantined jobs can be inspected again'); diff --git a/scraper/test/queue-inspection.test.js b/scraper/test/queue-inspection.test.js new file mode 100644 index 0000000..edf04ce --- /dev/null +++ b/scraper/test/queue-inspection.test.js @@ -0,0 +1,38 @@ +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert/strict'); +const { ScraperService } = require('../service'); + +async function confirm(item) { + const service = Object.create(ScraperService.prototype); + service.state = { queue: [item], currentIds: {}, sourcePaused: {} }; + service.event = async () => {}; + await service.control('confirm', item.id); + return item; +} + +test('confirming an inspection uses the canonical title for automatic metadata lookup', async () => { + const item = await confirm({ id: 'job', seriesId: '01J76XYFHVMSQVDJPQ42TN2XYN', + title: '01J76XYFHVMSQVDJPQ42TN2XYN', metadataSearch: '01J76XYFHVMSQVDJPQ42TN2XYN', + sourceTitle: 'The Exiled Heavy Knight Knows How to Game the System', kind: 'inspection', status: 'ready' }); + assert.equal(item.title, item.sourceTitle); + assert.equal(item.metadataSearch, item.sourceTitle); + assert.equal(item.metadataSearchExplicit, true); + assert.equal(item.kind, 'download'); +}); + +test('confirming an inspection preserves an explicitly supplied metadata search', async () => { + const item = await confirm({ id: 'job', seriesId: 'series-id', title: 'Heavy Knight', + metadataSearch: 'Heavy Knight (Romanized Title)', metadataSearchExplicit: true, + sourceTitle: 'The Exiled Heavy Knight Knows How to Game the System', kind: 'inspection', status: 'ready' }); + assert.equal(item.metadataSearch, 'Heavy Knight (Romanized Title)'); + assert.equal(item.title, 'Heavy Knight'); +}); + +test('legacy automatic title searches are replaced with the inspected canonical title', async () => { + const item = await confirm({ id: 'job', seriesId: 'series-id', title: 'heavy-knight-slug', + metadataSearch: 'heavy-knight-slug', sourceTitle: 'The Exiled Heavy Knight Knows How to Game the System', + kind: 'inspection', status: 'ready' }); + assert.equal(item.metadataSearch, item.sourceTitle); +});