@zhin.js/adapter-discord 1.0.7 → 1.0.8
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 +9 -0
- package/lib/index.d.ts +12 -14
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +211 -199
- package/lib/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1041 -952
package/lib/index.js
CHANGED
|
@@ -1,25 +1,24 @@
|
|
|
1
|
-
import { Client, GatewayIntentBits, EmbedBuilder, AttachmentBuilder, ChannelType, REST, Routes, InteractionType, InteractionResponseType } from
|
|
2
|
-
import { Adapter, registerAdapter, Message, segment, useContext } from "zhin.js";
|
|
3
|
-
import { createReadStream } from
|
|
4
|
-
import { promises as fs } from
|
|
5
|
-
import path from
|
|
1
|
+
import { Client, GatewayIntentBits, EmbedBuilder, AttachmentBuilder, ChannelType, REST, Routes, InteractionType, InteractionResponseType, } from "discord.js";
|
|
2
|
+
import { Adapter, registerAdapter, Message, segment, useContext, usePlugin, } from "zhin.js";
|
|
3
|
+
import { createReadStream } from "fs";
|
|
4
|
+
import { promises as fs } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
const plugin = usePlugin();
|
|
6
7
|
// 主要的 DiscordBot 类
|
|
7
8
|
export class DiscordBot extends Client {
|
|
8
|
-
plugin;
|
|
9
9
|
$config;
|
|
10
10
|
$connected;
|
|
11
11
|
slashCommandHandlers = new Map();
|
|
12
|
-
constructor(
|
|
12
|
+
constructor($config) {
|
|
13
13
|
const intents = $config.intents || [
|
|
14
14
|
GatewayIntentBits.Guilds,
|
|
15
15
|
GatewayIntentBits.GuildMessages,
|
|
16
16
|
GatewayIntentBits.MessageContent,
|
|
17
17
|
GatewayIntentBits.DirectMessages,
|
|
18
18
|
GatewayIntentBits.GuildMembers,
|
|
19
|
-
GatewayIntentBits.GuildMessageReactions
|
|
19
|
+
GatewayIntentBits.GuildMessageReactions,
|
|
20
20
|
];
|
|
21
21
|
super({ intents });
|
|
22
|
-
this.plugin = plugin;
|
|
23
22
|
this.$config = $config;
|
|
24
23
|
this.$connected = false;
|
|
25
24
|
}
|
|
@@ -28,9 +27,9 @@ export class DiscordBot extends Client {
|
|
|
28
27
|
if (msg.author.bot)
|
|
29
28
|
return;
|
|
30
29
|
const message = this.$formatMessage(msg);
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
plugin.dispatch("message.receive", message);
|
|
31
|
+
plugin.logger.info(`recv ${message.$channel.type}(${message.$channel.id}): ${segment.raw(message.$content)}`);
|
|
32
|
+
plugin.dispatch(`message.${message.$channel.type}.receive`, message);
|
|
34
33
|
}
|
|
35
34
|
async handleSlashCommand(interaction) {
|
|
36
35
|
const commandName = interaction.commandName;
|
|
@@ -38,13 +37,16 @@ export class DiscordBot extends Client {
|
|
|
38
37
|
if (handler) {
|
|
39
38
|
try {
|
|
40
39
|
await handler(interaction);
|
|
41
|
-
|
|
40
|
+
plugin.logger.info(`Executed slash command: /${commandName} by ${interaction.user.tag}`);
|
|
42
41
|
}
|
|
43
42
|
catch (error) {
|
|
44
|
-
|
|
45
|
-
const errorMessage =
|
|
43
|
+
plugin.logger.error(`Error executing slash command /${commandName}:`, error);
|
|
44
|
+
const errorMessage = "An error occurred while executing this command.";
|
|
46
45
|
if (interaction.replied || interaction.deferred) {
|
|
47
|
-
await interaction.followUp({
|
|
46
|
+
await interaction.followUp({
|
|
47
|
+
content: errorMessage,
|
|
48
|
+
ephemeral: true,
|
|
49
|
+
});
|
|
48
50
|
}
|
|
49
51
|
else {
|
|
50
52
|
await interaction.reply({ content: errorMessage, ephemeral: true });
|
|
@@ -52,11 +54,11 @@ export class DiscordBot extends Client {
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
else {
|
|
55
|
-
|
|
57
|
+
plugin.logger.warn(`Unknown slash command: /${commandName}`);
|
|
56
58
|
if (!interaction.replied) {
|
|
57
59
|
await interaction.reply({
|
|
58
|
-
content:
|
|
59
|
-
ephemeral: true
|
|
60
|
+
content: "Unknown command.",
|
|
61
|
+
ephemeral: true,
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
64
|
}
|
|
@@ -64,24 +66,24 @@ export class DiscordBot extends Client {
|
|
|
64
66
|
async $connect() {
|
|
65
67
|
return new Promise((resolve, reject) => {
|
|
66
68
|
// 监听消息事件
|
|
67
|
-
this.on(
|
|
69
|
+
this.on("messageCreate", this.handleDiscordMessage.bind(this));
|
|
68
70
|
// 监听交互事件(Slash Commands)
|
|
69
71
|
if (this.$config.enableSlashCommands) {
|
|
70
|
-
this.on(
|
|
72
|
+
this.on("interactionCreate", async (interaction) => {
|
|
71
73
|
if (interaction.isChatInputCommand()) {
|
|
72
74
|
await this.handleSlashCommand(interaction);
|
|
73
75
|
}
|
|
74
76
|
});
|
|
75
77
|
}
|
|
76
78
|
// 监听就绪事件
|
|
77
|
-
this.once(
|
|
79
|
+
this.once("clientReady", async () => {
|
|
78
80
|
this.$connected = true;
|
|
79
|
-
|
|
81
|
+
plugin.logger.info(`Discord bot ${this.$config.name} connected successfully as ${this.user?.tag}`);
|
|
80
82
|
// 设置活动状态
|
|
81
83
|
if (this.$config.defaultActivity) {
|
|
82
84
|
this.user?.setActivity(this.$config.defaultActivity.name, {
|
|
83
85
|
type: this.getActivityType(this.$config.defaultActivity.type),
|
|
84
|
-
url: this.$config.defaultActivity.url
|
|
86
|
+
url: this.$config.defaultActivity.url,
|
|
85
87
|
});
|
|
86
88
|
}
|
|
87
89
|
// 注册 Slash Commands
|
|
@@ -91,14 +93,14 @@ export class DiscordBot extends Client {
|
|
|
91
93
|
resolve();
|
|
92
94
|
});
|
|
93
95
|
// 监听错误事件
|
|
94
|
-
this.on(
|
|
95
|
-
|
|
96
|
+
this.on("error", (error) => {
|
|
97
|
+
plugin.logger.error("Discord client error:", error);
|
|
96
98
|
this.$connected = false;
|
|
97
99
|
reject(error);
|
|
98
100
|
});
|
|
99
101
|
// 登录
|
|
100
102
|
this.login(this.$config.token).catch((error) => {
|
|
101
|
-
|
|
103
|
+
plugin.logger.error("Failed to login to Discord:", error);
|
|
102
104
|
this.$connected = false;
|
|
103
105
|
reject(error);
|
|
104
106
|
});
|
|
@@ -108,10 +110,10 @@ export class DiscordBot extends Client {
|
|
|
108
110
|
try {
|
|
109
111
|
await this.destroy();
|
|
110
112
|
this.$connected = false;
|
|
111
|
-
|
|
113
|
+
plugin.logger.info(`Discord bot ${this.$config.name} disconnected`);
|
|
112
114
|
}
|
|
113
115
|
catch (error) {
|
|
114
|
-
|
|
116
|
+
plugin.logger.error("Error disconnecting Discord bot:", error);
|
|
115
117
|
throw error;
|
|
116
118
|
}
|
|
117
119
|
}
|
|
@@ -120,30 +122,30 @@ export class DiscordBot extends Client {
|
|
|
120
122
|
let channelType;
|
|
121
123
|
let channelId;
|
|
122
124
|
if (msg.channel.type === ChannelType.DM) {
|
|
123
|
-
channelType =
|
|
125
|
+
channelType = "private";
|
|
124
126
|
channelId = msg.channel.id;
|
|
125
127
|
}
|
|
126
128
|
else if (msg.channel.type === ChannelType.GroupDM) {
|
|
127
|
-
channelType =
|
|
129
|
+
channelType = "group";
|
|
128
130
|
channelId = msg.channel.id;
|
|
129
131
|
}
|
|
130
132
|
else {
|
|
131
|
-
channelType =
|
|
133
|
+
channelType = "channel";
|
|
132
134
|
channelId = msg.channel.id;
|
|
133
135
|
}
|
|
134
136
|
// 转换消息内容为 segment 格式
|
|
135
137
|
const content = this.parseMessageContent(msg);
|
|
136
138
|
const result = Message.from(msg, {
|
|
137
139
|
$id: msg.id,
|
|
138
|
-
$adapter:
|
|
140
|
+
$adapter: "discord",
|
|
139
141
|
$bot: this.$config.name,
|
|
140
142
|
$sender: {
|
|
141
143
|
id: msg.author.id,
|
|
142
|
-
name: msg.member?.displayName || msg.author.displayName
|
|
144
|
+
name: msg.member?.displayName || msg.author.displayName,
|
|
143
145
|
},
|
|
144
146
|
$channel: {
|
|
145
147
|
id: channelId,
|
|
146
|
-
type: channelType
|
|
148
|
+
type: channelType,
|
|
147
149
|
},
|
|
148
150
|
$content: content,
|
|
149
151
|
$raw: msg.content,
|
|
@@ -160,12 +162,12 @@ export class DiscordBot extends Client {
|
|
|
160
162
|
sendOptions.reply = { messageReference: replyMessage };
|
|
161
163
|
}
|
|
162
164
|
catch (error) {
|
|
163
|
-
|
|
165
|
+
plugin.logger.warn(`Could not find message to reply to: ${replyId}`);
|
|
164
166
|
}
|
|
165
167
|
}
|
|
166
168
|
const res = await this.sendContentToChannel(msg.channel, content, sendOptions);
|
|
167
169
|
return res.id;
|
|
168
|
-
}
|
|
170
|
+
},
|
|
169
171
|
});
|
|
170
172
|
return result;
|
|
171
173
|
}
|
|
@@ -175,12 +177,12 @@ export class DiscordBot extends Client {
|
|
|
175
177
|
// 回复消息处理
|
|
176
178
|
if (msg.reference) {
|
|
177
179
|
segments.push({
|
|
178
|
-
type:
|
|
180
|
+
type: "reply",
|
|
179
181
|
data: {
|
|
180
182
|
id: msg.reference.messageId,
|
|
181
183
|
channel_id: msg.reference.channelId,
|
|
182
|
-
guild_id: msg.reference.guildId
|
|
183
|
-
}
|
|
184
|
+
guild_id: msg.reference.guildId,
|
|
185
|
+
},
|
|
184
186
|
});
|
|
185
187
|
}
|
|
186
188
|
// 文本消息(包含提及、表情等)
|
|
@@ -194,7 +196,7 @@ export class DiscordBot extends Client {
|
|
|
194
196
|
// Embed 消息
|
|
195
197
|
for (const embed of msg.embeds) {
|
|
196
198
|
segments.push({
|
|
197
|
-
type:
|
|
199
|
+
type: "embed",
|
|
198
200
|
data: {
|
|
199
201
|
title: embed.title,
|
|
200
202
|
description: embed.description,
|
|
@@ -205,24 +207,26 @@ export class DiscordBot extends Client {
|
|
|
205
207
|
author: embed.author,
|
|
206
208
|
footer: embed.footer,
|
|
207
209
|
fields: embed.fields,
|
|
208
|
-
timestamp: embed.timestamp
|
|
209
|
-
}
|
|
210
|
+
timestamp: embed.timestamp,
|
|
211
|
+
},
|
|
210
212
|
});
|
|
211
213
|
}
|
|
212
214
|
// 贴纸消息
|
|
213
215
|
for (const sticker of msg.stickers.values()) {
|
|
214
216
|
segments.push({
|
|
215
|
-
type:
|
|
217
|
+
type: "sticker",
|
|
216
218
|
data: {
|
|
217
219
|
id: sticker.id,
|
|
218
220
|
name: sticker.name,
|
|
219
221
|
url: sticker.url,
|
|
220
222
|
format: sticker.format,
|
|
221
|
-
tags: sticker.tags
|
|
222
|
-
}
|
|
223
|
+
tags: sticker.tags,
|
|
224
|
+
},
|
|
223
225
|
});
|
|
224
226
|
}
|
|
225
|
-
return segments.length > 0
|
|
227
|
+
return segments.length > 0
|
|
228
|
+
? segments
|
|
229
|
+
: [{ type: "text", data: { text: "" } }];
|
|
226
230
|
}
|
|
227
231
|
// 解析文本内容,处理提及、频道引用、角色引用等
|
|
228
232
|
parseTextContent(content, msg) {
|
|
@@ -230,7 +234,7 @@ export class DiscordBot extends Client {
|
|
|
230
234
|
let lastIndex = 0;
|
|
231
235
|
// 匹配用户提及 <@!?用户ID>
|
|
232
236
|
const userMentionRegex = /<@!?(\d+)>/g;
|
|
233
|
-
// 匹配频道提及 <#频道ID>
|
|
237
|
+
// 匹配频道提及 <#频道ID>
|
|
234
238
|
const channelMentionRegex = /<#(\d+)>/g;
|
|
235
239
|
// 匹配角色提及 <@&角色ID>
|
|
236
240
|
const roleMentionRegex = /<@&(\d+)>/g;
|
|
@@ -240,16 +244,16 @@ export class DiscordBot extends Client {
|
|
|
240
244
|
// 收集所有匹配项
|
|
241
245
|
let match;
|
|
242
246
|
while ((match = userMentionRegex.exec(content)) !== null) {
|
|
243
|
-
allMatches.push({ match, type:
|
|
247
|
+
allMatches.push({ match, type: "user" });
|
|
244
248
|
}
|
|
245
249
|
while ((match = channelMentionRegex.exec(content)) !== null) {
|
|
246
|
-
allMatches.push({ match, type:
|
|
250
|
+
allMatches.push({ match, type: "channel" });
|
|
247
251
|
}
|
|
248
252
|
while ((match = roleMentionRegex.exec(content)) !== null) {
|
|
249
|
-
allMatches.push({ match, type:
|
|
253
|
+
allMatches.push({ match, type: "role" });
|
|
250
254
|
}
|
|
251
255
|
while ((match = emojiRegex.exec(content)) !== null) {
|
|
252
|
-
allMatches.push({ match, type:
|
|
256
|
+
allMatches.push({ match, type: "emoji" });
|
|
253
257
|
}
|
|
254
258
|
// 按位置排序
|
|
255
259
|
allMatches.sort((a, b) => a.match.index - b.match.index);
|
|
@@ -261,60 +265,60 @@ export class DiscordBot extends Client {
|
|
|
261
265
|
if (matchStart > lastIndex) {
|
|
262
266
|
const beforeText = content.slice(lastIndex, matchStart);
|
|
263
267
|
if (beforeText.trim()) {
|
|
264
|
-
segments.push({ type:
|
|
268
|
+
segments.push({ type: "text", data: { text: beforeText } });
|
|
265
269
|
}
|
|
266
270
|
}
|
|
267
271
|
// 添加特殊内容段
|
|
268
272
|
switch (type) {
|
|
269
|
-
case
|
|
273
|
+
case "user":
|
|
270
274
|
const userId = match[1];
|
|
271
275
|
const user = msg.mentions.users.get(userId);
|
|
272
276
|
segments.push({
|
|
273
|
-
type:
|
|
277
|
+
type: "at",
|
|
274
278
|
data: {
|
|
275
279
|
id: userId,
|
|
276
|
-
name: user?.username ||
|
|
277
|
-
text: match[0]
|
|
278
|
-
}
|
|
280
|
+
name: user?.username || "Unknown",
|
|
281
|
+
text: match[0],
|
|
282
|
+
},
|
|
279
283
|
});
|
|
280
284
|
break;
|
|
281
|
-
case
|
|
285
|
+
case "channel":
|
|
282
286
|
const channelId = match[1];
|
|
283
287
|
const channel = msg.mentions.channels.get(channelId);
|
|
284
288
|
segments.push({
|
|
285
|
-
type:
|
|
289
|
+
type: "channel_mention",
|
|
286
290
|
data: {
|
|
287
291
|
id: channelId,
|
|
288
|
-
name: channel?.name ||
|
|
289
|
-
text: match[0]
|
|
290
|
-
}
|
|
292
|
+
name: channel?.name || "unknown-channel",
|
|
293
|
+
text: match[0],
|
|
294
|
+
},
|
|
291
295
|
});
|
|
292
296
|
break;
|
|
293
|
-
case
|
|
297
|
+
case "role":
|
|
294
298
|
const roleId = match[1];
|
|
295
299
|
const role = msg.mentions.roles.get(roleId);
|
|
296
300
|
segments.push({
|
|
297
|
-
type:
|
|
301
|
+
type: "role_mention",
|
|
298
302
|
data: {
|
|
299
303
|
id: roleId,
|
|
300
|
-
name: role?.name ||
|
|
301
|
-
text: match[0]
|
|
302
|
-
}
|
|
304
|
+
name: role?.name || "unknown-role",
|
|
305
|
+
text: match[0],
|
|
306
|
+
},
|
|
303
307
|
});
|
|
304
308
|
break;
|
|
305
|
-
case
|
|
309
|
+
case "emoji":
|
|
306
310
|
const emojiName = match[1];
|
|
307
311
|
const emojiId = match[2];
|
|
308
|
-
const isAnimated = match[0].startsWith(
|
|
312
|
+
const isAnimated = match[0].startsWith("<a:");
|
|
309
313
|
segments.push({
|
|
310
|
-
type:
|
|
314
|
+
type: "emoji",
|
|
311
315
|
data: {
|
|
312
316
|
id: emojiId,
|
|
313
317
|
name: emojiName,
|
|
314
318
|
animated: isAnimated,
|
|
315
|
-
url: `https://cdn.discordapp.com/emojis/${emojiId}.${isAnimated ?
|
|
316
|
-
text: match[0]
|
|
317
|
-
}
|
|
319
|
+
url: `https://cdn.discordapp.com/emojis/${emojiId}.${isAnimated ? "gif" : "png"}`,
|
|
320
|
+
text: match[0],
|
|
321
|
+
},
|
|
318
322
|
});
|
|
319
323
|
break;
|
|
320
324
|
}
|
|
@@ -324,17 +328,19 @@ export class DiscordBot extends Client {
|
|
|
324
328
|
if (lastIndex < content.length) {
|
|
325
329
|
const remainingText = content.slice(lastIndex);
|
|
326
330
|
if (remainingText.trim()) {
|
|
327
|
-
segments.push({ type:
|
|
331
|
+
segments.push({ type: "text", data: { text: remainingText } });
|
|
328
332
|
}
|
|
329
333
|
}
|
|
330
|
-
return segments.length > 0
|
|
334
|
+
return segments.length > 0
|
|
335
|
+
? segments
|
|
336
|
+
: [{ type: "text", data: { text: content } }];
|
|
331
337
|
}
|
|
332
338
|
// 解析附件
|
|
333
339
|
parseAttachment(attachment) {
|
|
334
340
|
const segments = [];
|
|
335
|
-
if (attachment.contentType?.startsWith(
|
|
341
|
+
if (attachment.contentType?.startsWith("image/")) {
|
|
336
342
|
segments.push({
|
|
337
|
-
type:
|
|
343
|
+
type: "image",
|
|
338
344
|
data: {
|
|
339
345
|
id: attachment.id,
|
|
340
346
|
name: attachment.name,
|
|
@@ -343,26 +349,26 @@ export class DiscordBot extends Client {
|
|
|
343
349
|
size: attachment.size,
|
|
344
350
|
width: attachment.width,
|
|
345
351
|
height: attachment.height,
|
|
346
|
-
content_type: attachment.contentType
|
|
347
|
-
}
|
|
352
|
+
content_type: attachment.contentType,
|
|
353
|
+
},
|
|
348
354
|
});
|
|
349
355
|
}
|
|
350
|
-
else if (attachment.contentType?.startsWith(
|
|
356
|
+
else if (attachment.contentType?.startsWith("audio/")) {
|
|
351
357
|
segments.push({
|
|
352
|
-
type:
|
|
358
|
+
type: "audio",
|
|
353
359
|
data: {
|
|
354
360
|
id: attachment.id,
|
|
355
361
|
name: attachment.name,
|
|
356
362
|
url: attachment.url,
|
|
357
363
|
proxy_url: attachment.proxyURL,
|
|
358
364
|
size: attachment.size,
|
|
359
|
-
content_type: attachment.contentType
|
|
360
|
-
}
|
|
365
|
+
content_type: attachment.contentType,
|
|
366
|
+
},
|
|
361
367
|
});
|
|
362
368
|
}
|
|
363
|
-
else if (attachment.contentType?.startsWith(
|
|
369
|
+
else if (attachment.contentType?.startsWith("video/")) {
|
|
364
370
|
segments.push({
|
|
365
|
-
type:
|
|
371
|
+
type: "video",
|
|
366
372
|
data: {
|
|
367
373
|
id: attachment.id,
|
|
368
374
|
name: attachment.name,
|
|
@@ -371,38 +377,38 @@ export class DiscordBot extends Client {
|
|
|
371
377
|
size: attachment.size,
|
|
372
378
|
width: attachment.width,
|
|
373
379
|
height: attachment.height,
|
|
374
|
-
content_type: attachment.contentType
|
|
375
|
-
}
|
|
380
|
+
content_type: attachment.contentType,
|
|
381
|
+
},
|
|
376
382
|
});
|
|
377
383
|
}
|
|
378
384
|
else {
|
|
379
385
|
segments.push({
|
|
380
|
-
type:
|
|
386
|
+
type: "file",
|
|
381
387
|
data: {
|
|
382
388
|
id: attachment.id,
|
|
383
389
|
name: attachment.name,
|
|
384
390
|
url: attachment.url,
|
|
385
391
|
proxy_url: attachment.proxyURL,
|
|
386
392
|
size: attachment.size,
|
|
387
|
-
content_type: attachment.contentType
|
|
388
|
-
}
|
|
393
|
+
content_type: attachment.contentType,
|
|
394
|
+
},
|
|
389
395
|
});
|
|
390
396
|
}
|
|
391
397
|
return segments;
|
|
392
398
|
}
|
|
393
399
|
async $sendMessage(options) {
|
|
394
|
-
options = await
|
|
400
|
+
options = await plugin.app.handleBeforeSend(options);
|
|
395
401
|
try {
|
|
396
402
|
const channel = await this.channels.fetch(options.id);
|
|
397
403
|
if (!channel || !channel.isTextBased()) {
|
|
398
404
|
throw new Error(`Channel ${options.id} is not a text channel`);
|
|
399
405
|
}
|
|
400
406
|
const result = await this.sendContentToChannel(channel, options.content);
|
|
401
|
-
|
|
407
|
+
plugin.logger.info(`send ${options.type}(${options.id}): ${segment.raw(options.content)}`);
|
|
402
408
|
return result.id;
|
|
403
409
|
}
|
|
404
410
|
catch (error) {
|
|
405
|
-
|
|
411
|
+
plugin.logger.error("Failed to send Discord message:", error);
|
|
406
412
|
throw error;
|
|
407
413
|
}
|
|
408
414
|
}
|
|
@@ -411,38 +417,40 @@ export class DiscordBot extends Client {
|
|
|
411
417
|
if (!Array.isArray(content))
|
|
412
418
|
content = [content];
|
|
413
419
|
const messageOptions = { ...extraOptions };
|
|
414
|
-
let textContent =
|
|
420
|
+
let textContent = "";
|
|
415
421
|
const embeds = [];
|
|
416
422
|
const files = [];
|
|
417
423
|
for (const segment of content) {
|
|
418
|
-
if (typeof segment ===
|
|
424
|
+
if (typeof segment === "string") {
|
|
419
425
|
textContent += segment;
|
|
420
426
|
continue;
|
|
421
427
|
}
|
|
422
428
|
const { type, data } = segment;
|
|
423
429
|
switch (type) {
|
|
424
|
-
case
|
|
425
|
-
textContent += data.text ||
|
|
430
|
+
case "text":
|
|
431
|
+
textContent += data.text || "";
|
|
426
432
|
break;
|
|
427
|
-
case
|
|
433
|
+
case "at":
|
|
428
434
|
textContent += `<@${data.id}>`;
|
|
429
435
|
break;
|
|
430
|
-
case
|
|
436
|
+
case "channel_mention":
|
|
431
437
|
textContent += `<#${data.id}>`;
|
|
432
438
|
break;
|
|
433
|
-
case
|
|
439
|
+
case "role_mention":
|
|
434
440
|
textContent += `<@&${data.id}>`;
|
|
435
441
|
break;
|
|
436
|
-
case
|
|
437
|
-
textContent += data.animated
|
|
442
|
+
case "emoji":
|
|
443
|
+
textContent += data.animated
|
|
444
|
+
? `<a:${data.name}:${data.id}>`
|
|
445
|
+
: `<:${data.name}:${data.id}>`;
|
|
438
446
|
break;
|
|
439
|
-
case
|
|
440
|
-
case
|
|
441
|
-
case
|
|
442
|
-
case
|
|
447
|
+
case "image":
|
|
448
|
+
case "audio":
|
|
449
|
+
case "video":
|
|
450
|
+
case "file":
|
|
443
451
|
await this.handleFileSegment(data, files, textContent);
|
|
444
452
|
break;
|
|
445
|
-
case
|
|
453
|
+
case "embed":
|
|
446
454
|
embeds.push(this.createEmbedFromData(data));
|
|
447
455
|
break;
|
|
448
456
|
default:
|
|
@@ -463,26 +471,25 @@ export class DiscordBot extends Client {
|
|
|
463
471
|
// 发送消息
|
|
464
472
|
return await channel.send(messageOptions);
|
|
465
473
|
}
|
|
466
|
-
async $recallMessage(id) {
|
|
467
|
-
}
|
|
474
|
+
async $recallMessage(id) { }
|
|
468
475
|
// 处理文件段
|
|
469
476
|
async handleFileSegment(data, files, textContent) {
|
|
470
|
-
if (data.file && await this.fileExists(data.file)) {
|
|
477
|
+
if (data.file && (await this.fileExists(data.file))) {
|
|
471
478
|
// 本地文件
|
|
472
479
|
files.push(new AttachmentBuilder(createReadStream(data.file), {
|
|
473
|
-
name: data.name || path.basename(data.file)
|
|
480
|
+
name: data.name || path.basename(data.file),
|
|
474
481
|
}));
|
|
475
482
|
}
|
|
476
483
|
else if (data.url) {
|
|
477
484
|
// URL 文件
|
|
478
485
|
files.push(new AttachmentBuilder(data.url, {
|
|
479
|
-
name: data.name ||
|
|
486
|
+
name: data.name || "attachment",
|
|
480
487
|
}));
|
|
481
488
|
}
|
|
482
489
|
else if (data.buffer) {
|
|
483
490
|
// Buffer 数据
|
|
484
491
|
files.push(new AttachmentBuilder(data.buffer, {
|
|
485
|
-
name: data.name ||
|
|
492
|
+
name: data.name || "attachment",
|
|
486
493
|
}));
|
|
487
494
|
}
|
|
488
495
|
}
|
|
@@ -515,11 +522,11 @@ export class DiscordBot extends Client {
|
|
|
515
522
|
// 工具方法:获取活动类型
|
|
516
523
|
getActivityType(type) {
|
|
517
524
|
const activityTypes = {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
525
|
+
PLAYING: 0,
|
|
526
|
+
STREAMING: 1,
|
|
527
|
+
LISTENING: 2,
|
|
528
|
+
WATCHING: 3,
|
|
529
|
+
COMPETING: 5,
|
|
523
530
|
};
|
|
524
531
|
return activityTypes[type] || 0;
|
|
525
532
|
}
|
|
@@ -528,22 +535,24 @@ export class DiscordBot extends Client {
|
|
|
528
535
|
if (!this.$config.slashCommands || !this.user)
|
|
529
536
|
return;
|
|
530
537
|
try {
|
|
531
|
-
const rest = new REST({ version:
|
|
538
|
+
const rest = new REST({ version: "10" }).setToken(this.$config.token);
|
|
532
539
|
if (this.$config.globalCommands) {
|
|
533
540
|
// 注册全局命令
|
|
534
|
-
await rest.put(Routes.applicationCommands(this.user.id), {
|
|
535
|
-
|
|
541
|
+
await rest.put(Routes.applicationCommands(this.user.id), {
|
|
542
|
+
body: this.$config.slashCommands,
|
|
543
|
+
});
|
|
544
|
+
plugin.logger.info("Successfully registered global slash commands");
|
|
536
545
|
}
|
|
537
546
|
else {
|
|
538
547
|
// 为每个服务器注册命令
|
|
539
548
|
for (const guild of this.guilds.cache.values()) {
|
|
540
549
|
await rest.put(Routes.applicationGuildCommands(this.user.id, guild.id), { body: this.$config.slashCommands });
|
|
541
550
|
}
|
|
542
|
-
|
|
551
|
+
plugin.logger.info("Successfully registered guild slash commands");
|
|
543
552
|
}
|
|
544
553
|
}
|
|
545
554
|
catch (error) {
|
|
546
|
-
|
|
555
|
+
plugin.logger.error("Failed to register slash commands:", error);
|
|
547
556
|
}
|
|
548
557
|
}
|
|
549
558
|
// 添加 Slash Command 处理器
|
|
@@ -568,54 +577,54 @@ export class DiscordBot extends Client {
|
|
|
568
577
|
static formatContentToText(content) {
|
|
569
578
|
if (!Array.isArray(content))
|
|
570
579
|
content = [content];
|
|
571
|
-
return content
|
|
572
|
-
|
|
580
|
+
return content
|
|
581
|
+
.map((segment) => {
|
|
582
|
+
if (typeof segment === "string")
|
|
573
583
|
return segment;
|
|
574
584
|
switch (segment.type) {
|
|
575
|
-
case
|
|
576
|
-
return segment.data.text ||
|
|
577
|
-
case
|
|
585
|
+
case "text":
|
|
586
|
+
return segment.data.text || "";
|
|
587
|
+
case "at":
|
|
578
588
|
return `@${segment.data.name || segment.data.id}`;
|
|
579
|
-
case
|
|
589
|
+
case "channel_mention":
|
|
580
590
|
return `#${segment.data.name}`;
|
|
581
|
-
case
|
|
591
|
+
case "role_mention":
|
|
582
592
|
return `@${segment.data.name}`;
|
|
583
|
-
case
|
|
584
|
-
return
|
|
585
|
-
case
|
|
586
|
-
return
|
|
587
|
-
case
|
|
588
|
-
return
|
|
589
|
-
case
|
|
590
|
-
return
|
|
591
|
-
case
|
|
592
|
-
return
|
|
593
|
-
case
|
|
593
|
+
case "image":
|
|
594
|
+
return "[图片]";
|
|
595
|
+
case "audio":
|
|
596
|
+
return "[音频]";
|
|
597
|
+
case "video":
|
|
598
|
+
return "[视频]";
|
|
599
|
+
case "file":
|
|
600
|
+
return "[文件]";
|
|
601
|
+
case "embed":
|
|
602
|
+
return "[嵌入消息]";
|
|
603
|
+
case "emoji":
|
|
594
604
|
return `:${segment.data.name}:`;
|
|
595
605
|
default:
|
|
596
606
|
return `[${segment.type}]`;
|
|
597
607
|
}
|
|
598
|
-
})
|
|
608
|
+
})
|
|
609
|
+
.join("");
|
|
599
610
|
}
|
|
600
611
|
}
|
|
601
612
|
// ================================================================================================
|
|
602
613
|
// DiscordInteractionsBot 类(Interactions 端点模式)
|
|
603
614
|
// ================================================================================================
|
|
604
|
-
import * as nacl from
|
|
615
|
+
import * as nacl from "tweetnacl";
|
|
605
616
|
export class DiscordInteractionsBot extends Client {
|
|
606
|
-
plugin;
|
|
607
617
|
$config;
|
|
608
618
|
$connected;
|
|
609
619
|
router;
|
|
610
620
|
slashCommandHandlers = new Map();
|
|
611
|
-
constructor(
|
|
621
|
+
constructor(router, $config) {
|
|
612
622
|
const intents = $config.intents || [
|
|
613
623
|
GatewayIntentBits.Guilds,
|
|
614
624
|
GatewayIntentBits.GuildMessages,
|
|
615
625
|
GatewayIntentBits.MessageContent,
|
|
616
626
|
];
|
|
617
627
|
super({ intents });
|
|
618
|
-
this.plugin = plugin;
|
|
619
628
|
this.$config = $config;
|
|
620
629
|
this.$connected = false;
|
|
621
630
|
this.router = router;
|
|
@@ -630,14 +639,14 @@ export class DiscordInteractionsBot extends Client {
|
|
|
630
639
|
}
|
|
631
640
|
async handleInteraction(ctx) {
|
|
632
641
|
try {
|
|
633
|
-
const signature = ctx.get(
|
|
634
|
-
const timestamp = ctx.get(
|
|
642
|
+
const signature = ctx.get("x-signature-ed25519");
|
|
643
|
+
const timestamp = ctx.get("x-signature-timestamp");
|
|
635
644
|
const bodyString = JSON.stringify(ctx.request.body);
|
|
636
645
|
// 验证请求签名
|
|
637
646
|
if (!this.verifyDiscordSignature(bodyString, signature, timestamp)) {
|
|
638
|
-
|
|
647
|
+
plugin.logger.warn("Invalid Discord signature");
|
|
639
648
|
ctx.status = 401;
|
|
640
|
-
ctx.body =
|
|
649
|
+
ctx.body = "Unauthorized";
|
|
641
650
|
return;
|
|
642
651
|
}
|
|
643
652
|
const interaction = ctx.request.body;
|
|
@@ -654,24 +663,24 @@ export class DiscordInteractionsBot extends Client {
|
|
|
654
663
|
else {
|
|
655
664
|
// 其他交互类型
|
|
656
665
|
ctx.status = 400;
|
|
657
|
-
ctx.body =
|
|
666
|
+
ctx.body = "Unsupported interaction type";
|
|
658
667
|
}
|
|
659
668
|
}
|
|
660
669
|
catch (error) {
|
|
661
|
-
|
|
670
|
+
plugin.logger.error("Interactions error:", error);
|
|
662
671
|
ctx.status = 500;
|
|
663
|
-
ctx.body =
|
|
672
|
+
ctx.body = "Internal Server Error";
|
|
664
673
|
}
|
|
665
674
|
}
|
|
666
675
|
verifyDiscordSignature(body, signature, timestamp) {
|
|
667
676
|
try {
|
|
668
|
-
const publicKey = Buffer.from(this.$config.publicKey,
|
|
669
|
-
const sig = Buffer.from(signature,
|
|
670
|
-
const message = Buffer.from(timestamp + body,
|
|
677
|
+
const publicKey = Buffer.from(this.$config.publicKey, "hex");
|
|
678
|
+
const sig = Buffer.from(signature, "hex");
|
|
679
|
+
const message = Buffer.from(timestamp + body, "utf8");
|
|
671
680
|
return nacl.sign.detached.verify(message, sig, publicKey);
|
|
672
681
|
}
|
|
673
682
|
catch (error) {
|
|
674
|
-
|
|
683
|
+
plugin.logger.error("Signature verification error:", error);
|
|
675
684
|
return false;
|
|
676
685
|
}
|
|
677
686
|
}
|
|
@@ -680,7 +689,7 @@ export class DiscordInteractionsBot extends Client {
|
|
|
680
689
|
const commandName = interaction.data.name;
|
|
681
690
|
// 转换为标准消息格式并分发
|
|
682
691
|
const message = this.formatInteractionAsMessage(interaction);
|
|
683
|
-
|
|
692
|
+
plugin.dispatch("message.receive", message);
|
|
684
693
|
// 查找自定义处理器
|
|
685
694
|
const handler = this.slashCommandHandlers.get(commandName);
|
|
686
695
|
if (handler) {
|
|
@@ -688,7 +697,7 @@ export class DiscordInteractionsBot extends Client {
|
|
|
688
697
|
await handler(interaction);
|
|
689
698
|
}
|
|
690
699
|
catch (error) {
|
|
691
|
-
|
|
700
|
+
plugin.logger.error(`Error in slash command handler for ${commandName}:`, error);
|
|
692
701
|
}
|
|
693
702
|
}
|
|
694
703
|
// 默认响应
|
|
@@ -696,12 +705,12 @@ export class DiscordInteractionsBot extends Client {
|
|
|
696
705
|
type: InteractionResponseType.ChannelMessageWithSource,
|
|
697
706
|
data: {
|
|
698
707
|
content: `处理命令: ${commandName}`,
|
|
699
|
-
flags: 64 // EPHEMERAL - 只有用户可见
|
|
700
|
-
}
|
|
708
|
+
flags: 64, // EPHEMERAL - 只有用户可见
|
|
709
|
+
},
|
|
701
710
|
};
|
|
702
711
|
}
|
|
703
712
|
formatInteractionAsMessage(interaction) {
|
|
704
|
-
const channelType = interaction.guild_id ?
|
|
713
|
+
const channelType = interaction.guild_id ? "channel" : "private";
|
|
705
714
|
const channelId = interaction.channel_id;
|
|
706
715
|
// 解析命令参数为内容
|
|
707
716
|
const options = interaction.data.options || [];
|
|
@@ -711,15 +720,15 @@ export class DiscordInteractionsBot extends Client {
|
|
|
711
720
|
}
|
|
712
721
|
return Message.from(interaction, {
|
|
713
722
|
$id: interaction.id,
|
|
714
|
-
$adapter:
|
|
723
|
+
$adapter: "discord-interactions",
|
|
715
724
|
$bot: this.$config.name,
|
|
716
725
|
$sender: {
|
|
717
726
|
id: interaction.user?.id || interaction.member?.user?.id,
|
|
718
|
-
name: interaction.user?.username || interaction.member?.user?.username
|
|
727
|
+
name: interaction.user?.username || interaction.member?.user?.username,
|
|
719
728
|
},
|
|
720
729
|
$channel: {
|
|
721
730
|
id: channelId,
|
|
722
|
-
type: channelType
|
|
731
|
+
type: channelType,
|
|
723
732
|
},
|
|
724
733
|
$raw: JSON.stringify(interaction),
|
|
725
734
|
$timestamp: Date.now(),
|
|
@@ -727,29 +736,29 @@ export class DiscordInteractionsBot extends Client {
|
|
|
727
736
|
$reply: async (content) => {
|
|
728
737
|
return this.$sendMessage({
|
|
729
738
|
...this.$formatMessage(interaction),
|
|
730
|
-
content: content
|
|
739
|
+
content: content,
|
|
731
740
|
});
|
|
732
|
-
}
|
|
741
|
+
},
|
|
733
742
|
});
|
|
734
743
|
}
|
|
735
744
|
formatSendContent(content) {
|
|
736
|
-
if (typeof content ===
|
|
745
|
+
if (typeof content === "string") {
|
|
737
746
|
return { content };
|
|
738
747
|
}
|
|
739
748
|
if (Array.isArray(content)) {
|
|
740
749
|
const textParts = [];
|
|
741
750
|
let embed = null;
|
|
742
751
|
for (const item of content) {
|
|
743
|
-
if (typeof item ===
|
|
752
|
+
if (typeof item === "string") {
|
|
744
753
|
textParts.push(item);
|
|
745
754
|
}
|
|
746
755
|
else {
|
|
747
756
|
const segment = item;
|
|
748
757
|
switch (segment.type) {
|
|
749
|
-
case
|
|
750
|
-
textParts.push(segment.data.text || segment.data.content ||
|
|
758
|
+
case "text":
|
|
759
|
+
textParts.push(segment.data.text || segment.data.content || "");
|
|
751
760
|
break;
|
|
752
|
-
case
|
|
761
|
+
case "embed":
|
|
753
762
|
embed = segment.data;
|
|
754
763
|
break;
|
|
755
764
|
}
|
|
@@ -757,7 +766,7 @@ export class DiscordInteractionsBot extends Client {
|
|
|
757
766
|
}
|
|
758
767
|
const result = {};
|
|
759
768
|
if (textParts.length > 0) {
|
|
760
|
-
result.content = textParts.join(
|
|
769
|
+
result.content = textParts.join("");
|
|
761
770
|
}
|
|
762
771
|
if (embed) {
|
|
763
772
|
result.embeds = [embed];
|
|
@@ -779,16 +788,16 @@ export class DiscordInteractionsBot extends Client {
|
|
|
779
788
|
if (this.$config.defaultActivity) {
|
|
780
789
|
this.user?.setActivity(this.$config.defaultActivity.name, {
|
|
781
790
|
type: this.getActivityType(this.$config.defaultActivity.type),
|
|
782
|
-
url: this.$config.defaultActivity.url
|
|
791
|
+
url: this.$config.defaultActivity.url,
|
|
783
792
|
});
|
|
784
793
|
}
|
|
785
794
|
}
|
|
786
795
|
this.$connected = true;
|
|
787
|
-
|
|
788
|
-
|
|
796
|
+
plugin.logger.info(`Discord interactions bot connected: ${this.$config.name}`);
|
|
797
|
+
plugin.logger.info(`Interactions endpoint: ${this.$config.interactionsPath}`);
|
|
789
798
|
}
|
|
790
799
|
catch (error) {
|
|
791
|
-
|
|
800
|
+
plugin.logger.error("Failed to connect Discord interactions bot:", error);
|
|
792
801
|
throw error;
|
|
793
802
|
}
|
|
794
803
|
}
|
|
@@ -798,10 +807,10 @@ export class DiscordInteractionsBot extends Client {
|
|
|
798
807
|
await this.destroy();
|
|
799
808
|
}
|
|
800
809
|
this.$connected = false;
|
|
801
|
-
|
|
810
|
+
plugin.logger.info("Discord interactions bot disconnected");
|
|
802
811
|
}
|
|
803
812
|
catch (error) {
|
|
804
|
-
|
|
813
|
+
plugin.logger.error("Error disconnecting Discord interactions bot:", error);
|
|
805
814
|
}
|
|
806
815
|
}
|
|
807
816
|
// Slash Commands 管理
|
|
@@ -809,17 +818,19 @@ export class DiscordInteractionsBot extends Client {
|
|
|
809
818
|
if (!this.$config.slashCommands)
|
|
810
819
|
return;
|
|
811
820
|
try {
|
|
812
|
-
const rest = new REST({ version:
|
|
821
|
+
const rest = new REST({ version: "10" }).setToken(this.$config.token);
|
|
813
822
|
if (this.$config.globalCommands) {
|
|
814
|
-
await rest.put(Routes.applicationCommands(this.$config.applicationId), {
|
|
815
|
-
|
|
823
|
+
await rest.put(Routes.applicationCommands(this.$config.applicationId), {
|
|
824
|
+
body: this.$config.slashCommands,
|
|
825
|
+
});
|
|
826
|
+
plugin.logger.info("Successfully registered global slash commands");
|
|
816
827
|
}
|
|
817
828
|
else {
|
|
818
|
-
|
|
829
|
+
plugin.logger.info("Note: Guild commands registration requires connecting to Gateway first");
|
|
819
830
|
}
|
|
820
831
|
}
|
|
821
832
|
catch (error) {
|
|
822
|
-
|
|
833
|
+
plugin.logger.error("Failed to register slash commands:", error);
|
|
823
834
|
}
|
|
824
835
|
}
|
|
825
836
|
// 添加 Slash Command 处理器
|
|
@@ -833,11 +844,11 @@ export class DiscordInteractionsBot extends Client {
|
|
|
833
844
|
// 工具方法
|
|
834
845
|
getActivityType(type) {
|
|
835
846
|
const activityTypes = {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
847
|
+
PLAYING: 0,
|
|
848
|
+
STREAMING: 1,
|
|
849
|
+
LISTENING: 2,
|
|
850
|
+
WATCHING: 3,
|
|
851
|
+
COMPETING: 5,
|
|
841
852
|
};
|
|
842
853
|
return activityTypes[type] || 0;
|
|
843
854
|
}
|
|
@@ -848,22 +859,23 @@ export class DiscordInteractionsBot extends Client {
|
|
|
848
859
|
async $sendMessage(options) {
|
|
849
860
|
// 简化实现 - 通过 REST API 发送消息
|
|
850
861
|
try {
|
|
851
|
-
const rest = new REST({ version:
|
|
862
|
+
const rest = new REST({ version: "10" }).setToken(this.$config.token);
|
|
852
863
|
const messageContent = this.formatSendContent(options.content);
|
|
853
|
-
await rest.post(Routes.channelMessages(options.id), {
|
|
864
|
+
await rest.post(Routes.channelMessages(options.id), {
|
|
865
|
+
body: messageContent,
|
|
866
|
+
});
|
|
854
867
|
}
|
|
855
868
|
catch (error) {
|
|
856
|
-
|
|
869
|
+
plugin.logger.error("Failed to send message:", error);
|
|
857
870
|
}
|
|
858
|
-
return
|
|
859
|
-
}
|
|
860
|
-
async $recallMessage(id) {
|
|
871
|
+
return "";
|
|
861
872
|
}
|
|
873
|
+
async $recallMessage(id) { }
|
|
862
874
|
}
|
|
863
875
|
// 注册 Gateway 模式适配器
|
|
864
|
-
registerAdapter(new Adapter(
|
|
876
|
+
registerAdapter(new Adapter("discord", (config) => new DiscordBot(config)));
|
|
865
877
|
// 注册 Interactions 端点模式适配器(需要 router)
|
|
866
|
-
useContext(
|
|
867
|
-
registerAdapter(new Adapter(
|
|
878
|
+
useContext("router", (router) => {
|
|
879
|
+
registerAdapter(new Adapter("discord-interactions", (config) => new DiscordInteractionsBot(router, config)));
|
|
868
880
|
});
|
|
869
881
|
//# sourceMappingURL=index.js.map
|