Add Command Functionality to Discord Package & Implement Feedback Webhooks
This commit is contained in:
		@ -46,6 +46,9 @@ func (app *Bot) Initialize(cfg *core.Config) error {
 | 
				
			|||||||
	if app.guildID == "" {
 | 
						if app.guildID == "" {
 | 
				
			||||||
		return fmt.Errorf("discord Guild ID is not set")
 | 
							return fmt.Errorf("discord Guild ID is not set")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if cfg.Discord.ApplicationID == "" {
 | 
				
			||||||
 | 
							return fmt.Errorf("discord Application ID is not set")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if cfg.Mastodon.ClientID != "" && cfg.Mastodon.ClientSecret != "" &&
 | 
						if cfg.Mastodon.ClientID != "" && cfg.Mastodon.ClientSecret != "" &&
 | 
				
			||||||
		cfg.Mastodon.Username != "" && cfg.Mastodon.Password != "" &&
 | 
							cfg.Mastodon.Username != "" && cfg.Mastodon.Password != "" &&
 | 
				
			||||||
@ -54,7 +57,7 @@ func (app *Bot) Initialize(cfg *core.Config) error {
 | 
				
			|||||||
			cfg.Mastodon.Username, cfg.Mastodon.Password)
 | 
								cfg.Mastodon.Username, cfg.Mastodon.Password)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	app.Session = discord.New(app.guildID, cfg.Discord.Token)
 | 
						app.Session = discord.New(cfg.Discord.ApplicationID, app.guildID, cfg.Discord.Token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Register Event Handlers
 | 
						// Register Event Handlers
 | 
				
			||||||
	app.Session.OnReady(app.onReady)
 | 
						app.Session.OnReady(app.onReady)
 | 
				
			||||||
 | 
				
			|||||||
@ -6,12 +6,14 @@ import "strings"
 | 
				
			|||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
	Discord  DiscordConfig  `yaml:"discord"`
 | 
						Discord  DiscordConfig  `yaml:"discord"`
 | 
				
			||||||
	Mastodon MastodonConfig `yaml:"mastodon"`
 | 
						Mastodon MastodonConfig `yaml:"mastodon"`
 | 
				
			||||||
 | 
						Feedback Feedback       `yaml:"feedback"`
 | 
				
			||||||
	Features Features       `yaml:"features"`
 | 
						Features Features       `yaml:"features"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DiscordConfig contains discord specific configuration
 | 
					// DiscordConfig contains discord specific configuration
 | 
				
			||||||
type DiscordConfig struct {
 | 
					type DiscordConfig struct {
 | 
				
			||||||
	Token         string `yaml:"token" env:"DISCORD_TOKEN"`
 | 
						Token         string `yaml:"token" env:"DISCORD_TOKEN"`
 | 
				
			||||||
 | 
						ApplicationID string `yaml:"application_id" env:"DISCORD_APPLICATION_ID"`
 | 
				
			||||||
	GuildID       string `yaml:"guild_id" env:"DISCORD_GUILD_ID"`
 | 
						GuildID       string `yaml:"guild_id" env:"DISCORD_GUILD_ID"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	EventCategory       string `yaml:"event_category" env:"DISCORD_EVENT_CATEGORY"`
 | 
						EventCategory       string `yaml:"event_category" env:"DISCORD_EVENT_CATEGORY"`
 | 
				
			||||||
@ -21,6 +23,14 @@ type DiscordConfig struct {
 | 
				
			|||||||
	RoleSelections []RoleSelectionConfig `yaml:"role_selection"`
 | 
						RoleSelections []RoleSelectionConfig `yaml:"role_selection"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Feedback struct {
 | 
				
			||||||
 | 
						WebhookURL  string `yaml:"url" env:"BIRD_FEEDBACK_URL"`
 | 
				
			||||||
 | 
						PayloadType string `yaml:"type" env:"BIRD_FEEDBACK_TYPE"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						SuccessMessage string `yaml:"success_message"`
 | 
				
			||||||
 | 
						FailureMessage string `yaml:"failure_message"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type RoleSelectionConfig struct {
 | 
					type RoleSelectionConfig struct {
 | 
				
			||||||
	Title       string `yaml:"title"`
 | 
						Title       string `yaml:"title"`
 | 
				
			||||||
	Description string `yaml:"description"`
 | 
						Description string `yaml:"description"`
 | 
				
			||||||
@ -49,6 +59,7 @@ type Features struct {
 | 
				
			|||||||
	AnnounceEvents      Feature `yaml:"announce_events" env:"BIRD_ANNOUNCE_EVENTS"`
 | 
						AnnounceEvents      Feature `yaml:"announce_events" env:"BIRD_ANNOUNCE_EVENTS"`
 | 
				
			||||||
	RecurringEvents     Feature `yaml:"recurring_events" env:"BIRD_RECURRING_EVENTS"`
 | 
						RecurringEvents     Feature `yaml:"recurring_events" env:"BIRD_RECURRING_EVENTS"`
 | 
				
			||||||
	RoleSelection       Feature `yaml:"role_selection" env:"BIRD_ROLE_SELECTION"`
 | 
						RoleSelection       Feature `yaml:"role_selection" env:"BIRD_ROLE_SELECTION"`
 | 
				
			||||||
 | 
						Feedback            Feature `yaml:"feedback" env:"BIRD_FEEDBACK"`
 | 
				
			||||||
	LoadGamePlugins     Feature `yaml:"load_game_plugins" env:"BIRD_LOAD_GAME_PLUGINS"`
 | 
						LoadGamePlugins     Feature `yaml:"load_game_plugins" env:"BIRD_LOAD_GAME_PLUGINS"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										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
 | 
					// OnClick registers an event when the button is clicked
 | 
				
			||||||
func (button *Button) OnClick(action func(user common.User)) {
 | 
					func (button *Button) OnClick(action func(user common.User)) {
 | 
				
			||||||
	button.discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.InteractionCreate) {
 | 
						button.discord.session.AddHandler(func(s *discordgo.Session, r *discordgo.InteractionCreate) {
 | 
				
			||||||
 | 
							if r.Interaction.Type != discordgo.InteractionMessageComponent {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if r.MessageComponentData().CustomID == button.ID {
 | 
							if r.MessageComponentData().CustomID == button.ID {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			action(NewUser(r.Member.User))
 | 
								action(NewUser(r.Member.User))
 | 
				
			||||||
 | 
				
			|||||||
@ -15,24 +15,31 @@ type Discord struct {
 | 
				
			|||||||
	mock.Mock
 | 
						mock.Mock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	guildID       string
 | 
						guildID       string
 | 
				
			||||||
 | 
						applicationID string
 | 
				
			||||||
	session       *discordgo.Session
 | 
						session       *discordgo.Session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						commands        map[string]*discordgo.ApplicationCommand
 | 
				
			||||||
 | 
						commandHandlers map[string]func(session *discordgo.Session, i *discordgo.InteractionCreate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Signal for shutdown
 | 
						// Signal for shutdown
 | 
				
			||||||
	stop chan os.Signal
 | 
						stop chan os.Signal
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// New creates a new Discord session
 | 
					// 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
 | 
						// Create Discord Session
 | 
				
			||||||
	session, err := discordgo.New(fmt.Sprint("Bot ", token))
 | 
						session, err := discordgo.New(fmt.Sprint("Bot ", token))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalf("Failed to create Discord session: %v", err)
 | 
							log.Fatalf("Failed to create Discord session: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						session.ShouldReconnectOnError = true
 | 
				
			||||||
	return &Discord{
 | 
						return &Discord{
 | 
				
			||||||
		session:         session,
 | 
							session:         session,
 | 
				
			||||||
 | 
							applicationID:   applicationID,
 | 
				
			||||||
		guildID:         guildID,
 | 
							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()
 | 
						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
 | 
						// Keep alive
 | 
				
			||||||
	discord.stop = make(chan os.Signal, 1)
 | 
						discord.stop = make(chan os.Signal, 1)
 | 
				
			||||||
	signal.Notify(discord.stop, os.Interrupt)
 | 
						signal.Notify(discord.stop, os.Interrupt)
 | 
				
			||||||
	<-discord.stop
 | 
						<-discord.stop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						discord.ClearCommands()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -18,6 +18,7 @@ func NewUser(user *discordgo.User) common.User {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return common.User{
 | 
						return common.User{
 | 
				
			||||||
 | 
							DisplayName: user.Username,
 | 
				
			||||||
		ID:          user.ID,
 | 
							ID:          user.ID,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										20
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								go.mod
									
									
									
									
									
								
							@ -3,26 +3,26 @@ module github.com/yeslayla/birdbot
 | 
				
			|||||||
go 1.20
 | 
					go 1.20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/bwmarrin/discordgo v0.26.1
 | 
						github.com/bwmarrin/discordgo v0.27.1
 | 
				
			||||||
	github.com/ilyakaznacheev/cleanenv v1.4.0
 | 
						github.com/ilyakaznacheev/cleanenv v1.4.2
 | 
				
			||||||
	github.com/stretchr/testify v1.8.1
 | 
						github.com/mattn/go-mastodon v0.0.6
 | 
				
			||||||
 | 
						github.com/mattn/go-sqlite3 v1.14.17
 | 
				
			||||||
 | 
						github.com/rubenv/sql-migrate v1.4.0
 | 
				
			||||||
 | 
						github.com/stretchr/testify v1.8.4
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/BurntSushi/toml v1.2.1 // indirect
 | 
						github.com/BurntSushi/toml v1.3.2 // indirect
 | 
				
			||||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
	github.com/go-gorp/gorp/v3 v3.1.0 // indirect
 | 
						github.com/go-gorp/gorp/v3 v3.1.0 // indirect
 | 
				
			||||||
	github.com/gorilla/websocket v1.5.0 // indirect
 | 
						github.com/gorilla/websocket v1.5.0 // indirect
 | 
				
			||||||
	github.com/joho/godotenv v1.4.0 // indirect
 | 
						github.com/joho/godotenv v1.5.1 // indirect
 | 
				
			||||||
	github.com/magefile/mage v1.14.0 // indirect
 | 
						github.com/magefile/mage v1.14.0 // indirect
 | 
				
			||||||
	github.com/mattn/go-mastodon v0.0.5 // indirect
 | 
					 | 
				
			||||||
	github.com/mattn/go-sqlite3 v1.14.16 // indirect
 | 
					 | 
				
			||||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
	github.com/rubenv/sql-migrate v1.4.0 // indirect
 | 
					 | 
				
			||||||
	github.com/stretchr/objx v0.5.0 // indirect
 | 
						github.com/stretchr/objx v0.5.0 // indirect
 | 
				
			||||||
	github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
 | 
						github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.5.0 // indirect
 | 
						golang.org/x/crypto v0.10.0 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.4.0 // indirect
 | 
						golang.org/x/sys v0.9.0 // indirect
 | 
				
			||||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
	olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
 | 
						olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								go.sum
									
									
									
									
									
								
							@ -41,6 +41,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 | 
				
			|||||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
					github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
				
			||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 | 
					github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 | 
				
			||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
					github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
				
			||||||
 | 
					github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
 | 
				
			||||||
 | 
					github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
				
			||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
					github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 | 
				
			||||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 | 
					github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 | 
				
			||||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 | 
					github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 | 
				
			||||||
@ -63,6 +65,8 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
 | 
				
			|||||||
github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 | 
					github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM=
 | 
				
			||||||
github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE=
 | 
					github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE=
 | 
				
			||||||
github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
 | 
					github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
 | 
				
			||||||
 | 
					github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
 | 
				
			||||||
 | 
					github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
 | 
				
			||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
					github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 | 
				
			||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 | 
					github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 | 
				
			||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
					github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 | 
				
			||||||
@ -226,11 +230,15 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
 | 
				
			|||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
					github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 | 
				
			||||||
github.com/ilyakaznacheev/cleanenv v1.4.0 h1:Gvwxt6wAPUo9OOxyp5Xz9eqhLsAey4AtbCF5zevDnvs=
 | 
					github.com/ilyakaznacheev/cleanenv v1.4.0 h1:Gvwxt6wAPUo9OOxyp5Xz9eqhLsAey4AtbCF5zevDnvs=
 | 
				
			||||||
github.com/ilyakaznacheev/cleanenv v1.4.0/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
 | 
					github.com/ilyakaznacheev/cleanenv v1.4.0/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
 | 
				
			||||||
 | 
					github.com/ilyakaznacheev/cleanenv v1.4.2 h1:nRqiriLMAC7tz7GzjzUTBHfzdzw6SQ7XvTagkFqe/zU=
 | 
				
			||||||
 | 
					github.com/ilyakaznacheev/cleanenv v1.4.2/go.mod h1:i0owW+HDxeGKE0/JPREJOdSCPIyOnmh6C0xhWAkF/xA=
 | 
				
			||||||
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 | 
					github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
 | 
				
			||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 | 
					github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 | 
				
			||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
					github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
 | 
				
			||||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
 | 
					github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
 | 
				
			||||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
					github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
				
			||||||
 | 
					github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 | 
				
			||||||
 | 
					github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 | 
				
			||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 | 
					github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
 | 
				
			||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
					github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 | 
				
			||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 | 
					github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 | 
				
			||||||
@ -273,11 +281,15 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
 | 
				
			|||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
					github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
				
			||||||
github.com/mattn/go-mastodon v0.0.5 h1:P0e1/R2v3ho6kM7BUW0noQm8gAqHE0p8Gq1TMapIVAc=
 | 
					github.com/mattn/go-mastodon v0.0.5 h1:P0e1/R2v3ho6kM7BUW0noQm8gAqHE0p8Gq1TMapIVAc=
 | 
				
			||||||
github.com/mattn/go-mastodon v0.0.5/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8=
 | 
					github.com/mattn/go-mastodon v0.0.5/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8=
 | 
				
			||||||
 | 
					github.com/mattn/go-mastodon v0.0.6 h1:lqU1sOeeIapaDsDUL6udDZIzMb2Wqapo347VZlaOzf0=
 | 
				
			||||||
 | 
					github.com/mattn/go-mastodon v0.0.6/go.mod h1:cg7RFk2pcUfHZw/IvKe1FUzmlq5KnLFqs7eV2PHplV8=
 | 
				
			||||||
github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
 | 
					github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
 | 
				
			||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 | 
					github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 | 
					github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
 | 
					github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 | 
					github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 | 
				
			||||||
 | 
					github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
 | 
				
			||||||
 | 
					github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
 | 
				
			||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 | 
					github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 | 
				
			||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 | 
					github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
 | 
				
			||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 | 
					github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 | 
				
			||||||
@ -373,6 +385,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
 | 
				
			|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
					github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
				
			||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 | 
					github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 | 
				
			||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
					github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 | 
				
			||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 | 
					github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 | 
				
			||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 | 
					github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 | 
				
			||||||
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
 | 
					github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
 | 
				
			||||||
@ -421,6 +435,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 | 
				
			|||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 | 
					golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 | 
				
			||||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 | 
					golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
 | 
				
			||||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
 | 
					golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
 | 
				
			||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
					golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
				
			||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
					golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 | 
				
			||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 | 
					golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 | 
				
			||||||
@ -583,6 +599,8 @@ golang.org/x/sys v0.0.0-20221013171732-95e765b1cc43/go.mod h1:oPkhp1MJrh7nUepCBc
 | 
				
			|||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
 | 
					golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
 | 
				
			||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
					golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 | 
					golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								main.go
									
									
									
									
									
								
							@ -83,6 +83,15 @@ func main() {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if cfg.Features.Feedback.IsEnabled() {
 | 
				
			||||||
 | 
							loader.LoadComponent(modules.NewFeedbackWebhookComponent(cfg.Feedback.WebhookURL, modules.FeedbackWebhookConfiguration{
 | 
				
			||||||
 | 
								PayloadType: cfg.Feedback.PayloadType,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								SuccessMessage: cfg.Feedback.SuccessMessage,
 | 
				
			||||||
 | 
								FailureMessage: cfg.Feedback.FailureMessage,
 | 
				
			||||||
 | 
							}, bot.Session))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if _, err := os.Stat(PluginsDirectory); !os.IsNotExist(err) {
 | 
						if _, err := os.Stat(PluginsDirectory); !os.IsNotExist(err) {
 | 
				
			||||||
		components := app.LoadPlugins(PluginsDirectory)
 | 
							components := app.LoadPlugins(PluginsDirectory)
 | 
				
			||||||
		for _, comp := range components {
 | 
							for _, comp := range components {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										125
									
								
								modules/feedback_webhook.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								modules/feedback_webhook.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,125 @@
 | 
				
			|||||||
 | 
					package modules
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/yeslayla/birdbot/common"
 | 
				
			||||||
 | 
						"github.com/yeslayla/birdbot/discord"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type feedbackWebhookModule struct {
 | 
				
			||||||
 | 
						session        *discord.Discord
 | 
				
			||||||
 | 
						webhookURL     string
 | 
				
			||||||
 | 
						payloadType    string
 | 
				
			||||||
 | 
						successMessage string
 | 
				
			||||||
 | 
						failureMessage string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FeedbackWebhookConfiguration struct {
 | 
				
			||||||
 | 
						SuccessMessage string
 | 
				
			||||||
 | 
						FailureMessage string
 | 
				
			||||||
 | 
						PayloadType    string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewFeedbackWebhookComponent creates a new component
 | 
				
			||||||
 | 
					func NewFeedbackWebhookComponent(webhookURL string, config FeedbackWebhookConfiguration, session *discord.Discord) common.Module {
 | 
				
			||||||
 | 
						m := &feedbackWebhookModule{
 | 
				
			||||||
 | 
							session:        session,
 | 
				
			||||||
 | 
							webhookURL:     webhookURL,
 | 
				
			||||||
 | 
							payloadType:    "default",
 | 
				
			||||||
 | 
							successMessage: "Feedback recieved!",
 | 
				
			||||||
 | 
							failureMessage: "Failed to send feedback!",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if config.SuccessMessage != "" {
 | 
				
			||||||
 | 
							m.successMessage = config.SuccessMessage
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if config.FailureMessage != "" {
 | 
				
			||||||
 | 
							m.failureMessage = config.FailureMessage
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if config.PayloadType != "" {
 | 
				
			||||||
 | 
							m.payloadType = config.PayloadType
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return m
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (c *feedbackWebhookModule) Initialize(birdbot common.ModuleManager) error {
 | 
				
			||||||
 | 
						c.session.RegisterCommand("feedback", discord.CommandConfiguration{
 | 
				
			||||||
 | 
							Description:       "Sends a feedback message",
 | 
				
			||||||
 | 
							EphemeralResponse: true,
 | 
				
			||||||
 | 
							Options: map[string]discord.CommandOption{
 | 
				
			||||||
 | 
								"message": {
 | 
				
			||||||
 | 
									Description: "Content of what you'd like to communicate in your feedback.",
 | 
				
			||||||
 | 
									Type:        discord.CommandTypeString,
 | 
				
			||||||
 | 
									Required:    true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}, func(user common.User, args map[string]any) string {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							message, ok := args["message"]
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return "Missing content in command"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var data []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Supported payload types
 | 
				
			||||||
 | 
							switch c.payloadType {
 | 
				
			||||||
 | 
							case "discord":
 | 
				
			||||||
 | 
								data, _ = json.Marshal(map[string]any{
 | 
				
			||||||
 | 
									"content": fmt.Sprintf("%s: %s", user.DisplayName, message),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							case "slack":
 | 
				
			||||||
 | 
								data, _ = json.Marshal(map[string]any{
 | 
				
			||||||
 | 
									"text": fmt.Sprintf("%s: %s", user.DisplayName, message),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								data, _ = json.Marshal(map[string]any{
 | 
				
			||||||
 | 
									"message":  message,
 | 
				
			||||||
 | 
									"username": user.DisplayName,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							body := bytes.NewBuffer(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Send HTTP request
 | 
				
			||||||
 | 
							resp, err := http.Post(c.webhookURL, "application/json", body)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Printf("Failed to post feedback to url '%s': %s", c.webhookURL, err)
 | 
				
			||||||
 | 
								return c.failureMessage
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Validate response
 | 
				
			||||||
 | 
							if resp.Status[0] != '2' {
 | 
				
			||||||
 | 
								log.Printf("Webhook returned %v: %s", resp.Status, message)
 | 
				
			||||||
 | 
								return c.failureMessage
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Read body for any special response
 | 
				
			||||||
 | 
							response := map[any]any{}
 | 
				
			||||||
 | 
							responseBody, err := ioutil.ReadAll(resp.Body)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return c.successMessage
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err = json.Unmarshal(responseBody, &response); err != nil {
 | 
				
			||||||
 | 
								return c.successMessage
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if message, ok := response["message"]; ok {
 | 
				
			||||||
 | 
								v := fmt.Sprint(message)
 | 
				
			||||||
 | 
								if len(v) > 0 {
 | 
				
			||||||
 | 
									return v
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return c.successMessage
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user