Files
MangaCircus/scraper/lib/source-ledger.js
T

55 lines
2.7 KiB
JavaScript

'use strict';
const { normalizeWorkIdentity } = require('./work-identity');
function workKey(item) {
const identity = normalizeWorkIdentity(item.workIdentity);
if (identity) return `${identity.scheme}:${identity.kind}:${identity.value}`;
if (item.libraryMangaId) return `library:${item.libraryMangaId}`;
return `source:${item.source}:${item.seriesId || item.url}`;
}
function sourceKey(item) {
return `${item.source}:${item.seriesId || item.url}`;
}
function recordSource(ledger, item, observation = {}) {
const key = sourceKey(item);
const prior = ledger[key] || {};
const availability = observation.availability || item.availability;
const count = observation.catalogTotal ?? item.catalogTotal;
const observedAt = observation.observedAt || (count != null ? item.checked || item.completed : null) || null;
const catalog = count != null ? 'found' : prior.catalog || 'unverified';
const adapter = count != null
? Number(count) > 0 ? 'compatible' : availability?.reason === 'unrecognized-labels' ? 'unsupported-labels' : 'no-selected-pages'
: prior.adapter || 'unverified';
ledger[key] = {
...prior, workKey: workKey(item), source: item.source, seriesId: item.seriesId || null,
url: item.url || prior.url || null, title: item.title || prior.title || null,
sourceTitle: observation.sourceTitle || item.sourceTitle || prior.sourceTitle || null,
language: availability?.language || prior.language || null,
catalog, adapter, rights: prior.rights || 'unverified',
hostedCount: count == null ? prior.hostedCount ?? null : Number(count),
externalCount: availability?.externalReleases ?? prior.externalCount ?? null,
coverage: availability?.coverage || prior.coverage || 'unknown',
reason: observation.availability ? availability.reason || null : availability?.reason || prior.reason || null,
lastObservedAt: observedAt || prior.lastObservedAt || null,
evidence: count != null && observedAt ? { kind: 'scraper-catalog', jobId: item.id || null } : prior.evidence || { kind: 'queue-url', jobId: item.id || null },
};
if (observation.catalogTotal != null && Number(count) > 0) delete ledger[key].lastFailure;
return ledger[key];
}
function recordFailure(ledger, item, failure, at = new Date().toISOString()) {
const row = recordSource(ledger, item);
row.lastFailure = { kind: failure.kind, message: String(failure.message || '').slice(0, 300), at };
return row;
}
function ledgerRows(ledger) {
return Object.values(ledger || {}).sort((a, b) => String(a.title || '').localeCompare(String(b.title || ''))
|| String(a.source || '').localeCompare(String(b.source || '')));
}
module.exports = { workKey, sourceKey, recordSource, recordFailure, ledgerRows };