Reputation: 1
I am making a JS discord bot that when a command is run, an embed is displayed with a button row underneath the embed. Im having some problems when multiple users run the same command or when the same user runs the command twice in a row; discord throws an Unknown Interaction.
// utilities/buttonHandler.js VERSION 5
const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const crypto = require('crypto'); // To generate unique IDs
const activeCollectors = new Map(); // Map to track active collectors
function generateUniqueId(prefix) {
return `${prefix}_${crypto.randomUUID()}`;
}
async function handleButtonInteraction(interaction, customId, user1, user2, client) {
// Disable all buttons immediately after the interaction
const buttons = [
new ButtonBuilder().setCustomId(generateUniqueId('createCase')).setLabel('Create Case').setStyle(ButtonStyle.Success).setDisabled(true),
new ButtonBuilder().setCustomId(generateUniqueId('generateLeads')).setLabel('Investigate Further').setStyle(ButtonStyle.Primary).setDisabled(true),
new ButtonBuilder().setCustomId(generateUniqueId('actionLead')).setLabel('Action User').setStyle(ButtonStyle.Danger).setDisabled(true),
new ButtonBuilder().setCustomId(generateUniqueId('nothing')).setLabel('Close & Exit').setStyle(ButtonStyle.Secondary).setDisabled(true)
];
const disabledRow = new ActionRowBuilder().addComponents(buttons);
await interaction.editReply({ components: [disabledRow] }); // Update message to disable buttons
const action = customId.split('_')[0]; // Extract the action from the custom ID
switch (action) {
case 'createCase':
`<Function Here>`
break;
case 'generateLeads':
`<Function Here>`
break;
case 'actionLead':
`<Function Here>`
break;
case 'uploadAvatar':
`<Function Here>`
break;
case 'nothing':
await interaction.followUp('Closing interaction...');
break;
}
}
async function buttonHandler(command, interaction, user1, user2) {
// Check for active interaction
if (activeCollectors.has(interaction.user.id)) {
// Automatically select the "nothing" option for the current interaction
const currentCollector = activeCollectors.get(interaction.user.id);
// Call the function to handle the "nothing" button interaction
await handleButtonInteraction(interaction, 'nothing', user1, user2);
// Stop the current interaction
currentCollector.stop();
}
// Define button config for each command
let buttons;
if (command === 'null') {
buttons = [
new ButtonBuilder().setCustomId(generateUniqueId('errorButton')).setLabel('Error Displaying Button Menu').setStyle(ButtonStyle.Danger).setDisabled(true)
];
} else if (command === 'comparepfp') {
buttons = [
new ButtonBuilder().setCustomId(generateUniqueId('createCase')).setLabel('Create Case').setStyle(ButtonStyle.Success),
new ButtonBuilder().setCustomId(generateUniqueId('generateLeads')).setLabel('Investigate Further').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId(generateUniqueId('actionLead')).setLabel('Action User').setStyle(ButtonStyle.Danger),
new ButtonBuilder().setCustomId(generateUniqueId('nothing')).setLabel('Close & Exit').setStyle(ButtonStyle.Secondary)
];
} else if (command === 'ping') {
buttons = [
new ButtonBuilder().setCustomId(generateUniqueId('createPing')).setLabel('Ping').setStyle(ButtonStyle.Success),
new ButtonBuilder().setCustomId(generateUniqueId('createPong')).setLabel('Pong').setStyle(ButtonStyle.Primary)
];
} else if (command === 'avatar') {
buttons = [
new ButtonBuilder().setCustomId(generateUniqueId('uploadAvatar')).setLabel('Attach to A Case').setStyle(ButtonStyle.Success),
new ButtonBuilder().setCustomId(generateUniqueId('archiveAvatar')).setLabel('Archive Avatar').setStyle(ButtonStyle.Primary),
new ButtonBuilder().setCustomId(generateUniqueId('nothingAvatar')).setLabel('Close & Exit').setStyle(ButtonStyle.Secondary)
];
}
const actionRow = new ActionRowBuilder().addComponents(buttons);
//const filter = i => i.user.id === interaction.user.id; // Filter to restrict interactions
const collector = interaction.channel.createMessageComponentCollector({ /*filter,*/ time: parseInt(process.env.SPAM_COOLDOWN, 10) * 1000 || 5000 }); // Collector time
activeCollectors.set(interaction.user.id, collector); // Store the collector for this user
collector.on('collect', async i => {
if (i.user.id !== interaction.user.id) {
return i.reply({ content: `**<:no:1297791314645090316>\xa0\xa0You cannot use this button because you did not initiate this command!**`, ephemeral: true }); // Deny interaction for unauthorized users
//return; // Stop processing if the user is not the command initiator
}
console.log('Button interaction:', i.customId);
await i.deferUpdate(); // Defer the interaction update
// Call the function to handle the button interaction
await handleButtonInteraction(interaction, i.customId, user1, user2, interaction.client);
// After handling the interaction, disable the buttons
const disabledButtons = buttons.map(button => button.setDisabled(true));
await interaction.editReply({ components: [new ActionRowBuilder().addComponents(disabledButtons)] });
collector.stop();
});
collector.on('end', async () => {
// Ensure buttons are disabled when collector ends
const disabledButtons = buttons.map(button => button.setDisabled(true));
//await interaction.editReply({ components: [disabledRow] });
await interaction.editReply({ components: [new ActionRowBuilder().addComponents(disabledButtons)] });
activeCollectors.delete(interaction.user.id); // Clean up the map
});
// Return action row to be used in reply
return actionRow;
}
module.exports = buttonHandler;
NOTE: This error only happen when multiple users run the commands that provide these buttons at the same time OR when the same user runs the command twice. If the buttons are interacted with before another command is run this error does not occur.
Logged command: avatar by small.fry
Button interaction: nothingAvatar_82c80203-0298-4a80-ba23-82db49d8f2dd
Logged command: avatar by small.fry
Error [InteractionNotReplied]: The reply to this interaction has not been sent or deferred.
at ChatInputCommandInteraction.editReply (E:\HydraFusion-Bot\node_modules\discord.js\src\structures\interfaces\InteractionResponses.js:161:48)
at handleButtonInteraction (E:\HydraFusion-Bot\utilities\buttonHandler.js:29:23)
at buttonHandler (E:\HydraFusion-Bot\utilities\buttonHandler.js:106:15)
at Object.execute (E:\HydraFusion-Bot\commands\avatar.js:47:33)
at Client.<anonymous> (E:\HydraFusion-Bot\index.js:88:19)
at Client.emit (node:events:531:35)
at InteractionCreateAction.handle (E:\HydraFusion-Bot\node_modules\discord.js\src\client\actions\InteractionCreate.js:97:12)
at module.exports [as INTERACTION_CREATE] (E:\HydraFusion-Bot\node_modules\discord.js\src\client\websocket\handlers\INTERACTION_CREATE.js:4:36)
at WebSocketManager.handlePacket (E:\HydraFusion-Bot\node_modules\discord.js\src\client\websocket\WebSocketManager.js:348:31)
at WebSocketManager.<anonymous> (E:\HydraFusion-Bot\node_modules\discord.js\src\client\websocket\WebSocketManager.js:232:12) {
code: 'InteractionNotReplied'
}
Logged command: avatar by small.fry
Error [InteractionNotReplied]: The reply to this interaction has not been sent or deferred.
at ChatInputCommandInteraction.editReply (E:\HydraFusion-Bot\node_modules\discord.js\src\structures\interfaces\InteractionResponses.js:161:48)
at handleButtonInteraction (E:\HydraFusion-Bot\utilities\buttonHandler.js:29:23)
at buttonHandler (E:\HydraFusion-Bot\utilities\buttonHandler.js:106:15)
at Object.execute (E:\HydraFusion-Bot\commands\avatar.js:47:33)
at Client.<anonymous> (E:\HydraFusion-Bot\index.js:88:19)
at Client.emit (node:events:531:35)
at InteractionCreateAction.handle (E:\HydraFusion-Bot\node_modules\discord.js\src\client\actions\InteractionCreate.js:97:12)
at module.exports [as INTERACTION_CREATE] (E:\HydraFusion-Bot\node_modules\discord.js\src\client\websocket\handlers\INTERACTION_CREATE.js:4:36)
at WebSocketManager.handlePacket (E:\HydraFusion-Bot\node_modules\discord.js\src\client\websocket\WebSocketManager.js:348:31)
at WebSocketManager.<anonymous> (E:\HydraFusion-Bot\node_modules\discord.js\src\client\websocket\WebSocketManager.js:232:12) {
code: 'InteractionNotReplied'
}
Logged command: avatar by small.fry
Button interaction: nothingAvatar_f5f3610d-6657-41fe-b6e7-2e630361c90c
Logged command: avatar by its_weebow
Logged command: avatar by small.fry
Button interaction: archiveAvatar_5370bd72-9bba-4bf1-ba91-f60b81a68db5
E:\HydraFusion-Bot\node_modules\@discordjs\rest\dist\index.js:727
throw new DiscordAPIError(data, "code" in data ? data.code : data.error, status, method, url, requestData);
^
DiscordAPIError[10062]: Unknown interaction
at handleErrors (E:\HydraFusion-Bot\node_modules\@discordjs\rest\dist\index.js:727:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async BurstHandler.runRequest (E:\HydraFusion-Bot\node_modules\@discordjs\rest\dist\index.js:831:23)
at async _REST.request (E:\HydraFusion-Bot\node_modules\@discordjs\rest\dist\index.js:1272:22)
at async ButtonInteraction.reply (E:\HydraFusion-Bot\node_modules\discord.js\src\structures\interfaces\InteractionResponses.js:115:5) {
requestBody: {
files: [],
json: {
type: 4,
data: {
content: '**<:no:1297791314645090316> You cannot use this button because you did not initiate this command!**',
tts: false,
nonce: undefined,
enforce_nonce: false,
embeds: undefined,
components: undefined,
username: undefined,
avatar_url: undefined,
allowed_mentions: undefined,
flags: 64,
message_reference: undefined,
attachments: undefined,
sticker_ids: undefined,
thread_name: undefined,
applied_tags: undefined,
poll: undefined
}
}
},
rawError: { message: 'Unknown interaction', code: 10062 },
code: 10062,
status: 404,
method: 'POST',
url: 'https://discord.com/api/v10/interactions/1328154629439094835/aW50ZXJhY3Rpb246MTMyODE1NDYyOTQzOTA5NDgzNTpWUGRjS3VORllVandHeWQ0QjhMSE5heTgxOXc5eUt6MGZvZnFNOWlXMFhkbEpmUkhtcERzdHlqS3lhbkdrWTE5dHE3cU92TTFTbkpNMFNWT2VtN0VwVXhNTHBWRThoV0xuZmtUUHg0bjluTm5QalVoaHZlajdiYWtGallKMURPag/callback'
}
Node.js v20.18.0
As you can see above I've already started using crypto to serialize the button custom IDs themselves, which is working fine, but this is not changing the outcome. I need the buttons for each reply to function independently of each-other and specifically to their own interaction but this did not help.
Upvotes: 0
Views: 40