@zhin.js/adapter-qq 7.0.3 → 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 +80 -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.js +55 -45
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/platform-permit.d.ts +2 -3
- package/lib/platform-permit.js +3 -11
- package/lib/protocol.d.ts +9 -5
- package/lib/protocol.js +11 -7
- 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 -16
- package/plugin.js +6 -4
- package/schema.json +66 -9
- package/src/endpoint.ts +69 -46
- package/src/index.ts +10 -1
- package/src/platform-permit.ts +4 -9
- package/src/protocol.ts +19 -11
- 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]].js → [[id]].js} +0 -0
- /package/commands/endpoint/add/{[[name]].ts → [[id]].ts} +0 -0
- /package/commands/endpoint/remove/{[name].js → [id].js} +0 -0
- /package/commands/endpoint/remove/{[name].ts → [id].ts} +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
3
|
+
import { completeQqPendingBotKind, } from "../lib/qq-endpoint-commands.js";
|
|
4
|
+
import { parseQqBotKindAnswer, qqCommandSessionKey, QQ_BOT_KIND_PROMPT, } from "../lib/qq-bot-kind-prompt.js";
|
|
5
|
+
import { qqRuntimeStateToken } from "../lib/qq-runtime-state.js";
|
|
6
|
+
/**
|
|
7
|
+
* 扫码绑定成功后,在同一会话收取 public/private(或 公域/私域)回复并写入 intents。
|
|
8
|
+
* 非待确认会话、或内容无法识别为 botKind 时放行后续中间件/命令。
|
|
9
|
+
*/
|
|
10
|
+
export default defineMiddleware({
|
|
11
|
+
target: 'inbound',
|
|
12
|
+
phase: 'before-dispatch',
|
|
13
|
+
order: -50,
|
|
14
|
+
async handle(context, next) {
|
|
15
|
+
const state = context.use(qqRuntimeStateToken);
|
|
16
|
+
const pending = state.pendingBotKind;
|
|
17
|
+
if (!pending) {
|
|
18
|
+
await next();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const sessionKey = qqCommandSessionKey(context.input);
|
|
22
|
+
if (!sessionKey || sessionKey !== pending.sessionKey) {
|
|
23
|
+
await next();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const raw = String(context.input.content ?? '').trim();
|
|
27
|
+
// 让 qq.endpoint cancel 等命令继续走命令链
|
|
28
|
+
if (raw.toLowerCase().startsWith('qq.endpoint')) {
|
|
29
|
+
await next();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const botKind = parseQqBotKindAnswer(raw);
|
|
33
|
+
if (!botKind) {
|
|
34
|
+
await context.input.$reply?.(`未识别「${raw}」。${QQ_BOT_KIND_PROMPT}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const text = completeQqPendingBotKind(pending, botKind);
|
|
39
|
+
state.pendingBotKind = null;
|
|
40
|
+
await context.input.$reply?.(text);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
await context.input.$reply?.(`写入配置失败:${error instanceof Error ? error.message : String(error)}`);
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { defineMiddleware } from '@zhin.js/middleware';
|
|
2
|
+
import type { CommandMessage } from '@zhin.js/command';
|
|
3
|
+
import {
|
|
4
|
+
completeQqPendingBotKind,
|
|
5
|
+
} from '../src/qq-endpoint-commands.js';
|
|
6
|
+
import {
|
|
7
|
+
parseQqBotKindAnswer,
|
|
8
|
+
qqCommandSessionKey,
|
|
9
|
+
QQ_BOT_KIND_PROMPT,
|
|
10
|
+
} from '../src/qq-bot-kind-prompt.js';
|
|
11
|
+
import { qqRuntimeStateToken } from '../src/qq-runtime-state.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 扫码绑定成功后,在同一会话收取 public/private(或 公域/私域)回复并写入 intents。
|
|
15
|
+
* 非待确认会话、或内容无法识别为 botKind 时放行后续中间件/命令。
|
|
16
|
+
*/
|
|
17
|
+
export default defineMiddleware<CommandMessage>({
|
|
18
|
+
target: 'inbound',
|
|
19
|
+
phase: 'before-dispatch',
|
|
20
|
+
order: -50,
|
|
21
|
+
async handle(context, next) {
|
|
22
|
+
const state = context.use(qqRuntimeStateToken);
|
|
23
|
+
const pending = state.pendingBotKind;
|
|
24
|
+
if (!pending) {
|
|
25
|
+
await next();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const sessionKey = qqCommandSessionKey(context.input);
|
|
30
|
+
if (!sessionKey || sessionKey !== pending.sessionKey) {
|
|
31
|
+
await next();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const raw = String(context.input.content ?? '').trim();
|
|
36
|
+
// 让 qq.endpoint cancel 等命令继续走命令链
|
|
37
|
+
if (raw.toLowerCase().startsWith('qq.endpoint')) {
|
|
38
|
+
await next();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const botKind = parseQqBotKindAnswer(raw);
|
|
43
|
+
if (!botKind) {
|
|
44
|
+
await context.input.$reply?.(
|
|
45
|
+
`未识别「${raw}」。${QQ_BOT_KIND_PROMPT}`,
|
|
46
|
+
);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const text = completeQqPendingBotKind(pending, botKind);
|
|
52
|
+
state.pendingBotKind = null;
|
|
53
|
+
await context.input.$reply?.(text);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
await context.input.$reply?.(
|
|
56
|
+
`写入配置失败:${error instanceof Error ? error.message : String(error)}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-qq",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.5",
|
|
4
4
|
"description": "Zhin.js QQ Official adapter for Plugin Runtime (WebSocket gateway)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -35,21 +35,23 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"qq-official-bot": "^1.2.3",
|
|
38
|
-
"@zhin.js/adapter": "1.1.
|
|
39
|
-
"@zhin.js/command": "1.0.
|
|
40
|
-
"@zhin.js/core": "1.5.
|
|
41
|
-
"@zhin.js/host-http": "1.0.
|
|
42
|
-
"@zhin.js/im-contract": "1.0.
|
|
43
|
-
"@zhin.js/logger": "1.0.
|
|
44
|
-
"@zhin.js/
|
|
38
|
+
"@zhin.js/adapter": "1.1.7",
|
|
39
|
+
"@zhin.js/command": "1.0.9",
|
|
40
|
+
"@zhin.js/core": "1.5.4",
|
|
41
|
+
"@zhin.js/host-http": "1.0.8",
|
|
42
|
+
"@zhin.js/im-contract": "1.0.3",
|
|
43
|
+
"@zhin.js/logger": "1.0.76",
|
|
44
|
+
"@zhin.js/middleware": "1.0.8",
|
|
45
|
+
"@zhin.js/permission": "1.0.1",
|
|
46
|
+
"@zhin.js/plugin-runtime": "1.1.5"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"zod": "^4.0.0",
|
|
48
|
-
"@zhin.js/adapter": "1.1.
|
|
49
|
-
"@zhin.js/agent": "1.1.
|
|
50
|
-
"@zhin.js/core": "1.5.
|
|
51
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
52
|
-
"zhin.js": "6.0.
|
|
50
|
+
"@zhin.js/adapter": "1.1.7",
|
|
51
|
+
"@zhin.js/agent": "1.1.5",
|
|
52
|
+
"@zhin.js/core": "1.5.4",
|
|
53
|
+
"@zhin.js/plugin-runtime": "1.1.5",
|
|
54
|
+
"zhin.js": "6.0.4"
|
|
53
55
|
},
|
|
54
56
|
"peerDependenciesMeta": {
|
|
55
57
|
"zhin.js": {
|
|
@@ -67,13 +69,14 @@
|
|
|
67
69
|
"typescript": "^6.0.3",
|
|
68
70
|
"vitest": "^4.1.10",
|
|
69
71
|
"zod": "^4.4.3",
|
|
70
|
-
"@zhin.js/agent": "1.1.
|
|
71
|
-
"@zhin.js/host-http": "1.0.
|
|
72
|
-
"zhin.js": "6.0.
|
|
72
|
+
"@zhin.js/agent": "1.1.5",
|
|
73
|
+
"@zhin.js/host-http": "1.0.8",
|
|
74
|
+
"zhin.js": "6.0.4"
|
|
73
75
|
},
|
|
74
76
|
"files": [
|
|
75
77
|
"adapters",
|
|
76
78
|
"commands",
|
|
79
|
+
"middlewares",
|
|
77
80
|
"plugin.js",
|
|
78
81
|
"schema.json",
|
|
79
82
|
"src",
|
|
@@ -103,6 +106,10 @@
|
|
|
103
106
|
{
|
|
104
107
|
"package": "@zhin.js/command",
|
|
105
108
|
"api": "^1.0.0"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"package": "@zhin.js/middleware",
|
|
112
|
+
"api": "^1.0.0"
|
|
106
113
|
}
|
|
107
114
|
],
|
|
108
115
|
"plugins": []
|
package/plugin.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Generated by build-plugin-runtime-entries.mjs. Do not edit.
|
|
2
2
|
import { definePlugin } from '@zhin.js/plugin-runtime';
|
|
3
|
-
import {
|
|
3
|
+
import { permissionHostToken } from '@zhin.js/permission';
|
|
4
|
+
import { checkQqPlatformPermit } from "./lib/platform-permit.js";
|
|
4
5
|
import { createQqRuntimeState, qqRuntimeStateToken } from "./lib/qq-runtime-state.js";
|
|
5
6
|
export default definePlugin({
|
|
6
7
|
name: 'qq',
|
|
@@ -8,9 +9,10 @@ export default definePlugin({
|
|
|
8
9
|
displayName: 'QQ Official WebSocket Adapter',
|
|
9
10
|
},
|
|
10
11
|
setup(context) {
|
|
11
|
-
// 运行中 endpoint 注册表 + 扫码绑定单例,供 adapter create 与 `qq.endpoint` 命令共享
|
|
12
12
|
context.resources.provide(qqRuntimeStateToken, createQqRuntimeState());
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
if (context.resources.has(permissionHostToken)) {
|
|
14
|
+
const host = context.resources.use(permissionHostToken);
|
|
15
|
+
return host.registerPlatform('qq', checkQqPlatformPermit);
|
|
16
|
+
}
|
|
15
17
|
},
|
|
16
18
|
});
|
package/schema.json
CHANGED
|
@@ -17,15 +17,39 @@
|
|
|
17
17
|
"type": "boolean",
|
|
18
18
|
"default": false
|
|
19
19
|
},
|
|
20
|
+
"botKind": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"enum": [
|
|
23
|
+
"public",
|
|
24
|
+
"private"
|
|
25
|
+
],
|
|
26
|
+
"default": "public",
|
|
27
|
+
"description": "公域/私域:均含 GROUP_AND_C2C_EVENT;频道消息分别为 PUBLIC_GUILD_MESSAGES / GUILD_MESSAGES。未显式配置 intents 时按此展开"
|
|
28
|
+
},
|
|
20
29
|
"master": {
|
|
21
|
-
"type": [
|
|
22
|
-
|
|
30
|
+
"type": [
|
|
31
|
+
"string",
|
|
32
|
+
"number"
|
|
33
|
+
],
|
|
34
|
+
"description": "框架 master(endpoint 管理命令、AI/工具权限);openid。endpoints[i].master 可逐项覆盖"
|
|
35
|
+
},
|
|
36
|
+
"trusted": {
|
|
37
|
+
"type": "array",
|
|
38
|
+
"items": {
|
|
39
|
+
"type": [
|
|
40
|
+
"string",
|
|
41
|
+
"number"
|
|
42
|
+
],
|
|
43
|
+
"description": "Trusted openid"
|
|
44
|
+
},
|
|
45
|
+
"description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
|
|
23
46
|
},
|
|
24
47
|
"intents": {
|
|
25
48
|
"type": "array",
|
|
26
49
|
"items": {
|
|
27
50
|
"type": "string"
|
|
28
|
-
}
|
|
51
|
+
},
|
|
52
|
+
"description": "WebSocket Identify intents;省略时按 botKind 自动生成(public→含 PUBLIC_GUILD_MESSAGES)"
|
|
29
53
|
},
|
|
30
54
|
"accessTokenUrl": {
|
|
31
55
|
"type": "string"
|
|
@@ -48,15 +72,11 @@
|
|
|
48
72
|
},
|
|
49
73
|
"endpoints": {
|
|
50
74
|
"type": "array",
|
|
51
|
-
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(
|
|
75
|
+
"description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
|
|
52
76
|
"items": {
|
|
53
77
|
"type": "object",
|
|
54
78
|
"additionalProperties": true,
|
|
55
79
|
"properties": {
|
|
56
|
-
"name": {
|
|
57
|
-
"type": "string",
|
|
58
|
-
"description": "QQ bot name"
|
|
59
|
-
},
|
|
60
80
|
"appid": {
|
|
61
81
|
"type": "string",
|
|
62
82
|
"description": "QQ official bot app ID"
|
|
@@ -64,10 +84,47 @@
|
|
|
64
84
|
"secret": {
|
|
65
85
|
"type": "string",
|
|
66
86
|
"description": "QQ official bot secret"
|
|
87
|
+
},
|
|
88
|
+
"master": {
|
|
89
|
+
"type": [
|
|
90
|
+
"string",
|
|
91
|
+
"number"
|
|
92
|
+
],
|
|
93
|
+
"description": "本 endpoint 的框架 master(openid);覆盖顶层 master"
|
|
94
|
+
},
|
|
95
|
+
"trusted": {
|
|
96
|
+
"type": "array",
|
|
97
|
+
"items": {
|
|
98
|
+
"type": [
|
|
99
|
+
"string",
|
|
100
|
+
"number"
|
|
101
|
+
],
|
|
102
|
+
"description": "Trusted openid"
|
|
103
|
+
},
|
|
104
|
+
"description": "本 endpoint 的 trusted 列表"
|
|
105
|
+
},
|
|
106
|
+
"botKind": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"enum": [
|
|
109
|
+
"public",
|
|
110
|
+
"private"
|
|
111
|
+
],
|
|
112
|
+
"description": "覆盖顶层 botKind(公域/私域),用于按 endpoint 生成 intents"
|
|
113
|
+
},
|
|
114
|
+
"intents": {
|
|
115
|
+
"type": "array",
|
|
116
|
+
"items": {
|
|
117
|
+
"type": "string"
|
|
118
|
+
},
|
|
119
|
+
"description": "覆盖顶层 intents"
|
|
120
|
+
},
|
|
121
|
+
"id": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"description": "QQ bot name"
|
|
67
124
|
}
|
|
68
125
|
},
|
|
69
126
|
"required": [
|
|
70
|
-
"
|
|
127
|
+
"id",
|
|
71
128
|
"appid",
|
|
72
129
|
"secret"
|
|
73
130
|
]
|
package/src/endpoint.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
} from '@zhin.js/adapter';
|
|
10
10
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
11
11
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
12
|
-
import { formatCompact,
|
|
12
|
+
import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
|
|
13
13
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
14
14
|
import { formatOutbound } from './outbound.js';
|
|
15
15
|
import { registerQqAgentEndpoint } from './qq-agent-deps.js';
|
|
@@ -38,8 +38,6 @@ import {
|
|
|
38
38
|
type QqBotTransport,
|
|
39
39
|
} from './ws.js';
|
|
40
40
|
|
|
41
|
-
const logger = getLogger('qq');
|
|
42
|
-
|
|
43
41
|
export type { CreateQqBot, QqBotTransport } from './ws.js';
|
|
44
42
|
export type { CreateQqHttpBot, QqHttpBotTransport } from './webhook.js';
|
|
45
43
|
|
|
@@ -51,6 +49,8 @@ export interface QqEndpointOptions {
|
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
export class QqWebsocketEndpoint implements EndpointInstance {
|
|
52
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
53
|
+
|
|
54
54
|
readonly #options: QqEndpointOptions;
|
|
55
55
|
readonly #createBot: CreateQqBot;
|
|
56
56
|
#bot: QqBotTransport | null = null;
|
|
@@ -60,32 +60,30 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
60
60
|
readonly management: EndpointManagement = createQqEndpointManagement(this);
|
|
61
61
|
|
|
62
62
|
constructor(options: QqEndpointOptions) {
|
|
63
|
+
this.#logger = getAdapterLogger('qq', options.config.id);
|
|
63
64
|
this.#options = options;
|
|
64
65
|
this.#createBot = options.createBot ?? defaultCreateBot;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
|
|
68
69
|
get name(): string {
|
|
69
|
-
return this.#options.config.
|
|
70
|
+
return this.#options.config.id;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
async start(): Promise<void> {
|
|
73
74
|
if (this.#started) return;
|
|
74
75
|
this.#started = true;
|
|
75
76
|
try {
|
|
76
|
-
this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.
|
|
77
|
+
this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.id, this);
|
|
77
78
|
this.#bot = this.#createBot(this.#options.config);
|
|
78
79
|
this.#bindBot(this.#bot);
|
|
79
80
|
await this.#bot.start();
|
|
80
|
-
logger.info(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
mode: 'websocket',
|
|
84
|
-
appid: this.#options.config.appid,
|
|
85
|
-
}));
|
|
81
|
+
this.#logger.info(
|
|
82
|
+
`connected (websocket) | appid: ${this.#options.config.appid}`,
|
|
83
|
+
);
|
|
86
84
|
} catch (error) {
|
|
87
85
|
await this.stop();
|
|
88
|
-
logger.error('Failed to connect QQ websocket:', error);
|
|
86
|
+
this.#logger.error('Failed to connect QQ websocket:', error);
|
|
89
87
|
const raw = error instanceof Error ? error.message : String(error);
|
|
90
88
|
throw new Error(
|
|
91
89
|
`QQ WebSocket 连接失败:请检查 appid/secret 是否配对、网关地址(gatewayUrl/accessTokenUrl)是否可达(原始错误:${raw})`,
|
|
@@ -116,7 +114,7 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
116
114
|
this.#bot = null;
|
|
117
115
|
}
|
|
118
116
|
this.#started = false;
|
|
119
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
117
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
120
118
|
}
|
|
121
119
|
|
|
122
120
|
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
@@ -140,12 +138,11 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
140
138
|
break;
|
|
141
139
|
}
|
|
142
140
|
const messageId = `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
143
|
-
logger.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}));
|
|
141
|
+
this.#logger.info(
|
|
142
|
+
`send ${kind}:${conversation.id}`
|
|
143
|
+
+ ` | id: ${messageId}`
|
|
144
|
+
+ ` | ${truncatePreview(typeof body === 'string' ? body : String(body), 80)}`,
|
|
145
|
+
);
|
|
149
146
|
return messageId;
|
|
150
147
|
}
|
|
151
148
|
|
|
@@ -166,9 +163,9 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
166
163
|
await bot.recallDirectMessage?.(channelId, qqMsgId);
|
|
167
164
|
break;
|
|
168
165
|
}
|
|
169
|
-
logger.debug(formatCompact({
|
|
166
|
+
this.#logger.debug(formatCompact({
|
|
170
167
|
op: 'qq_recall',
|
|
171
|
-
endpoint: this.#options.config.
|
|
168
|
+
endpoint: this.#options.config.id,
|
|
172
169
|
kind,
|
|
173
170
|
messageId,
|
|
174
171
|
}));
|
|
@@ -178,23 +175,33 @@ export class QqWebsocketEndpoint implements EndpointInstance {
|
|
|
178
175
|
admit(msg: QqInboundMessage): void {
|
|
179
176
|
if (!this.#open) return;
|
|
180
177
|
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
178
|
+
const content = formatInboundContent(msg);
|
|
179
|
+
this.#logger.info(
|
|
180
|
+
`recv ${conversation.kind}:${conversation.id}`
|
|
181
|
+
+ (msg.authorId ? ` from ${msg.authorId}` : '')
|
|
182
|
+
+ (msg.mentioned ? ' (mentioned)' : '')
|
|
183
|
+
+ ` | ${truncatePreview(content, 80)}`,
|
|
184
|
+
);
|
|
181
185
|
void this.#options.gateway.receive({
|
|
182
186
|
conversation,
|
|
183
187
|
message: { conversation, id: msg.id },
|
|
184
|
-
content
|
|
188
|
+
content,
|
|
185
189
|
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
186
|
-
sender:
|
|
190
|
+
sender: {
|
|
191
|
+
id: msg.authorId,
|
|
192
|
+
name: senderDisplayName(msg) || undefined,
|
|
193
|
+
...(msg.authorRoles?.length ? { roles: msg.authorRoles } : {}),
|
|
194
|
+
},
|
|
195
|
+
endpointId: this.#options.config.id,
|
|
196
|
+
...(msg.mentioned ? { mentioned: true } : {}),
|
|
187
197
|
metadata: Object.freeze({
|
|
188
|
-
endpoint: this.#options.config.name,
|
|
189
198
|
channelKind: msg.channelKind,
|
|
190
199
|
userId: msg.authorId,
|
|
191
200
|
guildId: msg.guildId,
|
|
192
201
|
roles: msg.authorRoles,
|
|
193
|
-
// AT 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
194
|
-
...(msg.mentioned ? { mentioned: true } : {}),
|
|
195
202
|
}),
|
|
196
203
|
}).catch((err) => {
|
|
197
|
-
logger.warn(formatCompact({
|
|
204
|
+
this.#logger.warn(formatCompact({
|
|
198
205
|
op: 'qq_gateway_receive_failed',
|
|
199
206
|
target: `${conversation.kind}:${conversation.id}`,
|
|
200
207
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -257,6 +264,8 @@ export interface QqHttpEndpointOptions {
|
|
|
257
264
|
|
|
258
265
|
/** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
|
|
259
266
|
export class QqHttpEndpoint implements EndpointInstance {
|
|
267
|
+
readonly #logger!: ReturnType<typeof getAdapterLogger>;
|
|
268
|
+
|
|
260
269
|
readonly #options: QqHttpEndpointOptions;
|
|
261
270
|
readonly #createBot: CreateQqHttpBot;
|
|
262
271
|
#bot: QqHttpBotTransport | null = null;
|
|
@@ -267,13 +276,14 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
267
276
|
readonly management: EndpointManagement = createQqEndpointManagement(this);
|
|
268
277
|
|
|
269
278
|
constructor(options: QqHttpEndpointOptions) {
|
|
279
|
+
this.#logger = getAdapterLogger('qq', options.config.id);
|
|
270
280
|
this.#options = options;
|
|
271
281
|
this.#createBot = options.createBot ?? defaultCreateHttpBot;
|
|
272
282
|
}
|
|
273
283
|
|
|
274
284
|
/** Live endpoint 名(Console/AdapterIndex 展示用)。 */
|
|
275
285
|
get name(): string {
|
|
276
|
-
return this.#options.config.
|
|
286
|
+
return this.#options.config.id;
|
|
277
287
|
}
|
|
278
288
|
|
|
279
289
|
async start(): Promise<void> {
|
|
@@ -281,22 +291,19 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
281
291
|
this.#started = true;
|
|
282
292
|
try {
|
|
283
293
|
this.#unregisterAgent = registerQqAgentEndpoint(
|
|
284
|
-
this.#options.config.
|
|
294
|
+
this.#options.config.id,
|
|
285
295
|
this as unknown as QqWebsocketEndpoint,
|
|
286
296
|
);
|
|
287
297
|
this.#setupRoutes();
|
|
288
298
|
this.#bot = this.#createBot(this.#options.config);
|
|
289
299
|
this.#bindBot(this.#bot);
|
|
290
300
|
await this.#bot.start();
|
|
291
|
-
logger.info(
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
mode: this.#options.config.mode,
|
|
295
|
-
path: this.#options.config.webhookPath,
|
|
296
|
-
}));
|
|
301
|
+
this.#logger.info(
|
|
302
|
+
`connected (${this.#options.config.mode}) | path: ${this.#options.config.webhookPath}`,
|
|
303
|
+
);
|
|
297
304
|
} catch (error) {
|
|
298
305
|
await this.stop();
|
|
299
|
-
logger.error('Failed to connect QQ HTTP receiver:', error);
|
|
306
|
+
this.#logger.error('Failed to connect QQ HTTP receiver:', error);
|
|
300
307
|
throw error;
|
|
301
308
|
}
|
|
302
309
|
}
|
|
@@ -324,7 +331,7 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
324
331
|
this.#bot = null;
|
|
325
332
|
}
|
|
326
333
|
this.#started = false;
|
|
327
|
-
logger.debug(formatCompact({ op: 'disconnect'
|
|
334
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
328
335
|
}
|
|
329
336
|
|
|
330
337
|
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
@@ -347,7 +354,13 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
347
354
|
result = await bot.sendDirectMessage(conversation.id, body);
|
|
348
355
|
break;
|
|
349
356
|
}
|
|
350
|
-
|
|
357
|
+
const messageId = `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
|
|
358
|
+
this.#logger.info(
|
|
359
|
+
`send ${kind}:${conversation.id}`
|
|
360
|
+
+ ` | id: ${messageId}`
|
|
361
|
+
+ ` | ${truncatePreview(typeof body === 'string' ? body : String(body), 80)}`,
|
|
362
|
+
);
|
|
363
|
+
return messageId;
|
|
351
364
|
}
|
|
352
365
|
|
|
353
366
|
async recallMessage(messageId: string): Promise<void> {
|
|
@@ -367,9 +380,9 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
367
380
|
await bot.recallDirectMessage?.(channelId, qqMsgId);
|
|
368
381
|
break;
|
|
369
382
|
}
|
|
370
|
-
logger.debug(formatCompact({
|
|
383
|
+
this.#logger.debug(formatCompact({
|
|
371
384
|
op: 'qq_recall',
|
|
372
|
-
endpoint: this.#options.config.
|
|
385
|
+
endpoint: this.#options.config.id,
|
|
373
386
|
kind,
|
|
374
387
|
messageId,
|
|
375
388
|
}));
|
|
@@ -378,23 +391,33 @@ export class QqHttpEndpoint implements EndpointInstance {
|
|
|
378
391
|
admit(msg: QqInboundMessage): void {
|
|
379
392
|
if (!this.#open) return;
|
|
380
393
|
const conversation = qqInboundConversation(String(this.#options.id), msg);
|
|
394
|
+
const content = formatInboundContent(msg);
|
|
395
|
+
this.#logger.info(
|
|
396
|
+
`recv ${conversation.kind}:${conversation.id}`
|
|
397
|
+
+ (msg.authorId ? ` from ${msg.authorId}` : '')
|
|
398
|
+
+ (msg.mentioned ? ' (mentioned)' : '')
|
|
399
|
+
+ ` | ${truncatePreview(content, 80)}`,
|
|
400
|
+
);
|
|
381
401
|
void this.#options.gateway.receive({
|
|
382
402
|
conversation,
|
|
383
403
|
message: { conversation, id: msg.id },
|
|
384
|
-
content
|
|
404
|
+
content,
|
|
385
405
|
...(msg.segments?.length ? { segments: msg.segments } : {}),
|
|
386
|
-
sender:
|
|
406
|
+
sender: {
|
|
407
|
+
id: msg.authorId,
|
|
408
|
+
name: senderDisplayName(msg) || undefined,
|
|
409
|
+
...(msg.authorRoles?.length ? { roles: msg.authorRoles } : {}),
|
|
410
|
+
},
|
|
411
|
+
endpointId: this.#options.config.id,
|
|
412
|
+
...(msg.mentioned ? { mentioned: true } : {}),
|
|
387
413
|
metadata: Object.freeze({
|
|
388
|
-
endpoint: this.#options.config.name,
|
|
389
414
|
channelKind: msg.channelKind,
|
|
390
415
|
userId: msg.authorId,
|
|
391
416
|
guildId: msg.guildId,
|
|
392
417
|
roles: msg.authorRoles,
|
|
393
|
-
// AT 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
394
|
-
...(msg.mentioned ? { mentioned: true } : {}),
|
|
395
418
|
}),
|
|
396
419
|
}).catch((err) => {
|
|
397
|
-
logger.warn(formatCompact({
|
|
420
|
+
this.#logger.warn(formatCompact({
|
|
398
421
|
op: 'qq_gateway_receive_failed',
|
|
399
422
|
target: `${conversation.kind}:${conversation.id}`,
|
|
400
423
|
error: err instanceof Error ? err.message : String(err),
|
package/src/index.ts
CHANGED
|
@@ -13,6 +13,16 @@ export {
|
|
|
13
13
|
type ResolvedQqWebsocketConfig,
|
|
14
14
|
} from './protocol.js';
|
|
15
15
|
|
|
16
|
+
export {
|
|
17
|
+
DEFAULT_QQ_BOT_KIND,
|
|
18
|
+
QQ_INTENTS_BY_KIND,
|
|
19
|
+
defaultQqEndpointIntentFields,
|
|
20
|
+
parseQqBotKind,
|
|
21
|
+
resolveQqIntents,
|
|
22
|
+
type QqBotKind,
|
|
23
|
+
type QqIntent,
|
|
24
|
+
} from './qq-intents.js';
|
|
25
|
+
|
|
16
26
|
export {
|
|
17
27
|
formatOutbound,
|
|
18
28
|
resolveMediaFile,
|
|
@@ -58,5 +68,4 @@ export {
|
|
|
58
68
|
normalizeQqGuildSenderForPermit,
|
|
59
69
|
platformPermit,
|
|
60
70
|
qqGuildPermitResolver,
|
|
61
|
-
registerQqPlatformPermitChecker,
|
|
62
71
|
} from './platform-permit.js';
|
package/src/platform-permit.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* QQ 官方频道 platform permit(guild/channel,非 QQ 群 group_*)
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import type { PermissionSubject } from '@zhin.js/permission';
|
|
5
5
|
|
|
6
6
|
const ADAPTER = 'qq';
|
|
7
7
|
|
|
@@ -36,10 +36,9 @@ export function normalizeQqGuildSenderForPermit(input: {
|
|
|
36
36
|
return { role: 'member', permissions };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
export function checkQqPlatformPermit(perm: string,
|
|
40
|
-
const
|
|
41
|
-
const permissions = sender
|
|
42
|
-
const role = sender.role;
|
|
39
|
+
export function checkQqPlatformPermit(perm: string, subject: PermissionSubject): boolean {
|
|
40
|
+
const role = subject.sender?.role?.[0];
|
|
41
|
+
const permissions = subject.sender?.permissions ?? [];
|
|
43
42
|
const has = (t: string) => permissions.includes(t);
|
|
44
43
|
|
|
45
44
|
switch (perm) {
|
|
@@ -55,7 +54,3 @@ export function checkQqPlatformPermit(perm: string, message: Message<any>): bool
|
|
|
55
54
|
return false;
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
|
-
|
|
59
|
-
export function registerQqPlatformPermitChecker(): () => void {
|
|
60
|
-
return registerPlatformPermitChecker(ADAPTER, checkQqPlatformPermit);
|
|
61
|
-
}
|