mirror of
https://github.com/priyanshujain/margin.git
synced 2026-10-02 11:07:04 +00:00
fix(import): validate ISBN with checksum
The importer accepted any 9-13 digit run as the ISBN, so a UUID or arbitrary number became a bogus ISBN. Prefer scheme-tagged identifiers and accept only values that pass the ISBN-10/13 length and checksum test, leaving it empty otherwise. Claude-Session: https://claude.ai/code/session_01RzTWrLiy4zGuw2hZSQGamk
This commit is contained in:
1 parent
5caf66a6fc
commit
b94b51140a
1 file changed
+36
-2
+36
-2
@@ -274,6 +274,41 @@ function headingText(node: JSONContent): string {
|
||||
return (node.content ?? []).map((n) => n.text ?? "").join("").trim();
|
||||
}
|
||||
|
||||
function isbnChecksum(s: string): boolean {
|
||||
if (s.length === 10) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const v = s[i] === "X" ? 10 : Number(s[i]);
|
||||
if (Number.isNaN(v) || (s[i] === "X" && i !== 9)) return false;
|
||||
sum += v * (10 - i);
|
||||
}
|
||||
return sum % 11 === 0;
|
||||
}
|
||||
if (s.length === 13 && /^\d{13}$/.test(s)) {
|
||||
let sum = 0;
|
||||
for (let i = 0; i < 13; i++) sum += Number(s[i]) * (i % 2 === 0 ? 1 : 3);
|
||||
return sum % 10 === 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function findIsbn(opf: Document): string {
|
||||
const ids = Array.from(opf.getElementsByTagName("*")).filter((el) => el.localName === "identifier");
|
||||
const ranked = ids
|
||||
.map((el) => ({
|
||||
text: el.textContent ?? "",
|
||||
tagged:
|
||||
/isbn/i.test(el.getAttribute("opf:scheme") || el.getAttribute("scheme") || "") ||
|
||||
/urn:isbn:/i.test(el.textContent ?? ""),
|
||||
}))
|
||||
.sort((a, b) => Number(b.tagged) - Number(a.tagged));
|
||||
for (const { text } of ranked) {
|
||||
const cleaned = text.replace(/[^0-9Xx]/g, "").toUpperCase();
|
||||
if (isbnChecksum(cleaned)) return cleaned;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
interface ManifestItem {
|
||||
href: string;
|
||||
mediaType: string;
|
||||
@@ -462,12 +497,11 @@ export function filesToBook(files: RawFile[], fallbackName = "Imported book"): B
|
||||
});
|
||||
if (!chapters.length) throw new Error("Not a valid EPUB: no readable chapters found.");
|
||||
|
||||
const identifier = localText(opf, "identifier");
|
||||
const metadata: BookMetadata = {
|
||||
title: localText(opf, "title") || fallbackName,
|
||||
subtitle: "",
|
||||
author: localText(opf, "creator"),
|
||||
isbn: /\d{9,13}/.test(identifier) ? identifier.replace(/[^0-9Xx]/g, "") : "",
|
||||
isbn: findIsbn(opf),
|
||||
language: localText(opf, "language") || "en",
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user