@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/lib/index.js CHANGED
@@ -1,25 +1,24 @@
1
- import { Client, GatewayIntentBits, EmbedBuilder, AttachmentBuilder, ChannelType, REST, Routes, InteractionType, InteractionResponseType } from 'discord.js';
2
- import { Adapter, registerAdapter, Message, segment, useContext } from "zhin.js";
3
- import { createReadStream } from 'fs';
4
- import { promises as fs } from 'fs';
5
- import path from 'path';
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(plugin, $config) {
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
- this.plugin.dispatch('message.receive', message);
32
- this.plugin.logger.info(`recv ${message.$channel.type}(${message.$channel.id}): ${segment.raw(message.$content)}`);
33
- this.plugin.dispatch(`message.${message.$channel.type}.receive`, message);
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
- this.plugin.logger.info(`Executed slash command: /${commandName} by ${interaction.user.tag}`);
40
+ plugin.logger.info(`Executed slash command: /${commandName} by ${interaction.user.tag}`);
42
41
  }
43
42
  catch (error) {
44
- this.plugin.logger.error(`Error executing slash command /${commandName}:`, error);
45
- const errorMessage = 'An error occurred while executing this command.';
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({ content: errorMessage, ephemeral: true });
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
- this.plugin.logger.warn(`Unknown slash command: /${commandName}`);
57
+ plugin.logger.warn(`Unknown slash command: /${commandName}`);
56
58
  if (!interaction.replied) {
57
59
  await interaction.reply({
58
- content: 'Unknown command.',
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('messageCreate', this.handleDiscordMessage.bind(this));
69
+ this.on("messageCreate", this.handleDiscordMessage.bind(this));
68
70
  // 监听交互事件(Slash Commands)
69
71
  if (this.$config.enableSlashCommands) {
70
- this.on('interactionCreate', async (interaction) => {
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('ready', async () => {
79
+ this.once("clientReady", async () => {
78
80
  this.$connected = true;
79
- this.plugin.logger.info(`Discord bot ${this.$config.name} connected successfully as ${this.user?.tag}`);
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('error', (error) => {
95
- this.plugin.logger.error('Discord client error:', error);
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
- this.plugin.logger.error('Failed to login to Discord:', error);
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
- this.plugin.logger.info(`Discord bot ${this.$config.name} disconnected`);
113
+ plugin.logger.info(`Discord bot ${this.$config.name} disconnected`);
112
114
  }
113
115
  catch (error) {
114
- this.plugin.logger.error('Error disconnecting Discord bot:', error);
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 = 'private';
125
+ channelType = "private";
124
126
  channelId = msg.channel.id;
125
127
  }
126
128
  else if (msg.channel.type === ChannelType.GroupDM) {
127
- channelType = 'group';
129
+ channelType = "group";
128
130
  channelId = msg.channel.id;
129
131
  }
130
132
  else {
131
- channelType = 'channel';
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: 'discord',
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
- this.plugin.logger.warn(`Could not find message to reply to: ${replyId}`);
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: 'reply',
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: 'embed',
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: 'sticker',
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 ? segments : [{ type: 'text', data: { text: '' } }];
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: 'user' });
247
+ allMatches.push({ match, type: "user" });
244
248
  }
245
249
  while ((match = channelMentionRegex.exec(content)) !== null) {
246
- allMatches.push({ match, type: 'channel' });
250
+ allMatches.push({ match, type: "channel" });
247
251
  }
248
252
  while ((match = roleMentionRegex.exec(content)) !== null) {
249
- allMatches.push({ match, type: 'role' });
253
+ allMatches.push({ match, type: "role" });
250
254
  }
251
255
  while ((match = emojiRegex.exec(content)) !== null) {
252
- allMatches.push({ match, type: 'emoji' });
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: 'text', data: { text: beforeText } });
268
+ segments.push({ type: "text", data: { text: beforeText } });
265
269
  }
266
270
  }
267
271
  // 添加特殊内容段
268
272
  switch (type) {
269
- case 'user':
273
+ case "user":
270
274
  const userId = match[1];
271
275
  const user = msg.mentions.users.get(userId);
272
276
  segments.push({
273
- type: 'at',
277
+ type: "at",
274
278
  data: {
275
279
  id: userId,
276
- name: user?.username || 'Unknown',
277
- text: match[0]
278
- }
280
+ name: user?.username || "Unknown",
281
+ text: match[0],
282
+ },
279
283
  });
280
284
  break;
281
- case 'channel':
285
+ case "channel":
282
286
  const channelId = match[1];
283
287
  const channel = msg.mentions.channels.get(channelId);
284
288
  segments.push({
285
- type: 'channel_mention',
289
+ type: "channel_mention",
286
290
  data: {
287
291
  id: channelId,
288
- name: channel?.name || 'unknown-channel',
289
- text: match[0]
290
- }
292
+ name: channel?.name || "unknown-channel",
293
+ text: match[0],
294
+ },
291
295
  });
292
296
  break;
293
- case 'role':
297
+ case "role":
294
298
  const roleId = match[1];
295
299
  const role = msg.mentions.roles.get(roleId);
296
300
  segments.push({
297
- type: 'role_mention',
301
+ type: "role_mention",
298
302
  data: {
299
303
  id: roleId,
300
- name: role?.name || 'unknown-role',
301
- text: match[0]
302
- }
304
+ name: role?.name || "unknown-role",
305
+ text: match[0],
306
+ },
303
307
  });
304
308
  break;
305
- case 'emoji':
309
+ case "emoji":
306
310
  const emojiName = match[1];
307
311
  const emojiId = match[2];
308
- const isAnimated = match[0].startsWith('<a:');
312
+ const isAnimated = match[0].startsWith("<a:");
309
313
  segments.push({
310
- type: 'emoji',
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 ? 'gif' : 'png'}`,
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: 'text', data: { text: remainingText } });
331
+ segments.push({ type: "text", data: { text: remainingText } });
328
332
  }
329
333
  }
330
- return segments.length > 0 ? segments : [{ type: 'text', data: { text: content } }];
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('image/')) {
341
+ if (attachment.contentType?.startsWith("image/")) {
336
342
  segments.push({
337
- type: 'image',
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('audio/')) {
356
+ else if (attachment.contentType?.startsWith("audio/")) {
351
357
  segments.push({
352
- type: 'audio',
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('video/')) {
369
+ else if (attachment.contentType?.startsWith("video/")) {
364
370
  segments.push({
365
- type: 'video',
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: 'file',
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 this.plugin.app.handleBeforeSend(options);
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
- this.plugin.logger.info(`send ${options.type}(${options.id}): ${segment.raw(options.content)}`);
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
- this.plugin.logger.error('Failed to send Discord message:', error);
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 === 'string') {
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 'text':
425
- textContent += data.text || '';
430
+ case "text":
431
+ textContent += data.text || "";
426
432
  break;
427
- case 'at':
433
+ case "at":
428
434
  textContent += `<@${data.id}>`;
429
435
  break;
430
- case 'channel_mention':
436
+ case "channel_mention":
431
437
  textContent += `<#${data.id}>`;
432
438
  break;
433
- case 'role_mention':
439
+ case "role_mention":
434
440
  textContent += `<@&${data.id}>`;
435
441
  break;
436
- case 'emoji':
437
- textContent += data.animated ? `<a:${data.name}:${data.id}>` : `<:${data.name}:${data.id}>`;
442
+ case "emoji":
443
+ textContent += data.animated
444
+ ? `<a:${data.name}:${data.id}>`
445
+ : `<:${data.name}:${data.id}>`;
438
446
  break;
439
- case 'image':
440
- case 'audio':
441
- case 'video':
442
- case 'file':
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 'embed':
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 || 'attachment'
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 || 'attachment'
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
- 'PLAYING': 0,
519
- 'STREAMING': 1,
520
- 'LISTENING': 2,
521
- 'WATCHING': 3,
522
- 'COMPETING': 5
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: '10' }).setToken(this.$config.token);
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), { body: this.$config.slashCommands });
535
- this.plugin.logger.info('Successfully registered global slash commands');
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
- this.plugin.logger.info('Successfully registered guild slash commands');
551
+ plugin.logger.info("Successfully registered guild slash commands");
543
552
  }
544
553
  }
