Documentation
¶
Overview ¶
Package strut builds Discord commands from Go structs, in the spirit of poise.
A command is a struct whose exported fields are its options, plus a Meta method describing it and a Run method executing it:
type Ban struct {
User discord.ResolvedMember `strut:"user" desc:"Member to ban"`
Reason strut.Option[string] `strut:"reason" desc:"Why"`
}
func (*Ban) Meta() strut.Meta {
return strut.Meta{Name: "ban", Description: "Ban a member"}
}
func (c *Ban) Run(e *strut.Event[Data]) error {
return e.Sayf("banned %s", c.Reason.LoadOr("no reason"))
}
f := strut.New(client, strut.Options[Data]{Data: state})
f.Add(&Ban{}).MustValidate()
client.AddEventListeners(f)
f.SyncGuild(ctx, guildID)
Structs are analysed once, when a command is added. Everything Discord would reject is reported by Validate at start up rather than on first use.
Option[T] marks an option optional, keeping absent distinct from zero. Meta carries a command's data; behaviour goes on the optional interfaces Allower, Cooldowner, ErrorHandler, Localizer, Helper and Parent, each detected once at registration.
One struct can serve several surfaces at once through Meta.Kinds: slash commands, both context menus, and prefix commands when Options.Prefix is set. Components and modals are structs too, with their state encoded into the custom_id.
Commands can be exercised without a gateway; see Invoke, InvokeText, InvokeComponent, InvokeModal and InvokeAutocomplete.
See the README and _examples for a fuller tour.
Index ¶
- Constants
- Variables
- func DefaultErrorHandler[D any](err *Error[D])
- func SendModal[M, D any](e *Event[D], prefill M, override ...ModalSpec) error
- type Allower
- type Argument
- type ArgumentError
- type Check
- type Choice
- type Command
- type CommandInfo
- type Component
- func Button[S any](state S, label string, style Style) Component
- func ChannelSelect[S any](state S, placeholder string) Component
- func LinkButton(label, url string) Component
- func MentionableSelect[S any](state S, placeholder string) Component
- func RoleSelect[S any](state S, placeholder string) Component
- func Select[S, V any](state S, placeholder string, choices ...Choice[V]) Component
- func UserSelect[S any](state S, placeholder string) Component
- type Container
- type CooldownConfig
- type CooldownKey
- type CooldownOpt
- type CooldownTracker
- type Cooldowner
- type EditTracker
- type EditTrackerConfig
- type Error
- type ErrorHandler
- type ErrorKind
- type Event
- func (e *Event[D]) Author() discord.User
- func (e *Event[D]) ChannelID() snowflake.ID
- func (e *Event[D]) Client() *bot.Client
- func (e *Event[D]) Command() Meta
- func (e *Event[D]) Context() context.Context
- func (e *Event[D]) Data() D
- func (e *Event[D]) Defer() error
- func (e *Event[D]) DeferEphemeral() error
- func (e *Event[D]) Edit(r Reply) error
- func (e *Event[D]) Followup(r Reply) (*discord.Message, error)
- func (e *Event[D]) GuildID() *snowflake.ID
- func (e *Event[D]) Interaction() (*handler.CommandEvent, bool)
- func (e *Event[D]) Kind() Kind
- func (e *Event[D]) Locale() discord.Locale
- func (e *Event[D]) Logger() *slog.Logger
- func (e *Event[D]) Member() *discord.ResolvedMember
- func (e *Event[D]) Message() (*events.MessageCreate, bool)
- func (e *Event[D]) Reply(r Reply) error
- func (e *Event[D]) Say(s string) error
- func (e *Event[D]) Sayf(format string, a ...any) error
- type EventOpt
- type FieldSpec
- type Framework
- func (f *Framework[D]) Add(cmds ...Command[D]) *Framework[D]
- func (f *Framework[D]) Command(name string) (CommandInfo, bool)
- func (f *Framework[D]) Commands() iter.Seq[CommandInfo]
- func (f *Framework[D]) Cooldowns() *CooldownTracker
- func (f *Framework[D]) Data() D
- func (f *Framework[D]) InitOwners(ctx context.Context, opts ...rest.RequestOpt) error
- func (f *Framework[D]) MustValidate() *Framework[D]
- func (f *Framework[D]) Mux() *handler.Mux
- func (f *Framework[D]) OnComponent[S any](h func(*Event[D], S) error) *Framework[D]
- func (f *Framework[D]) OnEntitySelect[S any](h func(*Event[D], S, []snowflake.ID) error) *Framework[D]
- func (f *Framework[D]) OnEvent(event bot.Event)
- func (f *Framework[D]) OnModal[M any](h func(*Event[D], M) error) *Framework[D]
- func (f *Framework[D]) OnSelect[S, V any](h func(*Event[D], S, []V) error) *Framework[D]
- func (f *Framework[D]) OptionTypes(route string) (map[string]discord.ApplicationCommandOptionType, bool)
- func (f *Framework[D]) Schema() []discord.ApplicationCommandCreate
- func (f *Framework[D]) SchemaWhere(keep func(Meta) bool) []discord.ApplicationCommandCreate
- func (f *Framework[D]) Shutdown(ctx context.Context) error
- func (f *Framework[D]) ShuttingDown() bool
- func (f *Framework[D]) SimulateAutocomplete(route, option string, p Partial, e *Event[D]) ([]discord.AutocompleteChoice, error)
- func (f *Framework[D]) SimulateCommand(route string, data discord.SlashCommandInteractionData, e *Event[D]) (*Error[D], error)
- func (f *Framework[D]) SimulateComponent(state any, e *Event[D]) (*Error[D], error)
- func (f *Framework[D]) SimulateModal(modal reflect.Type, data discord.ModalSubmitInteractionData, e *Event[D]) (*Error[D], error)
- func (f *Framework[D]) SimulateText(content string, e *Event[D]) (*Error[D], error)
- func (f *Framework[D]) Sync(ctx context.Context, o SyncOptions, opts ...rest.RequestOpt) (bool, error)
- func (f *Framework[D]) SyncGlobal(ctx context.Context, opts ...rest.RequestOpt) error
- func (f *Framework[D]) SyncGuild(ctx context.Context, guildID snowflake.ID, opts ...rest.RequestOpt) error
- func (f *Framework[D]) Use(m ...Middleware[D]) *Framework[D]
- func (f *Framework[D]) UseRaw(m ...handler.Middleware) *Framework[D]
- func (f *Framework[D]) Validate() error
- type Handler
- type Helper
- type Kind
- type L10n
- type Lexer
- type Localizer
- type MemoryStore
- type Mention
- type MessageTarget
- type Meta
- type Middleware
- type ModalOpener
- type ModalSpec
- type Modaler
- type Option
- type OptionInfo
- type Options
- type Parent
- type Partial
- type PrefixOptions
- type Reply
- func (r Reply) AsEphemeral() Reply
- func (r Reply) AsReply() Reply
- func (r Reply) Container(c Container) Reply
- func (r Reply) Row(components ...Component) Reply
- func (r Reply) Section(s Section) Reply
- func (r Reply) Separator() Reply
- func (r Reply) Text(content string) Reply
- func (r Reply) Textf(format string, a ...any) Reply
- func (r Reply) WithAllowedMentions(a *discord.AllowedMentions) Reply
- func (r Reply) WithComponents(c ...discord.LayoutComponent) Reply
- func (r Reply) WithContent(s string) Reply
- func (r Reply) WithEmbeds(e ...discord.Embed) Reply
- func (r Reply) WithFiles(f ...*discord.File) Reply
- type Responder
- type Runner
- type Section
- type Source
- type Store
- type Style
- type SubcommandInfo
- type SyncOptions
- type TextArgument
- type TrackedReply
- type Translation
- type UserTarget
- type Validator
Constants ¶
const ( Primary = discord.ButtonStylePrimary Secondary = discord.ButtonStyleSecondary Success = discord.ButtonStyleSuccess Danger = discord.ButtonStyleDanger Link = discord.ButtonStyleLink )
Variables ¶
var ErrNotYours = errors.New("strut: this component is not yours")
ErrNotYours is a convenience check failure for components clicked by someone other than the user they were created for.
Functions ¶
func DefaultErrorHandler ¶
DefaultErrorHandler replies to the user for failures they caused and logs the rest.
Types ¶
type Argument ¶
type Argument interface {
OptionType() discord.ApplicationCommandOptionType
FromInteraction(data discord.SlashCommandInteractionData, name string) error
}
Argument makes a type usable as a command option. Declare both methods on the pointer receiver; the field itself stays the value type.
type ArgumentError ¶
ArgumentError is a single option that could not be supplied. It is wrapped in an Error with kind ErrArgumentParse before reaching a handler.
func (*ArgumentError) Error ¶
func (e *ArgumentError) Error() string
func (*ArgumentError) Unwrap ¶
func (e *ArgumentError) Unwrap() error
type Check ¶
Check reports whether a command may run. Returning false without an error rejects silently; returning an error reports it through OnError.
type Command ¶
Command is a top-level command. Subcommands only implement Runner: their name and description come from the parent's field tag, so nesting is described where it is declared.
type CommandInfo ¶
type CommandInfo struct {
Meta Meta
// Options are the command's own options, empty when it holds
// subcommands instead.
Options []OptionInfo
// Subcommands is the tree below this command, one level per nesting.
Subcommands []SubcommandInfo
}
CommandInfo describes one registered command, for a help command or any other listing. It is derived from the payload sent to Discord, so it says what a user will actually see.
type Component ¶
type Component struct {
// contains filtered or unexported fields
}
Component is one built component. A build error travels with it and surfaces when the reply is sent, so a builder chain stays readable.
Everything is placed with Row. Discord decides what may share one: up to five buttons, or a single select menu.
func ChannelSelect ¶
ChannelSelect picks channels.
func LinkButton ¶
LinkButton opens a URL. It carries no state, since Discord never sends an interaction for it.
func MentionableSelect ¶
MentionableSelect picks users and roles together.
func RoleSelect ¶
RoleSelect picks roles.
func UserSelect ¶
UserSelect picks users. The chosen ids reach the handler registered with OnEntitySelect.
func (Component) WithDisabled ¶
WithDisabled greys the component out, which is how a menu is closed off once a choice has been made.
type Container ¶
type Container struct {
// contains filtered or unexported fields
}
Container groups components behind an accent colour.
type CooldownConfig ¶
type CooldownConfig struct {
Global time.Duration
User time.Duration
Guild time.Duration
Channel time.Duration
Member time.Duration
}
CooldownConfig is a set of cooldowns. Every non-zero duration is enforced, and the longest one still running wins.
type CooldownKey ¶
type CooldownKey struct {
Command string
User snowflake.ID
Guild snowflake.ID
Channel snowflake.ID
}
CooldownKey identifies who ran a command and where.
type CooldownOpt ¶
type CooldownOpt func(*CooldownTracker)
CooldownOpt configures a CooldownTracker.
func WithCooldownStore ¶
func WithCooldownStore(s Store[bucket, time.Time]) CooldownOpt
WithCooldownStore keeps cooldowns somewhere other than this process, so they survive a restart and hold across shards.
type CooldownTracker ¶
type CooldownTracker struct {
// contains filtered or unexported fields
}
CooldownTracker records when each bucket was last used.
Entries expire with the cooldown that created them, so the tracker stays bounded without any sweeping of its own.
func NewCooldownTracker ¶
func NewCooldownTracker(opts ...CooldownOpt) *CooldownTracker
NewCooldownTracker returns a tracker backed by an in-process store unless given another one.
func (*CooldownTracker) Remaining ¶
func (t *CooldownTracker) Remaining(cfg CooldownConfig, key CooldownKey) (time.Duration, bool)
Remaining reports how long until the command may run again, and whether any bucket is still cooling down.
func (*CooldownTracker) Reset ¶
func (t *CooldownTracker) Reset(cfg CooldownConfig, key CooldownKey)
Reset clears every bucket for one invocation's scopes.
func (*CooldownTracker) Use ¶
func (t *CooldownTracker) Use(cfg CooldownConfig, key CooldownKey)
Use marks the command as just run, starting every configured bucket.
type Cooldowner ¶
type Cooldowner interface {
Cooldown() CooldownConfig
}
Cooldowner rate limits a command.
type EditTracker ¶
type EditTracker struct {
// contains filtered or unexported fields
}
EditTracker remembers which bot message answered which invocation, so an edited command can update its original reply instead of sending a new one.
func NewEditTracker ¶
func NewEditTracker(cfg EditTrackerConfig) *EditTracker
NewEditTracker returns a tracker with the given configuration.
type EditTrackerConfig ¶
type EditTrackerConfig struct {
// TTL is how long an invocation stays editable. Zero means ten minutes.
TTL time.Duration
// ExecuteUntrackedEdits runs a command when an edit turns a message into
// a valid invocation, even though the original was not one.
ExecuteUntrackedEdits bool
// IgnoreEditsIfNotResponded skips edits to invocations the bot never
// answered.
IgnoreEditsIfNotResponded bool
// Store keeps the invocation to reply mapping. Nil uses an in-process
// store, which is usually right: a guild always lands on one shard.
Store Store[snowflake.ID, TrackedReply]
}
EditTrackerConfig tunes how message edits are followed.
type Error ¶
type Error[D any] struct { Kind ErrorKind Event *Event[D] Err error Input string // ErrArgumentParse: the text that failed to parse Field string // ErrArgumentParse: the option it belongs to Remaining time.Duration // ErrCooldown Missing discord.Permissions // ErrMissingUserPerms, ErrMissingBotPerms Stack []byte // ErrCommandPanic }
Error is every failure strut reports, from argument parsing through to a command returning an error.
type ErrorHandler ¶
ErrorHandler overrides Options.OnError for one command.
type ErrorKind ¶
type ErrorKind int
ErrorKind classifies why an invocation failed. Sentinel values are comparable with errors.Is against an *Error.
type Event ¶
type Event[D any] struct { // contains filtered or unexported fields }
Event carries one command invocation, abstracting over slash, context menu and prefix sources. Named Event, not Context, to leave context.Context the name callers expect.
func NewEvent ¶
func NewEvent[D any](data D, meta Meta, kind Kind, src Source, res Responder, opts ...EventOpt[D]) *Event[D]
NewEvent builds an Event around the given seams. It exists for test harnesses and alternative front ends; a bot never calls it.
func ShowModal ¶
ShowModal displays a modal and waits for it to be submitted.
The returned Event is bound to the submission, not the original interaction, and is the one that must be responded to. A dismissed modal is indistinguishable from an abandoned one, so waiting ends in ErrModalTimeout either way.
func ShowModalWith ¶
ShowModalWith displays a modal prefilled from an existing value.
func (*Event[D]) Data ¶
func (e *Event[D]) Data() D
Data returns the application state passed to New.
func (*Event[D]) Defer ¶
Defer buys more than Discord's three second response window. Prefix commands broadcast typing instead.
func (*Event[D]) DeferEphemeral ¶
DeferEphemeral defers with an ephemeral response.
func (*Event[D]) Interaction ¶
func (e *Event[D]) Interaction() (*handler.CommandEvent, bool)
Interaction returns the underlying disgo command event. It is absent for prefix invocations, which have no interaction to respond to.
func (*Event[D]) Logger ¶
Logger returns the framework logger, tagged with the invocation, so a bot does not repeat those fields on every line.
func (*Event[D]) Member ¶
func (e *Event[D]) Member() *discord.ResolvedMember
Member returns the invoking member, or nil outside a guild.
func (*Event[D]) Message ¶
func (e *Event[D]) Message() (*events.MessageCreate, bool)
Message returns the underlying message event, set only for prefix invocations.
type EventOpt ¶
EventOpt configures an Event built by NewEvent.
func WithContext ¶
WithContext sets the invocation's context.
func WithFramework ¶
WithFramework binds the event to a framework, so replies pick up its defaults and commands can reach it.
func WithModalOpener ¶
func WithModalOpener[D any](o ModalOpener) EventOpt[D]
WithModalOpener lets the event open modals.
type Framework ¶
type Framework[D any] struct { // contains filtered or unexported fields }
Framework routes Discord interactions to commands. It implements bot.EventListener, so it is added to a client like any other listener.
func (*Framework[D]) Add ¶
Add registers commands. Problems are collected rather than returned, so a chain of calls stays readable; Validate reports them.
Add is not safe to call once the client is receiving events. Register everything before OpenGateway.
func (*Framework[D]) Command ¶
func (f *Framework[D]) Command(name string) (CommandInfo, bool)
Command returns one registered command by name.
func (*Framework[D]) Commands ¶
func (f *Framework[D]) Commands() iter.Seq[CommandInfo]
Commands iterates every registered command.
func (*Framework[D]) Cooldowns ¶
func (f *Framework[D]) Cooldowns() *CooldownTracker
Cooldowns exposes the tracker, for commands using Meta.ManualCooldown.
func (*Framework[D]) Data ¶
func (f *Framework[D]) Data() D
Data returns the application state every command is given.
func (*Framework[D]) InitOwners ¶
func (*Framework[D]) MustValidate ¶
MustValidate panics if any command is invalid. Call it during start up: an invalid command is a programming error, not a runtime condition.
func (*Framework[D]) Mux ¶
Mux exposes the underlying router, so raw disgo handlers can be mixed in.
func (*Framework[D]) OnComponent ¶
OnComponent registers a handler for components carrying S. The route comes from S's type name, so it survives a restart.
func (*Framework[D]) OnEntitySelect ¶
func (f *Framework[D]) OnEntitySelect[S any](h func(*Event[D], S, []snowflake.ID) error) *Framework[D]
OnEntitySelect registers a handler for a user, role, channel or mentionable select carrying S. Those menus always return ids.
func (*Framework[D]) OnEvent ¶
OnEvent implements bot.EventListener. Handlers run on their own goroutine, so a slow command does not hold up the bot.
func (*Framework[D]) OnModal ¶
OnModal registers a handler for modals of type M sent with SendModal.
func (*Framework[D]) OnSelect ¶
OnSelect registers a handler for a select menu carrying S. The chosen values are decoded into V.
func (*Framework[D]) OptionTypes ¶
func (f *Framework[D]) OptionTypes(route string) (map[string]discord.ApplicationCommandOptionType, bool)
OptionTypes reports the wire type of each option of a command, so a harness can build interaction data without restating them.
func (*Framework[D]) Schema ¶
func (f *Framework[D]) Schema() []discord.ApplicationCommandCreate
Schema returns the payloads for every registered command.
func (*Framework[D]) SchemaWhere ¶
func (f *Framework[D]) SchemaWhere(keep func(Meta) bool) []discord.ApplicationCommandCreate
SchemaWhere returns the payloads for the commands keep accepts. A nil keep returns everything.
func (*Framework[D]) Shutdown ¶
Shutdown stops accepting interactions and waits for the ones already running, giving up when ctx does. Call it before closing the disgo client.
An invocation waiting on a user, such as a blocking ShowModal, is released rather than holding the drain open.
func (*Framework[D]) ShuttingDown ¶
ShuttingDown reports whether Shutdown has been called.
func (*Framework[D]) SimulateAutocomplete ¶
func (f *Framework[D]) SimulateAutocomplete(route, option string, p Partial, e *Event[D]) ([]discord.AutocompleteChoice, error)
SimulateAutocomplete asks a command's autocomplete method for suggestions.
func (*Framework[D]) SimulateCommand ¶
func (f *Framework[D]) SimulateCommand(route string, data discord.SlashCommandInteractionData, e *Event[D]) (*Error[D], error)
SimulateCommand runs the command at route against the given interaction data.
func (*Framework[D]) SimulateComponent ¶
SimulateComponent runs the handler registered for the given state value.
func (*Framework[D]) SimulateModal ¶
func (f *Framework[D]) SimulateModal(modal reflect.Type, data discord.ModalSubmitInteractionData, e *Event[D]) (*Error[D], error)
SimulateModal runs the handler registered for modals of the given type.
func (*Framework[D]) SimulateText ¶
SimulateText runs a prefix invocation. content is the whole message, including the prefix.
func (*Framework[D]) Sync ¶
func (f *Framework[D]) Sync(ctx context.Context, o SyncOptions, opts ...rest.RequestOpt) (bool, error)
Sync registers commands with Discord, reporting whether it wrote anything.
func (*Framework[D]) SyncGlobal ¶
SyncGlobal registers every command globally.
func (*Framework[D]) SyncGuild ¶
func (f *Framework[D]) SyncGuild(ctx context.Context, guildID snowflake.ID, opts ...rest.RequestOpt) error
SyncGuild registers every command in one server, which takes effect immediately rather than after Discord's global propagation delay.
func (*Framework[D]) Use ¶
func (f *Framework[D]) Use(m ...Middleware[D]) *Framework[D]
Use adds middleware around every command.
type Helper ¶
Helper supplies help text computed at display time. strut never calls it; it is the shape a help command can assert against, so commands agree on one.
type Kind ¶
type Kind uint8
Kind is the set of invocation surfaces a command is exposed on.
type L10n ¶
type L10n map[discord.Locale]Translation
L10n maps a locale to a translated name and description.
type Lexer ¶
type Lexer struct {
// contains filtered or unexported fields
}
Lexer tokenises the argument text of a prefix command. Tokens are whitespace separated, with double quotes grouping a token that contains spaces. A backslash escapes the next character inside quotes.
type Localizer ¶
type Localizer interface {
Localize() L10n
}
Localizer supplies translations for the command itself. Option translations come from name.<locale> and desc.<locale> tags.
type MemoryStore ¶
type MemoryStore[K comparable, V any] struct { // contains filtered or unexported fields }
MemoryStore is the default Store: a mutex guarded map that drops expired entries as it goes. It is safe for concurrent use.
func NewMemoryStore ¶
func NewMemoryStore[K comparable, V any]() *MemoryStore[K, V]
NewMemoryStore returns an empty in-process store.
func (*MemoryStore[K, V]) Get ¶
func (s *MemoryStore[K, V]) Get(k K) (V, bool)
func (*MemoryStore[K, V]) Len ¶
func (s *MemoryStore[K, V]) Len() int
func (*MemoryStore[K, V]) Put ¶
func (s *MemoryStore[K, V]) Put(k K, v V, ttl time.Duration)
func (*MemoryStore[K, V]) Remove ¶
func (s *MemoryStore[K, V]) Remove(k K) (V, bool)
type Mention ¶
Mention is a snowflake parsed from any of Discord's mention forms, so entity-like options work in prefix commands.
It is an ordinary snowflake for slash commands, where Discord resolves the entity itself.
func (*Mention) FromInteraction ¶
func (m *Mention) FromInteraction(d discord.SlashCommandInteractionData, name string) error
FromInteraction reads the resolved user id.
func (*Mention) OptionType ¶
func (*Mention) OptionType() discord.ApplicationCommandOptionType
OptionType makes Mention a user option, which is what a mention usually refers to. Use a plain snowflake.ID field for other entity types.
type MessageTarget ¶
MessageTarget is the message a message context menu command was invoked on.
type Meta ¶
type Meta struct {
Name string
Description string
Aliases []string // prefix only
Category string
Kinds Kind // zero means Slash
// Gating, all checked before a command runs.
GuildOnly bool
DMOnly bool
NSFWOnly bool
OwnersOnly bool
UserPerms discord.Permissions
BotPerms discord.Permissions
// DefaultMemberPermissions is sent to Discord. Nil leaves it unset, which
// differs from a zero value: zero hides the command from everyone.
DefaultMemberPermissions *discord.Permissions
// How replies to this command behave.
Ephemeral bool
// ReuseResponse edits the first response instead of sending a followup.
ReuseResponse bool
// BroadcastTyping shows typing for prefix commands, and defers for
// interactions, both of which buy time before the reply.
BroadcastTyping bool
// Prefix behaviour, all requiring PrefixOptions.EditTracker.
InvokeOnEdit bool
TrackDeletion bool
// Where Discord offers the command.
IntegrationTypes []discord.ApplicationIntegrationType
Contexts []discord.InteractionContextType
ContextMenuName string
// EntryPointHandler decides whether Discord launches the activity itself
// or hands the interaction to the bot. Only read for Kinds EntryPoint.
EntryPointHandler discord.EntryPointCommandHandlerType
// How the command appears in the built-in help.
HideInHelp bool
HelpText string
// SubcommandRequired rejects a bare prefix invocation of a command that
// has subcommands, instead of running the parent's own Run. Slash
// commands cannot be invoked bare, so it has no effect there.
SubcommandRequired bool
ManualCooldown bool
CustomData any
}
Meta is a command's static description. Behaviour lives on the optional interfaces above rather than here.
type Middleware ¶
Middleware wraps command execution. It sees the typed Event and the decoded command, unlike a router middleware which sees the raw interaction.
type ModalOpener ¶
type ModalOpener interface {
OpenModal(discord.ModalCreate) error
}
modalOpener shows a modal. Commands and components can open one; a prefix invocation has no interaction, and Discord does not allow a modal to be opened from another modal's submission.
type ModalSpec ¶
type ModalSpec struct {
Title string
Timeout time.Duration
// Fields overrides per-field tag settings at show time, for text that is
// only known at runtime.
Fields map[string]FieldSpec
}
ModalSpec is a modal's presentation. A modal type supplies its own by implementing Modaler, so call sites do not restate it.
type Modaler ¶
type Modaler interface {
Modal() ModalSpec
}
Modaler supplies a modal's presentation. Without it, the struct tags are used and the title falls back to the type name.
type Option ¶
type Option[T any] struct { // contains filtered or unexported fields }
Option holds a value that may be absent. Wrapping a field makes it optional, and Discord requires those to be declared after required ones.
func (Option[T]) IsZero ¶
IsZero reports absence, so encoding/json omits it under `json:",omitzero"`.
func (Option[T]) LoadOr ¶
func (o Option[T]) LoadOr(def T) T
LoadOr returns the value, or def if absent.
func (Option[T]) MarshalJSON ¶
MarshalJSON encodes an absent Option as null.
func (*Option[T]) UnmarshalJSON ¶
UnmarshalJSON decodes null as absent.
type OptionInfo ¶
type OptionInfo struct {
Name string
Description string
Type discord.ApplicationCommandOptionType
Required bool
// Choices are the fixed values offered, if any.
Choices []string
// Autocomplete reports whether values are suggested as the user types.
Autocomplete bool
}
OptionInfo is one option a user fills in.
type Options ¶
type Options[D any] struct { Data D // Validate checks a decoded command or modal for anything Discord cannot // express, such as an email. It runs before the command, on a pointer to // the populated struct, and takes any function: // // Validate: func(v any) error { return validator.New().Struct(v) }, Validate func(any) error OnError func(*Error[D]) PreCommand func(*Event[D]) PostCommand func(*Event[D]) GlobalCheck Check[D] ReplyCallback func(*Event[D], *Reply) AllowedMentions *discord.AllowedMentions Logger *slog.Logger // EventHandler receives every Discord event, so a bot can react to // things that are not commands without a second listener. EventHandler func(bot.Event) Owners []snowflake.ID SkipChecksForOwners bool // InitOwners fills Owners from the application's owner and team when the // framework first syncs. InitOwners bool // OwnerTeamRoles limits which team roles count as owners. Empty accepts // every team member. OwnerTeamRoles []discord.TeamRole // RequireCacheForGuildCheck fails a guild-only command when the guild is // not cached, rather than trusting the interaction's guild id. RequireCacheForGuildCheck bool // DisableRecover lets a panicking command crash the process instead of // being reported as ErrCommandPanic. DisableRecover bool // OnUnknown runs for an interaction no registered handler matched, which // usually means a command was removed while Discord still offers it. OnUnknown func(*handler.InteractionEvent) // AutoDefer acknowledges an interaction that has not answered within this // long, so a slow command does not hit Discord's three second deadline // and fail outright. Zero disables it. // // A command that answers quickly is unaffected: the acknowledgement only // happens if it is still working. AutoDefer time.Duration // Cooldowns rate limits commands. Nil uses an in-process tracker. Cooldowns *CooldownTracker // Prefix enables text commands. Nil keeps the framework slash only, // which needs no message content intent. Prefix *PrefixOptions[D] }
Options configures a Framework. The zero value is usable: every field opts into behaviour, none opts out.
type Partial ¶
type Partial struct {
// Value is the text typed into the focused option so far.
Value string
// Name is the focused option's name.
Name string
// contains filtered or unexported fields
}
Partial is the state of an option list while the user is still typing. Options they have already filled in are readable; the focused one is not yet a value.
type PrefixOptions ¶
type PrefixOptions[D any] struct { Prefix string Additional []string // Dynamic returns the prefix for one message, for per-guild prefixes. Dynamic func(*events.MessageCreate) string // StrippedDynamic handles prefixes Dynamic cannot express, returning the // content after the prefix. It wins over Prefix and Dynamic. StrippedDynamic func(*events.MessageCreate) (rest string, ok bool) MentionAsPrefix bool CaseInsensitive bool AllowBots bool AllowSelf bool AllowThreadCreation bool // EditTracker follows message edits, so an edited invocation updates its // original reply. Nil disables edit handling. EditTracker *EditTracker // NonCommand runs for messages that carry no command. NonCommand func(*events.MessageCreate) }
PrefixOptions enables text commands. Leaving it nil in Options keeps the framework slash only, which needs no message content intent.
Every field opts into behaviour: bots and thread creation messages are ignored unless allowed.
type Reply ¶
type Reply struct {
Content string
Embeds []discord.Embed
Components []discord.LayoutComponent
Files []*discord.File
Ephemeral bool
// Reference replies to the invoking message. Prefix commands only.
Reference bool
AllowedMentions *discord.AllowedMentions
// contains filtered or unexported fields
}
Reply is one response, built once and sent through whichever surface the command was invoked on.
func (Reply) AsEphemeral ¶
AsEphemeral makes the reply visible only to the invoker. Ignored by prefix commands, which have no ephemeral messages.
func (Reply) Row ¶
Row adds one action row. Discord allows up to five buttons in a row, or a single select menu on its own.
func (Reply) WithAllowedMentions ¶
func (r Reply) WithAllowedMentions(a *discord.AllowedMentions) Reply
WithAllowedMentions overrides the framework default.
func (Reply) WithComponents ¶
func (r Reply) WithComponents(c ...discord.LayoutComponent) Reply
WithComponents appends component rows.
func (Reply) WithEmbeds ¶
WithEmbeds appends embeds.
type Responder ¶
type Responder interface {
Create(Reply) error
DeferResponse(ephemeral bool) error
Update(Reply) error
Followup(Reply) (*discord.Message, error)
Typing() error
}
responder performs the raw output operations.
type Section ¶
type Section struct {
// contains filtered or unexported fields
}
Section pairs text with one accessory, such as a button or a thumbnail.
func NewSection ¶
NewSection starts a section holding the given markdown blocks.
func (Section) WithButton ¶
WithButton puts a button beside the text.
func (Section) WithThumbnail ¶
WithThumbnail puts an image beside the text.
type Source ¶
type Source interface {
Author() discord.User
Member() *discord.ResolvedMember
GuildID() *snowflake.ID
ChannelID() snowflake.ID
Locale() discord.Locale
NSFW() bool
AppPerms() *discord.Permissions
}
source supplies invocation identity.
type Store ¶
type Store[K comparable, V any] interface { // Get returns the value for k, if it is present and unexpired. Get(k K) (V, bool) // Put stores v under k for ttl. A ttl of zero never expires. Put(k K, v V, ttl time.Duration) // Remove drops k, returning whatever it held. Remove(k K) (V, bool) // Len reports how many unexpired entries are held. Len() int }
Store keeps state that outlives a single interaction. Everything strut stores expires, so Put takes a time to live rather than trusting the caller to clean up.
The default is an in-process map. A shared one keeps cooldowns honest across several processes, where a local map lets a user hit another shard.
type SubcommandInfo ¶
type SubcommandInfo struct {
Name string
Description string
Options []OptionInfo
// Subcommands is set for a group, which holds subcommands rather than
// options.
Subcommands []SubcommandInfo
}
SubcommandInfo is one subcommand or subcommand group.
type SyncOptions ¶
type SyncOptions struct {
// Guild registers to one server, which takes effect immediately. Zero
// registers globally, which Discord propagates over about an hour.
Guild snowflake.ID
// Only keeps the commands it accepts, so a server can be given a
// different set from the rest. Nil keeps everything.
Only func(Meta) bool
// IfChanged reads back what Discord already holds and writes only when it
// differs. Registration is heavily rate limited, so this makes syncing on
// every start up cheap.
IfChanged bool
}
SyncOptions selects what to register and where.
type TextArgument ¶
TextArgument is an Argument that can also be parsed from a prefix command's raw text. Required only for options used by prefix commands.
type TrackedReply ¶
type TrackedReply struct {
// Response is nil when the command produced no reply.
Response *discord.Message
// DeleteWithInvocation records Meta.TrackDeletion, so deleting the
// invocation removes the answer with it.
DeleteWithInvocation bool
}
TrackedReply is the answer strut gave to one invocation.
type Translation ¶
Translation is a localized command name and description. Either may be empty.
type UserTarget ¶
type UserTarget struct {
User discord.User
// Member is nil when the command was invoked outside a guild.
Member *discord.ResolvedMember
}
UserTarget is the user a user context menu command was invoked on. It is filled from the interaction, never registered as an option.
Source Files
¶
- analyze.go
- argument.go
- autocomplete.go
- command.go
- component.go
- componentstate.go
- cooldown.go
- decode.go
- dispatch.go
- edit.go
- error.go
- event.go
- harness.go
- introspect.go
- kind.go
- layout.go
- lexer.go
- middleware.go
- modal.go
- option.go
- prefix.go
- prefixargs.go
- register.go
- reply.go
- schema.go
- shutdown.go
- source.go
- store.go
- strut.go
- sync.go
- tag.go
- target.go
Directories
¶
| Path | Synopsis |
|---|---|
|
_examples
|
|
|
autocomplete
command
Command autocomplete is a bot showing the two ways to offer a user a set of values: a fixed list Discord renders itself, and suggestions computed as they type.
|
Command autocomplete is a bot showing the two ways to offer a user a set of values: a fixed list Discord renders itself, and suggestions computed as they type. |
|
basic
command
Command basic is a slash-only bot: one command, a gate, a cooldown and an error handler.
|
Command basic is a slash-only bot: one command, a gate, a cooldown and an error handler. |
|
components
command
Command components is a bot showing typed component state and modals.
|
Command components is a bot showing typed component state and modals. |
|
contextmenu
command
Command contextmenu is a bot showing right-click commands, where one struct serves a slash command and both context menus.
|
Command contextmenu is a bot showing right-click commands, where one struct serves a slash command and both context menus. |
|
prefix
command
Command prefix is a bot where one struct serves both slash and text commands, with edit tracking.
|
Command prefix is a bot where one struct serves both slash and text commands, with edit tracking. |
|
testing
Package moderation is a set of commands in their own package, the way a real bot would organise them, so they can be tested on their own.
|
Package moderation is a set of commands in their own package, the way a real bot would organise them, so they can be tested on their own. |
|
Package struttest runs strut commands without a gateway or a REST client.
|
Package struttest runs strut commands without a gateway or a REST client. |