foliko 1.1.38 → 1.1.39

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 (165) hide show
  1. package/.agent/agents/network-requester.md +44 -0
  2. package/.agent/agents/poster-designer.md +52 -0
  3. package/.agent/agents/ui-designer.md +1 -1
  4. package/.agent/data/default.json +23 -407
  5. package/.agent/data/email/processed-emails.json +1 -0
  6. package/.agent/data/plugins-state.json +103 -200
  7. package/.agent/data/web/web-config.json +5 -0
  8. package/.agent/data/weixin/images/file_1776188148383jpg +0 -0
  9. package/.agent/data/weixin/images/file_1776188458326.jpg +0 -0
  10. package/.agent/data/weixin/images/file_1776188689423.jpg +0 -0
  11. package/.agent/data/weixin/images/file_1776188813604.jpg +0 -0
  12. package/.agent/data/weixin/images/file_1776189097450.jpg +0 -0
  13. package/.agent/data/weixin/videos/file_1776188318431.mp4 +0 -0
  14. package/.agent/mcp_config.json +4 -17
  15. package/.agent/memory/user/mof6gk94-kneeuh.md +9 -0
  16. package/.agent/package.json +8 -0
  17. package/.agent/plugins/marknative/README.md +134 -0
  18. package/.agent/plugins/marknative/fonts/SegoeUI Emoji.ttf +0 -0
  19. package/.agent/plugins/marknative/fonts.zip +0 -0
  20. package/.agent/plugins/marknative/index.js +256 -0
  21. package/.agent/plugins/marknative/package.json +12 -0
  22. package/.agent/plugins/test-plugin.py +99 -0
  23. package/.agent/plugins.json +11 -5
  24. package/.agent/python-scripts/test_sample.py +24 -0
  25. package/.agent/sessions/cli_default.json +2890 -18
  26. package/.agent/sessions/default.json +82 -0
  27. package/.agent/sessions/qq_c2c_D960F12877541624D7B2C836110B3D0F.json +25 -0
  28. package/.agent/sessions/test-clean.json +26 -0
  29. package/.agent/sessions/test-compress.json +1462 -0
  30. package/.agent/sessions/test-session.json +13 -0
  31. package/.agent/sessions/weixin_test.json +22 -0
  32. package/.agent/sessions/weixin_test_user_123.json +11 -0
  33. package/.agent/skills/agent-browser/SKILL.md +311 -0
  34. package/.agent/skills/agent-browser/TEST_PLAN.md +200 -0
  35. package/.agent/skills/sysinfo/SKILL.md +38 -0
  36. package/.agent/skills/sysinfo/system-info.sh +130 -0
  37. package/.agent/skills/workflow/SKILL.md +324 -0
  38. package/.agent/test-agent.js +35 -0
  39. package/.agent/weixin.json +6 -0
  40. package/.agent/workflows/email-digest.json +50 -0
  41. package/.agent/workflows/file-backup.json +21 -0
  42. package/.agent/workflows/get-ip-notify.json +32 -0
  43. package/.agent/workflows/news-aggregator.json +93 -0
  44. package/.agent/workflows/news-dashboard-v2.json +94 -0
  45. package/.agent/workflows/notification-batch.json +32 -0
  46. package/.claude/settings.local.json +2 -1
  47. package/.env.example +56 -56
  48. package/README.md +441 -441
  49. package/cli/src/utils/debounce.js +6 -3
  50. package/docs/qq-bot.md +976 -0
  51. package/package.json +2 -1
  52. package/plugins/qq-plugin.js +939 -0
  53. package/skills/find-skills/AGENTS.md +162 -162
  54. package/skills/find-skills/SKILL.md +133 -133
  55. package/temp_img.md +1 -0
  56. package/website_v2/styles/animations.css +7 -7
  57. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +0 -26
  58. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +0 -97
  59. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +0 -101
  60. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +0 -31
  61. package/.agent/.shared/ui-ux-pro-max/data/products.csv +0 -97
  62. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +0 -24
  63. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +0 -45
  64. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
  65. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
  66. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
  67. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
  68. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
  69. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
  70. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
  71. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +0 -54
  72. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
  73. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
  74. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
  75. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +0 -50
  76. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +0 -59
  77. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +0 -58
  78. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +0 -101
  79. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
  80. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +0 -31
  81. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
  82. package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
  83. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +0 -258
  84. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +0 -1067
  85. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +0 -106
  86. package/.agent/ARCHITECTURE.md +0 -288
  87. package/.agent/data/ambient/goals.json +0 -1
  88. package/.agent/data/puppeteer-sessions/undefined.json +0 -6
  89. package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
  90. package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
  91. package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
  92. package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
  93. package/.agent/memory/feedback/mnygjgox-ualjip.md +0 -11
  94. package/.agent/memory/feedback/mnzugpej-esdwsr.md +0 -9
  95. package/.agent/memory/feedback/mo2quxke-saxs56.md +0 -9
  96. package/.agent/memory/feedback/mo9l6nw9-sm1ye2.md +0 -13
  97. package/.agent/memory/feedback/mo9uxkb3-bbxz3x.md +0 -9
  98. package/.agent/memory/feedback/mo9wpqy0-ftsez9.md +0 -13
  99. package/.agent/memory/feedback/mo9x2ps4-p7huqq.md +0 -11
  100. package/.agent/memory/feedback/mo9x2uay-z7ndjn.md +0 -9
  101. package/.agent/memory/feedback/mo9x2y7d-9wecyx.md +0 -9
  102. package/.agent/memory/feedback/mo9x39q3-nn7myt.md +0 -9
  103. package/.agent/memory/feedback/mo9xfir9-4g8a8j.md +0 -9
  104. package/.agent/memory/feedback/mo9xfui8-ujqrc6.md +0 -9
  105. package/.agent/memory/feedback/mocnx1wx-qtxxlh.md +0 -9
  106. package/.agent/memory/project/mnqx54u5-loqtoe.md +0 -9
  107. package/.agent/memory/project/mnqx84cv-mx6dmd.md +0 -9
  108. package/.agent/memory/project/mnsacuyr-hgtk5n.md +0 -20
  109. package/.agent/memory/project/mnu5hy2x-bjsg7u.md +0 -9
  110. package/.agent/memory/project/mny28ot4-8qe9au.md +0 -9
  111. package/.agent/memory/project/mnztftxj-af82rh.md +0 -9
  112. package/.agent/memory/project/mo0y04d0-bmaefl.md +0 -9
  113. package/.agent/memory/project/mo2iztxb-3c7v81.md +0 -9
  114. package/.agent/memory/project/mo2ssa5x-tpi1p3.md +0 -9
  115. package/.agent/memory/reference/mnre3cww-penbo1.md +0 -9
  116. package/.agent/memory/reference/mns9wn48-luerua.md +0 -14
  117. package/.agent/memory/reference/mns9yz5c-thc2s0.md +0 -16
  118. package/.agent/memory/reference/mnsfy4um-910f1o.md +0 -23
  119. package/.agent/memory/reference/mnsg37dp-lmfj18.md +0 -32
  120. package/.agent/memory/reference/mnsll60q-0j911u.md +0 -36
  121. package/.agent/memory/reference/mnsmlb5y-nej31u.md +0 -16
  122. package/.agent/memory/reference/mnssle72-yrot96.md +0 -9
  123. package/.agent/memory/reference/mnygj8nb-bjthmc.md +0 -20
  124. package/.agent/memory/reference/mnzfvs4m-ufyg9a.md +0 -12
  125. package/.agent/memory/user/mnsfuon6-l416q1.md +0 -21
  126. package/.agent/memory/user/mnsg9kut-95m7rf.md +0 -20
  127. package/.agent/memory/user/mnu2eo1v-yy6fhe.md +0 -9
  128. package/.agent/memory/user/mnu2etuo-8u8jk8.md +0 -9
  129. package/.agent/memory/user/mnx0rk6g-gsznjj.md +0 -9
  130. package/.agent/memory/user/mnyf1riz-4yo5yz.md +0 -9
  131. package/.agent/memory/user/mnzuh4cw-zvee8w.md +0 -13
  132. package/.agent/memory/user/mnzvewyj-jl67cq.md +0 -9
  133. package/.agent/memory/user/mnzwh9xo-43ys3f.md +0 -9
  134. package/.agent/memory/user/mo0ycvpn-eebsxc.md +0 -9
  135. package/.agent/memory/user/mo2k8c8n-132r2u.md +0 -9
  136. package/.agent/memory/user/mo6y2dei-5cf537.md +0 -13
  137. package/.agent/memory/user/mo9xsdo6-8vylww.md +0 -13
  138. package/.agent/plugins/puppeteer-plugin/README.md +0 -147
  139. package/.agent/plugins/puppeteer-plugin/index.js +0 -1422
  140. package/.agent/plugins/puppeteer-plugin/package.json +0 -9
  141. package/.agent/rules/GEMINI.md +0 -273
  142. package/.agent/rules/allow-rule.md +0 -77
  143. package/.agent/rules/log-rule.md +0 -83
  144. package/.agent/rules/security-rule.md +0 -93
  145. package/.agent/scripts/auto_preview.py +0 -148
  146. package/.agent/scripts/checklist.py +0 -217
  147. package/.agent/scripts/session_manager.py +0 -120
  148. package/.agent/scripts/verify_all.py +0 -327
  149. package/.agent/skills/doc.md +0 -177
  150. package/.agent/skills/fk-poster/SKILL.md +0 -1129
  151. package/.agent/skills/fkbuilder/SKILL.md +0 -730
  152. package/.agent/skills/mmx-cli/SKILL.md +0 -431
  153. package/.agent/workflows/brainstorm.md +0 -113
  154. package/.agent/workflows/create.md +0 -59
  155. package/.agent/workflows/debug.md +0 -103
  156. package/.agent/workflows/deploy.md +0 -176
  157. package/.agent/workflows/enhance.md +0 -63
  158. package/.agent/workflows/orchestrate.md +0 -237
  159. package/.agent/workflows/plan.md +0 -89
  160. package/.agent/workflows/preview.md +0 -81
  161. package/.agent/workflows/simple-test.md +0 -42
  162. package/.agent/workflows/status.md +0 -86
  163. package/.agent/workflows/structured-orchestrate.md +0 -180
  164. package/.agent/workflows/test.md +0 -144
  165. package/.agent/workflows/ui-ux-pro-max.md +0 -296
