@zhin.js/adapter-qq 1.0.84 → 1.1.2

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 (107) hide show
  1. package/CHANGELOG.md +940 -17
  2. package/PERMITS.md +25 -0
  3. package/README.md +135 -233
  4. package/adapters/qq/index.js +50 -0
  5. package/adapters/qq/index.ts +71 -0
  6. package/agents/qq/agent.json +20 -0
  7. package/agents/qq/boundaries.md +3 -0
  8. package/agents/qq/conventions.md +3 -0
  9. package/agents/qq/skills/qq/SKILL.md +22 -0
  10. package/agents/qq/skills/qq/tools/add_role/index.js +21 -0
  11. package/agents/qq/skills/qq/tools/add_role/index.ts +21 -0
  12. package/agents/qq/skills/qq/tools/channel_info/index.js +17 -0
  13. package/agents/qq/skills/qq/tools/channel_info/index.ts +17 -0
  14. package/agents/qq/skills/qq/tools/create_role/index.js +20 -0
  15. package/agents/qq/skills/qq/tools/create_role/index.ts +20 -0
  16. package/agents/qq/skills/qq/tools/list_channels/index.js +18 -0
  17. package/agents/qq/skills/qq/tools/list_channels/index.ts +18 -0
  18. package/agents/qq/skills/qq/tools/list_guilds/index.js +14 -0
  19. package/agents/qq/skills/qq/tools/list_guilds/index.ts +14 -0
  20. package/agents/qq/skills/qq/tools/list_roles/index.js +18 -0
  21. package/agents/qq/skills/qq/tools/list_roles/index.ts +18 -0
  22. package/agents/qq/skills/qq/tools/member_detail/index.js +18 -0
  23. package/agents/qq/skills/qq/tools/member_detail/index.ts +18 -0
  24. package/agents/qq/skills/qq/tools/remove_role/index.js +21 -0
  25. package/agents/qq/skills/qq/tools/remove_role/index.ts +21 -0
  26. package/agents/qq/system.md +3 -0
  27. package/commands/qq/endpoint/add/[[id]]/index.js +3 -0
  28. package/commands/qq/endpoint/add/[[id]]/index.ts +3 -0
  29. package/commands/qq/endpoint/cancel/index.js +11 -0
  30. package/commands/qq/endpoint/cancel/index.ts +15 -0
  31. package/commands/qq/endpoint/definition.js +188 -0
  32. package/commands/qq/endpoint/definition.ts +263 -0
  33. package/commands/qq/endpoint/list/index.js +3 -0
  34. package/commands/qq/endpoint/list/index.ts +3 -0
  35. package/commands/qq/endpoint/remove/[id]/index.js +3 -0
  36. package/commands/qq/endpoint/remove/[id]/index.ts +3 -0
  37. package/lib/client.d.ts +12 -0
  38. package/lib/client.js +2 -0
  39. package/lib/endpoint.d.ts +56 -0
  40. package/lib/endpoint.js +456 -0
  41. package/lib/index.d.ts +8 -18
  42. package/lib/index.js +8 -338
  43. package/lib/outbound.d.ts +17 -0
  44. package/lib/outbound.js +234 -0
  45. package/lib/platform-permit.d.ts +15 -0
  46. package/lib/platform-permit.js +41 -0
  47. package/lib/protocol.d.ts +90 -0
  48. package/lib/protocol.js +161 -0
  49. package/lib/qq-bind-api.d.ts +36 -0
  50. package/lib/qq-bind-api.js +89 -0
  51. package/lib/qq-bind-flow.d.ts +19 -0
  52. package/lib/qq-bind-flow.js +85 -0
  53. package/lib/qq-bot-kind-prompt.d.ts +9 -0
  54. package/lib/qq-bot-kind-prompt.js +37 -0
  55. package/lib/qq-intents.d.ts +27 -0
  56. package/lib/qq-intents.js +51 -0
  57. package/lib/qq-runtime-state.d.ts +30 -0
  58. package/lib/qq-runtime-state.js +14 -0
  59. package/lib/side-event-dispatch.d.ts +10 -0
  60. package/lib/side-event-dispatch.js +114 -0
  61. package/lib/webhook.d.ts +18 -0
  62. package/lib/webhook.js +103 -0
  63. package/lib/ws.d.ts +55 -0
  64. package/lib/ws.js +234 -0
  65. package/middlewares/bot-kind-reply/index.js +47 -0
  66. package/middlewares/bot-kind-reply/index.ts +65 -0
  67. package/package.json +82 -29
  68. package/plugin.js +18 -0
  69. package/schema.json +158 -0
  70. package/src/client.ts +16 -0
  71. package/src/endpoint.ts +534 -0
  72. package/src/index.ts +66 -326
  73. package/src/outbound.ts +255 -0
  74. package/src/platform-permit.ts +56 -0
  75. package/src/protocol.ts +249 -0
  76. package/src/qq-bind-api.ts +145 -0
  77. package/src/qq-bind-flow.ts +127 -0
  78. package/src/qq-bot-kind-prompt.ts +47 -0
  79. package/src/qq-intents.ts +77 -0
  80. package/src/qq-runtime-state.ts +51 -0
  81. package/src/side-event-dispatch.ts +140 -0
  82. package/src/webhook.ts +132 -0
  83. package/src/ws.ts +331 -0
  84. package/client/Dashboard.tsx +0 -230
  85. package/client/index.tsx +0 -11
  86. package/client/tsconfig.json +0 -7
  87. package/client/utils/api.ts +0 -17
  88. package/dist/index.js +0 -30
  89. package/lib/adapter.d.ts +0 -17
  90. package/lib/adapter.d.ts.map +0 -1
  91. package/lib/adapter.js +0 -49
  92. package/lib/adapter.js.map +0 -1
  93. package/lib/bot.d.ts +0 -102
  94. package/lib/bot.d.ts.map +0 -1
  95. package/lib/bot.js +0 -337
  96. package/lib/bot.js.map +0 -1
  97. package/lib/index.d.ts.map +0 -1
  98. package/lib/index.js.map +0 -1
  99. package/lib/types.d.ts +0 -16
  100. package/lib/types.d.ts.map +0 -1
  101. package/lib/types.js +0 -2
  102. package/lib/types.js.map +0 -1
  103. package/plugin.yml +0 -3
  104. package/skills/qq/SKILL.md +0 -97
  105. package/src/adapter.ts +0 -58
  106. package/src/bot.ts +0 -347
  107. package/src/types.ts +0 -24
