├── plugins ├── morning-alert │ ├── config.json │ └── index.js ├── ai-speedtest │ ├── config.json │ └── index.js ├── api-toolkit │ └── config.json ├── weather │ └── index.js └── ai-chat │ └── index.js ├── .gitignore ├── config.example.json ├── permission-manager.js ├── README.md ├── plugin-loader.js ├── core.js └── 智能助手.js /plugins/morning-alert/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "time": "07:00", 4 | "defaultCity": "北京", 5 | "templates": { 6 | "morning": "早上好,今天是 {{date}},{{holiday}}。\n\n今日天气:\n{{weather}}\n\n{{forecast}}\n\nAI生活建议:\n{{advice}}" 7 | } 8 | } -------------------------------------------------------------------------------- /plugins/ai-speedtest/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "testInterval": 3600000, 4 | "testPrompt": "用一句话简明扼要地回答:今天天气好吗?", 5 | "testTimeout": 15000, 6 | "autoSwitch": true, 7 | "skipDisabled": true, 8 | "excludeModels": [], 9 | "currentFastest": null, 10 | "lastTestTime": 0, 11 | "testResults": {} 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 依赖目录 2 | node_modules/ 3 | 4 | # 日志文件 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # 运行时数据 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # 测试覆盖率目录 18 | coverage/ 19 | 20 | # IDE配置 21 | .vscode/ 22 | .idea/ 23 | 24 | # 系统文件 25 | .DS_Store 26 | Thumbs.db 27 | 28 | # 环境变量 29 | .env 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | 35 | # 临时文件 36 | tmp/ 37 | temp/ 38 | 39 | # 敏感配置文件(只忽略根目录的config.json) 40 | /config.json -------------------------------------------------------------------------------- /config.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabledPlugins": ["weather", "ai-chat", "morning-alert", "api-toolkit", "api-speedtest"], 3 | "pluginSettings": { 4 | "weather": { 5 | "api": "amap", 6 | "key": "YOUR_AMAP_API_KEY_HERE", 7 | "defaultCity": "北京" 8 | }, 9 | "ai-chat": { 10 | "defaultModel": "deepseek", 11 | "models": { 12 | "deepseek": { 13 | "name": "DeepSeek", 14 | "apiKey": "YOUR_DEEPSEEK_API_KEY_HERE", 15 | "enabled": true 16 | }, 17 | "siliconflow": { 18 | "name": "SiliconFlow", 19 | "apiKey": "YOUR_SILICONFLOW_API_KEY_HERE", 20 | "enabled": true, 21 | "model": "deepseek-ai/DeepSeek-V3" 22 | }, 23 | "openai": { 24 | "name": "OpenAI", 25 | "apiKey": "", 26 | "enabled": false 27 | } 28 | } 29 | }, 30 | "morning-alert": { 31 | "enabled": true, 32 | "time": "07:00" 33 | } 34 | }, 35 | "adminUsers": [] 36 | } -------------------------------------------------------------------------------- /permission-manager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 权限管理模块 3 | * @description permission-manager 4 | * @version 1.0.0 5 | * @author shuaijin 6 | * @team shuaijin 7 | * @rule none 8 | * @admin true 9 | * @public false 10 | * @disable true 11 | */ 12 | 13 | const fs = require('fs'); 14 | const path = require('path'); 15 | 16 | class PermissionManager { 17 | constructor() { 18 | this.configPath = path.join(__dirname, 'config.json'); 19 | this.config = null; 20 | this.adminUsers = ''; 21 | this.loadConfig(); 22 | } 23 | 24 | // 加载配置 25 | loadConfig() { 26 | try { 27 | if (fs.existsSync(this.configPath)) { 28 | const configContent = fs.readFileSync(this.configPath, 'utf8'); 29 | this.config = JSON.parse(configContent); 30 | 31 | // 处理 adminUsers 字段,统一转为字符串格式 32 | if (this.config.adminUsers !== undefined) { 33 | if (Array.isArray(this.config.adminUsers)) { 34 | // 如果是数组,转换为逗号分隔的字符串 35 | this.adminUsers = this.config.adminUsers.join(','); 36 | // 也更新配置对象,确保保存时是字符串 37 | this.config.adminUsers = this.adminUsers; 38 | } else if (typeof this.config.adminUsers === 'string') { 39 | this.adminUsers = this.config.adminUsers; 40 | } else { 41 | this.adminUsers = ''; 42 | } 43 | } else { 44 | this.adminUsers = ''; 45 | } 46 | 47 | console.log(`[权限管理] 已加载管理员用户: ${this.adminUsers || '无'}`); 48 | } else { 49 | console.error('[权限管理] 配置文件不存在'); 50 | this.config = { adminUsers: '' }; 51 | this.adminUsers = ''; 52 | } 53 | } catch (error) { 54 | console.error(`[权限管理] 加载配置失败: ${error.message}`); 55 | this.config = { adminUsers: '' }; 56 | this.adminUsers = ''; 57 | } 58 | } 59 | 60 | // 保存配置 61 | saveConfig() { 62 | try { 63 | // 确保adminUsers已更新到config对象 64 | this.config.adminUsers = this.adminUsers; 65 | 66 | // 写入文件 67 | fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2), 'utf8'); 68 | 69 | console.log(`[权限管理] 配置已保存,管理员用户: ${this.adminUsers || '无'}`); 70 | return true; 71 | } catch (error) { 72 | console.error(`[权限管理] 保存配置失败: ${error.message}`); 73 | return false; 74 | } 75 | } 76 | 77 | // 检查是否为管理员 78 | isAdmin(userId) { 79 | if (!this.adminUsers) return false; 80 | return this.adminUsers.split(',').includes(userId); 81 | } 82 | 83 | // 添加管理员 84 | addAdmin(userId) { 85 | if (this.isAdmin(userId)) { 86 | return false; // 已经是管理员 87 | } 88 | 89 | if (this.adminUsers) { 90 | this.adminUsers += ',' + userId; 91 | } else { 92 | this.adminUsers = userId; 93 | } 94 | return this.saveConfig(); 95 | } 96 | 97 | // 移除管理员 98 | removeAdmin(userId) { 99 | if (!this.isAdmin(userId)) { 100 | return false; // 不是管理员 101 | } 102 | 103 | const admins = this.adminUsers.split(',').filter(id => id !== userId); 104 | this.adminUsers = admins.join(','); 105 | return this.saveConfig(); 106 | } 107 | 108 | // 获取所有管理员 109 | getAllAdmins() { 110 | return this.adminUsers ? this.adminUsers.split(',') : []; 111 | } 112 | 113 | // 检查权限 114 | checkPermission(userId, permission) { 115 | // 目前只实现了admin权限,未来可以扩展更多权限类型 116 | if (permission === 'admin') { 117 | return this.isAdmin(userId); 118 | } 119 | 120 | // 默认返回false,未来可以实现更细粒度的权限 121 | return false; 122 | } 123 | 124 | // 重新加载配置 125 | reload() { 126 | this.loadConfig(); 127 | return true; 128 | } 129 | } 130 | 131 | // 创建单例 132 | const permissionManager = new PermissionManager(); 133 | 134 | module.exports = permissionManager; -------------------------------------------------------------------------------- /plugins/api-toolkit/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": true, 3 | "commandPrefix": "api", 4 | "apis": { 5 | "heisi": { 6 | "name": "黑丝图片", 7 | "url": "https://v2.xxapi.cn/api/heisi", 8 | "method": "GET", 9 | "type": "image", 10 | "enabled": true, 11 | "description": "随机返回黑丝图片", 12 | "params": {} 13 | }, 14 | "baisi": { 15 | "name": "白丝图片", 16 | "url": "https://v2.xxapi.cn/api/baisi", 17 | "method": "GET", 18 | "type": "image", 19 | "enabled": true, 20 | "description": "随机返回白丝图片", 21 | "params": {} 22 | }, 23 | "avatar": { 24 | "name": "随机头像", 25 | "url": "https://v2.xxapi.cn/api/head", 26 | "method": "GET", 27 | "type": "image", 28 | "enabled": true, 29 | "description": "随机返回头像图片", 30 | "params": {} 31 | }, 32 | "acg-pc": { 33 | "name": "二次元图片", 34 | "url": "https://v2.xxapi.cn/api/randomAcgPic", 35 | "method": "GET", 36 | "type": "image", 37 | "enabled": true, 38 | "description": "随机返回电脑端二次元图片 ", 39 | "params": { 40 | "type":"pc" 41 | } 42 | }, 43 | "acg-wap": { 44 | "name": "二次元图片", 45 | "url": "https://v2.xxapi.cn/api/randomAcgPic", 46 | "method": "GET", 47 | "type": "image", 48 | "enabled": true, 49 | "description": "随机返回手机端二次元图片", 50 | "params": { 51 | "type":"wap" 52 | } 53 | }, 54 | "4k-acg": { 55 | "name": "4K图片", 56 | "url": "https://v2.xxapi.cn/api/random4kPic", 57 | "method": "GET", 58 | "type": "image", 59 | "enabled": true, 60 | "description": "随机返回4K二次元图片", 61 | "params": { 62 | "type":"acg" 63 | } 64 | }, 65 | "4k-wallpaper": { 66 | "name": "4K图片", 67 | "url": "https://v2.xxapi.cn/api/random4kPic", 68 | "method": "GET", 69 | "type": "image", 70 | "enabled": true, 71 | "description": "随机返回4K二次元图片", 72 | "params": { 73 | "type":"wallpaper" 74 | } 75 | }, 76 | "wallpaper": { 77 | "name": "壁纸图片", 78 | "url": "https://v2.xxapi.cn/api/wallpaper", 79 | "method": "GET", 80 | "type": "image", 81 | "enabled": true, 82 | "description": "随机返回壁纸图片", 83 | "params": {} 84 | }, 85 | "girl": { 86 | "name": "小姐姐图片", 87 | "url": "https://v2.xxapi.cn/api/meinvpic", 88 | "method": "GET", 89 | "type": "image", 90 | "enabled": true, 91 | "description": "随机返回小姐姐图片", 92 | "params": { 93 | 94 | }, 95 | "dataPath": "data.url" 96 | }, 97 | "girlvideo": { 98 | "name": "小姐姐视频", 99 | "url": "https://v2.xxapi.cn/api/meinv", 100 | "method": "GET", 101 | "type": "video", 102 | "enabled": true, 103 | "description": "随机返回小姐姐视频", 104 | "params": {} 105 | }, 106 | "meitui": { 107 | "name": "美腿", 108 | "url": "https://api.dwo.cc/api/meizi", 109 | "method": "GET", 110 | "type": "image", 111 | "enabled": true, 112 | "description": "随机返回美腿图片", 113 | "params": { 114 | "type": "json" 115 | }, 116 | "dataPath": "数据.pic" 117 | }, 118 | "douyinhot": { 119 | "name": "抖音热点", 120 | "url": "https://v2.xxapi.cn/api/douyinhot", 121 | "method": "GET", 122 | "type": "text", 123 | "enabled": true, 124 | "description": "获取抖音热搜榜", 125 | "params": {}, 126 | "dataPath": "data" 127 | }, 128 | "weibohot": { 129 | "name": "微博热搜", 130 | "url": "https://v2.xxapi.cn/api/weibohot", 131 | "method": "GET", 132 | "type": "text", 133 | "enabled": true, 134 | "description": "获取微博热搜榜", 135 | "params": {}, 136 | "dataPath": "data" 137 | }, 138 | "baiduhot": { 139 | "name": "百度热搜", 140 | "url": "https://v2.xxapi.cn/api/baiduhot", 141 | "method": "GET", 142 | "type": "text", 143 | "enabled": true, 144 | "description": "获取百度热搜榜", 145 | "params": {}, 146 | "dataPath": "data" 147 | }, 148 | "bilibilihot": { 149 | "name": "B站热门", 150 | "url": "https://v2.xxapi.cn/api/bilibilihot", 151 | "method": "GET", 152 | "type": "text", 153 | "enabled": true, 154 | "description": "获取B站热门视频", 155 | "params": {}, 156 | "dataPath": "data" 157 | }, 158 | "hot": { 159 | "name": "热搜聚合", 160 | "url": null, 161 | "method": "GET", 162 | "type": "text", 163 | "enabled": true, 164 | "description": "聚合所有热搜榜,一次性展示(allhot别名)", 165 | "params": {}, 166 | "customHandler": true 167 | } 168 | }, 169 | "rateLimit": { 170 | "perUser": 10, 171 | "timeWindow": 60000, 172 | "enabled": true 173 | }, 174 | "cache": { 175 | "enabled": true, 176 | "expiry": 3600000 177 | } 178 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信智能助手插件系统 2 | 3 | 一个为无界BNCR框架设计的模块化、可扩展的微信智能助手插件系统,支持天气查询、AI聊天、API工具箱等多种功能。 4 | 5 | ## 主要功能 6 | 7 | - 🌤️ **天气服务**:查询实时天气和天气预报,自动提供AI生活建议 8 | - 🤖 **AI聊天**:支持多种AI模型(DeepSeek、SiliconFlow、OpenAI等) 9 | - 🔧 **API工具箱**:提供各种实用API服务,包括美女图片,二次元壁纸等 10 | - ⏱️ **早报提醒**:每日定时推送信息 11 | - 🚀 **网速测试**:测试AI接口响应速度 12 | - 🔒 **权限管理**:基本的管理员权限控制 13 | - 🔄 **多种插件兼容**:支持函数式和对象式两种插件导出格式 14 | 15 | ## 安装方法 16 | 17 | ### 前置要求 18 | 19 | - 无界BNCR框架环境 20 | - Node.js (建议v14.0.0+) 21 | 22 | ### 安装步骤 23 | 24 | 1. 进入无界BNCR插件目录新建文件夹WeChatAssistant 25 | 26 | 2. 克隆仓库,将本仓库所有代码放置到文件夹中 27 | ```bash 28 | git clone https://github.com/2013888483/WeChatAssistant.git 29 | ``` 30 | 31 | 3. 去插件配置页面配置参数 32 | 4. 重启无界BNCR服务,插件将自动加载 33 | 34 | ## 使用方法 35 | 36 | ### 基础命令 37 | 38 | - `/help` - 显示帮助信息 39 | - `/plugins list` - 列出所有可用插件 (需管理员权限) 40 | - `/plugins enable <插件名>` - 启用指定插件 (需管理员权限) 41 | - `/plugins disable <插件名>` - 禁用指定插件 (需管理员权限) 42 | - `/plugins reload <插件名>` - 重新加载插件 (需管理员权限) 43 | - `/admin list` - 查看当前管理员列表 (需管理员权限) 44 | - `/admin add <用户ID>` - 添加新管理员 (需管理员权限) 45 | - `/admin remove <用户ID>` - 移除管理员 (需管理员权限) 46 | 47 | ### 天气服务 48 | 49 | - `/天气 <城市名>` 或 `/weather <城市名>` - 查询指定城市的实时天气 50 | - `/天气配置` 或 `/weather_config` - 查看当前天气服务配置 51 | - `/天气配置 set api ` - 设置天气API类型(amap或openweather) 52 | - `/天气配置 set key ` - 设置API密钥 53 | - `/天气配置 set defaultCity <城市名>` - 设置默认城市 54 | - `/天气配置 set showAIAdvice ` - 设置是否显示AI生活建议 55 | 56 | ### AI聊天 57 | 58 | - `/chat <内容>` - 与AI对话 59 | - `/model list` - 查看可用AI模型 60 | - `/model use <模型名>` - 切换AI模型 61 | - `/model config <模型名> <参数名> <参数值>` - 设置模型参数 62 | 63 | ### API工具箱 64 | 65 | - `/api list` - 列出所有可用API 66 | - `/api <参数>` - 调用指定API 67 | 68 | ### 早报提醒 69 | 70 | - `/morning on` - 开启每日早报 71 | - `/morning off` - 关闭每日早报 72 | - `/morning time <时间>` - 设置早报时间,格式如 07:30 73 | 74 | 可以执行插件管理和系统管理命令 75 | 76 | ## 插件机制 77 | 78 | 该系统支持两种插件导出格式: 79 | 80 | ### 1. 对象式导出 (推荐) 81 | 82 | ```javascript 83 | // 对象式导出 84 | exports.meta = { 85 | name: "插件名称", 86 | version: "1.0.0", 87 | description: "插件描述", 88 | author: "作者" 89 | }; 90 | 91 | // 添加标记,表明这是正确支持的导出格式 92 | exports.exportFormat = "object"; 93 | 94 | // 插件默认配置 95 | exports.defaultConfig = { 96 | enabled: true, 97 | // 其他配置项 98 | }; 99 | 100 | // 初始化config属性 101 | exports.config = exports.defaultConfig; 102 | 103 | // 插件初始化方法 104 | exports.initialize = async function(core, pluginConfig) { 105 | // 插件初始化代码 106 | return true; 107 | }; 108 | 109 | // 命令处理 110 | exports.onMessage = async function(message) { 111 | // 消息处理代码 112 | return response; 113 | }; 114 | 115 | // 自定义方法 116 | exports.customMethod = async function() { 117 | // 自定义功能 118 | }; 119 | 120 | // 卸载函数 121 | exports.unload = async function() { 122 | // 清理代码 123 | return true; 124 | }; 125 | ``` 126 | 127 | ### 2. 函数式导出 (兼容模式) 128 | 129 | ```javascript 130 | // 函数式导出 131 | module.exports = async function(core, pluginConfig) { 132 | return { 133 | name: "插件名称", 134 | description: "插件描述", 135 | commands: [ 136 | { 137 | command: /^\/command/, 138 | handler: async (message, sender) => { 139 | // 处理逻辑 140 | return '响应'; 141 | } 142 | } 143 | ], 144 | initialize: async () => { 145 | // 初始化逻辑 146 | return true; 147 | }, 148 | // 其他方法 149 | }; 150 | }; 151 | ``` 152 | 153 | 系统会自动识别插件的导出格式并正确处理。 154 | 155 | ## 新增功能 156 | 157 | ### 1. 模拟天气数据 158 | 159 | 当未配置天气API密钥时,天气插件会自动返回模拟天气数据,方便测试和演示。模拟数据包括: 160 | - 当前温度、天气状况、风向和湿度 161 | - 未来3天的天气预报 162 | - 使用说明提示 163 | 164 | ### 2. AI生活建议 165 | 166 | 天气服务现在会根据天气情况自动提供AI生活建议,包括: 167 | - 穿着建议 168 | - 出行建议 169 | - 健康提示 170 | 171 | 即使未安装AI聊天插件,系统也会使用内置逻辑生成基本的生活建议。 172 | 173 | ### 3. 插件兼容性增强 174 | 175 | 智能助手现在支持多种插件导出格式,提高了与第三方插件的兼容性: 176 | - 对象式导出(直接导出属性和方法) 177 | - 函数式导出(返回插件对象的函数) 178 | 179 | ## 自定义开发 180 | 181 | 您可以通过开发新插件来扩展系统功能: 182 | 183 | 1. 在 `plugins/` 目录下创建新的插件目录 184 | 2. 实现上述插件结构(对象式或函数式) 185 | 3. 在 `config.json` 的 `enabledPlugins` 数组中添加插件名称 186 | 4. 修改智能助手.js中的@rule规则 187 | 5. 重启无界BNCR框架或使用 `/plugins reload <插件名>` 命令加载插件 188 | 189 | ## 权限管理系统 190 | 191 | 当前系统实现了基本的权限管理机制,主要通过`config.json`文件中的`adminUsers`数组配置: 192 | 193 | ```json 194 | { 195 | "adminUsers": ["wxid_ao7xxx0iz3m822", "user123"] 196 | } 197 | ``` 198 | 199 | ### 管理员权限 200 | 201 | 拥有管理员权限的用户可以执行以下操作: 202 | - 插件管理(启用/禁用/重载) 203 | - 管理员用户管理(添加/删除管理员) 204 | - 修改全局配置 205 | - 执行特权命令 206 | 207 | ### 权限相关命令 208 | 209 | - `/admin list` - 查看当前管理员列表(仅管理员可用) 210 | - `/admin add <用户ID>` - 添加管理员(仅管理员可用) 211 | - `/admin remove <用户ID>` - 移除管理员(仅管理员可用) 212 | 213 | ### 权限系统实现 214 | 215 | 权限系统基于`permission-manager.js`模块实现,该模块提供了以下功能: 216 | - 从`config.json`文件加载管理员列表 217 | - 提供管理员身份验证 218 | - 管理员添加和移除 219 | - 保存权限更改到配置文件 220 | 221 | 权限检查已集成到所有需要管理员权限的命令中,未授权用户将收到权限不足的提示。 222 | 223 | ## 更新日志 224 | 225 | ### 2023年4月3日 226 | - 修复了天气配置命令不能正常工作的问题 227 | - 增强了系统对core对象的获取方式,提高了稳定性 228 | - 优化了天气插件配置保存逻辑,增加了多重备选方案 229 | - 解决了"Cannot read properties of undefined (reading 'core')"错误 230 | 231 | ### 1. 错误修复 232 | 233 | - 解决了智能助手.js中core对象undefined导致的配置保存失败问题 234 | - 增加了多路径获取core对象的逻辑,提高了系统健壮性 235 | - 修复了天气插件中saveConfig函数的调用问题 236 | 237 | ### 2. 系统增强 238 | 239 | - 优化了插件通信机制,提高了跨插件调用的稳定性 240 | - 改进了配置保存逻辑,即使在core对象不可用的情况下也能保持基本功能 241 | - 添加了更详细的错误日志,便于排查问题 242 | 243 | ## 故障排除 244 | 245 | ### 插件无法加载 246 | 247 | 1. 检查插件结构是否符合规范 248 | 2. 确认插件在 `enabledPlugins` 列表中 249 | 3. 查看日志输出,确认错误信息 250 | 4. 使用 `/plugins reload <插件名>` 尝试重新加载 251 | 252 | ### 天气插件返回错误 253 | 254 | 1. 检查API密钥是否已配置 255 | - 使用 `/天气配置 set key <您的密钥>` 设置API密钥 256 | - 无API密钥也可获取模拟数据 257 | 2. 确认城市名称是否正确 258 | 3. 验证API类型设置(amap或openweather) 259 | 260 | ### API工具箱调用失败 261 | 262 | 1. 确认API配置是否正确 263 | 2. 检查API endpoints是否可访问 264 | 3. 查看网络连接是否正常 265 | 4. 使用 `/api list` 确认API是否启用 266 | 267 | ### 权限问题 268 | 269 | 1. 使用 `/admin list` 确认管理员列表 270 | 2. 检查您的用户ID是否在管理员列表中 271 | 272 | 273 | ## 常见问题 274 | 275 | **Q: 如何添加新的管理员?** 276 | A: 有两种方法: 277 | 1. 使用命令:管理员可以执行 `/admin add wxid_xxx` 命令添加新管理员 278 | 2. 编辑配置:修改 `config.json` 文件,在 `adminUsers` 数组中添加用户ID,格式: `"adminUsers": ["wxid_ao7xxx0iz3m822"]` 279 | 280 | **Q: 插件不工作怎么办?** 281 | A: 检查以下几点: 282 | 1. 确认插件在 `enabledPlugins` 列表中 283 | 2. 查看插件配置是否正确(特别是API密钥) 284 | 3. 使用 `/plugins reload <插件名>` 重新加载插件(需管理员权限) 285 | 4. 检查无界BNCR的日志输出 286 | 287 | **Q: 如何更新插件系统?** 288 | A: 进入插件目录,执行 `git pull` 拉取最新代码,然后重启无界BNCR框架。 289 | 290 | **Q: 提示您没有管理员权限** 291 | A: 进入config.json配置adminUsers,格式: "adminUsers": ["wxid_ao7xxx0iz3m822"] 292 | 293 | **Q: 天气查询没有生活建议怎么办?** 294 | A: 有两种方法开启AI生活建议: 295 | 1. 使用命令:执行 `/天气配置 set showAIAdvice true` 开启生活建议功能 296 | 2. 编辑配置:在 `config.json` 中设置 `weather.showAIAdvice` 为 `true` 297 | 298 | **Q: 使用不同的插件导出格式有什么影响?** 299 | A: 智能助手支持两种插件格式: 300 | 1. 对象式导出:更简洁,直接导出属性和方法,推荐新插件使用 301 | 2. 函数式导出:兼容模式,适用于一些旧插件或特殊需求场景 302 | 303 | ## 免责声明 304 | 305 | 本项目仅供学习和个人使用,请遵守相关法律法规和微信使用条款。 306 | 307 | ## 许可证 308 | 309 | 本项目采用 MIT 许可证。 310 | -------------------------------------------------------------------------------- /plugin-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author shuaijin 3 | * @name plugin-loader 4 | * @team shuaijin 5 | * @origin bncr 6 | * @version 1.0.0 7 | * @description 负责加载和管理微信智能助手的插件模块 8 | * @rule none 9 | * @admin true 10 | * @public false 11 | * @disable true 12 | */ 13 | 14 | const fs = require('fs'); 15 | const path = require('path'); 16 | 17 | class PluginLoader { 18 | constructor(core) { 19 | this.core = core; 20 | this.pluginDir = path.join(__dirname, 'plugins'); 21 | this.loadedPlugins = new Map(); 22 | } 23 | 24 | // 获取所有可用插件 25 | async getAvailablePlugins() { 26 | try { 27 | // 确保插件目录存在 28 | if (!fs.existsSync(this.pluginDir)) { 29 | fs.mkdirSync(this.pluginDir, { recursive: true }); 30 | return []; 31 | } 32 | 33 | // 读取所有插件目录 34 | const pluginDirs = fs.readdirSync(this.pluginDir).filter(file => { 35 | const stats = fs.statSync(path.join(this.pluginDir, file)); 36 | return stats.isDirectory(); 37 | }); 38 | 39 | // 收集插件信息 40 | const availablePlugins = []; 41 | for (const dir of pluginDirs) { 42 | const pluginPath = path.join(this.pluginDir, dir); 43 | const indexPath = path.join(pluginPath, 'index.js'); 44 | const configPath = path.join(pluginPath, 'config.json'); 45 | 46 | // 检查插件文件是否存在 47 | if (!fs.existsSync(indexPath)) { 48 | console.warn(`插件 ${dir} 缺少 index.js 文件`); 49 | continue; 50 | } 51 | 52 | try { 53 | // 尝试加载插件信息 54 | delete require.cache[require.resolve(indexPath)]; 55 | const plugin = require(indexPath); 56 | 57 | // 读取配置 58 | let config = {}; 59 | if (fs.existsSync(configPath)) { 60 | try { 61 | const configContent = fs.readFileSync(configPath, 'utf8'); 62 | config = JSON.parse(configContent); 63 | } catch (configError) { 64 | console.error(`解析插件 ${dir} 配置文件时出错:`, configError.message); 65 | config = {}; 66 | } 67 | } 68 | 69 | // 检查插件格式是否正确 70 | if (!plugin.meta || !plugin.meta.name) { 71 | console.warn(`插件 ${dir} 格式不正确,缺少 meta.name`); 72 | continue; 73 | } 74 | 75 | availablePlugins.push({ 76 | id: dir, 77 | name: plugin.meta.name, 78 | description: plugin.meta.description || '暂无描述', 79 | version: plugin.meta.version || '0.0.1', 80 | enabled: config.enabled !== false, // 默认启用 81 | path: pluginPath, 82 | config: config 83 | }); 84 | } catch (error) { 85 | console.error(`加载插件 ${dir} 信息时出错:`, error); 86 | } 87 | } 88 | 89 | return availablePlugins; 90 | } catch (error) { 91 | console.error('获取可用插件列表出错:', error); 92 | return []; 93 | } 94 | } 95 | 96 | // 加载插件 97 | async loadPlugin(pluginId) { 98 | try { 99 | // 检查插件是否已加载 100 | if (this.loadedPlugins.has(pluginId)) { 101 | return this.loadedPlugins.get(pluginId); 102 | } 103 | 104 | const pluginPath = path.join(this.pluginDir, pluginId); 105 | const indexPath = path.join(pluginPath, 'index.js'); 106 | 107 | // 检查插件是否存在 108 | if (!fs.existsSync(indexPath)) { 109 | console.error(`插件 ${pluginId} 不存在`); 110 | return null; 111 | } 112 | 113 | // 加载插件 114 | delete require.cache[require.resolve(indexPath)]; 115 | const plugin = require(indexPath); 116 | 117 | // 检查插件结构 118 | if (!plugin.meta || !plugin.initialize) { 119 | console.error(`插件 ${pluginId} 格式不正确,缺少必要的组件`); 120 | return null; 121 | } 122 | 123 | // 读取配置 124 | const configPath = path.join(pluginPath, 'config.json'); 125 | let config = {}; 126 | if (fs.existsSync(configPath)) { 127 | try { 128 | const configContent = fs.readFileSync(configPath, 'utf8'); 129 | config = JSON.parse(configContent); 130 | } catch (configError) { 131 | console.error(`解析插件 ${pluginId} 配置文件时出错:`, configError.message); 132 | config = {}; 133 | } 134 | } 135 | 136 | // 初始化插件 137 | try { 138 | await plugin.initialize(this.core, config); 139 | console.log(`插件 ${plugin.meta.name} (${pluginId}) v${plugin.meta.version} 加载成功`); 140 | 141 | // 存储已加载的插件 142 | this.loadedPlugins.set(pluginId, { 143 | id: pluginId, 144 | instance: plugin, 145 | config: config, 146 | meta: plugin.meta 147 | }); 148 | 149 | return this.loadedPlugins.get(pluginId); 150 | } catch (initError) { 151 | console.error(`初始化插件 ${pluginId} 失败:`, initError); 152 | return null; 153 | } 154 | } catch (error) { 155 | console.error(`加载插件 ${pluginId} 时出错:`, error); 156 | return null; 157 | } 158 | } 159 | 160 | // 卸载插件 161 | async unloadPlugin(pluginId) { 162 | // 检查插件是否已加载 163 | if (!this.loadedPlugins.has(pluginId)) { 164 | console.warn(`插件 ${pluginId} 未加载,无需卸载`); 165 | return false; 166 | } 167 | 168 | const plugin = this.loadedPlugins.get(pluginId); 169 | 170 | // 调用插件的卸载方法(如果有) 171 | try { 172 | if (typeof plugin.instance.unload === 'function') { 173 | await plugin.instance.unload(); 174 | } 175 | 176 | // 从加载列表中移除 177 | this.loadedPlugins.delete(pluginId); 178 | 179 | // 清除缓存 180 | const indexPath = path.join(this.pluginDir, pluginId, 'index.js'); 181 | delete require.cache[require.resolve(indexPath)]; 182 | 183 | console.log(`插件 ${plugin.meta.name} (${pluginId}) 已卸载`); 184 | return true; 185 | } catch (error) { 186 | console.error(`卸载插件 ${pluginId} 时出错:`, error); 187 | return false; 188 | } 189 | } 190 | 191 | // 重新加载插件 192 | async reloadPlugin(pluginId) { 193 | // 先卸载插件 194 | await this.unloadPlugin(pluginId); 195 | 196 | // 再加载插件 197 | return await this.loadPlugin(pluginId); 198 | } 199 | 200 | // 保存插件配置 201 | async savePluginConfig(pluginId, config) { 202 | try { 203 | const configPath = path.join(this.pluginDir, pluginId, 'config.json'); 204 | const configContent = JSON.stringify(config, null, 2); 205 | 206 | fs.writeFileSync(configPath, configContent, 'utf8'); 207 | 208 | // 如果插件已加载,更新其配置 209 | if (this.loadedPlugins.has(pluginId)) { 210 | const plugin = this.loadedPlugins.get(pluginId); 211 | plugin.config = config; 212 | 213 | // 如果插件有配置更新方法,调用它 214 | if (typeof plugin.instance.onConfigUpdate === 'function') { 215 | await plugin.instance.onConfigUpdate(config); 216 | } 217 | } 218 | 219 | console.log(`插件 ${pluginId} 配置已保存`); 220 | return true; 221 | } catch (error) { 222 | console.error(`保存插件 ${pluginId} 配置时出错:`, error); 223 | return false; 224 | } 225 | } 226 | 227 | // 加载所有启用的插件 228 | async loadEnabledPlugins() { 229 | try { 230 | const availablePlugins = await this.getAvailablePlugins(); 231 | 232 | for (const plugin of availablePlugins) { 233 | if (plugin.enabled) { 234 | await this.loadPlugin(plugin.id); 235 | } 236 | } 237 | 238 | console.log(`已加载 ${this.loadedPlugins.size} 个启用的插件`); 239 | return true; 240 | } catch (error) { 241 | console.error('加载启用的插件时出错:', error); 242 | return false; 243 | } 244 | } 245 | } 246 | 247 | module.exports = PluginLoader; -------------------------------------------------------------------------------- /plugins/morning-alert/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 早间提醒插件 3 | * @version 1.0.0 4 | * @description 提供每日早间提醒服务,包含天气和AI生活建议 5 | * @author shuaijin 6 | */ 7 | 8 | // 导入模块 9 | const fs = require('fs'); 10 | const path = require('path'); 11 | const schedule = require('node-schedule'); 12 | 13 | // 创建订阅用户存储 14 | const subscribedUsers = new Map(); 15 | 16 | // 插件元数据 17 | exports.meta = { 18 | name: "早间提醒", 19 | version: "1.0.0", 20 | description: "提供每日早间提醒服务,包含天气和AI生活建议", 21 | author: "shuaijin" 22 | }; 23 | 24 | // 插件默认配置 25 | exports.defaultConfig = { 26 | enabled: true, 27 | time: "07:00", // 默认早上7点发送提醒 28 | defaultCity: "北京", // 默认城市 29 | templates: { 30 | morning: "早上好,今天是 {{date}},{{holiday}}。\n\n今日天气:\n{{weather}}\n\n{{forecast}}\n\nAI生活建议:\n{{advice}}" 31 | } 32 | }; 33 | 34 | // 定时任务 35 | let morningJob = null; 36 | 37 | // 加载订阅用户 38 | function loadSubscribedUsers() { 39 | try { 40 | const dataPath = path.join(__dirname, 'subscribers.json'); 41 | if (fs.existsSync(dataPath)) { 42 | const data = JSON.parse(fs.readFileSync(dataPath, 'utf8')); 43 | 44 | // 转换数据为Map 45 | for (const [userId, userData] of Object.entries(data)) { 46 | subscribedUsers.set(userId, userData); 47 | } 48 | 49 | console.log(`[早间提醒] 已加载 ${subscribedUsers.size} 个订阅用户`); 50 | } 51 | } catch (error) { 52 | console.error('[早间提醒] 加载订阅用户数据失败:', error); 53 | } 54 | } 55 | 56 | // 保存订阅用户 57 | function saveSubscribedUsers() { 58 | try { 59 | const dataPath = path.join(__dirname, 'subscribers.json'); 60 | const data = Object.fromEntries(subscribedUsers); 61 | fs.writeFileSync(dataPath, JSON.stringify(data, null, 2), 'utf8'); 62 | } catch (error) { 63 | console.error('[早间提醒] 保存订阅用户数据失败:', error); 64 | } 65 | } 66 | 67 | // 获取天气数据 68 | async function getWeatherData(city, core) { 69 | try { 70 | // 获取天气插件实例 71 | const weatherPlugin = core.plugins.get('weather')?.instance; 72 | if (!weatherPlugin) { 73 | throw new Error('天气插件未加载或不可用'); 74 | } 75 | 76 | // 调用天气插件的方法获取数据 77 | const config = weatherPlugin.config; 78 | const { getWeather, getWeatherForecast, formatWeatherInfo, formatForecastInfo } = weatherPlugin; 79 | 80 | // 获取当天天气和预报 81 | const weatherData = await getWeather(city, config); 82 | const forecastData = await getWeatherForecast(city, config); 83 | 84 | return { 85 | weather: weatherData, 86 | forecast: forecastData 87 | }; 88 | } catch (error) { 89 | console.error(`[早间提醒] 获取天气数据失败:`, error); 90 | throw error; 91 | } 92 | } 93 | 94 | // 获取AI生活建议 95 | async function getAIAdvice(weatherData, forecastData, core) { 96 | try { 97 | // 获取AI聊天插件实例 98 | const aiChatPlugin = core.plugins.get('ai-chat')?.instance; 99 | if (!aiChatPlugin) { 100 | throw new Error('AI聊天插件未加载或不可用'); 101 | } 102 | 103 | // 构建提示信息 104 | const today = forecastData.forecasts[0]; 105 | const prompt = `根据今天的天气情况:${weatherData.city},天气${weatherData.weather},温度${weatherData.temperature}°C,${weatherData.windDirection}风${weatherData.windPower}级,湿度${weatherData.humidity}%。 106 | 预计白天${today.dayWeather},${today.dayTemp}°C,晚上${today.nightWeather},${today.nightTemp}°C。 107 | 请针对这种天气,给出今日穿着、出行和健康建议,简明扼要,不超过150字。`; 108 | 109 | // 使用AI生成建议 110 | const userId = 'system_morning_alert'; // 系统用户ID 111 | const advice = await aiChatPlugin.chatWithAI(prompt, userId, null, aiChatPlugin.config); 112 | 113 | return advice; 114 | } catch (error) { 115 | console.error(`[早间提醒] 获取AI生活建议失败:`, error); 116 | return "抱歉,无法获取今日生活建议。"; 117 | } 118 | } 119 | 120 | // 格式化提醒消息 121 | function formatMorningMessage(weatherData, forecastData, advice, template) { 122 | const today = new Date(); 123 | const dateStr = today.toLocaleDateString('zh-CN', { 124 | year: 'numeric', 125 | month: 'long', 126 | day: 'numeric', 127 | weekday: 'long' 128 | }); 129 | 130 | // 节假日信息(示例,实际可以接入节假日API) 131 | const holiday = ""; 132 | 133 | // 当天天气 134 | const weather = `${weatherData.city},${weatherData.weather},${weatherData.temperature}°C 135 | 风向:${weatherData.windDirection},风力:${weatherData.windPower}级 136 | 湿度:${weatherData.humidity}%`; 137 | 138 | // 未来天气预报(仅显示今明两天) 139 | let forecastStr = "未来天气预报:\n"; 140 | for (let i = 0; i < Math.min(2, forecastData.forecasts.length); i++) { 141 | const f = forecastData.forecasts[i]; 142 | const day = i === 0 ? "今天" : "明天"; 143 | forecastStr += `${day}:白天 ${f.dayWeather} ${f.dayTemp}°C,夜间 ${f.nightWeather} ${f.nightTemp}°C\n`; 144 | } 145 | 146 | // 替换模板 147 | return template 148 | .replace('{{date}}', dateStr) 149 | .replace('{{holiday}}', holiday) 150 | .replace('{{weather}}', weather) 151 | .replace('{{forecast}}', forecastStr) 152 | .replace('{{advice}}', advice); 153 | } 154 | 155 | // 发送早间提醒 156 | async function sendMorningAlert(core) { 157 | console.log('[早间提醒] 开始发送早间提醒...'); 158 | 159 | // 检查是否有订阅用户 160 | if (subscribedUsers.size === 0) { 161 | console.log('[早间提醒] 没有用户订阅,跳过发送'); 162 | return; 163 | } 164 | 165 | try { 166 | // 获取默认城市的天气数据 167 | const city = exports.config.defaultCity; 168 | const weatherResult = await getWeatherData(city, core); 169 | 170 | // 获取AI生活建议 171 | const advice = await getAIAdvice(weatherResult.weather, weatherResult.forecast, core); 172 | 173 | // 发送给每个订阅用户 174 | for (const [userId, userData] of subscribedUsers.entries()) { 175 | try { 176 | // 获取用户配置的城市,如果没有则使用默认城市 177 | const userCity = userData.city || city; 178 | 179 | // 如果用户城市与默认城市不同,获取用户城市的天气 180 | let userWeatherResult = weatherResult; 181 | if (userCity !== city) { 182 | userWeatherResult = await getWeatherData(userCity, core); 183 | } 184 | 185 | // 获取模板 186 | const template = exports.config.templates.morning; 187 | 188 | // 格式化消息 189 | const message = formatMorningMessage( 190 | userWeatherResult.weather, 191 | userWeatherResult.forecast, 192 | advice, 193 | template 194 | ); 195 | 196 | // 发送消息 197 | await core.sendMsg(userId, message); 198 | console.log(`[早间提醒] 已向用户 ${userId} 发送早间提醒`); 199 | } catch (error) { 200 | console.error(`[早间提醒] 向用户 ${userId} 发送提醒失败:`, error); 201 | } 202 | } 203 | 204 | console.log('[早间提醒] 早间提醒发送完成'); 205 | } catch (error) { 206 | console.error('[早间提醒] 发送早间提醒失败:', error); 207 | } 208 | } 209 | 210 | // 初始化定时任务 211 | function initScheduleJob(core) { 212 | // 取消之前的任务 213 | if (morningJob) { 214 | morningJob.cancel(); 215 | } 216 | 217 | // 设置新任务 218 | const time = exports.config.time.split(':'); 219 | const hour = parseInt(time[0]); 220 | const minute = parseInt(time[1]); 221 | 222 | morningJob = schedule.scheduleJob(`${minute} ${hour} * * *`, function() { 223 | sendMorningAlert(core); 224 | }); 225 | 226 | console.log(`[早间提醒] 定时任务已设置,将在每天 ${exports.config.time} 发送提醒`); 227 | } 228 | 229 | // 插件初始化方法 230 | exports.initialize = async function(core, pluginConfig) { 231 | // 存储core引用和配置 232 | this.core = core; 233 | 234 | // 使用传入的pluginConfig,因为它现在应来自Schema 235 | exports.config = pluginConfig || this.defaultConfig; 236 | 237 | // 验证配置的重要字段 238 | if (!exports.config.time) { 239 | console.warn('[早间提醒] 警告: 提醒时间未配置,使用默认值"07:00"'); 240 | exports.config.time = "07:00"; 241 | } 242 | 243 | if (!exports.config.templates || !exports.config.templates.morning) { 244 | console.warn('[早间提醒] 警告: 提醒模板未配置,使用默认模板'); 245 | exports.config.templates = exports.config.templates || {}; 246 | exports.config.templates.morning = this.defaultConfig.templates.morning; 247 | } 248 | 249 | // 加载订阅用户 250 | loadSubscribedUsers(); 251 | 252 | // 初始化定时任务 253 | initScheduleJob(core); 254 | 255 | console.log(`[早间提醒] 插件已初始化,将在每天 ${exports.config.time} 发送提醒`); 256 | return true; 257 | }; 258 | 259 | // 插件命令列表 260 | exports.commands = [ 261 | { 262 | name: "subscribe", 263 | pattern: /^\/subscribe$/, 264 | description: "订阅每日早间提醒服务", 265 | handler: async function(sender, match) { 266 | const userId = sender.getUserId(); 267 | 268 | // 检查是否已订阅 269 | if (subscribedUsers.has(userId)) { 270 | await sender.reply("✅ 您已订阅早间提醒服务"); 271 | return; 272 | } 273 | 274 | // 添加订阅 275 | subscribedUsers.set(userId, { 276 | subscribeTime: new Date().toISOString(), 277 | city: null // 默认使用全局配置的城市 278 | }); 279 | 280 | // 保存订阅数据 281 | saveSubscribedUsers(); 282 | 283 | await sender.reply("✅ 您已成功订阅早间提醒服务,将在每天早上收到天气和生活建议"); 284 | } 285 | }, 286 | { 287 | name: "unsubscribe", 288 | pattern: /^\/unsubscribe$/, 289 | description: "取消订阅每日早间提醒服务", 290 | handler: async function(sender, match) { 291 | const userId = sender.getUserId(); 292 | 293 | // 检查是否已订阅 294 | if (!subscribedUsers.has(userId)) { 295 | await sender.reply("❌ 您尚未订阅早间提醒服务"); 296 | return; 297 | } 298 | 299 | // 取消订阅 300 | subscribedUsers.delete(userId); 301 | 302 | // 保存订阅数据 303 | saveSubscribedUsers(); 304 | 305 | await sender.reply("✅ 您已取消订阅早间提醒服务"); 306 | } 307 | } 308 | ]; 309 | 310 | // 插件卸载方法 311 | exports.unload = async function() { 312 | // 取消定时任务 313 | if (morningJob) { 314 | morningJob.cancel(); 315 | morningJob = null; 316 | } 317 | 318 | console.log('[早间提醒] 插件已卸载'); 319 | return true; 320 | }; 321 | 322 | // 保存插件配置 323 | async function saveConfig(plugin) { 324 | try { 325 | // 使用BNCR事件系统更新配置 326 | if (plugin.core) { 327 | await plugin.core.emit('config_updated', { 328 | pluginName: 'morning-alert', 329 | config: exports.config 330 | }); 331 | console.log('[早间提醒] 已通过事件系统更新配置'); 332 | return true; 333 | } 334 | 335 | // 如果没有core引用,返回失败 336 | console.warn('[早间提醒] 未找到core引用,无法保存配置'); 337 | return false; 338 | } catch (error) { 339 | console.error('[早间提醒] 保存配置失败:', error); 340 | return false; 341 | } 342 | } -------------------------------------------------------------------------------- /plugins/weather/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 天气服务插件 3 | * @version 1.0.0 4 | * @description 提供天气查询功能 5 | * @author shuaijin 6 | */ 7 | 8 | // 导入模块 9 | const axios = require('axios'); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | 13 | // 插件元数据 14 | exports.meta = { 15 | name: "天气服务", 16 | version: "1.0.0", 17 | description: "提供天气查询功能,支持实时天气和天气预报", 18 | author: "shuaijin" 19 | }; 20 | 21 | // 添加标记,表明这是正确支持的导出格式 22 | exports.exportFormat = "object"; 23 | 24 | // 插件默认配置 25 | exports.defaultConfig = { 26 | enabled: true, 27 | api: 'amap', 28 | key: '', 29 | defaultCity: '北京', 30 | showAIAdvice: false 31 | }; 32 | 33 | // 初始化config属性,以便在智能助手中直接使用 34 | exports.config = exports.defaultConfig; 35 | 36 | // 插件初始化方法 37 | exports.initialize = async function(core, pluginConfig) { 38 | // 存储core引用和配置 39 | this.core = core; 40 | 41 | // 合并配置 - 使用传入的pluginConfig,因为它现在应来自Schema 42 | this.config = pluginConfig || this.defaultConfig; 43 | 44 | // 验证配置 45 | if (!this.config.key) { 46 | console.warn('[天气服务] 警告: API密钥未配置,天气服务可能无法正常工作'); 47 | } 48 | 49 | if (!this.config.api) { 50 | console.warn('[天气服务] 警告: API类型未配置,使用默认值"amap"'); 51 | this.config.api = 'amap'; 52 | } 53 | 54 | console.log(`[天气服务] 插件已初始化,API: ${this.config.api}, 默认城市: ${this.config.defaultCity || '未设置'}`); 55 | return true; 56 | }; 57 | 58 | // 保存配置 59 | async function saveConfig(config) { 60 | try { 61 | // 使用BNCR事件系统更新配置 62 | if (config.core) { 63 | await config.core.emit('config_updated', { 64 | pluginName: 'weather', 65 | config: config 66 | }); 67 | console.log('[天气服务] 已通过事件系统更新配置'); 68 | return true; 69 | } 70 | 71 | // 如果没有core引用,返回失败 72 | console.warn('[天气服务] 未找到core引用,无法保存配置'); 73 | return false; 74 | } catch (error) { 75 | console.error('[天气服务] 保存配置失败:', error); 76 | return false; 77 | } 78 | } 79 | 80 | // 处理命令 81 | exports.onMessage = async function(message) { 82 | // 简单的命令解析,你可以使用更复杂的解析方式 83 | const content = message.content.trim(); 84 | const [command, ...args] = content.split(/\s+/); 85 | 86 | // 处理天气查询指令 87 | if (command === '/天气' || command === '/weather') { 88 | // 获取城市参数,如果没有提供则使用默认城市 89 | let city = args.join(' ').trim(); 90 | if (!city) { 91 | city = this.config.defaultCity; 92 | if (!city) { 93 | return '请指定城市名称,例如: /天气 北京'; 94 | } 95 | } 96 | 97 | try { 98 | const weatherData = await this.getWeather(city); 99 | return weatherData; 100 | } catch (error) { 101 | console.error('[天气服务] 获取天气失败:', error); 102 | return `获取天气信息失败: ${error.message || '未知错误'}`; 103 | } 104 | } 105 | 106 | // 处理配置命令 107 | if (command === '/天气配置' || command === '/weather_config') { 108 | const subCommand = args[0]; 109 | 110 | // 显示当前配置 111 | if (!subCommand || subCommand === 'show') { 112 | const configInfo = `当前天气服务配置: 113 | - API类型: ${this.config.api || '未设置'} 114 | - 默认城市: ${this.config.defaultCity || '未设置'} 115 | - API密钥: ${this.config.key ? '已设置' : '未设置'} 116 | - 显示AI建议: ${this.config.showAIAdvice ? '是' : '否'}`; 117 | return configInfo; 118 | } 119 | 120 | // 设置配置项 121 | if (subCommand === 'set') { 122 | const key = args[1]; 123 | const value = args.slice(2).join(' '); 124 | 125 | if (!key || !value) { 126 | return '用法: /天气配置 set [选项] [值]\n可用选项: api, defaultCity, key, showAIAdvice'; 127 | } 128 | 129 | // 更新配置 130 | if (['api', 'defaultCity', 'key'].includes(key)) { 131 | this.config[key] = value; 132 | const saved = await saveConfig(this); 133 | if (saved) { 134 | return `已更新配置: ${key} = ${key === 'key' ? '******' : value}`; 135 | } else { 136 | return `配置已更新,但保存失败: ${key} = ${key === 'key' ? '******' : value}`; 137 | } 138 | } else if (key === 'showAIAdvice') { 139 | this.config[key] = value.toLowerCase() === 'true'; 140 | const saved = await saveConfig(this); 141 | if (saved) { 142 | return `已更新配置: ${key} = ${this.config[key]}`; 143 | } else { 144 | return `配置已更新,但保存失败: ${key} = ${this.config[key]}`; 145 | } 146 | } else { 147 | return `无效的配置选项: ${key}\n可用选项: api, defaultCity, key, showAIAdvice`; 148 | } 149 | } 150 | 151 | return '未知的天气配置命令,可用命令: show, set'; 152 | } 153 | 154 | // 如果不是本插件处理的命令,返回null表示不处理 155 | return null; 156 | }; 157 | 158 | // 获取实时天气 159 | exports.getWeather = async function(city) { 160 | if (!this.config.key) { 161 | // 使用测试模式,返回模拟数据 162 | console.log('[天气服务] API密钥未配置,使用模拟数据'); 163 | return this.getMockWeather(city); 164 | } 165 | 166 | try { 167 | let weather; 168 | if (this.config.api === 'amap') { 169 | weather = await this.getAmapWeather(city); 170 | } else if (this.config.api === 'openweather') { 171 | weather = await this.getOpenWeatherData(city); 172 | } else { 173 | throw new Error(`不支持的API类型: ${this.config.api}`); 174 | } 175 | return weather; 176 | } catch (error) { 177 | console.error(`[天气服务] 获取天气失败: ${error.message}`); 178 | throw error; 179 | } 180 | }; 181 | 182 | // 获取模拟天气数据 183 | exports.getMockWeather = function(city) { 184 | const now = new Date(); 185 | const temp = Math.floor(Math.random() * 10) + 15; // 15-25度之间 186 | const conditions = ['晴朗', '多云', '阴天', '小雨', '晴间多云'][Math.floor(Math.random() * 5)]; 187 | const wind = ['东北', '东南', '西北', '西南', '北', '南', '东', '西'][Math.floor(Math.random() * 8)]; 188 | const humidity = Math.floor(Math.random() * 30) + 40; // 40-70%之间 189 | 190 | // 生成未来几天的日期 191 | const days = []; 192 | for (let i = 0; i < 3; i++) { 193 | const day = new Date(now); 194 | day.setDate(now.getDate() + i); 195 | days.push(day); 196 | } 197 | 198 | let weatherInfo = `📍 ${city} 天气信息 (模拟数据)\n`; 199 | weatherInfo += `🌡️ 当前温度: ${temp}°C\n`; 200 | weatherInfo += `🌤️ 天气: ${conditions}\n`; 201 | weatherInfo += `💨 风向: ${wind}风 ${Math.floor(Math.random() * 5) + 1}级\n`; 202 | weatherInfo += `💧 湿度: ${humidity}%\n\n`; 203 | 204 | weatherInfo += `🔮 未来天气预报 (模拟数据):\n`; 205 | 206 | // 添加未来几天的天气预报 207 | for (let i = 0; i < 3; i++) { 208 | const date = days[i]; 209 | const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()]; 210 | const formattedDate = `${date.getMonth() + 1}月${date.getDate()}日`; 211 | const dayTemp = Math.floor(Math.random() * 10) + 20; // 20-30度之间 212 | const nightTemp = dayTemp - Math.floor(Math.random() * 8) - 5; // 白天温度减5-13度 213 | const weather = ['晴朗', '多云', '阴天', '小雨', '晴间多云'][Math.floor(Math.random() * 5)]; 214 | 215 | weatherInfo += `${formattedDate} ${dayOfWeek}: ${weather} ${dayTemp}°C ~ ${nightTemp}°C\n`; 216 | } 217 | 218 | weatherInfo += `\n⚠️ 注意: 这是模拟数据,API密钥未配置。请使用 /weconfig set weather.key 您的密钥 配置API密钥获取真实天气。\n`; 219 | 220 | return weatherInfo; 221 | }; 222 | 223 | // 获取高德地图天气数据 224 | exports.getAmapWeather = async function(city) { 225 | try { 226 | // 请求高德地图API获取城市编码 227 | const cityUrl = `https://restapi.amap.com/v3/geocode/geo?address=${encodeURIComponent(city)}&key=${this.config.key}`; 228 | const cityResponse = await axios.get(cityUrl); 229 | const cityData = cityResponse.data; 230 | 231 | if (cityData.status !== '1' || !cityData.geocodes || cityData.geocodes.length === 0) { 232 | throw new Error(`找不到城市: ${city}`); 233 | } 234 | 235 | const adcode = cityData.geocodes[0].adcode; 236 | const formattedAddress = cityData.geocodes[0].formatted_address; 237 | 238 | // 请求高德地图天气API 239 | const weatherUrl = `https://restapi.amap.com/v3/weather/weatherInfo?city=${adcode}&key=${this.config.key}&extensions=all`; 240 | const weatherResponse = await axios.get(weatherUrl); 241 | const weatherData = weatherResponse.data; 242 | 243 | if (weatherData.status !== '1' || !weatherData.forecasts || weatherData.forecasts.length === 0) { 244 | throw new Error(`获取天气信息失败: ${weatherData.info || '未知错误'}`); 245 | } 246 | 247 | // 获取实时天气数据 248 | const liveUrl = `https://restapi.amap.com/v3/weather/weatherInfo?city=${adcode}&key=${this.config.key}&extensions=base`; 249 | const liveResponse = await axios.get(liveUrl); 250 | const liveData = liveResponse.data; 251 | 252 | if (liveData.status !== '1' || !liveData.lives || liveData.lives.length === 0) { 253 | throw new Error(`获取实时天气信息失败: ${liveData.info || '未知错误'}`); 254 | } 255 | 256 | // 格式化天气信息 257 | const forecast = weatherData.forecasts[0]; 258 | const live = liveData.lives[0]; 259 | 260 | let weatherInfo = `📍 ${formattedAddress} 天气信息\n`; 261 | weatherInfo += `🌡️ 当前温度: ${live.temperature}°C\n`; 262 | weatherInfo += `🌤️ 天气: ${live.weather}\n`; 263 | weatherInfo += `💨 风向: ${live.winddirection}风 ${live.windpower}级\n`; 264 | weatherInfo += `💧 湿度: ${live.humidity}%\n\n`; 265 | 266 | weatherInfo += `🔮 未来天气预报:\n`; 267 | 268 | // 添加未来几天的天气预报 269 | for (let i = 0; i < Math.min(forecast.casts.length, 3); i++) { 270 | const day = forecast.casts[i]; 271 | const date = new Date(day.date); 272 | const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()]; 273 | const formattedDate = `${date.getMonth() + 1}月${date.getDate()}日`; 274 | 275 | weatherInfo += `${formattedDate} ${dayOfWeek}: ${day.dayweather} ${day.daytemp}°C ~ ${day.nighttemp}°C\n`; 276 | } 277 | 278 | // 如果启用了AI建议,获取并添加 279 | if (this.config.showAIAdvice) { 280 | try { 281 | const aiAdvice = await this.getAIAdvice(live, forecast); 282 | if (aiAdvice) { 283 | weatherInfo += `\n🤖 AI建议: ${aiAdvice}\n`; 284 | } 285 | } catch (error) { 286 | console.error('[天气服务] 获取AI建议失败:', error); 287 | } 288 | } 289 | 290 | return weatherInfo; 291 | } catch (error) { 292 | console.error('[天气服务] 高德地图API调用失败:', error); 293 | throw error; 294 | } 295 | }; 296 | 297 | // 获取OpenWeather数据 298 | exports.getOpenWeatherData = async function(city) { 299 | try { 300 | const url = `https://api.openweathermap.org/data/2.5/forecast?q=${encodeURIComponent(city)}&appid=${this.config.key}&units=metric&lang=zh_cn`; 301 | const response = await axios.get(url); 302 | const data = response.data; 303 | 304 | if (data.cod !== '200') { 305 | throw new Error(data.message || '未知错误'); 306 | } 307 | 308 | // 获取当前天气 309 | const currentUrl = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&appid=${this.config.key}&units=metric&lang=zh_cn`; 310 | const currentResponse = await axios.get(currentUrl); 311 | const currentData = currentResponse.data; 312 | 313 | if (currentData.cod !== 200) { 314 | throw new Error(currentData.message || '未知错误'); 315 | } 316 | 317 | // 格式化天气信息 318 | let weatherInfo = `📍 ${data.city.name}, ${data.city.country} 天气信息\n`; 319 | weatherInfo += `🌡️ 当前温度: ${currentData.main.temp.toFixed(1)}°C (体感温度: ${currentData.main.feels_like.toFixed(1)}°C)\n`; 320 | weatherInfo += `🌤️ 天气: ${currentData.weather[0].description}\n`; 321 | weatherInfo += `💨 风速: ${currentData.wind.speed} m/s\n`; 322 | weatherInfo += `💧 湿度: ${currentData.main.humidity}%\n`; 323 | weatherInfo += `☁️ 云量: ${currentData.clouds.all}%\n\n`; 324 | 325 | weatherInfo += `🔮 未来天气预报:\n`; 326 | 327 | // 获取未来几天不同时间的天气预报 328 | const forecastsByDay = {}; 329 | 330 | data.list.forEach(item => { 331 | const date = new Date(item.dt * 1000); 332 | const day = date.toISOString().split('T')[0]; 333 | 334 | if (!forecastsByDay[day]) { 335 | forecastsByDay[day] = []; 336 | } 337 | 338 | forecastsByDay[day].push(item); 339 | }); 340 | 341 | // 取每天的中午左右时间的预报作为当天的代表 342 | Object.keys(forecastsByDay).slice(0, 3).forEach(day => { 343 | const forecasts = forecastsByDay[day]; 344 | // 尝试找中午12点左右的预报 345 | let midday = forecasts.reduce((prev, curr) => { 346 | const date = new Date(curr.dt * 1000); 347 | const hours = date.getHours(); 348 | const prevHours = new Date(prev.dt * 1000).getHours(); 349 | 350 | return Math.abs(hours - 12) < Math.abs(prevHours - 12) ? curr : prev; 351 | }, forecasts[0]); 352 | 353 | const date = new Date(midday.dt * 1000); 354 | const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()]; 355 | const formattedDate = `${date.getMonth() + 1}月${date.getDate()}日`; 356 | 357 | weatherInfo += `${formattedDate} ${dayOfWeek}: ${midday.weather[0].description} ${midday.main.temp_min.toFixed(1)}°C ~ ${midday.main.temp_max.toFixed(1)}°C\n`; 358 | }); 359 | 360 | // 如果启用了AI建议,获取并添加 361 | if (this.config.showAIAdvice) { 362 | try { 363 | // 构造一个类似高德API返回的数据结构供AI建议使用 364 | const liveData = { 365 | weather: currentData.weather[0].description, 366 | temperature: currentData.main.temp.toFixed(1), 367 | windpower: (currentData.wind.speed * 2).toFixed(0), // 粗略转换为风力等级 368 | humidity: currentData.main.humidity, 369 | city: data.city.name 370 | }; 371 | 372 | const forecastData = { 373 | casts: [{ 374 | dayweather: data.list[0].weather[0].description, 375 | daytemp: data.list[0].main.temp_max.toFixed(1), 376 | nighttemp: data.list[0].main.temp_min.toFixed(1) 377 | }] 378 | }; 379 | 380 | const aiAdvice = await this.getAIAdvice(liveData, forecastData); 381 | if (aiAdvice) { 382 | weatherInfo += `\n🤖 AI建议: ${aiAdvice}\n`; 383 | } 384 | } catch (error) { 385 | console.error('[天气服务] 获取AI建议失败:', error); 386 | } 387 | } 388 | 389 | return weatherInfo; 390 | } catch (error) { 391 | console.error('[天气服务] OpenWeather API调用失败:', error); 392 | throw error; 393 | } 394 | }; 395 | 396 | // 获取AI生活建议 397 | exports.getAIAdvice = async function(weatherData, forecastData) { 398 | try { 399 | // 获取AI聊天插件实例 400 | const aiChatPlugin = this.core?.plugins?.get('ai-chat')?.instance; 401 | if (!aiChatPlugin) { 402 | console.log('[天气服务] AI聊天插件未加载或不可用,使用本地生成建议'); 403 | // 如果AI聊天插件不可用,提供一个基于天气状况的简单建议 404 | return generateLocalAdvice(weatherData, forecastData); 405 | } 406 | 407 | // 构建提示信息 408 | const today = forecastData.casts[0]; 409 | const prompt = `根据今天的天气情况:${weatherData.city},天气${weatherData.weather},温度${weatherData.temperature}°C,湿度${weatherData.humidity}%。 410 | 预计白天${today.dayweather},最高温度${today.daytemp}°C,最低温度${today.nighttemp}°C。 411 | 请针对这种天气,给出今日穿着、出行和健康建议,简明扼要,不超过100字。`; 412 | 413 | // 使用AI生成建议 414 | const userId = 'system_weather_service'; // 系统用户ID 415 | const advice = await aiChatPlugin.chatWithAI(prompt, userId, null, aiChatPlugin.config); 416 | 417 | return advice; 418 | } catch (error) { 419 | console.error(`[天气服务] 获取AI生活建议失败:`, error); 420 | // 出错时也使用本地生成的建议 421 | return generateLocalAdvice(weatherData, forecastData); 422 | } 423 | }; 424 | 425 | // 本地生成天气建议的函数 426 | function generateLocalAdvice(weatherData, forecastData) { 427 | try { 428 | const weather = weatherData.weather?.toLowerCase() || ''; 429 | const temp = parseFloat(weatherData.temperature) || 0; 430 | 431 | let clothingAdvice = ''; 432 | let travelAdvice = ''; 433 | let healthAdvice = ''; 434 | 435 | // 根据温度给出穿衣建议 436 | if (temp >= 30) { 437 | clothingAdvice = '穿轻薄透气的衣物,注意防晒'; 438 | } else if (temp >= 25) { 439 | clothingAdvice = '穿短袖短裤等夏季服装'; 440 | } else if (temp >= 20) { 441 | clothingAdvice = '穿薄长袖或短袖'; 442 | } else if (temp >= 15) { 443 | clothingAdvice = '穿长袖衬衫或薄外套'; 444 | } else if (temp >= 10) { 445 | clothingAdvice = '穿外套或薄毛衣'; 446 | } else if (temp >= 5) { 447 | clothingAdvice = '穿保暖外套、毛衣、围巾'; 448 | } else { 449 | clothingAdvice = '穿厚外套、保暖内衣、手套、围巾等保暖装备'; 450 | } 451 | 452 | // 根据天气状况给出出行建议 453 | if (weather.includes('雨')) { 454 | travelAdvice = '记得带伞,最好穿防水鞋'; 455 | } else if (weather.includes('雪')) { 456 | travelAdvice = '注意道路结冰,穿防滑鞋'; 457 | } else if (weather.includes('雾') || weather.includes('霾')) { 458 | travelAdvice = '外出佩戴口罩,开车注意安全'; 459 | } else if (weather.includes('晴') && temp > 28) { 460 | travelAdvice = '外出做好防晒措施,多补充水分'; 461 | } else if (weather.includes('多云')) { 462 | travelAdvice = '天气较为舒适,适宜外出活动'; 463 | } else { 464 | travelAdvice = '注意随时关注天气变化'; 465 | } 466 | 467 | // 健康建议 468 | if (temp > 30) { 469 | healthAdvice = '避免长时间户外活动,多喝水,防止中暑'; 470 | } else if (temp < 5) { 471 | healthAdvice = '注意保暖,预防感冒'; 472 | } else if (weather.includes('雨') || weather.includes('雪')) { 473 | healthAdvice = '保持室内空气流通,防止湿气'; 474 | } else { 475 | healthAdvice = '适当户外运动,增强身体抵抗力'; 476 | } 477 | 478 | return `${clothingAdvice};${travelAdvice};${healthAdvice}。`; 479 | } catch (e) { 480 | console.error('[天气服务] 生成本地建议失败:', e); 481 | return '今日穿着适当,出行注意安全,保持良好心情。'; 482 | } 483 | } 484 | 485 | // 方便其他插件调用的兼容API 486 | exports.getWeatherForecast = async function(city) { 487 | // 转发到新的API实现 488 | return this.getWeather(city); 489 | }; 490 | 491 | // 插件卸载方法 492 | exports.unload = async function() { 493 | console.log('[天气服务] 插件已卸载'); 494 | return true; 495 | }; 496 | 497 | // 处理天气命令 498 | exports.handleWeatherCommand = async function(city, sender) { 499 | if (!city && this.config.defaultCity) { 500 | city = this.config.defaultCity; 501 | } 502 | 503 | if (!city) { 504 | throw new Error('请指定城市名称,例如: /weather 北京'); 505 | } 506 | 507 | try { 508 | console.log(`[天气服务] 处理天气查询命令,城市: ${city}`); 509 | 510 | // 检查配置是否存在,如果不存在则尝试从sender获取配置 511 | if (!this.config.key) { 512 | console.log('[天气服务] API密钥未配置,尝试从智能助手配置中读取'); 513 | 514 | // 尝试从sender的额外配置中获取配置 515 | const configFromSender = sender?.plugin?.config?.weather; 516 | if (configFromSender && configFromSender.key) { 517 | console.log('[天气服务] 从智能助手配置中获取到API密钥'); 518 | this.config.key = configFromSender.key; 519 | this.config.api = configFromSender.api || this.config.api; 520 | this.config.defaultCity = configFromSender.defaultCity || this.config.defaultCity; 521 | } 522 | } 523 | 524 | // 启用AI建议功能 525 | this.config.showAIAdvice = true; 526 | 527 | const weatherData = await this.getWeather(city); 528 | return weatherData; 529 | } catch (error) { 530 | console.error('[天气服务] 获取天气失败:', error); 531 | throw error; 532 | } 533 | }; -------------------------------------------------------------------------------- /plugins/ai-chat/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name AI聊天插件 3 | * @version 1.0.0 4 | * @description 提供AI聊天功能,支持多种AI模型 5 | * @author shuaijin 6 | */ 7 | 8 | // 导入模块 9 | const axios = require('axios'); 10 | const fs = require('fs'); 11 | const path = require('path'); 12 | 13 | // 插件元数据 14 | exports.meta = { 15 | name: "AI聊天", 16 | version: "1.0.0", 17 | description: "提供AI聊天功能,支持多种AI模型", 18 | author: "shuaijin" 19 | }; 20 | 21 | // 加载主配置文件 22 | function loadGlobalConfig() { 23 | try { 24 | const configPath = path.join(__dirname, '../../config.json'); 25 | if (fs.existsSync(configPath)) { 26 | const configContent = fs.readFileSync(configPath, 'utf8'); 27 | return JSON.parse(configContent); 28 | } 29 | } catch (error) { 30 | console.error('[AI聊天] 加载全局配置文件失败:', error.message); 31 | } 32 | return null; 33 | } 34 | 35 | // 检查配置中是否包含必要的字段 36 | function validateConfig(config) { 37 | if (!config) return false; 38 | 39 | // 检查models是否存在 40 | if (!config.models) return false; 41 | 42 | // 检查models是否为数组或对象且非空 43 | if (Array.isArray(config.models)) { 44 | if (config.models.length === 0) return false; 45 | } else if (typeof config.models === 'object') { 46 | if (Object.keys(config.models).length === 0) return false; 47 | } else { 48 | return false; 49 | } 50 | 51 | if (!config.defaultModel) return false; 52 | 53 | // 检查默认模型是否存在 54 | const defaultModelExists = Array.isArray(config.models) ? 55 | config.models.some(m => m.id === config.defaultModel) : 56 | !!config.models[config.defaultModel]; 57 | 58 | if (!defaultModelExists) return false; 59 | 60 | return true; 61 | } 62 | 63 | // 插件默认配置 - 只在配置文件不存在或配置无效时使用 64 | const fallbackConfig = { 65 | enabled: true, 66 | defaultModel: "deepseek", 67 | models: [ 68 | { 69 | id: "deepseek", 70 | name: "DeepSeek", 71 | apiKey: "", 72 | url: "https://api.deepseek.com/v1", 73 | enabled: true 74 | } 75 | ], 76 | chatTimeout: 7200000 // 聊天记录保存2小时(毫秒) 77 | }; 78 | 79 | // 插件初始化方法 80 | exports.initialize = async function(core, pluginConfig) { 81 | // 存储core引用 82 | this.core = core; 83 | 84 | // 尝试从全局配置加载 85 | const globalConfig = loadGlobalConfig(); 86 | let aiChatConfig = null; 87 | 88 | if (globalConfig && globalConfig.pluginSettings && globalConfig.pluginSettings['ai-chat']) { 89 | aiChatConfig = globalConfig.pluginSettings['ai-chat']; 90 | console.log('[AI聊天] 从全局配置文件加载配置'); 91 | } 92 | 93 | // 如果全局配置无效,尝试使用传入的pluginConfig 94 | if (!validateConfig(aiChatConfig) && pluginConfig && validateConfig(pluginConfig)) { 95 | aiChatConfig = pluginConfig; 96 | console.log('[AI聊天] 从插件配置加载配置'); 97 | } 98 | 99 | // 如果配置仍然无效,使用最小化的fallback配置并记录警告 100 | if (!validateConfig(aiChatConfig)) { 101 | console.warn('[AI聊天] 配置无效或不完整,插件可能无法正常工作'); 102 | console.warn('[AI聊天] 请在config.json的pluginSettings中添加有效的ai-chat配置'); 103 | 104 | // 使用最小化的后备配置 105 | aiChatConfig = fallbackConfig; 106 | console.log('[AI聊天] 使用后备配置'); 107 | 108 | // 尝试将后备配置写入全局配置 109 | if (globalConfig) { 110 | if (!globalConfig.pluginSettings) { 111 | globalConfig.pluginSettings = {}; 112 | } 113 | globalConfig.pluginSettings['ai-chat'] = aiChatConfig; 114 | 115 | try { 116 | const configPath = path.join(__dirname, '../../config.json'); 117 | fs.writeFileSync(configPath, JSON.stringify(globalConfig, null, 2), 'utf8'); 118 | console.log('[AI聊天] 已将后备配置写入全局配置文件'); 119 | } catch (error) { 120 | console.error('[AI聊天] 写入配置文件失败:', error.message); 121 | } 122 | } 123 | } 124 | 125 | // 最终设置配置 126 | this.config = aiChatConfig; 127 | 128 | // 定期清理过期的聊天历史 129 | setInterval(() => { 130 | this.cleanupChatHistories(); 131 | }, 3600000); // 每小时检查一次 132 | 133 | console.log(`[AI聊天] 插件已初始化,默认模型: ${this.config.defaultModel}`); 134 | return true; 135 | }; 136 | 137 | // 聊天历史记录缓存 138 | const chatHistories = new Map(); 139 | 140 | // 清理过期的聊天历史 141 | exports.cleanupChatHistories = function() { 142 | const now = Date.now(); 143 | const timeout = this.config.chatTimeout || 7200000; 144 | 145 | for (const [userId, history] of chatHistories.entries()) { 146 | if (now - history.lastUpdate > timeout) { 147 | chatHistories.delete(userId); 148 | console.log(`[AI聊天] 已清理用户 ${userId} 的聊天历史`); 149 | } 150 | } 151 | }; 152 | 153 | // 获取或创建用户的聊天历史 154 | function getUserChatHistory(userId) { 155 | if (!chatHistories.has(userId)) { 156 | chatHistories.set(userId, { 157 | messages: [], 158 | lastUpdate: Date.now(), 159 | currentModel: null 160 | }); 161 | } 162 | 163 | const history = chatHistories.get(userId); 164 | history.lastUpdate = Date.now(); 165 | 166 | return history; 167 | } 168 | 169 | // 使用DeepSeek模型聊天 170 | async function chatWithDeepSeek(message, history, apiKey) { 171 | try { 172 | const response = await axios.post('https://api.deepseek.com/v1/chat/completions', { 173 | model: "deepseek-chat", 174 | messages: [ 175 | { role: "system", content: "你是一个友好的AI助手,名叫小助手。请用中文回答问题,简明扼要地回复,不要太长。" }, 176 | ...history.map(msg => ({ role: msg.role, content: msg.content })), 177 | { role: "user", content: message } 178 | ], 179 | temperature: 0.7, 180 | max_tokens: 800 181 | }, { 182 | headers: { 183 | 'Content-Type': 'application/json', 184 | 'Authorization': `Bearer ${apiKey}` 185 | } 186 | }); 187 | 188 | return response.data.choices[0].message.content; 189 | } catch (error) { 190 | console.error('DeepSeek API调用出错:', error.response?.data || error.message); 191 | throw new Error(`DeepSeek模型请求失败: ${error.response?.data?.error?.message || error.message}`); 192 | } 193 | } 194 | 195 | // 使用SiliconFlow模型聊天 196 | async function chatWithSiliconFlow(message, history, config) { 197 | try { 198 | const response = await axios.post('https://api.siliconflow.cn/v1/chat/completions', { 199 | model: config.model || "deepseek-ai/DeepSeek-V3", 200 | messages: [ 201 | { role: "system", content: "你是一个友好的AI助手,名叫小助手。请用中文回答问题,简明扼要地回复,不要太长。" }, 202 | ...history.map(msg => ({ role: msg.role, content: msg.content })), 203 | { role: "user", content: message } 204 | ], 205 | temperature: 0.7, 206 | max_tokens: 800 207 | }, { 208 | headers: { 209 | 'Content-Type': 'application/json', 210 | 'Authorization': `Bearer ${config.apiKey}` 211 | } 212 | }); 213 | 214 | return response.data.choices[0].message.content; 215 | } catch (error) { 216 | console.error('SiliconFlow API调用出错:', error.response?.data || error.message); 217 | throw new Error(`SiliconFlow模型请求失败: ${error.response?.data?.error?.message || error.message}`); 218 | } 219 | } 220 | 221 | // 使用OpenAI模型聊天 222 | async function chatWithOpenAI(message, history, apiKey) { 223 | try { 224 | const response = await axios.post('https://api.openai.com/v1/chat/completions', { 225 | model: "gpt-3.5-turbo", 226 | messages: [ 227 | { role: "system", content: "你是一个友好的AI助手,名叫小助手。请用中文回答问题,简明扼要地回复,不要太长。" }, 228 | ...history.map(msg => ({ role: msg.role, content: msg.content })), 229 | { role: "user", content: message } 230 | ], 231 | temperature: 0.7, 232 | max_tokens: 800 233 | }, { 234 | headers: { 235 | 'Content-Type': 'application/json', 236 | 'Authorization': `Bearer ${apiKey}` 237 | } 238 | }); 239 | 240 | return response.data.choices[0].message.content; 241 | } catch (error) { 242 | console.error('OpenAI API调用出错:', error.response?.data || error.message); 243 | throw new Error(`OpenAI模型请求失败: ${error.response?.data?.error?.message || error.message}`); 244 | } 245 | } 246 | 247 | // 调用AI聊天 248 | async function chatWithAI(message, userId, modelName, config) { 249 | // 获取用户的聊天历史 250 | const historyObj = getUserChatHistory(userId); 251 | const { messages } = historyObj; 252 | 253 | // 如果指定了模型,更新用户当前使用的模型 254 | if (modelName) { 255 | historyObj.currentModel = modelName; 256 | } else if (!historyObj.currentModel) { 257 | // 如果用户没有设置过模型,使用默认模型 258 | historyObj.currentModel = config.defaultModel || "deepseek"; 259 | } 260 | 261 | // 使用当前模型 262 | const currentModel = historyObj.currentModel; 263 | 264 | // 尝试从数组中获取模型配置 265 | let modelConfig = null; 266 | 267 | // 检查是否使用新的数组结构 268 | if (Array.isArray(config.models)) { 269 | modelConfig = config.models.find(m => m.id === currentModel); 270 | } else { 271 | // 兼容旧版对象结构 272 | modelConfig = config.models[currentModel]; 273 | } 274 | 275 | if (!modelConfig) { 276 | throw new Error(`未知的AI模型: ${currentModel}`); 277 | } 278 | 279 | if (!(modelConfig.enabled || modelConfig.enable)) { 280 | throw new Error(`AI模型 ${modelConfig.name || currentModel} 已禁用`); 281 | } 282 | 283 | if (!modelConfig.apiKey) { 284 | throw new Error(`AI模型 ${modelConfig.name || currentModel} 未配置API密钥,请在config.json中配置`); 285 | } 286 | 287 | console.log(`[AI聊天] 使用模型 ${modelConfig.name || currentModel} 回复用户 ${userId}`); 288 | 289 | // 根据模型类型调用不同的API 290 | let reply; 291 | try { 292 | switch (currentModel) { 293 | case 'deepseek': 294 | reply = await chatWithDeepSeek(message, messages, modelConfig.apiKey); 295 | break; 296 | case 'siliconflow': 297 | reply = await chatWithSiliconFlow(message, messages, modelConfig); 298 | break; 299 | case 'openai': 300 | reply = await chatWithOpenAI(message, messages, modelConfig.apiKey); 301 | break; 302 | default: 303 | throw new Error(`不支持的AI模型类型: ${currentModel}`); 304 | } 305 | 306 | // 更新聊天历史 307 | messages.push({ role: "user", content: message }); 308 | messages.push({ role: "assistant", content: reply }); 309 | 310 | // 限制历史记录长度,防止过长 311 | if (messages.length > 20) { 312 | messages.splice(0, 2); // 移除最早的一问一答 313 | } 314 | 315 | return reply; 316 | } catch (error) { 317 | console.error(`[AI聊天] 模型 ${currentModel} 调用失败: ${error.message}`); 318 | throw new Error(`AI请求失败: ${error.message}`); 319 | } 320 | } 321 | 322 | // 确保函数被导出 323 | exports.chatWithAI = chatWithAI; 324 | 325 | // 清除用户聊天历史 326 | function clearUserChatHistory(userId) { 327 | if (chatHistories.has(userId)) { 328 | const history = chatHistories.get(userId); 329 | history.messages = []; 330 | history.lastUpdate = Date.now(); 331 | return true; 332 | } 333 | return false; 334 | } 335 | 336 | // 插件命令列表 337 | exports.commands = [ 338 | { 339 | name: "chat", 340 | pattern: /^\/chat (.+)$/, 341 | description: "与AI进行对话交流", 342 | handler: async function(sender, match) { 343 | try { 344 | const userId = sender.getUserId(); 345 | const message = match[1]; 346 | 347 | console.log(`[AI聊天] 收到用户 ${userId} 的聊天请求: ${message}`); 348 | 349 | // 发送正在思考的提示 350 | const thinkingMsg = await sender.reply("🤔 小助手正在思考中..."); 351 | 352 | // 调用AI获取回复 353 | console.log(`[AI聊天] 准备调用AI模型,用户ID: ${userId}, 配置:`, JSON.stringify(this.config)); 354 | const reply = await chatWithAI(message, userId, null, this.config); 355 | console.log(`[AI聊天] AI回复: ${reply.substring(0, 100)}${reply.length > 100 ? '...' : ''}`); 356 | 357 | // 撤回思考消息并发送回复 358 | await sender.delMsg(thinkingMsg); 359 | await sender.reply(reply); 360 | 361 | // 记录命令使用统计 362 | this.core.emit('command_used', { 363 | pluginName: 'ai-chat', 364 | commandName: 'chat', 365 | userId: userId, 366 | model: getUserChatHistory(userId).currentModel 367 | }); 368 | } catch (error) { 369 | console.error(`[AI聊天] 处理聊天请求时出错:`, error); 370 | await sender.reply(`AI聊天失败: ${error.message}`); 371 | } 372 | } 373 | }, 374 | { 375 | name: "clear", 376 | pattern: /^\/clear$/, 377 | description: "清除当前的聊天历史记录", 378 | handler: async function(sender) { 379 | const userId = sender.getUserId(); 380 | const result = clearUserChatHistory(userId); 381 | await sender.reply(result ? "✅ 聊天记录已清除" : "❌ 没有找到聊天记录"); 382 | } 383 | }, 384 | { 385 | name: "model_list", 386 | pattern: /^\/model list$/, 387 | description: "显示所有可用的AI模型列表", 388 | handler: async function(sender) { 389 | const userId = sender.getUserId(); 390 | const models = this.config.models; 391 | 392 | // 获取用户当前使用的模型 393 | const userHistory = getUserChatHistory(userId); 394 | const currentModel = userHistory.currentModel || this.config.defaultModel; 395 | 396 | // 构建模型列表消息 397 | let reply = "🤖 可用的AI模型列表:\n\n"; 398 | 399 | // 检查models是否为数组 400 | if (Array.isArray(models)) { 401 | for (const model of models) { 402 | const status = (model.enabled || model.enable) ? "✅ 已启用" : "❌ 已禁用"; 403 | const current = model.id === currentModel ? "【当前使用】" : ""; 404 | reply += `${model.id} (${model.name}) ${status} ${current}\n`; 405 | } 406 | } else { 407 | // 兼容旧版对象模式 408 | for (const [key, model] of Object.entries(models)) { 409 | const status = (model.enabled || model.enable) ? "✅ 已启用" : "❌ 已禁用"; 410 | const current = key === currentModel ? "【当前使用】" : ""; 411 | reply += `${key} (${model.name}) ${status} ${current}\n`; 412 | } 413 | } 414 | 415 | reply += "\n使用 /model use 切换模型"; 416 | 417 | await sender.reply(reply); 418 | } 419 | }, 420 | { 421 | name: "model_use", 422 | pattern: /^\/model use (.+)$/, 423 | description: "切换使用指定的AI模型", 424 | handler: async function(sender, match) { 425 | const userId = sender.getUserId(); 426 | const modelName = match[1].toLowerCase(); 427 | 428 | // 查找模型配置 429 | let modelConfig = null; 430 | 431 | // 检查models是否为数组 432 | if (Array.isArray(this.config.models)) { 433 | modelConfig = this.config.models.find(m => m.id === modelName); 434 | } else { 435 | // 兼容旧版对象模式 436 | modelConfig = this.config.models[modelName]; 437 | } 438 | 439 | // 检查模型是否存在 440 | if (!modelConfig) { 441 | await sender.reply(`❌ 未找到模型 "${modelName}",使用 /model list 查看可用模型`); 442 | return; 443 | } 444 | 445 | // 检查模型是否启用 446 | if (!(modelConfig.enabled || modelConfig.enable)) { 447 | await sender.reply(`❌ 模型 "${modelName}" 已被禁用`); 448 | return; 449 | } 450 | 451 | // 检查模型API密钥是否配置 452 | if (!modelConfig.apiKey) { 453 | await sender.reply(`❌ 模型 "${modelName}" 未配置API密钥`); 454 | return; 455 | } 456 | 457 | // 更新用户的模型设置 458 | const history = getUserChatHistory(userId); 459 | history.currentModel = modelName; 460 | 461 | await sender.reply(`✅ 已切换到 ${modelConfig.name} 模型`); 462 | } 463 | }, 464 | { 465 | name: "model_config", 466 | pattern: /^\/model config (.+) (.+) (.+)$/, 467 | description: "配置AI模型参数(模型名 参数名 参数值)", 468 | handler: async function(sender, match) { 469 | const isAdmin = await sender.isAdmin(); 470 | if (!isAdmin) { 471 | await sender.reply("❌ 只有管理员可以配置模型参数"); 472 | return; 473 | } 474 | 475 | const modelName = match[1].toLowerCase(); 476 | const paramName = match[2].toLowerCase(); 477 | const paramValue = match[3]; 478 | 479 | // 查找模型配置 480 | let modelConfig = null; 481 | let modelIndex = -1; 482 | 483 | // 检查models是否为数组 484 | if (Array.isArray(this.config.models)) { 485 | modelIndex = this.config.models.findIndex(m => m.id === modelName); 486 | if (modelIndex >= 0) { 487 | modelConfig = this.config.models[modelIndex]; 488 | } 489 | } else { 490 | // 兼容旧版对象模式 491 | if (this.config.models[modelName]) { 492 | modelConfig = this.config.models[modelName]; 493 | } 494 | } 495 | 496 | // 检查模型是否存在 497 | if (!modelConfig) { 498 | await sender.reply(`❌ 未找到模型 "${modelName}"`); 499 | return; 500 | } 501 | 502 | try { 503 | // 设置参数 504 | switch (paramName) { 505 | case 'apikey': 506 | modelConfig.apiKey = paramValue; 507 | await sender.reply(`✅ 已更新 ${modelName} 的API密钥`); 508 | break; 509 | 510 | case 'url': 511 | modelConfig.url = paramValue; 512 | await sender.reply(`✅ 已更新 ${modelName} 的API地址为 ${paramValue}`); 513 | break; 514 | 515 | case 'enable': 516 | case 'enabled': 517 | const enableValue = paramValue.toLowerCase() === 'true'; 518 | modelConfig.enable = enableValue; 519 | modelConfig.enabled = enableValue; // 兼容旧版 520 | await sender.reply(`✅ 已${enableValue ? '启用' : '禁用'} ${modelName} 模型`); 521 | break; 522 | 523 | case 'model': 524 | if (modelName === 'siliconflow') { 525 | modelConfig.model = paramValue; 526 | } else { 527 | await sender.reply(`❌ 模型 "${modelName}" 不支持自定义模型类型`); 528 | return; 529 | } 530 | break; 531 | default: 532 | await sender.reply(`❌ 未知的参数 "${paramName}"`); 533 | return; 534 | } 535 | 536 | // 如果是数组格式,更新数组中的模型 537 | if (Array.isArray(this.config.models) && modelIndex >= 0) { 538 | this.config.models[modelIndex] = modelConfig; 539 | } 540 | 541 | // 保存配置 542 | try { 543 | // 尝试使用事件来更新全局配置 544 | await this.core.emit('config_updated', { 545 | pluginName: 'ai-chat', 546 | config: this.config 547 | }); 548 | 549 | // 同时更新全局配置文件 550 | const globalConfig = loadGlobalConfig(); 551 | if (globalConfig) { 552 | if (!globalConfig.pluginSettings) { 553 | globalConfig.pluginSettings = {}; 554 | } 555 | globalConfig.pluginSettings['ai-chat'] = this.config; 556 | 557 | // 保存更新后的全局配置 558 | const configPath = path.join(__dirname, '../../config.json'); 559 | fs.writeFileSync(configPath, JSON.stringify(globalConfig, null, 2), 'utf8'); 560 | console.log('[AI聊天] 已更新全局配置文件'); 561 | } 562 | 563 | await sender.reply(`✅ 已更新模型 ${modelConfig.name || modelName} 的 ${paramName} 参数`); 564 | } catch (saveError) { 565 | console.error('[AI聊天] 保存配置失败:', saveError); 566 | await sender.reply(`⚠️ 已更新内存中的配置,但保存配置文件失败: ${saveError.message}`); 567 | } 568 | } catch (error) { 569 | await sender.reply(`❌ 配置更新失败: ${error.message}`); 570 | } 571 | } 572 | } 573 | ]; 574 | 575 | // 插件卸载方法 576 | exports.unload = async function() { 577 | // 清理资源 578 | chatHistories.clear(); 579 | console.log('[AI聊天] 插件已卸载'); 580 | return true; 581 | }; 582 | -------------------------------------------------------------------------------- /plugins/ai-speedtest/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name AI模型测速插件 3 | * @version 1.0.0 4 | * @description 测试多个AI模型的响应速度并自动选择最快模型 5 | * @author shuaijin 6 | */ 7 | 8 | // 插件元数据 9 | exports.meta = { 10 | name: "AI模型测速", 11 | version: "1.0.0", 12 | description: "测试多个AI模型的响应速度并自动选择最快模型", 13 | author: "shuaijin" 14 | }; 15 | 16 | // 插件默认配置 17 | exports.defaultConfig = { 18 | enabled: true, 19 | testInterval: 3600000, // 默认每小时测试一次 (毫秒) 20 | testPrompt: "用一句话简明扼要地回答:今天天气好吗?", // 测试用的提问 21 | testTimeout: 15000, // 测试超时时间 (毫秒) 22 | autoSwitch: true, // 是否自动切换到最快的模型 23 | skipDisabled: true, // 是否跳过已禁用的模型 24 | excludeModels: [], // 要排除的模型列表 25 | currentFastest: null, // 当前最快的模型 26 | lastTestTime: 0, // 上次测试时间 27 | testResults: {} // 测试结果 28 | }; 29 | 30 | // 定时任务 31 | let speedTestInterval = null; 32 | 33 | // 插件初始化方法 34 | exports.initialize = async function(core, pluginConfig) { 35 | // 存储core引用和配置 36 | this.core = core; 37 | this.config = pluginConfig; 38 | 39 | // 如果已经有最快模型的记录,直接设置 40 | if (this.config.currentFastest && this.config.autoSwitch) { 41 | console.log(`[AI模型测速] 使用上次测试结果:${this.config.currentFastest}是最快的模型`); 42 | } 43 | 44 | // 设置定时测速任务 45 | this.setupSpeedTest(); 46 | 47 | // 输出调试信息 48 | console.log('[AI模型测速] 插件已初始化'); 49 | console.log('[AI模型测速] 可用插件列表:', Array.from(core.plugins.keys()).join(', ')); 50 | 51 | // 尝试获取ai-chat插件 52 | const aiChatPlugin = core.plugins.get('ai-chat'); 53 | if (aiChatPlugin) { 54 | console.log('[AI模型测速] 成功找到AI聊天插件'); 55 | } else { 56 | console.warn('[AI模型测速] 警告: 未找到AI聊天插件,请确保ai-chat插件已加载'); 57 | // 尝试其他可能的名称 58 | const possibleNames = ['ai-chat', 'ai_chat', 'ai.chat', 'aichat', 'AI-chat', 'ai']; 59 | for (const name of possibleNames) { 60 | if (core.plugins.has(name)) { 61 | console.log(`[AI模型测速] 发现可能的AI聊天插件: ${name}`); 62 | } 63 | } 64 | } 65 | 66 | return true; 67 | }; 68 | 69 | // 设置定时测速任务 70 | exports.setupSpeedTest = function() { 71 | // 清除现有的定时任务 72 | if (speedTestInterval) { 73 | clearInterval(speedTestInterval); 74 | } 75 | 76 | // 设置新的定时任务 77 | speedTestInterval = setInterval(() => { 78 | this.runSpeedTest(); 79 | }, this.config.testInterval); 80 | 81 | console.log(`[AI模型测速] 已设置定时测速任务,间隔 ${this.config.testInterval / 60000} 分钟`); 82 | }; 83 | 84 | // 获取可测试的AI模型列表 85 | function getTestableModels(aiConfig, testConfig) { 86 | const result = []; 87 | const { skipDisabled, excludeModels } = testConfig; 88 | 89 | // 确保aiConfig和aiConfig.models存在 90 | if (!aiConfig || !aiConfig.models) { 91 | console.log(`[AI模型测速] AI配置或模型配置不存在`); 92 | return result; 93 | } 94 | 95 | // 检查是否使用数组格式的模型配置 96 | if (Array.isArray(aiConfig.models)) { 97 | // 处理数组格式 98 | for (const modelConfig of aiConfig.models) { 99 | const modelName = modelConfig.id; // 使用id作为模型名称 100 | 101 | // 检查模型是否要排除 102 | if (excludeModels.includes(modelName)) { 103 | console.log(`[AI模型测速] 模型 ${modelName} 在排除列表中,跳过测试`); 104 | continue; 105 | } 106 | 107 | // 检查模型是否已禁用且需要跳过 108 | if (skipDisabled && !(modelConfig.enable || modelConfig.enabled)) { 109 | console.log(`[AI模型测速] 模型 ${modelName} 已禁用,跳过测试`); 110 | continue; 111 | } 112 | 113 | // 检查模型是否有API密钥 114 | if (!modelConfig.apiKey) { 115 | console.log(`[AI模型测速] 模型 ${modelName} 未配置API密钥,跳过测试`); 116 | continue; 117 | } 118 | 119 | // 添加到可测试模型列表 120 | result.push(modelName); 121 | } 122 | } else { 123 | // 处理对象格式(旧格式) 124 | for (const [modelName, modelConfig] of Object.entries(aiConfig.models)) { 125 | // 检查模型是否要排除 126 | if (excludeModels.includes(modelName)) { 127 | console.log(`[AI模型测速] 模型 ${modelName} 在排除列表中,跳过测试`); 128 | continue; 129 | } 130 | 131 | // 检查模型是否已禁用且需要跳过 132 | if (skipDisabled && !(modelConfig.enable || modelConfig.enabled)) { 133 | console.log(`[AI模型测速] 模型 ${modelName} 已禁用,跳过测试`); 134 | continue; 135 | } 136 | 137 | // 检查模型是否有API密钥 138 | if (!modelConfig.apiKey) { 139 | console.log(`[AI模型测速] 模型 ${modelName} 未配置API密钥,跳过测试`); 140 | continue; 141 | } 142 | 143 | // 添加到可测试模型列表 144 | result.push(modelName); 145 | } 146 | } 147 | 148 | return result; 149 | } 150 | 151 | // 运行模型测速 152 | exports.runSpeedTest = async function(sender = null) { 153 | try { 154 | console.log(`[AI模型测速] 开始测速测试,sender:`, sender ? '用户触发' : '自动触发'); 155 | console.log(`[AI模型测速] 可用插件列表:`, Array.from(this.core.plugins.keys()).join(', ')); 156 | 157 | // 获取AI插件 - 尝试多个可能的名称 158 | let aiChatPlugin = null; 159 | const possibleNames = ['ai-chat', 'ai_chat', 'ai.chat', 'aichat', 'AI-chat', 'ai']; 160 | 161 | for (const name of possibleNames) { 162 | const plugin = this.core.plugins.get(name); 163 | if (plugin?.instance) { 164 | aiChatPlugin = plugin.instance; 165 | console.log(`[AI模型测速] 使用插件名称 "${name}" 找到AI聊天插件`); 166 | break; 167 | } 168 | } 169 | 170 | if (!aiChatPlugin) { 171 | const error = "AI聊天插件未加载或不可用"; 172 | console.error(`[AI模型测速] ${error}`); 173 | if (sender) await sender.reply(`❌ ${error}`); 174 | return; 175 | } 176 | 177 | // 确保chatWithAI函数存在并且可以调用 178 | if (typeof aiChatPlugin.chatWithAI !== 'function') { 179 | console.error(`[AI模型测速] AI聊天插件结构:`, Object.keys(aiChatPlugin)); 180 | 181 | // 查找可能的聊天方法 182 | let chatMethod = null; 183 | for (const key of Object.keys(aiChatPlugin)) { 184 | if (typeof aiChatPlugin[key] === 'function' && 185 | (key.includes('chat') || key.includes('talk') || key.includes('ask'))) { 186 | chatMethod = key; 187 | console.log(`[AI模型测速] 发现可能的聊天方法: ${key}`); 188 | } 189 | } 190 | 191 | const error = `AI聊天插件的chatWithAI方法不可用${chatMethod ? `,但找到可能的方法: ${chatMethod}` : ''}`; 192 | console.error(`[AI模型测速] ${error}`); 193 | if (sender) await sender.reply(`❌ ${error}`); 194 | return; 195 | } 196 | 197 | // 获取AI配置 198 | const aiConfig = aiChatPlugin.config; 199 | if (!aiConfig) { 200 | const error = "AI聊天插件配置不可用"; 201 | console.error(`[AI模型测速] ${error}`); 202 | if (sender) await sender.reply(`❌ ${error}`); 203 | return; 204 | } 205 | 206 | console.log(`[AI模型测速] AI配置加载成功,配置项:`, Object.keys(aiConfig)); 207 | 208 | // 检查模型配置 209 | if (!aiConfig.models) { 210 | console.log(`[AI模型测速] 警告: 未找到models配置,尝试自动填充默认模型`); 211 | 212 | // 尝试使用defaultModel创建一个临时模型配置 213 | if (aiConfig.defaultModel) { 214 | // 创建临时模型配置 215 | if (Array.isArray(aiConfig.models)) { 216 | aiConfig.models = [ 217 | { 218 | id: aiConfig.defaultModel, 219 | name: aiConfig.defaultModel, 220 | enable: true, 221 | enabled: true, // 兼容旧版本 222 | apiKey: '已配置' // 假设已经在内部配置 223 | } 224 | ]; 225 | } else { 226 | aiConfig.models = { 227 | [aiConfig.defaultModel]: { 228 | enable: true, 229 | enabled: true, // 兼容旧版本 230 | apiKey: '已配置' // 假设已经在内部配置 231 | } 232 | }; 233 | } 234 | console.log(`[AI模型测速] 使用默认模型创建临时配置: ${aiConfig.defaultModel}`); 235 | } else { 236 | const error = "AI聊天插件缺少模型配置"; 237 | console.error(`[AI模型测速] ${error}`); 238 | if (sender) await sender.reply(`❌ ${error}`); 239 | return; 240 | } 241 | } 242 | 243 | // 从AI聊天插件配置中获取可测试的模型 244 | const modelsToTest = getTestableModels(aiConfig, this.config); 245 | 246 | if (modelsToTest.length === 0) { 247 | const error = "没有可用的模型可供测试"; 248 | console.error(`[AI模型测速] ${error}`); 249 | if (sender) await sender.reply(`❌ ${error}`); 250 | return; 251 | } 252 | 253 | // 如果是手动触发,发送测试开始消息 254 | let statusMsg = null; 255 | if (sender) { 256 | statusMsg = await sender.reply(`🔍 开始测试模型响应速度: ${modelsToTest.join(', ')}...`); 257 | } 258 | 259 | console.log(`[AI模型测速] 开始测试模型响应速度: ${modelsToTest.join(', ')}`); 260 | 261 | // 测试结果 262 | const results = {}; 263 | 264 | // 测试每个模型 265 | for (const modelName of modelsToTest) { 266 | try { 267 | console.log(`[AI模型测速] 测试模型: ${modelName}`); 268 | 269 | // 测试响应时间 270 | const startTime = Date.now(); 271 | 272 | // 设置超时处理 273 | const timeoutPromise = new Promise((_, reject) => { 274 | setTimeout(() => reject(new Error('请求超时')), this.config.testTimeout); 275 | }); 276 | 277 | // 使用一个唯一的用户ID,避免使用聊天历史的缓存 278 | const testUserId = `speedtest_${Date.now()}`; 279 | console.log(`[AI模型测速] 使用测试用户ID: ${testUserId}`); 280 | 281 | // 正确调用chatWithAI 282 | // 确保使用正确的上下文(this),绑定到aiChatPlugin 283 | const chatWithAIBound = aiChatPlugin.chatWithAI.bind(aiChatPlugin); 284 | 285 | console.log(`[AI模型测速] 准备调用chatWithAI,prompt: "${this.config.testPrompt.substring(0, 30)}..."`); 286 | 287 | // 发送请求 288 | const responsePromise = chatWithAIBound( 289 | this.config.testPrompt, 290 | testUserId, 291 | modelName, 292 | aiConfig 293 | ); 294 | 295 | // 等待响应或超时 296 | const response = await Promise.race([responsePromise, timeoutPromise]); 297 | console.log(`[AI模型测速] 收到响应: "${response.substring(0, 30)}..."`); 298 | 299 | // 计算响应时间 300 | const endTime = Date.now(); 301 | const responseTime = endTime - startTime; 302 | 303 | // 记录结果 304 | results[modelName] = { 305 | responseTime, 306 | status: 'success', 307 | message: `响应时间: ${responseTime}ms` 308 | }; 309 | 310 | console.log(`[AI模型测速] 模型 ${modelName} 响应时间: ${responseTime}ms`); 311 | } catch (error) { 312 | // 记录错误 313 | results[modelName] = { 314 | responseTime: Infinity, 315 | status: 'error', 316 | message: error.message || '未知错误' 317 | }; 318 | 319 | console.error(`[AI模型测速] 模型 ${modelName} 测试失败:`, error.message || '未知错误'); 320 | } 321 | } 322 | 323 | // 找出响应最快的模型 324 | let fastestModel = null; 325 | let fastestTime = Infinity; 326 | 327 | for (const [modelName, result] of Object.entries(results)) { 328 | if (result.status === 'success' && result.responseTime < fastestTime) { 329 | fastestModel = modelName; 330 | fastestTime = result.responseTime; 331 | } 332 | } 333 | 334 | // 更新配置 335 | this.config.testResults = results; 336 | this.config.lastTestTime = Date.now(); 337 | 338 | // 如果找到最快的模型 339 | if (fastestModel) { 340 | this.config.currentFastest = fastestModel; 341 | console.log(`[AI模型测速] 最快的模型是: ${fastestModel} (${fastestTime}ms)`); 342 | 343 | // 如果启用了自动切换,设置为默认模型 344 | if (this.config.autoSwitch) { 345 | try { 346 | const originalDefault = aiConfig.defaultModel; 347 | aiConfig.defaultModel = fastestModel; 348 | 349 | // 保存AI插件配置 350 | await this.core.emit('config_updated', { 351 | pluginName: 'ai-chat', 352 | config: aiConfig 353 | }); 354 | 355 | console.log(`[AI模型测速] 已将默认模型从 ${originalDefault} 切换为 ${fastestModel}`); 356 | } catch (error) { 357 | console.error(`[AI模型测速] 更新默认模型失败:`, error.message || '未知错误'); 358 | } 359 | } 360 | } else { 361 | console.warn(`[AI模型测速] 所有模型测试都失败`); 362 | } 363 | 364 | // 保存测速插件配置 365 | try { 366 | await this.core.emit('config_updated', { 367 | pluginName: 'ai-speedtest', 368 | config: this.config 369 | }); 370 | } catch (error) { 371 | console.error(`[AI模型测速] 保存测速插件配置失败:`, error.message || '未知错误'); 372 | } 373 | 374 | // 如果是手动触发,发送测试结果 375 | if (sender) { 376 | // 格式化结果 377 | let resultMsg = "🚀 AI模型速度测试结果:\n\n"; 378 | 379 | for (const [modelName, result] of Object.entries(results)) { 380 | const status = result.status === 'success' ? '✅' : '❌'; 381 | resultMsg += `${status} ${modelName}: ${result.message}\n`; 382 | } 383 | 384 | if (fastestModel) { 385 | resultMsg += `\n🏆 最快的模型: ${fastestModel} (${fastestTime}ms)` 386 | 387 | if (this.config.autoSwitch) { 388 | resultMsg += `\n✨ 已自动将默认模型切换为 ${fastestModel}`; 389 | } 390 | } else { 391 | resultMsg += "\n⚠️ 所有模型测试都失败"; 392 | } 393 | 394 | // 发送结果 395 | try { 396 | if (statusMsg) { 397 | await sender.delMsg(statusMsg); 398 | } 399 | await sender.reply(resultMsg); 400 | } catch (error) { 401 | console.error(`[AI模型测速] 发送测试结果失败:`, error.message || '未知错误'); 402 | try { 403 | await sender.reply(`❌ 发送测试结果失败: ${error.message || '未知错误'}`); 404 | } catch (e) { 405 | // 忽略最终错误 406 | } 407 | } 408 | } 409 | 410 | return fastestModel; 411 | } catch (error) { 412 | console.error(`[AI模型测速] 测速过程出错:`, error.message || '未知错误'); 413 | if (sender) { 414 | try { 415 | await sender.reply(`❌ 测速失败: ${error.message || '未知错误'}`); 416 | } catch (e) { 417 | // 忽略最终错误 418 | } 419 | } 420 | return null; 421 | } 422 | }; 423 | 424 | // 插件命令列表 425 | exports.commands = [ 426 | { 427 | name: "speedtest", 428 | pattern: /^\/speedtest$/i, 429 | description: "测试所有AI模型的响应速度", 430 | handler: async function(sender, match) { 431 | try { 432 | // 记录消息信息 433 | const message = sender.getMsg(); 434 | const userId = sender.getUserId(); 435 | console.log(`[AI模型测速] 接收到用户 ${userId} 的测速命令: ${message}`); 436 | 437 | console.log(`[AI模型测速] 开始执行/speedtest命令`); 438 | 439 | // 先发送一条提示消息 440 | const responseMsg = await sender.reply("正在准备AI模型速度测试,这可能需要几分钟时间..."); 441 | console.log(`[AI模型测速] 已发送准备消息,ID: ${responseMsg || 'unknown'}`); 442 | 443 | // 获取AI插件,先检查是否可用 444 | const aiChatPlugin = this.core.plugins.get('ai-chat')?.instance; 445 | if (!aiChatPlugin) { 446 | console.error(`[AI模型测速] AI聊天插件未加载或不可用`); 447 | await sender.reply(`❌ 无法执行测速: AI聊天插件未加载或不可用`); 448 | return; 449 | } 450 | 451 | console.log(`[AI模型测速] 成功获取AI聊天插件: ${typeof aiChatPlugin}`); 452 | 453 | // 检查方法是否存在 454 | if (typeof this.runSpeedTest !== 'function') { 455 | console.error(`[AI模型测速] runSpeedTest方法不存在或不是函数`); 456 | await sender.reply(`❌ 插件错误: 测速核心方法不可用,请联系管理员`); 457 | return; 458 | } 459 | 460 | console.log(`[AI模型测速] runSpeedTest方法检查通过`); 461 | 462 | if (typeof aiChatPlugin.chatWithAI !== 'function') { 463 | console.error(`[AI模型测速] AI聊天插件的chatWithAI方法不是函数`); 464 | await sender.reply(`❌ 无法执行测速: AI聊天插件的对话功能不可用`); 465 | return; 466 | } 467 | 468 | console.log(`[AI模型测速] AI聊天插件的chatWithAI方法检查通过`); 469 | 470 | // 确保配置有效 471 | if (!this.config) { 472 | console.error(`[AI模型测速] 插件配置不可用`); 473 | await sender.reply(`❌ 无法执行测速: 插件配置不可用`); 474 | return; 475 | } 476 | 477 | console.log(`[AI模型测速] 插件配置检查通过,开始执行测速测试`); 478 | 479 | // 执行测速 480 | try { 481 | console.log(`[AI模型测速] 调用 this.runSpeedTest(sender)`); 482 | await this.runSpeedTest(sender); 483 | console.log(`[AI模型测速] 测速测试完成`); 484 | } catch (speedTestError) { 485 | console.error(`[AI模型测速] 执行测速过程中出错:`, speedTestError); 486 | await sender.reply(`❌ 测速过程中出错: ${speedTestError.message || '未知错误'}\n如果问题持续存在,请尝试使用 /speedtest info 查看配置`); 487 | } 488 | } catch (error) { 489 | console.error(`[AI模型测速] 执行测速命令失败:`, error); 490 | try { 491 | await sender.reply(`❌ 执行测速命令失败: ${error.message || '未知错误'}\n${error.stack || ''}`); 492 | } catch (e) { 493 | console.error(`[AI模型测速] 发送错误消息失败:`, e); 494 | } 495 | } 496 | } 497 | }, 498 | { 499 | name: "speedtest_config", 500 | pattern: /^\/speedtest config (.+) (.+)$/i, 501 | description: "配置测速参数(参数名 参数值)", 502 | handler: async function(sender, match) { 503 | try { 504 | const isAdmin = await sender.isAdmin(); 505 | if (!isAdmin) { 506 | await sender.reply("❌ 只有管理员可以配置测速参数"); 507 | return; 508 | } 509 | 510 | const paramName = match[1].toLowerCase(); 511 | const paramValue = match[2]; 512 | 513 | try { 514 | switch (paramName) { 515 | case 'interval': 516 | // 设置测试间隔(分钟转毫秒) 517 | const interval = parseInt(paramValue) * 60000; 518 | if (isNaN(interval) || interval < 60000) { 519 | await sender.reply("❌ 测试间隔必须大于等于1分钟"); 520 | return; 521 | } 522 | this.config.testInterval = interval; 523 | this.setupSpeedTest(); // 重新设置定时任务 524 | break; 525 | 526 | case 'prompt': 527 | // 设置测试用的提问 528 | this.config.testPrompt = paramValue; 529 | break; 530 | 531 | case 'timeout': 532 | // 设置超时时间(秒转毫秒) 533 | const timeout = parseInt(paramValue) * 1000; 534 | if (isNaN(timeout) || timeout < 1000) { 535 | await sender.reply("❌ 超时时间必须大于等于1秒"); 536 | return; 537 | } 538 | this.config.testTimeout = timeout; 539 | break; 540 | 541 | case 'autoswitch': 542 | // 设置是否自动切换 543 | this.config.autoSwitch = paramValue.toLowerCase() === 'true'; 544 | break; 545 | 546 | case 'skipdisabled': 547 | // 设置是否跳过已禁用的模型 548 | this.config.skipDisabled = paramValue.toLowerCase() === 'true'; 549 | break; 550 | 551 | case 'exclude': 552 | // 设置要排除的模型列表 553 | const models = paramValue ? paramValue.split(',').map(m => m.trim()) : []; 554 | this.config.excludeModels = models; 555 | break; 556 | 557 | default: 558 | await sender.reply(`❌ 未知的参数 "${paramName}"`); 559 | return; 560 | } 561 | 562 | // 保存配置 563 | await this.core.emit('config_updated', { 564 | pluginName: 'ai-speedtest', 565 | config: this.config 566 | }); 567 | 568 | await sender.reply(`✅ 已更新测速参数 ${paramName} 为 ${paramValue}`); 569 | } catch (error) { 570 | await sender.reply(`❌ 配置更新失败: ${error.message || '未知错误'}`); 571 | } 572 | } catch (error) { 573 | console.error(`[AI模型测速] 处理配置命令失败:`, error.message || '未知错误'); 574 | try { 575 | await sender.reply(`❌ 处理配置命令失败: ${error.message || '未知错误'}`); 576 | } catch (e) { 577 | // 忽略最终错误 578 | } 579 | } 580 | } 581 | }, 582 | { 583 | name: "speedtest_info", 584 | pattern: /^\/speedtest info$/i, 585 | description: "查看测速插件的配置和上次测试结果", 586 | handler: async function(sender, match) { 587 | try { 588 | console.log(`[AI模型测速] 执行/speedtest info命令`); 589 | 590 | // 获取AI插件配置 591 | const aiChatPlugin = this.core.plugins.get('ai-chat')?.instance; 592 | if (!aiChatPlugin) { 593 | await sender.reply("❌ AI聊天插件未加载或不可用"); 594 | return; 595 | } 596 | 597 | // 获取可测试的模型列表 598 | const aiConfig = aiChatPlugin.config; 599 | const modelsToTest = getTestableModels(aiConfig, this.config); 600 | 601 | // 格式化配置信息 602 | const { testInterval, testTimeout, autoSwitch, skipDisabled, excludeModels, currentFastest, lastTestTime, testResults } = this.config; 603 | 604 | let info = "⚙️ AI模型测速插件配置:\n\n"; 605 | 606 | info += `测试间隔: ${testInterval / 60000} 分钟\n`; 607 | info += `测试超时: ${testTimeout / 1000} 秒\n`; 608 | info += `自动切换: ${autoSwitch ? '✅ 启用' : '❌ 禁用'}\n`; 609 | info += `跳过禁用模型: ${skipDisabled ? '✅ 是' : '❌ 否'}\n`; 610 | 611 | if (excludeModels.length > 0) { 612 | info += `排除模型: ${excludeModels.join(', ')}\n`; 613 | } 614 | 615 | info += `可测试模型: ${modelsToTest.join(', ') || '无'}\n`; 616 | 617 | // 添加上次测试结果 618 | if (lastTestTime > 0) { 619 | const lastTestDate = new Date(lastTestTime).toLocaleString(); 620 | info += `\n📊 上次测试时间: ${lastTestDate}\n`; 621 | 622 | if (currentFastest) { 623 | info += `🏆 最快模型: ${currentFastest}`; 624 | if (testResults[currentFastest]) { 625 | info += ` (${testResults[currentFastest].responseTime}ms)\n`; 626 | } else { 627 | info += "\n"; 628 | } 629 | } 630 | 631 | // 添加详细结果 632 | if (Object.keys(testResults).length > 0) { 633 | info += "\n📈 测试结果:\n"; 634 | 635 | for (const [modelName, result] of Object.entries(testResults)) { 636 | const status = result.status === 'success' ? '✅' : '❌'; 637 | info += `${status} ${modelName}: ${result.message}\n`; 638 | } 639 | } 640 | } else { 641 | info += "\n⚠️ 尚未进行过测试"; 642 | } 643 | 644 | await sender.reply(info); 645 | console.log(`[AI模型测速] /speedtest info命令完成`); 646 | } catch (error) { 647 | console.error(`[AI模型测速] 获取信息失败:`, error.message || '未知错误'); 648 | try { 649 | await sender.reply(`❌ 获取信息失败: ${error.message || '未知错误'}`); 650 | } catch (e) { 651 | // 忽略最终错误 652 | } 653 | } 654 | } 655 | } 656 | ]; 657 | 658 | // 插件卸载方法 659 | exports.unload = async function() { 660 | // 清除定时任务 661 | if (speedTestInterval) { 662 | clearInterval(speedTestInterval); 663 | speedTestInterval = null; 664 | } 665 | 666 | console.log('[AI模型测速] 插件已卸载'); 667 | return true; 668 | }; 669 | 670 | // 保存配置 671 | async function saveConfig(plugin) { 672 | try { 673 | // 使用BNCR事件系统更新配置 674 | if (plugin.core) { 675 | await plugin.core.emit('config_updated', { 676 | pluginName: 'ai-speedtest', 677 | config: plugin.config 678 | }); 679 | console.log('[AI模型测速] 已通过事件系统更新配置'); 680 | return true; 681 | } 682 | 683 | // 如果没有core引用,返回失败 684 | console.warn('[AI模型测速] 未找到core引用,无法保存配置'); 685 | return false; 686 | } catch (error) { 687 | console.error('[AI模型测速] 保存配置失败:', error); 688 | return false; 689 | } 690 | } -------------------------------------------------------------------------------- /core.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author shuaijin 3 | * @name core 4 | * @team shuaijin 5 | * @origin bncr 6 | * @version 1.0.0 7 | * @description 微信智能助手核心模块,负责插件加载和管理 8 | * @rule ^/plugins (list|enable|disable|reload)( .+)?$ 9 | * @admin true 10 | * @public true 11 | * @priority 100 12 | * @disable false 13 | * @classification 系统 14 | */ 15 | 16 | // 导入所需模块 17 | const fs = require('fs'); 18 | const path = require('path'); 19 | const { EventEmitter } = require('events'); 20 | 21 | // 获取全局变量 22 | const sysMethod = global.sysMethod; 23 | const router = sysMethod.router; 24 | const BncrDB = global.BncrDB; 25 | 26 | // 创建数据库实例 27 | const configDB = new BncrDB('wechat_assistant_config'); 28 | const pluginsDB = new BncrDB('wechat_assistant_plugins'); 29 | 30 | // 导入权限管理模块 31 | const permissionManager = require('./permission-manager'); 32 | 33 | // 创建事件发射器 34 | class WechatAssistantCore extends EventEmitter { 35 | constructor() { 36 | super(); 37 | this.plugins = new Map(); // 存储已加载的插件 38 | this.commands = new Map(); // 存储注册的命令 39 | this.baseDir = path.join(__dirname); // 插件基础目录 40 | this.pluginsDir = path.join(this.baseDir, 'plugins'); // 插件目录 41 | this.defaultConfig = { 42 | enabledPlugins: [], // 默认启用的插件 43 | }; 44 | this.config = {}; // 全局配置 45 | this.permissionManager = permissionManager; // 权限管理器 46 | } 47 | 48 | // 设置事件监听 49 | setupEventListeners() { 50 | // 监听配置更新事件 51 | this.on('config_updated', async (data) => { 52 | try { 53 | if (data.pluginName) { 54 | // 更新特定插件的配置 55 | if (this.plugins.has(data.pluginName)) { 56 | const plugin = this.plugins.get(data.pluginName); 57 | plugin.config = data.config; 58 | await configDB.set(`plugin:${data.pluginName}`, data.config); 59 | console.log(`[微信助手核心] 已更新插件配置: ${data.pluginName}`); 60 | } 61 | } else if (data.config) { 62 | // 更新全局配置 63 | this.config = data.config; 64 | await configDB.set('core:config', this.config); 65 | console.log('[微信助手核心] 已更新全局配置'); 66 | } 67 | } catch (error) { 68 | console.error(`[微信助手核心] 配置更新失败: ${error.message}`); 69 | } 70 | }); 71 | 72 | // 如果支持BNCR无界的配置更新,添加监听器 73 | if (global.BncrOnConfigUpdate) { 74 | global.BncrOnConfigUpdate('wechat-assistant', async (newConfig) => { 75 | try { 76 | console.log('[微信助手核心] 收到来自BNCR无界的配置更新'); 77 | 78 | // 处理启用的插件变化 79 | if (newConfig.enabledPlugins) { 80 | const oldEnabled = this.config.enabledPlugins || []; 81 | const newEnabled = newConfig.enabledPlugins || []; 82 | 83 | // 找出需要启用的插件 84 | const toEnable = newEnabled.filter(p => !oldEnabled.includes(p)); 85 | 86 | // 找出需要禁用的插件 87 | const toDisable = oldEnabled.filter(p => !newEnabled.includes(p)); 88 | 89 | // 依次启用插件 90 | for (const pluginName of toEnable) { 91 | await this.enablePlugin(pluginName); 92 | } 93 | 94 | // 依次禁用插件 95 | for (const pluginName of toDisable) { 96 | await this.disablePlugin(pluginName); 97 | } 98 | } 99 | 100 | // 更新插件配置 101 | if (newConfig.pluginSettings) { 102 | for (const [pluginName, config] of Object.entries(newConfig.pluginSettings)) { 103 | if (this.plugins.has(pluginName)) { 104 | const plugin = this.plugins.get(pluginName); 105 | plugin.config = config; 106 | await configDB.set(`plugin:${pluginName}`, config); 107 | console.log(`[微信助手核心] 已从无界更新插件配置: ${pluginName}`); 108 | } 109 | } 110 | } 111 | 112 | // 更新管理员用户列表 113 | if (newConfig.adminUsers) { 114 | this.config.adminUsers = newConfig.adminUsers; 115 | // 重新加载权限管理器配置 116 | await permissionManager.loadConfig(); 117 | console.log('[微信助手核心] 已更新管理员用户列表'); 118 | } 119 | 120 | // 更新全局配置 121 | this.config = newConfig; 122 | await configDB.set('core:config', this.config); 123 | console.log('[微信助手核心] 已保存来自无界的全局配置更新'); 124 | 125 | return true; 126 | } catch (error) { 127 | console.error(`[微信助手核心] 处理无界配置更新失败: ${error.message}`); 128 | return false; 129 | } 130 | }); 131 | console.log('[微信助手核心] 已注册BNCR无界配置更新回调'); 132 | } 133 | } 134 | 135 | // 初始化 136 | async initialize() { 137 | // 确保插件目录存在 138 | if (!fs.existsSync(this.pluginsDir)) { 139 | fs.mkdirSync(this.pluginsDir, { recursive: true }); 140 | } 141 | 142 | // 加载全局配置 143 | this.config = await configDB.get('core:config', this.defaultConfig); 144 | 145 | // 设置事件监听 146 | this.setupEventListeners(); 147 | 148 | // 加载所有插件 149 | await this.loadPlugins(); 150 | 151 | // 设置命令处理 152 | this.setupCommandHandlers(); 153 | 154 | // 初始化成功 155 | console.log('[微信助手核心] 初始化完成'); 156 | return true; 157 | } 158 | 159 | // 加载所有插件 160 | async loadPlugins() { 161 | console.log('[微信助手核心] 开始加载插件'); 162 | 163 | try { 164 | // 读取插件目录中的所有文件夹 165 | const pluginFolders = fs.readdirSync(this.pluginsDir).filter(file => { 166 | return fs.statSync(path.join(this.pluginsDir, file)).isDirectory(); 167 | }); 168 | 169 | // 只加载enabledPlugins中的插件 170 | const enabledPlugins = this.config.enabledPlugins || []; 171 | console.log(`[微信助手核心] 启用的插件: ${enabledPlugins.join(', ') || '无'}`); 172 | 173 | // 加载每个启用的插件 174 | for (const pluginName of enabledPlugins) { 175 | // 检查插件目录是否存在 176 | if (pluginFolders.includes(pluginName)) { 177 | await this.loadPlugin(pluginName); 178 | } else { 179 | console.error(`[微信助手核心] 启用的插件目录不存在: ${pluginName}`); 180 | } 181 | } 182 | 183 | console.log(`[微信助手核心] 成功加载 ${this.plugins.size} 个插件`); 184 | } catch (error) { 185 | console.error(`[微信助手核心] 加载插件出错: ${error.message}`); 186 | } 187 | } 188 | 189 | // 加载单个插件 190 | async loadPlugin(pluginName) { 191 | try { 192 | const pluginPath = path.join(this.pluginsDir, pluginName, 'index.js'); 193 | 194 | // 检查插件是否存在 195 | if (!fs.existsSync(pluginPath)) { 196 | console.error(`[微信助手核心] 插件不存在: ${pluginName}`); 197 | return false; 198 | } 199 | 200 | // 清除缓存,确保获取最新版本 201 | delete require.cache[require.resolve(pluginPath)]; 202 | 203 | // 导入插件 204 | const plugin = require(pluginPath); 205 | 206 | // 检查插件结构 207 | if (!plugin.meta || !plugin.initialize || !plugin.commands) { 208 | console.error(`[微信助手核心] 插件格式不正确: ${pluginName}`); 209 | return false; 210 | } 211 | 212 | // 获取插件配置 213 | const pluginConfig = await configDB.get(`plugin:${pluginName}`, plugin.defaultConfig || {}); 214 | 215 | // 检查是否启用 216 | const isEnabled = this.config.enabledPlugins.includes(pluginName); 217 | if (!isEnabled) { 218 | console.log(`[微信助手核心] 插件未启用: ${pluginName}`); 219 | return false; 220 | } 221 | 222 | // 初始化插件 223 | const initResult = await plugin.initialize(this, pluginConfig); 224 | if (!initResult) { 225 | console.error(`[微信助手核心] 插件初始化失败: ${pluginName}`); 226 | return false; 227 | } 228 | 229 | // 存储插件 230 | this.plugins.set(pluginName, { 231 | instance: plugin, 232 | config: pluginConfig, 233 | meta: plugin.meta 234 | }); 235 | 236 | // 注册命令 237 | for (const command of plugin.commands) { 238 | this.registerCommand(pluginName, command); 239 | } 240 | 241 | console.log(`[微信助手核心] 插件加载成功: ${pluginName} v${plugin.meta.version}`); 242 | return true; 243 | } catch (error) { 244 | console.error(`[微信助手核心] 加载插件 ${pluginName} 时出错: ${error.message}`); 245 | return false; 246 | } 247 | } 248 | 249 | // 注册命令 250 | registerCommand(pluginName, command) { 251 | const commandKey = `${pluginName}:${command.name}`; 252 | this.commands.set(commandKey, { 253 | ...command, 254 | pluginName 255 | }); 256 | console.log(`[微信助手核心] 注册命令: ${commandKey}`); 257 | } 258 | 259 | // 设置命令处理器 260 | setupCommandHandlers() { 261 | // 处理帮助命令 262 | this.registerCommand('core', { 263 | name: 'help', 264 | pattern: /^\/help$/, 265 | description: '显示所有可用命令', 266 | handler: async function(sender) { 267 | // 获取所有已加载插件的命令列表 268 | const helpSections = []; 269 | 270 | // 添加核心帮助信息 271 | helpSections.push(`🤖 【微信智能助手 v1.0.0】命令列表`); 272 | 273 | // 按插件分组整理命令 274 | const pluginCommands = new Map(); 275 | 276 | // 将命令按插件归类 277 | for (const [commandKey, command] of this.commands.entries()) { 278 | const pluginName = command.pluginName; 279 | if (!pluginCommands.has(pluginName)) { 280 | pluginCommands.set(pluginName, []); 281 | } 282 | pluginCommands.get(pluginName).push(command); 283 | } 284 | 285 | // 整理每个插件的命令列表 286 | for (const [pluginName, commands] of pluginCommands.entries()) { 287 | // 特殊处理core插件 288 | if (pluginName === 'core') { 289 | const coreInfo = `\n🛠️ 【核心功能】`; 290 | const cmdList = commands.map(cmd => { 291 | // 将正则模式转换为实际命令 292 | let cmdText = cmd.pattern.toString(); 293 | // 提取命令,通常格式为 /^\/command(.+)$/ 294 | const match = cmdText.match(/\^\\\/([a-zA-Z0-9_]+)/); 295 | let pattern = match ? `/${match[1]}` : cmdText; 296 | 297 | // 处理各种参数格式 298 | pattern = this.formatCommandPattern(pattern, cmdText); 299 | 300 | return ` ➤ ${pattern}`; 301 | }).join('\n'); 302 | helpSections.push(`${coreInfo}\n${cmdList}`); 303 | continue; 304 | } 305 | 306 | const plugin = this.plugins.get(pluginName); 307 | if (plugin) { 308 | const pluginInfo = `\n📌 【${plugin.meta.name || pluginName} v${plugin.meta.version || '1.0.0'}】`; 309 | const cmdList = commands.map(cmd => { 310 | // 将正则模式转换为实际命令 311 | let cmdText = cmd.pattern.toString(); 312 | // 提取命令,通常格式为 /^\/command(.+)$/ 313 | const match = cmdText.match(/\^\\\/([a-zA-Z0-9_]+)/); 314 | let pattern = match ? `/${match[1]}` : cmdText; 315 | 316 | // 处理各种参数格式 317 | pattern = this.formatCommandPattern(pattern, cmdText); 318 | 319 | return ` ➤ ${pattern}`; 320 | }).join('\n'); 321 | helpSections.push(`${pluginInfo}\n${cmdList}`); 322 | } 323 | } 324 | 325 | // 添加管理命令 326 | if (this.permissionManager.isAdmin(sender.getUserId())) { 327 | helpSections.push(`\n🔧 【管理员命令】 328 | ➤ /plugins list - 查看所有插件 329 | ➤ /plugins enable <插件名> - 启用插件 330 | ➤ /plugins disable <插件名> - 禁用插件 331 | ➤ /plugins reload <插件名> - 重新加载插件 332 | ➤ /admin list - 查看所有管理员 333 | ➤ /admin add <用户ID> - 添加管理员 334 | ➤ /admin remove <用户ID> - 移除管理员`); 335 | } 336 | 337 | // 发送帮助信息 338 | await sender.reply(helpSections.join('\n')); 339 | } 340 | }); 341 | 342 | // 添加管理员命令 343 | this.registerCommand('core', { 344 | name: 'admin_list', 345 | pattern: /^\/admin list$/, 346 | description: '列出所有管理员', 347 | handler: async function(sender) { 348 | // 检查权限 349 | const userId = sender.getUserId(); 350 | if (!this.permissionManager.isAdmin(userId)) { 351 | await sender.reply('您没有管理员权限'); 352 | return; 353 | } 354 | 355 | // 获取所有管理员 356 | const admins = this.permissionManager.getAllAdmins(); 357 | if (admins.length === 0) { 358 | await sender.reply('当前没有配置任何管理员'); 359 | } else { 360 | await sender.reply(`管理员列表:\n${admins.join('\n')}`); 361 | } 362 | } 363 | }); 364 | 365 | this.registerCommand('core', { 366 | name: 'admin_add', 367 | pattern: /^\/admin add (\S+)$/, 368 | description: '添加管理员', 369 | handler: async function(sender, match) { 370 | // 检查权限 371 | const userId = sender.getUserId(); 372 | if (!this.permissionManager.isAdmin(userId)) { 373 | await sender.reply('您没有管理员权限'); 374 | return; 375 | } 376 | 377 | // 添加管理员 378 | const newAdminId = match[1]; 379 | const result = this.permissionManager.addAdmin(newAdminId); 380 | 381 | if (result) { 382 | await sender.reply(`已成功添加管理员:${newAdminId}`); 383 | } else { 384 | await sender.reply(`添加管理员失败,可能该用户已经是管理员`); 385 | } 386 | } 387 | }); 388 | 389 | this.registerCommand('core', { 390 | name: 'admin_remove', 391 | pattern: /^\/admin remove (\S+)$/, 392 | description: '移除管理员', 393 | handler: async function(sender, match) { 394 | // 检查权限 395 | const userId = sender.getUserId(); 396 | if (!this.permissionManager.isAdmin(userId)) { 397 | await sender.reply('您没有管理员权限'); 398 | return; 399 | } 400 | 401 | // 移除管理员 402 | const adminId = match[1]; 403 | const result = this.permissionManager.removeAdmin(adminId); 404 | 405 | if (result) { 406 | await sender.reply(`已成功移除管理员:${adminId}`); 407 | } else { 408 | await sender.reply(`移除管理员失败,可能该用户不是管理员`); 409 | } 410 | } 411 | }); 412 | } 413 | 414 | // 格式化命令参数 415 | formatCommandPattern(pattern, regexStr) { 416 | // 针对不同命令类型进行特殊处理 417 | 418 | // 处理API命令 419 | if (pattern === '/api') { 420 | if (regexStr.includes('help')) { 421 | return '/api help - 显示API工具箱的帮助信息'; 422 | } else if (regexStr.includes('list')) { 423 | return '/api list - 显示可用的API列表'; 424 | } else { 425 | return '/api - 调用指定的API服务'; 426 | } 427 | } 428 | 429 | // 处理聊天命令 430 | if (pattern === '/chat') { 431 | return '/chat <内容> - 与AI进行对话交流'; 432 | } 433 | 434 | // 处理天气命令 435 | if (pattern === '/weather') { 436 | return '/weather <城市名> - 查询指定城市的当前天气状况'; 437 | } 438 | 439 | // 处理天气预报命令 440 | if (pattern === '/forecast') { 441 | return '/forecast <城市名> - 查看指定城市的未来天气预报'; 442 | } 443 | 444 | // 处理模型命令 445 | if (pattern === '/model') { 446 | if (regexStr.includes('list')) { 447 | return '/model list - 显示所有可用的AI模型列表'; 448 | } else if (regexStr.includes('use')) { 449 | return '/model use <模型名> - 切换使用指定的AI模型'; 450 | } else if (regexStr.includes('config')) { 451 | return '/model config <模型名> <参数名> <参数值> - 配置AI模型参数'; 452 | } 453 | return '/model - 管理AI模型'; 454 | } 455 | 456 | // 处理测速命令 457 | if (pattern === '/speedtest') { 458 | if (regexStr.includes('info')) { 459 | return '/speedtest info - 查看测速插件的配置和上次测试结果'; 460 | } else if (regexStr.includes('config')) { 461 | return '/speedtest config <参数名> <参数值> - 配置测速参数'; 462 | } 463 | return '/speedtest - 测试所有AI模型的响应速度'; 464 | } 465 | 466 | // 处理配置命令 467 | if (pattern === '/config') { 468 | if (regexStr.includes('list')) { 469 | return '/config list - 查看个人配置'; 470 | } else if (regexStr.includes('set')) { 471 | return '/config set <键名> <值> - 设置配置项'; 472 | } else if (regexStr.includes('get')) { 473 | return '/config get <键名> - 获取配置项值'; 474 | } else if (regexStr.includes('del')) { 475 | return '/config del <键名> - 删除配置项'; 476 | } 477 | return '/config - 管理个人配置'; 478 | } 479 | 480 | // 处理默认正则参数 481 | return pattern 482 | .replace(/\(\.[\+\*]\)/g, "<参数>") 483 | .replace(/\(\.\+\)/g, "<参数>") 484 | .replace(/\(list\|enable\|disable\|reload\)/, "") 485 | .replace(/\(list\|set\|get\|del\)/, "") 486 | .replace(/\(list\|use\|config\)/, "") 487 | .replace(/\(help\|list\|[a-zA-Z0-9_]+\)/, "<命令|API名称>"); 488 | } 489 | 490 | // 处理消息 491 | async handleMessage(sender) { 492 | const message = sender.getMsg(); 493 | const userId = sender.getUserId(); 494 | 495 | // 触发消息事件,让插件可以处理 496 | this.emit('message', { sender, message, userId }); 497 | 498 | // 检查是否匹配命令 499 | for (const [commandKey, command] of this.commands.entries()) { 500 | const match = message.match(command.pattern); 501 | if (match) { 502 | try { 503 | // 判断是否为核心命令(core插件) 504 | if (command.pluginName === 'core') { 505 | // 对于核心命令,直接使用this作为上下文 506 | await command.handler.call(this, sender, match); 507 | } else { 508 | // 获取插件实例 509 | const plugin = this.plugins.get(command.pluginName).instance; 510 | 511 | // 调用命令处理函数 512 | await command.handler.call(plugin, sender, match); 513 | } 514 | 515 | // 记录命令使用 516 | this.recordCommandUsage(command.pluginName, command.name); 517 | 518 | return true; 519 | } catch (error) { 520 | console.error(`[微信助手核心] 处理命令 ${commandKey} 时出错: ${error.message}`); 521 | } 522 | } 523 | } 524 | 525 | return false; 526 | } 527 | 528 | // 记录命令使用 529 | async recordCommandUsage(pluginName, commandName) { 530 | const today = new Date().toISOString().split('T')[0]; 531 | const statsKey = `stats:${today}`; 532 | 533 | try { 534 | // 获取今日统计 535 | const stats = await configDB.get(statsKey, { commands: {} }); 536 | 537 | // 更新命令计数 538 | const commandKey = `${pluginName}:${commandName}`; 539 | stats.commands[commandKey] = (stats.commands[commandKey] || 0) + 1; 540 | 541 | // 保存统计 542 | await configDB.set(statsKey, stats); 543 | } catch (error) { 544 | console.error(`[微信助手核心] 记录命令使用统计失败: ${error.message}`); 545 | } 546 | } 547 | 548 | // 启用插件 549 | async enablePlugin(pluginName) { 550 | // 检查插件是否已在启用列表中 551 | if (this.config.enabledPlugins.includes(pluginName)) { 552 | return false; // 已经启用 553 | } 554 | 555 | // 检查插件是否存在 556 | const pluginPath = path.join(this.pluginsDir, pluginName, 'index.js'); 557 | if (!fs.existsSync(pluginPath)) { 558 | console.error(`[微信助手核心] 插件不存在: ${pluginName}`); 559 | return false; 560 | } 561 | 562 | // 添加到启用列表 563 | this.config.enabledPlugins.push(pluginName); 564 | await configDB.set('core:config', this.config); 565 | 566 | // 加载插件 567 | return await this.loadPlugin(pluginName); 568 | } 569 | 570 | // 禁用插件 571 | async disablePlugin(pluginName) { 572 | if (this.config.enabledPlugins.includes(pluginName)) { 573 | this.config.enabledPlugins = this.config.enabledPlugins.filter(name => name !== pluginName); 574 | await configDB.set('core:config', this.config); 575 | 576 | // 如果插件已加载,则卸载它 577 | if (this.plugins.has(pluginName)) { 578 | const plugin = this.plugins.get(pluginName).instance; 579 | if (typeof plugin.unload === 'function') { 580 | await plugin.unload(); 581 | } 582 | 583 | // 移除插件的命令 584 | for (const [commandKey, command] of this.commands.entries()) { 585 | if (command.pluginName === pluginName) { 586 | this.commands.delete(commandKey); 587 | } 588 | } 589 | 590 | this.plugins.delete(pluginName); 591 | } 592 | return true; 593 | } 594 | return false; 595 | } 596 | 597 | // 重新加载插件 598 | async reloadPlugin(pluginName) { 599 | // 先卸载 600 | if (this.plugins.has(pluginName)) { 601 | const plugin = this.plugins.get(pluginName).instance; 602 | if (typeof plugin.unload === 'function') { 603 | await plugin.unload(); 604 | } 605 | 606 | // 移除插件的命令 607 | for (const [commandKey, command] of this.commands.entries()) { 608 | if (command.pluginName === pluginName) { 609 | this.commands.delete(commandKey); 610 | } 611 | } 612 | 613 | this.plugins.delete(pluginName); 614 | } 615 | 616 | // 检查插件是否启用 617 | if (!this.config.enabledPlugins.includes(pluginName)) { 618 | console.log(`[微信助手核心] 插件未启用,无法重新加载: ${pluginName}`); 619 | return false; 620 | } 621 | 622 | // 重新加载 623 | return await this.loadPlugin(pluginName); 624 | } 625 | } 626 | 627 | // 创建核心实例 628 | const core = new WechatAssistantCore(); 629 | 630 | // 导出模块 631 | module.exports = async (sender) => { 632 | // 如果尚未初始化,则初始化 633 | if (!core.initialized) { 634 | core.initialized = await core.initialize(); 635 | } 636 | 637 | const message = sender.getMsg(); 638 | const userId = sender.getUserId(); 639 | 640 | // 处理插件管理命令 - 使用权限管理器检查 641 | if (message.startsWith('/plugins')) { 642 | // 检查管理员权限 643 | const isAdmin = core.permissionManager.isAdmin(userId); 644 | if (!isAdmin) { 645 | await sender.reply('您没有管理员权限,无法执行插件管理命令'); 646 | return; 647 | } 648 | 649 | const match = message.match(/^\/plugins (list|enable|disable|reload)( .+)?$/); 650 | if (match) { 651 | const action = match[1]; 652 | const pluginName = match[2] ? match[2].trim() : null; 653 | 654 | switch (action) { 655 | case 'list': 656 | // 列出所有插件 657 | try { 658 | // 读取插件目录中的所有文件夹 659 | const pluginFolders = fs.readdirSync(core.pluginsDir).filter(file => { 660 | return fs.statSync(path.join(core.pluginsDir, file)).isDirectory(); 661 | }); 662 | 663 | const pluginList = []; 664 | 665 | // 遍历插件目录 666 | for (const folderName of pluginFolders) { 667 | try { 668 | const pluginPath = path.join(core.pluginsDir, folderName, 'index.js'); 669 | 670 | // 检查插件文件是否存在 671 | if (!fs.existsSync(pluginPath)) { 672 | continue; 673 | } 674 | 675 | // 如果插件已加载,使用已加载的信息 676 | if (core.plugins.has(folderName)) { 677 | const plugin = core.plugins.get(folderName); 678 | pluginList.push(`【${folderName}】 ${plugin.meta.description} (v${plugin.meta.version}) - ${core.config.enabledPlugins.includes(folderName) ? '已启用' : '已禁用'}`); 679 | } else { 680 | // 否则尝试读取插件信息 681 | try { 682 | const requireCache = require.cache[require.resolve(pluginPath)]; 683 | if (requireCache) { 684 | delete require.cache[require.resolve(pluginPath)]; 685 | } 686 | 687 | const plugin = require(pluginPath); 688 | if (plugin.meta) { 689 | pluginList.push(`【${folderName}】 ${plugin.meta.description || '无描述'} (v${plugin.meta.version || '1.0.0'}) - ${core.config.enabledPlugins.includes(folderName) ? '已启用' : '未启用'}`); 690 | } else { 691 | pluginList.push(`【${folderName}】 无法获取描述信息 - ${core.config.enabledPlugins.includes(folderName) ? '已启用' : '未启用'}`); 692 | } 693 | } catch (error) { 694 | pluginList.push(`【${folderName}】 插件加载错误: ${error.message} - ${core.config.enabledPlugins.includes(folderName) ? '已启用' : '未启用'}`); 695 | } 696 | } 697 | } catch (error) { 698 | console.error(`[微信助手核心] 获取插件 ${folderName} 信息失败: ${error.message}`); 699 | } 700 | } 701 | 702 | if (pluginList.length === 0) { 703 | await sender.reply('当前没有任何可用插件'); 704 | } else { 705 | await sender.reply(`可用插件列表:\n${pluginList.join('\n')}`); 706 | } 707 | } catch (error) { 708 | console.error(`[微信助手核心] 列出插件时出错: ${error.message}`); 709 | await sender.reply(`列出插件时出错: ${error.message}`); 710 | } 711 | break; 712 | 713 | case 'enable': 714 | // 启用插件 715 | if (!pluginName) { 716 | await sender.reply('请指定要启用的插件名'); 717 | return; 718 | } 719 | 720 | const enableResult = await core.enablePlugin(pluginName); 721 | await sender.reply(enableResult ? `插件 ${pluginName} 已启用` : `启用插件 ${pluginName} 失败或已启用`); 722 | break; 723 | 724 | case 'disable': 725 | // 禁用插件 726 | if (!pluginName) { 727 | await sender.reply('请指定要禁用的插件名'); 728 | return; 729 | } 730 | 731 | const disableResult = await core.disablePlugin(pluginName); 732 | await sender.reply(disableResult ? `插件 ${pluginName} 已禁用` : `禁用插件 ${pluginName} 失败或已禁用`); 733 | break; 734 | 735 | case 'reload': 736 | // 重新加载插件 737 | if (!pluginName) { 738 | await sender.reply('请指定要重新加载的插件名'); 739 | return; 740 | } 741 | 742 | const reloadResult = await core.reloadPlugin(pluginName); 743 | await sender.reply(reloadResult ? `插件 ${pluginName} 已重新加载` : `重新加载插件 ${pluginName} 失败`); 744 | break; 745 | } 746 | 747 | return; 748 | } 749 | } 750 | 751 | // 处理普通消息 752 | return await core.handleMessage(sender); 753 | }; -------------------------------------------------------------------------------- /智能助手.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author shuaijin 3 | * @name 智能助手 4 | * @team shuaijin 5 | * @origin bncr 6 | * @version 1.0.3 7 | * @description 智能微信助手,支持天气播报、AI对话、每日提醒等功能 8 | * @rule ^/plugins (list|enable|disable|reload)( .+)?$ 9 | * @rule ^/help$ 10 | * @rule ^/weather (.+)$ 11 | * @rule ^/forecast (.+)$ 12 | * @rule ^/chat (.+)$ 13 | * @rule ^/subscribe$ 14 | * @rule ^/unsubscribe$ 15 | * @rule ^/config (list|set|get|del)( .+)?$ 16 | * @rule ^/model (list|use|config)( .+)?$ 17 | * @rule ^/speedtest( info)?$ 18 | * @rule ^/speedtest config (.+) (.+)$ 19 | * @rule ^/api help$ 20 | * @rule ^/api list$ 21 | * @rule ^/api ([a-zA-Z0-9_]+)$ 22 | * @rule ^/clear$ 23 | * @rule ^/admin list$ 24 | * @rule ^/admin add (\S+)$ 25 | * @rule ^/admin remove (\S+)$ 26 | * @rule ^/weconfig$ 27 | * @rule ^/weconfig set (.+) (.+)$ 28 | * @rule ^/weconfig get (.+)$ 29 | * @rule ^/天气配置$ 30 | * @rule ^/天气配置 (show|set|reset|api-help)( .+)?$ 31 | * @rule ^/weather_config$ 32 | * @rule ^/weather_config (show|set|reset|api-help)( .+)?$ 33 | * @admin false 34 | * @public true 35 | * @priority 100 36 | * @disable false 37 | * @cron 0 0 7 * * * 38 | * @classification 工具 39 | */ 40 | 41 | // 导入所需模块 42 | const fs = require('fs'); 43 | const path = require('path'); 44 | 45 | // 获取全局变量 46 | const sysMethod = global.sysMethod; 47 | const router = sysMethod.router; 48 | const BncrDB = global.BncrDB; 49 | 50 | // 创建配置Schema 51 | const jsonSchema = BncrCreateSchema.object({ 52 | // 天气插件配置 53 | weather: BncrCreateSchema.object({ 54 | enable: BncrCreateSchema.boolean() 55 | .setTitle('启用状态') 56 | .setDescription('是否启用天气插件') 57 | .setDefault(true), 58 | api: BncrCreateSchema.string() 59 | .setTitle('天气API') 60 | .setDescription('选择天气API提供商,支持amap(高德地图)或openweather') 61 | .setDefault('amap') 62 | .setEnum(['amap', 'openweather']), 63 | key: BncrCreateSchema.string() 64 | .setTitle('API密钥') 65 | .setDescription('API提供商的密钥,需要在对应平台申请') 66 | .setDefault(''), 67 | defaultCity: BncrCreateSchema.string() 68 | .setTitle('默认城市') 69 | .setDescription('默认查询的城市,如北京、上海等') 70 | .setDefault('北京'), 71 | showAIAdvice: BncrCreateSchema.boolean() 72 | .setTitle('显示AI建议') 73 | .setDescription('是否在天气信息中显示AI生成的生活建议') 74 | .setDefault(false) 75 | }).setTitle('天气插件设置'), 76 | 77 | // AI聊天插件配置 78 | 'ai-chat': BncrCreateSchema.object({ 79 | enable: BncrCreateSchema.boolean() 80 | .setTitle('启用状态') 81 | .setDescription('是否启用AI聊天插件') 82 | .setDefault(true), 83 | defaultModel: BncrCreateSchema.string() 84 | .setTitle('默认模型') 85 | .setDescription('默认使用的AI模型ID,必须与下方"模型列表"中的某个模型ID一致') 86 | .setDefault('deepseek'), 87 | event: BncrCreateSchema.array( 88 | BncrCreateSchema.object({ 89 | enable: BncrCreateSchema.boolean().setTitle('启用').setDescription('是否启用此模型').setDefault(true), 90 | rule: BncrCreateSchema.object({ 91 | id: BncrCreateSchema.string().setTitle('模型ID').setDescription('模型唯一标识符,用于引用此模型').setDefault(''), 92 | name: BncrCreateSchema.string().setTitle('模型名称').setDescription('模型的显示名称').setDefault(''), 93 | url: BncrCreateSchema.string().setTitle('接口地址').setDescription('API接口地址,例如: https://api.deepseek.com/v1').setDefault(''), 94 | apiKey: BncrCreateSchema.string().setTitle('API密钥').setDescription('访问模型所需的API密钥').setDefault(''), 95 | model: BncrCreateSchema.string().setTitle('模型标识').setDescription('部分API需要指定具体模型名称').setDefault(''), 96 | }), 97 | })).setTitle('模型列表') 98 | .setDescription('配置可用的AI聊天模型列表') 99 | .setDefault([ 100 | { 101 | enable: false, 102 | rule:{ 103 | id: 'openai', 104 | name: 'OpenAI', 105 | url: 'https://api.openai.com/v1', 106 | apiKey: '', 107 | model: '' 108 | }}, 109 | { 110 | enable: true, 111 | rule:{ 112 | id: 'deepseek', 113 | name: 'DeepSeek', 114 | url: 'https://api.deepseek.com/v1', 115 | apiKey: '', 116 | model: '' 117 | }}, 118 | { 119 | enable: false, 120 | rule:{ 121 | id: 'siliconflow', 122 | name: 'SiliconFlow', 123 | url: 'https://api.siliconflow.com/v1', 124 | apiKey: '', 125 | model: 'deepseek-ai/DeepSeek-V3' 126 | }} 127 | ]) 128 | }).setTitle('AI聊天插件设置'), 129 | 130 | // 每日提醒插件配置 131 | 'morning-alert': BncrCreateSchema.object({ 132 | enable: BncrCreateSchema.boolean() 133 | .setTitle('启用状态') 134 | .setDescription('是否启用每日提醒插件') 135 | .setDefault(false), 136 | time: BncrCreateSchema.string() 137 | .setTitle('提醒时间') 138 | .setDescription('每日提醒的时间,格式为HH:MM') 139 | .setDefault('07:00') 140 | }).setTitle('每日提醒插件设置'), 141 | 142 | // AI模型测速插件 143 | 'ai-speedtest': BncrCreateSchema.object({ 144 | enable: BncrCreateSchema.boolean() 145 | .setTitle('启用状态') 146 | .setDescription('是否启用AI模型测速插件') 147 | .setDefault(true), 148 | interval: BncrCreateSchema.number() 149 | .setTitle('测试间隔') 150 | .setDescription('自动测试的间隔(分钟)') 151 | .setDefault(60) 152 | }).setTitle('AI模型测速插件'), 153 | 154 | // API工具箱插件 155 | 'api-toolkit': BncrCreateSchema.object({ 156 | enable: BncrCreateSchema.boolean() 157 | .setTitle('启用状态') 158 | .setDescription('是否启用API工具箱插件') 159 | .setDefault(true) 160 | }).setTitle('API工具箱插件'), 161 | 162 | // 管理员用户列表 163 | adminUsers: BncrCreateSchema.string() 164 | .setTitle('管理员用户') 165 | .setDescription('管理员用户ID,多个用户用逗号分隔') 166 | .setDefault('') 167 | }); 168 | 169 | // 创建配置管理器 - 使用纯Schema方式 170 | const ConfigDB = new BncrPluginConfig(jsonSchema); 171 | 172 | // 初始化配置 173 | let config = null; 174 | 175 | // 导出插件 176 | module.exports = async (sender) => { 177 | try { 178 | // 初始化配置 179 | if (!config) { 180 | config = await initConfig(); 181 | } 182 | 183 | // 处理配置命令 184 | if (sender.getMsg().startsWith('/weconfig')) { 185 | return await handleConfigCommand(sender); 186 | } 187 | 188 | // 处理其他命令 189 | const msg = sender.getMsg(); 190 | const userId = sender.getUserId(); 191 | 192 | // 检查是否为管理员 193 | const isAdmin = config.adminUsers && config.adminUsers.split(',').includes(userId); 194 | 195 | // 处理插件管理命令 196 | if (msg.startsWith('/plugins')) { 197 | if (!isAdmin) { 198 | await sender.reply('您没有管理员权限,无法执行插件管理命令'); 199 | return true; 200 | } 201 | 202 | const match = msg.match(/^\/plugins (list|enable|disable|reload)( .+)?$/); 203 | if (match) { 204 | const action = match[1]; 205 | const pluginName = match[2] ? match[2].trim() : null; 206 | 207 | switch (action) { 208 | case 'list': 209 | // 列出所有插件 210 | const pluginList = []; 211 | for (const pluginName of ['weather', 'ai-chat', 'morning-alert', 'ai-speedtest', 'api-toolkit']) { 212 | const settings = getPluginConfig(pluginName); 213 | pluginList.push(`【${pluginName}】 ${settings.enable ? '已启用' : '已禁用'}`); 214 | } 215 | await sender.reply(`可用插件列表:\n${pluginList.join('\n')}`); 216 | break; 217 | 218 | case 'enable': 219 | // 启用插件 220 | if (!pluginName) { 221 | await sender.reply('请指定要启用的插件名'); 222 | return true; 223 | } 224 | 225 | const pluginConfig = getPluginConfig(pluginName); 226 | if (pluginConfig) { 227 | pluginConfig.enable = true; 228 | await ConfigDB.set(config); 229 | await sender.reply(`插件 ${pluginName} 已启用`); 230 | } else { 231 | await sender.reply(`插件 ${pluginName} 不存在`); 232 | } 233 | break; 234 | 235 | case 'disable': 236 | // 禁用插件 237 | if (!pluginName) { 238 | await sender.reply('请指定要禁用的插件名'); 239 | return true; 240 | } 241 | 242 | const pluginConfig2 = getPluginConfig(pluginName); 243 | if (pluginConfig2) { 244 | pluginConfig2.enable = false; 245 | await ConfigDB.set(config); 246 | await sender.reply(`插件 ${pluginName} 已禁用`); 247 | } else { 248 | await sender.reply(`插件 ${pluginName} 不存在`); 249 | } 250 | break; 251 | 252 | case 'reload': 253 | // 重新加载插件 254 | if (!pluginName) { 255 | await sender.reply('请指定要重新加载的插件名'); 256 | return true; 257 | } 258 | 259 | const pluginConfig3 = getPluginConfig(pluginName); 260 | if (pluginConfig3) { 261 | // 重新加载配置 262 | config = await initConfig(); 263 | await sender.reply(`插件 ${pluginName} 已重新加载`); 264 | } else { 265 | await sender.reply(`插件 ${pluginName} 不存在`); 266 | } 267 | break; 268 | } 269 | return true; 270 | } 271 | } 272 | 273 | // 处理管理员命令 274 | if (msg.startsWith('/admin')) { 275 | if (!isAdmin) { 276 | await sender.reply('您没有管理员权限,无法执行管理员命令'); 277 | return true; 278 | } 279 | 280 | const match = msg.match(/^\/admin (list|add|remove)( .+)?$/); 281 | if (match) { 282 | const action = match[1]; 283 | const userId = match[2] ? match[2].trim() : null; 284 | 285 | switch (action) { 286 | case 'list': 287 | // 列出所有管理员 288 | if (config.adminUsers && config.adminUsers.length > 0) { 289 | await sender.reply(`管理员列表:\n${config.adminUsers}`); 290 | } else { 291 | await sender.reply('当前没有配置任何管理员'); 292 | } 293 | break; 294 | 295 | case 'add': 296 | // 添加管理员 297 | if (!userId) { 298 | await sender.reply('请指定要添加的用户ID'); 299 | return true; 300 | } 301 | 302 | if (config.adminUsers && config.adminUsers.split(',').includes(userId)) { 303 | await sender.reply(`用户 ${userId} 已经是管理员`); 304 | return true; 305 | } 306 | 307 | config.adminUsers = config.adminUsers ? `${config.adminUsers},${userId}` : userId; 308 | await ConfigDB.set(config); 309 | await sender.reply(`已成功添加管理员:${userId}`); 310 | break; 311 | 312 | case 'remove': 313 | // 移除管理员 314 | if (!userId) { 315 | await sender.reply('请指定要移除的用户ID'); 316 | return true; 317 | } 318 | 319 | if (!config.adminUsers || !config.adminUsers.split(',').includes(userId)) { 320 | await sender.reply(`用户 ${userId} 不是管理员`); 321 | return true; 322 | } 323 | 324 | config.adminUsers = config.adminUsers.split(',').filter(id => id !== userId).join(','); 325 | await ConfigDB.set(config); 326 | await sender.reply(`已成功移除管理员:${userId}`); 327 | break; 328 | } 329 | return true; 330 | } 331 | } 332 | 333 | // 处理帮助命令 334 | if (msg === '/help') { 335 | return await handleMessageLegacy(sender); 336 | } 337 | 338 | // 转发到相应的子插件处理 339 | // 天气插件命令 340 | if (msg.startsWith('/weather') || msg.startsWith('/forecast') || 341 | msg.startsWith('/天气配置') || msg.startsWith('/weather_config')) { 342 | const weatherConfig = getPluginConfig('weather'); 343 | if (weatherConfig && weatherConfig.enable) { 344 | try { 345 | const weatherPlugin = require('./plugins/weather/index.js'); 346 | // 检查插件导出格式 347 | if (typeof weatherPlugin === 'function') { 348 | return await weatherPlugin(sender); 349 | } else if (weatherPlugin && typeof weatherPlugin.main === 'function') { 350 | return await weatherPlugin.main(sender); 351 | } else if (weatherPlugin && weatherPlugin.meta) { 352 | // 插件使用exports对象格式,需要直接处理命令 353 | console.log('[智能助手] 天气插件使用exports格式'); 354 | 355 | // 获取查询的城市名称或处理配置命令 356 | let city = ''; 357 | let isConfigCommand = false; 358 | 359 | if (msg.startsWith('/天气配置') || msg.startsWith('/weather_config')) { 360 | isConfigCommand = true; 361 | } else if (msg.startsWith('/weather')) { 362 | city = msg.substring('/weather'.length).trim(); 363 | } else if (msg.startsWith('/forecast')) { 364 | city = msg.substring('/forecast'.length).trim(); 365 | } 366 | 367 | // 确保插件配置正确 368 | if (!weatherPlugin.config) { 369 | weatherPlugin.config = weatherConfig; 370 | } else { 371 | // 确保关键配置项存在 372 | if (weatherConfig.key && !weatherPlugin.config.key) { 373 | weatherPlugin.config.key = weatherConfig.key; 374 | } 375 | if (weatherConfig.api && !weatherPlugin.config.api) { 376 | weatherPlugin.config.api = weatherConfig.api; 377 | } 378 | if (weatherConfig.defaultCity && !weatherPlugin.config.defaultCity) { 379 | weatherPlugin.config.defaultCity = weatherConfig.defaultCity; 380 | } 381 | if (weatherConfig.showAIAdvice !== undefined) { 382 | weatherPlugin.config.showAIAdvice = weatherConfig.showAIAdvice; 383 | } 384 | } 385 | 386 | // 处理不同类型的命令 387 | if (isConfigCommand) { 388 | // 处理配置命令 389 | if (typeof weatherPlugin.onMessage === 'function') { 390 | try { 391 | console.log(`[智能助手] 调用天气插件的onMessage方法处理配置命令: ${msg}`); 392 | 393 | // 获取core对象 - 修复获取方式 394 | let core = null; 395 | if (global.sysMethod && global.sysMethod.router) { 396 | core = global.sysMethod.router.core; 397 | } else if (global.core) { 398 | core = global.core; 399 | } else if (router && router.core) { 400 | core = router.core; 401 | } 402 | 403 | // 确保core对象存在 404 | if (!core) { 405 | console.warn('[智能助手] 无法获取core对象,尝试使用sysMethod作为替代'); 406 | core = global.sysMethod; 407 | } 408 | 409 | // 添加插件相关信息到sender对象 410 | sender.plugin = { 411 | config: config, 412 | core: core // 传递core对象给插件 413 | }; 414 | 415 | // 确保消息内容是有效的字符串 416 | if (!sender.content && typeof sender.getMsg === 'function') { 417 | sender.content = sender.getMsg(); 418 | } 419 | 420 | // 传递消息到插件,并获取响应 421 | const response = await weatherPlugin.onMessage(sender); 422 | 423 | // 如果插件返回了响应,显示给用户 424 | if (response) { 425 | await sender.reply(response); 426 | } 427 | return true; 428 | } catch (configError) { 429 | console.error(`[智能助手] 处理天气配置命令出错: ${configError.message}`); 430 | console.error(configError.stack); // 打印完整错误堆栈 431 | await sender.reply(`处理天气配置命令出错: ${configError.message}`); 432 | return true; 433 | } 434 | } else { 435 | await sender.reply('天气插件未实现配置功能,请检查插件版本。'); 436 | return true; 437 | } 438 | } else { 439 | // 处理天气查询命令 440 | if (!city && weatherConfig.defaultCity) { 441 | city = weatherConfig.defaultCity; 442 | } 443 | 444 | if (!city) { 445 | await sender.reply('请指定城市名称,例如: /weather 北京'); 446 | return true; 447 | } 448 | 449 | // 发送"正在查询"提示 450 | const loadingMsg = await sender.reply(`⏳ 正在查询${city}的天气,请稍候...`); 451 | 452 | try { 453 | // 优先使用handleWeatherCommand方法 454 | if (typeof weatherPlugin.handleWeatherCommand === 'function') { 455 | console.log(`[智能助手] 调用天气插件的handleWeatherCommand方法查询: ${city}`); 456 | // 添加发送方信息,包含插件对象 457 | sender.plugin = { 458 | config: config 459 | }; 460 | const weatherResult = await weatherPlugin.handleWeatherCommand(city, sender); 461 | 462 | // 删除加载消息 463 | if (loadingMsg) { 464 | await sender.delMsg(loadingMsg); 465 | } 466 | 467 | // 发送天气信息 468 | await sender.reply(weatherResult); 469 | return true; 470 | } 471 | // 备用:调用getWeather方法 472 | else if (typeof weatherPlugin.getWeather === 'function') { 473 | console.log(`[智能助手] 调用天气插件的getWeather方法查询: ${city}`); 474 | const weatherResult = await weatherPlugin.getWeather(city); 475 | 476 | // 删除加载消息 477 | if (loadingMsg) { 478 | await sender.delMsg(loadingMsg); 479 | } 480 | 481 | // 发送天气信息 482 | await sender.reply(weatherResult); 483 | return true; 484 | } else { 485 | if (loadingMsg) { 486 | await sender.delMsg(loadingMsg); 487 | } 488 | await sender.reply('天气插件未正确导出天气查询方法,无法查询天气。'); 489 | return true; 490 | } 491 | } catch (error) { 492 | console.error(`[智能助手] 查询天气出错: ${error.message}`); 493 | 494 | // 删除加载消息 495 | if (loadingMsg) { 496 | await sender.delMsg(loadingMsg); 497 | } 498 | 499 | await sender.reply(`查询天气失败: ${error.message}`); 500 | return true; 501 | } 502 | } 503 | } else { 504 | console.error('[智能助手] 天气插件格式不兼容'); 505 | await sender.reply('天气查询功能暂时不可用,请联系管理员检查插件格式。'); 506 | return true; 507 | } 508 | } catch (error) { 509 | console.error('[智能助手] 加载天气插件出错:', error); 510 | await sender.reply('天气查询功能暂时不可用,请稍后再试。'); 511 | return true; 512 | } 513 | } else { 514 | await sender.reply('天气插件未启用,请联系管理员启用此功能。'); 515 | return true; 516 | } 517 | } 518 | 519 | // AI聊天插件命令 520 | if (msg.startsWith('/chat') || msg.startsWith('/model') || msg === '/clear') { 521 | const aiChatConfig = getPluginConfig('ai-chat'); 522 | if (aiChatConfig && aiChatConfig.enable) { 523 | try { 524 | const aiChatPlugin = require('./plugins/ai-chat/index.js'); 525 | // 检查插件导出格式 526 | if (typeof aiChatPlugin === 'function') { 527 | return await aiChatPlugin(sender); 528 | } else if (aiChatPlugin && typeof aiChatPlugin.main === 'function') { 529 | return await aiChatPlugin.main(sender); 530 | } else if (aiChatPlugin && aiChatPlugin.meta) { 531 | // 插件使用exports对象格式,需要创建处理函数 532 | console.log('[智能助手] AI聊天插件使用exports格式'); 533 | if (msg.startsWith('/chat')) { 534 | const content = msg.slice(6).trim(); 535 | await sender.reply(`抱歉,AI聊天组件格式不兼容,无法处理您的请求: ${content}`); 536 | } else if (msg.startsWith('/model')) { 537 | await sender.reply('抱歉,AI聊天组件格式不兼容,无法处理模型管理请求。'); 538 | } else if (msg === '/clear') { 539 | await sender.reply('抱歉,AI聊天组件格式不兼容,无法清除聊天历史。'); 540 | } 541 | return true; 542 | } else { 543 | console.error('[智能助手] AI聊天插件格式不兼容'); 544 | await sender.reply('AI聊天功能暂时不可用,请联系管理员检查插件格式。'); 545 | return true; 546 | } 547 | } catch (error) { 548 | console.error('[智能助手] 加载AI聊天插件出错:', error); 549 | await sender.reply('AI聊天功能暂时不可用,请稍后再试。'); 550 | return true; 551 | } 552 | } else { 553 | await sender.reply('AI聊天插件未启用,请联系管理员启用此功能。'); 554 | return true; 555 | } 556 | } 557 | 558 | // 早间提醒插件命令 559 | if (msg === '/subscribe' || msg === '/unsubscribe') { 560 | const morningAlertConfig = getPluginConfig('morning-alert'); 561 | if (morningAlertConfig && morningAlertConfig.enable) { 562 | try { 563 | const morningAlertPlugin = require('./plugins/morning-alert/index.js'); 564 | // 检查插件导出格式 565 | if (typeof morningAlertPlugin === 'function') { 566 | return await morningAlertPlugin(sender); 567 | } else if (morningAlertPlugin && typeof morningAlertPlugin.main === 'function') { 568 | return await morningAlertPlugin.main(sender); 569 | } else if (morningAlertPlugin && morningAlertPlugin.meta) { 570 | // 插件使用exports对象格式,提供临时响应 571 | console.log('[智能助手] 早间提醒插件使用exports格式'); 572 | if (msg === '/subscribe') { 573 | await sender.reply('抱歉,早间提醒组件格式不兼容,无法订阅提醒。'); 574 | } else if (msg === '/unsubscribe') { 575 | await sender.reply('抱歉,早间提醒组件格式不兼容,无法取消订阅。'); 576 | } 577 | return true; 578 | } else { 579 | console.error('[智能助手] 早间提醒插件格式不兼容'); 580 | await sender.reply('早间提醒功能暂时不可用,请联系管理员检查插件格式。'); 581 | return true; 582 | } 583 | } catch (error) { 584 | console.error('[智能助手] 加载早间提醒插件出错:', error); 585 | await sender.reply('早间提醒功能暂时不可用,请稍后再试。'); 586 | return true; 587 | } 588 | } else { 589 | await sender.reply('早间提醒插件未启用,请联系管理员启用此功能。'); 590 | return true; 591 | } 592 | } 593 | 594 | // AI速度测试插件命令 595 | if (msg.startsWith('/speedtest')) { 596 | const speedtestConfig = getPluginConfig('ai-speedtest'); 597 | if (speedtestConfig && speedtestConfig.enable) { 598 | try { 599 | const speedtestPlugin = require('./plugins/ai-speedtest/index.js'); 600 | // 检查插件导出格式 601 | if (typeof speedtestPlugin === 'function') { 602 | return await speedtestPlugin(sender); 603 | } else if (speedtestPlugin && typeof speedtestPlugin.main === 'function') { 604 | return await speedtestPlugin.main(sender); 605 | } else if (speedtestPlugin && speedtestPlugin.meta) { 606 | // 插件使用exports对象格式,通过runSpeedTest方法处理 607 | console.log('[智能助手] AI速度测试插件使用exports对象格式'); 608 | 609 | if (speedtestPlugin.runSpeedTest) { 610 | if (msg === '/speedtest' || msg === '/speedtest ') { 611 | await sender.reply('正在测试AI模型速度,请稍候...'); 612 | try { 613 | // 直接调用runSpeedTest方法进行测试 614 | await speedtestPlugin.runSpeedTest(sender); 615 | return true; 616 | } catch (testError) { 617 | console.error('[智能助手] 运行速度测试出错:', testError); 618 | await sender.reply(`运行速度测试出错: ${testError.message}`); 619 | return true; 620 | } 621 | } else if (msg.startsWith('/speedtest info')) { 622 | // 返回测试信息 623 | if (speedtestPlugin.config) { 624 | const lastTestTime = speedtestPlugin.config.lastTestTime ? 625 | new Date(speedtestPlugin.config.lastTestTime).toLocaleString() : 626 | '从未测试'; 627 | const currentFastest = speedtestPlugin.config.currentFastest || '未知'; 628 | 629 | const infoText = `📊 AI模型速度测试信息: 630 | 上次测试时间: ${lastTestTime} 631 | 当前最快模型: ${currentFastest} 632 | 测试间隔: ${(speedtestPlugin.config.testInterval || 3600000) / 60000} 分钟 633 | 自动切换: ${speedtestPlugin.config.autoSwitch ? '已启用' : '已禁用'}`; 634 | 635 | await sender.reply(infoText); 636 | } else { 637 | await sender.reply('无法获取测速配置信息。'); 638 | } 639 | return true; 640 | } 641 | } else { 642 | await sender.reply('AI速度测试插件格式不兼容,未找到runSpeedTest方法。'); 643 | return true; 644 | } 645 | } else { 646 | console.error('[智能助手] AI速度测试插件格式不兼容'); 647 | await sender.reply('AI速度测试功能暂时不可用,请联系管理员检查插件格式。'); 648 | return true; 649 | } 650 | } catch (error) { 651 | console.error('[智能助手] 加载AI速度测试插件出错:', error); 652 | await sender.reply('AI速度测试功能暂时不可用,请稍后再试。'); 653 | return true; 654 | } 655 | } else { 656 | await sender.reply('AI速度测试插件未启用,请联系管理员启用此功能。'); 657 | return true; 658 | } 659 | } 660 | 661 | // API工具箱插件命令 662 | if (msg.startsWith('/api')) { 663 | const apiToolkitConfig = getPluginConfig('api-toolkit'); 664 | if (apiToolkitConfig && apiToolkitConfig.enable) { 665 | try { 666 | const apiToolkitPlugin = require('./plugins/api-toolkit/index.js'); 667 | // 检查插件导出格式 668 | if (typeof apiToolkitPlugin === 'function') { 669 | return await apiToolkitPlugin(sender); 670 | } else if (apiToolkitPlugin && typeof apiToolkitPlugin.main === 'function') { 671 | return await apiToolkitPlugin.main(sender); 672 | } else if (apiToolkitPlugin && apiToolkitPlugin.meta) { 673 | console.log(`[智能助手] API工具箱插件使用exports格式`); 674 | 675 | // 加载API工具箱配置 676 | let apiConfig; 677 | 678 | // 1. 首先尝试使用插件内置的loadConfig方法 679 | if (apiToolkitPlugin.loadConfig && typeof apiToolkitPlugin.loadConfig === 'function') { 680 | console.log(`[智能助手][调试] 使用插件内置的loadConfig方法`); 681 | apiConfig = apiToolkitPlugin.loadConfig(); 682 | } else { 683 | // 2. 否则尝试自己加载配置文件 684 | apiConfig = loadApiConfig(); 685 | } 686 | 687 | // 处理API命令 688 | if (msg === '/api help') { 689 | // 显示帮助信息 690 | const helpText = apiToolkitPlugin.generateHelpText ? 691 | apiToolkitPlugin.generateHelpText(apiConfig) : 692 | `API工具箱使用指南: 693 | /api list - 查看可用API列表 694 | /api <名称> - 调用指定API`; 695 | 696 | await sender.reply(helpText); 697 | return true; 698 | } else if (msg === '/api list') { 699 | // 显示API列表 700 | const listText = apiToolkitPlugin.generateAPIListText ? 701 | apiToolkitPlugin.generateAPIListText(apiConfig) : 702 | '抱歉,无法获取API列表。'; 703 | 704 | await sender.reply(listText); 705 | return true; 706 | } else if (msg.match(/^\/api ([a-zA-Z0-9_]+)$/)) { 707 | // 调用指定API 708 | const apiKey = msg.split(' ')[1].trim(); 709 | 710 | console.log(`[智能助手][调试] 准备调用API: ${apiKey}`); 711 | // 检查apiConfig中是否存在该API 712 | if (apiConfig.apis && apiConfig.apis[apiKey]) { 713 | console.log(`[智能助手][调试] 找到API: ${apiKey}, URL: ${apiConfig.apis[apiKey].url}`); 714 | } else { 715 | console.warn(`[智能助手][调试] 警告: 配置中不存在API: ${apiKey}`); 716 | console.log(`[智能助手][调试] 可用API列表: ${Object.keys(apiConfig.apis || {}).join(', ')}`); 717 | } 718 | 719 | // 关键修改: 首先检查插件是否提供了handleAPICommand方法 720 | if (apiToolkitPlugin.handleAPICommand && typeof apiToolkitPlugin.handleAPICommand === 'function') { 721 | console.log(`[智能助手][调试] 使用插件提供的handleAPICommand方法`); 722 | try { 723 | await apiToolkitPlugin.handleAPICommand(apiKey, sender, apiConfig); 724 | console.log(`[智能助手][调试] API命令处理完成: ${apiKey}`); 725 | return true; 726 | } catch (error) { 727 | console.error(`[智能助手][调试] 调用API时出错: ${error.message}`); 728 | await sender.reply(`调用API时出错: ${error.message}`); 729 | return true; 730 | } 731 | } else { 732 | console.error(`[智能助手][调试] 插件未提供handleAPICommand方法`); 733 | await sender.reply(`抱歉,API工具箱插件未正确导出handleAPICommand方法,无法调用API: ${apiKey}`); 734 | return true; 735 | } 736 | } 737 | 738 | // 其他API相关命令 739 | await sender.reply('抱歉,无法识别的API命令。请使用 /api help 查看帮助。'); 740 | return true; 741 | } else { 742 | console.error('[智能助手] API工具箱插件格式不兼容'); 743 | await sender.reply('API工具箱功能暂时不可用,请联系管理员检查插件格式。'); 744 | return true; 745 | } 746 | } catch (error) { 747 | console.error('[智能助手] 加载API工具箱插件出错:', error); 748 | await sender.reply('API工具箱功能暂时不可用,请稍后再试。'); 749 | return true; 750 | } 751 | } else { 752 | await sender.reply('API工具箱插件未启用,请联系管理员启用此功能。'); 753 | return true; 754 | } 755 | } 756 | 757 | // 配置命令 758 | if (msg.startsWith('/config')) { 759 | await sender.reply('配置功能已移至 /weconfig 命令,请使用 /weconfig 查看配置。'); 760 | return true; 761 | } 762 | 763 | // 未处理的命令 764 | console.log(`[智能助手] 未处理的命令: ${msg}`); 765 | return false; 766 | } catch (error) { 767 | console.error('[智能助手] 处理消息时出错:', error); 768 | await sender.reply(`处理消息时出错: ${error.message}`); 769 | return false; 770 | } 771 | }; 772 | 773 | // 初始化配置 774 | async function initConfig() { 775 | try { 776 | // 从BNCR Schema读取配置 777 | await ConfigDB.get(); 778 | config = ConfigDB.userConfig; 779 | console.log('[智能助手] 从Schema读取配置成功'); 780 | 781 | // 注册配置更新事件处理器 782 | try { 783 | const core = global.BncrCore; 784 | if (core && typeof core.on === 'function') { 785 | // 注册从插件接收配置更新的事件 786 | core.on('assistant_config_updated', async (updateData) => { 787 | console.log('[智能助手] 收到插件配置更新事件:', Object.keys(updateData)); 788 | 789 | // 合并配置更新 790 | if (updateData.weather) { 791 | console.log('[智能助手] 更新天气插件配置'); 792 | config.weather = {...config.weather, ...updateData.weather}; 793 | await ConfigDB.set(config); 794 | } 795 | 796 | // 可以在这里添加其他插件的配置更新处理 797 | }); 798 | console.log('[智能助手] 已注册配置更新事件处理器'); 799 | } 800 | } catch (eventError) { 801 | console.warn('[智能助手] 注册配置更新事件处理器失败:', eventError); 802 | } 803 | 804 | // 如果配置为空,尝试从本地文件读取一次 805 | if (!config || Object.keys(config).length === 0) { 806 | const configFile = path.join(__dirname, 'config.json'); 807 | if (fs.existsSync(configFile)) { 808 | try { 809 | const fileConfig = JSON.parse(fs.readFileSync(configFile, 'utf8')); 810 | console.log('[智能助手] 从本地文件读取配置成功,将导入到Schema'); 811 | 812 | // 转换旧版配置格式 813 | if (fileConfig.enabledPlugins && Array.isArray(fileConfig.enabledPlugins)) { 814 | // 旧版使用enabledPlugins数组,转换为每个插件的enabled属性 815 | const pluginNames = ['weather', 'ai-chat', 'morning-alert', 'ai-speedtest', 'api-toolkit']; 816 | pluginNames.forEach(name => { 817 | if (!fileConfig[name]) { 818 | fileConfig[name] = {}; 819 | } 820 | // 检查插件是否在启用列表中 821 | fileConfig[name].enable = fileConfig.enabledPlugins.includes(name); 822 | }); 823 | // 可以删除旧的enabledPlugins数组 824 | delete fileConfig.enabledPlugins; 825 | } 826 | 827 | // 转换管理员用户列表为字符串格式 828 | if (fileConfig.adminUsers && Array.isArray(fileConfig.adminUsers)) { 829 | fileConfig.adminUsers = fileConfig.adminUsers.join(','); 830 | } 831 | 832 | // 如果使用旧版AI模型配置,转换为新的数组格式 833 | if (fileConfig['ai-chat'] && fileConfig['ai-chat'].models && !Array.isArray(fileConfig['ai-chat'].models)) { 834 | const oldModels = fileConfig['ai-chat'].models; 835 | const newModels = []; 836 | 837 | // 处理旧版中的每个模型 838 | ['openai', 'deepseek', 'siliconflow'].forEach(id => { 839 | if (oldModels[id]) { 840 | newModels.push({ 841 | id: id, 842 | name: oldModels[id].name || id.charAt(0).toUpperCase() + id.slice(1), 843 | url: oldModels[id].url || `https://api.${id}.com/v1`, 844 | apiKey: oldModels[id].apiKey || '', 845 | enable: oldModels[id].enable || oldModels[id].enabled || false, 846 | model: oldModels[id].model || '', 847 | }); 848 | } 849 | }); 850 | 851 | // 如果没有找到任何模型,添加默认模型 852 | if (newModels.length === 0) { 853 | newModels.push({ 854 | id: 'deepseek', 855 | name: 'DeepSeek', 856 | url: 'https://api.deepseek.com/v1', 857 | apiKey: '', 858 | enable: true, 859 | model: '' 860 | }); 861 | } 862 | 863 | // 更新配置 864 | fileConfig['ai-chat'].models = newModels; 865 | } 866 | 867 | await ConfigDB.set(fileConfig); 868 | config = fileConfig; 869 | } catch (e) { 870 | console.error('[智能助手] 从文件读取配置失败:', e); 871 | } 872 | } 873 | } 874 | 875 | if (!config) { 876 | config = {}; // 确保config是对象 877 | } 878 | 879 | // 确保基本配置项存在 880 | if (!config.adminUsers) config.adminUsers = ''; 881 | 882 | // 确保天气插件配置项完整 883 | if (!config.weather) { 884 | config.weather = { 885 | enable: true, 886 | api: 'amap', 887 | key: '', 888 | defaultCity: '北京', 889 | showAIAdvice: false 890 | }; 891 | } else { 892 | // 确保所有天气配置项存在 893 | if (config.weather.enable === undefined) config.weather.enable = true; 894 | if (!config.weather.api) config.weather.api = 'amap'; 895 | if (!config.weather.key) config.weather.key = ''; 896 | if (!config.weather.defaultCity) config.weather.defaultCity = '北京'; 897 | if (config.weather.showAIAdvice === undefined) config.weather.showAIAdvice = false; 898 | } 899 | 900 | } catch (err) { 901 | console.error('[智能助手] 初始化配置失败:', err); 902 | // 如果Schema方式失败,尝试使用本地文件 903 | config = loadConfigFromFile(); 904 | } 905 | 906 | const enabledPlugins = getEnabledPlugins(); 907 | console.log(`[智能助手] 已加载配置,启用的插件: ${enabledPlugins.join(', ')}`); 908 | return config; 909 | } 910 | 911 | // 备用:从本地文件加载配置 912 | function loadConfigFromFile() { 913 | try { 914 | const configFile = path.join(__dirname, 'config.json'); 915 | if (fs.existsSync(configFile)) { 916 | const configData = fs.readFileSync(configFile, 'utf8'); 917 | return JSON.parse(configData); 918 | } 919 | } catch (err) { 920 | console.error('[智能助手] 从文件加载配置失败:', err); 921 | } 922 | 923 | // 默认配置 924 | return { 925 | adminUsers: '', 926 | pluginSettings: { 927 | weather: { api: 'amap', key: '', defaultCity: '北京', showAIAdvice: false }, 928 | 'ai-chat': { defaultModel: 'deepseek' } 929 | } 930 | }; 931 | } 932 | 933 | // 获取已启用的插件列表 934 | function getEnabledPlugins() { 935 | const pluginNames = ['weather', 'ai-chat', 'morning-alert', 'ai-speedtest', 'api-toolkit']; 936 | return pluginNames.filter(name => { 937 | // 检查插件配置中的enable字段 938 | return config[name] && config[name].enable === true; 939 | }); 940 | } 941 | 942 | // 获取插件配置 943 | function getPluginConfig(pluginName) { 944 | // 先从顶级配置获取 945 | if (config[pluginName]) { 946 | return config[pluginName]; 947 | } 948 | 949 | // 兼容旧版配置:尝试从pluginSettings获取 950 | if (config.pluginSettings && config.pluginSettings[pluginName]) { 951 | // 如果没有enable字段,默认添加为true 952 | const pluginConfig = config.pluginSettings[pluginName]; 953 | if (pluginConfig.enable === undefined) { 954 | pluginConfig.enable = true; 955 | } 956 | return pluginConfig; 957 | } 958 | 959 | // 返回默认配置 960 | return { enable: false }; 961 | } 962 | 963 | // 格式化配置显示 964 | function formatConfig() { 965 | let result = '📋 微信助手配置:\n\n'; 966 | 967 | // 启用的插件 968 | result += '🔌 启用的插件:\n'; 969 | const enabledPlugins = getEnabledPlugins(); 970 | if (enabledPlugins.length > 0) { 971 | enabledPlugins.forEach(plugin => { 972 | result += `- ${plugin}\n`; 973 | }); 974 | } else { 975 | result += '- 无\n'; 976 | } 977 | 978 | // 插件设置 979 | result += '\n⚙️ 插件设置:\n'; 980 | for (const pluginName of ['weather', 'ai-chat', 'morning-alert', 'ai-speedtest', 'api-toolkit']) { 981 | const settings = getPluginConfig(pluginName); 982 | if (settings && Object.keys(settings).length > 0) { 983 | result += `\n💠 ${pluginName} (${settings.enable ? '已启用' : '已禁用'}):\n`; 984 | 985 | // 处理嵌套对象,跳过enable属性 986 | for (const [key, value] of Object.entries(settings)) { 987 | if (key === 'enable') continue; // 跳过enable属性,已在插件名称后显示 988 | 989 | if (key === 'models' && Array.isArray(value)) { 990 | // 特殊处理AI模型列表 991 | result += ` 📊 模型列表:\n`; 992 | value.forEach(model => { 993 | result += ` 🤖 ${model.name} (${model.id}) [${model.enable ? '已启用' : '已禁用'}]\n`; 994 | if (model.url) result += ` 🔗 URL: ${model.url}\n`; 995 | if (model.model) result += ` 📋 模型版本: ${model.model}\n`; 996 | // 不显示apiKey,保护敏感信息 997 | }); 998 | } else if (pluginName === 'weather' && key === 'api') { 999 | // 特殊处理天气API类型,提供友好显示 1000 | let apiDisplay = value; 1001 | if (value === 'amap') { 1002 | apiDisplay = '高德地图 (amap)'; 1003 | } else if (value === 'openweather') { 1004 | apiDisplay = 'OpenWeather (openweather)'; 1005 | } 1006 | result += ` 🔹 ${key}: ${apiDisplay}\n`; 1007 | } else if (pluginName === 'weather' && key === 'key' && value) { 1008 | // 特殊处理API密钥,保护敏感信息 1009 | result += ` 🔹 ${key}: ${value.substring(0, 3)}*****\n`; 1010 | } else if (typeof value === 'object' && value !== null) { 1011 | result += ` 📊 ${key}: [复合配置]\n`; 1012 | } else { 1013 | result += ` 🔹 ${key}: ${value}\n`; 1014 | } 1015 | } 1016 | } 1017 | } 1018 | 1019 | // 管理员用户 1020 | result += '\n👤 管理员用户:\n'; 1021 | if (config.adminUsers && config.adminUsers.length > 0) { 1022 | result += `- ${config.adminUsers}\n`; 1023 | } else { 1024 | result += '- 无\n'; 1025 | } 1026 | 1027 | return result; 1028 | } 1029 | 1030 | // 处理配置命令 1031 | async function handleConfigCommand(sender) { 1032 | try { 1033 | const msg = sender.getMsg(); 1034 | const param1 = sender.param(1); // 子命令(set/get) 1035 | const param2 = sender.param(2); // 键 1036 | const param3 = sender.param(3); // 值(如果是set命令) 1037 | 1038 | console.log(`[智能助手] 收到配置命令: ${msg}`); 1039 | 1040 | // 处理命令 1041 | if (msg === '/weconfig') { 1042 | // 显示所有配置 1043 | const configText = formatConfig(); 1044 | await sender.reply(configText); 1045 | return true; 1046 | } else if (param1 === 'set') { 1047 | // 设置配置 1048 | try { 1049 | // 解析路径 (如 weather.api 或 ai-chat.defaultModel) 1050 | const parts = param2.split('.'); 1051 | const pluginName = parts[0]; 1052 | 1053 | if (parts.length === 1) { 1054 | // 设置顶级配置 1055 | if (pluginName === 'adminUsers') { 1056 | // 直接设置字符串值 1057 | config[pluginName] = param3; 1058 | await ConfigDB.set(config); 1059 | await sender.reply(`✅ 成功设置 ${pluginName} = ${param3}`); 1060 | } else { 1061 | await sender.reply(`❌ 未知的顶级配置: ${pluginName}`); 1062 | } 1063 | } else if (parts.length === 2) { 1064 | // 设置插件配置 1065 | const key = parts[1]; 1066 | 1067 | // 确保插件配置对象存在 1068 | if (!config[pluginName]) { 1069 | config[pluginName] = {}; 1070 | } 1071 | 1072 | // 特殊处理某些插件的特定配置项 1073 | if (pluginName === 'weather' && key === 'api') { 1074 | // 验证API类型 1075 | if (!['amap', 'openweather'].includes(param3)) { 1076 | await sender.reply(`❌ 无效的天气API类型: ${param3} 1077 | 目前支持的API类型: 1078 | - amap: 高德地图天气API 1079 | - openweather: OpenWeather API`); 1080 | return true; 1081 | } 1082 | } else if (pluginName === 'weather' && key === 'showAIAdvice') { 1083 | // 特殊处理布尔值配置项 1084 | try { 1085 | // 尝试转换为布尔值 1086 | if (['true', '是', '开启', '启用'].includes(param3.toLowerCase())) { 1087 | config[pluginName][key] = true; 1088 | await ConfigDB.set(config); 1089 | await sender.reply(`✅ 成功设置 ${pluginName}.${key} = true (已启用)`); 1090 | return true; 1091 | } else if (['false', '否', '关闭', '禁用'].includes(param3.toLowerCase())) { 1092 | config[pluginName][key] = false; 1093 | await ConfigDB.set(config); 1094 | await sender.reply(`✅ 成功设置 ${pluginName}.${key} = false (已禁用)`); 1095 | return true; 1096 | } else { 1097 | await sender.reply(`❌ 无效的值: ${param3} 1098 | 请使用 true/false, 是/否, 开启/关闭, 或 启用/禁用`); 1099 | return true; 1100 | } 1101 | } catch (e) { 1102 | await sender.reply(`❌ 设置布尔值出错: ${e.message}`); 1103 | return true; 1104 | } 1105 | } 1106 | 1107 | // 尝试解析值 1108 | let value = param3; 1109 | try { 1110 | // 尝试解析为JSON 1111 | value = JSON.parse(param3); 1112 | } catch (e) { 1113 | // 如果不是有效的JSON,保持原始字符串 1114 | } 1115 | 1116 | config[pluginName][key] = value; 1117 | await ConfigDB.set(config); 1118 | 1119 | // 特殊处理API密钥显示 1120 | if (key === 'key') { 1121 | await sender.reply(`✅ 成功设置 ${pluginName}.${key} = ${value.substring(0, 3)}*****`); 1122 | } else { 1123 | await sender.reply(`✅ 成功设置 ${pluginName}.${key} = ${JSON.stringify(value)}`); 1124 | } 1125 | } else { 1126 | await sender.reply('❌ 配置路径格式错误,应为 pluginName.key'); 1127 | } 1128 | } catch (error) { 1129 | console.error('[智能助手] 设置配置失败:', error); 1130 | await sender.reply(`❌ 设置配置失败: ${error.message}`); 1131 | } 1132 | return true; 1133 | } else if (param1 === 'get') { 1134 | // 获取特定配置 1135 | try { 1136 | const parts = param2.split('.'); 1137 | const pluginName = parts[0]; 1138 | 1139 | if (parts.length === 1) { 1140 | // 获取顶级配置 1141 | if (config[pluginName] !== undefined) { 1142 | await sender.reply(`${pluginName}: ${JSON.stringify(config[pluginName], null, 2)}`); 1143 | } else { 1144 | await sender.reply(`❌ 配置 ${pluginName} 不存在`); 1145 | } 1146 | } else if (parts.length === 2) { 1147 | // 获取插件配置 1148 | const key = parts[1]; 1149 | const pluginConfig = getPluginConfig(pluginName); 1150 | 1151 | if (pluginConfig && pluginConfig[key] !== undefined) { 1152 | const value = pluginConfig[key]; 1153 | await sender.reply(`${pluginName}.${key}: ${JSON.stringify(value, null, 2)}`); 1154 | } else { 1155 | await sender.reply(`❌ 配置 ${pluginName}.${key} 不存在`); 1156 | } 1157 | } else { 1158 | await sender.reply('❌ 配置路径格式错误,应为 pluginName.key'); 1159 | } 1160 | } catch (error) { 1161 | console.error('[智能助手] 获取配置失败:', error); 1162 | await sender.reply(`❌ 获取配置失败: ${error.message}`); 1163 | } 1164 | return true; 1165 | } 1166 | 1167 | return false; 1168 | } catch (error) { 1169 | console.error('[智能助手] 执行配置命令错误:', error); 1170 | await sender.reply(`❌ 执行配置命令出错: ${error.message}`); 1171 | return false; 1172 | } 1173 | } 1174 | 1175 | // 获取AI模型配置 1176 | function getAIModelConfig(modelId) { 1177 | // 获取ai-chat插件配置 1178 | const aiChatConfig = getPluginConfig('ai-chat'); 1179 | 1180 | // 检查models是否为数组 1181 | if (aiChatConfig && Array.isArray(aiChatConfig.models)) { 1182 | // 在数组中查找指定ID的模型 1183 | const model = aiChatConfig.models.find(m => m.id === modelId); 1184 | if (model) { 1185 | return model; 1186 | } 1187 | } 1188 | 1189 | // 兼容旧版:从对象格式中查找 1190 | if (aiChatConfig && aiChatConfig.models && typeof aiChatConfig.models === 'object') { 1191 | if (aiChatConfig.models[modelId]) { 1192 | return aiChatConfig.models[modelId]; 1193 | } 1194 | } 1195 | 1196 | // 如果找不到指定的模型,返回默认配置 1197 | return { 1198 | id: modelId, 1199 | name: modelId.charAt(0).toUpperCase() + modelId.slice(1), 1200 | url: `https://api.${modelId}.com/v1`, 1201 | apiKey: '', 1202 | enable: false, 1203 | model: '' 1204 | }; 1205 | } 1206 | 1207 | // 获取默认AI模型 1208 | function getDefaultAIModel() { 1209 | const aiChatConfig = getPluginConfig('ai-chat'); 1210 | 1211 | // 获取默认模型ID 1212 | const defaultModelId = aiChatConfig.defaultModel || 'deepseek'; 1213 | 1214 | // 尝试获取默认模型配置 1215 | const modelConfig = getAIModelConfig(defaultModelId); 1216 | 1217 | // 如果默认模型被禁用或不存在,尝试找一个启用的模型 1218 | if (!modelConfig.enable && Array.isArray(aiChatConfig.models)) { 1219 | const enabledModel = aiChatConfig.models.find(m => m.enable); 1220 | if (enabledModel) { 1221 | return enabledModel; 1222 | } 1223 | } 1224 | 1225 | return modelConfig; 1226 | } 1227 | 1228 | // 获取所有启用的AI模型 1229 | function getEnabledAIModels() { 1230 | const aiChatConfig = getPluginConfig('ai-chat'); 1231 | 1232 | if (aiChatConfig && Array.isArray(aiChatConfig.models)) { 1233 | return aiChatConfig.models.filter(model => model.enable); 1234 | } 1235 | 1236 | // 兼容旧版:从对象格式中提取 1237 | if (aiChatConfig && aiChatConfig.models && typeof aiChatConfig.models === 'object') { 1238 | const models = []; 1239 | for (const [id, model] of Object.entries(aiChatConfig.models)) { 1240 | if (model.enable || model.enabled) { 1241 | models.push({ 1242 | id, 1243 | name: model.name || id.charAt(0).toUpperCase() + id.slice(1), 1244 | apiKey: model.apiKey || '', 1245 | url: model.url || `https://api.${id}.com/v1`, 1246 | enable: true, 1247 | model: model.model || '', 1248 | ...model 1249 | }); 1250 | } 1251 | } 1252 | return models; 1253 | } 1254 | 1255 | return []; 1256 | } 1257 | 1258 | // 兼容模式:旧版本的消息处理函数 1259 | async function handleMessageLegacy(sender) { 1260 | const message = sender.getMsg(); 1261 | const userId = sender.getUserId(); 1262 | 1263 | // 简化版的旧功能,以便在新系统出现问题时仍能提供基本服务 1264 | if (message === '/help') { 1265 | const helpText = `智能助手使用指南: 1266 | /weather 城市 - 查询实时天气 1267 | /forecast 城市 - 查询天气预报 1268 | /chat 内容 - 与AI对话 1269 | /subscribe - 订阅早间提醒 1270 | /unsubscribe - 取消订阅早间提醒 1271 | /clear - 清除聊天历史 1272 | /model list - 查看可用AI模型 1273 | /model use 模型名 - 切换AI模型 1274 | /weconfig - 查看配置信息 1275 | /weconfig set weather.api [amap/openweather] - 设置天气API类型 1276 | /weconfig set weather.key [API密钥] - 设置天气API密钥 1277 | /天气配置 - 查看天气插件配置 1278 | /天气配置 api-help - 查看天气API申请指南 1279 | /speedtest - 测试所有AI模型的响应速度 1280 | /speedtest info - 查看测速插件的配置和上次测试结果 1281 | /speedtest config 参数名 参数值 - 配置测速参数(需管理员权限) 1282 | /api help - 查看API工具箱帮助 1283 | /api list - 查看可用API列表 1284 | /api 名称 - 调用指定API`; 1285 | 1286 | await sender.reply(helpText); 1287 | return true; 1288 | } 1289 | 1290 | // 其他命令将显示未实现信息 1291 | await sender.reply("兼容模式下,此功能暂时不可用。请联系管理员解决插件系统问题。"); 1292 | return false; 1293 | } 1294 | 1295 | // 加载API工具箱配置的辅助函数 1296 | function loadApiConfig() { 1297 | try { 1298 | // 尝试加载插件自身的配置文件 1299 | const pluginConfigPath = path.join(__dirname, 'plugins', 'api-toolkit', 'config.json'); 1300 | console.log(`[智能助手][调试] 尝试加载API工具箱配置: ${pluginConfigPath}`); 1301 | if (fs.existsSync(pluginConfigPath)) { 1302 | console.log(`[智能助手][调试] 配置文件存在,开始读取`); 1303 | const configContent = fs.readFileSync(pluginConfigPath, 'utf8'); 1304 | console.log(`[智能助手][调试] 配置文件内容长度: ${configContent.length} 字节`); 1305 | const pluginConfig = JSON.parse(configContent); 1306 | console.log(`[智能助手][调试] 成功解析配置文件,API数量: ${Object.keys(pluginConfig.apis || {}).length}`); 1307 | 1308 | // 检查必要的属性是否存在 1309 | if (!pluginConfig.apis) { 1310 | console.warn(`[智能助手][调试] 警告: 配置文件中没有apis属性,添加空对象`); 1311 | pluginConfig.apis = {}; 1312 | } 1313 | 1314 | return pluginConfig; 1315 | } else { 1316 | console.warn(`[智能助手][调试] 配置文件不存在: ${pluginConfigPath}`); 1317 | } 1318 | 1319 | // 返回默认配置 1320 | return { 1321 | enabled: true, 1322 | commandPrefix: "api", 1323 | apis: { 1324 | // 添加一些常用API作为后备 1325 | baisi: { 1326 | name: "白丝图片", 1327 | url: "https://v2.xxapi.cn/api/baisi", 1328 | method: "GET", 1329 | type: "image", 1330 | enabled: true, 1331 | description: "随机返回白丝图片" 1332 | }, 1333 | heisi: { 1334 | name: "黑丝图片", 1335 | url: "https://v2.xxapi.cn/api/heisi", 1336 | method: "GET", 1337 | type: "image", 1338 | enabled: true, 1339 | description: "随机返回黑丝图片" 1340 | } 1341 | }, 1342 | rateLimit: { 1343 | perUser: 10, 1344 | timeWindow: 60000, 1345 | enabled: true 1346 | }, 1347 | cache: { 1348 | enabled: true, 1349 | expiry: 3600000 1350 | } 1351 | }; 1352 | } catch (error) { 1353 | console.error(`[智能助手][调试] 加载API工具箱配置出错:`, error); 1354 | // 返回带有常用API的默认配置 1355 | return { 1356 | enabled: true, 1357 | commandPrefix: "api", 1358 | apis: { 1359 | // 添加一些常用API作为后备 1360 | baisi: { 1361 | name: "白丝图片", 1362 | url: "https://v2.xxapi.cn/api/baisi", 1363 | method: "GET", 1364 | type: "image", 1365 | enabled: true, 1366 | description: "随机返回白丝图片" 1367 | } 1368 | }, 1369 | rateLimit: { 1370 | perUser: 10, 1371 | timeWindow: 60000, 1372 | enabled: true 1373 | }, 1374 | cache: { 1375 | enabled: true, 1376 | expiry: 3600000 1377 | } 1378 | }; 1379 | } 1380 | } --------------------------------------------------------------------------------