bot

package
v3.4.0 Latest Latest
Warning

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

Go to latest
Published: Aug 6, 2024 License: ISC Imports: 24 Imported by: 3

README

What are the performance impacts of this library?

Not a lot for a Discord bot:

THIS IS OUTDATED. TODO: UPDATE.

# Cold functions, or functions that are called once in runtime:
BenchmarkConstructor-8               	  150537	      7617 ns/op
BenchmarkSubcommandConstructor-8     	  155068	      7721 ns/op

# Hot functions, or functions that can be called multiple times:
BenchmarkCall-8                      	 1000000	      1194 ns/op
BenchmarkHelp-8                      	 1751619	       680 ns/op

# Hot functions, but called implicitly on non-message-create events:
BenchmarkReflectChannelID_1Level-8   	10111023	       113 ns/op
BenchmarkReflectChannelID_5Level-8   	 1872080	       686 ns/op

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrTooManyArgs   = errors.New("too many arguments given")
	ErrNotEnoughArgs = errors.New("not enough arguments given")
)
View Source
var Break = errors.New("break middleware chain, non-fatal")

Break is a non-fatal error that could be returned from middlewares to stop the chain of execution.

View Source
var DefaultArgsParser = shellwords.Parse

DefaultArgsParser implements a parser similar to that of shell's, implementing quotes as well as escapes.

View Source
var HelpUnderline = true

HelpUnderline formats command arguments with an underline, similar to manpages.

View Source
var InvalidUsageString = func(err *InvalidUsageError) string {
	if err.Index == 0 && err.Wrap != nil {
		return "invalid usage, error: " + err.Wrap.Error() + "."
	}

	if err.Index == 0 || len(err.Args) == 0 {
		return "missing arguments. Refer to help."
	}

	body := "Invalid usage at " +

		err.Prefix +

		strings.Join(err.Args[:err.Index], " ") +

		" __" + err.Args[err.Index] + "__ " +

		strings.Join(err.Args[err.Index+1:], " ")

	if err.Wrap != nil {
		body += "\nError: " + err.Wrap.Error() + "."
	}

	return body
}
View Source
var ShellwordsEscaper = strings.NewReplacer(
	"\\", "\\\\",
)
View Source
var UnknownCommandString = func(err *UnknownCommandError) string {

	if err.Subcmd.StructName == "" || len(err.Parts) < 2 {
		return "unknown command: " + err.Parts[0] + "."
	}

	return fmt.Sprintf("unknown %s subcommand: %s.", err.Parts[0], err.Parts[1])
}

Functions

func IndentLines

func IndentLines(input string) string

IndentLine prefixes every line from input with a single-level indentation.

func NewShardFunc

func NewShardFunc(fn func(*state.State) (*Context, error)) shard.NewShardFunc

NewShardFunc creates a shard constructor that shares the same internal store. If opts sets its own cabinet, then a new store isn't created.

func Run

func Run(token string, cmd interface{}, opts func(*Context) error)

Run starts the bot, prints a message into the console, and blocks until SIGINT. "Bot" is prepended into the token automatically, similar to Start. The function will call os.Exit(1) on an initialization or cleanup error.

func Start

func Start(
	token string, cmd interface{},
	opts func(*Context) error) (wait func() error, err error)

Start quickly starts a bot with the given command. It will prepend "Bot" into the token automatically. Refer to example/ for usage.

func WaitForInterrupt

func WaitForInterrupt()

WaitForInterrupt blocks until SIGINT.

Types

type ArgsParser

type ArgsParser func(content string) ([]string, error)

ArgsParser is the function type for parsing message content into fields, usually delimited by spaces.

type Argument

type Argument struct {
	String string
	// contains filtered or unexported fields
}

Argument is each argument in a method.

func (*Argument) Type

func (a *Argument) Type() reflect.Type

type ArgumentParts

type ArgumentParts []string

ArgumentParts implements ManualParser, in case you want to parse arguments manually. It borrows the library's argument parser.

func (ArgumentParts) After

