@tencent-connect/openclaw-qqbot 1.6.4-alpha.15 → 1.6.4-alpha.18

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 (39) hide show
  1. package/dist/src/admin-resolver.d.ts +27 -0
  2. package/dist/src/admin-resolver.js +122 -0
  3. package/dist/src/gateway.js +88 -1524
  4. package/dist/src/inbound-attachments.d.ts +58 -0
  5. package/dist/src/inbound-attachments.js +234 -0
  6. package/dist/src/message-queue.d.ts +50 -0
  7. package/dist/src/message-queue.js +115 -0
  8. package/dist/src/outbound-deliver.d.ts +48 -0
  9. package/dist/src/outbound-deliver.js +462 -0
  10. package/dist/src/outbound.js +3 -3
  11. package/dist/src/reply-dispatcher.d.ts +35 -0
  12. package/dist/src/reply-dispatcher.js +311 -0
  13. package/dist/src/slash-commands.js +20 -13
  14. package/dist/src/startup-greeting.d.ts +28 -0
  15. package/dist/src/startup-greeting.js +70 -0
  16. package/dist/src/stt.d.ts +21 -0
  17. package/dist/src/stt.js +70 -0
  18. package/dist/src/typing-keepalive.d.ts +27 -0
  19. package/dist/src/typing-keepalive.js +64 -0
  20. package/dist/src/update-checker.d.ts +5 -0
  21. package/dist/src/update-checker.js +13 -5
  22. package/dist/src/utils/text-parsing.d.ts +32 -0
  23. package/dist/src/utils/text-parsing.js +78 -0
  24. package/package.json +1 -1
  25. package/scripts/cleanup-legacy-plugins.sh +3 -6
  26. package/scripts/upgrade-via-source.sh +50 -0
  27. package/src/admin-resolver.ts +140 -0
  28. package/src/gateway.ts +109 -1598
  29. package/src/inbound-attachments.ts +304 -0
  30. package/src/message-queue.ts +169 -0
  31. package/src/outbound-deliver.ts +552 -0
  32. package/src/outbound.ts +3 -3
  33. package/src/reply-dispatcher.ts +334 -0
  34. package/src/slash-commands.ts +21 -13
  35. package/src/startup-greeting.ts +88 -0
  36. package/src/stt.ts +86 -0
  37. package/src/typing-keepalive.ts +59 -0
  38. package/src/update-checker.ts +21 -5
  39. package/src/utils/text-parsing.ts +80 -0
