feat:优化

This commit is contained in:
Jerry 2025-09-10 18:26:23 +08:00
parent fbd78dd7ad
commit afb42e5f87
14 changed files with 192 additions and 537 deletions

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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}`)) {
async readJSON(filePath) {
try {
return JSON.parse(fs.readFileSync(`${root}/${file}`, 'utf8'));
const data = await fsp.readFile(filePath, 'utf8');
return JSON.parse(data);
} catch (e) {
console.log(e);
}
}
return {};
},
statSync(file = '', root = '') {
root = getRoot(root);
try {
return fs.statSync(`${root}/${file}`);
} catch (e) {
console.log(e);
}
},
/**
* 写入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<void>}
*/
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'
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)
) {
this.deepMerge(target[key], source[key]);
} else {
target[key] = source[key];
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('_')
for (const entry of entries) {
if (entry.isFile()) {
if (path.extname(entry.name) === `.${extension}` && !entry.name.startsWith('_')) {
files.push(entry.name);
}
} else if (entry.isDirectory()) {
if (entry.name === excludeDir) continue;
const subFiles = await this.readDirRecursive(
path.join(directory, entry.name),
extension,
excludeDir
);
files
.filter((file) => fs.statSync(path.join(directory, file)).isDirectory())
.forEach((subdirectory) => {
if (subdirectory === excludeDir) {
return;
}
const subdirectoryPath = path.join(directory, subdirectory);
jsFiles.push(
...fc
.readDirRecursive(subdirectoryPath, extension, excludeDir)
.map((fileName) => path.join(subdirectory, fileName))
);
});
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;
}
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]);
files.push(...subFiles.map((fileName) => path.join(entry.name, fileName)));
}
}
return target;
};
return clone(source);
return files;
},
};
export default fc;

View File

@ -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<object>} 模块导出对象如导入失败返回空对象
* @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<object>} 模块的默认导出如失败返回空对象
* @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;

View File

@ -1,10 +1,8 @@
let tools = {
/**
* 异步延时函数
* 延时函数
* @param {number} ms - 等待的毫秒数
* @returns {Promise<void>}
* @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;

View File

@ -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//最大上下文
}
```

View File

@ -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');

View File

@ -1,6 +0,0 @@
const relativelyPath = {
config: '/data/crystelf/config.json',
data: '/data/crystelf/data/',
};
export default relativelyPath;

View File

@ -12,10 +12,11 @@ logger.info(
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}`);

View File

@ -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() {

View File

@ -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;
}
},

View File

@ -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 完成初始化');
},
};

View File

@ -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);
}
},
};