func (r ArgumentParts) After(n int) string

func (ArgumentParts) Arg

func (r ArgumentParts) Arg(n int) string

func (ArgumentParts) Length

func (r ArgumentParts) Length() int

func (*ArgumentParts) ParseContent

func (r *ArgumentParts) ParseContent(args []string) error

ParseContent implements ManualParser.

func (ArgumentParts) String

func (r ArgumentParts) String() string

func (ArgumentParts) Usage

func (r ArgumentParts) Usage() string

Usage implements Usager.

type CanHelp

type CanHelp interface {
	Help() string
}

CanHelp is an interface that subcommands can implement to return its own help message. Those messages will automatically be indented into suitable sections by the default Help() implementation. Unlike Usager or CanSetup, the Help() method will be called every time it's needed.

type CanSetup

type CanSetup interface {
	// Setup should panic when it has an error.
	Setup(*Subcommand)
}

CanSetup is used for subcommands to change variables, such as Description. This method will be triggered when InitCommands is called, which is during New for Context and during RegisterSubcommand for subcommands.

type Context

type Context struct {
	*Subcommand
	*state.State

	// Descriptive (but optional) bot name
	Name string

	// Descriptive help body
	Description string

	// Called to parse message content, default to DefaultArgsParser().
	ParseArgs ArgsParser

	// Called to check a message's prefix. The default prefix is "!". Refer to
	// NewPrefix().
	HasPrefix Prefixer

	// AllowBot makes the router also process MessageCreate events from bots.
	// This is false by default and only applies to MessageCreate.
	AllowBot bool

	// QuietUnknownCommand, if true, will not make the bot reply with an unknown
	// command error into the chat. This will apply to all other subcommands.
	// SilentUnknown controls whether or not an UnknownCommandError should be
	// returned (instead of a silent error).
	SilentUnknown struct {
		// Command when true will silent only unknown commands. Known
		// subcommands with unknown commands will still error out.
		Command bool
		// Subcommand when true will suppress unknown subcommands.
		Subcommand bool
	}

	// FormatError formats any errors returned by anything, including the method
	// commands or the reflect functions. This also includes invalid usage
	// errors or unknown command errors. Returning an empty string means
	// ignoring the error.
	//
	// By default, this field replaces all @ with @\u200b, which prevents an
	// @everyone mention.
	FormatError func(error) string

	// ErrorLogger logs any error that anything makes and the library can't
	// reply to the client. This includes any event callback errors that aren't
	// Message Create.
	ErrorLogger func(error)

	// ReplyError when true replies to the user the error. This only applies to
	// MessageCreate events.
	ReplyError bool

	// ErrorReplier is an optional function that allows changing how the error
	// is replied. It overrides ReplyError and is only used for MessageCreate
	// events.
	//
	// Note that errors that are passed in here will bypas FormatError; in other
	// words, the implementation might only care about ErrorReplier and leave
	// FormatError as it is.
	ErrorReplier func(err error, src *gateway.MessageCreateEvent) api.SendMessageData

	// EditableCommands when true will also listen for MessageUpdateEvent and
	// treat them as newly created messages. This is convenient if you want
	// to quickly edit a message and re-execute the command.
	EditableCommands bool
	// contains filtered or unexported fields
}

Context is the bot state for commands and subcommands.

Commands

A command can be created by making it a method of Commands, or whatever struct was given to the constructor. This following example creates a command with a single integer argument (which can be ran with "~example 123"):

func (c *Commands) Example(
    m *gateway.MessageCreateEvent, i int) (string, error) {

    return fmt.Sprintf("You sent: %d", i)
}

Commands' exported methods will all be used as commands. Messages are parsed with its first argument (the command) mapped accordingly to c.MapName, which capitalizes the first letter automatically to reflect the exported method name.

A command can either return either an error, or data and error. The only data types allowed are string, *discord.Embed, and *api.SendMessageData. Any other return types will invalidate the method.

Events

An event can only have one argument, which is the pointer to the event struct. It can also only return error.

