1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-01 04:24:19 +00:00
arikawa/discord/interaction.go
diamondburned d8438f3b51
discord: Refactor interactions and components
This commit gets rid of contain-it-all structs and instead opt for
interface union types containing underlying concrete types with no
overloading.

The code is much more verbose by doing this, but the API is much nicer
to use. The only disadvantage in that regard is the interface assertion
being too verbose and risky for users at times.
2021-11-09 18:31:06 -08:00

338 lines
9.4 KiB
Go

package discord
import (
"strconv"
"github.com/diamondburned/arikawa/v3/utils/json"
"github.com/pkg/errors"
)
// InteractionEvent describes the full incoming interaction event. It may be a
// gateway event or a webhook event.
//
// https://discord.com/developers/docs/topics/gateway#interactions
type InteractionEvent struct {
ID InteractionID `json:"id"`
Data InteractionData `json:"data"`
AppID AppID `json:"application_id"`
ChannelID ChannelID `json:"channel_id,omitempty"`
Token string `json:"token"`
Version int `json:"version"`
// Message is the message the component was attached to.
// Only present for component interactions, not command interactions.
Message *Message `json:"message,omitempty"`
// Member is only present if this came from a guild. To get a user, use the
// Sender method.
Member *Member `json:"member,omitempty"`
GuildID GuildID `json:"guild_id,omitempty"`
// User is only present if this didn't come from a guild. To get a user, use
// the Sender method.
User *User `json:"user,omitempty"`
}
// 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 {
return err
}
var err error
switch target.Type {
case PingInteractionType:
e.Data = &PingInteraction{}
case CommandInteractionType:
e.Data = &CommandInteraction{}
case ComponentInteractionType:
d, err := ParseComponentInteraction(target.Data)
if err != nil {
return errors.Wrap(err, "failed to unmarshal component interaction event data")
}
e.Data = d
return nil
case AutocompleteInteractionType:
e.Data = &AutocompleteInteraction{}
default:
e.Data = &UnknownInteractionData{
Raw: target.Data,
typ: target.Type,
}
return nil
}
if err := json.Unmarshal(target.Data, e.Data); err != nil {
return errors.Wrap(err, "failed to unmarshal interaction event data")
}
return err
}
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
const (
PingInteractionType InteractionDataType = iota + 1
CommandInteractionType
ComponentInteractionType
AutocompleteInteractionType
)
// InteractionData holds the respose data of an interaction, or more
// specifically, the data that Discord sends to us. Type assertions should be
// made on it to access the underlying data. The underlying types of the
// Responses are value types. See the constructors for the possible types.
type InteractionData interface {
InteractionType() InteractionDataType
data()
}
// 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.
Name string `json:"name"`
CommandType CommandType `json:"type"`
Version string `json:"version"`
Options []AutocompleteOption `json:"options"`
}
// AutocompleteOption is an autocompletion option in an AutocompleteInteraction.
type AutocompleteOption struct {
Type CommandOptionType `json:"type"`
Name string `json:"name"`
Value string `json:"value"`
Focused bool `json:"focused"`
}
// Type implements ComponentInteraction.
func (*AutocompleteInteraction) InteractionType() InteractionDataType {
return AutocompleteInteractionType
}
func (*AutocompleteInteraction) data() {}
// ComponentInteraction is a union component interaction response types. The
// types can be whatever the constructors for this type will return. Underlying
// types of Response are all value types.
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()
}
// SelectInteraction is a select component's response.
type SelectInteraction struct {
CustomID ComponentID `json:"custom_id"`
Values []string `json:"values"`
}
// ID implements ComponentInteraction.
func (s *SelectInteraction) ID() ComponentID { return s.CustomID }
// Type implements ComponentInteraction.
func (s *SelectInteraction) Type() ComponentType { return SelectComponentType }
// InteractionType implements InteractionData.
func (s *SelectInteraction) InteractionType() InteractionDataType {
return ComponentInteractionType
}
func (s *SelectInteraction) resp() {}
func (s *SelectInteraction) data() {}
// 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"`
}
// 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
}
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 {
return nil, errors.Wrap(err, "failed to unmarshal component interaction header")
}
var d ComponentInteraction
switch t.Type {
case ButtonComponentType:
d = &ButtonInteraction{CustomID: t.CustomID}
case SelectComponentType:
d = &SelectInteraction{CustomID: t.CustomID}
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 {
return nil, errors.Wrap(err, "failed to unmarshal component interaction data")
}
return d, nil
}
// CommandInteraction is a command interaction that Discord sends to us.
type CommandInteraction struct {
ID CommandID `json:"id"`
Name string `json:"name"`
Options []CommandInteractionOption `json:"options"`
}
// InteractionType implements InteractionData.
func (*CommandInteraction) InteractionType() InteractionDataType {
return CommandInteractionType
}
func (*CommandInteraction) data() {}
// CommandInteractionOption is an option for a Command interaction response.
type CommandInteractionOption struct {
Name string `json:"name"`
Value json.Raw `json:"value"`
Options []CommandInteractionOption `json:"options"`
}
// 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 CommandInteractionOption) String() string {
val := string(o.Value)
s, err := strconv.Unquote(val)
if err != nil {
return val
}
return s
}
// IntValue reads the option's value as an int.
func (o CommandInteractionOption) 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 CommandInteractionOption) 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 CommandInteractionOption) 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 CommandInteractionOption) FloatValue() (float64, error) {
var f float64
err := o.Value.UnmarshalTo(&f)
return f, err
}
// 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() {}