mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-16 06:25:42 -04:00
fix(imports): tolerate a UTF-8 BOM in uploaded Trakt JSON
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2 files changed
+19
-2
No files matched your search
@@ -424,6 +424,11 @@ function addTraktDocument(agg: TraktAggregate, json: unknown): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** JSON.parse that tolerates a leading UTF-8 BOM (as Blob.json() does). */
|
||||||
|
function parseJsonText(text: string): unknown {
|
||||||
|
return JSON.parse(text.charCodeAt(0) === 0xfeff ? text.slice(1) : text);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse an uploaded Trakt export: Trakt's official ZIP (watched-history-*.json,
|
* Parse an uploaded Trakt export: Trakt's official ZIP (watched-history-*.json,
|
||||||
* ratings-*.json, lists-watchlist.json, …), any single JSON file from it, or the
|
* ratings-*.json, lists-watchlist.json, …), any single JSON file from it, or the
|
||||||
@@ -447,7 +452,7 @@ export async function parseTraktExport(file: Blob): Promise<ParseResult> {
|
|||||||
const name = entry.entryName.split("/").pop() ?? entry.entryName;
|
const name = entry.entryName.split("/").pop() ?? entry.entryName;
|
||||||
if (!name.toLowerCase().endsWith(".json")) continue;
|
if (!name.toLowerCase().endsWith(".json")) continue;
|
||||||
try {
|
try {
|
||||||
addTraktDocument(agg, JSON.parse(entry.getData().toString("utf-8")));
|
addTraktDocument(agg, parseJsonText(entry.getData().toString("utf-8")));
|
||||||
} catch {
|
} catch {
|
||||||
log.debug(`Skipping unreadable Trakt export entry: ${entry.entryName}`);
|
log.debug(`Skipping unreadable Trakt export entry: ${entry.entryName}`);
|
||||||
}
|
}
|
||||||
@@ -455,7 +460,7 @@ export async function parseTraktExport(file: Blob): Promise<ParseResult> {
|
|||||||
} else {
|
} else {
|
||||||
let json: unknown;
|
let json: unknown;
|
||||||
try {
|
try {
|
||||||
json = JSON.parse(buf.toString("utf8"));
|
json = parseJsonText(buf.toString("utf8"));
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error("Invalid JSON file");
|
throw new Error("Invalid JSON file");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1362,6 +1362,18 @@ describe("parseTraktExport", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("accepts a JSON file with a UTF-8 byte-order mark", async () => {
|
||||||
|
const bytes = new Uint8Array([
|
||||||
|
0xef,
|
||||||
|
0xbb,
|
||||||
|
0xbf,
|
||||||
|
...new TextEncoder().encode(JSON.stringify(historyItems)),
|
||||||
|
]);
|
||||||
|
const result = await parseTraktExport(new Blob([bytes], { type: "application/json" }));
|
||||||
|
expect(result.data.movies).toHaveLength(1);
|
||||||
|
expect(result.data.episodes).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
test("skips an unparsable JSON entry inside the ZIP", async () => {
|
test("skips an unparsable JSON entry inside the ZIP", async () => {
|
||||||
const zip = createZip({
|
const zip = createZip({
|
||||||
"watched-history-1.json": JSON.stringify(historyItems),
|
"watched-history-1.json": JSON.stringify(historyItems),
|
||||||
|
|||||||
Reference in new issue
Block a user