func (c *Commands) Example(o *gateway.TypingStartEvent) error {
    log.Println("Someone's typing!")
    return nil
}

func New

func New(s *state.State, cmd interface{}) (*Context, error)

New makes a new context with a "~" as the prefix. cmds must be a pointer to a struct with a *Context field. Example:

type Commands struct {
    Ctx *Context
}

cmds := &Commands{}
c, err := bot.New(session, cmds)

The default prefix is "~", which means commands must start with "~" followed by the command name in the first argument, else it will be ignored.

c.Start() should be called afterwards to actually handle incoming events.

func (*Context) AddIntents

func (ctx *Context) AddIntents(i gateway.Intents)

AddIntents adds the given Gateway Intent into the Gateway. This is a convenient function that calls Gateway's AddIntent.

func (*Context) Call

func (ctx *Context) Call(event interface{}) error

Call should only be used if you know what you're doing.

func (*Context) DeriveIntents

func (ctx *Context) DeriveIntents() gateway.Intents

DeriveIntents derives all possible gateway intents from this context and all its subcommands' method handlers and middlewares.

func (*Context) FindCommand

func (ctx *Context) FindCommand(structName, methodName string) *MethodContext

FindMethod finds a method based on the struct and method name. The queried names will have their flags stripped.

// Find a command from the main context:
cmd := ctx.FindMethod("", "Method")
// Find a command from a subcommand:
cmd  = ctx.FindMethod("Starboard", "Reset")

func (*Context) Help

func (ctx *Context) Help() string

Help generates a full Help message. It serves mainly as a reference for people to reimplement and change. It doesn't show hidden commands.

func (*Context) HelpGenerate

func (ctx *Context) HelpGenerate(showHidden bool) string

HelpGenerate generates a full Help message. It serves mainly as a reference for people to reimplement and change. If showHidden is true, then hidden subcommands and commands will be shown.

func (*Context) MustRegisterSubcommand

func (ctx *Context) MustRegisterSubcommand(cmd interface{}, names ...string) *Subcommand

MustRegisterSubcommand tries to register a subcommand, and will panic if it fails. This is recommended, as subcommands won't change after initializing once in runtime, thus fairly harmless after development.

If no names are given or if the first name is empty, then the subcommand name will be derived from the struct name. If one name is given, then that name will override the struct name. Any other name values will be aliases.

It is recommended to use this method to add subcommand aliases over manually altering the Aliases slice of each Subcommand, as it does collision checks against other subcommands as well.

func (*Context) Open

func (ctx *Context) Open(cancelCtx context.Context) error

Open starts the bot context and the gateway connection. It automatically binds the needed handlers.

func (*Context) RegisterSubcommand

func (ctx *Context) RegisterSubcommand(cmd interface{}, names ...string) (*Subcommand, error)

RegisterSubcommand registers and adds cmd to the list of subcommands. It will also return the resulting Subcommand. Refer to MustRegisterSubcommand for the names argument.

func (*Context) Start

func (ctx *Context) Start() func()

Start adds itself into the session handlers. If Start is called more than once, then it does nothing. The caller doesn't have to call Start if they call Open.

The returned function is a delete function, which removes itself from the Session handlers. The delete function is not safe to use concurrently.

func (*Context) Subcommands

func (ctx *Context) Subcommands() []*Subcommand

Subcommands returns the slice of subcommands. To add subcommands, use RegisterSubcommand().

type CustomParser

type CustomParser interface {
	CustomParse(arguments string) error
}

CustomParser has a CustomParse method, which would be passed in the full message content with the prefix, command, subcommand and space trimmed. This is used for commands that require more advanced parsing than the default parser.

type InvalidUsageError

type InvalidUsageError struct {
	Prefix string
	Args   []string
	Index  int
	Wrap   error

	// TODO: usage generator?
	// Here, as a reminder
	Ctx *MethodContext
}

func (*InvalidUsageError) Error

func (err *InvalidUsageError) Error() string

func (*InvalidUsageError) Unwrap

func (err *InvalidUsageError) Unwrap() error

