Skip to content

buzzysin/buzzybot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

44 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

@buzzybot/buzzybot

Introduction

What is this?

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!

Why?

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).

Packages

This repo currently defines two repos: @buzzybot/injex-discord-plugin and @buzzybot/cli.

Usage

Please note that the following only contains intended API usage. If the usage is unimplemented, it will be marked as such.

Prerequisites

It would help you if you were familiar with the Discord.JS Guide. I'm still learning myself.

@buzzybot/cli

Directory structure

> src
|-- > commands
|   |-- > example.cmd.ts # Custom extensions don't matter here
|
|-- > middleware
|   |-- > example.mdw.ts
|
|-- > setup/injex
|   |-- > bootstrap.ts
|   |-- > index.ts
...

@buzzybot/injex-discord-plugin

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).

@slash

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.

@slash.group and @slash.group.command

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.
}

@slash.sub

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
  }

}

Features

(Almost) Automatic

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?)

Granular Protection

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 faff

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!

Disclaimers

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

Thanks

Projects

discordjs lerna

People

uditalias leone25


๐Ÿ

Copyright ยฉ @buzzysin 2021

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

Packages

No packages published