@zhin.js/adapter-qq 5.0.1 → 5.0.3

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 (134) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +52 -408
  3. package/adapters/qq.ts +45 -0
  4. package/agent/tools/add_role.ts +23 -0
  5. package/agent/tools/channel_info.ts +19 -0
  6. package/agent/tools/create_role.ts +22 -0
  7. package/agent/tools/list_channels.ts +20 -0
  8. package/agent/tools/list_guilds.ts +17 -0
  9. package/agent/tools/list_roles.ts +20 -0
  10. package/agent/tools/member_detail.ts +20 -0
  11. package/agent/tools/remove_role.ts +23 -0
  12. package/lib/endpoint.d.ts +64 -120
  13. package/lib/endpoint.js +279 -564
  14. package/lib/index.d.ts +6 -23
  15. package/lib/index.js +6 -375
  16. package/lib/platform-permit.d.ts +1 -2
  17. package/lib/platform-permit.js +4 -2
  18. package/lib/protocol.d.ts +96 -0
  19. package/lib/protocol.js +129 -0
  20. package/lib/qq-agent-deps.d.ts +21 -0
  21. package/lib/qq-agent-deps.js +30 -0
  22. package/lib/webhook.d.ts +18 -0
  23. package/lib/webhook.js +100 -0
  24. package/lib/ws.d.ts +24 -0
  25. package/lib/ws.js +118 -0
  26. package/package.json +47 -33
  27. package/plugin.ts +13 -0
  28. package/schema.json +51 -0
  29. package/src/endpoint.ts +313 -605
  30. package/src/index.ts +55 -368
  31. package/src/platform-permit.ts +1 -2
  32. package/src/protocol.ts +229 -0
  33. package/src/qq-agent-deps.ts +60 -0
  34. package/src/webhook.ts +127 -0
  35. package/src/ws.ts +172 -0
  36. package/client/Dashboard.tsx +0 -230
  37. package/client/index.tsx +0 -11
  38. package/client/tsconfig.json +0 -7
  39. package/client/utils/api.ts +0 -30
  40. package/dist/index.js +0 -30
  41. package/lib/adapter.d.ts +0 -32
  42. package/lib/adapter.d.ts.map +0 -1
  43. package/lib/adapter.js +0 -82
  44. package/lib/adapter.js.map +0 -1
  45. package/lib/endpoint.d.ts.map +0 -1
  46. package/lib/endpoint.js.map +0 -1
  47. package/lib/gateway-config.d.ts +0 -14
  48. package/lib/gateway-config.d.ts.map +0 -1
  49. package/lib/gateway-config.js +0 -32
  50. package/lib/gateway-config.js.map +0 -1
  51. package/lib/group-at-normalize.d.ts +0 -8
  52. package/lib/group-at-normalize.d.ts.map +0 -1
  53. package/lib/group-at-normalize.js +0 -68
  54. package/lib/group-at-normalize.js.map +0 -1
  55. package/lib/inbound-action.d.ts +0 -15
  56. package/lib/inbound-action.d.ts.map +0 -1
  57. package/lib/inbound-action.js +0 -57
  58. package/lib/inbound-action.js.map +0 -1
  59. package/lib/inbound-normalize.d.ts +0 -11
  60. package/lib/inbound-normalize.d.ts.map +0 -1
  61. package/lib/inbound-normalize.js +0 -47
  62. package/lib/inbound-normalize.js.map +0 -1
  63. package/lib/index.d.ts.map +0 -1
  64. package/lib/index.js.map +0 -1
  65. package/lib/mention-enrich.d.ts +0 -12
  66. package/lib/mention-enrich.d.ts.map +0 -1
  67. package/lib/mention-enrich.js +0 -52
  68. package/lib/mention-enrich.js.map +0 -1
  69. package/lib/outbound-keyboard.d.ts +0 -4
  70. package/lib/outbound-keyboard.d.ts.map +0 -1
  71. package/lib/outbound-keyboard.js +0 -96
  72. package/lib/outbound-keyboard.js.map +0 -1
  73. package/lib/outbound-markdown.d.ts +0 -24
  74. package/lib/outbound-markdown.d.ts.map +0 -1
  75. package/lib/outbound-markdown.js +0 -177
  76. package/lib/outbound-markdown.js.map +0 -1
  77. package/lib/outbound-media.d.ts +0 -11
  78. package/lib/outbound-media.d.ts.map +0 -1
  79. package/lib/outbound-media.js +0 -51
  80. package/lib/outbound-media.js.map +0 -1
  81. package/lib/outbound-mixed-media.d.ts +0 -21
  82. package/lib/outbound-mixed-media.d.ts.map +0 -1
  83. package/lib/outbound-mixed-media.js +0 -78
  84. package/lib/outbound-mixed-media.js.map +0 -1
  85. package/lib/platform-permit.d.ts.map +0 -1
  86. package/lib/platform-permit.js.map +0 -1
  87. package/lib/qq-bind-api.d.ts +0 -37
  88. package/lib/qq-bind-api.d.ts.map +0 -1
  89. package/lib/qq-bind-api.js +0 -90
  90. package/lib/qq-bind-api.js.map +0 -1
  91. package/lib/qq-bind-flow.d.ts +0 -20
  92. package/lib/qq-bind-flow.d.ts.map +0 -1
  93. package/lib/qq-bind-flow.js +0 -86
  94. package/lib/qq-bind-flow.js.map +0 -1
  95. package/lib/qq-bind-persist.d.ts +0 -11
  96. package/lib/qq-bind-persist.d.ts.map +0 -1
  97. package/lib/qq-bind-persist.js +0 -52
  98. package/lib/qq-bind-persist.js.map +0 -1
  99. package/lib/qq-endpoint-manager.d.ts +0 -16
  100. package/lib/qq-endpoint-manager.d.ts.map +0 -1
  101. package/lib/qq-endpoint-manager.js +0 -159
  102. package/lib/qq-endpoint-manager.js.map +0 -1
  103. package/lib/sdk-version.d.ts +0 -8
  104. package/lib/sdk-version.d.ts.map +0 -1
  105. package/lib/sdk-version.js +0 -19
  106. package/lib/sdk-version.js.map +0 -1
  107. package/lib/segment-mapper.d.ts +0 -2
  108. package/lib/segment-mapper.d.ts.map +0 -1
  109. package/lib/segment-mapper.js +0 -2
  110. package/lib/segment-mapper.js.map +0 -1
  111. package/lib/types.d.ts +0 -45
  112. package/lib/types.d.ts.map +0 -1
  113. package/lib/types.js +0 -2
  114. package/lib/types.js.map +0 -1
  115. package/plugin.yml +0 -3
  116. package/src/adapter.ts +0 -101
  117. package/src/gateway-config.ts +0 -48
  118. package/src/group-at-normalize.ts +0 -76
  119. package/src/inbound-action.ts +0 -87
  120. package/src/inbound-normalize.ts +0 -57
  121. package/src/mention-enrich.ts +0 -71
  122. package/src/outbound-keyboard.ts +0 -118
  123. package/src/outbound-markdown.ts +0 -203
  124. package/src/outbound-media.ts +0 -58
  125. package/src/outbound-mixed-media.ts +0 -101
  126. package/src/qq-bind-api.ts +0 -145
  127. package/src/qq-bind-flow.ts +0 -127
  128. package/src/qq-bind-persist.ts +0 -71
  129. package/src/qq-endpoint-manager.ts +0 -199
  130. package/src/sdk-version.ts +0 -21
  131. package/src/segment-mapper.ts +0 -1
  132. package/src/types.ts +0 -53
  133. /package/{skills/qq → agent}/PERMITS.md +0 -0
  134. /package/{skills/qq/SKILL.md → agent/skills/qq.md} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # @zhin.js/adapter-qq
