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 <c@rsonhoffman.com> * 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 <c@rsonhoffman.com> Co-authored-by: plally <pierce@vulpes.dev>
This commit is contained in:
parent
ab47f123ba
commit
4ebe5a08ee
6 changed files with 467 additions and 110 deletions
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
discordgo.Button{
|
||||
Label: "No",
|
||||
Style: discordgo.DangerButton,
|
||||
Disabled: false,
|
||||
CustomID: "no_btn",
|
||||
},
|
||||
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: "🤷",
|
||||
},
|
||||
// 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: "📜",
|
||||
},
|
||||
Label: "Documentation",
|
||||
Style: discordgo.LinkButton,
|
||||
URL: "https://discord.com/developers/docs/interactions/message-components#buttons",
|
||||
},
|
||||
},
|
||||
// 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",
|
||||
discordgo.Button{
|
||||
Emoji: discordgo.ComponentEmoji{
|
||||
Name: "🔧",
|
||||
},
|
||||
Label: "Discord developers",
|
||||
Style: discordgo.LinkButton,
|
||||
URL: "https://discord.gg/discord-developers",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
// Type for button press will be always InteractionButton (3)
|
||||
if i.Type != discordgo.InteractionMessageComponent {
|
||||
return
|
||||
}
|
||||
|
||||
content := "Thanks for your feedback "
|
||||
|
||||
// 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)"
|
||||
}
|
||||
|
||||
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,
|
||||
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: "💠",
|
||||
discordgo.Button{
|
||||
Emoji: discordgo.ComponentEmoji{
|
||||
Name: "🦫",
|
||||
},
|
||||
Label: "Discord Gophers",
|
||||
Style: discordgo.LinkButton,
|
||||
URL: "https://discord.gg/7RuRrVHyXF",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
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)
|
||||
}
|
||||
},
|
||||
"select": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
var response *discordgo.InteractionResponse
|
||||
|
||||
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()
|
||||
|
||||
const stackoverflowFormat = `https://stackoverflow.com/questions/tagged/%s`
|
||||
|
||||
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{
|
||||
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 {
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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:"-"`
|
||||
|
|
27
restapi.go
27
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue