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异常
修复方案
- 将所有
interactionCreate事件监听器移到edit指令逻辑外,在客户端初始化阶段全局只注册一次,禁止在指令执行分支内重复绑定事件 - 给按钮、模态框的customId拼接对应书架的唯一标识(比如书架名),通过customId前缀区分不同交互场景,避免串扰
- 所有交互响应方法(
showModal/deferUpdate/reply等)前统一加await,保证异步时序正确 - 修正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
相关产品推荐
相关产品推荐

