@sliverp/qqbot 1.3.0

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.
Files changed (78) hide show
  1. package/README.md +231 -0
  2. package/clawdbot.plugin.json +16 -0
  3. package/dist/index.d.ts +17 -0
  4. package/dist/index.js +22 -0
  5. package/dist/src/api.d.ts +194 -0
  6. package/dist/src/api.js +555 -0
  7. package/dist/src/channel.d.ts +3 -0
  8. package/dist/src/channel.js +146 -0
  9. package/dist/src/config.d.ts +25 -0
  10. package/dist/src/config.js +148 -0
  11. package/dist/src/gateway.d.ts +17 -0
  12. package/dist/src/gateway.js +722 -0
  13. package/dist/src/image-server.d.ts +62 -0
  14. package/dist/src/image-server.js +401 -0
  15. package/dist/src/known-users.d.ts +100 -0
  16. package/dist/src/known-users.js +264 -0
  17. package/dist/src/onboarding.d.ts +10 -0
  18. package/dist/src/onboarding.js +190 -0
  19. package/dist/src/outbound.d.ts +149 -0
  20. package/dist/src/outbound.js +476 -0
  21. package/dist/src/proactive.d.ts +170 -0
  22. package/dist/src/proactive.js +398 -0
  23. package/dist/src/runtime.d.ts +3 -0
  24. package/dist/src/runtime.js +10 -0
  25. package/dist/src/session-store.d.ts +49 -0
  26. package/dist/src/session-store.js +242 -0
  27. package/dist/src/types.d.ts +116 -0
  28. package/dist/src/types.js +1 -0
  29. package/dist/src/utils/image-size.d.ts +51 -0
  30. package/dist/src/utils/image-size.js +234 -0
  31. package/dist/src/utils/payload.d.ts +112 -0
  32. package/dist/src/utils/payload.js +186 -0
  33. package/index.ts +27 -0
  34. package/moltbot.plugin.json +16 -0
  35. package/node_modules/ws/LICENSE +20 -0
  36. package/node_modules/ws/README.md +548 -0
  37. package/node_modules/ws/browser.js +8 -0
  38. package/node_modules/ws/index.js +13 -0
  39. package/node_modules/ws/lib/buffer-util.js +131 -0
  40. package/node_modules/ws/lib/constants.js +19 -0
  41. package/node_modules/ws/lib/event-target.js +292 -0
  42. package/node_modules/ws/lib/extension.js +203 -0
  43. package/node_modules/ws/lib/limiter.js +55 -0
  44. package/node_modules/ws/lib/permessage-deflate.js +528 -0
  45. package/node_modules/ws/lib/receiver.js +706 -0
  46. package/node_modules/ws/lib/sender.js +602 -0
  47. package/node_modules/ws/lib/stream.js +161 -0
  48. package/node_modules/ws/lib/subprotocol.js +62 -0
  49. package/node_modules/ws/lib/validation.js +152 -0
  50. package/node_modules/ws/lib/websocket-server.js +554 -0
  51. package/node_modules/ws/lib/websocket.js +1393 -0
  52. package/node_modules/ws/package.json +69 -0
  53. package/node_modules/ws/wrapper.mjs +8 -0
  54. package/openclaw.plugin.json +16 -0
  55. package/package.json +38 -0
  56. package/qqbot-1.3.0.tgz +0 -0
  57. package/scripts/proactive-api-server.ts +346 -0
  58. package/scripts/send-proactive.ts +273 -0
  59. package/scripts/upgrade.sh +106 -0
  60. package/skills/qqbot-cron/SKILL.md +490 -0
  61. package/skills/qqbot-media/SKILL.md +138 -0
  62. package/src/api.ts +752 -0
  63. package/src/channel.ts +303 -0
  64. package/src/config.ts +172 -0
  65. package/src/gateway.ts +1588 -0
  66. package/src/image-server.ts +474 -0
  67. package/src/known-users.ts +358 -0
  68. package/src/onboarding.ts +254 -0
  69. package/src/openclaw-plugin-sdk.d.ts +483 -0
  70. package/src/outbound.ts +571 -0
  71. package/src/proactive.ts +528 -0
  72. package/src/runtime.ts +14 -0
  73. package/src/session-store.ts +292 -0
  74. package/src/types.ts +123 -0
  75. package/src/utils/image-size.ts +266 -0
  76. package/src/utils/payload.ts +265 -0
  77. package/tsconfig.json +16 -0
  78. package/upgrade-and-run.sh +89 -0
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "ws",
3
+ "version": "8.19.0",
4
+ "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
5
+ "keywords": [
6
+ "HyBi",
7
+ "Push",
8
+ "RFC-6455",
9
+ "WebSocket",
10
+ "WebSockets",
11
+ "real-time"
12
+ ],
13
+ "homepage": "https://github.com/websockets/ws",
14
+ "bugs": "https://github.com/websockets/ws/issues",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/websockets/ws.git"
18
+ },
19
+ "author": "Einar Otto Stangvik <einaros@gmail.com> (http://2x.io)",
20
+ "license": "MIT",
21
+ "main": "index.js",
22
+ "exports": {
23
+ ".": {
24
+ "browser": "./browser.js",
25
+ "import": "./wrapper.mjs",
26
+ "require": "./index.js"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "browser": "browser.js",
31
+ "engines": {
32
+ "node": ">=10.0.0"
33
+ },
34
+ "files": [
35
+ "browser.js",
36
+ "index.js",
37
+ "lib/*.js",
38
+ "wrapper.mjs"
39
+ ],
40
+ "scripts": {
41
+ "test": "nyc --reporter=lcov --reporter=text mocha --throw-deprecation test/*.test.js",
42
+ "integration": "mocha --throw-deprecation test/*.integration.js",
43
+ "lint": "eslint . && prettier --check --ignore-path .gitignore \"**/*.{json,md,yaml,yml}\""
44
+ },
45
+ "peerDependencies": {
46
+ "bufferutil": "^4.0.1",
47
+ "utf-8-validate": ">=5.0.2"
48
+ },
49
+ "peerDependenciesMeta": {
50
+ "bufferutil": {
51
+ "optional": true
52
+ },
53
+ "utf-8-validate": {
54
+ "optional": true
55
+ }
56
+ },
57
+ "devDependencies": {
58
+ "benchmark": "^2.1.4",
59
+ "bufferutil": "^4.0.1",
60
+ "eslint": "^9.0.0",
61
+ "eslint-config-prettier": "^10.0.1",
62
+ "eslint-plugin-prettier": "^5.0.0",
63
+ "globals": "^16.0.0",
64
+ "mocha": "^8.4.0",
65
+ "nyc": "^15.0.0",
66
+ "prettier": "^3.0.0",
67
+ "utf-8-validate": "^6.0.0"
68
+ }
69
+ }
@@ -0,0 +1,8 @@
1
+ import createWebSocketStream from './lib/stream.js';
2
+ import Receiver from './lib/receiver.js';
3
+ import Sender from './lib/sender.js';
4
+ import WebSocket from './lib/websocket.js';
5
+ import WebSocketServer from './lib/websocket-server.js';
6
+
7
+ export { createWebSocketStream, Receiver, Sender, WebSocket, WebSocketServer };
8
+ export default WebSocket;
@@ -0,0 +1,16 @@
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/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@sliverp/qqbot",
3
+ "version": "1.3.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "clawdbot": {
8
+ "extensions": ["./index.ts"]
9
+ },
10
+ "moltbot": {
11
+ "extensions": ["./index.ts"]
12
+ },
13
+ "openclaw": {
14
+ "extensions": ["./index.ts"]
15
+ },
16
+ "scripts": {
17
+ "build": "tsc || true",
18
+ "dev": "tsc --watch",
19
+ "prepack": "npm install --omit=dev"
20
+ },
21
+ "dependencies": {
22
+ "ws": "^8.18.0"
23
+ },
24
+ "bundledDependencies": [
25
+ "ws"
26
+ ],
27
+ "devDependencies": {
28
+ "@types/node": "^20.0.0",
29
+ "@types/ws": "^8.5.0",
30
+ "typescript": "^5.9.3"
31
+ },
32
+ "peerDependencies": {
33
+ "clawdbot": "*",
34
+ "moltbot": "*",
35
+ "openclaw": "*"
36
+ },
37
+ "homepage": "https://github.com/sliverp/qqbot"
38
+ }
Binary file
@@ -0,0 +1,346 @@
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();