@character-foundry/character-foundry 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/app-framework.cjs +143 -26
  2. package/dist/app-framework.cjs.map +1 -1
  3. package/dist/app-framework.d.cts +16 -1
  4. package/dist/app-framework.d.ts +16 -1
  5. package/dist/app-framework.js +151 -34
  6. package/dist/app-framework.js.map +1 -1
  7. package/dist/charx.cjs +70 -8
  8. package/dist/charx.cjs.map +1 -1
  9. package/dist/charx.js +70 -8
  10. package/dist/charx.js.map +1 -1
  11. package/dist/core.cjs +93 -6
  12. package/dist/core.cjs.map +1 -1
  13. package/dist/core.d.cts +42 -1
  14. package/dist/core.d.ts +42 -1
  15. package/dist/core.js +93 -6
  16. package/dist/core.js.map +1 -1
  17. package/dist/exporter.cjs +88 -8
  18. package/dist/exporter.cjs.map +1 -1
  19. package/dist/exporter.js +89 -9
  20. package/dist/exporter.js.map +1 -1
  21. package/dist/federation.cjs +1 -0
  22. package/dist/federation.cjs.map +1 -1
  23. package/dist/federation.js +1 -0
  24. package/dist/federation.js.map +1 -1
  25. package/dist/image-utils.cjs +249 -0
  26. package/dist/image-utils.cjs.map +1 -0
  27. package/dist/image-utils.d.cts +136 -0
  28. package/dist/image-utils.d.ts +136 -0
  29. package/dist/image-utils.js +226 -0
  30. package/dist/image-utils.js.map +1 -0
  31. package/dist/index.cjs +122 -18
  32. package/dist/index.cjs.map +1 -1
  33. package/dist/index.js +123 -19
  34. package/dist/index.js.map +1 -1
  35. package/dist/loader.cjs +42 -14
  36. package/dist/loader.cjs.map +1 -1
  37. package/dist/loader.js +43 -15
  38. package/dist/loader.js.map +1 -1
  39. package/dist/lorebook.cjs +1 -0
  40. package/dist/lorebook.cjs.map +1 -1
  41. package/dist/lorebook.js +1 -0
  42. package/dist/lorebook.js.map +1 -1
  43. package/dist/media.cjs +1 -0
  44. package/dist/media.cjs.map +1 -1
  45. package/dist/media.js +1 -0
  46. package/dist/media.js.map +1 -1
  47. package/dist/normalizer.cjs +1 -0
  48. package/dist/normalizer.cjs.map +1 -1
  49. package/dist/normalizer.d.cts +1 -0
  50. package/dist/normalizer.d.ts +1 -0
  51. package/dist/normalizer.js +1 -0
  52. package/dist/normalizer.js.map +1 -1
  53. package/dist/png.cjs +19 -0
  54. package/dist/png.cjs.map +1 -1
  55. package/dist/png.js +19 -0
  56. package/dist/png.js.map +1 -1
  57. package/dist/schemas.cjs +80 -0
  58. package/dist/schemas.cjs.map +1 -1
  59. package/dist/schemas.d.cts +30 -0
  60. package/dist/schemas.d.ts +30 -0
  61. package/dist/schemas.js +80 -0
  62. package/dist/schemas.js.map +1 -1
  63. package/dist/voxta.cjs +14 -102
  64. package/dist/voxta.cjs.map +1 -1
  65. package/dist/voxta.js +15 -103
  66. package/dist/voxta.js.map +1 -1
  67. package/package.json +16 -5
package/dist/index.js CHANGED
@@ -226,10 +226,14 @@ function alloc(size) {
226
226
  return new Uint8Array(size);
227
227
  }
228
228
  var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