2
2
 
3
+ ## 5.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - cc5c94d: 约定式插件运行时迁移(breaking):插件与适配器由 `usePlugin()` / `extends Adapter` 迁移为 `definePlugin` / `defineAdapter` + `plugin.ts` + 约定目录(`adapters/`、`commands/`、`components/`、`tools/` 等)。
8
+
9
+ - 新增约定式运行时包:`@zhin.js/plugin-runtime`、`@zhin.js/adapter`、`@zhin.js/runtime`、`@zhin.js/host-http`(首版 1.0.0 走 init-publish,不在本 changeset 内 bump)。
10
+ - 全部 20 个平台适配器改为约定式 `defineAdapter`,旧 `usePlugin` / `extends Adapter` / `segment-mapper` 生产入口已删除;onebot11 反向 WSS、onebot12 webhook/wss、milky sse/webhook/wss、satori webhook、kook webhook、qq webhook/middleware 等 slice 1 推迟的连接模式已补齐。
11
+ - 游戏 / 工具 / 服务插件同步迁移到约定目录结构。
12
+ - CLI 增加 plugin-runtime host installer(http/database/outbound/schedule/console 等)。
13
+
14
+ 后续加固(同批):
15
+
16
+ - CLI:`zhin runtime start --daemon`(pidfile/崩溃拉起/风暴保护),orphan watchdog 防僵尸进程;legacy `zhin dev` / `zhin start` 已移除(含 `zhin restart`),`zhin stop` 兼容新 daemon。
17
+ - 安全:builtin 工具统一走 `security/policy-facade.ts` 的 `runToolPolicies`(声明式策略表,deny 优先);审计日志 close flush + 背压队列;`splitCompoundCommand` 引号感知、`extractCommandName` 去引号堵绕过。
18
+ - 日志:Logger 双堆栈修复、本地时区、`getLogger` 挂树(`setLevel` 递归生效)、第三方库(log4js/discord)桥接、启动人读总结。
19
+ - 结构:`plugins/games/shared` 迁为 `packages/game-kit`(`@zhin.js/game-kit`);死目录 `plugins/adapters/common` 删除。
20
+ - 脚手架:`create-zhin-app` / `zhin new` / scaffold-wizard 生成物改为 Plugin Runtime 形态(minimal-bot 同构,新配置格式)。
21
+ - Console:endpoint.list 真实名称与 phase、schema:get-all 按 instanceKey 映射、db:\* 接 DatabaseHost。
22
+
23
+ 注:按仓库发布惯例(见 1bb345dd2),本次 breaking 迁移统一使用 patch,避免 zhin.js 5.0 级联。
24
+
25
+ - 447f3e2: 迁移缺口修复(legacy 功能对齐):
26
+
27
+ - html 段出站规范化:经 `@zhin.js/html-renderer` 渲染为 image 段(sandbox 豁免、无渲染器时降级文本),修复真实平台 `[object Object]`。
28
+ - 群聊 @ 触发 AI:适配器入站标注 `metadata.mentioned`(icqq/qq/slack/onebot11/onebot12/napcat/milky/discord/telegram/kook/dingtalk/satori),`matchAiTrigger` 补齐 ignorePrefixes/respondToAt/respondToPrivate/keywords(默认值与 legacy 对齐)。
29
+ - im_transcripts 全量流水恢复写入(chat_history 工具可用);群聊旁听上下文回迁。
30
+ - `ai.trigger.timeout/thinkingMessage/errorTemplate` 生效;masters/trusted 角色解析对齐 legacy。
31
+ - `Message.sender` 统一为用户 ID(onebot11/12、napcat、milky 原误传显示名);quote_id 经 metadata 接入 AI 引用上下文。
32
+
33
+ - Updated dependencies [16ec4e8]
34
+ - Updated dependencies [cc5c94d]
35
+ - Updated dependencies [447f3e2]
36
+ - @zhin.js/core@1.3.5
37
+ - @zhin.js/agent@1.0.4
38
+ - @zhin.js/host-http@1.0.1
39
+ - zhin.js@4.1.3
40
+ - @zhin.js/logger@1.0.75
41
+ - @zhin.js/plugin-runtime@1.0.1
42
+ - @zhin.js/adapter@1.0.1
43
+
44
+ ## 5.0.2
45
+
46
+ ### Patch Changes
47
+
48
+ - 872c583: Slack 适配器 Phase 1/2:mrkdwn 出站、长消息切分、斜杠/按钮 ephemeral 反馈、入站 mrkdwn→Markdown、editMessage 对齐 core。
49
+
50
+ Logger 表格日志与 string-width 列宽;Agent AI Handler 框线表格与 introspection/MCP 导出;Core side-event 归一化;Schedule 时区规划;多适配器 side-event 与 API surface 更新。
51
+
52
+ - 872c583: fix: 代码格式优化
53
+ - Updated dependencies [872c583]
54
+ - Updated dependencies [872c583]
55
+ - @zhin.js/agent@1.0.3
56
+ - @zhin.js/client@2.0.5
57
+ - @zhin.js/contract@1.0.3
58
+ - @zhin.js/host-api@2.0.5
59
+ - @zhin.js/host-router@2.0.3
60
+ - @zhin.js/logger@1.0.74
61
+ - zhin.js@4.1.2
62
+
3
63
  ## 5.0.1
