Quick Start

This quick start guide will take you from an empty project to a basic bot in no time. For info about getting started with Pages, head over to the special quick start guide for Pages after you finish the basic setup.

Installation

Slashasaurus does not provide a version of discord.js itself, so you'll need to install both.

yarn install discord.js@latest slashasaurus@latest

The Main File

Setting up a slashasaurus bot is very similar to setting up a discord.js bot. In your main file, create a new SlashasaurusClient, this wraps the discord.js client and adds all the extra features that slashasaurus provides. The constructor takes two arguments, the first is the options to be passed on to the discord.js client (you can find the options here), the second is the options for slashasaurus (you can find the options here).

index.ts
import { Intents } from 'discord.js';
import path from 'path';
import { SlashasaurusClient } from 'slashasaurus';

const client = new SlashasaurusClient(
  {
    intents: [Intents.FLAGS.GUILDS],
    restRequestTimeout: 30 * 1000,
  },
  {
    devServerId: '939038490023301150',
  }
);

client.once('ready', () => {
  logger.info(`Client ready and logged in as ${client.user?.tag}`);
});

client.login(process.env.TOKEN);

Creating Your First Command

Now that we have the basics set up, we're ready to make our first command. When using slashasaurus, commands are stored in a special way in the file system. Let's make a folder to store them in called commands.

my-bot
├─ src
│  ├─ index.ts
│  └─ commands

├─ tsconfig.json
├─ README.md
└─ package.json

The first command we're gonna add is going to be a slash command. For this we'll need to make another folder named chat inside our commands folder, this will store all of our slash commands.

my-bot
├─ src
│  ├─ index.ts
│  └─ commands
│     └─ chat

├─ tsconfig.json
├─ README.md
└─ package.json

Let's make a basic /ping command to get familiar with the basics of slash commands in Slashasaurus. Inside of commands/chat , make a new file called ping.ts. In the file we'll create a new SlashCommand and export it as default.

ping.ts
import { SlashCommand } from 'slashasaurus';

export default new SlashCommand(
  {
    name: 'ping',
    description: 'Pings the bot to make sure everything is working',
    options: [],
  },
  {
    run: (interaction) => {
      interaction.reply({
        content: `Pong!`,
        ephemeral: true,
      });
    },
  }
);

Let's break down what we have above. The first argument of SlashCommand is the basic info about the command: the name, description, and options. The second argument contains our run handler, with a pretty simple reply. That's it, the command is all ready to go! Just one more quick thing before you can start the bot. We need to tell slashasaurus where this command is. Let's head back to our main file and get that set up.

We can run client.registerCommandsFrom to load all the commands from our commands folder. Simply pass it the path to the commands folder and where you want to register the commands. In development, you'll want to pass "dev" as the second argument, this will register the commands to your dev server directly, making them available immediately. In production you'll want to register your commands to "global", this will make them available in all guilds, but can take up to an hour for changes to appear (don't worry, if a command changes the options, you won't receive any incorrect ones).

If you plan on sharding your bot, you'll want to avoid registering the commands multiple times at startup, so instead you can pass "none" and the command handlers will be registered, but the command data won't be sent to discord.

import { Intents } from 'discord.js';
import path from 'path';
import { SlashasaurusClient } from 'slashasaurus';

const client = new SlashasaurusClient(
  {
    intents: [Intents.FLAGS.GUILDS],
    restRequestTimeout: 30 * 1000,
  },
  {
    devServerId: '939038490023301150',
  }
);

client.once('ready', () => {
  logger.info(`Client ready and logged in as ${client.user?.tag}`);
  // Register our commands from our folder
  client.registerCommandsFrom(
    path.join(__dirname, '/commands'),
    process.env.NODE_ENV === 'development' ? 'dev' : 'global'
  );
});

client.login(process.env.TOKEN);

That's it, build and run your code to see your new command!

A Command With Options

Now let's look at how a command with options works, there's one special thing to watch out for while working with options. Let's make a command called /hello that will ask the user for their name and greet them.

my-bot
├─ src
│  ├─ index.ts
│  └─ commands
│     └─ chat
│        ├─ hello.ts
│        └─ ping.ts

├─ tsconfig.json
├─ README.md
└─ package.json

We'll name our option "name" (crazy, right?). Take a close look at the options here though, there's something special in there.

import { SlashCommand } from 'slashasaurus';

export default new SlashCommand(
  {
    name: 'hello',
    description: 'Makes the bot greet you',
    options: [
      {
        type: 'STRING',
        name: 'name',
        description: 'Your name',
        required: true,
      },
    ] as const,
  },
  {
    run: (interaction, client, options) => {
      interaction.reply({
        content: `Hello, ${options.name}. Nice to meet you!`,
        ephemeral: true,
      });
    },
  }
);

You might be thinking as const? What is that? The as const here tells TypeScript that your options array will always remain constant and allows it to make some more inferrences about the types of each member of the array. This powers something special in slashasaurus. If you typed this into your editor yourself you may have noticed that as you were typing options.name your editor automatically suggested name and it's typed as a string. This works with any slash command option type.

A Command With Choices

