@zhin.js/adapter-qq 5.0.2 → 5.0.3

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 (116) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +51 -417
  3. package/adapters/qq.ts +45 -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/lib/endpoint.d.ts +69 -0
  13. package/lib/endpoint.js +315 -0
  14. package/lib/index.d.ts +6 -0
  15. package/lib/index.js +6 -0
  16. package/lib/platform-permit.d.ts +16 -0
  17. package/lib/{src/platform-permit.js → platform-permit.js} +1 -2
  18. package/lib/protocol.d.ts +96 -0
  19. package/lib/protocol.js +129 -0
  20. package/lib/qq-agent-deps.d.ts +21 -0
  21. package/lib/qq-agent-deps.js +30 -0
  22. package/lib/webhook.d.ts +18 -0
  23. package/lib/webhook.js +100 -0
  24. package/lib/ws.d.ts +24 -0
  25. package/lib/ws.js +118 -0
  26. package/package.json +44 -40
  27. package/plugin.ts +13 -0
  28. package/schema.json +51 -0
  29. package/src/endpoint.ts +314 -600
  30. package/src/index.ts +56 -186
  31. package/src/platform-permit.ts +1 -1
  32. package/src/protocol.ts +229 -0
  33. package/src/qq-agent-deps.ts +49 -12
  34. package/src/webhook.ts +127 -0
  35. package/src/ws.ts +172 -0
  36. package/client/Dashboard.tsx +0 -230
  37. package/client/index.tsx +0 -11
  38. package/client/tsconfig.json +0 -7
  39. package/client/utils/api.ts +0 -30
  40. package/dist/index.js +0 -30
  41. package/lib/agent/tools/add_role.js +0 -23
  42. package/lib/agent/tools/add_role.js.map +0 -1
  43. package/lib/agent/tools/channel_info.js +0 -19
  44. package/lib/agent/tools/channel_info.js.map +0 -1
  45. package/lib/agent/tools/create_role.js +0 -22
  46. package/lib/agent/tools/create_role.js.map +0 -1
  47. package/lib/agent/tools/list_channels.js +0 -20
  48. package/lib/agent/tools/list_channels.js.map +0 -1
  49. package/lib/agent/tools/list_guilds.js +0 -17
  50. package/lib/agent/tools/list_guilds.js.map +0 -1
  51. package/lib/agent/tools/list_roles.js +0 -20
  52. package/lib/agent/tools/list_roles.js.map +0 -1
  53. package/lib/agent/tools/member_detail.js +0 -20
  54. package/lib/agent/tools/member_detail.js.map +0 -1
  55. package/lib/agent/tools/remove_role.js +0 -23
  56. package/lib/agent/tools/remove_role.js.map +0 -1
  57. package/lib/package.json +0 -105
  58. package/lib/src/adapter.js +0 -81
  59. package/lib/src/adapter.js.map +0 -1
  60. package/lib/src/endpoint.js +0 -600
  61. package/lib/src/endpoint.js.map +0 -1
  62. package/lib/src/gateway-config.js +0 -32
  63. package/lib/src/gateway-config.js.map +0 -1
  64. package/lib/src/group-at-normalize.js +0 -68
  65. package/lib/src/group-at-normalize.js.map +0 -1
  66. package/lib/src/inbound-action.js +0 -57
  67. package/lib/src/inbound-action.js.map +0 -1
  68. package/lib/src/inbound-normalize.js +0 -47
  69. package/lib/src/inbound-normalize.js.map +0 -1
  70. package/lib/src/index.js +0 -194
  71. package/lib/src/index.js.map +0 -1
  72. package/lib/src/mention-enrich.js +0 -52
  73. package/lib/src/mention-enrich.js.map +0 -1
  74. package/lib/src/outbound-keyboard.js +0 -100
  75. package/lib/src/outbound-keyboard.js.map +0 -1
  76. package/lib/src/outbound-markdown.js +0 -189
  77. package/lib/src/outbound-markdown.js.map +0 -1
  78. package/lib/src/outbound-media.js +0 -51
  79. package/lib/src/outbound-media.js.map +0 -1
  80. package/lib/src/outbound-mixed-media.js +0 -78
  81. package/lib/src/outbound-mixed-media.js.map +0 -1
  82. package/lib/src/platform-permit.js.map +0 -1
  83. package/lib/src/qq-agent-deps.js +0 -10
  84. package/lib/src/qq-agent-deps.js.map +0 -1
  85. package/lib/src/qq-bind-api.js +0 -90
  86. package/lib/src/qq-bind-api.js.map +0 -1
  87. package/lib/src/qq-bind-flow.js +0 -86
  88. package/lib/src/qq-bind-flow.js.map +0 -1
  89. package/lib/src/qq-bind-persist.js +0 -52
  90. package/lib/src/qq-bind-persist.js.map +0 -1
  91. package/lib/src/qq-endpoint-manager.js +0 -159
  92. package/lib/src/qq-endpoint-manager.js.map +0 -1
  93. package/lib/src/sdk-version.js +0 -19
  94. package/lib/src/sdk-version.js.map +0 -1
  95. package/lib/src/segment-mapper.js +0 -2
  96. package/lib/src/segment-mapper.js.map +0 -1
  97. package/lib/src/types.js +0 -2
  98. package/lib/src/types.js.map +0 -1
  99. package/plugin.yml +0 -3
  100. package/src/adapter.ts +0 -97
  101. package/src/gateway-config.ts +0 -48
  102. package/src/group-at-normalize.ts +0 -75
  103. package/src/inbound-action.ts +0 -87
  104. package/src/inbound-normalize.ts +0 -57
  105. package/src/mention-enrich.ts +0 -71
  106. package/src/outbound-keyboard.ts +0 -117
  107. package/src/outbound-markdown.ts +0 -212
  108. package/src/outbound-media.ts +0 -58
  109. package/src/outbound-mixed-media.ts +0 -101
  110. package/src/qq-bind-api.ts +0 -145
  111. package/src/qq-bind-flow.ts +0 -127
  112. package/src/qq-bind-persist.ts +0 -71
  113. package/src/qq-endpoint-manager.ts +0 -199
  114. package/src/sdk-version.ts +0 -21
  115. package/src/segment-mapper.ts +0 -1
  116. package/src/types.ts +0 -52