4
64
 
5
65
  ### Patch Changes
package/README.md CHANGED
@@ -1,6 +1,15 @@
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({ target, payload })` → QQ API(`private:` / `group:` / `channel:` / `direct:`)
10
+ - 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
11
+ - Webhook / middleware 模式已实现(经 `httpHostToken` 注册 POST 路由)
12
+ - AI `@` 触发标注:群消息(GROUP_AT_MESSAGE_CREATE 仅 @ 时下发)与频道 `mentions[].bot` 会在入站 metadata 标 `mentioned: true`(新 Plugin Runtime 纯文本 content 经 metadata 传递 @)
4
13
 
5
14
  ## 安装
6
15
 
@@ -8,434 +17,69 @@ Zhin.js QQ 官方机器人适配器,基于 QQ 官方机器人 API 开发,支
8
17
  pnpm add @zhin.js/adapter-qq
9
18
  ```
10
19
 
11
- ## 扫码添加机器人(/endpoint)
12
-
13
- 无需手动复制 AppID/AppSecret:在 IM 中由 **master / trusted** 操作员发送命令,适配器会内联调用 QQ 开放平台绑定协议(等价 `@tencent-connect/qqbot-connector`,**不依赖**该 npm 包),扫码成功后:
14
-
15
- - 凭据写入项目根 **`.env`**(键名 `QQ_<name>_APPID` / `QQ_<name>_SECRET`)
16
- - `zhin.config.yml` 仅保留 **`${...}` 引用**(与脚手架其它 adapter 一致)
17
- - **`master`** 与 **`aiAccess.users`** 自动设为 `poll_bind_result` 返回的扫码者 OpenID(接口无该字段时回退为发起命令的用户 ID)
18
-
19
- | 命令 | 说明 |
20
- |------|------|
21
- | `/endpoint add qq [name]` | 发起扫码绑定;`name` 默认使用 AppID |
22
- | `/endpoint sync` | 将内存 endpoint 写回 `zhin.config.yml` |
23
- | `/endpoint cancel` | 取消进行中的绑定 |
24
- | `/endpoints` | 查看运行时 qq endpoints 在线状态 |
25
- | `/endpoint help` | Endpoint 管理帮助 |
26
-
27
- 二维码通过 IM `$reply` 发送 `segment.qrcode(url)`;各 Adapter 在 **`$sendMessage`** 内解析该段(IM → `image`,process → 终端 ASCII,GitHub 等 → 文本链接)。
28
-
29
- 二维码渲染使用 core 的 `segment.qrcode` / `GeneratedQrCode`(`import { segment } from 'zhin.js'` 或 `import { GeneratedQrCode } from 'zhin.js/qrcode'`)。
30
-
31
- ## AIGC 合规(AI 白名单)
32
-
33
- QQ 开放平台限制 AIGC 进入社群场景、禁止面向全量用户开放生成式能力。请配置 **`endpoints[].aiAccess`**(推荐)或全局 **`ai.access`**,仅 gate LLM 回复;斜杠命令、游戏插件等不受影响。
34
-
35
- ```yaml
36
- endpoints:
37
- - context: qq
38
- name: my-bot
39
- appid: ...
40
- secret: ...
41
- aiAccess:
42
- mode: whitelist
43
- users: ['QQ用户openid']
44
- groups: ['QQ群openid']
45
- denyMessage: 当前未开放 AI,请联系管理员。
46
- ```
47
-
48
- 多 Endpoint 时可分别设置(例如正式服 `whitelist`、沙箱 `open`)。未写 `endpoints[].aiAccess` 时回退到全局 `ai.access`。
20
+ ## Plugin Runtime
49
21
 
