@zhin.js/adapter-qq 7.0.2 → 7.0.5
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 +96 -0
- package/README.md +36 -9
- package/adapters/qq.js +9 -7
- package/adapters/qq.ts +9 -7
- package/commands/endpoint/cancel.js +1 -1
- package/commands/endpoint/cancel.ts +1 -1
- package/lib/endpoint.d.ts +3 -9
- package/lib/endpoint.js +79 -75
- package/lib/index.d.ts +3 -2
- package/lib/index.js +3 -2
- package/lib/platform-permit.d.ts +2 -3
- package/lib/platform-permit.js +3 -11
- package/lib/protocol.d.ts +14 -11
- package/lib/protocol.js +26 -19
- package/lib/qq-agent-deps.d.ts +2 -2
- package/lib/qq-agent-deps.js +7 -7
- package/lib/qq-bind-persist.d.ts +2 -2
- package/lib/qq-bind-persist.js +5 -5
- 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 +13 -6
- package/lib/qq-endpoint-commands.js +89 -31
- package/lib/qq-endpoint-config.d.ts +4 -4
- package/lib/qq-endpoint-config.js +4 -4
- package/lib/qq-intents.d.ts +27 -0
- package/lib/qq-intents.js +50 -0
- package/lib/qq-runtime-state.d.ts +17 -4
- package/lib/qq-runtime-state.js +3 -2
- package/lib/webhook.js +2 -0
- package/lib/ws.js +2 -2
- package/middlewares/bot-kind-reply.js +46 -0
- package/middlewares/bot-kind-reply.ts +60 -0
- package/package.json +23 -15
- package/plugin.js +6 -4
- package/schema.json +66 -9
- package/src/endpoint.ts +95 -79
- package/src/index.ts +10 -4
- package/src/platform-permit.ts +4 -9
- package/src/protocol.ts +34 -28
- package/src/qq-agent-deps.ts +8 -8
- package/src/qq-bind-persist.ts +5 -5
- package/src/qq-bot-kind-prompt.ts +47 -0
- package/src/qq-endpoint-commands.ts +125 -34
- package/src/qq-endpoint-config.ts +5 -5
- package/src/qq-intents.ts +76 -0
- package/src/qq-runtime-state.ts +21 -6
- package/src/webhook.ts +2 -0
- package/src/ws.ts +2 -2
- /package/commands/endpoint/add/{[name:string=].js → [[id]].js} +0 -0
- /package/commands/endpoint/add/{[name:string=].ts → [[id]].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [id].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# @zhin.js/adapter-qq
|
|
2
2
|
|
|
3
|
+
## 7.0.5
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 51015d6: refactor(adapters): migrate platform-permit to PermissionSubject
|
|
8
|
+
|
|
9
|
+
All adapter platform-permit checkers now accept PermissionSubject
|
|
10
|
+
(duck-typed from Message/CommandSession) instead of Message directly.
|
|
11
|
+
registerPlatformPermitChecker is replaced by host.registerPlatform in
|
|
12
|
+
the adapter plugin setup. activity-feedback service updated for new
|
|
13
|
+
PermissionHost integration.
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [6f9c366]
|
|
16
|
+
- Updated dependencies [373a56b]
|
|
17
|
+
- Updated dependencies [0de46a8]
|
|
18
|
+
- Updated dependencies [c106ecc]
|
|
19
|
+
- Updated dependencies [ca92e03]
|
|
20
|
+
- Updated dependencies [b0f37ae]
|
|
21
|
+
- Updated dependencies [ba08a2f]
|
|
22
|
+
- Updated dependencies [b08f7fe]
|
|
23
|
+
- Updated dependencies [daffd4c]
|
|
24
|
+
- Updated dependencies [36c7400]
|
|
25
|
+
- Updated dependencies [a9fa72e]
|
|
26
|
+
- Updated dependencies [c8de3ef]
|
|
27
|
+
- Updated dependencies [60f0fc8]
|
|
28
|
+
- Updated dependencies [574c990]
|
|
29
|
+
- Updated dependencies [d096f16]
|
|
30
|
+
- Updated dependencies [3f29623]
|
|
31
|
+
- Updated dependencies [2916852]
|
|
32
|
+
- Updated dependencies [d047869]
|
|
33
|
+
- Updated dependencies [162fa34]
|
|
34
|
+
- Updated dependencies [e62561e]
|
|
35
|
+
- Updated dependencies [3eeeb46]
|
|
36
|
+
- Updated dependencies [85d0f82]
|
|
37
|
+
- Updated dependencies [61bfc1c]
|
|
38
|
+
- Updated dependencies [04bad47]
|
|
39
|
+
- Updated dependencies [e40b048]
|
|
40
|
+
- Updated dependencies [5a5b1bb]
|
|
41
|
+
- Updated dependencies [f1708c3]
|
|
42
|
+
- Updated dependencies [d254a81]
|
|
43
|
+
- Updated dependencies [7123c47]
|
|
44
|
+
- Updated dependencies [05befc1]
|
|
45
|
+
- Updated dependencies [0663b6a]
|
|
46
|
+
- Updated dependencies [f28f9b3]
|
|
47
|
+
- Updated dependencies [9f340f7]
|
|
48
|
+
- Updated dependencies [e53444f]
|
|
49
|
+
- Updated dependencies [5eedd26]
|
|
50
|
+
- Updated dependencies [92b0dd7]
|
|
51
|
+
- Updated dependencies [f919b6f]
|
|
52
|
+
- Updated dependencies [098e411]
|
|
53
|
+
- Updated dependencies [e1b7c01]
|
|
54
|
+
- Updated dependencies [9b94f87]
|
|
55
|
+
- Updated dependencies [a7df753]
|
|
56
|
+
- @zhin.js/agent@1.1.5
|
|
57
|
+
- @zhin.js/host-http@1.0.8
|
|
58
|
+
- @zhin.js/permission@1.0.1
|
|
59
|
+
- @zhin.js/im-contract@1.0.3
|
|
60
|
+
- @zhin.js/adapter@1.1.7
|
|
61
|
+
- @zhin.js/plugin-runtime@1.1.5
|
|
62
|
+
- @zhin.js/command@1.0.9
|
|
63
|
+
- @zhin.js/core@1.5.4
|
|
64
|
+
- zhin.js@6.0.4
|
|
65
|
+
- @zhin.js/middleware@1.0.8
|
|
66
|
+
|
|
67
|
+
## 7.0.4
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- f8c7a54: fix: im
|
|
72
|
+
- Updated dependencies [f8c7a54]
|
|
73
|
+
- @zhin.js/logger@1.0.76
|
|
74
|
+
- @zhin.js/host-http@1.0.7
|
|
75
|
+
- @zhin.js/adapter@1.1.6
|
|
76
|
+
- @zhin.js/agent@1.1.4
|
|
77
|
+
- @zhin.js/command@1.0.8
|
|
78
|
+
- @zhin.js/core@1.5.3
|
|
79
|
+
- @zhin.js/im-contract@1.0.2
|
|
80
|
+
- @zhin.js/plugin-runtime@1.1.4
|
|
81
|
+
- zhin.js@6.0.3
|
|
82
|
+
|
|
83
|
+
## 7.0.3
|
|
84
|
+
|
|
85
|
+
### Patch Changes
|
|
86
|
+
|
|
87
|
+
- Updated dependencies [afc0e66]
|
|
88
|
+
- Updated dependencies [2e41ad5]
|
|
89
|
+
- Updated dependencies [9f57124]
|
|
90
|
+
- @zhin.js/core@1.5.2
|
|
91
|
+
- @zhin.js/adapter@1.1.5
|
|
92
|
+
- @zhin.js/im-contract@1.0.1
|
|
93
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
94
|
+
- @zhin.js/command@1.0.7
|
|
95
|
+
- @zhin.js/host-http@1.0.6
|
|
96
|
+
- @zhin.js/agent@1.1.3
|
|
97
|
+
- zhin.js@6.0.2
|
|
98
|
+
|
|
3
99
|
## 7.0.2
|
|
4
100
|
|
|
5
101
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Zhin.js QQ 官方机器人适配器(Plugin Runtime),默认通过 **WebSock
|
|
|
6
6
|
|
|
7
7
|
- WebSocket Gateway 入站(默认;无需公网 HTTPS / host)
|
|
8
8
|
- 解析私聊 / 群 / 频道消息
|
|
9
|
-
- 出站 `send({
|
|
9
|
+
- 出站 `send({ conversation, payload })` → QQ API(`conversation.kind`/`id`/`parent` 结构化寻址)
|
|
10
10
|
- 约定式 `defineAdapter` / `definePlugin`(无需 `usePlugin`)
|
|
11
11
|
- Webhook / middleware 模式已实现(经 `httpHostToken` 注册 POST 路由)
|
|
12
12
|
- AI `@` 触发标注:群消息(GROUP_AT_MESSAGE_CREATE 仅 @ 时下发)与频道 `mentions[].bot` 会在入站 metadata 标 `mentioned: true`(新 Plugin Runtime 纯文本 content 经 metadata 传递 @)
|
|
@@ -25,8 +25,8 @@ pnpm add @zhin.js/adapter-qq
|
|
|
25
25
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
26
26
|
- **无需** `@zhin.js/host-http` / `@zhin.js/host-router`(WebSocket 路径)
|
|
27
27
|
|
|
28
|
-
入站:`gateway.receive({
|
|
29
|
-
出站:`send({
|
|
28
|
+
入站:`gateway.receive({ conversation, message, content, sender, metadata })`(ConversationRef 结构化寻址)
|
|
29
|
+
出站:`send({ conversation, payload })` → `sendPrivateMessage` / `sendGroupMessage` / `sendGuildMessage`
|
|
30
30
|
|
|
31
31
|
## 前置条件
|
|
32
32
|
|
|
@@ -49,22 +49,49 @@ plugins:
|
|
|
49
49
|
- name: my-qq-bot
|
|
50
50
|
appid: ${QQ_APPID}
|
|
51
51
|
secret: ${QQ_SECRET}
|
|
52
|
+
# botKind / intents 可由向导写入;`qq.endpoint add` 扫码后会追问公域/私域再写入
|
|
52
53
|
```
|
|
53
54
|
|
|
55
|
+
### botKind 与 intents
|
|
56
|
+
|
|
57
|
+
WebSocket Identify 的 `intents` **必须与开放平台已开通的权限一致**,否则会断连。
|
|
58
|
+
群聊无公/私域之分,两类都订阅 `GROUP_AND_C2C_EVENT`;差异只在频道消息 intent:
|
|
59
|
+
|
|
60
|
+
| `botKind` | 频道消息 intent | 共用 intents |
|
|
61
|
+
|-----------|-----------------|--------------|
|
|
62
|
+
| `public`(默认,公域) | `PUBLIC_GUILD_MESSAGES`(仅 @) | `GROUP_AND_C2C_EVENT`, `GUILDS`, `GUILD_MEMBERS`, `DIRECT_MESSAGE` |
|
|
63
|
+
| `private`(私域) | `GUILD_MESSAGES`(频道全量) | 同上 |
|
|
64
|
+
|
|
65
|
+
公域机器人订阅 `GUILD_MESSAGES` 会 Identify 失败断连。显式配置 `intents` 时优先于 `botKind`。
|
|
66
|
+
`create-zhin` / `zhin setup` 会询问公/私域并写入;`qq.endpoint add` 扫码成功后在同一会话追问 `public`/`private`(或 `公域`/`私域`),确认后一次性写 `.env` 与 yaml。
|
|
67
|
+
|
|
54
68
|
多账号:一个插件实例挂多个 endpoint(`endpoints` 数组逐项覆盖顶层字段,`name` 必填):
|
|
55
69
|
|
|
56
70
|
```yaml
|
|
57
71
|
plugins:
|
|
58
72
|
qq:
|
|
59
73
|
mode: websocket
|
|
60
|
-
intents: [GUILDS, GROUP_AND_C2C_EVENT]
|
|
61
74
|
endpoints:
|
|
62
75
|
- name: main-bot
|
|
63
76
|
appid: ${QQ_APPID}
|
|
64
77
|
secret: ${QQ_SECRET}
|
|
65
|
-
|
|
78
|
+
botKind: public
|
|
79
|
+
intents:
|
|
80
|
+
- GROUP_AND_C2C_EVENT
|
|
81
|
+
- GUILDS
|
|
82
|
+
- GUILD_MEMBERS
|
|
83
|
+
- DIRECT_MESSAGE
|
|
84
|
+
- PUBLIC_GUILD_MESSAGES
|
|
85
|
+
- name: private-bot
|
|
66
86
|
appid: ${QQ_APPID_2}
|
|
67
87
|
secret: ${QQ_SECRET_2}
|
|
88
|
+
botKind: private
|
|
89
|
+
intents:
|
|
90
|
+
- GROUP_AND_C2C_EVENT
|
|
91
|
+
- GUILDS
|
|
92
|
+
- GUILD_MEMBERS
|
|
93
|
+
- DIRECT_MESSAGE
|
|
94
|
+
- GUILD_MESSAGES
|
|
68
95
|
```
|
|
69
96
|
|
|
70
97
|
根插件 `zhin.plugins`(或项目图)需引用 `@zhin.js/adapter-qq`(`instanceKey: qq`)。
|
|
@@ -75,13 +102,13 @@ plugins:
|
|
|
75
102
|
|
|
76
103
|
| 命令 | 说明 |
|
|
77
104
|
|------|------|
|
|
78
|
-
| `qq.endpoint add [name]` | 手机 QQ
|
|
79
|
-
| `qq.endpoint cancel` |
|
|
105
|
+
| `qq.endpoint add [name]` | 手机 QQ 扫码绑定 → **追问公域/私域** → 一次性写入 `.env` + `plugins.qq.endpoints`(重启生效) |
|
|
106
|
+
| `qq.endpoint cancel` | 取消进行中的扫码绑定或待确认的公域/私域选择 |
|
|
80
107
|
| `qq.endpoint list` | 列出运行中与配置中的 endpoints |
|
|
81
108
|
| `qq.endpoint remove <name>` | 从配置移除 endpoint(`.env` 键保留,可手动清理) |
|
|
82
109
|
|
|
83
|
-
add/cancel/remove 受 `master` 限制:实例配置声明了 `master`(顶层或 `endpoints[i]`)时仅
|
|
84
|
-
master
|
|
110
|
+
add/cancel/remove 受 `master` 限制:实例配置声明了 `master`(顶层或 `endpoints[i].master`)时仅
|
|
111
|
+
master 可执行;未配置则放行(首个扫码绑定者会写入该 endpoint 的 `master`)。二维码当前以链接文本下发
|
|
85
112
|
(出站富媒体待迁移),用手机 QQ 打开链接即可扫码。
|
|
86
113
|
|
|
87
114
|
## 环境变量
|
package/adapters/qq.js
CHANGED
|
@@ -22,26 +22,28 @@ export default defineAdapter({
|
|
|
22
22
|
const gateway = context.use(messageGatewayToken);
|
|
23
23
|
// 注册到插件运行时状态(qq.endpoint list 的"运行中"数据源)
|
|
24
24
|
const state = context.use(qqRuntimeStateToken);
|
|
25
|
-
state.endpoints.set(config.
|
|
26
|
-
|
|
25
|
+
state.endpoints.set(config.id, {
|
|
26
|
+
id: config.id,
|
|
27
27
|
mode: config.mode,
|
|
28
28
|
});
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
// 正向判 websocket:TS 对 `mode === 'webhook' || mode === 'middleware'` 的
|
|
30
|
+
// 否定分支不做联合消减(多字面量判别式),反向写会让 config 窄化失败。
|
|
31
|
+
const endpoint = config.mode === 'websocket'
|
|
32
|
+
? new QqWebsocketEndpoint({
|
|
31
33
|
id: context.id,
|
|
32
34
|
gateway,
|
|
33
|
-
http: context.use(httpHostToken),
|
|
34
35
|
config,
|
|
35
36
|
})
|
|
36
|
-
: new
|
|
37
|
+
: new QqHttpEndpoint({
|
|
37
38
|
id: context.id,
|
|
38
39
|
gateway,
|
|
40
|
+
http: context.use(httpHostToken),
|
|
39
41
|
config,
|
|
40
42
|
});
|
|
41
43
|
// 运行状态表只增不减:stop 时同步摘除,避免 stop 后 qq.endpoint list 仍显示运行中。
|
|
42
44
|
const stopEndpoint = endpoint.stop.bind(endpoint);
|
|
43
45
|
endpoint.stop = async () => {
|
|
44
|
-
state.endpoints.delete(config.
|
|
46
|
+
state.endpoints.delete(config.id);
|
|
45
47
|
await stopEndpoint();
|
|
46
48
|
};
|
|
47
49
|
return endpoint;
|
package/adapters/qq.ts
CHANGED
|
@@ -37,26 +37,28 @@ export default defineAdapter<QqAdapterConfig>({
|
|
|
37
37
|
const gateway = context.use(messageGatewayToken);
|
|
38
38
|
// 注册到插件运行时状态(qq.endpoint list 的"运行中"数据源)
|
|
39
39
|
const state = context.use(qqRuntimeStateToken);
|
|
40
|
-
state.endpoints.set(config.
|
|
41
|
-
|
|
40
|
+
state.endpoints.set(config.id, {
|
|
41
|
+
id: config.id,
|
|
42
42
|
mode: config.mode,
|
|
43
43
|
});
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// 正向判 websocket:TS 对 `mode === 'webhook' || mode === 'middleware'` 的
|
|
45
|
+
// 否定分支不做联合消减(多字面量判别式),反向写会让 config 窄化失败。
|
|
46
|
+
const endpoint = config.mode === 'websocket'
|
|
47
|
+
? new QqWebsocketEndpoint({
|
|
46
48
|
id: context.id,
|
|
47
49
|
gateway,
|
|
48
|
-
http: context.use(httpHostToken),
|
|
49
50
|
config,
|
|
50
51
|
})
|
|
51
|
-
: new
|
|
52
|
+
: new QqHttpEndpoint({
|
|
52
53
|
id: context.id,
|
|
53
54
|
gateway,
|
|
55
|
+
http: context.use(httpHostToken),
|
|
54
56
|
config,
|
|
55
57
|
});
|
|
56
58
|
// 运行状态表只增不减:stop 时同步摘除,避免 stop 后 qq.endpoint list 仍显示运行中。
|
|
57
59
|
const stopEndpoint = endpoint.stop.bind(endpoint);
|
|
58
60
|
endpoint.stop = async () => {
|
|
59
|
-
state.endpoints.delete(config.
|
|
61
|
+
state.endpoints.delete(config.id);
|
|
60
62
|
await stopEndpoint();
|
|
61
63
|
};
|
|
62
64
|
return endpoint;
|
|
@@ -3,7 +3,7 @@ import { defineCommand } from '@zhin.js/command';
|
|
|
3
3
|
import { isQqEndpointOperator, QQ_ENDPOINT_FORBIDDEN, runQqEndpointCancel, } from "../../lib/qq-endpoint-commands.js";
|
|
4
4
|
import { qqRuntimeStateToken } from "../../lib/qq-runtime-state.js";
|
|
5
5
|
export default defineCommand({
|
|
6
|
-
description: '取消进行中的 QQ
|
|
6
|
+
description: '取消进行中的 QQ 扫码绑定或公域/私域选择',
|
|
7
7
|
execute({ config, input, use }) {
|
|
8
8
|
if (!isQqEndpointOperator(config, input))
|
|
9
9
|
return QQ_ENDPOINT_FORBIDDEN;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { qqRuntimeStateToken } from '../../src/qq-runtime-state.js';
|
|
8
8
|
|
|
9
9
|
export default defineCommand({
|
|
10
|
-
description: '取消进行中的 QQ
|
|
10
|
+
description: '取消进行中的 QQ 扫码绑定或公域/私域选择',
|
|
11
11
|
execute({ config, input, use }) {
|
|
12
12
|
if (!isQqEndpointOperator(config, input)) return QQ_ENDPOINT_FORBIDDEN;
|
|
13
13
|
return runQqEndpointCancel(use(qqRuntimeStateToken));
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QQ endpoints — lifecycle, outbound, admit, agent tool surface.
|
|
3
3
|
*/
|
|
4
|
-
import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
4
|
+
import type { EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
5
5
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
6
6
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
7
7
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -26,10 +26,7 @@ export declare class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
26
26
|
open(): void;
|
|
27
27
|
close(): void;
|
|
28
28
|
stop(): Promise<void>;
|
|
29
|
-
send({
|
|
30
|
-
readonly target: string;
|
|
31
|
-
readonly payload: unknown;
|
|
32
|
-
}): Promise<string>;
|
|
29
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
33
30
|
recallMessage(messageId: string): Promise<void>;
|
|
34
31
|
/** Test / internal: admit a message when open. */
|
|
35
32
|
admit(msg: QqInboundMessage): void;
|
|
@@ -60,10 +57,7 @@ export declare class QqHttpEndpoint implements EndpointInstance {
|
|
|
60
57
|
open(): void;
|
|
61
58
|
close(): void;
|
|
62
59
|
stop(): Promise<void>;
|
|
63
|
-
send({
|
|
64
|
-
readonly target: string;
|
|
65
|
-
readonly payload: unknown;
|
|
66
|
-
}): Promise<string>;
|
|
60
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
67
61
|
recallMessage(messageId: string): Promise<void>;
|
|
68
62
|
admit(msg: QqInboundMessage): void;
|
|
69
63
|
getGuilds(): Promise<unknown[]>;
|
package/lib/endpoint.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { formatCompact,
|
|
1
|
+
import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
|
|
2
2
|
import { formatOutbound } from './outbound.js';
|
|
3
3
|
import { registerQqAgentEndpoint } from './qq-agent-deps.js';
|
|
4
|
-
import { formatInboundContent,
|
|
4
|
+
import { formatInboundContent, parseCompoundMessageId, qqInboundConversation, qqOutboundKind, resolveOutboundMessageId, senderDisplayName, } from './protocol.js';
|
|
5
5
|
import { defaultCreateHttpBot, registerQqWebhookRoutes, } from './webhook.js';
|
|
6
6
|
import { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
7
|
-
const logger = getLogger('qq');
|
|
8
7
|
export class QqWebsocketEndpoint {
|
|
8
|
+
#logger;
|
|
9
9
|
#options;
|
|
10
10
|
#createBot;
|
|
11
11
|
#bot = null;
|
|
@@ -14,32 +14,28 @@ export class QqWebsocketEndpoint {
|
|
|
14
14
|
#unregisterAgent;
|
|
15
15
|
management = createQqEndpointManagement(this);
|
|
16
16
|
constructor(options) {
|
|
17
|
+
this.#logger = getAdapterLogger('qq', options.config.id);
|
|
17
18
|
this.#options = options;
|
|
18
19
|
this.#createBot = options.createBot ?? defaultCreateBot;
|
|
19
20
|
}
|
|
20
21
|
/** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
|
|
21
22
|
get name() {
|
|
22
|
-
return this.#options.config.
|
|
23
|
+
return this.#options.config.id;
|
|
23
24
|
}
|
|
24
25
|
async start() {
|
|
25
26
|
if (this.#started)
|
|
26
27
|
return;
|
|
27
28
|
this.#started = true;
|
|
28
29
|
try {
|
|
29
|
-
this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.
|
|
30
|
+
this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.id, this);
|
|
30
31
|
this.#bot = this.#createBot(this.#options.config);
|
|
31
32
|
this.#bindBot(this.#bot);
|
|
32
33
|
await this.#bot.start();
|
|
33
|
-
logger.info(
|
|
34
|
-
op: 'connect',
|
|
35
|
-
endpoint: this.#options.config.name,
|
|
36
|
-
mode: 'websocket',
|
|
37
|
-
appid: this.#options.config.appid,
|
|
38
|
-
}));
|
|
34
|
+
this.#logger.info(`connected (websocket) | appid: ${this.#options.config.appid}`);
|
|
39
35
|
}
|
|
40
36
|
catch (error) {
|
|
41
37
|
await this.stop();
|
|
42
|
-
logger.error('Failed to connect QQ websocket:', error);
|
|
38
|
+
this.#logger.error('Failed to connect QQ websocket:', error);
|
|
43
39
|
const raw = error instanceof Error ? error.message : String(error);
|
|
44
40
|
throw new Error(`QQ WebSocket 连接失败:请检查 appid/secret 是否配对、网关地址(gatewayUrl/accessTokenUrl)是否可达(原始错误:${raw})`, { cause: error });
|
|
45
41
|
}
|
|
@@ -65,38 +61,33 @@ export class QqWebsocketEndpoint {
|
|
|
65
61
|
this.#bot = null;
|
|
66
62
|
}
|
|
67
63
|
this.#started = false;
|
|
68
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
64
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
69
65
|
}
|
|
70
|
-
async send({
|
|
66
|
+
async send({ conversation, payload }) {
|
|
71
67
|
const body = formatOutbound(payload);
|
|
72
|
-
const
|
|
68
|
+
const kind = qqOutboundKind(conversation);
|
|
73
69
|
const bot = this.#requireBot();
|
|
74
70
|
let result;
|
|
75
|
-
switch (
|
|
71
|
+
switch (kind) {
|
|
76
72
|
case 'private':
|
|
77
|
-
result = await bot.sendPrivateMessage(
|
|
73
|
+
result = await bot.sendPrivateMessage(conversation.id, body);
|
|
78
74
|
break;
|
|
79
75
|
case 'group':
|
|
80
|
-
result = await bot.sendGroupMessage(
|
|
76
|
+
result = await bot.sendGroupMessage(conversation.id, body);
|
|
81
77
|
break;
|
|
82
78
|
case 'channel':
|
|
83
|
-
result = await bot.sendGuildMessage(
|
|
79
|
+
result = await bot.sendGuildMessage(conversation.id, body);
|
|
84
80
|
break;
|
|
85
81
|
case 'direct':
|
|
86
82
|
if (!bot.sendDirectMessage)
|
|
87
83
|
throw new Error('QQ direct message not supported by transport');
|
|
88
|
-
result = await bot.sendDirectMessage(
|
|
84
|
+
result = await bot.sendDirectMessage(conversation.id, body);
|
|
89
85
|
break;
|
|
90
|
-
default:
|
|
91
|
-
throw new Error(`unsupported QQ target kind: ${String(parsed.kind)}`);
|
|
92
86
|
}
|
|
93
|
-
const messageId = `${
|
|
94
|
-
logger.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
target,
|
|
98
|
-
messageId,
|
|
99
|
-
}));
|
|
87
|
+
const messageId = `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
88
|
+
this.#logger.info(`send ${kind}:${conversation.id}`
|
|
89
|
+
+ ` | id: ${messageId}`
|
|
90
|
+
+ ` | ${truncatePreview(typeof body === 'string' ? body : String(body), 80)}`);
|
|
100
91
|
return messageId;
|
|
101
92
|
}
|
|
102
93
|
async recallMessage(messageId) {
|
|
@@ -116,9 +107,9 @@ export class QqWebsocketEndpoint {
|
|
|
116
107
|
await bot.recallDirectMessage?.(channelId, qqMsgId);
|
|
117
108
|
break;
|
|
118
109
|
}
|
|
119
|
-
logger.debug(formatCompact({
|
|
110
|
+
this.#logger.debug(formatCompact({
|
|
120
111
|
op: 'qq_recall',
|
|
121
|
-
endpoint: this.#options.config.
|
|
112
|
+
endpoint: this.#options.config.id,
|
|
122
113
|
kind,
|
|
123
114
|
messageId,
|
|
124
115
|
}));
|
|
@@ -127,27 +118,34 @@ export class QqWebsocketEndpoint {
|
|
|
127
118
|
admit(msg) {
|
|
128
119
|
if (!this.#open)
|
|
129
120
|
return;
|
|
130
|
-
const
|
|
121
|
+
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
122
|
+
const content = formatInboundContent(msg);
|
|
123
|
+
this.#logger.info(`recv ${conversation.kind}:${conversation.id}`
|
|
124
|
+
+ (msg.authorId ? ` from ${msg.authorId}` : '')
|
|
125
|
+
+ (msg.mentioned ? ' (mentioned)' : '')
|
|
126
|
+
+ ` | ${truncatePreview(content, 80)}`);
|
|
131
127
|
void this.#options.gateway.receive({
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
content
|
|
128
|
+
conversation,
|
|
129
|
+
message: { conversation, id: msg.id },
|
|
130
|
+
content,
|
|
135
131
|
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
136
|
-
sender:
|
|
137
|
-
|
|
132
|
+
sender: {
|
|
133
|
+
id: msg.authorId,
|
|
134
|
+
name: senderDisplayName(msg) || undefined,
|
|
135
|
+
...(msg.authorRoles?.length ? { roles: msg.authorRoles } : {}),
|
|
136
|
+
},
|
|
137
|
+
endpointId: this.#options.config.id,
|
|
138
|
+
...(msg.mentioned ? { mentioned: true } : {}),
|
|
138
139
|
metadata: Object.freeze({
|
|
139
|
-
endpoint: this.#options.config.name,
|
|
140
140
|
channelKind: msg.channelKind,
|
|
141
141
|
userId: msg.authorId,
|
|
142
142
|
guildId: msg.guildId,
|
|
143
143
|
roles: msg.authorRoles,
|
|
144
|
-
// AT 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
145
|
-
...(msg.mentioned ? { mentioned: true } : {}),
|
|
146
144
|
}),
|
|
147
145
|
}).catch((err) => {
|
|
148
|
-
logger.warn(formatCompact({
|
|
146
|
+
this.#logger.warn(formatCompact({
|
|
149
147
|
op: 'qq_gateway_receive_failed',
|
|
150
|
-
target
|
|
148
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
151
149
|
error: err instanceof Error ? err.message : String(err),
|
|
152
150
|
}));
|
|
153
151
|
});
|
|
@@ -191,6 +189,7 @@ export class QqWebsocketEndpoint {
|
|
|
191
189
|
}
|
|
192
190
|
/** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
|
|
193
191
|
export class QqHttpEndpoint {
|
|
192
|
+
#logger;
|
|
194
193
|
#options;
|
|
195
194
|
#createBot;
|
|
196
195
|
#bot = null;
|
|
@@ -200,33 +199,29 @@ export class QqHttpEndpoint {
|
|
|
200
199
|
#unregisterAgent;
|
|
201
200
|
management = createQqEndpointManagement(this);
|
|
202
201
|
constructor(options) {
|
|
202
|
+
this.#logger = getAdapterLogger('qq', options.config.id);
|
|
203
203
|
this.#options = options;
|
|
204
204
|
this.#createBot = options.createBot ?? defaultCreateHttpBot;
|
|
205
205
|
}
|
|
206
206
|
/** Live endpoint 名(Console/AdapterIndex 展示用)。 */
|
|
207
207
|
get name() {
|
|
208
|
-
return this.#options.config.
|
|
208
|
+
return this.#options.config.id;
|
|
209
209
|
}
|
|
210
210
|
async start() {
|
|
211
211
|
if (this.#started)
|
|
212
212
|
return;
|
|
213
213
|
this.#started = true;
|
|
214
214
|
try {
|
|
215
|
-
this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.
|
|
215
|
+
this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.id, this);
|
|
216
216
|
this.#setupRoutes();
|
|
217
217
|
this.#bot = this.#createBot(this.#options.config);
|
|
218
218
|
this.#bindBot(this.#bot);
|
|
219
219
|
await this.#bot.start();
|
|
220
|
-
logger.info(
|
|
221
|
-
op: 'connect',
|
|
222
|
-
endpoint: this.#options.config.name,
|
|
223
|
-
mode: this.#options.config.mode,
|
|
224
|
-
path: this.#options.config.webhookPath,
|
|
225
|
-
}));
|
|
220
|
+
this.#logger.info(`connected (${this.#options.config.mode}) | path: ${this.#options.config.webhookPath}`);
|
|
226
221
|
}
|
|
227
222
|
catch (error) {
|
|
228
223
|
await this.stop();
|
|
229
|
-
logger.error('Failed to connect QQ HTTP receiver:', error);
|
|
224
|
+
this.#logger.error('Failed to connect QQ HTTP receiver:', error);
|
|
230
225
|
throw error;
|
|
231
226
|
}
|
|
232
227
|
}
|
|
@@ -253,32 +248,34 @@ export class QqHttpEndpoint {
|
|
|
253
248
|
this.#bot = null;
|
|
254
249
|
}
|
|
255
250
|
this.#started = false;
|
|
256
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
251
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
257
252
|
}
|
|
258
|
-
async send({
|
|
253
|
+
async send({ conversation, payload }) {
|
|
259
254
|
const body = formatOutbound(payload);
|
|
260
|
-
const
|
|
255
|
+
const kind = qqOutboundKind(conversation);
|
|
261
256
|
const bot = this.#requireBot();
|
|
262
257
|
let result;
|
|
263
|
-
switch (
|
|
258
|
+
switch (kind) {
|
|
264
259
|
case 'private':
|
|
265
|
-
result = await bot.sendPrivateMessage(
|
|
260
|
+
result = await bot.sendPrivateMessage(conversation.id, body);
|
|
266
261
|
break;
|
|
267
262
|
case 'group':
|
|
268
|
-
result = await bot.sendGroupMessage(
|
|
263
|
+
result = await bot.sendGroupMessage(conversation.id, body);
|
|
269
264
|
break;
|
|
270
265
|
case 'channel':
|
|
271
|
-
result = await bot.sendGuildMessage(
|
|
266
|
+
result = await bot.sendGuildMessage(conversation.id, body);
|
|
272
267
|
break;
|
|
273
268
|
case 'direct':
|
|
274
269
|
if (!bot.sendDirectMessage)
|
|
275
270
|
throw new Error('QQ direct message not supported by transport');
|
|
276
|
-
result = await bot.sendDirectMessage(
|
|
271
|
+
result = await bot.sendDirectMessage(conversation.id, body);
|
|
277
272
|
break;
|
|
278
|
-
default:
|
|
279
|
-
throw new Error(`unsupported QQ target kind: ${String(parsed.kind)}`);
|
|
280
273
|
}
|
|
281
|
-
|
|
274
|
+
const messageId = `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
275
|
+
this.#logger.info(`send ${kind}:${conversation.id}`
|
|
276
|
+
+ ` | id: ${messageId}`
|
|
277
|
+
+ ` | ${truncatePreview(typeof body === 'string' ? body : String(body), 80)}`);
|
|
278
|
+
return messageId;
|
|
282
279
|
}
|
|
283
280
|
async recallMessage(messageId) {
|
|
284
281
|
const { kind, channelId, qqMsgId } = parseCompoundMessageId(messageId);
|
|
@@ -297,9 +294,9 @@ export class QqHttpEndpoint {
|
|
|
297
294
|
await bot.recallDirectMessage?.(channelId, qqMsgId);
|
|
298
295
|
break;
|
|
299
296
|
}
|
|
300
|
-
logger.debug(formatCompact({
|
|
297
|
+
this.#logger.debug(formatCompact({
|
|
301
298
|
op: 'qq_recall',
|
|
302
|
-
endpoint: this.#options.config.
|
|
299
|
+
endpoint: this.#options.config.id,
|
|
303
300
|
kind,
|
|
304
301
|
messageId,
|
|
305
302
|
}));
|
|
@@ -307,27 +304,34 @@ export class QqHttpEndpoint {
|
|
|
307
304
|
admit(msg) {
|
|
308
305
|
if (!this.#open)
|
|
309
306
|
return;
|
|
310
|
-
const
|
|
307
|
+
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
308
|
+
const content = formatInboundContent(msg);
|
|
309
|
+
this.#logger.info(`recv ${conversation.kind}:${conversation.id}`
|
|
310
|
+
+ (msg.authorId ? ` from ${msg.authorId}` : '')
|
|
311
|
+
+ (msg.mentioned ? ' (mentioned)' : '')
|
|
312
|
+
+ ` | ${truncatePreview(content, 80)}`);
|
|
311
313
|
void this.#options.gateway.receive({
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
content
|
|
314
|
+
conversation,
|
|
315
|
+
message: { conversation, id: msg.id },
|
|
316
|
+
content,
|
|
315
317
|
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
316
|
-
sender:
|
|
317
|
-
|
|
318
|
+
sender: {
|
|
319
|
+
id: msg.authorId,
|
|
320
|
+
name: senderDisplayName(msg) || undefined,
|
|
321
|
+
...(msg.authorRoles?.length ? { roles: msg.authorRoles } : {}),
|
|
322
|
+
},
|
|
323
|
+
endpointId: this.#options.config.id,
|
|
324
|
+
...(msg.mentioned ? { mentioned: true } : {}),
|
|
318
325
|
metadata: Object.freeze({
|
|
319
|
-
endpoint: this.#options.config.name,
|
|
320
326
|
channelKind: msg.channelKind,
|
|
321
327
|
userId: msg.authorId,
|
|
322
328
|
guildId: msg.guildId,
|
|
323
329
|
roles: msg.authorRoles,
|
|
324
|
-
// AT 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
325
|
-
...(msg.mentioned ? { mentioned: true } : {}),
|
|
326
330
|
}),
|
|
327
331
|
}).catch((err) => {
|
|
328
|
-
logger.warn(formatCompact({
|
|
332
|
+
this.#logger.warn(formatCompact({
|
|
329
333
|
op: 'qq_gateway_receive_failed',
|
|
330
|
-
target
|
|
334
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
331
335
|
error: err instanceof Error ? err.message : String(err),
|
|
332
336
|
}));
|
|
333
337
|
});
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { formatInboundContent,
|
|
1
|
+
export { formatInboundContent, formatOutboundText, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, type QqAdapterConfig, type QqChannelKind, type QqInboundMessage, type QqWireSegment, type ResolvedQqConfig, type ResolvedQqHttpConfig, type ResolvedQqWebsocketConfig, } from './protocol.js';
|
|
2
|
+
export { DEFAULT_QQ_BOT_KIND, QQ_INTENTS_BY_KIND, defaultQqEndpointIntentFields, parseQqBotKind, resolveQqIntents, type QqBotKind, type QqIntent, } from './qq-intents.js';
|
|
2
3
|
export { formatOutbound, resolveMediaFile, type QqOutboundElem, type QqOutboundMessage, } from './outbound.js';
|
|
3
4
|
export { QqHttpEndpoint, QqWebsocketEndpoint, type CreateQqBot, type CreateQqHttpBot, type QqBotTransport, type QqEndpointOptions, type QqHttpBotTransport, type QqHttpEndpointOptions, } from './endpoint.js';
|
|
4
5
|
export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, type QqWebhookHandler, } from './webhook.js';
|
|
5
6
|
export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
6
7
|
export { getQqAgentDeps, registerQqAgentEndpoint, setQqAgentDeps, type QqAgentDeps, type QqAgentEndpoint, } from './qq-agent-deps.js';
|
|
7
|
-
export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver,
|
|
8
|
+
export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, } from './platform-permit.js';
|
package/lib/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { formatInboundContent,
|
|
1
|
+
export { formatInboundContent, formatOutboundText, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, } from './protocol.js';
|
|
2
|
+
export { DEFAULT_QQ_BOT_KIND, QQ_INTENTS_BY_KIND, defaultQqEndpointIntentFields, parseQqBotKind, resolveQqIntents, } from './qq-intents.js';
|
|
2
3
|
export { formatOutbound, resolveMediaFile, } from './outbound.js';
|
|
3
4
|
export { QqHttpEndpoint, QqWebsocketEndpoint, } from './endpoint.js';
|
|
4
5
|
export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, } from './webhook.js';
|
|
5
6
|
export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
6
7
|
export { getQqAgentDeps, registerQqAgentEndpoint, setQqAgentDeps, } from './qq-agent-deps.js';
|
|
7
|
-
export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver,
|
|
8
|
+
export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, } from './platform-permit.js';
|