Discord JS: bot reply via message button - javascript

I'm currently learning how to use Discords Buttons but I ran into a problem.
The following code should create a button with the command /startgame, when you press this button it should just say "hello".
const { SlashCommandBuilder } = require("#discordjs/builders")
const { MessageButton, MessageActionRow } = require("discord.js")
module.exports ={
data: new SlashCommandBuilder()
.setName("startgame")
.setDescription("Startet das Spiel"),
async execute(interactionCreate) {
if (interactionCreate.isCommand()) {
const row1 = new MessageActionRow()
.addComponents(
new MessageButton()
.setCustomId("start")
.setLabel(" Start ")
.setStyle(3)
.setDisabled(false),
)
await interactionCreate.reply({content: " ", components: [row1]})
}
else if(interactionCreate.isButton()) {
switch (interactionCreate.customId) {
case "start": {
return interactionCreate.reply("hello")
}
}
}
}
}
It creates the button, but when you press it, it only says: This interaction failed I appreciate any help.

OK so one of these parts should be under your event listener interactionCreate rather than in a command. So if you have that section in your main bot.js file it would look like this:
Command File
const {
SlashCommandBuilder
} = require("#discordjs/builders")
const {
MessageButton,
MessageActionRow
} = require("discord.js")
module.exports = {
data: new SlashCommandBuilder()
.setName("startgame")
.setDescription("Startet das Spiel"),
async execute(interaction) {
const row1 = new MessageActionRow()
.addComponents(
new MessageButton()
.setCustomId("start")
.setLabel(" Start ")
.setStyle(3)
)
return interaction.reply({
components: [row1]
})
}
}
bot.js file - could be named whatever you named it
client.on('interactionCreate', async interaction => {
if (interaction.isButton()) {
const buttonID = interaction.customId
if (buttonID === 'start') {
interaction.reply({
content: 'Hello'
})
}
}
})

Related

Discord.js Failing at linking up embeds with a SelectMenu builder

