ken

package module
v0.11.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 20, 2021 License: MIT Imports: 11 Imported by: 21

README

ken   GitHub tag (latest by date) Go Report Card

⚠️ Disclaimer
This package is still in a very early state of development and future updates might include breaking changes to the API until the first official release.

(ken - japanese for Sword) - A cutting edge (haha), prototype, object-oriented and highly modular slash-command handler for Discordgo.

This work-in-progress slash-command handler is designed to be used in future versions of shinpuru.

For basic usage examples, see the basic example section. Examples on how to use middlewares can be found here.

Why should you use this package?

It may sound crazy, but ken tries to simplify the complexity behind slash-command registration and command handling while giving you full control over the event handling and registration process.

High modularity due to OOP

The command-registration and middleware system is built so that you can add whatever functionality you want to your command structure and handle it all in your middleware which can be called before and/or after command execution. The only thing required is that your commands implement the basic Command interface.

Command Pipeline

In the middleware example, you can take a look at how to implement custom functionality in your command structure and add middleware functions to handle these.

Via options you can also specify a custom state handler, if you are using something like dgrs for example.

Quality of Life Implementations

ken passes a single Ctx object to the command handlers which contains everything you need. It allows you to access raw discordgo.InteractionCreate event data, the Command instance which has been called, the discordgo.Session, as well as many utility functions.

For example, you can easily respond to the event by using the Respond method to send a response to an interaction. Defer does the same but sends a defer response so you can send follow-up messages afterwards with a delay.

Speaking of follow-up messages, there are some simple functions like FollowUp, FollowUpEmbed or FollowUpError to make building these follow-up messages easier. These functions return a single FollowUpMessage object so that you can chain calls like DeleteAfter to delete the follow-up message after a given timespan. 🤯

The Ctx also allows you to handle sub-commands using HandleSubCommands. Simply pass a name and handler function as SubCommandHandler to build your sub-command tree. The sub-command handlers are passed a specialized SubCommandCtx, which scopes the Options method to the options of the sub-command. So you can handle options like you would in a top-level command. In this example you can see a practical implementation of sub-commands.

Performance

To avoid registering and unregistering commands everytime the bot restarts, ken allows to cache commands using a CommandStore. Although disabled by default, the provided default implementation LocalCommandStore can be used. It stores the commands in a file which will be tried to read from on the next startup. You can also implement your own store, with Redis for example.

ken uses sync.Pool as object pools for the command context which are used for command and sub-command execution. This is done to avoid creating a new context object for each command execution which would put strain on the garbage collector, especially on high command execution frequencies.

Example Usage

package main

// imports ...

type TestCommand struct{}

var _ ken.Command = (*TestCommand)(nil)

func (c *TestCommand) Name() string {
	return "ping"
}

func (c *TestCommand) Description() string {
	return "Basic Ping Command"
}

func (c *TestCommand) Version() string {
	return "1.0.0"
}

func (c *TestCommand) Type() discordgo.ApplicationCommandType {
	return discordgo.ChatApplicationCommand
}

func (c *TestCommand) Options() []*discordgo.ApplicationCommandOption {
	return []*discordgo.ApplicationCommandOption{}
}

func (c *TestCommand) Run(ctx *ken.Ctx) (err error) {
	err = ctx.Respond(&discordgo.InteractionResponse{
		Type: discordgo.InteractionResponseChannelMessageWithSource,
		Data: &discordgo.InteractionResponseData{
			Content: "Pong!",
		},
	})
	return
}

func main() {
	token := os.Getenv("TOKEN")

	session, err := discordgo.New("Bot " + token)
	if err != nil {
		panic(err)
	}
	defer session.Close()

	k := ken.New(session)
	k.RegisterCommands(
		new(commands.TestCommand),
	)

	defer k.Unregister()

	err = session.Open()
	if err != nil {
		panic(err)
	}

	sc := make(chan os.Signal, 1)
	signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
	<-sc
}

You can also find a "real world" implementation in my Discord Bot shinpuru, where ken is used as main slash command framework.

FAQ

Why do my commands not show up in Discord?

