@zhin.js/adapter-qq 4.0.2 → 5.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.
Files changed (84) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +54 -0
  3. package/dist/index.js +2 -2
  4. package/lib/adapter.d.ts +14 -3
  5. package/lib/adapter.d.ts.map +1 -1
  6. package/lib/adapter.js +25 -4
  7. package/lib/adapter.js.map +1 -1
  8. package/lib/endpoint.d.ts +6 -1
  9. package/lib/endpoint.d.ts.map +1 -1
  10. package/lib/endpoint.js +80 -3
  11. package/lib/endpoint.js.map +1 -1
  12. package/lib/group-at-normalize.d.ts.map +1 -1
  13. package/lib/group-at-normalize.js +2 -5
  14. package/lib/group-at-normalize.js.map +1 -1
  15. package/lib/inbound-action.d.ts +15 -0
  16. package/lib/inbound-action.d.ts.map +1 -0
  17. package/lib/inbound-action.js +57 -0
  18. package/lib/inbound-action.js.map +1 -0
  19. package/lib/index.d.ts +3 -0
  20. package/lib/index.d.ts.map +1 -1
  21. package/lib/index.js +11 -4
  22. package/lib/index.js.map +1 -1
  23. package/lib/mention-enrich.d.ts +12 -0
  24. package/lib/mention-enrich.d.ts.map +1 -0
  25. package/lib/mention-enrich.js +52 -0
  26. package/lib/mention-enrich.js.map +1 -0
  27. package/lib/outbound-keyboard.d.ts +4 -0
  28. package/lib/outbound-keyboard.d.ts.map +1 -0
  29. package/lib/outbound-keyboard.js +96 -0
  30. package/lib/outbound-keyboard.js.map +1 -0
  31. package/lib/outbound-markdown.d.ts +16 -4
  32. package/lib/outbound-markdown.d.ts.map +1 -1
  33. package/lib/outbound-markdown.js +116 -17
  34. package/lib/outbound-markdown.js.map +1 -1
  35. package/lib/outbound-media.d.ts +2 -0
  36. package/lib/outbound-media.d.ts.map +1 -1
  37. package/lib/outbound-media.js +2 -1
  38. package/lib/outbound-media.js.map +1 -1
  39. package/lib/outbound-mixed-media.d.ts +21 -0
  40. package/lib/outbound-mixed-media.d.ts.map +1 -0
  41. package/lib/outbound-mixed-media.js +78 -0
  42. package/lib/outbound-mixed-media.js.map +1 -0
  43. package/lib/platform-permit.js +2 -2
  44. package/lib/qq-bind-api.d.ts +37 -0
  45. package/lib/qq-bind-api.d.ts.map +1 -0
  46. package/lib/qq-bind-api.js +90 -0
  47. package/lib/qq-bind-api.js.map +1 -0
  48. package/lib/qq-bind-flow.d.ts +20 -0
  49. package/lib/qq-bind-flow.d.ts.map +1 -0
  50. package/lib/qq-bind-flow.js +86 -0
  51. package/lib/qq-bind-flow.js.map +1 -0
  52. package/lib/qq-bind-persist.d.ts +11 -0
  53. package/lib/qq-bind-persist.d.ts.map +1 -0
  54. package/lib/qq-bind-persist.js +52 -0
  55. package/lib/qq-bind-persist.js.map +1 -0
  56. package/lib/qq-endpoint-manager.d.ts +16 -0
  57. package/lib/qq-endpoint-manager.d.ts.map +1 -0
  58. package/lib/qq-endpoint-manager.js +159 -0
  59. package/lib/qq-endpoint-manager.js.map +1 -0
  60. package/lib/segment-mapper.d.ts +2 -0
  61. package/lib/segment-mapper.d.ts.map +1 -0
  62. package/lib/segment-mapper.js +2 -0
  63. package/lib/segment-mapper.js.map +1 -0
  64. package/lib/types.d.ts +7 -0
  65. package/lib/types.d.ts.map +1 -1
  66. package/package.json +16 -16
  67. package/skills/qq/PERMITS.md +1 -1
  68. package/src/adapter.ts +28 -4
  69. package/src/endpoint.ts +118 -6
  70. package/src/group-at-normalize.ts +2 -4
  71. package/src/inbound-action.ts +87 -0
  72. package/src/index.ts +13 -5
  73. package/src/mention-enrich.ts +71 -0
  74. package/src/outbound-keyboard.ts +118 -0
  75. package/src/outbound-markdown.ts +127 -17
  76. package/src/outbound-media.ts +2 -1
  77. package/src/outbound-mixed-media.ts +101 -0
  78. package/src/platform-permit.ts +2 -2
  79. package/src/qq-bind-api.ts +145 -0
  80. package/src/qq-bind-flow.ts +127 -0
  81. package/src/qq-bind-persist.ts +71 -0
  82. package/src/qq-endpoint-manager.ts +199 -0
  83. package/src/segment-mapper.ts +1 -0
  84. package/src/types.ts +7 -0
