3.0.0 • Published 1 year ago

discord-decorated v3.0.0

Weekly downloads
-
License
-
Repository
github
Last release
1 year ago

Features

Installation

discord-decorated depends on discord.js tsyringe and a reflect polyfill - for the example we'll install reflect-metadata.

npm i discord-decorated discord.js tsyringe reflect-metadata
yarn add discord-decorated discord.js tsyringe reflect-metadata

Getting Started

discord-decorated seeks to make the setup for a discord bot simple, just create a DiscordClient instance - which just inherits from discord.js's Client - then assign command handlers and login!

// bot.ts
import "reflect-metadata"; // ensure to import a reflect-metadata as early as possible
import { DiscordClient } from "discord-decorated";
import { PingModule } from "./command-modules/PingModule";
import { GatewayIntentBits } from "discord.js";

const bot = new DiscordClient({
  commandPrefix: "!",
  commandModules: [PingModule],
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
    // ...
  ],
});

// DiscordClient extends Client, all discord.js methods are available
bot.on("ready", () => console.log("ready!"));

bot.login(process.env.token);
// command-modules/PingModule.ts
import { command } from "discord-decorated";
import { Message } from "discord.js";

export class PingModule {
  @command("ping")
  public async ping(msg: Message) {
    await msg.reply("pong!");
  }
}

You'll also have to modify your tsconfig.json to include the following settings

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Command Decorators

discord-decorated adds the ability to decorate class methods with command decorators allowing easier command creation / classification.

// PongModule.ts
export class PongModule {
  @command("ping")
  public async pong(message: Message) {
    return message.reply("pong!");
  }

  @command("zing")
  public async zong(message: Message) {
    return message.reply("zong!");
  }
}

A CommandModule can contain any number of commands, and the commands themselves can recieve up to two arguments: the Message, from discord.js, and the arguments, which currently are space delimited words from the original message (after the command string).

export class EchoModule {
  @command("echo")
  public async echo(message: Message, args: string[]) {
    // from discord: !echo hello world!
    // will recieve reply: hello world!

    return message.reply(args.join(" "));
  }
}

Dependency Injection

discord-decorated using tsyringe for dependency injection on it's CommandModule's and should work out of the box after following the install instructions.

// use tsyringe's decorators
@injectable()
export class DependentModule {
  // dbService injected through constructor using tsyringe
  constructor(private readonly dbService: DbService) {}

  @command("getSomeData")
  public async getSomeData(msg: Message) {
    // use dbService to grab some data and send it to the discord chat
    const data = await this.dbService.getData();

    return msg.reply(data.toString());
  }
}

Argument Transforming

discord-decorated comes with two built in argument transformers by default, the ParseNumberTransformer and the ParseIntTransformer which takes the string arguments from discord and parse them numbers / integers.

export class ParserModule {
  // pass the transformer in to the command annotation
  @command("parse-numbers", new ParseNumberTransformer())
  public async parse(message: Message, args: number[]) {
    // args will be an array of numbers
    console.log(args);
  }
}

You can easily create your own transformers by creating a class that implements the ITransformer interface from discord-decorated

import { ITransformer } from "discord-decorated";

// customer transformer which takes the arguments and converts them to caps
export class MyTransformer implements ITransformer<string> {
  // ITransformer requires a Promise returning an array of the desired transformed type
  async transform(args: string[]): Promise<string[]> {
    // simply transform each argument to uppercase
    return args.map((arg) => arg.toUpperCase());
  }
}
export class MyCommandModule {
  // pass the transformer in to the command annotation
  @command("transform", new MyTransformer())
  public async transform(message: Message, args: number[]) {
    // from discord: ["hello"]

    // args == ["HELLO"]
    console.log(args);
  }
}

To exit a transformer's parse method, in case parsing has failed, just throw an error and the command will not be run.

import { ITransformer } from "discord-decorated";

export class DisallowTheNumberOne implements ITransformer<number> {
  async transform(args: string[]): Promise<number[]> {
    return args.map((arg) => {
      const arg = parseFloat(arg);

      // if a user sends 1 in to the command, it will not be run!
      if (arg === 1) throw new Error();

      return arg;
    });
  }
}

Command Registration

After creating command modules you can register them to the DiscordClient by passing their constructors to the DiscordClientOptions:

const client = new DiscordClient({
  // ...
  commandModules: [PongModule, EchoModule, DependentModule],
  // ...
});

client.login(token);