@leoqlin/openclaw-qqbot 1.6.7-alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +484 -0
- package/README.zh.md +479 -0
- package/bin/qqbot-cli.js +243 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +26 -0
- package/dist/src/admin-resolver.d.ts +33 -0
- package/dist/src/admin-resolver.js +157 -0
- package/dist/src/api.d.ts +301 -0
- package/dist/src/api.js +890 -0
- package/dist/src/channel.d.ts +29 -0
- package/dist/src/channel.js +452 -0
- package/dist/src/config.d.ts +56 -0
- package/dist/src/config.js +278 -0
- package/dist/src/credential-backup.d.ts +31 -0
- package/dist/src/credential-backup.js +66 -0
- package/dist/src/deliver-debounce.d.ts +74 -0
- package/dist/src/deliver-debounce.js +174 -0
- package/dist/src/gateway.d.ts +18 -0
- package/dist/src/gateway.js +2005 -0
- package/dist/src/group-history.d.ts +136 -0
- package/dist/src/group-history.js +226 -0
- package/dist/src/image-server.d.ts +87 -0
- package/dist/src/image-server.js +570 -0
- package/dist/src/inbound-attachments.d.ts +60 -0
- package/dist/src/inbound-attachments.js +248 -0
- package/dist/src/known-users.d.ts +100 -0
- package/dist/src/known-users.js +263 -0
- package/dist/src/message-gating.d.ts +53 -0
- package/dist/src/message-gating.js +107 -0
- package/dist/src/message-queue.d.ts +89 -0
- package/dist/src/message-queue.js +257 -0
- package/dist/src/onboarding.d.ts +10 -0
- package/dist/src/onboarding.js +203 -0
- package/dist/src/outbound-deliver.d.ts +48 -0
- package/dist/src/outbound-deliver.js +392 -0
- package/dist/src/outbound.d.ts +205 -0
- package/dist/src/outbound.js +938 -0
- package/dist/src/proactive.d.ts +170 -0
- package/dist/src/proactive.js +399 -0
- package/dist/src/ref-index-store.d.ts +101 -0
- package/dist/src/ref-index-store.js +298 -0
- package/dist/src/reply-dispatcher.d.ts +35 -0
- package/dist/src/reply-dispatcher.js +311 -0
- package/dist/src/request-context.d.ts +25 -0
- package/dist/src/request-context.js +37 -0
- package/dist/src/runtime.d.ts +3 -0
- package/dist/src/runtime.js +10 -0
- package/dist/src/session-store.d.ts +52 -0
- package/dist/src/session-store.js +254 -0
- package/dist/src/slash-commands.d.ts +77 -0
- package/dist/src/slash-commands.js +1866 -0
- package/dist/src/startup-greeting.d.ts +30 -0
- package/dist/src/startup-greeting.js +97 -0
- package/dist/src/streaming.d.ts +247 -0
- package/dist/src/streaming.js +899 -0
- package/dist/src/stt.d.ts +21 -0
- package/dist/src/stt.js +70 -0
- package/dist/src/tools/channel.d.ts +16 -0
- package/dist/src/tools/channel.js +234 -0
- package/dist/src/tools/remind.d.ts +2 -0
- package/dist/src/tools/remind.js +256 -0
- package/dist/src/types.d.ts +367 -0
- package/dist/src/types.js +17 -0
- package/dist/src/typing-keepalive.d.ts +27 -0
- package/dist/src/typing-keepalive.js +64 -0
- package/dist/src/update-checker.d.ts +36 -0
- package/dist/src/update-checker.js +171 -0
- package/dist/src/utils/audio-convert.d.ts +98 -0
- package/dist/src/utils/audio-convert.js +755 -0
- package/dist/src/utils/chunked-upload.d.ts +68 -0
- package/dist/src/utils/chunked-upload.js +341 -0
- package/dist/src/utils/file-utils.d.ts +61 -0
- package/dist/src/utils/file-utils.js +172 -0
- package/dist/src/utils/image-size.d.ts +51 -0
- package/dist/src/utils/image-size.js +234 -0
- package/dist/src/utils/media-send.d.ts +158 -0
- package/dist/src/utils/media-send.js +499 -0
- package/dist/src/utils/media-tags.d.ts +14 -0
- package/dist/src/utils/media-tags.js +165 -0
- package/dist/src/utils/payload.d.ts +112 -0
- package/dist/src/utils/payload.js +186 -0
- package/dist/src/utils/pkg-version.d.ts +5 -0
- package/dist/src/utils/pkg-version.js +61 -0
- package/dist/src/utils/platform.d.ts +137 -0
- package/dist/src/utils/platform.js +390 -0
- package/dist/src/utils/ssrf-guard.d.ts +25 -0
- package/dist/src/utils/ssrf-guard.js +91 -0
- package/dist/src/utils/text-parsing.d.ts +36 -0
- package/dist/src/utils/text-parsing.js +75 -0
- package/dist/src/utils/upload-cache.d.ts +34 -0
- package/dist/src/utils/upload-cache.js +93 -0
- package/index.ts +31 -0
- package/node_modules/@eshaz/web-worker/LICENSE +201 -0
- package/node_modules/@eshaz/web-worker/README.md +134 -0
- package/node_modules/@eshaz/web-worker/browser.js +17 -0
- package/node_modules/@eshaz/web-worker/cjs/browser.js +16 -0
- package/node_modules/@eshaz/web-worker/cjs/node.js +219 -0
- package/node_modules/@eshaz/web-worker/index.d.ts +4 -0
- package/node_modules/@eshaz/web-worker/node.js +223 -0
- package/node_modules/@eshaz/web-worker/package.json +54 -0
- package/node_modules/@wasm-audio-decoders/common/index.js +5 -0
- package/node_modules/@wasm-audio-decoders/common/package.json +36 -0
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderCommon.js +231 -0
- package/node_modules/@wasm-audio-decoders/common/src/WASMAudioDecoderWorker.js +129 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/README +67 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/build_puff.js +31 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.c +863 -0
- package/node_modules/@wasm-audio-decoders/common/src/puff/puff.h +35 -0
- package/node_modules/@wasm-audio-decoders/common/src/utilities.js +3 -0
- package/node_modules/@wasm-audio-decoders/common/types.d.ts +7 -0
- package/node_modules/mpg123-decoder/README.md +265 -0
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js +185 -0
- package/node_modules/mpg123-decoder/dist/mpg123-decoder.min.js.map +1 -0
- package/node_modules/mpg123-decoder/index.js +8 -0
- package/node_modules/mpg123-decoder/package.json +58 -0
- package/node_modules/mpg123-decoder/src/EmscriptenWasm.js +464 -0
- package/node_modules/mpg123-decoder/src/MPEGDecoder.js +200 -0
- package/node_modules/mpg123-decoder/src/MPEGDecoderWebWorker.js +21 -0
- package/node_modules/mpg123-decoder/types.d.ts +30 -0
- package/node_modules/silk-wasm/LICENSE +21 -0
- package/node_modules/silk-wasm/README.md +85 -0
- package/node_modules/silk-wasm/lib/index.cjs +16 -0
- package/node_modules/silk-wasm/lib/index.d.ts +70 -0
- package/node_modules/silk-wasm/lib/index.mjs +16 -0
- package/node_modules/silk-wasm/lib/silk.wasm +0 -0
- package/node_modules/silk-wasm/lib/utils.d.ts +4 -0
- package/node_modules/silk-wasm/package.json +39 -0
- package/node_modules/simple-yenc/.github/FUNDING.yml +1 -0
- package/node_modules/simple-yenc/.prettierignore +1 -0
- package/node_modules/simple-yenc/LICENSE +7 -0
- package/node_modules/simple-yenc/README.md +163 -0
- package/node_modules/simple-yenc/dist/esm.js +1 -0
- package/node_modules/simple-yenc/dist/index.js +1 -0
- package/node_modules/simple-yenc/package.json +50 -0
- package/node_modules/simple-yenc/rollup.config.js +27 -0
- package/node_modules/simple-yenc/src/simple-yenc.js +302 -0
- package/node_modules/ws/LICENSE +20 -0
- package/node_modules/ws/README.md +548 -0
- package/node_modules/ws/browser.js +8 -0
- package/node_modules/ws/index.js +22 -0
- package/node_modules/ws/lib/buffer-util.js +131 -0
- package/node_modules/ws/lib/constants.js +19 -0
- package/node_modules/ws/lib/event-target.js +292 -0
- package/node_modules/ws/lib/extension.js +203 -0
- package/node_modules/ws/lib/limiter.js +55 -0
- package/node_modules/ws/lib/permessage-deflate.js +528 -0
- package/node_modules/ws/lib/receiver.js +706 -0
- package/node_modules/ws/lib/sender.js +602 -0
- package/node_modules/ws/lib/stream.js +161 -0
- package/node_modules/ws/lib/subprotocol.js +62 -0
- package/node_modules/ws/lib/validation.js +152 -0
- package/node_modules/ws/lib/websocket-server.js +554 -0
- package/node_modules/ws/lib/websocket.js +1393 -0
- package/node_modules/ws/package.json +70 -0
- package/node_modules/ws/wrapper.mjs +21 -0
- package/openclaw.plugin.json +17 -0
- package/package.json +70 -0
- package/preload.cjs +33 -0
- package/scripts/cleanup-legacy-plugins.sh +124 -0
- package/scripts/link-sdk-core.cjs +185 -0
- package/scripts/postinstall-link-sdk.js +126 -0
- package/scripts/proactive-api-server.ts +369 -0
- package/scripts/send-proactive.ts +293 -0
- package/scripts/set-markdown.sh +156 -0
- package/scripts/test-sendmedia.ts +116 -0
- package/scripts/upgrade-via-npm.ps1 +460 -0
- package/scripts/upgrade-via-npm.sh +652 -0
- package/scripts/upgrade-via-source.sh +1026 -0
- package/skills/qqbot-channel/SKILL.md +263 -0
- package/skills/qqbot-channel/references/api_references.md +521 -0
- package/skills/qqbot-media/SKILL.md +60 -0
- package/skills/qqbot-remind/SKILL.md +159 -0
- package/src/admin-resolver.ts +181 -0
- package/src/api.ts +1284 -0
- package/src/channel.ts +477 -0
- package/src/config.ts +347 -0
- package/src/credential-backup.ts +72 -0
- package/src/deliver-debounce.ts +229 -0
- package/src/gateway.ts +2245 -0
- package/src/group-history.ts +328 -0
- package/src/image-server.ts +675 -0
- package/src/inbound-attachments.ts +321 -0
- package/src/known-users.ts +353 -0
- package/src/message-gating.ts +190 -0
- package/src/message-queue.ts +352 -0
- package/src/onboarding.ts +274 -0
- package/src/openclaw-plugin-sdk.d.ts +587 -0
- package/src/outbound-deliver.ts +473 -0
- package/src/outbound.ts +1131 -0
- package/src/proactive.ts +530 -0
- package/src/ref-index-store.ts +412 -0
- package/src/reply-dispatcher.ts +334 -0
- package/src/request-context.ts +49 -0
- package/src/runtime.ts +14 -0
- package/src/session-store.ts +303 -0
- package/src/slash-commands.ts +2030 -0
- package/src/startup-greeting.ts +120 -0
- package/src/streaming.ts +1077 -0
- package/src/stt.ts +86 -0
- package/src/tools/channel.ts +281 -0
- package/src/tools/remind.ts +308 -0
- package/src/types.ts +391 -0
- package/src/typing-keepalive.ts +59 -0
- package/src/update-checker.ts +186 -0
- package/src/utils/audio-convert.ts +859 -0
- package/src/utils/chunked-upload.ts +483 -0
- package/src/utils/file-utils.ts +193 -0
- package/src/utils/image-size.ts +266 -0
- package/src/utils/media-send.ts +631 -0
- package/src/utils/media-tags.ts +183 -0
- package/src/utils/payload.ts +265 -0
- package/src/utils/pkg-version.ts +64 -0
- package/src/utils/platform.ts +435 -0
- package/src/utils/ssrf-guard.ts +102 -0
- package/src/utils/text-parsing.ts +85 -0
- package/src/utils/upload-cache.ts +128 -0
- package/tsconfig.json +16 -0
package/bin/qqbot-cli.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* qqbot CLI - 用于升级和管理 qqbot 插件
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* npx openclaw-qqbot upgrade # 升级插件
|
|
8
|
+
* npx openclaw-qqbot install # 安装插件
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, rmSync } from 'fs';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import { join, dirname } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
|
|
20
|
+
// 获取包的根目录
|
|
21
|
+
const PKG_ROOT = join(__dirname, '..');
|
|
22
|
+
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const command = args[0];
|
|
25
|
+
|
|
26
|
+
// 检测使用的是 clawdbot 还是 openclaw
|
|
27
|
+
function detectInstallation() {
|
|
28
|
+
const home = homedir();
|
|
29
|
+
if (existsSync(join(home, '.openclaw'))) {
|
|
30
|
+
return 'openclaw';
|
|
31
|
+
}
|
|
32
|
+
if (existsSync(join(home, '.clawdbot'))) {
|
|
33
|
+
return 'clawdbot';
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 需要清理的所有可能的插件 ID / 包名(原仓库 + 本仓库 + 框架推断名)
|
|
39
|
+
const PLUGIN_IDS = ['qqbot', 'openclaw-qq', '@sliverp/qqbot', '@tencent-connect/openclaw-qq', '@tencent-connect/qqbot', '@tencent-connect/openclaw-qqbot', 'openclaw-qqbot'];
|
|
40
|
+
// 可能的扩展目录名
|
|
41
|
+
const EXTENSION_DIR_NAMES = ['qqbot', 'openclaw-qq', 'openclaw-qqbot'];
|
|
42
|
+
|
|
43
|
+
// 清理旧版本插件,返回旧的 qqbot 配置
|
|
44
|
+
function cleanupInstallation(appName) {
|
|
45
|
+
const home = homedir();
|
|
46
|
+
const appDir = join(home, `.${appName}`);
|
|
47
|
+
const configFile = join(appDir, `${appName}.json`);
|
|
48
|
+
|
|
49
|
+
let oldQqbotConfig = null;
|
|
50
|
+
|
|
51
|
+
console.log(`\n>>> 处理 ${appName} 安装...`);
|
|
52
|
+
|
|
53
|
+
// 1. 先读取旧的 qqbot 配置(尝试所有可能的 channel key)
|
|
54
|
+
if (existsSync(configFile)) {
|
|
55
|
+
try {
|
|
56
|
+
const config = JSON.parse(readFileSync(configFile, 'utf8'));
|
|
57
|
+
if (config.channels?.qqbot) {
|
|
58
|
+
oldQqbotConfig = { ...config.channels.qqbot };
|
|
59
|
+
console.log('已保存旧的 qqbot 配置');
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('读取配置文件失败:', err.message);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 2. 删除所有可能的旧扩展目录
|
|
67
|
+
for (const dirName of EXTENSION_DIR_NAMES) {
|
|
68
|
+
const extensionDir = join(appDir, 'extensions', dirName);
|
|
69
|
+
if (existsSync(extensionDir)) {
|
|
70
|
+
console.log(`删除旧版本插件: ${extensionDir}`);
|
|
71
|
+
rmSync(extensionDir, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. 清理配置文件中所有可能的插件 ID 相关字段
|
|
76
|
+
if (existsSync(configFile)) {
|
|
77
|
+
console.log('清理配置文件中的插件字段...');
|
|
78
|
+
try {
|
|
79
|
+
const config = JSON.parse(readFileSync(configFile, 'utf8'));
|
|
80
|
+
|
|
81
|
+
for (const id of PLUGIN_IDS) {
|
|
82
|
+
// 删除 channels.<id>
|
|
83
|
+
if (config.channels?.[id]) {
|
|
84
|
+
delete config.channels[id];
|
|
85
|
+
console.log(` - 已删除 channels.${id}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 删除 plugins.entries.<id>
|
|
89
|
+
if (config.plugins?.entries?.[id]) {
|
|
90
|
+
delete config.plugins.entries[id];
|
|
91
|
+
console.log(` - 已删除 plugins.entries.${id}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 删除 plugins.installs.<id>
|
|
95
|
+
if (config.plugins?.installs?.[id]) {
|
|
96
|
+
delete config.plugins.installs[id];
|
|
97
|
+
console.log(` - 已删除 plugins.installs.${id}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 删除 plugins.allow 中的 <id>
|
|
101
|
+
if (Array.isArray(config.plugins?.allow)) {
|
|
102
|
+
const before = config.plugins.allow.length;
|
|
103
|
+
config.plugins.allow = config.plugins.allow.filter((x) => x !== id);
|
|
104
|
+
if (config.plugins.allow.length !== before) {
|
|
105
|
+
console.log(` - 已删除 plugins.allow.${id}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
111
|
+
console.log('配置文件已更新');
|
|
112
|
+
} catch (err) {
|
|
113
|
+
console.error('清理配置文件失败:', err.message);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
console.log(`未找到配置文件: ${configFile}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return oldQqbotConfig;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 执行命令并继承 stdio
|
|
123
|
+
function runCommand(cmd, args = []) {
|
|
124
|
+
try {
|
|
125
|
+
execSync([cmd, ...args].join(' '), { stdio: 'inherit' });
|
|
126
|
+
return true;
|
|
127
|
+
} catch (err) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 升级命令
|
|
133
|
+
function upgrade() {
|
|
134
|
+
console.log('=== qqbot 插件升级脚本 ===');
|
|
135
|
+
|
|
136
|
+
let foundInstallation = null;
|
|
137
|
+
let savedConfig = null;
|
|
138
|
+
const home = homedir();
|
|
139
|
+
|
|
140
|
+
// 检查 openclaw
|
|
141
|
+
if (existsSync(join(home, '.openclaw'))) {
|
|
142
|
+
savedConfig = cleanupInstallation('openclaw');
|
|
143
|
+
foundInstallation = 'openclaw';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 检查 clawdbot
|
|
147
|
+
if (existsSync(join(home, '.clawdbot'))) {
|
|
148
|
+
const clawdbotConfig = cleanupInstallation('clawdbot');
|
|
149
|
+
if (!savedConfig) savedConfig = clawdbotConfig;
|
|
150
|
+
foundInstallation = 'clawdbot';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!foundInstallation) {
|
|
154
|
+
console.log('\n未找到 clawdbot 或 openclaw 安装目录');
|
|
155
|
+
console.log('请确认已安装 clawdbot 或 openclaw');
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('\n=== 清理完成 ===');
|
|
160
|
+
|
|
161
|
+
// 自动安装插件
|
|
162
|
+
console.log('\n[1/2] 安装新版本插件...');
|
|
163
|
+
runCommand(foundInstallation, ['plugins', 'install', 'openclaw-qqbot']);
|
|
164
|
+
|
|
165
|
+
// 自动配置通道(使用保存的 appId 和 clientSecret)
|
|
166
|
+
console.log('\n[2/2] 配置机器人通道...');
|
|
167
|
+
if (savedConfig?.appId && savedConfig?.clientSecret) {
|
|
168
|
+
const token = `${savedConfig.appId}:${savedConfig.clientSecret}`;
|
|
169
|
+
console.log(`使用已保存的配置: appId=${savedConfig.appId}`);
|
|
170
|
+
runCommand(foundInstallation, ['channels', 'add', '--channel', 'qqbot', '--token', `"${token}"`]);
|
|
171
|
+
|
|
172
|
+
// 恢复其他配置项(如 markdownSupport)
|
|
173
|
+
if (savedConfig.markdownSupport !== undefined) {
|
|
174
|
+
runCommand(foundInstallation, ['config', 'set', 'channels.qqbot.markdownSupport', String(savedConfig.markdownSupport)]);
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
console.log('未找到已保存的 qqbot 配置,请手动配置:');
|
|
178
|
+
console.log(` ${foundInstallation} channels add --channel qqbot --token "appid:appsecret"`);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log('\n=== 升级完成 ===');
|
|
183
|
+
console.log(`\n可以运行以下命令前台运行启动机器人:`);
|
|
184
|
+
console.log(` ${foundInstallation} gateway stop && ${foundInstallation} gateway --port 18789 --verbose`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 安装命令
|
|
188
|
+
function install() {
|
|
189
|
+
console.log('=== qqbot 插件安装 ===');
|
|
190
|
+
|
|
191
|
+
const cmd = detectInstallation();
|
|
192
|
+
if (!cmd) {
|
|
193
|
+
console.log('未找到 clawdbot 或 openclaw 安装');
|
|
194
|
+
console.log('请先安装 openclaw 或 clawdbot');
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(`\n使用 ${cmd} 安装插件...`);
|
|
199
|
+
runCommand(cmd, ['plugins', 'install', '@tencent-connect/openclaw-qqbot']);
|
|
200
|
+
|
|
201
|
+
console.log('\n=== 安装完成 ===');
|
|
202
|
+
console.log('\n请配置机器人通道:');
|
|
203
|
+
console.log(` ${cmd} channels add --channel qqbot --token "appid:appsecret"`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 显示帮助
|
|
207
|
+
function showHelp() {
|
|
208
|
+
console.log(`
|
|
209
|
+
qqbot CLI - QQ机器人插件管理工具
|
|
210
|
+
|
|
211
|
+
用法:
|
|
212
|
+
npx openclaw-qqbot <命令>
|
|
213
|
+
|
|
214
|
+
命令:
|
|
215
|
+
upgrade 清理旧版本插件(升级前执行)
|
|
216
|
+
install 安装插件到 openclaw/clawdbot
|
|
217
|
+
|
|
218
|
+
示例:
|
|
219
|
+
npx openclaw-qqbot upgrade
|
|
220
|
+
npx openclaw-qqbot install
|
|
221
|
+
`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 主入口
|
|
225
|
+
switch (command) {
|
|
226
|
+
case 'upgrade':
|
|
227
|
+
upgrade();
|
|
228
|
+
break;
|
|
229
|
+
case 'install':
|
|
230
|
+
install();
|
|
231
|
+
break;
|
|
232
|
+
case '-h':
|
|
233
|
+
case '--help':
|
|
234
|
+
case 'help':
|
|
235
|
+
showHelp();
|
|
236
|
+
break;
|
|
237
|
+
default:
|
|
238
|
+
if (command) {
|
|
239
|
+
console.log(`未知命令: ${command}`);
|
|
240
|
+
}
|
|
241
|
+
showHelp();
|
|
242
|
+
process.exit(command ? 1 : 0);
|
|
243
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
declare const plugin: {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
configSchema: unknown;
|
|
7
|
+
register(api: OpenClawPluginApi): void;
|
|
8
|
+
};
|
|
9
|
+
export default plugin;
|
|
10
|
+
export { qqbotPlugin } from "./src/channel.js";
|
|
11
|
+
export { setQQBotRuntime, getQQBotRuntime } from "./src/runtime.js";
|
|
12
|
+
export { qqbotOnboardingAdapter } from "./src/onboarding.js";
|
|
13
|
+
export * from "./src/types.js";
|
|
14
|
+
export * from "./src/api.js";
|
|
15
|
+
export * from "./src/config.js";
|
|
16
|
+
export * from "./src/gateway.js";
|
|
17
|
+
export * from "./src/outbound.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
|
|
2
|
+
import { qqbotPlugin } from "./src/channel.js";
|
|
3
|
+
import { setQQBotRuntime } from "./src/runtime.js";
|
|
4
|
+
import { registerChannelTool } from "./src/tools/channel.js";
|
|
5
|
+
import { registerRemindTool } from "./src/tools/remind.js";
|
|
6
|
+
const plugin = {
|
|
7
|
+
id: "openclaw-qqbot",
|
|
8
|
+
name: "QQ Bot",
|
|
9
|
+
description: "QQ Bot channel plugin",
|
|
10
|
+
configSchema: emptyPluginConfigSchema(),
|
|
11
|
+
register(api) {
|
|
12
|
+
setQQBotRuntime(api.runtime);
|
|
13
|
+
api.registerChannel({ plugin: qqbotPlugin });
|
|
14
|
+
registerChannelTool(api);
|
|
15
|
+
registerRemindTool(api);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
export default plugin;
|
|
19
|
+
export { qqbotPlugin } from "./src/channel.js";
|
|
20
|
+
export { setQQBotRuntime, getQQBotRuntime } from "./src/runtime.js";
|
|
21
|
+
export { qqbotOnboardingAdapter } from "./src/onboarding.js";
|
|
22
|
+
export * from "./src/types.js";
|
|
23
|
+
export * from "./src/api.js";
|
|
24
|
+
export * from "./src/config.js";
|
|
25
|
+
export * from "./src/gateway.js";
|
|
26
|
+
export * from "./src/outbound.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理员解析器模块
|
|
3
|
+
* - 管理员 openid 持久化读写
|
|
4
|
+
* - 升级问候目标读写
|
|
5
|
+
* - 启动问候语发送
|
|
6
|
+
*/
|
|
7
|
+
export interface AdminResolverContext {
|
|
8
|
+
accountId: string;
|
|
9
|
+
appId: string;
|
|
10
|
+
clientSecret: string;
|
|
11
|
+
log?: {
|
|
12
|
+
info: (msg: string) => void;
|
|
13
|
+
error: (msg: string) => void;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 读取 admin openid(按 accountId + appId 区分)
|
|
18
|
+
* 兼容策略:新路径优先 → fallback 旧路径 → 自动迁移
|
|
19
|
+
*/
|
|
20
|
+
export declare function loadAdminOpenId(accountId: string, appId: string): string | undefined;
|
|
21
|
+
export declare function saveAdminOpenId(accountId: string, appId: string, openid: string): void;
|
|
22
|
+
export declare function loadUpgradeGreetingTargetOpenId(accountId: string, appId: string, log?: {
|
|
23
|
+
info: (msg: string) => void;
|
|
24
|
+
}): string | undefined;
|
|
25
|
+
export declare function clearUpgradeGreetingTargetOpenId(accountId: string, appId: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* 解析管理员 openid:
|
|
28
|
+
* 1. 优先读持久化文件(按 accountId + appId 区分)
|
|
29
|
+
* 2. fallback 取第一个私聊用户,并写入文件锁定
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveAdminOpenId(ctx: Pick<AdminResolverContext, "accountId" | "appId" | "log">): string | undefined;
|
|
32
|
+
/** 异步发送启动问候语(优先发给升级触发者,fallback 发给管理员) */
|
|
33
|
+
export declare function sendStartupGreetings(ctx: AdminResolverContext, trigger: "READY" | "RESUMED"): void;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 管理员解析器模块
|
|
3
|
+
* - 管理员 openid 持久化读写
|
|
4
|
+
* - 升级问候目标读写
|
|
5
|
+
* - 启动问候语发送
|
|
6
|
+
*/
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import * as fs from "node:fs";
|
|
9
|
+
import { getQQBotDataDir } from "./utils/platform.js";
|
|
10
|
+
import { listKnownUsers } from "./known-users.js";
|
|
11
|
+
import { getAccessToken, sendProactiveC2CMessage } from "./api.js";
|
|
12
|
+
import { getStartupGreetingPlan, markStartupGreetingSent, markStartupGreetingFailed } from "./startup-greeting.js";
|
|
13
|
+
// ---- 文件路径 ----
|
|
14
|
+
function safeName(id) {
|
|
15
|
+
return id.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
16
|
+
}
|
|
17
|
+
/** 新版 admin 文件路径(按 accountId + appId 区分) */
|
|
18
|
+
function getAdminMarkerFile(accountId, appId) {
|
|
19
|
+
return path.join(getQQBotDataDir("data"), `admin-${safeName(accountId)}-${safeName(appId)}.json`);
|
|
20
|
+
}
|
|
21
|
+
/** 旧版 admin 文件路径(仅按 accountId 区分,用于迁移兼容) */
|
|
22
|
+
function getLegacyAdminMarkerFile(accountId) {
|
|
23
|
+
return path.join(getQQBotDataDir("data"), `admin-${accountId}.json`);
|
|
24
|
+
}
|
|
25
|
+
function getUpgradeGreetingTargetFile(accountId, appId) {
|
|
26
|
+
return path.join(getQQBotDataDir("data"), `upgrade-greeting-target-${safeName(accountId)}-${safeName(appId)}.json`);
|
|
27
|
+
}
|
|
28
|
+
// ---- 管理员 openid 持久化 ----
|
|
29
|
+
/**
|
|
30
|
+
* 读取 admin openid(按 accountId + appId 区分)
|
|
31
|
+
* 兼容策略:新路径优先 → fallback 旧路径 → 自动迁移
|
|
32
|
+
*/
|
|
33
|
+
export function loadAdminOpenId(accountId, appId) {
|
|
34
|
+
try {
|
|
35
|
+
// 1. 先尝试新版路径
|
|
36
|
+
const newFile = getAdminMarkerFile(accountId, appId);
|
|
37
|
+
if (fs.existsSync(newFile)) {
|
|
38
|
+
const data = JSON.parse(fs.readFileSync(newFile, "utf8"));
|
|
39
|
+
if (data.openid)
|
|
40
|
+
return data.openid;
|
|
41
|
+
}
|
|
42
|
+
// 2. fallback 旧版路径(仅按 accountId)
|
|
43
|
+
const legacyFile = getLegacyAdminMarkerFile(accountId);
|
|
44
|
+
if (fs.existsSync(legacyFile)) {
|
|
45
|
+
const data = JSON.parse(fs.readFileSync(legacyFile, "utf8"));
|
|
46
|
+
if (data.openid) {
|
|
47
|
+
// 自动迁移:写到新路径,删除旧文件
|
|
48
|
+
saveAdminOpenId(accountId, appId, data.openid);
|
|
49
|
+
try {
|
|
50
|
+
fs.unlinkSync(legacyFile);
|
|
51
|
+
}
|
|
52
|
+
catch { /* ignore */ }
|
|
53
|
+
return data.openid;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch { /* 文件损坏视为无 */ }
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
export function saveAdminOpenId(accountId, appId, openid) {
|
|
61
|
+
try {
|
|
62
|
+
fs.writeFileSync(getAdminMarkerFile(accountId, appId), JSON.stringify({ accountId, appId, openid, savedAt: new Date().toISOString() }));
|
|
63
|
+
}
|
|
64
|
+
catch { /* ignore */ }
|
|
65
|
+
}
|
|
66
|
+
// ---- 升级问候目标 ----
|
|
67
|
+
export function loadUpgradeGreetingTargetOpenId(accountId, appId, log) {
|
|
68
|
+
try {
|
|
69
|
+
const file = getUpgradeGreetingTargetFile(accountId, appId);
|
|
70
|
+
if (fs.existsSync(file)) {
|
|
71
|
+
const data = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
72
|
+
if (!data.openid) {
|
|
73
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target file found but openid is empty`);
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
if (data.appId && data.appId !== appId) {
|
|
77
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target appId mismatch: file=${data.appId}, current=${appId}`);
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
if (data.accountId && data.accountId !== accountId) {
|
|
81
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target accountId mismatch: file=${data.accountId}, current=${accountId}`);
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target loaded: openid=${data.openid}`);
|
|
85
|
+
return data.openid;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target file not found: ${file}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
log?.info(`[qqbot:${accountId}] upgrade-greeting-target file read error: ${err}`);
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
export function clearUpgradeGreetingTargetOpenId(accountId, appId) {
|
|
97
|
+
try {
|
|
98
|
+
const file = getUpgradeGreetingTargetFile(accountId, appId);
|
|
99
|
+
if (fs.existsSync(file)) {
|
|
100
|
+
fs.unlinkSync(file);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch { /* ignore */ }
|
|
104
|
+
}
|
|
105
|
+
// ---- 解析管理员 ----
|
|
106
|
+
/**
|
|
107
|
+
* 解析管理员 openid:
|
|
108
|
+
* 1. 优先读持久化文件(按 accountId + appId 区分)
|
|
109
|
+
* 2. fallback 取第一个私聊用户,并写入文件锁定
|
|
110
|
+
*/
|
|
111
|
+
export function resolveAdminOpenId(ctx) {
|
|
112
|
+
const saved = loadAdminOpenId(ctx.accountId, ctx.appId);
|
|
113
|
+
if (saved)
|
|
114
|
+
return saved;
|
|
115
|
+
const first = listKnownUsers({ accountId: ctx.accountId, type: "c2c", sortBy: "firstSeenAt", sortOrder: "asc", limit: 1 })[0]?.openid;
|
|
116
|
+
if (first) {
|
|
117
|
+
saveAdminOpenId(ctx.accountId, ctx.appId, first);
|
|
118
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Auto-detected admin openid: ${first} (persisted)`);
|
|
119
|
+
}
|
|
120
|
+
return first;
|
|
121
|
+
}
|
|
122
|
+
// ---- 启动问候语 ----
|
|
123
|
+
/** 异步发送启动问候语(优先发给升级触发者,fallback 发给管理员) */
|
|
124
|
+
export function sendStartupGreetings(ctx, trigger) {
|
|
125
|
+
(async () => {
|
|
126
|
+
const plan = getStartupGreetingPlan(ctx.accountId, ctx.appId);
|
|
127
|
+
if (!plan.shouldSend || !plan.greeting) {
|
|
128
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Skipping startup greeting (${plan.reason ?? "debounced"}, trigger=${trigger})`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const upgradeTargetOpenId = loadUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId, ctx.log);
|
|
132
|
+
// 没有 upgrade-greeting-target 文件 → 不是通过 /bot-upgrade 触发的升级
|
|
133
|
+
// (console 手动重启、脚本升级等场景),静默更新 marker 不发消息
|
|
134
|
+
if (!upgradeTargetOpenId) {
|
|
135
|
+
markStartupGreetingSent(ctx.accountId, ctx.appId, plan.version);
|
|
136
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Version changed but no upgrade-greeting-target, silently updating marker (trigger=${trigger})`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Sending startup greeting to upgrade-requester (trigger=${trigger}): "${plan.greeting}"`);
|
|
141
|
+
const token = await getAccessToken(ctx.appId, ctx.clientSecret);
|
|
142
|
+
const GREETING_TIMEOUT_MS = 10_000;
|
|
143
|
+
await Promise.race([
|
|
144
|
+
sendProactiveC2CMessage(token, upgradeTargetOpenId, plan.greeting),
|
|
145
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Startup greeting send timeout (10s)")), GREETING_TIMEOUT_MS)),
|
|
146
|
+
]);
|
|
147
|
+
markStartupGreetingSent(ctx.accountId, ctx.appId, plan.version);
|
|
148
|
+
clearUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId);
|
|
149
|
+
ctx.log?.info(`[qqbot:${ctx.accountId}] Sent startup greeting to upgrade-requester: ${upgradeTargetOpenId}`);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
153
|
+
markStartupGreetingFailed(ctx.accountId, ctx.appId, plan.version, message);
|
|
154
|
+
ctx.log?.error(`[qqbot:${ctx.accountId}] Failed to send startup greeting: ${message}`);
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
}
|