This is a proof-of-concept 'framework' (using that term very lightly) that makes use of dependency injection to write Discord bots. I'm currently writing mine with this!
This is mainly a project to develop my understanding of:
- Typescript
- Industry-standard publishing lifecycle of NPM projects
- Continuous integration/deployment and workflows/actions
In no way is this expected to be used in a serious manner (see disclaimers).
This repo currently defines two repos: @buzzybot/injex-discord-plugin
and @buzzybot/cli
.
Please note that the following only contains intended API usage. If the usage is unimplemented, it will be marked as such.
It would help you if you were familiar with the Discord.JS Guide. I'm still learning myself.
> src
|-- > commands
| |-- > example.cmd.ts # Custom extensions don't matter here
|
|-- > middleware
| |-- > example.mdw.ts
|
|-- > setup/injex
| |-- > bootstrap.ts
| |-- > index.ts
...
This Injex plugin introduces a few decorators to be used on classes.
For your information
06/09/2021 - As of writing this, there is only support for slash commands, and the names of the decorators are written to reflect this. Full slash support hopefully means a full release (1.0.0).
This ClassDecorator
factory has three possible uses. It can be used to define a slash command, define subcommands, or scoped subcommands (subcommand groups). It is the bulkiest and most flexible available - a whole tree of subcommands and group subcommands can be created using this.
// example-one.cmd.ts
// ... imports
@slash({
name: "ping",
description: "Send the user a ping!",
options: addUserOption /* recieves a `SlashCommandOptions` builder* */,
protect: [OnlyRolesThatCanPing],
protectAll: [OnlyOnlineMembers],
})
export class ExampleOneCommand {
/* must implement run(...) if the command is a root slash command ONLY */
async run(interaction: CommandInteraction) {
const user = interaction.options.getUser();
if (user) {
await command.reply(`Hello, ${user}! ${command.user.username} says hi!`);
} else {
await command.reply(`Hello ${command.user}!`);
}
}
}
Asterisk* - implementation omitted for brevity.
The true features of this API come to light under the protect
and protectAll
options. These take in middleware-like class definitions that provide different levels of protection.
The ClassDecorator
factory @slash.group
does not define a command, so using @slash({ name: "..." })
is still required.
However using @slash({ groups: [] })
is almost functionally equivalent and is a viable alternative if you want rules and options to be scoped by group.
This is almost analogous to the @define() decorator in Injex. In fact, it uses it.
The MethodDecorator
@slash.group.command
works in tandem with @slash.group
, as it defines commands but not the groups they belong to. If a group is not defined, the subcommand it would belong to is ignored.
Perhaps there is scope for this to be done dynamically.
// bank-funds.cmd.ts
// ... imports
// @slash({ groups: [...] }) syntax
@slash({
name: "funds",
description: "Manage a user's wallet",
groups: [
{
name: "edit",
description: "Edit the value of a user's wallet",
/* this compose function does not exist, it is merely conceptual */
options: compose([requiredIntegerOption, requiredUserOption]),
protect: [AdminsOnly],
},
{
name: "transact",
description: "Give another user some money",
options: requiredUserOption,
protect: [NonEmptyWallet]
},
],
})
export class BankFundsCommand {
/* Note that `run()` is no longer implemented */
/**
* Also note that the defining a group does not define its subcommands,
* therefore this class could still be valid even if it was empty.
*/
/**
* Finally note that options and protections apply to ALL subcommands in
* the defined group, so they will have the same options and protections.
*/
/**
* As above, @slash.group.commands is used to define the grouped commands.
*/
@slash.group.command({
group: "edit",
name: "increase",
description: "Add `n` to the user's wallet.",
})
fundsEditIncrease(command: CommandInteraction) {
// ... command.options.getUser() is defined
// ... command.options.getInteger() is defined
}
fundsEditDecrease(...) { /* ... */ }
@slash.group.command({
group: "transact",
name: "pay",
description: "Add `n` to the user's wallet.",
})
fundsTransactPay(command: CommandInteraction) {
// ... command.options.getUser() is defined
// ... command.options.getInteger() is NOT defined (not available in this group)
}
// ... etc.
}
This MethodDecorator
factory is almost identical in fashion to @slash.command
, except there is no need to specify a group (since subcommands do not have groups). Subcommands (not to be confused with grouped subcommands) can coexist with grouped subcommands, but both erase the use of a base command (refer to this page) and therefore run()
is no longer required. Reusing the previous example:
/**
* ...the same @slash declaration as before, just in a
* different syntax
*/
@slash({ name: "funds", ... })
@slash.group({ name: "edit", ... })
@slash.group({ name: "transact", ... })
export class BankFundsCommand {
/** ...same functions and @slash.group.commands as before */
/** introducing @slash.sub */
@slash.sub({
name: "get",
description: "Get the value of a wallet",
options: optionalUserOption
})
fundsGet(command: CommandInteraction) {
// command.options.user() is available
}
}
Because of Injex's ability to read and register command files, any decorated command class is registered and immediately available for use. The full power of this framework isn't even realised fully here! (Yet?)
The protect
and protectAll
options available on each decorator provides you with base command-, group command-, endpoint- and method-level protection, hopefully making scoping permissions easier.
No more of this:
if (!interaction.isCommand()) return;
if (interaction.options.getSubcommand() !== subCommand) return;
...
... I mean, it is in the source code if you want to read it, but you don't have to type it!
This is a proof-of-concept project only. Use at your own risk, as this library is:
- UNtested - is yet to be
jest
-ified - UNverified - the only user is me
- UNsafe - there is no sanitisation
Projects
People
Copyright ยฉ @buzzysin 2021