@@ -0,0 +1,159 @@
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qq-endpoint-manager.js","sourceRoot":"","sources":["../src/qq-endpoint-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
2
+ //# sourceMappingURL=segment-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment-mapper.d.ts","sourceRoot":"","sources":["../src/segment-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { toCanonicalSegments, fromCanonicalSegments } from 'zhin.js';
2
+ //# sourceMappingURL=segment-mapper.js.map
@@ -0,0 +1 @@
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/types.d.ts CHANGED
@@ -31,6 +31,13 @@ export type QQEndpointConfig<T extends ReceiverMode, M extends ApplicationPlatfo
31
31
  * - `false`:保持纯文本
32
32
  */
33
33
  outboundMarkdown?: boolean | "auto";
34
+ /** 本 Endpoint AI 访问控制(见 docs/advanced/content-moderation.md) */
35
+ aiAccess?: {
36
+ mode?: 'open' | 'closed' | 'whitelist';
37
+ users?: string[];
38
+ groups?: string[];
39
+ denyMessage?: string;
40
+ };
34
41
  };
35
42
  export interface QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform = ApplicationPlatform> {
36
43
  $config: QQEndpointConfig<T, M>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEnE,MAAM,MAAM,gBAAgB,CAC1B,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,mBAAmB,GAAG,mBAAmB,IACjD,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAC/B,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACrC,CAAC;AAEF,MAAM,WAAW,UAAU,CACzB,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,mBAAmB,GAAG,mBAAmB;IAEnD,OAAO,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CACjC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,GAAG,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEzE,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEnE,MAAM,MAAM,gBAAgB,CAC1B,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,mBAAmB,GAAG,mBAAmB,IACjD,aAAa,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG;IAC/B,OAAO,EAAE,IAAI,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACpC,gEAAgE;IAChE,QAAQ,CAAC,EAAE;QACT,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;QACvC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAClB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH,CAAC;AAEF,MAAM,WAAW,UAAU,CACzB,CAAC,SAAS,YAAY,EACtB,CAAC,SAAS,mBAAmB,GAAG,mBAAmB;IAEnD,OAAO,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;CACjC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-qq",
3
- "version": "4.0.2",
3
+ "version": "5.0.1",
4
4
  "description": "Zhin.js adapter for QQ Official Bot",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -34,28 +34,28 @@
34
34
  "directory": "plugins/adapters/qq"
35
35
  },
