- scraper/metadata/release-dates.js fills x-dates.release from MangaUpdates publisher releases. MangaUpdates also lists English back-catalog re-releases, so a date is kept only inside the AniList original run, when no scanlation or source upload is more than 4 days earlier, and when it fits the release order (no lower chapter dated after a higher one). Seasons that restart chapter numbers are skipped; dates this job wrote and no longer accepts are withdrawn. - The scraper refreshes ongoing series nightly before its update checks, only while no download runs, and bumps the publish revision so the reader reconciles. - tools/release-dates.js for a whole library (dry run default). Production dry run: 728 chapters dated (Kagurabachi, JJK, Chainsaw Man, One Piece from 928). - Peer sync: chapters this library already has adopt a peer's earlier ecosystem date (job result ecosystemLowered); the peer listing now carries dates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
43 lines
2.8 KiB
JavaScript
43 lines
2.8 KiB
JavaScript
#!/usr/bin/env node
|
|
'use strict';
|
|
// Official chapter release dates from MangaUpdates (BL-0048) for a whole library or one manga.
|
|
// Dry run by default. Apply only with the scraper stopped (--apply --writers-stopped), then let a
|
|
// full reconciliation (or admin Rescan) pick up the edited chapter.json files.
|
|
const fs = require('node:fs').promises;
|
|
const path = require('node:path');
|
|
const { ReleaseDateSources, refreshMangaReleaseDates } = require('../scraper/metadata/release-dates');
|
|
|
|
const args = process.argv.slice(2);
|
|
const apply = args.includes('--apply');
|
|
const only = args.find(arg => arg.startsWith('--manga='))?.slice('--manga='.length);
|
|
const cacheDir = args.find(arg => arg.startsWith('--cache='))?.slice('--cache='.length) || path.join(require('node:os').tmpdir(), 'mangacircus-release-dates');
|
|
const root = args.find(arg => !arg.startsWith('--'));
|
|
if (!root || args.some(arg => arg.startsWith('--') && !['--apply', '--writers-stopped'].includes(arg) && !/^--(manga|cache)=/.test(arg))
|
|
|| (apply && !args.includes('--writers-stopped'))) {
|
|
console.error('Usage: node tools/release-dates.js <library> [--manga=<folder>] [--cache=<dir>] [--apply --writers-stopped]');
|
|
process.exit(2);
|
|
}
|
|
|
|
async function main() {
|
|
const sources = new ReleaseDateSources({ cacheDir });
|
|
const totals = { manga: 0, matched: 0, chapters: 0, accepted: 0, written: 0, cleared: 0, outsideRun: 0, earlier: 0, outOfOrder: 0, noRun: 0 };
|
|
const names = (await fs.readdir(root, { withFileTypes: true })).filter(e => e.isDirectory() && !e.name.startsWith('.')).map(e => e.name).sort();
|
|
for (const name of names) {
|
|
if (only && name !== only) continue;
|
|
try {
|
|
const s = await refreshMangaReleaseDates(path.join(root, name), sources, { apply });
|
|
totals.manga++; totals.chapters += s.chapters;
|
|
if (s.ambiguous) { console.log(`${name.slice(0, 44).padEnd(44)} skipped: chapter numbers repeat across books`); continue; }
|
|
if (!s.series) { console.log(`${name.slice(0, 44).padEnd(44)} no MangaUpdates match`); continue; }
|
|
totals.matched++; totals.accepted += s.accepted; totals.written += s.written; totals.cleared += s.cleared;
|
|
for (const k of ['outsideRun', 'earlier', 'outOfOrder', 'noRun']) totals[k] += s.rejected[k];
|
|
console.log(`${name.slice(0, 44).padEnd(44)} ${String(s.chapters).padStart(5)} ch official ${String(s.official).padStart(4)} accepted ${String(s.accepted).padStart(4)}`
|
|
+ ` rejected: outside run ${s.rejected.outsideRun}, earlier evidence ${s.rejected.earlier}, out of order ${s.rejected.outOfOrder}`
|
|
+ `${s.rejected.noRun ? `, no AniList run ${s.rejected.noRun}` : ''} (${s.series.title})`);
|
|
} catch (error) { console.warn(`${name}: ${error.message}`); }
|
|
}
|
|
console.log(JSON.stringify({ apply, ...totals }));
|
|
}
|
|
|
|
main().catch(error => { console.error(error); process.exit(1); });
|