From b8188269f98bdf8261265582807a4f547c46cb7c Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Sat, 3 Dec 2022 01:40:30 +0300 Subject: [PATCH] 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. --- components.go | 46 ++++++++++++++---- examples/components/main.go | 93 ++++++++++++++++++++++++++----------- 2 files changed, 102 insertions(+), 37 deletions(-) diff --git a/components.go b/components.go index 6ee4e28..a5f86b3 100644 --- a/components.go +++ b/components.go @@ -10,10 +10,14 @@ type ComponentType uint // MessageComponent types. const ( - ActionsRowComponent ComponentType = 1 - ButtonComponent ComponentType = 2 - SelectMenuComponent ComponentType = 3 - TextInputComponent ComponentType = 4 + ActionsRowComponent ComponentType = 1 + ButtonComponent ComponentType = 2 + SelectMenuComponent ComponentType = 3 + TextInputComponent ComponentType = 4 + UserSelectMenuComponent ComponentType = 5 + RoleSelectMenuComponent ComponentType = 6 + MentionableSelectMenuComponent ComponentType = 7 + ChannelSelectMenuComponent ComponentType = 8 ) // MessageComponent is a base interface for all message components. @@ -41,7 +45,8 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error { umc.MessageComponent = &ActionsRow{} case ButtonComponent: umc.MessageComponent = &Button{} - case SelectMenuComponent: + case SelectMenuComponent, ChannelSelectMenuComponent, UserSelectMenuComponent, + RoleSelectMenuComponent, MentionableSelectMenuComponent: umc.MessageComponent = &SelectMenu{} case TextInputComponent: umc.MessageComponent = &TextInput{} @@ -169,8 +174,23 @@ type SelectMenuOption struct { 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. 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"` // 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"` @@ -179,25 +199,31 @@ type SelectMenu struct { // 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. MaxValues int `json:"max_values,omitempty"` - Options []SelectMenuOption `json:"options"` + Options []SelectMenuOption `json:"options,omitempty"` 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. -func (SelectMenu) Type() ComponentType { +func (s SelectMenu) Type() ComponentType { + if s.MenuType != 0 { + return ComponentType(s.MenuType) + } return SelectMenuComponent } // 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 return Marshal(struct { selectMenu Type ComponentType `json:"type"` }{ - selectMenu: selectMenu(m), - Type: m.Type(), + selectMenu: selectMenu(s), + Type: s.Type(), }) } diff --git a/examples/components/main.go b/examples/components/main.go index a8bb341..b3feab3 100644 --- a/examples/components/main.go +++ b/examples/components/main.go @@ -165,38 +165,52 @@ var ( } time.Sleep(time.Second) // Doing that so user won't see instant response. _, 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.", - Components: []discordgo.MessageComponent{ - discordgo.ActionsRow{ - Components: []discordgo.MessageComponent{ - discordgo.Button{ - Emoji: discordgo.ComponentEmoji{ - Name: "📜", + 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{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "📜", + }, + Label: "Documentation", + Style: discordgo.LinkButton, + URL: "https://discord.com/developers/docs/interactions/message-components#select-menus", }, - Label: "Documentation", - Style: discordgo.LinkButton, - URL: "https://discord.com/developers/docs/interactions/message-components#select-menus", - }, - discordgo.Button{ - Emoji: discordgo.ComponentEmoji{ - Name: "🔧", + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "🔧", + }, + Label: "Discord developers", + Style: discordgo.LinkButton, + URL: "https://discord.gg/discord-developers", }, - Label: "Discord developers", - Style: discordgo.LinkButton, - URL: "https://discord.gg/discord-developers", - }, - discordgo.Button{ - Emoji: discordgo.ComponentEmoji{ - Name: "đŸĻĢ", + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "đŸĻĢ", + }, + Label: "Discord Gophers", + Style: discordgo.LinkButton, + URL: "https://discord.gg/7RuRrVHyXF", }, - Label: "Discord Gophers", - Style: discordgo.LinkButton, - URL: "https://discord.gg/7RuRrVHyXF", }, }, }, + + Flags: discordgo.MessageFlagsEphemeral, }, - Flags: discordgo.MessageFlagsEphemeral, }) if err != nil { panic(err) @@ -318,7 +332,7 @@ var ( response = &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, 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", Flags: discordgo.MessageFlagsEphemeral, 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) if err != nil { @@ -430,6 +464,11 @@ func main() { Name: "single", 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", })