@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +36 -9
  3. package/adapters/qq.js +9 -7
  4. package/adapters/qq.ts +9 -7
  5. package/commands/endpoint/cancel.js +1 -1
  6. package/commands/endpoint/cancel.ts +1 -1
  7. package/lib/endpoint.js +55 -45
  8. package/lib/index.d.ts +2 -1
  9. package/lib/index.js +2 -1
  10. package/lib/platform-permit.d.ts +2 -3
  11. package/lib/platform-permit.js +3 -11
  12. package/lib/protocol.d.ts +9 -5
  13. package/lib/protocol.js +11 -7
  14. package/lib/qq-agent-deps.d.ts +2 -2
  15. package/lib/qq-agent-deps.js +7 -7
  16. package/lib/qq-bind-persist.d.ts +2 -2
  17. package/lib/qq-bind-persist.js +5 -5
  18. package/lib/qq-bot-kind-prompt.d.ts +9 -0
  19. package/lib/qq-bot-kind-prompt.js +37 -0
  20. package/lib/qq-endpoint-commands.d.ts +13 -6
  21. package/lib/qq-endpoint-commands.js +89 -31
  22. package/lib/qq-endpoint-config.d.ts +4 -4
  23. package/lib/qq-endpoint-config.js +4 -4
  24. package/lib/qq-intents.d.ts +27 -0
  25. package/lib/qq-intents.js +50 -0
  26. package/lib/qq-runtime-state.d.ts +17 -4
  27. package/lib/qq-runtime-state.js +3 -2
  28. package/lib/webhook.js +2 -0
  29. package/lib/ws.js +2 -2
  30. package/middlewares/bot-kind-reply.js +46 -0
  31. package/middlewares/bot-kind-reply.ts +60 -0
  32. package/package.json +23 -16
  33. package/plugin.js +6 -4
  34. package/schema.json +66 -9
  35. package/src/endpoint.ts +69 -46
  36. package/src/index.ts +10 -1
  37. package/src/platform-permit.ts +4 -9
  38. package/src/protocol.ts +19 -11
  39. package/src/qq-agent-deps.ts +8 -8
  40. package/src/qq-bind-persist.ts +5 -5
  41. package/src/qq-bot-kind-prompt.ts +47 -0
  42. package/src/qq-endpoint-commands.ts +125 -34
  43. package/src/qq-endpoint-config.ts +5 -5
  44. package/src/qq-intents.ts +76 -0
  45. package/src/qq-runtime-state.ts +21 -6
  46. package/src/webhook.ts +2 -0
  47. package/src/ws.ts +2 -2
  48. /package/commands/endpoint/add/{[[name]].js → [[id]].js} +0 -0
  49. /package/commands/endpoint/add/{[[name]].ts → [[id]].ts} +0 -0
  50. /package/commands/endpoint/remove/{[name].js → [id].js} +0 -0
  51. /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",
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.5",
39
- "@zhin.js/command": "1.0.7",
40
- "@zhin.js/core": "1.5.2",
41
- "@zhin.js/host-http": "1.0.6",
42
- "@zhin.js/im-contract": "1.0.1",
43
- "@zhin.js/logger": "1.0.75",
44
- "@zhin.js/plugin-runtime": "1.1.3"
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.5",
49
- "@zhin.js/agent": "1.1.3",
50
- "@zhin.js/core": "1.5.2",
51
- "@zhin.js/plugin-runtime": "1.1.3",
52
- "zhin.js": "6.0.2"
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.3",
71
- "@zhin.js/host-http": "1.0.6",
72
- "zhin.js": "6.0.2"
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 { registerQqPlatformPermitChecker } from "./lib/platform-permit.js";
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
- // 平台权限门禁:guild_owner / guild_admin / manage_roles / manage_channels(agent 工具 platformPermit)
14
- return registerQqPlatformPermitChecker();
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": ["string", "number"],
22
- "description": "Endpoint Owner(qq endpoint 管理命令权限、AI master 角色);endpoints[i] 可逐项覆盖"
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。每项与顶层字段同构(name 必填,其余覆盖顶层)",
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
- "name",
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, getLogger } from '@zhin.js/logger';
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.name;
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.name, this);
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(formatCompact({
81
- op: 'connect',
82
- endpoint: this.#options.config.name,
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', endpoint: this.#options.config.name }));
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.debug(formatCompact({
144
- op: 'qq_send',
145
- endpoint: this.#options.config.name,
146
- target: `${kind}:${conversation.id}`,
147
- messageId,
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.name,
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: formatInboundContent(msg),
188
+ content,
185
189
  ...(msg.segments?.length ? { segments: msg.segments } : {}),
186
- sender: senderDisplayName(msg),
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.name;
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.name,
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(formatCompact({
292
- op: 'connect',
293
- endpoint: this.#options.config.name,
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', endpoint: this.#options.config.name }));
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
- return `${kind}-${conversation.id}:${resolveOutboundMessageId(result)}`;
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.name,
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: formatInboundContent(msg),
404
+ content,
385
405
  ...(msg.segments?.length ? { segments: msg.segments } : {}),
386
- sender: senderDisplayName(msg),
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';
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * QQ 官方频道 platform permit(guild/channel,非 QQ 群 group_*)
3
3
  */
4
- import { registerPlatformPermitChecker, type Message } from '@zhin.js/core';
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, message: Message<any>): boolean {
40
- const sender = message.$sender as { role?: string; permissions?: string[] };
41
- const permissions = sender.permissions ?? [];
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
- }