@zhin.js/adapter-qq 8.0.0 → 8.0.1

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 CHANGED
@@ -1,5 +1,28 @@
1
1
  # @zhin.js/adapter-qq
2
2
 
3
+ ## 8.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 1fc78bc: Unify native platform Client access behind the literal `adapter` discriminant. Handlers infer both native events and Clients, while command, inbound/outbound middleware, and both Agent tool authoring surfaces expose the exact operation-scoped Client through a lazy `$client` getter. Definitions without `adapter` keep `$client` typed as `unknown`, and runtime dispatch rejects adapter mismatches before resolving the Client. Bundled platform tools now use this single path instead of model-provided endpoint ids and adapter-specific dependency wrappers. Every adapter registers one Client/EventMap contract, and protocol adapters including NapCat, Milky, OneBot and Satori now produce transport-independent Client objects rather than letting Endpoint instances impersonate Clients.
8
+ - Updated dependencies [e9c6a73]
9
+ - Updated dependencies [4e8117c]
10
+ - Updated dependencies [902fa35]
11
+ - Updated dependencies [54bfd6b]
12
+ - Updated dependencies [12025ee]
13
+ - Updated dependencies [09b14d6]
14
+ - Updated dependencies [1fc78bc]
15
+ - @zhin.js/agent@1.1.16
16
+ - @zhin.js/adapter@1.2.1
17
+ - @zhin.js/core@1.5.14
18
+ - @zhin.js/host-http@1.0.13
19
+ - @zhin.js/command@1.0.16
20
+ - @zhin.js/logger@1.0.77
21
+ - @zhin.js/middleware@1.0.13
22
+ - zhin.js@6.0.14
23
+ - @zhin.js/feature-kit@1.0.13
24
+ - @zhin.js/permission@1.0.4
25
+
3
26
  ## 8.0.0
4
27
 
5
28
  ### Patch Changes
package/README.md CHANGED
@@ -20,7 +20,7 @@ pnpm add @zhin.js/adapter-qq
20
20
  ## Plugin Runtime
21
21
 
22
22
  - `@zhin.js/adapter` — 约定式 `adapters/qq.ts`(`defineAdapter`)
23
- - `@zhin.js/core` — `messageGatewayToken` 入站/出站
23
+ - `@zhin.js/core` — `Endpoint.emit(...)` 入站、`outboundMessageToken` 出站
24
24
  - `zhin.js` — `plugin.ts`(`definePlugin`)
25
25
  - 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
26
26
  - **无需** `@zhin.js/host-http` / `@zhin.js/host-router`(WebSocket 路径)
@@ -144,6 +144,15 @@ Markdown + 指令按钮组。按钮值与手动输入共用同一解析入口,
144
144
  不支持原生按钮的 Adapter 会在 Core 中把同一 keyboard 语义降级为编号列表,并保存数字到
145
145
  payload 的回跳映射。图片、音频、视频和文件仍沿 canonical `MediaRef` 出站链投递。
146
146
 
147
+ ## 故障排查
148
+
149
+ | 现象 | 排查 |
150
+ | --- | --- |
151
+ | Gateway Identify 后断线 | 检查 `botKind` 与 intents;公域机器人不要订阅受限 intent |
152
+ | 二维码绑定后仍离线 | 在 Console 登录待办确认结果,并检查 App ID、Secret 与 Token |
153
+ | Webhook 无事件 | 检查 HTTP Host、公网路径、平台回调与签名参数 |
154
+ | Markdown/按钮发送失败 | 检查平台模板与权限,并按 Endpoint 能力降级 |
155
+
147
156
  ## 许可证
148
157
 
149
158
  MIT License
package/adapters/qq.js CHANGED
@@ -3,7 +3,6 @@
3
3
  * Convention entry: discover `adapters/qq.ts` → defineAdapter.
4
4
  */
5
5
  import { defineAdapter } from 'zhin.js/adapter';
6
- import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
7
6
  import { httpHostToken } from '@zhin.js/host-http';
8
7
  import { QqHttpEndpoint, QqWebsocketEndpoint } from "../lib/endpoint.js";
9
8
  import { resolveQqConfig, } from "../lib/protocol.js";
