@zhin.js/adapter-qq 1.0.83 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +853 -16
- package/README.md +136 -233
- package/adapters/qq.js +50 -0
- package/adapters/qq.ts +71 -0
- package/agent/PERMITS.md +25 -0
- package/{skills/qq/SKILL.md → agent/skills/qq.md} +1 -1
- package/agent/tools/add_role.ts +21 -0
- package/agent/tools/channel_info.ts +17 -0
- package/agent/tools/create_role.ts +20 -0
- package/agent/tools/list_channels.ts +18 -0
- package/agent/tools/list_guilds.ts +14 -0
- package/agent/tools/list_roles.ts +18 -0
- package/agent/tools/member_detail.ts +18 -0
- package/agent/tools/remove_role.ts +21 -0
- package/commands/endpoint/add/[[id]].js +3 -0
- package/commands/endpoint/add/[[id]].ts +3 -0
- package/commands/endpoint/cancel.js +12 -0
- package/commands/endpoint/cancel.ts +15 -0
- package/commands/endpoint/list.js +3 -0
- package/commands/endpoint/list.ts +3 -0
- package/commands/endpoint/remove/[id].js +3 -0
- package/commands/endpoint/remove/[id].ts +3 -0
- package/lib/client.d.ts +12 -0
- package/lib/client.js +2 -0
- package/lib/endpoint.d.ts +56 -0
- package/lib/endpoint.js +456 -0
- package/lib/index.d.ts +8 -18
- package/lib/index.js +8 -338
- package/lib/outbound.d.ts +17 -0
- package/lib/outbound.js +234 -0
- package/lib/platform-permit.d.ts +15 -0
- package/lib/platform-permit.js +41 -0
- package/lib/protocol.d.ts +111 -0
- package/lib/protocol.js +166 -0
- package/lib/qq-bind-api.d.ts +36 -0
- package/lib/qq-bind-api.js +89 -0
- package/lib/qq-bind-flow.d.ts +19 -0
- package/lib/qq-bind-flow.js +85 -0
- package/lib/qq-bind-persist.d.ts +12 -0
- package/lib/qq-bind-persist.js +22 -0
- package/lib/qq-bot-kind-prompt.d.ts +9 -0
- package/lib/qq-bot-kind-prompt.js +37 -0
- package/lib/qq-endpoint-commands.d.ts +45 -0
- package/lib/qq-endpoint-commands.js +180 -0
- package/lib/qq-endpoint-config.d.ts +17 -0
- package/lib/qq-endpoint-config.js +22 -0
- package/lib/qq-intents.d.ts +27 -0
- package/lib/qq-intents.js +51 -0
- package/lib/qq-runtime-state.d.ts +30 -0
- package/lib/qq-runtime-state.js +14 -0
- package/lib/side-event-dispatch.d.ts +10 -0
- package/lib/side-event-dispatch.js +114 -0
- package/lib/webhook.d.ts +18 -0
- package/lib/webhook.js +103 -0
- package/lib/ws.d.ts +55 -0
- package/lib/ws.js +234 -0
- package/middlewares/bot-kind-reply.js +46 -0
- package/middlewares/bot-kind-reply.ts +60 -0
- package/package.json +69 -28
- package/plugin.js +18 -0
- package/schema.json +142 -0
- package/src/client.ts +16 -0
- package/src/endpoint.ts +534 -0
- package/src/index.ts +66 -326
- package/src/outbound.ts +255 -0
- package/src/platform-permit.ts +56 -0
- package/src/protocol.ts +273 -0
- package/src/qq-bind-api.ts +145 -0
- package/src/qq-bind-flow.ts +127 -0
- package/src/qq-bind-persist.ts +44 -0
- package/src/qq-bot-kind-prompt.ts +47 -0
- package/src/qq-endpoint-commands.ts +251 -0
- package/src/qq-endpoint-config.ts +44 -0
- package/src/qq-intents.ts +77 -0
- package/src/qq-runtime-state.ts +51 -0
- package/src/side-event-dispatch.ts +140 -0
- package/src/webhook.ts +132 -0
- package/src/ws.ts +331 -0
- package/client/Dashboard.tsx +0 -230
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -17
- package/dist/index.js +0 -30
- package/lib/adapter.d.ts +0 -17
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -49
- package/lib/adapter.js.map +0 -1
- package/lib/bot.d.ts +0 -102
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js +0 -337
- package/lib/bot.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/types.d.ts +0 -16
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -58
- package/src/bot.ts +0 -347
- package/src/types.ts +0 -24
package/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# @zhin.js/adapter-qq
|
|
2
2
|
|
|
3
|
-
Zhin.js QQ
|
|
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,167 @@ 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
|
-
|
|
24
|
+
- `@zhin.js/adapter` — 约定式 `adapters/qq.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
|
-
|
|
33
|
+
## 前置条件
|
|
79
34
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- `
|
|
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]`):`name`、`appid`、`secret`。
|
|
89
42
|
|
|
90
|
-
|
|
43
|
+
## 最小配置
|
|
91
44
|
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
45
|
+
```yaml
|
|
46
|
+
# zhin.config.yml(Plugin Runtime)
|
|
47
|
+
plugins:
|
|
48
|
+
qq:
|
|
49
|
+
# mode: websocket # 默认
|
|
50
|
+
endpoints:
|
|
51
|
+
- name: my-qq-bot
|
|
52
|
+
appid: ${QQ_APPID}
|
|
53
|
+
secret: ${QQ_SECRET}
|
|
54
|
+
# botKind / intents 可由向导写入;`qq.endpoint add` 扫码后会追问公域/私域再写入
|
|
100
55
|
```
|
|
101
56
|
|
|
102
|
-
###
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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` 数组逐项覆盖顶层字段,`name` 必填):
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
plugins:
|
|
74
|
+
qq:
|
|
75
|
+
mode: websocket
|
|
76
|
+
endpoints:
|
|
77
|
+
- name: 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
|
+
- name: 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
|
-
|
|
121
|
-
console.log(`群聊消息来自:${message.$sender.name}`)
|
|
122
|
-
console.log(`消息内容:${message.$raw}`)
|
|
123
|
-
})
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 私聊消息
|
|
103
|
+
## Endpoint 管理命令
|
|
127
104
|
|
|
128
|
-
|
|
129
|
-
import { onPrivateMessage } from 'zhin.js'
|
|
105
|
+
适配器自带 `qq.endpoint` 命令组(聊天内直接使用,默认无前缀;受 `commandPrefix` 影响):
|
|
130
106
|
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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` / `QQ_BOT_APPID` | 应用 AppID |
|
|
123
|
+
| `QQ_SECRET` / `QQ_BOT_SECRET` | 应用 Secret |
|
|
124
|
+
| `QQ_BOT_NAME` | 可选,默认 endpoint 名 |
|
|
173
125
|
|
|
174
|
-
|
|
175
|
-
- ✅ @ 提及
|
|
176
|
-
- ✅ 图片消息
|
|
177
|
-
- ✅ 表情消息
|
|
178
|
-
- ✅ Ark 模板消息
|
|
179
|
-
- ✅ Embed 消息
|
|
180
|
-
- ✅ Markdown 消息
|
|
126
|
+
## Webhook / middleware
|
|
181
127
|
|
|
182
|
-
|
|
128
|
+
`mode: webhook` 或 `mode: middleware` 经 `httpHostToken` 注册 POST 路由(默认 `/qq/webhook`),使用 qq-official-bot Middleware 接收器验签并入站;出站仍走 QQ HTTP API。Host 需注入 `httpHostToken`。
|
|
183
129
|
|
|
184
|
-
|
|
185
|
-
- ✅ 图片消息
|
|
186
|
-
- ✅ Ark 模板消息
|
|
187
|
-
- ✅ Embed 富文本消息
|
|
188
|
-
- ✅ Markdown 消息
|
|
189
|
-
- ✅ 消息引用(回复)
|
|
130
|
+
## AI 工具(Skill)
|
|
190
131
|
|
|
191
|
-
|
|
132
|
+
| 类别 | 路径 |
|
|
133
|
+
|------|------|
|
|
134
|
+
| Permit 词汇 | `agent/PERMITS.md` |
|
|
135
|
+
| 平台工具 | `agent/tools/`(频道、角色等) |
|
|
136
|
+
| 技能说明 | `agent/skills/qq.md` |
|
|
192
137
|
|
|
193
|
-
|
|
138
|
+
## 平台 Client 与 QQ API
|
|
194
139
|
|
|
195
|
-
|
|
196
|
-
|
|
140
|
+
`qqClient` 返回当前 endpoint 的传输 client。常用发送、撤回、频道查询仍有顶层快捷方法;
|
|
141
|
+
`client.api` 暴露完整的 `qq-official-bot` 1.3 业务 Service 与实体入口;连接启停仍由 Endpoint 生命周期统一管理:
|
|
197
142
|
|
|
198
|
-
|
|
199
|
-
|
|
143
|
+
```ts
|
|
144
|
+
import { qqClient } from '@zhin.js/adapter-qq';
|
|
200
145
|
|
|
201
|
-
|
|
202
|
-
await bot.sendGroupMessage(groupId, '消息内容')
|
|
146
|
+
const client = qqClient.get(context, 'my-qq-bot');
|
|
203
147
|
|
|
204
|
-
|
|
205
|
-
await
|
|
148
|
+
const group = await client.api.groupService.getInfo(groupOpenid);
|
|
149
|
+
const requests = await client.api.groupService.getJoinRequests(groupOpenid);
|
|
150
|
+
await client.api.groupService.setMemberMute(groupOpenid, [
|
|
151
|
+
{ op: 'add', member_openid: memberOpenid, mute_expire_at: expiresAt },
|
|
152
|
+
]);
|
|
206
153
|
|
|
207
|
-
|
|
208
|
-
await
|
|
154
|
+
const menu = await client.api.menuPanelService.getCustomMenu();
|
|
155
|
+
const panels = await client.api.menuPanelService.getCommandPanels({ scope: 'group' });
|
|
209
156
|
```
|
|
210
157
|
|
|
211
|
-
|
|
158
|
+
群管理接口中有部分是白名单能力,入群审批和禁言还要求机器人具备群管理员权限;
|
|
159
|
+
具体可用范围以 QQ 开放平台为准。
|
|
212
160
|
|
|
213
|
-
|
|
161
|
+
## 平台权限(platform permit)
|
|
214
162
|
|
|
215
|
-
|
|
216
|
-
- 群聊:`group-{groupId}:{messageId}`
|
|
217
|
-
- 频道:`channel-{channelId}:{messageId}`
|
|
218
|
-
- 私域频道:`direct-{guildId}:{messageId}`
|
|
163
|
+
platform permit checker 由 `plugin.ts` 的 generation 生命周期注册;`@zhin.js/tool` descriptor 保留 `platforms` / `scopes` / `permissions`,CapabilityIngress 与 ToolSystem 统一经 Core `canAccessTool()` 执行门禁。
|
|
219
164
|
|
|
220
|
-
##
|
|
165
|
+
## Markdown 与交互输出
|
|
221
166
|
|
|
222
|
-
|
|
167
|
+
QQ Endpoint 声明原生 `markdown` / `keyboard` 能力。普通 AI Markdown 会保留为 QQ Markdown
|
|
168
|
+
消息;命令或 Agent 通过 `UserInteraction.ask()` 发起 `confirm` / `select` 时,会生成
|
|
169
|
+
Markdown + 指令按钮组。按钮值与手动输入共用同一解析入口,不维护 QQ 专用交互状态。
|
|
223
170
|
|
|
224
|
-
|
|
225
|
-
|
|
171
|
+
不支持原生按钮的 Adapter 会在 Core 中把同一 keyboard 语义降级为编号列表,并保存数字到
|
|
172
|
+
payload 的回跳映射。图片、音频、视频和文件仍沿 canonical `MediaRef` 出站链投递。
|
|
226
173
|
|
|
227
|
-
|
|
174
|
+
## 故障排查
|
|
228
175
|
|
|
229
|
-
|
|
230
|
-
|
|
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)
|
|
176
|
+
| 现象 | 排查 |
|
|
177
|
+
| --- | --- |
|
|
178
|
+
| Gateway Identify 后断线 | 检查 `botKind` 与 intents;公域机器人不要订阅受限 intent |
|
|
179
|
+
| 二维码绑定后仍离线 | 在 Console 登录待办确认结果,并检查 App ID、Secret 与 Token |
|
|
180
|
+
| Webhook 无事件 | 检查 HTTP Host、公网路径、平台回调与签名参数 |
|
|
181
|
+
| Markdown/按钮发送失败 | 检查平台模板与权限,并按 Endpoint 能力降级 |
|
|
275
182
|
|
|
276
183
|
## 许可证
|
|
277
184
|
|
|
278
185
|
MIT License
|
|
279
|
-
|
|
280
|
-
## 贡献
|
|
281
|
-
|
|
282
|
-
欢迎提交 Issue 和 Pull Request!
|
package/adapters/qq.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
/**
|
|
3
|
+
* Convention entry: discover `adapters/qq.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
|
+
});
|
package/adapters/qq.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convention entry: discover `adapters/qq.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 QqAdapterConfig,
|
|
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<QqAdapterConfig>({
|
|
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
|
+
});
|
package/agent/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`
|
|
@@ -5,7 +5,7 @@ platforms:
|
|
|
5
5
|
description: >-
|
|
6
6
|
QQ 官方机器人管理能力。当用户在 QQ 官方机器人(非 OneBot/NapCat)中请求群管(踢人、
|
|
7
7
|
禁言、全员禁言)、频道/子频道管理、角色管理(创建/添加/移除角色)、
|
|
8
|
-
或查询成员详情时使用。即使用户没有明确提到 QQ 官方机器人,只要
|
|
8
|
+
或查询成员详情时使用。即使用户没有明确提到 QQ 官方机器人,只要 Endpoint 平台是 qq
|
|
9
9
|
(官方 API)且涉及上述操作,就应触发。仅有昵称时先 list_members 查 user_id。
|
|
10
10
|
keywords:
|
|
11
11
|
- qq
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { platformPermit } from '../../src/platform-permit.js';
|
|
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: [platformPermit('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
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { platformPermit } from '../../src/platform-permit.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{ channel_id: string }>({
|
|
6
|
+
description: '获取 QQ 频道中指定子频道的详细信息',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
channel_id: z.string().describe('子频道 ID'),
|
|
9
|
+
}),
|
|
10
|
+
adapter: 'qq',
|
|
11
|
+
tags: ['qq'],
|
|
12
|
+
permissions: [platformPermit('guild_admin')],
|
|
13
|
+
async execute({ channel_id }: { channel_id: string }, context) {
|
|
14
|
+
const client = context.$client;
|
|
15
|
+
return client.getChannelInfo(channel_id);
|
|
16
|
+
},
|
|
17
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { platformPermit } from '../../src/platform-permit.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{ guild_id: string; name: string; color?: number }>({
|
|
6
|
+
description: '创建 QQ 频道角色',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
guild_id: z.string().describe('频道 ID'),
|
|
9
|
+
name: z.string().describe('角色名称'),
|
|
10
|
+
color: z.number().optional().describe('颜色(RGB 十进制数值)'),
|
|
11
|
+
}),
|
|
12
|
+
adapter: 'qq',
|
|
13
|
+
tags: ['qq'],
|
|
14
|
+
permissions: [platformPermit('guild_owner')],
|
|
15
|
+
async execute({ guild_id, name, color }: { guild_id: string; name: string; color?: number }, context) {
|
|
16
|
+
const client = context.$client;
|
|
17
|
+
const role = await client.createGuildRole(guild_id, name, color);
|
|
18
|
+
return { success: !!role, role, message: role ? `角色 "${name}" 创建成功` : '创建失败' };
|
|
19
|
+
},
|
|
20
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { platformPermit } from '../../src/platform-permit.js';
|
|
4
|
+
|
|
5
|
+
export default defineAgentTool<{ guild_id: string }>({
|
|
6
|
+
description: '获取 QQ 频道下的子频道列表',
|
|
7
|
+
inputSchema: z.object({
|
|
8
|
+
guild_id: z.string().describe('频道 ID'),
|
|
9
|
+
}),
|
|
10
|
+
adapter: 'qq',
|
|
11
|
+
tags: ['qq'],
|
|
12
|
+
permissions: [platformPermit('manage_channels')],
|
|
13
|
+
async execute({ guild_id }: { guild_id: string }, context) {
|
|
14
|
+
const client = context.$client;
|
|
15
|
+
const channels = await client.getChannels(guild_id);
|
|
16
|
+
return { channels, count: channels.length };
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { defineAgentTool } from '@zhin.js/agent/tools';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export default defineAgentTool<Record<string, never>>({
|
|
5
|
+
description: '获取 QQ 频道列表',
|
|
6
|
+
inputSchema: z.object({}),
|
|
7
|
+
adapter: 'qq',
|
|
8
|
+
tags: ['qq'],
|
|
9
|
+
async execute(_input, context) {
|
|
10
|
+
const client = context.$client;
|
|
11
|
+
const guilds = await client.getGuilds();
|
|
12
|
+
return { guilds, count: guilds.length };
|
|
13
|
+
},
|
|
14
|
+
});
|