36
36
  "dependencies": {
37
- "qq-official-bot": "^1.2.2"
37
+ "qq-official-bot": "^1.2.3"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/react": "^19.2.17",
41
41
  "@types/react-dom": "^19.2.3",
42
- "lucide-react": "^1.17.0",
42
+ "lucide-react": "^1.22.0",
43
43
  "typescript": "^6.0.3",
44
- "@zhin.js/client": "2.0.3",
45
- "@zhin.js/contract": "1.0.1",
46
- "@zhin.js/host-api": "2.0.2",
47
- "@zhin.js/cli": "1.0.90",
48
- "@zhin.js/host-router": "2.0.0",
49
- "zhin.js": "4.0.1",
50
- "@zhin.js/logger": "0.1.71"
44
+ "@zhin.js/cli": "1.0.92",
45
+ "@zhin.js/client": "2.0.4",
46
+ "@zhin.js/contract": "1.0.2",
47
+ "@zhin.js/host-api": "2.0.4",
48
+ "@zhin.js/host-router": "2.0.2",
49
+ "@zhin.js/logger": "1.0.73",
50
+ "zhin.js": "4.1.1"
51
51
  },
52
52
  "peerDependencies": {
53
- "@zhin.js/client": "2.0.3",
54
- "@zhin.js/host-api": "2.0.2",
55
- "@zhin.js/host-router": "2.0.0",
56
- "@zhin.js/logger": "0.1.71",
57
- "@zhin.js/contract": "1.0.1",
58
- "zhin.js": "4.0.1"
53
+ "@zhin.js/client": "2.0.4",
54
+ "@zhin.js/host-api": "2.0.4",
55
+ "@zhin.js/contract": "1.0.2",
56
+ "@zhin.js/host-router": "2.0.2",
57
+ "@zhin.js/logger": "1.0.73",
58
+ "zhin.js": "4.1.1"
59
59
  },
