@zhin.js/adapter-qq 5.0.2 → 6.0.0

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 (130) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +75 -409
  3. package/adapters/qq.ts +51 -0
  4. package/agent/tools/add_role.ts +2 -2
  5. package/agent/tools/channel_info.ts +2 -2
  6. package/agent/tools/create_role.ts +2 -2
  7. package/agent/tools/list_channels.ts +2 -2
  8. package/agent/tools/list_guilds.ts +2 -2
  9. package/agent/tools/list_roles.ts +2 -2
  10. package/agent/tools/member_detail.ts +2 -2
  11. package/agent/tools/remove_role.ts +2 -2
  12. package/commands/endpoint/add/[name:string=].ts +21 -0
  13. package/commands/endpoint/cancel.ts +15 -0
  14. package/commands/endpoint/list.ts +10 -0
  15. package/commands/endpoint/remove/[name:string].ts +15 -0
  16. package/lib/endpoint.d.ts +75 -0
  17. package/lib/endpoint.js +358 -0
  18. package/lib/index.d.ts +6 -0
  19. package/lib/index.js +6 -0
  20. package/lib/platform-permit.d.ts +16 -0
  21. package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
  22. package/lib/protocol.d.ts +96 -0
  23. package/lib/protocol.js +129 -0
  24. package/lib/qq-agent-deps.d.ts +21 -0
  25. package/lib/qq-agent-deps.js +30 -0
  26. package/lib/qq-bind-api.d.ts +36 -0
  27. package/lib/{src/qq-bind-api.js → qq-bind-api.js} +0 -1
  28. package/lib/qq-bind-flow.d.ts +19 -0
  29. package/lib/{src/qq-bind-flow.js → qq-bind-flow.js} +0 -1
  30. package/lib/qq-bind-persist.d.ts +12 -0
  31. package/lib/{src/qq-bind-persist.js → qq-bind-persist.js} +4 -5
  32. package/lib/qq-endpoint-commands.d.ts +25 -0
  33. package/lib/qq-endpoint-commands.js +147 -0
  34. package/lib/qq-endpoint-config.d.ts +17 -0
  35. package/lib/qq-endpoint-config.js +110 -0
  36. package/lib/qq-runtime-state.d.ts +17 -0
  37. package/lib/qq-runtime-state.js +13 -0
  38. package/lib/webhook.d.ts +18 -0
  39. package/lib/webhook.js +100 -0
  40. package/lib/ws.d.ts +24 -0
  41. package/lib/ws.js +118 -0
  42. package/package.json +51 -40
  43. package/plugin.ts +16 -0
  44. package/schema.json +85 -0
  45. package/src/endpoint.ts +362 -596
  46. package/src/index.ts +56 -186
  47. package/src/platform-permit.ts +1 -1
  48. package/src/protocol.ts +229 -0
  49. package/src/qq-agent-deps.ts +49 -12
  50. package/src/qq-bind-persist.ts +4 -4
  51. package/src/qq-endpoint-commands.ts +174 -0
  52. package/src/qq-endpoint-config.ts +135 -0
  53. package/src/qq-runtime-state.ts +36 -0
  54. package/src/webhook.ts +127 -0
  55. package/src/ws.ts +172 -0
  56. package/client/Dashboard.tsx +0 -230
  57. package/client/index.tsx +0 -11
  58. package/client/tsconfig.json +0 -7
  59. package/client/utils/api.ts +0 -30
  60. package/dist/index.js +0 -30
  61. package/lib/agent/tools/add_role.js +0 -23
  62. package/lib/agent/tools/add_role.js.map +0 -1
  63. package/lib/agent/tools/channel_info.js +0 -19
  64. package/lib/agent/tools/channel_info.js.map +0 -1
  65. package/lib/agent/tools/create_role.js +0 -22
  66. package/lib/agent/tools/create_role.js.map +0 -1
  67. package/lib/agent/tools/list_channels.js +0 -20
  68. package/lib/agent/tools/list_channels.js.map +0 -1
  69. package/lib/agent/tools/list_guilds.js +0 -17
  70. package/lib/agent/tools/list_guilds.js.map +0 -1
  71. package/lib/agent/tools/list_roles.js +0 -20
  72. package/lib/agent/tools/list_roles.js.map +0 -1
  73. package/lib/agent/tools/member_detail.js +0 -20
  74. package/lib/agent/tools/member_detail.js.map +0 -1
  75. package/lib/agent/tools/remove_role.js +0 -23
  76. package/lib/agent/tools/remove_role.js.map +0 -1
  77. package/lib/package.json +0 -105
  78. package/lib/src/adapter.js +0 -81
  79. package/lib/src/adapter.js.map +0 -1
  80. package/lib/src/endpoint.js +0 -600
  81. package/lib/src/endpoint.js.map +0 -1
  82. package/lib/src/gateway-config.js +0 -32
  83. package/lib/src/gateway-config.js.map +0 -1
  84. package/lib/src/group-at-normalize.js +0 -68
  85. package/lib/src/group-at-normalize.js.map +0 -1
  86. package/lib/src/inbound-action.js +0 -57
  87. package/lib/src/inbound-action.js.map +0 -1
  88. package/lib/src/inbound-normalize.js +0 -47
  89. package/lib/src/inbound-normalize.js.map +0 -1
  90. package/lib/src/index.js +0 -194
  91. package/lib/src/index.js.map +0 -1
  92. package/lib/src/mention-enrich.js +0 -52
  93. package/lib/src/mention-enrich.js.map +0 -1
  94. package/lib/src/outbound-keyboard.js +0 -100
  95. package/lib/src/outbound-keyboard.js.map +0 -1
  96. package/lib/src/outbound-markdown.js +0 -189
  97. package/lib/src/outbound-markdown.js.map +0 -1
  98. package/lib/src/outbound-media.js +0 -51
  99. package/lib/src/outbound-media.js.map +0 -1
  100. package/lib/src/outbound-mixed-media.js +0 -78
  101. package/lib/src/outbound-mixed-media.js.map +0 -1
  102. package/lib/src/platform-permit.js.map +0 -1
  103. package/lib/src/qq-agent-deps.js +0 -10
  104. package/lib/src/qq-agent-deps.js.map +0 -1
  105. package/lib/src/qq-bind-api.js.map +0 -1
  106. package/lib/src/qq-bind-flow.js.map +0 -1
  107. package/lib/src/qq-bind-persist.js.map +0 -1
  108. package/lib/src/qq-endpoint-manager.js +0 -159
  109. package/lib/src/qq-endpoint-manager.js.map +0 -1
  110. package/lib/src/sdk-version.js +0 -19
  111. package/lib/src/sdk-version.js.map +0 -1
  112. package/lib/src/segment-mapper.js +0 -2
  113. package/lib/src/segment-mapper.js.map +0 -1
  114. package/lib/src/types.js +0 -2
  115. package/lib/src/types.js.map +0 -1
  116. package/plugin.yml +0 -3
  117. package/src/adapter.ts +0 -97
  118. package/src/gateway-config.ts +0 -48
  119. package/src/group-at-normalize.ts +0 -75
  120. package/src/inbound-action.ts +0 -87
  121. package/src/inbound-normalize.ts +0 -57
  122. package/src/mention-enrich.ts +0 -71
  123. package/src/outbound-keyboard.ts +0 -117
  124. package/src/outbound-markdown.ts +0 -212
  125. package/src/outbound-media.ts +0 -58
  126. package/src/outbound-mixed-media.ts +0 -101
  127. package/src/qq-endpoint-manager.ts +0 -199
  128. package/src/sdk-version.ts +0 -21
  129. package/src/segment-mapper.ts +0 -1
  130. package/src/types.ts +0 -52