package/PERMITS.md ADDED
@@ -0,0 +1,25 @@
1
+ # QQ 官方频道 Platform Permits
2
+
3
+ > 与 QQ 群 `group_*` **分开命名**,仅用于 guild/channel 场景。
4
+
5
+ ## 入站字段
6
+
7
+ `message_type === guild` 时 `getGuildMember` + `getGuildInfo`(TTL 60s):
8
+
9
+ | 字段 | 取值 |
10
+ |------|------|
11
+ | `$sender.role` | `owner` · `admin` · `member` |
12
+ | `$sender.permissions` | `guild_owner` · `guild_admin` · `manage_roles` · `manage_channels` · 角色 ID |
13
+
14
+ ## Permit 词汇表
15
+
16
+ | `platform(qq,…)` | 含义 |
17
+ |------------------|------|
18
+ | `guild_owner` | 频道主 |
19
+ | `guild_admin` | 频道管理员 |
20
+ | `manage_roles` | 管理身份组 |
21
+ | `manage_channels` | 管理子频道 |
22
+
23
+ ## 工厂映射
24
+
25
+ `scene_admin` → `guild_admin` · `scene_owner` → `guild_owner`
package/README.md CHANGED
@@ -1,6 +1,17 @@
1
1
  # @zhin.js/adapter-qq
2
2
 
3
- Zhin.js QQ 官方机器人适配器,基于 QQ 官方机器人 API 开发,支持频道、群聊和私聊消息。
3
+ Zhin.js QQ 官方机器人适配器(Plugin Runtime),默认通过 **WebSocket Gateway**(`qq-official-bot`)收发消息(无需 host-router / host-http)。
4
+
5
+ ## 功能
6
+
7
+ - WebSocket Gateway 入站(默认;无需公网 HTTPS / host)
8
+ - 解析私聊 / 群 / 频道消息
9
+ - 出站 `send({ conversation, payload })` → QQ API(`conversation.kind`/`id`/`parent` 结构化寻址)
10
+ - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
11
+ - Webhook / middleware 模式已实现(经 `httpHostToken` 注册 POST 路由)
12
+ - AI `@` 触发标注:群消息(GROUP_AT_MESSAGE_CREATE 仅 @ 时下发)与频道 `mentions[].bot` 会在入站 metadata 标 `mentioned: true`(新 Plugin Runtime 纯文本 content 经 metadata 传递 @)
13
+ - 群事件:机器人进群/退群、群成员加入/退出、入群申请(支持 `$approve()` / `$reject()`)
14
+ - 完整 SDK 业务 API:通过 `qqClient.get(context, endpoint).api` 使用群管理、禁言、自动审批策略、自定义菜单、指令面板等 `qq-official-bot` 能力
4
15
 
5
16
  ## 安装
6
17
 
