如何使用 setTimeout 多次更新 discord.js 的交互?

How to update multiple time a discord.js's interaction using setTimeout?

我目前正在使用打字稿开发一个 discordjs v13.6 机器人,当使用 /day 交互命令时,它 post 一个带有日期的嵌入对象。 简而言之:我制作了 /day 并且我的机器人 post 嵌入了当前日期、天气等...

它工作正常,我在嵌入下添加了 3 个按钮:

  1. “重新加载”按钮:它只会更新嵌入(包含当前天气预报)。
  2. “上一个”按钮:将嵌入的交互更新到前一天。
  3. “下一步”按钮:将嵌入的交互更新到第二天。

我的代码对于附加到我的嵌入的 3 个按钮来说应该是这样工作的:

import { MessageActionRow, MessageButton } from 'discord.js';

// All customId property are formated like: `{method}@{date}`, ie: `reload@2022-03-10` is the button who'll reload the embed to date 03/10/2022.
export const createNavigationButtons = (date) => (
  new MessageActionRow().addComponents([
    new MessageButton()
      .setCustomId(`prev@${date}`)
      .setStyle('SECONDARY')
      .setEmoji("946186554517389332"),
    new MessageButton()
      .setCustomId(`reload@${date}`)
      .setStyle('SUCCESS')
      .setEmoji("946190012154794055"),
    new MessageButton()
      .setCustomId(`next@${date}`)
      .setStyle('SECONDARY')
      .setEmoji("946186699745161296")
  ])
)

对于逻辑:

import { ButtonInteraction               } from 'discord.js';
import { createEmbed                     } from "../utils/embed";
import { createNavigationButtons         } from "../utils/buttons";
import * as year                           from "../../resources/year.json";
import * as moment                         from 'moment';
import { setTimeout as wait }  from 'node:timers/promises';

// This function is called in the on('interactionCreate') event, when interaction.isButton() is true
export const button = async (interaction: ButtonInteraction): Promise<void> => {
  const [action, date]: string[] = interaction.customId?.split('@');
  await interaction.deferUpdate();
  await wait(1000);
  const newDate: string = {
    prev:   moment(date, "YYYY-MM-DD").subtract(1, "day").format("YYYY-MM-DD"),
    next:   moment(date, "YYYY-MM-DD").add(     1, "day").format("YYYY-MM-DD"),
    reload: date
  }[action];
  await interaction.editReply({
    embeds:     [await createEmbed(year[newDate])],
    components: [createNavigationButtons(newDate)]
  });
}

如我所愿。但是,每个人都可以使用这些按钮(我不想发送 /day 的答案是短暂的,我希望每个人都能看到回复)。因此,如果我们使用 /day 2022-03-10 2022 年 3 月 10 日的嵌入。但是如果 authorsomeone else (我不请注意)使用按钮,嵌入将更新为另一个日期(我没问题!)。但是我想在按下按钮几秒/分钟后将我的嵌入回滚到原始日期。

我尝试了一些像这样的 setTimeout 的原始方法:

export const button = async (interaction: ButtonInteraction, config: any): Promise<void> => {
  // In this test, button's customId are formated like {method}@{date}@{date's origin} (where 'origin' is the original requested's date, optional)
  const [action, date, origin]: string[] = interaction.customId?.split('@');
  await interaction.deferUpdate();
  await wait(1000);
  const newDate: string = {
    prev:   moment(date, "YYYY-MM-DD").subtract(1, "day").format("YYYY-MM-DD"),
    next:   moment(date, "YYYY-MM-DD").add(     1, "day").format("YYYY-MM-DD"),
    reload: date
  }[action];
  // Here is my setTimeout who is supposed to recursively recall this function with a "reload" and the original date
  setTimeout(async () => {
    interaction.customId = `reload@${origin ?? date}`;
    console.log(interaction.customId);
    await button(interaction, config);
  }, 5000);
  await interaction.editReply({
    embeds:     [await createEmbed(year[newDate])],
    components: [createNavigationButtons(newDate)]
  });
};

当我用这个按下我的按钮时,它会正确更新,但是 5 秒(setTimeout 的值)之后它会出现错误消息:

reload@2022-03-10
/home/toto/tata/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:180
    if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
                                             ^
Error [INTERACTION_ALREADY_REPLIED]: The reply to this interaction has already been sent or deferred.
    at ButtonInteraction.deferUpdate (/home/toto/tata/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:180:46)
    at button (/home/toto/tata/src/services/button.ts:12:21)
    at Timeout._onTimeout (/home/toto/tata/src/services/button.ts:28:21)
    at listOnTimeout (node:internal/timers:559:17)
    at processTimers (node:internal/timers:502:7) {
  [Symbol(code)]: 'INTERACTION_ALREADY_REPLIED'
}

我知道我似乎无法像这样使用相同的令牌重新更新我的交互,那么我应该如何实现我的目标呢?可能 setTimeout 不是一个合适的解决方案(但实现起来非常简单,所以我首先尝试了)。有什么想法吗?

我这样成功达到了 objective:

// All customId property are formated like: `{method}@{date}@{origin}`, ie: `reload@2022-03-10` is the button who'll reload the embed to date 03/10/2022.
export const createNavigationButtons = (date: string, mode?: boolean) => (
  new MessageActionRow().addComponents([
    new MessageButton()
      .setCustomId(`prev@${date}${!mode ? `@${date}` : ''}`)
      .setStyle('SECONDARY')
      .setEmoji("946186554517389332"),
    new MessageButton()
      .setCustomId(`reload@${date}`)
      .setStyle('SUCCESS')
      .setEmoji("946190012154794055"),
    new MessageButton()
      .setCustomId(`remind@${date}`)
      .setStyle('PRIMARY')
      .setEmoji("946192601806155806"),
    new MessageButton()
      .setCustomId(`next@${date}${!mode ? `@${date}` : ''}`)
      .setStyle('SECONDARY')
      .setEmoji("946186699745161296")
  ])
);

export const createButtons = (date, mode?: boolean) => ({
  components: [ createNavigationButtons(date, mode) ]
});
export const button = async (interaction: ButtonInteraction, config: any): Promise<void> => {
  const [action, date, origin]: string[] = interaction.customId?.split('@');
  const newDate: string = {
    prev:   moment(date, "YYYY-MM-DD").subtract(1, "day").format("YYYY-MM-DD"),
    next:   moment(date, "YYYY-MM-DD").add(     1, "day").format("YYYY-MM-DD"),
    reload: date
  }[action];
  origin && !interaction.deferred && setTimeout(async () => {
    await interaction.editReply({
      embeds: [await createEmbed(year[origin], config.server)],
      ...createButtons(origin)
    });
  }, 120000);
  !interaction.deferred && await interaction.deferUpdate();
  await interaction.editReply({
    embeds: [await createEmbed(year[newDate], config.server)],
    ...createButtons(newDate, true)
  });
};

简而言之,当我第一次创建嵌入时,我放置了一个 @{origin}(谁是约会对象),当我使用我的按钮导航和更新我的嵌入时,我不发送来源(仅 {action}@{date},而不是 {action}@{date}@origin 通过将 true 传递给我的 createButtons 方法。