This may have multiple reasons.

  1. Check if you invited your Bot with the applications.commands OAuth2 scope. This option was added for bots to be permitted to create slash commands for a guild. When you already invited your bot before that, the bot will not be able to register slash commands. To fix this, simply kick the bot and re-invite it with an invite link containing the required applications.commands scope.

    Pro-Tip: You can go to the OAuth2 Page in the Discord Application settings to generate a suitable invite link.

  2. When a command needs to be created (if you start the bot with a newly added command), It may take up to 15-30 minutes (based on personal experience) until the command shows up in Guilds. After that (when CommandStore is enabled), commands are re-used and updated, which does not take that much time.

    Pro-Tip: To create a command, you just need to specify a valid name, description and options and add it to ken's handler register. After that, start the bot in the background and then implement the command logic to bridge the time until the command shows up to test it.

  3. As I have experienced personally, sometimes, you might need to restart your Discord client until commands show op on Guilds. And sometimes you even need to kick and re-invite the bot so that they show up. I don't really know if this is a registration issue on ken's site. If you know more about that, please let me know!

Why do I get the error command name is invalid?

Discord has some very strong restrictions to the naming of commands and command options. The name of a command, sub command or command option must be unique and must match the regex ^[a-z0-9-_]{1,32}$¹.

If you are not familiar with regex, the name must match the following conditions:

  • It must only contain lowercase letters, numbers, dashes and underscores.
  • It must be at least 1 character and at most 32 characters long.

¹ The pattern described in the Discord docs ^[\w-]{1,32}$ is actually not accurate, because you can not use uppercase letters in names, but you can use underscores.


© 2021 Ringo Hoffmann (zekro Development).
Covered by the MIT Licence.

Documentation

Overview

Package ken provides an object-oriented and highly modular slash command handler for discordgo.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrEmptyCommandName         = errors.New("command name can not be empty")
	ErrCommandAlreadyRegistered = errors.New("command with the same name has already been rgistered")
	ErrInvalidMiddleware        = errors.New("the instance must implement MiddlewareBefore, MiddlewareAfter or both")
	ErrNotDMCapable             = errors.New("The executed command is not able to be executed in DMs")
)

Functions

This section is empty.

Types

type Command

type Command interface {
	// Name returns the unique name of the command.
	Name() string

	// Description returns a brief text which concisely
	// describes the commands purpose.
	Description() string

	// Run is called on command invokation getting
	// passed the invocation context.
	//
	// When something goes wrong during command
	// execution, you can return an error which is
	// then handled by Ken's OnCommandError handler.
	Run(ctx *Ctx) (err error)
}

type CommandInfo added in v0.7.0

type CommandInfo struct {
	ApplicationCommand *discordgo.ApplicationCommand `json:"application_command"`
	Implementations    map[string][]interface{}      `json:"implementations"`
}

CommandInfo contains the parsed application command structure of a command as well as additional method implementations. This also includes external implementations aside from the Command interface.

func (CommandInfo) String added in v0.7.0

func (c CommandInfo) String() string

String returns the parsed JSON data of the CommandInfo.

type CommandInfoList added in v0.7.0

type CommandInfoList []*CommandInfo

CommandInfoList is a slice of CommandInfo elements.

func (CommandInfoList) String added in v0.7.0

func (c CommandInfoList) String() string

String returns the parsed JSON data of the CommandInfoList.

type CommandOption added in v0.3.0

type CommandOption struct {
	*discordgo.ApplicationCommandInteractionDataOption
}

CommandOption wraps a ApplicationCommandInteractionDataOption to provide additional functionalities and method overrides.

func (*CommandOption) ChannelValue added in v0.3.0

func (o *CommandOption) ChannelValue(ctx *Ctx) *discordgo.Channel

ChannelValue is a utility function for casting option value to channel object.

The object is taken from the specified state instance.

func (*CommandOption) RoleValue added in v0.3.0

func (o *CommandOption) RoleValue(ctx *Ctx) *discordgo.Role

RoleValue is a utility function for casting option value to role object.

The object is taken from the specified state instance.

func (*CommandOption) StringValue added in v0.8.0

func (o *CommandOption) StringValue() (v string)

StringValue is a utility function for casting option value to string.

Because you can not pass multiline string entries to slash commands, this converts `\n` in a message to an actual line break.

func (*CommandOption) UserValue added in v0.3.0

func (o *CommandOption) UserValue(ctx *Ctx) *discordgo.User

UserValue is a utility function for casting option value to user object.

The object is taken from the specified state instance.