60
60
  "peerDependenciesMeta": {
61
61
  "@zhin.js/client": {
@@ -22,4 +22,4 @@
22
22
 
23
23
  ## 工厂映射
24
24
 
25
- `group_admin` → `guild_admin` · `group_owner` → `guild_owner`
25
+ `scene_admin` → `guild_admin` · `scene_owner` → `guild_owner`
package/src/adapter.ts CHANGED
@@ -6,13 +6,30 @@ import {
6
6
  Plugin,
7
7
  } from "zhin.js";
8
8
  import type { Router } from "@zhin.js/host-router";
9
+ import type { EndpointManager } from "zhin.js";
9
10
  import { QQEndpoint } from "./endpoint.js";
11
+ import { QqEndpointManager } from "./qq-endpoint-manager.js";
10
12
  import type { QQEndpointConfig, ReceiverMode } from "./types.js";
13
+ import type { OutboundRichSegmentPolicy } from "zhin.js";
14
+ import { QQ_AI_OUTBOUND_EXTENSIONS } from "zhin.js";
11
15
 
12
16
  export class QQAdapter extends Adapter<QQEndpoint<ReceiverMode>> {
13
17
  static override readonly capabilities = ['inbound', 'outbound'] as const;
18
+ static override outboundRichSegmentPolicy: OutboundRichSegmentPolicy = {
19
+ qrcode: 'image',
20
+ html: 'image',
21
+ markdown: 'origin',
22
+ };
23
+ static override interactivePolicy = 'native' as const;
24
+ static override aiOutboundCapabilities = {
25
+ mentions: true,
26
+ richSegments: ['qrcode', 'html', 'markdown', 'tts'],
27
+ interactive: 'native' as const,
28
+ };
29
+ static override aiOutboundExtensions = QQ_AI_OUTBOUND_EXTENSIONS;
14
30
 
15
31
  #router?: Router;
32
+ #endpointManager?: QqEndpointManager;
16
33
 
17
34
  constructor(plugin: Plugin, router?: Router) {
18
35
  super(plugin, "qq", []);
@@ -27,9 +44,16 @@ export class QQAdapter extends Adapter<QQEndpoint<ReceiverMode>> {
27
44
  return new QQEndpoint(this, config);
28
45
  }
29
46
 
30
- // ── IGroupManagement 标准群管方法 ──────────────────────────────────
47
+ override getEndpointManager(): EndpointManager {
48
+ if (!this.#endpointManager) {
49
+ this.#endpointManager = new QqEndpointManager(this);
50
+ }
51
+ return this.#endpointManager;
52
+ }
53
+
54
+ // ── ISceneManagement 标准群管方法 ──────────────────────────────────
31
55
 
32
- async kickMember(endpointId: string, sceneId: string, userId: string) {
56
+ async removeMember(endpointId: string, sceneId: string, userId: string) {
33
57
  const endpoint = this.endpoints.get(endpointId);
34
58
  if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
35
59
  return endpoint.removeGuildMember(sceneId, userId, false);
@@ -41,7 +65,7 @@ export class QQAdapter extends Adapter<QQEndpoint<ReceiverMode>> {
41
65
  return endpoint.muteMembers(sceneId, [userId], duration);
42
66
  }
43
67
 
44
- async muteAll(endpointId: string, sceneId: string, enable = true) {
68
+ async setSceneMuted(endpointId: string, sceneId: string, enable = true) {
45
69
  const endpoint = this.endpoints.get(endpointId);
46
70
  if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
47
71
  return endpoint.muteAll(sceneId, enable ? 600 : 0);
@@ -53,7 +77,7 @@ export class QQAdapter extends Adapter<QQEndpoint<ReceiverMode>> {
53
77
  return endpoint.getGuildMembers(sceneId);
54
78
  }
55
79
 
56
- async getGroupInfo(endpointId: string, sceneId: string) {
80
+ async getSceneInfo(endpointId: string, sceneId: string) {
57
81
  const endpoint = this.endpoints.get(endpointId);
58
82
  if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
59
83
  return endpoint.getGuildInfo(sceneId);
package/src/endpoint.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  SendOptions,
16
16
  SendContent,
17
17
  segment,
18
- } from "zhin.js";
18
+ } from 'zhin.js';
19
19
  import type { MessageElement } from "zhin.js";
20
20
  import { ReceiverMode, type QQEndpointConfig, type ApplicationPlatform } from "./types.js";
21
21
  import type { QQAdapter } from "./adapter.js";
@@ -23,9 +23,19 @@ import { normalizeQqInboundWsPayload, type QqWsPacket } from "./inbound-normaliz
23
23
  import { normalizeGroupAtPrefix } from "./group-at-normalize.js";
24
24
  import { SDK_VERSION, SDK_VERSION_HEADER } from "./sdk-version.js";
25
25
  import { applyCustomAuthEndpoints } from "./gateway-config.js";
26
- import { normalizeOutboundMarkdown } from "./outbound-markdown.js";
26
+ import { normalizeOutboundMarkdown, asOutboundSegments, splitReplyPrefix } from "./outbound-markdown.js";
27
27
  import { normalizeOutboundMedia } from "./outbound-media.js";
28
+ import { fromCanonicalSegments, toCanonicalSegments } from "./segment-mapper.js";
29
+ import { enrichCanonicalMentionNames } from "./mention-enrich.js";
30
+ import {
31
+ buildMixedMediaMessagePayload,
32
+ buildQqImageUploadPayload,
33
+ firstImageSegment,
34
+ shouldSendTextImageMixedMedia,
35
+ } from "./outbound-mixed-media.js";
28
36
  import { normalizeQqGuildSenderForPermit } from "./platform-permit.js";
37
+ import { expandKeyboardSegmentsForQq } from "./outbound-keyboard.js";
38
+ import { formatQqActionMessage, type QqActionNoticeEvent } from "./inbound-action.js";
29
39
 
30
40
  /** 从 qq-official-bot SendResult / 审核回包中解析出站消息 ID */
31
41
  export function resolveOutboundMessageId(result: unknown): string {
@@ -172,6 +182,34 @@ export class QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform =
172
182
  }
173
183
  }
174
184
 
185
+ private async handleQQAction(event: QqActionNoticeEvent): Promise<void> {
186
+ try {
187
+ await event.reply(0);
188
+ const channel = event.notice_type === 'group'
189
+ ? { id: String(event.group_id), type: 'group' as const }
190
+ : event.notice_type === 'guild'
191
+ ? { id: String(event.channel_id), type: 'channel' as const }
192
+ : { id: String(event.operator_id), type: 'private' as const };
193
+ const message = formatQqActionMessage(event, {
194
+ endpointName: this.$config.name,
195
+ send: async (content) => this.adapter.sendMessage({
196
+ ...channel,
197
+ context: 'qq',
198
+ endpoint: this.$config.name,
199
+ content,
200
+ }),
201
+ });
202
+ await this.adapter.receiveMessageAwait(message);
203
+ this.pluginLogger.debug(
204
+ `qq action ${message.$channel.type}(${message.$channel.id}): ${message.$raw}`,
205
+ );
206
+ } catch (err) {
207
+ this.pluginLogger.warn(
208
+ `qq format action failed: ${err instanceof Error ? err.message : String(err)}`,
209
+ );
210
+ }
211
+ }
212
+
175
213
  private handleGroupNotice(event: string, payload: { group_id?: string; operator_id?: string }): void {
176
214
  this.pluginLogger.info(
177
215
  `qq notice ${event}: group=${payload.group_id ?? '?'} operator=${payload.operator_id ?? '?'}`,
@@ -182,6 +220,9 @@ export class QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform =
182
220
  this.on("message.group", this.handleQQMessage.bind(this));
183
221
  this.on("message.guild", this.handleQQMessage.bind(this));
184
222
  this.on("message.private", this.handleQQMessage.bind(this));
223
+ this.on("notice.friend.action", this.handleQQAction.bind(this));
224
+ this.on("notice.group.action", this.handleQQAction.bind(this));
225
+ this.on("notice.guild.action", this.handleQQAction.bind(this));
185
226
  this.on("notice.group.increase", (e) => this.handleGroupNotice('group.add_robot', e));
186
227
  this.on("notice.group.decrease", (e) => this.handleGroupNotice('group.del_robot', e));
187
228
  this.on("notice.group.receive_open", (e) => this.handleGroupNotice('group.msg_receive_open', e));
@@ -253,10 +294,18 @@ export class QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform =
253
294
  }
254
295
  }
255
296
 
297
+ private toWireSendContent(content: SendContent): MessageElement[] {
298
+ const items = (Array.isArray(content) ? content : [content]).map((item) =>
299
+ typeof item === "string" ? { type: "text", data: { text: item } } : item as MessageElement,
300
+ );
301
+ return fromCanonicalSegments(toCanonicalSegments(items as never));
302
+ }
303
+
256
304
  $formatMessage(msg: PrivateMessageEvent | GroupMessageEvent) {
257
305
  const raw = msg as PrivateMessageEvent & GroupMessageEvent & {
258
306
  group_openid?: string;
259
307
  author?: { member_openid?: string; user_openid?: string; id?: string; username?: string };
308
+ mentions?: Array<{ id?: string; member_openid?: string; username?: string; nickname?: string }>;
260
309
  __zhin_group_at?: boolean;
261
310
  };
262
311
 
@@ -283,6 +332,14 @@ export class QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform =
283
332
  content = normalizeGroupAtPrefix(content, endpointAtIds, raw.__zhin_group_at === true);
284
333
  }
285
334
 
335
+ if (Array.isArray(content)) {
336
+ content = enrichCanonicalMentionNames(
337
+ toCanonicalSegments(content),
338
+ this.adapter,
339
+ raw.mentions,
340
+ );
341
+ }
342
+
286
343
  const result = Message.from(msg, {
287
344
  $id: msg.message_id?.toString(),
288
345
  $adapter: "qq" as const,
@@ -316,10 +373,20 @@ export class QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform =
316
373
  }
317
374
 
318
375
  async $sendMessage(options: SendOptions): Promise<string> {
319
- const content = normalizeOutboundMarkdown(
320
- normalizeOutboundMedia(options.content),
321
- this.$config.outboundMarkdown,
322
- );
376
+ const expanded = expandKeyboardSegmentsForQq(options.content);
377
+ const canonicalized = this.toWireSendContent(expanded);
378
+ const mediaNorm = normalizeOutboundMedia(canonicalized);
379
+ const markdownMode = this.$config.outboundMarkdown;
380
+
381
+ if (
382
+ (options.type === "group" || options.type === "private")
383
+ && !options.id.startsWith("direct:")
384
+ && shouldSendTextImageMixedMedia(mediaNorm, markdownMode)
385
+ ) {
386
+ return await this.$sendTextImageMixedMedia(options, mediaNorm);
387
+ }
388
+
389
+ const content = normalizeOutboundMarkdown(mediaNorm, markdownMode);
323
390
  switch (options.type) {
324
391
  case "private": {
325
392
  if (options.id.startsWith("direct:")) {
@@ -348,6 +415,51 @@ export class QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform =
348
415
  }
349
416
  }
350
417
 
418
+ /** 内联图 + 文本:上传 file_info 后以 msg_type=7 图文混排发送(二维码等场景) */
419
+ private async $sendTextImageMixedMedia(
420
+ options: SendOptions,
421
+ content: SendContent,
422
+ ): Promise<string> {
423
+ const endpointPath = this.resolveV2EndpointPath(options);
424
+ const { body } = splitReplyPrefix(asOutboundSegments(content));
425
+ const image = firstImageSegment(body);
426
+ if (!image) {
427
+ throw new Error("QQ 图文混排:未找到图片段");
428
+ }
429
+
430
+ const uploadPayload = buildQqImageUploadPayload(image);
431
+ const { data: uploadResult } = await this.request.post(
432
+ `${endpointPath}/files`,
433
+ { ...uploadPayload, srv_send_msg: false },
434
+ ) as { data: { file_info?: string } };
435
+
436
+ if (!uploadResult?.file_info) {
437
+ throw new Error("QQ 图文混排:图片上传未返回 file_info");
438
+ }
439
+
440
+ const messagePayload = buildMixedMediaMessagePayload(content, uploadResult.file_info);
441
+ const { data: result } = await this.request.post(
442
+ `${endpointPath}/messages`,
443
+ messagePayload,
444
+ );
445
+ this.pluginLogger.debug(
446
+ `${this.$config.name} send mixed-media ${options.type}(${options.id}):${segment.raw(content)}`,
447
+ );
448
+ const idPrefix = options.type === "group" ? "group" : "private";
449
+ return `${idPrefix}-${options.id}:${resolveOutboundMessageId(result)}`;
450
+ }
451
+
452
+ private resolveV2EndpointPath(options: SendOptions): string {
453
+ switch (options.type) {
454
+ case "group":
455
+ return `/v2/groups/${options.id}`;
456
+ case "private":
457
+ return `/v2/users/${options.id}`;
458
+ default:
459
+ throw new Error(`QQ 图文混排不支持 ${options.type}`);
460
+ }
461
+ }
462
+
351
463
  async $recallMessage(id: string): Promise<void> {
352
464
  if (!/^(private|group|channel|direct)-([^\:]+):(.+)$/.test(id)) throw new Error(`invalid message id ${id}`);
353
465
  const match = id.match(/^(private|group|channel|direct)-([^\:]+):(.+)$/);
@@ -1,10 +1,8 @@
1
1
  import type { MessageElement } from "zhin.js";
2
+ import { readMentionSegmentTarget } from "zhin.js";
2
3
 
3
4
  function segmentAtId(seg: MessageElement): string {
4
- if (seg.type !== "at" && seg.type !== "mention") return "";
5
- const data = seg.data as Record<string, unknown> | undefined;
6
- const raw = data?.user_id ?? data?.qq ?? data?.id;
7
- return raw == null ? "" : String(raw);
5
+ return readMentionSegmentTarget(seg);
8
6
  }
9
7
 
10
8
  function textMentionsEndpoint(text: string, endpointIds: string[]): boolean {
@@ -0,0 +1,87 @@
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
+ }