@@ -0,0 +1,27 @@
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
+ export declare function loadAdminOpenId(accountId: string): string | undefined;
17
+ export declare function saveAdminOpenId(accountId: string, openid: string): void;
18
+ export declare function loadUpgradeGreetingTargetOpenId(accountId: string, appId: string): string | undefined;
19
+ export declare function clearUpgradeGreetingTargetOpenId(accountId: string, appId: string): void;
20
+ /**
21
+ * 解析管理员 openid:
22
+ * 1. 优先读持久化文件(稳定)
23
+ * 2. fallback 取第一个私聊用户,并写入文件锁定
24
+ */
25
+ export declare function resolveAdminOpenId(ctx: Pick<AdminResolverContext, "accountId" | "log">): string | undefined;
26
+ /** 异步发送启动问候语(仅发给管理员) */
27
+ export declare function sendStartupGreetings(ctx: AdminResolverContext, trigger: "READY" | "RESUMED"): void;
@@ -0,0 +1,122 @@
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 getAdminMarkerFile(accountId) {
15
+ return path.join(getQQBotDataDir("data"), `admin-${accountId}.json`);
16
+ }
17
+ function getUpgradeGreetingTargetFile(accountId, appId) {
18
+ const safeAccountId = accountId.replace(/[^a-zA-Z0-9._-]/g, "_");
19
+ const safeAppId = appId.replace(/[^a-zA-Z0-9._-]/g, "_");
20
+ return path.join(getQQBotDataDir("data"), `upgrade-greeting-target-${safeAccountId}-${safeAppId}.json`);
21
+ }
22
+ // ---- 管理员 openid 持久化 ----
23
+ export function loadAdminOpenId(accountId) {
24
+ try {
25
+ const file = getAdminMarkerFile(accountId);
26
+ if (fs.existsSync(file)) {
27
+ const data = JSON.parse(fs.readFileSync(file, "utf8"));
28
+ if (data.openid)
29
+ return data.openid;
30
+ }
31
+ }
32
+ catch { /* 文件损坏视为无 */ }
33
+ return undefined;
34
+ }
35
+ export function saveAdminOpenId(accountId, openid) {
36
+ try {
37
+ fs.writeFileSync(getAdminMarkerFile(accountId), JSON.stringify({ openid, savedAt: new Date().toISOString() }));
38
+ }
39
+ catch { /* ignore */ }
40
+ }
41
+ // ---- 升级问候目标 ----
42
+ export function loadUpgradeGreetingTargetOpenId(accountId, appId) {
43
+ try {
44
+ const file = getUpgradeGreetingTargetFile(accountId, appId);
45
+ if (fs.existsSync(file)) {
46
+ const data = JSON.parse(fs.readFileSync(file, "utf8"));
47
+ if (!data.openid)
48
+ return undefined;
49
+ if (data.appId && data.appId !== appId)
50
+ return undefined;
51
+ if (data.accountId && data.accountId !== accountId)
52
+ return undefined;
53
+ return data.openid;
54
+ }
55
+ }
56
+ catch { /* 文件损坏视为无 */ }
57
+ return undefined;
58
+ }
59
+ export function clearUpgradeGreetingTargetOpenId(accountId, appId) {
60
+ try {
61
+ const file = getUpgradeGreetingTargetFile(accountId, appId);
62
+ if (fs.existsSync(file)) {
63
+ fs.unlinkSync(file);
64
+ }
65
+ }
66
+ catch { /* ignore */ }
67
+ }
68
+ // ---- 解析管理员 ----
69
+ /**
70
+ * 解析管理员 openid:
71
+ * 1. 优先读持久化文件(稳定)
72
+ * 2. fallback 取第一个私聊用户,并写入文件锁定
73
+ */
74
+ export function resolveAdminOpenId(ctx) {
75
+ const saved = loadAdminOpenId(ctx.accountId);
76
+ if (saved)
77
+ return saved;
78
+ const first = listKnownUsers({ accountId: ctx.accountId, type: "c2c", sortBy: "firstSeenAt", sortOrder: "asc", limit: 1 })[0]?.openid;
79
+ if (first) {
80
+ saveAdminOpenId(ctx.accountId, first);
81
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Auto-detected admin openid: ${first} (persisted)`);
82
+ }
83
+ return first;
84
+ }
85
+ // ---- 启动问候语 ----
86
+ /** 异步发送启动问候语(仅发给管理员) */
87
+ export function sendStartupGreetings(ctx, trigger) {
88
+ (async () => {
89
+ const plan = getStartupGreetingPlan();
90
+ if (!plan.shouldSend || !plan.greeting) {
91
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Skipping startup greeting (${plan.reason ?? "debounced"}, trigger=${trigger})`);
92
+ return;
93
+ }
94
+ const upgradeTargetOpenId = loadUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId);
95
+ const targetOpenId = upgradeTargetOpenId || resolveAdminOpenId(ctx);
96
+ if (!targetOpenId) {
97
+ markStartupGreetingFailed(plan.version, "no-admin");
98
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Skipping startup greeting (no admin or known user)`);
99
+ return;
100
+ }
101
+ try {
102
+ const receiverType = upgradeTargetOpenId ? "upgrade-requester" : "admin";
103
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Sending startup greeting to ${receiverType} (trigger=${trigger}): "${plan.greeting}"`);
104
+ const token = await getAccessToken(ctx.appId, ctx.clientSecret);
105
+ const GREETING_TIMEOUT_MS = 10_000;
106
+ await Promise.race([
107
+ sendProactiveC2CMessage(token, targetOpenId, plan.greeting),
108
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Startup greeting send timeout (10s)")), GREETING_TIMEOUT_MS)),
109
+ ]);
110
+ markStartupGreetingSent(plan.version);
111
+ if (upgradeTargetOpenId) {
112
+ clearUpgradeGreetingTargetOpenId(ctx.accountId, ctx.appId);
113
+ }
114
+ ctx.log?.info(`[qqbot:${ctx.accountId}] Sent startup greeting to ${receiverType}: ${targetOpenId}`);
115
+ }
116
+ catch (err) {
117
+ const message = err instanceof Error ? err.message : String(err);
118
+ markStartupGreetingFailed(plan.version, message);
119
+ ctx.log?.error(`[qqbot:${ctx.accountId}] Failed to send startup greeting: ${message}`);
120
+ }
121
+ })();
122
+ }