A command handler for JADL
npm install @jadl/cmd"experimentalDecorators": true in your tsconfig.json
ts
import { CommandHandler } from '@jadl/cmd'
import { WaveCommand } from './commands/WaveCommand' // example command
const commands = new CommandHandler(worker, [
// your command classes
WaveCommand
])
`
Creating a command
Commands are classes that will be filled with decorator to dictate their functionality
Our main decorator for a command is @Command(name: string, description: string)
Once you've declared your class, you create a method to run the command through, and mark it with a @Run() decorator
You can return anything that can be parsed by @jadl/builders, like a string, or one of the many builders. Here's an example;
`ts
import { Command } from '@jadl/cmd'
@Command('wave', 'Wave at someone!') // sets the command name to /wave
export class WaveCommand {
@Run() // marks this method as the main running command
wave () {
return 'Hello!'
}
}
`
Simplest command in the book. Lets get a bit more advanced.
$3
Using parameter decorators is how we access all of our important data, to make your command a real command
Many of them exist and allow for adding and using interaction options, or just accessing your normal data objects
e.g let's add a user via the Options.User decorator. This will create a Discord interaction user option
`ts
import { Command, Run, Worker, Author } from '@jadl/cmd'
import { Embed } from '@jadl/builders' // optional, but used for many different compatible builders!
@Command('hello', 'Say hello!')
export class HelloCommand {
@Run()
hello (
@Worker() worker: Bot, // creates a paramater that is your worker
@Author() author: APIUser // gets the user who sent the command
) {
// you can now use this parameter as it's actual value! making it super easy to do what you need to do
return new Embed()
.description(Hey <@${author.id}>! Thanks for saying hi! My name is ${worker.user.username}!)
}
}
// note, discord-api-types is EXTREMELY useful and will let you add types for all of these
`
Some helpful decorators:
- @Worker, gets the main worker
- @Interaction gets the interaction object
- @Guild gets the guild
- @Author gets the running user
- @Member gets the running member
- @Me gets the worker's member in this guild
(many more to come) // WIP
$3
The @Options.[] decorator is used to apply options to a command. For example @Options.String('name', 'description', { options })
e.g let's add a user via the Options.User decorator. This will create a Discord interaction user option
`ts
import { Command, Run, Options } from '@jadl/cmd'
import { MessageBuilder, Embed } from '@jadl/builders'
@Command('wave', 'Wave at someone!')
export class WaveCommand {
@Run()
wave (
@Options.User('user', 'User to wave at', {
required: true
}) user: APIUser // creates an option accepting type user
) {
// you can now use this parameter as it's actual value! making it super easy to do what you need to do
return new MessageBuilder({ content: Hey <@${user.id}>})
.addEmbed(
new Embed()
.description('Someone waved at you')
)
}
}
`
$3
#### Permissions
For permissions v2 there is a Permissions(permission) decorator
`ts
import { Command, Permissions, Run } from '@jadl/cmd'
@Command('admin', 'Only users with Manager Server can run this command')
@Permissions('manageServer') // only Manage Server permissions (overrideable by permissions v2)
export class AdminCommand {
@Run()
run () {
superSecretThing() // only people who are allowed to run the command in the server can do it
}
}
`
$3
You can create a user or message command by passing the type of command you'd like to the @Command() 3rd parameter
And then you can use the @Targets decorator as a value for those targets. Be careful to make sure to use the correct target for your command
e.g lets make our wave command a user command!
`ts
import { Command, Run, Targets, Author } from '@jadl/cmd'
import { Embed } from '@jadl/builders'
import { ApplicationCommandType } from 'discord-api-types/v9'
@Command('Wave at user', undefined, ApplicationCommandType.User)
export class WaveCommand {
@Run()
wave (
@Targets.User() user: APIUser, // gets the user who the command was ran ON
@Author() author: APIUser // gets the user who the command was ran FROM
) {
return new Embed()
.description(Hey <@${user.id}>! <@${author.id}> waved at you!)
}
}
`
$3
With decorators it's super easy to create and use decorator for specific commands. There's a few built in. For example @UserPerms()
`ts
@Command('ban', 'Ban a user')
class BanCommand {
@Run()
@UserPerms('administrator') // this will require the user has the administrator permission
run () {
// run ban
}
}
`
$3
You can add an autocomplete method to your parameters with the AutoComplete decorator
`ts
@Command('location', 'Find location')
export class LocationCommand {
@Run()
location (
@AutoComplete(async (term) => { // make sure autocomplete is ABOVE option def
const search = await searcher.search(term) // term is the search term
return search.map(x => { name: x.name, value: x.name }) // return options
})
@Options.String('location', 'Location to find')
location: string
) {
...
}
}
`
Creating your own
You can also make your own decorators with the Decorators object
There are 3 types of decorators
$3
Base decorators go above the command class similar to the @Command() decorator. These are mostly used for internal things, but it's available
You can create this with the Decorators.createBaseDecorator() method
$3
Command decorators are the decorators that go over the method being ran, and are generally used as middleware / interceptors, or just defining extra metadata about the command
You can create these with the Decorators.createCommandDecorator() method
e.g let's create a specific user only decorator
src/decorators/UserLocked.ts
`ts
import { Decorators } from '@jadl/cmd' // global utility for everything decorators
export const UserLocked = Decorators.createCommandDecorator<[
// options
userId: string
]>(([userId], cmd) => {
// Use the canRun array of functions
cmd.canRun.push((interaction, handler) => { // interaction being the raw object
// return a boolean of whether or not the author is the user locked
return interaction.user.id === userId
})
// you can also use the onRun array of functions to disregard returning and errors
})
`
You can now use this in your command like so
`ts
import { UserLocked } from '../decorators/UserLocked'
@Command('wave', 'Wave at someone!')
export class WaveCommand {
@Run()
@UserLocked('277183033344524288') // this will now apply the above canRun method
run () {
return 'Wave!' // only ran if the user matches the user locked to this command
}
}
`
$3
Parameter decorators are used to pass data to the running method when a command has been ran
The essential way this works is that you're giving the command handler a function that will be ran and positioned to your method based on the parameter
You can create these with the Decorators.createParameterDecorator() method
e.g let's make a database decorator
src/decorators/Db.ts
`ts
import { Decorators } from '@jadl/cmd'
// you can add options the same was as was done above, however we don't need that here
export const Db = Decorators.createParameterDecorator((options) => {
return async (interaction, { worker }) => { // this method is ran EVERYTIME a command is ran, and it's return value is what shows up on the parameter for your method
return await worker.db.guildSettings.get(interaction.guild_id) // returns the guild's database
}
})
`
Now we can use the @Db() decorator shorthand in our method
`ts
import { Db } from '../decorators/Db'
@Command('wave', 'Wave at someone!')
export class WaveCommand {
@Run()
wave (
@Db() db: GuildSettings, // makes our db parameter
@Author() author: APIUser // and you can add as many of these params as you'd like!
) {
// and now db will be whatever was returned in the Db decorator!
if (!db.users.includes(author.id)) return "You can't do that!"
return 'Wave!'
}
}
`
Extras
@jadl/cmd comes with some pre-built extra interaction helpers, not necesarilly just do with commands
$3
Creates an easy to understand button navigation menu
Say we want to create a categorized help command
src/menus/HelpMenu.ts
`ts
import { ButtonMenu, Section } from '@jadl/cmd'
export class HelpMenu extends ButtonMenu {
@Section({
// button object
style: ButtonStyle.Primary,
label: 'Fun'
})
fun () {
return new Embed()
.title('Fun')
.field('/8ball', '8-ball command')
.field('/fortune', 'Gets your fortune')
}
@Section({
style: ButtonStyle.Primary,
label: 'Admin'
})
admin () {
return new Embed()
.title('Admin')
.field('/ban', 'Bans user')
.field('/kick', 'Kicks user')
}
}
export const helpMenu = new HelpMenu() // instantiate your menu
`
src/index.ts
`ts
// you need to import the menu as a WorkerInject into the command handler
new CommandHandler(
worker,
[
HelpCommand,
// ... your commands
helpMenu // the instantiated help menu
// ... whatever else
]
)
`
src/commands/HelpCommand
`ts
import { helpMenu } from '../menu/HelpMenu'
@Command('help', 'Get help')
export class HelpCommand {
@Run()
help () {
return helpMenu.start(
'fun' // the method name where you want to start
)
// .start() returns a MessageBuilder
}
}
`
This will just return a menu, with two buttons, where when you click on the corresponding button, the embed for that section will show up!
#### Stateless design
ButtonMenu's can hold stateless values within the button's custom_id. All you need to do is use the first parameter of every function, which is a mutable object that holds the data, and will update on response. E.g
`ts
interface BarMenuData {
foo: string // the selected video
}
export class BarMenu extends ButtonMenu {
@Section({
...
})
foo (data: BarMenuData) {
data.foo = 'abc' // set the data
return 'hello' // respond
}
@Section({
...
})
bar (data: BarMenuData) {
data.foo // will be 'abc' if foo was clicked first
// change or manipulate or use
...
}
}
// when starting the menu you must instantiate with initial data as well
barMenu.start('foo', { foo: 'def' })
`
Because it holds all of this data statelessly, it will even maintain it's data over restarts.
> :warning: There is a limitation on the length of the custom_id, it is not recommended to store information entered by a user / not maintainably short. Data is held in a URLSearchParams format.
$3
ComponentRunner allows you to create a button + handler within a single class, and feed it to a MessageBuilder, making code more seamless and easy to comprehend.
src/commands/HelloWorld.ts
`ts
import { ComponentRunner } from '@jadl/cmd'
import { MessageBuilder } from '@jadl/builders'
export class HelloWorldCommand {
// static to make it easier to import
static helloWorldButton = new ComponentRunner({
// component object
type: ComponentType.Button // works with select menu too
style: ButtonStyle.Primary,
custom_id: 'hello_world_button', // Make sure this is unique!
label: 'Click me'
})
.setHandle((int, worker) => {
// handle the interaction
return new Embed() // return anything parseable
.title('You click it good job.')
})
@Run()
run () {
return new MessageBuilder()
.setMessage({ content: 'Click button!!' })
.addComponentRow(HelloWorldCommand.helloWorldButton) // add the button to a component row
}
}
`
src/index.ts
`ts
// you need to import the button as a WorkerInject into the command handler
new CommandHandler(
worker,
[
HelloWorldCommand,
// ... your commands
HelloWorldCommand.helloWorldButton // the button
// ... whatever else
]
)
`
And your message will now have a button, that when pressed will return your new embed. Much nicer to organize!
$3
ModalRunner allows for a similar experience as ComponentRunner, except with modals
src/commands/HelloWorld.ts
`ts
import { ComponentRunner } from '@jadl/cmd'
import { MessageBuilder } from '@jadl/builders'
export class HelloWorldCommand {
// static to make it easier to import
static helloWorldModal = new ModalRunner('Hello World!') // enter the title in the constructor
.addTextInput('Foo Bar', { // add a label for the component
// now add any extra props, the only required one is "style"
style: TextInputStyle.Paragraph
})
.addTextInput('Welcome', { // add multiple in a builder format
style: TextInputStyle.Short,
placeholder: 'Put some stuff in here!'
})
.setHandle((options, worker, interaction) => {
// get all of your info here
// options is a cool object that will be an object mapped by the label name to it's value
console.log(options['Foo Bar'])
// our object will look something like { 'Foo Bar': '...', 'Welcome': '...' }
// and you can return a message
return new MessageBuilder()
.setMessage({ content: 'got your submission!' })
// or return nothing for no response
})
@Run()
run () {
return HelloWorldCommand.helloWorldModal.render() // return the modal to any interaction response!
}
}
``