@@ -8,275 +19,166 @@ Zhin.js QQ 官方机器人适配器,基于 QQ 官方机器人 API 开发,支
8
19
  pnpm add @zhin.js/adapter-qq
9
20
  ```
10
21
 
11
- ## 配置
12
-
13
- ### 基础配置
14
-
15
- ```typescript
16
- import { defineConfig } from 'zhin.js';
17
-
18
- export default defineConfig({
19
- bots: [
20
- {
21
- context: 'qq',
22
- name: '你的机器人ID', // 机器人的 AppID
23
- appID: process.env.QQ_APP_ID,
24
- token: process.env.QQ_TOKEN,
25
- secret: process.env.QQ_SECRET,
26
- mode: 'public', // 或 'private'
27
- platform: 'qq', // 'qq' | 'qzone'
28
- data_dir: './data' // 数据存储目录
29
- }
30
- ],
31
- plugins: [
32
- 'http',
33
- 'adapter-qq'
34
- ]
35
- })
36
- ```
37
-
38
- ### 完整配置选项
39
-
40
- ```typescript
41
- const config: QQBotConfig = {
42
- context: 'qq',
43
- name: '机器人ID',
44
- appID: 'YOUR_APP_ID', // 机器人 AppID(必需)
45
- token: 'YOUR_TOKEN', // 机器人 Token(必需)
46
- secret: 'YOUR_SECRET', // 机器人 Secret(必需)
47
- mode: 'public', // 'public' | 'private' 接收模式
48
- platform: 'qq', // 'qq' | 'qzone' 平台类型
49
- intents: [ // 事件订阅意图
50
- 'GUILDS',
51
- 'GUILD_MEMBERS',
52
- 'DIRECT_MESSAGE',
53
- 'GROUP_AT_MESSAGE_CREATE'
54
- ],
55
- data_dir: './data', // 数据目录(可选)
56
- sandbox: false // 是否为沙箱环境(可选)
57
- }
58
- ```
59
-
60
- ## 获取配置信息
61
-
62
- ### 1. 注册 QQ 机器人
63
-
64
- 1. 访问 [QQ 开放平台](https://q.qq.com/bot)
65
- 2. 登录并创建机器人应用
66
- 3. 在「开发设置」中获取:
67
- - **AppID**: 机器人应用 ID
68
- - **Token**: 机器人令牌
69
- - **Secret**: 机器人密钥
22
+ ## Plugin Runtime
70
23
 
71
- ### 2. 配置权限
24
+ - `@zhin.js/adapter` — 约定式 `adapters/qq/index.ts`(`defineAdapter`)
25
+ - `@zhin.js/core` — `Endpoint.emit(...)` 入站、`outboundMessageToken` 出站
26
+ - `zhin.js` — `plugin.ts`(`definePlugin`)
27
+ - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
28
+ - **无需** `@zhin.js/host-http` / `@zhin.js/host-router`(WebSocket 路径)
72
29
 
73
- 在机器人设置中:
74
- - 配置需要的事件订阅
75
- - 设置消息接收模式(公域/私域)
76
- - 添加频道/群聊白名单(如需要)
30
+ 入站:`gateway.receive({ conversation, message, content, sender, metadata })`(ConversationRef 结构化寻址)
31
+ 出站:`send({ conversation, payload })` → `sendPrivateMessage` / `sendGroupMessage` / `sendGuildMessage`
77
32
 
78
- ### 3. 事件订阅(Intents)
33
+ ## 前置条件
79
34
 
80
- 可订阅的事件类型:
81
- - `GUILDS` - 频道事件
82
- - `GUILD_MEMBERS` - 成员变动
83
- - `GUILD_MESSAGES` - 频道消息
84
- - `DIRECT_MESSAGE` - 私信消息
85
- - `GROUP_AT_MESSAGE_CREATE` - 群聊 @ 消息
86
- - `INTERACTION` - 互动事件
35
+ | 要求 | 说明 |
36
+ |------|------|
37
+ | **AppID / Secret** | [QQ 开放平台](https://q.qq.com/) 创建机器人应用并获取 |
38
+ | **WebSocket(默认)** | `qq-official-bot` 正向连接;无需公网回调 |
39
+ | **host-http** | WebSocket **不需要**;Webhook / middleware 模式需要(经 `httpHostToken`) |
87
40
 
88
- ## 使用示例
41
+ 必填字段(`endpoints[i]`):`id`、`appid`、`secret`。
89
42
 
90
- ### 基础消息处理
43
+ ## 最小配置
91
44
 
92
- ```typescript
93
- import { addCommand, MessageCommand } from 'zhin.js'
94
-
95
- addCommand(new MessageCommand('hello <name:text>')
96
- .action(async (message, result) => {
97
- return `你好,${result.params.name}!`
98
- })
99
- )
45
+ ```yaml
46
+ # zhin.config.yml(Plugin Runtime)
47
+ plugins:
48
+ qq:
49
+ # mode: websocket # 默认
50
+ endpoints:
51
+ - id: my-qq-bot
52
+ appid: ${QQ_APPID}
53
+ secret: ${QQ_SECRET}
54
+ # botKind / intents 可由向导写入;`qq endpoint add` 扫码后会追问公域/私域再写入
100
55
  ```