type ManualParser

type ManualParser interface {
	// $0 will have its prefix trimmed.
	ParseContent([]string) error
}

ManualParser has a ParseContent(string) method. If the library sees this for an argument, it will send all of the arguments into the method. If used, this should be the only argument followed after the Message Create event. Any more and the router will ignore.

type MethodContext

type MethodContext struct {
	Description string

	// MethodName is the name of the method. This field should NOT be changed.
	MethodName string

	// Command is the Discord command used to call the method.
	Command string // plumb if empty

	// Aliases is alternative way to call command in Discord.
	Aliases []string

	// Hidden if true will not be shown by (*Subcommand).HelpGenerate().
	Hidden bool

	// Variadic is true if the function is a variadic one or if the last
	// argument accepts multiple strings.
	Variadic bool

	Arguments []Argument
	// contains filtered or unexported fields
}

MethodContext is an internal struct containing fields to make this library work. As such, they're all unexported. Description, however, is exported for editing, and may be used to generate more informative help messages.

func (*MethodContext) SetName

func (cctx *MethodContext) SetName(name string)

SetName sets the command name.

func (*MethodContext) Usage

func (cctx *MethodContext) Usage() []string

type MiddlewareContext

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

func ParseMiddleware

func ParseMiddleware(mw interface{}) *MiddlewareContext

ParseMiddleware parses a middleware function. This function panics.

type Parser

type Parser interface {
	Parse(string) error
}

Parser implements a Parse(string) method for data structures that can be used as arguments.

type Prefixer

type Prefixer func(*gateway.MessageCreateEvent) (prefix string, ok bool)

Prefixer checks a message if it starts with the desired prefix. By default, NewPrefix() is used.

func NewPrefix

func NewPrefix(prefixes ...string) Prefixer

NewPrefix creates a simple prefix checker using strings. As the default prefix is "!", the function is called as NewPrefix("!").

type RawArguments

type RawArguments string

RawArguments implements the CustomParser interface, which sets all the arguments into it as raw as it could.

func (*RawArguments) CustomParse

func (a *RawArguments) CustomParse(arguments string) error

type Subcommand

type Subcommand struct {
	// Description is a string that's appended after the subcommand name in
	// (*Context).Help().
	Description string

	// Hidden if true will not be shown by (*Context).Help(). It will
	// also cause unknown command errors to be suppressed.
	Hidden bool

	// Raw struct name, including the flag (only filled for actual subcommands,
	// will be empty for Context):
	StructName string
	// Parsed command name:
	Command string

	// Aliases is alternative way to call this subcommand in Discord.
	Aliases []string

	// All registered method contexts:
	Events   []*MethodContext
	Commands []*MethodContext
	// contains filtered or unexported fields
}

Subcommand is any form of command, which could be a top-level command or a subcommand.

Allowed method signatures

These are the acceptable function signatures that would be parsed as commands or events. A return type <T> implies that return value will be ignored.

func(*gateway.MessageCreateEvent, ...) (string, error)
func(*gateway.MessageCreateEvent, ...) (*discord.Embed, error)
func(*gateway.MessageCreateEvent, ...) (*api.SendMessageData, error)
func(*gateway.MessageCreateEvent, ...) (T, error)
func(*gateway.MessageCreateEvent, ...) error
func(*gateway.MessageCreateEvent, ...)
func(<AnyEvent>) (T, error)
func(<AnyEvent>) error
func(<AnyEvent>)

func NewSubcommand

func NewSubcommand(cmd interface{}) (*Subcommand, error)

NewSubcommand is used to make a new subcommand. You usually wouldn't call this function, but instead use (*Context).RegisterSubcommand().

func (*Subcommand) AddAliases

func (sub *Subcommand) AddAliases(commandName interface{}, aliases ...string)

AddAliases add alias(es) to specific command (defined with commandName).

func (*Subcommand) AddMiddleware

func (sub *Subcommand) AddMiddleware(method, middleware interface{})

