Search code examples
javascriptdrop-down-menudiscord.jsembedjquery-ui-selectmenu

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.

The error inside VSCode

Menu works, but whatever I select it says "interaction failed"

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;
            }
        }
    }
}

Solution

  • 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