101
56
 
102
- ### 频道消息
103
-
104
- ```typescript
105
- import { onMessage } from 'zhin.js'
106
-
107
- onMessage(async (message) => {
108
- // 仅处理频道消息
109
- if (message.$channel.type === 'channel') {
110
- console.log(`频道消息:${message.$raw}`)
111
- }
112
- })
57
+ ### botKind 与 intents
58
+
59
+ WebSocket Identify 的 `intents` **必须与开放平台已开通的权限一致**,否则会断连。
60
+ 群聊无公/私域之分,两类都订阅 `GROUP_AND_C2C_EVENT` 和 `GROUP_MEMBER`;差异只在频道消息 intent:
61
+
62
+ | `botKind` | 频道消息 intent | 共用 intents |
63
+ |-----------|-----------------|--------------|
64
+ | `public`(默认,公域) | `PUBLIC_GUILD_MESSAGES`(仅 @) | `GROUP_AND_C2C_EVENT`, `GROUP_MEMBER`, `GUILDS`, `GUILD_MEMBERS`, `DIRECT_MESSAGE` |
65
+ | `private`(私域) | `GUILD_MESSAGES`(频道全量) | 同上 |
66
+
67
+ 公域机器人订阅 `GUILD_MESSAGES` 会 Identify 失败断连。显式配置 `intents` 时优先于 `botKind`。
68
+ `create-zhin` / `zhin setup` 会询问公/私域并写入;`qq endpoint add` 扫码成功后在同一会话追问 `public`/`private`(或 `公域`/`私域`),确认后一次性写 `.env` 与 yaml。
69
+
70
+ 多账号:一个插件实例挂多个 endpoint(`endpoints` 数组逐项覆盖顶层字段,`id` 必填):
71
+
72
+ ```yaml
73
+ plugins:
74
+ qq:
75
+ mode: websocket
76
+ endpoints:
77
+ - id: main-bot
78
+ appid: ${QQ_APPID}
79
+ secret: ${QQ_SECRET}
80
+ botKind: public
81
+ intents:
82
+ - GROUP_AND_C2C_EVENT
83
+ - GROUP_MEMBER
84
+ - GUILDS
85
+ - GUILD_MEMBERS
86
+ - DIRECT_MESSAGE
87
+ - PUBLIC_GUILD_MESSAGES
88
+ - id: private-bot
89
+ appid: ${QQ_APPID_2}
90
+ secret: ${QQ_SECRET_2}
91
+ botKind: private
92
+ intents:
93
+ - GROUP_AND_C2C_EVENT
94
+ - GROUP_MEMBER
95
+ - GUILDS
96
+ - GUILD_MEMBERS
97
+ - DIRECT_MESSAGE
98
+ - GUILD_MESSAGES
113
99
  ```
114
100
 
115
- ### 群聊消息
116
-
117
- ```typescript
118
- import { onGroupMessage } from 'zhin.js'
101
+ 根插件 `zhin.plugins`(或项目图)需引用 `@zhin.js/adapter-qq`(`instanceKey: qq`)。
119
102
 
120
- onGroupMessage(async (message) => {
121
- console.log(`群聊消息来自:${message.$sender.name}`)
122
- console.log(`消息内容:${message.$raw}`)
123
- })
124
- ```
125
-
126
- ### 私聊消息
103
+ ## Endpoint 管理命令
127
104
 
128
- ```typescript
129
- import { onPrivateMessage } from 'zhin.js'
105
+ 适配器自带 `qq endpoint` 命令组(聊天内直接使用,默认无前缀;受 `commandPrefix` 影响):
130
106
 
