mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' of https://github.com/city-unit/SillyTavern into feature/exorcism
This commit is contained in:
505
server.js
505
server.js
@@ -152,10 +152,24 @@ let main_api = "kobold";
|
||||
let response_generate_novel;
|
||||
let characters = {};
|
||||
let response_dw_bg;
|
||||
let response_getstatus;
|
||||
let first_run = true;
|
||||
|
||||
|
||||
let color = {
|
||||
byNum: (mess, fgNum) => {
|
||||
mess = mess || '';
|
||||
fgNum = fgNum === undefined ? 31 : fgNum;
|
||||
return '\u001b[' + fgNum + 'm' + mess + '\u001b[39m';
|
||||
},
|
||||
black: (mess) => color.byNum(mess, 30),
|
||||
red: (mess) => color.byNum(mess, 31),
|
||||
green: (mess) => color.byNum(mess, 32),
|
||||
yellow: (mess) => color.byNum(mess, 33),
|
||||
blue: (mess) => color.byNum(mess, 34),
|
||||
magenta: (mess) => color.byNum(mess, 35),
|
||||
cyan: (mess) => color.byNum(mess, 36),
|
||||
white: (mess) => color.byNum(mess, 37)
|
||||
};
|
||||
|
||||
function get_mancer_headers() {
|
||||
const api_key_mancer = readSecret(SECRET_KEYS.MANCER);
|
||||
@@ -317,7 +331,8 @@ const directories = {
|
||||
instruct: 'public/instruct',
|
||||
context: 'public/context',
|
||||
backups: 'backups/',
|
||||
quickreplies: 'public/QuickReplies'
|
||||
quickreplies: 'public/QuickReplies',
|
||||
assets: 'public/assets',
|
||||
};
|
||||
|
||||
// CSRF Protection //
|
||||
@@ -367,7 +382,10 @@ app.use(CORS);
|
||||
|
||||
if (listen && config.basicAuthMode) app.use(basicAuthMiddleware);
|
||||
|
||||
app.use(function (req, res, next) { //Security
|
||||
// IP Whitelist //
|
||||
let knownIPs = new Set();
|
||||
|
||||
function getIpFromRequest(req) {
|
||||
let clientIp = req.connection.remoteAddress;
|
||||
let ip = ipaddr.parse(clientIp);
|
||||
// Check if the IP address is IPv4-mapped IPv6 address
|
||||
@@ -378,33 +396,35 @@ app.use(function (req, res, next) { //Security
|
||||
clientIp = ip;
|
||||
clientIp = clientIp.toString();
|
||||
}
|
||||
return clientIp;
|
||||
}
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
const clientIp = getIpFromRequest(req);
|
||||
|
||||
if (listen && !knownIPs.has(clientIp)) {
|
||||
const userAgent = req.headers['user-agent'];
|
||||
console.log(color.yellow(`New connection from ${clientIp}; User Agent: ${userAgent}\n`));
|
||||
knownIPs.add(clientIp);
|
||||
|
||||
// Write access log
|
||||
const timestamp = new Date().toISOString();
|
||||
const log = `${timestamp} ${clientIp} ${userAgent}\n`;
|
||||
fs.appendFile('access.log', log, (err) => {
|
||||
if (err) {
|
||||
console.error('Failed to write access log:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//clientIp = req.connection.remoteAddress.split(':').pop();
|
||||
if (whitelistMode === true && !whitelist.some(x => ipMatching.matches(clientIp, ipMatching.getMatch(x)))) {
|
||||
console.log('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.\n');
|
||||
console.log(color.red('Forbidden: Connection attempt from ' + clientIp + '. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.\n'));
|
||||
return res.status(403).send('<b>Forbidden</b>: Connection attempt from <b>' + clientIp + '</b>. If you are attempting to connect, please add your IP address in whitelist or disable whitelist mode in config.conf in root of SillyTavern folder.');
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.url.startsWith('/characters/') && is_colab && process.env.googledrive == 2) {
|
||||
|
||||
const filePath = path.join(charactersPath, decodeURIComponent(req.url.substr('/characters'.length)));
|
||||
console.log('req.url: ' + req.url);
|
||||
console.log(filePath);
|
||||
fs.access(filePath, fs.constants.R_OK, (err) => {
|
||||
if (!err) {
|
||||
res.sendFile(filePath, { root: process.cwd() });
|
||||
} else {
|
||||
res.send('Character not found: ' + filePath);
|
||||
//next();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.use(express.static(process.cwd() + "/public", { refresh: true }));
|
||||
|
||||
@@ -755,14 +775,15 @@ app.post("/getchat", jsonParser, function (request, response) {
|
||||
}
|
||||
});
|
||||
|
||||
app.post("/getstatus", jsonParser, async function (request, response_getstatus = response) {
|
||||
if (!request.body) return response_getstatus.sendStatus(400);
|
||||
app.post("/getstatus", jsonParser, async function (request, response) {
|
||||
if (!request.body) return response.sendStatus(400);
|
||||
api_server = request.body.api_server;
|
||||
main_api = request.body.main_api;
|
||||
if (api_server.indexOf('localhost') != -1) {
|
||||
api_server = api_server.replace('localhost', '127.0.0.1');
|
||||
}
|
||||
var args = {
|
||||
|
||||
const args = {
|
||||
headers: { "Content-Type": "application/json" }
|
||||
};
|
||||
|
||||
@@ -770,9 +791,10 @@ app.post("/getstatus", jsonParser, async function (request, response_getstatus =
|
||||
args.headers = Object.assign(args.headers, get_mancer_headers());
|
||||
}
|
||||
|
||||
var url = api_server + "/v1/model";
|
||||
const url = api_server + "/v1/model";
|
||||
let version = '';
|
||||
let koboldVersion = {};
|
||||
|
||||
if (main_api == "kobold") {
|
||||
try {
|
||||
version = (await getAsync(api_server + "/v1/info/version")).result;
|
||||
@@ -790,29 +812,27 @@ app.post("/getstatus", jsonParser, async function (request, response_getstatus =
|
||||
};
|
||||
}
|
||||
}
|
||||
client.get(url, args, async function (data, response) {
|
||||
if (typeof data !== 'object') {
|
||||
|
||||
try {
|
||||
let data = await getAsync(url, args);
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
data = {};
|
||||
}
|
||||
if (response.statusCode == 200) {
|
||||
data.version = version;
|
||||
data.koboldVersion = koboldVersion;
|
||||
if (data.result == "ReadOnly") {
|
||||
data.result = "no_connection";
|
||||
}
|
||||
} else {
|
||||
data.response = data.result;
|
||||
|
||||
if (data.result == "ReadOnly") {
|
||||
data.result = "no_connection";
|
||||
}
|
||||
response_getstatus.send(data);
|
||||
}).on('error', function () {
|
||||
response_getstatus.send({ result: "no_connection" });
|
||||
});
|
||||
});
|
||||
|
||||
const formatApiUrl = (url) => (url.indexOf('localhost') !== -1)
|
||||
? url.replace('localhost', '127.0.0.1')
|
||||
: url;
|
||||
data.version = version;
|
||||
data.koboldVersion = koboldVersion;
|
||||
|
||||
return response.send(data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.send({ result: "no_connection" });
|
||||
}
|
||||
});
|
||||
|
||||
function getVersion() {
|
||||
let pkgVersion = 'UNKNOWN';
|
||||
@@ -1920,7 +1940,7 @@ app.post("/generate_novelai", jsonParser, async function (request, response_gene
|
||||
"stop_sequences": request.body.stop_sequences,
|
||||
"bad_words_ids": badWordsList,
|
||||
"logit_bias_exp": logit_bias_exp,
|
||||
//generate_until_sentence = true;
|
||||
"generate_until_sentence": request.body.generate_until_sentence,
|
||||
"use_cache": request.body.use_cache,
|
||||
"use_string": true,
|
||||
"return_full_text": request.body.return_full_text,
|
||||
@@ -3315,6 +3335,72 @@ async function sendScaleRequest(request, response) {
|
||||
}
|
||||
}
|
||||
|
||||
app.post("/generate_altscale", jsonParser, function (request, response_generate_scale) {
|
||||
if (!request.body) return response_generate_scale.sendStatus(400);
|
||||
|
||||
fetch('https://dashboard.scale.com/spellbook/api/trpc/v2.variant.run', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'cookie': `_jwt=${readSecret(SECRET_KEYS.SCALE_COOKIE)}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
json: {
|
||||
variant: {
|
||||
name: 'New Variant',
|
||||
appId: '',
|
||||
taxonomy: null
|
||||
},
|
||||
prompt: {
|
||||
id: '',
|
||||
template: '{{input}}\n',
|
||||
exampleVariables: {},
|
||||
variablesSourceDataId: null,
|
||||
systemMessage: request.body.sysprompt
|
||||
},
|
||||
modelParameters: {
|
||||
id: '',
|
||||
modelId: 'GPT4',
|
||||
modelType: 'OpenAi',
|
||||
maxTokens: request.body.max_tokens,
|
||||
temperature: request.body.temp,
|
||||
stop: "user:",
|
||||
suffix: null,
|
||||
topP: request.body.top_p,
|
||||
logprobs: null,
|
||||
logitBias: request.body.logit_bias
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
index: '-1',
|
||||
valueByName: {
|
||||
input: request.body.prompt
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
meta: {
|
||||
values: {
|
||||
'variant.taxonomy': ['undefined'],
|
||||
'prompt.variablesSourceDataId': ['undefined'],
|
||||
'modelParameters.suffix': ['undefined'],
|
||||
'modelParameters.logprobs': ['undefined'],
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data.result.data.json.outputs[0])
|
||||
return response_generate_scale.send({ output: data.result.data.json.outputs[0] });
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Error:', error)
|
||||
return response_generate_scale.send({ error: true })
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
async function sendClaudeRequest(request, response) {
|
||||
const fetch = require('node-fetch').default;
|
||||
|
||||
@@ -3339,6 +3425,12 @@ async function sendClaudeRequest(request, response) {
|
||||
}
|
||||
|
||||
console.log('Claude request:', requestPrompt);
|
||||
const stop_sequences = ["\n\nHuman:", "\n\nSystem:", "\n\nAssistant:"];
|
||||
|
||||
// Add custom stop sequences
|
||||
if (Array.isArray(request.body.stop)) {
|
||||
stop_sequences.push(...request.body.stop);
|
||||
}
|
||||
|
||||
const generateResponse = await fetch(api_url + '/complete', {
|
||||
method: "POST",
|
||||
@@ -3347,7 +3439,7 @@ async function sendClaudeRequest(request, response) {
|
||||
prompt: requestPrompt,
|
||||
model: request.body.model,
|
||||
max_tokens_to_sample: request.body.max_tokens,
|
||||
stop_sequences: ["\n\nHuman:", "\n\nSystem:", "\n\nAssistant:"],
|
||||
stop_sequences: stop_sequences,
|
||||
temperature: request.body.temperature,
|
||||
top_p: request.body.top_p,
|
||||
top_k: request.body.top_k,
|
||||
@@ -3427,12 +3519,21 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
|
||||
// OpenRouter needs to pass the referer: https://openrouter.ai/docs
|
||||
headers = { 'HTTP-Referer': request.headers.referer };
|
||||
bodyParams = { 'transforms': ["middle-out"] };
|
||||
|
||||
if (request.body.use_fallback) {
|
||||
bodyParams['route'] = 'fallback';
|
||||
}
|
||||
}
|
||||
|
||||
if (!api_key_openai && !request.body.reverse_proxy) {
|
||||
return response_generate_openai.status(401).send({ error: true });
|
||||
}
|
||||
|
||||
// Add custom stop sequences
|
||||
if (Array.isArray(request.body.stop)) {
|
||||
bodyParams['stop'] = request.body.stop;
|
||||
}
|
||||
|
||||
const isTextCompletion = Boolean(request.body.model && (request.body.model.startsWith('text-') || request.body.model.startsWith('code-')));
|
||||
const textPrompt = isTextCompletion ? convertChatMLPrompt(request.body.messages) : '';
|
||||
const endpointUrl = isTextCompletion ? `${api_url}/completions` : `${api_url}/chat/completions`;
|
||||
@@ -3752,6 +3853,8 @@ function getPresetSettingsByAPI(apiId) {
|
||||
return { folder: directories.textGen_Settings, extension: '.settings' };
|
||||
case 'instruct':
|
||||
return { folder: directories.instruct, extension: '.json' };
|
||||
case 'context':
|
||||
return { folder: directories.context, extension: '.json' };
|
||||
default:
|
||||
return { folder: null, extension: null };
|
||||
}
|
||||
@@ -3787,11 +3890,19 @@ app.post("/tokenize_via_api", jsonParser, async function (request, response) {
|
||||
|
||||
if (main_api == 'textgenerationwebui' && request.body.use_mancer) {
|
||||
args.headers = Object.assign(args.headers, get_mancer_headers());
|
||||
const data = await postAsync(api_server + "/v1/token-count", args);
|
||||
return response.send({ count: data['results'][0]['tokens'] });
|
||||
}
|
||||
|
||||
const data = await postAsync(api_server + "/v1/token-count", args);
|
||||
console.log(data);
|
||||
return response.send({ count: data['results'][0]['tokens'] });
|
||||
else if (main_api == 'kobold') {
|
||||
const data = await postAsync(api_server + "/extra/tokencount", args);
|
||||
const count = data['value'];
|
||||
return response.send({ count: count });
|
||||
}
|
||||
|
||||
else {
|
||||
return response.send({ error: true });
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return response.send({ error: true });
|
||||
@@ -3875,21 +3986,23 @@ const setupTasks = async function () {
|
||||
|
||||
if (autorun) open(autorunUrl.toString());
|
||||
|
||||
console.log('\x1b[32mSillyTavern is listening on: ' + tavernUrl + '\x1b[0m');
|
||||
console.log(color.green('SillyTavern is listening on: ' + tavernUrl));
|
||||
|
||||
if (listen) {
|
||||
console.log('\n0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost (127.0.0.1), change the setting in config.conf to “listen=false”\n');
|
||||
console.log('\n0.0.0.0 means SillyTavern is listening on all network interfaces (Wi-Fi, LAN, localhost). If you want to limit it only to internal localhost (127.0.0.1), change the setting in config.conf to "listen=false". Check "access.log" file in the SillyTavern directory if you want to inspect incoming connections.\n');
|
||||
}
|
||||
}
|
||||
|
||||
if (listen && !config.whitelistMode && !config.basicAuthMode) {
|
||||
if (config.securityOverride)
|
||||
console.warn("Security has been override. If it's not a trusted network, change the settings.");
|
||||
if (config.securityOverride) {
|
||||
console.warn(color.red("Security has been overridden. If it's not a trusted network, change the settings."));
|
||||
}
|
||||
else {
|
||||
console.error('Your SillyTavern is currently unsecurely open to the public. Enable whitelisting or basic authentication.');
|
||||
console.error(color.red('Your SillyTavern is currently unsecurely open to the public. Enable whitelisting or basic authentication.'));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (true === cliArguments.ssl)
|
||||
https.createServer(
|
||||
{
|
||||
@@ -4001,9 +4114,12 @@ const SECRET_KEYS = {
|
||||
NOVEL: 'api_key_novel',
|
||||
CLAUDE: 'api_key_claude',
|
||||
DEEPL: 'deepl',
|
||||
LIBRE: 'libre',
|
||||
LIBRE_URL: 'libre_url',
|
||||
OPENROUTER: 'api_key_openrouter',
|
||||
SCALE: 'api_key_scale',
|
||||
AI21: 'api_key_ai21'
|
||||
AI21: 'api_key_ai21',
|
||||
SCALE_COOKIE: 'scale_cookie',
|
||||
}
|
||||
|
||||
function migrateSecrets() {
|
||||
@@ -4228,6 +4344,46 @@ app.post('/horde_generateimage', jsonParser, async (request, response) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/libre_translate', jsonParser, async (request, response) => {
|
||||
const key = readSecret(SECRET_KEYS.LIBRE);
|
||||
const url = readSecret(SECRET_KEYS.LIBRE_URL);
|
||||
|
||||
const text = request.body.text;
|
||||
const lang = request.body.lang;
|
||||
|
||||
if (!text || !lang) {
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
console.log('Input text: ' + text);
|
||||
|
||||
try {
|
||||
const result = await fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
q: text,
|
||||
source: "auto",
|
||||
target: lang,
|
||||
format: "text",
|
||||
api_key: key
|
||||
}),
|
||||
headers: { "Content-Type": "application/json" }
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
return response.sendStatus(result.status);
|
||||
}
|
||||
|
||||
const json = await result.json();
|
||||
console.log('Translated text: ' + json.translatedText);
|
||||
|
||||
return response.send(json.translatedText);
|
||||
} catch (error) {
|
||||
console.log("Translation error: " + error.message);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/google_translate', jsonParser, async (request, response) => {
|
||||
const { generateRequestUrl, normaliseResponse } = require('google-translate-api-browser');
|
||||
|
||||
@@ -4947,3 +5103,242 @@ app.post('/delete_extension', jsonParser, async (request, response) => {
|
||||
return response.status(500).send(`Server Error: ${error.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to retrieve name of all files of a given folder path.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object. Require folder path in query
|
||||
* @param {Object} response - HTTP Response object will contain a list of file path.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/get_assets', jsonParser, async (request, response) => {
|
||||
const folderPath = path.join(directories.assets);
|
||||
let output = {}
|
||||
//console.info("Checking files into",folderPath);
|
||||
|
||||
try {
|
||||
if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
|
||||
const folders = fs.readdirSync(folderPath)
|
||||
.filter(filename => {
|
||||
return fs.statSync(path.join(folderPath, filename)).isDirectory();
|
||||
});
|
||||
|
||||
for (const folder of folders) {
|
||||
if (folder == "temp")
|
||||
continue;
|
||||
const files = fs.readdirSync(path.join(folderPath, folder))
|
||||
.filter(filename => {
|
||||
return filename != ".placeholder";
|
||||
});
|
||||
output[folder] = [];
|
||||
for (const file of files) {
|
||||
output[folder].push(path.join("assets", folder, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
finally {
|
||||
return response.send(output);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function checkAssetFileName(inputFilename) {
|
||||
// Sanitize filename
|
||||
if (inputFilename.indexOf('\0') !== -1) {
|
||||
console.debug("Bad request: poisong null bytes in filename.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_\-\.]+$/.test(inputFilename)) {
|
||||
console.debug("Bad request: illegal character in filename, only alphanumeric, '_', '-' are accepted.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (contentManager.unsafeExtensions.some(ext => inputFilename.toLowerCase().endsWith(ext))) {
|
||||
console.debug("Bad request: forbidden file extension.");
|
||||
return '';
|
||||
}
|
||||
|
||||
if (inputFilename.startsWith('.')) {
|
||||
console.debug("Bad request: filename cannot start with '.'");
|
||||
return '';
|
||||
}
|
||||
|
||||
return path.normalize(inputFilename).replace(/^(\.\.(\/|\\|$))+/, '');;
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to download the requested asset.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a url, a category and a filename.
|
||||
* @param {Object} response - HTTP Response only gives status.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/asset_download', jsonParser, async (request, response) => {
|
||||
const { Readable } = require('stream');
|
||||
const { finished } = require('stream/promises');
|
||||
const url = request.body.url;
|
||||
const inputCategory = request.body.category;
|
||||
const inputFilename = sanitize(request.body.filename);
|
||||
const validCategories = ["bgm", "ambient"];
|
||||
|
||||
// Check category
|
||||
let category = null;
|
||||
for (i of validCategories)
|
||||
if (i == inputCategory)
|
||||
category = i;
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
// Sanitize filename
|
||||
const safe_input = checkAssetFileName(inputFilename);
|
||||
if (safe_input == '')
|
||||
return response.sendFile(400);
|
||||
|
||||
const temp_path = path.join(directories.assets, "temp", safe_input)
|
||||
const file_path = path.join(directories.assets, category, safe_input)
|
||||
console.debug("Request received to download", url, "to", file_path);
|
||||
|
||||
try {
|
||||
// Download to temp
|
||||
const downloadFile = (async (url, temp_path) => {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Unexpected response ${res.statusText}`);
|
||||
}
|
||||
const destination = path.resolve(temp_path);
|
||||
// Delete if previous download failed
|
||||
if (fs.existsSync(temp_path)) {
|
||||
fs.unlink(temp_path, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
}
|
||||
const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
|
||||
await finished(Readable.fromWeb(res.body).pipe(fileStream));
|
||||
});
|
||||
|
||||
await downloadFile(url, temp_path);
|
||||
|
||||
// Move into asset place
|
||||
console.debug("Download finished, moving file from", temp_path, "to", file_path);
|
||||
fs.renameSync(temp_path, file_path);
|
||||
response.sendStatus(200);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP POST handler function to delete the requested asset.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a category and a filename
|
||||
* @param {Object} response - HTTP Response only gives stats.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/asset_delete', jsonParser, async (request, response) => {
|
||||
const { Readable } = require('stream');
|
||||
const { finished } = require('stream/promises');
|
||||
const inputCategory = request.body.category;
|
||||
const inputFilename = sanitize(request.body.filename);
|
||||
const validCategories = ["bgm", "ambient"];
|
||||
|
||||
// Check category
|
||||
let category = null;
|
||||
for (i of validCategories)
|
||||
if (i == inputCategory)
|
||||
category = i;
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
// Sanitize filename
|
||||
const safe_input = checkAssetFileName(inputFilename);
|
||||
if (safe_input == '')
|
||||
return response.sendFile(400);
|
||||
|
||||
const file_path = path.join(directories.assets, category, safe_input)
|
||||
console.debug("Request received to delete", category, file_path);
|
||||
|
||||
try {
|
||||
// Delete if previous download failed
|
||||
if (fs.existsSync(file_path)) {
|
||||
fs.unlink(file_path, (err) => {
|
||||
if (err) throw err;
|
||||
});
|
||||
console.debug("Asset deleted.");
|
||||
}
|
||||
else {
|
||||
console.debug("Asset not found.");
|
||||
response.sendStatus(400);
|
||||
}
|
||||
// Move into asset place
|
||||
response.sendStatus(200);
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error);
|
||||
response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
///////////////////////////////
|
||||
/**
|
||||
* HTTP POST handler function to retrieve a character background music list.
|
||||
*
|
||||
* @param {Object} request - HTTP Request object, expects a character name in the query.
|
||||
* @param {Object} response - HTTP Response object will contain a list of audio file path.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
app.post('/get_character_assets_list', jsonParser, async (request, response) => {
|
||||
const name = sanitize(request.query.name);
|
||||
const inputCategory = request.query.category;
|
||||
const validCategories = ["bgm", "ambient"]
|
||||
|
||||
// Check category
|
||||
let category = null
|
||||
for (i of validCategories)
|
||||
if (i == inputCategory)
|
||||
category = i
|
||||
|
||||
if (category === null) {
|
||||
console.debug("Bad request: unsuported asset category.");
|
||||
return response.sendStatus(400);
|
||||
}
|
||||
|
||||
const folderPath = path.join(directories.characters, name, category);
|
||||
|
||||
let output = [];
|
||||
try {
|
||||
if (fs.existsSync(folderPath) && fs.statSync(folderPath).isDirectory()) {
|
||||
const files = fs.readdirSync(folderPath)
|
||||
.filter(filename => {
|
||||
return filename != ".placeholder";
|
||||
});
|
||||
|
||||
for (i of files)
|
||||
output.push(`/characters/${name}/${category}/${i}`);
|
||||
|
||||
}
|
||||
return response.send(output);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
return response.sendStatus(500);
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user