From afb42e5f877db6e22abaeee9f3ba44ace2b93986 Mon Sep 17 00:00:00 2001 From: Jerryplusy Date: Wed, 10 Sep 2025 18:26:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/reportBots.js | 8 +- components/date.js | 43 +---- components/json.js | 251 +++++++-------------------- components/module.js | 57 ------ components/tool.js | 110 +----------- config/{default.json => config.json} | 0 config/config.md | 61 ------- constants/path.js | 10 +- constants/relativelyPath.js | 6 - index.js | 7 +- lib/config/configControl.js | 131 ++++++++++---- lib/core/botControl.js | 12 +- lib/system/init.js | 5 - lib/system/updater.js | 28 +-- 14 files changed, 192 insertions(+), 537 deletions(-) delete mode 100644 components/module.js rename config/{default.json => config.json} (100%) delete mode 100644 config/config.md delete mode 100644 constants/relativelyPath.js diff --git a/apps/reportBots.js b/apps/reportBots.js index eb1b4ee..43a65b6 100644 --- a/apps/reportBots.js +++ b/apps/reportBots.js @@ -25,7 +25,7 @@ export default class ReportBots extends plugin { } async autoReport() { - logger.mark(`正在自动同步bot数据到晶灵核心..`); + logger.mark(`[crystelf-admin] 正在自动同步bot数据到晶灵核心..`); if (configControl.get('core')) { await botControl.reportBots(); } @@ -33,13 +33,13 @@ export default class ReportBots extends plugin { async manualReport(e) { if (!configControl.get('core')) { - return e.reply(`晶灵核心未启用..`, true); + return e.reply(`[crystelf-admin] 晶灵核心未启用..`, true); } let success = await botControl.reportBots(); if (success) { - await e.reply('crystelf Bot信息已同步到核心..', true); + await e.reply('Bot信息已同步到核心..', true); } else { - await e.reply('crystelf Bot同步失败:核心未连接..', true); + await e.reply('Bot同步失败:核心未连接..', true); } } diff --git a/components/date.js b/components/date.js index 53ee308..2a4b254 100644 --- a/components/date.js +++ b/components/date.js @@ -1,44 +1,3 @@ -let date = { - /** - * 格式化日期时间 - * @param {Date|number|string} [date=new Date()] - 可接收Date对象、时间戳或日期字符串 - * @param {string} [format='YYYY-MM-DD HH:mm:ss'] - 格式模板,支持: - * YYYY-年, MM-月, DD-日, - * HH-时, mm-分, ss-秒 - * @returns {string} 格式化后的日期字符串 - * @example - * fc.formatDate(new Date(), 'YYYY年MM月DD日') // "2023年08月15日" - */ - formatDate(date = new Date(), format = 'YYYY-MM-DD HH:mm:ss') { - const d = new Date(date); - const pad = (n) => n.toString().padStart(2, '0'); - - return format - .replace(/YYYY/g, pad(d.getFullYear())) - .replace(/MM/g, pad(d.getMonth() + 1)) - .replace(/DD/g, pad(d.getDate())) - .replace(/HH/g, pad(d.getHours())) - .replace(/mm/g, pad(d.getMinutes())) - .replace(/ss/g, pad(d.getSeconds())); - }, - - formatDuration(seconds) { - const days = Math.floor(seconds / 86400); - const hours = Math.floor((seconds % 86400) / 3600); - const mins = Math.floor((seconds % 3600) / 60); - const secs = seconds % 60; - - return ( - [ - days > 0 ? `${days}天` : '', - hours > 0 ? `${hours}小时` : '', - mins > 0 ? `${mins}分钟` : '', - secs > 0 ? `${secs}秒` : '', - ] - .filter(Boolean) - .join(' ') || '0秒' - ); - }, -}; +let date = {}; //咕咕咕 export default date; diff --git a/components/json.js b/components/json.js index 89a185a..cc3a1e0 100644 --- a/components/json.js +++ b/components/json.js @@ -2,13 +2,14 @@ import fs from 'fs'; import path from 'path'; import Version from '../lib/system/version.js'; +const fsp = fs.promises; const Plugin_Name = Version.name; const _path = process.cwd(); const getRoot = (root = '') => { if (root === 'root' || root === 'yunzai') { root = `${_path}/`; - } else if (!root) { + } else if (!root || root === '') { root = `${_path}/plugins/${Plugin_Name}/`; } return root; @@ -17,231 +18,97 @@ const getRoot = (root = '') => { let fc = { /** * 递归创建目录结构 - * @param {string} [path=""] - 要创建的相对路径,支持多级目录(如 "dir1/dir2") - * @param {string} [root=""] - 基础根目录,可选值: - * - "root" 或 "yunzai": 使用 Yunzai 根目录 - * - 空值: 使用插件目录 - * @param {boolean} [includeFile=false] - 是否包含最后一级作为文件名 - * @example - * fc.createDir("config/deepseek", "root") // 在 Yunzai 根目录创建 config/deepseek 目录 */ - createDir(path = '', root = '', includeFile = false) { + async createDir(p = '', root = '', includeFile = false) { root = getRoot(root); - let pathList = path.split('/'); + const pathList = p.split('/'); let nowPath = root; - pathList.forEach((name, idx) => { - name = name.trim(); + + for (let idx = 0; idx < pathList.length; idx++) { + const name = pathList[idx].trim(); if (!includeFile && idx <= pathList.length - 1) { nowPath += name + '/'; if (name) { - if (!fs.existsSync(nowPath)) { - fs.mkdirSync(nowPath); + try { + await fsp.access(nowPath); + } catch { + await fsp.mkdir(nowPath); } } } - }); + } }, /** - * 读取JSON文件 - * @param {string} [file=""] - JSON文件路径(相对路径) - * @param {string} [root=""] - 基础根目录(同 createDir) - * @returns {object} 解析后的JSON对象,如文件不存在或解析失败返回空对象 - * @example - * const config = fc.readJSON("config.json", "root") + * 读取json文件 + * @param filePath 绝对路径 + * @returns {Promise<{}|any>} */ - readJSON(file = '', root = '') { - root = getRoot(root); - if (fs.existsSync(`${root}/${file}`)) { - try { - return JSON.parse(fs.readFileSync(`${root}/${file}`, 'utf8')); - } catch (e) { - console.log(e); - } - } - return {}; - }, - - statSync(file = '', root = '') { - root = getRoot(root); + async readJSON(filePath) { try { - return fs.statSync(`${root}/${file}`); + const data = await fsp.readFile(filePath, 'utf8'); + return JSON.parse(data); } catch (e) { - console.log(e); + return {}; } }, /** - * 写入JSON文件(完全覆盖) - * @param {string} file - 目标文件路径 - * @param {object} data - 要写入的JSON数据 - * @param {string} [root=""] - 基础根目录(同 createDir) - * @param {number} [space=4] - JSON格式化缩进空格数 - * @returns {boolean} 是否写入成功 - * @warning 此方法会完全覆盖目标文件原有内容 - * @example - * fc.writeJSON("config.json", {key: "value"}, "root", 4) + * 写入 + * @param filePath 绝对路径 + * @param data + * @returns {Promise} */ - writeJSON(file, data, root = '', space = 4) { - fc.createDir(file, root, true); - root = getRoot(root); - try { - fs.writeFileSync(`${root}/${file}`, JSON.stringify(data, null, space)); - return true; - } catch (err) { - logger.error(err); - return false; - } + async writeJSON(filePath, data) { + await fsp.writeFile(filePath, JSON.stringify(data, null, 4), 'utf8'); }, /** - * 安全写入JSON文件(合并模式) - * @param {string} file - 目标文件路径 - * @param {object} data - 要合并的数据 - * @param {string} [root=""] - 基础根目录(同 createDir) - * @param {number} [space=4] - JSON格式化缩进空格数 - * @returns {boolean} 是否写入成功 - * @description - * - 如果目标文件不存在,创建新文件 - * - 如果目标文件存在,深度合并新旧数据 - * - 如果目标文件损坏,会创建新文件并记录警告 - * @example - * fc.safewriteJSON("config.json", {newKey: "value"}) + * 合并配置 + * @param base 基准配置 + * @param addon 额外配置 + * @returns {*} */ - safeWriteJSON(file, data, root = '', space = 4) { - fc.createDir(file, root, true); - root = getRoot(root); - const filePath = `${root}/${file}`; - - try { - let existingData = {}; - if (fs.existsSync(filePath)) { - try { - existingData = JSON.parse(fs.readFileSync(filePath, 'utf8')) || {}; - } catch (e) { - logger.warn(`无法解析现有JSON文件 ${filePath},将创建新文件`); - } - } - - const mergedData = this.deepMerge(existingData, data); - - fs.writeFileSync(filePath, JSON.stringify(mergedData, null, space)); - return true; - } catch (err) { - logger.error(`写入JSON文件失败 ${filePath}:`, err); - return false; - } - }, - - /** - * 深度合并两个对象 - * @param {object} target - 目标对象(将被修改) - * @param {object} source - 源对象 - * @returns {object} 合并后的目标对象 - * @description - * - 递归合并嵌套对象 - * - 对于非对象属性直接覆盖 - * - 不会合并数组(数组会被直接覆盖) - * @example - * const merged = fc.deepMerge({a: 1}, {b: {c: 2}}) - * // 返回 {a: 1, b: {c: 2}} - */ - deepMerge(target, source) { - for (const key in source) { - if (source.hasOwnProperty(key)) { - if ( - source[key] && - typeof source[key] === 'object' && - target[key] && - typeof target[key] === 'object' - ) { - this.deepMerge(target[key], source[key]); - } else { - target[key] = source[key]; - } + mergeConfig(base, addon) { + const result = { ...base }; + for (const [key, value] of Object.entries(addon)) { + if (!(key in result)) { + result[key] = value; + } else if ( + typeof result[key] === 'object' && + typeof value === 'object' && + !Array.isArray(result[key]) && + !Array.isArray(value) + ) { + result[key] = this.mergeConfig(result[key], value); } } - return target; + return result; }, /** - * 递归读取目录中的特定扩展名文件 - * @param {string} directory - 要搜索的目录路径 - * @param {string} extension - 文件扩展名(不带点) - * @param {string} [excludeDir] - 要排除的目录名 - * @returns {string[]} 匹配的文件相对路径数组 - * @description - * - 自动跳过以下划线开头的文件 - * - 结果包含子目录中的文件 - * @example - * const jsFiles = fc.readDirRecursive("./plugins", "js", "node_modules") + * 异步递归读取目录中的特定扩展名文件 */ - readDirRecursive(directory, extension, excludeDir) { - let files = fs.readdirSync(directory); + async readDirRecursive(directory, extension, excludeDir) { + const entries = await fsp.readdir(directory, { withFileTypes: true }); + let files = []; - let jsFiles = files.filter( - (file) => path.extname(file) === `.${extension}` && !file.startsWith('_') - ); - - files - .filter((file) => fs.statSync(path.join(directory, file)).isDirectory()) - .forEach((subdirectory) => { - if (subdirectory === excludeDir) { - return; + for (const entry of entries) { + if (entry.isFile()) { + if (path.extname(entry.name) === `.${extension}` && !entry.name.startsWith('_')) { + files.push(entry.name); } - - const subdirectoryPath = path.join(directory, subdirectory); - jsFiles.push( - ...fc - .readDirRecursive(subdirectoryPath, extension, excludeDir) - .map((fileName) => path.join(subdirectory, fileName)) + } else if (entry.isDirectory()) { + if (entry.name === excludeDir) continue; + const subFiles = await this.readDirRecursive( + path.join(directory, entry.name), + extension, + excludeDir ); - }); - - return jsFiles; - }, - - /** - * 深度克隆对象(支持基本类型/数组/对象/Date/RegExp) - * @param {*} source - 要克隆的数据 - * @returns {*} 深度克隆后的副本 - * @description - * - 处理循环引用 - * - 保持原型链 - * - 支持特殊对象类型(Date/RegExp等) - * @example - * const obj = { a: 1, b: [2, 3] }; - * const cloned = fc.deepClone(obj); - */ - deepClone(source) { - const cache = new WeakMap(); - - const clone = (value) => { - if (value === null || typeof value !== 'object') { - return value; + files.push(...subFiles.map((fileName) => path.join(entry.name, fileName))); } - - if (cache.has(value)) { - return cache.get(value); - } - - if (value instanceof Date) return new Date(value); - if (value instanceof RegExp) return new RegExp(value); - - const target = new value.constructor(); - cache.set(value, target); - - for (const key in value) { - if (value.hasOwnProperty(key)) { - target[key] = clone(value[key]); - } - } - - return target; - }; - - return clone(source); + } + return files; }, }; - export default fc; diff --git a/components/module.js b/components/module.js deleted file mode 100644 index 4966149..0000000 --- a/components/module.js +++ /dev/null @@ -1,57 +0,0 @@ -import Version from '../lib/system/version.js'; - -const Plugin_Name = Version.name; - -const _path = process.cwd(); -const getRoot = (root = '') => { - if (root === 'root' || root === 'yunzai') { - root = `${_path}/`; - } else if (!root) { - root = `${_path}/plugins/${Plugin_Name}/`; - } - return root; -}; - -let mc = { - /** - * 动态导入JS模块 - * @param {string} file - 模块文件路径(可省略.js后缀) - * @param {string} [root=""] - 基础根目录(同 createDir) - * @returns {Promise} 模块导出对象,如导入失败返回空对象 - * @description - * - 自动添加时间戳参数防止缓存 - * - 自动补全.js后缀 - * @example - * const module = await fc.importModule("utils/helper") - */ - async importModule(file, root = '') { - root = getRoot(root); - if (!/\.js$/.test(file)) { - file = file + '.js'; - } - if (fs.existsSync(`${root}/${file}`)) { - try { - let data = await import(`file://${root}/${file}?t=${new Date() * 1}`); - return data || {}; - } catch (e) { - console.log(e); - } - } - return {}; - }, - - /** - * 动态导入JS模块的默认导出 - * @param {string} file - 模块文件路径 - * @param {string} [root=""] - 基础根目录(同 createDir) - * @returns {Promise} 模块的默认导出,如失败返回空对象 - * @example - * const defaultExport = await fc.importDefault("components/Header") - */ - async importDefault(file, root) { - let ret = await fc.importModule(file, root); - return ret.default || {}; - }, -}; - -export default mc; diff --git a/components/tool.js b/components/tool.js index 894b32f..98233d0 100644 --- a/components/tool.js +++ b/components/tool.js @@ -1,10 +1,8 @@ let tools = { /** - * 异步延时函数 + * 延时函数 * @param {number} ms - 等待的毫秒数 * @returns {Promise} - * @example - * await fc.sleep(1000) // 等待1秒 */ sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -12,114 +10,12 @@ let tools = { /** * 生成指定范围内的随机整数 - * @param {number} min - 最小值(包含) - * @param {number} max - 最大值(包含) - * @returns {number} 范围内的随机整数 - * @example - * const randomNum = fc.randomInt(1, 10) // 可能返回 5 + * @param {number} min - 最小值 + * @param {number} max - 最大值 */ randomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }, - - /** - * 防抖函数 - * @param {Function} fn - 要执行的函数 - * @param {number} [delay=300] - 延迟时间(毫秒) - * @param {boolean} [immediate=false] - 是否立即执行 - * @returns {Function} 防抖处理后的函数 - * @description - * 1. immediate=true时:先立即执行,后续调用在delay时间内被忽略 - * 2. immediate=false时:延迟执行,重复调用会重置计时器 - * @example - * window.addEventListener('resize', fc.debounce(() => { - * console.log('resize end'); - * }, 500)); - */ - debounce(fn, delay = 300, immediate = false) { - let timer = null; - return function (...args) { - if (immediate && !timer) { - fn.apply(this, args); - } - - clearTimeout(timer); - timer = setTimeout(() => { - if (!immediate) { - fn.apply(this, args); - } - timer = null; - }, delay); - }; - }, - - /** - * 异步重试机制 - * @param {Function} asyncFn - 返回Promise的异步函数 - * @param {number} [maxRetries=3] - 最大重试次数 - * @param {number} [delay=1000] - 重试间隔(毫秒) - * @param {Function} [retryCondition] - 重试条件函数(err => boolean) - * @returns {Promise} 最终成功或失败的结果 - * @example - * await fc.retry(fetchData, 5, 2000, err => err.status !== 404); - */ - async retry(asyncFn, maxRetries = 3, delay = 1000, retryCondition = () => true) { - let attempt = 0; - let lastError; - - while (attempt <= maxRetries) { - try { - return await asyncFn(); - } catch (err) { - lastError = err; - if (attempt === maxRetries || !retryCondition(err)) { - break; - } - attempt++; - await this.sleep(delay); - } - } - - throw lastError; - }, - - /** - * 将对象转换为URL查询字符串 - * @param {object} params - 参数对象 - * @param {boolean} [encode=true] - 是否进行URL编码 - * @returns {string} 查询字符串(不带问号) - * @example - * fc.objectToQuery({a: 1, b: 'test'}) // "a=1&b=test" - */ - objectToQuery(params, encode = true) { - return Object.entries(params) - .map(([key, val]) => { - const value = val === null || val === undefined ? '' : val; - return `${key}=${encode ? encodeURIComponent(value) : value}`; - }) - .join('&'); - }, - - /** - * 从错误堆栈中提取简洁的错误信息 - * @param {Error} error - 错误对象 - * @param {number} [depth=3] - 保留的堆栈深度 - * @returns {string} 格式化后的错误信息 - * @example - * try { ... } catch(err) { - * logger.error(fc.formatError(err)); - * } - */ - formatError(error, depth = 3) { - if (!(error instanceof Error)) return String(error); - - const stack = error.stack?.split('\n') || []; - const message = `${error.name}: ${error.message}`; - - if (stack.length <= 1) return message; - - return [message, ...stack.slice(1, depth + 1).map((line) => line.trim())].join('\n at '); - }, }; export default tools; diff --git a/config/default.json b/config/config.json similarity index 100% rename from config/default.json rename to config/config.json diff --git a/config/config.md b/config/config.md deleted file mode 100644 index ae3813b..0000000 --- a/config/config.md +++ /dev/null @@ -1,61 +0,0 @@ -### 参考以下注释进行配置,不要带注释粘贴,也不要改动`default.json` - -配置文件位于`yunzai`根目录`/data/crystelf下` - -参考注释: - -``` yaml -{ - "debug": true,\\是否启用调试模式 - "core": true,\\是否启用晶灵核心相关功能 - "coreConfig": { //晶灵核心配置 - "coreUrl": "", //核心网址,需要加https://前缀 - "wsUrl": "", //ws连接地址如ws:// - "wsClientId": "",//端id - "wsSecret": "", wsmiy - "wsReConnectInterval": "5000", - "token": ""//postAPI调用密钥 - }, - "maxFeed": 10,//最大缓存rss流 - "feeds": [//rss相关配置,无需手动更改 - { - "url": "", - "targetGroups": [114,154], - "screenshot": true - } - ], - "fanqieConfig": {//番茄小说功能 - "url": "http://127.0.0.1:6868", - "outDir": "/home/user/debian/cache/Downloads" - }, - "poke": {//戳一戳概率,加起来不超过1,余下的概率为反击概率 - "replyText": 0.4, - "replyVoice": 0.2, - "mutePick": 0.1, - ""muteTime": 2" - }, - "mode": "deepseek",//deepseekORopenai - "modelType": "deepseek-ai/DeepSeek-V3",//无需更改 - "historyLength": 3, - "maxLength": 3, - "chatTemperature": 1, - "pluginTemperature": 0.5, - "nickName": "寄气人",//昵称 - "checkChat": { - "rdNum": 2,//随机数,0-100 - "masterReply": true,//主人回复 - "userId": [ //一定回复的人 - 114514 - ], - "blackGroups": [//不许使用的群聊 - 114, - 514 - ], - "enableGroups": [//一定回复的群聊 - 11115 - ] - }, - "maxMessageLength": 100//最大上下文 -} - -``` diff --git a/constants/path.js b/constants/path.js index 3a67434..196c37d 100644 --- a/constants/path.js +++ b/constants/path.js @@ -6,21 +6,23 @@ const __filename = url.fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const rootDir = path.join(__dirname, '..'); +//这里储存的都是绝对路径 const Path = { root: rootDir, apps: path.join(rootDir, 'apps'), components: path.join(rootDir, 'components'), - defaultConfig: path.join(rootDir, 'config/default.json'), - config: path.resolve(rootDir, '../../data/crystelf-admin'), + defaultConfig: path.join(rootDir, 'config/config.json'), + defaultConfigPath: path.join(rootDir, 'config'), + config: path.resolve(rootDir, '../../data/crystelf'), constants: path.join(rootDir, 'constants'), lib: path.join(rootDir, 'lib'), models: path.join(rootDir, 'models'), index: path.join(rootDir, 'index.js'), pkg: path.join(rootDir, 'package.json'), yunzai: path.join(rootDir, '../../'), - data: path.join(rootDir, '../../data/crystelf-admin/data'), + data: path.join(rootDir, '../../data/crystelf/data'), rssHTML: path.join(rootDir, 'constants/rss/rss_template.html'), - rssCache: path.join(rootDir, '../../data/crystelf-admin'), + rssCache: path.join(rootDir, '../../data/crystelf'), }; const configFile = fs.readFileSync(Path.defaultConfig, 'utf8'); diff --git a/constants/relativelyPath.js b/constants/relativelyPath.js deleted file mode 100644 index b3215cd..0000000 --- a/constants/relativelyPath.js +++ /dev/null @@ -1,6 +0,0 @@ -const relativelyPath = { - config: '/data/crystelf/config.json', - data: '/data/crystelf/data/', -}; - -export default relativelyPath; diff --git a/index.js b/index.js index bfde29e..d27bd5b 100644 --- a/index.js +++ b/index.js @@ -6,16 +6,17 @@ import { crystelfInit } from './lib/system/init.js'; import updater from './lib/system/updater.js'; logger.info( - chalk.rgb(134, 142, 204)(`crystelf-admin ${Version.ver} 初始化 ~ by ${Version.author}`) + chalk.rgb(134, 142, 204)(`crystelf-admin ${Version.ver} 初始化~ by ${Version.author}`) ); updater.checkAndUpdate().catch((err) => { logger.err(err); }); -await crystelfInit.CSH(); +//不要加await!!! +crystelfInit.CSH().then(logger.mark('[crystelf-admin] crystelf-admin 完成初始化')); const appPath = Path.apps; -const jsFiles = fc.readDirRecursive(appPath, 'js'); +const jsFiles = await fc.readDirRecursive(appPath, 'js'); let ret = jsFiles.map((file) => { return import(`./apps/${file}`); diff --git a/lib/config/configControl.js b/lib/config/configControl.js index 2448fbe..2d4694f 100644 --- a/lib/config/configControl.js +++ b/lib/config/configControl.js @@ -1,55 +1,88 @@ import Path, { defaultConfig } from '../../constants/path.js'; -import fc from '../../components/json.js'; import path from 'path'; import fs from 'fs'; -import relativelyPath from '../../constants/relativelyPath.js'; +import fc from '../../components/json.js'; -const configPath = Path.config; -const dataPath = Path.data; -const configFile = path.join(configPath, 'config.json'); -const configDir = relativelyPath.config; +const fsp = fs.promises; +const pluginConfigPath = Path.defaultConfigPath; +const dataConfigPath = Path.config; +const configFile = path.join(dataConfigPath, 'config.json'); +let configCache = {}; // 缓存 -let configCache = {}; -let lastModified = 0; - -function init() { +/** + * 初始化配置 + */ +async function init() { try { - if (!fs.existsSync(configPath)) { - fs.mkdirSync(configPath, { recursive: true }); - fs.mkdirSync(dataPath, { recursive: true }); - logger.mark(`crystelf 配置文件夹创建成功,位于 ${configPath}..`); + try { + await fsp.access(dataConfigPath); + } catch { + await fsp.mkdir(dataConfigPath, { recursive: true }); + logger.mark(`[crystelf-admin] 配置目录创建成功: ${dataConfigPath}`); } - - if (!fs.existsSync(configFile)) { - fs.writeFileSync(configFile, JSON.stringify(defaultConfig, null, 4), 'utf8'); - logger.mark('crystelf 配置文件创建成功..'); - } else { - const cfgFile = fs.readFileSync(configFile, 'utf8'); - const loadedConfig = JSON.parse(cfgFile); - const cfg = { ...defaultConfig, ...loadedConfig }; - - if (JSON.stringify(cfg) !== JSON.stringify(loadedConfig)) { - fs.writeFileSync(configFile, JSON.stringify(cfg, null, 4), 'utf8'); - logger.mark('crystelf 配置文件已更新,补充配置项..'); + const pluginDefaultFile = path.join(pluginConfigPath, 'config.json'); + try { + await fsp.access(configFile); + } catch { + await fsp.copyFile(pluginDefaultFile, configFile); + logger.mark(`[crystelf-admin] 默认配置复制成功: ${configFile}`); + } + const pluginFiles = (await fsp.readdir(pluginConfigPath)).filter((f) => f.endsWith('.json')); + for (const file of pluginFiles) { + const pluginFilePath = path.join(pluginConfigPath, file); + const dataFilePath = path.join(dataConfigPath, file); + try { + await fsp.access(dataFilePath); + } catch { + await fsp.copyFile(pluginFilePath, dataFilePath); + logger.mark(`[crystelf-admin] 配置文件缺失,已复制: ${file}`); } } - - const stats = fc.statSync(configDir, 'root'); - configCache = fc.readJSON(configDir, 'root'); - lastModified = stats.mtimeMs; - + const files = (await fsp.readdir(dataConfigPath)).filter((f) => f.endsWith('.json')); + configCache = {}; + for (const file of files) { + const filePath = path.join(dataConfigPath, file); + const name = path.basename(file, '.json'); + try { + let data = await fc.readJSON(filePath); + const pluginFilePath = path.join(pluginConfigPath, file); + try { + await fsp.access(pluginFilePath); + const pluginData = await fc.readJSON(pluginFilePath); + if (Array.isArray(data) && Array.isArray(pluginData)) { + const strSet = new Set(data.map((x) => JSON.stringify(x))); + for (const item of pluginData) { + const str = JSON.stringify(item); + if (!strSet.has(str)) { + data.push(item); + strSet.add(str); + } + } + } else if (!Array.isArray(data) && !Array.isArray(pluginData)) { + data = fc.mergeConfig(data, pluginData); + } + await fc.writeJSON(filePath, data); + } catch {} + configCache[name] = data; + } catch (e) { + logger.warn(`[crystelf-admin] 读取配置文件 ${file} 失败:`, e); + } + } + if (!Array.isArray(configCache)) { + configCache = fc.mergeConfig(configCache, defaultConfig); + } if (configCache.debug) { - logger.info('crystelf-plugin 配置模块初始化成功..'); + logger.info('[crystelf-admin] 配置模块初始化成功..'); } } catch (err) { - logger.warn('crystelf-plugin 初始化配置失败,使用空配置..', err); + logger.warn('[crystelf-admin] 配置初始化失败,使用空配置..', err); configCache = {}; } } const configControl = { async init() { - init(); + await init(); }, get(key) { @@ -58,11 +91,37 @@ const configControl = { async set(key, value) { configCache[key] = value; - return fc.safeWriteJSON(configDir, configCache, 'root', 4); + const filePath = path.join(dataConfigPath, `${key}.json`); + try { + await fsp.access(filePath); + await fc.writeJSON(filePath, value); + } catch { + let cfg = await fc.readJSON(configFile); + if (Array.isArray(cfg)) { + cfg.push(value); + } else { + cfg[key] = value; + } + await fc.writeJSON(configFile, cfg); + } }, async save() { - return fc.safeWriteJSON(configDir, configCache, 'root', 4); + for (const [key, value] of Object.entries(configCache)) { + const filePath = path.join(dataConfigPath, `${key}.json`); + try { + await fsp.access(filePath); + await fc.writeJSON(filePath, value); + } catch { + let cfg = await fc.readJSON(configFile); + if (Array.isArray(cfg)) { + cfg = value; + } else { + cfg[key] = value; + } + await fc.writeJSON(configFile, cfg); + } + } }, async reload() { diff --git a/lib/core/botControl.js b/lib/core/botControl.js index e963432..896f84f 100644 --- a/lib/core/botControl.js +++ b/lib/core/botControl.js @@ -48,20 +48,20 @@ const botControl = { async getGroupInfo(botId, groupId) { const bot = Bot[botId]; if (!bot) { - logger.warn(`未找到bot: ${botId}`); + logger.warn(`[crystelf-admin] 未找到bot: ${botId}`); return null; } const group = bot.pickGroup(groupId); if (!group) { - logger.warn(`Bot ${botId}中未找到群${groupId}`); + logger.warn(`[crystelf-admin] Bot ${botId}中未找到群${groupId}`); return null; } try { return await group.getInfo(); } catch (e) { - logger.error(`获取群聊信息失败:${groupId}..`); + logger.error(`[crystelf-admin] 获取群聊信息失败:${groupId}..`); return null; } }, @@ -76,20 +76,20 @@ const botControl = { async sendMessage(botId, message, groupId) { const bot = Bot[botId]; if (!bot) { - logger.warn(`未找到bot: ${botId}`); + logger.warn(`[crystelf-admin] 未找到bot: ${botId}`); return false; } const group = bot.pickGroup(groupId); if (!group) { - logger.warn(`Bot ${botId}中未找到群${groupId}`); + logger.warn(`[crystelf-admin] Bot ${botId}中未找到群${groupId}`); return false; } try { return !!(await group.send(message)); } catch (e) { - logger.error(`发送群信息失败:${groupId}..`); + logger.error(`[crystelf-admin] 发送群信息失败:${groupId}..`); return false; } }, diff --git a/lib/system/init.js b/lib/system/init.js index eaf5afd..1c20632 100644 --- a/lib/system/init.js +++ b/lib/system/init.js @@ -1,12 +1,7 @@ import configControl from '../config/configControl.js'; -import wsClient from '../../modules/ws/wsClient.js'; export const crystelfInit = { async CSH() { await configControl.init(); - if (configControl.get('core')) { - await wsClient.initialize(); - } - logger.mark('crystelf-admin 完成初始化'); }, }; diff --git a/lib/system/updater.js b/lib/system/updater.js index 60cce9a..ef9240d 100644 --- a/lib/system/updater.js +++ b/lib/system/updater.js @@ -9,25 +9,25 @@ const GIT_DIR = path.join(Path.root, '.git'); const execStr = (cmd) => child_process.execSync(cmd, { cwd: Path.root }).toString().trim(); const Updater = { - isGitRepo() { + async isGitRepo() { return fs.existsSync(GIT_DIR); }, - getBranch() { + async getBranch() { return execStr('git symbolic-ref --short HEAD'); }, - getLocalHash() { + async getLocalHash() { return execStr('git rev-parse HEAD'); }, - getRemoteHash(branch = 'main') { + async getRemoteHash(branch = 'main') { return execStr(`git rev-parse origin/${branch}`); }, async hasUpdate() { try { - const branch = this.getBranch(); + const branch = await this.getBranch(); await new Promise((resolve, reject) => { child_process.exec('git fetch', { cwd: Path.root }, (err) => { @@ -39,28 +39,28 @@ const Updater = { }); }); - const local = this.getLocalHash(); - const remote = this.getRemoteHash(branch); + const local = await this.getLocalHash(); + const remote = await this.getRemoteHash(branch); return local !== remote; } catch (err) { - logger.error('[crystelf-plugin] 检查更新失败:', err); + logger.error('[crystelf-admin] 检查更新失败:', err); return false; } }, async update() { - logger.mark(chalk.cyan('[crystelf-plugin] 检测到插件有更新,自动执行 git pull')); + logger.mark(chalk.cyan('[crystelf-admin] 检测到插件有更新,自动执行 git pull')); child_process.execSync('git pull', { cwd: Path.root, stdio: 'inherit', }); - logger.mark(chalk.green('[crystelf-plugin] 插件已自动更新完成')); + logger.mark(chalk.green('[crystelf-admin] 插件已自动更新完成')); }, async checkAndUpdate() { - if (!this.isGitRepo()) { - logger.warn('[crystelf-plugin] 当前目录不是 Git 仓库,自动更新功能已禁用'); + if (!(await this.isGitRepo())) { + logger.warn('[crystelf-admin] 当前目录不是 Git 仓库,自动更新功能已禁用'); return; } @@ -68,10 +68,10 @@ const Updater = { if (await this.hasUpdate()) { await this.update(); } else { - logger.info('[crystelf-plugin] 当前已是最新版本,无需更新'); + logger.info('[crystelf-admin] 当前已是最新版本,无需更新'); } } catch (err) { - logger.error('[crystelf-plugin] 自动更新失败:', err); + logger.error('[crystelf-admin] 自动更新失败:', err); } }, };