50
- - 群/频道未放行:**静默**(不回复 AI)
51
- - 私聊未放行:回复 `denyMessage`
52
- - 白名单:`users` 与 `groups` **OR** 匹配
22
+ - `@zhin.js/adapter` — 约定式 `adapters/qq.ts`(`defineAdapter`)
23
+ - `@zhin.js/core` — `messageGatewayToken` 入站/出站
24
+ - `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
25
+ - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
+ - **无需** `@zhin.js/host-http` / `@zhin.js/host-router`(WebSocket 路径)
53
27
 
54
- 详见 [内容审查 / AI Access Gate](../../../docs/advanced/content-moderation.md)。
28
+ 入站:`gateway.receive({ adapter, target: 'group:…'|…, content, sender, metadata })`
29
+ 出站:`send({ target, payload })` → `sendPrivateMessage` / `sendGroupMessage` / `sendGuildMessage`
55
30
 
56
- ## 配置
57
-
58
- ### 基础配置
59
-
60
- ```typescript
61
- import { defineConfig } from 'zhin.js';
62
-
63
- export default defineConfig({
64
- endpoints: [
65
- {
66
- context: 'qq',
67
- name: 'my-qq-bot',
68
- appid: process.env.QQ_APPID,
69
- secret: process.env.QQ_SECRET,
70
- mode: 'middleware',
71
- application: 'koa',
72
- webhookPath: '/qq/webhook',
73
- sandbox: true,
74
- data_dir: './data',
75
- },
76
- ],
77
- plugins: [
78
- '@zhin.js/adapter-qq',
79
- ],
80
- })
81
- ```
82
-
83
- ### 完整配置选项
84
-
85
- ```typescript
86
- const config: QQEndpointConfig = {
87
- context: 'qq',
88
- name: 'my-qq-bot',
89
- appid: 'YOUR_APPID', // 机器人 AppID(必需,小写 appid)
90
- secret: 'YOUR_SECRET', // 机器人 Secret(必需)
91
- mode: 'middleware', // websocket | webhook | middleware(推荐挂 host-router)
92
- application: 'koa', // middleware 模式必填
93
- webhookPath: '/qq/webhook', // middleware 回调路径(完整 URL: {host}:8086/qq/webhook,无 /api 前缀)
94
- // mode: 'webhook', // 独立 HTTP 端口模式
95
- // port: 8087,
96
- // path: '/qq/webhook',
97
- platform: 'qq', // 'qq' | 'qzone' 平台类型
98
- intents: [ // 事件订阅意图(qq-official-bot ^1.2 使用 GROUP_AND_C2C_EVENT)
99
- 'GROUP_AND_C2C_EVENT', // QQ 群 @ 消息 + C2C 私聊(必需)
100
- 'GUILDS',
101
- 'GUILD_MEMBERS',
102
- 'GUILD_MESSAGES', // 频道私域 @ 消息(公域用 PUBLIC_GUILD_MESSAGES)
103
- 'DIRECT_MESSAGE',
104
- 'PUBLIC_GUILD_MESSAGES',
105
- ],
106
- data_dir: './data', // 数据目录(可选)
107
- sandbox: false // 是否为沙箱环境(可选)
108
- }
109
- ```
110
-
111
- ## 入站模式(二选一)
112
-
113
- | 模式 | 配置 | 说明 |
114
- |------|------|------|
115
- | **middleware**(推荐) | `mode: middleware` + `application: koa` + `webhookPath` | 挂在 `@zhin.js/host-router`,与 Host **同端口**(如 `8086`) |
116
- | **webhook**(独立端口) | `mode: webhook` + `port` + `path` | `qq-official-bot` 自建 HTTP 服务,需单独暴露端口 |
117
- | websocket | `mode: websocket` | 长连接,无需公网回调 |
118
-
119
- ### 自定义网关(qq-official-bot ^1.2.2)
120
-
121
- 经代理或私有部署时,可覆盖官方 token / gateway 接口:
122
-
123
- ```yaml
124
- # WebSocket 入站:gatewayUrl 与 accessTokenUrl 均生效
125
- - context: qq
126
- name: my-qq-bot
127
- mode: websocket
128
- appid: ${QQ_APPID}
129
- secret: ${QQ_SECRET}
130
- accessTokenUrl: https://your-proxy.example.com/app/getAppAccessToken
131
- gatewayUrl: https://your-proxy.example.com/gateway/bot
132
- intents:
133
- - GROUP_AND_C2C_EVENT
134
- ```
31
+ ## 前置条件
135
32
 
136
- ```yaml
137
- # middleware / webhook:仅 accessTokenUrl 影响出站鉴权(入站走 HTTP 回调,不用 gateway)
138
- - context: qq
139
- name: my-qq-bot
140
- mode: middleware
141
- application: koa
142
- webhookPath: /qq/webhook
143
- accessTokenUrl: https://your-proxy.example.com/app/getAppAccessToken
144
- ```
145
-
146
- - `gatewayUrl` 可为完整 URL 或相对路径(默认 `/gateway/bot`,相对 `api.sgroup.qq.com` baseURL)
147
- - 连接时日志会输出 `op: qq_gateway` 及实际使用的地址
148
-
149
- ### middleware 回调地址(test-bot 默认)
150
-
151
- 1. 确保 `plugins` 含 `@zhin.js/host-router`(test-bot 已启用)
152
- 2. `zhin.config.yml` 示例见上文 `webhookPath: /qq/webhook`
153
- 3. 在 [QQ 开放平台](https://q.qq.com/bot) → **开发 → 回调配置** 填写:
154
-
155
- ```
156
- https://<你的公网域名>/qq/webhook
157
- ```
158
-
159
- 本地:`http://127.0.0.1:8086/qq/webhook`(经 ngrok / cloudflared 暴露公网即可)。
160
-
161
- > **注意**:Host 的 Console/API 走 `/api/*`(如 `/api/stats`),但适配器 webhook 直接挂在 Router 根路径,**不要**加 `/api` 前缀;填 `/api/qq/webhook` 会命中 Bearer 鉴权,QQ 平台回调会 401。
162
-
163
- 4. 勾选事件:**群事件**、**C2C 私聊**、**频道事件**(按需)
164
- 5. 保存时平台会发 `SIGN_VERIFY` 签名校验,适配器会自动响应
165
-
166
- > Host 对含 `/webhook` 的路径**免 Bearer 鉴权**,由 QQ 的 `X-Signature-Ed25519` 验签。
167
-
168
- ### webhook 独立端口
169
-
170
- ```yaml
171
- mode: webhook
172
- port: 8087
173
- path: /qq/webhook
174
- ```
175
-
176
- 回调填 `https://<域名>:8087/qq/webhook`(或反代到该端口)。
177
-
178
- ## 获取配置信息
179
-
180
- ### 1. 注册 QQ 机器人
181
-
182
- 1. 访问 [QQ 开放平台](https://q.qq.com/bot)
183
- 2. 登录并创建机器人应用
184
- 3. 在「开发设置」中获取:
185
- - **AppID**(配置字段 `appid`): 机器人应用 ID
186
- - **Secret**: 机器人密钥(`secret`)
187
- - **Secret**: 机器人密钥
188
-
189
- ### 2. 配置权限
190
-
191
- 在机器人设置中:
192
- - 配置需要的事件订阅
193
- - 设置消息接收模式(公域/私域)
194
- - 添加频道/群聊白名单(如需要)
195
-
196
- ### 3. 事件订阅(Intents)
197
-
198
- 可订阅的事件类型:
199
- - `GUILDS` - 频道事件
200
- - `GUILD_MEMBERS` - 成员变动
201
- - `GUILD_MESSAGES` - 频道消息
202
- - `DIRECT_MESSAGE` - 私信消息
203
- - `GROUP_AND_C2C_EVENT` - QQ 群 @ 消息 + C2C 私聊(`GROUP_AT_MESSAGE_CREATE` / `C2C_MESSAGE_CREATE`)
204
- - `INTERACTION` - 互动事件
205
-
206
- ## 使用示例
207
-
208
- ### 基础消息处理
209
-
210
- ```typescript
211
- import { addCommand, MessageCommand } from 'zhin.js'
212
-
213
- addCommand(new MessageCommand('hello <name:text>')
214
- .action(async (message, result) => {
215
- return `你好,${result.params.name}!`
216
- })
217
- )
218
- ```
219
-
220
- ### 频道消息
221
-
222
- ```typescript
223
- import { onMessage } from 'zhin.js'
224
-
225
- onMessage(async (message) => {
226
- // 仅处理频道消息
227
- if (message.$channel.type === 'channel') {
228
- console.log(`频道消息:${message.$raw}`)
229
- }
230
- })
231
- ```
232
-
233
- ### 群聊消息
234
-
235
- ```typescript
236
- import { onGroupMessage } from 'zhin.js'
237
-
238
- onGroupMessage(async (message) => {
239
- console.log(`群聊消息来自:${message.$sender.name}`)
240
- console.log(`消息内容:${message.$raw}`)
241
- })
242
- ```
243
-
244
- ### 私聊消息
245
-
246
- ```typescript
247
- import { onPrivateMessage } from 'zhin.js'
248
-
249
- onPrivateMessage(async (message) => {
250
- await message.$reply('收到你的私信了!')
251
- })
252
- ```
253
-
254
- ### 发送不同类型消息
255
-
256
- ```typescript
257
- addCommand(new MessageCommand('card')
258
- .action(async (message) => {
259
- // 发送 Ark 模板卡片
260
- return {
261
- type: 'ark',
262
- template_id: 23,
263
- kv: [
264
- { key: '#TITLE#', value: '标题' },
265
- { key: '#DESC#', value: '描述' },
266
- { key: '#PROMPT#', value: '提示' }
267
- ]
268
- }
269
- })
270
- )
271
-
272
- addCommand(new MessageCommand('embed')
273
- .action(async (message) => {
274
- // 发送 Embed 消息
275
- return {
276
- type: 'embed',
277
- title: 'Embed 标题',
278
- prompt: '消息提示',
279
- thumbnail: { url: 'https://example.com/image.png' },
280
- fields: [
281
- { name: '字段1', value: '值1' }
282
- ]
283
- }
284
- })
285
- )
286
- ```
287
-
288
- ## 消息类型支持
289
-
290
- ### 接收消息类型
291
-
292
- - ✅ 文本消息
293
- - ✅ @ 提及
294
- - ✅ 图片消息
295
- - ✅ 表情消息
296
- - ✅ Ark 模板消息
297
- - ✅ Embed 消息
298
- - ✅ Markdown 消息
299
-
300
- ### 发送消息类型
301
-
302
- - ✅ 文本消息
303
- - ✅ 图片消息
304
- - ✅ Ark 模板消息
305
- - ✅ Embed 富文本消息
306
- - ✅ Markdown 消息
307
- - ✅ 消息引用(回复)
308
-
309
- ### AI 出站 Markdown(默认开启)
33
+ | 要求 | 说明 |
34
+ |------|------|
35
+ | **AppID / Secret** | [QQ 开放平台](https://q.qq.com/) 创建机器人应用并获取 |
36
+ | **WebSocket(默认)** | `qq-official-bot` 正向连接;无需公网回调 |
37
+ | **host-http** | WebSocket **不需要**;Webhook / middleware 模式需要(经 `httpHostToken`) |
310
38
 
311
- 官方已支持全量 Markdown 后,适配器会在 `$sendMessage` 出站前自动把 AI 的纯文本段转为 `msg_type=2` 的 Markdown 消息(保留 leading `reply`)。配置项 `outboundMarkdown`:
39
+ 必填字段:`appid`、`secret`。
312
40
 
313
- | 值 | 行为 |
314
- |---|---|
315
- | `auto`(默认) | 正文含 `**`、列表、代码块等 Markdown 语法时才转换 |
316
- | `true` | 纯文本也走 Markdown |
317
- | `false` | 始终发纯文本(与旧行为一致) |
41
+ ## 最小配置
318
42
 
319
43
  ```yaml
320
- endpoints:
321
- - context: qq
322
- name: my-bot
323
- outboundMarkdown: auto # 或 true / false
324
- ```
325
-
326
- 含图片/文件等富媒体的多段消息不会合并,避免破坏分条发送。
327
-
328
- **图文混排**(文本 + 图片,典型如二维码说明 + `segment.qrcode`)会自动处理:
329
-
330
- | 图片类型 | 行为 |
331
- |---|---|
332
- | 公网 `https` URL | 合并为单条 Markdown(`msg_type=2`),图片用 `![说明 #宽 #高](url)` |
333
- | 内联 base64 / data URI(如二维码渲染) | 群聊/私聊先上传 `file_info`,再以 `msg_type=7` 图文混排同条发送 |
334
-
335
- `outboundMarkdown: false` 时关闭上述合并,恢复分条发送。
336
-
337
- ## API 方法
338
-
339
- ### 基础方法
340
-
341
- ```typescript
342
- const endpoint = app.adapters.get('qq')?.endpoints.get('你的机器人ID')
343
-
344
- // 发送私信
345
- await endpoint.sendPrivateMessage(userId, '消息内容')
346
-
347
- // 发送群消息
348
- await endpoint.sendGroupMessage(groupId, '消息内容')
349
-
350
- // 发送频道消息
351
- await endpoint.sendGuildMessage(channelId, '消息内容')
352
-
353
- // 撤回消息
354
- await endpoint.$recallMessage(messageId)
44
+ # zhin.config.yml(Plugin Runtime)
45
+ plugins:
46
+ qq:
47
+ name: my-qq-bot
48
+ appid: ${QQ_APPID}
49
+ secret: ${QQ_SECRET}
50
+ # mode: websocket # 默认
355
51
  ```
356
52
 
357
- ## 消息 ID 格式
358
-
359
- 本适配器使用特殊的消息 ID 格式来区分不同类型的消息:
360
-
361
- - 私信:`private-{userId}:{messageId}`
362
- - 群聊:`group-{groupId}:{messageId}`
363
- - 频道:`channel-{channelId}:{messageId}`
364
- - 私域频道:`direct-{guildId}:{messageId}`
365
-
366
- ## 注意事项
367
-
368
- ### 接收模式
369
-
370
- - **公域模式 (public)**: 仅接收 @ 机器人的消息
371
- - **私域模式 (private)**: 可接收频道内所有消息(需要申请权限)
372
-
373
- ### 频率限制
53
+ 根插件 `zhin.plugins`(或项目图)需引用 `@zhin.js/adapter-qq`(`instanceKey: qq`)。
374
54
 
375
- QQ 机器人有严格的频率限制:
376
- - 主动消息:每个用户每天 5 条
377
- - 被动消息(回复):无限制
378
- - 建议在被动模式下使用(用户 @ 后回复)
55
+ ## 环境变量
379
56
 
380
- ### 沙箱环境
381
-
382
- 开发时可以使用沙箱环境测试:
383
-
384
- ```typescript
385
- {
386
- context: 'qq',
387
- sandbox: true, // 启用沙箱
388
- // ...其他配置
389
- }
390
- ```
391
-
392
- ## 常见问题
393
-
394
- ### Q: 机器人无法收到消息?
395
-
396
- A: 检查以下几点:
397
- 1. AppID、Secret 是否正确;`intents` 必须含 **`GROUP_AND_C2C_EVENT`**(不是旧名 `GROUP_AT_MESSAGE_CREATE`)
398
- 2. **沙箱环境**:在 [QQ 开放平台](https://q.qq.com/bot) → 开发设置 → 沙箱配置,把测试 **QQ 群** 加入沙箱白名单(私聊能通不代表群已配置)
399
- 3. 机器人是否已被拉入该 QQ 群(日志应出现 `qq notice group.add_robot`)
400
- 4. 群管理员是否在机器人资料页 **开启消息接收**(`qq notice group.msg_receive_open`);若出现 `group.msg_receive_close` 则群聊不会推送
401
- 5. 公域/群聊场景必须 **@ 机器人** 才会推送(`GROUP_AT_MESSAGE_CREATE`)
402
- 6. **QQ 频道** 与 **QQ 群** 是两套事件:频道 @ 需要 `GUILD_MESSAGES` 或 `PUBLIC_GUILD_MESSAGES`,不是 `GROUP_AND_C2C_EVENT`
403
-
404
- ### Q: Typing Indicator 群聊报 `40034105 主动消息失败, 无权限`?
57
+ | 变量 | 说明 |
58
+ |------|------|
59
+ | `QQ_APPID` / `QQ_BOT_APPID` | 应用 AppID |
60
+ | `QQ_SECRET` / `QQ_BOT_SECRET` | 应用 Secret |
61
+ | `QQ_BOT_NAME` | 可选,默认 endpoint 名 |
405
62
 
406
- A: QQ **群聊**不允许无引用的主动消息。框架已对 QQ 群场景的「正在处理中…」自动附带 `reply` 引用触发消息;若仍失败,可在 Endpoint 配置中关闭群聊 typing:
63
+ ## Webhook / middleware
407
64
 
408
- ```yaml
409
- typingIndicator:
410
- enabled: true
411
- groupConfig:
412
- type: none
413
- ```
65
+ `mode: webhook` 或 `mode: middleware` 经 `httpHostToken` 注册 POST 路由(默认 `/qq/webhook`),使用 qq-official-bot Middleware 接收器验签并入站;出站仍走 QQ HTTP API。Host 需注入 `httpHostToken`。
414
66
 
415
- ### Q: 发送消息失败?
67
+ ## AI 工具(Skill)
416
68
 
417
- A: 可能的原因:
418
- 1. 超过主动消息频率限制
419
- 2. 没有对应频道/群的发送权限
420
- 3. 消息格式不符合规范
421
- 4. Token 已过期或失效
69
+ | 类别 | 路径 |
70
+ |------|------|
71
+ | Permit 词汇 | `agent/PERMITS.md` |
72
+ | 平台工具 | `agent/tools/`(频道、角色等) |
73
+ | 技能说明 | `agent/skills/qq.md` |
422
74
 
423
- ### Q: 如何处理不同平台?
75
+ ## 平台权限(platform permit)
424
76
 
425
- A: 使用 `platform` 配置:
426
- - `qq`: QQ 频道和群聊
427
- - `qzone`: QQ 空间(如支持)
77
+ platform permit checker 由 `plugin.ts` 的 generation 生命周期注册;`@zhin.js/tool` descriptor 保留 `platforms` / `scopes` / `permissions`,CapabilityIngress 与 ToolSystem 统一经 Core `canAccessTool()` 执行门禁。
428
78
 
429
- ## 相关链接
79
+ ## 迁移后出站能力变化
430
80
 
431
- - [QQ 开放平台](https://q.qq.com/bot)
432
- - [QQ 机器人开发文档](https://bot.q.qq.com/wiki/)
433
- - [Zhin.js 官方文档](https://github.com/zhinjs/zhin)
81
+ 迁移到 Plugin Runtime 后,出站统一经 `messageGatewayToken` 渲染为文本后发送(`sendPrivateMessage` / `sendGroupMessage` / `sendGuildMessage`)。旧 Adapter 的富媒体出站能力(图片 / 语音 / 视频、keyboard 按钮、markdown 模板等)暂未迁移,当前出站等价于纯文本。如需富媒体,可通过 endpoint 的 QQ API 封装或直接调用 QQ HTTP API 作为逃生舱。
434
82
 
435
83
  ## 许可证
436
84
 
437
85
  MIT License
438
-
439
- ## 贡献
440
-
441
- 欢迎提交 Issue 和 Pull Request!
package/adapters/qq.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Convention entry: discover `adapters/qq.ts` → defineAdapter.
3
+ */
4
+ import { defineAdapter } from '@zhin.js/adapter';
5
+ import { messageGatewayToken } from '@zhin.js/core/runtime';
6
+ import { httpHostToken } from '@zhin.js/host-http';
7
+ import { QqHttpEndpoint, QqWebsocketEndpoint } from '../src/endpoint.js';
8
+ import {
9
+ resolveQqConfig,
10
+ type QqAdapterConfig,
11
+ } from '../src/protocol.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
+ export default defineAdapter<QqAdapterConfig>({
27
+ capabilities: ['inbound', 'outbound'],
28
+ create(context) {
29
+ const config = resolveQqConfig(context.config);
30
+ const gateway = context.use(messageGatewayToken);
31
+ if (config.mode === 'webhook' || config.mode === 'middleware') {
32
+ return new QqHttpEndpoint({
33
+ id: context.id,
34
+ gateway,
35
+ http: context.use(httpHostToken),
36
+ config,
37
+ });
38
+ }
39
+ return new QqWebsocketEndpoint({
40
+ id: context.id,
41
+ gateway,
42
+ config,
43
+ });
44
+ },
45
+ });
@@ -0,0 +1,23 @@
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
+ import { z } from 'zod';
3
+ import { platformPermit } from '../../src/platform-permit.js';
4
+ import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
+
6
+ export default defineAgentTool<{ endpoint_id: string; guild_id: string; channel_id: string; user_id: string; role_id: string }>({
7
+ description: '给成员添加 QQ 频道角色',
8
+ inputSchema: z.object({
9
+ endpoint_id: z.string().describe('Endpoint 名称'),
10
+ guild_id: z.string().describe('频道 ID'),
11
+ channel_id: z.string().describe('子频道 ID'),
12
+ user_id: z.string().describe('用户 ID'),
13
+ role_id: z.string().describe('角色 ID'),
14
+ }),
15
+ platforms: ['qq'],
16
+ tags: ['qq'],
17
+ permissions: [platformPermit('manage_roles')],
18
+ async execute({ endpoint_id, guild_id, channel_id, user_id, role_id }: { endpoint_id: string; guild_id: string; channel_id: string; user_id: string; role_id: string }) {
19
+ const endpoint = getQqAgentDeps().getEndpoint(endpoint_id);
20
+ const success = await endpoint.addMemberRole(guild_id, channel_id, user_id, role_id);
21
+ return { success, message: success ? '已给成员添加角色' : '操作失败' };
22
+ },
23
+ });
@@ -0,0 +1,19 @@
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
+ import { z } from 'zod';
3
+ import { platformPermit } from '../../src/platform-permit.js';
4
+ import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
+
6
+ export default defineAgentTool<{ endpoint_id: string; channel_id: string }>({
7
+ description: '获取 QQ 频道中指定子频道的详细信息',
8
+ inputSchema: z.object({
9
+ endpoint_id: z.string().describe('Endpoint 名称'),
10
+ channel_id: z.string().describe('子频道 ID'),
11
+ }),
12
+ platforms: ['qq'],
13
+ tags: ['qq'],
14
+ permissions: [platformPermit('guild_admin')],
15
+ async execute({ endpoint_id, channel_id }: { endpoint_id: string; channel_id: string }) {
16
+ const endpoint = getQqAgentDeps().getEndpoint(endpoint_id);
17
+ return endpoint.getChannelInfo(channel_id);
18
+ },
19
+ });
@@ -0,0 +1,22 @@
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
+ import { z } from 'zod';
3
+ import { platformPermit } from '../../src/platform-permit.js';
4
+ import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
+
6
+ export default defineAgentTool<{ endpoint_id: string; guild_id: string; name: string; color?: number }>({
7
+ description: '创建 QQ 频道角色',
8
+ inputSchema: z.object({
9
+ endpoint_id: z.string().describe('Endpoint 名称'),
10
+ guild_id: z.string().describe('频道 ID'),
11
+ name: z.string().describe('角色名称'),
12
+ color: z.number().optional().describe('颜色(RGB 十进制数值)'),
13
+ }),
14
+ platforms: ['qq'],
15
+ tags: ['qq'],
16
+ permissions: [platformPermit('guild_owner')],
17
+ async execute({ endpoint_id, guild_id, name, color }: { endpoint_id: string; guild_id: string; name: string; color?: number }) {
18
+ const endpoint = getQqAgentDeps().getEndpoint(endpoint_id);
19
+ const role = await endpoint.createGuildRole(guild_id, name, color);
20
+ return { success: !!role, role, message: role ? `角色 "${name}" 创建成功` : '创建失败' };
21
+ },
22
+ });