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.
- package/.agent/agents/network-requester.md +44 -0
- package/.agent/agents/poster-designer.md +52 -0
- package/.agent/agents/ui-designer.md +1 -1
- package/.agent/data/default.json +23 -407
- package/.agent/data/email/processed-emails.json +1 -0
- package/.agent/data/plugins-state.json +103 -200
- package/.agent/data/web/web-config.json +5 -0
- package/.agent/data/weixin/images/file_1776188148383jpg +0 -0
- package/.agent/data/weixin/images/file_1776188458326.jpg +0 -0
- package/.agent/data/weixin/images/file_1776188689423.jpg +0 -0
- package/.agent/data/weixin/images/file_1776188813604.jpg +0 -0
- package/.agent/data/weixin/images/file_1776189097450.jpg +0 -0
- package/.agent/data/weixin/videos/file_1776188318431.mp4 +0 -0
- package/.agent/mcp_config.json +4 -17
- package/.agent/memory/user/mof6gk94-kneeuh.md +9 -0
- package/.agent/package.json +8 -0
- package/.agent/plugins/marknative/README.md +134 -0
- package/.agent/plugins/marknative/fonts/SegoeUI Emoji.ttf +0 -0
- package/.agent/plugins/marknative/fonts.zip +0 -0
- package/.agent/plugins/marknative/index.js +256 -0
- package/.agent/plugins/marknative/package.json +12 -0
- package/.agent/plugins/test-plugin.py +99 -0
- package/.agent/plugins.json +11 -5
- package/.agent/python-scripts/test_sample.py +24 -0
- package/.agent/sessions/cli_default.json +2890 -18
- package/.agent/sessions/default.json +82 -0
- package/.agent/sessions/qq_c2c_D960F12877541624D7B2C836110B3D0F.json +25 -0
- package/.agent/sessions/test-clean.json +26 -0
- package/.agent/sessions/test-compress.json +1462 -0
- package/.agent/sessions/test-session.json +13 -0
- package/.agent/sessions/weixin_test.json +22 -0
- package/.agent/sessions/weixin_test_user_123.json +11 -0
- package/.agent/skills/agent-browser/SKILL.md +311 -0
- package/.agent/skills/agent-browser/TEST_PLAN.md +200 -0
- package/.agent/skills/sysinfo/SKILL.md +38 -0
- package/.agent/skills/sysinfo/system-info.sh +130 -0
- package/.agent/skills/workflow/SKILL.md +324 -0
- package/.agent/test-agent.js +35 -0
- package/.agent/weixin.json +6 -0
- package/.agent/workflows/email-digest.json +50 -0
- package/.agent/workflows/file-backup.json +21 -0
- package/.agent/workflows/get-ip-notify.json +32 -0
- package/.agent/workflows/news-aggregator.json +93 -0
- package/.agent/workflows/news-dashboard-v2.json +94 -0
- package/.agent/workflows/notification-batch.json +32 -0
- package/.claude/settings.local.json +2 -1
- package/.env.example +56 -56
- package/README.md +441 -441
- package/cli/src/utils/debounce.js +6 -3
- package/docs/qq-bot.md +976 -0
- package/package.json +2 -1
- package/plugins/qq-plugin.js +939 -0
- package/skills/find-skills/AGENTS.md +162 -162
- package/skills/find-skills/SKILL.md +133 -133
- package/temp_img.md +1 -0
- package/website_v2/styles/animations.css +7 -7
- package/.agent/.shared/ui-ux-pro-max/data/charts.csv +0 -26
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +0 -97
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +0 -101
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +0 -31
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +0 -97
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +0 -24
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +0 -45
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +0 -53
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +0 -56
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +0 -53
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +0 -53
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +0 -51
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +0 -59
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +0 -52
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +0 -54
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +0 -61
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +0 -54
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +0 -51
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +0 -50
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +0 -59
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +0 -58
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +0 -101
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +0 -100
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +0 -31
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +0 -258
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +0 -1067
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +0 -106
- package/.agent/ARCHITECTURE.md +0 -288
- package/.agent/data/ambient/goals.json +0 -1
- package/.agent/data/puppeteer-sessions/undefined.json +0 -6
- package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
- package/.agent/memory/feedback/mnygjgox-ualjip.md +0 -11
- package/.agent/memory/feedback/mnzugpej-esdwsr.md +0 -9
- package/.agent/memory/feedback/mo2quxke-saxs56.md +0 -9
- package/.agent/memory/feedback/mo9l6nw9-sm1ye2.md +0 -13
- package/.agent/memory/feedback/mo9uxkb3-bbxz3x.md +0 -9
- package/.agent/memory/feedback/mo9wpqy0-ftsez9.md +0 -13
- package/.agent/memory/feedback/mo9x2ps4-p7huqq.md +0 -11
- package/.agent/memory/feedback/mo9x2uay-z7ndjn.md +0 -9
- package/.agent/memory/feedback/mo9x2y7d-9wecyx.md +0 -9
- package/.agent/memory/feedback/mo9x39q3-nn7myt.md +0 -9
- package/.agent/memory/feedback/mo9xfir9-4g8a8j.md +0 -9
- package/.agent/memory/feedback/mo9xfui8-ujqrc6.md +0 -9
- package/.agent/memory/feedback/mocnx1wx-qtxxlh.md +0 -9
- package/.agent/memory/project/mnqx54u5-loqtoe.md +0 -9
- package/.agent/memory/project/mnqx84cv-mx6dmd.md +0 -9
- package/.agent/memory/project/mnsacuyr-hgtk5n.md +0 -20
- package/.agent/memory/project/mnu5hy2x-bjsg7u.md +0 -9
- package/.agent/memory/project/mny28ot4-8qe9au.md +0 -9
- package/.agent/memory/project/mnztftxj-af82rh.md +0 -9
- package/.agent/memory/project/mo0y04d0-bmaefl.md +0 -9
- package/.agent/memory/project/mo2iztxb-3c7v81.md +0 -9
- package/.agent/memory/project/mo2ssa5x-tpi1p3.md +0 -9
- package/.agent/memory/reference/mnre3cww-penbo1.md +0 -9
- package/.agent/memory/reference/mns9wn48-luerua.md +0 -14
- package/.agent/memory/reference/mns9yz5c-thc2s0.md +0 -16
- package/.agent/memory/reference/mnsfy4um-910f1o.md +0 -23
- package/.agent/memory/reference/mnsg37dp-lmfj18.md +0 -32
- package/.agent/memory/reference/mnsll60q-0j911u.md +0 -36
- package/.agent/memory/reference/mnsmlb5y-nej31u.md +0 -16
- package/.agent/memory/reference/mnssle72-yrot96.md +0 -9
- package/.agent/memory/reference/mnygj8nb-bjthmc.md +0 -20
- package/.agent/memory/reference/mnzfvs4m-ufyg9a.md +0 -12
- package/.agent/memory/user/mnsfuon6-l416q1.md +0 -21
- package/.agent/memory/user/mnsg9kut-95m7rf.md +0 -20
- package/.agent/memory/user/mnu2eo1v-yy6fhe.md +0 -9
- package/.agent/memory/user/mnu2etuo-8u8jk8.md +0 -9
- package/.agent/memory/user/mnx0rk6g-gsznjj.md +0 -9
- package/.agent/memory/user/mnyf1riz-4yo5yz.md +0 -9
- package/.agent/memory/user/mnzuh4cw-zvee8w.md +0 -13
- package/.agent/memory/user/mnzvewyj-jl67cq.md +0 -9
- package/.agent/memory/user/mnzwh9xo-43ys3f.md +0 -9
- package/.agent/memory/user/mo0ycvpn-eebsxc.md +0 -9
- package/.agent/memory/user/mo2k8c8n-132r2u.md +0 -9
- package/.agent/memory/user/mo6y2dei-5cf537.md +0 -13
- package/.agent/memory/user/mo9xsdo6-8vylww.md +0 -13
- package/.agent/plugins/puppeteer-plugin/README.md +0 -147
- package/.agent/plugins/puppeteer-plugin/index.js +0 -1422
- package/.agent/plugins/puppeteer-plugin/package.json +0 -9
- package/.agent/rules/GEMINI.md +0 -273
- package/.agent/rules/allow-rule.md +0 -77
- package/.agent/rules/log-rule.md +0 -83
- package/.agent/rules/security-rule.md +0 -93
- package/.agent/scripts/auto_preview.py +0 -148
- package/.agent/scripts/checklist.py +0 -217
- package/.agent/scripts/session_manager.py +0 -120
- package/.agent/scripts/verify_all.py +0 -327
- package/.agent/skills/doc.md +0 -177
- package/.agent/skills/fk-poster/SKILL.md +0 -1129
- package/.agent/skills/fkbuilder/SKILL.md +0 -730
- package/.agent/skills/mmx-cli/SKILL.md +0 -431
- package/.agent/workflows/brainstorm.md +0 -113
- package/.agent/workflows/create.md +0 -59
- package/.agent/workflows/debug.md +0 -103
- package/.agent/workflows/deploy.md +0 -176
- package/.agent/workflows/enhance.md +0 -63
- package/.agent/workflows/orchestrate.md +0 -237
- package/.agent/workflows/plan.md +0 -89
- package/.agent/workflows/preview.md +0 -81
- package/.agent/workflows/simple-test.md +0 -42
- package/.agent/workflows/status.md +0 -86
- package/.agent/workflows/structured-orchestrate.md +0 -180
- package/.agent/workflows/test.md +0 -144
- 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
|