@zhin.js/adapter-qq 3.0.0 → 3.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 +14 -0
- package/README.md +9 -9
- package/client/Dashboard.tsx +32 -32
- package/dist/index.js +1 -1
- package/lib/adapter.d.ts +10 -9
- package/lib/adapter.d.ts.map +1 -1
- package/lib/adapter.js +33 -32
- package/lib/adapter.js.map +1 -1
- package/lib/{bot.d.ts → endpoint.d.ts} +11 -8
- package/lib/endpoint.d.ts.map +1 -0
- package/lib/{bot.js → endpoint.js} +77 -31
- package/lib/endpoint.js.map +1 -0
- package/lib/gateway-config.d.ts +3 -3
- package/lib/gateway-config.d.ts.map +1 -1
- package/lib/gateway-config.js +2 -2
- package/lib/gateway-config.js.map +1 -1
- package/lib/group-at-normalize.d.ts +1 -1
- package/lib/group-at-normalize.d.ts.map +1 -1
- package/lib/group-at-normalize.js +8 -8
- package/lib/group-at-normalize.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +102 -92
- package/lib/index.js.map +1 -1
- package/lib/platform-permit.d.ts +17 -0
- package/lib/platform-permit.d.ts.map +1 -0
- package/lib/platform-permit.js +47 -0
- package/lib/platform-permit.js.map +1 -0
- package/lib/sdk-version.d.ts +1 -1
- package/lib/sdk-version.js +1 -1
- package/lib/types.d.ts +4 -4
- package/lib/types.d.ts.map +1 -1
- package/package.json +12 -9
- package/skills/qq/PERMITS.md +25 -0
- package/skills/qq/SKILL.md +1 -1
- package/src/adapter.ts +27 -25
- package/src/{bot.ts → endpoint.ts} +90 -38
- package/src/gateway-config.ts +5 -5
- package/src/group-at-normalize.ts +8 -8
- package/src/index.ts +96 -81
- package/src/platform-permit.ts +62 -0
- package/src/sdk-version.ts +1 -1
- package/src/types.ts +5 -5
- package/lib/bot.d.ts.map +0 -1
- package/lib/bot.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-qq",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Zhin.js adapter for QQ Official Bot",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -41,18 +41,21 @@
|
|
|
41
41
|
"@types/react-dom": "^19.2.3",
|
|
42
42
|
"lucide-react": "^1.17.0",
|
|
43
43
|
"typescript": "^6.0.3",
|
|
44
|
-
"@zhin.js/
|
|
44
|
+
"@zhin.js/cli": "1.0.89",
|
|
45
|
+
"@zhin.js/client": "2.0.3",
|
|
45
46
|
"@zhin.js/contract": "1.0.1",
|
|
46
|
-
"@zhin.js/
|
|
47
|
-
"zhin.js": "
|
|
47
|
+
"@zhin.js/host-api": "1.0.1",
|
|
48
|
+
"@zhin.js/host-router": "1.0.1",
|
|
49
|
+
"@zhin.js/logger": "0.1.71",
|
|
50
|
+
"zhin.js": "2.0.1"
|
|
48
51
|
},
|
|
49
52
|
"peerDependencies": {
|
|
50
|
-
"@zhin.js/client": "2.0.
|
|
53
|
+
"@zhin.js/client": "2.0.3",
|
|
54
|
+
"@zhin.js/host-api": "1.0.1",
|
|
51
55
|
"@zhin.js/contract": "1.0.1",
|
|
52
|
-
"@zhin.js/
|
|
53
|
-
"@zhin.js/
|
|
54
|
-
"
|
|
55
|
-
"zhin.js": "2.0.0"
|
|
56
|
+
"@zhin.js/host-router": "1.0.1",
|
|
57
|
+
"@zhin.js/logger": "0.1.71",
|
|
58
|
+
"zhin.js": "2.0.1"
|
|
56
59
|
},
|
|
57
60
|
"peerDependenciesMeta": {
|
|
58
61
|
"@zhin.js/client": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# QQ 官方频道 Platform Permits
|
|
2
|
+
|
|
3
|
+
> 与 QQ 群 `group_*` **分开命名**,仅用于 guild/channel 场景。
|
|
4
|
+
|
|
5
|
+
## 入站字段
|
|
6
|
+
|
|
7
|
+
`message_type === guild` 时 `getGuildMember` + `getGuildInfo`(TTL 60s):
|
|
8
|
+
|
|
9
|
+
| 字段 | 取值 |
|
|
10
|
+
|------|------|
|
|
11
|
+
| `$sender.role` | `owner` · `admin` · `member` |
|
|
12
|
+
| `$sender.permissions` | `guild_owner` · `guild_admin` · `manage_roles` · `manage_channels` · 角色 ID |
|
|
13
|
+
|
|
14
|
+
## Permit 词汇表
|
|
15
|
+
|
|
16
|
+
| `platform(qq,…)` | 含义 |
|
|
17
|
+
|------------------|------|
|
|
18
|
+
| `guild_owner` | 频道主 |
|
|
19
|
+
| `guild_admin` | 频道管理员 |
|
|
20
|
+
| `manage_roles` | 管理身份组 |
|
|
21
|
+
| `manage_channels` | 管理子频道 |
|
|
22
|
+
|
|
23
|
+
## 工厂映射
|
|
24
|
+
|
|
25
|
+
`group_admin` → `guild_admin` · `group_owner` → `guild_owner`
|
package/skills/qq/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ platforms:
|
|
|
5
5
|
description: >-
|
|
6
6
|
QQ 官方机器人管理能力。当用户在 QQ 官方机器人(非 OneBot/NapCat)中请求群管(踢人、
|
|
7
7
|
禁言、全员禁言)、频道/子频道管理、角色管理(创建/添加/移除角色)、
|
|
8
|
-
或查询成员详情时使用。即使用户没有明确提到 QQ 官方机器人,只要
|
|
8
|
+
或查询成员详情时使用。即使用户没有明确提到 QQ 官方机器人,只要 Endpoint 平台是 qq
|
|
9
9
|
(官方 API)且涉及上述操作,就应触发。仅有昵称时先 list_members 查 user_id。
|
|
10
10
|
keywords:
|
|
11
11
|
- qq
|
package/src/adapter.ts
CHANGED
|
@@ -6,10 +6,12 @@ import {
|
|
|
6
6
|
Plugin,
|
|
7
7
|
} from "zhin.js";
|
|
8
8
|
import type { Router } from "@zhin.js/host-router";
|
|
9
|
-
import {
|
|
10
|
-
import type {
|
|
9
|
+
import { QQEndpoint } from "./endpoint.js";
|
|
10
|
+
import type { QQEndpointConfig, ReceiverMode } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export class QQAdapter extends Adapter<QQEndpoint<ReceiverMode>> {
|
|
13
|
+
static override readonly capabilities = ['inbound', 'outbound'] as const;
|
|
11
14
|
|
|
12
|
-
export class QQAdapter extends Adapter<QQBot<ReceiverMode>> {
|
|
13
15
|
#router?: Router;
|
|
14
16
|
|
|
15
17
|
constructor(plugin: Plugin, router?: Router) {
|
|
@@ -21,40 +23,40 @@ export class QQAdapter extends Adapter<QQBot<ReceiverMode>> {
|
|
|
21
23
|
return this.#router;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
return new
|
|
26
|
+
createEndpoint(config: QQEndpointConfig<ReceiverMode>): QQEndpoint<ReceiverMode> {
|
|
27
|
+
return new QQEndpoint(this, config);
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
// ── IGroupManagement 标准群管方法 ──────────────────────────────────
|
|
29
31
|
|
|
30
|
-
async kickMember(
|
|
31
|
-
const
|
|
32
|
-
if (!
|
|
33
|
-
return
|
|
32
|
+
async kickMember(endpointId: string, sceneId: string, userId: string) {
|
|
33
|
+
const endpoint = this.endpoints.get(endpointId);
|
|
34
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
35
|
+
return endpoint.removeGuildMember(sceneId, userId, false);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
async muteMember(
|
|
37
|
-
const
|
|
38
|
-
if (!
|
|
39
|
-
return
|
|
38
|
+
async muteMember(endpointId: string, sceneId: string, userId: string, duration = 600) {
|
|
39
|
+
const endpoint = this.endpoints.get(endpointId);
|
|
40
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
41
|
+
return endpoint.muteMembers(sceneId, [userId], duration);
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
async muteAll(
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
45
|
-
return
|
|
44
|
+
async muteAll(endpointId: string, sceneId: string, enable = true) {
|
|
45
|
+
const endpoint = this.endpoints.get(endpointId);
|
|
46
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
47
|
+
return endpoint.muteAll(sceneId, enable ? 600 : 0);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
async listMembers(
|
|
49
|
-
const
|
|
50
|
-
if (!
|
|
51
|
-
return
|
|
50
|
+
async listMembers(endpointId: string, sceneId: string) {
|
|
51
|
+
const endpoint = this.endpoints.get(endpointId);
|
|
52
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
53
|
+
return endpoint.getGuildMembers(sceneId);
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
async getGroupInfo(
|
|
55
|
-
const
|
|
56
|
-
if (!
|
|
57
|
-
return
|
|
56
|
+
async getGroupInfo(endpointId: string, sceneId: string) {
|
|
57
|
+
const endpoint = this.endpoints.get(endpointId);
|
|
58
|
+
if (!endpoint) throw new Error(`Endpoint ${endpointId} 不存在`);
|
|
59
|
+
return endpoint.getGuildInfo(sceneId);
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// ── 生命周期 ───────────────────────────────────────────────────────
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* QQ 官方
|
|
2
|
+
* QQ 官方 Endpoint 实现
|
|
3
3
|
*/
|
|
4
4
|
import {
|
|
5
|
-
Bot,
|
|
5
|
+
Bot as QQOfficialClient,
|
|
6
6
|
PrivateMessageEvent,
|
|
7
7
|
GroupMessageEvent,
|
|
8
8
|
} from "qq-official-bot";
|
|
@@ -10,14 +10,14 @@ import path from "path";
|
|
|
10
10
|
import { formatCompact } from "@zhin.js/logger";
|
|
11
11
|
import { registerFetchRoute, type RouterContext } from "@zhin.js/host-router/router";
|
|
12
12
|
import {
|
|
13
|
-
|
|
13
|
+
Endpoint,
|
|
14
14
|
Message,
|
|
15
15
|
SendOptions,
|
|
16
16
|
SendContent,
|
|
17
17
|
segment,
|
|
18
18
|
} from "zhin.js";
|
|
19
19
|
import type { MessageElement } from "zhin.js";
|
|
20
|
-
import { ReceiverMode, type
|
|
20
|
+
import { ReceiverMode, type QQEndpointConfig, type ApplicationPlatform } from "./types.js";
|
|
21
21
|
import type { QQAdapter } from "./adapter.js";
|
|
22
22
|
import { normalizeQqInboundWsPayload, type QqWsPacket } from "./inbound-normalize.js";
|
|
23
23
|
import { normalizeGroupAtPrefix } from "./group-at-normalize.js";
|
|
@@ -25,6 +25,7 @@ import { SDK_VERSION, SDK_VERSION_HEADER } from "./sdk-version.js";
|
|
|
25
25
|
import { applyCustomAuthEndpoints } from "./gateway-config.js";
|
|
26
26
|
import { normalizeOutboundMarkdown } from "./outbound-markdown.js";
|
|
27
27
|
import { normalizeOutboundMedia } from "./outbound-media.js";
|
|
28
|
+
import { normalizeQqGuildSenderForPermit } from "./platform-permit.js";
|
|
28
29
|
|
|
29
30
|
/** 从 qq-official-bot SendResult / 审核回包中解析出站消息 ID */
|
|
30
31
|
export function resolveOutboundMessageId(result: unknown): string {
|
|
@@ -49,12 +50,12 @@ export function resolveOutboundMessageId(result: unknown): string {
|
|
|
49
50
|
return String(id);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
export class
|
|
53
|
-
extends
|
|
54
|
-
implements
|
|
53
|
+
export class QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform = ApplicationPlatform>
|
|
54
|
+
extends QQOfficialClient
|
|
55
|
+
implements Endpoint<QQEndpointConfig<T, M>, PrivateMessageEvent | GroupMessageEvent>
|
|
55
56
|
{
|
|
56
57
|
$connected: boolean = false;
|
|
57
|
-
declare $config:
|
|
58
|
+
declare $config: QQEndpointConfig<T, M>;
|
|
58
59
|
/** 平台侧机器人 user_id,用于 @ 触发匹配 */
|
|
59
60
|
$platformUserId?: string;
|
|
60
61
|
|
|
@@ -66,10 +67,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
66
67
|
return this.$config.name;
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
constructor(public adapter: QQAdapter, config:
|
|
70
|
+
constructor(public adapter: QQAdapter, config: QQEndpointConfig<T, M>) {
|
|
70
71
|
if (!config.data_dir) config.data_dir = path.join(process.cwd(), "data");
|
|
71
72
|
if (config.mode === ReceiverMode.MIDDLEWARE) {
|
|
72
|
-
const mw = config as
|
|
73
|
+
const mw = config as QQEndpointConfig<ReceiverMode.MIDDLEWARE, M> & {
|
|
73
74
|
platform?: ApplicationPlatform;
|
|
74
75
|
};
|
|
75
76
|
if (!mw.platform) {
|
|
@@ -106,9 +107,60 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
106
107
|
super.dispatchEvent(event, wsRes);
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
private
|
|
110
|
+
private guildMemberCache = new Map<string, { at: number; role?: string; permissions: string[] }>();
|
|
111
|
+
private guildOwnerCache = new Map<string, { at: number; ownerId?: string }>();
|
|
112
|
+
|
|
113
|
+
private async enrichGuildSender(
|
|
114
|
+
message: ReturnType<QQEndpoint<ReceiverMode>["$formatMessage"]>,
|
|
115
|
+
guildId: string,
|
|
116
|
+
userId: string,
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const key = `${guildId}:${userId}`;
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
const cached = this.guildMemberCache.get(key);
|
|
121
|
+
if (cached && now - cached.at < 60_000) {
|
|
122
|
+
message.$sender.role = cached.role;
|
|
123
|
+
message.$sender.permissions = cached.permissions;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
let ownerId: string | undefined;
|
|
128
|
+
const ownerCached = this.guildOwnerCache.get(guildId);
|
|
129
|
+
if (ownerCached && now - ownerCached.at < 300_000) {
|
|
130
|
+
ownerId = ownerCached.ownerId;
|
|
131
|
+
} else {
|
|
132
|
+
const guild = await this.getGuildInfo(guildId);
|
|
133
|
+
ownerId = guild?.owner_id?.toString?.() ?? guild?.owner?.id?.toString?.();
|
|
134
|
+
this.guildOwnerCache.set(guildId, { at: now, ownerId });
|
|
135
|
+
}
|
|
136
|
+
const member = await this.getGuildMember(guildId, userId);
|
|
137
|
+
const roleIds = Array.isArray(member?.roles)
|
|
138
|
+
? member.roles.map((r: unknown) =>
|
|
139
|
+
typeof r === "string" ? r : (r as { id?: string })?.id ?? String(r),
|
|
140
|
+
)
|
|
141
|
+
: [];
|
|
142
|
+
const isOwner = ownerId === userId;
|
|
143
|
+
const isAdmin = !isOwner && roleIds.length > 0;
|
|
144
|
+
const normalized = normalizeQqGuildSenderForPermit({ roles: roleIds, isOwner, isAdmin });
|
|
145
|
+
const entry = {
|
|
146
|
+
at: now,
|
|
147
|
+
role: normalized.role,
|
|
148
|
+
permissions: normalized.permissions ?? [],
|
|
149
|
+
};
|
|
150
|
+
this.guildMemberCache.set(key, entry);
|
|
151
|
+
message.$sender.role = entry.role;
|
|
152
|
+
message.$sender.permissions = entry.permissions;
|
|
153
|
+
} catch {
|
|
154
|
+
// 保守拒绝
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async handleQQMessage(msg: PrivateMessageEvent | GroupMessageEvent): Promise<void> {
|
|
110
159
|
try {
|
|
111
160
|
const message = this.$formatMessage(msg);
|
|
161
|
+
if (msg.message_type === "guild" && msg.guild_id && message.$sender.id) {
|
|
162
|
+
await this.enrichGuildSender(message, String(msg.guild_id), message.$sender.id);
|
|
163
|
+
}
|
|
112
164
|
this.adapter.emit("message.receive", message);
|
|
113
165
|
this.pluginLogger.debug(
|
|
114
166
|
`${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`,
|
|
@@ -150,7 +202,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
150
202
|
}
|
|
151
203
|
|
|
152
204
|
async $disconnect(): Promise<void> {
|
|
153
|
-
//
|
|
205
|
+
// Endpoint 继承自 EventEmitter,清除 $connect() 注册的所有监听器
|
|
154
206
|
(this as unknown as import('node:events').EventEmitter).removeAllListeners();
|
|
155
207
|
await this.stop();
|
|
156
208
|
this.$connected = false;
|
|
@@ -190,7 +242,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
190
242
|
return;
|
|
191
243
|
}
|
|
192
244
|
if (mode === ReceiverMode.WEBHOOK) {
|
|
193
|
-
const cfg = this.$config as
|
|
245
|
+
const cfg = this.$config as QQEndpointConfig<ReceiverMode.WEBHOOK>;
|
|
194
246
|
this.pluginLogger.info(formatCompact({
|
|
195
247
|
op: "webhook",
|
|
196
248
|
mode: "standalone",
|
|
@@ -225,16 +277,16 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
225
277
|
|
|
226
278
|
let content = msg.message;
|
|
227
279
|
if (msg.message_type === "group" && Array.isArray(content)) {
|
|
228
|
-
const
|
|
280
|
+
const endpointAtIds = [this.$platformUserId, this.$config.appid]
|
|
229
281
|
.filter((id): id is string => Boolean(id))
|
|
230
282
|
.map(String);
|
|
231
|
-
content = normalizeGroupAtPrefix(content,
|
|
283
|
+
content = normalizeGroupAtPrefix(content, endpointAtIds, raw.__zhin_group_at === true);
|
|
232
284
|
}
|
|
233
285
|
|
|
234
286
|
const result = Message.from(msg, {
|
|
235
287
|
$id: msg.message_id?.toString(),
|
|
236
288
|
$adapter: "qq" as const,
|
|
237
|
-
$
|
|
289
|
+
$endpoint: this.$config.name,
|
|
238
290
|
$sender: {
|
|
239
291
|
id: msg.sender.user_id?.toString(),
|
|
240
292
|
name: msg.sender.user_name?.toString(),
|
|
@@ -255,7 +307,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
255
307
|
return await this.adapter.sendMessage({
|
|
256
308
|
...result.$channel,
|
|
257
309
|
context: "qq",
|
|
258
|
-
|
|
310
|
+
endpoint: this.$config.name,
|
|
259
311
|
content,
|
|
260
312
|
});
|
|
261
313
|
},
|
|
@@ -316,7 +368,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
316
368
|
try {
|
|
317
369
|
return await this.guildService.getList();
|
|
318
370
|
} catch (error) {
|
|
319
|
-
this.pluginLogger.error(`QQ
|
|
371
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取频道列表失败:`, error);
|
|
320
372
|
throw error;
|
|
321
373
|
}
|
|
322
374
|
}
|
|
@@ -329,7 +381,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
329
381
|
try {
|
|
330
382
|
return await this.guildService.getInfo(guildId);
|
|
331
383
|
} catch (error) {
|
|
332
|
-
this.pluginLogger.error(`QQ
|
|
384
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取频道详情失败:`, error);
|
|
333
385
|
throw error;
|
|
334
386
|
}
|
|
335
387
|
}
|
|
@@ -342,7 +394,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
342
394
|
try {
|
|
343
395
|
return await this.channelService.getList(guildId);
|
|
344
396
|
} catch (error) {
|
|
345
|
-
this.pluginLogger.error(`QQ
|
|
397
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取子频道列表失败:`, error);
|
|
346
398
|
throw error;
|
|
347
399
|
}
|
|
348
400
|
}
|
|
@@ -355,7 +407,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
355
407
|
try {
|
|
356
408
|
return await this.channelService.getInfo(channelId);
|
|
357
409
|
} catch (error) {
|
|
358
|
-
this.pluginLogger.error(`QQ
|
|
410
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取子频道详情失败:`, error);
|
|
359
411
|
throw error;
|
|
360
412
|
}
|
|
361
413
|
}
|
|
@@ -368,7 +420,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
368
420
|
try {
|
|
369
421
|
return await this.memberService.getGuildMemberList(guildId);
|
|
370
422
|
} catch (error) {
|
|
371
|
-
this.pluginLogger.error(`QQ
|
|
423
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取频道成员列表失败:`, error);
|
|
372
424
|
throw error;
|
|
373
425
|
}
|
|
374
426
|
}
|
|
@@ -382,7 +434,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
382
434
|
try {
|
|
383
435
|
return await this.memberService.getGuildMemberInfo(guildId, userId);
|
|
384
436
|
} catch (error) {
|
|
385
|
-
this.pluginLogger.error(`QQ
|
|
437
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取成员详情失败:`, error);
|
|
386
438
|
throw error;
|
|
387
439
|
}
|
|
388
440
|
}
|
|
@@ -397,10 +449,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
397
449
|
async removeGuildMember(guildId: string, userId: string, addBlacklist?: boolean, deleteHistoryMsg?: -1 | 0 | 3 | 7 | 15 | 30): Promise<boolean> {
|
|
398
450
|
try {
|
|
399
451
|
await this.memberService.kickMember(guildId, userId, deleteHistoryMsg, addBlacklist);
|
|
400
|
-
this.pluginLogger.info(`QQ
|
|
452
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 踢出成员 ${userId}(频道 ${guildId})`);
|
|
401
453
|
return true;
|
|
402
454
|
} catch (error) {
|
|
403
|
-
this.pluginLogger.error(`QQ
|
|
455
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 踢出成员失败:`, error);
|
|
404
456
|
throw error;
|
|
405
457
|
}
|
|
406
458
|
}
|
|
@@ -413,7 +465,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
413
465
|
try {
|
|
414
466
|
return await this.guildService.getRoles(guildId);
|
|
415
467
|
} catch (error) {
|
|
416
|
-
this.pluginLogger.error(`QQ
|
|
468
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取角色列表失败:`, error);
|
|
417
469
|
throw error;
|
|
418
470
|
}
|
|
419
471
|
}
|
|
@@ -428,10 +480,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
428
480
|
async createGuildRole(guildId: string, name: string, color?: number, hoist?: 0 | 1): Promise<any> {
|
|
429
481
|
try {
|
|
430
482
|
const result = await this.guildService.createRole(guildId, { name, color: color || 0, hoist: hoist ?? 0 });
|
|
431
|
-
this.pluginLogger.info(`QQ
|
|
483
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 创建角色 "${name}"(频道 ${guildId})`);
|
|
432
484
|
return result;
|
|
433
485
|
} catch (error) {
|
|
434
|
-
this.pluginLogger.error(`QQ
|
|
486
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 创建角色失败:`, error);
|
|
435
487
|
throw error;
|
|
436
488
|
}
|
|
437
489
|
}
|
|
@@ -446,10 +498,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
446
498
|
async addMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean> {
|
|
447
499
|
try {
|
|
448
500
|
await this.memberService.addMemberRole(guildId, channelId, userId, roleId);
|
|
449
|
-
this.pluginLogger.info(`QQ
|
|
501
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 给成员 ${userId} 添加角色 ${roleId}(频道 ${guildId})`);
|
|
450
502
|
return true;
|
|
451
503
|
} catch (error) {
|
|
452
|
-
this.pluginLogger.error(`QQ
|
|
504
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 添加角色失败:`, error);
|
|
453
505
|
throw error;
|
|
454
506
|
}
|
|
455
507
|
}
|
|
@@ -464,10 +516,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
464
516
|
async removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean> {
|
|
465
517
|
try {
|
|
466
518
|
await this.memberService.removeMemberRole(guildId, channelId, userId, roleId);
|
|
467
|
-
this.pluginLogger.info(`QQ
|
|
519
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 移除成员 ${userId} 的角色 ${roleId}(频道 ${guildId})`);
|
|
468
520
|
return true;
|
|
469
521
|
} catch (error) {
|
|
470
|
-
this.pluginLogger.error(`QQ
|
|
522
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 移除角色失败:`, error);
|
|
471
523
|
throw error;
|
|
472
524
|
}
|
|
473
525
|
}
|
|
@@ -482,14 +534,14 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
482
534
|
try {
|
|
483
535
|
if (muteSeconds > 0) {
|
|
484
536
|
await this.memberService.muteMembers(guildId, userIds, muteSeconds);
|
|
485
|
-
this.pluginLogger.info(`QQ
|
|
537
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 禁言成员 ${userIds.join(',')} ${muteSeconds}秒(频道 ${guildId})`);
|
|
486
538
|
} else {
|
|
487
539
|
await this.memberService.unmuteMembers(guildId, userIds);
|
|
488
|
-
this.pluginLogger.info(`QQ
|
|
540
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 解除成员 ${userIds.join(',')} 禁言(频道 ${guildId})`);
|
|
489
541
|
}
|
|
490
542
|
return true;
|
|
491
543
|
} catch (error) {
|
|
492
|
-
this.pluginLogger.error(`QQ
|
|
544
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 禁言操作失败:`, error);
|
|
493
545
|
throw error;
|
|
494
546
|
}
|
|
495
547
|
}
|
|
@@ -503,14 +555,14 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
503
555
|
try {
|
|
504
556
|
if (muteSeconds > 0) {
|
|
505
557
|
await this.guildService.mute(guildId, muteSeconds);
|
|
506
|
-
this.pluginLogger.info(`QQ
|
|
558
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 开启全员禁言(频道 ${guildId})`);
|
|
507
559
|
} else {
|
|
508
560
|
await this.guildService.unmute(guildId);
|
|
509
|
-
this.pluginLogger.info(`QQ
|
|
561
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 关闭全员禁言(频道 ${guildId})`);
|
|
510
562
|
}
|
|
511
563
|
return true;
|
|
512
564
|
} catch (error) {
|
|
513
|
-
this.pluginLogger.error(`QQ
|
|
565
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 全员禁言操作失败:`, error);
|
|
514
566
|
throw error;
|
|
515
567
|
}
|
|
516
568
|
}
|
package/src/gateway-config.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Logger } from "@zhin.js/logger";
|
|
2
2
|
import { formatCompact } from "@zhin.js/logger";
|
|
3
|
-
import type { Bot } from "qq-official-bot";
|
|
3
|
+
import type { Bot as QQOfficialBot } from "qq-official-bot";
|
|
4
4
|
import { ReceiverMode } from "qq-official-bot";
|
|
5
|
-
import type {
|
|
5
|
+
import type { QQEndpointConfig } from "./types.js";
|
|
6
6
|
|
|
7
7
|
/** qq-official-bot 默认 token 接口 */
|
|
8
8
|
export const DEFAULT_ACCESS_TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
|
|
@@ -23,15 +23,15 @@ type AuthManagerSession = {
|
|
|
23
23
|
* SDK 仅在 websocket 模式自动读取配置;middleware / webhook 需在此补丁。
|
|
24
24
|
*/
|
|
25
25
|
export function applyCustomAuthEndpoints(
|
|
26
|
-
|
|
27
|
-
config: Pick<
|
|
26
|
+
endpoint: QQOfficialBot,
|
|
27
|
+
config: Pick<QQEndpointConfig<ReceiverMode>, "mode" | "accessTokenUrl" | "gatewayUrl">,
|
|
28
28
|
logger: Logger,
|
|
29
29
|
): void {
|
|
30
30
|
const { accessTokenUrl, gatewayUrl, mode } = config;
|
|
31
31
|
if (!accessTokenUrl && !gatewayUrl) return;
|
|
32
32
|
|
|
33
33
|
if (mode !== ReceiverMode.WEBSOCKET) {
|
|
34
|
-
const session =
|
|
34
|
+
const session = endpoint.sessionManager as unknown as AuthManagerSession;
|
|
35
35
|
if (accessTokenUrl) session.authManager.config.accessTokenUrl = accessTokenUrl;
|
|
36
36
|
if (gatewayUrl) session.authManager.config.gatewayUrl = gatewayUrl;
|
|
37
37
|
}
|
|
@@ -7,17 +7,17 @@ function segmentAtId(seg: MessageElement): string {
|
|
|
7
7
|
return raw == null ? "" : String(raw);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
function
|
|
11
|
-
for (const id of
|
|
10
|
+
function textMentionsEndpoint(text: string, endpointIds: string[]): boolean {
|
|
11
|
+
for (const id of endpointIds) {
|
|
12
12
|
const re = new RegExp(`@${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
|
|
13
13
|
if (re.test(text)) return true;
|
|
14
14
|
}
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function
|
|
18
|
+
function stripInlineAtEndpoint(text: string, endpointIds: string[]): string {
|
|
19
19
|
let result = text;
|
|
20
|
-
for (const id of
|
|
20
|
+
for (const id of endpointIds) {
|
|
21
21
|
if (!id) continue;
|
|
22
22
|
const re = new RegExp(`^@${id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
|
|
23
23
|
result = result.replace(re, "");
|
|
@@ -32,10 +32,10 @@ function stripInlineAtBot(text: string, botIds: string[]): string {
|
|
|
32
32
|
*/
|
|
33
33
|
export function normalizeGroupAtPrefix(
|
|
34
34
|
content: MessageElement[],
|
|
35
|
-
|
|
35
|
+
endpointAtIds: string[],
|
|
36
36
|
forceAt: boolean,
|
|
37
37
|
): MessageElement[] {
|
|
38
|
-
const ids = [...new Set(
|
|
38
|
+
const ids = [...new Set(endpointAtIds.map(String).filter(Boolean))];
|
|
39
39
|
if (!ids.length || !Array.isArray(content)) return content;
|
|
40
40
|
|
|
41
41
|
let mentioned = forceAt;
|
|
@@ -53,8 +53,8 @@ export function normalizeGroupAtPrefix(
|
|
|
53
53
|
}
|
|
54
54
|
if (seg.type === "text" && seg.data && typeof seg.data === "object") {
|
|
55
55
|
const raw = String((seg.data as { text?: string }).text ?? "");
|
|
56
|
-
if (
|
|
57
|
-
const stripped =
|
|
56
|
+
if (textMentionsEndpoint(raw, ids)) mentioned = true;
|
|
57
|
+
const stripped = stripInlineAtEndpoint(raw, ids);
|
|
58
58
|
if (stripped) {
|
|
59
59
|
body.push({ ...seg, data: { ...(seg.data as object), text: stripped } });
|
|
60
60
|
}
|