1.0.2 • Published 6 years ago

@joogl/disco.js v1.0.2

Weekly downloads
-
License
MIT
Repository
github
Last release
6 years ago

Disco

Wrapper around the Discord.JS package to easily create command-based bots.

Installation

npm i @joogl/disco.js

Usage

Creating a bot

The Bot class exported by the package extends the Client class from Discord.JS, so all methods you're used to from that package will also work here. Mind you that this package wraps around the Discord.JS library, so you can work as you're used to with no hassle.

This means you can still do this to create a new client instance.

const { Bot } = require("disco.js");

let client = new Bot();
await client.login("token");

Creating and adding commands

The package exports a Command class which can be extended to create a command implementation. Commands must inherit two functions, otherwise the bot will throw an error when trying to execute the command. These functions are getName and execute. A help command to send a list of available commands to the channel a command was executed in would look like this.

const { Command } = require("disco.js");

class HelpCommand extends Command {

    getName() {
        return "help";
    }

    async execute(message, args) {
        await message.channel.send("To be implemented.");
    }

}

The constructor from the Command class takes a bot instance as an argument. So unless you override the constructor and give a bot as an argument to the super call, it is best to not implement any logic in the command constructor itself.

To register the command, the Bot class provides the function registerCommand which takes a command class constructor as an argument. When registered, the bot will listen to incoming messages, evaluate them and see if a command can be executed. Using the example from before, you'd register the help command like this.

client.registerCommand(HelpCommand);

Command names are case-insensitive. This means that a command with the name help will also be called if someone in chat uses it as Help or HELP or whatever else you can think of.

To trigger a command execution, the command has to be prefixed. That's usually just one character. By default, the bot reacts to the . prefix. You can always change the command prefix using the setCommandPrefix method exposed by the Bot class. So the above command implementation would only be executed if someone in chat sent a message containing .help.

Creating storage implementations

Storage implementations can be defined using the Storage class. All classes that extend the storage class must infer a type variable representing the storage created when instantiated. They must also override the instantiate method. A class to implement ioredis for instance could look like this.

const Redis = require("ioredis");
const { Storage } = require("disco.js");

class RedisStorage extends Storage<Redis.Redis> {

    async instantiate() {
        return new Redis();
    }

}

The idea behind having a seperate storage implementation is to add convenience functions to the class, like reading and writing data in a database for a set of server properties, and have them all running in the same spot.

A storage implementation can be bound to a bot instance. Bots expose the setStorage function which takes a storage implementation as an argument. The bot will then try to instantiate the storage. If it's successful, the storage instance will be able to be accessed from any part of code that has access to the bot using getStorage.

await client.setStorage(new RedisStorage());

This is especially useful within command classes since they have access to the bot instance they're assigned to by default. So commands can easily query storage solutions.

async execute(message, args) {
    this.bot.getStorage().getSomeServerVariable(message.guild);
}

Resolving arguments

When you have a predictable command syntax, you sometimes want mentions to resolve to their instances at runtime. Assuming you want a command that pings a server member, the message that is sent over the wire looks something like .ping <@302551113424678982>. You want the first argument to be automatically resolved into a user instance.

The package provides an ArgumentResolver. It provides functions to resolve channels, members, users and roles from a message given a mention like in the example above. Mind you it can only resolve objects that have been mentioned in the message. Otherwise any of its methods will throw an error. For the above command example, it'd look something like this.

async execute(message, args) {
    let resolver = new ArgumentResolver(message);
    let user = resolver.resolveMember(args[0]);
}

Guessing channels, users and roles from text

In some cases, you can't mention certain things on your server like voice channels, roles or members in case you just don't want to annoy them. Hence it's better to not mention but rather provide the name for the thing you want to mention.

For this exact purpose, this package provides the Guesser class. Given user input, the class returns a list of items that are most similar to the given input in descending order. It uses the Levenshtein distance as a metric to determine the similarity between user input and the given role, user, member and channel names in a server.

Say you have the example of the ping command and you want to mention a user who has whitespaces in their nickname, then you can implement it a little something like this.

