cyberbot-core
Version:
cyberbot, 基于napcat-ts, nodejs,轻量qq机器人框架。
669 lines (623 loc) • 28.3 kB
JavaScript
#!/usr/bin/env node
import * as fs from 'fs';
import * as path from 'path';
import * as readline from '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: '请输入机器人qq:'
},
{
name: 'baseUrl',
message: '请输入napcat服务端正向websocket地址(示例:ws://127.0.0.1:3001):'
},
{
name: 'accessToken',
message: '请输入napcat webui内你自己设置的accessToken:'
}
];
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) {
// 生成配置文件内容 - 使用扁平结构的JSON
const jsonConfig = {
baseUrl: config.baseUrl,
accessToken: config.accessToken,
throwPromise: false,
reconnection: {
enable: true,
attempts: 10,
delay: 5000,
debug: false
},
// bot 直接转为数字
bot: Number(config.bot),
master: config.master.split(' ').map(Number).filter(n => !isNaN(n)),
admins: config.admins.split(' ').map(Number).filter(n => !isNaN(n)),
plugins: {
system: ["cmds"],
user: ["demo"]
},
logger: {
level: "info",
maxSize: "10m",
maxDays: 7
}
};
// 转换为JSON字符串,使用2空格缩进以提高可读性
const jsonContent = JSON.stringify(jsonConfig, null, 2);
// 写入配置文件
const configFilePath = path.join(projectDir, 'config.json');
fs.writeFileSync(configFilePath, jsonContent);
console.log(`配置文件已生成 ${configFilePath}`);
// 生成 package.json
const packageJson = {
scripts: {
start: "node app.js"
},
dependencies: {
"axios": "^1.7.8",
"jiti": "^2.4.0",
"log4js": "^6.9.1",
"node-cron": "^3.0.3",
"node-napcat-ts": "^0.4.0",
"systeminformation": "^5.25.11",
"cyberbot-core": "^0.5.0"
}
};
const packagePath = path.join(projectDir, 'package.json');
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
console.log('已生成package.json文件');
// 生成 app.js
const appJsContent = `const { Bot } = require("cyberbot-core");\nnew Bot().start();`;
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 {definePlugin, Structs, http} from "cyberbot-core"
export default definePlugin({
// 插件名应和文件名一致, 不然可能会出问题
name: "demo",
description: "插件描述",
setup: (ctx) => {
ctx.handle("message", async (e) => {
// 收到 hello 消息时回复 world
if (e.raw_message === 'hello') {
// 第三个参数表示是否回复愿消息
const { message_id } = await e.reply('world', true)
//5s撤回
setTimeout(() => {
ctx.delete_msg(message_id)
}, 5000);
}
// 收到 love 消息时回复爱你哟和一个爱心 QQ 表情
if (e.raw_message === 'love') {
// 复杂消息消息可以使用数组组合
e.reply(['爱你哟 ', Structs.face(66)])
}
// 收到 壁纸 消息时回复今天的 bing 壁纸
if (e.raw_message === '壁纸') {
// 第一个参数是图片的 URL,第二个参数是是否使用缓存,true 为使用缓存,false 为不使用缓存
e.reply([Structs.image('https://p2.qpic.cn/gdynamic/m7yRCticIwlKMnXkIat8nNRyD95wf24YNBoiblNYKYdXs/0')])
}
// 收到 一言 消息时回复一言
if (e.raw_message === '一言') {
const { data } = await http.get('https://v1.hitokoto.cn/')
e.reply(data.hitokoto, true)
}
})
ctx.handle("message.group", async (e) => {
// 处理群消息
if(e.raw_message === "群消息"){
e.reply("这是一条群消息")
}
})
ctx.handle("message.private", async (e) => {
// 处理私聊消息
if(e.raw_message == "私聊"){
await e.reply("私聊消息")
}
})
ctx.handle("request", async (e: any) => {
// 处理所有请求:好友、群,添加好友、邀请入群等等
console.log('收到请求:', JSON.stringify(e));
// 群组相关请求
if (e.request_type === 'group') {
// 自动同意群邀请或加群请求
await ctx.aprroveGroup(e.flag);
console.log('已自动同意群组请求');
}
// 好友相关请求可以在这里处理
if (e.request_type === 'friend') {
// 处理好友请求
}
})
ctx.handle("notice", async (e: any) => {
// 处理所有通知:好友、群的数量增加与减少、戳一戳、撤回,以及群的签到、禁言、管理变动、转让等等
// console.log('收到通知:', JSON.stringify(e));
})
// 可设置多个 cron
// ctx.cron([
// [
// '*/5 * * * * *', // 每5秒执行一次
// async (ctx, e) => {
// await ctx.sendPrivateMessage(1234567, "每5秒执行一次")
// }
// ],
// [
// '*/3 * * * * *', // 每分钟执行一次
// async (ctx, e) => {
// await ctx.sendPrivateMessage(1234567, "每3秒执行一次")
// }
// ],
// ])
}
})
`;
fs.writeFileSync(demoPluginPath, demoPluginContent);
const cmdsPluginContent = `
/***
* @author: @星火
* @description:
* 本文件是机器人框架的核心命令插件,负责处理所有与机器人命令相关的逻辑。此插件不可删除,因为它是控制和管理机器人的基础。
*
* 主要功能:
* - 提供管理员权限验证机制,确保只有授权用户可以执行敏感操作。
* - 实现了对插件的启停、禁用及重新加载等命令的支持,通过解析用户输入的命令参数来动态调整配置。
* - 提供详细的错误处理机制,确保在命令执行失败时能够及时反馈给用户。
* - 提供对插件的安装、卸载、更新等命令的支持,通过解析用户输入的命令参数来动态调整配置。
*/
import {join} from "path";
import {existsSync} from "fs";
import {definePlugin,NCWebsocket,AllHandlers} from "cyberbot-core";
import {exec} from "child_process";
import {promisify} from "util";
import * as os from 'os'
import { fsSize } from 'systeminformation';
const execAsync = promisify(exec);
interface PluginInfo {
version: string;
description: string;
type: 'system' | 'user';
setup: {
enable: boolean;
listeners: Array<{
event: keyof AllHandlers;
fn: any;
}>;
cron: Array<any>;
};
}
interface CommandContext {
plugin: {
getPlugins: () => Map<string, PluginInfo>;
onPlugin: (pluginName: string) => string;
offPlugin: (pluginName: string) => string;
reloadPlugin: (pluginName: string) => Promise<any>;
loadPlugin: (pluginName: string) => Promise<any>;
getPluginsFromDir: () => string[];
};
isMaster: (e: any) => boolean;
config: {
master: number[];
admins: number[];
plugins: {
system?: string[];
user?: string[];
};
};
bot: NCWebsocket;
}
interface CommandEvent {
raw_message: string;
reply: (message: string) => Promise<{ message_id: number }>;
}
interface CommandHandler {
handler?: (ctx: CommandContext, e: CommandEvent, args: string[]) => Promise<{ message_id: number } | void>;
subcommands?: {
[key: string]: (ctx: CommandContext, e: CommandEvent, args: string[]) => Promise<{ message_id: number } | void>;
};
help?: string;
}
// 定义一个接口来描述硬盘信息的结构
interface DiskInfo {
total: number;
used: number;
available: number;
}
const commands: { [key: string]: CommandHandler } = {
"关于": {
handler: async (ctx, e) => {
return await e.reply("〓 🚀 CyberBot〓\\n新一代QQ机器人框架\\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\\n\\n✦ 核心特性 ✦\\n├─ 🪶 极简轻量:不依赖复杂环境,安装即用\\n├─ 🎨 优雅架构:TypeScript 全栈开发,类型安全\\n├─ 🧩 热插拔插件:模块化设计,功能扩展无忧\\n├─ ⚡ 性能怪兽:基于 Node.js 事件驱动模型\\n├─ 🌐 跨平台支持:Windows/Linux/macOS 全兼容\\n\\n✦ 技术架构 ✦\\n└─ 🔧 底层协议:NapcatQQ 核心驱动\\n└─ 🧬 开发框架:node-napcat-ts 深度整合\\n└─ 📦 生态支持:npm 海量模块即插即用\\n\\n✦ 开发者友好 ✦\\n💡 完善文档 + 示例项目 = 1分钟快速上手\\n💎 支持插件市场机制,共享机器人能力\\n🛠️ 提供cli工具链,创建/调试/打包一气呵成\\n\\n✨ 开源协议:MIT License,欢迎贡献代码!");
}
},
"状态": {
handler: async (ctx, e) => {
try {
const plugins = ctx.plugin.getPlugins();
const values = Array.from(plugins.values());
const enabledCount = values.filter(info => info?.setup?.enable ?? false).length;
// 获取所有可用插件数量
const totalAvailablePlugins = ctx.plugin.getPluginsFromDir().length;
// 框架版本信息
let ver_info = { app_name: "CyberBot", protocol_version: "Unknown", app_version: "Unknown" };
try {
// 使用NCWebsocket的get_version_info方法
const versionInfo = await ctx.bot.get_version_info();
ver_info = {
app_name: versionInfo.app_name || "CyberBot",
protocol_version: versionInfo.protocol_version || "Unknown",
app_version: versionInfo.app_version || "Unknown"
};
} catch (err) {
console.error("获取版本信息失败:", err);
}
// 获取登录QQ信息
let login_qq = { nickname: "Unknown", user_id: "Unknown" };
try {
// 使用NCWebsocket的get_login_info方法
const loginInfo = await ctx.bot.get_login_info();
login_qq = {
nickname: loginInfo.nickname || "Unknown",
user_id: String(loginInfo.user_id) || "Unknown"
};
} catch (err) {
console.error("获取登录信息失败:", err);
}
// 获取好友列表
let friend_list: any[] = [];
try {
// 使用NCWebsocket的get_friend_list方法
friend_list = await ctx.bot.get_friend_list();
} catch (err) {
console.error("获取好友列表失败:", err);
}
// 获取群列表
let group_list: any[] = [];
try {
// 使用NCWebsocket的get_group_list方法
group_list = await ctx.bot.get_group_list();
} catch (err) {
console.error("获取群列表失败:", err);
}
// 内存使用情况
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 e.reply(
\`\${status}\\n\` +
\`🤖 CyberBot(\${login_qq.nickname})\\n\` +
\`❄ \${login_qq.user_id}\\n\` +
\`🧩 插件\${enabledCount}/\${totalAvailablePlugins}个已启用\\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 / totalMemory) * 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) / totalMemory) * 100).toFixed(2)}%\\n\` +
\`💾 \${used.toFixed(0)} GB/\${total.toFixed(0)} GB-\${((used/total) * 100).toFixed(2)}%\`
);
} catch (error) {
console.error("状态命令执行失败:", error);
await e.reply(\`[-]获取状态信息失败: \${error.message || "未知错误"}\`);
}
}
},
"插件": {
subcommands: {
"列表": async (ctx, e) => {
let msg = "〓 🧩 CyberBot 插件 〓\\n";
const plugins = ctx.plugin.getPlugins();
// 获取文件系统中的所有插件
const allPluginsInDir = ctx.plugin.getPluginsFromDir();
// 创建一个所有可用插件的集合,包括已加载和未加载的
const allAvailablePlugins = new Set([
...Array.from(plugins.keys()),
...allPluginsInDir
]);
// 获取插件类型信息(系统/用户)
const configSystemPlugins = ctx.config.plugins.system || [];
// 转换为数组并排序:先系统插件,再用户插件,每组内按名称字母顺序
const sortedPlugins = Array.from(allAvailablePlugins).sort((a, b) => {
const aIsSystem = configSystemPlugins.includes(a);
const bIsSystem = configSystemPlugins.includes(b);
if (aIsSystem && !bIsSystem) return -1;
if (!aIsSystem && bIsSystem) return 1;
return a.localeCompare(b);
});
// 显示所有插件及其状态
for (const pluginName of sortedPlugins) {
const plugin = plugins.get(pluginName);
const isEnabled = plugin?.setup?.enable || false;
const typeLabel = configSystemPlugins.includes(pluginName) ? '内置' : '用户';
const versionInfo = plugin?.version ? \`-\${plugin.version}\` : '';
// 只使用两种状态标识:绿色(启用)和红色(未启用)
msg += \`\${isEnabled ? '🟢' : '🔴'} \${pluginName}\${versionInfo} (\${typeLabel})\\n\`;
}
await e.reply(msg.trim());
},
"启用": async (ctx, e, args) => {
const pluginName = args[0];
if (!pluginName) return await e.reply("[-]请指定插件名");
const plugins = ctx.plugin.getPlugins();
if (plugins.has(pluginName)) {
const plugin = plugins.get(pluginName);
if (!plugin) return await e.reply("[-]插件信息获取失败");
if (plugin.setup.enable) {
await e.reply(\`[-]插件\${pluginName}已经在运行中\`);
return;
}
await e.reply(ctx.plugin.onPlugin(pluginName));
} else {
if (!existsSync(join(process.cwd(), "plugins", pluginName))) {
await e.reply(\`[-]未找到该插件, 请确认插件存在: \${pluginName}\`);
return;
}
// 先加载插件
const result = await ctx.plugin.loadPlugin(pluginName);
if (!result) {
await e.reply(\`[-]插件\${pluginName}加载失败, 具体原因请看日志\`);
return;
}
// 加载成功后立即启用
const enableResult = ctx.plugin.onPlugin(pluginName);
if (enableResult.startsWith("[-]")) {
await e.reply(\`[!]插件\${pluginName}已加载但启用失败: \${enableResult}\`);
return;
}
await e.reply(\`[+]插件\${pluginName}已加载并启用\`);
}
},
"禁用": async (ctx, e, args) => {
const pluginName = args[0];
if (!pluginName) return await e.reply("[-]请指定插件名");
// 防止禁用系统关键插件
if (pluginName === "cmds") {
return await e.reply("[-]不能禁用核心命令插件");
}
// 执行禁用操作
await e.reply(\`[*]正在禁用插件: \${pluginName}...\`);
const result = ctx.plugin.offPlugin(pluginName);
await e.reply(result);
},
"重载": async (ctx, e, args) => {
const pluginName = args[0];
if (!pluginName) return await e.reply("[-]请指定插件名");
const result = await ctx.plugin.reloadPlugin(pluginName);
if (!result) {
await e.reply(\`[-]插件\${pluginName}重载失败\`);
return;
}
await e.reply(\`[+]插件\${pluginName}已重载\`);
}
},
help: "〓 🧩 Bot 插件 〓\\n#插件 列表\\n#插件 启用 <插件名>\\n#插件 禁用 <插件名>\\n#插件 重载 <插件名>"
},
"设置": {
subcommands: {
"详情": async (ctx, e) => {
if (!ctx.isMaster(e)) return await e.reply("[-]权限不足");
const msg = \`〓 ⚙️ Bot 设置 〓\\n主人: \${ctx.config.master.join(", ")}\\n管理员: \${ctx.config.admins.join(", ")}\`;
await e.reply(msg);
},
"加主人": async (ctx, e, args) => {
if (!ctx.isMaster(e)) return await e.reply("[-]权限不足");
// TODO: Implement config modification
await e.reply("[-]功能开发中");
},
"删主人": async (ctx, e, args) => {
if (!ctx.isMaster(e)) return await e.reply("[-]权限不足");
// TODO: Implement config modification
await e.reply("[-]功能开发中");
},
"加管理": async (ctx, e, args) => {
if (!ctx.isMaster(e)) return await e.reply("[-]权限不足");
// TODO: Implement config modification
await e.reply("[-]功能开发中");
},
"删管理": async (ctx, e, args) => {
if (!ctx.isMaster(e)) return await e.reply("[-]权限不足");
// TODO: Implement config modification
await e.reply("[-]功能开发中");
}
},
help: "〓 ⚙️ Bot 设置 〓\\n#设置 详情\\n#设置 [加/删]主人 <QQ/AT>\\n#设置 [加/删]管理 <QQ/AT>"
},
"帮助": {
handler: async (ctx, e) => {
const msg = "〓 💡 CyberBot 帮助 〓\\n#帮助 👉 显示帮助信息\\n#插件 👉 框架插件管理\\n#设置 👉 框架设置管理\\n#状态 👉 显示框架状态\\n#更新 👉 更新框架版本\\n#退出 👉 退出框架进程";
await e.reply(msg);
}
},
"更新": {
handler: async (ctx, e) => {
try {
if (!ctx.isMaster(e)) return await e.reply("[-]权限不足");
await e.reply("[*]正在检查 cyberbot-core 更新...");
// 获取当前版本
const {stdout: currentVersion} = await execAsync("npm list cyberbot-core --json");
const currentVersionData = JSON.parse(currentVersion);
const currentVersionNumber = currentVersionData.dependencies?.["cyberbot-core"]?.version || "未知";
// 检查最新版本
const {stdout: latestVersion} = await execAsync("npm view cyberbot-core version");
const latestVersionNumber = latestVersion.trim();
if (currentVersionNumber === "未知") {
await e.reply("[!]无法获取当前版本信息");
return;
}
await e.reply(\`[*]当前版本: \${currentVersionNumber}\\n[*]最新版本: \${latestVersionNumber}\`);
// 比较版本
if (currentVersionNumber === latestVersionNumber) {
await e.reply("[+]已经是最新版本,无需更新");
return;
}
// 执行更新
await e.reply("[*]开始更新 cyberbot-core...");
const {stdout: updateOutput} = await execAsync("npm update cyberbot-core");
await e.reply(\`[+]更新成功!\\n从 \${currentVersionNumber} 更新到 \${latestVersionNumber}\\n需要重启框架才能生效\`);
} catch (error) {
console.error("更新失败:", error);
await e.reply(\`[-]更新失败: \${error.message || "未知错误"}\`);
}
}
},
"退出": {
handler: async (ctx, e) => {
if (!ctx.isMaster(e)) return await e.reply("[-]权限不足");
await e.reply("[+]正在关闭...");
process.exit(0);
}
}
};
export default definePlugin({
name: "cmds",
version: "1.0.0",
description: "基础插件",
setup: (ctx) => {
ctx.handle("message", async (e) => {
if (!e.raw_message.startsWith("#") || !ctx.hasRight(e.sender.user_id)) return;
const [cmd, subcmd, ...args] = e.raw_message.slice(1).split(" ");
const command = commands[cmd];
if (!command) return;
try {
if (command.handler) {
return await command.handler(ctx, e, args);
} else if (command.subcommands) {
if (!subcmd) {
return await e.reply(command.help || "[-]请指定子命令");
}
const subHandler = command.subcommands[subcmd];
if (subHandler) {
return await subHandler(ctx, e, args);
} else {
return await e.reply(command.help || "[-]未知的子命令");
}
}
} catch (error) {
return await e.reply(\`[-]命令执行出错: \${error.message || "未知错误"}\`);
}
});
}
});
type FsSizeData = {
fs: string;
type: string;
size: number;
used: number;
available: number;
mount: string;
[key: string]: any; // 允许其他可能的属性
}
// 封装成一个函数,获取指定路径所在硬盘的信息
const getDiskInfo = async (path = process.cwd()) => {
try {
const disks = await fsSize();
const GB = 1073741824;
// 明确声明 targetDisk 可能是 FsSizeData 或 null
let targetDisk: FsSizeData | null = null;
let maxMountLength = 0;
for (const disk of disks) {
if (path.startsWith(disk.mount) && disk.mount.length > maxMountLength) {
targetDisk = disk;
maxMountLength = disk.mount.length;
}
}
if (!targetDisk) throw new Error(\`找不到路径 \${path} 对应的磁盘\`);
const sizeGB = targetDisk.size / GB;
const availableGB = targetDisk.available / GB;
return {
total: parseFloat(sizeGB.toFixed(2)),
used: parseFloat((sizeGB - availableGB).toFixed(2)),
available: parseFloat(availableGB.toFixed(2))
};
} catch (err) {
console.error("获取磁盘信息失败:", err);
return { total: 100, used: 50, available: 50 };
}
};
`;
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');
}
// 提示用户输入并生成配置文件
export 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, 'config.json');
if (fs.existsSync(configFilePath)) {
console.log('配置文件 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();
});
}
// 调用 generateConfig 函数
generateConfig();
//# sourceMappingURL=cli.js.map