AddMiddleware adds a middleware into multiple or all methods, including commands and events. Multiple method names can be comma-delimited. For all methods, use a star (*). The given middleware argument can either be a function with one of the allowed methods or a *MiddlewareContext.

Allowed function signatures

Below are the acceptable function signatures that would be parsed as a proper middleware. A return value of type T will be ignored. If the given function is invalid, then this method will panic.

func(<AnyEvent>) (T, error)
func(<AnyEvent>) error
func(<AnyEvent>)

Note that although technically all of the above function signatures are acceptable, one should almost always return only an error.

func (*Subcommand) ChangeCommandInfo

func (sub *Subcommand) ChangeCommandInfo(method interface{}, cmd, desc string)

ChangeCommandInfo changes the matched method's Command and Description. Empty means unchanged. This function panics if the given method is not found.

func (*Subcommand) DeriveIntents

func (sub *Subcommand) DeriveIntents() gateway.Intents

DeriveIntents derives all possible gateway intents from the method handlers and middlewares.

func (*Subcommand) FindCommand

func (sub *Subcommand) FindCommand(method interface{}) *MethodContext

FindCommand finds the MethodContext using either the given method or the given method name. It panics if the given method is not found.

There are two ways to use FindCommand:

sub.FindCommand("MethodName")
sub.FindCommand(thing.MethodName)

func (*Subcommand) Help

func (sub *Subcommand) Help() string

Help calls the subcommand's Help() or auto-generates one with HelpGenerate() if the subcommand doesn't implement CanHelp. It doesn't show hidden commands by default.

func (*Subcommand) HelpGenerate

func (sub *Subcommand) HelpGenerate(showHidden bool) string

HelpGenerate auto-generates a help message, which contains only a list of commands. It does not print the subcommand header. Use this only if you want to override the Subcommand's help, else use Help(). This function will show hidden commands if showHidden is true.

func (*Subcommand) HelpShowHidden

func (sub *Subcommand) HelpShowHidden(showHidden bool) string

HelpShowHidden does the same as Help(), except it will render hidden commands if the subcommand doesn't implement CanHelp and showHiddeen is true.

func (*Subcommand) Hide

func (sub *Subcommand) Hide(method interface{})

Hide marks a command as hidden, meaning it won't be shown in help and its UnknownCommand errors will be suppressed.

func (*Subcommand) InitCommands

func (sub *Subcommand) InitCommands(ctx *Context) error

InitCommands fills a Subcommand with a context. This shouldn't be called at all, rather you should use the RegisterSubcommand method of a Context.

func (*Subcommand) IsPlumbed

func (sub *Subcommand) IsPlumbed() bool

IsPlumbed returns true if the subcommand is plumbed. To get the plumbed method, use PlumbedMethod().

func (*Subcommand) NeedsName

func (sub *Subcommand) NeedsName()

NeedsName sets the name for this subcommand. Like InitCommands, this shouldn't be called at all, rather you should use RegisterSubcommand.

func (*Subcommand) PlumbedMethod

func (sub *Subcommand) PlumbedMethod() *MethodContext

PlumbedMethod returns the plumbed method's context, or nil if the subcommand is not plumbed.

func (*Subcommand) SetPlumb

func (sub *Subcommand) SetPlumb(method interface{})

SetPlumb sets the method as the plumbed command. If method is nil, then the plumbing is also disabled.

type UnknownCommandError

type UnknownCommandError struct {
	Parts  []string // max len 2
	Subcmd *Subcommand
}

func (*UnknownCommandError) Error

func (err *UnknownCommandError) Error() string

type Usager

type Usager interface {
	Usage() string
}

Usager is used in place of the automatically parsed struct name for Parser and other interfaces.

Directories

Path Synopsis
_example
advanced_bot
Package main demonstrates an advanced bot that uses the bot router library to make commands.
Package main demonstrates an advanced bot that uses the bot router library to make commands.
extras
infer
Package infer implements reflect functions that package bot uses.
Package infer implements reflect functions that package bot uses.

Jump to

Keyboard shortcuts

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