type CommandOptions added in v0.2.0

CommandOptions provides additional functionailities to an array of ApplicationCommandInteractionDataOptions.

func (CommandOptions) Get added in v0.2.0

func (co CommandOptions) Get(i int) *CommandOption

Get safely returns an options from command options by index.

func (CommandOptions) GetByName added in v0.2.0

func (co CommandOptions) GetByName(name string) (opt *CommandOption)

GetByName returns an option by name.

This should only be used on required options.

func (CommandOptions) GetByNameOptional added in v0.2.0

func (co CommandOptions) GetByNameOptional(name string) (opt *CommandOption, ok bool)

GetByNameOptional returns an option by name. If the option with the name does not exist, the returned value for ok is false.

This should be used for non-required options.

func (CommandOptions) Options added in v0.2.0

func (co CommandOptions) Options(i int) CommandOptions

Options returns wrapped underlying options of a sub command by ID.

type Ctx

type Ctx struct {
	ObjectMap

	// Ken keeps a reference to the main Ken instance.
	Ken *Ken
	// Session holds the discordgo session instance.
	Session *discordgo.Session
	// Event provides the InteractionCreate event
	// instance.
	Event *discordgo.InteractionCreate
	// Command provides the called command instance.
	Command Command
	// contains filtered or unexported fields
}

Ctx holds the invokation context of a command.

The Ctx must not be stored or used after command execution.

func (*Ctx) Channel

func (c *Ctx) Channel() (*discordgo.Channel, error)

Channel tries to fetch the channel object from the contained channel ID using the specified state manager.

func (*Ctx) Defer

func (c *Ctx) Defer() (err error)

Defer is shorthand for Respond with an InteractionResponse of the type InteractionResponseDeferredChannelMessageWithSource.

It should be used when the interaction response can not be instantly returned.

func (*Ctx) FollowUp

func (c *Ctx) FollowUp(wait bool, data *discordgo.WebhookParams) (fum *FollowUpMessage)

FollowUp creates a follow up message to the interaction event and returns a FollowUpMessage object containing the created message as well as an error instance, if an error occurred.

This way it allows to be chained in one call with subsequent FollowUpMessage method calls.

func (*Ctx) FollowUpEmbed

func (c *Ctx) FollowUpEmbed(emb *discordgo.MessageEmbed) (fum *FollowUpMessage)

FollowUpEmbed is shorthand for FollowUp with an embed payload as passed.

func (*Ctx) FollowUpError

func (c *Ctx) FollowUpError(content, title string) (fum *FollowUpMessage)

FollowUpError is shorthand for FollowUpEmbed with an error embed as message with the passed content and title.

func (*Ctx) Get

func (c *Ctx) Get(key string) (v interface{})

Get either returns an instance from the internal object map - if existent. Otherwise, the object is looked up in the specified dependency provider, if available. When no object was found in either of both maps, nil is returned.

func (*Ctx) Guild

func (c *Ctx) Guild() (*discordgo.Guild, error)

Channel tries to fetch the guild object from the contained guild ID using the specified state manager.

func (*Ctx) HandleSubCommands added in v0.6.0

func (c *Ctx) HandleSubCommands(handler ...SubCommandHandler) (err error)

HandleSubCommands takes a list of sub command handles. When the command is executed, the options are scanned for the sib command calls by their names. If one of the registered sub commands has been called, the specified handler function is executed.

If the call occured, the passed handler function is getting passed the scoped sub command Ctx.

The SubCommandCtx passed must not be stored or used after command execution.

func (*Ctx) MessageCommand added in v0.11.0

func (c *Ctx) MessageCommand() (cmd MessageCommand, ok bool)

MessageCommand returns the contexts Command as a MessageCommand interface.

func (*Ctx) Options added in v0.2.0

func (c *Ctx) Options() CommandOptions

Options returns the application command data options with additional functionality methods.

func (*Ctx) Respond

func (c *Ctx) Respond(r *discordgo.InteractionResponse) (err error)

Respond to an interaction event with the given interaction response payload.

func (*Ctx) RespondEmbed added in v0.10.0

func (c *Ctx) RespondEmbed(emb *discordgo.MessageEmbed) (err error)

RespondEmbed is shorthand for Respond with an embed payload as passed.

func (*Ctx) RespondError added in v0.10.0

