javascriptnode.jsdiscorddiscord.jsgoogle-image-search

How can I make this slash command a message (prefix) command?


I'm hosting a Discord bot for a small private server of mine with the purpose of pulling a random image from Google Images search results. I've been using this repository as the basis, and after fixing the outdated intents in index.js it has been working great. However, this bot only supports slash commands, but I also want a message command (otherwise known as a prefix command) to be an alternative option for users.

Here is the relevant code for performing an image search:

client.on('interactionCreate', async interaction => {
    if (!interaction.isCommand()) return;

    const { commandName } = interaction;

    if (commandName === 'search') {
        query = interaction.options.getString('input');
        google.search(query)
        .then(images => {
            const random_idx = Math.floor(Math.random() * images.length);
            interaction.reply(images[random_idx].url); // random image from result
        });
    }
});

Essentially, I want this exact same function to occur if the user does .im query instead of /search query. I am very new to JavaScript and I've come to realize that slash commands are handled completely differently from message commands, so I have no idea where to even begin. If someone could assist I'd greatly appreciate it. Thank you!


Solution

  • What you could do if you want both is move the command logic into a function like so:

    function searchImages(query) {
        google.search(query).then(images => {
            const random_idx = Math.floor(Math.random() * images.length);
            interaction.reply(images[random_idx].url); // random image from result
        });
    }
    

    Then as we don't have access to the interaction, and won't have an interaction but a message for the prefixed command, we can make it return a Promise, like so:

    function searchImages(query) {
        return new Promise((resolve,reject) => {
            google.search(query).then((images) => {
                const random_idx = Math.floor(Math.random() * images.length);
                resolve(images[random_idx].url);
            }).catch(reject);
        });
    }
    

    Then make the interactionCreate listener use the function:

    client.on('interactionCreate', async interaction => {
        if (!interaction.isCommand()) return;
    
        const { commandName } = interaction;
    
        if (commandName === 'search') {
            // I don't know if you missed it or if you have it in the file as a var but we can safely make the query be stored in a const
            const query = interaction.options.getString('input');
            interaction.deferReply(); // we should defer as we are doing asynchronous calls otherwise we could timeout
            searchImages(query).then((image) => {
                interaction.editReply(image); // return the image url
            }).catch((e) => {
                // optionally log the error to console
                console.error(e);
                interaction.editReply('Uh oh, an error occurred');
            });
        }
    });
    

    And now we make the prefixed command:

    client.on('messageCreate', (message) => {
        const msg = message.content;
        if (!msg.startsWith('.')) return;
        const args = msg.split(' '); // split on spaces to get the arguments
        const command = args.shift()?.substring(1); // remove the first element (the name) and return it then remove the dot
        if (command == 'im') {
            searchImages(args.join(' ')).then((image) => {
                message.reply(image);
            }).catch((e) => {
                // again optionally log to console
                console.error(e);
                message.reply('Uh oh, an error occurred');
            });
        }
    });
    

    So the finished result would be:

    function searchImages(query) {
        return new Promise((resolve,reject) => {
            google.search(query).then((images) => {
                const random_idx = Math.floor(Math.random() * images.length);
                resolve(images[random_idx].url);
            }).catch(reject);
        });
    }
    
    client.on('interactionCreate', async interaction => {
        if (!interaction.isCommand()) return;
    
        const { commandName } = interaction;
    
        if (commandName === 'search') {
            const query = interaction.options.getString('input');
            interaction.deferReply(); // we should defer as we are doing asynchronous calls otherwise we could timeout
            searchImages(query).then((image) => {
                interaction.editReply(image); // return the image url
            }).catch((e) => {
                // optionally log the error to console
                console.error(e);
                interaction.editReply('Uh oh, an error occurred');
            });
        }
    });
    
    client.on('messageCreate', (message) => {
        const msg = message.content;
        if (!msg.startsWith('.')) return;
        const args = msg.split(' '); // split on spaces to get the arguments
        const command = args.shift()?.substring(1); // remove the first element (the name) and return it then remove the dot
        if (command == 'im') {
            searchImages(args.join(' ')).then((image) => {
                message.reply(image);
            }).catch((e) => {
                // again optionally log to console
                console.error(e);
                message.reply('Uh oh, an error occurred');
            });
        }
    });
    

    Doing this should all work, however I cannot currently test it myself.