feat: 添加 CLI 插件管理工具

This commit is contained in:
sliverp
2026-02-05 17:05:33 +08:00
parent 98c7a598d8
commit 15ea067d8d
4 changed files with 500 additions and 915 deletions

227
bin/qqbot-cli.js Normal file
View File

@@ -0,0 +1,227 @@
#!/usr/bin/env node
/**
* QQBot CLI - 用于升级和管理 QQBot 插件
*
* 用法:
* npx @sliverp/qqbot upgrade # 升级插件
* npx @sliverp/qqbot install # 安装插件
*/
import { execSync } from 'child_process';
import { existsSync, readFileSync, writeFileSync, rmSync } from 'fs';
import { homedir } from 'os';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 获取包的根目录
const PKG_ROOT = join(__dirname, '..');
const args = process.argv.slice(2);
const command = args[0];
// 检测使用的是 clawdbot 还是 openclaw
function detectInstallation() {
const home = homedir();
if (existsSync(join(home, '.openclaw'))) {
return 'openclaw';
}
if (existsSync(join(home, '.clawdbot'))) {
return 'clawdbot';
}
return null;
}
// 清理旧版本插件,返回旧的 qqbot 配置
function cleanupInstallation(appName) {
const home = homedir();
const appDir = join(home, `.${appName}`);
const configFile = join(appDir, `${appName}.json`);
const extensionDir = join(appDir, 'extensions', 'qqbot');
let oldQqbotConfig = null;
console.log(`\n>>> 处理 ${appName} 安装...`);
// 1. 先读取旧的 qqbot 配置
if (existsSync(configFile)) {
try {
const config = JSON.parse(readFileSync(configFile, 'utf8'));
if (config.channels?.qqbot) {
oldQqbotConfig = { ...config.channels.qqbot };
console.log('已保存旧的 qqbot 配置');
}
} catch (err) {
console.error('读取配置文件失败:', err.message);
}
}
// 2. 删除旧的扩展目录
if (existsSync(extensionDir)) {
console.log(`删除旧版本插件: ${extensionDir}`);
rmSync(extensionDir, { recursive: true, force: true });
} else {
console.log('未找到旧版本插件目录,跳过删除');
}
// 3. 清理配置文件中的 qqbot 相关字段
if (existsSync(configFile)) {
console.log('清理配置文件中的 qqbot 字段...');
try {
const config = JSON.parse(readFileSync(configFile, 'utf8'));
// 删除 channels.qqbot
if (config.channels?.qqbot) {
delete config.channels.qqbot;
console.log(' - 已删除 channels.qqbot');
}
// 删除 plugins.entries.qqbot
if (config.plugins?.entries?.qqbot) {
delete config.plugins.entries.qqbot;
console.log(' - 已删除 plugins.entries.qqbot');
}
// 删除 plugins.installs.qqbot
if (config.plugins?.installs?.qqbot) {
delete config.plugins.installs.qqbot;
console.log(' - 已删除 plugins.installs.qqbot');
}
writeFileSync(configFile, JSON.stringify(config, null, 2));
console.log('配置文件已更新');
} catch (err) {
console.error('清理配置文件失败:', err.message);
}
} else {
console.log(`未找到配置文件: ${configFile}`);
}
return oldQqbotConfig;
}
// 执行命令并继承 stdio
function runCommand(cmd, args = []) {
try {
execSync([cmd, ...args].join(' '), { stdio: 'inherit' });
return true;
} catch (err) {
return false;
}
}
// 升级命令
function upgrade() {
console.log('=== QQBot 插件升级脚本 ===');
let foundInstallation = null;
let savedConfig = null;
const home = homedir();
// 检查 openclaw
if (existsSync(join(home, '.openclaw'))) {
savedConfig = cleanupInstallation('openclaw');
foundInstallation = 'openclaw';
}
// 检查 clawdbot
if (existsSync(join(home, '.clawdbot'))) {
const clawdbotConfig = cleanupInstallation('clawdbot');
if (!savedConfig) savedConfig = clawdbotConfig;
foundInstallation = 'clawdbot';
}
if (!foundInstallation) {
console.log('\n未找到 clawdbot 或 openclaw 安装目录');
console.log('请确认已安装 clawdbot 或 openclaw');
process.exit(1);
}
console.log('\n=== 清理完成 ===');
// 自动安装插件
console.log('\n[1/2] 安装新版本插件...');
runCommand(foundInstallation, ['plugins', 'install', '@sliverp/qqbot']);
// 自动配置通道(使用保存的 appId 和 clientSecret
console.log('\n[2/2] 配置机器人通道...');
if (savedConfig?.appId && savedConfig?.clientSecret) {
const token = `${savedConfig.appId}:${savedConfig.clientSecret}`;
console.log(`使用已保存的配置: appId=${savedConfig.appId}`);
runCommand(foundInstallation, ['channels', 'add', '--channel', 'qqbot', '--token', `"${token}"`]);
// 恢复其他配置项(如 markdownSupport
if (savedConfig.markdownSupport !== undefined) {
runCommand(foundInstallation, ['config', 'set', 'channels.qqbot.markdownSupport', String(savedConfig.markdownSupport)]);
}
} else {
console.log('未找到已保存的 qqbot 配置,请手动配置:');
console.log(` ${foundInstallation} channels add --channel qqbot --token "AppID:AppSecret"`);
return;
}
console.log('\n=== 升级完成 ===');
console.log(`\n可以运行以下命令前台运行启动机器人:`);
console.log(` ${foundInstallation} gateway stop && ${foundInstallation} gateway --port 18789 --verbose`);
}
// 安装命令
function install() {
console.log('=== QQBot 插件安装 ===');
const cmd = detectInstallation();
if (!cmd) {
console.log('未找到 clawdbot 或 openclaw 安装');
console.log('请先安装 openclaw 或 clawdbot');
process.exit(1);
}
console.log(`\n使用 ${cmd} 安装插件...`);
runCommand(cmd, ['plugins', 'install', '@sliverp/qqbot']);
console.log('\n=== 安装完成 ===');
console.log('\n请配置机器人通道:');
console.log(` ${cmd} channels add --channel qqbot --token "AppID:AppSecret"`);
}
// 显示帮助
function showHelp() {
console.log(`
QQBot CLI - QQ机器人插件管理工具
用法:
npx @sliverp/qqbot <命令>
命令:
upgrade 清理旧版本插件(升级前执行)
install 安装插件到 openclaw/clawdbot
示例:
npx @sliverp/qqbot upgrade
npx @sliverp/qqbot install
`);
}
// 主入口
switch (command) {
case 'upgrade':
upgrade();
break;
case 'install':
install();
break;
case '-h':
case '--help':
case 'help':
showHelp();
break;
default:
if (command) {
console.log(`未知命令: ${command}`);
}
showHelp();
process.exit(command ? 1 : 0);
}

