Discord.js添加按钮后报Interaction已确认API错误排查
报错根因
DiscordAPIError: Interaction has already been acknowledged 本质是同一个Discord交互被重复响应,你怀疑的异步时序问题确实存在,代码里一共4处直接触发这个报错的问题:
- 条件判断漏写花括号:
if (!interaction.isButton())后没有包裹代码块,JS默认仅将紧邻的下一行纳入判断范围,后续的接口请求、发消息逻辑在按钮交互触发时也会执行,导致重复响应。 - 斜杠命令响应超时:Discord规定斜杠交互必须在触发后3秒内返回响应,你当前逻辑是等TMDB接口返回数据后才调用
interaction.reply(),接口耗时超过3秒时交互会被Discord自动标记为已处理,再调用reply就会触发报错。 - 收集器过滤器逻辑错误:
i => i.customId === 'red' || 'orange' || 'blue' || 'green'是无效写法,非空字符串在JS里是真值,这个过滤器会收进频道内所有按钮交互,极易误响应其他交互触发报错。 - 收集器绑定范围错误:你用
interaction.channel.createMessageComponentCollector给整个频道绑定收集器,每触发一次命令就会新增一个收集器,多次触发后多个收集器会同时响应同一个按钮点击,导致重复操作交互。
修复后可直接运行的代码
async execute(interaction) { if(!interaction.isCommand()) return; // 提前延迟响应,将交互响应窗口延长至15分钟,彻底解决3秒超时问题 await interaction.deferReply(); const getChoices = interaction.options.getString('ne_öğrenmek_istersin'); if(getChoices === 'popüler') { // 统一用await处理异步,替换then链式调用避免时序混乱 const res = await moviedb.moviePopular({ language: 'tr'}) const cizgi = '`' const num = Math.floor(Math.random () * 3) const row = new MessageActionRow() .addComponents( new MessageButton().setCustomId('red').setLabel('🟥').setStyle('PRIMARY'), new MessageButton().setCustomId('orange').setLabel('🟧').setStyle('PRIMARY'), new MessageButton().setCustomId('blue').setLabel('🟦').setStyle('PRIMARY'), new MessageButton().setCustomId('green').setLabel('🟩').setStyle('PRIMARY'), ); const embed = new MessageEmbed() .setColor('#f4900c') .setThumbnail(`https://image.tmdb.org/t/p/original/${res.results[num].poster_path}`) .setTimestamp() .setTitle('Popüler Filmler') .addFields( {name: `🟥 __${res.results[0].title}__`, value: `>>> 📜${cizgi}${res.results[0].release_date}${cizgi} 🌟${cizgi}${res.results[0].vote_average}${cizgi} 🌐 ${res.results[0].original_language}`, inline: true}, {name: `🟧 __${res.results[1].title}__`, value: `>>> 📜${cizgi}${res.results[1].release_date}${cizgi} 🌟${cizgi}${res.results[1].vote_average}${cizgi} 🌐 ${res.results[1].original_language}`, inline: true}, {name: `\u200B`, value: `\u200B`}, {name: `🟦 __${res.results[2].title}__`, value: `>>> 📜${cizgi}${res.results[2].release_date}${cizgi} 🌟${cizgi}${res.results[2].vote_average}${cizgi} 🌐 ${res.results[2].original_language}`, inline: true}, {name: `🟩 __${res.results[3].title}__`, value: `>>> 📜${cizgi}${res.results[3].release_date}${cizgi} 🌟${cizgi}${res.results[3].vote_average}${cizgi} 🌐 ${res.results[3].original_language}`, inline: true}, {name: 'Filmler Hakkında Detaylı Bilgi İçin Alttaki Butonları Kullanabilirsiniz', value: '-------------------------------------------------------------------------------'}, ); // 修复过滤器逻辑,仅响应当前消息上的四个颜色按钮 const filter = i => ['red', 'orange', 'blue', 'green'].includes(i.customId); // 发送消息后拿到消息实例,仅给当前消息绑定收集器,不污染整个频道 const replyMsg = await interaction.editReply({embeds: [embed], components: [row]}); const collector = replyMsg.createMessageComponentCollector({ filter, time: 15000 }); collector.on('collect', async i => { // 按钮交互先占位响应,避免按钮点击3秒超时 await i.deferUpdate(); const btnIndexMap = {red:0, orange:1, blue:2, green:3}; const targetIndex = btnIndexMap[i.customId]; const tırnak = '`', space = ' '; const detailEmbed = new MessageEmbed() .setColor('DARK_GOLD') .setThumbnail(`https://image.tmdb.org/t/p/original${res.results[targetIndex].poster_path}`) .setImage(`https://image.tmdb.org/t/p/original${res.results[targetIndex].backdrop_path}`) .setTitle(`Film`) .setDescription(`__**${res.results[targetIndex].title}**__`) .addFields( { name: `Film Hakkında Bilgiler\n----------------------------`, value: `**Orjinal İsmi:** ${tırnak}${res.results[targetIndex].original_title}${tırnak}\n${space}\n**Orjinal Dili:** ${tırnak}${res.results[targetIndex].original_language}${tırnak} **Yapım Yılı:** ${tırnak}${res.results[targetIndex].release_date}${tırnak} **Puanı:**${tırnak}☆ ${res.results[targetIndex].vote_average}${tırnak}`}, ) .addField(`Özet:`,`${tırnak}${res.results[targetIndex].overview}${tırnak}`); await i.editReply({ embeds: [detailEmbed], components: []}); }); collector.on('end', collected => console.log(`Collected ${collected.size} items`)); } else if(getChoices === 'vizyondakiler') { await interaction.editReply('Vizyondakiler !') } }
关键改动说明
- 所有交互在做耗时操作前先调用
deferReply/deferUpdate占位,从根源解决超时导致的已确认报错。 - 统一用
await处理异步逻辑,去掉then混写的方式,避免时序错乱。 - 修正过滤器和收集器绑定范围,不会误响应其他交互。
- 调用
deferReply后所有原交互的响应用editReply,不会重复调用reply触发报错。 - 去掉了重复的embed声明代码,用映射表匹配按钮对应的电影下标,减少冗余代码。
内容的提问来源于stack exchange,提问作者n00b
相关产品推荐
相关产品推荐

