@zhin.js/adapter-qq 2.0.12 → 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 +20 -4
- 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} +13 -8
- package/lib/endpoint.d.ts.map +1 -0
- package/lib/{bot.js → endpoint.js} +101 -35
- 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 +124 -96
- 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 +13 -10
- 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} +116 -42
- package/src/gateway-config.ts +5 -5
- package/src/group-at-normalize.ts +8 -8
- package/src/index.ts +118 -85
- 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
|
+
"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/cli": "1.0.
|
|
45
|
-
"@zhin.js/
|
|
46
|
-
"zhin.js": "1.0.
|
|
47
|
-
"@zhin.js/
|
|
44
|
+
"@zhin.js/cli": "1.0.89",
|
|
45
|
+
"@zhin.js/client": "2.0.3",
|
|
46
|
+
"@zhin.js/contract": "1.0.1",
|
|
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.
|
|
51
|
-
"@zhin.js/host-api": "
|
|
52
|
-
"@zhin.js/host-router": "0.0.3",
|
|
53
|
+
"@zhin.js/client": "2.0.3",
|
|
54
|
+
"@zhin.js/host-api": "1.0.1",
|
|
53
55
|
"@zhin.js/contract": "1.0.1",
|
|
54
|
-
"@zhin.js/
|
|
55
|
-
"zhin.js": "1.
|
|
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,14 +25,37 @@ 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
|
|
|
30
|
+
/** 从 qq-official-bot SendResult / 审核回包中解析出站消息 ID */
|
|
31
|
+
export function resolveOutboundMessageId(result: unknown): string {
|
|
32
|
+
if (!result || typeof result !== "object") {
|
|
33
|
+
throw new Error("QQ 发送消息失败:响应为空");
|
|
34
|
+
}
|
|
35
|
+
const row = result as Record<string, unknown>;
|
|
36
|
+
const nested = row.data && typeof row.data === "object"
|
|
37
|
+
? row.data as Record<string, unknown>
|
|
38
|
+
: undefined;
|
|
39
|
+
const audit = (row.message_audit ?? nested?.message_audit) as Record<string, unknown> | undefined;
|
|
40
|
+
const id = row.id ?? row.message_id ?? audit?.audit_id;
|
|
41
|
+
if (id == null || id === "") {
|
|
42
|
+
const code = row.code;
|
|
43
|
+
const msg = row.message;
|
|
44
|
+
throw new Error(
|
|
45
|
+
code != null
|
|
46
|
+
? `QQ 发送消息失败(${String(code)})${msg ? `: ${String(msg)}` : ""}`
|
|
47
|
+
: "QQ 发送消息失败:响应缺少消息 ID",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return String(id);
|
|
51
|
+
}
|
|
29
52
|
|
|
30
|
-
export class
|
|
31
|
-
extends
|
|
32
|
-
implements
|
|
53
|
+
export class QQEndpoint<T extends ReceiverMode, M extends ApplicationPlatform = ApplicationPlatform>
|
|
54
|
+
extends QQOfficialClient
|
|
55
|
+
implements Endpoint<QQEndpointConfig<T, M>, PrivateMessageEvent | GroupMessageEvent>
|
|
33
56
|
{
|
|
34
57
|
$connected: boolean = false;
|
|
35
|
-
declare $config:
|
|
58
|
+
declare $config: QQEndpointConfig<T, M>;
|
|
36
59
|
/** 平台侧机器人 user_id,用于 @ 触发匹配 */
|
|
37
60
|
$platformUserId?: string;
|
|
38
61
|
|
|
@@ -44,10 +67,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
44
67
|
return this.$config.name;
|
|
45
68
|
}
|
|
46
69
|
|
|
47
|
-
constructor(public adapter: QQAdapter, config:
|
|
70
|
+
constructor(public adapter: QQAdapter, config: QQEndpointConfig<T, M>) {
|
|
48
71
|
if (!config.data_dir) config.data_dir = path.join(process.cwd(), "data");
|
|
49
72
|
if (config.mode === ReceiverMode.MIDDLEWARE) {
|
|
50
|
-
const mw = config as
|
|
73
|
+
const mw = config as QQEndpointConfig<ReceiverMode.MIDDLEWARE, M> & {
|
|
51
74
|
platform?: ApplicationPlatform;
|
|
52
75
|
};
|
|
53
76
|
if (!mw.platform) {
|
|
@@ -84,9 +107,60 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
84
107
|
super.dispatchEvent(event, wsRes);
|
|
85
108
|
}
|
|
86
109
|
|
|
87
|
-
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> {
|
|
88
159
|
try {
|
|
89
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
|
+
}
|
|
90
164
|
this.adapter.emit("message.receive", message);
|
|
91
165
|
this.pluginLogger.debug(
|
|
92
166
|
`${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`,
|
|
@@ -128,7 +202,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
128
202
|
}
|
|
129
203
|
|
|
130
204
|
async $disconnect(): Promise<void> {
|
|
131
|
-
//
|
|
205
|
+
// Endpoint 继承自 EventEmitter,清除 $connect() 注册的所有监听器
|
|
132
206
|
(this as unknown as import('node:events').EventEmitter).removeAllListeners();
|
|
133
207
|
await this.stop();
|
|
134
208
|
this.$connected = false;
|
|
@@ -168,7 +242,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
168
242
|
return;
|
|
169
243
|
}
|
|
170
244
|
if (mode === ReceiverMode.WEBHOOK) {
|
|
171
|
-
const cfg = this.$config as
|
|
245
|
+
const cfg = this.$config as QQEndpointConfig<ReceiverMode.WEBHOOK>;
|
|
172
246
|
this.pluginLogger.info(formatCompact({
|
|
173
247
|
op: "webhook",
|
|
174
248
|
mode: "standalone",
|
|
@@ -203,16 +277,16 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
203
277
|
|
|
204
278
|
let content = msg.message;
|
|
205
279
|
if (msg.message_type === "group" && Array.isArray(content)) {
|
|
206
|
-
const
|
|
280
|
+
const endpointAtIds = [this.$platformUserId, this.$config.appid]
|
|
207
281
|
.filter((id): id is string => Boolean(id))
|
|
208
282
|
.map(String);
|
|
209
|
-
content = normalizeGroupAtPrefix(content,
|
|
283
|
+
content = normalizeGroupAtPrefix(content, endpointAtIds, raw.__zhin_group_at === true);
|
|
210
284
|
}
|
|
211
285
|
|
|
212
286
|
const result = Message.from(msg, {
|
|
213
287
|
$id: msg.message_id?.toString(),
|
|
214
288
|
$adapter: "qq" as const,
|
|
215
|
-
$
|
|
289
|
+
$endpoint: this.$config.name,
|
|
216
290
|
$sender: {
|
|
217
291
|
id: msg.sender.user_id?.toString(),
|
|
218
292
|
name: msg.sender.user_name?.toString(),
|
|
@@ -233,7 +307,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
233
307
|
return await this.adapter.sendMessage({
|
|
234
308
|
...result.$channel,
|
|
235
309
|
context: "qq",
|
|
236
|
-
|
|
310
|
+
endpoint: this.$config.name,
|
|
237
311
|
content,
|
|
238
312
|
});
|
|
239
313
|
},
|
|
@@ -252,22 +326,22 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
252
326
|
const id = options.id.replace("direct:", "");
|
|
253
327
|
const result = await this.sendDirectMessage(id, content);
|
|
254
328
|
this.pluginLogger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
255
|
-
return `direct-${options.id}:${result
|
|
329
|
+
return `direct-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
256
330
|
} else {
|
|
257
331
|
const result = await this.sendPrivateMessage(options.id, content);
|
|
258
332
|
this.pluginLogger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
259
|
-
return `private-${options.id}:${result
|
|
333
|
+
return `private-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
260
334
|
}
|
|
261
335
|
}
|
|
262
336
|
case "group": {
|
|
263
337
|
const result = await this.sendGroupMessage(options.id, content);
|
|
264
338
|
this.pluginLogger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
265
|
-
return `group-${options.id}:${result
|
|
339
|
+
return `group-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
266
340
|
}
|
|
267
341
|
case "channel": {
|
|
268
342
|
const result = await this.sendGuildMessage(options.id, content);
|
|
269
343
|
this.pluginLogger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
270
|
-
return `channel-${options.id}:${result
|
|
344
|
+
return `channel-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
271
345
|
}
|
|
272
346
|
default:
|
|
273
347
|
throw new Error(`unsupported channel type ${options.type}`);
|
|
@@ -294,7 +368,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
294
368
|
try {
|
|
295
369
|
return await this.guildService.getList();
|
|
296
370
|
} catch (error) {
|
|
297
|
-
this.pluginLogger.error(`QQ
|
|
371
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取频道列表失败:`, error);
|
|
298
372
|
throw error;
|
|
299
373
|
}
|
|
300
374
|
}
|
|
@@ -307,7 +381,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
307
381
|
try {
|
|
308
382
|
return await this.guildService.getInfo(guildId);
|
|
309
383
|
} catch (error) {
|
|
310
|
-
this.pluginLogger.error(`QQ
|
|
384
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取频道详情失败:`, error);
|
|
311
385
|
throw error;
|
|
312
386
|
}
|
|
313
387
|
}
|
|
@@ -320,7 +394,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
320
394
|
try {
|
|
321
395
|
return await this.channelService.getList(guildId);
|
|
322
396
|
} catch (error) {
|
|
323
|
-
this.pluginLogger.error(`QQ
|
|
397
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取子频道列表失败:`, error);
|
|
324
398
|
throw error;
|
|
325
399
|
}
|
|
326
400
|
}
|
|
@@ -333,7 +407,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
333
407
|
try {
|
|
334
408
|
return await this.channelService.getInfo(channelId);
|
|
335
409
|
} catch (error) {
|
|
336
|
-
this.pluginLogger.error(`QQ
|
|
410
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取子频道详情失败:`, error);
|
|
337
411
|
throw error;
|
|
338
412
|
}
|
|
339
413
|
}
|
|
@@ -346,7 +420,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
346
420
|
try {
|
|
347
421
|
return await this.memberService.getGuildMemberList(guildId);
|
|
348
422
|
} catch (error) {
|
|
349
|
-
this.pluginLogger.error(`QQ
|
|
423
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取频道成员列表失败:`, error);
|
|
350
424
|
throw error;
|
|
351
425
|
}
|
|
352
426
|
}
|
|
@@ -360,7 +434,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
360
434
|
try {
|
|
361
435
|
return await this.memberService.getGuildMemberInfo(guildId, userId);
|
|
362
436
|
} catch (error) {
|
|
363
|
-
this.pluginLogger.error(`QQ
|
|
437
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取成员详情失败:`, error);
|
|
364
438
|
throw error;
|
|
365
439
|
}
|
|
366
440
|
}
|
|
@@ -375,10 +449,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
375
449
|
async removeGuildMember(guildId: string, userId: string, addBlacklist?: boolean, deleteHistoryMsg?: -1 | 0 | 3 | 7 | 15 | 30): Promise<boolean> {
|
|
376
450
|
try {
|
|
377
451
|
await this.memberService.kickMember(guildId, userId, deleteHistoryMsg, addBlacklist);
|
|
378
|
-
this.pluginLogger.info(`QQ
|
|
452
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 踢出成员 ${userId}(频道 ${guildId})`);
|
|
379
453
|
return true;
|
|
380
454
|
} catch (error) {
|
|
381
|
-
this.pluginLogger.error(`QQ
|
|
455
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 踢出成员失败:`, error);
|
|
382
456
|
throw error;
|
|
383
457
|
}
|
|
384
458
|
}
|
|
@@ -391,7 +465,7 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
391
465
|
try {
|
|
392
466
|
return await this.guildService.getRoles(guildId);
|
|
393
467
|
} catch (error) {
|
|
394
|
-
this.pluginLogger.error(`QQ
|
|
468
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取角色列表失败:`, error);
|
|
395
469
|
throw error;
|
|
396
470
|
}
|
|
397
471
|
}
|
|
@@ -406,10 +480,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
406
480
|
async createGuildRole(guildId: string, name: string, color?: number, hoist?: 0 | 1): Promise<any> {
|
|
407
481
|
try {
|
|
408
482
|
const result = await this.guildService.createRole(guildId, { name, color: color || 0, hoist: hoist ?? 0 });
|
|
409
|
-
this.pluginLogger.info(`QQ
|
|
483
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 创建角色 "${name}"(频道 ${guildId})`);
|
|
410
484
|
return result;
|
|
411
485
|
} catch (error) {
|
|
412
|
-
this.pluginLogger.error(`QQ
|
|
486
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 创建角色失败:`, error);
|
|
413
487
|
throw error;
|
|
414
488
|
}
|
|
415
489
|
}
|
|
@@ -424,10 +498,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
424
498
|
async addMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean> {
|
|
425
499
|
try {
|
|
426
500
|
await this.memberService.addMemberRole(guildId, channelId, userId, roleId);
|
|
427
|
-
this.pluginLogger.info(`QQ
|
|
501
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 给成员 ${userId} 添加角色 ${roleId}(频道 ${guildId})`);
|
|
428
502
|
return true;
|
|
429
503
|
} catch (error) {
|
|
430
|
-
this.pluginLogger.error(`QQ
|
|
504
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 添加角色失败:`, error);
|
|
431
505
|
throw error;
|
|
432
506
|
}
|
|
433
507
|
}
|
|
@@ -442,10 +516,10 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
442
516
|
async removeMemberRole(guildId: string, channelId: string, userId: string, roleId: string): Promise<boolean> {
|
|
443
517
|
try {
|
|
444
518
|
await this.memberService.removeMemberRole(guildId, channelId, userId, roleId);
|
|
445
|
-
this.pluginLogger.info(`QQ
|
|
519
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 移除成员 ${userId} 的角色 ${roleId}(频道 ${guildId})`);
|
|
446
520
|
return true;
|
|
447
521
|
} catch (error) {
|
|
448
|
-
this.pluginLogger.error(`QQ
|
|
522
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 移除角色失败:`, error);
|
|
449
523
|
throw error;
|
|
450
524
|
}
|
|
451
525
|
}
|
|
@@ -460,14 +534,14 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
460
534
|
try {
|
|
461
535
|
if (muteSeconds > 0) {
|
|
462
536
|
await this.memberService.muteMembers(guildId, userIds, muteSeconds);
|
|
463
|
-
this.pluginLogger.info(`QQ
|
|
537
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 禁言成员 ${userIds.join(',')} ${muteSeconds}秒(频道 ${guildId})`);
|
|
464
538
|
} else {
|
|
465
539
|
await this.memberService.unmuteMembers(guildId, userIds);
|
|
466
|
-
this.pluginLogger.info(`QQ
|
|
540
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 解除成员 ${userIds.join(',')} 禁言(频道 ${guildId})`);
|
|
467
541
|
}
|
|
468
542
|
return true;
|
|
469
543
|
} catch (error) {
|
|
470
|
-
this.pluginLogger.error(`QQ
|
|
544
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 禁言操作失败:`, error);
|
|
471
545
|
throw error;
|
|
472
546
|
}
|
|
473
547
|
}
|
|
@@ -481,14 +555,14 @@ export class QQBot<T extends ReceiverMode, M extends ApplicationPlatform = Appli
|
|
|
481
555
|
try {
|
|
482
556
|
if (muteSeconds > 0) {
|
|
483
557
|
await this.guildService.mute(guildId, muteSeconds);
|
|
484
|
-
this.pluginLogger.info(`QQ
|
|
558
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 开启全员禁言(频道 ${guildId})`);
|
|
485
559
|
} else {
|
|
486
560
|
await this.guildService.unmute(guildId);
|
|
487
|
-
this.pluginLogger.info(`QQ
|
|
561
|
+
this.pluginLogger.info(`QQ Endpoint ${this.$id} 关闭全员禁言(频道 ${guildId})`);
|
|
488
562
|
}
|
|
489
563
|
return true;
|
|
490
564
|
} catch (error) {
|
|
491
|
-
this.pluginLogger.error(`QQ
|
|
565
|
+
this.pluginLogger.error(`QQ Endpoint ${this.$id} 全员禁言操作失败:`, error);
|
|
492
566
|
throw error;
|
|
493
567
|
}
|
|
494
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
|
}
|