@@ -1,159 +0,0 @@
1
- import { ReceiverMode as QQReceiverMode } from './types.js';
2
- import { startQqBindFlow } from './qq-bind-flow.js';
3
- import { persistQqCredentialsToEnv } from './qq-bind-persist.js';
4
- const DEFAULT_INTENTS = [
5
- 'GUILDS',
6
- 'GUILD_MEMBERS',
7
- 'GUILD_MESSAGE_REACTIONS',
8
- 'DIRECT_MESSAGE',
9
- 'GROUP_AND_C2C_EVENT',
10
- 'INTERACTION',
11
- 'MESSAGE_AUDIT',
12
- 'AUDIO_ACTION',
13
- 'PUBLIC_GUILD_MESSAGES',
14
- ];
15
- function clonePlainConfig(value) {
16
- return JSON.parse(JSON.stringify(value));
17
- }
18
- function resolveQqEndpointTemplate(root) {
19
- const configService = root.inject('config');
20
- const raw = configService?.primaryFile && configService.getRaw
21
- ? configService.getRaw(configService.primaryFile)
22
- : undefined;
23
- const existing = raw?.endpoints?.find((e) => e.context === 'qq');
24
- if (!existing) {
25
- return {
26
- mode: QQReceiverMode.WEBSOCKET,
27
- sandbox: false,
28
- timeout: 10_000,
29
- data_dir: './data',
30
- intents: [...DEFAULT_INTENTS],
31
- };
32
- }
33
- const { name: _n, appid: _a, secret: _s, context: _c, ...rest } = existing;
34
- return clonePlainConfig(rest);
35
- }
36
- function resolveBindOperatorId(userOpenId, ctx) {
37
- const fromApi = userOpenId?.trim();
38
- if (fromApi)
39
- return fromApi;
40
- return ctx.message.$sender?.id?.trim() ?? '';
41
- }
42
- function applyBindOwnership(template, operatorId) {
43
- const { master: _m, aiAccess, ...rest } = template;
44
- if (!operatorId) {
45
- return rest;
46
- }
47
- const prior = aiAccess;
48
- return {
49
- ...rest,
50
- master: operatorId,
51
- aiAccess: {
52
- mode: prior?.mode ?? 'whitelist',
53
- users: [operatorId],
54
- groups: Array.isArray(prior?.groups) ? [...prior.groups] : [],
55
- denyMessage: typeof prior?.denyMessage === 'string'
56
- ? prior.denyMessage
57
- : '当前用户尚未开放 AI 功能,请联系开启。',
58
- },
59
- };
60
- }
61
- function readQqConfigs(root) {
62
- const configService = root.inject('config');
63
- if (!configService?.primaryFile || !configService.getRaw)
64
- return [];
65
- const raw = configService.getRaw(configService.primaryFile);
66
- return (raw?.endpoints ?? []).filter((e) => e.context === 'qq');
67
- }
68
- /** 全局单例:同一时间只允许一个 QQ 扫码绑定 */
69
- let activeStopFlow = null;
70
- export class QqEndpointManager {
71
- adapter;
72
- constructor(adapter) {
73
- this.adapter = adapter;
74
- }
75
- supportsProvision() {
76
- return true;
77
- }
78
- listEndpoints() {
79
- return readQqConfigs(this.adapter.plugin.root);
80
- }
81
- cancelProvision() {
82
- if (!activeStopFlow)
83
- return false;
84
- activeStopFlow();
85
- activeStopFlow = null;
86
- return true;
87
- }
88
- async addEndpoint(ctx) {
89
- if (activeStopFlow) {
90
- throw new Error('已有进行中的 QQ 机器人绑定,请先 /endpoint cancel');
91
- }
92
- let endpointName;
93
- const nameMatch = ctx.message.$raw?.match(/\/endpoint\s+add\s+qq\s+(\S+)/);
94
- if (nameMatch?.[1]) {
95
- endpointName = nameMatch[1];
96
- }
97
- return new Promise((resolve, reject) => {
98
- const stopFlow = startQqBindFlow({
99
- onQrDisplayed: async (url) => {
100
- await ctx.onStatusUpdate('请用手机 QQ 扫描二维码完成机器人绑定。', {
101
- qrcode: url,
102
- });
103
- },
104
- onQrExpired: async () => {
105
- await ctx.onStatusUpdate('二维码已过期,正在刷新,请扫描新二维码…');
106
- },
107
- onSuccess: async (credentials) => {
108
- activeStopFlow = null;
109
- const [{ appId, appSecret, userOpenId }] = credentials;
110
- const name = endpointName?.trim() || appId;
111
- await ctx.onStatusUpdate('凭据绑定成功,正在写入配置…');
112
- const envKeys = persistQqCredentialsToEnv(name, appId, appSecret);
113
- const template = resolveQqEndpointTemplate(ctx.root);
114
- const operatorId = resolveBindOperatorId(userOpenId, ctx);
115
- const owned = applyBindOwnership(template, operatorId);
116
- resolve({
117
- context: 'qq',
118
- name,
119
- appid: envKeys.appidRef,
120
- secret: envKeys.secretRef,
121
- ...owned,
122
- });
123
- },
124
- onFailure: async (error) => {
125
- activeStopFlow = null;
126
- reject(error);
127
- },
128
- }, { source: 'zhin' });
129
- activeStopFlow = stopFlow;
130
- });
131
- }
132
- async editEndpoint(name, ctx) {
133
- const existing = this.listEndpoints().find((e) => e.name === name);
134
- if (!existing) {
135
- throw new Error(`配置中不存在 qq/${name}`);
136
- }
137
- await ctx.onStatusUpdate(`QQ 官方机器人暂不支持在线改凭据,请 /endpoint remove qq ${name} 后重新 /endpoint add qq。`);
138
- return existing;
139
- }
140
- async removeEndpoint(name) {
141
- return this.listEndpoints().some((e) => e.name === name);
142
- }
143
- async startEndpoint(name, ctx) {
144
- if (!this.listEndpoints().some((e) => e.name === name)) {
145
- throw new Error(`配置中不存在 qq/${name}`);
146
- }
147
- await ctx.onStatusUpdate(`正在连接 qq/${name}…`);
148
- }
149
- async stopEndpoint(name) {
150
- return this.adapter.endpoints.has(name);
151
- }
152
- }
153
- export function disposeQqEndpointProvision() {
154
- if (activeStopFlow) {
155
- activeStopFlow();
156
- activeStopFlow = null;
157
- }
158
- }
159
- //# sourceMappingURL=qq-endpoint-manager.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"qq-endpoint-manager.js","sourceRoot":"","sources":["../../src/qq-endpoint-manager.ts"],"names":[],"mappings":"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"}
@@ -1,19 +0,0 @@
1
- import { createRequire } from "node:module";
2
- import adapterPkg from "../package.json" with { type: "json" };
3
- const require = createRequire(import.meta.url);
4
- function readDepVersion(dep) {
5
- try {
6
- return require(`${dep}/package.json`).version;
7
- }
8
- catch {
9
- return "unknown";
10
- }
11
- }
12
- /** HTTP header name for outbound QQ API requests. */
13
- export const SDK_VERSION_HEADER = "x-sdk-version";
14
- /**
15
- * Composite SDK identity: Zhin adapter + underlying qq-official-endpoint.
16
- * Example: `zhin-adapter-qq/v2.0.11+qq-official-bot/v1.2.1`
17
- */
18
- export const SDK_VERSION = `zhin-adapter-qq/v${adapterPkg.version}+qq-official-bot/v${readDepVersion("qq-official-bot")}`;
19
- //# sourceMappingURL=sdk-version.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"sdk-version.js","sourceRoot":"","sources":["../../src/sdk-version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,UAAU,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAE/D,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,SAAS,cAAc,CAAC,GAAW;IACjC,IAAI,CAAC;QACH,OAAQ,OAAO,CAAC,GAAG,GAAG,eAAe,CAAyB,CAAC,OAAO,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,qDAAqD;AACrD,MAAM,CAAC,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAElD;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,UAAU,CAAC,OAAO,qBAAqB,cAAc,CAAC,iBAAiB,CAAC,EAAE,CAAC"}
@@ -1,2 +0,0 @@
1
- export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
2
- //# sourceMappingURL=segment-mapper.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"segment-mapper.js","sourceRoot":"","sources":["../../src/segment-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC"}
package/lib/src/types.js DELETED
@@ -1,2 +0,0 @@
1
- export { ReceiverMode } from "qq-official-bot";
2
- //# sourceMappingURL=types.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
package/plugin.yml DELETED
@@ -1,3 +0,0 @@
1
- name: adapter-qq
2
- description: QQ 官方机器人适配器:群管理、频道/子频道管理、角色管理
3
- version: 1.0.59
package/src/adapter.ts DELETED
@@ -1,97 +0,0 @@
1
- /**
2
- * QQ 官方适配器
3
- */
4
- import { Adapter, Plugin, QQ_AI_OUTBOUND_EXTENSIONS, type EndpointManager, type OutboundRichSegmentPolicy } from 'zhin.js';
5
- import type { Router } from "@zhin.js/host-router";
6
-
7
- import { QQEndpoint } from "./endpoint.js";
8
- import { QqEndpointManager } from "./qq-endpoint-manager.js";
9
- import type { QQEndpointConfig, ReceiverMode } from "./types.js";
10
-
11
-
12
- export class QQAdapter extends Adapter<QQEndpoint<ReceiverMode>> {
13
- static override readonly capabilities = ['inbound', 'outbound'] as const;
14
- static override outboundRichSegmentPolicy: OutboundRichSegmentPolicy = {
15
- qrcode: 'image',
16
- html: 'image',
17
- markdown: 'origin',
18
- };
19
- static override interactivePolicy = 'native' as const;
20
- static override aiOutboundCapabilities = {
21
- mentions: true,
22
- richSegments: ['qrcode', 'html', 'markdown', 'tts'],
23
- interactive: 'native' as const,
24
- };
25
- static override aiOutboundExtensions = QQ_AI_OUTBOUND_EXTENSIONS;
26
-
27
- #router?: Router;
28
- #endpointManager?: QqEndpointManager;
29
-
30
- constructor(plugin: Plugin, router?: Router) {
31
- super(plugin, "qq", []);
32
- this.#router = router;
33
- }
34
-
35
- getRouter(): Router | undefined {
36
- return this.#router;
37
- }
38
-
39
- createEndpoint(config: QQEndpointConfig<ReceiverMode>): QQEndpoint<ReceiverMode> {
40
- return new QQEndpoint(this, config);
41
- }
42
-
43
- override getEndpointManager(): EndpointManager {
44
- if (!this.#endpointManager) {
45
- this.#endpointManager = new QqEndpointManager(this);
46
- }
47
- return this.#endpointManager;
48
- }
49
-
50
- // ── ISceneManagement 标准群管方法 ──────────────────────────────────
51
-
52
- async removeMember(endpointId: string, sceneId: string, userId: string) {
53
- const endpoint = this.endpoints.get(endpointId);
54
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
55
- return endpoint.removeGuildMember(sceneId, userId, false);
56
- }
57
-
58
- async muteMember(endpointId: string, sceneId: string, userId: string, duration = 600) {
59
- const endpoint = this.endpoints.get(endpointId);
60
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
61
- return endpoint.muteMembers(sceneId, [userId], duration);
62
- }
63
-
64
- async setSceneMuted(endpointId: string, sceneId: string, enable = true) {
65
- const endpoint = this.endpoints.get(endpointId);
66
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
67
- return endpoint.muteAll(sceneId, enable ? 600 : 0);
68
- }
69
-
70
- async listMembers(endpointId: string, sceneId: string) {
71
- const endpoint = this.endpoints.get(endpointId);
72
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
73
- return endpoint.getGuildMembers(sceneId);
74
- }
75
-
76
- async getSceneInfo(endpointId: string, sceneId: string) {
77
- const endpoint = this.endpoints.get(endpointId);
78
- if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
79
- return endpoint.getGuildInfo(sceneId);
80
- }
81
-
82
- // ── 生命周期 ───────────────────────────────────────────────────────
83
-
84
- async start(): Promise<void> {
85
- if (!this.#router) {
86
- this.#router = (this.plugin.inject as (key: string) => Router | undefined)("router");
87
- }
88
- (this.plugin.useContext as (key: string, fn: (router: Router) => void) => void)(
89
- "router",
90
- (router) => {
91
- this.#router = router;
92
- },
93
- );
94
- await super.start();
95
- }
96
-
97
- }
@@ -1,48 +0,0 @@
1
- import { formatCompact, type Logger } from '@zhin.js/logger';
2
-
3
- import { ReceiverMode, type Bot as QQOfficialBot } from 'qq-official-bot';
4
-
5
- import type { QQEndpointConfig } from "./types.js";
6
-
7
- /** qq-official-bot 默认 token 接口 */
8
- export const DEFAULT_ACCESS_TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
9
- /** qq-official-bot 默认 gateway 路径(相对 API baseURL) */
10
- export const DEFAULT_GATEWAY_URL = "/gateway/bot";
11
-
12
- type AuthManagerSession = {
13
- authManager: {
14
- config: {
15
- accessTokenUrl?: string;
16
- gatewayUrl?: string;
17
- };
18
- };
19
- };
20
-
21
- /**
22
- * 将自定义 gateway / token 地址注入 SDK Auth。
23
- * SDK 仅在 websocket 模式自动读取配置;middleware / webhook 需在此补丁。
24
- */
25
- export function applyCustomAuthEndpoints(
26
- endpoint: QQOfficialBot,
27
- config: Pick<QQEndpointConfig<ReceiverMode>, "mode" | "accessTokenUrl" | "gatewayUrl">,
28
- logger: Logger,
29
- ): void {
30
- const { accessTokenUrl, gatewayUrl, mode } = config;
31
- if (!accessTokenUrl && !gatewayUrl) return;
32
-
33
- if (mode !== ReceiverMode.WEBSOCKET) {
34
- const session = endpoint.sessionManager as unknown as AuthManagerSession;
35
- if (accessTokenUrl) session.authManager.config.accessTokenUrl = accessTokenUrl;
36
- if (gatewayUrl) session.authManager.config.gatewayUrl = gatewayUrl;
37
- }
38
-
39
- logger.debug(formatCompact({
40
- op: "qq_gateway",
41
- mode,
42
- accessTokenUrl: accessTokenUrl ?? DEFAULT_ACCESS_TOKEN_URL,
43
- gatewayUrl: gatewayUrl ?? DEFAULT_GATEWAY_URL,
44
- note: mode === ReceiverMode.WEBSOCKET
45
- ? "websocket 入站使用 gatewayUrl"
46
- : "middleware/webhook 仅 accessTokenUrl 生效",
47
- }));
48
- }
@@ -1,75 +0,0 @@
1
- import { readMentionSegmentTarget, type MessageElement } from 'zhin.js';
2
-
3
- function segmentAtId(seg: MessageElement): string {
4
- return readMentionSegmentTarget(seg);
5
- }
6
-
7
- function textMentionsEndpoint(text: string, endpointIds: string[]): boolean {
8
- for (const id of endpointIds) {
9
- const re = new RegExp(`@${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
10
- if (re.test(text)) return true;
11
- }
12
- return false;
13
- }
14
-
15
- function stripInlineAtEndpoint(text: string, endpointIds: string[]): string {
16
- let result = text;
17
- for (const id of endpointIds) {
18
- if (!id) continue;
19
- const re = new RegExp(`^@${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
20
- result = result.replace(re, "");
21
- }
22
- return result.trimStart();
23
- }
24
-
25
- /**
26
- * 统一 QQ 群 @ 机器人前缀:
27
- * - 去掉正文前的 at 段与内联 @,使命令/文本从首段开始匹配
28
- * - 若曾 @ 机器人,在末尾追加规范 at 段,供 AI @ 触发器识别
29
- */
30
- export function normalizeGroupAtPrefix(
31
- content: MessageElement[],
32
- endpointAtIds: string[],
33
- forceAt: boolean,
34
- ): MessageElement[] {
35
- const ids = [...new Set(endpointAtIds.map(String).filter(Boolean))];
36
- if (!ids.length || !Array.isArray(content)) return content;
37
-
38
- let mentioned = forceAt;
39
- const body: MessageElement[] = [];
40
-
41
- for (const seg of content) {
42
- if (seg.type === "at" || seg.type === "mention") {
43
- const uid = segmentAtId(seg);
44
- if (uid && ids.includes(uid)) {
45
- mentioned = true;
46
- continue;
47
- }
48
- body.push(seg);
49
- continue;
50
- }
51
- if (seg.type === "text" && seg.data && typeof seg.data === "object") {
52
- const raw = String((seg.data as { text?: string }).text ?? "");
53
- if (textMentionsEndpoint(raw, ids)) mentioned = true;
54
- const stripped = stripInlineAtEndpoint(raw, ids);
55
- if (stripped) {
56
- body.push({ ...seg, data: { ...(seg.data as object), text: stripped } });
57
- }
58
- continue;
59
- }
60
- body.push(seg);
61
- }
62
-
63
- while (body.length > 0 && body[0]?.type === "text") {
64
- const t = String((body[0].data as { text?: string })?.text ?? "").trim();
65
- if (t) break;
66
- body.shift();
67
- }
68
-
69
- if (mentioned) {
70
- const canonicalId = ids[0]!;
71
- body.push({ type: "at", data: { qq: canonicalId, id: canonicalId } });
72
- }
73
-
74
- return body.length ? body : content;
75
- }
@@ -1,87 +0,0 @@
1
- /**
2
- * QQ 官方互动事件 → 标准 action 段
3
- * @see https://bot.q.qq.com/wiki/develop/api-v2/server-inter/message/trans/msg-btn.html
4
- */
5
- import type {
6
- FriendActionNoticeEvent,
7
- GroupActionNoticeEvent,
8
- GuildActionNoticeEvent,
9
- } from 'qq-official-bot';
10
- import { Message, type MessageSegment, type SendContent } from 'zhin.js';
11
-
12
- export type QqActionNoticeEvent =
13
- | FriendActionNoticeEvent
14
- | GroupActionNoticeEvent
15
- | GuildActionNoticeEvent;
16
-
17
- export interface FormatQqActionMessageOptions {
18
- endpointName: string;
19
- send: (content: SendContent) => Promise<string>;
20
- }
21
-
22
- /** `group-{openid}:{msgId}` → 平台 msg_id */
23
- export function stripQqOutboundMessageId(storedId: string): string {
24
- const match = /^(?:private|group|channel|direct)-[^:]+:(.+)$/.exec(storedId);
25
- return match ? match[1]! : storedId;
26
- }
27
-
28
- function prependReply(content: SendContent, msgId: string): MessageSegment[] {
29
- const items = Array.isArray(content) ? [...content] : [content];
30
- items.unshift({ type: 'reply', data: { id: msgId } });
31
- return items as MessageSegment[];
32
- }
33
-
34
- function createQqActionReplyHandler(
35
- send: (content: SendContent) => Promise<string>,
36
- ) {
37
- return async (content: SendContent, quote: boolean | string = false): Promise<string> => {
38
- const outbound = typeof quote === 'string'
39
- ? prependReply(content, stripQqOutboundMessageId(quote))
40
- : content;
41
- return await send(outbound);
42
- };
43
- }
44
-
45
- export function formatQqActionMessage(
46
- event: QqActionNoticeEvent,
47
- options: FormatQqActionMessageOptions,
48
- ): ReturnType<typeof Message.from<QqActionNoticeEvent>> {
49
- const resolved = event.data?.resolved;
50
- const buttonId = String(resolved?.button_id ?? '');
51
- const buttonData = resolved?.button_data != null ? String(resolved.button_data) : '';
52
- const payload = buttonData || buttonId;
53
- const sourceMessageId = resolved?.message_id != null ? String(resolved.message_id) : undefined;
54
-
55
- let channelType: 'private' | 'group' | 'channel' = 'private';
56
- let channelId = String(event.operator_id ?? '');
57
-
58
- if (event.notice_type === 'group') {
59
- channelType = 'group';
60
- channelId = String(event.group_id);
61
- } else if (event.notice_type === 'guild') {
62
- channelType = 'channel';
63
- channelId = String(event.channel_id);
64
- }
65
-
66
- const senderId = String(event.operator_id ?? '');
67
-
68
- return Message.from(event, {
69
- $id: event.notice_id ?? event.event_id ?? `action:${Date.now()}`,
70
- $adapter: 'qq',
71
- $endpoint: options.endpointName,
72
- $sender: { id: senderId, name: senderId },
73
- $channel: { id: channelId, type: channelType },
74
- $content: [{
75
- type: 'action',
76
- data: {
77
- id: buttonId,
78
- payload,
79
- sourceMessageId,
80
- },
81
- }],
82
- $raw: payload,
83
- $timestamp: Date.now(),
84
- $recall: async () => {},
85
- $reply: createQqActionReplyHandler(options.send),
86
- });
87
- }
@@ -1,57 +0,0 @@
1
- /**
2
- * QQ 官方 API v2 入站 WebSocket payload 归一化(group_openid → group_id 等)。
3
- * qq-official-bot 解析层仍按旧字段名取值,未归一化时群 @ 可能解析失败并被 SDK 静默丢弃。
4
- */
5
-
6
- export type QqWsPacket = {
7
- d?: Record<string, unknown>;
8
- id?: string;
9
- t?: string;
10
- };
11
-
12
- const GROUP_MESSAGE_EVENTS = new Set([
13
- 'GROUP_AT_MESSAGE_CREATE',
14
- 'GROUP_MESSAGE_CREATE',
15
- ]);
16
-
17
- export function normalizeQqInboundWsPayload(event: string, packet: QqWsPacket): void {
18
- const d = packet.d;
19
- if (!d || typeof d !== 'object') return;
20
-
21
- if (GROUP_MESSAGE_EVENTS.has(event)) {
22
- if (!d.group_id && typeof d.group_openid === 'string') {
23
- d.group_id = d.group_openid;
24
- }
25
-
26
- const author = d.author;
27
- if (author && typeof author === 'object') {
28
- const a = author as Record<string, unknown>;
29
- if (!a.id) {
30
- a.id = a.member_openid ?? a.user_openid ?? a.id;
31
- }
32
- if (!a.username && !a.user_name) {
33
- a.username = a.member_openid ?? a.user_openid ?? 'unknown';
34
- }
35
- }
36
-
37
- if (!Array.isArray(d.mentions) && typeof d.content === 'string') {
38
- const mentions: Array<{ id: string }> = [];
39
- const re = /<@!?(\d+)>/g;
40
- let m: RegExpExecArray | null;
41
- while ((m = re.exec(d.content))) {
42
- mentions.push({ id: m[1]! });
43
- }
44
- if (mentions.length) d.mentions = mentions;
45
- }
46
-
47
- if (Array.isArray(d.attachments)) {
48
- for (const att of d.attachments) {
49
- if (att && typeof att === 'object' && (att as Record<string, unknown>).url == null) {
50
- (att as Record<string, unknown>).url = '';
51
- }
52
- }
53
- }
54
-
55
- d.__zhin_group_at = event === 'GROUP_AT_MESSAGE_CREATE';
56
- }
57
- }
@@ -1,71 +0,0 @@
1
- import type { Segment } from 'zhin.js';
2
- import type { QQAdapter } from './adapter.js';
3
-
4
- type MentionWire = {
5
- id?: string;
6
- member_openid?: string;
7
- username?: string;
8
- nickname?: string;
9
- };
10
-
11
- type QQEndpointLike = {
12
- $id: string;
13
- $platformUserId?: string;
14
- $config: { name?: string; appid?: string; nickname?: string };
15
- };
16
-
17
- function mentionWireId(row: MentionWire): string {
18
- return String(row.id ?? row.member_openid ?? '').trim();
19
- }
20
-
21
- function mentionWireName(row: MentionWire): string | undefined {
22
- const name = row.username ?? row.nickname;
23
- return typeof name === 'string' && name.trim() ? name.trim() : undefined;
24
- }
25
-
26
- function endpointLookupIds(endpoint: QQEndpointLike): string[] {
27
- const cfg = endpoint.$config;
28
- return [
29
- endpoint.$platformUserId,
30
- cfg.appid,
31
- cfg.name,
32
- endpoint.$id,
33
- ].filter((id): id is string => Boolean(id)).map(String);
34
- }
35
-
36
- function resolveEndpointDisplayName(endpoint: QQEndpointLike): string | undefined {
37
- const cfg = endpoint.$config;
38
- return cfg.nickname?.trim() || cfg.name?.trim() || endpoint.$id;
39
- }
40
-
41
- /** 为 canonical mention 段补齐展示名(平台 payload / endpoint 配置)。 */
42
- export function enrichCanonicalMentionNames(
43
- segments: Segment[],
44
- adapter: QQAdapter,
45
- wireMentions?: MentionWire[],
46
- ): Segment[] {
47
- const nameById = new Map<string, string>();
48
- for (const row of wireMentions ?? []) {
49
- const id = mentionWireId(row);
50
- const name = mentionWireName(row);
51
- if (id && name) nameById.set(id, name);
52
- }
53
-
54
- for (const endpoint of adapter.endpoints.values()) {
55
- const display = resolveEndpointDisplayName(endpoint);
56
- if (!display) continue;
57
- for (const id of endpointLookupIds(endpoint)) {
58
- if (!nameById.has(id)) nameById.set(id, display);
59
- }
60
- }
61
-
62
- return segments.map((seg) => {
63
- if (seg.type !== 'mention') return seg;
64
- const data = seg.data as { target?: string; name?: string };
65
- const target = String(data.target ?? '').trim();
66
- if (!target || data.name) return seg;
67
- const name = nameById.get(target);
68
- if (!name) return seg;
69
- return { ...seg, data: { target, name } };
70
- });
71
- }