@@ -22,8 +21,6 @@ export default defineAdapter({
22
21
  },
23
22
  create(context) {
24
23
  const config = resolveQqConfig(context.config);
25
- const gateway = context.use(messageGatewayToken);
26
- const sideEvents = context.use(sideEventGatewayToken);
27
24
  // 注册到插件运行时状态(qq.endpoint list 的"运行中"数据源)
28
25
  const state = context.use(qqRuntimeStateToken);
29
26
  state.endpoints.set(config.id, {
@@ -35,14 +32,10 @@ export default defineAdapter({
35
32
  const endpoint = config.mode === 'websocket'
36
33
  ? new QqWebsocketEndpoint({
37
34
  id: context.id,
38
- gateway,
39
- sideEvents,
40
35
  config,
41
36
  })
42
37
  : new QqHttpEndpoint({
43
38
  id: context.id,
44
- gateway,
45
- sideEvents,
46
39
  http: context.use(httpHostToken),
47
40
  config,
48
41
  });
package/adapters/qq.ts CHANGED
@@ -2,7 +2,6 @@
2
2
  * Convention entry: discover `adapters/qq.ts` → defineAdapter.
3
3
  */
4
4
  import { defineAdapter } from 'zhin.js/adapter';
5
- import { messageGatewayToken, sideEventGatewayToken } from '@zhin.js/core/runtime';
6
5
  import { httpHostToken } from '@zhin.js/host-http';
7
6
  import { QqHttpEndpoint, QqWebsocketEndpoint } from '../src/endpoint.js';
8
7
  import {
@@ -43,8 +42,6 @@ export default defineAdapter<QqAdapterConfig>({
43
42
  },
44
43
  create(context) {
45
44
  const config = resolveQqConfig(context.config);
46
- const gateway = context.use(messageGatewayToken);
47
- const sideEvents = context.use(sideEventGatewayToken);
48
45
  // 注册到插件运行时状态(qq.endpoint list 的"运行中"数据源)
49
46
  const state = context.use(qqRuntimeStateToken);
50
47
  state.endpoints.set(config.id, {
@@ -56,14 +53,10 @@ export default defineAdapter<QqAdapterConfig>({
56
53
  const endpoint = config.mode === 'websocket'
57
54
  ? new QqWebsocketEndpoint({
58
55
  id: context.id,
59
- gateway,
60
- sideEvents,
61
56
  config,
62
57
  })
63
58
  : new QqHttpEndpoint({
64
59
  id: context.id,
65
- gateway,
66
- sideEvents,
67
60
  http: context.use(httpHostToken),
68
61
  config,
69
62
  });
@@ -1,23 +1,21 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
- import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
4
 
6
- export default defineAgentTool<{ endpoint_id: string; guild_id: string; channel_id: string; user_id: string; role_id: string }>({
5
+ export default defineAgentTool<{ guild_id: string; channel_id: string; user_id: string; role_id: string }>({
7
6
  description: '给成员添加 QQ 频道角色',
8
7
  inputSchema: z.object({
9
- endpoint_id: z.string().describe('Endpoint 名称'),
10
8
  guild_id: z.string().describe('频道 ID'),
11
9
  channel_id: z.string().describe('子频道 ID'),
12
10
  user_id: z.string().describe('用户 ID'),
13
11
  role_id: z.string().describe('角色 ID'),
14
12
  }),
15
- platforms: ['qq'],
13
+ adapter: 'qq',
16
14
  tags: ['qq'],
17
15
  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);
16
+ async execute({ guild_id, channel_id, user_id, role_id }: { guild_id: string; channel_id: string; user_id: string; role_id: string }, context) {
17
+ const client = context.$client;
18
+ const success = await client.addMemberRole(guild_id, channel_id, user_id, role_id);
21
19
  return { success, message: success ? '已给成员添加角色' : '操作失败' };
22
20
  },
23
21
  });
@@ -1,19 +1,17 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
- import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
4
 
6
- export default defineAgentTool<{ endpoint_id: string; channel_id: string }>({
5
+ export default defineAgentTool<{ channel_id: string }>({
7
6
  description: '获取 QQ 频道中指定子频道的详细信息',
8
7
  inputSchema: z.object({
9
- endpoint_id: z.string().describe('Endpoint 名称'),
10
8
  channel_id: z.string().describe('子频道 ID'),
11
9
  }),
12
- platforms: ['qq'],
10
+ adapter: 'qq',
13
11
  tags: ['qq'],
14
12
  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);
13
+ async execute({ channel_id }: { channel_id: string }, context) {
14
+ const client = context.$client;
15
+ return client.getChannelInfo(channel_id);
18
16
  },
19
17
  });
@@ -1,22 +1,20 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
- import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
4
 
6
- export default defineAgentTool<{ endpoint_id: string; guild_id: string; name: string; color?: number }>({
5
+ export default defineAgentTool<{ guild_id: string; name: string; color?: number }>({
7
6
  description: '创建 QQ 频道角色',
8
7
  inputSchema: z.object({
9
- endpoint_id: z.string().describe('Endpoint 名称'),
10
8
  guild_id: z.string().describe('频道 ID'),
11
9
  name: z.string().describe('角色名称'),
12
10
  color: z.number().optional().describe('颜色(RGB 十进制数值)'),
13
11
  }),
14
- platforms: ['qq'],
12
+ adapter: 'qq',
15
13
  tags: ['qq'],
16
14
  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);
15
+ async execute({ guild_id, name, color }: { guild_id: string; name: string; color?: number }, context) {
16
+ const client = context.$client;
17
+ const role = await client.createGuildRole(guild_id, name, color);
20
18
  return { success: !!role, role, message: role ? `角色 "${name}" 创建成功` : '创建失败' };
21
19
  },
22
20
  });
@@ -1,20 +1,18 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
- import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
4
 
6
- export default defineAgentTool<{ endpoint_id: string; guild_id: string }>({
5
+ export default defineAgentTool<{ guild_id: string }>({
7
6
  description: '获取 QQ 频道下的子频道列表',
8
7
  inputSchema: z.object({
9
- endpoint_id: z.string().describe('Endpoint 名称'),
10
8
  guild_id: z.string().describe('频道 ID'),
11
9
  }),
12
- platforms: ['qq'],
10
+ adapter: 'qq',
13
11
  tags: ['qq'],
14
12
  permissions: [platformPermit('manage_channels')],
15
- async execute({ endpoint_id, guild_id }: { endpoint_id: string; guild_id: string }) {
16
- const endpoint = getQqAgentDeps().getEndpoint(endpoint_id);
17
- const channels = await endpoint.getChannels(guild_id);
13
+ async execute({ guild_id }: { guild_id: string }, context) {
14
+ const client = context.$client;
15
+ const channels = await client.getChannels(guild_id);
18
16
  return { channels, count: channels.length };
19
17
  },
20
18
  });
@@ -1,17 +1,14 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
- import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
4
3
 
5
- export default defineAgentTool<{ endpoint_id: string }>({
4
+ export default defineAgentTool<Record<string, never>>({
6
5
  description: '获取 QQ 频道列表',
7
- inputSchema: z.object({
8
- endpoint_id: z.string().describe('Endpoint 名称'),
9
- }),
10
- platforms: ['qq'],
6
+ inputSchema: z.object({}),
7
+ adapter: 'qq',
11
8
  tags: ['qq'],
12
- async execute({ endpoint_id }: { endpoint_id: string }) {
13
- const endpoint = getQqAgentDeps().getEndpoint(endpoint_id);
14
- const guilds = await endpoint.getGuilds();
9
+ async execute(_input, context) {
10
+ const client = context.$client;
11
+ const guilds = await client.getGuilds();
15
12
  return { guilds, count: guilds.length };
16
13
  },
17
14
  });
@@ -1,20 +1,18 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
- import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
4
 
6
- export default defineAgentTool<{ endpoint_id: string; guild_id: string }>({
5
+ export default defineAgentTool<{ guild_id: string }>({
7
6
  description: '获取 QQ 频道角色列表',
8
7
  inputSchema: z.object({
9
- endpoint_id: z.string().describe('Endpoint 名称'),
10
8
  guild_id: z.string().describe('频道 ID'),
11
9
  }),
12
- platforms: ['qq'],
10
+ adapter: 'qq',
13
11
  tags: ['qq'],
14
12
  permissions: [platformPermit('manage_roles')],
15
- async execute({ endpoint_id, guild_id }: { endpoint_id: string; guild_id: string }) {
16
- const endpoint = getQqAgentDeps().getEndpoint(endpoint_id);
17
- const roles = await endpoint.getGuildRoles(guild_id);
13
+ async execute({ guild_id }: { guild_id: string }, context) {
14
+ const client = context.$client;
15
+ const roles = await client.getGuildRoles(guild_id);
18
16
  return { roles, count: roles.length };
19
17
  },
20
18
  });
@@ -1,20 +1,18 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
- import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
4
 
6
- export default defineAgentTool<{ endpoint_id: string; guild_id: string; user_id: string }>({
5
+ export default defineAgentTool<{ guild_id: string; user_id: string }>({
7
6
  description: '获取 QQ 频道中指定成员的详细信息',
8
7
  inputSchema: z.object({
9
- endpoint_id: z.string().describe('Endpoint 名称'),
10
8
  guild_id: z.string().describe('频道 ID'),
11
9
  user_id: z.string().describe('用户 ID'),
12
10
  }),
13
- platforms: ['qq'],
11
+ adapter: 'qq',
14
12
  tags: ['qq'],
15
13
  permissions: [platformPermit('guild_admin')],
16
- async execute({ endpoint_id, guild_id, user_id }: { endpoint_id: string; guild_id: string; user_id: string }) {
17
- const endpoint = getQqAgentDeps().getEndpoint(endpoint_id);
18
- return endpoint.getGuildMember(guild_id, user_id);
14
+ async execute({ guild_id, user_id }: { guild_id: string; user_id: string }, context) {
15
+ const client = context.$client;
16
+ return client.getGuildMember(guild_id, user_id);
19
17
  },
20
18
  });
@@ -1,23 +1,21 @@
1
1
  import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
- import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
4
 
6
- export default defineAgentTool<{ endpoint_id: string; guild_id: string; channel_id: string; user_id: string; role_id: string }>({
5
+ export default defineAgentTool<{ guild_id: string; channel_id: string; user_id: string; role_id: string }>({
7
6
  description: '移除成员的 QQ 频道角色',
8
7
  inputSchema: z.object({
9
- endpoint_id: z.string().describe('Endpoint 名称'),
10
8
  guild_id: z.string().describe('频道 ID'),
11
9
  channel_id: z.string().describe('子频道 ID'),
12
10
  user_id: z.string().describe('用户 ID'),
13
11
  role_id: z.string().describe('角色 ID'),
14
12
  }),
15
- platforms: ['qq'],
13
+ adapter: 'qq',
16
14
  tags: ['qq'],
17
15
  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.removeMemberRole(guild_id, channel_id, user_id, role_id);
16
+ async execute({ guild_id, channel_id, user_id, role_id }: { guild_id: string; channel_id: string; user_id: string; role_id: string }, context) {
17
+ const client = context.$client;
18
+ const success = await client.removeMemberRole(guild_id, channel_id, user_id, role_id);
21
19
  return { success, message: success ? '已移除成员的角色' : '操作失败' };
22
20
  },
23
21
  });
@@ -0,0 +1,12 @@
1
+ import type { QqInboundMessage } from './protocol.js';
2
+ import type { QqBotTransport } from './ws.js';
3
+ export type QqClientEventMap = Record<string, QqInboundMessage | unknown>;
4
+ declare module '@zhin.js/feature-kit' {
5
+ interface AdapterClientRegistry {
6
+ readonly qq: {
7
+ readonly client: QqBotTransport;
8
+ readonly events: QqClientEventMap;
9
+ };
10
+ }
11
+ }
12
+ export declare const qqClient: import("@zhin.js/adapter").EndpointClientToken<QqBotTransport, QqClientEventMap>;
package/lib/client.js ADDED
@@ -0,0 +1,2 @@
1
+ import { defineEndpointClient } from 'zhin.js/adapter';
2
+ export const qqClient = defineEndpointClient('qq');
package/lib/endpoint.d.ts CHANGED
@@ -1,27 +1,26 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * QQ endpoints — lifecycle, outbound, admit, agent tool surface.
3
4
  */
4
- import { type EndpointControl, type EndpointInstance, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
5
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
5
+ import { type EndpointControl, type EndpointManagement, type EndpointSendRequest } from 'zhin.js/adapter';
6
6
  import type { HttpHost } from '@zhin.js/host-http';
7
7
  import type { CapabilityId } from 'zhin.js';
8
8
  import { type QqInboundMessage, type ResolvedQqHttpConfig, type ResolvedQqWebsocketConfig } from './protocol.js';
9
- import { type CreateQqHttpBot } from './webhook.js';
10
- import { type CreateQqBot } from './ws.js';
9
+ import { type CreateQqHttpBot, type QqHttpBotTransport } from './webhook.js';
10
+ import { type CreateQqBot, type QqBotTransport } from './ws.js';
11
11
  export type { CreateQqBot, QqBotTransport } from './ws.js';
12
12
  export type { CreateQqHttpBot, QqHttpBotTransport } from './webhook.js';
13
13
  export interface QqEndpointOptions {
14
14
  readonly id: CapabilityId;
15
- readonly gateway: MessageGateway;
16
- readonly sideEvents?: SideEventGateway;
17
15
  readonly config: ResolvedQqWebsocketConfig;
18
16
  readonly createBot?: CreateQqBot;
19
17
  }
20
- export declare class QqWebsocketEndpoint implements EndpointInstance {
18
+ export declare class QqWebsocketEndpoint extends Endpoint<QqBotTransport> {
21
19
  #private;
22
20
  readonly management: EndpointManagement;
23
21
  readonly control: EndpointControl;
24
22
  constructor(options: QqEndpointOptions);
23
+ get client(): QqBotTransport;
25
24
  /** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
26
25
  get name(): string;
27
26
  start(): Promise<void>;
@@ -32,29 +31,20 @@ export declare class QqWebsocketEndpoint implements EndpointInstance {
32
31
  recallMessage(messageId: string): Promise<void>;
33
32
  /** Test / internal: admit a message when open. */
34
33
  admit(msg: QqInboundMessage): void;
35
- getGuilds(): Promise<unknown[]>;
36
- getChannels(guildId: string): Promise<unknown[]>;
37
- getChannelInfo(channelId: string): Promise<unknown>;
38
- getGuildMember(guildId: string, userId: string): Promise<unknown>;
39
- getGuildRoles(guildId: string): Promise<unknown[]>;
40
- createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
41
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
42
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
43
34
  }
44
35
  export interface QqHttpEndpointOptions {
45
36
  readonly id: CapabilityId;
46
- readonly gateway: MessageGateway;
47
- readonly sideEvents?: SideEventGateway;
48
37
  readonly http: HttpHost;
49
38
  readonly config: ResolvedQqHttpConfig;
50
39
  readonly createBot?: CreateQqHttpBot;
51
40
  }
52
41
  /** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
53
- export declare class QqHttpEndpoint implements EndpointInstance {
42
+ export declare class QqHttpEndpoint extends Endpoint<QqHttpBotTransport> {
54
43
  #private;
55
44
  readonly management: EndpointManagement;
56
45
  readonly control: EndpointControl;
57
46
  constructor(options: QqHttpEndpointOptions);
47
+ get client(): QqHttpBotTransport;
58
48
  /** Live endpoint 名(Console/AdapterIndex 展示用)。 */
59
49
  get name(): string;
60
50
  start(): Promise<void>;
@@ -64,12 +54,4 @@ export declare class QqHttpEndpoint implements EndpointInstance {
64
54
  send({ conversation, payload }: EndpointSendRequest): Promise<string>;
65
55
  recallMessage(messageId: string): Promise<void>;
66
56
  admit(msg: QqInboundMessage): void;
67
- getGuilds(): Promise<unknown[]>;
68
- getChannels(guildId: string): Promise<unknown[]>;
69
- getChannelInfo(channelId: string): Promise<unknown>;
70
- getGuildMember(guildId: string, userId: string): Promise<unknown>;
71
- getGuildRoles(guildId: string): Promise<unknown[]>;
72
- createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
73
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
74
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
75
57
  }
package/lib/endpoint.js CHANGED
@@ -1,29 +1,32 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * QQ endpoints — lifecycle, outbound, admit, agent tool surface.
3
4
  */
4
5
  import { createRecallEndpointControl, } from 'zhin.js/adapter';
5
6
  import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
6
7
  import { formatOutbound } from './outbound.js';
7
- import { registerQqAgentEndpoint } from './qq-agent-deps.js';
8
8
  import { formatInboundContent, parseCompoundMessageId, qqInboundConversation, qqOutboundKind, resolveOutboundMessageId, senderDisplayName, } from './protocol.js';
9
9
  import { defaultCreateHttpBot, registerQqWebhookRoutes, } from './webhook.js';
10
10
  import { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
11
11
  import { bindQqBotSideEvents, receiveQqSideEvent, } from './side-event-dispatch.js';
12
- export class QqWebsocketEndpoint {
12
+ export class QqWebsocketEndpoint extends Endpoint {
13
13
  #logger;
14
14
  #options;
15
15
  #createBot;
16
16
  #bot = null;
17
17
  #open = false;
18
18
  #started = false;
19
- #unregisterAgent;
20
- management = createQqEndpointManagement(this);
19
+ management = createQqEndpointManagement(() => this.#requireBot());
21
20
  control = createRecallEndpointControl((id) => this.recallMessage(id));
22
21
  constructor(options) {
22
+ super();
23
23
  this.#logger = getAdapterLogger('qq', options.config.id);
24
24
  this.#options = options;
25
25
  this.#createBot = options.createBot ?? defaultCreateBot;
26
26
  }
27
+ get client() {
28
+ return this.#requireBot();
29
+ }
27
30
  /** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
28
31
  get name() {
29
32
  return this.#options.config.id;
@@ -33,7 +36,6 @@ export class QqWebsocketEndpoint {
33
36
  return;
34
37
  this.#started = true;
35
38
  try {
36
- this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.id, this);
37
39
  this.#bot = this.#createBot(this.#options.config);
38
40
  this.#bindBot(this.#bot);
39
41
  await this.#bot.start();
@@ -54,8 +56,6 @@ export class QqWebsocketEndpoint {
54
56
  }
55
57
  async stop() {
56
58
  this.#open = false;
57
- this.#unregisterAgent?.();
58
- this.#unregisterAgent = undefined;
59
59
  if (this.#bot) {
60
60
  try {
61
61
  this.#bot.removeAllListeners();
@@ -130,7 +130,7 @@ export class QqWebsocketEndpoint {
130
130
  + (msg.authorId ? ` from ${msg.authorId}` : '')
131
131
  + (msg.mentioned ? ' (mentioned)' : '')
132
132
  + ` | ${truncatePreview(content, 80)}`);
133
- void this.#options.gateway.receive({
133
+ void this.emit('message.receive', {
134
134
  conversation,
135
135
  message: { conversation, id: msg.id },
136
136
  content,
@@ -156,38 +156,25 @@ export class QqWebsocketEndpoint {
156
156
  }));
157
157
  });
158
158
  }
159
- getGuilds() {
160
- return this.#requireBot().getGuilds();
161
- }
162
- getChannels(guildId) {
163
- return this.#requireBot().getChannels(guildId);
164
- }
165
- getChannelInfo(channelId) {
166
- return this.#requireBot().getChannelInfo(channelId);
167
- }
168
- getGuildMember(guildId, userId) {
169
- return this.#requireBot().getGuildMember(guildId, userId);
170
- }
171
- getGuildRoles(guildId) {
172
- return this.#requireBot().getGuildRoles(guildId);
173
- }
174
- createGuildRole(guildId, name, color) {
175
- return this.#requireBot().createGuildRole(guildId, name, color);
176
- }
177
- addMemberRole(guildId, channelId, userId, roleId) {
178
- return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
179
- }
180
- removeMemberRole(guildId, channelId, userId, roleId) {
181
- return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
182
- }
183
159
  #bindBot(bot) {
184
160
  bindQqBotInboundEvents(bot, (raw) => {
161
+ this.#emitPlatformEvent('message', raw);
185
162
  const msg = normalizeQqMessage(raw);
186
163
  if (msg)
187
164
  this.admit(msg);
188
165
  });
189
166
  bindQqBotSideEvents(bot, (eventName, raw) => {
190
- receiveQqSideEvent(this.#options.sideEvents, this.#options.config.id, bot, eventName, raw, this.#logger);
167
+ this.#emitPlatformEvent(eventName, raw);
168
+ receiveQqSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, bot, eventName, raw, this.#logger);
169
+ });
170
+ }
171
+ #emitPlatformEvent(name, event) {
172
+ void this.emitPlatform(name, event).catch((error) => {
173
+ this.#logger.warn(formatCompact({
174
+ op: 'qq_platform_event_failed',
175
+ event: name,
176
+ error: error instanceof Error ? error.message : String(error),
177
+ }));
191
178
  });
192
179
  }
193
180
  #requireBot() {
@@ -197,7 +184,7 @@ export class QqWebsocketEndpoint {
197
184
  }
198
185
  }
199
186
  /** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
200
- export class QqHttpEndpoint {
187
+ export class QqHttpEndpoint extends Endpoint {
201
188
  #logger;
202
189
  #options;
203
190
  #createBot;
@@ -205,14 +192,17 @@ export class QqHttpEndpoint {
205
192
  #routeReleases = [];
206
193
  #open = false;
207
194
  #started = false;
208
- #unregisterAgent;
209
- management = createQqEndpointManagement(this);
195
+ management = createQqEndpointManagement(() => this.#requireBot());
210
196
  control = createRecallEndpointControl((id) => this.recallMessage(id));
211
197
  constructor(options) {
198
+ super();
212
199
  this.#logger = getAdapterLogger('qq', options.config.id);
213
200
  this.#options = options;
214
201
  this.#createBot = options.createBot ?? defaultCreateHttpBot;
215
202
  }
203
+ get client() {
204
+ return this.#requireBot();
205
+ }
216
206
  /** Live endpoint 名(Console/AdapterIndex 展示用)。 */
217
207
  get name() {
218
208
  return this.#options.config.id;
@@ -222,7 +212,6 @@ export class QqHttpEndpoint {
222
212
  return;
223
213
  this.#started = true;
224
214
  try {
225
- this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.id, this);
226
215
  this.#setupRoutes();
227
216
  this.#bot = this.#createBot(this.#options.config);
228
217
  this.#bindBot(this.#bot);
@@ -245,8 +234,6 @@ export class QqHttpEndpoint {
245
234
  this.#open = false;
246
235
  for (const release of this.#routeReleases.splice(0))
247
236
  release();
248
- this.#unregisterAgent?.();
249
- this.#unregisterAgent = undefined;
250
237
  if (this.#bot) {
251
238
  try {
252
239
  this.#bot.removeAllListeners();
@@ -320,7 +307,7 @@ export class QqHttpEndpoint {
320
307
  + (msg.authorId ? ` from ${msg.authorId}` : '')
321
308
  + (msg.mentioned ? ' (mentioned)' : '')
322
309
  + ` | ${truncatePreview(content, 80)}`);
323
- void this.#options.gateway.receive({
310
+ void this.emit('message.receive', {
324
311
  conversation,
325
312
  message: { conversation, id: msg.id },
326
313
  content,
@@ -346,30 +333,6 @@ export class QqHttpEndpoint {
346
333
  }));
347
334
  });
348
335
  }
349
- getGuilds() {
350
- return this.#requireBot().getGuilds();
351
- }
352
- getChannels(guildId) {
353
- return this.#requireBot().getChannels(guildId);
354
- }
355
- getChannelInfo(channelId) {
356
- return this.#requireBot().getChannelInfo(channelId);
357
- }
358
- getGuildMember(guildId, userId) {
359
- return this.#requireBot().getGuildMember(guildId, userId);
360
- }
361
- getGuildRoles(guildId) {
362
- return this.#requireBot().getGuildRoles(guildId);
363
- }
364
- createGuildRole(guildId, name, color) {
365
- return this.#requireBot().createGuildRole(guildId, name, color);
366
- }
367
- addMemberRole(guildId, channelId, userId, roleId) {
368
- return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
369
- }
370
- removeMemberRole(guildId, channelId, userId, roleId) {
371
- return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
372
- }
373
336
  #setupRoutes() {
374
337
  this.#routeReleases.push(...registerQqWebhookRoutes(this.#options.http, {
375
338
  config: this.#options.config,
@@ -378,12 +341,23 @@ export class QqHttpEndpoint {
378
341
  }
379
342
  #bindBot(bot) {
380
343
  bindQqBotInboundEvents(bot, (raw) => {
344
+ this.#emitPlatformEvent('message', raw);
381
345
  const msg = normalizeQqMessage(raw);
382
346
  if (msg)
383
347
  this.admit(msg);
384
348
  });
385
349
  bindQqBotSideEvents(bot, (eventName, raw) => {
386
- receiveQqSideEvent(this.#options.sideEvents, this.#options.config.id, bot, eventName, raw, this.#logger);
350
+ this.#emitPlatformEvent(eventName, raw);
351
+ receiveQqSideEvent((name, payload) => this.emit(name, payload), this.#options.config.id, bot, eventName, raw, this.#logger);
352
+ });
353
+ }
354
+ #emitPlatformEvent(name, event) {
355
+ void this.emitPlatform(name, event).catch((error) => {
356
+ this.#logger.warn(formatCompact({
357
+ op: 'qq_platform_event_failed',
358
+ event: name,
359
+ error: error instanceof Error ? error.message : String(error),
360
+ }));
387
361
  });
388
362
  }
389
363
  #requireBot() {
@@ -392,18 +366,19 @@ export class QqHttpEndpoint {
392
366
  return this.#bot;
393
367
  }
394
368
  }
395
- function createQqEndpointManagement(endpoint) {
369
+ function createQqEndpointManagement(requireClient) {
396
370
  return Object.freeze({
397
371
  async listChannels() {
398
372
  const channels = [];
399
- const guilds = await endpoint.getGuilds();
373
+ const client = requireClient();
374
+ const guilds = await client.getGuilds();
400
375
  for (const guildValue of Array.isArray(guilds) ? guilds : []) {
401
376
  const guild = asRecord(guildValue);
402
377
  const guildId = String(guild.id ?? guild.guild_id ?? guildValue ?? '');
403
378
  if (!guildId)
404
379
  continue;
405
380
  const guildName = String(guild.name ?? guild.guild_name ?? guildId);
406
- const rows = await endpoint.getChannels(guildId);
381
+ const rows = await client.getChannels(guildId);
407
382
  for (const channelValue of Array.isArray(rows) ? rows : []) {
408
383
  const channel = asRecord(channelValue);
409
384
  const id = String(channel.id ?? channel.channel_id ?? channelValue ?? '');
package/lib/index.d.ts CHANGED
@@ -4,5 +4,5 @@ export { formatOutbound, resolveMediaFile, type QqOutboundElem, type QqOutboundM
4
4
  export { QqHttpEndpoint, QqWebsocketEndpoint, type CreateQqBot, type CreateQqHttpBot, type QqBotTransport, type QqEndpointOptions, type QqHttpBotTransport, type QqHttpEndpointOptions, } from './endpoint.js';
5
5
  export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, type QqWebhookHandler, } from './webhook.js';
6
6
  export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
7
- export { getQqAgentDeps, registerQqAgentEndpoint, setQqAgentDeps, type QqAgentDeps, type QqAgentEndpoint, } from './qq-agent-deps.js';
7
+ export { qqClient, type QqClientEventMap } from './client.js';
8
8
  export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, } from './platform-permit.js';
package/lib/index.js CHANGED
@@ -4,5 +4,5 @@ export { formatOutbound, resolveMediaFile, } from './outbound.js';
4
4
  export { QqHttpEndpoint, QqWebsocketEndpoint, } from './endpoint.js';
5
5
  export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, } from './webhook.js';
6
6
  export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
7
- export { getQqAgentDeps, registerQqAgentEndpoint, setQqAgentDeps, } from './qq-agent-deps.js';
7
+ export { qqClient } from './client.js';
8
8
  export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, } from './platform-permit.js';
@@ -42,4 +42,4 @@ export declare function runQqEndpointRemove(_state: QqRuntimeState, id: string,
42
42
  * 通用套件生成的 QQ endpoint 命令(add 经 bindFlow 钩子走扫码绑定)。
43
43
  * commands/endpoint/ 下的 list / add / remove 直接默认导出这三项;cancel 为 QQ 特化,单独定义。
44
44
  */
45
- export declare const qqEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage>>>;
45
+ export declare const qqEndpointCommands: import("@zhin.js/adapter").EndpointCommands<Readonly<import("@zhin.js/command").CommandDefinition<unknown, unknown, import("@zhin.js/command").CommandMessage, string | undefined>>>;
@@ -1,4 +1,4 @@
1
- import type { SideEventGateway } from '@zhin.js/core/runtime';
1
+ import type { EndpointEventEmitter } from 'zhin.js/adapter';
2
2
  import { type getAdapterLogger } from '@zhin.js/logger';
3
3
  export interface QqSideEventCaller {
4
4
  approveGroupJoinRequest?(groupId: string, userId: string, options: {
@@ -10,4 +10,4 @@ export interface QqSideEventCaller {
10
10
  export declare function bindQqBotSideEvents(bot: {
11
11
  on(event: string, listener: (...args: unknown[]) => void): void;
12
12
  }, dispatch: (eventName: string, raw: unknown) => void): void;
13
- export declare function receiveQqSideEvent(sideEvents: SideEventGateway | undefined, endpointKey: string, caller: QqSideEventCaller, eventName: string, raw: unknown, logger: ReturnType<typeof getAdapterLogger>): void;
13
+ export declare function receiveQqSideEvent(emit: EndpointEventEmitter, endpointKey: string, caller: QqSideEventCaller, eventName: string, raw: unknown, logger: ReturnType<typeof getAdapterLogger>): void;
@@ -51,8 +51,8 @@ function toOneBotLikeRaw(eventName, raw) {
51
51
  }
52
52
  return null;
53
53
  }
54
- export function receiveQqSideEvent(sideEvents, endpointKey, caller, eventName, raw, logger) {
55
- if (!sideEvents)
54
+ export function receiveQqSideEvent(emit, endpointKey, caller, eventName, raw, logger) {
55
+ if (!emit)
56
56
  return;
57
57
  const mapped = toOneBotLikeRaw(eventName, raw);
58
58
  if (!mapped)
@@ -61,7 +61,7 @@ export function receiveQqSideEvent(sideEvents, endpointKey, caller, eventName, r
61
61
  const joinRaw = isJoinRequest && raw && typeof raw === 'object'
62
62
  ? raw
63
63
  : undefined;
64
- void receiveOneBotLikeSideEvent(sideEvents, {
64
+ void receiveOneBotLikeSideEvent(emit, {
65
65
  adapter: 'qq',
66
66
  endpointKey,
67
67
  platform: 'qq',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-qq",
3
- "version": "8.0.0",
3
+ "version": "8.0.1",
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,22 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "qq-official-bot": "^1.2.3",
38
- "@zhin.js/adapter": "1.2.0",
39
- "@zhin.js/core": "1.5.13",
40
- "@zhin.js/host-http": "1.0.12",
38
+ "@zhin.js/adapter": "1.2.1",
39
+ "@zhin.js/core": "1.5.14",
40
+ "@zhin.js/feature-kit": "1.0.13",
41
+ "@zhin.js/host-http": "1.0.13",
41
42
  "@zhin.js/im-contract": "1.0.4",
42
- "@zhin.js/logger": "1.0.76",
43
- "@zhin.js/permission": "1.0.3"
43
+ "@zhin.js/logger": "1.0.77",
44
+ "@zhin.js/permission": "1.0.4"
44
45
  },
45
46
  "peerDependencies": {
46
47
  "zod": "^4.0.0",
47
- "@zhin.js/adapter": "1.2.0",
48
- "@zhin.js/agent": "1.1.15",
49
- "@zhin.js/command": "1.0.15",
50
- "@zhin.js/core": "1.5.13",
51
- "@zhin.js/middleware": "1.0.12",
52
- "zhin.js": "6.0.13"
48
+ "@zhin.js/adapter": "1.2.1",
49
+ "@zhin.js/agent": "1.1.16",
50
+ "@zhin.js/command": "1.0.16",
51
+ "@zhin.js/core": "1.5.14",
52
+ "@zhin.js/middleware": "1.0.13",
53
+ "zhin.js": "6.0.14"
53
54
  },
54
55
  "peerDependenciesMeta": {
55
56
  "@zhin.js/agent": {
@@ -73,9 +74,9 @@
73
74
  "typescript": "^6.0.3",
74
75
  "vitest": "^4.1.10",
75
76
  "zod": "^4.4.3",
76
- "@zhin.js/agent": "1.1.15",
77
- "@zhin.js/host-http": "1.0.12",
78
- "zhin.js": "6.0.13"
77
+ "@zhin.js/agent": "1.1.16",
78
+ "@zhin.js/host-http": "1.0.13",
79
+ "zhin.js": "6.0.14"
79
80
  },
80
81
  "files": [
81
82
  "adapters",
package/src/client.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineEndpointClient } from 'zhin.js/adapter';
2
+ import type { QqInboundMessage } from './protocol.js';
3
+ import type { QqBotTransport } from './ws.js';
4
+
5
+ export type QqClientEventMap = Record<string, QqInboundMessage | unknown>;
6
+
7
+ declare module '@zhin.js/feature-kit' {
8
+ interface AdapterClientRegistry {
9
+ readonly qq: {
10
+ readonly client: QqBotTransport;
11
+ readonly events: QqClientEventMap;
12
+ };
13
+ }
14
+ }
15
+
16
+ export const qqClient = defineEndpointClient<QqBotTransport, QqClientEventMap>('qq');
package/src/endpoint.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { Endpoint } from 'zhin.js/adapter';
1
2
  /**
2
3
  * QQ endpoints — lifecycle, outbound, admit, agent tool surface.
3
4
  */
@@ -5,16 +6,13 @@ import {
5
6
  createRecallEndpointControl,
6
7
  type EndpointControl,
7
8
  type EndpointChannel,
8
- type EndpointInstance,
9
9
  type EndpointManagement,
10
10
  type EndpointSendRequest,
11
11
  } from 'zhin.js/adapter';
12
- import type { MessageGateway, SideEventGateway } from '@zhin.js/core/runtime';
13
12
  import type { HttpHost } from '@zhin.js/host-http';
14
13
  import { formatCompact, getAdapterLogger, truncatePreview } from '@zhin.js/logger';
15
14
  import type { CapabilityId } from 'zhin.js';
16
15
  import { formatOutbound } from './outbound.js';
17
- import { registerQqAgentEndpoint } from './qq-agent-deps.js';
18
16
  import {
19
17
  formatInboundContent,
20
18
  parseCompoundMessageId,
@@ -50,13 +48,11 @@ export type { CreateQqHttpBot, QqHttpBotTransport } from './webhook.js';
50
48
 
51
49
  export interface QqEndpointOptions {
52
50
  readonly id: CapabilityId;
53
- readonly gateway: MessageGateway;
54
- readonly sideEvents?: SideEventGateway;
55
51
  readonly config: ResolvedQqWebsocketConfig;
56
52
  readonly createBot?: CreateQqBot;
57
53
  }
58
54
 
59
- export class QqWebsocketEndpoint implements EndpointInstance {
55
+ export class QqWebsocketEndpoint extends Endpoint<QqBotTransport> {
60
56
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
61
57
 
62
58
  readonly #options: QqEndpointOptions;
@@ -64,16 +60,20 @@ export class QqWebsocketEndpoint implements EndpointInstance {
64
60
  #bot: QqBotTransport | null = null;
65
61
  #open = false;
66
62
  #started = false;
67
- #unregisterAgent?: () => void;
68
- readonly management: EndpointManagement = createQqEndpointManagement(this);
63
+ readonly management: EndpointManagement = createQqEndpointManagement(() => this.#requireBot());
69
64
  readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
70
65
 
71
66
  constructor(options: QqEndpointOptions) {
67
+ super();
72
68
  this.#logger = getAdapterLogger('qq', options.config.id);
73
69
  this.#options = options;
74
70
  this.#createBot = options.createBot ?? defaultCreateBot;
75
71
  }
76
72
 
73
+ get client(): QqBotTransport {
74
+ return this.#requireBot();
75
+ }
76
+
77
77
  /** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
78
78
  get name(): string {
79
79
  return this.#options.config.id;
@@ -83,7 +83,6 @@ export class QqWebsocketEndpoint implements EndpointInstance {
83
83
  if (this.#started) return;
84
84
  this.#started = true;
85
85
  try {
86
- this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.id, this);
87
86
  this.#bot = this.#createBot(this.#options.config);
88
87
  this.#bindBot(this.#bot);
89
88
  await this.#bot.start();
@@ -111,8 +110,6 @@ export class QqWebsocketEndpoint implements EndpointInstance {
111
110
 
112
111
  async stop(): Promise<void> {
113
112
  this.#open = false;
114
- this.#unregisterAgent?.();
115
- this.#unregisterAgent = undefined;
116
113
  if (this.#bot) {
117
114
  try {
118
115
  this.#bot.removeAllListeners();
@@ -191,7 +188,7 @@ export class QqWebsocketEndpoint implements EndpointInstance {
191
188
  + (msg.mentioned ? ' (mentioned)' : '')
192
189
  + ` | ${truncatePreview(content, 80)}`,
193
190
  );
194
- void this.#options.gateway.receive({
191
+ void this.emit('message.receive', {
195
192
  conversation,
196
193
  message: { conversation, id: msg.id },
197
194
  content,
@@ -218,46 +215,16 @@ export class QqWebsocketEndpoint implements EndpointInstance {
218
215
  });
219
216
  }
220
217
 
221
- getGuilds() {
222
- return this.#requireBot().getGuilds();
223
- }
224
-
225
- getChannels(guildId: string) {
226
- return this.#requireBot().getChannels(guildId);
227
- }
228
-
229
- getChannelInfo(channelId: string) {
230
- return this.#requireBot().getChannelInfo(channelId);
231
- }
232
-
233
- getGuildMember(guildId: string, userId: string) {
234
- return this.#requireBot().getGuildMember(guildId, userId);
235
- }
236
-
237
- getGuildRoles(guildId: string) {
238
- return this.#requireBot().getGuildRoles(guildId);
239
- }
240
-
241
- createGuildRole(guildId: string, name: string, color?: number) {
242
- return this.#requireBot().createGuildRole(guildId, name, color);
243
- }
244
-
245
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
246
- return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
247
- }
248
-
249
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
250
- return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
251
- }
252
-
253
218
  #bindBot(bot: QqBotTransport): void {
254
219
  bindQqBotInboundEvents(bot, (raw) => {
220
+ this.#emitPlatformEvent('message', raw);
255
221
  const msg = normalizeQqMessage(raw);
256
222
  if (msg) this.admit(msg);
257
223
  });
258
224
  bindQqBotSideEvents(bot, (eventName, raw) => {
225
+ this.#emitPlatformEvent(eventName, raw);
259
226
  receiveQqSideEvent(
260
- this.#options.sideEvents,
227
+ (name, payload) => this.emit(name, payload),
261
228
  this.#options.config.id,
262
229
  bot as QqSideEventCaller,
263
230
  eventName,
@@ -267,6 +234,16 @@ export class QqWebsocketEndpoint implements EndpointInstance {
267
234
  });
268
235
  }
269
236
 
237
+ #emitPlatformEvent(name: string, event: unknown): void {
238
+ void this.emitPlatform(name, event).catch((error) => {
239
+ this.#logger.warn(formatCompact({
240
+ op: 'qq_platform_event_failed',
241
+ event: name,
242
+ error: error instanceof Error ? error.message : String(error),
243
+ }));
244
+ });
245
+ }
246
+
270
247
  #requireBot(): QqBotTransport {
271
248
  if (!this.#bot) throw new Error('QQ bot not connected');
272
249
  return this.#bot;
@@ -275,15 +252,13 @@ export class QqWebsocketEndpoint implements EndpointInstance {
275
252
 
276
253
  export interface QqHttpEndpointOptions {
277
254
  readonly id: CapabilityId;
278
- readonly gateway: MessageGateway;
279
- readonly sideEvents?: SideEventGateway;
280
255
  readonly http: HttpHost;
281
256
  readonly config: ResolvedQqHttpConfig;
282
257
  readonly createBot?: CreateQqHttpBot;
283
258
  }
284
259
 
285
260
  /** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
286
- export class QqHttpEndpoint implements EndpointInstance {
261
+ export class QqHttpEndpoint extends Endpoint<QqHttpBotTransport> {
287
262
  readonly #logger!: ReturnType<typeof getAdapterLogger>;
288
263
 
289
264
  readonly #options: QqHttpEndpointOptions;
@@ -292,16 +267,20 @@ export class QqHttpEndpoint implements EndpointInstance {
292
267
  #routeReleases: ReturnType<typeof registerQqWebhookRoutes> = [];
293
268
  #open = false;
294
269
  #started = false;
295
- #unregisterAgent?: () => void;
296
- readonly management: EndpointManagement = createQqEndpointManagement(this);
270
+ readonly management: EndpointManagement = createQqEndpointManagement(() => this.#requireBot());
297
271
  readonly control: EndpointControl = createRecallEndpointControl((id) => this.recallMessage(id));
298
272
 
299
273
  constructor(options: QqHttpEndpointOptions) {
274
+ super();
300
275
  this.#logger = getAdapterLogger('qq', options.config.id);
301
276
  this.#options = options;
302
277
  this.#createBot = options.createBot ?? defaultCreateHttpBot;
303
278
  }
304
279
 
280
+ get client(): QqHttpBotTransport {
281
+ return this.#requireBot();
282
+ }
283
+
305
284
  /** Live endpoint 名(Console/AdapterIndex 展示用)。 */
306
285
  get name(): string {
307
286
  return this.#options.config.id;
@@ -311,10 +290,6 @@ export class QqHttpEndpoint implements EndpointInstance {
311
290
  if (this.#started) return;
312
291
  this.#started = true;
313
292
  try {
314
- this.#unregisterAgent = registerQqAgentEndpoint(
315
- this.#options.config.id,
316
- this as unknown as QqWebsocketEndpoint,
317
- );
318
293
  this.#setupRoutes();
319
294
  this.#bot = this.#createBot(this.#options.config);
320
295
  this.#bindBot(this.#bot);
@@ -340,8 +315,6 @@ export class QqHttpEndpoint implements EndpointInstance {
340
315
  async stop(): Promise<void> {
341
316
  this.#open = false;
342
317
  for (const release of this.#routeReleases.splice(0)) release();
343
- this.#unregisterAgent?.();
344
- this.#unregisterAgent = undefined;
345
318
  if (this.#bot) {
346
319
  try {
347
320
  this.#bot.removeAllListeners();
@@ -419,7 +392,7 @@ export class QqHttpEndpoint implements EndpointInstance {
419
392
  + (msg.mentioned ? ' (mentioned)' : '')
420
393
  + ` | ${truncatePreview(content, 80)}`,
421
394
  );
422
- void this.#options.gateway.receive({
395
+ void this.emit('message.receive', {
423
396
  conversation,
424
397
  message: { conversation, id: msg.id },
425
398
  content,
@@ -446,38 +419,6 @@ export class QqHttpEndpoint implements EndpointInstance {
446
419
  });
447
420
  }
448
421
 
449
- getGuilds() {
450
- return this.#requireBot().getGuilds();
451
- }
452
-
453
- getChannels(guildId: string) {
454
- return this.#requireBot().getChannels(guildId);
455
- }
456
-
457
- getChannelInfo(channelId: string) {
458
- return this.#requireBot().getChannelInfo(channelId);
459
- }
460
-
461
- getGuildMember(guildId: string, userId: string) {
462
- return this.#requireBot().getGuildMember(guildId, userId);
463
- }
464
-
465
- getGuildRoles(guildId: string) {
466
- return this.#requireBot().getGuildRoles(guildId);
467
- }
468
-
469
- createGuildRole(guildId: string, name: string, color?: number) {
470
- return this.#requireBot().createGuildRole(guildId, name, color);
471
- }
472
-
473
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
474
- return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
475
- }
476
-
477
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string) {
478
- return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
479
- }
480
-
481
422
  #setupRoutes(): void {
482
423
  this.#routeReleases.push(...registerQqWebhookRoutes(this.#options.http, {
483
424
  config: this.#options.config,
@@ -487,12 +428,14 @@ export class QqHttpEndpoint implements EndpointInstance {
487
428
 
488
429
  #bindBot(bot: QqBotTransport): void {
489
430
  bindQqBotInboundEvents(bot, (raw) => {
431
+ this.#emitPlatformEvent('message', raw);
490
432
  const msg = normalizeQqMessage(raw);
491
433
  if (msg) this.admit(msg);
492
434
  });
493
435
  bindQqBotSideEvents(bot, (eventName, raw) => {
436
+ this.#emitPlatformEvent(eventName, raw);
494
437
  receiveQqSideEvent(
495
- this.#options.sideEvents,
438
+ (name, payload) => this.emit(name, payload),
496
439
  this.#options.config.id,
497
440
  bot as QqSideEventCaller,
498
441
  eventName,
@@ -502,26 +445,36 @@ export class QqHttpEndpoint implements EndpointInstance {
502
445
  });
503
446
  }
504
447
 
505
- #requireBot(): QqBotTransport {
448
+ #emitPlatformEvent(name: string, event: unknown): void {
449
+ void this.emitPlatform(name, event).catch((error) => {
450
+ this.#logger.warn(formatCompact({
451
+ op: 'qq_platform_event_failed',
452
+ event: name,
453
+ error: error instanceof Error ? error.message : String(error),
454
+ }));
455
+ });
456
+ }
457
+
458
+ #requireBot(): QqHttpBotTransport {
506
459
  if (!this.#bot) throw new Error('QQ bot not connected');
507
460
  return this.#bot;
508
461
  }
509
462
  }
510
463
 
511
- function createQqEndpointManagement(endpoint: {
512
- getGuilds(): Promise<unknown[]>;
513
- getChannels(guildId: string): Promise<unknown[]>;
514
- }): EndpointManagement {
464
+ function createQqEndpointManagement(
465
+ requireClient: () => Pick<QqBotTransport, 'getGuilds' | 'getChannels'>,
466
+ ): EndpointManagement {
515
467
  return Object.freeze<EndpointManagement>({
516
468
  async listChannels(): Promise<readonly EndpointChannel[]> {
517
469
  const channels: EndpointChannel[] = [];
518
- const guilds = await endpoint.getGuilds();
470
+ const client = requireClient();
471
+ const guilds = await client.getGuilds();
519
472
  for (const guildValue of Array.isArray(guilds) ? guilds : []) {
520
473
  const guild = asRecord(guildValue);
521
474
  const guildId = String(guild.id ?? guild.guild_id ?? guildValue ?? '');
522
475
  if (!guildId) continue;
523
476
  const guildName = String(guild.name ?? guild.guild_name ?? guildId);
524
- const rows = await endpoint.getChannels(guildId);
477
+ const rows = await client.getChannels(guildId);
525
478
  for (const channelValue of Array.isArray(rows) ? rows : []) {
526
479
  const channel = asRecord(channelValue);
527
480
  const id = String(channel.id ?? channel.channel_id ?? channelValue ?? '');
package/src/index.ts CHANGED
@@ -55,13 +55,7 @@ export {
55
55
  normalizeQqMessage,
56
56
  } from './ws.js';
57
57
 
58
- export {
59
- getQqAgentDeps,
60
- registerQqAgentEndpoint,
61
- setQqAgentDeps,
62
- type QqAgentDeps,
63
- type QqAgentEndpoint,
64
- } from './qq-agent-deps.js';
58
+ export { qqClient, type QqClientEventMap } from './client.js';
65
59
 
66
60
  export {
67
61
  checkQqPlatformPermit,
@@ -1,5 +1,5 @@
1
1
  import { receiveOneBotLikeSideEvent, type OneBotLikeRawEvent } from '@zhin.js/core';
2
- import type { SideEventGateway } from '@zhin.js/core/runtime';
2
+ import type { EndpointEventEmitter } from 'zhin.js/adapter';
3
3
  import { formatCompact, type getAdapterLogger } from '@zhin.js/logger';
4
4
 
5
5
  export interface QqSideEventCaller {
@@ -66,21 +66,21 @@ function toOneBotLikeRaw(eventName: string, raw: unknown): OneBotLikeRawEvent |
66
66
  }
67
67
 
68
68
  export function receiveQqSideEvent(
69
- sideEvents: SideEventGateway | undefined,
69
+ emit: EndpointEventEmitter,
70
70
  endpointKey: string,
71
71
  caller: QqSideEventCaller,
72
72
  eventName: string,
73
73
  raw: unknown,
74
74
  logger: ReturnType<typeof getAdapterLogger>,
75
75
  ): void {
76
- if (!sideEvents) return;
76
+ if (!emit) return;
77
77
  const mapped = toOneBotLikeRaw(eventName, raw);
78
78
  if (!mapped) return;
79
79
  const isJoinRequest = eventName === 'notice.group.join_request';
80
80
  const joinRaw = isJoinRequest && raw && typeof raw === 'object'
81
81
  ? raw as { group_id?: string | number; user_id?: string | number; join_request_id?: string | number }
82
82
  : undefined;
83
- void receiveOneBotLikeSideEvent(sideEvents, {
83
+ void receiveOneBotLikeSideEvent(emit, {
84
84
  adapter: 'qq',
85
85
  endpointKey,
86
86
  platform: 'qq',
@@ -1,21 +0,0 @@
1
- /**
2
- * Agent tool deps for qq.
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
- export interface QqAgentEndpoint {
6
- getGuilds(): Promise<unknown[]>;
7
- getChannels(guildId: string): Promise<unknown[]>;
8
- getChannelInfo(channelId: string): Promise<unknown>;
9
- getGuildMember(guildId: string, userId: string): Promise<unknown>;
10
- getGuildRoles(guildId: string): Promise<unknown[]>;
11
- createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
12
- addMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
13
- removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean>;
14
- }
15
- export interface QqAgentDeps {
16
- getEndpoint: (endpointKey: string) => QqAgentEndpoint;
17
- }
18
- export declare function registerQqAgentEndpoint(endpointKey: string, endpoint: QqAgentEndpoint): () => void;
19
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
20
- export declare function setQqAgentDeps(deps: QqAgentDeps | null): void;
21
- export declare function getQqAgentDeps(): QqAgentDeps;
@@ -1,30 +0,0 @@
1
- /**
2
- * Agent tool deps for qq.
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
- const endpoints = new Map();
6
- let override = null;
7
- export function registerQqAgentEndpoint(endpointKey, endpoint) {
8
- endpoints.set(endpointKey, endpoint);
9
- return () => {
10
- if (endpoints.get(endpointKey) === endpoint) {
11
- endpoints.delete(endpointKey);
12
- }
13
- };
14
- }
15
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
16
- export function setQqAgentDeps(deps) {
17
- override = deps;
18
- }
19
- export function getQqAgentDeps() {
20
- if (override)
21
- return override;
22
- return {
23
- getEndpoint(endpointKey) {
24
- const registered = endpoints.get(endpointKey);
25
- if (!registered)
26
- throw new Error(`Endpoint ${endpointKey} 不存在`);
27
- return registered;
28
- },
29
- };
30
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * Agent tool deps for qq.
3
- * Endpoints register themselves on start; tools look up by config name / endpoint id.
4
- */
5
-
6
- export interface QqAgentEndpoint {
7
- getGuilds(): Promise<unknown[]>;
8
- getChannels(guildId: string): Promise<unknown[]>;
9
- getChannelInfo(channelId: string): Promise<unknown>;
10
- getGuildMember(guildId: string, userId: string): Promise<unknown>;
11
- getGuildRoles(guildId: string): Promise<unknown[]>;
12
- createGuildRole(guildId: string, name: string, color?: number): Promise<unknown>;
13
- addMemberRole(
14
- guildId: string,
15
- channelId: string,
16
- userId: string,
17
- roleId: string,
18
- ): Promise<boolean>;
19
- removeMemberRole(
20
- guildId: string,
21
- channelId: string,
22
- userId: string,
23
- roleId: string,
24
- ): Promise<boolean>;
25
- }
26
-
27
- export interface QqAgentDeps {
28
- getEndpoint: (endpointKey: string) => QqAgentEndpoint;
29
- }
30
-
31
- const endpoints = new Map<string, QqAgentEndpoint>();
32
- let override: QqAgentDeps | null = null;
33
-
34
- export function registerQqAgentEndpoint(
35
- endpointKey: string,
36
- endpoint: QqAgentEndpoint,
37
- ): () => void {
38
- endpoints.set(endpointKey, endpoint);
39
- return () => {
40
- if (endpoints.get(endpointKey) === endpoint) {
41
- endpoints.delete(endpointKey);
42
- }
43
- };
44
- }
45
-
46
- /** Optional override used by tests / transitional callers. Pass `null` to clear. */
47
- export function setQqAgentDeps(deps: QqAgentDeps | null): void {
48
- override = deps;
49
- }
50
-
51
- export function getQqAgentDeps(): QqAgentDeps {
52
- if (override) return override;
53
- return {
54
- getEndpoint(endpointKey: string): QqAgentEndpoint {
55
- const registered = endpoints.get(endpointKey);
56
- if (!registered) throw new Error(`Endpoint ${endpointKey} 不存在`);
57
- return registered;
58
- },
59
- };
60
- }