UNPKG

cyberbot-core

Version:

cyberbot, 基于napcat-ts, nodejs,轻量qq机器人框架。

1,254 lines (1,253 loc) 70 kB
import { NCWebsocket, Structs } from "node-napcat-ts"; import { join } from "path"; import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, readdirSync } from "fs"; import { writeFile } from 'fs/promises'; import axios from "axios"; import { createJiti } from "jiti"; import { createHash } from 'crypto'; // @ts-ignore import * as cron from "node-cron"; // 导入日志模块 import { logger } from "./logger.js"; export { Structs, NCWebsocket, axios as http }; // Config // 使用单例模式存储配置,避免多次解析 let configCache = null; export function getConfig() { // 如果缓存存在,直接返回 if (configCache) return configCache; const configPath = join(process.cwd(), "config.json"); if (!existsSync(configPath)) { throw new Error("配置文件未找到。请在项目根目录创建 config.json 文件。"); } try { // 读取并解析配置文件 const rawContent = readFileSync(configPath, "utf-8"); const parsed = JSON.parse(rawContent); // 验证必要的配置项 if (!parsed.baseUrl) { throw new Error("配置错误: 缺少 baseUrl 字段"); } if (!parsed.accessToken) { throw new Error("配置错误: 缺少 accessToken 字段"); } // 缓存配置 configCache = parsed; return configCache; } catch (error) { if (error instanceof SyntaxError) { throw new Error(`配置文件解析错误: ${error.message}`); } throw error; } } // Index const logo = ` .oooooo. .o8 oooooooooo. . d8P' \`Y8b "888 \`888' \`Y8b .o8 888 oooo ooo 888oooo. .ooooo. oooo d8b 888 888 .ooooo. .o888oo 888 \`88. .8' d88' \`88b d88' \`88b \`888\"\"8P 888oooo888' d88' \`88b 888 888 \`88..8' 888 888 888ooo888 888 888 \`88b 888 888 888 \`88b ooo \`888' 888 888 888 .o 888 888 .88P 888 888 888 . \`Y8bood8P' .8' \`Y8bod8P' \`Y8bod8P' d888b o888bood8P' \`Y8bod8P' "888" .o..P' \`Y8P' CyberBot 一个基于 node-napcat-ts 的 QQ 机器人 参考: kivibot@viki && Abot@takayama @auther: 星火 `; // 初始化日志系统 export const log = logger; export class Bot { constructor() { // 获取配置,如果失败则抛出错误 try { this.config = getConfig(); } catch (error) { log.error(`[-]配置加载失败: ${error instanceof Error ? error.message : String(error)}`); throw error; } // 创建websocket连接 this.bot = new NCWebsocket({ "baseUrl": this.config.baseUrl, "accessToken": this.config.accessToken, "reconnection": { "enable": this.config.reconnection?.enable ?? true, "attempts": this.config.reconnection?.attempts ?? 10, "delay": this.config.reconnection?.delay ?? 5000 } }, this.config.reconnection?.debug ?? false); this.pluginManager = new PluginManager(this.bot, this.config); this.plugins = null; // 初始化错误处理器 ErrorHandler.initialize(); } async start() { this.bot.on("socket.open", (ctx) => { log.info("[*]开始连接: " + this.config.baseUrl); }); this.bot.on("socket.error", (ctx) => { log.error("[-]websocket 连接错误: " + ctx.error_type); }); this.bot.on("socket.close", (ctx) => { log.error("[-]websocket 连接关闭: " + ctx.code); }); this.bot.on("meta_event.lifecycle", (ctx) => { if (ctx.sub_type == "connect") { log.info(`[+]连接成功: ${this.config.baseUrl}`); log.info(logo); } }); this.bot.on("meta_event.heartbeat", (ctx) => { log.info(`[*]心跳包♥`); }); this.bot.on("message", (ctx) => { log.info("[*]receive message: " + ctx.raw_message); }); this.bot.on("api.response.failure", (ctx) => { log.error(`[-]ApiError, status: ${ctx.status}, message: ${ctx.message}`); }); this.bot.on("api.preSend", (ctx) => { log.info(`[*]${ctx.action}: ${JSON.stringify(ctx.params)}`); }); this.plugins = await this.pluginManager.init(); await this.bot.connect(); // 在连接成功并加载插件后向主人发送上线通知 this.sendOnlineNotificationToMasters(); // 设置错误日志内存优化模式 // 如果配置中有debug标志,则不启用内存优化 const debugMode = this.config.reconnection?.debug ?? false; ErrorHandler.setMemoryOptimizedMode(!debugMode); } /** * 向所有主人发送机器人上线通知 */ async sendOnlineNotificationToMasters() { // 等待短暂时间确保连接稳定 await new Promise(resolve => setTimeout(resolve, 1000)); this.config.master.forEach(async (masterId) => { try { // 获取插件信息,确保plugins是Map类型 let pluginCount = 0; let totalPlugins = 0; if (this.pluginManager) { const plugins = this.pluginManager.plugins; pluginCount = Array.from(plugins.values()).filter(info => info.setup && info.setup.enable).length; // 从plugins目录获取所有可用插件数量 totalPlugins = this.pluginManager.getPluginsFromDir().length; } await this.bot.send_msg({ user_id: masterId, message: [ Structs.text(`[Bot🤖] 已成功上线!\n` + `📅 ${new Date().toLocaleString()}\n` + `🧩 插件状态: ${pluginCount}/${totalPlugins} 已启用\n` + `💻 系统信息: ${process.platform} ${process.arch}\n` + `🎉 机器人已准备就绪,随时为您服务!`) ] }); log.info(`[+]已向主人 ${masterId} 发送上线通知`); } catch (error) { log.error(`[-]向主人 ${masterId} 发送上线通知失败: ${error}`); } }); } // 添加停止方法,在系统关闭时调用 async stop() { // 在这里可以添加其他清理逻辑 log.info('[*]系统已停止,资源已清理'); } } // Plugin export function definePlugin(plugin) { return plugin; } /** * 错误处理工具类 */ class ErrorHandler { /** * 初始化错误处理器 */ static initialize() { if (this.isInitialized) return; try { this.loadErrorLogs(); this.isInitialized = true; // 启动时清理旧日志 this.cleanOldLogs(); // 设置进程退出时保存日志 process.on('exit', () => { this.saveErrorLogsSynchronously(); }); // 设置每小时自动保存 setInterval(() => { this.saveErrorLogs().catch(err => { console.error('Failed to auto-save error logs:', err); }); }, 60 * 60 * 1000); log.info(`[*]错误处理系统已初始化,最大日志数量: ${this.MAX_ERROR_LOGS}`); } catch (error) { console.error('Error initializing ErrorHandler:', error); } } /** * 启用内存优化模式,减少日志细节 * @param enable 是否启用 */ static setMemoryOptimizedMode(enable) { this.memoryOptimizedMode = enable; log.info(`[*]错误日志内存优化模式已${enable ? '启用' : '禁用'}`); } /** * 格式化错误对象为字符串 */ static formatError(error) { if (!error) return 'Unknown error'; // 在内存优化模式下简化错误信息 if (this.memoryOptimizedMode) { return error.message || String(error); } // 常规模式下,返回更详细的错误信息 try { if (error instanceof Error) { return `${error.name}: ${error.message}\n${error.stack || ''}`; } if (typeof error === 'string') { return error; } return JSON.stringify(error, null, 2); } catch (e) { return String(error); } } /** * 记录错误日志 * @param plugin 插件名称 * @param type 错误类型 * @param error 错误对象 */ static logError(plugin, type, error) { try { if (!this.isInitialized) { this.initialize(); } // 格式化错误消息 const message = this.formatError(error); // 在内存优化模式下限制错误消息长度 const limitedMessage = this.memoryOptimizedMode && message.length > 500 ? message.substring(0, 500) + '...(truncated)' : message; // 添加到错误日志数组 this.errorLogs.unshift({ timestamp: Date.now(), plugin, type, message: limitedMessage, // 在内存优化模式下不保存代码片段 code: this.memoryOptimizedMode ? undefined : (error.code || undefined) }); // 保持日志数量在限制范围内 if (this.errorLogs.length > this.MAX_ERROR_LOGS) { this.errorLogs = this.errorLogs.slice(0, this.MAX_ERROR_LOGS); } // 延迟保存以减少I/O操作 this.pendingSave = true; const now = Date.now(); if (now - this.lastSaveTime > this.SAVE_DELAY) { this.saveErrorLogs().catch(e => console.error('Failed to save error logs:', e)); this.lastSaveTime = now; this.pendingSave = false; } else if (!this.pendingSave) { // 设置延迟保存 this.pendingSave = true; setTimeout(() => { if (this.pendingSave) { this.saveErrorLogs().catch(e => console.error('Failed to save error logs:', e)); this.lastSaveTime = Date.now(); this.pendingSave = false; } }, this.SAVE_DELAY); } // 同时输出错误日志到控制台 log.error(`[${plugin}][${type}] ${message}`); } catch (e) { console.error('Error in logError:', e); } } /** * 加载错误日志从文件 */ static loadErrorLogs() { try { // 确保日志目录存在 const logDir = join(process.cwd(), "logs"); if (!existsSync(logDir)) { mkdirSync(logDir, { recursive: true }); } if (!existsSync(this.ERROR_LOGS_FILE)) { this.errorLogs = []; return; } const data = readFileSync(this.ERROR_LOGS_FILE, 'utf8'); this.errorLogs = JSON.parse(data); // 验证并清理损坏的日志 this.errorLogs = this.errorLogs.filter(log => log && typeof log === 'object' && typeof log.timestamp === 'number' && typeof log.plugin === 'string' && typeof log.type === 'string'); log.info(`[*]已加载 ${this.errorLogs.length} 条错误日志记录`); } catch (error) { console.error('Error loading error logs:', error); this.errorLogs = []; } } /** * 异步保存错误日志到文件 */ static async saveErrorLogs() { try { // 确保日志目录存在 const logDir = join(process.cwd(), "logs"); if (!existsSync(logDir)) { mkdirSync(logDir, { recursive: true }); } const data = JSON.stringify(this.errorLogs); await writeFile(this.ERROR_LOGS_FILE, data, 'utf8'); } catch (error) { console.error('Error saving error logs:', error); } } /** * 同步保存错误日志到文件(进程退出时使用) */ static saveErrorLogsSynchronously() { try { // 确保日志目录存在 const logDir = join(process.cwd(), "logs"); if (!existsSync(logDir)) { mkdirSync(logDir, { recursive: true }); } const data = JSON.stringify(this.errorLogs); writeFileSync(this.ERROR_LOGS_FILE, data, 'utf8'); } catch (error) { console.error('Error saving error logs synchronously:', error); } } /** * 获取特定插件的错误日志 */ static getPluginErrors(pluginName) { return this.errorLogs.filter(log => log.plugin === pluginName); } /** * 清理旧的错误日志 * @param maxAge 最大保留时间(毫秒) */ static cleanOldLogs(maxAge = this.MAX_LOG_AGE_DAYS * 24 * 60 * 60 * 1000) { try { const now = Date.now(); const oldSize = this.errorLogs.length; this.errorLogs = this.errorLogs.filter(log => (now - log.timestamp) < maxAge); const removedCount = oldSize - this.errorLogs.length; if (removedCount > 0) { log.info(`[*]已清理 ${removedCount} 条过期错误日志`); // 仅当有日志被删除时才保存 this.saveErrorLogs().catch(e => console.error('Failed to save logs after cleaning:', e)); } } catch (error) { console.error('Error cleaning old logs:', error); } } /** * 立即清空所有错误日志 */ static clearAllLogs() { this.errorLogs = []; this.saveErrorLogs().catch(e => console.error('Failed to save after clearing logs:', e)); log.info('[*]已清空所有错误日志'); } } ErrorHandler.MAX_ERROR_LOGS = 50; // 最大错误日志数量 ErrorHandler.ERROR_LOGS_FILE = join(process.cwd(), "logs", "error_logs.json"); ErrorHandler.MAX_LOG_AGE_DAYS = 3; // 默认保留3天的错误日志 ErrorHandler.errorLogs = []; ErrorHandler.isInitialized = false; // 提高性能的内存优化标志 ErrorHandler.memoryOptimizedMode = false; // 最后保存时间,用于延迟写入 ErrorHandler.lastSaveTime = 0; // 待保存标志 ErrorHandler.pendingSave = false; // 保存延迟 ErrorHandler.SAVE_DELAY = 5000; // 5秒 export class PluginManager { constructor(bot, config) { this.pluginCtxProxies = new Map(); this.sharedMethodWrappers = new Map(); this.pluginErrorHandlers = new Map(); this.cronTaskPool = new Map(); this.plugins = new Map(); this.bot = bot; this.tempListener = []; this.tempCronJob = []; // 初始化定时任务池 this.cronTaskPool = new Map(); this.jiti = createJiti(import.meta.url, { moduleCache: false }); this.ctx = { config: config, http: axios, bot: this.bot, bot_uin: config.bot, cron: (cronTasks, func) => { // 存储定时任务的数组 const cronJobInstances = []; // 如果是数组格式,表示多个定时任务 if (Array.isArray(cronTasks)) { for (const [cronExpression, callback] of cronTasks) { if (!cron.validate(cronExpression)) { log.error(`[-]无效的 cron 表达式: ${cronExpression}`); cronJobInstances.push(null); // 占位,保持索引一致 this.tempCronJob.push(false); continue; } // 1. 创建一个轻量级事件对象模板 - 避免在闭包中重复创建 const baseEventTemplate = { message_type: 'group', raw_message: '', message_id: 0, user_id: 0, group_id: 0, sender: { user_id: 0 } }; // 2. 预先创建reply方法,避免每次调用都创建 const replyMethod = async (message, quote = false) => { try { let messageArray = Array.isArray(message) ? message : [message]; const processedMessages = messageArray.map(item => { if (typeof item === 'string' || typeof item === 'number') { return Structs.text(item.toString()); } return item; }); return await this.bot.send_msg({ user_id: 0, // 默认值,实际发送时不会用到 message: processedMessages }); } catch (error) { log.error(`Failed to send cron message: ${error}`); return { message_id: 0 }; } }; // 3. 提取真正需要的ctx属性,而不是捕获整个ctx // 创建一个最小化的上下文对象,包含常用属性 const minimalCtx = { bot: this.bot, config: { master: [...this.ctx.config.master], // 复制数组,避免引用 bot: this.ctx.config.bot }, // 添加任务可能需要的其他最小化属性 sendPrivateMessage: this.ctx.sendPrivateMessage, sendGroupMessage: this.ctx.sendGroupMessage }; // 4. 创建轻量级回调包装器 const wrappedCallback = () => { try { // 每次创建新的事件对象,避免状态共享问题 const eventObj = { ...baseEventTemplate }; // 添加reply方法 eventObj.reply = replyMethod; // 使用类型断言处理类型兼容问题 return callback(minimalCtx, eventObj); } catch (error) { // 捕获并记录错误,但不中断cron执行 log.error(`[-]Cron任务执行错误: ${error}`); } }; // 5. 创建定时任务实例,但初始状态为暂停 const job = cron.schedule(cronExpression, wrappedCallback, { scheduled: false }); // 存储到临时数组和结果数组 cronJobInstances.push(job); this.tempCronJob.push(job); } // 返回创建的所有任务实例,便于后续管理 return cronJobInstances; } // 原有的字符串格式处理(单个定时任务) if (!cron.validate(cronTasks)) { log.error(`[-]无效的 cron 表达式: ${cronTasks}`); this.tempCronJob.push(false); return null; } // 同样使用最小化上下文创建单个任务 const job = cron.schedule(cronTasks, func, { scheduled: false }); this.tempCronJob.push(job); cronJobInstances.push(job); return job; // 返回单个任务实例 }, plugin: { getPlugins: () => { return this.getPlugins(); }, onPlugin: (pluginName) => { return this.onPlugin(pluginName); }, offPlugin: (pluginName) => { return this.offPlugin(pluginName); }, reloadPlugin: (pluginName) => { return this.reloadPlugin(pluginName); }, getPluginsFromDir: () => { return this.getPluginsFromDir(); }, loadPlugin: (pluginName) => { return this.loadPlugin(pluginName); } }, handle: (eventName, func) => { const wrappedFunc = async (e) => { try { // 添加reply方法 const extendedEvent = this.ctx.utils.addReplyMethod(e); // @ts-ignore: 忽略复杂联合类型的错误 return await func(extendedEvent); } catch (error) { // 记录错误但不中断事件处理流程 log.error(`[-]处理${eventName}事件时出错: ${error}`); // 避免错误影响整个系统 return null; } }; const obj = { event: eventName, fn: wrappedFunc }; this.tempListener.push(obj); }, isMaster: (e) => { if (typeof e === 'number' && !isNaN(e)) { return this.ctx.config.master.includes(e); } if (typeof e === 'object' && e.sender && typeof e.sender.user_id === 'number') { return this.ctx.config.master.includes(e.sender.user_id); } return false; }, isAdmin: (e) => { if (typeof e === 'number' && !isNaN(e)) { return this.ctx.config.master.includes(e) || this.ctx.config.admins.includes(e); } if (typeof e === 'object' && e.sender && typeof e.sender.user_id === 'number') { const userId = e.sender.user_id; return this.ctx.config.master.includes(userId) || this.ctx.config.admins.includes(userId); } return false; }, hasRight: (user_id) => { return this.ctx.isMaster(user_id) || this.ctx.isAdmin(user_id); }, sendPrivateMessage: async (user_id, message) => { try { return await this.bot.send_private_msg({ user_id: user_id, message: Array.isArray(message) ? message : [Structs.text(String(message))] }); } catch (error) { log.error(`Failed to send message: ${error}`); return { message_id: 0 }; } }, sendGroupMessage: async (group_id, message) => { try { return await this.bot.send_group_msg({ group_id: group_id, message: Array.isArray(message) ? message : [Structs.text(String(message))] }); } catch (error) { log.error(`Failed to send message: ${error}`); return { message_id: 0 }; } }, delete_msg: async (message_id) => { try { await this.bot.delete_msg({ message_id }); } catch (error) { log.error(`Failed to delete message: ${error}`); } }, kick: async (group_id, user_id, reject_add_request) => { try { await this.bot.set_group_kick({ group_id: group_id, user_id: user_id, reject_add_request: reject_add_request }); } catch (error) { log.error(`Failed to kick user ${user_id} from group ${group_id}: ${error}`); } }, ban: async (group_id, user_id, duration) => { try { await this.bot.set_group_ban({ group_id: group_id, user_id: user_id, duration: duration }); } catch (error) { log.error(`Failed to ban user ${user_id} in group ${group_id}: ${error}`); } }, banAll: async (group_id, enable) => { try { await this.bot.set_group_whole_ban({ group_id: group_id, enable: enable }); } catch (error) { log.error(`Failed to set whole ban for group ${group_id} to ${enable}: ${error}`); } }, setGroupName: async (group_id, name) => { try { await this.bot.set_group_name({ group_id: group_id, group_name: name }); } catch (error) { log.error(`Failed to set group name for group ${group_id} to ${name}: ${error}`); } }, setAdmin: async (group_id, user_id, enable) => { try { await this.bot.set_group_admin({ group_id: group_id, user_id: user_id, enable: enable }); } catch (error) { log.error(`Failed to set admin status for user ${user_id} in group ${group_id} to ${enable}: ${error}`); } }, setTitle: async (group_id, user_id, title) => { try { await this.bot.set_group_special_title({ group_id: group_id, user_id: user_id, special_title: title }); } catch (error) { log.error(`Failed to set special title for user ${user_id} in group ${group_id} to ${title}: ${error}`); } }, aprroveGroup: async (flag) => { try { await this.bot.set_group_add_request({ flag: flag, approve: true }); } catch (error) { log.error(`Failed to approve group request: ${error}`); } }, rejectGroup: async (flag) => { try { await this.bot.set_group_add_request({ flag: flag, approve: false }); } catch (error) { log.error(`Failed to reject group request: ${error}`); } }, isGroupAdmin: async (group_id, user_id) => { try { const memberInfo = await this.bot.get_group_member_info({ group_id, user_id }); return memberInfo.role === 'admin' || memberInfo.role === 'owner'; } catch (error) { log.error(`Failed to check if user ${user_id} is an admin in group ${group_id}: ${error}`); return false; } }, isGroupOwner: async (group_id, user_id) => { try { const memberInfo = await this.bot.get_group_member_info({ group_id, user_id }); return memberInfo.role === 'owner'; } catch (error) { log.error(`Failed to check if user ${user_id} is an owner in group ${group_id}: ${error}`); return false; } }, md5: (text) => { const hash = createHash('md5'); hash.update(text); return hash.digest('hex'); }, randomInt: (min, max) => { return Math.floor(Math.random() * (max - min + 1)) + min; }, randomItem: (array) => { return array[Math.floor(Math.random() * array.length)]; }, getGroupAvatarLink: (group_id, size) => { return `https://p.qlogo.cn/gh/${group_id}/${group_id}/${size || 40}`; }, getQQAvatarLink: (user_id, size) => { return `https://q2.qlogo.cn/headimg_dl?dst_uin=${user_id}&spec=${size || 40}`; }, getImageLink: (e) => { try { if (!Array.isArray(e.message)) return ""; const imageItem = e.message.find(item => item.type === "image"); return imageItem?.data?.url.trim() || ""; } catch (error) { log.error('提取图片链接时发生错误:', error); return ""; } }, getDirectLink: async (url) => { try { const rKey = await this.bot.nc_get_rkey(); if (!rKey) { log.error('获取 rkey 失败,无法替换'); return ""; } // 从URL中提取appid const appidMatch = url.match(/appid=(\d+)/); const appid = appidMatch ? appidMatch[1] : null; // 根据appid选择rkey let current_rkey; if (appid === '1406') { current_rkey = rKey[0]?.rkey; } else if (appid === '1407') { current_rkey = rKey[1]?.rkey; } else { log.error('未知的appid或无法从URL中提取appid'); return ""; } // 使用正则表达式提取 &rkey= 之前的内容 const regex = /^(.*?)&rkey=/; const baseUrl = url.match(regex)?.[1]; // 如果匹配到内容,拼接 rKey,否则返回空字符串 return baseUrl ? `${baseUrl}${current_rkey}` : ""; } catch (error) { log.error('获取直链失败:', error); return ""; } }, getReplyMessageId: (e) => { try { if (!Array.isArray(e.message)) return ""; const replyObj = e.message.find(item => item.type === "reply"); return replyObj?.data?.id.trim() || ""; // 转为 number 或 null } catch (error) { log.error('提取消息ID时发生错误:', error); return ""; } }, getMessageAt: (e) => { try { if (!Array.isArray(e.message)) return []; return e.message .filter(item => item.type === "at") // 筛选所有 type 为 "at" 的项 .map(item => Number(item.data?.qq)) // 提取 qq 字段 .filter(qq => !isNaN(qq)); // 过滤掉 undefined } catch (error) { log.error('提取消息ID时发生错误:', error); return []; } }, getText: (e) => { try { if (!Array.isArray(e.message)) return ""; const textObj = e.message.find(item => item.type === "text"); return textObj?.data?.text.trim() || ""; // 返回 "%%" 或其他文本 } catch (error) { log.error('提取纯文本内容时发生错误:', error); return ""; } }, getQuotedText: async (e) => { try { const message_id = this.ctx.getReplyMessageId(e); if (!message_id) return ""; // 提前返回无效情况 const { raw_message } = await this.bot.get_msg({ message_id: Number(message_id) }); return raw_message || ""; // 确保总是返回字符串 } catch (error) { logger.error('提取被引用的文本时发生错误:', error); return ""; } }, fakeMessage: async (target_id, message, isGroup) => { try { // 调用 send_group_forward_msg 函数 /**@ =message例子= * message: [ * { * type: 'node', * data: { * content: [ * Structs.text(message) // 消息内容,使用 Structs.text 生成文本消息 * ] * } * } * ] **/ // 动态构建参数对象 const params = isGroup ? { group_id: target_id, message: message } // 群聊消息 : { user_id: target_id, message: message }; // 私聊消息 // 调用转发消息函数 return await this.bot.send_forward_msg(params); } catch (error) { log.error(`Failed to send fake message to target ${target_id}: ${error}`); throw error; } }, /** 工具函数 */ utils: { addReplyMethod: (e) => { // 如果已经有reply方法,直接返回 if (e.reply) return e; // 提取消息类型和ID,避免闭包持有整个事件对象 const messageType = e.message_type || 'private'; const messageId = e.message_id; const userId = e.user_id; const groupId = e.group_id; // 添加reply方法,尽量减少引用 e.reply = async (message, quote = false) => { // 处理消息内容,统一转为数组格式 let messageArray = Array.isArray(message) ? message : [message]; // 转换文本和数字为消息段 const processedMessages = messageArray.map(item => { if (typeof item === 'string' || typeof item === 'number') { return Structs.text(item.toString()); } return item; }); // 添加回复消息段(如果需要引用) if (quote && messageId) { processedMessages.unshift(Structs.reply(messageId)); } // 根据消息类型确定发送参数 const sendParams = (() => { if (messageType === 'group' || groupId) { return { group_id: groupId }; } else { return { user_id: userId }; } })(); // 发送消息并返回结果 try { const response = await this.bot.send_msg({ ...sendParams, message: processedMessages }); return { message_id: response.message_id }; } catch (error) { log.error(`Failed to send message: ${error}`); return { message_id: 0 }; } }; // 添加kick方法,方便移除群成员 if (messageType === 'group' && groupId) { e.kick = async (kickUserId, reject_add_request) => { try { await this.bot.set_group_kick({ group_id: groupId, user_id: kickUserId, reject_add_request }); } catch (error) { log.error(`Failed to kick user ${kickUserId}: ${error}`); } }; } return e; } } }; } // 创建插件上下文代理的方法 createPluginContextProxy(pluginName) { // 如果已经存在此插件的代理,直接返回 if (this.pluginCtxProxies.has(pluginName)) { return this.pluginCtxProxies.get(pluginName); } // 确保此插件有错误处理函数缓存 if (!this.pluginErrorHandlers.has(pluginName)) { this.pluginErrorHandlers.set(pluginName, new Map()); } const pluginErrorCache = this.pluginErrorHandlers.get(pluginName); // 为特定插件创建的状态,可以单独维护 const pluginState = { name: pluginName, // 这里可以添加插件特定的状态 }; // 创建轻量级代理对象 - 直接代理原始ctx const pluginCtxProxy = new Proxy(this.ctx, { get: (target, prop, receiver) => { const value = Reflect.get(target, prop, receiver); // 处理函数类型的属性 if (typeof value === 'function') { const propKey = String(prop); // 1. 首先尝试获取插件特定的错误处理包装器 if (pluginErrorCache.has(propKey)) { return pluginErrorCache.get(propKey); } // 2. 然后尝试从共享函数缓存获取 if (this.sharedMethodWrappers.has(propKey)) { // 获取通用的函数包装 const sharedWrapper = this.sharedMethodWrappers.get(propKey); // 创建插件特定的错误处理包装 - 复用通用逻辑但添加插件特定的错误处理 const errorHandler = (...args) => { try { return sharedWrapper.apply(target, args); } catch (error) { // 添加插件特定的错误处理 ErrorHandler.logError(pluginName, `ctx_method_${propKey}`, error); log.warn(`[!]插件${pluginName}调用${propKey}方法出错: ${ErrorHandler.formatError(error)}`); throw error; } }; // 缓存此插件特定的错误处理包装 pluginErrorCache.set(propKey, errorHandler); return errorHandler; } // 3. 如果缓存中不存在,创建并存储通用函数包装 const genericWrapper = function (...args) { return value.apply(target, args); }; // 存储到共享函数缓存 this.sharedMethodWrappers.set(propKey, genericWrapper); // 创建并缓存插件特定的错误处理包装 const errorHandler = (...args) => { try { return genericWrapper.apply(target, args); } catch (error) { // 添加插件特定的错误处理 ErrorHandler.logError(pluginName, `ctx_method_${propKey}`, error); log.warn(`[!]插件${pluginName}调用${propKey}方法出错: ${ErrorHandler.formatError(error)}`); throw error; } }; // 缓存此插件特定的错误处理包装 pluginErrorCache.set(propKey, errorHandler); return errorHandler; } // 处理需要隔离的属性 if (prop === 'plugin') { // 确保plugin工具方法在调用时能正确获取当前插件名 return { ...value, // 重写可能需要特殊处理的方法 reloadPlugin: (name) => { // 默认重载自己 if (!name || name === '') { return this.reloadPlugin(pluginName); } return value.reloadPlugin(name); } }; } return value; } }); // 保存到代理缓存中 this.pluginCtxProxies.set(pluginName, pluginCtxProxy); return pluginCtxProxy; } async init() { // 移除对initSharedContext的调用 // this.initSharedContext(); // 之前的方法是获取所有插件目录中的插件 //const pluginList = this.getPluginsFromDir(); // 修改为只获取配置文件中指定的系统和用户插件 const configSystemPlugins = this.ctx.config.plugins.system || []; const configUserPlugins = this.ctx.config.plugins.user || []; // 合并系统插件和用户插件 const pluginList = [...configSystemPlugins, ...configUserPlugins]; // 输出加载的插件 log.info(`[+]正在加载配置中的插件: ${pluginList.join(', ') || '无'}`); let success = 0, fail = 0; for (const p of pluginList) { try { const result = await this.loadPlugin(p); if (result) { success++; } else { log.error(`[-]插件${p}加载失败`); fail++; } } catch (err) { log.error(`[-]插件${p}导入失败: ${err}`); fail++; } } log.info(`[+]插件加载完毕, 一共导入${success + fail}个插件, 成功: ${success}, 失败: ${fail}`); // 显示启用插件数量比例(相对于所有可用插件) const enabledCount = Array.from(this.plugins.values()).filter(info => info.setup.enable).length; const totalAvailablePlugins = this.getPluginsFromDir().length; log.info(`[+]已启用插件: ${enabledCount}/${totalAvailablePlugins} (已加载/可用)`); return this.plugins; } getPluginsFromDir() { const pluginsPath = join(process.cwd(), "plugins"); const plugins = []; // 读取所有文件和目录 if (existsSync(pluginsPath)) { const allFiles = readdirSync(pluginsPath); // 处理所有文件和目录 for (const item of allFiles) { const fullPath = join(pluginsPath, item); const stats = statSync(fullPath); if (stats.isDirectory()) { // 如果是目录,检查是否有index.ts或index.js const hasTsIndex = existsSync(join(fullPath, "index.ts")); const hasJsIndex = existsSync(join(fullPath, "index.js")); if (hasTsIndex || hasJsIndex) { plugins.push(item); } } else if (stats.isFile()) { // 如果是文件,检查是否是.ts或.js文件 if (item.endsWith('.ts') || item.endsWith('.js')) { // 去掉文件扩展名 const pluginName = item.replace(/\.(ts|js)$/, ''); plugins.push(pluginName); } } } } return plugins; } async loadPlugin(pluginName) { try { log.info(`[*]正在加载插件 ${pluginName}...`); // 使用绝对路径替代相对路径 const pluginDir = join(process.cwd(), "plugins"); // 优先检查JS文件(编译后的文件)再检查TS文件 // 检查子目录中的插件文件 const subDirJsPath = join(pluginDir, pluginName, "index.js"); const subDirTsPath = join(pluginDir, pluginName, "index.ts"); // 检查直接的插件文件 const directJsPath = join(pluginDir, `${pluginName}.js`); const directTsPath = join(pluginDir, `${pluginName}.ts`); // 首先检查子目录js,然后子目录ts,然后直接js,最后直接ts let pluginPath = ''; if (existsSync(subDirJsPath)) { pluginPath = subDirJsPath; } else if (existsSync(subDirTsPath)) { pluginPath = subDirTsPath; } else if (existsSync(directJsPath)) { pluginPath = directJsPath; } else if (existsSync(directTsPath)) { pluginPath = directTsPath; } else { log.error(`[-]插件${pluginName}不存在`); return `[-]插件${pluginName}不存在`; } // 尝试加载插件 try { // 清除之前的模块缓存 this.cleanPluginModuleCache(pluginName); // 动态导入插件 const plugin = await this.jiti(pluginPath); // 检查插件结构 if (!plugin || !plugin.default || !plugin.default.name) { log.error(`[-]插件${pluginName}格式错误,缺少必要字段`); return `[-]插件${pluginName}格式错误,缺少必要字段`; } // 创建此插件的上下文代理 const pluginCtx = this.createPluginContextProxy(pluginName); // 安全地执行插件初始化 try { this.tempListener = []; this.tempCronJob = []; // 使用插件特定的上下文代理 await Promise.resolve(plugin.default.setup(pluginCtx)); // 设置插件信息 const pluginType = this.ctx.config.plugins.system.includes(pluginName) ? 'system' : 'user'; this.plugins.set(pluginName, { version: plugin.default.version || "0.1.0", description: plugin.default.description || "", type: pluginType, setup: { enable: false, listeners: [...this.tempListener], // 创建新数组,避免引用 cron: [...this.tempCronJob] } }); // 存储插件的定时任务到任务池中,便于后续管理 if (this.tempCronJob.length > 0) { // 过滤掉无效的任务(null或false值) const validJobs = this.tempCronJob.filter(job => job && typeof job === 'object'); if (validJobs.length > 0) { this.cronTaskPool.set(pluginName, validJobs); log.debug(`[*]已存储插件 ${pluginName} 的 ${validJobs.length} 个定时任务`); } } // 检查是否需要自动启用 const enabledPlugins = pluginType === 'system' ? this.ctx.config.plugins.system : this.ctx.config.plugins.user; if (enabledPlugins.includes(pluginName)) { log.info(`[*]插件${pluginName}在配置中已启用,正在激活...`); // 使用 onPlugin 方法来确保正确启用 const result = this.onPlugin(pluginName); if (result.startsWith('[-]')) { log.error(`[-]插件${pluginName}自动启用失败: ${result}`); return false; } } return true; } catch (error) { log.error(`[-]插件${pluginName}初始化失败: ${error}`); // 清理已注册的临时资源 this.tempListener = []; this.tempCronJob = []; return false; } } catch (error) { ErrorHandler.logError(pluginName, 'plugin_load', error); log.error(`[-]加载插件${pluginName}失败: ${ErrorHandler.formatError(error)}`); return `[-]加载插件${pluginName}失败: ${ErrorHandler.formatError(error)}`; } } catch (error) { ErrorHandler.logError(pluginName, 'plugin_load_outer', error); log.error(`[-]加载插件${pluginName}外部错误: ${ErrorHandler.formatError(error)}`); return `[-]加载插件${pluginName}外部错误: ${ErrorHandler.formatError(error)}`; } } getPlugins() { // 获取实际文件系统中的插件列表 const actualPlugins = this.getPluginsFromDir(); // 清理不存在的插件 for (const [pluginName] of this.plugins) { if (!actualPlugins.includes(pluginName)) { this.plugins.delete(pluginName); // 从配置文件中移除该插件 this.saveConfig(pluginName, false); } } return this.plugins; } /** * 保存配置到文件 * @param pluginName 插件名称 * @param isEnabled 是否启用 * @private */ saveConfig(pluginName, isEnabled) { try { const configPath = join(process.cwd(), "config.json"); // 读取完整配置文件 const configContent = readFileSync(configPath, "utf-8"); // 使用显式类型注解 const fullConfig = JSON.parse(configContent); // 确保plugins部分存在 if (!fullConfig.plugins) { fullConfig.plugins = { system: [], user: [] }; } // 判断是系统插件还是用户插件 const pluginInfo = this.plugins.get(pluginName); const pluginType = pluginInfo?.type || 'user'; // 确保对应数组存在 if (!fullConfig.plugins[pluginType]) { fullConfig.plugins[pluginType] = []; } const targetArray = fullConfig.plugins[pluginType]; // 添加或移除插件名 if (isEnabled && !targetArray.includes(pluginName)) { targetArray.push(pluginName); } else if (!isEnabled) { const index = targetArray.indexOf(pluginName); if (index > -1) { targetArray.splice(index, 1); } } // 保存回文件,使用同步方法避免并发问题 writeFileSync(configPath, JSON.stringify(fullConfig, null, 2)); log.info(`[+]配置文件已更新: ${pluginName} ${isEnabled ? '已启用' : '已禁用'}`); } catch (error) { // 添加类型注解 log.error(`[-]保存配置文件失败: ${error}`); // 通知出现错误,而不是默默失败 throw new Error(`保存配置文件失败: ${error.message || String(error)}`); } } offPlugin(pluginName) { const map = this.plugins.get(pluginName); if (!this.plugins.has(pluginName)) { return "[-]该插件不存在"; } // 如果插件已经是禁用状态,则直接返回 if (map?.setup && map.setup.enable === false) { log.debug(`[*]插件${pl