it might be a stupid question but since I'm a beginner I sadly don't understand most of how javascript works yet.
I'm trying to create a small Discord bot for a project that I'm working on and I would like to make it so you can select an option from a dropdown menu and whatever you select it sends a different embedded message in the channel. But whatever I do it doesn't send the embedded message. I've looked up multiple tutorials, but it seems like I'm missing something. Maybe someone can direct me in the right direction.
It is sending the dropdown menu, but sending error "InteractionAlreadyReplied". And whenever I try to select an option it won't send the embedded messages.
Here is my code so you can see the approach I'm using. I've tried using "MessageActionRow" and "MessageSelectMenu", but it seems like they don't work anymore. After inserting "ActionRowBuilder" and "StringSelectMenuBuilder" I got the menu working, but as said I don't get the embedded messages as an option. (didn't include the .env file since its only the token in there) I'm just not sure how I can make it so it connects the values from the dropdown menu to the right embeds.
CommandFile:
const { Discord, MessageActionRow, MessageSelectMenu,
SlashCommandBuilder, EmbedBuilder, roleMention, ButtonBuilder,
StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ActionRowBuilder } = require('discord.js');
const { MessageEmbed } = require("discord.js")
module.exports = {
data: new SlashCommandBuilder()
.setName('dropdown')
.setDescription('Returns a select dropdown!'),
async execute(interaction, client, message, args) {
const row = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId("select")
.setPlaceholder("Wähle etwas aus:")
.addOptions([
{
label: "Auswahl 1",
description: "Beschreibung Auswahl 1",
value: "first"
},
{
label: "Auswahl 2",
description: "Beschreibung Auswahl 2",
value: "second"
},
{
label: "Auswahl 3",
description: "Beschreibung Auswahl 3",
value: "third"
},
])
)
let embed = new EmbedBuilder()
.setTitle("Embed Startseite")
.setDescription("Ab hier kann man selecten, was man möchte")
.setColor(0x18e1ee)
let sendmsg = await interaction.reply({ content:
"ㅤ", ephemeral:true, embeds: [embed], components: [row] })
let embed1 = new EmbedBuilder()
.setTitle("Embed 1")
.setDescription("Embed 1 Beschreibung")
.setColor(0x18e1ee)
let embed2 = new EmbedBuilder()
.setTitle("Embed 2")
.setDescription("Embed 2 Beschreibung")
.setColor(0x18e1ee)
let embed3 = new EmbedBuilder()
.setTitle("Embed 3")
.setDescription("Embed 3 Beschreibung")
.setColor(0x18e1ee)
const collector = message.createMessageComponentCollector({
componentType: "StringSelectMenuBuilder"
})
collector.on("collect", async (collected) => {
const value = collected.values[0]
if(value === "first") {
collected.reply({ embeds: [embed1], ephemeral:true })
}
if(value === "second") {
collected.reply({ embeds: [embed2], ephemeral:true })
}
if(value === "third") {
collected.reply({ embeds: [embed3], ephemeral:true })
}
})
}
}
bot.js:
require("dotenv").config();
const { token } = process.env;
const { Client, Collection, GatewayIntentBits, ChannelSelectMenuInteraction } = require("discord.js");
const fs = require("fs");
const client = new Client({ intents: GatewayIntentBits.Guilds });
client.commands = new Collection();
client.buttons = new Collection();
client.selectMenus = new Collection();
client.commandArray = [];
const functionFolders = fs.readdirSync(`./src/functions`);
for (const folder of functionFolders) {
const functionFiles = fs
.readdirSync(`./src/functions/${folder}`)
.filter((file) => file.endsWith(".js"));
for (const file of functionFiles)
require(`./functions/${folder}/${file}`)(client);
}
client.handleEvents();
client.handleCommands();
client.handleComponents();
client.login(token);
HandleComponents.js :
const { readdirSync } = require('fs');
module.exports = (client) => {
client.handleComponents = async () => {
const componentFolders = readdirSync(`./src/components`);
for (const folder of componentFolders) {
const componentFiles = readdirSync(`./src/components/${folder}`).filter(
(file) => file.endsWith('.js')
);
const { buttons, selectMenus } = client;
switch (folder) {
case "buttons":
for (const file of componentFiles) {
const button = require(`../../components/${folder}/${file}`);
buttons.set(button.data.name, button);
}
break;
case "selectMenus":
for (const file of componentFiles) {
const menu = require(`../../components/${folder}/${file}`);
selectMenus.set(menu.data.name, menu);
}
break;
default:
break;
}
}
};
};
HandleCommands.js :
const { REST } = require('#discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const fs = require("fs");
module.exports = (client) => {
client.handleCommands = async () => {
const commandFolders = fs.readdirSync("./src/commands");
for (const folder of commandFolders) {
const commandFiles = fs
.readdirSync(`./src/commands/${folder}`)
.filter(file => file.endsWith(".js"));
const { commands, commandArray } = client;
for (const file of commandFiles) {
const command = require(`../../commands/${folder}/${file}`);
commands.set(command.data.name, command);
commandArray.push(command.data.toJSON());
}
}
const clientId = "IDbutCensored";
const guildId = "IDbutCensored";
const rest = new REST({ version: '9' }).setToken(process.env.token);
try {
console.log("Started refreshing application (/) commands.");
await rest.put(Routes.applicationGuildCommands(clientId, guildId), {
body: client.commandArray,
});
console.log("Successfully reloaded application (/) commands.");
} catch (error) {
console.error(error);
}
};
};
handleEvents.js :
const fs = require("fs");
module.exports = (client) => {
client.handleEvents = async () => {
const eventFolders = fs.readdirSync(`./src/events`);
for (const folder of eventFolders) {
const eventFiles = fs
.readdirSync(`./src/events/${folder}`)
.filter((file) => file.endsWith(".js"));
switch (folder) {
case "client":
for (const file of eventFiles) {
const event = require(`../../events/${folder}/${file}`);
if (event.once) client.once(event.name, (...args) => event.execute(...args, client));
else client.on(event.name, (...args) => event.execute(...args, client));
}
break;
default:
break;
}
}
}
}
The error is pretty self explanatory. The error InteractionAlreadyReplied means you already replied and you're trying to reply again.
You can solve this by doing the following;
• Use .followUp() to send a new message
• If you deferred reply it's better to use .editReply()
• Information about responding to slash commands / buttons / select menus
From the Discord docs

Errors making a slash command that replys with a message (that edits itself every second) and buttons that send a message on press

I am trying to make a slash command (discord.js 14) where it creates an embed (of the game state) which has buttons. If you press a button, I want the bot to notify the user with an ephemeral message.
With my current system, it seems I can either have buttons that reply to the button press OR an embed that counts down. I would like to have both. How would I go about doing that?
I have tried changing the interaction.reply (within the client.on(`interactionCreate...)) to followUp and deferReply but the issue, from my understanding, is once the embed is edited OR a button press happens, the code is like "We already replied" or its confused on how to reply?
Code for Slash command
const {
SlashCommandBuilder,
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
EmbedBuilder,
} = require(`discord.js`);
const wait = require(`node:timers/promises`).setTimeout;
module.exports = {
data: new SlashCommandBuilder()
// Command Info
.setName("race")
.setDescription("Starts a Horse Race!"),
async execute(interaction, client) {
// Game variables
const time = 10;
const players = {
Hearts: [],
Diamonds: [],
Spades: [],
Clubs: [],
};
// Embed Creation
const embed = new EmbedBuilder()
.setTitle("Welcome to horse race, select a suit below!")
.setDescription(`The race will begin in ${time} seconds`);
const buttonArray = [
new ButtonBuilder()
.setLabel(`Hearts`)
.setCustomId(`hearts-button`)
.setEmoji(`❤️`)
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setLabel(`Diamonds`)
.setCustomId(`diamonds-button`)
.setEmoji(`♦️`)
.setStyle(ButtonStyle.Success),
new ButtonBuilder()
.setLabel(`Spades`)
.setCustomId(`spades-button`)
.setEmoji(`♠️`)
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setLabel(`Clubs`)
.setCustomId(`clubs-button`)
.setEmoji(`♣️`)
.setStyle(ButtonStyle.Success),
];
// Button press listener
await client.on("interactionCreate", (interaction) => {
if (!interaction.isButton()) return;
player = interaction.user.id;
// Makes the choice into a word instead of the buttons id
choice = interaction.customId.split("-");
choice = choice[0];
choice = choice.charAt(0).toUpperCase() + choice.slice(1);
// Create message to send on button press
msg = {
content: `You voted for ${choice}`,
ephemeral: true,
};
// If player has already pressed a button this command use
// Reply and don't add them to the players array
if (
players.Hearts.includes(player) ||
players.Diamonds.includes(player) ||
players.Spades.includes(player) ||
players.Clubs.includes(player)
) {
interaction.channel.reply({
content: `You already voted this round.`,
ephemeral: true,
});
// Else, add them to the array and respond so the user knows their press went through
} else {
players[choice].push(player);
interaction.reply(msg);
}
//console.log(interaction)
//console.log(players)
});
// Original message that is displayed on initial call
msg = {
embeds: [embed],
components: [
new ActionRowBuilder()
.addComponents(buttonArray[0])
.addComponents(buttonArray[1])
.addComponents(buttonArray[2])
.addComponents(buttonArray[3]),
],
};
// Send original message
await interaction.reply(msg);
// Count down and edit embed with a timer
for (let i = 1; i <= time; i++) {
await wait(1000);
// Remove s on seconds when at 1 second
msg.embeds[0].setDescription(
`The race will begin in ${time - i} seconds`
);
await interaction.editReply(msg);
}
},
};
interactionCreate.js
const { InteractionType } = require(`discord.js`);
module.exports = {
name: "interactionCreate",
async execute(interaction, client) {
if (interaction.isChatInputCommand()) {
const { commands } = client;
const { commandName } = interaction;
const command = commands.get(commandName);
if (!command) return;
try {
await command.execute(interaction, client);
} catch (error) {
console.error(error);
await interaction.reply({
content: `Something went wrong`,
ephemeral: true,
});
}
} else if (interaction.isButton()) {
const { buttons } = client;
const { customId } = interaction;
const button = buttons.get(customId);
if (!button) return new Error(`Button error`);
try {
await button.execute(interaction, client);
} catch (error) {
console.error(error);
}
} else if (interaction.isSelectMenu()) {
const { selectMenus } = client;
const { customId } = interaction;
const menu = selectMenus.get(customId);
if (!menu) return new Error(`Menu error`);
try {
await menu.execute(interaction, client);
} catch (error) {
console.error(error);
}
} else if (interaction.type == InteractionType.ModalSubmit) {
const { modals } = client;
const { customId } = interaction;
const modal = modals.get(customId);
if (!modal) return new Error(`Modal error`);
try {
await modal.execute(interaction, client);
} catch (error) {
console.error(error);
}
} else if (interaction.isContextMenuCommand()) {
const { commands } = client;
const { commandName } = interaction;
const contextCommand = commands.get(commandName);
if (!contextCommand) return;
try {
await contextCommand.execute(interaction, client);
} catch (error) {
console.log(error);
}
}
},
};
Error
TypeError: Cannot read properties of undefined (reading '0')
at Object.execute (/home/aleksander/Desktop/discord-bot/src/commands/tools/startHorseRace.js:85:23)
at async Object.execute (/home/aleksander/Desktop/discord-bot/src/events/client/interactionCreate.js:13:9)
node:events:491
throw er; // Unhandled 'error' event
^
Error [InteractionAlreadyReplied]: The reply to this interaction has already been sent or deferred.
at ChatInputCommandInteraction.reply (/home/aleksander/Desktop/discord-bot/node_modules/discord.js/src/structures/interfaces/InteractionResponses.js:102:46)
at Object.execute (/home/aleksander/Desktop/discord-bot/src/events/client/interactionCreate.js:16:27)
Emitted 'error' event on Client instance at:
at emitUnhandledRejectionOrErr (node:events:394:10)
at processTicksAndRejections (node:internal/process/task_queues:85:21) {
code: 'InteractionAlreadyReplied'
}

Struggling as to what I need to add into my code so that when the user enters a specific password they get a role (discord.js)

Hello here is my code currently, I can type any word or number phrase and it brings up a button which says enter password I click it and it brings up a modal, My issue is I can enter anything into the modal and it accepts it. I want it to only accept a specific password or number phrase and if the password is correct it gives a specific role.
I am unsure what I need to add into my code to make this happen.
const {
Client,
Intents,
MessageActionRow,
MessageButton,
Modal,
TextInputComponent,
} = require('discord.js');
const TOKEN = 'My Token';
const client = new Client({
intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES],
});
client.on('messageCreate', (message) => {
if (message.author.bot) return;
let button = new MessageActionRow();
button.addComponents(
new MessageButton()
.setCustomId('verification-button')
.setStyle('PRIMARY')
.setLabel('Enter Password...'),
);
message.reply({
components: [button],
});
});
client.on('interactionCreate', async (interaction) => {
if (interaction.isButton()) {
if (interaction.customId === 'verification-button') {
const modal = new Modal()
.setCustomId('verification-modal')
.setTitle('Verify yourself')
.addComponents([
new MessageActionRow().addComponents(
new TextInputComponent()
.setCustomId('verification-input')
.setLabel('Answer')
.setStyle('SHORT')
.setMinLength(4)
.setMaxLength(12)
.setPlaceholder('111111')
.setRequired(true),
),
]);
await interaction.showModal(modal);
}
}
if (interaction.isModalSubmit()) {
if (interaction.customId === 'verification-modal') {
const response =
interaction.fields.getTextInputValue('verification-input');
interaction.reply(`Yay, your answer is submitted: "${response}"`);
}
}
});
client.once('ready', () => {
console.log('Bot v13 is connected...');
});
client.login(TOKEN);
if (interaction.isModalSubmit()) {
if (interaction.customId === 'verification-modal') {
const response =
interaction.fields.getTextInputValue('verification-input');
if (response === "Correct answer") {
interaction.reply(`Yay, your answer is correct: "${response}"`);
interaction.member.roles.add(...)
} else {
interaction.reply(`Oh no, your answer is incorrect: "${response}"`);
}
}
}

Can I import the value of an interaction into another file?

I'm trying to make a draft bot that takes the player amount from an interaction in my draftSize.js file and uses that value in my join.js file as a maximum value for my MessageComponentCollector.
Here is draftSize.js
const { SlashCommandBuilder } = require('#discordjs/builders');
const { MessageActionRow, MessageButton } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('size')
.setDescription('Sets the size of total players in your draft.')
.addIntegerOption(option =>
option.setName('players')
.setDescription('Amount of players participating in the draft.')
.setRequired(true)),
async execute(interaction) {
let playerSize = interaction.options.getInteger('players');
await interaction.reply({ content: `You\'ve selected a ${playerSize} player draft. \n Next, type: ` + "```" + '/captains {#FirstCaptain} {#SecondCaptain}' + "```"});
console.log("test " + playerSize)
},
};
And here is join.js:
const { SlashCommandBuilder } = require('#discordjs/builders');
const { MessageActionRow, MessageButton, Message } = require('discord.js');
const { playerSize } = require('./draftSize');
module.exports = {
data: new SlashCommandBuilder()
.setName('join')
.setDescription('Allow players to join your draft.'),
async execute(interaction, channel) {
const row = new MessageActionRow()
.addComponents(
new MessageButton()
.setCustomId('joinButton')
.setLabel('Join')
.setStyle('SUCCESS'),
);
await interaction.reply({ content: `To join the draft, click the button below. Once the specified player amount has joined, the draft will begin!`, components: [row] });
const filter = i => {
return i.customId === 'joinButton'
};
const playerList = []
const collectChannel = interaction.channel;
const collector = collectChannel.createMessageComponentCollector({
filter,
max: playerSize,
time: 1000 * 30
})
collector.on('collect', (i = MessageComponentInteraction) => {
i.reply({
content: 'you clicked a button',
ephemeral: true
})
console.log(playerSize)
})
collector.on('end', (collection) => {
collection.forEach((click) => {
playerList.push(click.user.username)
})
collectChannel.send({ content: 'testing'})
console.log(playerList);
})
},
};
In my draftSize.js I get my playerSize value, but whenever I try to import the value into my join.js file to use as my collectors max value it always returns undefined.
Sorry if any of this was unclear. Let me know if you need any clarification.
Thanks!

TypeError: command.run is not a function

I'm kinda new to coding and i cannot figure this out. im trying to add permission handler and when i run a slash command, it responds within the error given below.
error:
D:\VS Code\######\events\interactionCreate.js:9
command.run(client, inter)
^
TypeError: command.run is not a function
code (interactionCreate):
const { MessageEmbed, Message, Interaction } = require("discord.js")
const client = require("../index").client
client.on('interactionCreate', async inter => {
if(inter.isCommand()) {
const command = client.commands.get(inter.commandName);
if(!command) return;
command.run(client, inter)
if (command.help?.permission) {
const authorPerms = inter.channel.permissionsFor(inter.member);
if (!authorPerms || !authorPerms.has(command.permission)) {
const noPerms = new MessageEmbed()
.setColor('RED')
.setDescription(`bruh no perms for ya: ${command.permission}`)
return inter.editReply({embeds: [noPerms], ephemeral: true})
.then((sent) =>{
setTimeout(() => {
sent.delete()
}, 10000)
})
return;
}
}
}
}
)
this is how one of my command file looks like, its a handler which goes "commands > moderation > ban.js"
const { MessageEmbed, Message } = require("discord.js")
module.exports.run = async (client, inter) => {
const user = inter.options.getUser('user')
let BanReason = inter.options.getString('reason')
const member = inter.guild.members.cache.get(user.id)
if(!BanReason) reason = 'No reason provided.'
const existEmbed = new MessageEmbed()
.setColor('#2f3136')
.setDescription('<:pending:961309491784196166> The member does not exist in the server.')
const bannedEmbed = new MessageEmbed()
.setColor('#46b281')
.setDescription(`<:success:961309491935182909> Successfully banned **${user.tag}** from the server with the reason of: **${BanReason}**.`)
const failedEmbed = new MessageEmbed()
.setColor('#ec4e4b')
.setDescription(`<:failed:961309491763228742> Cannot ban **${user.tag}**. Please make sure I have permission to ban.`)
if(!member) return inter.reply({ embeds: [existEmbed]})
try {
await inter.guild.members.ban(member, { reasons: BanReason })
} catch(e) {
return inter.reply({ embeds: [failedEmbed]})
}
inter.reply({ embeds: [bannedEmbed]})
}
module.exports.help = {
name: 'ban',
permission: 'BAN_MEMBERS'
}
btw inter = interaction

Categories