bot

package
v0.0.0-...-4e18fe8 Latest Latest
Warning

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

Go to latest
Published: Dec 9, 2021 License: MIT Imports: 27 Imported by: 2

Documentation

Overview

Package bot provides the Bot handling all commands.

Index

Constants

This section is empty.

Variables

View Source
var ErrMiddleware = errors.New("the passed function does not resemble a valid middleware")

ErrMiddleware is the error returned if a middleware given to MiddlewareManager.TryAddMiddleware is not a valid middleware type.

View Source
var ErrUnknownCommand = errors.NewUserErrorl(unknownCommandErrorDescription)

ErrUnknownCommand is the error used if a message with a matching prefix does not contain a valid command invoke.

Functions

func DefaultErrorHandler

func DefaultErrorHandler(err error, s *state.State, ctx *plugin.Context)

func DefaultGatewayErrorHandler

func DefaultGatewayErrorHandler(err error)

func DefaultPanicHandler

func DefaultPanicHandler(recovered interface{}, s *state.State, ctx *plugin.Context)

func DefaultThrottlerCancelCheck

func DefaultThrottlerCancelCheck(err error) bool

DefaultThrottlerCancelCheck checks if the error is an *InformationalError. If so, it returns false otherwise it returns true.

func FilterGatewayError

func FilterGatewayError(err error) bool

FilterGatewayError filters out informational reconnect errors.

Types

type Bot

type Bot struct {
	State *state.State

	MiddlewareManager

	Owners []discord.UserID

	EditAge time.Duration

	ErrorHandler func(error, *state.State, *plugin.Context)

	PanicHandler             func(recovered interface{}, s *state.State, ctx *plugin.Context)
	MessageCreateMiddlewares []interface{}
	MessageUpdateMiddlewares []interface{}
	// contains filtered or unexported fields
}

Bot is the bot executing all commands.

func New

func New(o Options) (b *Bot, err error)

New creates a new Bot from the passed options. The Options.Token field must be set.

func (*Bot) AddCommand

func (b *Bot) AddCommand(cmd plugin.Command)

AddCommand adds the passed top-level command to the bot.

func (*Bot) AddIntents

func (b *Bot) AddIntents(i gateway.Intents)

AddIntents adds the passed gateway.Intents to the bot.

func (*Bot) AddModule

func (b *Bot) AddModule(mod plugin.Module)

AddModule adds the passed top-level module to the Bot.

If automatic handler adding is enabled, all methods of the Module representing a handler func will be added to the State's event handler. The same goes for all sub-modules and sub-commands of the module.

func (*Bot) AddPluginSource

func (b *Bot) AddPluginSource(name string, f PluginSourceFunc)

AddPluginSource adds the passed PluginSourceFunc under the passed unique name. The name is similar to a key and can be used later on to distinguish between different plugin sources. It is typically snake_case.

'built_in' is reserved for built-in plugins, and AddPluginSource will panic if attempting to use it.

The plugin sources will be used in the order they are added in.

func (*Bot) AddPostMiddleware

func (b *Bot) AddPostMiddleware(f interface{})

AddPostMiddleware is the same as TryAddPostMiddleware, but panics if TryAddPostMiddleware returns an error.

func (*Bot) Close

func (b *Bot) Close(ctx context.Context) error

Close closes all gateways handled by the bot.

If an error occurs, Close will attempt to close all remaining gateways first, before returning. If multiple errors occur during that process, a MultiError will be returned.

The passed context will only be checked while waiting for all event handlers to finish. Even if the context expires, Close guarantees that all gateways are closed, except if errors occurred.

func (*Bot) Open

func (b *Bot) Open(timeout time.Duration) error

Open opens a connection to the gateway and starts the bot.

If no gateway.Intents were added to the State before opening, Open will derive intents from the registered handlers.

gateway.IntentGuildMessages and gateway.IntentDirectMessages will always be added. Additionally, gateway.IntentGuilds will be added, if guild caching is enabled.

Refer to the doc of State.Open to understand how the timeout is applied.

func (*Bot) Route

func (b *Bot) Route(base *event.Base, msg *discord.Message, member *discord.Member)

Route attempts to route the passed message. It aborts if the message is not a valid invoke.