package/docs/qq-bot.md ADDED
@@ -0,0 +1,976 @@
1
+ # QQ Bot SDK
2
+
3
+ TypeScript 编写的 QQ 机器人开发 SDK,基于 QQ 开放平台 API v2,支持 WebSocket 长连接接收消息和 HTTP API 发送消息。
4
+
5
+ ## 功能特性
6
+
7
+ - **WebSocket 网关** - 支持 WebSocket 长连接实时接收消息事件
8
+ - **消息发送** - 支持 C2C 私聊、群聊、频道消息发送
9
+ - **富媒体消息** - 支持图片、语音、视频、文件(URL 方式和本地上传)
10
+ - **本地文件上传** - 支持本地上传图片、语音、视频、文件(分片上传)
11
+ - **流式消息** - 支持 C2C 私聊流式消息(打字机效果)
12
+ - **按钮交互** - 支持接收和回应按钮交互事件(需平台审核)
13
+ - **TypeScript 支持** - 完整的类型定义,开箱即用
14
+ - **双模块支持** - 支持 ESM 和 CommonJS
15
+
16
+ ## 安装
17
+
18
+ ```bash
19
+ npm install @chnak/qq-bot
20
+ ```
21
+
22
+ 或使用 yarn:
23
+
24
+ ```bash
25
+ yarn add @chnak/qq-bot
26
+ ```
27
+
28
+ 或使用 pnpm:
29
+
30
+ ```bash
31
+ pnpm add @chnak/qq-bot
32
+ ```
33
+
34
+ ## 模块引入方式
35
+
36
+ ### ESM (ES Modules)
37
+
38
+ ```typescript
39
+ import { createQQBotClient } from '@chnak/qq-bot';
40
+ ```
41
+
42
+ ### CommonJS
43
+
44
+ ```javascript
45
+ const { createQQBotClient } = require('@chnak/qq-bot');
46
+ ```
47
+
48
+ ## 快速开始
49
+
50
+ ### 1. 初始化客户端
51
+
52
+ ```typescript
53
+ import { createQQBotClient } from '@chnak/qq-bot';
54
+
55
+ const client = createQQBotClient({
56
+ appId: 'YOUR_APP_ID',
57
+ clientSecret: 'YOUR_CLIENT_SECRET',
58
+ log: {
59
+ info: (msg) => console.log('[INFO]', msg),
60
+ error: (msg) => console.error('[ERROR]', msg),
61
+ warn: (msg) => console.warn('[WARN]', msg),
62
+ },
63
+ });
64
+ ```
65
+
66
+ ### 2. 订阅消息事件
67
+
68
+ ```typescript
69
+ // 监听私聊消息
70
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
71
+ console.log('收到私聊消息:', event.content);
72
+ console.log('发送者:', event.author.user_openid);
73
+
74
+ // 回复消息
75
+ await client.sendC2CMessage({
76
+ openid: event.author.user_openid,
77
+ content: '收到消息: ' + event.content,
78
+ msgId: event.id,
79
+ });
80
+ });
81
+
82
+ // 监听群聊@消息
83
+ client.on('GROUP_AT_MESSAGE_CREATE', async (event) => {
84
+ console.log('收到群消息:', event.content);
85
+ console.log('群ID:', event.group_openid);
86
+ console.log('发送者:', event.author.username);
87
+ });
88
+
89
+ // 监听所有事件(调试用)
90
+ client.onAny((event) => {
91
+ console.log(`[事件] ${event.type}`);
92
+ });
93
+ ```
94
+
95
+ ### 3. 启动连接
96
+
97
+ ```typescript
98
+ async function main() {
99
+ await client.connect();
100
+ console.log('QQ Bot 已连接!');
101
+
102
+ // 保持运行
103
+ setTimeout(() => {
104
+ client.disconnect();
105
+ console.log('Bot 已断开连接');
106
+ process.exit(0);
107
+ }, 60000);
108
+ }
109
+
110
+ main().catch(console.error);
111
+ ```
112
+
113
+ ## 完整示例
114
+
115
+ ### 自动回复机器人
116
+
117
+ ```typescript
118
+ import { createQQBotClient } from '@chnak/qq-bot';
119
+
120
+ const client = createQQBotClient({
121
+ appId: process.env.APP_ID!,
122
+ clientSecret: process.env.CLIENT_SECRET!,
123
+ });
124
+
125
+ // 监听私聊消息,收到后自动回复
126
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
127
+ const openid = event.author.user_openid || event.author.id;
128
+
129
+ try {
130
+ // 回复文本消息
131
+ await client.sendC2CMessage({
132
+ openid,
133
+ content: `收到消息: ${event.content}`,
134
+ msgId: event.id,
135
+ });
136
+
137
+ // 发送图片(使用 URL)
138
+ await client.sendC2CImageMessage({
139
+ openid,
140
+ imageUrl: 'https://example.com/image.png',
141
+ msgId: event.id,
142
+ });
143
+
144
+ } catch (err) {
145
+ console.error('回复失败:', err);
146
+ }
147
+ });
148
+
149
+ // 启动
150
+ await client.connect();
151
+ console.log('Bot 已启动,按 Ctrl+C 退出');
152
+
153
+ // 保持运行
154
+ process.stdin.resume();
155
+ ```
156
+
157
+ ### 发送本地文件
158
+
159
+ ```typescript
160
+ import { createQQBotClient } from '@chnak/qq-bot';
161
+
162
+ const client = createQQBotClient({
163
+ appId: process.env.APP_ID!,
164
+ clientSecret: process.env.CLIENT_SECRET!,
165
+ });
166
+
167
+ async function sendFiles() {
168
+ // 发送本地图片
169
+ await client.sendC2CLocalImageMessage({
170
+ openid: 'USER_OPENID',
171
+ filePath: '/path/to/image.png',
172
+ content: '这是一张图片',
173
+ });
174
+
175
+ // 发送本地视频
176
+ await client.sendC2CLocalVideoMessage({
177
+ openid: 'USER_OPENID',
178
+ filePath: '/path/to/video.mp4',
179
+ content: '这是一段视频',
180
+ });
181
+
182
+ // 发送本地文件
183
+ await client.sendC2CLocalFileMessage({
184
+ openid: 'USER_OPENID',
185
+ filePath: '/path/to/document.pdf',
186
+ content: '这是一份文档',
187
+ });
188
+
189
+ // 发送本地语音
190
+ await client.sendC2CLocalVoiceMessage({
191
+ openid: 'USER_OPENID',
192
+ filePath: '/path/to/voice.silk',
193
+ });
194
+ }
195
+ ```
196
+
197
+ ### 流式消息(打字机效果)
198
+
199
+ 流式消息会实时更新内容,用户可以看到内容逐渐增加,支持打字机效果。
200
+
201
+ **注意**:必须从实际收到的消息事件中获取 `msgId` 和 `eventId`。
202
+
203
+ ```typescript
204
+ import { createQQBotClient, StreamSession } from '@chnak/qq-bot';
205
+
206
+ const client = createQQBotClient({
207
+ appId: process.env.APP_ID!,
208
+ clientSecret: process.env.CLIENT_SECRET!,
209
+ });
210
+
211
+ // 监听私聊消息,使用流式消息回复
212
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
213
+ const openid = event.author.user_openid;
214
+
215
+ // 创建流式会话
216
+ const stream = new StreamSession(
217
+ client, // 直接传入 API 客户端
218
+ openid,
219
+ event.id, // 使用用户消息的 ID 作为 msgId
220
+ `stream_${Date.now()}`, // 新的 eventId
221
+ {
222
+ chunk: (content) => {
223
+ // 每次 write 后触发,可用于调试
224
+ process.stdout.write(content);
225
+ },
226
+ done: () => {
227
+ console.log('\n流式消息发送完成');
228
+ },
229
+ error: (err) => {
230
+ console.error('流式消息错误:', err);
231
+ },
232
+ }
233
+ );
234
+
235
+ // 分段发送内容(累积模式,每次 write 会累积内容后一起发送)
236
+ const chunks = ['第一段:', '这是一条流式消息。\n', '第二段:', '内容会逐步显示!\n'];
237
+ for (const chunk of chunks) {
238
+ await stream.write(chunk);
239
+ // 适当延迟,实现打字机效果
240
+ await new Promise(resolve => setTimeout(resolve, 300));
241
+ }
242
+
243
+ // 结束流式消息
244
+ await stream.done();
245
+ });
246
+ ```
247
+
248
+ ### Markdown 消息
249
+
250
+ 启用 Markdown 支持后,发送的消息会渲染为 Markdown 格式。
251
+
252
+ ```typescript
253
+ import { createQQBotClient } from '@chnak/qq-bot';
254
+
255
+ const client = createQQBotClient({
256
+ appId: process.env.APP_ID!,
257
+ clientSecret: process.env.CLIENT_SECRET!,
258
+ markdownSupport: true, // 启用 markdown 支持
259
+ });
260
+
261
+ // 监听私聊消息,回复 Markdown 消息
262
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
263
+ const markdownContent = `# QQ Bot SDK
264
+
265
+ 这是一个 **Markdown** 消息测试!
266
+
267
+ ## 支持的格式
268
+
269
+ - **粗体**
270
+ - *斜体*
271
+ - ~~删除线~~
272
+ - \`行内代码\`
273
+
274
+ ## 列表
275
+
276
+ 1. 第一项
277
+ 2. 第二项
278
+ 3. 第三项
279
+
280
+ ## 链接
281
+
282
+ [QQ 开放平台](https://bot.q.qq.com)
283
+
284
+ ## 引用
285
+
286
+ > 这是一段引用文本
287
+
288
+ ---
289
+ *以上内容由 QQ Bot SDK 自动发送*`;
290
+
291
+ await client.sendC2CMessage({
292
+ openid: event.author.user_openid,
293
+ content: markdownContent,
294
+ msgId: event.id,
295
+ });
296
+ });
297
+ ```
298
+
299
+ ### 按钮交互处理
300
+
301
+ 发送带按钮的消息,用户点击后会触发 `INTERACTION_CREATE` 事件。
302
+
303
+ **注意**:
304
+ - 消息内嵌按钮需要在 QQ 开放平台审核通过后才能使用
305
+ - 可使用 `id` 字段指定已审核的模板,或使用 `content` 字段自定义按钮(同样需要审核)
306
+
307
+ ```typescript
308
+ import { createQQBotClient } from '@chnak/qq-bot';
309
+
310
+ const client = createQQBotClient({
311
+ appId: process.env.APP_ID!,
312
+ clientSecret: process.env.CLIENT_SECRET!,
313
+ });
314
+
315
+ // 发送带按钮的消息
316
+ async function sendButtonMessage(openid) {
317
+ // 使用模板 ID(需平台审核)
318
+ // await client.sendC2CMessageWithInlineKeyboard(openid, '请选择:', { id: 'TEMPLATE_ID' });
319
+
320
+ // 使用自定义按钮(需平台审核)
321
+ await client.sendC2CMessageWithInlineKeyboard(openid, '请点击下方按钮:', {
322
+ content: {
323
+ rows: [
324
+ {
325
+ buttons: [
326
+ {
327
+ id: 'btn_hello',
328
+ render_data: { label: '你好', visited_label: '已点击', style: 1 },
329
+ action: {
330
+ type: 1, // Callback 类型
331
+ data: 'hello_data',
332
+ permission: { type: 2 }, // 所有人可操作
333
+ click_limit: 1, // 每人只能点一次
334
+ },
335
+ group_id: 'test_group',
336
+ },
337
+ {
338
+ id: 'btn_bye',
339
+ render_data: { label: '再见', visited_label: '已点击', style: 0 },
340
+ action: {
341
+ type: 1,
342
+ data: 'bye_data',
343
+ permission: { type: 2 },
344
+ click_limit: 1,
345
+ },
346
+ group_id: 'test_group',
347
+ },
348
+ ],
349
+ },
350
+ ],
351
+ },
352
+ });
353
+ }
354
+
355
+ // 监听按钮交互事件
356
+ client.on('INTERACTION_CREATE', async (event) => {
357
+ const { button_data, button_id } = event.raw.data.resolved;
358
+
359
+ console.log('收到按钮交互:', { button_id, button_data });
360
+
361
+ // 确认交互
362
+ await client.acknowledgeInteraction(event.id, 0, {
363
+ msg: `收到按钮点击: ${button_id}`,
364
+ });
365
+ });
366
+
367
+ // 监听私聊消息,发送按钮消息
368
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
369
+ if (event.content === '按钮') {
370
+ await sendButtonMessage(event.author.user_openid);
371
+ }
372
+ });
373
+
374
+ await client.connect();
375
+ ```
376
+
377
+ ## API 参考
378
+
379
+ ### 客户端配置
380
+
381
+ ```typescript
382
+ interface QQBotClientConfig {
383
+ /** 应用 ID */
384
+ appId: string;
385
+ /** 应用密钥 */
386
+ clientSecret?: string;
387
+ /** 直接传入 access token(如果已获取) */
388
+ accessToken?: string;
389
+ /** 是否启用 markdown 支持(默认 false) */
390
+ markdownSupport?: boolean;
391
+ /** 重试配置 */
392
+ retry?: RetryOptions;
393
+ }
394
+
395
+ interface RetryOptions {
396
+ /** 最大重试次数(默认 3) */
397
+ maxRetries?: number;
398
+ /** 初始重试延迟(ms,默认 1000) */
399
+ retryDelayMs?: number;
400
+ /** 是否启用重试(默认 true) */
401
+ enabled?: boolean;
402
+ }
403
+
404
+ interface Logger {
405
+ info: (msg: string) => void;
406
+ error: (msg: string) => void;
407
+ warn?: (msg: string) => void;
408
+ debug?: (msg: string) => void;
409
+ }
410
+ ```
411
+
412
+ ### 消息发送 API
413
+
414
+ #### 文本消息
415
+
416
+ ```typescript
417
+ // C2C 私聊消息
418
+ await client.sendC2CMessage({
419
+ openid: 'USER_OPENID',
420
+ content: '你好!',
421
+ msgId: 'ORIGINAL_MSG_ID', // 可选,用于回复
422
+ });
423
+
424
+ // 群聊消息
425
+ await client.sendGroupMessage({
426
+ groupOpenid: 'GROUP_OPENID',
427
+ content: '大家好!',
428
+ msgId: 'ORIGINAL_MSG_ID',
429
+ });
430
+
431
+ // 频道消息
432
+ await client.sendChannelMessage({
433
+ channelId: 'CHANNEL_ID',
434
+ guildId: 'GUILD_ID',
435
+ content: '频道消息',
436
+ });
437
+ ```
438
+
439
+ #### 正在输入状态
440
+
441
+ 发送 "正在输入" 状态提示,仅 C2C 私聊有效。可用于长消息处理前告知用户。
442
+
443
+ ```typescript
444
+ // 发送正在输入状态(默认 60 秒)
445
+ await client.sendC2CInputNotify('USER_OPENID');
446
+
447
+ // 指定保持秒数
448
+ await client.sendC2CInputNotify('USER_OPENID', 'MSG_ID', 30);
449
+ ```
450
+
451
+ #### Markdown 消息
452
+
453
+ 需要在初始化客户端时设置 `markdownSupport: true`。
454
+
455
+ ```typescript
456
+ const client = createQQBotClient({
457
+ appId: 'YOUR_APP_ID',
458
+ clientSecret: 'YOUR_CLIENT_SECRET',
459
+ markdownSupport: true, // 启用 markdown 支持
460
+ });
461
+
462
+ // 发送 Markdown 格式消息
463
+ await client.sendC2CMessage({
464
+ openid: 'USER_OPENID',
465
+ content: `# 标题
466
+
467
+ 这是一个 **粗体** 和 *斜体* 的消息。
468
+
469
+ ## 列表
470
+ 1. 第一项
471
+ 2. 第二项
472
+
473
+ > 引用文本
474
+
475
+ [链接](https://example.com)`,
476
+ msgId: 'MSG_ID',
477
+ });
478
+ ```
479
+
480
+ #### 富媒体消息(URL 方式)
481
+
482
+ ```typescript
483
+ // 发送图片
484
+ await client.sendC2CImageMessage({
485
+ openid: 'USER_OPENID',
486
+ imageUrl: 'https://example.com/image.png',
487
+ msgId: 'MSG_ID',
488
+ content: '图片描述',
489
+ });
490
+
491
+ // 发送语音
492
+ await client.sendC2CVoiceMessage({
493
+ openid: 'USER_OPENID',
494
+ voiceUrl: 'https://example.com/voice.silk',
495
+ });
496
+
497
+ // 发送视频
498
+ await client.sendC2CVideoMessage({
499
+ openid: 'USER_OPENID',
500
+ videoUrl: 'https://example.com/video.mp4',
501
+ });
502
+ ```
503
+
504
+ #### 本地文件上传
505
+
506
+ ```typescript
507
+ // 发送本地图片
508
+ await client.sendC2CLocalImageMessage({
509
+ openid: 'USER_OPENID',
510
+ filePath: '/path/to/image.png',
511
+ msgId: 'MSG_ID',
512
+ content: '图片描述',
513
+ });
514
+
515
+ // 发送本地语音
516
+ await client.sendC2CLocalVoiceMessage({
517
+ openid: 'USER_OPENID',
518
+ filePath: '/path/to/voice.silk',
519
+ });
520
+
521
+ // 发送本地视频
522
+ await client.sendC2CLocalVideoMessage({
523
+ openid: 'USER_OPENID',
524
+ filePath: '/path/to/video.mp4',
525
+ content: '视频描述',
526
+ });
527
+
528
+ // 发送本地文件
529
+ await client.sendC2CLocalFileMessage({
530
+ openid: 'USER_OPENID',
531
+ filePath: '/path/to/file.pdf',
532
+ content: '文件描述',
533
+ });
534
+ ```
535
+
536
+ #### 群聊富媒体消息
537
+
538
+ ```typescript
539
+ // 群聊图片
540
+ await client.sendGroupLocalImageMessage({
541
+ groupOpenid: 'GROUP_OPENID',
542
+ filePath: '/path/to/image.png',
543
+ });
544
+
545
+ // 群聊语音
546
+ await client.sendGroupLocalVoiceMessage({
547
+ groupOpenid: 'GROUP_OPENID',
548
+ filePath: '/path/to/voice.silk',
549
+ });
550
+
551
+ // 群聊视频
552
+ await client.sendGroupLocalVideoMessage({
553
+ groupOpenid: 'GROUP_OPENID',
554
+ filePath: '/path/to/video.mp4',
555
+ });
556
+
557
+ // 群聊文件
558
+ await client.sendGroupLocalFileMessage({
559
+ groupOpenid: 'GROUP_OPENID',
560
+ filePath: '/path/to/file.pdf',
561
+ });
562
+ ```
563
+
564
+ ### 事件类型
565
+
566
+ ```typescript
567
+ type MessageEventType =
568
+ | 'C2C_MESSAGE_CREATE' // 私聊消息
569
+ | 'GROUP_AT_MESSAGE_CREATE' // 群聊@消息
570
+ | 'AT_MESSAGE_CREATE' // 频道@消息
571
+ | 'DIRECT_MESSAGE_CREATE' // 频道私信
572
+ | 'INTERACTION_CREATE'; // 按钮交互
573
+ ```
574
+
575
+ ### 流式消息 API
576
+
577
+ ```typescript
578
+ // 直接使用 StreamSession(需要传入 API 客户端)
579
+ import { QQBotAPIClient, StreamSession } from '@chnak/qq-bot';
580
+
581
+ const api = new QQBotAPIClient({ appId, clientSecret });
582
+
583
+ const stream = new StreamSession(
584
+ api,
585
+ openid,
586
+ msgId, // 必须是用户发送的真实消息 ID
587
+ eventId, // 事件 ID
588
+ {
589
+ chunk: (content) => {
590
+ // 每次 write 后触发
591
+ },
592
+ done: () => {
593
+ // 流式消息发送完成
594
+ },
595
+ error: (err) => {
596
+ // 发送错误
597
+ },
598
+ }
599
+ );
600
+
601
+ // 发送内容(累积模式)
602
+ await stream.write('第一段:');
603
+ await stream.write('第二段:');
604
+ await stream.write('内容累积后一起发送');
605
+
606
+ // 结束流式消息
607
+ await stream.done();
608
+ ```
609
+
610
+ ### 事件消息结构
611
+
612
+ ```typescript
613
+ interface MessageEvent {
614
+ type: MessageEventType; // 事件类型
615
+ id: string; // 消息 ID
616
+ content: string; // 消息内容
617
+ author: {
618
+ id: string; // 用户 ID
619
+ username?: string; // 用户名
620
+ union_openid?: string; // 统一 ID
621
+ user_openid?: string; // 用户 openid
622
+ member_openid?: string; // 成员 openid(群聊)
623
+ };
624
+ group_openid?: string; // 群 ID(仅群聊)
625
+ channelId?: string; // 频道 ID(仅频道)
626
+ guildId?: string; // Guild ID(仅频道)
627
+ attachments?: MessageAttachment[]; // 附件
628
+ raw: C2CMessageEvent | GroupMessageEvent | GuildMessageEvent | InteractionEvent; // 原始数据
629
+ }
630
+ ```
631
+
632
+ ## 直接使用 API 客户端
633
+
634
+ 如果你只需要调用 API 发送消息,不需要 WebSocket 连接,可以直接使用 `QQBotAPIClient`:
635
+
636
+ ### ESM
637
+
638
+ ```typescript
639
+ import { QQBotAPIClient } from '@chnak/qq-bot';
640
+
641
+ const api = new QQBotAPIClient({
642
+ appId: 'YOUR_APP_ID',
643
+ clientSecret: 'YOUR_CLIENT_SECRET',
644
+ markdownSupport: true, // 可选,启用 Markdown 支持
645
+ retry: {
646
+ maxRetries: 3, // 最大重试次数
647
+ retryDelayMs: 1000, // 初始重试延迟(指数退避)
648
+ enabled: true, // 是否启用重试
649
+ },
650
+ });
651
+
652
+ // 发送文本消息
653
+ const result = await api.sendC2CMessage('USER_OPENID', '你好!');
654
+
655
+ // 发送 Markdown 消息(需要启用 markdownSupport)
656
+ const mdResult = await api.sendC2CMessage('USER_OPENID', '# 标题\n\n**粗体**内容');
657
+
658
+ // 发送本地图片
659
+ const imageResult = await api.sendC2CLocalImageMessage(
660
+ 'USER_OPENID',
661
+ '/path/to/image.png',
662
+ undefined,
663
+ '图片描述'
664
+ );
665
+
666
+ // 获取 access token
667
+ const token = await api.getToken();
668
+
669
+ // 获取网关 URL
670
+ const gatewayUrl = await api.getGatewayUrl();
671
+ ```
672
+
673
+ ### CommonJS
674
+
675
+ ```javascript
676
+ const { QQBotAPIClient } = require('@chnak/qq-bot');
677
+
678
+ const api = new QQBotAPIClient({
679
+ appId: 'YOUR_APP_ID',
680
+ clientSecret: 'YOUR_CLIENT_SECRET',
681
+ markdownSupport: true,
682
+ retry: {
683
+ maxRetries: 3,
684
+ retryDelayMs: 1000,
685
+ enabled: true,
686
+ },
687
+ });
688
+
689
+ async function main() {
690
+ // 发送文本消息
691
+ const result = await api.sendC2CMessage('USER_OPENID', '你好!');
692
+
693
+ // 发送本地图片
694
+ const imageResult = await api.sendC2CLocalImageMessage(
695
+ 'USER_OPENID',
696
+ '/path/to/image.png',
697
+ undefined,
698
+ '图片描述'
699
+ );
700
+ }
701
+
702
+ main().catch(console.error);
703
+ ```
704
+
705
+ ## 文件大小限制
706
+
707
+ | 类型 | 限制 |
708
+ |------|------|
709
+ | 图片 (IMAGE) | 30 MB |
710
+ | 视频 (VIDEO) | 100 MB |
711
+ | 语音 (VOICE) | 20 MB |
712
+ | 文件 (FILE) | 100 MB |
713
+
714
+ ## 注意事项
715
+
716
+ ### 关于 openid
717
+
718
+ - QQ 开放平台使用 `openid` 作为用户唯一标识
719
+ - `openid` 是基于 `appId + userId` 加密生成的,无法从 QQ 号直接获取
720
+ - **必须让用户先给机器人发消息**,才能从消息事件中获取用户的 openid
721
+
722
+ ### 流式消息
723
+
724
+ - **必须使用真实的消息 ID** - `msgId` 和 `eventId` 必须来自用户实际发送的消息,不能使用模拟值
725
+ - **累积模式** - 每次 `write()` 会累积内容后一起发送到 QQ 服务器
726
+ - **前缀不可修改** - QQ 要求后续发送的内容必须保持之前内容的前缀不变
727
+ - **网络延迟** - 流式消息的实时性取决于网络状况,如果延迟明显可适当增加分段间隔
728
+
729
+ ### 本地文件上传流程
730
+
731
+ 本地文件上传采用分片上传机制:
732
+
733
+ 1. 计算文件哈希(md5、sha1、md5_10m)
734
+ 2. 调用 `upload_prepare` 接口获取上传预签名 URL
735
+ 3. 并行上传所有分片到 COS
736
+ 4. 调用 `upload_part_finish` 通知每个分片完成
737
+ 5. 调用 `complete_upload` 完成上传
738
+ 6. 使用返回的 `file_info` 发送消息
739
+
740
+ ### 按钮交互
741
+
742
+ - 消息内嵌按钮需要在 QQ 开放平台审核通过后才能使用
743
+ - 按钮支持两种类型:
744
+ - **模板按钮**:使用 `id` 字段指定已审核的模板 ID
745
+ - **自定义按钮**:使用 `content` 字段自定义按钮布局(也需要审核)
746
+
747
+ ### 重试机制
748
+
749
+ SDK 默认启用重试机制,所有 API 请求在失败时会自动重试:
750
+
751
+ - **网络错误**:自动重试(包括 `fetch failed`、`timeout` 等)
752
+ - **服务器错误**:对 502、503、504 等错误自动重试
753
+ - **指数退避**:每次重试延迟时间翻倍(1s → 2s → 4s...)
754
+ - **可配置**:可通过 `retry` 选项自定义重试次数和延迟
755
+
756
+ ```typescript
757
+ const client = createQQBotClient({
758
+ appId: 'YOUR_APP_ID',
759
+ clientSecret: 'YOUR_CLIENT_SECRET',
760
+ retry: {
761
+ maxRetries: 3, // 最大重试次数(默认 3)
762
+ retryDelayMs: 1000, // 初始重试延迟(默认 1000ms)
763
+ enabled: true, // 是否启用重试(默认 true)
764
+ },
765
+ });
766
+ ```
767
+
768
+ ## 示例
769
+
770
+ ### 完整机器人示例
771
+
772
+ ```typescript
773
+ import { createQQBotClient } from '@chnak/qq-bot';
774
+
775
+ const client = createQQBotClient({
776
+ appId: process.env.APP_ID!,
777
+ clientSecret: process.env.CLIENT_SECRET!,
778
+ });
779
+
780
+ // 监听私聊消息
781
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
782
+ const openid = event.author.user_openid;
783
+ const content = event.content.trim();
784
+
785
+ // 命令处理
786
+ if (content === '你好') {
787
+ await client.sendC2CMessage({ openid, content: '你好!有什么可以帮助你的吗?' });
788
+ } else if (content === '帮助') {
789
+ await client.sendC2CMessage({
790
+ openid,
791
+ content: '可用命令:\n1. 你好 - 打招呼\n2. 帮助 - 显示帮助信息',
792
+ });
793
+ }
794
+ });
795
+
796
+ // 监听群聊@消息
797
+ client.on('GROUP_AT_MESSAGE_CREATE', async (event) => {
798
+ const content = event.content.trim();
799
+
800
+ if (content.startsWith('机器人')) {
801
+ await client.sendGroupMessage({
802
+ groupOpenid: event.group_openid!,
803
+ content: '收到!',
804
+ });
805
+ }
806
+ });
807
+
808
+ await client.connect();
809
+ console.log('Bot 已启动');
810
+ process.stdin.resume();
811
+ ```
812
+
813
+ ### 消息回复示例
814
+
815
+ ```typescript
816
+ // 回复用户消息
817
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
818
+ const openid = event.author.user_openid;
819
+
820
+ // 回复文本
821
+ await client.sendC2CMessage({
822
+ openid,
823
+ content: `你发送了: ${event.content}`,
824
+ msgId: event.id,
825
+ });
826
+ });
827
+ ```
828
+
829
+ ### 图片和文件示例
830
+
831
+ ```typescript
832
+ // 发送本地图片
833
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
834
+ const openid = event.author.user_openid;
835
+
836
+ if (event.content === '图片') {
837
+ await client.sendC2CLocalImageMessage({
838
+ openid,
839
+ filePath: './test.png',
840
+ content: '这是一张测试图片',
841
+ });
842
+ }
843
+ });
844
+
845
+ // 发送本地视频
846
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
847
+ const openid = event.author.user_openid;
848
+
849
+ if (event.content === '视频') {
850
+ await client.sendC2CLocalVideoMessage({
851
+ openid,
852
+ filePath: './video.mp4',
853
+ });
854
+ }
855
+ });
856
+
857
+ // 发送本地文件
858
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
859
+ const openid = event.author.user_openid;
860
+
861
+ if (event.content === '文件') {
862
+ await client.sendC2CLocalFileMessage({
863
+ openid,
864
+ filePath: './document.pdf',
865
+ content: '文件已发送',
866
+ });
867
+ }
868
+ });
869
+ ```
870
+
871
+ ### 群聊消息示例
872
+
873
+ ```typescript
874
+ // 发送群聊文本
875
+ await client.sendGroupMessage({
876
+ groupOpenid: 'GROUP_OPENID',
877
+ content: '大家好!',
878
+ });
879
+
880
+ // 发送群聊图片
881
+ await client.sendGroupLocalImageMessage({
882
+ groupOpenid: 'GROUP_OPENID',
883
+ filePath: './group-photo.png',
884
+ content: '群聊图片',
885
+ });
886
+ ```
887
+
888
+ ### 流式消息示例
889
+
890
+ ```typescript
891
+ client.on('C2C_MESSAGE_CREATE', async (event) => {
892
+ const openid = event.author.user_openid;
893
+
894
+ if (event.content === '流式') {
895
+ const stream = await client.createStreamSession({
896
+ openid,
897
+ msgId: event.id,
898
+ });
899
+
900
+ const messages = ['第', '一', '条', '流', '式', '消', '息'];
901
+
902
+ for (const msg of messages) {
903
+ await stream.write(msg);
904
+ await new Promise(r => setTimeout(r, 200));
905
+ }
906
+
907
+ await stream.done();
908
+ }
909
+ });
910
+ ```
911
+
912
+ ### 按钮交互示例
913
+
914
+ ```typescript
915
+ client.on('INTERACTION_CREATE', async (event) => {
916
+ const { button_data, button_id } = event.raw.data.resolved;
917
+
918
+ console.log(`按钮交互: ${button_id} - ${button_data}`);
919
+
920
+ // 确认交互
921
+ await client.acknowledgeInteraction(event.id, 0, {
922
+ msg: '已收到点击',
923
+ });
924
+ });
925
+ ```
926
+
927
+ ## 开发
928
+
929
+ ```bash
930
+ # 安装依赖
931
+ npm install
932
+
933
+ # 类型检查
934
+ npm run typecheck
935
+
936
+ # 构建
937
+ npm run build
938
+
939
+ # 监听模式构建
940
+ npm run dev
941
+ ```
942
+
943
+ ## 目录结构
944
+
945
+ ```
946
+ @chnak/qq-bot/
947
+ ├── src/
948
+ │ ├── index.ts # 主入口
949
+ │ ├── api/
950
+ │ │ ├── client.ts # API 客户端
951
+ │ │ └── auth.ts # 鉴权模块
952
+ │ ├── gateway/
953
+ │ │ ├── connection.ts # WebSocket 连接
954
+ │ │ └── dispatcher.ts # 事件分发器
955
+ │ ├── types/
956
+ │ │ └── index.ts # 类型定义
957
+ │ ├── utils/
958
+ │ │ ├── logger.ts # 日志工具
959
+ │ │ └── file-hash.ts # 文件哈希工具
960
+ │ └── stream-session.ts # 流式消息会话
961
+ ├── examples/ # 示例文件
962
+ │ ├── 01-basic-bot.ts # 基础机器人
963
+ │ ├── 02-command-handler.ts # 命令处理器
964
+ │ ├── 03-send-media.ts # 发送媒体文件
965
+ │ ├── 04-stream-message.ts # 流式消息
966
+ │ ├── 05-group-bot.ts # 群聊机器人
967
+ │ ├── 06-markdown-bot.ts # Markdown 消息
968
+ │ └── 07-api-only.ts # 仅 API 模式
969
+ ├── dist/ # 编译输出
970
+ ├── package.json
971
+ └── tsconfig.json
972
+ ```
973
+
974
+ ## License
975
+
976
+ MIT