2021-09-09 23:06:44 +00:00
|
|
|
package discord
|
|
|
|
|
|
|
|
import (
|
2022-08-15 06:32:49 +00:00
|
|
|
"fmt"
|
|
|
|
"reflect"
|
2022-01-03 21:37:18 +00:00
|
|
|
"strings"
|
|
|
|
|
2023-09-19 15:23:25 +00:00
|
|
|
"errors"
|
2022-08-15 21:57:30 +00:00
|
|
|
"github.com/diamondburned/arikawa/v3/internal/rfutil"
|
2021-09-09 23:06:44 +00:00
|
|
|
"github.com/diamondburned/arikawa/v3/utils/json"
|
|
|
|
)
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// InteractionEvent describes the full incoming interaction event. It may be a
|
|
|
|
// gateway event or a webhook event.
|
|
|
|
//
|
2021-09-09 23:06:44 +00:00
|
|
|
// https://discord.com/developers/docs/topics/gateway#interactions
|
2021-10-10 22:44:31 +00:00
|
|
|
type InteractionEvent struct {
|
2021-09-09 23:06:44 +00:00
|
|
|
ID InteractionID `json:"id"`
|
2021-10-10 22:44:31 +00:00
|
|
|
Data InteractionData `json:"data"`
|
2021-09-09 23:06:44 +00:00
|
|
|
AppID AppID `json:"application_id"`
|
|
|
|
ChannelID ChannelID `json:"channel_id,omitempty"`
|
|
|
|
Token string `json:"token"`
|
|
|
|
Version int `json:"version"`
|
|
|
|
|
2023-04-07 09:41:09 +00:00
|
|
|
// Channel is the channel that the interaction was sent from.
|
|
|
|
Channel *Channel `json:"channel,omitempty"`
|
|
|
|
|
2021-09-09 23:06:44 +00:00
|
|
|
// Message is the message the component was attached to.
|
|
|
|
// Only present for component interactions, not command interactions.
|
|
|
|
Message *Message `json:"message,omitempty"`
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// Member is only present if this came from a guild. To get a user, use the
|
|
|
|
// Sender method.
|
2021-09-09 23:06:44 +00:00
|
|
|
Member *Member `json:"member,omitempty"`
|
|
|
|
GuildID GuildID `json:"guild_id,omitempty"`
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// User is only present if this didn't come from a guild. To get a user, use
|
|
|
|
// the Sender method.
|
2021-09-09 23:06:44 +00:00
|
|
|
User *User `json:"user,omitempty"`
|
2022-04-12 17:43:02 +00:00
|
|
|
|
|
|
|
// Locale is the selected language of the invoking user. It is returned in
|
|
|
|
// all interactions except ping interactions. Use this Locale field to
|
|
|
|
// obtain the language of the user who used the interaction.
|
|
|
|
Locale Language `json:"locale,omitempty"`
|
|
|
|
// GuildLocale is the guild's preferred locale, if invoked in a guild.
|
|
|
|
GuildLocale string `json:"guild_locale,omitempty"`
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// Sender returns the sender of this event from either the Member field or the
|
|
|
|
// User field. If neither of those fields are available, then nil is returned.
|
|
|
|
func (e *InteractionEvent) Sender() *User {
|
|
|
|
if e.User != nil {
|
|
|
|
return e.User
|
|
|
|
}
|
|
|
|
if e.Member != nil {
|
|
|
|
return &e.Member.User
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// SenderID returns the sender's ID. See Sender for more information. If Sender
|
|
|
|
// returns nil, then 0 is returned.
|
|
|
|
func (e *InteractionEvent) SenderID() UserID {
|
|
|
|
if sender := e.Sender(); sender != nil {
|
|
|
|
return sender.ID
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *InteractionEvent) UnmarshalJSON(b []byte) error {
|
|
|
|
type event InteractionEvent
|
|
|
|
|
|
|
|
target := struct {
|
|
|
|
Type InteractionDataType `json:"type"`
|
|
|
|
Data json.Raw `json:"data"`
|
|
|
|
*event
|
|
|
|
}{
|
|
|
|
event: (*event)(e),
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(b, &target); err != nil {
|
2021-09-09 23:06:44 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
var err error
|
|
|
|
|
|
|
|
switch target.Type {
|
|
|
|
case PingInteractionType:
|
|
|
|
e.Data = &PingInteraction{}
|
2021-12-27 23:10:09 +00:00
|
|
|
return nil // Ping isn't actually an object.
|
2021-10-10 22:44:31 +00:00
|
|
|
case CommandInteractionType:
|
|
|
|
e.Data = &CommandInteraction{}
|
|
|
|
case ComponentInteractionType:
|
|
|
|
d, err := ParseComponentInteraction(target.Data)
|
|
|
|
if err != nil {
|
2023-09-19 15:23:25 +00:00
|
|
|
return fmt.Errorf("failed to unmarshal component interaction event data: %w", err)
|
2021-10-10 22:44:31 +00:00
|
|
|
}
|
|
|
|
e.Data = d
|
2021-09-09 23:06:44 +00:00
|
|
|
return nil
|
2021-10-10 22:44:31 +00:00
|
|
|
case AutocompleteInteractionType:
|
|
|
|
e.Data = &AutocompleteInteraction{}
|
2022-02-14 03:15:28 +00:00
|
|
|
case ModalInteractionType:
|
|
|
|
e.Data = &ModalInteraction{}
|
2021-09-09 23:06:44 +00:00
|
|
|
default:
|
2021-10-10 22:44:31 +00:00
|
|
|
e.Data = &UnknownInteractionData{
|
|
|
|
Raw: target.Data,
|
|
|
|
typ: target.Type,
|
|
|
|
}
|
|
|
|
return nil
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
if err := json.Unmarshal(target.Data, e.Data); err != nil {
|
2023-09-19 15:23:25 +00:00
|
|
|
return fmt.Errorf("failed to unmarshal interaction event data: %w", err)
|
2021-10-10 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
func (e *InteractionEvent) MarshalJSON() ([]byte, error) {
|
|
|
|
type event InteractionEvent
|
|
|
|
|
|
|
|
if e.Data == nil {
|
|
|
|
return nil, errors.New("missing InteractionEvent.Data")
|
|
|
|
}
|
|
|
|
if e.Data.InteractionType() == 0 {
|
|
|
|
return nil, errors.New("unexpected 0 InteractionEvent.Data.Type")
|
|
|
|
}
|
|
|
|
|
|
|
|
v := struct {
|
|
|
|
Type InteractionDataType `json:"type"`
|
|
|
|
*event
|
|
|
|
}{
|
|
|
|
Type: e.Data.InteractionType(),
|
|
|
|
event: (*event)(e),
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Marshal(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
// InteractionDataType is the type of each Interaction, enumerated in
|
|
|
|
// integers.
|
|
|
|
type InteractionDataType uint
|
2021-09-09 23:06:44 +00:00
|
|
|
|
|
|
|
const (
|
2021-10-10 22:44:31 +00:00
|
|
|
PingInteractionType InteractionDataType = iota + 1
|
|
|
|
CommandInteractionType
|
|
|
|
ComponentInteractionType
|
|
|
|
AutocompleteInteractionType
|
2022-02-14 03:15:28 +00:00
|
|
|
ModalInteractionType
|
2021-09-09 23:06:44 +00:00
|
|
|
)
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// InteractionData holds the respose data of an interaction, or more
|
|
|
|
// specifically, the data that Discord sends to us. Type assertions should be
|
2021-11-25 23:02:24 +00:00
|
|
|
// made on it to access the underlying data.
|
|
|
|
//
|
|
|
|
// The following types implement this interface:
|
|
|
|
//
|
2023-09-19 15:23:25 +00:00
|
|
|
// - *PingInteraction
|
|
|
|
// - *AutocompleteInteraction
|
|
|
|
// - *CommandInteraction
|
|
|
|
// - *ModalInteraction
|
|
|
|
// - *StringSelectInteraction (also ComponentInteraction)
|
|
|
|
// - *RoleSelectInteraction (also ComponentInteraction)
|
|
|
|
// - *UserSelectInteraction (also ComponentInteraction)
|
|
|
|
// - *ChannelSelectInteraction (also ComponentInteraction)
|
|
|
|
// - *MentionableSelectInteraction (also ComponentInteraction)
|
|
|
|
// - *ButtonInteraction (also ComponentInteraction)
|
2021-09-09 23:06:44 +00:00
|
|
|
type InteractionData interface {
|
2021-10-10 22:44:31 +00:00
|
|
|
InteractionType() InteractionDataType
|
|
|
|
data()
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// PingInteraction is a ping Interaction response.
|
|
|
|
type PingInteraction struct{}
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (*PingInteraction) InteractionType() InteractionDataType { return PingInteractionType }
|
|
|
|
func (*PingInteraction) data() {}
|
|
|
|
|
|
|
|
// AutocompleteInteraction is an autocompletion Interaction response.
|
|
|
|
type AutocompleteInteraction struct {
|
|
|
|
CommandID CommandID `json:"id"`
|
|
|
|
|
|
|
|
// Name of command autocomplete is triggered for.
|
2022-05-31 12:31:30 +00:00
|
|
|
Name string `json:"name"`
|
|
|
|
CommandType CommandType `json:"type"`
|
|
|
|
Version string `json:"version"`
|
|
|
|
Options AutocompleteOptions `json:"options"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type implements ComponentInteraction.
|
|
|
|
func (*AutocompleteInteraction) InteractionType() InteractionDataType {
|
|
|
|
return AutocompleteInteractionType
|
|
|
|
}
|
|
|
|
func (*AutocompleteInteraction) data() {}
|
|
|
|
|
|
|
|
// AutocompleteOptions is a list of autocompletion options.
|
|
|
|
// Use `Find` to get your named autocompletion option.
|
|
|
|
type AutocompleteOptions []AutocompleteOption
|
|
|
|
|
|
|
|
// Find returns the named autocomplete option.
|
|
|
|
func (o AutocompleteOptions) Find(name string) AutocompleteOption {
|
|
|
|
for _, opt := range o {
|
|
|
|
if strings.EqualFold(opt.Name, name) {
|
|
|
|
return opt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return AutocompleteOption{}
|
2021-10-10 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
2022-10-14 05:53:18 +00:00
|
|
|
// Focused returns the option that the user is currently focused on.
|
|
|
|
func (o AutocompleteOptions) Focused() AutocompleteOption {
|
|
|
|
for _, opt := range o {
|
|
|
|
if opt.Focused {
|
|
|
|
return opt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return AutocompleteOption{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal behaves similarly to CommandInteractionOptions.Unmarshal. It
|
|
|
|
// supports the same types. Refer to its documentation for more.
|
|
|
|
func (o AutocompleteOptions) Unmarshal(v interface{}) error {
|
|
|
|
return unmarshalOptions(
|
|
|
|
func(name string) unmarshalingOption { return o.Find(name).forUnmarshal() },
|
|
|
|
reflect.ValueOf(v),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o AutocompleteOption) forUnmarshal() unmarshalingOption {
|
|
|
|
return unmarshalingOption{
|
|
|
|
Type: o.Type,
|
|
|
|
Name: o.Name,
|
|
|
|
Value: o.Value,
|
|
|
|
Find: func(name string) unmarshalingOption { return o.Options.Find(name).forUnmarshal() },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// AutocompleteOption is an autocompletion option in an AutocompleteInteraction.
|
|
|
|
type AutocompleteOption struct {
|
2022-10-14 05:30:27 +00:00
|
|
|
Type CommandOptionType `json:"type"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
Value json.Raw `json:"value,omitempty"`
|
|
|
|
Focused bool `json:"focused,omitempty"`
|
|
|
|
Options AutocompleteOptions `json:"options,omitempty"`
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 12:32:27 +00:00
|
|
|
// String will return the value if the option's value is a valid string.
|
|
|
|
// Otherwise, it will return the raw JSON value of the other type.
|
|
|
|
func (o AutocompleteOption) String() string {
|
|
|
|
var value string
|
|
|
|
if err := json.Unmarshal(o.Value, &value); err != nil {
|
|
|
|
return string(o.Value)
|
|
|
|
}
|
|
|
|
return value
|
|
|
|
}
|
|
|
|
|
|
|
|
// IntValue reads the option's value as an int.
|
|
|
|
func (o AutocompleteOption) IntValue() (int64, error) {
|
|
|
|
var i int64
|
|
|
|
err := o.Value.UnmarshalTo(&i)
|
|
|
|
return i, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// BoolValue reads the option's value as a bool.
|
|
|
|
func (o AutocompleteOption) BoolValue() (bool, error) {
|
|
|
|
var b bool
|
|
|
|
err := o.Value.UnmarshalTo(&b)
|
|
|
|
return b, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// SnowflakeValue reads the option's value as a snowflake.
|
|
|
|
func (o AutocompleteOption) SnowflakeValue() (Snowflake, error) {
|
|
|
|
var id Snowflake
|
|
|
|
err := o.Value.UnmarshalTo(&id)
|
|
|
|
return id, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// FloatValue reads the option's value as a float64.
|
|
|
|
func (o AutocompleteOption) FloatValue() (float64, error) {
|
|
|
|
var f float64
|
|
|
|
err := o.Value.UnmarshalTo(&f)
|
|
|
|
return f, err
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// ComponentInteraction is a union component interaction response types. The
|
2021-11-25 23:02:24 +00:00
|
|
|
// types can be whatever the constructors for this type will return.
|
|
|
|
//
|
|
|
|
// The following types implement this interface:
|
|
|
|
//
|
2023-09-19 15:23:25 +00:00
|
|
|
// - *StringSelectInteraction
|
|
|
|
// - *ChannelSelectInteraction
|
|
|
|
// - *RoleSelectInteraction
|
|
|
|
// - *UserSelectInteraction
|
|
|
|
// - *MentionableSelectInteraction
|
|
|
|
// - *ButtonInteraction
|
2021-10-10 22:44:31 +00:00
|
|
|
type ComponentInteraction interface {
|
|
|
|
InteractionData
|
|
|
|
// ID returns the ID of the component in response. Not all component
|
|
|
|
// interactions will have a component ID.
|
|
|
|
ID() ComponentID
|
|
|
|
// Type returns the type of the component in response.
|
|
|
|
Type() ComponentType
|
|
|
|
resp()
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// SelectInteraction is a select component's response.
|
2023-02-22 00:28:14 +00:00
|
|
|
//
|
|
|
|
// Deprecated: Use StringSelectInteraction instead.
|
|
|
|
type SelectInteraction = StringSelectInteraction
|
|
|
|
|
|
|
|
// StringSelectInteraction is a string select component's response.
|
|
|
|
type StringSelectInteraction struct {
|
2021-10-10 22:44:31 +00:00
|
|
|
CustomID ComponentID `json:"custom_id"`
|
|
|
|
Values []string `json:"values"`
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// ID implements ComponentInteraction.
|
2023-02-22 00:28:14 +00:00
|
|
|
func (s *StringSelectInteraction) ID() ComponentID { return s.CustomID }
|
|
|
|
|
|
|
|
// Type implements ComponentInteraction.
|
|
|
|
func (s *StringSelectInteraction) Type() ComponentType { return StringSelectComponentType }
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (s *StringSelectInteraction) InteractionType() InteractionDataType {
|
|
|
|
return ComponentInteractionType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StringSelectInteraction) resp() {}
|
|
|
|
func (s *StringSelectInteraction) data() {}
|
|
|
|
|
|
|
|
// ChannelSelectInteraction is a channel select component's response.
|
|
|
|
type ChannelSelectInteraction struct {
|
|
|
|
CustomID ComponentID `json:"custom_id"`
|
|
|
|
Values []ChannelID `json:"values"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ID implements ComponentInteraction.
|
|
|
|
func (s *ChannelSelectInteraction) ID() ComponentID { return s.CustomID }
|
|
|
|
|
|
|
|
// Type implements ComponentInteraction.
|
|
|
|
func (s *ChannelSelectInteraction) Type() ComponentType { return ChannelSelectComponentType }
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (s *ChannelSelectInteraction) InteractionType() InteractionDataType {
|
|
|
|
return ComponentInteractionType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *ChannelSelectInteraction) resp() {}
|
|
|
|
func (s *ChannelSelectInteraction) data() {}
|
|
|
|
|
|
|
|
// RoleSelectInteraction is a role select component's response.
|
|
|
|
type RoleSelectInteraction struct {
|
|
|
|
CustomID ComponentID `json:"custom_id"`
|
|
|
|
Values []RoleID `json:"values"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ID implements ComponentInteraction.
|
|
|
|
func (s *RoleSelectInteraction) ID() ComponentID { return s.CustomID }
|
|
|
|
|
|
|
|
// Type implements ComponentInteraction.
|
|
|
|
func (s *RoleSelectInteraction) Type() ComponentType { return RoleSelectComponentType }
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (s *RoleSelectInteraction) InteractionType() InteractionDataType {
|
|
|
|
return ComponentInteractionType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *RoleSelectInteraction) resp() {}
|
|
|
|
func (s *RoleSelectInteraction) data() {}
|
|
|
|
|
|
|
|
// UserSelectInteraction is a user select component's response.
|
|
|
|
type UserSelectInteraction struct {
|
|
|
|
CustomID ComponentID `json:"custom_id"`
|
|
|
|
Values []UserID `json:"values"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ID implements ComponentInteraction.
|
|
|
|
func (s *UserSelectInteraction) ID() ComponentID { return s.CustomID }
|
|
|
|
|
|
|
|
// Type implements ComponentInteraction.
|
|
|
|
func (s *UserSelectInteraction) Type() ComponentType { return UserSelectComponentType }
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (s *UserSelectInteraction) InteractionType() InteractionDataType {
|
|
|
|
return ComponentInteractionType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *UserSelectInteraction) resp() {}
|
|
|
|
func (s *UserSelectInteraction) data() {}
|
|
|
|
|
|
|
|
// MentionableSelectInteraction is a mentionable select component's response.
|
|
|
|
type MentionableSelectInteraction struct {
|
|
|
|
CustomID ComponentID `json:"custom_id"`
|
|
|
|
Values []Snowflake `json:"values"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ID implements ComponentInteraction.
|
|
|
|
func (s *MentionableSelectInteraction) ID() ComponentID { return s.CustomID }
|
2021-10-01 18:48:17 +00:00
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// Type implements ComponentInteraction.
|
2023-02-22 00:28:14 +00:00
|
|
|
func (s *MentionableSelectInteraction) Type() ComponentType { return MentionableSelectComponentType }
|
2021-10-10 22:44:31 +00:00
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
2023-02-22 00:28:14 +00:00
|
|
|
func (s *MentionableSelectInteraction) InteractionType() InteractionDataType {
|
2021-10-10 22:44:31 +00:00
|
|
|
return ComponentInteractionType
|
2021-10-01 18:48:17 +00:00
|
|
|
}
|
|
|
|
|
2023-02-22 00:28:14 +00:00
|
|
|
func (s *MentionableSelectInteraction) resp() {}
|
|
|
|
func (s *MentionableSelectInteraction) data() {}
|
2021-10-10 22:44:31 +00:00
|
|
|
|
|
|
|
// ButtonInteraction is a button component's response. It is the custom ID of
|
|
|
|
// the button within the component tree.
|
|
|
|
type ButtonInteraction struct {
|
|
|
|
CustomID ComponentID `json:"custom_id"`
|
2021-10-01 18:48:17 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// ID implements ComponentInteraction.
|
|
|
|
func (b *ButtonInteraction) ID() ComponentID { return b.CustomID }
|
|
|
|
|
|
|
|
// Type implements ComponentInteraction.
|
|
|
|
func (b *ButtonInteraction) Type() ComponentType { return ButtonComponentType }
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (b *ButtonInteraction) InteractionType() InteractionDataType {
|
|
|
|
return ComponentInteractionType
|
2021-10-01 18:48:17 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
func (b *ButtonInteraction) data() {}
|
|
|
|
func (b *ButtonInteraction) resp() {}
|
|
|
|
|
|
|
|
// ParseComponentInteraction parses the given bytes as a component response.
|
|
|
|
func ParseComponentInteraction(b []byte) (ComponentInteraction, error) {
|
|
|
|
var t struct {
|
|
|
|
Type ComponentType `json:"component_type"`
|
|
|
|
CustomID ComponentID `json:"custom_id"`
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(b, &t); err != nil {
|
2023-09-19 15:23:25 +00:00
|
|
|
return nil, fmt.Errorf("failed to unmarshal component interaction header: %w", err)
|
2021-10-10 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var d ComponentInteraction
|
|
|
|
|
|
|
|
switch t.Type {
|
|
|
|
case ButtonComponentType:
|
|
|
|
d = &ButtonInteraction{CustomID: t.CustomID}
|
2022-11-19 17:47:08 +00:00
|
|
|
case StringSelectComponentType:
|
2023-02-22 00:28:14 +00:00
|
|
|
d = &StringSelectInteraction{CustomID: t.CustomID}
|
|
|
|
case ChannelSelectComponentType:
|
|
|
|
d = &ChannelSelectInteraction{CustomID: t.CustomID}
|
|
|
|
case RoleSelectComponentType:
|
|
|
|
d = &RoleSelectInteraction{CustomID: t.CustomID}
|
|
|
|
case UserSelectComponentType:
|
|
|
|
d = &UserSelectInteraction{CustomID: t.CustomID}
|
|
|
|
case MentionableSelectComponentType:
|
|
|
|
d = &MentionableSelectInteraction{CustomID: t.CustomID}
|
2021-10-10 22:44:31 +00:00
|
|
|
default:
|
|
|
|
d = &UnknownComponent{
|
|
|
|
Raw: append(json.Raw(nil), b...),
|
|
|
|
id: t.CustomID,
|
|
|
|
typ: t.Type,
|
|
|
|
}
|
|
|
|
return d, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := json.Unmarshal(b, d); err != nil {
|
2023-09-19 15:23:25 +00:00
|
|
|
return nil, fmt.Errorf("failed to unmarshal component interaction data: %w", err)
|
2021-10-10 22:44:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return d, nil
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2022-01-03 21:37:18 +00:00
|
|
|
// CommandInteractionOptions is a list of interaction options.
|
|
|
|
// Use `Find` to get your named interaction option
|
|
|
|
type CommandInteractionOptions []CommandInteractionOption
|
|
|
|
|
2021-12-27 23:10:09 +00:00
|
|
|
// CommandInteraction is an application command interaction that Discord sends
|
|
|
|
// to us.
|
2021-10-10 22:44:31 +00:00
|
|
|
type CommandInteraction struct {
|
2022-07-04 23:30:39 +00:00
|
|
|
ID CommandID `json:"id"`
|
|
|
|
Name string `json:"name"`
|
2022-10-14 05:29:17 +00:00
|
|
|
Options CommandInteractionOptions `json:"options,omitempty"`
|
2022-07-04 23:30:39 +00:00
|
|
|
// GuildID is the id of the guild the command is registered to
|
|
|
|
GuildID GuildID `json:"guild_id,omitempty"`
|
2022-07-05 13:01:23 +00:00
|
|
|
// TargetID is the id of the user or message targeted by a user or message command.
|
|
|
|
//
|
|
|
|
// See TargetUserID and TargetMessageID
|
2022-07-04 23:30:39 +00:00
|
|
|
TargetID Snowflake `json:"target_id,omitempty"`
|
2021-12-27 23:10:09 +00:00
|
|
|
Resolved struct {
|
|
|
|
// User contains user objects.
|
|
|
|
Users map[UserID]User `json:"users,omitempty"`
|
|
|
|
// Members contains partial member objects (missing User, Deaf and
|
|
|
|
// Mute).
|
|
|
|
Members map[UserID]Member `json:"members,omitempty"`
|
|
|
|
// Role contains role objects.
|
|
|
|
Roles map[RoleID]Role `json:"roles,omitempty"`
|
|
|
|
// Channels contains partial channel objects that only have ID, Name,
|
|
|
|
// Type and Permissions. Threads will also have ThreadMetadata and
|
|
|
|
// ParentID.
|
|
|
|
Channels map[ChannelID]Channel `json:"channels,omitempty"`
|
|
|
|
// Messages contains partial message objects. All fields without
|
|
|
|
// omitempty are presumably present.
|
|
|
|
Messages map[MessageID]Message `json:"messages,omitempty"`
|
2022-07-09 23:05:58 +00:00
|
|
|
// Attachments contains attachments objects.
|
|
|
|
Attachments map[AttachmentID]Attachment `json:"attachments,omitempty"`
|
2021-12-27 23:10:09 +00:00
|
|
|
}
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (*CommandInteraction) InteractionType() InteractionDataType {
|
|
|
|
return CommandInteractionType
|
|
|
|
}
|
|
|
|
|
2022-07-05 13:01:23 +00:00
|
|
|
// TargetUserID is the id of the user targeted by a user command
|
|
|
|
func (c *CommandInteraction) TargetUserID() UserID {
|
|
|
|
return UserID(c.TargetID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TargetMessageID is the id of the message targeted by a message command
|
|
|
|
func (c *CommandInteraction) TargetMessageID() MessageID {
|
|
|
|
return MessageID(c.TargetID)
|
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
func (*CommandInteraction) data() {}
|
|
|
|
|
|
|
|
// CommandInteractionOption is an option for a Command interaction response.
|
|
|
|
type CommandInteractionOption struct {
|
2022-02-14 06:40:26 +00:00
|
|
|
Type CommandOptionType `json:"type"`
|
2022-01-03 21:37:18 +00:00
|
|
|
Name string `json:"name"`
|
2022-10-14 05:29:17 +00:00
|
|
|
Value json.Raw `json:"value,omitempty"`
|
|
|
|
Options CommandInteractionOptions `json:"options,omitempty"`
|
2022-01-03 21:37:18 +00:00
|
|
|
}
|
|
|
|
|
2022-08-15 07:13:12 +00:00
|
|
|
var optionSupportedSnowflakeTypes = map[reflect.Type]CommandOptionType{
|
|
|
|
reflect.TypeOf(ChannelID(0)): ChannelOptionType,
|
|
|
|
reflect.TypeOf(UserID(0)): UserOptionType,
|
|
|
|
reflect.TypeOf(RoleID(0)): RoleOptionType,
|
|
|
|
reflect.TypeOf(Snowflake(0)): MentionableOptionType,
|
|
|
|
}
|
|
|
|
|
|
|
|
var optionKindMap = map[reflect.Kind]CommandOptionType{
|
|
|
|
reflect.Int: NumberOptionType,
|
|
|
|
reflect.Int8: NumberOptionType,
|
|
|
|
reflect.Int16: NumberOptionType,
|
|
|
|
reflect.Int32: NumberOptionType,
|
|
|
|
reflect.Int64: NumberOptionType,
|
|
|
|
reflect.Uint: NumberOptionType,
|
|
|
|
reflect.Uint8: NumberOptionType,
|
|
|
|
reflect.Uint16: NumberOptionType,
|
|
|
|
reflect.Uint32: NumberOptionType,
|
|
|
|
reflect.Uint64: NumberOptionType,
|
|
|
|
reflect.Float32: NumberOptionType,
|
|
|
|
reflect.Float64: NumberOptionType,
|
|
|
|
reflect.String: StringOptionType,
|
|
|
|
reflect.Bool: BooleanOptionType,
|
2022-08-15 06:32:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unmarshal unmarshals the options into the struct pointer v. Each struct field
|
|
|
|
// must be exported and is of a supported type.
|
|
|
|
//
|
|
|
|
// Fields that don't satisfy any of the above are ignored. The "discord" struct
|
|
|
|
// tag with a value "-" is ignored. Fields that aren't found in the list of
|
|
|
|
// options and have a "?" at the end of the "discord" struct tag are ignored.
|
|
|
|
//
|
2023-09-19 15:23:25 +00:00
|
|
|
// # Supported Types
|
2022-08-15 06:32:49 +00:00
|
|
|
//
|
|
|
|
// The following types are supported:
|
|
|
|
//
|
2023-09-19 15:23:25 +00:00
|
|
|
// - ChannelID (ChannelOptionType)
|
|
|
|
// - UserID (UserOptionType)
|
|
|
|
// - RoleID (RoleOptionType)
|
|
|
|
// - Snowflake (MentionableOptionType)
|
|
|
|
// - string (StringOptionType)
|
|
|
|
// - bool (BooleanOptionType)
|
|
|
|
// - int* (int, int8, int16, int32, int64) (NumberOptionType)
|
|
|
|
// - uint* (uint, uint8, uint16, uint32, uint64) (NumberOptionType)
|
|
|
|
// - float* (float32, float64) (NumberOptionType)
|
|
|
|
// - (any struct and struct pointer) (not Discord-type-checked)
|
2022-08-15 06:32:49 +00:00
|
|
|
//
|
|
|
|
// Any types that are derived from any of the above built-in types are also
|
|
|
|
// supported.
|
2022-08-15 21:57:30 +00:00
|
|
|
//
|
|
|
|
// Pointer types to any of the above types are also supported and will also
|
|
|
|
// implicitly imply optionality.
|
2022-08-15 06:32:49 +00:00
|
|
|
func (o CommandInteractionOptions) Unmarshal(v interface{}) error {
|
2022-10-14 05:53:18 +00:00
|
|
|
return unmarshalOptions(
|
|
|
|
func(name string) unmarshalingOption { return o.Find(name).forUnmarshal() },
|
|
|
|
reflect.ValueOf(v),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o CommandInteractionOption) forUnmarshal() unmarshalingOption {
|
|
|
|
return unmarshalingOption{
|
|
|
|
Type: o.Type,
|
|
|
|
Name: o.Name,
|
|
|
|
Value: o.Value,
|
|
|
|
Find: func(name string) unmarshalingOption { return o.Options.Find(name).forUnmarshal() },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type unmarshalingOption struct {
|
|
|
|
Type CommandOptionType
|
|
|
|
Name string
|
|
|
|
Value json.Raw
|
|
|
|
Find func(name string) unmarshalingOption
|
2022-08-15 06:32:49 +00:00
|
|
|
}
|
|
|
|
|
2022-10-14 05:53:18 +00:00
|
|
|
func unmarshalOptions(find func(string) unmarshalingOption, rv reflect.Value) error {
|
2022-08-15 21:57:30 +00:00
|
|
|
rv, rt, err := rfutil.StructRValue(rv)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2022-08-15 06:32:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
numField := rt.NumField()
|
|
|
|
for i := 0; i < numField; i++ {
|
|
|
|
fieldStruct := rt.Field(i)
|
|
|
|
if !fieldStruct.IsExported() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
name := fieldStruct.Tag.Get("discord")
|
|
|
|
switch name {
|
|
|
|
case "-":
|
|
|
|
continue
|
|
|
|
case "?":
|
|
|
|
name = fieldStruct.Name + "?"
|
|
|
|
case "":
|
|
|
|
name = fieldStruct.Name
|
|
|
|
}
|
|
|
|
|
2022-10-14 05:53:18 +00:00
|
|
|
option := find(strings.TrimSuffix(name, "?"))
|
2022-08-15 06:32:49 +00:00
|
|
|
fieldv := rv.Field(i)
|
|
|
|
fieldt := fieldStruct.Type
|
|
|
|
|
|
|
|
if strings.HasSuffix(name, "?") {
|
|
|
|
name = strings.TrimSuffix(name, "?")
|
|
|
|
if option.Type == 0 {
|
|
|
|
// not found
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else if fieldStruct.Type.Kind() == reflect.Ptr {
|
|
|
|
fieldt = fieldt.Elem()
|
|
|
|
if option.Type == 0 {
|
|
|
|
// not found
|
|
|
|
fieldv.Set(reflect.NewAt(fieldt, nil))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// found, so allocate new value and use that to set
|
|
|
|
newv := reflect.New(fieldt)
|
|
|
|
fieldv.Set(newv)
|
|
|
|
fieldv = newv.Elem()
|
|
|
|
} else if option.Type == 0 {
|
|
|
|
// not found AND the field is not a pointer, so error out
|
|
|
|
return fmt.Errorf("option %q is required but not found", name)
|
|
|
|
}
|
|
|
|
|
2022-08-15 07:13:12 +00:00
|
|
|
if expectType, ok := optionSupportedSnowflakeTypes[fieldt]; ok {
|
|
|
|
if option.Type != expectType {
|
|
|
|
return fmt.Errorf("option %q expecting type %v, got %v", name, expectType, option.Type)
|
|
|
|
}
|
|
|
|
|
2022-10-14 05:53:18 +00:00
|
|
|
var snowflake Snowflake
|
|
|
|
if err := option.Value.UnmarshalTo(&snowflake); err != nil {
|
2023-09-19 15:23:25 +00:00
|
|
|
return fmt.Errorf("option %q is not a valid snowflake: %w", name, err)
|
2022-08-15 06:32:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fieldv.Set(reflect.ValueOf(snowflake).Convert(fieldt))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-08-15 07:13:12 +00:00
|
|
|
fieldk := fieldt.Kind()
|
|
|
|
if expectType, ok := optionKindMap[fieldk]; ok {
|
|
|
|
if option.Type != expectType {
|
|
|
|
return fmt.Errorf("option %q expecting type %v, got %v", name, expectType, option.Type)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch fieldk {
|
2022-08-15 06:32:49 +00:00
|
|
|
case reflect.Struct:
|
2022-10-14 05:53:18 +00:00
|
|
|
if err := unmarshalOptions(option.Find, fieldv.Addr()); err != nil {
|
2023-09-19 15:23:25 +00:00
|
|
|
return fmt.Errorf("option %q has invalid suboptions: %w", name, err)
|
2022-08-15 06:32:49 +00:00
|
|
|
}
|
2022-10-14 05:53:18 +00:00
|
|
|
|
|
|
|
case reflect.Bool, reflect.String, reflect.Float32, reflect.Float64,
|
|
|
|
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
|
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
|
|
|
|
|
|
v := reflect.New(fieldt)
|
|
|
|
if err := option.Value.UnmarshalTo(v.Interface()); err != nil {
|
2023-09-19 15:23:25 +00:00
|
|
|
return fmt.Errorf("option %q is not a valid %s: %w", name, fieldt, err)
|
2022-08-15 06:32:49 +00:00
|
|
|
}
|
2022-10-14 05:53:18 +00:00
|
|
|
fieldv.Set(v.Elem())
|
|
|
|
|
2022-08-15 06:32:49 +00:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("field %s (%q) has unknown type %s", fieldStruct.Name, name, fieldt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-03 21:37:18 +00:00
|
|
|
// Find returns the named command option
|
|
|
|
func (o CommandInteractionOptions) Find(name string) CommandInteractionOption {
|
|
|
|
for _, opt := range o {
|
|
|
|
if strings.EqualFold(opt.Name, name) {
|
|
|
|
return opt
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return CommandInteractionOption{}
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// String will return the value if the option's value is a valid string.
|
|
|
|
// Otherwise, it will return the raw JSON value of the other type.
|
2021-10-10 22:44:31 +00:00
|
|
|
func (o CommandInteractionOption) String() string {
|
2021-11-13 23:18:52 +00:00
|
|
|
var value string
|
|
|
|
if err := json.Unmarshal(o.Value, &value); err != nil {
|
|
|
|
return string(o.Value)
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
2021-11-13 23:18:52 +00:00
|
|
|
return value
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// IntValue reads the option's value as an int.
|
|
|
|
func (o CommandInteractionOption) IntValue() (int64, error) {
|
2021-09-09 23:06:44 +00:00
|
|
|
var i int64
|
|
|
|
err := o.Value.UnmarshalTo(&i)
|
|
|
|
return i, err
|
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// BoolValue reads the option's value as a bool.
|
|
|
|
func (o CommandInteractionOption) BoolValue() (bool, error) {
|
2021-09-09 23:06:44 +00:00
|
|
|
var b bool
|
|
|
|
err := o.Value.UnmarshalTo(&b)
|
|
|
|
return b, err
|
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// SnowflakeValue reads the option's value as a snowflake.
|
|
|
|
func (o CommandInteractionOption) SnowflakeValue() (Snowflake, error) {
|
2021-09-09 23:06:44 +00:00
|
|
|
var id Snowflake
|
|
|
|
err := o.Value.UnmarshalTo(&id)
|
|
|
|
return id, err
|
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// FloatValue reads the option's value as a float64.
|
|
|
|
func (o CommandInteractionOption) FloatValue() (float64, error) {
|
2021-09-09 23:06:44 +00:00
|
|
|
var f float64
|
|
|
|
err := o.Value.UnmarshalTo(&f)
|
|
|
|
return f, err
|
|
|
|
}
|
2021-10-10 22:44:31 +00:00
|
|
|
|
2022-02-14 03:15:28 +00:00
|
|
|
// ModalInteraction is the submitted modal form
|
|
|
|
type ModalInteraction struct {
|
|
|
|
CustomID ComponentID `json:"custom_id"`
|
|
|
|
Components ContainerComponents `json:"components"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (m *ModalInteraction) InteractionType() InteractionDataType {
|
|
|
|
return ModalInteractionType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *ModalInteraction) data() {}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// UnknownInteractionData describes an Interaction response with an unknown
|
|
|
|
// type.
|
|
|
|
type UnknownInteractionData struct {
|
|
|
|
json.Raw
|
|
|
|
typ InteractionDataType
|
|
|
|
}
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (u *UnknownInteractionData) InteractionType() InteractionDataType {
|
|
|
|
return u.typ
|
|
|
|
}
|
|
|
|
|
|
|
|
func (u *UnknownInteractionData) data() {}
|