|
|
import fs from 'node:fs'; |
|
|
import path from 'node:path'; |
|
|
|
|
|
import express from 'express'; |
|
|
import sanitize from 'sanitize-filename'; |
|
|
import _ from 'lodash'; |
|
|
import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
|
|
import { tryParse } from '../util.js'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function readWorldInfoFile(directories, worldInfoName, allowDummy) { |
|
|
const dummyObject = allowDummy ? { entries: {} } : null; |
|
|
|
|
|
if (!worldInfoName) { |
|
|
return dummyObject; |
|
|
} |
|
|
|
|
|
const filename = sanitize(`${worldInfoName}.json`); |
|
|
const pathToWorldInfo = path.join(directories.worlds, filename); |
|
|
|
|
|
if (!fs.existsSync(pathToWorldInfo)) { |
|
|
console.error(`World info file ${filename} doesn't exist.`); |
|
|
return dummyObject; |
|
|
} |
|
|
|
|
|
const worldInfoText = fs.readFileSync(pathToWorldInfo, 'utf8'); |
|
|
const worldInfo = JSON.parse(worldInfoText); |
|
|
return worldInfo; |
|
|
} |
|
|
|
|
|
export const router = express.Router(); |
|
|
|
|
|
router.post('/list', async (request, response) => { |
|
|
try { |
|
|
const data = []; |
|
|
const jsonFiles = (await fs.promises.readdir(request.user.directories.worlds, { withFileTypes: true })) |
|
|
.filter((file) => file.isFile() && path.extname(file.name).toLowerCase() === '.json') |
|
|
.sort((a, b) => a.name.localeCompare(b.name)); |
|
|
|
|
|
for (const file of jsonFiles) { |
|
|
try { |
|
|
const filePath = path.join(request.user.directories.worlds, file.name); |
|
|
const fileContents = await fs.promises.readFile(filePath, 'utf8'); |
|
|
const fileContentsParsed = tryParse(fileContents) || {}; |
|
|
const fileExtensions = fileContentsParsed?.extensions || {}; |
|
|
const fileNameWithoutExt = path.parse(file.name).name; |
|
|
const fileData = { |
|
|
file_id: fileNameWithoutExt, |
|
|
name: fileContentsParsed?.name || fileNameWithoutExt, |
|
|
extensions: _.isObjectLike(fileExtensions) ? fileExtensions : {}, |
|
|
}; |
|
|
data.push(fileData); |
|
|
} catch (err) { |
|
|
console.warn(`Error reading or parsing World Info file ${file.name}:`, err); |
|
|
} |
|
|
} |
|
|
|
|
|
return response.send(data); |
|
|
} catch (err) { |
|
|
console.error('Error reading World Info directory:', err); |
|
|
return response.sendStatus(500); |
|
|
} |
|
|
}); |
|
|
|
|
|
router.post('/get', (request, response) => { |
|
|
if (!request.body?.name) { |
|
|
return response.sendStatus(400); |
|
|
} |
|
|
|
|
|
const file = readWorldInfoFile(request.user.directories, request.body.name, true); |
|
|
|
|
|
return response.send(file); |
|
|
}); |
|
|
|
|
|
router.post('/delete', (request, response) => { |
|
|
if (!request.body?.name) { |
|
|
return response.sendStatus(400); |
|
|
} |
|
|
|
|
|
const worldInfoName = request.body.name; |
|
|
const filename = sanitize(`${worldInfoName}.json`); |
|
|
const pathToWorldInfo = path.join(request.user.directories.worlds, filename); |
|
|
|
|
|
if (!fs.existsSync(pathToWorldInfo)) { |
|
|
throw new Error(`World info file ${filename} doesn't exist.`); |
|
|
} |
|
|
|
|
|
fs.unlinkSync(pathToWorldInfo); |
|
|
|
|
|
return response.sendStatus(200); |
|
|
}); |
|
|
|
|
|
router.post('/import', (request, response) => { |
|
|
if (!request.file) return response.sendStatus(400); |
|
|
|
|
|
const filename = `${path.parse(sanitize(request.file.originalname)).name}.json`; |
|
|
|
|
|
let fileContents = null; |
|
|
|
|
|
if (request.body.convertedData) { |
|
|
fileContents = request.body.convertedData; |
|
|
} else { |
|
|
const pathToUpload = path.join(request.file.destination, request.file.filename); |
|
|
fileContents = fs.readFileSync(pathToUpload, 'utf8'); |
|
|
fs.unlinkSync(pathToUpload); |
|
|
} |
|
|
|
|
|
try { |
|
|
const worldContent = JSON.parse(fileContents); |
|
|
if (!('entries' in worldContent)) { |
|
|
throw new Error('File must contain a world info entries list'); |
|
|
} |
|
|
} catch (err) { |
|
|
return response.status(400).send('Is not a valid world info file'); |
|
|
} |
|
|
|
|
|
const pathToNewFile = path.join(request.user.directories.worlds, filename); |
|
|
const worldName = path.parse(pathToNewFile).name; |
|
|
|
|
|
if (!worldName) { |
|
|
return response.status(400).send('World file must have a name'); |
|
|
} |
|
|
|
|
|
writeFileAtomicSync(pathToNewFile, fileContents); |
|
|
return response.send({ name: worldName }); |
|
|
}); |
|
|
|
|
|
router.post('/edit', (request, response) => { |
|
|
if (!request.body) { |
|
|
return response.sendStatus(400); |
|
|
} |
|
|
|
|
|
if (!request.body.name) { |
|
|
return response.status(400).send('World file must have a name'); |
|
|
} |
|
|
|
|
|
try { |
|
|
if (!('entries' in request.body.data)) { |
|
|
throw new Error('World info must contain an entries list'); |
|
|
} |
|
|
} catch (err) { |
|
|
return response.status(400).send('Is not a valid world info file'); |
|
|
} |
|
|
|
|
|
const filename = sanitize(`${request.body.name}.json`); |
|
|
const pathToFile = path.join(request.user.directories.worlds, filename); |
|
|
|
|
|
writeFileAtomicSync(pathToFile, JSON.stringify(request.body.data, null, 4)); |
|
|
|
|
|
return response.send({ ok: true }); |
|
|
}); |
|
|
|