cyberbot-v2
Version:
cyberbot, 基于napcat-ts, nodejs,轻量qq机器人框架。
479 lines (440 loc) • 20.1 kB
JavaScript
#!/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();