diff --git a/scraper/service.js b/scraper/service.js index c6aef1b..9421bb4 100644 --- a/scraper/service.js +++ b/scraper/service.js @@ -647,6 +647,16 @@ class ScraperService { const source = this.resolveAdapter(item.url)?.adapter.id || item.source; const otherAccessError = this.state.queue.some(entry => entry.id !== item.id && entry.source === source && entry.status === 'error' && entry.failure?.kind === 'access'); if (otherAccessError) throw new Error('Review the access-blocked job for this source first'); + // Older queued records may still carry the URL slug/ID as an automatic + // metadata query. A retry must repair that fallback too (not just confirm). + const canonicalTitle = String(item.sourceTitle || '').trim(); + const search = String(item.metadataSearch || '').trim(); + const seriesId = String(item.seriesId || '').trim(); + if (canonicalTitle && !item.metadataSearchExplicit + && (!search || search === seriesId || search === String(item.title || '').trim())) { + item.metadataSearch = canonicalTitle; + if (String(item.title || '') === seriesId) item.title = canonicalTitle; + } item.status = 'queued'; delete item.error; delete item.failure; delete item.nextAttemptAt; item.recoveryAttempts = 0; delete this.state.sourcePaused[source]; delete this.state.circuits[source]; diff --git a/scraper/test/queue-inspection.test.js b/scraper/test/queue-inspection.test.js index edf04ce..e11379f 100644 --- a/scraper/test/queue-inspection.test.js +++ b/scraper/test/queue-inspection.test.js @@ -12,6 +12,16 @@ async function confirm(item) { return item; } +async function retry(item) { + const service = Object.create(ScraperService.prototype); + service.state = { queue: [item], currentIds: {}, sourcePaused: {}, circuits: {}, incidents: [], paused: true }; + service.event = async () => {}; + service.recomputeStatus = () => {}; + service.resolveAdapter = () => ({ adapter: { id: item.source } }); + await service.control('retry', 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', @@ -36,3 +46,14 @@ test('legacy automatic title searches are replaced with the inspected canonical kind: 'inspection', status: 'ready' }); assert.equal(item.metadataSearch, item.sourceTitle); }); + +test('retry repairs a legacy URL-ID lookup before requeueing it', async () => { + const item = await retry({ id: 'job', seriesId: '01J76XYFHVMSQVDJPQ42TN2XYN', + title: '01J76XYFHVMSQVDJPQ42TN2XYN', metadataSearch: '01J76XYFHVMSQVDJPQ42TN2XYN', + sourceTitle: 'The Exiled Heavy Knight Knows How to Game the System', source: 'weebcentral', + status: 'unavailable', error: 'AniList HTTP 404' }); + assert.equal(item.metadataSearch, item.sourceTitle); + assert.equal(item.title, item.sourceTitle); + assert.equal(item.status, 'queued'); + assert.equal(item.error, undefined); +});