Spaces:
Runtime error
Runtime error
Update index.js
Browse files
index.js
CHANGED
|
@@ -61,8 +61,9 @@ function checkTools() {
|
|
| 61 |
}
|
| 62 |
|
| 63 |
// ββ CHARACTER FILE HELPERS ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 64 |
-
// characters.txt is
|
| 65 |
-
//
|
|
|
|
| 66 |
|
| 67 |
function sessionDir(token) {
|
| 68 |
return token === 'persistent'
|
|
@@ -81,19 +82,63 @@ function readChars(token) {
|
|
| 81 |
return fs.readFileSync(p, 'utf8');
|
| 82 |
}
|
| 83 |
|
| 84 |
-
// Write back to disk β
|
| 85 |
function writeChars(token, raw) {
|
| 86 |
fs.writeFileSync(charsPath(token), raw, 'utf8');
|
| 87 |
}
|
| 88 |
|
| 89 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
function parseChars(raw) {
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
}
|
| 93 |
|
| 94 |
-
//
|
| 95 |
-
|
| 96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
}
|
| 98 |
|
| 99 |
// Extract characters.txt from an APK and save it to the session dir
|
|
@@ -223,15 +268,9 @@ app.post('/upload-url', async (req, res) => {
|
|
| 223 |
// The frontend NEVER sees the raw blob β only structured data it needs to render.
|
| 224 |
app.get('/characters/:token', (req, res) => {
|
| 225 |
try {
|
| 226 |
-
const raw
|
| 227 |
-
const
|
| 228 |
-
|
| 229 |
-
index,
|
| 230 |
-
name: fields[0] || '',
|
| 231 |
-
alias: fields[1] || '',
|
| 232 |
-
fields, // full field array so the UI can render all editable values
|
| 233 |
-
}));
|
| 234 |
-
res.json({ count: list.length, characters: list });
|
| 235 |
} catch (err) {
|
| 236 |
res.status(404).json({ error: err.message });
|
| 237 |
}
|
|
@@ -245,15 +284,10 @@ app.post('/edit', (req, res) => {
|
|
| 245 |
return res.status(400).json({ error: 'Missing token, characterIndex, fieldIndex, or value' });
|
| 246 |
}
|
| 247 |
try {
|
| 248 |
-
const raw
|
| 249 |
-
const
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
if (!records[ci]) return res.status(400).json({ error: `Character index ${ci} out of range` });
|
| 253 |
-
if (fi < 0 || fi >= records[ci].length) return res.status(400).json({ error: `Field index ${fi} out of range` });
|
| 254 |
-
records[ci][fi] = String(value);
|
| 255 |
-
writeChars(token, serializeChars(records));
|
| 256 |
-
res.json({ ok: true, characterIndex: ci, fieldIndex: fi, value: records[ci][fi] });
|
| 257 |
} catch (err) {
|
| 258 |
res.status(500).json({ error: err.message });
|
| 259 |
}
|
|
@@ -265,16 +299,13 @@ app.post('/edit-bulk', (req, res) => {
|
|
| 265 |
const { token, edits } = req.body;
|
| 266 |
if (!token || !Array.isArray(edits)) return res.status(400).json({ error: 'Missing token or edits array' });
|
| 267 |
try {
|
| 268 |
-
const raw
|
| 269 |
-
const
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
records[ci][fi] = String(value);
|
| 276 |
-
}
|
| 277 |
-
writeChars(token, serializeChars(records));
|
| 278 |
res.json({ ok: true, applied: edits.length });
|
| 279 |
} catch (err) {
|
| 280 |
res.status(500).json({ error: err.message });
|
|
|
|
| 61 |
}
|
| 62 |
|
| 63 |
// ββ CHARACTER FILE HELPERS ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 64 |
+
// characters.txt is a single line of pipe-delimited fields.
|
| 65 |
+
// Character boundaries are detected by the pattern: name | alias | number
|
| 66 |
+
// (same logic as the frontend parseCharacters function).
|
| 67 |
|
| 68 |
function sessionDir(token) {
|
| 69 |
return token === 'persistent'
|
|
|
|
| 82 |
return fs.readFileSync(p, 'utf8');
|
| 83 |
}
|
| 84 |
|
| 85 |
+
// Write back to disk β raw flat pipe-delimited string, no newlines added
|
| 86 |
function writeChars(token, raw) {
|
| 87 |
fs.writeFileSync(charsPath(token), raw, 'utf8');
|
| 88 |
}
|
| 89 |
|
| 90 |
+
// Mirror of the HTML's parseCharacters():
|
| 91 |
+
// Splits on '|', detects boundaries where: allFields[i-1]=name, allFields[i]=alias, allFields[i+1]=number
|
| 92 |
+
// Returns array of: { index, name, alias, fields, startIdx }
|
| 93 |
+
// - name: allFields[start - 1] (the token just before the boundary)
|
| 94 |
+
// - alias: fields[0] (first field inside the boundary = allFields[start])
|
| 95 |
+
// - fields: allFields[start .. next_boundary-2] (the character's own pipe segment)
|
| 96 |
+
// - startIdx: index into allFields where this character's fields begin
|
| 97 |
function parseChars(raw) {
|
| 98 |
+
const allFields = raw.split('|');
|
| 99 |
+
|
| 100 |
+
function isNameToken(s) {
|
| 101 |
+
s = s.trim();
|
| 102 |
+
return s.length > 1 && /[A-Za-z]/.test(s) && !/^-?[\d.]+$/.test(s);
|
| 103 |
+
}
|
| 104 |
+
function isNumeric(s) { return /^-?[\d.]+$/.test(s.trim()); }
|
| 105 |
+
|
| 106 |
+
const boundaries = [];
|
| 107 |
+
for (let i = 1; i < allFields.length - 1; i++) {
|
| 108 |
+
if (isNameToken(allFields[i]) && isNumeric(allFields[i + 1]) && isNameToken(allFields[i - 1])) {
|
| 109 |
+
boundaries.push(i);
|
| 110 |
+
}
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
const characters = [];
|
| 114 |
+
for (let b = 0; b < boundaries.length; b++) {
|
| 115 |
+
const start = boundaries[b];
|
| 116 |
+
const end = b + 1 < boundaries.length ? boundaries[b + 1] - 1 : allFields.length;
|
| 117 |
+
const fields = allFields.slice(start, end);
|
| 118 |
+
characters.push({
|
| 119 |
+
index: b,
|
| 120 |
+
name: allFields[start - 1].trim(),
|
| 121 |
+
alias: fields[0].trim(),
|
| 122 |
+
fields, // fields[0]=alias, fields[1..]=numeric stats
|
| 123 |
+
startIdx: start, // position in allFields β needed for writeBack
|
| 124 |
+
});
|
| 125 |
+
}
|
| 126 |
+
return { allFields, characters };
|
| 127 |
}
|
| 128 |
|
| 129 |
+
// Write edited characters back into the original allFields array and rejoin with '|'
|
| 130 |
+
// edits: [{ characterIndex, fieldIndex, value }]
|
| 131 |
+
function applyEdits(raw, edits) {
|
| 132 |
+
const { allFields, characters } = parseChars(raw);
|
| 133 |
+
for (const { characterIndex, fieldIndex, value } of edits) {
|
| 134 |
+
const ch = characters[characterIndex];
|
| 135 |
+
if (!ch) throw new Error(`Character index ${characterIndex} out of range`);
|
| 136 |
+
if (fieldIndex < 0 || fieldIndex >= ch.fields.length)
|
| 137 |
+
throw new Error(`Field index ${fieldIndex} out of range for character ${characterIndex}`);
|
| 138 |
+
// Write back into allFields at the correct absolute position
|
| 139 |
+
allFields[ch.startIdx + fieldIndex] = String(value);
|
| 140 |
+
}
|
| 141 |
+
return allFields.join('|');
|
| 142 |
}
|
| 143 |
|
| 144 |
// Extract characters.txt from an APK and save it to the session dir
|
|
|
|
| 268 |
// The frontend NEVER sees the raw blob β only structured data it needs to render.
|
| 269 |
app.get('/characters/:token', (req, res) => {
|
| 270 |
try {
|
| 271 |
+
const raw = readChars(req.params.token);
|
| 272 |
+
const { characters } = parseChars(raw);
|
| 273 |
+
res.json({ count: characters.length, characters });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
} catch (err) {
|
| 275 |
res.status(404).json({ error: err.message });
|
| 276 |
}
|
|
|
|
| 284 |
return res.status(400).json({ error: 'Missing token, characterIndex, fieldIndex, or value' });
|
| 285 |
}
|
| 286 |
try {
|
| 287 |
+
const raw = readChars(token);
|
| 288 |
+
const edited = applyEdits(raw, [{ characterIndex: Number(characterIndex), fieldIndex: Number(fieldIndex), value }]);
|
| 289 |
+
writeChars(token, edited);
|
| 290 |
+
res.json({ ok: true, characterIndex: Number(characterIndex), fieldIndex: Number(fieldIndex), value: String(value) });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
} catch (err) {
|
| 292 |
res.status(500).json({ error: err.message });
|
| 293 |
}
|
|
|
|
| 299 |
const { token, edits } = req.body;
|
| 300 |
if (!token || !Array.isArray(edits)) return res.status(400).json({ error: 'Missing token or edits array' });
|
| 301 |
try {
|
| 302 |
+
const raw = readChars(token);
|
| 303 |
+
const edited = applyEdits(raw, edits.map(e => ({
|
| 304 |
+
characterIndex: Number(e.characterIndex),
|
| 305 |
+
fieldIndex: Number(e.fieldIndex),
|
| 306 |
+
value: e.value,
|
| 307 |
+
})));
|
| 308 |
+
writeChars(token, edited);
|
|
|
|
|
|
|
|
|
|
| 309 |
res.json({ ok: true, applied: edits.length });
|
| 310 |
} catch (err) {
|
| 311 |
res.status(500).json({ error: err.message });
|