diff --git a/main.go b/main.go index 5c4607f..a8ed369 100644 --- a/main.go +++ b/main.go @@ -100,7 +100,10 @@ func main() { log.Fatal().Err(lastPingErr).Msg("Reached ping limit while trying to connect") } + // Ping redis pong, err := kv.Ping(ctx).Result() + + // Failed; log error and wait 2 seconds if err != nil { lastPingErr = err log.Warn().Err(err).Int("pings", pingCount).Int("remaining", totalPings-pingCount).Msg("Cannot ping redis") @@ -155,8 +158,43 @@ func main() { }) log.Info().Array("commands", arr).Msg("Registering commands") + // In development, use test server, otherwise empty (global) for command registration + guildTarget := "" + if isDevelopment { + guildTarget = os.Getenv("BOT_TARGET_GUILD") + } + // Register commands - Register() + existingCommands, err := session.ApplicationCommands(session.State.User.ID, guildTarget) + if err != nil { + log.Fatal().Err(err).Msg("Cannot get existing commands") + } + + newCommands, err := session.ApplicationCommandBulkOverwrite(session.State.User.ID, guildTarget, commandDefinitions) + if err != nil { + log.Fatal().Err(err).Msg("Cannot register commands") + } + + // Compare existing commands with new commands + for _, newCommand := range newCommands { + existingCommand, found := lo.Find(existingCommands, func(cmd *discordgo.ApplicationCommand) bool { + return cmd.Name == newCommand.Name + }) + + // New command + if !found { + log.Info().Str("commandName", newCommand.Name).Msg("Registered new command") + continue + } + + // Compare versions + if newCommand.Version != existingCommand.Version { + log.Info().Str("commandName", newCommand.Name). + Str("oldVersion", existingCommand.Version).Str("newVersion", newCommand.Version). + Msg("Command Updated") + } + } + log.Info().Msg("Command registration complete") // terms, _ := GetTerms("", 1, 25) diff --git a/register.go b/register.go deleted file mode 100644 index 1cf0422..0000000 --- a/register.go +++ /dev/null @@ -1,139 +0,0 @@ -package main - -import ( - "errors" - "fmt" - - "github.com/bwmarrin/discordgo" - "github.com/cespare/xxhash/v2" - "github.com/cnf/structhash" - "github.com/redis/go-redis/v9" - log "github.com/rs/zerolog/log" -) - -// Register registers all commands, and only registers commands that have changed. -// It also unregisters old commands if they are known to exist and have changed. -// This is to lower the cost of registering commands while iterating on the bot. -// In the case of an ambiguous state, all current commands are deleted while all new commands are registered. -func Register() ([]*discordgo.ApplicationCommand, error) { - // Since we don't know what currentComands existed before, we can't unregister them if they're removed. - // We also can't tell the difference between a command that was removed and one that was newly added. - // Thus, if the number of currently active currentComands is not equal to the number of currentComands we're registering, we'll just re-register all of them. - currentComands, err := session.ApplicationCommands(session.State.User.ID, "") - if err != nil { - log.Panic().Err(err).Msg("Cannot get commands") - } - log.Debug().Int("registered", len(currentComands)).Int("definitions", len(commandDefinitions)).Msg("Commands Counted") - if len(currentComands) != len(commandDefinitions) { - log.Info().Int("registered", len(currentComands)).Int("definitions", len(commandDefinitions)).Msg("Number of registered commands does not match number of command definitions, registering all commands") - return SimpleRegister(currentComands) - } - - registeredCommands := make([]*discordgo.ApplicationCommand, len(commandDefinitions)) - for i, cmdDefinition := range commandDefinitions { - // Create a hash of the command definition - hash := xxhash.Sum64(structhash.Dump(cmdDefinition, 1)) - key := fmt.Sprintf("%s:command:%s:xxhash", environment, cmdDefinition.Name) - - // Get the stored hash - storedHash, err := kv.Get(ctx, key).Uint64() - if err != nil { - if err != redis.Nil { - log.Err(err).Msg("Cannot get command hash from redis") - } else { - log.Debug().Str("command", cmdDefinition.Name).Str("key", key).Msg("Command hash not found in redis") - } - } - - // If the hash is the same, skip registering the command - if hash == storedHash { - log.Debug().Str("command", cmdDefinition.Name).Str("key", key).Uint64("hash", hash).Msg("Command hash matches, skipping registration") - continue - } - log.Info().Str("command", cmdDefinition.Name).Str("key", key).Uint64("hash", hash).Uint64("storedHash", storedHash).Msg("Command hash mismatch, registering command") - - // Unregister the old command first (retrieve the ID from redis) - oldCommandId, err := kv.Get(ctx, fmt.Sprintf("%s:command:%s:id", environment, cmdDefinition.Name)).Result() - if err != nil { - if err != redis.Nil { - // Not really sure what to do here, something failed with redis. Best to just keep the old commad in place. - log.Err(err).Str("name", cmdDefinition.Name).Str("key", key).Msg("Cannot get old command ID from redis (skipping registration/deletion)") - continue - } else { - // It's an unlikely case, but if required, we could get the old command ID from discord to unregisstter it. - log.Debug().Str("name", cmdDefinition.Name).Str("key", key).Msg("Old command ID not found in redis") - } - } else { - err = session.ApplicationCommandDelete(session.State.User.ID, "", oldCommandId) - if err != nil { - // No panic - the command is probably still registered. - log.Err(err).Str("name", cmdDefinition.Name).Str("key", key).Msg("Cannot unregister old command") - continue - } else { - log.Info().Str("name", cmdDefinition.Name).Str("key", key).Msg("Unregistered old command") - } - } - - // Register the command - cmdInstance, err := session.ApplicationCommandCreate(session.State.User.ID, "", cmdDefinition) - if err != nil { - return nil, errors.Join(err, fmt.Errorf("cannot register command %s", cmdDefinition.Name)) - } - registeredCommands[i] = cmdInstance - log.Info().Str("name", cmdDefinition.Name).Str("key", key).Msg("Registered command") - - // Store the hash for the new registered command - err = kv.Set(ctx, key, hash, 0).Err() - if err != nil { - // No panic - the command is still registered, hash is only to prevent unnecessary registrations - log.Err(err).Str("name", cmdDefinition.Name).Str("key", key).Msg("Cannot set command hash in redis") - continue - } - - // Store the command ID to unregister later - err = kv.Set(ctx, fmt.Sprintf("%s:command:%s:id", environment, cmdDefinition.Name), cmdInstance.ID, 0).Err() - if err != nil { - // No - log.Err(err).Str("name", cmdDefinition.Name).Str("key", key).Msg("Cannot set command ID in redis") - } - } - - return registeredCommands, nil -} - -func SimpleRegister(currentCommands []*discordgo.ApplicationCommand) ([]*discordgo.ApplicationCommand, error) { - registeredCommands := make([]*discordgo.ApplicationCommand, len(commandDefinitions)) - - // Unregister all commands - for _, cmd := range currentCommands { - err := session.ApplicationCommandDelete(session.State.User.ID, "", cmd.ID) - if err != nil { - log.Err(err).Str("name", cmd.Name).Msg("Cannot unregister command") - } else { - log.Info().Str("name", cmd.Name).Msg("Unregistered command") - } - } - - // Register all commands - for i, cmdDefinition := range commandDefinitions { - // Create a hash of the command definition - hash := xxhash.Sum64(structhash.Dump(cmdDefinition, 1)) - key := fmt.Sprintf("%s:command:%s:xxhash", environment, cmdDefinition.Name) - - cmdInstance, err := session.ApplicationCommandCreate(session.State.User.ID, "", cmdDefinition) - if err != nil { - log.Panic().Err(err).Str("name", cmdDefinition.Name).Msg("Cannot register command") - } - registeredCommands[i] = cmdInstance - log.Info().Str("name", cmdDefinition.Name).Msg("Registered command") - - // Store the hash for the new registered command - err = kv.Set(ctx, key, hash, 0).Err() - if err != nil { - // No panic - the command is still registered, hash is only to prevent unnecessary registrations - log.Err(err).Str("name", cmdDefinition.Name).Str("key", key).Msg("Cannot set command hash in redis") - continue - } - } - return registeredCommands, nil -}