Add first-party MangaPill source and distinct volume releases

This commit is contained in:
Codex committed 2026-10-14 09:05:34 +02:00
1 parent a8df57f165
commit a08e847932
20 files changed
+364 -34

No files matched your search

+15 -3
View File
@@ -11,6 +11,7 @@ const { sizeFromBuffer, imageSize } = require('../../library');
const { directionFields } = require('../../reading-direction');
const { mergeImportedMetadata } = require('./metadata-merge');
const { normalizeWorkIdentity } = require('./work-identity');
const { parseKey } = require('../../chapter-identity');
const token = () => crypto.randomBytes(5).toString('hex');
// Staging is a recovery asset, not disposable garbage. Legacy stages without an
@@ -91,6 +92,15 @@ async function findManga(libraryRoot, sourceUrls) {
return null;
}
async function bookDirectory(mangaDir, bookNumber, bookTitle) {
const names = (await fsp.readdir(mangaDir, { withFileTypes: true }))
.filter(entry => entry.isDirectory() && parseKey(entry.name)?.key === String(bookNumber))
.map(entry => entry.name);
if (names.length > 1) throw new Error(`Ambiguous library book ${bookNumber}: ${names.join(', ')}`);
const defaultName = `v${String(bookNumber).padStart(2, '0')}`;
return path.join(mangaDir, names[0] || (bookTitle ? `${defaultName} ${cleanPart(bookTitle)}` : defaultName));
}
function validateManifest(manifest) {
if (!manifest || manifest.version !== 1) throw new Error('Manifest version must be 1');
if (!manifest.series?.title) throw new Error('series.title is required');
@@ -172,7 +182,10 @@ async function stageChapter(bookDir, chapter, http, onPage, priorMeta = {}) {
title: chapter.title || '', source: chapter.source,
added: chapter.added || new Date().toISOString(),
priority: Number(chapter.priority || 0), extra: !!chapter.extra,
...(chapter.special ? { special: true, label: chapter.label || 'Special', sourceId: chapter.sourceId, sourceNumber: chapter.sourceNumber ?? null } : {}),
...(chapter.special ? { special: true, label: chapter.label || 'Special', sourceId: chapter.sourceId,
sourceNumber: chapter.sourceNumber ?? null,
...(chapter.releaseKind ? { releaseKind: chapter.releaseKind } : {}),
...(chapter.volume ? { volume: chapter.volume } : {}) } : {}),
...directionFields([priorMeta, chapter], dimensions),
}, null, 2) + '\n', { mode: 0o644 });
// A process interruption during a write can leave a hidden pending file.
@@ -230,8 +243,7 @@ async function publishManifest(manifest, options = {}) {
for (const chapter of manifest.chapters) {
const bookNumber = Number(chapter.book?.number || 1);
if (!Number.isInteger(bookNumber) || bookNumber < 1) throw new Error(`Invalid book number for chapter ${chapter.number}`);
const bookName = `v${String(bookNumber).padStart(2, '0')}${chapter.book?.title ? ` ${cleanPart(chapter.book.title)}` : ''}`;
const bookDir = path.join(mangaDir, bookName);
const bookDir = await bookDirectory(mangaDir, bookNumber, chapter.book?.title);
await fsp.mkdir(bookDir, { recursive: true, mode: 0o755 });
await cleanupOwnTemps(bookDir);
if (chapter.book?.title) {