From 4ebe5a08ee6752ac8768cc3ed2f05ff6aad595f5 Mon Sep 17 00:00:00 2001 From: Fedor Lapshin Date: Mon, 9 Aug 2021 03:16:07 +0300 Subject: [PATCH] Selects component (#954) * Interactions: the Buttons (#933) * Interactions: buttons * Doc fix * Gofmt fix * Fix typo * Remaking interaction data into interface * Godoc fix * Gofmt fix * Godoc fix * InteractionData helper functions and some fixes in slash commands example * Fix components example * Yet another fix of components example * Fix interaction unmarshaling * Gofmt fix * Godoc fix * Gofmt fix * Corrected naming and docs * Rolled back API version * Requested fixes * Added support of components to webhook and regular messages * Fix components unmarshaling * Godoc fix * Requested fixes * Fixed unmarshaling issues * Components example: cleanup * Added components tracking to state * Requested fixes * Renaming fix * Remove more named returns * Minor English fixes Co-authored-by: Carson Hoffman * Doc fix * Gofmt fix * Fix typo * Remaking interaction data into interface * Godoc fix * Gofmt fix * Godoc fix * InteractionData helper functions and some fixes in slash commands example * Fix components example * Yet another fix of components example * Fix interaction unmarshaling * Godoc fix * Gofmt fix * Corrected naming and docs * Rolled back API version * Requested fixes * Added support of components to webhook and regular messages * Interactions: select menus * Example fix * Merge fix * Some fixes * Added missing documentation * Fix components unmarshaling * Godoc fix * Requested fixes * Fixed unmarshaling issues * Components example: cleanup * Gofmt fix * Godoc fix * URL field renaming fix * Added flags to followups * Updated components example * Fixed typo in components example * Merge fix * Improve handling of invalid interaction situations * support allowing webhook edits with files, and responding to interactions with files (#931) * allow files in webhook message edits * add Files to WebhookEdit struct * move the construction of the multipart body for files into a shared function * allow interaction responses to have files * go fmt * fix err shadowing * document MakeFilesBody * rename MakeFilesBody -> EncodeWithFiles. fix InteractionRespond responding twice * use resp in InteractionRespond files, add basic-command-with-files example command * import strings and go fmt * EncodeWithFiles -> MultiPartBodyWithJSON * go fmt * fix example for slash_commands * move files to responsedata * Merge fixes * Fixed rebase consequences Co-authored-by: Carson Hoffman Co-authored-by: plally --- components.go | 57 +++- examples/components/main.go | 457 ++++++++++++++++++++++++++------ examples/slash_commands/main.go | 2 +- interactions.go | 4 +- restapi.go | 27 +- webhook.go | 2 + 6 files changed, 453 insertions(+), 96 deletions(-) diff --git a/components.go b/components.go index c786bca1e..124e95e5c 100644 --- a/components.go +++ b/components.go @@ -11,6 +11,7 @@ type ComponentType uint const ( ActionsRowComponent ComponentType = 1 ButtonComponent ComponentType = 2 + SelectMenuComponent ComponentType = 3 ) // MessageComponent is a base interface for all message components. @@ -82,6 +83,7 @@ func (r *ActionsRow) UnmarshalJSON(data []byte) error { for i, v := range v.RawComponents { r.Components[i] = v.MessageComponent } + return err } @@ -107,8 +109,8 @@ const ( LinkButton ButtonStyle = 5 ) -// ButtonEmoji represents button emoji, if it does have one. -type ButtonEmoji struct { +// ComponentEmoji represents button emoji, if it does have one. +type ComponentEmoji struct { Name string `json:"name,omitempty"` ID string `json:"id,omitempty"` Animated bool `json:"animated,omitempty"` @@ -116,10 +118,10 @@ type ButtonEmoji struct { // Button represents button component. type Button struct { - Label string `json:"label"` - Style ButtonStyle `json:"style"` - Disabled bool `json:"disabled"` - Emoji ButtonEmoji `json:"emoji"` + Label string `json:"label"` + Style ButtonStyle `json:"style"` + Disabled bool `json:"disabled"` + Emoji ComponentEmoji `json:"emoji"` // NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID. URL string `json:"url,omitempty"` @@ -144,6 +146,47 @@ func (b Button) MarshalJSON() ([]byte, error) { } // Type is a method to get the type of a component. -func (b Button) Type() ComponentType { +func (Button) Type() ComponentType { return ButtonComponent } + +// SelectMenuOption represents an option for a select menu. +type SelectMenuOption struct { + Label string `json:"label,omitempty"` + Value string `json:"value"` + Description string `json:"description"` + Emoji ComponentEmoji `json:"emoji"` + // Determines whenever option is selected by default or not. + Default bool `json:"default"` +} + +// SelectMenu represents select menu component. +type SelectMenu struct { + 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"` + // This value determines the minimal amount of selected items in the menu. + MinValues int `json:"min_values,omitempty"` + // 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"` +} + +// Type is a method to get the type of a component. +func (SelectMenu) Type() ComponentType { + return SelectMenuComponent +} + +// MarshalJSON is a method for marshaling SelectMenu to a JSON object. +func (m SelectMenu) MarshalJSON() ([]byte, error) { + type selectMenu SelectMenu + + return json.Marshal(struct { + selectMenu + Type ComponentType `json:"type"` + }{ + selectMenu: selectMenu(m), + Type: m.Type(), + }) +} diff --git a/examples/components/main.go b/examples/components/main.go index fee748042..2e80c6411 100644 --- a/examples/components/main.go +++ b/examples/components/main.go @@ -2,9 +2,12 @@ package main import ( "flag" + "fmt" "log" "os" "os/signal" + "strings" + "time" "github.com/bwmarrin/discordgo" ) @@ -28,110 +31,406 @@ func init() { } } -func main() { - s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { - log.Println("Bot is up!") - }) - // Buttons are part of interactions, so we register InteractionCreate handler - s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { - if i.Type == discordgo.InteractionApplicationCommand { - if i.ApplicationCommandData().Name == "feedback" { - err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Are you satisfied with Buttons?", - // Buttons and other components are specified in Components field. - Components: []discordgo.MessageComponent{ - // ActionRow is a container of all buttons within the same row. - discordgo.ActionsRow{ - Components: []discordgo.MessageComponent{ - discordgo.Button{ - Label: "Yes", - Style: discordgo.SuccessButton, - Disabled: false, - CustomID: "yes_btn", +// Important note: call every command in order it's placed in the example. + +var ( + componentsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ + "fd_no": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Huh. I see, maybe some of these resources might help you?", + Flags: 1 << 6, + Components: []discordgo.MessageComponent{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "📜", }, - discordgo.Button{ - Label: "No", - Style: discordgo.DangerButton, - Disabled: false, - CustomID: "no_btn", + Label: "Documentation", + Style: discordgo.LinkButton, + URL: "https://discord.com/developers/docs/interactions/message-components#buttons", + }, + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "🔧", }, - discordgo.Button{ - Label: "I don't know", - Style: discordgo.LinkButton, - Disabled: false, - // Link buttons don't require CustomID and do not trigger the gateway/HTTP event - URL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - Emoji: discordgo.ButtonEmoji{ - Name: "🤷", - }, + Label: "Discord developers", + Style: discordgo.LinkButton, + URL: "https://discord.gg/discord-developers", + }, + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "đŸĻĢ", }, + Label: "Discord Gophers", + Style: discordgo.LinkButton, + URL: "https://discord.gg/7RuRrVHyXF", }, }, - // The message may have multiple actions rows. - discordgo.ActionsRow{ - Components: []discordgo.MessageComponent{ - discordgo.Button{ - Label: "Discord Developers server", - Style: discordgo.LinkButton, - Disabled: false, - URL: "https://discord.gg/discord-developers", + }, + }, + }, + }) + if err != nil { + panic(err) + } + }, + "fd_yes": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Great! If you wanna know more or just have questions, feel free to visit Discord Devs and Discord Gophers server. " + + "But now, when you know how buttons work, let's move onto select menus (execute `/selects single`)", + Flags: 1 << 6, + Components: []discordgo.MessageComponent{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "🔧", }, + Label: "Discord developers", + Style: discordgo.LinkButton, + URL: "https://discord.gg/discord-developers", + }, + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "đŸĻĢ", + }, + Label: "Discord Gophers", + Style: discordgo.LinkButton, + URL: "https://discord.gg/7RuRrVHyXF", }, }, }, }, - }) - if err != nil { - panic(err) - } + }, + }) + if err != nil { + panic(err) } - return - } - // Type for button press will be always InteractionButton (3) - if i.Type != discordgo.InteractionMessageComponent { - return - } + }, + "select": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + var response *discordgo.InteractionResponse - content := "Thanks for your feedback " + data := i.MessageComponentData() + switch data.Values[0] { + case "go": + response = &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "This is the way.", + Flags: 1 << 6, + }, + } + default: + response = &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "It is not the way to go.", + Flags: 1 << 6, + }, + } + } + err := s.InteractionRespond(i.Interaction, response) + if err != nil { + panic(err) + } + time.Sleep(time.Second) // Doing that so user won't see instant response. + _, err = s.FollowupMessageCreate(*AppID, i.Interaction, true, &discordgo.WebhookParams{ + Content: "Anyways, now when you know how to use single select menus, let's see how multi select menus work. " + + "Try calling `/selects multi` command.", + Flags: 1 << 6, + }) + if err != nil { + panic(err) + } + }, + "stackoverflow_tags": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + data := i.MessageComponentData() - // CustomID field contains the same id as when was sent. It's used to identify the which button was clicked. - switch i.MessageComponentData().CustomID { - case "yes_btn": - content += "(yes)" - case "no_btn": - content += "(no)" - } + const stackoverflowFormat = `https://stackoverflow.com/questions/tagged/%s` - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - // Buttons also may update the message which to which they are attached. - // Or may just acknowledge (InteractionResponseDeferredMessageUpdate) that the event was received and not update the message. - // To update it later you need to use interaction response edit endpoint. - Type: discordgo.InteractionResponseUpdateMessage, - Data: &discordgo.InteractionResponseData{ - Content: content, + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Here is your stackoverflow URL: " + fmt.Sprintf(stackoverflowFormat, strings.Join(data.Values, "+")), + Flags: 1 << 6, + }, + }) + if err != nil { + panic(err) + } + time.Sleep(time.Second) // Doing that so user won't see instant response. + _, err = s.FollowupMessageCreate(*AppID, 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{ - Label: "Our sponsor", - Style: discordgo.LinkButton, - Disabled: false, - URL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - Emoji: discordgo.ButtonEmoji{ - Name: "💠", + Emoji: discordgo.ComponentEmoji{ + Name: "📜", }, + Label: "Documentation", + Style: discordgo.LinkButton, + URL: "https://discord.com/developers/docs/interactions/message-components#select-menus", + }, + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "🔧", + }, + Label: "Discord developers", + Style: discordgo.LinkButton, + URL: "https://discord.gg/discord-developers", + }, + discordgo.Button{ + Emoji: discordgo.ComponentEmoji{ + Name: "đŸĻĢ", + }, + Label: "Discord Gophers", + Style: discordgo.LinkButton, + URL: "https://discord.gg/7RuRrVHyXF", }, }, }, }, - }, - }) + Flags: 1 << 6, + }) + if err != nil { + panic(err) + } + }, + } + commandsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ + "buttons": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Are you comfortable with buttons and other message components?", + Flags: 1 << 6, + // Buttons and other components are specified in Components field. + Components: []discordgo.MessageComponent{ + // ActionRow is a container of all buttons within the same row. + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.Button{ + // Label is what the user will see on the button. + Label: "Yes", + // Style provides coloring of the button. There are not so many styles tho. + Style: discordgo.SuccessButton, + // Disabled allows bot to disable some buttons for users. + Disabled: false, + // CustomID is a thing telling Discord which data to send when this button will be pressed. + CustomID: "fd_yes", + }, + discordgo.Button{ + Label: "No", + Style: discordgo.DangerButton, + Disabled: false, + CustomID: "fd_no", + }, + discordgo.Button{ + Label: "I don't know", + Style: discordgo.LinkButton, + Disabled: false, + // Link buttons don't require CustomID and do not trigger the gateway/HTTP event + URL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + Emoji: discordgo.ComponentEmoji{ + Name: "🤷", + }, + }, + }, + }, + // The message may have multiple actions rows. + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.Button{ + Label: "Discord Developers server", + Style: discordgo.LinkButton, + Disabled: false, + URL: "https://discord.gg/discord-developers", + }, + }, + }, + }, + }, + }) + if err != nil { + panic(err) + } + }, + "selects": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + var response *discordgo.InteractionResponse + switch i.ApplicationCommandData().Options[0].Name { + case "single": + response = &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Now let's take a look on selects. This is single item select menu.", + Flags: 1 << 6, + Components: []discordgo.MessageComponent{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.SelectMenu{ + // Select menu, as other components, must have a customID, so we set it to this value. + CustomID: "select", + Placeholder: "Choose your favorite programming language 👇", + Options: []discordgo.SelectMenuOption{ + { + Label: "Go", + // As with components, this things must have their own unique "id" to identify which is which. + // In this case such id is Value field. + Value: "go", + Emoji: discordgo.ComponentEmoji{ + Name: "đŸĻĻ", + }, + // You can also make it a default option, but in this case we won't. + Default: false, + Description: "Go programming language", + }, + { + Label: "JS", + Value: "js", + Emoji: discordgo.ComponentEmoji{ + Name: "🟨", + }, + Description: "JavaScript programming language", + }, + { + Label: "Python", + Value: "py", + Emoji: discordgo.ComponentEmoji{ + Name: "🐍", + }, + Description: "Python programming language", + }, + }, + }, + }, + }, + }, + }, + } + case "multi": + 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: " + + "try generating your own stackoverflow search link", + Flags: 1 << 6, + Components: []discordgo.MessageComponent{ + discordgo.ActionsRow{ + Components: []discordgo.MessageComponent{ + discordgo.SelectMenu{ + CustomID: "stackoverflow_tags", + Placeholder: "Select tags to search on StackOverflow", + // This is where confusion comes from. If you don't specify these things you will get single item select. + // These fields control the minimum and maximum amount of selected items. + MinValues: 1, + MaxValues: 3, + Options: []discordgo.SelectMenuOption{ + { + Label: "Go", + Description: "Simple yet powerful programming language", + Value: "go", + // Default works the same for multi-select menus. + Default: false, + Emoji: discordgo.ComponentEmoji{ + Name: "đŸĻĻ", + }, + }, + { + Label: "JS", + Description: "Multiparadigm OOP language", + Value: "javascript", + Emoji: discordgo.ComponentEmoji{ + Name: "🟨", + }, + }, + { + Label: "Python", + Description: "OOP prototyping programming language", + Value: "python", + Emoji: discordgo.ComponentEmoji{ + Name: "🐍", + }, + }, + { + Label: "Web", + Description: "Web related technologies", + Value: "web", + Emoji: discordgo.ComponentEmoji{ + Name: "🌐", + }, + }, + { + Label: "Desktop", + Description: "Desktop applications", + Value: "desktop", + Emoji: discordgo.ComponentEmoji{ + Name: "đŸ’ģ", + }, + }, + }, + }, + }, + }, + }, + }, + } + + } + err := s.InteractionRespond(i.Interaction, response) + if err != nil { + panic(err) + } + }, + } +) + +func main() { + s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { + log.Println("Bot is up!") + }) + // Components are part of interactions, so we register InteractionCreate handler + s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { + switch i.Type { + case discordgo.InteractionApplicationCommand: + if h, ok := commandsHandlers[i.ApplicationCommandData().Name]; ok { + h(s, i) + } + case discordgo.InteractionMessageComponent: + + if h, ok := componentsHandlers[i.MessageComponentData().CustomID]; ok { + h(s, i) + } + } }) _, err := s.ApplicationCommandCreate(*AppID, *GuildID, &discordgo.ApplicationCommand{ - Name: "feedback", - Description: "Give your feedback", + Name: "buttons", + Description: "Test the buttons if you got courage", + }) + + if err != nil { + log.Fatalf("Cannot create slash command: %v", err) + } + _, err = s.ApplicationCommandCreate(*AppID, *GuildID, &discordgo.ApplicationCommand{ + Name: "selects", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionSubCommand, + Name: "multi", + Description: "Multi-item select menu", + }, + { + Type: discordgo.ApplicationCommandOptionSubCommand, + Name: "single", + Description: "Single-item select menu", + }, + }, + Description: "Lo and behold: dropdowns are coming", }) if err != nil { diff --git a/examples/slash_commands/main.go b/examples/slash_commands/main.go index f28bece83..64e37bfc6 100644 --- a/examples/slash_commands/main.go +++ b/examples/slash_commands/main.go @@ -290,7 +290,7 @@ var ( return } time.AfterFunc(time.Second*5, func() { - err = s.InteractionResponseEdit(s.State.User.ID, i.Interaction, &discordgo.WebhookEdit{ + _, err = s.InteractionResponseEdit(s.State.User.ID, i.Interaction, &discordgo.WebhookEdit{ Content: content + "\n\nWell, now you know how to create and edit responses. " + "But you still don't know how to delete them... so... wait 10 seconds and this " + "message will be deleted.", diff --git a/interactions.go b/interactions.go index c45a63590..281dd7960 100644 --- a/interactions.go +++ b/interactions.go @@ -219,6 +219,9 @@ func (ApplicationCommandInteractionData) Type() InteractionType { type MessageComponentInteractionData struct { CustomID string `json:"custom_id"` ComponentType ComponentType `json:"component_type"` + + // NOTE: Only filled when ComponentType is SelectMenuComponent (3). Otherwise is nil. + Values []string `json:"values"` } // Type returns the type of interaction data. @@ -379,7 +382,6 @@ type InteractionResponseData struct { Embeds []*MessageEmbed `json:"embeds,omitempty"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` - // NOTE: Undocumented feature, be careful with it. Flags uint64 `json:"flags,omitempty"` Files []*File `json:"-"` diff --git a/restapi.go b/restapi.go index 9b48e2cac..a4f1f6e98 100644 --- a/restapi.go +++ b/restapi.go @@ -2165,29 +2165,40 @@ func (s *Session) WebhookMessage(webhookID, token, messageID string) (message *M return } -// WebhookMessageEdit edits a webhook message. +// WebhookMessageEdit edits a webhook message and returns a new one. // webhookID : The ID of a webhook // token : The auth token for the webhook // messageID : The ID of message to edit -func (s *Session) WebhookMessageEdit(webhookID, token, messageID string, data *WebhookEdit) (err error) { +func (s *Session) WebhookMessageEdit(webhookID, token, messageID string, data *WebhookEdit) (st *Message, err error) { uri := EndpointWebhookMessage(webhookID, token, messageID) + + var response []byte if len(data.Files) > 0 { contentType, body, err := MultipartBodyWithJSON(data, data.Files) if err != nil { - return err + return nil, err } - _, err = s.request("PATCH", uri, contentType, body, uri, 0) + response, err = s.request("PATCH", uri, contentType, body, uri, 0) + if err != nil { + return nil, err + } } else { - _, err = s.RequestWithBucketID("PATCH", uri, data, EndpointWebhookToken("", "")) + response, err = s.RequestWithBucketID("PATCH", uri, data, EndpointWebhookToken("", "")) + + if err != nil { + return nil, err + } } + + err = unmarshal(response, &st) return } // WebhookMessageDelete deletes a webhook message. // webhookID : The ID of a webhook // token : The auth token for the webhook -// messageID : The ID of message to edit +// messageID : The ID of a message to edit func (s *Session) WebhookMessageDelete(webhookID, token, messageID string) (err error) { uri := EndpointWebhookMessage(webhookID, token, messageID) @@ -2512,7 +2523,7 @@ func (s *Session) InteractionResponse(appID string, interaction *Interaction) (* // appID : The application ID. // interaction : Interaction instance. // newresp : Updated response message data. -func (s *Session) InteractionResponseEdit(appID string, interaction *Interaction, newresp *WebhookEdit) error { +func (s *Session) InteractionResponseEdit(appID string, interaction *Interaction, newresp *WebhookEdit) (*Message, error) { return s.WebhookMessageEdit(appID, interaction.Token, "@original", newresp) } @@ -2541,7 +2552,7 @@ func (s *Session) FollowupMessageCreate(appID string, interaction *Interaction, // interaction : Interaction instance. // messageID : The followup message ID. // data : Data to update the message -func (s *Session) FollowupMessageEdit(appID string, interaction *Interaction, messageID string, data *WebhookEdit) error { +func (s *Session) FollowupMessageEdit(appID string, interaction *Interaction, messageID string, data *WebhookEdit) (*Message, error) { return s.WebhookMessageEdit(appID, interaction.Token, messageID, data) } diff --git a/webhook.go b/webhook.go index 438a6df99..f54a45ce1 100644 --- a/webhook.go +++ b/webhook.go @@ -35,6 +35,8 @@ type WebhookParams struct { Components []MessageComponent `json:"components"` Embeds []*MessageEmbed `json:"embeds,omitempty"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` + // NOTE: Works only for followup messages. + Flags uint64 `json:"flags,omitempty"` } // WebhookEdit stores data for editing of a webhook message.