545
554
  catch (error) {
546
- this.plugin.logger.error('Failed to register slash commands:', error);
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.map(segment => {
572
- if (typeof segment === 'string')
580
+ return content
581
+ .map((segment) => {
582
+ if (typeof segment === "string")
573
583
  return segment;
574
584
  switch (segment.type) {
575
- case 'text':
576
- return segment.data.text || '';
577
- case 'at':
585
+ case "text":
586
+ return segment.data.text || "";
587
+ case "at":
578
588
  return `@${segment.data.name || segment.data.id}`;
579
- case 'channel_mention':
589
+ case "channel_mention":
580
590
  return `#${segment.data.name}`;
581
- case 'role_mention':
591
+ case "role_mention":
582
592
  return `@${segment.data.name}`;
583
- case 'image':
584
- return '[图片]';
585
- case 'audio':
586
- return '[音频]';
587
- case 'video':
588
- return '[视频]';
589
- case 'file':
590
- return '[文件]';
591
- case 'embed':
592
- return '[嵌入消息]';
593
- case 'emoji':
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
- }).join('');
608
+ })
609
+ .join("");
599
610
  }
600
611
  }
601
612
  // ================================================================================================
602
613
  // DiscordInteractionsBot 类(Interactions 端点模式)
603
614
  // ================================================================================================
