Slash commands options auto completion (#1014)
* feat(interactions): options autocompletion * fix(examples/autocomplete): typo in comment Replaced "returining" with "returning"
This commit is contained in:
parent
007bf76790
commit
fd6228c0d5
2 changed files with 276 additions and 11 deletions
255
examples/autocomplete/main.go
Normal file
255
examples/autocomplete/main.go
Normal file
|
@ -0,0 +1,255 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
// Bot parameters
|
||||
var (
|
||||
GuildID = flag.String("guild", "", "Test guild ID. If not passed - bot registers commands globally")
|
||||
BotToken = flag.String("token", "", "Bot access token")
|
||||
RemoveCommands = flag.Bool("rmcmd", true, "Remove all commands after shutdowning or not")
|
||||
)
|
||||
|
||||
var s *discordgo.Session
|
||||
|
||||
func init() { flag.Parse() }
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
s, err = discordgo.New("Bot " + *BotToken)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid bot parameters: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
commands = []*discordgo.ApplicationCommand{
|
||||
{
|
||||
Name: "single-autocomplete",
|
||||
Description: "Showcase of single autocomplete option",
|
||||
Type: discordgo.ChatApplicationCommand,
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Name: "autocomplete-option",
|
||||
Description: "Autocomplete option",
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Required: true,
|
||||
Autocomplete: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "multi-autocomplete",
|
||||
Description: "Showcase of multiple autocomplete option",
|
||||
Type: discordgo.ChatApplicationCommand,
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Name: "autocomplete-option-1",
|
||||
Description: "Autocomplete option 1",
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Required: true,
|
||||
Autocomplete: true,
|
||||
},
|
||||
{
|
||||
Name: "autocomplete-option-2",
|
||||
Description: "Autocomplete option 2",
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Required: true,
|
||||
Autocomplete: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
|
||||
"single-autocomplete": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
switch i.Type {
|
||||
case discordgo.InteractionApplicationCommand:
|
||||
data := i.ApplicationCommandData()
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf(
|
||||
"You picked %q autocompletion",
|
||||
// Autocompleted options do not affect usual flow of handling application command. They are ordinary options at this stage
|
||||
data.Options[0].StringValue(),
|
||||
),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Autocomplete options introduce a new interaction type (8) for returning custom autocomplete results.
|
||||
case discordgo.InteractionApplicationCommandAutocomplete:
|
||||
data := i.ApplicationCommandData()
|
||||
choices := []*discordgo.ApplicationCommandOptionChoice{
|
||||
{
|
||||
Name: "Autocomplete",
|
||||
Value: "autocomplete",
|
||||
},
|
||||
{
|
||||
Name: "Autocomplete is best!",
|
||||
Value: "autocomplete_is_best",
|
||||
},
|
||||
{
|
||||
Name: "Choice 3",
|
||||
Value: "choice3",
|
||||
},
|
||||
{
|
||||
Name: "Choice 4",
|
||||
Value: "choice4",
|
||||
},
|
||||
{
|
||||
Name: "Choice 5",
|
||||
Value: "choice5",
|
||||
},
|
||||
// And so on, up to 25 choices
|
||||
}
|
||||
|
||||
if data.Options[0].StringValue() != "" {
|
||||
choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
|
||||
Name: data.Options[0].StringValue(), // To get user input you just get value of the autocomplete option.
|
||||
Value: "choice_custom",
|
||||
})
|
||||
}
|
||||
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionApplicationCommandAutocompleteResult,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Choices: choices, // This is basically the whole purpose of autocomplete interaction - return custom options to the user.
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
"multi-autocomplete": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
switch i.Type {
|
||||
case discordgo.InteractionApplicationCommand:
|
||||
data := i.ApplicationCommandData()
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf(
|
||||
"Option 1: %s\nOption 2: %s",
|
||||
data.Options[0].StringValue(),
|
||||
data.Options[1].StringValue(),
|
||||
),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case discordgo.InteractionApplicationCommandAutocomplete:
|
||||
data := i.ApplicationCommandData()
|
||||
var choices []*discordgo.ApplicationCommandOptionChoice
|
||||
switch {
|
||||
// In this case there are multiple autocomplete options. The Focused field shows which option user is focused on.
|
||||
case data.Options[0].Focused:
|
||||
choices = []*discordgo.ApplicationCommandOptionChoice{
|
||||
{
|
||||
Name: "Autocomplete 4 first option",
|
||||
Value: "autocomplete_default",
|
||||
},
|
||||
{
|
||||
Name: "Choice 3",
|
||||
Value: "choice3",
|
||||
},
|
||||
{
|
||||
Name: "Choice 4",
|
||||
Value: "choice4",
|
||||
},
|
||||
{
|
||||
Name: "Choice 5",
|
||||
Value: "choice5",
|
||||
},
|
||||
}
|
||||
if data.Options[0].StringValue() != "" {
|
||||
choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
|
||||
Name: data.Options[0].StringValue(),
|
||||
Value: "choice_custom",
|
||||
})
|
||||
}
|
||||
|
||||
case data.Options[1].Focused:
|
||||
choices = []*discordgo.ApplicationCommandOptionChoice{
|
||||
{
|
||||
Name: "Autocomplete 4 second option",
|
||||
Value: "autocomplete_1_default",
|
||||
},
|
||||
{
|
||||
Name: "Choice 3.1",
|
||||
Value: "choice3_1",
|
||||
},
|
||||
{
|
||||
Name: "Choice 4.1",
|
||||
Value: "choice4_1",
|
||||
},
|
||||
{
|
||||
Name: "Choice 5.1",
|
||||
Value: "choice5_1",
|
||||
},
|
||||
}
|
||||
if data.Options[1].StringValue() != "" {
|
||||
choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
|
||||
Name: data.Options[1].StringValue(),
|
||||
Value: "choice_custom_2",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionApplicationCommandAutocompleteResult,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Choices: choices,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func main() {
|
||||
s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { log.Println("Bot is up!") })
|
||||
s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
|
||||
h(s, i)
|
||||
}
|
||||
})
|
||||
err := s.Open()
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot open the session: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
createdCommands, err := s.ApplicationCommandBulkOverwrite(s.State.User.ID, *GuildID, commands)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot register commands: %v", err)
|
||||
}
|
||||
|
||||
stop := make(chan os.Signal)
|
||||
signal.Notify(stop, os.Interrupt) //nolint: staticcheck
|
||||
<-stop
|
||||
log.Println("Gracefully shutting down")
|
||||
|
||||
if *RemoveCommands {
|
||||
for _, cmd := range createdCommands {
|
||||
err := s.ApplicationCommandDelete(s.State.User.ID, *GuildID, cmd.ID)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot delete %q command: %v", cmd.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -89,10 +89,13 @@ type ApplicationCommandOption struct {
|
|||
// NOTE: This feature was on the API, but at some point developers decided to remove it.
|
||||
// So I commented it, until it will be officially on the docs.
|
||||
// Default bool `json:"default"`
|
||||
Required bool `json:"required"`
|
||||
ChannelTypes []ChannelType `json:"channel_types"`
|
||||
Required bool `json:"required"`
|
||||
Options []*ApplicationCommandOption `json:"options"`
|
||||
|
||||
// NOTE: mutually exclusive with Choices.
|
||||
Autocomplete bool `json:"autocomplete"`
|
||||
Choices []*ApplicationCommandOptionChoice `json:"choices"`
|
||||
Options []*ApplicationCommandOption `json:"options"`
|
||||
ChannelTypes []ChannelType `json:"channel_types"`
|
||||
}
|
||||
|
||||
// ApplicationCommandOptionChoice represents a slash command option choice.
|
||||
|
@ -106,9 +109,10 @@ type InteractionType uint8
|
|||
|
||||
// Interaction types
|
||||
const (
|
||||
InteractionPing InteractionType = 1
|
||||
InteractionApplicationCommand InteractionType = 2
|
||||
InteractionMessageComponent InteractionType = 3
|
||||
InteractionPing InteractionType = 1
|
||||
InteractionApplicationCommand InteractionType = 2
|
||||
InteractionMessageComponent InteractionType = 3
|
||||
InteractionApplicationCommandAutocomplete InteractionType = 4
|
||||
)
|
||||
|
||||
func (t InteractionType) String() string {
|
||||
|
@ -168,7 +172,7 @@ func (i *Interaction) UnmarshalJSON(raw []byte) error {
|
|||
*i = Interaction(tmp.interaction)
|
||||
|
||||
switch tmp.Type {
|
||||
case InteractionApplicationCommand:
|
||||
case InteractionApplicationCommand, InteractionApplicationCommandAutocomplete:
|
||||
v := ApplicationCommandInteractionData{}
|
||||
err = json.Unmarshal(tmp.Data, &v)
|
||||
if err != nil {
|
||||
|
@ -198,7 +202,7 @@ func (i Interaction) MessageComponentData() (data MessageComponentInteractionDat
|
|||
// ApplicationCommandData is helper function to assert the inner InteractionData to ApplicationCommandInteractionData.
|
||||
// Make sure to check that the Type of the interaction is InteractionApplicationCommand before calling.
|
||||
func (i Interaction) ApplicationCommandData() (data ApplicationCommandInteractionData) {
|
||||
if i.Type != InteractionApplicationCommand {
|
||||
if i.Type != InteractionApplicationCommand && i.Type != InteractionApplicationCommandAutocomplete {
|
||||
panic("ApplicationCommandData called on interaction of type " + i.Type.String())
|
||||
}
|
||||
return i.Data.(ApplicationCommandInteractionData)
|
||||
|
@ -259,6 +263,9 @@ type ApplicationCommandInteractionDataOption struct {
|
|||
// NOTE: Contains the value specified by Type.
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
Options []*ApplicationCommandInteractionDataOption `json:"options,omitempty"`
|
||||
|
||||
// NOTE: autocomplete interaction only.
|
||||
Focused bool `json:"focused,omitempty"`
|
||||
}
|
||||
|
||||
// IntValue is a utility function for casting option value to integer
|
||||
|
@ -389,6 +396,8 @@ const (
|
|||
InteractionResponseDeferredMessageUpdate InteractionResponseType = 6
|
||||
// InteractionResponseUpdateMessage is for updating the message to which message component was attached.
|
||||
InteractionResponseUpdateMessage InteractionResponseType = 7
|
||||
// InteractionApplicationCommandAutocompleteResult shows autocompletion results. Autocomplete interaction only.
|
||||
InteractionApplicationCommandAutocompleteResult InteractionResponseType = 8
|
||||
)
|
||||
|
||||
// InteractionResponse represents a response for an interaction event.
|
||||
|
@ -404,10 +413,11 @@ type InteractionResponseData struct {
|
|||
Components []MessageComponent `json:"components"`
|
||||
Embeds []*MessageEmbed `json:"embeds,omitempty"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
Flags uint64 `json:"flags,omitempty"`
|
||||
Files []*File `json:"-"`
|
||||
|
||||
Flags uint64 `json:"flags,omitempty"`
|
||||
|
||||
Files []*File `json:"-"`
|
||||
// NOTE: autocomplete interaction only.
|
||||
Choices []*ApplicationCommandOptionChoice `json:"choices,omitempty"`
|
||||
}
|
||||
|
||||
// VerifyInteraction implements message verification of the discord interactions api
|
||||
|
|
Loading…
Reference in a new issue