From db1e8121902d8272e074c96b63e49df79295217f Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Sun, 3 Aug 2025 20:05:22 +0100 Subject: [PATCH] discord: retry auth/command register, do latter in bulk re-use the auth retry options from the config for initial d.bot.Open and for registering commands. The latetr is now done with the BulkOverwrite method, since it seems to work now. For #427. --- common/common.go | 9 ++++++ discord.go | 71 ++++++++++++++++++++++++++++++++++++++++-------- main.go | 2 +- 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/common/common.go b/common/common.go index 6f33115..a56e1ff 100644 --- a/common/common.go +++ b/common/common.go @@ -11,6 +11,7 @@ import ( "net/http" "net/url" "strings" + "time" lm "github.com/hrfee/jfa-go/logmessages" ) @@ -155,3 +156,11 @@ func decodeResp(resp *http.Response) (string, error) { } return buf.String(), nil } + +// MustAuthenticateOptions is used to control the behaviour of the MustAuthenticate-like methods. +type MustAuthenticateOptions struct { + RetryCount int // Number of Retries before failure. + RetryGap time.Duration // Duration to wait between tries. + LogFailures bool // Whether or not to print failures to the log. + Counter int // The current retry count. +} diff --git a/discord.go b/discord.go index af0d52e..5bf60d3 100644 --- a/discord.go +++ b/discord.go @@ -8,6 +8,7 @@ import ( "time" dg "github.com/bwmarrin/discordgo" + "github.com/hrfee/jfa-go/common" lm "github.com/hrfee/jfa-go/logmessages" "github.com/timshannon/badgerhold/v4" ) @@ -28,6 +29,7 @@ type DiscordDaemon struct { commandHandlers map[string]func(s *dg.Session, i *dg.InteractionCreate, lang string) commandIDs []string commandDescriptions []*dg.ApplicationCommand + retryOpts *common.MustAuthenticateOptions } func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) { @@ -59,6 +61,16 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) { dd.users[user.ID] = user } + dd.retryOpts = &common.MustAuthenticateOptions{ + RetryCount: app.config.Section("advanced").Key("auth_retry_count").MustInt(6), + RetryGap: time.Duration(app.config.Section("advanced").Key("auth_retry_gap").MustInt(10)) * time.Second, + LogFailures: true, + } + + dd.bot.AddHandler(dd.commandHandler) + + dd.bot.Identify.Intents = dg.IntentsGuildMessages | dg.IntentsDirectMessages | dg.IntentsGuildMembers | dg.IntentsGuildInvites + return dd, nil } @@ -99,13 +111,27 @@ func (d *DiscordDaemon) MustGetUser(channelID, userID, discrim, username string) return d.NewUnknownUser(channelID, userID, discrim, username) } -func (d *DiscordDaemon) run() { - d.bot.AddHandler(d.commandHandler) +func (d *DiscordDaemon) Run() { + ro := common.MustAuthenticateOptions{} + ro = *d.retryOpts + ro.Counter = 0 + d.run(&ro) +} - d.bot.Identify.Intents = dg.IntentsGuildMessages | dg.IntentsDirectMessages | dg.IntentsGuildMembers | dg.IntentsGuildInvites +func (d *DiscordDaemon) run(retry *common.MustAuthenticateOptions) { if err := d.bot.Open(); err != nil { - d.app.err.Printf(lm.FailedStartDaemon, lm.Discord, err) - return + if retry == nil || retry.LogFailures { + d.app.err.Printf(lm.FailedStartDaemon, lm.Discord, err) + } + if retry != nil { + retry.Counter += 1 + if retry.Counter >= retry.RetryCount { + return + } + time.Sleep(retry.RetryGap) + d.run(retry) + return + } } // Wait for everything to populate, it's slow sometimes. for d.bot.State == nil { @@ -135,15 +161,18 @@ func (d *DiscordDaemon) run() { d.InviteChannel.Name = invChannel } } - err = d.bot.UpdateGameStatus(0, "/"+d.app.config.Section("discord").Key("start_command").MustString("start")) + d.bot.UpdateGameStatus(0, "/"+d.app.config.Section("discord").Key("start_command").MustString("start")) defer d.deregisterCommands() defer d.bot.Close() - go d.registerCommands() + ro := common.MustAuthenticateOptions{} + ro = *(d.retryOpts) + ro.Counter = 0 + + go d.registerCommands(&ro) <-d.ShutdownChannel d.ShutdownChannel <- "Down" - return } // ListRoles returns a list of available (excluding bot and @everyone) roles in a guild as a list of containing an array of the guild ID and its name. @@ -333,7 +362,7 @@ func (d *DiscordDaemon) Shutdown() { close(d.ShutdownChannel) } -func (d *DiscordDaemon) registerCommands() { +func (d *DiscordDaemon) registerCommands(retry *common.MustAuthenticateOptions) { d.commandDescriptions = []*dg.ApplicationCommand{ { Name: d.app.config.Section("discord").Key("start_command").MustString("start"), @@ -430,7 +459,27 @@ func (d *DiscordDaemon) registerCommands() { // if err != nil { // d.app.err.Printf("Discord: Cannot create commands: %v", err) // } - for i, cmd := range d.commandDescriptions { + + cCommands, err := d.bot.ApplicationCommandBulkOverwrite(d.bot.State.User.ID, d.guildID, d.commandDescriptions) + if err != nil { + if retry == nil || retry.LogFailures { + d.app.err.Printf(lm.FailedRegisterDiscordCommand, "*", err) + } + if retry != nil { + retry.Counter += 1 + if retry.Counter >= retry.RetryCount { + return + } + time.Sleep(retry.RetryGap) + d.registerCommands(retry) + } + } else { + for i := range len(d.commandDescriptions) { + d.commandIDs[i] = cCommands[i].ID + } + d.app.debug.Printf(lm.RegisterDiscordCommand, "*") + } + /* for i, cmd := range d.commandDescriptions { command, err := d.bot.ApplicationCommandCreate(d.bot.State.User.ID, d.guildID, cmd) if err != nil { d.app.err.Printf(lm.FailedRegisterDiscordCommand, cmd.Name, err) @@ -438,7 +487,7 @@ func (d *DiscordDaemon) registerCommands() { d.app.debug.Printf(lm.RegisterDiscordCommand, cmd.Name) d.commandIDs[i] = command.ID } - } + } */ } func (d *DiscordDaemon) deregisterCommands() { diff --git a/main.go b/main.go index 04ac95a..2993b3e 100644 --- a/main.go +++ b/main.go @@ -542,7 +542,7 @@ func start(asDaemon, firstCall bool) { discordEnabled = false } else { app.debug.Println(lm.InitDiscord) - go app.discord.run() + go app.discord.Run() defer app.discord.Shutdown() app.contactMethods = append(app.contactMethods, app.discord) }