@@ -1,9 +1,9 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
4
  import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
5
 
6
- export default defineTool<{ endpoint_id: string; channel_id: string }>({
6
+ export default defineAgentTool<{ endpoint_id: string; channel_id: string }>({
7
7
  description: '获取 QQ 频道中指定子频道的详细信息',
8
8
  inputSchema: z.object({
9
9
  endpoint_id: z.string().describe('Endpoint 名称'),
@@ -1,9 +1,9 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
4
  import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
5
 
6
- export default defineTool<{ endpoint_id: string; guild_id: string; name: string; color?: number }>({
6
+ export default defineAgentTool<{ endpoint_id: string; guild_id: string; name: string; color?: number }>({
7
7
  description: '创建 QQ 频道角色',
8
8
  inputSchema: z.object({
9
9
  endpoint_id: z.string().describe('Endpoint 名称'),
@@ -1,9 +1,9 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
4
  import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
5
 
6
- export default defineTool<{ endpoint_id: string; guild_id: string }>({
6
+ export default defineAgentTool<{ endpoint_id: string; guild_id: string }>({
7
7
  description: '获取 QQ 频道下的子频道列表',
8
8
  inputSchema: z.object({
9
9
  endpoint_id: z.string().describe('Endpoint 名称'),
@@ -1,8 +1,8 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
4
4
 
5
- export default defineTool<{ endpoint_id: string }>({
5
+ export default defineAgentTool<{ endpoint_id: string }>({
6
6
  description: '获取 QQ 频道列表',
7
7
  inputSchema: z.object({
8
8
  endpoint_id: z.string().describe('Endpoint 名称'),
@@ -1,9 +1,9 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
4
  import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
5
 
6
- export default defineTool<{ endpoint_id: string; guild_id: string }>({
6
+ export default defineAgentTool<{ endpoint_id: string; guild_id: string }>({
7
7
  description: '获取 QQ 频道角色列表',
8
8
  inputSchema: z.object({
9
9
  endpoint_id: z.string().describe('Endpoint 名称'),
@@ -1,9 +1,9 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
4
  import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
5
 
6
- export default defineTool<{ endpoint_id: string; guild_id: string; user_id: string }>({
6
+ export default defineAgentTool<{ endpoint_id: string; guild_id: string; user_id: string }>({
7
7
  description: '获取 QQ 频道中指定成员的详细信息',
8
8
  inputSchema: z.object({
9
9
  endpoint_id: z.string().describe('Endpoint 名称'),
@@ -1,9 +1,9 @@
1
- import { defineTool } from '@zhin.js/agent/tools';
1
+ import { defineAgentTool } from '@zhin.js/agent/tools';
2
2
  import { z } from 'zod';
3
3
  import { platformPermit } from '../../src/platform-permit.js';
4
4
  import { getQqAgentDeps } from '../../src/qq-agent-deps.js';
5
5
 
6
- export default defineTool<{ endpoint_id: string; guild_id: string; channel_id: string; user_id: string; role_id: string }>({
6
+ export default defineAgentTool<{ endpoint_id: string; guild_id: string; channel_id: string; user_id: string; role_id: string }>({
7
7
  description: '移除成员的 QQ 频道角色',
8
8
  inputSchema: z.object({
9
9
  endpoint_id: z.string().describe('Endpoint 名称'),
@@ -0,0 +1,21 @@
1
+ import { defineCommand } from '@zhin.js/command';
2
+ import {
3
+ extractQqCommandReply,
4
+ isQqEndpointOperator,
5
+ QQ_ENDPOINT_FORBIDDEN,
6
+ runQqEndpointAdd,
7
+ } from '../../../src/qq-endpoint-commands.js';
8
+ import { qqRuntimeStateToken } from '../../../src/qq-runtime-state.js';
9
+
10
+ export default defineCommand({
11
+ description: '手机 QQ 扫码绑定机器人,凭据写入 .env 并追加到 zhin.config.yml(重启生效)',
12
+ execute({ config, input, params, use }) {
13
+ if (!isQqEndpointOperator(config, input)) return QQ_ENDPOINT_FORBIDDEN;
14
+ const name = typeof params.name === 'string' && params.name.trim() ? params.name.trim() : undefined;
15
+ return runQqEndpointAdd(
16
+ use(qqRuntimeStateToken),
17
+ name,
18
+ extractQqCommandReply(input),
19
+ );
20
+ },
21
+ });
@@ -0,0 +1,15 @@
1
+ import { defineCommand } from '@zhin.js/command';
2
+ import {
3
+ isQqEndpointOperator,
4
+ QQ_ENDPOINT_FORBIDDEN,
5
+ runQqEndpointCancel,
6
+ } from '../../src/qq-endpoint-commands.js';
7
+ import { qqRuntimeStateToken } from '../../src/qq-runtime-state.js';
8
+
9
+ export default defineCommand({
10
+ description: '取消进行中的 QQ 扫码绑定流程',
11
+ execute({ config, input, use }) {
12
+ if (!isQqEndpointOperator(config, input)) return QQ_ENDPOINT_FORBIDDEN;
13
+ return runQqEndpointCancel(use(qqRuntimeStateToken));
14
+ },
15
+ });
@@ -0,0 +1,10 @@
1
+ import { defineCommand } from '@zhin.js/command';
2
+ import { runQqEndpointList } from '../../src/qq-endpoint-commands.js';
3
+ import { qqRuntimeStateToken } from '../../src/qq-runtime-state.js';
4
+
5
+ export default defineCommand({
6
+ description: '列出 QQ endpoints(运行中 + zhin.config.yml 配置)',
7
+ execute({ use }) {
8
+ return runQqEndpointList(use(qqRuntimeStateToken));
9
+ },
10
+ });
@@ -0,0 +1,15 @@
1
+ import { defineCommand } from '@zhin.js/command';
2
+ import {
3
+ isQqEndpointOperator,
4
+ QQ_ENDPOINT_FORBIDDEN,
5
+ runQqEndpointRemove,
6
+ } from '../../../src/qq-endpoint-commands.js';
7
+ import { qqRuntimeStateToken } from '../../../src/qq-runtime-state.js';
8
+
9
+ export default defineCommand({
10
+ description: '从 zhin.config.yml 的 plugins.qq.endpoints 移除指定 endpoint(重启生效)',
11
+ execute({ config, input, params, use }) {
12
+ if (!isQqEndpointOperator(config, input)) return QQ_ENDPOINT_FORBIDDEN;
13
+ return runQqEndpointRemove(use(qqRuntimeStateToken), String(params.name ?? ''));
14
+ },
15
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * QQ endpoints — lifecycle, outbound, admit, agent tool surface.
3
+ */
4
+ import type { EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
5
+ import type { MessageGateway } from '@zhin.js/core/runtime';
6
+ import type { HttpHost } from '@zhin.js/host-http';
7
+ import type { CapabilityId } from '@zhin.js/plugin-runtime';
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';
11
+ export type { CreateQqBot, QqBotTransport } from './ws.js';
12
+ export type { CreateQqHttpBot, QqHttpBotTransport } from './webhook.js';
13
+ export interface QqEndpointOptions {
14
+ readonly id: CapabilityId;
15
+ readonly gateway: MessageGateway;
16
+ readonly config: ResolvedQqWebsocketConfig;
17
+ readonly createBot?: CreateQqBot;
18
+ }
19
+ export declare class QqWebsocketEndpoint implements EndpointInstance {
20
+ #private;
21
+ readonly management: EndpointManagement;
22
+ constructor(options: QqEndpointOptions);
23
+ /** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
24
+ get name(): string;
25
+ start(): Promise<void>;
26
+ open(): void;
27
+ close(): void;
28
+ stop(): Promise<void>;
29
+ send({ target, payload }: {
30
+ readonly target: string;
31
+ readonly payload: unknown;
32
+ }): Promise<string>;
33
+ /** Test / internal: admit a message when open. */
34
+ 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
+ }
44
+ export interface QqHttpEndpointOptions {
45
+ readonly id: CapabilityId;
46
+ readonly gateway: MessageGateway;
47
+ readonly http: HttpHost;
48
+ readonly config: ResolvedQqHttpConfig;
49
+ readonly createBot?: CreateQqHttpBot;
50
+ }
51
+ /** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
52
+ export declare class QqHttpEndpoint implements EndpointInstance {
53
+ #private;
54
+ readonly management: EndpointManagement;
55
+ constructor(options: QqHttpEndpointOptions);
56
+ /** Live endpoint 名(Console/AdapterIndex 展示用)。 */
57
+ get name(): string;
58
+ start(): Promise<void>;
59
+ open(): void;
60
+ close(): void;
61
+ stop(): Promise<void>;
62
+ send({ target, payload }: {
63
+ readonly target: string;
64
+ readonly payload: unknown;
65
+ }): Promise<string>;
66
+ 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
+ }
@@ -0,0 +1,358 @@
1
+ import { formatCompact, getLogger } from '@zhin.js/logger';
2
+ import { registerQqAgentEndpoint } from './qq-agent-deps.js';
3
+ import { formatInboundContent, formatInboundTarget, formatOutboundText, parseSendTarget, resolveOutboundMessageId, senderDisplayName, } from './protocol.js';
4
+ import { defaultCreateHttpBot, registerQqWebhookRoutes, } from './webhook.js';
5
+ import { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
6
+ const logger = getLogger('qq');
7
+ export class QqWebsocketEndpoint {
8
+ #options;
9
+ #createBot;
10
+ #bot = null;
11
+ #open = false;
12
+ #started = false;
13
+ #unregisterAgent;
14
+ management = createQqEndpointManagement(this);
15
+ constructor(options) {
16
+ this.#options = options;
17
+ this.#createBot = options.createBot ?? defaultCreateBot;
18
+ }
19
+ /** Live endpoint 名(Console/AdapterIndex 展示用,如 bot appid 别名)。 */
20
+ get name() {
21
+ return this.#options.config.name;
22
+ }
23
+ async start() {
24
+ if (this.#started)
25
+ return;
26
+ this.#started = true;
27
+ try {
28
+ this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.name, this);
29
+ this.#bot = this.#createBot(this.#options.config);
30
+ this.#bindBot(this.#bot);
31
+ await this.#bot.start();
32
+ logger.info(formatCompact({
33
+ op: 'connect',
34
+ endpoint: this.#options.config.name,
35
+ mode: 'websocket',
36
+ appid: this.#options.config.appid,
37
+ }));
38
+ }
39
+ catch (error) {
40
+ await this.stop();
41
+ logger.error('Failed to connect QQ websocket:', error);
42
+ throw error;
43
+ }
44
+ }
45
+ open() {
46
+ this.#open = true;
47
+ }
48
+ close() {
49
+ this.#open = false;
50
+ }
51
+ async stop() {
52
+ this.#open = false;
53
+ this.#unregisterAgent?.();
54
+ this.#unregisterAgent = undefined;
55
+ if (this.#bot) {
56
+ try {
57
+ this.#bot.removeAllListeners();
58
+ await this.#bot.stop();
59
+ }
60
+ catch {
61
+ /* ignore */
62
+ }
63
+ this.#bot = null;
64
+ }
65
+ this.#started = false;
66
+ logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
67
+ }
68
+ async send({ target, payload }) {
69
+ const body = formatOutboundText(payload);
70
+ const parsed = parseSendTarget(target);
71
+ const bot = this.#requireBot();
72
+ let result;
73
+ switch (parsed.kind) {
74
+ case 'private':
75
+ result = await bot.sendPrivateMessage(parsed.id, body);
76
+ break;
77
+ case 'group':
78
+ result = await bot.sendGroupMessage(parsed.id, body);
79
+ break;
80
+ case 'channel':
81
+ result = await bot.sendGuildMessage(parsed.id, body);
82
+ break;
83
+ case 'direct':
84
+ if (!bot.sendDirectMessage)
85
+ throw new Error('QQ direct message not supported by transport');
86
+ result = await bot.sendDirectMessage(parsed.id, body);
87
+ break;
88
+ default:
89
+ throw new Error(`unsupported QQ target kind: ${String(parsed.kind)}`);
90
+ }
91
+ const messageId = `${parsed.kind}-${parsed.id}:${resolveOutboundMessageId(result)}`;
92
+ logger.debug(formatCompact({
93
+ op: 'qq_send',
94
+ endpoint: this.#options.config.name,
95
+ target,
96
+ messageId,
97
+ }));
98
+ return messageId;
99
+ }
100
+ /** Test / internal: admit a message when open. */
101
+ admit(msg) {
102
+ if (!this.#open)
103
+ return;
104
+ const target = formatInboundTarget(msg);
105
+ void this.#options.gateway.receive({
106
+ adapter: this.#options.id,
107
+ target,
108
+ content: formatInboundContent(msg),
109
+ sender: senderDisplayName(msg),
110
+ id: msg.id,
111
+ metadata: Object.freeze({
112
+ endpoint: this.#options.config.name,
113
+ channelKind: msg.channelKind,
114
+ userId: msg.authorId,
115
+ guildId: msg.guildId,
116
+ roles: msg.authorRoles,
117
+ // AT 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
118
+ ...(msg.mentioned ? { mentioned: true } : {}),
119
+ }),
120
+ }).catch((err) => {
121
+ logger.warn(formatCompact({
122
+ op: 'qq_gateway_receive_failed',
123
+ target,
124
+ error: err instanceof Error ? err.message : String(err),
125
+ }));
126
+ });
127
+ }
128
+ getGuilds() {
129
+ return this.#requireBot().getGuilds();
130
+ }
131
+ getChannels(guildId) {
132
+ return this.#requireBot().getChannels(guildId);
133
+ }
134
+ getChannelInfo(channelId) {
135
+ return this.#requireBot().getChannelInfo(channelId);
136
+ }
137
+ getGuildMember(guildId, userId) {
138
+ return this.#requireBot().getGuildMember(guildId, userId);
139
+ }
140
+ getGuildRoles(guildId) {
141
+ return this.#requireBot().getGuildRoles(guildId);
142
+ }
143
+ createGuildRole(guildId, name, color) {
144
+ return this.#requireBot().createGuildRole(guildId, name, color);
145
+ }
146
+ addMemberRole(guildId, channelId, userId, roleId) {
147
+ return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
148
+ }
149
+ removeMemberRole(guildId, channelId, userId, roleId) {
150
+ return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
151
+ }
152
+ #bindBot(bot) {
153
+ bindQqBotInboundEvents(bot, (raw) => {
154
+ const msg = normalizeQqMessage(raw);
155
+ if (msg)
156
+ this.admit(msg);
157
+ });
158
+ }
159
+ #requireBot() {
160
+ if (!this.#bot)
161
+ throw new Error('QQ bot not connected');
162
+ return this.#bot;
163
+ }
164
+ }
165
+ /** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
166
+ export class QqHttpEndpoint {
167
+ #options;
168
+ #createBot;
169
+ #bot = null;
170
+ #routeReleases = [];
171
+ #open = false;
172
+ #started = false;
173
+ #unregisterAgent;
174
+ management = createQqEndpointManagement(this);
175
+ constructor(options) {
176
+ this.#options = options;
177
+ this.#createBot = options.createBot ?? defaultCreateHttpBot;
178
+ }
179
+ /** Live endpoint 名(Console/AdapterIndex 展示用)。 */
180
+ get name() {
181
+ return this.#options.config.name;
182
+ }
183
+ async start() {
184
+ if (this.#started)
185
+ return;
186
+ this.#started = true;
187
+ try {
188
+ this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.name, this);
189
+ this.#setupRoutes();
190
+ this.#bot = this.#createBot(this.#options.config);
191
+ this.#bindBot(this.#bot);
192
+ await this.#bot.start();
193
+ logger.info(formatCompact({
194
+ op: 'connect',
195
+ endpoint: this.#options.config.name,
196
+ mode: this.#options.config.mode,
197
+ path: this.#options.config.webhookPath,
198
+ }));
199
+ }
200
+ catch (error) {
201
+ await this.stop();
202
+ logger.error('Failed to connect QQ HTTP receiver:', error);
203
+ throw error;
204
+ }
205
+ }
206
+ open() {
207
+ this.#open = true;
208
+ }
209
+ close() {
210
+ this.#open = false;
211
+ }
212
+ async stop() {
213
+ this.#open = false;
214
+ for (const release of this.#routeReleases.splice(0))
215
+ release();
216
+ this.#unregisterAgent?.();
217
+ this.#unregisterAgent = undefined;
218
+ if (this.#bot) {
219
+ try {
220
+ this.#bot.removeAllListeners();
221
+ await this.#bot.stop();
222
+ }
223
+ catch {
224
+ /* ignore */
225
+ }
226
+ this.#bot = null;
227
+ }
228
+ this.#started = false;
229
+ logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
230
+ }
231
+ async send({ target, payload }) {
232
+ const body = formatOutboundText(payload);
233
+ const parsed = parseSendTarget(target);
234
+ const bot = this.#requireBot();
235
+ let result;
236
+ switch (parsed.kind) {
237
+ case 'private':
238
+ result = await bot.sendPrivateMessage(parsed.id, body);
239
+ break;
240
+ case 'group':
241
+ result = await bot.sendGroupMessage(parsed.id, body);
242
+ break;
243
+ case 'channel':
244
+ result = await bot.sendGuildMessage(parsed.id, body);
245
+ break;
246
+ case 'direct':
247
+ if (!bot.sendDirectMessage)
248
+ throw new Error('QQ direct message not supported by transport');
249
+ result = await bot.sendDirectMessage(parsed.id, body);
250
+ break;
251
+ default:
252
+ throw new Error(`unsupported QQ target kind: ${String(parsed.kind)}`);
253
+ }
254
+ return `${parsed.kind}-${parsed.id}:${resolveOutboundMessageId(result)}`;
255
+ }
256
+ admit(msg) {
257
+ if (!this.#open)
258
+ return;
259
+ const target = formatInboundTarget(msg);
260
+ void this.#options.gateway.receive({
261
+ adapter: this.#options.id,
262
+ target,
263
+ content: formatInboundContent(msg),
264
+ sender: senderDisplayName(msg),
265
+ id: msg.id,
266
+ metadata: Object.freeze({
267
+ endpoint: this.#options.config.name,
268
+ channelKind: msg.channelKind,
269
+ userId: msg.authorId,
270
+ guildId: msg.guildId,
271
+ roles: msg.authorRoles,
272
+ // AT 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
273
+ ...(msg.mentioned ? { mentioned: true } : {}),
274
+ }),
275
+ }).catch((err) => {
276
+ logger.warn(formatCompact({
277
+ op: 'qq_gateway_receive_failed',
278
+ target,
279
+ error: err instanceof Error ? err.message : String(err),
280
+ }));
281
+ });
282
+ }
283
+ getGuilds() {
284
+ return this.#requireBot().getGuilds();
285
+ }
286
+ getChannels(guildId) {
287
+ return this.#requireBot().getChannels(guildId);
288
+ }
289
+ getChannelInfo(channelId) {
290
+ return this.#requireBot().getChannelInfo(channelId);
291
+ }
292
+ getGuildMember(guildId, userId) {
293
+ return this.#requireBot().getGuildMember(guildId, userId);
294
+ }
295
+ getGuildRoles(guildId) {
296
+ return this.#requireBot().getGuildRoles(guildId);
297
+ }
298
+ createGuildRole(guildId, name, color) {
299
+ return this.#requireBot().createGuildRole(guildId, name, color);
300
+ }
301
+ addMemberRole(guildId, channelId, userId, roleId) {
302
+ return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
303
+ }
304
+ removeMemberRole(guildId, channelId, userId, roleId) {
305
+ return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
306
+ }
307
+ #setupRoutes() {
308
+ this.#routeReleases.push(...registerQqWebhookRoutes(this.#options.http, {
309
+ config: this.#options.config,
310
+ getBot: () => this.#bot,
311
+ }));
312
+ }
313
+ #bindBot(bot) {
314
+ bindQqBotInboundEvents(bot, (raw) => {
315
+ const msg = normalizeQqMessage(raw);
316
+ if (msg)
317
+ this.admit(msg);
318
+ });
319
+ }
320
+ #requireBot() {
321
+ if (!this.#bot)
322
+ throw new Error('QQ bot not connected');
323
+ return this.#bot;
324
+ }
325
+ }
326
+ function createQqEndpointManagement(endpoint) {
327
+ return Object.freeze({
328
+ async listChannels() {
329
+ const channels = [];
330
+ const guilds = await endpoint.getGuilds();
331
+ for (const guildValue of Array.isArray(guilds) ? guilds : []) {
332
+ const guild = asRecord(guildValue);
333
+ const guildId = String(guild.id ?? guild.guild_id ?? guildValue ?? '');
334
+ if (!guildId)
335
+ continue;
336
+ const guildName = String(guild.name ?? guild.guild_name ?? guildId);
337
+ const rows = await endpoint.getChannels(guildId);
338
+ for (const channelValue of Array.isArray(rows) ? rows : []) {
339
+ const channel = asRecord(channelValue);
340
+ const id = String(channel.id ?? channel.channel_id ?? channelValue ?? '');
341
+ if (!id)
342
+ continue;
343
+ channels.push({
344
+ id,
345
+ name: String(channel.name ?? channel.channel_name ?? id),
346
+ parent: { type: 'guild', id: guildId, name: guildName },
347
+ });
348
+ }
349
+ }
350
+ return channels;
351
+ },
352
+ });
353
+ }
354
+ function asRecord(value) {
355
+ return value !== null && typeof value === 'object'
356
+ ? value
357
+ : {};
358
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { formatInboundContent, formatInboundTarget, formatOutboundText, parseSendTarget, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, type ParsedSendTarget, type QqAdapterConfig, type QqChannelKind, type QqInboundMessage, type QqWireSegment, type ResolvedQqConfig, type ResolvedQqHttpConfig, type ResolvedQqWebsocketConfig, } from './protocol.js';
2
+ export { QqHttpEndpoint, QqWebsocketEndpoint, type CreateQqBot, type CreateQqHttpBot, type QqBotTransport, type QqEndpointOptions, type QqHttpBotTransport, type QqHttpEndpointOptions, } from './endpoint.js';
3
+ export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, type QqWebhookHandler, } from './webhook.js';
4
+ export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
5
+ export { getQqAgentDeps, registerQqAgentEndpoint, setQqAgentDeps, type QqAgentDeps, type QqAgentEndpoint, } from './qq-agent-deps.js';
6
+ export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, registerQqPlatformPermitChecker, } from './platform-permit.js';
package/lib/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { formatInboundContent, formatInboundTarget, formatOutboundText, parseSendTarget, resolveOutboundMessageId, resolveQqConfig, senderDisplayName, } from './protocol.js';
2
+ export { QqHttpEndpoint, QqWebsocketEndpoint, } from './endpoint.js';
3
+ export { registerQqWebhookRoutes, handleQqWebhookRequest, readRequestBodyText, defaultCreateHttpBot, } from './webhook.js';
4
+ export { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
5
+ export { getQqAgentDeps, registerQqAgentEndpoint, setQqAgentDeps, } from './qq-agent-deps.js';
6
+ export { checkQqPlatformPermit, normalizeQqGuildSenderForPermit, platformPermit, qqGuildPermitResolver, registerQqPlatformPermitChecker, } from './platform-permit.js';
@@ -0,0 +1,16 @@
1
+ /**
2
+ * QQ 官方频道 platform permit(guild/channel,非 QQ 群 group_*)
3
+ */
4
+ import { type Message } from '@zhin.js/core';
5
+ export declare function platformPermit(perm: string): string;
6
+ export declare function qqGuildPermitResolver(logicalPerm: string): string;
7
+ export declare function normalizeQqGuildSenderForPermit(input: {
8
+ roles?: string[];
9
+ isOwner?: boolean;
10
+ isAdmin?: boolean;
11
+ }): {
12
+ role?: string;
13
+ permissions?: string[];
14
+ };
15
+ export declare function checkQqPlatformPermit(perm: string, message: Message<any>): boolean;
16
+ export declare function registerQqPlatformPermitChecker(): () => void;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * QQ 官方频道 platform permit(guild/channel,非 QQ 群 group_*)
3
3
  */
4
- import { registerPlatformPermitChecker } from 'zhin.js';
4
+ import { registerPlatformPermitChecker } from '@zhin.js/core';
5
5
  const ADAPTER = 'qq';
6
6
  export function platformPermit(perm) {
7
7
  return `platform(${ADAPTER},${perm})`;
@@ -47,4 +47,3 @@ export function checkQqPlatformPermit(perm, message) {
47
47
  export function registerQqPlatformPermitChecker() {
48
48
  return registerPlatformPermitChecker(ADAPTER, checkQqPlatformPermit);
49
49
  }
50
- //# sourceMappingURL=platform-permit.js.map