func (c *Ctx) RespondError(content, title string) (err error)

RespondError is shorthand for RespondEmbed with an error embed as message with the passed content and title.

func (*Ctx) SlashCommand added in v0.11.0

func (c *Ctx) SlashCommand() (cmd SlashCommand, ok bool)

SlashCommand returns the contexts Command as a SlashCommand interface.

func (*Ctx) User

func (c *Ctx) User() (u *discordgo.User)

User returns the User object of the executor either from the events User object or from the events Member object.

func (*Ctx) UserCommand added in v0.11.0

func (c *Ctx) UserCommand() (cmd UserCommand, ok bool)

UserCommand returns the contexts Command as a UserCommand interface.

type DmCapable

type DmCapable interface {
	// IsDmCapable returns true if the command can
	// be used in DMs.
	IsDmCapable() bool
}

DmCapable extends a command to specify if it is able to be executed in DMs or not.

type EmbedColors

type EmbedColors struct {
	// Default defines the default embed color used when
	// no other color is specified.
	Default int
	// Error specifies the embed color of error embeds.
	Error int
}

EmbedColors lets you define custom colors for embeds.

type FollowUpMessage

type FollowUpMessage struct {
	*discordgo.Message

	// Error contains the error instance of
	// error occurrences during method execution.
	Error error
	// contains filtered or unexported fields
}

FollowUpMessage wraps an interaction follow up message and collected errors.

func (*FollowUpMessage) Delete

func (m *FollowUpMessage) Delete() (err error)

Delete removes the follow up message.

func (*FollowUpMessage) DeleteAfter

func (m *FollowUpMessage) DeleteAfter(d time.Duration) *FollowUpMessage

DeleteAfter queues a deletion of the follow up message after the specified duration.

func (*FollowUpMessage) Edit

func (m *FollowUpMessage) Edit(data *discordgo.WebhookEdit) (err error)

Edit overwrites the given follow up message with the data specified.

func (*FollowUpMessage) EditEmbed added in v0.7.1

func (m *FollowUpMessage) EditEmbed(emb *discordgo.MessageEmbed) (err error)

EditEmbed is shorthand for edit with the passed embed as WebhookEdit data.

type Ken

type Ken struct {
	// contains filtered or unexported fields
}

Ken is the handler to register, manage and life-cycle commands as well as middlewares.

func New

func New(s *discordgo.Session, options ...Options) (k *Ken, err error)

New initializes a new instance of Ken with the passed discordgo Session s and optional Options.

If no options are passed, default parameters will be applied.

func (*Ken) GetCommandInfo added in v0.7.0

func (k *Ken) GetCommandInfo(keyTransformer ...keyTransformerFunc) (cis CommandInfoList)

GetCommandInfo returns a list with information about all registered commands.

This call is defaultly cached after first execution because it uses reflection to inspect external implementations. Because this can be performance straining when the method is called frequently, the result is cached until the number of commands changes.

If you want to disable this behavior, you can set Config.DisableCommandInfoCache to true on intializing Ken.

func (*Ken) RegisterCommands

func (k *Ken) RegisterCommands(cmds ...Command) (err error)

RegisterCommands registers the passed commands to the command register.

Keep in mind that commands are registered by Name, so there can be only one single command per name.

func (*Ken) RegisterMiddlewares

func (k *Ken) RegisterMiddlewares(mws ...interface{}) (err error)

RegisterMiddlewares allows to register passed commands to the middleware callstack.

Therefore, you can register MiddlewareBefore, which is called before the command handler is executed, or MiddlewareAfter, which is called directly after the command handler has been called. Of course, you can also implement both interfaces in the same middleware.

The middleware call order is determined by the order of middleware registration in each area ('before' or 'after').

func (*Ken) Unregister

func (k *Ken) Unregister() (err error)

Unregister should be called to cleanly unregister all registered slash commands from the discord backend.

This can be skipped if you are using a CommandStore.

type MessageCommand added in v0.11.0

type MessageCommand interface {
	Command

	// TypeMessage is used to differenciate between
	// UserCommand and MessageCommand which have
	// the same structure otherwise.
	//
	// This method must only be implemented and
	// will never be called by ken, so it can be
	// completely empty.
	TypeMessage()
}

MessageCommand defines a callable message command.

type Middleware