131
- onPrivateMessage(async (message) => {
132
- await message.$reply('收到你的私信了!')
133
- })
134
- ```
107
+ | 命令 | 说明 |
108
+ |------|------|
109
+ | `qq endpoint add [name]` | 手机 QQ 扫码绑定 → **追问公域/私域** → 一次性写入 `.env` + `plugins.qq.endpoints`(重启生效) |
110
+ | `qq endpoint cancel` | 取消进行中的扫码绑定或待确认的公域/私域选择 |
111
+ | `qq endpoint list` | 列出运行中与配置中的 endpoints |
112
+ | `qq endpoint remove <name>` | 从配置移除 endpoint(`.env` 键保留,可手动清理) |
135
113
 
136
- ### 发送不同类型消息
137
-
138
- ```typescript
139
- addCommand(new MessageCommand('card')
140
- .action(async (message) => {
141
- // 发送 Ark 模板卡片
142
- return {
143
- type: 'ark',
144
- template_id: 23,
145
- kv: [
146
- { key: '#TITLE#', value: '标题' },
147
- { key: '#DESC#', value: '描述' },
148
- { key: '#PROMPT#', value: '提示' }
149
- ]
150
- }
151
- })
152
- )
153
-
154
- addCommand(new MessageCommand('embed')
155
- .action(async (message) => {
156
- // 发送 Embed 消息
157
- return {
158
- type: 'embed',
159
- title: 'Embed 标题',
160
- prompt: '消息提示',
161
- thumbnail: { url: 'https://example.com/image.png' },
162
- fields: [
163
- { name: '字段1', value: '值1' }
164
- ]
165
- }
166
- })
167
- )
168
- ```
114
+ add/cancel/remove 受 `master` 限制:实例配置声明了 `master`(顶层或 `endpoints[i].master`)时仅
115
+ master 可执行;未配置则放行(首个扫码绑定者会写入该 endpoint 的 `master`)。二维码当前以链接文本下发
116
+ (出站富媒体待迁移),用手机 QQ 打开链接即可扫码。
169
117
 
170
- ## 消息类型支持
118
+ ## 环境变量
171
119
 
172
- ### 接收消息类型
120
+ | 变量 | 说明 |
121
+ |------|------|
122
+ | `QQ_APPID` | YAML 示例中 `appid` 引用的应用 AppID |
123
+ | `QQ_SECRET` | YAML 示例中 `secret` 引用的应用 Secret |
173
124
 
174
- - ✅ 文本消息
175
- - ✅ @ 提及
176
- - ✅ 图片消息
177
- - ✅ 表情消息
178
- - ✅ Ark 模板消息
179
- - ✅ Embed 消息
180
- - ✅ Markdown 消息
125
+ ## Webhook / middleware
181
126
 
182
- ### 发送消息类型
127
+ `mode: webhook` 或 `mode: middleware` 经 `httpHostToken` 注册 POST 路由(默认 `/qq/webhook`),使用 qq-official-bot Middleware 接收器验签并入站;出站仍走 QQ HTTP API。Host 需注入 `httpHostToken`。
183
128
 
184
- - ✅ 文本消息
185
- - ✅ 图片消息
186
- - ✅ Ark 模板消息
187
- - ✅ Embed 富文本消息
188
- - ✅ Markdown 消息
189
- - ✅ 消息引用(回复)
129
+ ## AI 工具(Skill)
190
130
 
191
- ## API 方法
131
+ | 类别 | 路径 |
132
+ |------|------|
133
+ | Permit 词汇 | `PERMITS.md` |
134
+ | 平台工具 | `tools/`(频道、角色等) |
135
+ | 技能说明 | `agents/qq/skills/qq/SKILL.md` |
192
136
 
193
- ### 基础方法
137
+ ## 平台 Client 与 QQ API
194
138
 
195
- ```typescript
196
- const bot = app.adapters.get('qq')?.bots.get('你的机器人ID')
139
+ `qqClient` 返回当前 endpoint 的传输 client。常用发送、撤回、频道查询仍有顶层快捷方法;
140
+ `client.api` 暴露完整的 `qq-official-bot` 1.3 业务 Service 与实体入口;连接启停仍由 Endpoint 生命周期统一管理:
197
141
 
198
- // 发送私信
199
- await bot.sendPrivateMessage(userId, '消息内容')
142
+ ```ts
143
+ import { qqClient } from '@zhin.js/adapter-qq';
200
144
 
201
- // 发送群消息
202
- await bot.sendGroupMessage(groupId, '消息内容')
145
+ const client = qqClient.get(context, 'my-qq-bot');
203
146
 
204
- // 发送频道消息
205
- await bot.sendGuildMessage(channelId, '消息内容')
147
+ const group = await client.api.groupService.getInfo(groupOpenid);
148
+ const requests = await client.api.groupService.getJoinRequests(groupOpenid);
149
+ await client.api.groupService.setMemberMute(groupOpenid, [
150
+ { op: 'add', member_openid: memberOpenid, mute_expire_at: expiresAt },
151
+ ]);
206
152
 
207
- // 撤回消息
208
- await bot.$recallMessage(messageId)
153
+ const menu = await client.api.menuPanelService.getCustomMenu();
154
+ const panels = await client.api.menuPanelService.getCommandPanels({ scope: 'group' });
209
155
  ```
210
156
 
211
- ## 消息 ID 格式
157
+ 群管理接口中有部分是白名单能力,入群审批和禁言还要求机器人具备群管理员权限;
158
+ 具体可用范围以 QQ 开放平台为准。
212
159
 
213
- 本适配器使用特殊的消息 ID 格式来区分不同类型的消息:
160
+ ## 平台权限(platform permit)
214
161
 
215
- - 私信:`private-{userId}:{messageId}`
216
- - 群聊:`group-{groupId}:{messageId}`
217
- - 频道:`channel-{channelId}:{messageId}`
218
- - 私域频道:`direct-{guildId}:{messageId}`
162
+ platform permit checker 由 `plugin.ts` 的 generation 生命周期注册;`@zhin.js/tool` descriptor 保留 `platforms` / `scopes` / `permissions`,CapabilityIngress 与 ToolSystem 统一经 Core `canAccessTool()` 执行门禁。
219
163
 
220
- ## 注意事项
164
+ ## Markdown 与交互输出
221
165
 
222
- ### 接收模式
166
+ QQ Endpoint 声明原生 `markdown` / `keyboard` 能力。普通 AI Markdown 会保留为 QQ Markdown
167
+ 消息;命令或 Agent 通过 `UserInteraction.ask()` 发起 `confirm` / `select` 时,会生成
168
+ Markdown + 指令按钮组。按钮值与手动输入共用同一解析入口,不维护 QQ 专用交互状态。
223
169
 
224
- - **公域模式 (public)**: 仅接收 @ 机器人的消息
225
- - **私域模式 (private)**: 可接收频道内所有消息(需要申请权限)
170
+ 不支持原生按钮的 Adapter 会在 Core 中把同一 keyboard 语义降级为编号列表,并保存数字到
171
+ payload 的回跳映射。图片、音频、视频和文件仍沿 canonical `MediaRef` 出站链投递。
226
172
 
227
- ### 频率限制
173
+ ## 故障排查
228
174
 
229
- QQ 机器人有严格的频率限制:
230
- - 主动消息:每个用户每天 5 条
231
- - 被动消息(回复):无限制
232
- - 建议在被动模式下使用(用户 @ 后回复)
233
-
234
- ### 沙箱环境
235
-
236
- 开发时可以使用沙箱环境测试:
237
-
238
- ```typescript
239
- {
240
- context: 'qq',
241
- sandbox: true, // 启用沙箱
242
- // ...其他配置
243
- }
244
- ```
245
-
246
- ## 常见问题
247
-
248
- ### Q: 机器人无法收到消息?
249
-
250
- A: 检查以下几点:
251
- 1. AppID、Token、Secret 是否正确
252
- 2. 事件订阅(Intents)是否包含对应类型
253
- 3. 公域模式下消息是否 @ 了机器人
254
- 4. 机器人是否已加入对应频道/群聊
255
-
256
- ### Q: 发送消息失败?
257
-
258
- A: 可能的原因:
259
- 1. 超过主动消息频率限制
260
- 2. 没有对应频道/群的发送权限
261
- 3. 消息格式不符合规范
262
- 4. Token 已过期或失效
263
-
264
- ### Q: 如何处理不同平台?
265
-
266
- A: 使用 `platform` 配置:
267
- - `qq`: QQ 频道和群聊
268
- - `qzone`: QQ 空间(如支持)
269
-
270
- ## 相关链接
271
-
272
- - [QQ 开放平台](https://q.qq.com/bot)
273
- - [QQ 机器人开发文档](https://bot.q.qq.com/wiki/)
274
- - [Zhin.js 官方文档](https://github.com/zhinjs/zhin)
175
+ | 现象 | 排查 |
176
+ | --- | --- |
177
+ | Gateway Identify 后断线 | 检查 `botKind` 与 intents;公域机器人不要订阅受限 intent |
178
+ | 二维码绑定后仍离线 | 在 Console 登录待办确认结果,并检查 App ID、Secret 与 Token |
179
+ | Webhook 无事件 | 检查 HTTP Host、公网路径、平台回调与签名参数 |
180
+ | Markdown/按钮发送失败 | 检查平台模板与权限,并按 Endpoint 能力降级 |
275
181
 
276
182
  ## 许可证
277
183
 
278
184
  MIT License
279
-
280
- ## 贡献
281
-
282
- 欢迎提交 Issue 和 Pull Request!
@@ -0,0 +1,50 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ /**
3
+ * Convention entry: discover `adapters/qq/index.ts` → defineAdapter.
4
+ */
5
+ import { defineAdapter } from 'zhin.js/adapter';
6
+ import { httpHostToken } from '@zhin.js/host-http';
7
+ import { QqHttpEndpoint, QqWebsocketEndpoint } from "../../lib/endpoint.js";
8
+ import { resolveQqConfig, } from "../../lib/protocol.js";
9
+ import { qqRuntimeStateToken } from "../../lib/qq-runtime-state.js";
10
+ export { QqHttpEndpoint, QqWebsocketEndpoint, } from "../../lib/endpoint.js";
11
+ export default defineAdapter({
12
+ capabilities: ['inbound', 'outbound'],
13
+ operations: ['recall'],
14
+ // 媒体 url 直发,base64/path 由 SDK formatMediaData 物化走 /files 上传;
15
+ // markdown/keyboard 原生按钮承载交互段。
16
+ segments: {
17
+ supported: ['text', 'mention', 'image', 'audio', 'video', 'file', 'reply', 'markdown', 'keyboard'],
18
+ markdown: 'native',
19
+ outboundMedia: ['url', 'upload'],
20
+ interactive: 'native',
21
+ },
22
+ create(context) {
23
+ const config = resolveQqConfig(context.config);
24
+ // 注册到插件运行时状态(qq endpoint list 的"运行中"数据源)
25
+ const state = context.use(qqRuntimeStateToken);
26
+ state.endpoints.set(config.id, {
27
+ id: config.id,
28
+ mode: config.mode,
29
+ });
30
+ // 正向判 websocket:TS 对 `mode === 'webhook' || mode === 'middleware'` 的
31
+ // 否定分支不做联合消减(多字面量判别式),反向写会让 config 窄化失败。
32
+ const endpoint = config.mode === 'websocket'
33
+ ? new QqWebsocketEndpoint({
34
+ id: context.id,
35
+ config,
36
+ })
37
+ : new QqHttpEndpoint({
38
+ id: context.id,
39
+ http: context.use(httpHostToken),
40
+ config,
41
+ });
42
+ // 运行状态表只增不减:stop 时同步摘除,避免 stop 后 qq endpoint list 仍显示运行中。
43
+ const stopEndpoint = endpoint.stop.bind(endpoint);
44
+ endpoint.stop = async () => {
45
+ state.endpoints.delete(config.id);
46
+ await stopEndpoint();
47
+ };
48
+ return endpoint;
49
+ },
50
+ });
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Convention entry: discover `adapters/qq/index.ts` → defineAdapter.
3
+ */
4
+ import { defineAdapter } from 'zhin.js/adapter';
5
+ import { httpHostToken } from '@zhin.js/host-http';
6
+ import { QqHttpEndpoint, QqWebsocketEndpoint } from '../../src/endpoint.js';
7
+ import {
8
+ resolveQqConfig,
9
+ type QqEndpointConfig,
10
+ } from '../../src/protocol.js';
11
+ import { qqRuntimeStateToken } from '../../src/qq-runtime-state.js';
12
+
13
+ export {
14
+ QqHttpEndpoint,
15
+ QqWebsocketEndpoint,
16
+ } from '../../src/endpoint.js';
17
+ export type {
18
+ CreateQqBot,
19
+ CreateQqHttpBot,
20
+ QqBotTransport,
21
+ QqEndpointOptions,
22
+ QqHttpBotTransport,
23
+ QqHttpEndpointOptions,
24
+ } from '../../src/endpoint.js';
25
+
26
+ declare module '@zhin.js/core' {
27
+ interface AdapterEndpoints {
28
+ qq: import('../../src/endpoint.js').QqWebsocketEndpoint;
29
+ }
30
+ }
31
+
32
+ export default defineAdapter<QqEndpointConfig>({
33
+ capabilities: ['inbound', 'outbound'],
34
+ operations: ['recall'],
35
+ // 媒体 url 直发,base64/path 由 SDK formatMediaData 物化走 /files 上传;
36
+ // markdown/keyboard 原生按钮承载交互段。
37
+ segments: {
38
+ supported: ['text', 'mention', 'image', 'audio', 'video', 'file', 'reply', 'markdown', 'keyboard'],
39
+ markdown: 'native',
40
+ outboundMedia: ['url', 'upload'],
41
+ interactive: 'native',
42
+ },
43
+ create(context) {
44
+ const config = resolveQqConfig(context.config);
45
+ // 注册到插件运行时状态(qq endpoint list 的"运行中"数据源)
46
+ const state = context.use(qqRuntimeStateToken);
47
+ state.endpoints.set(config.id, {
48
+ id: config.id,
49
+ mode: config.mode,
50
+ });
51
+ // 正向判 websocket:TS 对 `mode === 'webhook' || mode === 'middleware'` 的
52
+ // 否定分支不做联合消减(多字面量判别式),反向写会让 config 窄化失败。
53
+ const endpoint = config.mode === 'websocket'
54
+ ? new QqWebsocketEndpoint({
55
+ id: context.id,
56
+ config,
57
+ })
58
+ : new QqHttpEndpoint({
59
+ id: context.id,
60
+ http: context.use(httpHostToken),
61
+ config,
62
+ });
63
+ // 运行状态表只增不减:stop 时同步摘除,避免 stop 后 qq endpoint list 仍显示运行中。
64
+ const stopEndpoint = endpoint.stop.bind(endpoint);
65
+ endpoint.stop = async () => {
66
+ state.endpoints.delete(config.id);
67
+ await stopEndpoint();
68
+ };
69
+ return endpoint;
70
+ },
71
+ });
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "QQ Platform Agent",
3
+ "version": "1.0.0",
4
+ "description": "Handles qq platform-specific operations and progressively loads only the required platform Skill.",
5
+ "trigger_rules": {
6
+ "file_patterns": [],
7
+ "keywords": [
8
+ "qq",
9
+ "adapter:qq"
10
+ ]
11
+ },
12
+ "entry_points": [
13
+ "system.md",
14
+ "boundaries.md",
15
+ "conventions.md"
16
+ ],
17
+ "platforms": [
18
+ "qq"
19
+ ]
20
+ }
@@ -0,0 +1,3 @@
1
+ # Boundaries
2
+
3
+ Operate only on the active qq endpoint and authenticated conversation context. Respect every Tool permission and approval policy. Do not infer identifiers, roles, or authorization from display names.
@@ -0,0 +1,3 @@
1
+ # Conventions
2
+
3
+ Use stable platform identifiers from the current turn or directory queries. Load one task-specific Skill at a time, and use the unified reply and outbound message path.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: qq
3
+ platforms: [qq]
4
+ description: QQ 官方机器人频道、角色和成员详情能力。
5
+ keywords: [qq, guild, channel, role, member]
6
+ tags: [adapter, community]
7
+ tools:
8
+ - list_guilds
9
+ - list_channels
10
+ - list_roles
11
+ - create_role
12
+ - add_role
13
+ - remove_role
14
+ - channel_info
15
+ - member_detail
16
+ ---
17
+
18
+ # QQ 官方机器人
19
+
20
+ 先用 `list_guilds` 和 `list_channels` 确认频道上下文。角色操作前用 `list_roles` 获取角色 ID;成员操作前用 `member_detail` 确认目标。创建、授予和移除角色属于写操作,应遵守对应 Tool 的审批策略。
21
+
22
+ 当前 Agent Tool 不提供群踢人、禁言或成员列表能力。
@@ -0,0 +1,21 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { defineAgentTool } from '@zhin.js/tool';
3
+ import { z } from 'zod';
4
+ import { platformPermission } from '@zhin.js/permission';
5
+ export default defineAgentTool({
6
+ description: '给成员添加 QQ 频道角色',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ channel_id: z.string().describe('子频道 ID'),
10
+ user_id: z.string().describe('用户 ID'),
11
+ role_id: z.string().describe('角色 ID'),
12
+ }),
13
+ adapter: 'qq',
14
+ tags: ['qq'],
15
+ permissions: [platformPermission('qq', 'manage_roles')],
16
+ async execute({ guild_id, channel_id, user_id, role_id }, context) {
17
+ const client = context.$client;
18
+ const success = await client.addMemberRole(guild_id, channel_id, user_id, role_id);
19
+ return { success, message: success ? '已给成员添加角色' : '操作失败' };
20
+ },
21
+ });
@@ -0,0 +1,21 @@
1
+ import { defineAgentTool } from '@zhin.js/tool';
2
+ import { z } from 'zod';
3
+ import { platformPermission } from '@zhin.js/permission';
4
+
5
+ export default defineAgentTool<{ guild_id: string; channel_id: string; user_id: string; role_id: string }>({
6
+ description: '给成员添加 QQ 频道角色',
7
+ inputSchema: z.object({
8
+ guild_id: z.string().describe('频道 ID'),
9
+ channel_id: z.string().describe('子频道 ID'),
10
+ user_id: z.string().describe('用户 ID'),
11
+ role_id: z.string().describe('角色 ID'),
12
+ }),
13
+ adapter: 'qq',
14
+ tags: ['qq'],
15
+ permissions: [platformPermission('qq', 'manage_roles')],
16
+ async execute({ guild_id, channel_id, user_id, role_id }: { guild_id: string; channel_id: string; user_id: string; role_id: string }, context) {
17
+ const client = context.$client;
18
+ const success = await client.addMemberRole(guild_id, channel_id, user_id, role_id);
19
+ return { success, message: success ? '已给成员添加角色' : '操作失败' };
20
+ },
21
+ });