diff --git a/default/config.conf b/default/config.conf index 1e3c73b43..257db767e 100644 --- a/default/config.conf +++ b/default/config.conf @@ -10,6 +10,7 @@ const listen = true; // If true, Can be access from other device or PC. otherwis const allowKeysExposure = false; // If true, private API keys could be fetched to the frontend. const skipContentCheck = false; // If true, no new default content will be delivered to you. const thumbnailsQuality = 95; // Quality of thumbnails. 0-100 +const disableChatBackup = false; // Disables the backup of chat logs to the /backups folder // If true, Allows insecure settings for listen, whitelist, and authentication. // Change this setting only on "trusted networks". Do not change this value unless you are aware of the issues that can arise from changing this setting and configuring a insecure setting. @@ -51,4 +52,5 @@ module.exports = { requestOverrides, thumbnailsQuality, extras, + disableChatBackup, }; diff --git a/server.js b/server.js index bd1fb1fd5..dec4ae0a6 100644 --- a/server.js +++ b/server.js @@ -692,6 +692,7 @@ app.post("/savechat", jsonParser, function (request, response) { let chat_data = request.body.chat; let jsonlData = chat_data.map(JSON.stringify).join('\n'); writeFileAtomicSync(`${chatsPath + sanitize(dir_name)}/${sanitize(String(request.body.file_name))}.jsonl`, jsonlData, 'utf8'); + backupChat(dir_name, jsonlData) return response.send({ result: "ok" }); } catch (error) { response.send(error); @@ -2652,6 +2653,7 @@ app.post('/savegroupchat', jsonParser, (request, response) => { let chat_data = request.body.chat; let jsonlData = chat_data.map(JSON.stringify).join('\n'); writeFileAtomicSync(pathToFile, jsonlData, 'utf8'); + backupChat(String(id), jsonlData); return response.send({ ok: true }); }); @@ -3541,21 +3543,48 @@ if (true === cliArguments.ssl) { ); } -function backupSettings() { - const MAX_BACKUPS = 25; +function generateTimestamp() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); - function generateTimestamp() { - const now = new Date(); - const year = now.getFullYear(); - const month = String(now.getMonth() + 1).padStart(2, '0'); - const day = String(now.getDate()).padStart(2, '0'); - const hours = String(now.getHours()).padStart(2, '0'); - const minutes = String(now.getMinutes()).padStart(2, '0'); - const seconds = String(now.getSeconds()).padStart(2, '0'); + return `${year}${month}${day}-${hours}${minutes}${seconds}`; +} - return `${year}${month}${day}-${hours}${minutes}${seconds}`; +/** + * + * @param {string} name + * @param {string} chat + */ +function backupChat(name, chat) { + try { + const isBackupDisabled = config.disableChatBackup; + + if (isBackupDisabled) { + return; + } + + if (!fs.existsSync(DIRECTORIES.backups)) { + fs.mkdirSync(DIRECTORIES.backups); + } + + // replace non-alphanumeric characters with underscores + name = sanitize(name).replace(/[^a-z0-9]/gi, '_').toLowerCase(); + + const backupFile = path.join(DIRECTORIES.backups, `chat_${name}_${generateTimestamp()}.json`); + writeFileAtomicSync(backupFile, chat, 'utf-8'); + + removeOldBackups(`chat_${name}_`); + } catch (err) { + console.log(`Could not backup chat for ${name}`, err); } +} +function backupSettings() { try { if (!fs.existsSync(DIRECTORIES.backups)) { fs.mkdirSync(DIRECTORIES.backups); @@ -3564,18 +3593,27 @@ function backupSettings() { const backupFile = path.join(DIRECTORIES.backups, `settings_${generateTimestamp()}.json`); fs.copyFileSync(SETTINGS_FILE, backupFile); - let files = fs.readdirSync(DIRECTORIES.backups).filter(f => f.startsWith('settings_')); - if (files.length > MAX_BACKUPS) { - files = files.map(f => path.join(DIRECTORIES.backups, f)); - files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs); - - fs.rmSync(files[0]); - } + removeOldBackups('settings_'); } catch (err) { console.log('Could not backup settings file', err); } } +/** + * @param {string} prefix + */ +function removeOldBackups(prefix) { + const MAX_BACKUPS = 25; + + let files = fs.readdirSync(DIRECTORIES.backups).filter(f => f.startsWith(prefix)); + if (files.length > MAX_BACKUPS) { + files = files.map(f => path.join(DIRECTORIES.backups, f)); + files.sort((a, b) => fs.statSync(a).mtimeMs - fs.statSync(b).mtimeMs); + + fs.rmSync(files[0]); + } +} + function ensurePublicDirectoriesExist() { for (const dir of Object.values(DIRECTORIES)) { if (!fs.existsSync(dir)) { diff --git a/src/assets.js b/src/assets.js index f23b93c2f..b969f62b3 100644 --- a/src/assets.js +++ b/src/assets.js @@ -77,7 +77,7 @@ function registerEndpoints(app, jsonParser) { for (let file of fs.readdirSync(live2d_model_path)) { if (file.includes("model")) { //console.debug("Asset live2d model found:",file) - output[folder].push([`${model_folder}`,path.join("assets", folder, model_folder, file)]); + output[folder].push([`${model_folder}`, path.join("assets", folder, model_folder, file)]); } } } @@ -257,7 +257,7 @@ function registerEndpoints(app, jsonParser) { for (let file of fs.readdirSync(live2dModelPath)) { //console.debug("Character live2d model found:", file) if (file.includes("model")) - output.push([`${modelFolder}`,path.join("characters", name, category, modelFolder, file) ]); + output.push([`${modelFolder}`, path.join("characters", name, category, modelFolder, file)]); } } }