Lets take a look at building out a command with a string argument with a provided set of choices for the user.

Create a file called survey.ts in the same place as last time.

my-bot
├─ src
│  ├─ index.ts
│  └─ commands
│     └─ chat
│        ├─ hello.ts
│        ├─ ping.ts
│        └─ survey.ts

├─ tsconfig.json
├─ README.md
└─ package.json

In there we'll make our new command:

survey.ts
import { SlashCommand } from 'slashasaurus';

export default new SlashCommand(
  {
    name: 'survey',
    description: 'Give a response for our survey',
    options: [
      {
        type: 'STRING',
        name: 'language',
        description: 'What language do you use?',
        required: true,
        choices: [
          {
            name: 'JavaScript',
            value: 'js',
          },
          {
            name: 'TypeScript',
            value: 'ts',
          },
        ] as const,
      },
    ] as const,
  },
  {
    run: (interaction, client, options) => {
      if (options.language === 'js') {
        interaction.reply({
          content: `We've recorded your response. (also try TypeScript)`,
          ephemeral: true,
        });
      } else {
        interaction.reply({
          content: `We've recorded your response. (also try JavaScript)`,
          ephemeral: true,
        });
      }
    },
  }
);

Again you'll see the as const after the array of choices. This does the same thing with the types to help keep the autocomplete helpful. When you look at the type of options.language you'll see it's "js" | "ts" this means that you'll know exactly what the options are that the user has, instead of needing to remember them or scroll back and check. If you happen to notice that it's just typed as string this likely means you forgot the second as const.

A Command With Autocomplete

Before we move on we should take a look at autocomplete options.

Make a new file longsurvey.ts:

my-bot
├─ src
│  ├─ index.ts
│  └─ commands
│     └─ chat
│        ├─ hello.ts
│        ├─ ping.ts
│        ├─ longsurvey.ts
│        └─ survey.ts

├─ tsconfig.json
├─ README.md
└─ package.json

In here we'll make our command:

longsurvey.ts
import { SlashCommand } from 'slashasaurus';

const foods = ['cheese', 'apples', 'oranges', 'burgers', 'bacon', 'fish'];

export default new SlashCommand(
  {
    name: 'longsurvey',
    description: 'Give a response for our other survey',
    options: [
      {
        type: 'STRING',
        name: 'food',
        description: 'Which of these foods is your favorite?',
        required: true,
        autocomplete: true,
      },
    ] as const,
  },
  {
    run: (interaction, client, options) => {
      interaction.reply({
        content: `We've recorded your response of ${options.food}.`,
        ephemeral: true,
      });
    },
    autocomplete: (interaction, focusedName, focusedValue, client, options) => {
      if (focusedName === 'food') {
        interaction.respond(
          foods.filter((food) => food.startsWith(focusedValue))
        );
      }
    },
  }
);

Here we have our second handler, autocomplete. This handler is given the autocomplete interaction, the name of the field that's being filled out, the value the user has entered so far, the client, and finally the options again. The focusedName will only ever be one of the options with autocomplete set to true, so in this case it's type is "food", but if we had another it would be a union of the two, for instance: "food" | "other".

Optionally, you can specify onAutocomplete inside your food option, this function will receive interaction, value, and client. If you pass this handler, all autocomplete interactions related to this arg will call that function instead of the autocomplete inside your handlers. This is extra useful when you have a specific autocomplete that's used across multiple commands, for instance autocompleting the name of something in a search, etc.

You can also pass a custom transformer to your autocomplete option. This will take the value of the autocomplete and transform it before it gets sent to your run handler. For instance if you have an option to autocomplete a date, you can convert it to a Date inside the transformer and the run handler will receive the Date in place of the original value.

A Command With Subcommands

Lastly, lets take a look at how we make subcommands. Lets make a /role add and a /role remove.

First, make a new folder called role where you other commands are, then add an add.ts and a remove.ts inside.

my-bot
├─ src
│  ├─ index.ts
│  └─ commands
│     └─ chat
│        ├─ hello.ts
│        ├─ ping.ts
│        ├─ longsurvey.ts
│        ├─ role
│        │  ├─ add.ts
│        │  └─ remove.ts
│        │
│        └─ survey.ts

├─ tsconfig.json
├─ README.md
└─ package.json

With this one I won't show the exact commands since that isn't necessary to illusatrate the point. Here you'll see that making subcommands is almost the same as making top level commands. Just place them inside the folder and the top level command will use the folder name as its name. If you want to specify a description for the top level command you can create a file inside the folder named _meta.ts. You can export a description and defaultPermissions to set those on that command. For instance we can make the _meta.ts file:

my-bot
├─ src
│  ├─ index.ts
│  └─ commands
│     └─ chat
│        ├─ hello.ts
│        ├─ ping.ts
│        ├─ longsurvey.ts
│        ├─ role
│        │  ├─ _meta.ts
│        │  ├─ add.ts
│        │  └─ remove.ts
│        │
│        └─ survey.ts

├─ tsconfig.json
├─ README.md
└─ package.json

and put:

_meta.ts
export const description = 'Commands for managing your roles';

Now our top level /role command will have that description.

Last updated