@sliverp/qqbot 1.3.3 → 1.3.4
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/package.json +7 -1
- package/clawdbot.plugin.json +0 -16
- package/dist/index.d.ts +0 -17
- package/dist/src/api.d.ts +0 -194
- package/dist/src/api.js +0 -555
- package/dist/src/channel.d.ts +0 -3
- package/dist/src/channel.js +0 -146
- package/dist/src/config.d.ts +0 -25
- package/dist/src/config.js +0 -148
- package/dist/src/gateway.d.ts +0 -17
- package/dist/src/gateway.js +0 -722
- package/dist/src/image-server.d.ts +0 -62
- package/dist/src/image-server.js +0 -401
- package/dist/src/known-users.d.ts +0 -100
- package/dist/src/known-users.js +0 -264
- package/dist/src/onboarding.d.ts +0 -10
- package/dist/src/onboarding.js +0 -190
- package/dist/src/outbound.d.ts +0 -149
- package/dist/src/outbound.js +0 -476
- package/dist/src/proactive.d.ts +0 -170
- package/dist/src/proactive.js +0 -398
- package/dist/src/runtime.d.ts +0 -3
- package/dist/src/runtime.js +0 -10
- package/dist/src/session-store.d.ts +0 -49
- package/dist/src/session-store.js +0 -242
- package/dist/src/types.d.ts +0 -116
- package/dist/src/types.js +0 -1
- package/dist/src/utils/image-size.d.ts +0 -51
- package/dist/src/utils/image-size.js +0 -234
- package/dist/src/utils/payload.d.ts +0 -112
- package/dist/src/utils/payload.js +0 -186
- package/moltbot.plugin.json +0 -16
- package/openclaw.plugin.json +0 -16
- package/scripts/proactive-api-server.ts +0 -346
- package/scripts/send-proactive.ts +0 -273
- package/scripts/upgrade.sh +0 -106
- package/skills/qqbot-cron/SKILL.md +0 -490
- package/skills/qqbot-media/SKILL.md +0 -138
- package/upgrade-and-run.sh +0 -89
package/moltbot.plugin.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "qqbot",
|
|
3
|
-
"name": "QQ Bot Channel",
|
|
4
|
-
"description": "QQ Bot channel plugin with message support, cron jobs, and proactive messaging",
|
|
5
|
-
"channels": ["qqbot"],
|
|
6
|
-
"skills": ["skills/qqbot-cron", "skills/qqbot-media"],
|
|
7
|
-
"capabilities": {
|
|
8
|
-
"proactiveMessaging": true,
|
|
9
|
-
"cronJobs": true
|
|
10
|
-
},
|
|
11
|
-
"configSchema": {
|
|
12
|
-
"type": "object",
|
|
13
|
-
"additionalProperties": false,
|
|
14
|
-
"properties": {}
|
|
15
|
-
}
|
|
16
|
-
}
|
package/openclaw.plugin.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "qqbot",
|
|
3
|
-
"name": "QQ Bot Channel",
|
|
4
|
-
"description": "QQ Bot channel plugin with message support, cron jobs, and proactive messaging",
|
|
5
|
-
"channels": ["qqbot"],
|
|
6
|
-
"skills": ["skills/qqbot-cron", "skills/qqbot-media"],
|
|
7
|
-
"capabilities": {
|
|
8
|
-
"proactiveMessaging": true,
|
|
9
|
-
"cronJobs": true
|
|
10
|
-
},
|
|
11
|
-
"configSchema": {
|
|
12
|
-
"type": "object",
|
|
13
|
-
"additionalProperties": false,
|
|
14
|
-
"properties": {}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* QQBot 主动消息 HTTP API 服务
|
|
3
|
-
*
|
|
4
|
-
* 提供 RESTful API 用于:
|
|
5
|
-
* 1. 发送主动消息
|
|
6
|
-
* 2. 查询已知用户
|
|
7
|
-
* 3. 广播消息
|
|
8
|
-
*
|
|
9
|
-
* 启动方式:
|
|
10
|
-
* npx ts-node scripts/proactive-api-server.ts --port 3721
|
|
11
|
-
*
|
|
12
|
-
* API 端点:
|
|
13
|
-
* POST /send - 发送主动消息
|
|
14
|
-
* GET /users - 列出已知用户
|
|
15
|
-
* GET /users/stats - 获取用户统计
|
|
16
|
-
* POST /broadcast - 广播消息
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import * as http from "node:http";
|
|
20
|
-
import * as fs from "node:fs";
|
|
21
|
-
import * as path from "node:path";
|
|
22
|
-
import * as url from "node:url";
|
|
23
|
-
import {
|
|
24
|
-
sendProactiveMessageDirect,
|
|
25
|
-
listKnownUsers,
|
|
26
|
-
getKnownUsersStats,
|
|
27
|
-
getKnownUser,
|
|
28
|
-
broadcastMessage,
|
|
29
|
-
} from "../src/proactive.js";
|
|
30
|
-
import type { ResolvedQQBotAccount } from "../src/types.js";
|
|
31
|
-
|
|
32
|
-
// 默认端口
|
|
33
|
-
const DEFAULT_PORT = 3721;
|
|
34
|
-
|
|
35
|
-
// 从配置文件加载账户信息
|
|
36
|
-
function loadAccount(accountId = "default"): ResolvedQQBotAccount | null {
|
|
37
|
-
const configPath = path.join(process.env.HOME || "/home/ubuntu", "clawd", "config.json");
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
// 优先从环境变量获取
|
|
41
|
-
const envAppId = process.env.QQBOT_APP_ID;
|
|
42
|
-
const envClientSecret = process.env.QQBOT_CLIENT_SECRET;
|
|
43
|
-
|
|
44
|
-
if (!fs.existsSync(configPath)) {
|
|
45
|
-
if (envAppId && envClientSecret) {
|
|
46
|
-
return {
|
|
47
|
-
accountId,
|
|
48
|
-
appId: envAppId,
|
|
49
|
-
clientSecret: envClientSecret,
|
|
50
|
-
enabled: true,
|
|
51
|
-
secretSource: "env",
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
58
|
-
const qqbot = config.channels?.qqbot;
|
|
59
|
-
|
|
60
|
-
if (!qqbot) {
|
|
61
|
-
if (envAppId && envClientSecret) {
|
|
62
|
-
return {
|
|
63
|
-
accountId,
|
|
64
|
-
appId: envAppId,
|
|
65
|
-
clientSecret: envClientSecret,
|
|
66
|
-
enabled: true,
|
|
67
|
-
secretSource: "env",
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 解析账户配置
|
|
74
|
-
if (accountId === "default") {
|
|
75
|
-
return {
|
|
76
|
-
accountId: "default",
|
|
77
|
-
appId: qqbot.appId || envAppId,
|
|
78
|
-
clientSecret: qqbot.clientSecret || envClientSecret,
|
|
79
|
-
enabled: qqbot.enabled ?? true,
|
|
80
|
-
secretSource: qqbot.clientSecret ? "config" : "env",
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const accountConfig = qqbot.accounts?.[accountId];
|
|
85
|
-
if (accountConfig) {
|
|
86
|
-
return {
|
|
87
|
-
accountId,
|
|
88
|
-
appId: accountConfig.appId || qqbot.appId || envAppId,
|
|
89
|
-
clientSecret: accountConfig.clientSecret || qqbot.clientSecret || envClientSecret,
|
|
90
|
-
enabled: accountConfig.enabled ?? true,
|
|
91
|
-
secretSource: accountConfig.clientSecret ? "config" : "env",
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return null;
|
|
96
|
-
} catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 加载配置(用于 broadcastMessage)
|
|
102
|
-
function loadConfig(): Record<string, unknown> {
|
|
103
|
-
const configPath = path.join(process.env.HOME || "/home/ubuntu", "clawd", "config.json");
|
|
104
|
-
try {
|
|
105
|
-
if (fs.existsSync(configPath)) {
|
|
106
|
-
return JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
107
|
-
}
|
|
108
|
-
} catch {}
|
|
109
|
-
return {};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 解析请求体
|
|
113
|
-
async function parseBody(req: http.IncomingMessage): Promise<Record<string, unknown>> {
|
|
114
|
-
return new Promise((resolve) => {
|
|
115
|
-
let body = "";
|
|
116
|
-
req.on("data", (chunk) => {
|
|
117
|
-
body += chunk;
|
|
118
|
-
});
|
|
119
|
-
req.on("end", () => {
|
|
120
|
-
try {
|
|
121
|
-
resolve(body ? JSON.parse(body) : {});
|
|
122
|
-
} catch {
|
|
123
|
-
resolve({});
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// 发送 JSON 响应
|
|
130
|
-
function sendJson(res: http.ServerResponse, statusCode: number, data: unknown) {
|
|
131
|
-
res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8" });
|
|
132
|
-
res.end(JSON.stringify(data, null, 2));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// 处理请求
|
|
136
|
-
async function handleRequest(req: http.IncomingMessage, res: http.ServerResponse) {
|
|
137
|
-
const parsedUrl = url.parse(req.url || "", true);
|
|
138
|
-
const pathname = parsedUrl.pathname || "/";
|
|
139
|
-
const method = req.method || "GET";
|
|
140
|
-
const query = parsedUrl.query;
|
|
141
|
-
|
|
142
|
-
// CORS 支持
|
|
143
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
144
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
145
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
146
|
-
|
|
147
|
-
if (method === "OPTIONS") {
|
|
148
|
-
res.writeHead(204);
|
|
149
|
-
res.end();
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
console.log(`[${new Date().toISOString()}] ${method} ${pathname}`);
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
// POST /send - 发送主动消息
|
|
157
|
-
if (pathname === "/send" && method === "POST") {
|
|
158
|
-
const body = await parseBody(req);
|
|
159
|
-
const { to, text, type = "c2c", accountId = "default" } = body as {
|
|
160
|
-
to?: string;
|
|
161
|
-
text?: string;
|
|
162
|
-
type?: "c2c" | "group";
|
|
163
|
-
accountId?: string;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
if (!to || !text) {
|
|
167
|
-
sendJson(res, 400, { error: "Missing required fields: to, text" });
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const account = loadAccount(accountId);
|
|
172
|
-
if (!account) {
|
|
173
|
-
sendJson(res, 500, { error: "Failed to load account configuration" });
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const result = await sendProactiveMessageDirect(account, to, text, type);
|
|
178
|
-
sendJson(res, result.success ? 200 : 500, result);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// GET /users - 列出已知用户
|
|
183
|
-
if (pathname === "/users" && method === "GET") {
|
|
184
|
-
const type = query.type as "c2c" | "group" | "channel" | undefined;
|
|
185
|
-
const accountId = query.accountId as string | undefined;
|
|
186
|
-
const limit = query.limit ? parseInt(query.limit as string, 10) : undefined;
|
|
187
|
-
|
|
188
|
-
const users = listKnownUsers({ type, accountId, limit });
|
|
189
|
-
sendJson(res, 200, { total: users.length, users });
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// GET /users/stats - 获取用户统计
|
|
194
|
-
if (pathname === "/users/stats" && method === "GET") {
|
|
195
|
-
const accountId = query.accountId as string | undefined;
|
|
196
|
-
const stats = getKnownUsersStats(accountId);
|
|
197
|
-
sendJson(res, 200, stats);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// GET /users/:openid - 获取单个用户
|
|
202
|
-
if (pathname.startsWith("/users/") && method === "GET" && pathname !== "/users/stats") {
|
|
203
|
-
const openid = pathname.slice("/users/".length);
|
|
204
|
-
const type = (query.type as string) || "c2c";
|
|
205
|
-
const accountId = (query.accountId as string) || "default";
|
|
206
|
-
|
|
207
|
-
const user = getKnownUser(type, openid, accountId);
|
|
208
|
-
if (user) {
|
|
209
|
-
sendJson(res, 200, user);
|
|
210
|
-
} else {
|
|
211
|
-
sendJson(res, 404, { error: "User not found" });
|
|
212
|
-
}
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// POST /broadcast - 广播消息
|
|
217
|
-
if (pathname === "/broadcast" && method === "POST") {
|
|
218
|
-
const body = await parseBody(req);
|
|
219
|
-
const { text, type = "c2c", accountId, limit } = body as {
|
|
220
|
-
text?: string;
|
|
221
|
-
type?: "c2c" | "group";
|
|
222
|
-
accountId?: string;
|
|
223
|
-
limit?: number;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
if (!text) {
|
|
227
|
-
sendJson(res, 400, { error: "Missing required field: text" });
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const cfg = loadConfig();
|
|
232
|
-
const result = await broadcastMessage(text, cfg as any, { type, accountId, limit });
|
|
233
|
-
sendJson(res, 200, result);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// GET / - API 文档
|
|
238
|
-
if (pathname === "/" && method === "GET") {
|
|
239
|
-
sendJson(res, 200, {
|
|
240
|
-
name: "QQBot Proactive Message API",
|
|
241
|
-
version: "1.0.0",
|
|
242
|
-
endpoints: {
|
|
243
|
-
"POST /send": {
|
|
244
|
-
description: "发送主动消息",
|
|
245
|
-
body: {
|
|
246
|
-
to: "目标 openid (必需)",
|
|
247
|
-
text: "消息内容 (必需)",
|
|
248
|
-
type: "消息类型: c2c | group (默认 c2c)",
|
|
249
|
-
accountId: "账户 ID (默认 default)",
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
"GET /users": {
|
|
253
|
-
description: "列出已知用户",
|
|
254
|
-
query: {
|
|
255
|
-
type: "过滤类型: c2c | group | channel",
|
|
256
|
-
accountId: "过滤账户 ID",
|
|
257
|
-
limit: "限制返回数量",
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
"GET /users/stats": {
|
|
261
|
-
description: "获取用户统计",
|
|
262
|
-
query: {
|
|
263
|
-
accountId: "过滤账户 ID",
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
"GET /users/:openid": {
|
|
267
|
-
description: "获取单个用户信息",
|
|
268
|
-
query: {
|
|
269
|
-
type: "用户类型 (默认 c2c)",
|
|
270
|
-
accountId: "账户 ID (默认 default)",
|
|
271
|
-
},
|
|
272
|
-
},
|
|
273
|
-
"POST /broadcast": {
|
|
274
|
-
description: "广播消息给所有已知用户",
|
|
275
|
-
body: {
|
|
276
|
-
text: "消息内容 (必需)",
|
|
277
|
-
type: "消息类型: c2c | group (默认 c2c)",
|
|
278
|
-
accountId: "账户 ID",
|
|
279
|
-
limit: "限制发送数量",
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
notes: [
|
|
284
|
-
"只有曾经与机器人交互过的用户才能收到主动消息",
|
|
285
|
-
],
|
|
286
|
-
});
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// 404
|
|
291
|
-
sendJson(res, 404, { error: "Not found" });
|
|
292
|
-
} catch (err) {
|
|
293
|
-
console.error(`Error handling request: ${err}`);
|
|
294
|
-
sendJson(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// 解析命令行参数获取端口
|
|
299
|
-
function getPort(): number {
|
|
300
|
-
const args = process.argv.slice(2);
|
|
301
|
-
for (let i = 0; i < args.length; i++) {
|
|
302
|
-
if (args[i] === "--port" && args[i + 1]) {
|
|
303
|
-
return parseInt(args[i + 1], 10) || DEFAULT_PORT;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return parseInt(process.env.PROACTIVE_API_PORT || "", 10) || DEFAULT_PORT;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// 启动服务器
|
|
310
|
-
function main() {
|
|
311
|
-
const port = getPort();
|
|
312
|
-
|
|
313
|
-
const server = http.createServer(handleRequest);
|
|
314
|
-
|
|
315
|
-
server.listen(port, () => {
|
|
316
|
-
console.log(`
|
|
317
|
-
╔═══════════════════════════════════════════════════════════════╗
|
|
318
|
-
║ QQBot Proactive Message API Server ║
|
|
319
|
-
╠═══════════════════════════════════════════════════════════════╣
|
|
320
|
-
║ Server running at: http://localhost:${port.toString().padEnd(25)}║
|
|
321
|
-
║ ║
|
|
322
|
-
║ Endpoints: ║
|
|
323
|
-
║ GET / - API documentation ║
|
|
324
|
-
║ POST /send - Send proactive message ║
|
|
325
|
-
║ GET /users - List known users ║
|
|
326
|
-
║ GET /users/stats - Get user statistics ║
|
|
327
|
-
║ POST /broadcast - Broadcast message ║
|
|
328
|
-
║ ║
|
|
329
|
-
║ Example: ║
|
|
330
|
-
║ curl -X POST http://localhost:${port}/send \\ ║
|
|
331
|
-
║ -H "Content-Type: application/json" \\ ║
|
|
332
|
-
║ -d '{"to":"openid","text":"Hello!"}' ║
|
|
333
|
-
╚═══════════════════════════════════════════════════════════════╝
|
|
334
|
-
`);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// 优雅关闭
|
|
338
|
-
process.on("SIGINT", () => {
|
|
339
|
-
console.log("\nShutting down...");
|
|
340
|
-
server.close(() => {
|
|
341
|
-
process.exit(0);
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
main();
|
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx ts-node
|
|
2
|
-
/**
|
|
3
|
-
* QQBot 主动消息 CLI 工具
|
|
4
|
-
*
|
|
5
|
-
* 使用示例:
|
|
6
|
-
* # 发送私聊消息
|
|
7
|
-
* npx ts-node scripts/send-proactive.ts --to "用户openid" --text "你好!"
|
|
8
|
-
*
|
|
9
|
-
* # 发送群聊消息
|
|
10
|
-
* npx ts-node scripts/send-proactive.ts --to "群组openid" --type group --text "群公告"
|
|
11
|
-
*
|
|
12
|
-
* # 列出已知用户
|
|
13
|
-
* npx ts-node scripts/send-proactive.ts --list
|
|
14
|
-
*
|
|
15
|
-
* # 列出群聊用户
|
|
16
|
-
* npx ts-node scripts/send-proactive.ts --list --type group
|
|
17
|
-
*
|
|
18
|
-
* # 广播消息
|
|
19
|
-
* npx ts-node scripts/send-proactive.ts --broadcast --text "系统公告" --type c2c --limit 10
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import {
|
|
23
|
-
sendProactiveMessageDirect,
|
|
24
|
-
listKnownUsers,
|
|
25
|
-
getKnownUsersStats,
|
|
26
|
-
broadcastMessage,
|
|
27
|
-
} from "../src/proactive.js";
|
|
28
|
-
import type { ResolvedQQBotAccount } from "../src/types.js";
|
|
29
|
-
import * as fs from "node:fs";
|
|
30
|
-
import * as path from "node:path";
|
|
31
|
-
|
|
32
|
-
// 解析命令行参数
|
|
33
|
-
function parseArgs(): Record<string, string | boolean> {
|
|
34
|
-
const args: Record<string, string | boolean> = {};
|
|
35
|
-
const argv = process.argv.slice(2);
|
|
36
|
-
|
|
37
|
-
for (let i = 0; i < argv.length; i++) {
|
|
38
|
-
const arg = argv[i];
|
|
39
|
-
if (arg.startsWith("--")) {
|
|
40
|
-
const key = arg.slice(2);
|
|
41
|
-
const nextArg = argv[i + 1];
|
|
42
|
-
if (nextArg && !nextArg.startsWith("--")) {
|
|
43
|
-
args[key] = nextArg;
|
|
44
|
-
i++;
|
|
45
|
-
} else {
|
|
46
|
-
args[key] = true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return args;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// 从配置文件加载账户信息
|
|
55
|
-
function loadAccount(accountId = "default"): ResolvedQQBotAccount | null {
|
|
56
|
-
const configPath = path.join(process.env.HOME || "/home/ubuntu", "clawd", "config.json");
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
if (!fs.existsSync(configPath)) {
|
|
60
|
-
// 尝试从环境变量获取
|
|
61
|
-
const appId = process.env.QQBOT_APP_ID;
|
|
62
|
-
const clientSecret = process.env.QQBOT_CLIENT_SECRET;
|
|
63
|
-
|
|
64
|
-
if (appId && clientSecret) {
|
|
65
|
-
return {
|
|
66
|
-
accountId,
|
|
67
|
-
appId,
|
|
68
|
-
clientSecret,
|
|
69
|
-
enabled: true,
|
|
70
|
-
secretSource: "env",
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
console.error("配置文件不存在且环境变量未设置");
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
79
|
-
const qqbot = config.channels?.qqbot;
|
|
80
|
-
|
|
81
|
-
if (!qqbot) {
|
|
82
|
-
console.error("配置中没有 qqbot 配置");
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 解析账户配置
|
|
87
|
-
if (accountId === "default") {
|
|
88
|
-
return {
|
|
89
|
-
accountId: "default",
|
|
90
|
-
appId: qqbot.appId || process.env.QQBOT_APP_ID,
|
|
91
|
-
clientSecret: qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
|
|
92
|
-
enabled: qqbot.enabled ?? true,
|
|
93
|
-
secretSource: qqbot.clientSecret ? "config" : "env",
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const accountConfig = qqbot.accounts?.[accountId];
|
|
98
|
-
if (accountConfig) {
|
|
99
|
-
return {
|
|
100
|
-
accountId,
|
|
101
|
-
appId: accountConfig.appId || qqbot.appId || process.env.QQBOT_APP_ID,
|
|
102
|
-
clientSecret: accountConfig.clientSecret || qqbot.clientSecret || process.env.QQBOT_CLIENT_SECRET,
|
|
103
|
-
enabled: accountConfig.enabled ?? true,
|
|
104
|
-
secretSource: accountConfig.clientSecret ? "config" : "env",
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
console.error(`账户 ${accountId} 不存在`);
|
|
109
|
-
return null;
|
|
110
|
-
} catch (err) {
|
|
111
|
-
console.error(`加载配置失败: ${err}`);
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async function main() {
|
|
117
|
-
const args = parseArgs();
|
|
118
|
-
|
|
119
|
-
// 显示帮助
|
|
120
|
-
if (args.help || args.h) {
|
|
121
|
-
console.log(`
|
|
122
|
-
QQBot 主动消息 CLI 工具
|
|
123
|
-
|
|
124
|
-
用法:
|
|
125
|
-
npx ts-node scripts/send-proactive.ts [选项]
|
|
126
|
-
|
|
127
|
-
选项:
|
|
128
|
-
--to <openid> 目标用户或群组的 openid
|
|
129
|
-
--text <message> 要发送的消息内容
|
|
130
|
-
--type <type> 消息类型: c2c (私聊) 或 group (群聊),默认 c2c
|
|
131
|
-
--account <id> 账户 ID,默认 default
|
|
132
|
-
|
|
133
|
-
--list 列出已知用户
|
|
134
|
-
--stats 显示用户统计
|
|
135
|
-
--broadcast 广播消息给所有已知用户
|
|
136
|
-
--limit <n> 限制数量
|
|
137
|
-
|
|
138
|
-
--help, -h 显示帮助
|
|
139
|
-
|
|
140
|
-
示例:
|
|
141
|
-
# 发送私聊消息
|
|
142
|
-
npx ts-node scripts/send-proactive.ts --to "0Eda5EA7-xxx" --text "你好!"
|
|
143
|
-
|
|
144
|
-
# 发送群聊消息
|
|
145
|
-
npx ts-node scripts/send-proactive.ts --to "A1B2C3D4" --type group --text "群公告"
|
|
146
|
-
|
|
147
|
-
# 列出最近 10 个私聊用户
|
|
148
|
-
npx ts-node scripts/send-proactive.ts --list --type c2c --limit 10
|
|
149
|
-
|
|
150
|
-
# 广播消息
|
|
151
|
-
npx ts-node scripts/send-proactive.ts --broadcast --text "系统公告" --limit 5
|
|
152
|
-
`);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const accountId = (args.account as string) || "default";
|
|
157
|
-
const type = (args.type as "c2c" | "group") || "c2c";
|
|
158
|
-
const limit = args.limit ? parseInt(args.limit as string, 10) : undefined;
|
|
159
|
-
|
|
160
|
-
// 列出已知用户
|
|
161
|
-
if (args.list) {
|
|
162
|
-
const users = listKnownUsers({
|
|
163
|
-
type: args.type as "c2c" | "group" | "channel" | undefined,
|
|
164
|
-
accountId: args.account as string | undefined,
|
|
165
|
-
limit,
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
if (users.length === 0) {
|
|
169
|
-
console.log("没有已知用户");
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
console.log(`\n已知用户列表 (共 ${users.length} 个):\n`);
|
|
174
|
-
console.log("类型\t\tOpenID\t\t\t\t\t\t昵称\t\t最后交互时间");
|
|
175
|
-
console.log("─".repeat(100));
|
|
176
|
-
|
|
177
|
-
for (const user of users) {
|
|
178
|
-
const lastTime = new Date(user.lastInteractionAt).toLocaleString();
|
|
179
|
-
console.log(`${user.type}\t\t${user.openid.slice(0, 20)}...\t${user.nickname || "-"}\t\t${lastTime}`);
|
|
180
|
-
}
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 显示统计
|
|
185
|
-
if (args.stats) {
|
|
186
|
-
const stats = getKnownUsersStats(args.account as string | undefined);
|
|
187
|
-
console.log(`\n用户统计:`);
|
|
188
|
-
console.log(` 总计: ${stats.total}`);
|
|
189
|
-
console.log(` 私聊: ${stats.c2c}`);
|
|
190
|
-
console.log(` 群聊: ${stats.group}`);
|
|
191
|
-
console.log(` 频道: ${stats.channel}`);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// 广播消息
|
|
196
|
-
if (args.broadcast) {
|
|
197
|
-
if (!args.text) {
|
|
198
|
-
console.error("请指定消息内容 (--text)");
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 加载配置用于广播
|
|
203
|
-
const configPath = path.join(process.env.HOME || "/home/ubuntu", "clawd", "config.json");
|
|
204
|
-
let cfg: Record<string, unknown> = {};
|
|
205
|
-
try {
|
|
206
|
-
if (fs.existsSync(configPath)) {
|
|
207
|
-
cfg = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
208
|
-
}
|
|
209
|
-
} catch {}
|
|
210
|
-
|
|
211
|
-
console.log(`\n开始广播消息...\n`);
|
|
212
|
-
const result = await broadcastMessage(args.text as string, cfg as any, {
|
|
213
|
-
type,
|
|
214
|
-
accountId,
|
|
215
|
-
limit,
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
console.log(`\n广播完成:`);
|
|
219
|
-
console.log(` 发送总数: ${result.total}`);
|
|
220
|
-
console.log(` 成功: ${result.success}`);
|
|
221
|
-
console.log(` 失败: ${result.failed}`);
|
|
222
|
-
|
|
223
|
-
if (result.failed > 0) {
|
|
224
|
-
console.log(`\n失败详情:`);
|
|
225
|
-
for (const r of result.results) {
|
|
226
|
-
if (!r.result.success) {
|
|
227
|
-
console.log(` ${r.to}: ${r.result.error}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
return;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// 发送单条消息
|
|
235
|
-
if (args.to && args.text) {
|
|
236
|
-
const account = loadAccount(accountId);
|
|
237
|
-
if (!account) {
|
|
238
|
-
console.error("无法加载账户配置");
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
console.log(`\n发送消息...`);
|
|
243
|
-
console.log(` 目标: ${args.to}`);
|
|
244
|
-
console.log(` 类型: ${type}`);
|
|
245
|
-
console.log(` 内容: ${args.text}`);
|
|
246
|
-
|
|
247
|
-
const result = await sendProactiveMessageDirect(
|
|
248
|
-
account,
|
|
249
|
-
args.to as string,
|
|
250
|
-
args.text as string,
|
|
251
|
-
type
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
if (result.success) {
|
|
255
|
-
console.log(`\n✅ 发送成功!`);
|
|
256
|
-
console.log(` 消息ID: ${result.messageId}`);
|
|
257
|
-
console.log(` 时间戳: ${result.timestamp}`);
|
|
258
|
-
} else {
|
|
259
|
-
console.log(`\n❌ 发送失败: ${result.error}`);
|
|
260
|
-
process.exit(1);
|
|
261
|
-
}
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// 没有有效参数
|
|
266
|
-
console.error("请指定操作。使用 --help 查看帮助。");
|
|
267
|
-
process.exit(1);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
main().catch((err) => {
|
|
271
|
-
console.error(`执行失败: ${err}`);
|
|
272
|
-
process.exit(1);
|
|
273
|
-
});
|