package discordbot import ( "github.com/bwmarrin/discordgo" ) type SlashRouter struct { registeredCommands []*discordgo.ApplicationCommand commands []*discordgo.ApplicationCommand commandHandlers map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate) guildIDs []string existingCommands []Command } type SlashRouteOptions struct { Name string // match name that should trigger this route handler Description string // short description of this route Callback SlashHandler Options []*discordgo.ApplicationCommandOption // Array of application command options for the command } // Handler is the function signature required for a message route handler. type SlashHandler func(s *discordgo.Session, i *discordgo.InteractionCreate) func NewSlashRouter() *SlashRouter { r := &SlashRouter{} r.commandHandlers = make(map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate)) return r } func (r *SlashRouter) RegisterGuild(guildID string) { r.guildIDs = append(r.guildIDs, guildID) } type Command struct { applicationID string guildID string commandID string name string } func (r *SlashRouter) GetExistingCommand(appID, guildID, name string) (string, bool) { for _, cmd := range r.existingCommands { if cmd.applicationID == appID && cmd.guildID == guildID && cmd.name == name { return cmd.commandID, true } } return "", false } func (r *SlashRouter) LoadExistingCommands(s *discordgo.Session) error { logHandler.Info("Loading Existing Commands") for _, guildID := range r.guildIDs { logHandler.Infof("Getting applications for %s", guildID) cmds, err := s.ApplicationCommands(s.State.User.ID, guildID) if err != nil { r.existingCommands = nil return err } for _, cmd := range cmds { newCommand := Command{ applicationID: cmd.ApplicationID, guildID: cmd.GuildID, commandID: cmd.ID, name: cmd.Name, } r.existingCommands = append(r.existingCommands, newCommand) } } logHandler.Infof("Loaded %d commands", len(r.existingCommands)) return nil } func (r *SlashRouter) Register(name string, options SlashRouteOptions) error { if name == "" { return ErrRegisterRouteNameRequired } if options.Callback == nil { return ErrRegisterCallbackRequired } command := discordgo.ApplicationCommand{ Name: name, Description: options.Description, Options: options.Options, } r.commands = append(r.commands, &command) r.commandHandlers[name] = options.Callback return nil } func (r *SlashRouter) GenerateCommands(s *discordgo.Session) error { r.registeredCommands = make([]*discordgo.ApplicationCommand, len(r.commands)) s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { if h, ok := r.commandHandlers[i.ApplicationCommandData().Name]; ok { h(s, i) } }) for _, guildID := range r.guildIDs { for i, v := range r.commands { var cmd *discordgo.ApplicationCommand var err error if cmdID, exists := r.GetExistingCommand(s.State.User.ID, guildID, v.Name); exists { logHandler.Infof("Updating the existing command - %s", v.Name) cmd, err = s.ApplicationCommandEdit(s.State.User.ID, guildID, cmdID, v) } else { logHandler.Infof("Creating a new command - %s", v.Name) cmd, err = s.ApplicationCommandCreate(s.State.User.ID, guildID, v) } if err != nil { logHandler.Errorf("Cannot create '%v' command: %v", v.Name, err) return err } r.registeredCommands[i] = cmd } } return nil } func (r *SlashRouter) CleanCommands(s *discordgo.Session) error { for _, guildID := range r.guildIDs { for _, v := range r.registeredCommands { err := s.ApplicationCommandDelete(s.State.User.ID, guildID, v.ID) if err != nil { logHandler.Errorf("Cannot delete '%v' command: %v", v.Name, err) return err } } } return nil } // Create a Router // Added commands the bot will support func (bot *Bot) AddSlashCommands(guildID string, commands *[]discordgo.ApplicationCommand) error { err := bot.session.Open() if err != nil { return err } defer bot.session.Close() existingCommands, err := bot.session.ApplicationCommands(bot.session.State.User.ID, guildID) if err != nil { logHandler.WithError(err).Errorf("Failed to retrieve application commands from the server %s for user %s", guildID, bot.session.State.User.ID) return err } var find = func(name string, commands []*discordgo.ApplicationCommand) bool { for _, command := range commands { if command.Name == name { return true } } return false } for _, newCommand := range *commands { if exists := find(newCommand.Name, existingCommands); !exists { cmd, err := bot.session.ApplicationCommandCreate(bot.session.State.User.ID, guildID, &newCommand) if err != nil { logHandler.WithError(err).Errorf("Failed to create command %s", newCommand.Name) return err } logHandler.Infof("New Command for %s added, new command id is %s", newCommand.Name, cmd.ID) } } return nil } func (bot *Bot) RemoveSlashCommands(guildID string) error { err := bot.session.Open() if err != nil { return err } defer bot.session.Close() existingCommands, err := bot.session.ApplicationCommands(bot.session.State.User.ID, guildID) if err != nil { logHandler.WithError(err).Errorf("Failed to retrieve application commands from the server %s for user %s", guildID, bot.session.State.User.ID) return err } for _, existingCommand := range existingCommands { if err := bot.session.ApplicationCommandDelete(bot.session.State.User.ID, guildID, existingCommand.ID); err != nil { logHandler.WithError(err).Errorf("Failed to delete command %s", existingCommand.Name) return err } } return nil }