When calling the bot's middlewares, it guarantees that Message, Member, Base, BotOwnerIDs, Replier, Provider, DiscordDataProvider, and ErrorHandler are set. Further, Localizer will be set to a fallback localizer.

func (*Bot) TryAddPostMiddleware

func (b *Bot) TryAddPostMiddleware(f interface{}) error

TryAddPostMiddleware adds a middleware to the Bot that is invoked after all command and module middlewares were called. The order of invocation of post middlewares is the same as the order they were added in.

If the middleware's type is invalid, TryAddMiddleware will return ErrMiddleware.

Valid middleware types are:

  • func(*state.State, interface{})
  • func(*state.State, interface{}) error
  • func(*state.State, *event.Base)
  • func(*state.State, *event.Base) error
  • func(*state.State, *state.MessageCreateEvent)
  • func(*state.State, *state.MessageCreateEvent) error
  • func(*state.State, *state.MessageUpdateEvent)
  • func(*state.State, *state.MessageUpdateEvent) error
  • func(next CommandFunc) CommandFunc

type CommandFunc

type CommandFunc func(s *state.State, ctx *plugin.Context) error

CommandFunc is the signature of the Invoke function of a plugin.Command, without the reply (interface{}) return.

func CheckBotPermissions

func CheckBotPermissions(next CommandFunc) CommandFunc

CheckBotPermissions checks if the discord.Permissions the bot requires for the command are satisfied.

func CheckChannelTypes

func CheckChannelTypes(next CommandFunc) CommandFunc

CheckChannelTypes checks if the plugin.ChannelTypes of the command are satisfied.

func CheckHuman

func CheckHuman(next CommandFunc) CommandFunc

CheckHuman checks if the invoking message was written by a human. If so it calls the next middleware, otherwise it aborts with an *errors.InformationalError.

func CheckMessageType

func CheckMessageType(next CommandFunc) CommandFunc

CheckMessageType checks if the invoking message is of type discord.DefaultMessage. If so it calls the next middleware, otherwise it aborts with an *errors.InformationalError.

func CheckPrefix

func CheckPrefix(next CommandFunc) CommandFunc

CheckPrefix checks if the message starts with the prefix. The prefix must either be the mention of the bot, or one of the prefixes found in the context.

Direct messages don't require prefixes, however, if a message starts with a prefix, it will still be stripped from the invoke.

If the prefix doesn't match, an *errors.InformationalError will be returned.

The middleware sets the ctx.InvokeIndex context field.

func CheckRestrictions

func CheckRestrictions(next CommandFunc) CommandFunc

CheckRestrictions checks if the command is restricted.

func FindCommand

func FindCommand(next CommandFunc) CommandFunc

FindCommand attempts to find the command being invoked by the message. If no matching command is found, the middleware returns ErrUnknownCommand.

The middleware sets the InvokedCommand and ArgsIndex context fields.

func InvokeCommand

func InvokeCommand(next CommandFunc) CommandFunc

InvokeCommand invokes the command and sends a reply, if the command returned one.

func ParseArgs

func ParseArgs(next CommandFunc) CommandFunc

ParseArgs parses the ctx.RawArgs using the commands plugin.ArgConfig.

func SendTyping

func SendTyping(next CommandFunc) CommandFunc

SendTyping sends a typing event every 6 seconds until the command finishes executing.

type Middleware

type Middleware func(next CommandFunc) CommandFunc

Middleware is a middleware function.

func NewSettingsRetriever

func NewSettingsRetriever(settingsProvider SettingsProvider) Middleware

NewSettingsRetriever creates a new settings retriever middleware that retrieves the settings from the passed SettingsProvider. If the settings provider returns !ok, the returned middleware will abort by returning an *errors.InformationalError.

The returned middleware will set the Prefixes and Localizer context fields.

func NewThrottlerChecker

func NewThrottlerChecker(cancelChecker func(err error) bool) Middleware

NewThrottlerChecker creates a new bot.Middleware that checks if a command is being throttled. Additionally, it signals cancellation to the throttler.

type MiddlewareManager

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

MiddlewareManager is a struct that can be embedded in commands and modules to provide middleware capabilities. It implements Middlewarer.

