@zhin.js/adapter-qq 5.0.1 → 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.
- package/CHANGELOG.md +60 -0
- package/README.md +52 -408
- package/adapters/qq.ts +45 -0
- package/agent/tools/add_role.ts +23 -0
- package/agent/tools/channel_info.ts +19 -0
- package/agent/tools/create_role.ts +22 -0
- package/agent/tools/list_channels.ts +20 -0
- package/agent/tools/list_guilds.ts +17 -0
- package/agent/tools/list_roles.ts +20 -0
- package/agent/tools/member_detail.ts +20 -0
- package/agent/tools/remove_role.ts +23 -0
- package/lib/endpoint.d.ts +64 -120
- package/lib/endpoint.js +279 -564
- package/lib/index.d.ts +6 -23
- package/lib/index.js +6 -375
- package/lib/platform-permit.d.ts +1 -2
- package/lib/platform-permit.js +4 -2
- package/lib/protocol.d.ts +96 -0
- package/lib/protocol.js +129 -0
- package/lib/qq-agent-deps.d.ts +21 -0
- package/lib/qq-agent-deps.js +30 -0
- package/lib/webhook.d.ts +18 -0
- package/lib/webhook.js +100 -0
- package/lib/ws.d.ts +24 -0
- package/lib/ws.js +118 -0
- package/package.json +47 -33
- package/plugin.ts +13 -0
- package/schema.json +51 -0
- package/src/endpoint.ts +313 -605
- package/src/index.ts +55 -368
- package/src/platform-permit.ts +1 -2
- package/src/protocol.ts +229 -0
- package/src/qq-agent-deps.ts +60 -0
- package/src/webhook.ts +127 -0
- package/src/ws.ts +172 -0
- package/client/Dashboard.tsx +0 -230
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -7
- package/client/utils/api.ts +0 -30
- package/dist/index.js +0 -30
- package/lib/adapter.d.ts +0 -32
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -82
- package/lib/adapter.js.map +0 -1
- package/lib/endpoint.d.ts.map +0 -1
- package/lib/endpoint.js.map +0 -1
- package/lib/gateway-config.d.ts +0 -14
- package/lib/gateway-config.d.ts.map +0 -1
- package/lib/gateway-config.js +0 -32
- package/lib/gateway-config.js.map +0 -1
- package/lib/group-at-normalize.d.ts +0 -8
- package/lib/group-at-normalize.d.ts.map +0 -1
- package/lib/group-at-normalize.js +0 -68
- package/lib/group-at-normalize.js.map +0 -1
- package/lib/inbound-action.d.ts +0 -15
- package/lib/inbound-action.d.ts.map +0 -1
- package/lib/inbound-action.js +0 -57
- package/lib/inbound-action.js.map +0 -1
- package/lib/inbound-normalize.d.ts +0 -11
- package/lib/inbound-normalize.d.ts.map +0 -1
- package/lib/inbound-normalize.js +0 -47
- package/lib/inbound-normalize.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/mention-enrich.d.ts +0 -12
- package/lib/mention-enrich.d.ts.map +0 -1
- package/lib/mention-enrich.js +0 -52
- package/lib/mention-enrich.js.map +0 -1
- package/lib/outbound-keyboard.d.ts +0 -4
- package/lib/outbound-keyboard.d.ts.map +0 -1
- package/lib/outbound-keyboard.js +0 -96
- package/lib/outbound-keyboard.js.map +0 -1
- package/lib/outbound-markdown.d.ts +0 -24
- package/lib/outbound-markdown.d.ts.map +0 -1
- package/lib/outbound-markdown.js +0 -177
- package/lib/outbound-markdown.js.map +0 -1
- package/lib/outbound-media.d.ts +0 -11
- package/lib/outbound-media.d.ts.map +0 -1
- package/lib/outbound-media.js +0 -51
- package/lib/outbound-media.js.map +0 -1
- package/lib/outbound-mixed-media.d.ts +0 -21
- package/lib/outbound-mixed-media.d.ts.map +0 -1
- package/lib/outbound-mixed-media.js +0 -78
- package/lib/outbound-mixed-media.js.map +0 -1
- package/lib/platform-permit.d.ts.map +0 -1
- package/lib/platform-permit.js.map +0 -1
- package/lib/qq-bind-api.d.ts +0 -37
- package/lib/qq-bind-api.d.ts.map +0 -1
- package/lib/qq-bind-api.js +0 -90
- package/lib/qq-bind-api.js.map +0 -1
- package/lib/qq-bind-flow.d.ts +0 -20
- package/lib/qq-bind-flow.d.ts.map +0 -1
- package/lib/qq-bind-flow.js +0 -86
- package/lib/qq-bind-flow.js.map +0 -1
- package/lib/qq-bind-persist.d.ts +0 -11
- package/lib/qq-bind-persist.d.ts.map +0 -1
- package/lib/qq-bind-persist.js +0 -52
- package/lib/qq-bind-persist.js.map +0 -1
- package/lib/qq-endpoint-manager.d.ts +0 -16
- package/lib/qq-endpoint-manager.d.ts.map +0 -1
- package/lib/qq-endpoint-manager.js +0 -159
- package/lib/qq-endpoint-manager.js.map +0 -1
- package/lib/sdk-version.d.ts +0 -8
- package/lib/sdk-version.d.ts.map +0 -1
- package/lib/sdk-version.js +0 -19
- package/lib/sdk-version.js.map +0 -1
- package/lib/segment-mapper.d.ts +0 -2
- package/lib/segment-mapper.d.ts.map +0 -1
- package/lib/segment-mapper.js +0 -2
- package/lib/segment-mapper.js.map +0 -1
- package/lib/types.d.ts +0 -45
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js +0 -2
- package/lib/types.js.map +0 -1
- package/plugin.yml +0 -3
- package/src/adapter.ts +0 -101
- package/src/gateway-config.ts +0 -48
- package/src/group-at-normalize.ts +0 -76
- package/src/inbound-action.ts +0 -87
- package/src/inbound-normalize.ts +0 -57
- package/src/mention-enrich.ts +0 -71
- package/src/outbound-keyboard.ts +0 -118
- package/src/outbound-markdown.ts +0 -203
- package/src/outbound-media.ts +0 -58
- package/src/outbound-mixed-media.ts +0 -101
- package/src/qq-bind-api.ts +0 -145
- package/src/qq-bind-flow.ts +0 -127
- package/src/qq-bind-persist.ts +0 -71
- package/src/qq-endpoint-manager.ts +0 -199
- package/src/sdk-version.ts +0 -21
- package/src/segment-mapper.ts +0 -1
- package/src/types.ts +0 -53
- /package/{skills/qq → agent}/PERMITS.md +0 -0
- /package/{skills/qq/SKILL.md → agent/skills/qq.md} +0 -0
package/lib/endpoint.js
CHANGED
|
@@ -1,600 +1,315 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import { formatQqActionMessage } from "./inbound-action.js";
|
|
21
|
-
/** 从 qq-official-bot SendResult / 审核回包中解析出站消息 ID */
|
|
22
|
-
export function resolveOutboundMessageId(result) {
|
|
23
|
-
if (!result || typeof result !== "object") {
|
|
24
|
-
throw new Error("QQ 发送消息失败:响应为空");
|
|
25
|
-
}
|
|
26
|
-
const row = result;
|
|
27
|
-
const nested = row.data && typeof row.data === "object"
|
|
28
|
-
? row.data
|
|
29
|
-
: undefined;
|
|
30
|
-
const audit = (row.message_audit ?? nested?.message_audit);
|
|
31
|
-
const id = row.id ?? row.message_id ?? audit?.audit_id;
|
|
32
|
-
if (id == null || id === "") {
|
|
33
|
-
const code = row.code;
|
|
34
|
-
const msg = row.message;
|
|
35
|
-
throw new Error(code != null
|
|
36
|
-
? `QQ 发送消息失败(${String(code)})${msg ? `: ${String(msg)}` : ""}`
|
|
37
|
-
: "QQ 发送消息失败:响应缺少消息 ID");
|
|
38
|
-
}
|
|
39
|
-
return String(id);
|
|
40
|
-
}
|
|
41
|
-
export class QQEndpoint extends QQOfficialClient {
|
|
42
|
-
adapter;
|
|
43
|
-
$connected = false;
|
|
44
|
-
/** 平台侧机器人 user_id,用于 @ 触发匹配 */
|
|
45
|
-
$platformUserId;
|
|
46
|
-
get pluginLogger() {
|
|
47
|
-
return this.adapter.plugin.logger;
|
|
48
|
-
}
|
|
49
|
-
get $id() {
|
|
50
|
-
return this.$config.name;
|
|
51
|
-
}
|
|
52
|
-
constructor(adapter, config) {
|
|
53
|
-
if (!config.data_dir)
|
|
54
|
-
config.data_dir = path.join(process.cwd(), "data");
|
|
55
|
-
if (config.mode === ReceiverMode.MIDDLEWARE) {
|
|
56
|
-
const mw = config;
|
|
57
|
-
if (!mw.platform) {
|
|
58
|
-
mw.platform = (mw.application ?? "koa");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
super(config);
|
|
62
|
-
this.adapter = adapter;
|
|
63
|
-
this.$config = config;
|
|
64
|
-
this.attachSdkVersionHeader();
|
|
65
|
-
applyCustomAuthEndpoints(this, config, this.pluginLogger);
|
|
66
|
-
}
|
|
67
|
-
/** 出站 QQ API 请求附带 SDK 版本,便于平台侧日志排查 */
|
|
68
|
-
attachSdkVersionHeader() {
|
|
69
|
-
this.request.interceptors.request.use((reqConfig) => {
|
|
70
|
-
reqConfig.headers[SDK_VERSION_HEADER] = SDK_VERSION;
|
|
71
|
-
return reqConfig;
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
/** 归一化 QQ API v2 群聊字段后再交给 qq-official-bot 解析 */
|
|
75
|
-
dispatchEvent(event, wsRes) {
|
|
76
|
-
this.pluginLogger.debug({
|
|
77
|
-
op: 'inbound_ws',
|
|
78
|
-
event,
|
|
79
|
-
group: String(wsRes.d?.group_openid ?? wsRes.d?.group_id ?? '?'),
|
|
80
|
-
});
|
|
81
|
-
if (event === 'GROUP_AT_MESSAGE_CREATE' || event === 'GROUP_MESSAGE_CREATE') {
|
|
82
|
-
this.pluginLogger.info(`qq inbound ws: ${event} group=${String(wsRes.d?.group_openid ?? wsRes.d?.group_id ?? '?')}`);
|
|
83
|
-
}
|
|
84
|
-
normalizeQqInboundWsPayload(event, wsRes);
|
|
85
|
-
super.dispatchEvent(event, wsRes);
|
|
86
|
-
}
|
|
87
|
-
guildMemberCache = new Map();
|
|
88
|
-
guildOwnerCache = new Map();
|
|
89
|
-
async enrichGuildSender(message, guildId, userId) {
|
|
90
|
-
const key = `${guildId}:${userId}`;
|
|
91
|
-
const now = Date.now();
|
|
92
|
-
const cached = this.guildMemberCache.get(key);
|
|
93
|
-
if (cached && now - cached.at < 60_000) {
|
|
94
|
-
message.$sender.role = cached.role;
|
|
95
|
-
message.$sender.permissions = cached.permissions;
|
|
1
|
+
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
2
|
+
import { registerQqAgentEndpoint } from './qq-agent-deps.js';
|
|
3
|
+
import { formatInboundContent, formatInboundTarget, formatOutboundText, parseSendTarget, resolveOutboundMessageId, senderDisplayName, } from './protocol.js';
|
|
4
|
+
import { defaultCreateHttpBot, registerQqWebhookRoutes, } from './webhook.js';
|
|
5
|
+
import { bindQqBotInboundEvents, defaultCreateBot, normalizeQqMessage, } from './ws.js';
|
|
6
|
+
const logger = getLogger('qq');
|
|
7
|
+
export class QqWebsocketEndpoint {
|
|
8
|
+
#options;
|
|
9
|
+
#createBot;
|
|
10
|
+
#bot = null;
|
|
11
|
+
#open = false;
|
|
12
|
+
#started = false;
|
|
13
|
+
#unregisterAgent;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.#options = options;
|
|
16
|
+
this.#createBot = options.createBot ?? defaultCreateBot;
|
|
17
|
+
}
|
|
18
|
+
async start() {
|
|
19
|
+
if (this.#started)
|
|
96
20
|
return;
|
|
97
|
-
|
|
21
|
+
this.#started = true;
|
|
98
22
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
}
|
|
109
|
-
const member = await this.getGuildMember(guildId, userId);
|
|
110
|
-
const roleIds = Array.isArray(member?.roles)
|
|
111
|
-
? member.roles.map((r) => typeof r === "string" ? r : r?.id ?? String(r))
|
|
112
|
-
: [];
|
|
113
|
-
const isOwner = ownerId === userId;
|
|
114
|
-
const isAdmin = !isOwner && roleIds.length > 0;
|
|
115
|
-
const normalized = normalizeQqGuildSenderForPermit({ roles: roleIds, isOwner, isAdmin });
|
|
116
|
-
const entry = {
|
|
117
|
-
at: now,
|
|
118
|
-
role: normalized.role,
|
|
119
|
-
permissions: normalized.permissions ?? [],
|
|
120
|
-
};
|
|
121
|
-
this.guildMemberCache.set(key, entry);
|
|
122
|
-
message.$sender.role = entry.role;
|
|
123
|
-
message.$sender.permissions = entry.permissions;
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
// 保守拒绝
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
async handleQQMessage(msg) {
|
|
130
|
-
try {
|
|
131
|
-
const message = this.$formatMessage(msg);
|
|
132
|
-
if (msg.message_type === "guild" && msg.guild_id && message.$sender.id) {
|
|
133
|
-
await this.enrichGuildSender(message, String(msg.guild_id), message.$sender.id);
|
|
134
|
-
}
|
|
135
|
-
this.adapter.emit("message.receive", message);
|
|
136
|
-
this.pluginLogger.debug(`${this.$config.name} recv ${message.$channel.type}(${message.$channel.id}):${segment.raw(message.$content)}`);
|
|
23
|
+
this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.name, this);
|
|
24
|
+
this.#bot = this.#createBot(this.#options.config);
|
|
25
|
+
this.#bindBot(this.#bot);
|
|
26
|
+
await this.#bot.start();
|
|
27
|
+
logger.info(formatCompact({
|
|
28
|
+
op: 'connect',
|
|
29
|
+
endpoint: this.#options.config.name,
|
|
30
|
+
mode: 'websocket',
|
|
31
|
+
appid: this.#options.config.appid,
|
|
32
|
+
}));
|
|
137
33
|
}
|
|
138
|
-
catch (
|
|
139
|
-
this.
|
|
34
|
+
catch (error) {
|
|
35
|
+
await this.stop();
|
|
36
|
+
logger.error('Failed to connect QQ websocket:', error);
|
|
37
|
+
throw error;
|
|
140
38
|
}
|
|
141
39
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
await event.reply(0);
|
|
145
|
-
const channel = event.notice_type === 'group'
|
|
146
|
-
? { id: String(event.group_id), type: 'group' }
|
|
147
|
-
: event.notice_type === 'guild'
|
|
148
|
-
? { id: String(event.channel_id), type: 'channel' }
|
|
149
|
-
: { id: String(event.operator_id), type: 'private' };
|
|
150
|
-
const message = formatQqActionMessage(event, {
|
|
151
|
-
endpointName: this.$config.name,
|
|
152
|
-
send: async (content) => this.adapter.sendMessage({
|
|
153
|
-
...channel,
|
|
154
|
-
context: 'qq',
|
|
155
|
-
endpoint: this.$config.name,
|
|
156
|
-
content,
|
|
157
|
-
}),
|
|
158
|
-
});
|
|
159
|
-
await this.adapter.receiveMessageAwait(message);
|
|
160
|
-
this.pluginLogger.debug(`qq action ${message.$channel.type}(${message.$channel.id}): ${message.$raw}`);
|
|
161
|
-
}
|
|
162
|
-
catch (err) {
|
|
163
|
-
this.pluginLogger.warn(`qq format action failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
164
|
-
}
|
|
40
|
+
open() {
|
|
41
|
+
this.#open = true;
|
|
165
42
|
}
|
|
166
|
-
|
|
167
|
-
this
|
|
168
|
-
}
|
|
169
|
-
async $connect() {
|
|
170
|
-
this.on("message.group", this.handleQQMessage.bind(this));
|
|
171
|
-
this.on("message.guild", this.handleQQMessage.bind(this));
|
|
172
|
-
this.on("message.private", this.handleQQMessage.bind(this));
|
|
173
|
-
this.on("notice.friend.action", this.handleQQAction.bind(this));
|
|
174
|
-
this.on("notice.group.action", this.handleQQAction.bind(this));
|
|
175
|
-
this.on("notice.guild.action", this.handleQQAction.bind(this));
|
|
176
|
-
this.on("notice.group.increase", (e) => this.handleGroupNotice('group.add_robot', e));
|
|
177
|
-
this.on("notice.group.decrease", (e) => this.handleGroupNotice('group.del_robot', e));
|
|
178
|
-
this.on("notice.group.receive_open", (e) => this.handleGroupNotice('group.msg_receive_open', e));
|
|
179
|
-
this.on("notice.group.receive_close", (e) => this.handleGroupNotice('group.msg_receive_close', e));
|
|
180
|
-
await this.start();
|
|
181
|
-
this.mountWebhookReceiver();
|
|
182
|
-
this.$connected = true;
|
|
183
|
-
try {
|
|
184
|
-
const self = await this.getSelfInfo();
|
|
185
|
-
const uid = self?.id
|
|
186
|
-
?? self?.user_id;
|
|
187
|
-
if (uid)
|
|
188
|
-
this.$platformUserId = String(uid);
|
|
189
|
-
}
|
|
190
|
-
catch (err) {
|
|
191
|
-
this.pluginLogger.debug(`${this.$config.name} getSelfInfo failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
192
|
-
}
|
|
43
|
+
close() {
|
|
44
|
+
this.#open = false;
|
|
193
45
|
}
|
|
194
|
-
async
|
|
195
|
-
|
|
196
|
-
this
|
|
197
|
-
|
|
198
|
-
this
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
return raw.startsWith("/") ? raw : `/${raw}`;
|
|
203
|
-
}
|
|
204
|
-
/**
|
|
205
|
-
* Webhook 入站:middleware(挂 host-router)或独立 HTTP 端口(qq-official-bot 内置服务)。
|
|
206
|
-
*/
|
|
207
|
-
mountWebhookReceiver() {
|
|
208
|
-
const mode = this.$config.mode;
|
|
209
|
-
if (mode === ReceiverMode.MIDDLEWARE) {
|
|
210
|
-
const router = this.adapter.getRouter();
|
|
211
|
-
if (!router) {
|
|
212
|
-
throw new Error("QQ mode=middleware 需要启用 @zhin.js/host-router 插件");
|
|
46
|
+
async stop() {
|
|
47
|
+
this.#open = false;
|
|
48
|
+
this.#unregisterAgent?.();
|
|
49
|
+
this.#unregisterAgent = undefined;
|
|
50
|
+
if (this.#bot) {
|
|
51
|
+
try {
|
|
52
|
+
this.#bot.removeAllListeners();
|
|
53
|
+
await this.#bot.stop();
|
|
213
54
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
55
|
+
catch {
|
|
56
|
+
/* ignore */
|
|
57
|
+
}
|
|
58
|
+
this.#bot = null;
|
|
59
|
+
}
|
|
60
|
+
this.#started = false;
|
|
61
|
+
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
62
|
+
}
|
|
63
|
+
async send({ target, payload }) {
|
|
64
|
+
const body = formatOutboundText(payload);
|
|
65
|
+
const parsed = parseSendTarget(target);
|
|
66
|
+
const bot = this.#requireBot();
|
|
67
|
+
let result;
|
|
68
|
+
switch (parsed.kind) {
|
|
69
|
+
case 'private':
|
|
70
|
+
result = await bot.sendPrivateMessage(parsed.id, body);
|
|
71
|
+
break;
|
|
72
|
+
case 'group':
|
|
73
|
+
result = await bot.sendGroupMessage(parsed.id, body);
|
|
74
|
+
break;
|
|
75
|
+
case 'channel':
|
|
76
|
+
result = await bot.sendGuildMessage(parsed.id, body);
|
|
77
|
+
break;
|
|
78
|
+
case 'direct':
|
|
79
|
+
if (!bot.sendDirectMessage)
|
|
80
|
+
throw new Error('QQ direct message not supported by transport');
|
|
81
|
+
result = await bot.sendDirectMessage(parsed.id, body);
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
throw new Error(`unsupported QQ target kind: ${String(parsed.kind)}`);
|
|
85
|
+
}
|
|
86
|
+
const messageId = `${parsed.kind}-${parsed.id}:${resolveOutboundMessageId(result)}`;
|
|
87
|
+
logger.debug(formatCompact({
|
|
88
|
+
op: 'qq_send',
|
|
89
|
+
endpoint: this.#options.config.name,
|
|
90
|
+
target,
|
|
91
|
+
messageId,
|
|
92
|
+
}));
|
|
93
|
+
return messageId;
|
|
94
|
+
}
|
|
95
|
+
/** Test / internal: admit a message when open. */
|
|
96
|
+
admit(msg) {
|
|
97
|
+
if (!this.#open)
|
|
227
98
|
return;
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
99
|
+
const target = formatInboundTarget(msg);
|
|
100
|
+
void this.#options.gateway.receive({
|
|
101
|
+
adapter: this.#options.id,
|
|
102
|
+
target,
|
|
103
|
+
content: formatInboundContent(msg),
|
|
104
|
+
sender: senderDisplayName(msg),
|
|
105
|
+
id: msg.id,
|
|
106
|
+
metadata: Object.freeze({
|
|
107
|
+
endpoint: this.#options.config.name,
|
|
108
|
+
channelKind: msg.channelKind,
|
|
109
|
+
userId: msg.authorId,
|
|
110
|
+
guildId: msg.guildId,
|
|
111
|
+
roles: msg.authorRoles,
|
|
112
|
+
// AT 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
113
|
+
...(msg.mentioned ? { mentioned: true } : {}),
|
|
114
|
+
}),
|
|
115
|
+
}).catch((err) => {
|
|
116
|
+
logger.warn(formatCompact({
|
|
117
|
+
op: 'qq_gateway_receive_failed',
|
|
118
|
+
target,
|
|
119
|
+
error: err instanceof Error ? err.message : String(err),
|
|
237
120
|
}));
|
|
238
|
-
}
|
|
121
|
+
});
|
|
239
122
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
return fromCanonicalSegments(toCanonicalSegments(items));
|
|
123
|
+
getGuilds() {
|
|
124
|
+
return this.#requireBot().getGuilds();
|
|
243
125
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (Array.isArray(content)) {
|
|
271
|
-
content = enrichCanonicalMentionNames(toCanonicalSegments(content), this.adapter, raw.mentions);
|
|
272
|
-
}
|
|
273
|
-
const result = Message.from(msg, {
|
|
274
|
-
$id: msg.message_id?.toString(),
|
|
275
|
-
$adapter: "qq",
|
|
276
|
-
$endpoint: this.$config.name,
|
|
277
|
-
$sender: {
|
|
278
|
-
id: msg.sender.user_id?.toString(),
|
|
279
|
-
name: msg.sender.user_name?.toString(),
|
|
280
|
-
},
|
|
281
|
-
$channel: {
|
|
282
|
-
id: target_id,
|
|
283
|
-
type: msg.message_type === "guild" ? "channel" : msg.message_type,
|
|
284
|
-
},
|
|
285
|
-
$content: content,
|
|
286
|
-
$raw: msg.raw_message,
|
|
287
|
-
$timestamp: Date.now(),
|
|
288
|
-
$recall: async () => {
|
|
289
|
-
await this.$recallMessage(result.$id);
|
|
290
|
-
},
|
|
291
|
-
$reply: async (content, quote = true) => {
|
|
292
|
-
if (!Array.isArray(content))
|
|
293
|
-
content = [content];
|
|
294
|
-
if (quote)
|
|
295
|
-
content.unshift({ type: "reply", data: { id: typeof quote === "boolean" ? result.$id : quote } });
|
|
296
|
-
return await this.adapter.sendMessage({
|
|
297
|
-
...result.$channel,
|
|
298
|
-
context: "qq",
|
|
299
|
-
endpoint: this.$config.name,
|
|
300
|
-
content,
|
|
301
|
-
});
|
|
302
|
-
},
|
|
126
|
+
getChannels(guildId) {
|
|
127
|
+
return this.#requireBot().getChannels(guildId);
|
|
128
|
+
}
|
|
129
|
+
getChannelInfo(channelId) {
|
|
130
|
+
return this.#requireBot().getChannelInfo(channelId);
|
|
131
|
+
}
|
|
132
|
+
getGuildMember(guildId, userId) {
|
|
133
|
+
return this.#requireBot().getGuildMember(guildId, userId);
|
|
134
|
+
}
|
|
135
|
+
getGuildRoles(guildId) {
|
|
136
|
+
return this.#requireBot().getGuildRoles(guildId);
|
|
137
|
+
}
|
|
138
|
+
createGuildRole(guildId, name, color) {
|
|
139
|
+
return this.#requireBot().createGuildRole(guildId, name, color);
|
|
140
|
+
}
|
|
141
|
+
addMemberRole(guildId, channelId, userId, roleId) {
|
|
142
|
+
return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
|
|
143
|
+
}
|
|
144
|
+
removeMemberRole(guildId, channelId, userId, roleId) {
|
|
145
|
+
return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
|
|
146
|
+
}
|
|
147
|
+
#bindBot(bot) {
|
|
148
|
+
bindQqBotInboundEvents(bot, (raw) => {
|
|
149
|
+
const msg = normalizeQqMessage(raw);
|
|
150
|
+
if (msg)
|
|
151
|
+
this.admit(msg);
|
|
303
152
|
});
|
|
304
|
-
return result;
|
|
305
|
-
}
|
|
306
|
-
async $sendMessage(options) {
|
|
307
|
-
const expanded = expandKeyboardSegmentsForQq(options.content);
|
|
308
|
-
const canonicalized = this.toWireSendContent(expanded);
|
|
309
|
-
const mediaNorm = normalizeOutboundMedia(canonicalized);
|
|
310
|
-
const markdownMode = this.$config.outboundMarkdown;
|
|
311
|
-
if ((options.type === "group" || options.type === "private")
|
|
312
|
-
&& !options.id.startsWith("direct:")
|
|
313
|
-
&& shouldSendTextImageMixedMedia(mediaNorm, markdownMode)) {
|
|
314
|
-
return await this.$sendTextImageMixedMedia(options, mediaNorm);
|
|
315
|
-
}
|
|
316
|
-
const content = normalizeOutboundMarkdown(mediaNorm, markdownMode);
|
|
317
|
-
switch (options.type) {
|
|
318
|
-
case "private": {
|
|
319
|
-
if (options.id.startsWith("direct:")) {
|
|
320
|
-
const id = options.id.replace("direct:", "");
|
|
321
|
-
const result = await this.sendDirectMessage(id, content);
|
|
322
|
-
this.pluginLogger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
323
|
-
return `direct-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
const result = await this.sendPrivateMessage(options.id, content);
|
|
327
|
-
this.pluginLogger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
328
|
-
return `private-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
case "group": {
|
|
332
|
-
const result = await this.sendGroupMessage(options.id, content);
|
|
333
|
-
this.pluginLogger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
334
|
-
return `group-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
335
|
-
}
|
|
336
|
-
case "channel": {
|
|
337
|
-
const result = await this.sendGuildMessage(options.id, content);
|
|
338
|
-
this.pluginLogger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
339
|
-
return `channel-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
340
|
-
}
|
|
341
|
-
default:
|
|
342
|
-
throw new Error(`unsupported channel type ${options.type}`);
|
|
343
|
-
}
|
|
344
153
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const image = firstImageSegment(body);
|
|
350
|
-
if (!image) {
|
|
351
|
-
throw new Error("QQ 图文混排:未找到图片段");
|
|
352
|
-
}
|
|
353
|
-
const uploadPayload = buildQqImageUploadPayload(image);
|
|
354
|
-
const { data: uploadResult } = await this.request.post(`${endpointPath}/files`, { ...uploadPayload, srv_send_msg: false });
|
|
355
|
-
if (!uploadResult?.file_info) {
|
|
356
|
-
throw new Error("QQ 图文混排:图片上传未返回 file_info");
|
|
357
|
-
}
|
|
358
|
-
const messagePayload = buildMixedMediaMessagePayload(content, uploadResult.file_info);
|
|
359
|
-
const { data: result } = await this.request.post(`${endpointPath}/messages`, messagePayload);
|
|
360
|
-
this.pluginLogger.debug(`${this.$config.name} send mixed-media ${options.type}(${options.id}):${segment.raw(content)}`);
|
|
361
|
-
const idPrefix = options.type === "group" ? "group" : "private";
|
|
362
|
-
return `${idPrefix}-${options.id}:${resolveOutboundMessageId(result)}`;
|
|
363
|
-
}
|
|
364
|
-
resolveV2EndpointPath(options) {
|
|
365
|
-
switch (options.type) {
|
|
366
|
-
case "group":
|
|
367
|
-
return `/v2/groups/${options.id}`;
|
|
368
|
-
case "private":
|
|
369
|
-
return `/v2/users/${options.id}`;
|
|
370
|
-
default:
|
|
371
|
-
throw new Error(`QQ 图文混排不支持 ${options.type}`);
|
|
372
|
-
}
|
|
154
|
+
#requireBot() {
|
|
155
|
+
if (!this.#bot)
|
|
156
|
+
throw new Error('QQ bot not connected');
|
|
157
|
+
return this.#bot;
|
|
373
158
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
159
|
+
}
|
|
160
|
+
/** Webhook / middleware inbound via httpHostToken POST (qq-official-bot Middleware receiver). */
|
|
161
|
+
export class QqHttpEndpoint {
|
|
162
|
+
#options;
|
|
163
|
+
#createBot;
|
|
164
|
+
#bot = null;
|
|
165
|
+
#routeReleases = [];
|
|
166
|
+
#open = false;
|
|
167
|
+
#started = false;
|
|
168
|
+
#unregisterAgent;
|
|
169
|
+
constructor(options) {
|
|
170
|
+
this.#options = options;
|
|
171
|
+
this.#createBot = options.createBot ?? defaultCreateHttpBot;
|
|
172
|
+
}
|
|
173
|
+
async start() {
|
|
174
|
+
if (this.#started)
|
|
379
175
|
return;
|
|
380
|
-
|
|
381
|
-
if (target_type === "private")
|
|
382
|
-
await this.recallPrivateMessage(target_id, message_id);
|
|
383
|
-
if (target_type === "group")
|
|
384
|
-
await this.recallGroupMessage(target_id, message_id);
|
|
385
|
-
if (target_type === "channel")
|
|
386
|
-
await this.recallGuildMessage(target_id, message_id);
|
|
387
|
-
if (target_type === "direct")
|
|
388
|
-
await this.recallDirectMessage(target_id, message_id);
|
|
389
|
-
}
|
|
390
|
-
// ==================== 频道管理 API ====================
|
|
391
|
-
/**
|
|
392
|
-
* 获取频道列表
|
|
393
|
-
*/
|
|
394
|
-
async getGuilds() {
|
|
176
|
+
this.#started = true;
|
|
395
177
|
try {
|
|
396
|
-
|
|
178
|
+
this.#unregisterAgent = registerQqAgentEndpoint(this.#options.config.name, this);
|
|
179
|
+
this.#setupRoutes();
|
|
180
|
+
this.#bot = this.#createBot(this.#options.config);
|
|
181
|
+
this.#bindBot(this.#bot);
|
|
182
|
+
await this.#bot.start();
|
|
183
|
+
logger.info(formatCompact({
|
|
184
|
+
op: 'connect',
|
|
185
|
+
endpoint: this.#options.config.name,
|
|
186
|
+
mode: this.#options.config.mode,
|
|
187
|
+
path: this.#options.config.webhookPath,
|
|
188
|
+
}));
|
|
397
189
|
}
|
|
398
190
|
catch (error) {
|
|
399
|
-
this.
|
|
191
|
+
await this.stop();
|
|
192
|
+
logger.error('Failed to connect QQ HTTP receiver:', error);
|
|
400
193
|
throw error;
|
|
401
194
|
}
|
|
402
195
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
* @param guildId 频道 ID
|
|
406
|
-
*/
|
|
407
|
-
async getGuildInfo(guildId) {
|
|
408
|
-
try {
|
|
409
|
-
return await this.guildService.getInfo(guildId);
|
|
410
|
-
}
|
|
411
|
-
catch (error) {
|
|
412
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取频道详情失败:`, error);
|
|
413
|
-
throw error;
|
|
414
|
-
}
|
|
196
|
+
open() {
|
|
197
|
+
this.#open = true;
|
|
415
198
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
* @param guildId 频道 ID
|
|
419
|
-
*/
|
|
420
|
-
async getChannels(guildId) {
|
|
421
|
-
try {
|
|
422
|
-
return await this.channelService.getList(guildId);
|
|
423
|
-
}
|
|
424
|
-
catch (error) {
|
|
425
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取子频道列表失败:`, error);
|
|
426
|
-
throw error;
|
|
427
|
-
}
|
|
199
|
+
close() {
|
|
200
|
+
this.#open = false;
|
|
428
201
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
202
|
+
async stop() {
|
|
203
|
+
this.#open = false;
|
|
204
|
+
for (const release of this.#routeReleases.splice(0))
|
|
205
|
+
release();
|
|
206
|
+
this.#unregisterAgent?.();
|
|
207
|
+
this.#unregisterAgent = undefined;
|
|
208
|
+
if (this.#bot) {
|
|
209
|
+
try {
|
|
210
|
+
this.#bot.removeAllListeners();
|
|
211
|
+
await this.#bot.stop();
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
/* ignore */
|
|
215
|
+
}
|
|
216
|
+
this.#bot = null;
|
|
217
|
+
}
|
|
218
|
+
this.#started = false;
|
|
219
|
+
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
220
|
+
}
|
|
221
|
+
async send({ target, payload }) {
|
|
222
|
+
const body = formatOutboundText(payload);
|
|
223
|
+
const parsed = parseSendTarget(target);
|
|
224
|
+
const bot = this.#requireBot();
|
|
225
|
+
let result;
|
|
226
|
+
switch (parsed.kind) {
|
|
227
|
+
case 'private':
|
|
228
|
+
result = await bot.sendPrivateMessage(parsed.id, body);
|
|
229
|
+
break;
|
|
230
|
+
case 'group':
|
|
231
|
+
result = await bot.sendGroupMessage(parsed.id, body);
|
|
232
|
+
break;
|
|
233
|
+
case 'channel':
|
|
234
|
+
result = await bot.sendGuildMessage(parsed.id, body);
|
|
235
|
+
break;
|
|
236
|
+
case 'direct':
|
|
237
|
+
if (!bot.sendDirectMessage)
|
|
238
|
+
throw new Error('QQ direct message not supported by transport');
|
|
239
|
+
result = await bot.sendDirectMessage(parsed.id, body);
|
|
240
|
+
break;
|
|
241
|
+
default:
|
|
242
|
+
throw new Error(`unsupported QQ target kind: ${String(parsed.kind)}`);
|
|
440
243
|
}
|
|
244
|
+
return `${parsed.kind}-${parsed.id}:${resolveOutboundMessageId(result)}`;
|
|
441
245
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
246
|
+
admit(msg) {
|
|
247
|
+
if (!this.#open)
|
|
248
|
+
return;
|
|
249
|
+
const target = formatInboundTarget(msg);
|
|
250
|
+
void this.#options.gateway.receive({
|
|
251
|
+
adapter: this.#options.id,
|
|
252
|
+
target,
|
|
253
|
+
content: formatInboundContent(msg),
|
|
254
|
+
sender: senderDisplayName(msg),
|
|
255
|
+
id: msg.id,
|
|
256
|
+
metadata: Object.freeze({
|
|
257
|
+
endpoint: this.#options.config.name,
|
|
258
|
+
channelKind: msg.channelKind,
|
|
259
|
+
userId: msg.authorId,
|
|
260
|
+
guildId: msg.guildId,
|
|
261
|
+
roles: msg.authorRoles,
|
|
262
|
+
// AT 事件本身即 @ 机器人;新 Runtime 纯文本 content 需经 metadata 传递
|
|
263
|
+
...(msg.mentioned ? { mentioned: true } : {}),
|
|
264
|
+
}),
|
|
265
|
+
}).catch((err) => {
|
|
266
|
+
logger.warn(formatCompact({
|
|
267
|
+
op: 'qq_gateway_receive_failed',
|
|
268
|
+
target,
|
|
269
|
+
error: err instanceof Error ? err.message : String(err),
|
|
270
|
+
}));
|
|
271
|
+
});
|
|
454
272
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
* @param guildId 频道 ID
|
|
458
|
-
* @param userId 用户 ID
|
|
459
|
-
*/
|
|
460
|
-
async getGuildMember(guildId, userId) {
|
|
461
|
-
try {
|
|
462
|
-
return await this.memberService.getGuildMemberInfo(guildId, userId);
|
|
463
|
-
}
|
|
464
|
-
catch (error) {
|
|
465
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取成员详情失败:`, error);
|
|
466
|
-
throw error;
|
|
467
|
-
}
|
|
273
|
+
getGuilds() {
|
|
274
|
+
return this.#requireBot().getGuilds();
|
|
468
275
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
* @param guildId 频道 ID
|
|
472
|
-
* @param userId 用户 ID
|
|
473
|
-
* @param addBlacklist 是否加入黑名单
|
|
474
|
-
* @param deleteHistoryMsg 删除历史消息天数 (-1不删除,0全部删除,3/7/15/30)
|
|
475
|
-
*/
|
|
476
|
-
async removeGuildMember(guildId, userId, addBlacklist, deleteHistoryMsg) {
|
|
477
|
-
try {
|
|
478
|
-
await this.memberService.kickMember(guildId, userId, deleteHistoryMsg, addBlacklist);
|
|
479
|
-
this.pluginLogger.info(`QQ Endpoint ${this.$id} 踢出成员 ${userId}(频道 ${guildId})`);
|
|
480
|
-
return true;
|
|
481
|
-
}
|
|
482
|
-
catch (error) {
|
|
483
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 踢出成员失败:`, error);
|
|
484
|
-
throw error;
|
|
485
|
-
}
|
|
276
|
+
getChannels(guildId) {
|
|
277
|
+
return this.#requireBot().getChannels(guildId);
|
|
486
278
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
* @param guildId 频道 ID
|
|
490
|
-
*/
|
|
491
|
-
async getGuildRoles(guildId) {
|
|
492
|
-
try {
|
|
493
|
-
return await this.guildService.getRoles(guildId);
|
|
494
|
-
}
|
|
495
|
-
catch (error) {
|
|
496
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 获取角色列表失败:`, error);
|
|
497
|
-
throw error;
|
|
498
|
-
}
|
|
279
|
+
getChannelInfo(channelId) {
|
|
280
|
+
return this.#requireBot().getChannelInfo(channelId);
|
|
499
281
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
* @param guildId 频道 ID
|
|
503
|
-
* @param name 角色名
|
|
504
|
-
* @param color 颜色
|
|
505
|
-
* @param hoist 是否在成员列表中单独展示 (0 或 1)
|
|
506
|
-
*/
|
|
507
|
-
async createGuildRole(guildId, name, color, hoist) {
|
|
508
|
-
try {
|
|
509
|
-
const result = await this.guildService.createRole(guildId, { name, color: color || 0, hoist: hoist ?? 0 });
|
|
510
|
-
this.pluginLogger.info(`QQ Endpoint ${this.$id} 创建角色 "${name}"(频道 ${guildId})`);
|
|
511
|
-
return result;
|
|
512
|
-
}
|
|
513
|
-
catch (error) {
|
|
514
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 创建角色失败:`, error);
|
|
515
|
-
throw error;
|
|
516
|
-
}
|
|
282
|
+
getGuildMember(guildId, userId) {
|
|
283
|
+
return this.#requireBot().getGuildMember(guildId, userId);
|
|
517
284
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
* @param guildId 频道 ID
|
|
521
|
-
* @param channelId 子频道 ID
|
|
522
|
-
* @param userId 用户 ID
|
|
523
|
-
* @param roleId 角色 ID
|
|
524
|
-
*/
|
|
525
|
-
async addMemberRole(guildId, channelId, userId, roleId) {
|
|
526
|
-
try {
|
|
527
|
-
await this.memberService.addMemberRole(guildId, channelId, userId, roleId);
|
|
528
|
-
this.pluginLogger.info(`QQ Endpoint ${this.$id} 给成员 ${userId} 添加角色 ${roleId}(频道 ${guildId})`);
|
|
529
|
-
return true;
|
|
530
|
-
}
|
|
531
|
-
catch (error) {
|
|
532
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 添加角色失败:`, error);
|
|
533
|
-
throw error;
|
|
534
|
-
}
|
|
285
|
+
getGuildRoles(guildId) {
|
|
286
|
+
return this.#requireBot().getGuildRoles(guildId);
|
|
535
287
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
* @param guildId 频道 ID
|
|
539
|
-
* @param channelId 子频道 ID
|
|
540
|
-
* @param userId 用户 ID
|
|
541
|
-
* @param roleId 角色 ID
|
|
542
|
-
*/
|
|
543
|
-
async removeMemberRole(guildId, channelId, userId, roleId) {
|
|
544
|
-
try {
|
|
545
|
-
await this.memberService.removeMemberRole(guildId, channelId, userId, roleId);
|
|
546
|
-
this.pluginLogger.info(`QQ Endpoint ${this.$id} 移除成员 ${userId} 的角色 ${roleId}(频道 ${guildId})`);
|
|
547
|
-
return true;
|
|
548
|
-
}
|
|
549
|
-
catch (error) {
|
|
550
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 移除角色失败:`, error);
|
|
551
|
-
throw error;
|
|
552
|
-
}
|
|
288
|
+
createGuildRole(guildId, name, color) {
|
|
289
|
+
return this.#requireBot().createGuildRole(guildId, name, color);
|
|
553
290
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
* @param guildId 频道 ID
|
|
557
|
-
* @param userIds 用户 ID 列表
|
|
558
|
-
* @param muteSeconds 禁言时长(秒),0 表示解除禁言
|
|
559
|
-
*/
|
|
560
|
-
async muteMembers(guildId, userIds, muteSeconds) {
|
|
561
|
-
try {
|
|
562
|
-
if (muteSeconds > 0) {
|
|
563
|
-
await this.memberService.muteMembers(guildId, userIds, muteSeconds);
|
|
564
|
-
this.pluginLogger.info(`QQ Endpoint ${this.$id} 禁言成员 ${userIds.join(',')} ${muteSeconds}秒(频道 ${guildId})`);
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
await this.memberService.unmuteMembers(guildId, userIds);
|
|
568
|
-
this.pluginLogger.info(`QQ Endpoint ${this.$id} 解除成员 ${userIds.join(',')} 禁言(频道 ${guildId})`);
|
|
569
|
-
}
|
|
570
|
-
return true;
|
|
571
|
-
}
|
|
572
|
-
catch (error) {
|
|
573
|
-
this.pluginLogger.error(`QQ Endpoint ${this.$id} 禁言操作失败:`, error);
|
|
574
|
-
throw error;
|
|
575
|
-
}
|
|
291
|
+
addMemberRole(guildId, channelId, userId, roleId) {
|
|
292
|
+
return this.#requireBot().addMemberRole(guildId, channelId, userId, roleId);
|
|
576
293
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
this.
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
}
|
|
294
|
+
removeMemberRole(guildId, channelId, userId, roleId) {
|
|
295
|
+
return this.#requireBot().removeMemberRole(guildId, channelId, userId, roleId);
|
|
296
|
+
}
|
|
297
|
+
#setupRoutes() {
|
|
298
|
+
this.#routeReleases.push(...registerQqWebhookRoutes(this.#options.http, {
|
|
299
|
+
config: this.#options.config,
|
|
300
|
+
getBot: () => this.#bot,
|
|
301
|
+
}));
|
|
302
|
+
}
|
|
303
|
+
#bindBot(bot) {
|
|
304
|
+
bindQqBotInboundEvents(bot, (raw) => {
|
|
305
|
+
const msg = normalizeQqMessage(raw);
|
|
306
|
+
if (msg)
|
|
307
|
+
this.admit(msg);
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
#requireBot() {
|
|
311
|
+
if (!this.#bot)
|
|
312
|
+
throw new Error('QQ bot not connected');
|
|
313
|
+
return this.#bot;
|
|
598
314
|
}
|
|
599
315
|
}
|
|
600
|
-
//# sourceMappingURL=endpoint.js.map
|