UNPKG

cyberbot-v2

Version:

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

479 lines (440 loc) 20.1 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.generateConfig = generateConfig; const fs = __importStar(require("fs")); const path = __importStar(require("path")); const readline = __importStar(require("readline")); // 创建 readline 接口 const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // 定义用户输入的问题(项目名称后的其他问题) const questions = [ { name: 'master', message: '请输入主人qq:' }, { name: 'admins', message: '请输入管理员qq(空格分隔):' }, { name: 'bot', message: '请输入bot qq:' }, { name: 'baseUrl', message: '请输入napcat服务端正向websocket地址(示例:ws://127.0.0.1:3001):' }, { name: 'accessToken', message: '请输入napcat webui内你自己设置的accessToken:' } ]; // 提示用户输入并生成配置文件 function generateConfig() { // 首先询问项目名称 rl.question('请输入项目名称: ', (projectName) => { const projectDir = path.join(process.cwd(), projectName.trim()); // 创建项目目录 if (!fs.existsSync(projectDir)) { fs.mkdirSync(projectDir, { recursive: true }); } // 检查配置文件是否存在 const configFilePath = path.join(projectDir, 'bot.config.json'); if (fs.existsSync(configFilePath)) { console.log('配置文件 bot.config.json 已存在,跳过配置步骤。'); rl.close(); process.exit(0); } const answers = { projectName: projectName.trim() }; let questionIndex = 0; function askNextQuestion() { if (questionIndex >= questions.length) { saveConfig(projectDir, answers); rl.close(); return; } const question = questions[questionIndex]; rl.question(question.message + ' ', (answer) => { answers[question.name] = answer; questionIndex++; askNextQuestion(); }); } askNextQuestion(); }); } function createPluginDir(projectDir, pluginName) { const pluginPath = path.join(projectDir, 'plugins', pluginName, 'index.ts'); const pluginDir = path.dirname(pluginPath); if (!fs.existsSync(pluginDir)) { fs.mkdirSync(pluginDir, { recursive: true }); } return pluginPath; } function saveConfig(projectDir, config) { // 生成配置文件内容 const configContent = { master: Number(config.master), admins: config.admins.split(' ').map(Number), bot: Number(config.bot), baseUrl: config.baseUrl, accessToken: config.accessToken, built_in_plugins: ['cmds'], plugins: ['demo'], log_level: 'info' }; // 写入配置文件 const configFilePath = path.join(projectDir, 'bot.config.json'); fs.writeFileSync(configFilePath, JSON.stringify(configContent, null, 2)); console.log(`配置文件已生成 ${configFilePath}`); // 生成 package.json const packageJson = { scripts: { start: "node app.js" }, dependencies: { "axios": "^1.7.9", "chokidar": "^4.0.3", "dayjs": "^1.11.13", "diskusage-ng": "^1.0.4", "log4js": "^6.9.1", "ts-node": "^10.9.2", "cyberbot": "^0.0.24", "node-napcat-ts": "^0.4.6", } }; const packagePath = path.join(projectDir, 'package.json'); fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2)); console.log('已生成package.json文件'); // 生成 app.js const appJsContent = `require('cyberbot').bootstrap().catch(console.error);`; const appJsPath = path.join(projectDir, 'app.js'); fs.writeFileSync(appJsPath, appJsContent); console.log('已生成入口文件app.js'); // 生成示例插件 const demoPluginPath = createPluginDir(projectDir, 'demo'); // 生成内置cmds插件 const cmdsPluginPath = createPluginDir(projectDir, 'cmds'); const demoPluginContent = ` import { Bot, AllHandlers, Structs,http } from 'cyberbot'; export const name = 'demo'; export const setup = (bot: Bot) => ({ // 处理所有消息:群、好友、讨论组 'message': [async (context: AllHandlers['message']) => { // 收到 hello 消息时回复 world if (context.raw_message === 'hello') { // 第三个参数表示是否回复愿消息 const { message_id } = await bot.reply(context,'world', true) //5s撤回 setTimeout(() => { bot.delete_msg(message_id) }, 5000); } // 收到 love 消息时回复爱你哟和一个爱心 QQ 表情 if (context.raw_message === 'love') { // 复杂消息消息可以使用数组组合 Structs.face(66,"","")后两个参数为node-napcat-ts新版本增加的,作用未知 bot.reply(context, ['爱你哟 ', Structs.face(66,"","")]) } // 收到 壁纸 消息时回复今天的 bing 壁纸 if (context.raw_message === '壁纸') { // 第一个参数是图片的 URL,第二个参数是是否使用缓存,true 为使用缓存,false 为不使用缓存 bot.reply(context, [Structs.image('https://p2.qpic.cn/gdynamic/m7yRCticIwlKMnXkIat8nNRyD95wf24YNBoiblNYKYdXs/0')]) } // 收到 一言 消息时回复一言 if (context.raw_message === '一言') { const { data } = await http.get('https://v1.hitokoto.cn/') bot.reply(context, data.hitokoto, true) } }], 'message.group': [async (context: AllHandlers['message.group']) => { // 处理群消息 // if(context.raw_message === "pang"){ // bot.reply(context, "pang") // } }], 'message.private': [async (context: AllHandlers['message.private']) => { // 处理好友消息 }], 'message.request': [async (context: AllHandlers['request']) => { // 处理所有请求:好友、群,添加好友、邀请入群等等 bot.aprroveGroup(context); }], // 处理所有通知,好友、群的数量增加与减少、戳一戳、撤回,以及群的签到、禁言、管理变动、转让等等 'message.notice': [async (context: AllHandlers['notice']) => { // 处理所有通知,好友、群的数量增加与减少、戳一戳、撤回,以及群的签到、禁言、管理变动、转让等等 }], }); `; const cmdsPluginContent = ` /*** * @author: @星火 * @description: * 本文件是机器人框架的核心命令插件,负责处理所有与机器人命令相关的逻辑。此插件不可删除,因为它是控制和管理机器人的基础。 * * 主要功能: * - 提供管理员权限验证机制,确保只有授权用户可以执行敏感操作。 * - 实现了对插件的启停、禁用及重新加载等命令的支持,通过解析用户输入的命令参数来动态调整配置。 * - 提供详细的错误处理机制,确保在命令执行失败时能够及时反馈给用户。 * - 提供对插件的安装、卸载、更新等命令的支持,通过解析用户输入的命令参数来动态调整配置。 */ import { Bot, type AllHandlers, logger } from 'cyberbot'; import * as os from 'os' import diskusage from 'diskusage-ng'; export const name = 'cmds'; // 新增命令处理器类型 type CommandHandler = (bot: Bot, context: AllHandlers['message'], args: string[]) => Promise<void>; // 定义一个接口来描述硬盘信息的结构 interface DiskInfo { total: number; used: number; available: number; } export const setup = (bot: Bot) => ({ 'message': [async (context: AllHandlers['message']) => { try { const args = context.raw_message.trim().split(/\\s+/); const [prefix, command = '', ...restArgs] = args; // 添加默认值 // 命令前缀识别 if (!['#p', '#status', '#help', '#h','#exit','#about','#a'].some(p => prefix.startsWith(p))) return; // 权限检查(非status/help命令需要管理员) const isAdmin = bot.admins.includes(context.sender?.user_id); if (!isAdmin) { logger.warn('❌ 需要管理员权限'); return await bot.reply(context, '❌ 需要管理员权限', true); } // 命令路由逻辑 const handlerKey = prefix === '#p' ? command : prefix.slice(1); const handler = commandHandlers[handlerKey] || handleHelpInfo; await handler(bot, context, restArgs); } catch (error) { logger.error('❌ 操作失败:', error); await bot.reply(context, \`✖️ 操作失败: \${error instanceof Error ? error.message : error}\`, true); } }] }); // 通用配置操作 const updatePluginConfig = async (bot: Bot, pluginName: string, operation: 'add' | 'remove') => { const config = await bot.readConfigAsync(); const index = config.plugins.indexOf(pluginName); if (operation === 'add' && index === -1) { config.plugins.push(pluginName); } else if (operation === 'remove' && index !== -1) { config.plugins.splice(index, 1); } await bot.writeConfigAsync(config); } // 插件启用 const handleEnablePlugin: CommandHandler = async (bot: Bot, context: AllHandlers['message'], args: string[]) => { const [pluginName] = args; if (!pluginName || !(await bot.readAllPlugins()).includes(pluginName)) return; if (bot.builtInPlugins.has(pluginName)) { await bot.reply(context, \`❌ 插件 \${pluginName} 是内置插件,无法启用\`, true); return; } await updatePluginConfig(bot, pluginName, 'add'); logger.info(\`🟢 插件 \${pluginName} 已启用\`); await bot.reply(context, \`🟢 插件 \${pluginName} 已启用\`, true); } // 插件禁用 const handleDisablePlugin: CommandHandler = async (bot: Bot, context: AllHandlers['message'], args: string[]) => { const [pluginName] = args; if (!pluginName || !(await bot.readAllPlugins()).includes(pluginName)) return; if (bot.builtInPlugins.has(pluginName)) { await bot.reply(context, \`❌ 插件 \${pluginName} 是内置插件,无法禁用\`, true); return; } await updatePluginConfig(bot, pluginName, 'remove'); await bot.reply(context, \`🔴 插件 \${pluginName} 已禁用\`, true); logger.info(\`🔴 插件 \${pluginName} 已禁用\`); } // 插件重载 const handleReloadPlugin: CommandHandler = async (bot: Bot, context: AllHandlers['message'], args: string[]) => { const [pluginName] = args; if (!pluginName || !(await bot.readAllPlugins()).includes(pluginName)) return; if (bot.builtInPlugins.has(pluginName)) { await bot.reply(context, \`❌ 插件 \${pluginName} 是内置插件,无法重载\`, true); return; } const config = await bot.readConfigAsync(); const pluginsSet = new Set(config.plugins); pluginsSet[pluginsSet.has(pluginName) ? 'delete' : 'add'](pluginName); pluginsSet.add(pluginName); // 强制重新添加确保顺序 config.plugins = Array.from(pluginsSet); await bot.writeConfigAsync(config); await bot.reply(context, \`🔄 插件 \${pluginName} 已重新加载\`, true); logger.info(\`🔄 插件 \${pluginName} 已重新加载\`); } // 插件列表 const handleListPlugins: CommandHandler = async (bot: Bot, context: AllHandlers['message']) => { const [allPlugins, enabledPlugins] = await Promise.all([ bot.readAllPlugins(), bot.readEnabledPlugins() ]); const pluginList = allPlugins.map(plugin => \`\${enabledPlugins.includes(plugin) ? '🟢' : '🔴'} \${plugin}\` ).join('\\n'); await bot.reply(context, \`〓 🧩 插件列表 〓\\n\${pluginList}\n🔶 总数:\${allPlugins.length} | 启用:\${enabledPlugins.length}\`, true ); logger.info(\`〓 🧩 插件列表 〓\\n\${pluginList}\\n🔶 总数:\${allPlugins.length} | 启用:\${enabledPlugins.length}\`); } // 系统状态 const handleSystemStatus: CommandHandler = async (bot: Bot, context: AllHandlers['message']) => { // 框架版本 const ver_info = await bot.client.get_version_info(); // 登录qq const login_qq = await bot.client.get_login_info(); // 好友列表 const friend_list = await bot.client.get_friend_list(); // 群列表 const group_list = await bot.client.get_group_list(); // bot配置获取插件 const bot_config = await bot.readConfigAsync(); // 内存使用情况 const memoryUsage = process.memoryUsage(); const totalMemory = os.totalmem(); const freeMemory = os.freemem(); // nodejs版本信息 const nodeVersion = process.version; // 平台 const platform = os.platform() === 'win32' ? 'Windows' : os.platform(); const arch = os.arch(); // 一些时间信息 const uptimeSeconds = process.uptime(); const days = Math.floor(uptimeSeconds / (24 * 3600)); const hours = Math.floor((uptimeSeconds % (24 * 3600)) / 3600); const minutes = Math.floor((uptimeSeconds % 3600) / 60); const seconds = Math.floor(uptimeSeconds % 60); const formattedTime = \`\${days}d \${hours}h \${minutes}m \${seconds}s\`; const status = '〓 🟢 Bot 状态 〓' // 硬盘信息 const { total, used } = await getDiskInfo() await bot.reply(context, \`\${status}\\n🤖 \${login_qq.nickname}\\n🌀 \${login_qq.user_id}\\n🧩 启用\${bot_config.plugins.length}个插件\\n🕦 \${formattedTime}\\n📋 \${friend_list.length}个好友,\${group_list.length}个群\\n🔷 \${ver_info.app_name}-\${ver_info.protocol_version}-\${ver_info.app_version}\\n🚀 bot占用-\${(memoryUsage.rss / 1024 / 1024).toFixed(2)} MB-\${((memoryUsage.rss / 1024 / 1024) / (totalMemory / 1024 / 1024) * 100).toFixed(2)}%\\n💻 \${platform}-\${arch}-node\${nodeVersion.slice(1)}\\n📉 \${((totalMemory - freeMemory) / 1024 / 1024 / 1024).toFixed(2)} GB/\${(totalMemory / 1024 / 1024 / 1024).toFixed(2)} GB-\${(((totalMemory - freeMemory) / 1024) / (totalMemory / 1024) * 100).toFixed(2)}%\\n💾 \${used} GB/\${total} GB-\${(used/total).toFixed(2)}%\`); logger.info(\`\${status}\\n🤖 \${login_qq.nickname}\\n🌀 \${login_qq.user_id}\\n🧩 启用\${bot_config.plugins.length}个插件\\n🕦 \${formattedTime}\\n📋 \${friend_list.length}个好友,\${group_list.length}个群\\n🔷 \${ver_info.app_name}-\${ver_info.protocol_version}-\${ver_info.app_version}\\n🚀 bot占用-\${(memoryUsage.rss / 1024 / 1024).toFixed(2)} MB-\${((memoryUsage.rss / 1024 / 1024) / (totalMemory / 1024 / 1024) * 100).toFixed(2)}%\\n💻 \${platform}-\${arch}-node\${nodeVersion.slice(1)}\\n📉 \${((totalMemory - freeMemory) / 1024 / 1024 / 1024).toFixed(2)} GB/\${(totalMemory / 1024 / 1024 / 1024).toFixed(2)} GB-\${(((totalMemory - freeMemory) / 1024) / (totalMemory / 1024) * 100).toFixed(2)}%\\n💾 \${used} GB/\${total} GB-\${(used/total).toFixed(2)}%\`) } // 帮助信息 const handleHelpInfo: CommandHandler = async (bot: Bot, context: AllHandlers['message']) => { const helpMessage = [ '〓 📖 帮助文档 〓', '#p list - 查看插件列表', '#p on <插件> - 启用指定插件', '#p off <插件> - 禁用指定插件', '#p reload <插件> - 重载插件配置', '#status - 查看系统状态', '#help - 显示本帮助' ].join('\\n'); await bot.reply(context, helpMessage, true); logger.info(helpMessage) } const handleExit: CommandHandler = async (bot: Bot, context: AllHandlers['message']) => { // console.log('✅Bot已退出'); logger.info('✅Bot已退出') await bot.reply(context, '✅Bot已退出'); bot.disconnect(); process.exit(0); }; const handleAbout: CommandHandler = async (bot: Bot, context: AllHandlers['message']) => { const introduce = \` 〓 🚀 CyberBot 新一代QQ机器人框架 〓 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ✦ 核心特性 ✦ ├─ 🪶 极简轻量:不依赖复杂环境,安装即用 ├─ 🎨 优雅架构:TypeScript 全栈开发,类型安全 ├─ 🧩 热插拔插件:模块化设计,功能扩展无忧 ├─ ⚡ 性能怪兽:基于 Node.js 事件驱动模型 ├─ 🌐 跨平台支持:Windows/Linux/macOS 全兼容 ✦ 技术架构 ✦ └─ 🔧 底层协议:NapcatQQ 核心驱动 └─ 🧬 开发框架:node-napcat-ts 深度整合 └─ 📦 生态支持:npm 海量模块即插即用 ✦ 开发者友好 ✦ 💡 完善文档 + 示例项目 = 1分钟快速上手 💎 支持插件市场机制,共享机器人能力 🛠️ 提供cli工具链,创建/调试/打包一气呵成 ✨ 开源协议:MIT License,欢迎贡献代码! \`; await bot.reply(context, introduce); }; // 命令映射表 const commandHandlers: Record<string, CommandHandler> = { // 原有插件命令 on: handleEnablePlugin, off: handleDisablePlugin, reload: handleReloadPlugin, rl: handleReloadPlugin, list: handleListPlugins, exit: handleExit, // 新增命令 status: handleSystemStatus, help: handleHelpInfo, h: handleHelpInfo, about:handleAbout, a:handleAbout, }; // 封装成一个函数,获取指定路径所在硬盘的信息 const getDiskInfo = (): Promise<DiskInfo> => { const toGB = (bytes: number): number => parseFloat((bytes / (1024 * 1024 * 1024)).toFixed(2)); return new Promise<DiskInfo>((resolve, reject) => { diskusage(process.cwd(), (err, info:DiskInfo) => { if (err) { reject(err); } else { resolve({ total: toGB(info.total), used: toGB(info.used), available: toGB(info.available) }); } }); }); }; `; fs.writeFileSync(demoPluginPath, demoPluginContent); fs.writeFileSync(cmdsPluginPath, cmdsPluginContent); console.log(`插件文件已生成: ${demoPluginPath}、${cmdsPluginPath}`); console.log('\n请执行以下命令以下载依赖:'); console.log(`cd ${config.projectName}`); console.log('npm install'); console.log('\n等待依赖安装完成后执行以下命令启动机器人:'); console.log('npm start'); } // 调用 generateConfig 函数 generateConfig();