MiddlewareManagers zero value is an empty MiddlewareManager.

func (*MiddlewareManager) AddMiddleware

func (m *MiddlewareManager) AddMiddleware(f interface{})

AddMiddleware is the same as TryAddMiddleware, but panics if TryAddMiddleware returns an error.

func (*MiddlewareManager) Middlewares

func (m *MiddlewareManager) Middlewares() []Middleware

Middlewares returns the middlewares of the MiddlewareManager.

func (*MiddlewareManager) TryAddMiddleware

func (m *MiddlewareManager) TryAddMiddleware(f interface{}) error

TryAddMiddleware adds the passed middleware to the MiddlewareManager. If the middleware's type is invalid, TryAddMiddleware will return ErrMiddleware.

Valid middleware types are:

  • func(*state.State, interface{})
  • func(*state.State, interface{}) error
  • func(*state.State, *event.Base)
  • func(*state.State, *event.Base) error
  • func(*state.State, *state.MessageCreateEvent)
  • func(*state.State, *state.MessageCreateEvent) error
  • func(*state.State, *state.MessageUpdateEvent)
  • func(*state.State, *state.MessageUpdateEvent) error
  • func(next CommandFunc) CommandFunc

type Middlewarer

type Middlewarer interface {
	// Middlewares returns the MiddlewareFuncs of the plugin.
	Middlewares() []Middleware
}

Middlewarer is an abstraction of a plugin that provides middlewares. If a plugin does not implement the interface, it will be assumed that the plugin does not provide any middlewares.

type Options