604
- import * as nacl from 'tweetnacl';
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(plugin, router, $config) {
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('x-signature-ed25519');
634
- const timestamp = ctx.get('x-signature-timestamp');
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
- this.plugin.logger.warn('Invalid Discord signature');
647
+ plugin.logger.warn("Invalid Discord signature");
639
648
  ctx.status = 401;
640
- ctx.body = 'Unauthorized';
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 = 'Unsupported interaction type';
666
+ ctx.body = "Unsupported interaction type";
658
667
  }
659
668
  }
660
669
  catch (error) {
661
- this.plugin.logger.error('Interactions error:', error);
670
+ plugin.logger.error("Interactions error:", error);
662
671
  ctx.status = 500;
663
- ctx.body = 'Internal Server Error';
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, 'hex');
669
- const sig = Buffer.from(signature, 'hex');
670
- const message = Buffer.from(timestamp + body, 'utf8');
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
- this.plugin.logger.error('Signature verification error:', error);
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
- this.plugin.dispatch('message.receive', message);
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
- this.plugin.logger.error(`Error in slash command handler for ${commandName}:`, error);
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 ? 'channel' : 'private';
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: 'discord-interactions',
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 === 'string') {
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 === 'string') {
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 'text':
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 'embed':
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
- this.plugin.logger.info(`Discord interactions bot connected: ${this.$config.name}`);
788
- this.plugin.logger.info(`Interactions endpoint: ${this.$config.interactionsPath}`);
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
- this.plugin.logger.error('Failed to connect Discord interactions bot:', error);
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
- this.plugin.logger.info('Discord interactions bot disconnected');
810
+ plugin.logger.info("Discord interactions bot disconnected");
802
811
  }
803
812
  catch (error) {
804
- this.plugin.logger.error('Error disconnecting Discord interactions bot:', error);
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: '10' }).setToken(this.$config.token);
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), { body: this.$config.slashCommands });
815
- this.plugin.logger.info('Successfully registered global slash commands');
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
- this.plugin.logger.info('Note: Guild commands registration requires connecting to Gateway first');
829
+ plugin.logger.info("Note: Guild commands registration requires connecting to Gateway first");
819
830
  }
820
831
  }
821
832
  catch (error) {
822
- this.plugin.logger.error('Failed to register slash commands:', error);
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
- 'PLAYING': 0,
837
- 'STREAMING': 1,
838
- 'LISTENING': 2,
839
- 'WATCHING': 3,
840
- 'COMPETING': 5
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: '10' }).setToken(this.$config.token);
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), { body: messageContent });
864
+ await rest.post(Routes.channelMessages(options.id), {
865
+ body: messageContent,
866
+ });
854
867
  }
855
868
  catch (error) {
856
- this.plugin.logger.error('Failed to send message:', error);
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('discord', (plugin, config) => new DiscordBot(plugin, config)));
876
+ registerAdapter(new Adapter("discord", (config) => new DiscordBot(config)));
865
877
  // 注册 Interactions 端点模式适配器(需要 router)
866
- useContext('router', (router) => {
867
- registerAdapter(new Adapter('discord-interactions', (plugin, config) => new DiscordInteractionsBot(plugin, router, config)));
878
+ useContext("router", (router) => {
879
+ registerAdapter(new Adapter("discord-interactions", (config) => new DiscordInteractionsBot(router, config)));
868
880
  });
869
881
  //# sourceMappingURL=index.js.map