1

I am working on creating a discord bot in TypeScript. I wanted to create a generic command disbatcher and here is my work so far:

app.ts:

import * as Discord from 'discord.js';
import * as config from '../config'
import * as commands from './Commands/index'

const token : string = config.Token;

const _client = new Discord.Client();

_client.on('message', (msg) => {
    let args : Array<string> = msg.content.split(' ')
    let command : string = args.shift() || " ";

    if(!command.startsWith("!")) return;
    else{
        commands[`${command.toLower().substring(1)}`]
    }

})

Commands/Index.ts

export {default as ping} from './ping';
export {default as prong} from './prong';

Ping.ts : same structure for all commands

import { Message } from "discord.js";

export default {
    name : 'ping',
    description: 'Ping!',
    execute(message: Message, args: Array<string>){
        message.channel.send('Pong.');
    }
}

When indexing the commands import I can successfuly call the right execute function using this:

commands['pong'].execute()

however, when trying to dynamically index it like this:

commands[command].execute()

I recieve the following error:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'typeof import("c:/Users/alexs/Desktop/Discord Bot/src/Commands/index")'. No index signature with a parameter of type 'string' was found on type 'typeof import("c:/Users/alexs/Desktop/Discord Bot/src/Commands/index")'

Is there anyway I can typecast the command import as some kind of object or collection? If not, is there a way I could create some kind of accssesor to make this work? I am newer to typescript and am curious what is possible.

1
  • How are you calling commands[command].execute()? Where does command variable come from? Commented Apr 9, 2020 at 9:14

1 Answer 1

5

I suggest a different approach for your commands, this approach fixes 2 things:

  • You don't forget to export files properly
  • You get type safe commands

Let's first create a interface for your commands, this interface describes the metadata, add as many as you want

export interface Command {
  name: string
  description: string
  // Making `args` optional
  execute(message: Message, args?: string[]) => any
}

Now that you have a shape for your command, let's make sure all your commands have the right shape

import { Command } from "./types"

// This will complain if you don't provide the right types for each property
const command: Command = {
  name: "ping",
  description: "Ping!",
  execute(message: Message, args: string[]) => {
    message.channel.send("Pong")
  }
}

export = command

The next part is loading your commands, discord.js has glob as a dependency which can help you read files in a directory easily, let's use some utilities so we can have nice async / await usage

import glob from "glob" // included by discord.js
import { promisify } from "util" // Included by default
import { Command } from "./types"

// Make `glob` return a promise
const globPromise = promisify(glob)

const commands: Command = []

client.once("ready", async () => {
  // Load all JavaScript / TypeScript files so it works properly after compiling
  // Replace `test` with "await globPromise(`${__dirname}/commands/*.{.js,.ts}`)"
  // I just did this to fix SO's syntax highlighting!
  const commandFiles = test

  for (const file of commandFiles) {
    // I am not sure if this works, you could go for require(file) as well
    const command = await import(file) as Command
    commands.push(command)
  }
})

const prefix = "!"

client.on("message", message => {
  // Prevent the bot from replying to itself or other bots
  if (message.author.bot) {
    return
  }

  const [commandName, ...args] = message.content
    .slice(prefix.length)
    .split(/ +/)

  const command = commands.find(c => c.name === commandName)

  if (command) {
    command.execute(message, args)
  }
})

I hope this gives you some good starting point and shows you the power of TypeScript

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.