229
+ var LARGE_BUFFER_THRESHOLD = 1024 * 1024;
229
230
  function encode(data) {
230
231
  if (isNode) {
231
232
  return Buffer.from(data).toString("base64");
232
233
  }
234
+ if (data.length > LARGE_BUFFER_THRESHOLD) {
235
+ return encodeChunked(data);
236
+ }
233
237
  let binary = "";
234
238
  for (let i = 0; i < data.length; i++) {
235
239
  binary += String.fromCharCode(data[i]);
@@ -248,6 +252,21 @@ function decode(base64) {
248
252
  return result;
249
253
  }
250
254
  var ENCODE_CHUNK_SIZE = 64 * 1024;
255
+ function encodeChunked(data) {
256
+ if (isNode) {
257
+ return Buffer.from(data).toString("base64");
258
+ }
259
+ const chunks = [];
260
+ for (let i = 0; i < data.length; i += ENCODE_CHUNK_SIZE) {
261
+ const chunk = data.subarray(i, Math.min(i + ENCODE_CHUNK_SIZE, data.length));
262
+ let binary = "";
263
+ for (let j = 0; j < chunk.length; j++) {
264
+ binary += String.fromCharCode(chunk[j]);
265
+ }
266
+ chunks.push(binary);
267
+ }
268
+ return btoa(chunks.join(""));
269
+ }
251
270
  var FOUNDRY_ERROR_MARKER = /* @__PURE__ */ Symbol.for("@character-foundry/core:FoundryError");
252
271
  var FoundryError = class extends Error {
253
272
  constructor(message, code) {
@@ -1287,12 +1306,69 @@ function matchAssetsToDescriptors(extractedAssets, descriptors) {
1287
1306
  }
1288
1307
  return matched;
1289
1308
  }
1309
+ var SAFE_ASSET_TYPES = /* @__PURE__ */ new Set([
1310
+ "icon",
1311
+ "user_icon",
1312
+ "emotion",
1313
+ "background",
1314
+ "sound",
1315
+ "video",
1316
+ "custom",
1317
+ "x-risu-asset",
1318
+ "data",
1319
+ "unknown"
1320
+ ]);
1321
+ var SAFE_EXTENSIONS = /* @__PURE__ */ new Set([
1322
+ // Images
1323
+ "png",
1324
+ "jpg",
1325
+ "jpeg",
1326
+ "webp",
1327
+ "gif",
1328
+ "avif",
1329
+ "svg",
1330
+ "bmp",
1331
+ "ico",
1332
+ // Audio
1333
+ "mp3",
1334
+ "wav",
1335
+ "ogg",
1336
+ "flac",
1337
+ "m4a",
1338
+ "aac",
1339
+ "opus",
1340
+ // Video
1341
+ "mp4",
1342
+ "webm",
1343
+ "avi",
1344
+ "mov",
1345
+ "mkv",
1346
+ // Data
1347
+ "json",
1348
+ "txt",
1349
+ "bin"
1350
+ ]);
1290
1351
  function getCharxCategory(mimetype) {
1291
1352
  if (mimetype.startsWith("image/")) return "images";
1292
1353
  if (mimetype.startsWith("audio/")) return "audio";
1293
1354
  if (mimetype.startsWith("video/")) return "video";
1294
1355
  return "other";
1295
1356
  }
1357
+ function sanitizeAssetType(type) {
1358
+ const normalized = type.toLowerCase().replace(/[^a-z0-9-_]/g, "-");
1359
+ if (SAFE_ASSET_TYPES.has(normalized)) {
1360
+ return normalized;
1361
+ }
1362
+ const sanitized = normalized.replace(/[^a-z0-9]/g, "");
1363
+ return sanitized || "custom";
1364
+ }
1365
+ function sanitizeExtension(ext) {
1366
+ const normalized = ext.replace(/^\./, "").toLowerCase().replace(/[^a-z0-9]/g, "");
1367
+ if (SAFE_EXTENSIONS.has(normalized)) {
1368
+ return normalized;
1369
+ }
1370
+ return "bin";
1371
+ }
1296
1372
  function sanitizeName(name, ext) {
1297
1373
  let safeName = name;
1298
1374
  if (safeName.toLowerCase().endsWith(`.${ext.toLowerCase()}`)) {
@@ -1326,10 +1402,12 @@ Import this file into SillyTavern, RisuAI, or other compatible applications.
1326
1402
  let assetCount = 0;
1327
1403
  for (let i = 0; i < assets.length; i++) {
1328
1404
  const asset = assets[i];
1329
- const mimetype = getMimeTypeFromExt(asset.ext);
1405
+ const safeType = sanitizeAssetType(asset.type);
1406
+ const safeExt = sanitizeExtension(asset.ext);
1407
+ const mimetype = getMimeTypeFromExt(safeExt);
1330
1408
  const category = getCharxCategory(mimetype);
1331
- const safeName = sanitizeName(asset.name, asset.ext);
1332
- const assetPath = `assets/${asset.type}/${category}/${safeName}.${asset.ext}`;
1409
+ const safeName = sanitizeName(asset.name, safeExt);
1410
+ const assetPath = `assets/${safeType}/${category}/${safeName}.${safeExt}`;
1333
1411
  zipEntries[assetPath] = [asset.data, { level: compressionLevel }];
1334
1412
  assetCount++;
1335
1413
  if (emitXMeta && mimetype.startsWith("image/")) {
@@ -1350,16 +1428,18 @@ Import this file into SillyTavern, RisuAI, or other compatible applications.
1350
1428
  };
1351
1429
  }
1352
1430
  function transformAssetUris(card, assets) {
1353
- const transformed = JSON.parse(JSON.stringify(card));
1431
+ const transformed = typeof structuredClone === "function" ? structuredClone(card) : JSON.parse(JSON.stringify(card));
1354
1432
  transformed.data.assets = assets.map((asset) => {
1355
- const mimetype = getMimeTypeFromExt(asset.ext);
1433
+ const safeType = sanitizeAssetType(asset.type);
1434
+ const safeExt = sanitizeExtension(asset.ext);
1435
+ const mimetype = getMimeTypeFromExt(safeExt);
1356
1436
  const category = getCharxCategory(mimetype);
1357
- const safeName = sanitizeName(asset.name, asset.ext);
1437
+ const safeName = sanitizeName(asset.name, safeExt);
1358
1438
  return {
1359
1439
  type: asset.type,
1360
- uri: `embeded://assets/${asset.type}/${category}/${safeName}.${asset.ext}`,
1440
+ uri: `embeded://assets/${safeType}/${category}/${safeName}.${safeExt}`,
1361
1441
  name: safeName,
1362
- ext: asset.ext
1442
+ ext: safeExt
1363
1443
  };
1364
1444
  });
1365
1445
  return transformed;
@@ -7686,7 +7766,7 @@ function getExtension(format) {
7686
7766
  }
7687
7767
 
7688
7768
  // ../voxta/dist/index.js
7689
- import { unzipSync, zipSync as zipSync22 } from "fflate";
7769
+ import { zipSync as zipSync22 } from "fflate";
7690
7770
  var DEFAULT_OPTIONS2 = {
7691
7771
  maxFileSize: 50 * 1024 * 1024,
7692
7772
  // 50MB
@@ -8450,8 +8530,16 @@ function detectExtension(buffer) {
8450
8530
  if (buffer[0] === 73 && buffer[1] === 68 && buffer[2] === 51) {
8451
8531
  return "mp3";
8452
8532
  }
8453
- if (buffer.length > 1 && buffer[0] === 255 && (buffer[1] & 224) === 224) {
8454
- return "mp3";
8533
+ if (buffer.length >= 4 && buffer[0] === 255 && (buffer[1] & 224) === 224) {
8534
+ const byte2 = buffer[1];
8535
+ const byte3 = buffer[2];
8536
+ const version = byte2 >> 3 & 3;
8537
+ const layer = byte2 >> 1 & 3;
8538
+ const bitrateIndex = byte3 >> 4 & 15;
8539
+ const sampleRate = byte3 >> 2 & 3;
8540
+ if (version !== 1 && layer !== 0 && bitrateIndex !== 15 && sampleRate !== 3) {
8541
+ return "mp3";
8542
+ }
8455
8543
  }
8456
8544
  if (buffer.length >= 12 && buffer[0] === 82 && buffer[1] === 73 && buffer[2] === 70 && buffer[3] === 70 && buffer[8] === 87 && buffer[9] === 65 && buffer[10] === 86 && buffer[11] === 69) {
8457
8545
  return "wav";
@@ -8485,6 +8573,17 @@ function parsePng(data, options) {
8485
8573
  }];
8486
8574
  if (extracted.extraChunks && options.extractAssets && card.data.assets) {
8487
8575
  const usedChunks = /* @__PURE__ */ new Set();
8576
+ const chunkMap = /* @__PURE__ */ new Map();
8577
+ for (const chunk of extracted.extraChunks) {
8578
+ chunkMap.set(chunk.keyword, chunk);
8579
+ if (chunk.keyword.startsWith("chara-ext-asset_")) {
8580
+ const suffix = chunk.keyword.replace("chara-ext-asset_", "");
8581
+ chunkMap.set(suffix, chunk);
8582
+ if (suffix.startsWith(":")) {
8583
+ chunkMap.set(suffix.substring(1), chunk);
8584
+ }
8585
+ }
8586
+ }
8488
8587
  for (const descriptor of card.data.assets) {
8489
8588
  if (!descriptor.uri) continue;
8490
8589
  if (descriptor.uri.startsWith("__asset:") || descriptor.uri.startsWith("asset:") || descriptor.uri.startsWith("pngchunk:") || !descriptor.uri.includes(":")) {
@@ -8508,13 +8607,11 @@ function parsePng(data, options) {
8508
8607
  `chara-ext-asset_:${assetId}`
8509
8608
  // "chara-ext-asset_:0"
8510
8609
  ];
8511
- const chunk = extracted.extraChunks.find((c) => candidates.includes(c.keyword)) || extracted.extraChunks.find((c) => {
8512
- if (c.keyword.startsWith("chara-ext-asset_")) {
8513
- const suffix = c.keyword.replace("chara-ext-asset_", "");
8514
- return suffix === assetId || suffix === `:${assetId}` || suffix === descriptor.uri;
8515
- }
8516
- return false;
8517
- });
8610
+ let chunk;
8611
+ for (const candidate of candidates) {
8612
+ chunk = chunkMap.get(candidate);
8613
+ if (chunk) break;
8614
+ }
8518
8615
  if (chunk) {
8519
8616
  try {
8520
8617
  const estimatedSize = estimateBase64DecodedSize(chunk.text.length);
@@ -8716,6 +8813,9 @@ function parseVoxta(data, options) {
8716
8813
  };
8717
8814
  }
8718
8815
  function parseJson(data, options) {
8816
+ if (data.length > options.maxFileSize) {
8817
+ throw new SizeLimitError(data.length, options.maxFileSize, "JSON file");
8818
+ }
8719
8819
  let jsonStr;
8720
8820
  try {
8721
8821
  jsonStr = toString(data);
@@ -8734,6 +8834,9 @@ function parseJson(data, options) {
8734
8834
  "json"
8735
8835
  );
8736
8836
  }
8837
+ return parseJsonFromParsed(parsed, jsonStr, data, options);
8838
+ }
8839
+ function parseJsonFromParsed(parsed, jsonStr, rawBuffer, options) {
8737
8840
  const spec = detectSpec(parsed);
8738
8841
  let card;
8739
8842
  let sourceFormat;
@@ -8758,7 +8861,7 @@ function parseJson(data, options) {
8758
8861
  sourceFormat,
8759
8862
  originalShape: parsed,
8760
8863
  rawJson: jsonStr,
8761
- rawBuffer: data
8864
+ rawBuffer
8762
8865
  };
8763
8866
  }
8764
8867
  function parseCard(data, options = {}) {
@@ -8784,6 +8887,7 @@ function parseCard(data, options = {}) {
8784
8887
  async function parseCardAsync(data, options = {}) {
8785
8888
  return parseCard(data, options);
8786
8889
  }
8890
+ var DEFAULT_MAX_LOREBOOK_SIZE = 10 * 1024 * 1024;
8787
8891
 
8788
8892
  // ../exporter/dist/index.js
8789
8893
  function checkPngLoss(card, assets) {