@zaofan/dsh-qqbot 0.6.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 (227) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +308 -0
  3. package/README_EN.md +273 -0
  4. package/client/qqbot-settings.js +1463 -0
  5. package/cordis.patch.yml +36 -0
  6. package/dist/api/group-admin.d.ts +112 -0
  7. package/dist/api/group-admin.d.ts.map +1 -0
  8. package/dist/api/group-admin.js +179 -0
  9. package/dist/api/group-admin.js.map +1 -0
  10. package/dist/channel-tools.d.ts +14 -0
  11. package/dist/channel-tools.d.ts.map +1 -0
  12. package/dist/channel-tools.js +880 -0
  13. package/dist/channel-tools.js.map +1 -0
  14. package/dist/commands/help.d.ts +11 -0
  15. package/dist/commands/help.d.ts.map +1 -0
  16. package/dist/commands/help.js +21 -0
  17. package/dist/commands/help.js.map +1 -0
  18. package/dist/commands/index.d.ts +13 -0
  19. package/dist/commands/index.d.ts.map +1 -0
  20. package/dist/commands/index.js +29 -0
  21. package/dist/commands/index.js.map +1 -0
  22. package/dist/commands/misc.d.ts +14 -0
  23. package/dist/commands/misc.d.ts.map +1 -0
  24. package/dist/commands/misc.js +39 -0
  25. package/dist/commands/misc.js.map +1 -0
  26. package/dist/commands/model.d.ts +7 -0
  27. package/dist/commands/model.d.ts.map +1 -0
  28. package/dist/commands/model.js +58 -0
  29. package/dist/commands/model.js.map +1 -0
  30. package/dist/commands/session.d.ts +10 -0
  31. package/dist/commands/session.d.ts.map +1 -0
  32. package/dist/commands/session.js +26 -0
  33. package/dist/commands/session.js.map +1 -0
  34. package/dist/commands/status.d.ts +8 -0
  35. package/dist/commands/status.d.ts.map +1 -0
  36. package/dist/commands/status.js +24 -0
  37. package/dist/commands/status.js.map +1 -0
  38. package/dist/commands/types.d.ts +12 -0
  39. package/dist/commands/types.d.ts.map +1 -0
  40. package/dist/commands/types.js +2 -0
  41. package/dist/commands/types.js.map +1 -0
  42. package/dist/config.d.ts +205 -0
  43. package/dist/config.d.ts.map +1 -0
  44. package/dist/config.js +203 -0
  45. package/dist/config.js.map +1 -0
  46. package/dist/features/chat-ledger.d.ts +53 -0
  47. package/dist/features/chat-ledger.d.ts.map +1 -0
  48. package/dist/features/chat-ledger.js +185 -0
  49. package/dist/features/chat-ledger.js.map +1 -0
  50. package/dist/features/face-tags.d.ts +17 -0
  51. package/dist/features/face-tags.d.ts.map +1 -0
  52. package/dist/features/face-tags.js +79 -0
  53. package/dist/features/face-tags.js.map +1 -0
  54. package/dist/features/group-join-request.d.ts +65 -0
  55. package/dist/features/group-join-request.d.ts.map +1 -0
  56. package/dist/features/group-join-request.js +190 -0
  57. package/dist/features/group-join-request.js.map +1 -0
  58. package/dist/features/history-store.d.ts +8 -0
  59. package/dist/features/history-store.d.ts.map +1 -0
  60. package/dist/features/history-store.js +24 -0
  61. package/dist/features/history-store.js.map +1 -0
  62. package/dist/features/qq-approval.d.ts +78 -0
  63. package/dist/features/qq-approval.d.ts.map +1 -0
  64. package/dist/features/qq-approval.js +165 -0
  65. package/dist/features/qq-approval.js.map +1 -0
  66. package/dist/features/schedule-store.d.ts +62 -0
  67. package/dist/features/schedule-store.d.ts.map +1 -0
  68. package/dist/features/schedule-store.js +253 -0
  69. package/dist/features/schedule-store.js.map +1 -0
  70. package/dist/features/scheduler.d.ts +9 -0
  71. package/dist/features/scheduler.d.ts.map +1 -0
  72. package/dist/features/scheduler.js +146 -0
  73. package/dist/features/scheduler.js.map +1 -0
  74. package/dist/features/sticker-gate.d.ts +90 -0
  75. package/dist/features/sticker-gate.d.ts.map +1 -0
  76. package/dist/features/sticker-gate.js +391 -0
  77. package/dist/features/sticker-gate.js.map +1 -0
  78. package/dist/features/sticker-store.d.ts +158 -0
  79. package/dist/features/sticker-store.d.ts.map +1 -0
  80. package/dist/features/sticker-store.js +676 -0
  81. package/dist/features/sticker-store.js.map +1 -0
  82. package/dist/features/sticker-tagger.d.ts +10 -0
  83. package/dist/features/sticker-tagger.d.ts.map +1 -0
  84. package/dist/features/sticker-tagger.js +139 -0
  85. package/dist/features/sticker-tagger.js.map +1 -0
  86. package/dist/gateway/bootstrap.d.ts +12 -0
  87. package/dist/gateway/bootstrap.d.ts.map +1 -0
  88. package/dist/gateway/bootstrap.js +346 -0
  89. package/dist/gateway/bootstrap.js.map +1 -0
  90. package/dist/gateway/debounce.d.ts +40 -0
  91. package/dist/gateway/debounce.d.ts.map +1 -0
  92. package/dist/gateway/debounce.js +262 -0
  93. package/dist/gateway/debounce.js.map +1 -0
  94. package/dist/gateway/index.d.ts +3 -0
  95. package/dist/gateway/index.d.ts.map +1 -0
  96. package/dist/gateway/index.js +3 -0
  97. package/dist/gateway/index.js.map +1 -0
  98. package/dist/gateway/middleware-setup.d.ts +12 -0
  99. package/dist/gateway/middleware-setup.d.ts.map +1 -0
  100. package/dist/gateway/middleware-setup.js +156 -0
  101. package/dist/gateway/middleware-setup.js.map +1 -0
  102. package/dist/index.d.ts +14 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +187 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/middleware/attachment.d.ts +11 -0
  107. package/dist/middleware/attachment.d.ts.map +1 -0
  108. package/dist/middleware/attachment.js +17 -0
  109. package/dist/middleware/attachment.js.map +1 -0
  110. package/dist/middleware/media-history.d.ts +30 -0
  111. package/dist/middleware/media-history.d.ts.map +1 -0
  112. package/dist/middleware/media-history.js +54 -0
  113. package/dist/middleware/media-history.js.map +1 -0
  114. package/dist/middleware/sticker-capture.d.ts +17 -0
  115. package/dist/middleware/sticker-capture.d.ts.map +1 -0
  116. package/dist/middleware/sticker-capture.js +97 -0
  117. package/dist/middleware/sticker-capture.js.map +1 -0
  118. package/dist/model/index.d.ts +10 -0
  119. package/dist/model/index.d.ts.map +1 -0
  120. package/dist/model/index.js +9 -0
  121. package/dist/model/index.js.map +1 -0
  122. package/dist/model/model-resolver.d.ts +88 -0
  123. package/dist/model/model-resolver.d.ts.map +1 -0
  124. package/dist/model/model-resolver.js +151 -0
  125. package/dist/model/model-resolver.js.map +1 -0
  126. package/dist/model/prefs-store.d.ts +24 -0
  127. package/dist/model/prefs-store.d.ts.map +1 -0
  128. package/dist/model/prefs-store.js +126 -0
  129. package/dist/model/prefs-store.js.map +1 -0
  130. package/dist/model/settings-reader.d.ts +19 -0
  131. package/dist/model/settings-reader.d.ts.map +1 -0
  132. package/dist/model/settings-reader.js +76 -0
  133. package/dist/model/settings-reader.js.map +1 -0
  134. package/dist/model/types.d.ts +15 -0
  135. package/dist/model/types.d.ts.map +1 -0
  136. package/dist/model/types.js +5 -0
  137. package/dist/model/types.js.map +1 -0
  138. package/dist/session/idle-evictor.d.ts +18 -0
  139. package/dist/session/idle-evictor.d.ts.map +1 -0
  140. package/dist/session/idle-evictor.js +31 -0
  141. package/dist/session/idle-evictor.js.map +1 -0
  142. package/dist/session/index.d.ts +9 -0
  143. package/dist/session/index.d.ts.map +1 -0
  144. package/dist/session/index.js +8 -0
  145. package/dist/session/index.js.map +1 -0
  146. package/dist/session/session-manager.d.ts +97 -0
  147. package/dist/session/session-manager.d.ts.map +1 -0
  148. package/dist/session/session-manager.js +624 -0
  149. package/dist/session/session-manager.js.map +1 -0
  150. package/dist/session/types.d.ts +130 -0
  151. package/dist/session/types.d.ts.map +1 -0
  152. package/dist/session/types.js +2 -0
  153. package/dist/session/types.js.map +1 -0
  154. package/dist/session/workspace-attach.d.ts +22 -0
  155. package/dist/session/workspace-attach.d.ts.map +1 -0
  156. package/dist/session/workspace-attach.js +21 -0
  157. package/dist/session/workspace-attach.js.map +1 -0
  158. package/dist/setup.d.ts +25 -0
  159. package/dist/setup.d.ts.map +1 -0
  160. package/dist/setup.js +198 -0
  161. package/dist/setup.js.map +1 -0
  162. package/dist/shared/index.d.ts +7 -0
  163. package/dist/shared/index.d.ts.map +1 -0
  164. package/dist/shared/index.js +7 -0
  165. package/dist/shared/index.js.map +1 -0
  166. package/dist/shared/scope.d.ts +14 -0
  167. package/dist/shared/scope.d.ts.map +1 -0
  168. package/dist/shared/scope.js +12 -0
  169. package/dist/shared/scope.js.map +1 -0
  170. package/dist/shared/send-helper.d.ts +11 -0
  171. package/dist/shared/send-helper.d.ts.map +1 -0
  172. package/dist/shared/send-helper.js +13 -0
  173. package/dist/shared/send-helper.js.map +1 -0
  174. package/dist/shared/utils.d.ts +28 -0
  175. package/dist/shared/utils.d.ts.map +1 -0
  176. package/dist/shared/utils.js +143 -0
  177. package/dist/shared/utils.js.map +1 -0
  178. package/dist/transport/attachment.d.ts +18 -0
  179. package/dist/transport/attachment.d.ts.map +1 -0
  180. package/dist/transport/attachment.js +106 -0
  181. package/dist/transport/attachment.js.map +1 -0
  182. package/dist/transport/chunker.d.ts +14 -0
  183. package/dist/transport/chunker.d.ts.map +1 -0
  184. package/dist/transport/chunker.js +78 -0
  185. package/dist/transport/chunker.js.map +1 -0
  186. package/dist/transport/events.d.ts +80 -0
  187. package/dist/transport/events.d.ts.map +1 -0
  188. package/dist/transport/events.js +62 -0
  189. package/dist/transport/events.js.map +1 -0
  190. package/dist/transport/inbound.d.ts +8 -0
  191. package/dist/transport/inbound.d.ts.map +1 -0
  192. package/dist/transport/inbound.js +292 -0
  193. package/dist/transport/inbound.js.map +1 -0
  194. package/dist/transport/index.d.ts +10 -0
  195. package/dist/transport/index.d.ts.map +1 -0
  196. package/dist/transport/index.js +10 -0
  197. package/dist/transport/index.js.map +1 -0
  198. package/dist/transport/inject-rules.d.ts +24 -0
  199. package/dist/transport/inject-rules.d.ts.map +1 -0
  200. package/dist/transport/inject-rules.js +72 -0
  201. package/dist/transport/inject-rules.js.map +1 -0
  202. package/dist/transport/outbound-buffer.d.ts +56 -0
  203. package/dist/transport/outbound-buffer.d.ts.map +1 -0
  204. package/dist/transport/outbound-buffer.js +131 -0
  205. package/dist/transport/outbound-buffer.js.map +1 -0
  206. package/dist/transport/outbound.d.ts +30 -0
  207. package/dist/transport/outbound.d.ts.map +1 -0
  208. package/dist/transport/outbound.js +139 -0
  209. package/dist/transport/outbound.js.map +1 -0
  210. package/dist/transport/rich-media.d.ts +41 -0
  211. package/dist/transport/rich-media.d.ts.map +1 -0
  212. package/dist/transport/rich-media.js +104 -0
  213. package/dist/transport/rich-media.js.map +1 -0
  214. package/dist/transport/streaming-writer.d.ts +47 -0
  215. package/dist/transport/streaming-writer.d.ts.map +1 -0
  216. package/dist/transport/streaming-writer.js +110 -0
  217. package/dist/transport/streaming-writer.js.map +1 -0
  218. package/dist/transport/tool-presenter.d.ts +38 -0
  219. package/dist/transport/tool-presenter.d.ts.map +1 -0
  220. package/dist/transport/tool-presenter.js +121 -0
  221. package/dist/transport/tool-presenter.js.map +1 -0
  222. package/dist/types.d.ts +45 -0
  223. package/dist/types.d.ts.map +1 -0
  224. package/dist/types.js +5 -0
  225. package/dist/types.js.map +1 -0
  226. package/package.json +106 -0
  227. package/settings-host.js +927 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * chat-ledger.ts — 聊天台账(M3 v4, 2026-09-03 新增; 2026-09-05 加群成员维度)
