1
0
Fork 0
mirror of https://github.com/diamondburned/arikawa.git synced 2024-11-12 18:02:59 +00:00

discord: Add new select components

This commit is contained in:
twoscott 2022-11-19 17:47:08 +00:00 committed by diamondburned
parent e5aabda660
commit 1b31249626
3 changed files with 253 additions and 29 deletions

View file

@ -8,7 +8,6 @@ import (
"github.com/diamondburned/arikawa/v3/internal/rfutil" "github.com/diamondburned/arikawa/v3/internal/rfutil"
"github.com/diamondburned/arikawa/v3/utils/json" "github.com/diamondburned/arikawa/v3/utils/json"
"github.com/diamondburned/arikawa/v3/utils/json/option"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -19,8 +18,12 @@ const (
_ ComponentType = iota _ ComponentType = iota
ActionRowComponentType ActionRowComponentType
ButtonComponentType ButtonComponentType
SelectComponentType StringSelectComponentType
TextInputComponentType TextInputComponentType
UserSelectComponentType
RoleSelectComponentType
MentionableSelectComponentType
ChannelSelectComponentType
) )
// String formats Type's name as a string. // String formats Type's name as a string.
@ -30,10 +33,18 @@ func (t ComponentType) String() string {
return "ActionRow" return "ActionRow"
case ButtonComponentType: case ButtonComponentType:
return "Button" return "Button"
case SelectComponentType: case StringSelectComponentType:
return "Select" return "StringSelect"
case TextInputComponentType: case TextInputComponentType:
return "TextInput" return "TextInput"
case UserSelectComponentType:
return "User"
case RoleSelectComponentType:
return "Role"
case MentionableSelectComponentType:
return "Mentionable"
case ChannelSelectComponentType:
return "Channel"
default: default:
return fmt.Sprintf("ComponentType(%d)", int(t)) return fmt.Sprintf("ComponentType(%d)", int(t))
} }
@ -142,8 +153,8 @@ func (c *ContainerComponents) Unmarshal(v interface{}) error {
switch component := component.(type) { switch component := component.(type) {
case *TextInputComponent: case *TextInputComponent:
v = component.Value.Val v = component.Value
case *SelectComponent: case *StringSelectComponent:
switch len(component.Options) { switch len(component.Options) {
case 0: case 0:
// ok // ok
@ -186,7 +197,7 @@ func (c *ContainerComponents) Unmarshal(v interface{}) error {
switch elemt.Kind() { switch elemt.Kind() {
case reflect.String: case reflect.String:
switch component := component.(type) { switch component := component.(type) {
case *SelectComponent: case *StringSelectComponent:
fieldv.Set(reflect.MakeSlice(fieldt, len(component.Options), len(component.Options))) fieldv.Set(reflect.MakeSlice(fieldt, len(component.Options), len(component.Options)))
for i, option := range component.Options { for i, option := range component.Options {
fieldv.Index(i).Set(reflect.ValueOf(option.Value).Convert(elemt)) fieldv.Index(i).Set(reflect.ValueOf(option.Value).Convert(elemt))
@ -241,6 +252,10 @@ func (c *ContainerComponents) UnmarshalJSON(b []byte) error {
// - *ButtonComponent // - *ButtonComponent
// - *SelectComponent // - *SelectComponent
// - *TextInputComponent // - *TextInputComponent
// - *UserSelectComponent
// - *RoleSelectComponent
// - *MentionableSelectComponent
// - *ChannelSelectComponent
// //
type Component interface { type Component interface {
// Type returns the type of the underlying component. // Type returns the type of the underlying component.
@ -257,6 +272,10 @@ type Component interface {
// - *ButtonComponent // - *ButtonComponent
// - *SelectComponent // - *SelectComponent
// - *TextInputComponent // - *TextInputComponent
// - *UserSelectComponent
// - *RoleSelectComponent
// - *MentionableSelectComponent
// - *ChannelSelectComponent
// //
type InteractiveComponent interface { type InteractiveComponent interface {
Component Component
@ -296,8 +315,8 @@ func ParseComponent(b []byte) (Component, error) {
c = &ActionRowComponent{} c = &ActionRowComponent{}
case ButtonComponentType: case ButtonComponentType:
c = &ButtonComponent{} c = &ButtonComponent{}
case SelectComponentType: case StringSelectComponentType:
c = &SelectComponent{} c = &StringSelectComponent{}
case TextInputComponentType: case TextInputComponentType:
c = &TextInputComponent{} c = &TextInputComponent{}
default: default:
@ -562,9 +581,9 @@ func (b *ButtonComponent) UnmarshalJSON(j []byte) error {
return nil return nil
} }
// Select is a clickable button that may be added to an interaction // StringSelectComponent is a clickable button that may be added to an interaction
// response. // response.
type SelectComponent struct { type StringSelectComponent struct {
// Options are the choices in the select. // Options are the choices in the select.
Options []SelectOption `json:"options"` Options []SelectOption `json:"options"`
// CustomID is the custom unique ID. // CustomID is the custom unique ID.
@ -595,19 +614,19 @@ type SelectOption struct {
} }
// ID implements the Component interface. // ID implements the Component interface.
func (s *SelectComponent) ID() ComponentID { return s.CustomID } func (s *StringSelectComponent) ID() ComponentID { return s.CustomID }
// Type implements the Component interface. // Type implements the Component interface.
func (s *SelectComponent) Type() ComponentType { func (s *StringSelectComponent) Type() ComponentType {
return SelectComponentType return StringSelectComponentType
} }
func (s *SelectComponent) _cmp() {} func (s *StringSelectComponent) _cmp() {}
func (s *SelectComponent) _icp() {} func (s *StringSelectComponent) _icp() {}
// MarshalJSON marshals the select in the format Discord expects. // MarshalJSON marshals the select in the format Discord expects.
func (s *SelectComponent) MarshalJSON() ([]byte, error) { func (s *StringSelectComponent) MarshalJSON() ([]byte, error) {
type sel SelectComponent type sel StringSelectComponent
type Msg struct { type Msg struct {
Type ComponentType `json:"type"` Type ComponentType `json:"type"`
@ -617,7 +636,7 @@ func (s *SelectComponent) MarshalJSON() ([]byte, error) {
} }
msg := Msg{ msg := Msg{
Type: SelectComponentType, Type: StringSelectComponentType,
sel: (*sel)(s), sel: (*sel)(s),
} }
@ -654,9 +673,9 @@ type TextInputComponent struct {
// Required dictates whether or not the user must fill out the component // Required dictates whether or not the user must fill out the component
Required bool `json:"required"` Required bool `json:"required"`
// Value is the pre-filled value of this component (max 4000 chars) // Value is the pre-filled value of this component (max 4000 chars)
Value option.NullableString `json:"value,omitempty"` Value string `json:"value,omitempty"`
// Placeholder is the text that appears when the input is empty (max 100 chars) // Placeholder is the text that appears when the input is empty (max 100 chars)
Placeholder option.NullableString `json:"placeholder,omitempty"` Placeholder string `json:"placeholder,omitempty"`
} }
func (s *TextInputComponent) _cmp() {} func (s *TextInputComponent) _cmp() {}
@ -695,6 +714,212 @@ func (i *TextInputComponent) MarshalJSON() ([]byte, error) {
return json.Marshal(m) return json.Marshal(m)
} }
type UserSelectComponent struct {
// CustomID is the custom unique ID.
CustomID ComponentID `json:"custom_id,omitempty"`
// Placeholder is the custom placeholder text if nothing is selected. Max
// 100 characters.
Placeholder string `json:"placeholder,omitempty"`
// ValueLimits is the minimum and maximum number of items that can be
// chosen. The default is [1, 1] if ValueLimits is a zero-value.
ValueLimits [2]int `json:"-"`
// Disabled disables the select if true.
Disabled bool `json:"disabled,omitempty"`
}
// ID implements the Component interface.
func (s *UserSelectComponent) ID() ComponentID { return s.CustomID }
// Type implements the Component interface.
func (s *UserSelectComponent) Type() ComponentType {
return UserSelectComponentType
}
func (s *UserSelectComponent) _cmp() {}
func (s *UserSelectComponent) _icp() {}
// MarshalJSON marshals the select in the format Discord expects.
func (s *UserSelectComponent) MarshalJSON() ([]byte, error) {
type sel UserSelectComponent
type Msg struct {
Type ComponentType `json:"type"`
*sel
MinValues *int `json:"min_values,omitempty"`
MaxValues *int `json:"max_values,omitempty"`
}
msg := Msg{
Type: UserSelectComponentType,
sel: (*sel)(s),
}
if s.ValueLimits != [2]int{0, 0} {
msg.MinValues = new(int)
msg.MaxValues = new(int)
*msg.MinValues = s.ValueLimits[0]
*msg.MaxValues = s.ValueLimits[1]
}
return json.Marshal(msg)
}
type RoleSelectComponent struct {
// CustomID is the custom unique ID.
CustomID ComponentID `json:"custom_id,omitempty"`
// Placeholder is the custom placeholder text if nothing is selected. Max
// 100 characters.
Placeholder string `json:"placeholder,omitempty"`
// ValueLimits is the minimum and maximum number of items that can be
// chosen. The default is [1, 1] if ValueLimits is a zero-value.
ValueLimits [2]int `json:"-"`
// Disabled disables the select if true.
Disabled bool `json:"disabled,omitempty"`
}
// ID implements the Component interface.
func (s *RoleSelectComponent) ID() ComponentID { return s.CustomID }
// Type implements the Component interface.
func (s *RoleSelectComponent) Type() ComponentType {
return RoleSelectComponentType
}
func (s *RoleSelectComponent) _cmp() {}
func (s *RoleSelectComponent) _icp() {}
// MarshalJSON marshals the select in the format Discord expects.
func (s *RoleSelectComponent) MarshalJSON() ([]byte, error) {
type sel RoleSelectComponent
type Msg struct {
Type ComponentType `json:"type"`
*sel
MinValues *int `json:"min_values,omitempty"`
MaxValues *int `json:"max_values,omitempty"`
}
msg := Msg{
Type: RoleSelectComponentType,
sel: (*sel)(s),
}
if s.ValueLimits != [2]int{0, 0} {
msg.MinValues = new(int)
msg.MaxValues = new(int)
*msg.MinValues = s.ValueLimits[0]
*msg.MaxValues = s.ValueLimits[1]
}
return json.Marshal(msg)
}
type MentionableSelectComponent struct {
// CustomID is the custom unique ID.
CustomID ComponentID `json:"custom_id,omitempty"`
// Placeholder is the custom placeholder text if nothing is selected. Max
// 100 characters.
Placeholder string `json:"placeholder,omitempty"`
// ValueLimits is the minimum and maximum number of items that can be
// chosen. The default is [1, 1] if ValueLimits is a zero-value.
ValueLimits [2]int `json:"-"`
// Disabled disables the select if true.
Disabled bool `json:"disabled,omitempty"`
}
// ID implements the Component interface.
func (s *MentionableSelectComponent) ID() ComponentID { return s.CustomID }
// Type implements the Component interface.
func (s *MentionableSelectComponent) Type() ComponentType {
return MentionableSelectComponentType
}
func (s *MentionableSelectComponent) _cmp() {}
func (s *MentionableSelectComponent) _icp() {}
// MarshalJSON marshals the select in the format Discord expects.
func (s *MentionableSelectComponent) MarshalJSON() ([]byte, error) {
type sel MentionableSelectComponent
type Msg struct {
Type ComponentType `json:"type"`
*sel
MinValues *int `json:"min_values,omitempty"`
MaxValues *int `json:"max_values,omitempty"`
}
msg := Msg{
Type: MentionableSelectComponentType,
sel: (*sel)(s),
}
if s.ValueLimits != [2]int{0, 0} {
msg.MinValues = new(int)
msg.MaxValues = new(int)
*msg.MinValues = s.ValueLimits[0]
*msg.MaxValues = s.ValueLimits[1]
}
return json.Marshal(msg)
}
type ChannelSelectComponent struct {
// CustomID is the custom unique ID.
CustomID ComponentID `json:"custom_id,omitempty"`
// Placeholder is the custom placeholder text if nothing is selected. Max
// 100 characters.
Placeholder string `json:"placeholder,omitempty"`
// ValueLimits is the minimum and maximum number of items that can be
// chosen. The default is [1, 1] if ValueLimits is a zero-value.
ValueLimits [2]int `json:"-"`
// Disabled disables the select if true.
Disabled bool `json:"disabled,omitempty"`
// ChannelTypes is the types of channels that can be chosen from.
ChannelTypes []ChannelType `json:"channel_types,omitempty"`
}
// ID implements the Component interface.
func (s *ChannelSelectComponent) ID() ComponentID { return s.CustomID }
// Type implements the Component interface.
func (s *ChannelSelectComponent) Type() ComponentType {
return ChannelSelectComponentType
}
func (s *ChannelSelectComponent) _cmp() {}
func (s *ChannelSelectComponent) _icp() {}
// MarshalJSON marshals the select in the format Discord expects.
func (s *ChannelSelectComponent) MarshalJSON() ([]byte, error) {
type sel ChannelSelectComponent
type Msg struct {
Type ComponentType `json:"type"`
*sel
MinValues *int `json:"min_values,omitempty"`
MaxValues *int `json:"max_values,omitempty"`
}
msg := Msg{
Type: ChannelSelectComponentType,
sel: (*sel)(s),
}
if s.ValueLimits != [2]int{0, 0} {
msg.MinValues = new(int)
msg.MaxValues = new(int)
*msg.MinValues = s.ValueLimits[0]
*msg.MaxValues = s.ValueLimits[1]
}
return json.Marshal(msg)
}
// Unknown is reserved for components with unknown or not yet implemented // Unknown is reserved for components with unknown or not yet implemented
// components types. It can also be used in place of a ComponentInteraction. // components types. It can also be used in place of a ComponentInteraction.
type UnknownComponent struct { type UnknownComponent struct {

View file

@ -6,7 +6,6 @@ import (
"log" "log"
"github.com/diamondburned/arikawa/v3/discord" "github.com/diamondburned/arikawa/v3/discord"
"github.com/diamondburned/arikawa/v3/utils/json/option"
) )
func ExampleContainerComponents_Unmarshal() { func ExampleContainerComponents_Unmarshal() {
@ -14,21 +13,21 @@ func ExampleContainerComponents_Unmarshal() {
&discord.ActionRowComponent{ &discord.ActionRowComponent{
&discord.TextInputComponent{ &discord.TextInputComponent{
CustomID: "text1", CustomID: "text1",
Value: option.NewNullableString("hello"), Value: "hello",
}, },
}, },
&discord.ActionRowComponent{ &discord.ActionRowComponent{
&discord.TextInputComponent{ &discord.TextInputComponent{
CustomID: "text2", CustomID: "text2",
Value: option.NewNullableString("hello 2"), Value: "hello 2",
}, },
&discord.TextInputComponent{ &discord.TextInputComponent{
CustomID: "text3", CustomID: "text3",
Value: option.NewNullableString("hello 3"), Value: "hello 3",
}, },
}, },
&discord.ActionRowComponent{ &discord.ActionRowComponent{
&discord.SelectComponent{ &discord.StringSelectComponent{
CustomID: "select1", CustomID: "select1",
Options: []discord.SelectOption{ Options: []discord.SelectOption{
{Value: "option 1"}, {Value: "option 1"},
@ -40,7 +39,7 @@ func ExampleContainerComponents_Unmarshal() {
}, },
}, },
&discord.ActionRowComponent{ &discord.ActionRowComponent{
&discord.SelectComponent{ &discord.StringSelectComponent{
CustomID: "select2", CustomID: "select2",
Options: []discord.SelectOption{ Options: []discord.SelectOption{
{Value: "option 1"}, {Value: "option 1"},

View file

@ -305,7 +305,7 @@ type SelectInteraction struct {
func (s *SelectInteraction) ID() ComponentID { return s.CustomID } func (s *SelectInteraction) ID() ComponentID { return s.CustomID }
// Type implements ComponentInteraction. // Type implements ComponentInteraction.
func (s *SelectInteraction) Type() ComponentType { return SelectComponentType } func (s *SelectInteraction) Type() ComponentType { return StringSelectComponentType }
// InteractionType implements InteractionData. // InteractionType implements InteractionData.
func (s *SelectInteraction) InteractionType() InteractionDataType { func (s *SelectInteraction) InteractionType() InteractionDataType {
@ -351,7 +351,7 @@ func ParseComponentInteraction(b []byte) (ComponentInteraction, error) {
switch t.Type { switch t.Type {
case ButtonComponentType: case ButtonComponentType:
d = &ButtonInteraction{CustomID: t.CustomID} d = &ButtonInteraction{CustomID: t.CustomID}
case SelectComponentType: case StringSelectComponentType:
d = &SelectInteraction{CustomID: t.CustomID} d = &SelectInteraction{CustomID: t.CustomID}
default: default:
d = &UnknownComponent{ d = &UnknownComponent{