type Middleware interface {
	MiddlewareBefore
	MiddlewareAfter
}

Middleware combines MiddlewareBefore and MiddlewareAfter.

type MiddlewareAfter

type MiddlewareAfter interface {
	// After is called after a command has been executed.
	//
	// It is getting passed the Ctx which was also passed
	// to the command Run handler. Also, the method is
	// getting passed potential errors which were returned
	// from the executed command to be custom handled.
	//
	// The error returned is finally passed to the
	// OnCommandError handler.
	After(ctx *Ctx, cmdError error) (err error)
}

MiddlewareAfter specifies a middleware which is called after the execution of a command.

type MiddlewareBefore

type MiddlewareBefore interface {
	// Before is called before a command is executed.
	// It is getting passed the same context as which
	// will be passed to the command. So you are able
	// to attach or alter data of the context.
	//
	// Ctx contains an ObjectMap which can be used to
	// pass data to the command.
	//
	// The method returns a bool which specifies if
	// the subsequent command should be executed. If
	// it is set to false, the execution will be
	// canceled.
	//
	// The error return value is either bubbled up to
	// the OnCommandError, when next is set to false.
	// Otherwise, the error is passed to OnCommandError
	// but the execution continues.
	Before(ctx *Ctx) (next bool, err error)
}

MiddlewareBefore specifies a middleware which is called before the execution of a command.

type ObjectInjector

type ObjectInjector interface {
	// Set stores the given object by given
	// key.
	Set(key string, obj interface{})
}

ObjectInjector specifies an instance which allows storing an object by string key.

type ObjectMap

type ObjectMap interface {
	ObjectProvider
	ObjectInjector

	// Purge cleans all stored objects and
	// keys from the provider.
	Purge()
}

ObjectMap combines ObjectProvider and ObjectInjector.

type ObjectProvider

type ObjectProvider interface {
	// Get returns a stored object by its
	// key, if existent.
	Get(key string) interface{}
}

ObjectProvider specifies an instance providing objects by string key.

type Options

type Options struct {
	// State specifies the state manager to be used.
	// When not specified, the default discordgo state
	// manager is used.
	State state.State
	// CommandStore specifies a storage instance to
	// cache created commands.
	CommandStore store.CommandStore
	// DependencyProvider can be used to inject dependencies
	// to be used in a commands or middlewares Ctx by
	// a string key.
	DependencyProvider ObjectProvider
	// EmbedColors lets you define custom colors for embeds.
	EmbedColors EmbedColors
	// DisableCommandInfoCache disabled caching
	// the result of Ken#GetCommandInfo() after
	// first call of the method.
	//
	// Only disable if you change information of
	// a command during runtime.
	DisableCommandInfoCache bool
	// OnSystemError is called when a recoverable
	// system error occurs inside Ken's lifecycle.
	OnSystemError func(context string, err error, args ...interface{})
	// OnCommandError is called when an error occurs
	// during middleware or command execution.
	OnCommandError func(err error, ctx *Ctx)
}

Options holds configurations for Ken.

type SlashCommand added in v0.11.0

type SlashCommand interface {
	Command

	// Version returns the commands semantic version.
	Version() string

	// Options returns an array of application
	// command options.
	Options() []*discordgo.ApplicationCommandOption
}

SlashCommand defines a callable slash command.

type SubCommandCtx added in v0.5.0

type SubCommandCtx struct {
	*Ctx

	SubCommandName string
}

SubCommandCtx wraps the current command Ctx and with the called sub command name and scopes the command options to the options of the called sub command.

The SubCommandCtx must not be stored or used after command execution.

func (*SubCommandCtx) Options added in v0.5.0

func (c *SubCommandCtx) Options() CommandOptions

Options returns the options array of the called sub command.

type SubCommandHandler added in v0.5.0

type SubCommandHandler struct {
	Name string
	Run  func(ctx *SubCommandCtx) error
}

SubCommandHandler is the handler function used to handle sub command calls.

type UserCommand added in v0.11.0

type UserCommand interface {
	Command

	// TypeUser is used to differenciate between
	// UserCommand and MessageCommand which have
	// the same structure otherwise.
	//
	// This method must only be implemented and
	// will never be called by ken, so it can be
	// completely empty.
	TypeUser()
}

UserCommand defines a callable user command.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL