Auto populated select menus (#1269)

* feat(components): auto-populated selects

* Add component types for user, channel, role and mentionable selects
* Add MenuType field to SelectMenu for customization of select type
* Add basic example for auto-populated selects

* feat: implement SelectMenuType to restrict component types

Implement SelectMenuType to restrict component types that can be used
in MenuType field of SelectMenu structure.

* fix(SelectMenu): default type

Default to SelectMenuComponent type when MenuType is not specified.

* feat(examples/components): add ephemeral

Add ephemeral flag into response to match other component examples.

* feat(examples): option response and refactoring

* Add a response to the selected option.
* Refactor the command to match others.
* Remove showcase of multiple menu types.
This commit is contained in:
Fedor Lapshin 2022-12-03 01:40:30 +03:00 committed by GitHub
parent 2998b2c67d
commit b8188269f9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 102 additions and 37 deletions

View file

@ -14,6 +14,10 @@ const (
ButtonComponent ComponentType = 2 ButtonComponent ComponentType = 2
SelectMenuComponent ComponentType = 3 SelectMenuComponent ComponentType = 3
TextInputComponent ComponentType = 4 TextInputComponent ComponentType = 4
UserSelectMenuComponent ComponentType = 5
RoleSelectMenuComponent ComponentType = 6
MentionableSelectMenuComponent ComponentType = 7
ChannelSelectMenuComponent ComponentType = 8
) )
// MessageComponent is a base interface for all message components. // MessageComponent is a base interface for all message components.
@ -41,7 +45,8 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error {
umc.MessageComponent = &ActionsRow{} umc.MessageComponent = &ActionsRow{}
case ButtonComponent: case ButtonComponent:
umc.MessageComponent = &Button{} umc.MessageComponent = &Button{}
case SelectMenuComponent: case SelectMenuComponent, ChannelSelectMenuComponent, UserSelectMenuComponent,
RoleSelectMenuComponent, MentionableSelectMenuComponent:
umc.MessageComponent = &SelectMenu{} umc.MessageComponent = &SelectMenu{}
case TextInputComponent: case TextInputComponent:
umc.MessageComponent = &TextInput{} umc.MessageComponent = &TextInput{}
@ -169,8 +174,23 @@ type SelectMenuOption struct {
Default bool `json:"default"` Default bool `json:"default"`
} }
// SelectMenuType represents select menu type.
type SelectMenuType ComponentType
// SelectMenu types.
const (
StringSelectMenu = SelectMenuType(SelectMenuComponent)
UserSelectMenu = SelectMenuType(UserSelectMenuComponent)
RoleSelectMenu = SelectMenuType(RoleSelectMenuComponent)
MentionableSelectMenu = SelectMenuType(MentionableSelectMenuComponent)
ChannelSelectMenu = SelectMenuType(ChannelSelectMenuComponent)
)
// SelectMenu represents select menu component. // SelectMenu represents select menu component.
type SelectMenu struct { type SelectMenu struct {
// Type of the select menu.
MenuType SelectMenuType `json:"type,omitempty"`
// CustomID is a developer-defined identifier for the select menu.
CustomID string `json:"custom_id,omitempty"` CustomID string `json:"custom_id,omitempty"`
// The text which will be shown in the menu if there's no default options or all options was deselected and component was closed. // The text which will be shown in the menu if there's no default options or all options was deselected and component was closed.
Placeholder string `json:"placeholder"` Placeholder string `json:"placeholder"`
@ -179,25 +199,31 @@ type SelectMenu struct {
// This value determines the maximal amount of selected items in the menu. // This value determines the maximal amount of selected items in the menu.
// If MaxValues or MinValues are greater than one then the user can select multiple items in the component. // If MaxValues or MinValues are greater than one then the user can select multiple items in the component.
MaxValues int `json:"max_values,omitempty"` MaxValues int `json:"max_values,omitempty"`
Options []SelectMenuOption `json:"options"` Options []SelectMenuOption `json:"options,omitempty"`
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
// NOTE: Can only be used in SelectMenu with Channel menu type.
ChannelTypes []ChannelType `json:"channel_types,omitempty"`
} }
// Type is a method to get the type of a component. // Type is a method to get the type of a component.
func (SelectMenu) Type() ComponentType { func (s SelectMenu) Type() ComponentType {
if s.MenuType != 0 {
return ComponentType(s.MenuType)
}
return SelectMenuComponent return SelectMenuComponent
} }
// MarshalJSON is a method for marshaling SelectMenu to a JSON object. // MarshalJSON is a method for marshaling SelectMenu to a JSON object.
func (m SelectMenu) MarshalJSON() ([]byte, error) { func (s SelectMenu) MarshalJSON() ([]byte, error) {
type selectMenu SelectMenu type selectMenu SelectMenu
return Marshal(struct { return Marshal(struct {
selectMenu selectMenu
Type ComponentType `json:"type"` Type ComponentType `json:"type"`
}{ }{
selectMenu: selectMenu(m), selectMenu: selectMenu(s),
Type: m.Type(), Type: s.Type(),
}) })
} }

View file

@ -165,7 +165,19 @@ var (
} }
time.Sleep(time.Second) // Doing that so user won't see instant response. time.Sleep(time.Second) // Doing that so user won't see instant response.
_, err = s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ _, err = s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{
Content: "Now you know everything about select component. If you want to know more or ask a question - feel free to.", Content: "But wait, there is more! You can also auto populate the select menu. Try executing `/selects auto-populated`.",
Flags: discordgo.MessageFlagsEphemeral,
})
if err != nil {
panic(err)
}
},
"channel_select": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "This is it. You've reached your destination. Your choice was <#" + i.MessageComponentData().Values[0] + ">\n" +
"If you want to know more, check out the links below",
Components: []discordgo.MessageComponent{ Components: []discordgo.MessageComponent{
discordgo.ActionsRow{ discordgo.ActionsRow{
Components: []discordgo.MessageComponent{ Components: []discordgo.MessageComponent{
@ -196,7 +208,9 @@ var (
}, },
}, },
}, },
Flags: discordgo.MessageFlagsEphemeral, Flags: discordgo.MessageFlagsEphemeral,
},
}) })
if err != nil { if err != nil {
panic(err) panic(err)
@ -318,7 +332,7 @@ var (
response = &discordgo.InteractionResponse{ response = &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource, Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{ Data: &discordgo.InteractionResponseData{
Content: "The tastiest things are left for the end. Let's see how the multi-item select menu works: " + Content: "Now let's see how the multi-item select menu works: " +
"try generating your own stackoverflow search link", "try generating your own stackoverflow search link",
Flags: discordgo.MessageFlagsEphemeral, Flags: discordgo.MessageFlagsEphemeral,
Components: []discordgo.MessageComponent{ Components: []discordgo.MessageComponent{
@ -381,7 +395,27 @@ var (
}, },
}, },
} }
case "auto-populated":
response = &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: "The tastiest things are left for the end. Meet auto populated select menus.\n" +
"By setting `MenuType` on the select menu you can tell Discord to automatically populate the menu with entities of your choice: roles, members, channels. Try one below.",
Flags: discordgo.MessageFlagsEphemeral,
Components: []discordgo.MessageComponent{
discordgo.ActionsRow{
Components: []discordgo.MessageComponent{
discordgo.SelectMenu{
MenuType: discordgo.ChannelSelectMenu,
CustomID: "channel_select",
Placeholder: "Pick your favorite channel!",
ChannelTypes: []discordgo.ChannelType{discordgo.ChannelTypeGuildText},
},
},
},
},
},
}
} }
err := s.InteractionRespond(i.Interaction, response) err := s.InteractionRespond(i.Interaction, response)
if err != nil { if err != nil {
@ -430,6 +464,11 @@ func main() {
Name: "single", Name: "single",
Description: "Single-item select menu", Description: "Single-item select menu",
}, },
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "auto-populated",
Description: "Automatically populated select menu, which lets you pick a member, channel or role",
},
}, },
Description: "Lo and behold: dropdowns are coming", Description: "Lo and behold: dropdowns are coming",
}) })