DEV Community

Swislok-Dev
Swislok-Dev

Posted on

Setting Up Slash Commands For Discord Bot

Getting Started

Having a head start with this bot already we should have certain dependencies install, if not here they are:

npm install @discordjs/builders @discordjs/rest discord-api-types

These will be used in creating the Slash Commands for use in Discord.

This blog assumes you have already read what has been built previously in this post here

You will also need to have generated a new URL that include "bot" and "applications.commands" in order to create commands for the Discord server to used for the bot.

deploy-commands.js

const { SlashCommandBuilder } = require('@discordjs/builders')
const { REST } = require('@discordjs/rest')
const { Routes } = require('discord-api-types/v9')
const { clientId, guildId, token } = require('./config.json')
const rest = new REST({ version: '9' }).setToken(token)

const commands = [
  new SlashCommandBuilder()
    .setName('ping')
    .setDescription('Replies with pong'),
  new SlashCommandBuilder()
    .setName('server')
    .setDescription('Replies with server info'),
  new SlashCommandBuilder()
    .setName('user')
    .setDescription('Replies with user info'),
].map((command) => command.toJSON())

rest
  .put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
  .then(() => console.log('Successfully registered application commands.'))
  .catch(console.error())
Enter fullscreen mode Exit fullscreen mode

The clientId is taken from the developer portal for the bot and the guildId is which guild you would like to target these commands for.

There is a way to create commands which will omit the guildId in order to pass for all servers that the bot joins.

index.js

The following is to be added after the client has been called on initialization.

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

  const { commandName } = interaction

  if (commandName === 'ping') {
    await interaction.reply('Pong!')
  } else if (commandName === 'server') {
    await interaction.reply(`
      Server name: ${interaction.guild.name}\n
      Total members: ${interaction.guild.memberCout}\n
      Server created on: ${interaction.guild.createdAt}
    `)
  } else if (commandName === 'user') {
    await interaction.reply(`
      Your tag: ${interaction.user.tag}\n
      Your id: ${interaction.user.id}
    `)
  }
})
Enter fullscreen mode Exit fullscreen mode

You should now run node deploy-commands.js. This will only need to be run once unless you change anything how the command is built or what server you want the commands deployed to.

Running the bot in the server with the guildId that was used, in the text field placing a "/" should now have commands that you created for use by your bot. Using the slash commands will show the command name and the description as you have written it.

Refactorize!

If you're like me and tend to add a ton of commands to help out the general moderation of a server then you'll get a bit tired of writing commands out like this. Not to mention how messy things will end up getting.

We can create a few directories and refactor some of the existing ones

Let's start by creating the new files which will ultimately be the new home for all the commands that you wish to have.

Create a new directory called commands.

Within the command directory the ping, server, and user will become their own ".js" files.

ping.js

// commands/ping.js
const { SlashCommandBuilder } = require('@discordjs/builders')

module.exports = {
  data: new SlashCommandBuilder()
    .setName('ping')
    .setDescription('Replies with Pong!'),
  async execute(interaction) {
    await interaction.reply('Pong!')
  },
}
Enter fullscreen mode Exit fullscreen mode

server.js

// commands/server.js
const { SlashCommandBuilder } = require('@discordjs/builders')

module.exports = {
  data: new SlashCommandBuilder()
    .setName('server')
    .setDescription('Display info about this server.'),
  async execute(interaction) {
    return interaction.reply(
      `Server name: ${interaction.guild.name}\nTotal members: ${interaction.guild.memberCount}`
    )
  },
}
Enter fullscreen mode Exit fullscreen mode

user.js

// commands/user.js
const { SlashCommandBuilder } = require('@discordjs/builders')

module.exports = {
  data: new SlashCommandBuilder()
    .setName('user')
    .setDescription('Display info about yourself.'),
  async execute(interaction) {
    return interaction.reply(
      `Your username: ${interaction.user.username}\nYour ID: ${interaction.user.id}`
    )
  },
}
Enter fullscreen mode Exit fullscreen mode

Next edit the deploy-commands.js and index.js files

deploy-commands.js

// node modules that will be included to existing file
const fs = require('node:fs')
const path = require('node:path')

...

// remove everything inside the commands array and replace with an empty array
const commands = []

const commandsPath = path.join(__dirname, 'commands')
const commandFiles = fs
  .readdirSync(commandsPath)
  .filter((file) => file.endsWith('.js'))

for (const file of commandFiles) {
  const filePath = path.join(commandsPath, file)
  const command = require(filePath)
  commands.push(command.data.toJSON())
}

...
Enter fullscreen mode Exit fullscreen mode

A similar thing will be done with the index.js file to create all the commands.

index.js

const fs = require('node:fs')
const path = require('node:path')
const { Client, Intents, Collection } = require('discord.js')
const { token } = require('./config.json')

const client = newClient({ intents: [Intents.FLAGS.GUILDS] })

client.commands = new Collection()
const commandsPath = path.join(__dirname, 'commands')
const commandFiles = fs
  .readdirSync(commandsPath)
  .filter((file) => file.endsWith('.js'))

for (const file of commandFiles) {
  const filePath = path.join(commandsPath, file)
  const command = require(filePath)
  client.commands.set(command.data.name, command)
}

client.once('ready', () => {
  console.log('Ready!')
})

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

  const command = client.commands.get(interaction.commandName)

  if (!command) return

  try {
    await command.execute(interaction)
  } catch (error) {
    console.error(error)
    await interaction.reply({
      content: 'There was an error while executing this command!',
      ephemeral: true,
    })
  }
})

client.login(token)
Enter fullscreen mode Exit fullscreen mode

Now we have cleaned up the index.js and deploy-commands.js, we'll add one more command that will open up the options to add options on a command.

echo.js

// commands/echo.js
const { SlashCommandBuilder } = require('@discordjs/builders')

module.exports = {
  data: new SlashCommandBuilder()
    .setName('echo')
    .setDescription('Replies with you input!')
    .addStringOption((option) =>
      option
        .setName('input')
        .setDescription('The input to echo back')
        .setRequired(true)
    ),
  async execute(interaction) {
    await interaction.reply({
      content: interaction.options.getString('input'),
      ephemeral: true,
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

The .addStringOption() will allow for an input, in this case to reply what we say, and do something with it. In the event you wish you mute someone on the server you will be able to use /mute @this-noisy-user and the bot will take of it for you. Setting up for how long they will be muted for is up to you and if your bot stays running to unmute them.

Latest comments (0)