Context menus (#978)
* Interactions: context menus * Example for message context menus * Added flags to followups * Example for user context menus * Godoc fix * Rebase fix * Update message types to reflect new separations Co-authored-by: Carson Hoffman <c@rsonhoffman.com>
This commit is contained in:
parent
f7454d039f
commit
9d9602318a
3 changed files with 254 additions and 9 deletions
222
examples/context_menus/main.go
Normal file
222
examples/context_menus/main.go
Normal file
|
@ -0,0 +1,222 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
// Bot parameters
|
||||
var (
|
||||
GuildID = flag.String("guild", "", "Test guild ID")
|
||||
BotToken = flag.String("token", "", "Bot access token")
|
||||
AppID = flag.String("app", "", "Application ID")
|
||||
Cleanup = flag.Bool("cleanup", true, "Cleanup of commands")
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func searchLink(message, format, sep string) string {
|
||||
return fmt.Sprintf(format, strings.Join(
|
||||
strings.Split(
|
||||
message,
|
||||
" ",
|
||||
),
|
||||
sep,
|
||||
))
|
||||
}
|
||||
|
||||
var (
|
||||
commands = []discordgo.ApplicationCommand{
|
||||
{
|
||||
Name: "rickroll-em",
|
||||
Type: discordgo.UserApplicationCommand,
|
||||
},
|
||||
{
|
||||
Name: "google-it",
|
||||
Type: discordgo.MessageApplicationCommand,
|
||||
},
|
||||
{
|
||||
Name: "stackoverflow-it",
|
||||
Type: discordgo.MessageApplicationCommand,
|
||||
},
|
||||
{
|
||||
Name: "godoc-it",
|
||||
Type: discordgo.MessageApplicationCommand,
|
||||
},
|
||||
{
|
||||
Name: "discordjs-it",
|
||||
Type: discordgo.MessageApplicationCommand,
|
||||
},
|
||||
{
|
||||
Name: "discordpy-it",
|
||||
Type: discordgo.MessageApplicationCommand,
|
||||
},
|
||||
}
|
||||
commandsHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
|
||||
"rickroll-em": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: "Operation rickroll has begun",
|
||||
Flags: 1 << 6,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ch, err := s.UserChannelCreate(
|
||||
i.ApplicationCommandData().TargetID,
|
||||
)
|
||||
if err != nil {
|
||||
_, err = s.FollowupMessageCreate(*AppID, i.Interaction, true, &discordgo.WebhookParams{
|
||||
Content: fmt.Sprintf("Mission failed. Cannot send a message to this user: %q", err.Error()),
|
||||
Flags: 1 << 6,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
_, err = s.ChannelMessageSend(
|
||||
ch.ID,
|
||||
fmt.Sprintf("%s sent you this: https://youtu.be/dQw4w9WgXcQ", i.Member.Mention()),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
"google-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: searchLink(
|
||||
i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
|
||||
"https://google.com/search?q=%s", "+"),
|
||||
Flags: 1 << 6,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
"stackoverflow-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: searchLink(
|
||||
i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
|
||||
"https://stackoverflow.com/search?q=%s", "+"),
|
||||
Flags: 1 << 6,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
"godoc-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: searchLink(
|
||||
i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
|
||||
"https://pkg.go.dev/search?q=%s", "+"),
|
||||
Flags: 1 << 6,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
"discordjs-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: searchLink(
|
||||
i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
|
||||
"https://discord.js.org/#/docs/main/stable/search?query=%s", "+"),
|
||||
Flags: 1 << 6,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
},
|
||||
"discordpy-it": func(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &discordgo.InteractionResponseData{
|
||||
Content: searchLink(
|
||||
i.ApplicationCommandData().Resolved.Messages[i.ApplicationCommandData().TargetID].Content,
|
||||
"https://discordpy.readthedocs.io/en/stable/search.html?q=%s", "+"),
|
||||
Flags: 1 << 6,
|
||||
},
|
||||
})
|
||||
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 := commandsHandlers[i.ApplicationCommandData().Name]; ok {
|
||||
h(s, i)
|
||||
}
|
||||
})
|
||||
|
||||
cmdIDs := make(map[string]string, len(commands))
|
||||
|
||||
for _, cmd := range commands {
|
||||
rcmd, err := s.ApplicationCommandCreate(*AppID, *GuildID, &cmd)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot create slash command %q: %v", cmd.Name, err)
|
||||
}
|
||||
|
||||
cmdIDs[rcmd.ID] = rcmd.Name
|
||||
|
||||
}
|
||||
|
||||
err := s.Open()
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot open the session: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt)
|
||||
<-stop
|
||||
log.Println("Graceful shutdown")
|
||||
|
||||
if !*Cleanup {
|
||||
return
|
||||
}
|
||||
|
||||
for id, name := range cmdIDs {
|
||||
err := s.ApplicationCommandDelete(*AppID, *GuildID, id)
|
||||
if err != nil {
|
||||
log.Fatalf("Cannot delete slash command %q: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -15,14 +15,30 @@ import (
|
|||
// InteractionDeadline is the time allowed to respond to an interaction.
|
||||
const InteractionDeadline = time.Second * 3
|
||||
|
||||
// ApplicationCommandType represents the type of application command.
|
||||
type ApplicationCommandType uint8
|
||||
|
||||
// Application command types
|
||||
const (
|
||||
// ChatApplicationCommand is default command type. They are slash commands (i.e. called directly from the chat).
|
||||
ChatApplicationCommand ApplicationCommandType = 1
|
||||
// UserApplicationCommand adds command to user context menu.
|
||||
UserApplicationCommand ApplicationCommandType = 2
|
||||
// MessageApplicationCommand adds command to message context menu.
|
||||
MessageApplicationCommand ApplicationCommandType = 3
|
||||
)
|
||||
|
||||
// ApplicationCommand represents an application's slash command.
|
||||
type ApplicationCommand struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
ApplicationID string `json:"application_id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Options []*ApplicationCommandOption `json:"options"`
|
||||
ID string `json:"id,omitempty"`
|
||||
ApplicationID string `json:"application_id,omitempty"`
|
||||
Type ApplicationCommandType `json:"type,omitempty"`
|
||||
Name string `json:"name"`
|
||||
// NOTE: Chat commands only. Otherwise it mustn't be set.
|
||||
Description string `json:"description,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
// NOTE: Chat commands only. Otherwise it mustn't be set.
|
||||
Options []*ApplicationCommandOption `json:"options"`
|
||||
}
|
||||
|
||||
// ApplicationCommandOptionType indicates the type of a slash command's option.
|
||||
|
@ -197,10 +213,15 @@ type ApplicationCommandInteractionData struct {
|
|||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Resolved *ApplicationCommandInteractionDataResolved `json:"resolved"`
|
||||
Options []*ApplicationCommandInteractionDataOption `json:"options"`
|
||||
|
||||
// Slash command options
|
||||
Options []*ApplicationCommandInteractionDataOption `json:"options"`
|
||||
// Target (user/message) id on which context menu command was called.
|
||||
// The details are stored in Resolved according to command type.
|
||||
TargetID string `json:"target_id"`
|
||||
}
|
||||
|
||||
// ApplicationCommandInteractionDataResolved contains resolved data for command arguments.
|
||||
// ApplicationCommandInteractionDataResolved contains resolved data of command execution.
|
||||
// Partial Member objects are missing user, deaf and mute fields.
|
||||
// Partial Channel objects only have id, name, type and permissions fields.
|
||||
type ApplicationCommandInteractionDataResolved struct {
|
||||
|
@ -208,6 +229,7 @@ type ApplicationCommandInteractionDataResolved struct {
|
|||
Members map[string]*Member `json:"members"`
|
||||
Roles map[string]*Role `json:"roles"`
|
||||
Channels map[string]*Channel `json:"channels"`
|
||||
Messages map[string]*Message `json:"messages"`
|
||||
}
|
||||
|
||||
// Type returns the type of interaction data.
|
||||
|
|
|
@ -38,7 +38,8 @@ const (
|
|||
MessageTypeGuildDiscoveryDisqualified MessageType = 14
|
||||
MessageTypeGuildDiscoveryRequalified MessageType = 15
|
||||
MessageTypeReply MessageType = 19
|
||||
MessageTypeApplicationCommand MessageType = 20
|
||||
MessageTypeChatInputCommand MessageType = 20
|
||||
MessageTypeContextMenuCommand MessageType = 23
|
||||
)
|
||||
|
||||
// A Message stores all data related to a specific Discord message.
|
||||
|
|
Loading…
Reference in a new issue