You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Discord.js触发按钮Modal报Interaction已确认错误排查

报错根因

核心问题是每次执行edit指令分支逻辑时,都会重复给client绑定新的interactionCreate事件监听器,绑定的监听器不会随着指令执行结束自动销毁,会持续驻留响应所有后续交互事件:

  • 第一次调用edit指令:仅绑定1组按钮、模态框监听器,交互触发时只有1个监听器处理逻辑,运行正常
  • 第二次调用edit指令:会再绑定1组新的同逻辑监听器,此时客户端内累计有2组按钮监听器、2组模态框监听器。提交模态框时,第一个监听器执行inter.deferUpdate()完成交互确认后,第二个监听器再尝试对同一个已经确认的交互做响应操作,就会抛出DiscordAPIError: Interaction has already been acknowledged错误。

另外代码里还有三个隐患会放大问题:

  • 交互响应方法前未加await,异步操作时序不受控,容易出现偶发的交互确认冲突
  • 按钮、模态框使用固定的customId,不同次指令触发的交互会互相串扰
  • Mongoose文档更新逻辑冗余,直接new新的模型实例保存容易触发重复_id异常
修复方案
  1. 将所有interactionCreate事件监听器移到edit指令逻辑外,在客户端初始化阶段全局只注册一次,禁止在指令执行分支内重复绑定事件
  2. 给按钮、模态框的customId拼接对应书架的唯一标识(比如书架名),通过customId前缀区分不同交互场景,避免串扰
  3. 所有交互响应方法(showModal/deferUpdate/reply等)前统一加await,保证异步时序正确
  4. 修正Mongoose更新逻辑,直接修改查询到的文档实例后保存即可,不需要重新new模型实例

修复后核心代码

全局交互监听(仅注册1次,放在client初始化后即可)

// 用Map缓存指令发送的书架消息id,方便后续更新
const shelfMsgCache = new Map();

client.on("interactionCreate", async (interaction) => {
  // 处理编辑书架按钮点击
  if (interaction.isButton() && interaction.customId.startsWith("edit-shelf-")) {
    const shelfName = interaction.customId.slice("edit-shelf-".length);
    const shelfObj = await Shelves.findOne({ name: shelfName });
    if (!shelfObj) return interaction.reply({ content: "对应书架不存在", ephemeral: true });

    // 构建模态框
    const editInfo = new TextInputComponent()
      .setCustomId("editInput")
      .setLabel("Input your edit")
      .setValue(shelfObj.info)
      .setStyle("PARAGRAPH");
    const editRow = new MessageActionRow().addComponents(editInfo);
    const modal = new Modal()
      .setCustomId(`editModal-${shelfName}`)
      .setTitle("Edit Info")
      .addComponents(editRow);

    await interaction.showModal(modal);
    // 删除按钮消息
    if (interaction.message.deletable) await interaction.message.delete();
  }

  // 处理编辑模态框提交
  if (interaction.isModalSubmit() && interaction.customId.startsWith("editModal-")) {
    // 等待defer完成再执行后续逻辑
    await interaction.deferUpdate();
    const shelfName = interaction.customId.slice("editModal-".length);
    const shelfObj = await Shelves.findOne({ name: shelfName });
    if (!shelfObj) return interaction.followUp({ content: "对应书架不存在", ephemeral: true });

    const editVal = interaction.fields.getTextInputValue("editInput");
    // 直接更新文档保存
    shelfObj.info = editVal;
    await shelfObj.save();

    // 构建更新后的embed
    const updatedEmbed = new MessageEmbed()
      .setColor("#0099ff")
      .setThumbnail(profileImage)
      .setTitle(shelfObj.name)
      .setDescription("Displaying shelf info")
      .addField("Owner", `<@${shelfObj.owner}>`)
      .addField("Info", shelfObj.info.length <= 0 ? "No information added" : shelfObj.info);

    // 从缓存取原书架消息id,更新消息内容
    const targetMsgId = shelfMsgCache.get(shelfName);
    if (targetMsgId) {
      const targetMsg = await interaction.channel.messages.fetch(targetMsgId);
      await targetMsg.edit({ embeds: [updatedEmbed] });
    }
  }
})

edit指令分支逻辑(移除内部事件绑定)

} else if (order == "edit") {
  let shelfNameSpaces = "";
  for (let i = 1; i < args.length; i++) {
    shelfNameSpaces += args[i] + " ";
  }
  const shelfName = shelfNameSpaces.trim();
  const shelfObj = await Shelves.findOne({ name: shelfName });
  if (shelfObj) {
    const embed = new MessageEmbed()
      .setColor("#0099ff")
      .setThumbnail(profileImage)
      .setTitle(shelfObj.name)
      .setDescription("Displaying shelf info")
      .addField("Owner", `<@${shelfObj.owner}>`)
      .addField("Info", shelfObj.info.length <= 0 ? "No information added" : shelfObj.info);
    const shelfMsg = await message.channel.send({ embeds: [embed] });
    // 缓存消息id对应关系
    shelfMsgCache.set(shelfName, shelfMsg.id);

    const row = new MessageActionRow().addComponents(
      new MessageButton()
        // customId拼接书架名,区分不同按钮
        .setCustomId(`edit-shelf-${shelfName}`)
        .setLabel("Edit Shelf")
        .setStyle("PRIMARY")
    );
    const buttonsMsg = await message.channel.send({ components: [row] });
    setTimeout(() => {
      if (buttonsMsg.deletable) buttonsMsg.delete();
    }, 60000);
  } else {
    const embed = new MessageEmbed()
      .setColor("#ff0000")
      .setTitle("Shelves")
      .setDescription("Unknown Shelf")
      .addField("Error", "Shelf not found");
    message.channel.send({ embeds: [embed] });
  }
}

内容的提问来源于stack exchange,提问作者Cr0wn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 22:09:19