2021-05-12 05:36:03 +00:00
|
|
|
package discord
|
|
|
|
|
2021-05-30 04:28:37 +00:00
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
|
2021-06-02 02:53:19 +00:00
|
|
|
"github.com/diamondburned/arikawa/v3/utils/json"
|
2021-08-30 15:42:54 +00:00
|
|
|
"github.com/diamondburned/arikawa/v3/utils/json/option"
|
2021-05-30 04:28:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var ErrNestedActionRow = errors.New("action row cannot have action row as a child")
|
2021-05-12 05:36:03 +00:00
|
|
|
|
|
|
|
// ComponentType is the type of a component.
|
|
|
|
type ComponentType uint
|
|
|
|
|
|
|
|
const (
|
2021-05-30 04:28:37 +00:00
|
|
|
ActionRowComponentType ComponentType = iota + 1
|
|
|
|
ButtonComponentType
|
2021-08-10 21:02:30 +00:00
|
|
|
SelectComponentType
|
2021-05-12 05:36:03 +00:00
|
|
|
)
|
|
|
|
|
2021-05-30 04:32:05 +00:00
|
|
|
// ComponentWrap wraps Component for the purpose of JSON unmarshalling.
|
2021-05-30 04:28:37 +00:00
|
|
|
// Type assetions should be made on Component to access the underlying data.
|
|
|
|
type ComponentWrap struct {
|
|
|
|
Component Component
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnwrapComponents returns a slice of the underlying component interfaces.
|
|
|
|
func UnwrapComponents(wraps []ComponentWrap) []Component {
|
|
|
|
components := make([]Component, len(wraps))
|
|
|
|
for i, w := range wraps {
|
|
|
|
components[i] = w.Component
|
|
|
|
}
|
|
|
|
|
|
|
|
return components
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type returns the underlying component's type.
|
|
|
|
func (c ComponentWrap) Type() ComponentType {
|
|
|
|
return c.Component.Type()
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalJSON marshals the component in the format Discord expects.
|
|
|
|
func (c *ComponentWrap) MarshalJSON() ([]byte, error) {
|
|
|
|
return c.Component.MarshalJSON()
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnmarshalJSON unmarshals json into the component.
|
|
|
|
func (c *ComponentWrap) UnmarshalJSON(b []byte) error {
|
|
|
|
var t struct {
|
|
|
|
Type ComponentType `json:"type"`
|
|
|
|
}
|
|
|
|
|
|
|
|
err := json.Unmarshal(b, &t)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch t.Type {
|
|
|
|
case ActionRowComponentType:
|
|
|
|
c.Component = &ActionRowComponent{}
|
|
|
|
case ButtonComponentType:
|
|
|
|
c.Component = &ButtonComponent{}
|
|
|
|
default:
|
|
|
|
c.Component = &UnknownComponent{typ: t.Type}
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Unmarshal(b, c.Component)
|
|
|
|
}
|
|
|
|
|
2021-05-12 05:36:03 +00:00
|
|
|
// Component is a component that can be attached to an interaction response.
|
|
|
|
type Component interface {
|
|
|
|
json.Marshaler
|
|
|
|
Type() ComponentType
|
|
|
|
}
|
|
|
|
|
2021-08-30 20:15:59 +00:00
|
|
|
// ActionRowComponent is a row of components at the bottom of a message.
|
2021-05-30 04:28:37 +00:00
|
|
|
type ActionRowComponent struct {
|
2021-05-12 05:36:03 +00:00
|
|
|
Components []Component `json:"components"`
|
|
|
|
}
|
|
|
|
|
2021-05-30 04:32:05 +00:00
|
|
|
// Type implements the Component interface.
|
2021-05-30 04:28:37 +00:00
|
|
|
func (ActionRowComponent) Type() ComponentType {
|
|
|
|
return ActionRowComponentType
|
2021-05-12 05:36:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalJSON marshals the action row in the format Discord expects.
|
2021-05-30 04:28:37 +00:00
|
|
|
func (a ActionRowComponent) MarshalJSON() ([]byte, error) {
|
|
|
|
type actionRow ActionRowComponent
|
2021-05-12 05:36:03 +00:00
|
|
|
|
|
|
|
return json.Marshal(struct {
|
|
|
|
actionRow
|
|
|
|
Type ComponentType `json:"type"`
|
|
|
|
}{
|
|
|
|
actionRow: actionRow(a),
|
2021-05-30 04:28:37 +00:00
|
|
|
Type: ActionRowComponentType,
|
2021-05-12 05:36:03 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-30 04:28:37 +00:00
|
|
|
// UnmarshalJSON unmarshals json into the components.
|
|
|
|
func (a *ActionRowComponent) UnmarshalJSON(b []byte) error {
|
|
|
|
type actionRow ActionRowComponent
|
|
|
|
|
|
|
|
type rowTypes struct {
|
|
|
|
Components []struct {
|
|
|
|
Type ComponentType `json:"type"`
|
|
|
|
} `json:"components"`
|
|
|
|
}
|
|
|
|
|
|
|
|
var r rowTypes
|
|
|
|
err := json.Unmarshal(b, &r)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
a.Components = make([]Component, len(r.Components))
|
|
|
|
for i, t := range r.Components {
|
|
|
|
switch t.Type {
|
|
|
|
case ActionRowComponentType:
|
|
|
|
// ActionRow cannot have child components of type Actionrow
|
|
|
|
return ErrNestedActionRow
|
|
|
|
case ButtonComponentType:
|
|
|
|
a.Components[i] = &ButtonComponent{}
|
|
|
|
default:
|
|
|
|
a.Components[i] = &UnknownComponent{typ: t.Type}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
alias := actionRow(*a)
|
|
|
|
err = json.Unmarshal(b, &alias)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
*a = ActionRowComponent(alias)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-30 20:15:59 +00:00
|
|
|
// ButtonComponent is a clickable button that may be added to an interaction
|
|
|
|
// response.
|
2021-05-30 04:28:37 +00:00
|
|
|
type ButtonComponent struct {
|
2021-05-12 05:36:03 +00:00
|
|
|
Label string `json:"label"`
|
|
|
|
// CustomID attached to InteractionCreate event when clicked.
|
|
|
|
CustomID string `json:"custom_id"`
|
|
|
|
Style ButtonStyle `json:"style"`
|
|
|
|
Emoji *ButtonEmoji `json:"emoji,omitempty"`
|
2021-08-30 20:15:59 +00:00
|
|
|
// URL is only present on link-style buttons.
|
|
|
|
URL URL `json:"url,omitempty"`
|
|
|
|
Disabled bool `json:"disabled,omitempty"`
|
2021-05-12 05:36:03 +00:00
|
|
|
}
|
|
|
|
|
2021-05-30 04:32:05 +00:00
|
|
|
// Type implements the Component interface.
|
2021-05-30 04:28:37 +00:00
|
|
|
func (ButtonComponent) Type() ComponentType {
|
|
|
|
return ButtonComponentType
|
|
|
|
}
|
|
|
|
|
2021-05-12 05:36:03 +00:00
|
|
|
// ButtonStyle is the style to display a button in.
|
|
|
|
type ButtonStyle uint
|
|
|
|
|
|
|
|
// All types of ButtonStyle documented.
|
|
|
|
const (
|
2021-08-30 20:15:59 +00:00
|
|
|
// PrimaryButton is a blurple button.
|
|
|
|
PrimaryButton ButtonStyle = iota + 1
|
|
|
|
// SecondaryButton is a grey button.
|
|
|
|
SecondaryButton
|
|
|
|
// SuccessButton is a green button.
|
|
|
|
SuccessButton
|
|
|
|
// DangerButton is a red button.
|
|
|
|
DangerButton
|
|
|
|
// LinkButton is a button that navigates to a URL.
|
|
|
|
LinkButton
|
2021-05-12 05:36:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ButtonEmoji is the emoji displayed on the button before the text.
|
|
|
|
type ButtonEmoji struct {
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
ID EmojiID `json:"id,omitempty"`
|
|
|
|
Animated bool `json:"animated,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalJSON marshals the button in the format Discord expects.
|
2021-05-30 04:28:37 +00:00
|
|
|
func (b ButtonComponent) MarshalJSON() ([]byte, error) {
|
|
|
|
type button ButtonComponent
|
2021-05-12 05:36:03 +00:00
|
|
|
|
|
|
|
if b.Style == 0 {
|
|
|
|
b.Style = PrimaryButton // Sane default for button.
|
|
|
|
}
|
|
|
|
|
|
|
|
return json.Marshal(struct {
|
|
|
|
button
|
|
|
|
Type ComponentType `json:"type"`
|
|
|
|
}{
|
|
|
|
button: button(b),
|
2021-05-30 04:28:37 +00:00
|
|
|
Type: ButtonComponentType,
|
2021-05-12 05:36:03 +00:00
|
|
|
})
|
|
|
|
}
|
2021-05-30 04:28:37 +00:00
|
|
|
|
2021-08-30 20:15:59 +00:00
|
|
|
// SelectComponent is a clickable button that may be added to an interaction
|
|
|
|
// response.
|
2021-08-10 21:02:30 +00:00
|
|
|
type SelectComponent struct {
|
|
|
|
CustomID string `json:"custom_id"`
|
|
|
|
Options []SelectComponentOption `json:"options"`
|
|
|
|
Placeholder string `json:"placeholder,omitempty"`
|
2021-08-30 15:42:54 +00:00
|
|
|
MinValues option.Int `json:"min_values,omitempty"`
|
2021-08-10 21:02:30 +00:00
|
|
|
MaxValues int `json:"max_values,omitempty"`
|
|
|
|
Disabled bool `json:"disabled,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type SelectComponentOption struct {
|
|
|
|
Label string `json:"label"`
|
|
|
|
Value string `json:"value"`
|
|
|
|
Description string `json:"description,omitempty"`
|
|
|
|
Emoji *ButtonEmoji `json:"emoji,omitempty"`
|
|
|
|
Default bool `json:"default,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Type implements the Component interface.
|
|
|
|
func (SelectComponent) Type() ComponentType {
|
|
|
|
return SelectComponentType
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarshalJSON marshals the select in the format Discord expects.
|
|
|
|
func (s SelectComponent) MarshalJSON() ([]byte, error) {
|
|
|
|
type selectComponent SelectComponent
|
|
|
|
|
|
|
|
return json.Marshal(struct {
|
|
|
|
selectComponent
|
|
|
|
Type ComponentType `json:"type"`
|
|
|
|
}{
|
|
|
|
selectComponent: selectComponent(s),
|
|
|
|
Type: SelectComponentType,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-05-30 04:28:37 +00:00
|
|
|
// UnknownComponent is reserved for components with unknown or not yet
|
|
|
|
// implemented components types.
|
|
|
|
type UnknownComponent struct {
|
|
|
|
json.Raw
|
|
|
|
typ ComponentType
|
|
|
|
}
|
|
|
|
|
2021-05-30 04:32:05 +00:00
|
|
|
// Type implements the Component interface.
|
2021-05-30 04:28:37 +00:00
|
|
|
func (u UnknownComponent) Type() ComponentType {
|
|
|
|
return u.typ
|
|
|
|
}
|