forked from pothtonswer/discordmuffin
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 (
|
const (
|
||||||
ActionsRowComponent ComponentType = 1
|
ActionsRowComponent ComponentType = 1
|
||||||
ButtonComponent ComponentType = 2
|
ButtonComponent ComponentType = 2
|
||||||
|
SelectMenuComponent ComponentType = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// MessageComponent is a base interface for all message components.
|
// 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 {
|
for i, v := range v.RawComponents {
|
||||||
r.Components[i] = v.MessageComponent
|
r.Components[i] = v.MessageComponent
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,8 +109,8 @@ const (
|
||||||
LinkButton ButtonStyle = 5
|
LinkButton ButtonStyle = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
// ButtonEmoji represents button emoji, if it does have one.
|
// ComponentEmoji represents button emoji, if it does have one.
|
||||||
type ButtonEmoji struct {
|
type ComponentEmoji struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
Animated bool `json:"animated,omitempty"`
|
Animated bool `json:"animated,omitempty"`
|
||||||
|
@ -119,7 +121,7 @@ type Button struct {
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Style ButtonStyle `json:"style"`
|
Style ButtonStyle `json:"style"`
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
Emoji ButtonEmoji `json:"emoji"`
|
Emoji ComponentEmoji `json:"emoji"`
|
||||||
|
|
||||||
// NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID.
|
// NOTE: Only button with LinkButton style can have link. Also, URL is mutually exclusive with CustomID.
|
||||||
URL string `json:"url,omitempty"`
|
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.
|
// Type is a method to get the type of a component.
|
||||||
func (b Button) Type() ComponentType {
|
func (Button) Type() ComponentType {
|
||||||
return ButtonComponent
|
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 (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/bwmarrin/discordgo"
|
"github.com/bwmarrin/discordgo"
|
||||||
)
|
)
|
||||||
|
@ -28,34 +31,205 @@ func init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
// Important note: call every command in order it's placed in the example.
|
||||||
s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
|
|
||||||
log.Println("Bot is up!")
|
var (
|
||||||
})
|
componentsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
|
||||||
// Buttons are part of interactions, so we register InteractionCreate handler
|
"fd_no": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
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{
|
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
Data: &discordgo.InteractionResponseData{
|
Data: &discordgo.InteractionResponseData{
|
||||||
Content: "Are you satisfied with Buttons?",
|
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",
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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.
|
// Buttons and other components are specified in Components field.
|
||||||
Components: []discordgo.MessageComponent{
|
Components: []discordgo.MessageComponent{
|
||||||
// ActionRow is a container of all buttons within the same row.
|
// ActionRow is a container of all buttons within the same row.
|
||||||
discordgo.ActionsRow{
|
discordgo.ActionsRow{
|
||||||
Components: []discordgo.MessageComponent{
|
Components: []discordgo.MessageComponent{
|
||||||
discordgo.Button{
|
discordgo.Button{
|
||||||
|
// Label is what the user will see on the button.
|
||||||
Label: "Yes",
|
Label: "Yes",
|
||||||
|
// Style provides coloring of the button. There are not so many styles tho.
|
||||||
Style: discordgo.SuccessButton,
|
Style: discordgo.SuccessButton,
|
||||||
|
// Disabled allows bot to disable some buttons for users.
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
CustomID: "yes_btn",
|
// CustomID is a thing telling Discord which data to send when this button will be pressed.
|
||||||
|
CustomID: "fd_yes",
|
||||||
},
|
},
|
||||||
discordgo.Button{
|
discordgo.Button{
|
||||||
Label: "No",
|
Label: "No",
|
||||||
Style: discordgo.DangerButton,
|
Style: discordgo.DangerButton,
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
CustomID: "no_btn",
|
CustomID: "fd_no",
|
||||||
},
|
},
|
||||||
discordgo.Button{
|
discordgo.Button{
|
||||||
Label: "I don't know",
|
Label: "I don't know",
|
||||||
|
@ -63,7 +237,7 @@ func main() {
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
// Link buttons don't require CustomID and do not trigger the gateway/HTTP event
|
// Link buttons don't require CustomID and do not trigger the gateway/HTTP event
|
||||||
URL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
URL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||||
Emoji: discordgo.ButtonEmoji{
|
Emoji: discordgo.ComponentEmoji{
|
||||||
Name: "🤷",
|
Name: "🤷",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -86,52 +260,177 @@ func main() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
return
|
"selects": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||||
}
|
var response *discordgo.InteractionResponse
|
||||||
// Type for button press will be always InteractionButton (3)
|
switch i.ApplicationCommandData().Options[0].Name {
|
||||||
if i.Type != discordgo.InteractionMessageComponent {
|
case "single":
|
||||||
return
|
response = &discordgo.InteractionResponse{
|
||||||
}
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||||
|
|
||||||
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{
|
Data: &discordgo.InteractionResponseData{
|
||||||
Content: content,
|
Content: "Now let's take a look on selects. This is single item select menu.",
|
||||||
|
Flags: 1 << 6,
|
||||||
Components: []discordgo.MessageComponent{
|
Components: []discordgo.MessageComponent{
|
||||||
discordgo.ActionsRow{
|
discordgo.ActionsRow{
|
||||||
Components: []discordgo.MessageComponent{
|
Components: []discordgo.MessageComponent{
|
||||||
discordgo.Button{
|
discordgo.SelectMenu{
|
||||||
Label: "Our sponsor",
|
// Select menu, as other components, must have a customID, so we set it to this value.
|
||||||
Style: discordgo.LinkButton,
|
CustomID: "select",
|
||||||
Disabled: false,
|
Placeholder: "Choose your favorite programming language 👇",
|
||||||
URL: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
Options: []discordgo.SelectMenuOption{
|
||||||
Emoji: discordgo.ButtonEmoji{
|
{
|
||||||
Name: "💠",
|
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{
|
_, err := s.ApplicationCommandCreate(*AppID, *GuildID, &discordgo.ApplicationCommand{
|
||||||
Name: "feedback",
|
Name: "buttons",
|
||||||
Description: "Give your feedback",
|
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 {
|
if err != nil {
|
||||||
|
|
|
@ -290,7 +290,7 @@ var (
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.AfterFunc(time.Second*5, func() {
|
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. " +
|
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 " +
|
"But you still don't know how to delete them... so... wait 10 seconds and this " +
|
||||||
"message will be deleted.",
|
"message will be deleted.",
|
||||||
|
|
|
@ -219,6 +219,9 @@ func (ApplicationCommandInteractionData) Type() InteractionType {
|
||||||
type MessageComponentInteractionData struct {
|
type MessageComponentInteractionData struct {
|
||||||
CustomID string `json:"custom_id"`
|
CustomID string `json:"custom_id"`
|
||||||
ComponentType ComponentType `json:"component_type"`
|
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.
|
// Type returns the type of interaction data.
|
||||||
|
@ -379,7 +382,6 @@ type InteractionResponseData struct {
|
||||||
Embeds []*MessageEmbed `json:"embeds,omitempty"`
|
Embeds []*MessageEmbed `json:"embeds,omitempty"`
|
||||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||||
|
|
||||||
// NOTE: Undocumented feature, be careful with it.
|
|
||||||
Flags uint64 `json:"flags,omitempty"`
|
Flags uint64 `json:"flags,omitempty"`
|
||||||
|
|
||||||
Files []*File `json:"-"`
|
Files []*File `json:"-"`
|
||||||
|
|
29
restapi.go
29
restapi.go
|
@ -2165,29 +2165,40 @@ func (s *Session) WebhookMessage(webhookID, token, messageID string) (message *M
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebhookMessageEdit edits a webhook message.
|
// WebhookMessageEdit edits a webhook message and returns a new one.
|
||||||
// webhookID : The ID of a webhook
|
// webhookID : The ID of a webhook
|
||||||
// token : The auth token for the webhook
|
// token : The auth token for the webhook
|
||||||
// messageID : The ID of message to edit
|
// 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)
|
uri := EndpointWebhookMessage(webhookID, token, messageID)
|
||||||
|
|
||||||
|
var response []byte
|
||||||
if len(data.Files) > 0 {
|
if len(data.Files) > 0 {
|
||||||
contentType, body, err := MultipartBodyWithJSON(data, data.Files)
|
contentType, body, err := MultipartBodyWithJSON(data, data.Files)
|
||||||
if err != nil {
|
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)
|
||||||
} else {
|
if err != nil {
|
||||||
_, err = s.RequestWithBucketID("PATCH", uri, data, EndpointWebhookToken("", ""))
|
return nil, err
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
response, err = s.RequestWithBucketID("PATCH", uri, data, EndpointWebhookToken("", ""))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unmarshal(response, &st)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebhookMessageDelete deletes a webhook message.
|
// WebhookMessageDelete deletes a webhook message.
|
||||||
// webhookID : The ID of a webhook
|
// webhookID : The ID of a webhook
|
||||||
// token : The auth token for the 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) {
|
func (s *Session) WebhookMessageDelete(webhookID, token, messageID string) (err error) {
|
||||||
uri := EndpointWebhookMessage(webhookID, token, messageID)
|
uri := EndpointWebhookMessage(webhookID, token, messageID)
|
||||||
|
|
||||||
|
@ -2512,7 +2523,7 @@ func (s *Session) InteractionResponse(appID string, interaction *Interaction) (*
|
||||||
// appID : The application ID.
|
// appID : The application ID.
|
||||||
// interaction : Interaction instance.
|
// interaction : Interaction instance.
|
||||||
// newresp : Updated response message data.
|
// 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)
|
return s.WebhookMessageEdit(appID, interaction.Token, "@original", newresp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2541,7 +2552,7 @@ func (s *Session) FollowupMessageCreate(appID string, interaction *Interaction,
|
||||||
// interaction : Interaction instance.
|
// interaction : Interaction instance.
|
||||||
// messageID : The followup message ID.
|
// messageID : The followup message ID.
|
||||||
// data : Data to update the message
|
// 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)
|
return s.WebhookMessageEdit(appID, interaction.Token, messageID, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ type WebhookParams struct {
|
||||||
Components []MessageComponent `json:"components"`
|
Components []MessageComponent `json:"components"`
|
||||||
Embeds []*MessageEmbed `json:"embeds,omitempty"`
|
Embeds []*MessageEmbed `json:"embeds,omitempty"`
|
||||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,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.
|
// WebhookEdit stores data for editing of a webhook message.
|
||||||
|
|
Loading…
Reference in a new issue