2021-09-09 23:06:44 +00:00
|
|
|
package discord
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/diamondburned/arikawa/v3/utils/json"
|
2021-10-10 22:44:31 +00:00
|
|
|
"github.com/pkg/errors"
|
2021-09-09 23:06:44 +00:00
|
|
|
)
|
|
|
|
|
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"`
|
|
|
|
|
|
|
|
// 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"`
|
|
|
|
}
|
|
|
|
|
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{}
|
|
|
|
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
|
2021-09-09 23:06:44 +00:00
|
|
|
return nil
|
2021-10-10 22:44:31 +00:00
|
|
|
case AutocompleteInteractionType:
|
|
|
|
e.Data = &AutocompleteInteraction{}
|
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 {
|
|
|
|
return errors.Wrap(err, "failed to unmarshal interaction event data")
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
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
|
|
|
|
// made on it to access the underlying data. The underlying types of the
|
|
|
|
// Responses are value types. See the constructors for the possible types.
|
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.
|
|
|
|
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"`
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// Type implements ComponentInteraction.
|
|
|
|
func (*AutocompleteInteraction) InteractionType() InteractionDataType {
|
|
|
|
return AutocompleteInteractionType
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
2021-10-10 22:44:31 +00:00
|
|
|
func (*AutocompleteInteraction) data() {}
|
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
|
|
|
|
// 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()
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// SelectInteraction is a select component's response.
|
|
|
|
type SelectInteraction struct {
|
|
|
|
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.
|
|
|
|
func (s *SelectInteraction) ID() ComponentID { return s.CustomID }
|
2021-10-01 18:48:17 +00:00
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// Type implements ComponentInteraction.
|
|
|
|
func (s *SelectInteraction) Type() ComponentType { return SelectComponentType }
|
|
|
|
|
|
|
|
// InteractionType implements InteractionData.
|
|
|
|
func (s *SelectInteraction) InteractionType() InteractionDataType {
|
|
|
|
return ComponentInteractionType
|
2021-10-01 18:48:17 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
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"`
|
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 {
|
|
|
|
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
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
|
|
|
|
2021-10-10 22:44:31 +00:00
|
|
|
// 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"`
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
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"`
|
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-09-09 23:06:44 +00:00
|
|
|
val := string(o.Value)
|
2021-10-10 22:44:31 +00:00
|
|
|
|
2021-09-09 23:06:44 +00:00
|
|
|
s, err := strconv.Unquote(val)
|
|
|
|
if err != nil {
|
2021-09-10 04:12:22 +00:00
|
|
|
return val
|
2021-09-09 23:06:44 +00:00
|
|
|
}
|
2021-10-10 22:44:31 +00:00
|
|
|
|
2021-09-10 04:12:22 +00:00
|
|
|
return s
|
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
|
|
|
|
|
|
|
// 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() {}
|