diff --git a/index.js b/index.js index bec7be2..8e8b82e 100644 --- a/index.js +++ b/index.js @@ -2,76 +2,74 @@ const express = require('express'); const fs = require('fs').promises; const path = require('path'); -const { - plugins, - plugin_name_to_folder_name, - plugin_data_path, -} = require('../../../public/script.js'); +// These are now passed by the loader, not required directly. +// This makes the plugin more robust and independent. +// const { ... } = require('../../../public/script.js'); const extensionName = "Amily-Databin"; const extensionVersion = "0.0.1-a"; -const storageBasePath = path.join(plugin_data_path, 'cc.amily49.amily-databin'); let sourceTypes = {}; // Will be loaded from sourcetype.json /** - * Builds a safe file path and prevents path traversal attacks. + * Main plugin initialization function. + * SillyTavern's loader will call this function and provide context. + * @param {object} context - The context provided by SillyTavern's loader. + * @param {string} context.plugin_data_path - The base path for plugin data. + * @param {object} context.plugin_name_to_folder_name - A map of plugin names to folder names. */ -function getSafeFilePath(source, objName, file) { - if (typeof source !== 'string' && typeof source !== 'number' || typeof objName !== 'string' || typeof file !== 'string') { - return null; +async function initializePlugin(context) { + const { plugin_data_path, plugin_name_to_folder_name } = context; + + const storageBasePath = path.join(plugin_data_path, 'cc.amily49.amily-databin'); + + /** + * Builds a safe file path and prevents path traversal attacks. + */ + function getSafeFilePath(source, objName, file) { + if (typeof source !== 'string' && typeof source !== 'number' || typeof objName !== 'string' || typeof file !== 'string') { + return null; + } + const sanitize = (part) => String(part).replace(/\.\./g, '').replace(/[/\]/g, ''); + const safeSource = sanitize(source); + const safeObjName = sanitize(objName); + const safeFile = sanitize(file); + + if (!safeSource || !safeObjName || !safeFile) return null; + + return path.join(storageBasePath, safeSource, safeObjName, safeFile); } - const sanitize = (part) => String(part).replace(/\.\./g, '').replace(/[/\]/g, ''); - const safeSource = sanitize(source); - const safeObjName = sanitize(objName); - const safeFile = sanitize(file); - if (!safeSource || !safeObjName || !safeFile) return null; - - return path.join(storageBasePath, safeSource, safeObjName, safeFile); -} + // #region Server-side Business Logic per SourceType + async function defaultSaveStrategy(filePath, data) { + await fs.mkdir(path.dirname(filePath), { recursive: true }); + await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); + } + // #endregion -// #region Server-side Business Logic per SourceType -// These functions implement the "strategy" for each source type. - -/** Generic save handler */ -async function defaultSaveStrategy(filePath, data) { - await fs.mkdir(path.dirname(filePath), { recursive: true }); - await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); -} - -// #endregion - -(async () => { try { // Load source types from the file on startup. const sourceTypesRaw = await fs.readFile(path.join(__dirname, 'sourcetype.json'), 'utf8'); sourceTypes = JSON.parse(sourceTypesRaw); - // Ensure the base storage directory exists. await fs.mkdir(storageBasePath, { recursive: true }); } catch (error) { console.error(`[${extensionName}] Critical startup error:`, error); - return; // Stop loading the plugin + return null; // Return null to indicate failure } const router = express.Router(); - // Endpoint to serve the source type map to the client - router.get('/sourcetype', (req, res) => { - res.json(sourceTypes); - }); + router.get('/sourcetype', (req, res) => res.json(sourceTypes)); router.get('/data', async (req, res) => { const filePath = getSafeFilePath(req.query.source, req.query.objName, req.query.file); if (!filePath) return res.status(400).send("Invalid parameters."); - try { const fileContent = await fs.readFile(filePath, 'utf8'); res.json(JSON.parse(fileContent)); } catch (error) { if (error.code === 'ENOENT') return res.status(404).send('Not found.'); - console.error(`[${extensionName}] Error reading file ${filePath}:`, error); res.status(500).send('Internal Server Error'); } }); @@ -79,25 +77,18 @@ async function defaultSaveStrategy(filePath, data) { router.post('/data', async (req, res) => { const { source, objName, file, data } = req.body; const filePath = getSafeFilePath(source, objName, file); - - if (!filePath) return res.status(400).send("Invalid parameters."); - if (data === undefined) return res.status(400).send("'data' is required."); + if (!filePath || data === undefined) return res.status(400).send("Invalid or incomplete parameters."); const sourceType = parseInt(source, 10); - try { - // Strategy pattern based on source type switch (sourceType) { case sourceTypes.CHAT_LOG: case sourceTypes.CHARACTER_CARD: case sourceTypes.WORLD_BOOK: - // For now, all implemented types use the default save strategy. await defaultSaveStrategy(filePath, data); res.status(200).send('Data saved successfully.'); break; - default: - // If the source type is known but not implemented, or unknown. res.status(501).send(`Save handler for source type '${sourceType}' is not implemented.`); break; } @@ -110,18 +101,17 @@ async function defaultSaveStrategy(filePath, data) { router.delete('/data', async (req, res) => { const filePath = getSafeFilePath(req.query.source, req.query.objName, req.query.file); if (!filePath) return res.status(400).send("Invalid parameters."); - try { await fs.unlink(filePath); res.status(200).send('Data deleted successfully.'); } catch (error) { if (error.code === 'ENOENT') return res.status(200).send('Not found.'); - console.error(`[${extensionName}] Error deleting file ${filePath}:`, error); res.status(500).send('Internal Server Error'); } }); - plugins[extensionName] = { + // Return the plugin definition object + return { name: extensionName, version: extensionVersion, is_enabled: true, @@ -129,6 +119,7 @@ async function defaultSaveStrategy(filePath, data) { router: router, assets: ['client.js'], }; +} - console.log(`[${extensionName}] version ${extensionVersion} loaded. Storage path: ${storageBasePath}`); -})(); \ No newline at end of file +// Export the main initialization function +module.exports = initializePlugin;