type Options struct {
	// Token is the bot token without the 'Bot ' prefix.
	//
	// This field is required.
	Token string

	// SettingsProvider is the settings provider for the bot.
	// If left nil, only the mention prefix will be usable.
	//
	// Default: StaticSettings()
	SettingsProvider SettingsProvider
	// Owners are the ids of the bot owners.
	// These are accessible through plugin.Context.BotOwnerIDs.
	//
	// Default: nil
	Owners []discord.UserID
	// EditAge is the oldest age an edit message may have, to trigger a
	// command.
	// If a message older than EditAge is edited, it will be ignored.
	// If this is set to 0 or less, edited messages won't be watched.
	//
	// Default: 0
	EditAge time.Duration

	// Status is the status of the bot.
	//
	// Default: gateway.OnlineStatus
	Status discord.Status
	// ActivityType is the type of activity.
	// ActivityName must be set for this to take effect.
	//
	// Default: discord.GameActivity
	ActivityType discord.ActivityType
	// ActivityName is the name of the activity the bot will display, if any.
	// If this left empty, the bot won't display any activity.
	//
	// Default: None
	ActivityName string
	// ActivityURL is the URL of the activity.
	// Currently, this is only used if the activity is set to Streaming.
	//
	// Default: None
	ActivityURL discord.URL

	// ArgParser is the plugin.ArgParser used to parse the arguments of all
	// commands that don't define a custom one.
	//
	// Default: &arg.DelimiterParser{Delimiter: ','}
	ArgParser plugin.ArgParser

	// AllowBot specifies whether bots may trigger commands.
	//
	// Settings this field has no effect if NoDefaultMiddlewares is set to
	// true.
	//
	// Default: false
	AllowBot bool

	// ThrottlerCancelChecker is the function run every time a command returns
	// with a non-nil error.
	// If the function returns true, the command's throttler will not count the
	// invoke.
	//
	// Settings this field has no effect if NoDefaultMiddlewares is set to
	// true.
	//
	// Default: DefaultThrottlerCancelCheck
	ThrottlerCancelChecker func(error) bool

	// Cabinet is the store.Cabinet used for caching.
	// Use store.NoopCabinet to deactivate caching.
	//
	// Default: defaultstore.New()
	Cabinet *store.Cabinet

	// GatewayErrorHandler is the error handler of the gateway.
	//
	// Default: DefaultGatewayErrorHandler
	GatewayErrorHandler func(error)

	// StateErrorHandler is the error handler of the *state.State, called if an
	// event handler returns with an error.
	//
	// Default: func(err error) { log.Println("event handler:", err.String()) }
	StateErrorHandler func(error)
	// StatePanicHandler is the panic handler of the *state.State, called if an
	// event handler panics.
	//
	// Default:
	// 	func(rec interface{}) {
	// 		log.Printf("event handler: panic: %+v\n%s\n", rec)
	//	}
	StatePanicHandler func(recovered interface{})

	// ErrorHandler is the handler called if a command returns with a non-nil
	// error.
	//
	// Default: DefaultErrorHandler
	ErrorHandler func(error, *state.State, *plugin.Context)
	// PanicHandler is the handler called if a command panics.
	//
	// Default: DefaultPanicHandler
	PanicHandler func(recovered interface{}, s *state.State, ctx *plugin.Context)

	// HTTPClient is the http client that will be used to make requests.
	//
	// Default: httputil.NewClient()
	HTTPClient *httputil.Client

	// MessageCreateMiddlewares are the middlewares invoked before routing the
	// command, if the command was received through a message create event.
	//
	// The signature of the middleware funcs must satisfy the requirements
	// of state middlewares.
	MessageCreateMiddlewares []interface{}
	// MessageCreateMiddlewares are the middlewares invoked before routing the
	// command, if the command was received through a message update event.
	//
	// The signature of the middleware funcs must satisfy the requirements
	// of state middlewares.
	MessageUpdateMiddlewares []interface{}

	// TotalShards is the total number of shards.
	// If it is <= 0, the recommended number of shards will be used.
	TotalShards int
	// ShardIDs are the custom shard ids this Bot instance will use.
	//
	// If setting this, you also need to set TotalShards.
	//
	// Default: 0..TotalShards
	ShardIDs []int

	// Gateways are the initial gateways to use.
	// It is an alternative to TotalShards and ShardIDs, and you shouldn't set
	// both.
	Gateways []*gateway.Gateway

	// Rescale is the function called if Discord closes any of the gateways
	// with a 4011 close code aka. 'Sharding Required'.
	//
	// Usage
	//
	// To update the state's shard manager, you must call update.
	// All zero-value options in the Options you give to update, will be set to
	// the options you used when initially creating the state.
	// This does not apply to TotalShards, ShardIDs, and Gateways, which will
	// assume the defaults described in their respective documentation.
	// Furthermore, setting ErrorHandler or PanicHandler will have no effect.
	//
	// After calling update, you should reopen the state, by calling Open.
	// Alternatively, you can call open individually for State.Gateways().
	// Note, however, that you should call Sate.Handler.Open(State.Events) once
	// before calling Gateway.Open, should you choose to open individually.
	//
	// During update, the state's State field will be replaced, as well as the
	// gateways and the rescale function. The event handler will remain
	// untouched which is why you don't need to readd your handlers.
	//
	// Default
	//
	// If you set neither TotalShards nor Gateways, this will default to the
	// below unless you define a custom Rescale function.
	//
	// 	func(update func(Options) *State) {
	//		s, err := update(Options{})
	//		if err != nil {
	//			log.Println("could not update state during rescale:", err.Error())
	//			return
	//		}
	//
	//		err = s.Open(context.Background())
	//		if err != nil {
	//			log.Println("could not open state during rescale:", err.Error())
	//		}
	//	}
	//
	// Otherwise, you are required to set this function yourself.
	Rescale func(update func(state.Options) (*state.State, error))

	// NoDefaultMiddlewares, if true, does not add the default middlewares
	// upon creation of the bot.
	// These middlewares are responsible for validation as well as filling some
	// of the plugin.Context's fields.
	//
	// If setting this to true those checks, or equivalents of them, should be
	// manually added.
	// Although possible, it is highly discouraged to disable certain checks
	// unless the resulting behavior is explicitly desired, as default or
	// third-party plugins may rely on these checks in order to perform as
	// intended.
	//
	// When the first middleware is called not all fields of the context will
	// be set.
	// Instead, they are set by the default middlewares.
	// If you want to swap out a default middleware for a custom
	// implementation, refer to its doc to see which fields it sets.
	//
	// Also keep in mind that middlewares added after field-setting
	// middlewares, may rely on those field being set.
	// Fully removing a default middleware that sets some fields without
	// setting  them otherwise is highly discouraged, as plugins will assume
	// all fields to be set.
	//
	// To see which fields are always set, i.e. which fields are available
	// to all middlewares, refer to the doc of Bot.Route.
	//
	// By default, the following middlewares are added upon creation of the
	// bot.
	//
	//  Bot.AddMiddleware(CheckMessageType)
	//  Bot.AddMiddleware(CheckHuman) // if Options.AllowBot is true
	//	Bot.AddMiddleware(NewSettingsRetriever(Options.SettingsProvider))
	//  Bot.AddMiddleware(CheckPrefix)
	//	Bot.AddMiddleware(FindCommand)
	//	Bot.AddMiddleware(CheckChannelTypes)
	//	Bot.AddMiddleware(CheckBotPermissions)
	//	Bot.AddMiddleware(NewThrottlerChecker(Options.ThrottlerCancelChecker))
	//
	//	Bot.AddPostMiddleware(CheckRestrictions)
	//	Bot.AddPostMiddleware(ParseArgs)
	//	Bot.AddPostMiddleware(InvokeCommand)
	NoDefaultMiddlewares bool
}

