Add Command Functionality to Discord Package & Implement Feedback Webhooks (!3)
Co-authored-by: Layla <layla@layla.gg> Reviewed-on: https://gitea.sumulayla.synology.me/layla/birdbot/pulls/3
This commit is contained in:
		
							
								
								
									
										112
									
								
								discord/command.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								discord/command.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
			
		||||
package discord
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
 | 
			
		||||
	"github.com/bwmarrin/discordgo"
 | 
			
		||||
	"github.com/yeslayla/birdbot/common"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CommandConfiguration struct {
 | 
			
		||||
	Description       string
 | 
			
		||||
	EphemeralResponse bool
 | 
			
		||||
	Options           map[string]CommandOption
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CommandOption struct {
 | 
			
		||||
	Description string
 | 
			
		||||
	Type        CommandOptionType
 | 
			
		||||
	Required    bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CommandOptionType uint64
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	CommandTypeString CommandOptionType = CommandOptionType(discordgo.ApplicationCommandOptionString)
 | 
			
		||||
	CommandTypeInt    CommandOptionType = CommandOptionType(discordgo.ApplicationCommandOptionInteger)
 | 
			
		||||
	CommandTypeBool   CommandOptionType = CommandOptionType(discordgo.ApplicationCommandOptionBoolean)
 | 
			
		||||
	CommandTypeFloat  CommandOptionType = CommandOptionType(discordgo.ApplicationCommandOptionNumber)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RegisterCommand creates an new command that can be used to interact with bird bot
 | 
			
		||||
func (discord *Discord) RegisterCommand(name string, config CommandConfiguration, handler func(common.User, map[string]any) string) {
 | 
			
		||||
	command := &discordgo.ApplicationCommand{
 | 
			
		||||
		Name:        name,
 | 
			
		||||
		Description: config.Description,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Convert options to discordgo objects
 | 
			
		||||
	command.Options = make([]*discordgo.ApplicationCommandOption, len(config.Options))
 | 
			
		||||
	index := 0
 | 
			
		||||
	for name, option := range config.Options {
 | 
			
		||||
		command.Options[index] = &discordgo.ApplicationCommandOption{
 | 
			
		||||
			Name:        name,
 | 
			
		||||
			Description: option.Description,
 | 
			
		||||
			Required:    option.Required,
 | 
			
		||||
			Type:        discordgo.ApplicationCommandOptionType(option.Type),
 | 
			
		||||
		}
 | 
			
		||||
		index++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Register handler
 | 
			
		||||
	discord.commandHandlers[name] = func(session *discordgo.Session, r *discordgo.InteractionCreate) {
 | 
			
		||||
		if r.Interaction.Type != discordgo.InteractionApplicationCommand {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cmdOptions := r.ApplicationCommandData().Options
 | 
			
		||||
 | 
			
		||||
		// Parse option types
 | 
			
		||||
		optionsMap := make(map[string]any, len(cmdOptions))
 | 
			
		||||
		for _, opt := range cmdOptions {
 | 
			
		||||
			switch config.Options[opt.Name].Type {
 | 
			
		||||
			case CommandTypeString:
 | 
			
		||||
				optionsMap[opt.Name] = opt.StringValue()
 | 
			
		||||
			case CommandTypeInt:
 | 
			
		||||
				optionsMap[opt.Name] = opt.IntValue()
 | 
			
		||||
			case CommandTypeBool:
 | 
			
		||||
				optionsMap[opt.Name] = opt.BoolValue()
 | 
			
		||||
			case CommandTypeFloat:
 | 
			
		||||
				optionsMap[opt.Name] = opt.FloatValue()
 | 
			
		||||
			default:
 | 
			
		||||
				optionsMap[opt.Name] = opt.Value
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		result := handler(NewUser(r.Member.User), optionsMap)
 | 
			
		||||
 | 
			
		||||
		if result != "" {
 | 
			
		||||
			// Handle response
 | 
			
		||||
			responseData := &discordgo.InteractionResponseData{
 | 
			
		||||
				Content: result,
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if config.EphemeralResponse {
 | 
			
		||||
				responseData.Flags = discordgo.MessageFlagsEphemeral
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			session.InteractionRespond(r.Interaction, &discordgo.InteractionResponse{
 | 
			
		||||
				Type: discordgo.InteractionResponseChannelMessageWithSource,
 | 
			
		||||
				Data: responseData,
 | 
			
		||||
			})
 | 
			
		||||
		} else {
 | 
			
		||||
			log.Printf("Command '%s' did not return a response: %v", name, optionsMap)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cmd, err := discord.session.ApplicationCommandCreate(discord.applicationID, discord.guildID, command)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Cannot create command '%s': %v", name, err)
 | 
			
		||||
	}
 | 
			
		||||
	discord.commands[name] = cmd
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClearCommands deregisters all commands from the discord API
 | 
			
		||||
func (discord *Discord) ClearCommands() {
 | 
			
		||||
	for _, v := range discord.commands {
 | 
			
		||||
		err := discord.session.ApplicationCommandDelete(discord.session.State.User.ID, discord.guildID, v.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("Cannot delete command '%s': %v", v.Name, err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -24,6 +24,10 @@ func (discord *Discord) NewButton(id string, label string) *Button {
 | 
			
		||||
// OnClick registers an event when the button is clicked
 | 
			
		||||
func (button *Button) OnClick(action func(user common.User)) {
 | 
			
		||||
	button.discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.InteractionCreate) {
 | 
			
		||||
		if r.Interaction.Type != discordgo.InteractionMessageComponent {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if r.MessageComponentData().CustomID == button.ID {
 | 
			
		||||
 | 
			
		||||
			action(NewUser(r.Member.User))
 | 
			
		||||
 | 
			
		||||
@ -14,25 +14,32 @@ import (
 | 
			
		||||
type Discord struct {
 | 
			
		||||
	mock.Mock
 | 
			
		||||
 | 
			
		||||
	guildID string
 | 
			
		||||
	session *discordgo.Session
 | 
			
		||||
	guildID       string
 | 
			
		||||
	applicationID string
 | 
			
		||||
	session       *discordgo.Session
 | 
			
		||||
 | 
			
		||||
	commands        map[string]*discordgo.ApplicationCommand
 | 
			
		||||
	commandHandlers map[string]func(session *discordgo.Session, i *discordgo.InteractionCreate)
 | 
			
		||||
 | 
			
		||||
	// Signal for shutdown
 | 
			
		||||
	stop chan os.Signal
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New creates a new Discord session
 | 
			
		||||
func New(guildID string, token string) *Discord {
 | 
			
		||||
func New(applicationID string, guildID string, token string) *Discord {
 | 
			
		||||
 | 
			
		||||
	// Create Discord Session
 | 
			
		||||
	session, err := discordgo.New(fmt.Sprint("Bot ", token))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Failed to create Discord session: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	session.ShouldReconnectOnError = true
 | 
			
		||||
	return &Discord{
 | 
			
		||||
		session: session,
 | 
			
		||||
		guildID: guildID,
 | 
			
		||||
		session:         session,
 | 
			
		||||
		applicationID:   applicationID,
 | 
			
		||||
		guildID:         guildID,
 | 
			
		||||
		commands:        make(map[string]*discordgo.ApplicationCommand),
 | 
			
		||||
		commandHandlers: make(map[string]func(*discordgo.Session, *discordgo.InteractionCreate)),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -44,10 +51,24 @@ func (discord *Discord) Run() error {
 | 
			
		||||
	}
 | 
			
		||||
	defer discord.session.Close()
 | 
			
		||||
 | 
			
		||||
	// Register command handler
 | 
			
		||||
	discord.session.AddHandler(func(session *discordgo.Session, i *discordgo.InteractionCreate) {
 | 
			
		||||
		if i.GuildID != discord.guildID {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if handler, ok := discord.commandHandlers[i.ApplicationCommandData().Name]; ok {
 | 
			
		||||
			handler(session, i)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Keep alive
 | 
			
		||||
	discord.stop = make(chan os.Signal, 1)
 | 
			
		||||
	signal.Notify(discord.stop, os.Interrupt)
 | 
			
		||||
	<-discord.stop
 | 
			
		||||
 | 
			
		||||
	discord.ClearCommands()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,8 @@ func NewUser(user *discordgo.User) common.User {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return common.User{
 | 
			
		||||
		ID: user.ID,
 | 
			
		||||
		DisplayName: user.Username,
 | 
			
		||||
		ID:          user.ID,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user