Skip to content

Commit

Permalink
feat: (bot) handle server leaves, add JoinedAt to registrations
Browse files Browse the repository at this point in the history
  • Loading branch information
tyzbit committed May 1, 2023
1 parent 32ba92f commit 61573cb
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 50 deletions.
81 changes: 48 additions & 33 deletions bot/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,43 +43,46 @@ func (bot *ArchiverBot) BotReadyHandler(s *discordgo.Session, r *discordgo.Ready
// bot.deleteAllCommands()
// globals.RegisteredCommands, err = bot.DG.ApplicationCommandBulkOverwrite(bot.DG.State.User.ID, "", globals.Commands)
log.Debug("registering slash commands")
var err error
registeredCommands, err := bot.DG.ApplicationCommands(bot.DG.State.User.ID, "")
for _, botCommand := range globals.Commands {
for i, registeredCommand := range registeredCommands {
// Check if this registered command matches a configured bot command
if botCommand.Name == registeredCommand.Name {
// Only update if it differs from what's already registered
if botCommand != registeredCommand {
editedCmd, err := bot.DG.ApplicationCommandEdit(bot.DG.State.User.ID, "", registeredCommand.ID, botCommand)
if err != nil {
log.Errorf("cannot update command %s: %v", botCommand.Name, err)
if err != nil {
log.Errorf("unable to look up registered application commands, err: %s", err)
} else {
for _, botCommand := range globals.Commands {
for i, registeredCommand := range registeredCommands {
// Check if this registered command matches a configured bot command
if botCommand.Name == registeredCommand.Name {
// Only update if it differs from what's already registered
if botCommand != registeredCommand {
editedCmd, err := bot.DG.ApplicationCommandEdit(bot.DG.State.User.ID, "", registeredCommand.ID, botCommand)
if err != nil {
log.Errorf("cannot update command %s: %v", botCommand.Name, err)
}
globals.RegisteredCommands = append(globals.RegisteredCommands, editedCmd)

// Bot command was updated, so skip to the next bot command
break
}
globals.RegisteredCommands = append(globals.RegisteredCommands, editedCmd)

// Bot command was updated, so skip to the next bot command
break
}
}

// Check on the last item of registeredCommands
if i == len(registeredCommands) {
// This is a stale registeredCommand, so we should delete it
err := bot.DG.ApplicationCommandDelete(bot.DG.State.User.ID, "", registeredCommand.ID)
if err != nil {
log.Errorf("cannot remove command %s: %v", registeredCommand.Name, err)
// Check on the last item of registeredCommands
if i == len(registeredCommands) {
// This is a stale registeredCommand, so we should delete it
err := bot.DG.ApplicationCommandDelete(bot.DG.State.User.ID, "", registeredCommand.ID)
if err != nil {
log.Errorf("cannot remove command %s: %v", registeredCommand.Name, err)
}
}
}
}

// If we're here, then we have a command that needs to be registered
createdCmd, err := bot.DG.ApplicationCommandCreate(bot.DG.State.User.ID, "", botCommand)
if err != nil {
log.Errorf("cannot update command %s: %v", botCommand.Name, err)
}
globals.RegisteredCommands = append(globals.RegisteredCommands, createdCmd)
if err != nil {
log.Errorf("cannot update commands: %v", err)
// If we're here, then we have a command that needs to be registered
createdCmd, err := bot.DG.ApplicationCommandCreate(bot.DG.State.User.ID, "", botCommand)
if err != nil {
log.Errorf("cannot update command %s: %v", botCommand.Name, err)
}
globals.RegisteredCommands = append(globals.RegisteredCommands, createdCmd)
if err != nil {
log.Errorf("cannot update commands: %v", err)
}
}
}

Expand All @@ -89,14 +92,26 @@ func (bot *ArchiverBot) BotReadyHandler(s *discordgo.Session, r *discordgo.Ready
}
}

// GuildCreateHandler is called whenever the bot joins a new guild. It is also lazily called upon initial
// connection to Discord
// GuildCreateHandler is called whenever the bot joins a new guild.
func (bot *ArchiverBot) GuildCreateHandler(s *discordgo.Session, gc *discordgo.GuildCreate) {
if gc.Guild.Unavailable {
return
}

err := bot.registerOrUpdateServer(gc.Guild)
err := bot.registerOrUpdateServer(gc.Guild, false)
if err != nil {
log.Errorf("unable to register or update server: %v", err)
}
}

// GuildDeleteHandler is called whenever the bot leaves a guild.
func (bot *ArchiverBot) GuildDeleteHandler(s *discordgo.Session, gd *discordgo.GuildDelete) {
if gd.Guild.Unavailable {
return
}

log.Infof("guild %s(%s) deleted (bot was probably kicked)", gd.Guild.Name, gd.Guild.ID)
err := bot.registerOrUpdateServer(gd.BeforeDelete, true)
if err != nil {
log.Errorf("unable to register or update server: %v", err)
}
Expand Down
40 changes: 23 additions & 17 deletions bot/servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type ServerRegistration struct {
DiscordId string `gorm:"primaryKey"`
Name string `gorm:"default:default"`
UpdatedAt time.Time
JoinedAt time.Time
Active sql.NullBool `pretty:"Bot is active in the server" gorm:"default:true"`
Config ServerConfig `gorm:"foreignKey:DiscordId"`
}
Expand All @@ -33,42 +34,44 @@ const archiverRepoUrl string = "https://github.com/tyzbit/go-discord-archiver"

// registerOrUpdateServer checks if a guild is already registered in the database. If not,
// it creates it with sensibile defaults
func (bot *ArchiverBot) registerOrUpdateServer(g *discordgo.Guild) error {
// Do a lookup for the full guild object
guild, err := bot.DG.Guild(g.ID)
if err != nil {
return fmt.Errorf("unable to look up server by id: %v", g.ID)
func (bot *ArchiverBot) registerOrUpdateServer(g *discordgo.Guild, delete bool) error {
status := sql.NullBool{Valid: true, Bool: true}
if delete {
status = sql.NullBool{Valid: true, Bool: false}
}

var registration ServerRegistration
bot.DB.Find(&registration, g.ID)
active := sql.NullBool{Valid: true, Bool: true}
// The server registration does not exist, so we will create with defaults
if registration.Name == "default" {
log.Info("creating registration for new server: ", guild.Name, "(", g.ID, ")")
log.Info("creating registration for new server: ", g.Name, "(", g.ID, ")")
tx := bot.DB.Create(&ServerRegistration{
DiscordId: g.ID,
Name: guild.Name,
Active: active,
Name: g.Name,
Active: sql.NullBool{Valid: true, Bool: true},
UpdatedAt: time.Now(),
JoinedAt: g.JoinedAt,
Config: ServerConfig{
Name: guild.Name,
Name: g.Name,
},
})

// We only expect one server to be updated at a time. Otherwise, return an error
if tx.RowsAffected != 1 {
return fmt.Errorf("did not expect %v rows to be affected updating "+
"server registration for server: %v(%v)", fmt.Sprintf("%v", tx.RowsAffected), guild.Name, g.ID)
"server registration for server: %v(%v)", fmt.Sprintf("%v", tx.RowsAffected), g.Name, g.ID)
}
}

// Sort of a migration and also a catch-all for registrations that
// are not properly saved in the database
if registration.Active != active {
// Update the registration if the DB is wrong or if the server
// was deleted (the bot left) or if JoinedAt is not set
// (field was added later so early registrations won't have it)
if registration.Active != status || registration.JoinedAt.IsZero() {
log.Debugf("updating server %s", g.Name)
bot.DB.Model(&ServerRegistration{}).
Where(&ServerRegistration{DiscordId: registration.DiscordId}).
Updates(&ServerRegistration{Active: active})
Updates(&ServerRegistration{Active: status, JoinedAt: g.JoinedAt})
bot.updateServersWatched()
}

return nil
Expand All @@ -79,22 +82,25 @@ func (bot *ArchiverBot) registerOrUpdateServer(g *discordgo.Guild) error {
func (bot *ArchiverBot) updateInactiveRegistrations(activeGuilds []*discordgo.Guild) {
var sr []ServerRegistration
bot.DB.Find(&sr)
var status sql.NullBool

// Check all registrations for whether or not the server is active
for _, reg := range sr {
var status sql.NullBool
var joinedAt time.Time
// If there is no guild in r.Guilds, then we havea config
// for a server we're not in anymore
status = sql.NullBool{Valid: true, Bool: false}
for _, g := range activeGuilds {
if g.ID == reg.DiscordId {
status = sql.NullBool{Valid: true, Bool: true}
joinedAt = g.JoinedAt
}
}

// Now the registration is accurate, update the DB if needed
if reg.Active != status {
if reg.Active != status || reg.JoinedAt.IsZero() {
reg.Active = status
reg.JoinedAt = joinedAt
tx := bot.DB.Model(&ServerRegistration{}).Where(&ServerRegistration{DiscordId: reg.DiscordId}).
Updates(reg)

Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ func main() {
// Discord event
dg.AddHandler(archiveBot.BotReadyHandler)
dg.AddHandler(archiveBot.GuildCreateHandler)
dg.AddHandler(archiveBot.GuildDeleteHandler)
dg.AddHandler(archiveBot.MessageReactionAddHandler)
dg.AddHandler(archiveBot.InteractionHandler)

Expand Down

0 comments on commit 61573cb

Please sign in to comment.