Project Files
tests / sillyTavernPng.test.cjs
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const assert = require("node:assert/strict");
const { describe, it } = require("node:test");
const { extractMetadataDocument } = require("../dist/extract.js");
const { embedCharacterCardInPngFile } = require("../dist/pngCharacterWrite.js");
const { decodeCharacterCardFromPngChunks } = require("../dist/sillyTavernPng.js");
const { parsePngTextAndZtxt } = require("../dist/pngText.js");
const MINIMAL_PNG = Buffer.from(
"89504e470d0a1a0a0000000d4948445200000001000000010802000000907753de0000000c4944415408d763f8cfc000000301010018dd8db40000000049454e44ae426082",
"hex",
);
describe("SillyTavern PNG character card", () => {
it("embeds chara tEXt and extractMetadataDocument decodes character_card", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lmexif-st-"));
const p = path.join(dir, "c.png");
fs.writeFileSync(p, MINIMAL_PNG);
const card = { name: "Alice", description: "Test card", first_mes: "Hi" };
embedCharacterCardInPngFile(p, card, { keyword: "chara", compress: false });
const doc = await extractMetadataDocument(p, false);
assert.ok(doc.image);
assert.equal(doc.image.character_card.format, "v2");
assert.equal(doc.image.character_card.keyword, "chara");
assert.deepEqual(doc.image.character_card.json, card);
});
it("embeds ccv3 with zTXt and round-trips via extract", async () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "lmexif-st-"));
const p = path.join(dir, "v3.png");
fs.writeFileSync(p, MINIMAL_PNG);
const card = { spec: "chara_card_v3", spec_version: "3.0", data: { name: "Bob" } };
embedCharacterCardInPngFile(p, card, { keyword: "ccv3", compress: true });
const chunks = parsePngTextAndZtxt(p);
assert.equal(chunks.tEXt.length, 0);
assert.equal(chunks.zTXt.length, 1);
assert.equal(chunks.zTXt[0].keyword, "ccv3");
const doc = await extractMetadataDocument(p, false);
assert.equal(doc.image.character_card.format, "v3");
assert.equal(doc.image.character_card.keyword, "ccv3");
assert.deepEqual(doc.image.character_card.json, card);
});
it("decodeCharacterCardFromPngChunks prefers ccv3 over chara", () => {
const b64v2 = Buffer.from(JSON.stringify({ name: "Old" }), "utf8").toString("base64");
const b64v3 = Buffer.from(JSON.stringify({ spec: "chara_card_v3", data: {} }), "utf8").toString("base64");
const got = decodeCharacterCardFromPngChunks({
tEXt: [
{ keyword: "chara", text: b64v2 },
{ keyword: "ccv3", text: b64v3 },
],
zTXt: [],
});
assert.ok(got && !("error" in got));
assert.equal(got.keyword, "ccv3");
});
});