1153
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,22 @@
{
"name": "qqbot",
"version": "1.3.0",
"name": "@sliverp/qqbot",
"version": "1.3.7",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"bin": {
"qqbot": "./bin/qqbot-cli.js"
},
"files": [
"dist",
"bin",
"src",
"index.ts",
"tsconfig.json",
"openclaw.plugin.json",
"clawdbot.plugin.json",
"moltbot.plugin.json"
],
"clawdbot": {
"extensions": ["./index.ts"]
},
@@ -33,5 +46,6 @@
"clawdbot": "*",
"moltbot": "*",
"openclaw": "*"
}
},
"homepage": "https://github.com/sliverp/qqbot"
}

View File

@@ -2,7 +2,7 @@ import WebSocket from "ws";
import path from "node:path";
import * as fs from "node:fs";
import type { ResolvedQQBotAccount, WSPayload, C2CMessageEvent, GuildMessageEvent, GroupMessageEvent } from "./types.js";
import { getAccessToken, getGatewayUrl, sendC2CMessage, sendChannelMessage, sendGroupMessage, clearTokenCache, sendC2CImageMessage, sendGroupImageMessage, initApiConfig, startBackgroundTokenRefresh, stopBackgroundTokenRefresh } from "./api.js";
import { getAccessToken, getGatewayUrl, sendC2CMessage, sendChannelMessage, sendGroupMessage, clearTokenCache, sendC2CImageMessage, sendGroupImageMessage, initApiConfig, startBackgroundTokenRefresh, stopBackgroundTokenRefresh, sendC2CInputNotify } from "./api.js";
import { loadSession, saveSession, clearSession, type SessionState } from "./session-store.js";
import { recordKnownUser, flushKnownUsers } from "./known-users.js";
import { getQQBotRuntime } from "./runtime.js";
@@ -412,6 +412,13 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
direction: "inbound",
});
try{
await sendC2CInputNotify(accessToken, event.senderId, event.messageId, 60);
log?.info(`[qqbot:${account.accountId}] Sent input notify to ${event.senderId}`);
}catch(err){
log?.error(`[qqbot:${account.accountId}] sendC2CInputNotify error: ${err}`);
}
const isGroup = event.type === "guild" || event.type === "group";
const peerId = event.type === "guild" ? `channel:${event.channelId}`
: event.type === "group" ? `group:${event.groupOpenid}`
@@ -810,7 +817,7 @@ openclaw cron add \\
log?.info(`[qqbot:${account.accountId}] Sent image via <qqimg> tag: ${imagePath.slice(0, 60)}...`);
} catch (err) {
log?.error(`[qqbot:${account.accountId}] Failed to send image from <qqimg>: ${err}`);
await sendErrorMessage(`发送图片失败: ${err}`);
await sendErrorMessage(`图片发送失败,图片似乎不存在哦,图片路径:${imagePath}`);
}
}
}
@@ -1250,7 +1257,9 @@ openclaw cron add \\
}
},
},
replyOptions: {},
replyOptions: {
disableBlockStreaming: false,
},
});
// 等待分发完成或超时