Options contains different configurations for a Bot.

func (*Options) SetDefaults

func (o *Options) SetDefaults() (err error)

SetDefaults fills the defaults for all options, that weren't manually set.

type PluginSourceFunc

type PluginSourceFunc = resolved.PluginSourceFunc

A PluginSourceFunc is the function used to retrieve additional plugins from other sources only available at runtime. A typical example for this would be custom commands or tags.

PluginProviders will be called in the order they were added to a Bot, until one of the returns a matching plugin.

If there are no plugins to return, all return values should be nil. If there is an error the returned plugins will be discarded, and the error will be noted in the Context of the command, available via Context.UnavailablePluginSources().

type ReplyTypeError

type ReplyTypeError struct {
	Reply interface{}
}

ReplyTypeError is the error used if a reply returned by plugin.Command.Invoke is not of a supported types.

func (*ReplyTypeError) Error

func (r *ReplyTypeError) Error() string

type SettingsProvider

type SettingsProvider func(b *event.Base, m *discord.Message) (prefixes []string, localizer *i18n.Localizer, ok bool)

SettingsProvider is the function used to retrieve the settings for the guild or direct message.

The passed *event.Base is the base of the event triggering the settings check. This will either stem from a message create event, or a message update event, if Options.EditAge is greater than 0.

First Return Value

The first return value contains the prefixes used by the guild or user. In a guild, the message must start with one of the prefixes or with a bot mention. Direct Messages are not subject to this limitation. However, if prefixes are returned for a direct message invoke and the user prefixes their message with one, the prefix will still be stripped. Similarly, if the user prefixes a direct message with the bot's mention, it will also get stripped. This behavior can be changed by replacing the CheckPrefix default middleware.

All spaces and newlines between prefix and the rest of the message will be removed before given to the router. If prefixes is empty, the only valid prefix will be a mention of the bot.

Prefix matching is non-exhaustive, meaning the first matching prefix will be used and the bot will not look for longer prefix that matches as well.

Second Return Value

The second return value is the *1i8n.Localizer, used to generate translations. New localizers can be created using i18n.NewLocalizer.

Third Return Value

The last return value is an ok-type bool. If false, the message will be discarded, regardless of whether there is a matching prefix. This intended for use in error scenarios, when no prefix or localizer can be obtained, and fallbacks are undesired.

Error Handling

SettingsProvider intentionally uses a bool instead of an error return value to signal unsuccessful execution. This has two reasons.

Mainly, because it is not ensured if the message we are checking is even a command invoke. Suppose you are unable to retrieve your bots settings. This would lead to the bot responding to every message on every server it is with some sort of error.

Secondly, errors in adam are represented through errors.Error. However, to invoke errors.Error.Handle a plugin.Context is required, which at this point is not generated yet, because the message has not been identified as a command.

For those reasons, error handling is left implementation-specific, and you are responsible for ensuring that errors are properly captured.

func StaticSettings

func StaticSettings(prefixes ...string) SettingsProvider

StaticSettings creates a new SettingsProvider that returns the same prefixes for all guilds and users. The returned localizer will always be a fallback localizer.

Jump to

Keyboard shortcuts

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