From 1b31249626ff3836925af5d54ccf2bbdbf9cd58d Mon Sep 17 00:00:00 2001 From: twoscott Date: Sat, 19 Nov 2022 17:47:08 +0000 Subject: [PATCH] discord: Add new select components --- discord/component.go | 267 +++++++++++++++++++++++++++--- discord/component_example_test.go | 11 +- discord/interaction.go | 4 +- 3 files changed, 253 insertions(+), 29 deletions(-) diff --git a/discord/component.go b/discord/component.go index 3036f58..f14096b 100644 --- a/discord/component.go +++ b/discord/component.go @@ -8,7 +8,6 @@ import ( "github.com/diamondburned/arikawa/v3/internal/rfutil" "github.com/diamondburned/arikawa/v3/utils/json" - "github.com/diamondburned/arikawa/v3/utils/json/option" "github.com/pkg/errors" ) @@ -19,8 +18,12 @@ const ( _ ComponentType = iota ActionRowComponentType ButtonComponentType - SelectComponentType + StringSelectComponentType TextInputComponentType + UserSelectComponentType + RoleSelectComponentType + MentionableSelectComponentType + ChannelSelectComponentType ) // String formats Type's name as a string. @@ -30,10 +33,18 @@ func (t ComponentType) String() string { return "ActionRow" case ButtonComponentType: return "Button" - case SelectComponentType: - return "Select" + case StringSelectComponentType: + return "StringSelect" case TextInputComponentType: return "TextInput" + case UserSelectComponentType: + return "User" + case RoleSelectComponentType: + return "Role" + case MentionableSelectComponentType: + return "Mentionable" + case ChannelSelectComponentType: + return "Channel" default: return fmt.Sprintf("ComponentType(%d)", int(t)) } @@ -142,8 +153,8 @@ func (c *ContainerComponents) Unmarshal(v interface{}) error { switch component := component.(type) { case *TextInputComponent: - v = component.Value.Val - case *SelectComponent: + v = component.Value + case *StringSelectComponent: switch len(component.Options) { case 0: // ok @@ -186,7 +197,7 @@ func (c *ContainerComponents) Unmarshal(v interface{}) error { switch elemt.Kind() { case reflect.String: switch component := component.(type) { - case *SelectComponent: + case *StringSelectComponent: fieldv.Set(reflect.MakeSlice(fieldt, len(component.Options), len(component.Options))) for i, option := range component.Options { fieldv.Index(i).Set(reflect.ValueOf(option.Value).Convert(elemt)) @@ -241,6 +252,10 @@ func (c *ContainerComponents) UnmarshalJSON(b []byte) error { // - *ButtonComponent // - *SelectComponent // - *TextInputComponent +// - *UserSelectComponent +// - *RoleSelectComponent +// - *MentionableSelectComponent +// - *ChannelSelectComponent // type Component interface { // Type returns the type of the underlying component. @@ -257,6 +272,10 @@ type Component interface { // - *ButtonComponent // - *SelectComponent // - *TextInputComponent +// - *UserSelectComponent +// - *RoleSelectComponent +// - *MentionableSelectComponent +// - *ChannelSelectComponent // type InteractiveComponent interface { Component @@ -296,8 +315,8 @@ func ParseComponent(b []byte) (Component, error) { c = &ActionRowComponent{} case ButtonComponentType: c = &ButtonComponent{} - case SelectComponentType: - c = &SelectComponent{} + case StringSelectComponentType: + c = &StringSelectComponent{} case TextInputComponentType: c = &TextInputComponent{} default: @@ -562,9 +581,9 @@ func (b *ButtonComponent) UnmarshalJSON(j []byte) error { 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. -type SelectComponent struct { +type StringSelectComponent struct { // Options are the choices in the select. Options []SelectOption `json:"options"` // CustomID is the custom unique ID. @@ -595,19 +614,19 @@ type SelectOption struct { } // 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. -func (s *SelectComponent) Type() ComponentType { - return SelectComponentType +func (s *StringSelectComponent) Type() ComponentType { + return StringSelectComponentType } -func (s *SelectComponent) _cmp() {} -func (s *SelectComponent) _icp() {} +func (s *StringSelectComponent) _cmp() {} +func (s *StringSelectComponent) _icp() {} // MarshalJSON marshals the select in the format Discord expects. -func (s *SelectComponent) MarshalJSON() ([]byte, error) { - type sel SelectComponent +func (s *StringSelectComponent) MarshalJSON() ([]byte, error) { + type sel StringSelectComponent type Msg struct { Type ComponentType `json:"type"` @@ -617,7 +636,7 @@ func (s *SelectComponent) MarshalJSON() ([]byte, error) { } msg := Msg{ - Type: SelectComponentType, + Type: StringSelectComponentType, sel: (*sel)(s), } @@ -654,9 +673,9 @@ type TextInputComponent struct { // Required dictates whether or not the user must fill out the component Required bool `json:"required"` // 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 option.NullableString `json:"placeholder,omitempty"` + Placeholder string `json:"placeholder,omitempty"` } func (s *TextInputComponent) _cmp() {} @@ -695,6 +714,212 @@ func (i *TextInputComponent) MarshalJSON() ([]byte, error) { 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 // components types. It can also be used in place of a ComponentInteraction. type UnknownComponent struct { diff --git a/discord/component_example_test.go b/discord/component_example_test.go index cf675b2..4345182 100644 --- a/discord/component_example_test.go +++ b/discord/component_example_test.go @@ -6,7 +6,6 @@ import ( "log" "github.com/diamondburned/arikawa/v3/discord" - "github.com/diamondburned/arikawa/v3/utils/json/option" ) func ExampleContainerComponents_Unmarshal() { @@ -14,21 +13,21 @@ func ExampleContainerComponents_Unmarshal() { &discord.ActionRowComponent{ &discord.TextInputComponent{ CustomID: "text1", - Value: option.NewNullableString("hello"), + Value: "hello", }, }, &discord.ActionRowComponent{ &discord.TextInputComponent{ CustomID: "text2", - Value: option.NewNullableString("hello 2"), + Value: "hello 2", }, &discord.TextInputComponent{ CustomID: "text3", - Value: option.NewNullableString("hello 3"), + Value: "hello 3", }, }, &discord.ActionRowComponent{ - &discord.SelectComponent{ + &discord.StringSelectComponent{ CustomID: "select1", Options: []discord.SelectOption{ {Value: "option 1"}, @@ -40,7 +39,7 @@ func ExampleContainerComponents_Unmarshal() { }, }, &discord.ActionRowComponent{ - &discord.SelectComponent{ + &discord.StringSelectComponent{ CustomID: "select2", Options: []discord.SelectOption{ {Value: "option 1"}, diff --git a/discord/interaction.go b/discord/interaction.go index 7af4122..3f06fb9 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -305,7 +305,7 @@ type SelectInteraction struct { func (s *SelectInteraction) ID() ComponentID { return s.CustomID } // Type implements ComponentInteraction. -func (s *SelectInteraction) Type() ComponentType { return SelectComponentType } +func (s *SelectInteraction) Type() ComponentType { return StringSelectComponentType } // InteractionType implements InteractionData. func (s *SelectInteraction) InteractionType() InteractionDataType { @@ -351,7 +351,7 @@ func ParseComponentInteraction(b []byte) (ComponentInteraction, error) { switch t.Type { case ButtonComponentType: d = &ButtonInteraction{CustomID: t.CustomID} - case SelectComponentType: + case StringSelectComponentType: d = &SelectInteraction{CustomID: t.CustomID} default: d = &UnknownComponent{