3
+ *
4
+ * 定时唤醒/遥控台要"选一个聊天对象"却要主人手抄 openid——体验失败(主人已吐槽)。
5
+ * 本模块把机器人见过的每个聊天(群/私聊)自动记台账:
6
+ * {dataDir}/known-chats.jsonl append-only 一行一条
7
+ * {ts, scope:'group'|'c2c', id, name?}
8
+ * - c2c: id=用户 openid, name=用户昵称(QQ 平台给的 senderName)
9
+ * - group: id=group_openid, name=该群最近一条发言者昵称(群无群名, 用成员名帮主人辨认)
10
+ * 同时按群记成员(官方"成员列表"未开放时的本地兜底名单, 供群管理面板选人禁言):
11
+ * {dataDir}/group-members.jsonl {ts, gid, mid, name?} append-only
12
+ * host 半边读此文件聚合出"最近聊过的对象"下拉, ④区点选即填 targetId; 群管理读成员聚合出可选人。
13
+ * 中间件挂在 mentionGate 之前(未@消息也流经), 与活性计数同位置。
14
+ *
15
+ * ⚠️ 本地手改功能(fork 新增):维护清单见工作区根《插件改动维护注意事项.md》。
16
+ */
17
+ import { appendFileSync, readFileSync, statSync, writeFileSync } from 'node:fs';
18
+ import { join } from 'node:path';
19
+ /** 台账文件路径(与图库同 dataDir, host 侧可经 sticker store 推导) */
20
+ export function ledgerPath(dataDir) {
21
+ return join(dataDir, 'known-chats.jsonl');
22
+ }
23
+ /** 群成员台账路径(同 dataDir) */
24
+ export function groupMembersPath(dataDir) {
25
+ return join(dataDir, 'group-members.jsonl');
26
+ }
27
+ /** 由 dsh-qqbot 侧写入(中间件); host 侧只读聚合 */
28
+ export function appendLedger(dataDir, line) {
29
+ try {
30
+ appendFileSync(ledgerPath(dataDir), JSON.stringify(line) + '\n', 'utf8');
31
+ }
32
+ catch { /* 台账失败不影响主链 */ }
33
+ }
34
+ /** 由 dsh-qqbot 侧写入一条"群成员发言"(中间件); host 侧读聚合出可禁言名单 */
35
+ export function appendGroupMember(dataDir, line) {
36
+ try {
37
+ appendFileSync(groupMembersPath(dataDir), JSON.stringify(line) + '\n', 'utf8');
38
+ }
39
+ catch { /* 台账失败不影响主链 */ }
40
+ }
41
+ /** 读某群的本地成员清单(聚合: 每成员保留最近昵称/最近时间/发言次数, 按最近发言倒序) */
42
+ export function readGroupMembers(dataDir, gid) {
43
+ try {
44
+ const raw = readFileSync(groupMembersPath(dataDir), 'utf8');
45
+ const map = new Map();
46
+ for (const l of raw.split('\n')) {
47
+ const t = l.trim();
48
+ if (!t)
49
+ continue;
50
+ try {
51
+ const o = JSON.parse(t);
52
+ if (!o || typeof o.mid !== 'string' || !o.mid || typeof o.gid !== 'string' || !o.gid)
53
+ continue;
54
+ if (gid && o.gid !== gid)
55
+ continue;
56
+ const key = `${o.gid}:${o.mid}`;
57
+ const cur = map.get(key);
58
+ if (!cur)
59
+ map.set(key, { gid: o.gid, mid: o.mid, name: o.name, lastSeen: o.ts, count: 1 });
60
+ else {
61
+ cur.count += 1;
62
+ if (o.ts > cur.lastSeen) {
63
+ cur.lastSeen = o.ts;
64
+ if (o.name)
65
+ cur.name = o.name;
66
+ }
67
+ }
68
+ }
69
+ catch { /* 坏行跳过 */ }
70
+ }
71
+ return [...map.values()].sort((a, b) => b.lastSeen - a.lastSeen).slice(0, 300);
72
+ }
73
+ catch {
74
+ return [];
75
+ }
76
+ }
77
+ /** host 侧读台账文件(不存在/坏行容错), 返回按 ts 升序的行 */
78
+ export function readLedger(dataDir) {
79
+ try {
80
+ const raw = readFileSync(ledgerPath(dataDir), 'utf8');
81
+ const lines = [];
82
+ for (const l of raw.split('\n')) {
83
+ const t = l.trim();
84
+ if (!t)
85
+ continue;
86
+ try {
87
+ const o = JSON.parse(t);
88
+ if (o && typeof o.id === 'string' && (o.scope === 'group' || o.scope === 'c2c')) {
89
+ lines.push({ ts: typeof o.ts === 'number' ? o.ts : 0, scope: o.scope, id: o.id, name: typeof o.name === 'string' ? o.name : undefined });
90
+ }
91
+ }
92
+ catch { /* 坏行跳过 */ }
93
+ }
94
+ return lines;
95
+ }
96
+ catch {
97
+ return [];
98
+ }
99
+ }
100
+ /**
101
+ * 聚合"最近聊过的对象"(供下拉): 每对象保留最近 ts/最近昵称 + 消息数。
102
+ * c2c 优先展示昵称; group 无群名, name=该群最近发言者昵称。
103
+ */
104
+ export function aggregateKnownChats(lines, limit = 60) {
105
+ const map = new Map();
106
+ for (const l of lines) {
107
+ const key = `${l.scope}:${l.id}`;
108
+ const cur = map.get(key);
109
+ if (!cur) {
110
+ map.set(key, { scope: l.scope, id: l.id, name: l.name, lastSeen: l.ts, count: 1 });
111
+ }
112
+ else {
113
+ cur.count += 1;
114
+ if (l.ts > cur.lastSeen) {
115
+ cur.lastSeen = l.ts;
116
+ if (l.name)
117
+ cur.name = l.name; // group: 群名不可得, 用最近发言者昵称帮助辨认
118
+ }
119
+ }
120
+ }
121
+ return [...map.values()].sort((a, b) => b.lastSeen - a.lastSeen).slice(0, limit);
122
+ }
123
+ /** 台账文件大小(给 host 状态用; 不存在返回 0) */
124
+ export function ledgerSize(dataDir) {
125
+ try {
126
+ return statSync(ledgerPath(dataDir)).size;
127
+ }
128
+ catch {
129
+ return 0;
130
+ }
131
+ }
132
+ /**
133
+ * 移除某群某成员的本地记录(成员已退群/禁言无效时调用, 保持本地名单干净)。
134
+ * 重写文件剔除该 gid+mid 的所有行; 文件不存在/无匹配则不动。
135
+ */
136
+ export function removeGroupMember(dataDir, gid, mid) {
137
+ try {
138
+ const p = groupMembersPath(dataDir);
139
+ const raw = readFileSync(p, 'utf8');
140
+ const kept = [];
141
+ let removed = 0;
142
+ for (const l of raw.split('\n')) {
143
+ const t = l.trim();
144
+ if (!t) {
145
+ kept.push('');
146
+ continue;
147
+ }
148
+ try {
149
+ const o = JSON.parse(t);
150
+ if (o && o.gid === gid && o.mid === mid) {
151
+ removed += 1;
152
+ continue;
153
+ }
154
+ }
155
+ catch { /* 坏行保留 */ }
156
+ kept.push(l);
157
+ }
158
+ if (removed > 0) {
159
+ writeFileSync(p, kept.join('\n').replace(/\n{2,}/g, '\n').trimEnd() + (kept.length ? '\n' : ''), 'utf8');
160
+ }
161
+ }
162
+ catch { /* 忽略 */ }
163
+ }
164
+ /** 中间件: 记录见过的群/私聊 + 群内发言成员(放 mentionGate 之前, 未@消息也流经) */
165
+ export function chatLedgerRecorder(dataDir) {
166
+ return async (ctx, next) => {
167
+ try {
168
+ const msg = ctx.message;
169
+ const now = Date.now();
170
+ if (msg.kind === 'group' && msg.groupOpenid) {
171
+ appendLedger(dataDir, { ts: now, scope: 'group', id: msg.groupOpenid, name: msg.senderName || undefined });
172
+ // 群成员兜底名单(官方成员列表未开放): 每个发言者都记, 禁言面板按群聚合可"选人"
173
+ if (msg.senderId) {
174
+ appendGroupMember(dataDir, { ts: now, gid: msg.groupOpenid, mid: msg.senderId, name: msg.senderName || undefined });
175
+ }
176
+ }
177
+ else if ((msg.kind === 'c2c' || msg.kind === 'direct') && msg.senderId) {
178
+ appendLedger(dataDir, { ts: now, scope: 'c2c', id: msg.senderId, name: msg.senderName || undefined });
179
+ }
180
+ }
181
+ catch { /* 记录失败不影响主链 */ }
182
+ await next();
183
+ };
184
+ }
185
+ //# sourceMappingURL=chat-ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-ledger.js","sourceRoot":"","sources":["../../src/features/chat-ledger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * QQ 表情标签 → 可读文本(名字优先, faceId 兜底)
3
+ *
4
+ * QQ 入站消息里表情有两种形态:
5
+ * 老格式: [<face,id=0/>] → id 直接查表
6
+ * 新格式: <faceType=6,faceId="0",ext="base64"> → ext.text 优先, 空则按 faceId 查表
7
+ *
8
+ * ⚠️ 必须挂在 SDK contentSanitizer(parseFaceTags) **之前**: SDK 的正则
9
+ * 只解 ext.text, text 为空会把 faceId 一起丢掉 → 输出"未知表情"。
10
+ * 本层先把标签整体替换成可读文本, SDK 就再也匹配不到原标签了。
11
+ */
12
+ import type { MiddlewareContext } from '@tencent-connect/qqbot-nodejs';
13
+ /** 把消息 content 里的表情标签全部转成可读文本 */
14
+ export declare function resolveFaceTags(text: string): string;
15
+ /** 中间件: 在 SDK contentSanitizer 之前清洗表情标签(不依赖 SDK 的 parseFaceTags) */
16
+ export declare function faceTagResolver(): (ctx: MiddlewareContext, next: () => Promise<void>) => Promise<void>;
17
+ //# sourceMappingURL=face-tags.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"face-tags.d.ts","sourceRoot":"","sources":["../../src/features/face-tags.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAoDvE,iCAAiC;AACjC,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAepD;AAED,oEAAoE;AACpE,wBAAgB,eAAe,KACf,KAAK,iBAAiB,EAAE,MAAM,MAAM,OAAO,CAAC,IAAI,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAMhF"}
@@ -0,0 +1,79 @@
1
+ /** QQ 经典系统表情 faceId → 中文名(0=微笑…, 常见 0~138; 覆盖不全的按 ext.text) */
2
+ const QQ_FACE_NAMES = {
3
+ '0': '微笑', '1': '撇嘴', '2': '色', '3': '发呆', '4': '得意', '5': '流泪',
4
+ '6': '害羞', '7': '闭嘴', '8': '睡', '9': '大哭', '10': '尴尬', '11': '发怒',
5
+ '12': '调皮', '13': '呲牙', '14': '惊讶', '15': '难过', '16': '酷', '17': '冷汗',
6
+ '18': '抓狂', '19': '吐', '20': '偷笑', '21': '可爱', '22': '白眼', '23': '傲慢',
7
+ '24': '饥饿', '25': '困', '26': '惊恐', '27': '流汗', '28': '憨笑', '29': '悠闲',
8
+ '30': '奋斗', '31': '咒骂', '32': '疑问', '33': '嘘', '34': '晕', '35': '狂躁',
9
+ '36': '衰', '37': '骷髅', '38': '敲打', '39': '再见', '40': '擦汗', '41': '抠鼻',
10
+ '42': '鼓掌', '43': '糗大了', '44': '坏笑', '45': '左哼哼', '46': '右哼哼', '47': '哈欠',
11
+ '48': '鄙视', '49': '委屈', '50': '快哭了', '51': '阴险', '52': '亲亲', '53': '吓',
12
+ '54': '可怜', '55': '菜刀', '56': '西瓜', '57': '啤酒', '58': '篮球', '59': '乒乓',
13
+ '60': '咖啡', '61': '饭', '62': '猪头', '63': '玫瑰', '64': '凋谢', '65': '示爱',
14
+ '66': '爱心', '67': '心碎', '68': '蛋糕', '69': '闪电', '70': '炸弹', '71': '刀',
15
+ '72': '足球', '73': '便便', '74': '月亮', '75': '太阳', '76': '礼物', '77': '拥抱',
16
+ '78': '强', '79': '弱', '80': '握手', '81': '胜利', '82': '抱拳', '83': '勾引',
17
+ '84': '拳头', '85': '差劲', '86': '爱你', '87': 'NO', '88': 'OK', '89': '爱情',
18
+ '90': '飞吻', '91': '跳跳', '92': '发抖', '93': '怄火', '94': '转圈', '95': '磕头',
19
+ '96': '回头', '97': '跳绳', '98': '挥手', '99': '激动', '100': '街舞', '101': '献吻',
20
+ '102': '左太极', '103': '右太极', '104': '双喜', '105': '鞭炮', '106': '灯笼', '107': '发财',
21
+ '108': 'K歌', '109': '购物', '110': '邮件', '111': '帅', '112': '喝彩', '113': '祈祷',
22
+ '114': '爆筋', '115': '棒棒糖', '116': '喝奶', '117': '下面', '118': '香蕉', '119': '飞机',
23
+ '120': '开车', '121': '左车头', '122': '车厢', '123': '右车头', '124': '多云', '125': '下雨',
24
+ '126': '钞票', '127': '熊猫', '128': '灯泡', '129': '风车', '130': '闹钟', '131': '打伞',
25
+ '132': '彩球', '133': '钻戒', '134': '沙发', '135': '纸巾', '136': '药', '137': '手枪',
26
+ '138': '茶', '139': '眨眼', '140': '泪奔', '141': '无奈', '142': '托腮', '143': '卖萌',
27
+ '144': '斜眼笑', '145': 'doge', '146': '惊喜', '147': '骚扰', '148': '小纠结', '149': '赞',
28
+ '150': '无语', '151': '献花', '152': '撇嘴2', '153': '暗送秋波', '154': '挑眉', '155': '委屈2',
29
+ '156': '吓哭', '157': '财神', '158': '右太极', '159': '歌唱', '160': '水', '161': '猪头2',
30
+ };
31
+ /** 解新格式标签: 返回可读文本(找不到名字给通用占位) */
32
+ function decodeNewTag(_full, faceId, ext) {
33
+ // 1) ext 里带 text → 官方给的名字最准
34
+ if (ext) {
35
+ try {
36
+ const decoded = Buffer.from(ext, 'base64').toString('utf-8');
37
+ const parsed = JSON.parse(decoded);
38
+ const t = (parsed && typeof parsed.text === 'string' && parsed.text.trim()) ? parsed.text.trim() : '';
39
+ if (t)
40
+ return `【表情: ${t}】`;
41
+ }
42
+ catch {
43
+ /* ext 不可解 → 走 faceId 兜底 */
44
+ }
45
+ }
46
+ // 2) faceId 查表
47
+ const name = QQ_FACE_NAMES[faceId];
48
+ if (name)
49
+ return `【表情: ${name}】`;
50
+ return faceId ? `【表情: id${faceId}】` : '【表情】';
51
+ }
52
+ /** 把消息 content 里的表情标签全部转成可读文本 */
53
+ export function resolveFaceTags(text) {
54
+ if (!text)
55
+ return text;
56
+ // 新格式: <faceType=N,faceId="X",ext="b64"> (属性顺序可能与 SDK 不同, 宽松匹配)
57
+ let out = text.replace(/<faceType=\d+,faceId="([^"]*)",ext="([^"]*)">/g, (m, id, ex) => decodeNewTag(m, id, ex));
58
+ // 宽松形态(缺 ext / 顺序不同): <faceType=N,faceId="X">
59
+ out = out.replace(/<faceType=\d+,faceId="([^"]*)">/g, (_m, id) => {
60
+ const name = QQ_FACE_NAMES[id];
61
+ return name ? `【表情: ${name}】` : (id ? `【表情: id${id}】` : '【表情】');
62
+ });
63
+ // 老格式: [<face,id=N/>]
64
+ out = out.replace(/\[<face,id=(\d+)\/?>]/g, (_m, id) => {
65
+ const name = QQ_FACE_NAMES[id];
66
+ return name ? `【表情: ${name}】` : '【表情】';
67
+ });
68
+ return out;
69
+ }
70
+ /** 中间件: 在 SDK contentSanitizer 之前清洗表情标签(不依赖 SDK 的 parseFaceTags) */
71
+ export function faceTagResolver() {
72
+ return async (ctx, next) => {
73
+ if (typeof ctx.message.content === 'string') {
74
+ ctx.message.content = resolveFaceTags(ctx.message.content);
75
+ }
76
+ await next();
77
+ };
78
+ }
79
+ //# sourceMappingURL=face-tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"face-tags.js","sourceRoot":"","sources":["../../src/features/face-tags.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import type { QQBotSender } from '../transport/outbound-buffer.js';
2
+ import type { ImQQBotConfig } from '../config.js';
3
+ import type { Logger } from '../types.js';
4
+ import type { SessionManager } from '../session/index.js';
5
+ /** 官方 GROUP_JOIN_REQUEST 事件体(与本项目用到的字段) */
6
+ export interface GroupJoinRequestEvent {
7
+ group_openid?: string;
8
+ join_request_id?: string;
9
+ member_openid?: string;
10
+ username?: string;
11
+ apply_at?: string;
12
+ apply_source?: 'self_apply' | 'invited';
13
+ invited_by?: string;
14
+ risk_tips?: string;
15
+ union_openid?: string;
16
+ bot?: boolean;
17
+ verify_info?: {
18
+ method?: string;
19
+ verify_message?: string;
20
+ review_qa_list?: Array<{
21
+ question: string;
22
+ answer: string;
23
+ }>;
24
+ };
25
+ auto_approved?: {
26
+ strategy_id?: string;
27
+ };
28
+ [key: string]: unknown;
29
+ }
30
+ /** pending 待办条目(落盘, UI/工具可读) */
31
+ export interface PendingJoinRequest {
32
+ group_openid: string;
33
+ join_request_id: string;
34
+ member_openid: string;
35
+ username?: string;
36
+ apply_at?: string;
37
+ apply_source?: string;
38
+ invited_by?: string;
39
+ risk_tips?: string;
40
+ verify_message?: string;
41
+ /** 事件到达时间(本机 ISO) */
42
+ seen_at: string;
43
+ /** 是否已发过群内提醒 */
44
+ notified: boolean;
45
+ }
46
+ /** 追加一条 pending(按 join_request_id 去重, 最新在前) */
47
+ export declare function pushPendingJoinRequest(cwd: string | undefined, ev: PendingJoinRequest): void;
48
+ /** 读某群 pending(UI/工具用; gid 空 = 全部) */
49
+ export declare function readPendingJoinRequests(cwd: string | undefined, gid?: string): PendingJoinRequest[];
50
+ /**
51
+ * 处理一条 GROUP_JOIN_REQUEST 事件(rawEvent 数据):
52
+ * ① auto_approved 下行(自动审批已通过)→ 仅记审计, 不打扰;
53
+ * ② 否则去重 → pending 落盘 → **注入该群 agent 的 inbox(next-step 队列, wakeup=false)**:
54
+ * - 等效"模拟用户消息 + 不触发模型回合"(dsh 底层: agent.inject = send(msg,'next-step',wakeup:false),
55
+ * 区别于 followup 的唤醒; 消息持久化在会话, 等主人下一条真人消息开回合时同批进 AI 上下文)
56
+ * - 宿主 agent 无 inject 能力/无活跃会话 → 兜底发一条 QQ 群静态提醒卡片(不静默丢)
57
+ * @returns true = 事件被消费(记录/注入), false = 忽略(重复或无效)
58
+ */
59
+ export declare function handleGroupJoinRequestEvent(data: unknown, opts: {
60
+ sender: QQBotSender;
61
+ config: ImQQBotConfig;
62
+ logger: Logger;
63
+ manager: SessionManager;
64
+ }): Promise<boolean>;
65
+ //# sourceMappingURL=group-join-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-join-request.d.ts","sourceRoot":"","sources":["../../src/features/group-join-request.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAE1D,2CAA2C;AAC3C,MAAM,WAAW,qBAAqB;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,KAAK,CAAC;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;IACzH,aAAa,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,gCAAgC;AAChC,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB;IAChB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAuCD,+CAA+C;AAC/C,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,EAAE,EAAE,kBAAkB,GACrB,IAAI,CAUN;AAED,sCAAsC;AACtC,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,GAAG,CAAC,EAAE,MAAM,GACX,kBAAkB,EAAE,CAItB;AAED;;;;;;;;GAQG;AACH,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,OAAO,EACb,IAAI,EAAE;IACJ,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,cAAc,CAAC;CACzB,GACA,OAAO,CAAC,OAAO,CAAC,CA2GlB"}
@@ -0,0 +1,190 @@
1
+ /**
2
+ * group-join-request.ts — 入群申请事件(GROUP_JOIN_REQUEST)监听与提醒 (P2.5, 2026-09-05)
3
+ *
4
+ * 官方事件事实(参考文档/qqbot-api-v2-docs/autogen__event__group_join_request.html):
5
+ * - 事件名 GROUP_JOIN_REQUEST, Intent = GROUP_MEMBER_EVENT (1<<24), 仅机器人=群管理员收到;
6
+ * - 事件体: group_openid / join_request_id / member_openid / username / apply_at /
7
+ * apply_source(self_apply|invited) / invited_by / risk_tips / verify_info / auto_approved(下行, 自动审批通过时带);
8
+ * - 说明: 拉取/审批 REST 走 group-admin.ts(GroupAdminClient), 这里只接"实时事件 → 提醒 + 待办落盘"。
9
+ *
10
+ * SDK 接线(勘察 2026-09-05): @tencent-connect/qqbot-nodejs 未枚举该事件, 但三层透传全通:
11
+ * QQBot options.intents(自定义, 默认 FULL_INTENTS 不含 1<<24)
12
+ * → gateway dispatchEvent 未知事件返 {action:'raw'} → onRawEvent → bot.on('rawEvent')。
13
+ * 故订阅 = 构造 QQBot 时 intents 或上 1<<24 + 监听 rawEvent, SDK 零改动。
14
+ * 若官方未授权该 intent, Identify 可能被拒(close 4914/4915)→ 用 config.groupAdmin.watchJoinRequests 开关, 默认关。
15
+ *
16
+ * 提醒通道: QQ member_openid ≠ c2c user_openid(无法由 owners 群身份推导私聊目标),
17
+ * 故"提醒主人"落地 = ①在该群内发一条 bot 消息(群主/管理员即主人可见) + ②pending 待办落盘,
18
+ * UI ⑥区/P3 与群管理工具都能再读 pending; 全部动作走审计日志。
19
+ */
20
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
23
+ /** 内存去重(join_request_id)与群内提醒限频: 每群 5 分钟至多 1 条, 防风暴 */
24
+ const recentIds = new Map();
25
+ const lastGroupNotify = new Map();
26
+ const DEDUP_TTL_MS = 24 * 3600_000;
27
+ const GROUP_NOTIFY_MIN_GAP_MS = 300_000;
28
+ const MAX_PENDING_PER_GROUP = 50;
29
+ /** pending 文件路径({cwd}/.qqbot/join-pending.json, 与 groups.json 同目录) */
30
+ function pendingPath(cwd) {
31
+ return join(cwd || process.cwd(), '.qqbot', 'join-pending.json');
32
+ }
33
+ function loadPending(path) {
34
+ try {
35
+ if (!existsSync(path))
36
+ return {};
37
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
38
+ return raw && typeof raw === 'object' ? raw : {};
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ function savePending(path, store) {
45
+ try {
46
+ mkdirSync(join(path, '..'), { recursive: true });
47
+ writeFileSync(path, JSON.stringify(store, null, 1), 'utf8');
48
+ }
49
+ catch (err) {
50
+ // 落盘失败不阻断主流程, 调用方已留审计
51
+ void err;
52
+ }
53
+ }
54
+ /** 追加一条 pending(按 join_request_id 去重, 最新在前) */
55
+ export function pushPendingJoinRequest(cwd, ev) {
56
+ const path = pendingPath(cwd);
57
+ const store = loadPending(path);
58
+ const gid = ev.group_openid;
59
+ const list = store[gid] ?? [];
60
+ const idx = list.findIndex((x) => x.join_request_id === ev.join_request_id);
61
+ if (idx >= 0)
62
+ list.splice(idx, 1);
63
+ list.unshift(ev);
64
+ store[gid] = list.slice(0, MAX_PENDING_PER_GROUP);
65
+ savePending(path, store);
66
+ }
67
+ /** 读某群 pending(UI/工具用; gid 空 = 全部) */
68
+ export function readPendingJoinRequests(cwd, gid) {
69
+ const store = loadPending(pendingPath(cwd));
70
+ if (gid)
71
+ return store[gid] ?? [];
72
+ return Object.values(store).flat();
73
+ }
74
+ /**
75
+ * 处理一条 GROUP_JOIN_REQUEST 事件(rawEvent 数据):
76
+ * ① auto_approved 下行(自动审批已通过)→ 仅记审计, 不打扰;
77
+ * ② 否则去重 → pending 落盘 → **注入该群 agent 的 inbox(next-step 队列, wakeup=false)**:
78
+ * - 等效"模拟用户消息 + 不触发模型回合"(dsh 底层: agent.inject = send(msg,'next-step',wakeup:false),
79
+ * 区别于 followup 的唤醒; 消息持久化在会话, 等主人下一条真人消息开回合时同批进 AI 上下文)
80
+ * - 宿主 agent 无 inject 能力/无活跃会话 → 兜底发一条 QQ 群静态提醒卡片(不静默丢)
81
+ * @returns true = 事件被消费(记录/注入), false = 忽略(重复或无效)
82
+ */
83
+ export async function handleGroupJoinRequestEvent(data, opts) {
84
+ const { sender, config, logger, manager } = opts;
85
+ const ev = (data ?? {});
86
+ const gid = ev.group_openid;
87
+ const jid = ev.join_request_id;
88
+ const mid = ev.member_openid;
89
+ if (!gid || !jid || !mid) {
90
+ logger.warn(`[group-join] 事件缺关键字段, 忽略: ${JSON.stringify(ev).slice(0, 300)}`);
91
+ return false;
92
+ }
93
+ // 内存去重(重启后事件极少重推, 内存足够)
94
+ const now = Date.now();
95
+ for (const [id, ts] of recentIds) {
96
+ if (now - ts > DEDUP_TTL_MS)
97
+ recentIds.delete(id);
98
+ }
99
+ if (recentIds.has(jid)) {
100
+ logger.debug(`[group-join] 重复事件跳过: ${jid}`);
101
+ return false;
102
+ }
103
+ recentIds.set(jid, now);
104
+ // 自动审批下行: 只记审计, 不提醒(结果已确定, 无需管理员操作)
105
+ if (ev.auto_approved && ev.auto_approved.strategy_id) {
106
+ logger.info(`[group-join] 自动审批通过(下行): gid=${gid} user=${ev.username}(${mid}) strategy=${ev.auto_approved.strategy_id}`);
107
+ return true;
108
+ }
109
+ const verify = ev.verify_info?.verify_message || ev.verify_info?.method || '';
110
+ const item = {
111
+ group_openid: gid,
112
+ join_request_id: jid,
113
+ member_openid: mid,
114
+ username: ev.username,
115
+ apply_at: ev.apply_at,
116
+ apply_source: ev.apply_source,
117
+ invited_by: ev.invited_by,
118
+ risk_tips: ev.risk_tips,
119
+ verify_message: verify,
120
+ seen_at: new Date().toISOString(),
121
+ notified: false,
122
+ };
123
+ pushPendingJoinRequest(config.cwd, item);
124
+ logger.info(`[group-join] 新入群申请: gid=${gid} user=${ev.username ?? '?'}(${mid.slice(0, 10)}…) src=${ev.apply_source ?? '?'}${ev.risk_tips ? ` risk=${ev.risk_tips}` : ''}`);
125
+ // 通知策略(主人定稿 2026-09-05 21:10):
126
+ // 要"模拟用户消息 + 模拟点停止" = 把申请直接 append 为该群 agent 的 session log 一条
127
+ // user/message 事件(turn() 中让消息显示在 web 的那一步), 但【不 wake 不开回合】→
128
+ // web 会话流能看到这条消息(像用户真发过), AI 却不会主动思考/回复(不撞 QQ 限流)。
129
+ // 主人下次真人消息开新回合时, 这条 user/message 作为 log 历史被 deriveMessages 组装进
130
+ // 上下文 → AI 自然看到申请并能接应"通过/拒绝"。
131
+ if (config.groupAdmin?.notifyInGroup !== false) {
132
+ const last = lastGroupNotify.get(gid) ?? 0;
133
+ if (now - last >= GROUP_NOTIFY_MIN_GAP_MS) {
134
+ lastGroupNotify.set(gid, now);
135
+ const userPart = ev.username ?? '(未知昵称)';
136
+ const srcPart = ev.apply_source === 'invited'
137
+ ? `被邀请${ev.invited_by ? `(邀请人 ${ev.invited_by.slice(0, 10)}…)` : ''}`
138
+ : '主动申请';
139
+ const verifyPart = verify ? ` 验证「${verify}」` : '';
140
+ const riskPart = ev.risk_tips ? ` ⚠️${ev.risk_tips}` : '';
141
+ const summary = `[入群申请] ${userPart}(${srcPart})${verifyPart}${riskPart}`;
142
+ // 有活跃会话直接用; 没有(空闲被回收)→ getOrCreate 恢复/重建会话(不触发回合, 只保证 log 存在)
143
+ let record = manager.findByPeer('group', gid);
144
+ if (!record) {
145
+ try {
146
+ const target = { scope: 'group', targetId: gid };
147
+ record = await manager.getOrCreate('group', gid, mid, target);
148
+ logger.info(`[group-join] 会话不在, 已 getOrCreate 恢复 gid=${gid}`);
149
+ }
150
+ catch (err) {
151
+ logger.warn(`[group-join] getOrCreate 失败: ${err instanceof Error ? err.message : String(err)}`);
152
+ }
153
+ }
154
+ const sess = record?.agent?.session;
155
+ if (sess && typeof sess.append === 'function') {
156
+ try {
157
+ // 与 turn() 同款: session.append('user/message', msg, {surfaceOp:'append'}) → web 可见;
158
+ // 不调 send/followup → 不 wakeDriver → AI 不触发回合(等效用户发消息后点停止)。
159
+ const msg = createUserMessage({
160
+ content: [{ type: 'text', text: summary }],
161
+ source: { kind: 'user' },
162
+ });
163
+ sess.append('user/message', msg, { surfaceOp: 'append' });
164
+ item.notified = true;
165
+ pushPendingJoinRequest(config.cwd, item);
166
+ logger.info(`[group-join] 已 append user/message 到会话 log(不唤醒, 模拟用户消息+停止) gid=${gid}`);
167
+ return true;
168
+ }
169
+ catch (err) {
170
+ logger.warn(`[group-join] append 失败: ${err instanceof Error ? err.message : String(err)}`);
171
+ }
172
+ }
173
+ else {
174
+ logger.warn(`[group-join] 会话无 append 能力, 兜底发 QQ 卡片 gid=${gid}`);
175
+ }
176
+ // 兜底: 无会话/append 不可用 → 发一条静态群消息提醒(不静默丢)
177
+ try {
178
+ const target = { scope: 'group', targetId: gid };
179
+ await sender.sendMarkdown(target, `${summary}\n\n主人可回复让我查看/审批(如: 查看入群申请)。`);
180
+ item.notified = true;
181
+ pushPendingJoinRequest(config.cwd, item);
182
+ }
183
+ catch (err) {
184
+ logger.warn(`[group-join] 群内提醒发送失败: ${err instanceof Error ? err.message : String(err)}`);
185
+ }
186
+ }
187
+ }
188
+ return true;
189
+ }
190
+ //# sourceMappingURL=group-join-request.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-join-request.js","sourceRoot":"","sources":["../../src/features/group-join-request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { HistoryStore } from '@tencent-connect/qqbot-nodejs';
2
+ /** 获取共享历史存储(单例) */
3
+ export declare function getHistoryStore(): HistoryStore;
4
+ /** 用 appId 前缀隔离群历史(单账号下等价于 groupOpenid,保留多账号扩展) */
5
+ export declare function historyGroupKey(appId: string, groupId: string): string;
6
+ /** 清空群历史(回复后调用,避免下次 @ 时重复组包) */
7
+ export declare function clearGroupHistory(appId: string, groupId: string): void;
8
+ //# sourceMappingURL=history-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-store.d.ts","sourceRoot":"","sources":["../../src/features/history-store.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAIlE,mBAAmB;AACnB,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,gCAAgC;AAChC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAEtE"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 群历史存储共享模块
3
+ *
4
+ * historyBuffer 中间件需要一个跨调用可访问的 HistoryStore 实例,
5
+ * 以便在回复完成后清空某群的历史缓存(避免下次 @ 时重复组包)。
6
+ * 对齐 openclaw-qqbot 的 features/history-store。
7
+ */
8
+ import { MemoryHistoryStore } from '@tencent-connect/qqbot-nodejs';
9
+ let _store = null;
10
+ /** 获取共享历史存储(单例) */
11
+ export function getHistoryStore() {
12
+ if (!_store)
13
+ _store = new MemoryHistoryStore();
14
+ return _store;
15
+ }
16
+ /** 用 appId 前缀隔离群历史(单账号下等价于 groupOpenid,保留多账号扩展) */
17
+ export function historyGroupKey(appId, groupId) {
18
+ return `${appId}:${groupId}`;
19
+ }
20
+ /** 清空群历史(回复后调用,避免下次 @ 时重复组包) */
21
+ export function clearGroupHistory(appId, groupId) {
22
+ _store?.clear?.(historyGroupKey(appId, groupId));
23
+ }
24
+ //# sourceMappingURL=history-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history-store.js","sourceRoot":"","sources":["../../src/features/history-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGnE,IAAI,MAAM,GAAwB,IAAI,CAAC;AAEvC,mBAAmB;AACnB,MAAM,UAAU,eAAe;IAC7B,IAAI,CAAC,MAAM;QAAE,MAAM,GAAG,IAAI,kBAAkB,EAAE,CAAC;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,OAAe;IAC5D,OAAO,GAAG,KAAK,IAAI,OAAO,EAAE,CAAC;AAC/B,CAAC;AAED,gCAAgC;AAChC,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,OAAe;IAC9D,MAAM,EAAE,KAAK,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AACnD,CAAC"}