arikawa/discord/component.go

245 lines
6.1 KiB
Go

package discord
import (
"errors"
"github.com/diamondburned/arikawa/v3/utils/json"
)
var ErrNestedActionRow = errors.New("action row cannot have action row as a child")
// ComponentType is the type of a component.
type ComponentType uint
const (
ActionRowComponentType ComponentType = iota + 1
ButtonComponentType
SelectComponentType
)
// ComponentWrap wraps Component for the purpose of JSON unmarshalling.
// 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)
}
// Component is a component that can be attached to an interaction response.
type Component interface {
json.Marshaler
Type() ComponentType
}
// ActionRowComponent is a row of components at the bottom of a message.
type ActionRowComponent struct {
Components []Component `json:"components"`
}
// Type implements the Component interface.
func (ActionRowComponent) Type() ComponentType {
return ActionRowComponentType
}
// MarshalJSON marshals the action row in the format Discord expects.
func (a ActionRowComponent) MarshalJSON() ([]byte, error) {
type actionRow ActionRowComponent
return json.Marshal(struct {
actionRow
Type ComponentType `json:"type"`
}{
actionRow: actionRow(a),
Type: ActionRowComponentType,
})
}
// 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
}
// ButtonComponent is a clickable button that may be added to an interaction
// response.
type ButtonComponent struct {
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"`
// URL is only present on link-style buttons.
URL URL `json:"url,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
// Type implements the Component interface.
func (ButtonComponent) Type() ComponentType {
return ButtonComponentType
}
// ButtonStyle is the style to display a button in.
type ButtonStyle uint
// All types of ButtonStyle documented.
const (
// 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
)
// 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.
func (b ButtonComponent) MarshalJSON() ([]byte, error) {
type button ButtonComponent
if b.Style == 0 {
b.Style = PrimaryButton // Sane default for button.
}
return json.Marshal(struct {
button
Type ComponentType `json:"type"`
}{
button: button(b),
Type: ButtonComponentType,
})
}
// SelectComponent is a clickable button that may be added to an interaction
// response.
type SelectComponent struct {
CustomID string `json:"custom_id"`
Options []SelectComponentOption `json:"options"`
Placeholder string `json:"placeholder,omitempty"`
MinValues int `json:"min_values,omitempty"`
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,
})
}
// UnknownComponent is reserved for components with unknown or not yet
// implemented components types.
type UnknownComponent struct {
json.Raw
typ ComponentType
}
// Type implements the Component interface.
func (u UnknownComponent) Type() ComponentType {
return u.typ
}