async execute(message, args) {
    let name = args[0];
    let guesser = new Guesser(message.guild);
    let user = guesser.guessMembersByNickname(name)[0];

    await message.channel.send(`Hey ${user}, wake up!`);
}

Assuming someone executed the command like .ping "Big Mean Admin", the above code would then mention the user whose nickname is the closest to "Big Mean Admin".

By default, the Guesser ignores result with a Levenshtein distance higher than three to avoid far-fetched results. The threshold distance can always be changed using an additional constructor parameter.

Documentation

Bot => DiscordJS.Client

Constructor

new Bot()
Creates a new bot instance.

Methods

getCommandPrefix() => string
Gets the current command prefix. (. by default)

setCommandPrefix(newPrefix: string) => string
Sets the command prefix.

getStorage() => ? extends Storage
Gets the storage instance bound to this bot.

async setStorage(newStorage: ? extends Storage)
Sets the storage instance to be bound to this bot.

registerCommand(cmdClass: typeof Command) Registers a command.

Command

Constructor

new Command(bot: Bot)
Creates a new command instance. Takes the bot instance to bind to as a parameter.

Methods

getName() => string
Name of the command.

async execute(message: Message, args: string[])
Executes the command. Takes the message instance that triggered the command and a string array containing all the supplied arguments as parameters.

Properties

bot => Bot
Bot instance the command is registered to.

Storage\<T>

T defines the type of storage that will be instantiated.

Constructor

new Storage()
Creates a new storage instance.

Methods

async instantiate() => Promise<T>
Creates an instance of the storage implementation.

async create() => Promise<Storage<T>>
Creates the storage implementation instance and returns the storage wrapper.

isInstantiated() => boolean
Returns true if the storage was instantiated, false otherwise.

get() => T
Gets the storage implementation instance. Throws an error if it wasn't instantiated yet.

ArgumentResolver

Constructor

new ArgumentResolver(message: Message)
Creates a new argument resolver. Takes the message to resolve instances from as a parameter.

Methods

isTextChannel(str: string) => boolean
Checks if a mention can be resolved to a text channel.

isRole(str: string) => boolean
Checks if a mention can be resolved to a role.

isUser(str: string) => boolean
Checks if a mention can be resolved to a user.

isMember(str: string) => boolean
Checks if a mention can be resolved to a member.

resolveTextChannel(str: string) => TextChannel
Resolves a Discord text channel mention representation into its instance.

resolveRole(str: string) => Role
Resolves a Discord role mention representation into its instance.

resolveUser(str: string) => User
Resolves a Discord user mention representation into its instance.

resolveMember(str: string) => GuildMember
Resolves a Discord member mention representation into its instance.

Guesser

Constructor

new Guesser(guild: Guild, maxDistance?: number)
Creates a new guesser. Takes the guild to perform the guesses on as the first argument. Optionally allows a threshold Levenshtein distance as a second argument. All results with a distance higher than that will be discarded.

Methods

guessRoles(name: string) => Role[]
Considers all roles in a guild and returns a list of roles that are most similar to the name given as the argument in descending order. Takes a role name as an argument.

guessMembersByUsername(name: string) => GuildMember[]
Considers all members in a guild and returns a list of members that are most similar to the name given as the argument in descending order. Only their usernames will be considered. Takes a member name as an argument.

guessMembersByNickname(name: string) => GuildMember[]
Considers all members in a guild and returns a list of members that are most similar to the name given as the argument in descending order. Only their display names will be considered, or their user names if the former isn't available. Takes a member name as an argument.

guessTextChannels(name: string) => TextChannel[]
Considers all text channels in a guild and returns a list of text channels that are most similar to the name given as the argument in descending order. Takes a text channel name as an argument.

guessVoiceChannels(name: string) => VoiceChannel[]
Considers all voice channels in a guild and returns a list of voice channels that are most similar to the name given as the argument in descending order. Takes a voice channel name as an argument.

Contributing

This project is built on top of Typescript. It comes with a compiler configuration file. To work on the code, clone this repo, make and commit your changes and create a pull request with your suggested changes. All improvements and new helper ideas are welcome.

License

MIT.