@character-foundry/character-foundry 0.1.3 → 0.1.5
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.
- package/README.md +70 -0
- package/dist/app-framework.cjs +1742 -0
- package/dist/app-framework.cjs.map +1 -0
- package/dist/app-framework.d.cts +881 -0
- package/dist/app-framework.d.ts +881 -2
- package/dist/app-framework.js +1718 -1
- package/dist/app-framework.js.map +1 -1
- package/dist/charx.cjs +917 -0
- package/dist/charx.cjs.map +1 -0
- package/dist/charx.d.cts +640 -0
- package/dist/charx.d.ts +640 -2
- package/dist/charx.js +893 -1
- package/dist/charx.js.map +1 -1
- package/dist/core.cjs +668 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +363 -0
- package/dist/core.d.ts +363 -2
- package/dist/core.js +644 -1
- package/dist/core.js.map +1 -1
- package/dist/exporter.cjs +7539 -0
- package/dist/exporter.cjs.map +1 -0
- package/dist/exporter.d.cts +681 -0
- package/dist/exporter.d.ts +681 -2
- package/dist/exporter.js +7522 -1
- package/dist/exporter.js.map +1 -1
- package/dist/federation.cjs +3915 -0
- package/dist/federation.cjs.map +1 -0
- package/dist/federation.d.cts +2951 -0
- package/dist/federation.d.ts +2951 -2
- package/dist/federation.js +3891 -1
- package/dist/federation.js.map +1 -1
- package/dist/index.cjs +9109 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1119 -0
- package/dist/index.d.ts +1113 -20
- package/dist/index.js +9092 -26
- package/dist/index.js.map +1 -1
- package/dist/loader.cjs +8923 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.d.cts +1037 -0
- package/dist/loader.d.ts +1037 -2
- package/dist/loader.js +8906 -1
- package/dist/loader.js.map +1 -1
- package/dist/lorebook.cjs +865 -0
- package/dist/lorebook.cjs.map +1 -0
- package/dist/lorebook.d.cts +1008 -0
- package/dist/lorebook.d.ts +1008 -2
- package/dist/lorebook.js +841 -1
- package/dist/lorebook.js.map +1 -1
- package/dist/media.cjs +6660 -0
- package/dist/media.cjs.map +1 -0
- package/dist/media.d.cts +87 -0
- package/dist/media.d.ts +87 -2
- package/dist/media.js +6643 -1
- package/dist/media.js.map +1 -1
- package/dist/normalizer.cjs +502 -0
- package/dist/normalizer.cjs.map +1 -0
- package/dist/normalizer.d.cts +1216 -0
- package/dist/normalizer.d.ts +1216 -2
- package/dist/normalizer.js +478 -1
- package/dist/normalizer.js.map +1 -1
- package/dist/png.cjs +778 -0
- package/dist/png.cjs.map +1 -0
- package/dist/png.d.cts +786 -0
- package/dist/png.d.ts +786 -2
- package/dist/png.js +754 -1
- package/dist/png.js.map +1 -1
- package/dist/schemas.cjs +799 -0
- package/dist/schemas.cjs.map +1 -0
- package/dist/schemas.d.cts +2178 -0
- package/dist/schemas.d.ts +2178 -2
- package/dist/schemas.js +775 -1
- package/dist/schemas.js.map +1 -1
- package/dist/tokenizers.cjs +153 -0
- package/dist/tokenizers.cjs.map +1 -0
- package/dist/tokenizers.d.cts +155 -0
- package/dist/tokenizers.d.ts +155 -2
- package/dist/tokenizers.js +129 -1
- package/dist/tokenizers.js.map +1 -1
- package/dist/voxta.cjs +7995 -0
- package/dist/voxta.cjs.map +1 -0
- package/dist/voxta.d.cts +1349 -0
- package/dist/voxta.d.ts +1349 -2
- package/dist/voxta.js +7978 -1
- package/dist/voxta.js.map +1 -1
- package/package.json +177 -45
- package/dist/app-framework.d.ts.map +0 -1
- package/dist/charx.d.ts.map +0 -1
- package/dist/core.d.ts.map +0 -1
- package/dist/exporter.d.ts.map +0 -1
- package/dist/federation.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/loader.d.ts.map +0 -1
- package/dist/lorebook.d.ts.map +0 -1
- package/dist/media.d.ts.map +0 -1
- package/dist/normalizer.d.ts.map +0 -1
- package/dist/png.d.ts.map +0 -1
- package/dist/schemas.d.ts.map +0 -1
- package/dist/tokenizers.d.ts.map +0 -1
- package/dist/voxta.d.ts.map +0 -1
package/dist/charx.cjs
ADDED
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/charx.ts
|
|
21
|
+
var charx_exports = {};
|
|
22
|
+
__export(charx_exports, {
|
|
23
|
+
isCharX: () => isCharX,
|
|
24
|
+
isJpegCharX: () => isJpegCharX,
|
|
25
|
+
readCardJsonOnly: () => readCardJsonOnly,
|
|
26
|
+
readCharX: () => readCharX,
|
|
27
|
+
readCharXAsync: () => readCharXAsync,
|
|
28
|
+
writeCharX: () => writeCharX,
|
|
29
|
+
writeCharXAsync: () => writeCharXAsync
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(charx_exports);
|
|
32
|
+
|
|
33
|
+
// ../core/dist/index.js
|
|
34
|
+
function fromString(str) {
|
|
35
|
+
return new TextEncoder().encode(str);
|
|
36
|
+
}
|
|
37
|
+
function toString(data) {
|
|
38
|
+
return new TextDecoder().decode(data);
|
|
39
|
+
}
|
|
40
|
+
var isNode = typeof process !== "undefined" && process.versions != null && process.versions.node != null;
|
|
41
|
+
function decode(base64) {
|
|
42
|
+
if (isNode) {
|
|
43
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
44
|
+
}
|
|
45
|
+
const binary = atob(base64);
|
|
46
|
+
const result = new Uint8Array(binary.length);
|
|
47
|
+
for (let i = 0; i < binary.length; i++) {
|
|
48
|
+
result[i] = binary.charCodeAt(i);
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
var ENCODE_CHUNK_SIZE = 64 * 1024;
|
|
53
|
+
var FOUNDRY_ERROR_MARKER = /* @__PURE__ */ Symbol.for("@character-foundry/core:FoundryError");
|
|
54
|
+
var FoundryError = class extends Error {
|
|
55
|
+
constructor(message, code) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.code = code;
|
|
58
|
+
this.name = "FoundryError";
|
|
59
|
+
if (Error.captureStackTrace) {
|
|
60
|
+
Error.captureStackTrace(this, this.constructor);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** @internal Marker for cross-module identification */
|
|
64
|
+
[FOUNDRY_ERROR_MARKER] = true;
|
|
65
|
+
};
|
|
66
|
+
var ParseError = class extends FoundryError {
|
|
67
|
+
constructor(message, format) {
|
|
68
|
+
super(message, "PARSE_ERROR");
|
|
69
|
+
this.format = format;
|
|
70
|
+
this.name = "ParseError";
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var SizeLimitError = class extends FoundryError {
|
|
74
|
+
constructor(actualSize, maxSize, context) {
|
|
75
|
+
const actualMB = (actualSize / 1024 / 1024).toFixed(2);
|
|
76
|
+
const maxMB = (maxSize / 1024 / 1024).toFixed(2);
|
|
77
|
+
const msg = context ? `${context}: Size ${actualMB}MB exceeds limit ${maxMB}MB` : `Size ${actualMB}MB exceeds limit ${maxMB}MB`;
|
|
78
|
+
super(msg, "SIZE_LIMIT_EXCEEDED");
|
|
79
|
+
this.actualSize = actualSize;
|
|
80
|
+
this.maxSize = maxSize;
|
|
81
|
+
this.name = "SizeLimitError";
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function normalizeURI(uri) {
|
|
85
|
+
const trimmed = uri.trim();
|
|
86
|
+
if (trimmed.startsWith("embedded://")) {
|
|
87
|
+
return "embeded://" + trimmed.substring("embedded://".length);
|
|
88
|
+
}
|
|
89
|
+
if (trimmed.startsWith("__asset:")) {
|
|
90
|
+
const id = trimmed.substring("__asset:".length);
|
|
91
|
+
return `pngchunk:${id}`;
|
|
92
|
+
}
|
|
93
|
+
if (trimmed.startsWith("asset:")) {
|
|
94
|
+
const id = trimmed.substring("asset:".length);
|
|
95
|
+
return `pngchunk:${id}`;
|
|
96
|
+
}
|
|
97
|
+
if (trimmed.startsWith("chara-ext-asset_:")) {
|
|
98
|
+
const id = trimmed.substring("chara-ext-asset_:".length);
|
|
99
|
+
return `pngchunk:${id}`;
|
|
100
|
+
}
|
|
101
|
+
if (trimmed.startsWith("chara-ext-asset_")) {
|
|
102
|
+
const id = trimmed.substring("chara-ext-asset_".length);
|
|
103
|
+
return `pngchunk:${id}`;
|
|
104
|
+
}
|
|
105
|
+
return trimmed;
|
|
106
|
+
}
|
|
107
|
+
function parseURI(uri) {
|
|
108
|
+
const trimmed = uri.trim();
|
|
109
|
+
const normalized = normalizeURI(trimmed);
|
|
110
|
+
if (trimmed.startsWith("__asset:") || trimmed.startsWith("asset:") || trimmed.startsWith("chara-ext-asset_") || trimmed.startsWith("pngchunk:")) {
|
|
111
|
+
let assetId;
|
|
112
|
+
if (trimmed.startsWith("__asset:")) {
|
|
113
|
+
assetId = trimmed.substring("__asset:".length);
|
|
114
|
+
} else if (trimmed.startsWith("asset:")) {
|
|
115
|
+
assetId = trimmed.substring("asset:".length);
|
|
116
|
+
} else if (trimmed.startsWith("chara-ext-asset_:")) {
|
|
117
|
+
assetId = trimmed.substring("chara-ext-asset_:".length);
|
|
118
|
+
} else if (trimmed.startsWith("pngchunk:")) {
|
|
119
|
+
assetId = trimmed.substring("pngchunk:".length);
|
|
120
|
+
} else {
|
|
121
|
+
assetId = trimmed.substring("chara-ext-asset_".length);
|
|
122
|
+
}
|
|
123
|
+
const candidates = [
|
|
124
|
+
assetId,
|
|
125
|
+
// "0" or "filename.png"
|
|
126
|
+
trimmed,
|
|
127
|
+
// Original URI
|
|
128
|
+
`asset:${assetId}`,
|
|
129
|
+
// "asset:0"
|
|
130
|
+
`__asset:${assetId}`,
|
|
131
|
+
// "__asset:0"
|
|
132
|
+
`__asset_${assetId}`,
|
|
133
|
+
// "__asset_0"
|
|
134
|
+
`chara-ext-asset_${assetId}`,
|
|
135
|
+
// "chara-ext-asset_0"
|
|
136
|
+
`chara-ext-asset_:${assetId}`,
|
|
137
|
+
// "chara-ext-asset_:0"
|
|
138
|
+
`pngchunk:${assetId}`
|
|
139
|
+
// "pngchunk:0"
|
|
140
|
+
];
|
|
141
|
+
return {
|
|
142
|
+
scheme: "pngchunk",
|
|
143
|
+
originalUri: uri,
|
|
144
|
+
normalizedUri: normalized,
|
|
145
|
+
chunkKey: assetId,
|
|
146
|
+
chunkCandidates: candidates
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (trimmed === "ccdefault:" || trimmed.startsWith("ccdefault:")) {
|
|
150
|
+
return {
|
|
151
|
+
scheme: "ccdefault",
|
|
152
|
+
originalUri: uri,
|
|
153
|
+
normalizedUri: normalized
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (trimmed.startsWith("embeded://") || trimmed.startsWith("embedded://")) {
|
|
157
|
+
const path = trimmed.startsWith("embeded://") ? trimmed.substring("embeded://".length) : trimmed.substring("embedded://".length);
|
|
158
|
+
return {
|
|
159
|
+
scheme: "embeded",
|
|
160
|
+
originalUri: uri,
|
|
161
|
+
normalizedUri: normalized,
|
|
162
|
+
path
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
if (trimmed.startsWith("https://")) {
|
|
166
|
+
return {
|
|
167
|
+
scheme: "https",
|
|
168
|
+
originalUri: uri,
|
|
169
|
+
normalizedUri: normalized,
|
|
170
|
+
url: trimmed
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (trimmed.startsWith("http://")) {
|
|
174
|
+
return {
|
|
175
|
+
scheme: "http",
|
|
176
|
+
originalUri: uri,
|
|
177
|
+
normalizedUri: normalized,
|
|
178
|
+
url: trimmed
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (trimmed.startsWith("data:")) {
|
|
182
|
+
const parsed = parseDataURI(trimmed);
|
|
183
|
+
return {
|
|
184
|
+
scheme: "data",
|
|
185
|
+
originalUri: uri,
|
|
186
|
+
normalizedUri: normalized,
|
|
187
|
+
...parsed
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (trimmed.startsWith("file://")) {
|
|
191
|
+
const path = trimmed.substring("file://".length);
|
|
192
|
+
return {
|
|
193
|
+
scheme: "file",
|
|
194
|
+
originalUri: uri,
|
|
195
|
+
normalizedUri: normalized,
|
|
196
|
+
path
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
|
|
200
|
+
return {
|
|
201
|
+
scheme: "internal",
|
|
202
|
+
originalUri: uri,
|
|
203
|
+
normalizedUri: normalized,
|
|
204
|
+
path: trimmed
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
scheme: "unknown",
|
|
209
|
+
originalUri: uri,
|
|
210
|
+
normalizedUri: normalized
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function parseDataURI(uri) {
|
|
214
|
+
const match = uri.match(/^data:([^;,]+)?(;base64)?,(.*)$/);
|
|
215
|
+
if (!match) {
|
|
216
|
+
return {};
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
mimeType: match[1] || "text/plain",
|
|
220
|
+
encoding: match[2] ? "base64" : void 0,
|
|
221
|
+
data: match[3]
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function getMimeTypeFromExt(ext) {
|
|
225
|
+
const extToMime = {
|
|
226
|
+
// Images
|
|
227
|
+
"png": "image/png",
|
|
228
|
+
"jpg": "image/jpeg",
|
|
229
|
+
"jpeg": "image/jpeg",
|
|
230
|
+
"webp": "image/webp",
|
|
231
|
+
"gif": "image/gif",
|
|
232
|
+
"avif": "image/avif",
|
|
233
|
+
"svg": "image/svg+xml",
|
|
234
|
+
"bmp": "image/bmp",
|
|
235
|
+
"ico": "image/x-icon",
|
|
236
|
+
// Audio
|
|
237
|
+
"mp3": "audio/mpeg",
|
|
238
|
+
"wav": "audio/wav",
|
|
239
|
+
"ogg": "audio/ogg",
|
|
240
|
+
"flac": "audio/flac",
|
|
241
|
+
"m4a": "audio/mp4",
|
|
242
|
+
"aac": "audio/aac",
|
|
243
|
+
// Video
|
|
244
|
+
"mp4": "video/mp4",
|
|
245
|
+
"webm": "video/webm",
|
|
246
|
+
"avi": "video/x-msvideo",
|
|
247
|
+
"mov": "video/quicktime",
|
|
248
|
+
"mkv": "video/x-matroska",
|
|
249
|
+
// Text/Data
|
|
250
|
+
"json": "application/json",
|
|
251
|
+
"txt": "text/plain",
|
|
252
|
+
"html": "text/html",
|
|
253
|
+
"css": "text/css",
|
|
254
|
+
"js": "application/javascript"
|
|
255
|
+
};
|
|
256
|
+
return extToMime[ext.toLowerCase()] || "application/octet-stream";
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ../core/dist/zip.js
|
|
260
|
+
var import_fflate = require("fflate");
|
|
261
|
+
function indexOf(data, search, fromIndex = 0) {
|
|
262
|
+
outer: for (let i = fromIndex; i <= data.length - search.length; i++) {
|
|
263
|
+
for (let j = 0; j < search.length; j++) {
|
|
264
|
+
if (data[i + j] !== search[j]) continue outer;
|
|
265
|
+
}
|
|
266
|
+
return i;
|
|
267
|
+
}
|
|
268
|
+
return -1;
|
|
269
|
+
}
|
|
270
|
+
function concat(...arrays) {
|
|
271
|
+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
|
|
272
|
+
const result = new Uint8Array(totalLength);
|
|
273
|
+
let offset = 0;
|
|
274
|
+
for (const arr of arrays) {
|
|
275
|
+
result.set(arr, offset);
|
|
276
|
+
offset += arr.length;
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
var ZIP_SIGNATURE = new Uint8Array([80, 75, 3, 4]);
|
|
281
|
+
var JPEG_SIGNATURE = new Uint8Array([255, 216, 255]);
|
|
282
|
+
var DEFAULT_ZIP_LIMITS = {
|
|
283
|
+
maxFileSize: 50 * 1024 * 1024,
|
|
284
|
+
// 50MB per file (Risu standard)
|
|
285
|
+
maxTotalSize: 500 * 1024 * 1024,
|
|
286
|
+
// 500MB total (CharX can have many expression assets)
|
|
287
|
+
maxFiles: 1e4,
|
|
288
|
+
// CharX cards can have 2k+ expression assets
|
|
289
|
+
unsafePathHandling: "skip"
|
|
290
|
+
// Backwards compatible default
|
|
291
|
+
};
|
|
292
|
+
function isJPEG(data) {
|
|
293
|
+
return data.length >= 3 && data[0] === 255 && data[1] === 216 && data[2] === 255;
|
|
294
|
+
}
|
|
295
|
+
function isJpegCharX(data) {
|
|
296
|
+
if (!isJPEG(data)) return false;
|
|
297
|
+
return indexOf(data, ZIP_SIGNATURE) > 0;
|
|
298
|
+
}
|
|
299
|
+
function findZipStart(data) {
|
|
300
|
+
const index = indexOf(data, ZIP_SIGNATURE);
|
|
301
|
+
if (index > 0) {
|
|
302
|
+
return data.subarray(index);
|
|
303
|
+
}
|
|
304
|
+
return data;
|
|
305
|
+
}
|
|
306
|
+
function getZipOffset(data) {
|
|
307
|
+
return indexOf(data, ZIP_SIGNATURE);
|
|
308
|
+
}
|
|
309
|
+
function isPathSafe(path) {
|
|
310
|
+
if (path.startsWith("/") || /^[a-zA-Z]:/.test(path)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
if (path.includes("..")) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
if (path.includes("\\")) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
var ZipPreflightError = class extends Error {
|
|
322
|
+
constructor(message, totalSize, maxSize, oversizedEntry, entrySize, maxEntrySize) {
|
|
323
|
+
super(message);
|
|
324
|
+
this.totalSize = totalSize;
|
|
325
|
+
this.maxSize = maxSize;
|
|
326
|
+
this.oversizedEntry = oversizedEntry;
|
|
327
|
+
this.entrySize = entrySize;
|
|
328
|
+
this.maxEntrySize = maxEntrySize;
|
|
329
|
+
this.name = "ZipPreflightError";
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
function streamingUnzipSync(data, limits = DEFAULT_ZIP_LIMITS) {
|
|
333
|
+
const zipData = findZipStart(data);
|
|
334
|
+
const result = {};
|
|
335
|
+
let totalBytes = 0;
|
|
336
|
+
let fileCount = 0;
|
|
337
|
+
let error = null;
|
|
338
|
+
const unsafePathHandling = limits.unsafePathHandling ?? "skip";
|
|
339
|
+
const fileChunks = /* @__PURE__ */ new Map();
|
|
340
|
+
const unzipper = new import_fflate.Unzip((file) => {
|
|
341
|
+
if (error) return;
|
|
342
|
+
if (file.name.endsWith("/")) {
|
|
343
|
+
file.start();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (!isPathSafe(file.name)) {
|
|
347
|
+
const reason = file.name.includes("..") ? "path traversal (..)" : file.name.startsWith("/") || /^[a-zA-Z]:/.test(file.name) ? "absolute path" : "backslash in path";
|
|
348
|
+
if (unsafePathHandling === "reject") {
|
|
349
|
+
error = new ZipPreflightError(
|
|
350
|
+
`Unsafe path detected: "${file.name}" - ${reason}. This may be a path traversal attack.`
|
|
351
|
+
);
|
|
352
|
+
file.terminate();
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (unsafePathHandling === "warn" && limits.onUnsafePath) {
|
|
356
|
+
limits.onUnsafePath(file.name, reason);
|
|
357
|
+
}
|
|
358
|
+
file.ondata = () => {
|
|
359
|
+
};
|
|
360
|
+
file.start();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
fileCount++;
|
|
364
|
+
if (fileCount > limits.maxFiles) {
|
|
365
|
+
error = new ZipPreflightError(
|
|
366
|
+
`File count ${fileCount} exceeds limit ${limits.maxFiles}`
|
|
367
|
+
);
|
|
368
|
+
file.terminate();
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const chunks = [];
|
|
372
|
+
fileChunks.set(file.name, chunks);
|
|
373
|
+
let fileBytes = 0;
|
|
374
|
+
file.ondata = (err, chunk, final) => {
|
|
375
|
+
if (error) return;
|
|
376
|
+
if (err) {
|
|
377
|
+
error = err;
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (chunk && chunk.length > 0) {
|
|
381
|
+
fileBytes += chunk.length;
|
|
382
|
+
totalBytes += chunk.length;
|
|
383
|
+
if (fileBytes > limits.maxFileSize) {
|
|
384
|
+
error = new ZipPreflightError(
|
|
385
|
+
`File "${file.name}" actual size ${fileBytes} exceeds limit ${limits.maxFileSize}`,
|
|
386
|
+
void 0,
|
|
387
|
+
void 0,
|
|
388
|
+
file.name,
|
|
389
|
+
fileBytes,
|
|
390
|
+
limits.maxFileSize
|
|
391
|
+
);
|
|
392
|
+
file.terminate();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (totalBytes > limits.maxTotalSize) {
|
|
396
|
+
error = new ZipPreflightError(
|
|
397
|
+
`Total actual size ${totalBytes} exceeds limit ${limits.maxTotalSize}`,
|
|
398
|
+
totalBytes,
|
|
399
|
+
limits.maxTotalSize
|
|
400
|
+
);
|
|
401
|
+
file.terminate();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
chunks.push(chunk);
|
|
405
|
+
}
|
|
406
|
+
if (final && !error) {
|
|
407
|
+
result[file.name] = concat(...chunks);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
file.start();
|
|
411
|
+
});
|
|
412
|
+
unzipper.register(import_fflate.UnzipInflate);
|
|
413
|
+
unzipper.register(import_fflate.UnzipPassThrough);
|
|
414
|
+
unzipper.push(zipData, true);
|
|
415
|
+
if (error) {
|
|
416
|
+
throw error;
|
|
417
|
+
}
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ../schemas/dist/index.js
|
|
422
|
+
var import_zod = require("zod");
|
|
423
|
+
var import_zod2 = require("zod");
|
|
424
|
+
var import_zod3 = require("zod");
|
|
425
|
+
var import_zod4 = require("zod");
|
|
426
|
+
var ISO8601Schema = import_zod.z.string().datetime();
|
|
427
|
+
var UUIDSchema = import_zod.z.string().uuid();
|
|
428
|
+
var SpecSchema = import_zod.z.enum(["v2", "v3"]);
|
|
429
|
+
var SourceFormatSchema = import_zod.z.enum([
|
|
430
|
+
"png_v2",
|
|
431
|
+
// PNG with 'chara' chunk (v2)
|
|
432
|
+
"png_v3",
|
|
433
|
+
// PNG with 'ccv3' chunk (v3)
|
|
434
|
+
"json_v2",
|
|
435
|
+
// Raw JSON v2
|
|
436
|
+
"json_v3",
|
|
437
|
+
// Raw JSON v3
|
|
438
|
+
"charx",
|
|
439
|
+
// ZIP with card.json (v3 spec)
|
|
440
|
+
"charx_risu",
|
|
441
|
+
// ZIP with card.json + module.risum
|
|
442
|
+
"charx_jpeg",
|
|
443
|
+
// JPEG with appended ZIP (read-only)
|
|
444
|
+
"voxta"
|
|
445
|
+
// VoxPkg format
|
|
446
|
+
]);
|
|
447
|
+
var OriginalShapeSchema = import_zod.z.enum(["wrapped", "unwrapped", "legacy"]);
|
|
448
|
+
var AssetTypeSchema = import_zod.z.enum([
|
|
449
|
+
"icon",
|
|
450
|
+
"background",
|
|
451
|
+
"emotion",
|
|
452
|
+
"user_icon",
|
|
453
|
+
"sound",
|
|
454
|
+
"video",
|
|
455
|
+
"custom",
|
|
456
|
+
"x-risu-asset"
|
|
457
|
+
]);
|
|
458
|
+
var AssetDescriptorSchema = import_zod.z.object({
|
|
459
|
+
type: AssetTypeSchema,
|
|
460
|
+
uri: import_zod.z.string(),
|
|
461
|
+
name: import_zod.z.string(),
|
|
462
|
+
ext: import_zod.z.string()
|
|
463
|
+
});
|
|
464
|
+
var ExtractedAssetSchema = import_zod.z.object({
|
|
465
|
+
descriptor: AssetDescriptorSchema,
|
|
466
|
+
data: import_zod.z.instanceof(Uint8Array),
|
|
467
|
+
mimeType: import_zod.z.string()
|
|
468
|
+
});
|
|
469
|
+
var CCv2LorebookEntrySchema = import_zod2.z.object({
|
|
470
|
+
keys: import_zod2.z.array(import_zod2.z.string()),
|
|
471
|
+
content: import_zod2.z.string(),
|
|
472
|
+
enabled: import_zod2.z.boolean(),
|
|
473
|
+
insertion_order: import_zod2.z.number().int(),
|
|
474
|
+
// Optional fields
|
|
475
|
+
extensions: import_zod2.z.record(import_zod2.z.unknown()).optional(),
|
|
476
|
+
case_sensitive: import_zod2.z.boolean().optional(),
|
|
477
|
+
name: import_zod2.z.string().optional(),
|
|
478
|
+
priority: import_zod2.z.number().int().optional(),
|
|
479
|
+
id: import_zod2.z.number().int().optional(),
|
|
480
|
+
comment: import_zod2.z.string().optional(),
|
|
481
|
+
selective: import_zod2.z.boolean().optional(),
|
|
482
|
+
secondary_keys: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
483
|
+
constant: import_zod2.z.boolean().optional(),
|
|
484
|
+
position: import_zod2.z.enum(["before_char", "after_char"]).optional()
|
|
485
|
+
});
|
|
486
|
+
var CCv2CharacterBookSchema = import_zod2.z.object({
|
|
487
|
+
name: import_zod2.z.string().optional(),
|
|
488
|
+
description: import_zod2.z.string().optional(),
|
|
489
|
+
scan_depth: import_zod2.z.number().int().nonnegative().optional(),
|
|
490
|
+
token_budget: import_zod2.z.number().int().nonnegative().optional(),
|
|
491
|
+
recursive_scanning: import_zod2.z.boolean().optional(),
|
|
492
|
+
extensions: import_zod2.z.record(import_zod2.z.unknown()).optional(),
|
|
493
|
+
entries: import_zod2.z.array(CCv2LorebookEntrySchema)
|
|
494
|
+
});
|
|
495
|
+
var CCv2DataSchema = import_zod2.z.object({
|
|
496
|
+
// Core fields - use .default('') to handle missing fields in malformed cards
|
|
497
|
+
name: import_zod2.z.string().default(""),
|
|
498
|
+
description: import_zod2.z.string().default(""),
|
|
499
|
+
personality: import_zod2.z.string().nullable().default(""),
|
|
500
|
+
// Can be null in wild (141 cards)
|
|
501
|
+
scenario: import_zod2.z.string().default(""),
|
|
502
|
+
first_mes: import_zod2.z.string().default(""),
|
|
503
|
+
mes_example: import_zod2.z.string().nullable().default(""),
|
|
504
|
+
// Can be null in wild (186 cards)
|
|
505
|
+
// Optional fields
|
|
506
|
+
creator_notes: import_zod2.z.string().optional(),
|
|
507
|
+
system_prompt: import_zod2.z.string().optional(),
|
|
508
|
+
post_history_instructions: import_zod2.z.string().optional(),
|
|
509
|
+
alternate_greetings: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
510
|
+
character_book: CCv2CharacterBookSchema.optional().nullable(),
|
|
511
|
+
tags: import_zod2.z.array(import_zod2.z.string()).optional(),
|
|
512
|
+
creator: import_zod2.z.string().optional(),
|
|
513
|
+
character_version: import_zod2.z.string().optional(),
|
|
514
|
+
extensions: import_zod2.z.record(import_zod2.z.unknown()).optional()
|
|
515
|
+
});
|
|
516
|
+
var CCv2WrappedSchema = import_zod2.z.object({
|
|
517
|
+
spec: import_zod2.z.literal("chara_card_v2"),
|
|
518
|
+
spec_version: import_zod2.z.literal("2.0"),
|
|
519
|
+
data: CCv2DataSchema
|
|
520
|
+
});
|
|
521
|
+
var CCv3LorebookEntrySchema = import_zod3.z.object({
|
|
522
|
+
keys: import_zod3.z.array(import_zod3.z.string()),
|
|
523
|
+
content: import_zod3.z.string(),
|
|
524
|
+
enabled: import_zod3.z.boolean(),
|
|
525
|
+
insertion_order: import_zod3.z.number().int(),
|
|
526
|
+
// Optional fields
|
|
527
|
+
case_sensitive: import_zod3.z.boolean().optional(),
|
|
528
|
+
name: import_zod3.z.string().optional(),
|
|
529
|
+
priority: import_zod3.z.number().int().optional(),
|
|
530
|
+
id: import_zod3.z.number().int().optional(),
|
|
531
|
+
comment: import_zod3.z.string().optional(),
|
|
532
|
+
selective: import_zod3.z.boolean().optional(),
|
|
533
|
+
secondary_keys: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
534
|
+
constant: import_zod3.z.boolean().optional(),
|
|
535
|
+
position: import_zod3.z.enum(["before_char", "after_char"]).optional(),
|
|
536
|
+
extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
537
|
+
// v3 specific
|
|
538
|
+
automation_id: import_zod3.z.string().optional(),
|
|
539
|
+
role: import_zod3.z.enum(["system", "user", "assistant"]).optional(),
|
|
540
|
+
group: import_zod3.z.string().optional(),
|
|
541
|
+
scan_frequency: import_zod3.z.number().int().nonnegative().optional(),
|
|
542
|
+
probability: import_zod3.z.number().min(0).max(1).optional(),
|
|
543
|
+
use_regex: import_zod3.z.boolean().optional(),
|
|
544
|
+
depth: import_zod3.z.number().int().nonnegative().optional(),
|
|
545
|
+
selective_logic: import_zod3.z.enum(["AND", "NOT"]).optional()
|
|
546
|
+
});
|
|
547
|
+
var CCv3CharacterBookSchema = import_zod3.z.object({
|
|
548
|
+
name: import_zod3.z.string().optional(),
|
|
549
|
+
description: import_zod3.z.string().optional(),
|
|
550
|
+
scan_depth: import_zod3.z.number().int().nonnegative().optional(),
|
|
551
|
+
token_budget: import_zod3.z.number().int().nonnegative().optional(),
|
|
552
|
+
recursive_scanning: import_zod3.z.boolean().optional(),
|
|
553
|
+
extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
554
|
+
entries: import_zod3.z.array(CCv3LorebookEntrySchema)
|
|
555
|
+
});
|
|
556
|
+
var CCv3DataInnerSchema = import_zod3.z.object({
|
|
557
|
+
// Core fields - use .default('') to handle missing fields in malformed cards
|
|
558
|
+
name: import_zod3.z.string().default(""),
|
|
559
|
+
description: import_zod3.z.string().default(""),
|
|
560
|
+
personality: import_zod3.z.string().nullable().default(""),
|
|
561
|
+
// Can be null in wild (141 cards)
|
|
562
|
+
scenario: import_zod3.z.string().default(""),
|
|
563
|
+
first_mes: import_zod3.z.string().default(""),
|
|
564
|
+
mes_example: import_zod3.z.string().nullable().default(""),
|
|
565
|
+
// Can be null in wild (186 cards)
|
|
566
|
+
// "Required" per spec but often missing in wild - use defaults for leniency
|
|
567
|
+
creator: import_zod3.z.string().default(""),
|
|
568
|
+
character_version: import_zod3.z.string().default(""),
|
|
569
|
+
tags: import_zod3.z.array(import_zod3.z.string()).default([]),
|
|
570
|
+
group_only_greetings: import_zod3.z.array(import_zod3.z.string()).default([]),
|
|
571
|
+
// Optional fields
|
|
572
|
+
creator_notes: import_zod3.z.string().optional(),
|
|
573
|
+
system_prompt: import_zod3.z.string().optional(),
|
|
574
|
+
post_history_instructions: import_zod3.z.string().optional(),
|
|
575
|
+
alternate_greetings: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
576
|
+
character_book: CCv3CharacterBookSchema.optional().nullable(),
|
|
577
|
+
extensions: import_zod3.z.record(import_zod3.z.unknown()).optional(),
|
|
578
|
+
// v3 specific
|
|
579
|
+
assets: import_zod3.z.array(AssetDescriptorSchema).optional(),
|
|
580
|
+
nickname: import_zod3.z.string().optional(),
|
|
581
|
+
creator_notes_multilingual: import_zod3.z.record(import_zod3.z.string()).optional(),
|
|
582
|
+
source: import_zod3.z.array(import_zod3.z.string()).optional(),
|
|
583
|
+
creation_date: import_zod3.z.number().int().nonnegative().optional(),
|
|
584
|
+
// Unix timestamp in seconds
|
|
585
|
+
modification_date: import_zod3.z.number().int().nonnegative().optional()
|
|
586
|
+
// Unix timestamp in seconds
|
|
587
|
+
});
|
|
588
|
+
var CCv3DataSchema = import_zod3.z.object({
|
|
589
|
+
spec: import_zod3.z.literal("chara_card_v3"),
|
|
590
|
+
spec_version: import_zod3.z.literal("3.0"),
|
|
591
|
+
data: CCv3DataInnerSchema
|
|
592
|
+
});
|
|
593
|
+
function hasRisuExtensions(extensions) {
|
|
594
|
+
if (!extensions) return false;
|
|
595
|
+
return "risuai" in extensions || "risu" in extensions;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// ../charx/dist/index.js
|
|
599
|
+
var import_fflate2 = require("fflate");
|
|
600
|
+
var DEFAULT_OPTIONS = {
|
|
601
|
+
maxFileSize: 10 * 1024 * 1024,
|
|
602
|
+
// 10MB
|
|
603
|
+
maxAssetSize: 50 * 1024 * 1024,
|
|
604
|
+
// 50MB (Risu standard)
|
|
605
|
+
maxTotalSize: 200 * 1024 * 1024,
|
|
606
|
+
// 200MB
|
|
607
|
+
preserveXMeta: true,
|
|
608
|
+
preserveModuleRisum: true
|
|
609
|
+
};
|
|
610
|
+
function isCharX(data) {
|
|
611
|
+
const zipOffset = getZipOffset(data);
|
|
612
|
+
if (zipOffset < 0) return false;
|
|
613
|
+
const zipData = data.subarray(zipOffset);
|
|
614
|
+
const cardJsonMarker = new TextEncoder().encode("card.json");
|
|
615
|
+
const scanSize = Math.min(zipData.length, 65536);
|
|
616
|
+
const startOffset = zipData.length - scanSize;
|
|
617
|
+
for (let i = startOffset; i < zipData.length - cardJsonMarker.length; i++) {
|
|
618
|
+
let found = true;
|
|
619
|
+
for (let j = 0; j < cardJsonMarker.length; j++) {
|
|
620
|
+
if (zipData[i + j] !== cardJsonMarker[j]) {
|
|
621
|
+
found = false;
|
|
622
|
+
break;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (found) return true;
|
|
626
|
+
}
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
629
|
+
function readCharX(data, options = {}) {
|
|
630
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
631
|
+
let unzipped;
|
|
632
|
+
try {
|
|
633
|
+
unzipped = streamingUnzipSync(data, {
|
|
634
|
+
maxFileSize: opts.maxAssetSize,
|
|
635
|
+
maxTotalSize: opts.maxTotalSize,
|
|
636
|
+
maxFiles: 1e4
|
|
637
|
+
// CharX can have many assets
|
|
638
|
+
});
|
|
639
|
+
} catch (err) {
|
|
640
|
+
if (err instanceof ZipPreflightError) {
|
|
641
|
+
throw new SizeLimitError(
|
|
642
|
+
err.totalSize || err.entrySize || 0,
|
|
643
|
+
err.maxSize || err.maxEntrySize || opts.maxTotalSize,
|
|
644
|
+
err.oversizedEntry || "CharX archive"
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
throw new ParseError(
|
|
648
|
+
`Failed to unzip CharX: ${err instanceof Error ? err.message : String(err)}`,
|
|
649
|
+
"charx"
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
let cardJson = null;
|
|
653
|
+
const assets = [];
|
|
654
|
+
const metadata = /* @__PURE__ */ new Map();
|
|
655
|
+
let moduleRisum;
|
|
656
|
+
for (const [fileName, fileData] of Object.entries(unzipped)) {
|
|
657
|
+
if (fileName.endsWith("/") || fileData.length === 0) continue;
|
|
658
|
+
if (fileName === "card.json") {
|
|
659
|
+
if (fileData.length > opts.maxFileSize) {
|
|
660
|
+
throw new SizeLimitError(fileData.length, opts.maxFileSize, "card.json");
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
const content = toString(fileData);
|
|
664
|
+
cardJson = JSON.parse(content);
|
|
665
|
+
} catch (err) {
|
|
666
|
+
throw new ParseError(
|
|
667
|
+
`Failed to parse card.json: ${err instanceof Error ? err.message : String(err)}`,
|
|
668
|
+
"charx"
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (opts.preserveXMeta) {
|
|
674
|
+
const metaMatch = fileName.match(/^x_meta\/(\d+)\.json$/);
|
|
675
|
+
if (metaMatch) {
|
|
676
|
+
const index = parseInt(metaMatch[1], 10);
|
|
677
|
+
try {
|
|
678
|
+
const content = toString(fileData);
|
|
679
|
+
const meta = JSON.parse(content);
|
|
680
|
+
metadata.set(index, meta);
|
|
681
|
+
} catch {
|
|
682
|
+
}
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (fileName === "module.risum" && opts.preserveModuleRisum) {
|
|
687
|
+
moduleRisum = fileData;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (fileName.startsWith("assets/")) {
|
|
691
|
+
const name = fileName.split("/").pop() || "unknown";
|
|
692
|
+
const ext = name.split(".").pop() || "bin";
|
|
693
|
+
assets.push({
|
|
694
|
+
path: fileName,
|
|
695
|
+
descriptor: {
|
|
696
|
+
type: "custom",
|
|
697
|
+
name: name.replace(/\.[^.]+$/, ""),
|
|
698
|
+
// Remove extension
|
|
699
|
+
uri: `embeded://${fileName}`,
|
|
700
|
+
ext
|
|
701
|
+
},
|
|
702
|
+
buffer: fileData
|
|
703
|
+
});
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (!cardJson) {
|
|
708
|
+
throw new ParseError("CharX file does not contain card.json", "charx");
|
|
709
|
+
}
|
|
710
|
+
if (cardJson.spec !== "chara_card_v3") {
|
|
711
|
+
throw new ParseError(
|
|
712
|
+
`Invalid card spec: expected "chara_card_v3", got "${cardJson.spec}"`,
|
|
713
|
+
"charx"
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
const matchedAssets = matchAssetsToDescriptors(assets, cardJson.data.assets || []);
|
|
717
|
+
const isRisuFormat = !!moduleRisum || hasRisuExtensions(cardJson.data.extensions);
|
|
718
|
+
return {
|
|
719
|
+
card: cardJson,
|
|
720
|
+
assets: matchedAssets,
|
|
721
|
+
metadata: metadata.size > 0 ? metadata : void 0,
|
|
722
|
+
moduleRisum,
|
|
723
|
+
isRisuFormat
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function matchAssetsToDescriptors(extractedAssets, descriptors) {
|
|
727
|
+
const assetsByPath = /* @__PURE__ */ new Map();
|
|
728
|
+
for (const asset of extractedAssets) {
|
|
729
|
+
assetsByPath.set(asset.path, asset);
|
|
730
|
+
}
|
|
731
|
+
const matched = [];
|
|
732
|
+
for (const descriptor of descriptors) {
|
|
733
|
+
const parsed = parseURI(descriptor.uri);
|
|
734
|
+
if (parsed.scheme === "embeded" && parsed.path) {
|
|
735
|
+
const asset = assetsByPath.get(parsed.path);
|
|
736
|
+
if (asset) {
|
|
737
|
+
matched.push({
|
|
738
|
+
...asset,
|
|
739
|
+
descriptor
|
|
740
|
+
});
|
|
741
|
+
} else {
|
|
742
|
+
matched.push({
|
|
743
|
+
path: parsed.path,
|
|
744
|
+
descriptor,
|
|
745
|
+
buffer: void 0
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
} else if (parsed.scheme === "ccdefault") {
|
|
749
|
+
matched.push({
|
|
750
|
+
path: "ccdefault:",
|
|
751
|
+
descriptor,
|
|
752
|
+
buffer: void 0
|
|
753
|
+
});
|
|
754
|
+
} else if (parsed.scheme === "https" || parsed.scheme === "http") {
|
|
755
|
+
matched.push({
|
|
756
|
+
path: descriptor.uri,
|
|
757
|
+
descriptor,
|
|
758
|
+
buffer: void 0
|
|
759
|
+
});
|
|
760
|
+
} else if (parsed.scheme === "data") {
|
|
761
|
+
if (parsed.data && parsed.encoding === "base64") {
|
|
762
|
+
const buffer = decode(parsed.data);
|
|
763
|
+
matched.push({
|
|
764
|
+
path: "data:",
|
|
765
|
+
descriptor,
|
|
766
|
+
buffer
|
|
767
|
+
});
|
|
768
|
+
} else {
|
|
769
|
+
matched.push({
|
|
770
|
+
path: "data:",
|
|
771
|
+
descriptor,
|
|
772
|
+
buffer: void 0
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return matched;
|
|
778
|
+
}
|
|
779
|
+
function readCardJsonOnly(data) {
|
|
780
|
+
let unzipped;
|
|
781
|
+
try {
|
|
782
|
+
unzipped = streamingUnzipSync(data, {
|
|
783
|
+
maxFileSize: DEFAULT_OPTIONS.maxFileSize,
|
|
784
|
+
// 10MB for card.json
|
|
785
|
+
maxTotalSize: DEFAULT_OPTIONS.maxTotalSize,
|
|
786
|
+
// 200MB total
|
|
787
|
+
maxFiles: 1e4
|
|
788
|
+
});
|
|
789
|
+
} catch (err) {
|
|
790
|
+
throw new ParseError(
|
|
791
|
+
`Failed to unzip CharX: ${err instanceof Error ? err.message : String(err)}`,
|
|
792
|
+
"charx"
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
const cardData = unzipped["card.json"];
|
|
796
|
+
if (!cardData) {
|
|
797
|
+
throw new ParseError("card.json not found in CharX file", "charx");
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
const content = toString(cardData);
|
|
801
|
+
return JSON.parse(content);
|
|
802
|
+
} catch (err) {
|
|
803
|
+
throw new ParseError(
|
|
804
|
+
`Failed to parse card.json: ${err instanceof Error ? err.message : String(err)}`,
|
|
805
|
+
"charx"
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
async function readCharXAsync(data, options = {}) {
|
|
810
|
+
const result = readCharX(data, options);
|
|
811
|
+
if (!options.fetchRemoteAssets || !options.assetFetcher) {
|
|
812
|
+
return result;
|
|
813
|
+
}
|
|
814
|
+
const fetchedAssets = await Promise.all(
|
|
815
|
+
result.assets.map(async (asset) => {
|
|
816
|
+
if (asset.buffer) {
|
|
817
|
+
return asset;
|
|
818
|
+
}
|
|
819
|
+
const parsed = parseURI(asset.descriptor.uri);
|
|
820
|
+
if ((parsed.scheme === "https" || parsed.scheme === "http") && parsed.url) {
|
|
821
|
+
try {
|
|
822
|
+
const buffer = await options.assetFetcher(parsed.url);
|
|
823
|
+
if (buffer) {
|
|
824
|
+
return { ...asset, buffer };
|
|
825
|
+
}
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return asset;
|
|
830
|
+
})
|
|
831
|
+
);
|
|
832
|
+
return {
|
|
833
|
+
...result,
|
|
834
|
+
assets: fetchedAssets
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function getCharxCategory(mimetype) {
|
|
838
|
+
if (mimetype.startsWith("image/")) return "images";
|
|
839
|
+
if (mimetype.startsWith("audio/")) return "audio";
|
|
840
|
+
if (mimetype.startsWith("video/")) return "video";
|
|
841
|
+
return "other";
|
|
842
|
+
}
|
|
843
|
+
function sanitizeName(name, ext) {
|
|
844
|
+
let safeName = name;
|
|
845
|
+
if (safeName.toLowerCase().endsWith(`.${ext.toLowerCase()}`)) {
|
|
846
|
+
safeName = safeName.substring(0, safeName.length - (ext.length + 1));
|
|
847
|
+
}
|
|
848
|
+
safeName = safeName.replace(/[._]/g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
849
|
+
if (!safeName) safeName = "asset";
|
|
850
|
+
return safeName;
|
|
851
|
+
}
|
|
852
|
+
function writeCharX(card, assets, options = {}) {
|
|
853
|
+
const {
|
|
854
|
+
spec = "v3",
|
|
855
|
+
compressionLevel = 6,
|
|
856
|
+
emitXMeta = spec === "risu",
|
|
857
|
+
emitReadme = false,
|
|
858
|
+
moduleRisum
|
|
859
|
+
} = options;
|
|
860
|
+
const transformedCard = transformAssetUris(card, assets);
|
|
861
|
+
const zipEntries = {};
|
|
862
|
+
const cardJson = JSON.stringify(transformedCard, null, 2);
|
|
863
|
+
zipEntries["card.json"] = [fromString(cardJson), { level: compressionLevel }];
|
|
864
|
+
if (emitReadme) {
|
|
865
|
+
const readme = `Character: ${card.data.name}
|
|
866
|
+
Created with Character Foundry
|
|
867
|
+
|
|
868
|
+
This is a CharX character card package.
|
|
869
|
+
Import this file into SillyTavern, RisuAI, or other compatible applications.
|
|
870
|
+
`;
|
|
871
|
+
zipEntries["readme.txt"] = [fromString(readme), { level: compressionLevel }];
|
|
872
|
+
}
|
|
873
|
+
let assetCount = 0;
|
|
874
|
+
for (let i = 0; i < assets.length; i++) {
|
|
875
|
+
const asset = assets[i];
|
|
876
|
+
const mimetype = getMimeTypeFromExt(asset.ext);
|
|
877
|
+
const category = getCharxCategory(mimetype);
|
|
878
|
+
const safeName = sanitizeName(asset.name, asset.ext);
|
|
879
|
+
const assetPath = `assets/${asset.type}/${category}/${safeName}.${asset.ext}`;
|
|
880
|
+
zipEntries[assetPath] = [asset.data, { level: compressionLevel }];
|
|
881
|
+
assetCount++;
|
|
882
|
+
if (emitXMeta && mimetype.startsWith("image/")) {
|
|
883
|
+
const metaJson = JSON.stringify({
|
|
884
|
+
type: mimetype.split("/")[1]?.toUpperCase() || "PNG"
|
|
885
|
+
});
|
|
886
|
+
zipEntries[`x_meta/${i}.json`] = [fromString(metaJson), { level: compressionLevel }];
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (moduleRisum) {
|
|
890
|
+
zipEntries["module.risum"] = [moduleRisum, { level: compressionLevel }];
|
|
891
|
+
}
|
|
892
|
+
const buffer = (0, import_fflate2.zipSync)(zipEntries);
|
|
893
|
+
return {
|
|
894
|
+
buffer,
|
|
895
|
+
assetCount,
|
|
896
|
+
totalSize: buffer.length
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function transformAssetUris(card, assets) {
|
|
900
|
+
const transformed = JSON.parse(JSON.stringify(card));
|
|
901
|
+
transformed.data.assets = assets.map((asset) => {
|
|
902
|
+
const mimetype = getMimeTypeFromExt(asset.ext);
|
|
903
|
+
const category = getCharxCategory(mimetype);
|
|
904
|
+
const safeName = sanitizeName(asset.name, asset.ext);
|
|
905
|
+
return {
|
|
906
|
+
type: asset.type,
|
|
907
|
+
uri: `embeded://assets/${asset.type}/${category}/${safeName}.${asset.ext}`,
|
|
908
|
+
name: safeName,
|
|
909
|
+
ext: asset.ext
|
|
910
|
+
};
|
|
911
|
+
});
|
|
912
|
+
return transformed;
|
|
913
|
+
}
|
|
914
|
+
async function writeCharXAsync(card, assets, options = {}) {
|
|
915
|
+
return writeCharX(card, assets, options);
|
|
916
|
+
}
|
|
917
|
+
//# sourceMappingURL=charx.cjs.map
|