Skip to content

Commit

Permalink
feat: (stats) track servers configured and watched
Browse files Browse the repository at this point in the history
- misc doc updates, remove periods from comments
  • Loading branch information
tyzbit committed Apr 28, 2023
1 parent 3550ea3 commit 999c655
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 124 deletions.
9 changes: 5 additions & 4 deletions bot/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type MessageEvent struct {
ArchiveEventEvents []ArchiveEventEvent `gorm:"foreignKey:UUID"`
}

// A InteractionEvent when a user interacts with an Embed
type InteractionEvent struct {
CreatedAt time.Time
UUID string `gorm:"primaryKey"`
Expand All @@ -32,7 +33,7 @@ type InteractionEvent struct {
ArchiveEventEvents []ArchiveEventEvent `gorm:"foreignKey:UUID"`
}

// Every successful ArchiveEventEvent will come from a message.
// Every successful ArchiveEventEvent will come from a message
type ArchiveEventEvent struct {
CreatedAt time.Time
UUID string `gorm:"primaryKey"`
Expand All @@ -46,7 +47,7 @@ type ArchiveEventEvent struct {
}

// This is the representation of request and response URLs from users or
// the Archiver API.
// the Archiver API
type ArchiveEvent struct {
CreatedAt time.Time
UUID string `gorm:"primaryKey"`
Expand All @@ -60,13 +61,13 @@ type ArchiveEvent struct {
Cached bool
}

// createMessageEvent logs a given message event into the database.
// createMessageEvent logs a given message event into the database
func (bot *ArchiverBot) createMessageEvent(m MessageEvent) {
m.UUID = uuid.New().String()
bot.DB.Create(&m)
}

// createInteractionEvent logs a given message event into the database.
// createInteractionEvent logs a given message event into the database
func (bot *ArchiverBot) createInteractionEvent(i InteractionEvent) {
i.UUID = uuid.New().String()
bot.DB.Create(&i)
Expand Down
26 changes: 13 additions & 13 deletions bot/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ const (
)

// handleArchiveRequest takes a Discord session and a message string and
// calls go-archiver with a []string of URLs parsed from the message.
// It then sends an embed with the resulting archived URLs.
// calls go-archiver with a []string of URLs parsed from the message
// It then sends an embed with the resulting archived URLs
// TODO: break out into more functions?
func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, newSnapshot bool) (
replies []*discordgo.MessageSend, errs []error) {

typingStop := make(chan bool, 1)
go bot.typeInChannel(typingStop, r.ChannelID)

// If true, this is a DM.
// If true, this is a DM
if r.GuildID == "" {
typingStop <- true
replies = []*discordgo.MessageSend{
Expand All @@ -43,7 +43,7 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne
}

sc := bot.getServerConfig(r.GuildID)
if !sc.ArchiveEnabled {
if sc.ArchiveEnabled.Valid && !sc.ArchiveEnabled.Bool {
log.Info("URLs were not archived because automatic archive is not enabled")
typingStop <- true
return replies, errs
Expand Down Expand Up @@ -92,7 +92,7 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne
log.Debug("URLs parsed from message: ", strings.Join(messageUrls, ", "))

// This UUID will be used to tie together the ArchiveEventEvent,
// the archiveRequestUrls and the archiveResponseUrls.
// the archiveRequestUrls and the archiveResponseUrls
archiveEventUUID := uuid.New().String()

var archives []ArchiveEvent
Expand All @@ -102,13 +102,13 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne
log.Error("unable to get domain name for url: ", url)
}

// See if there is a response URL for a given request URL in the database.
// See if there is a response URL for a given request URL in the database
cachedArchiveEvents := []ArchiveEvent{}
bot.DB.Model(&ArchiveEvent{}).Where(&ArchiveEvent{RequestURL: url, Cached: false}).Find(&cachedArchiveEvents)
var responseUrl, responseDomainName string

// If we have a response, create a new ArchiveEvent with it,
// marking it as cached.
// marking it as cached
for _, cachedArchiveEvent := range cachedArchiveEvents {
if cachedArchiveEvent.ResponseURL != "" && cachedArchiveEvent.ResponseDomainName != "" {
responseUrl = cachedArchiveEvent.ResponseURL
Expand All @@ -134,7 +134,7 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne
}

// We have not already archived this URL, so build an object
// for doing so.
// for doing so
log.Debug("url was not cached: ", url)
archives = append(archives, ArchiveEvent{
UUID: uuid.New().String(),
Expand All @@ -152,9 +152,9 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne
if archive.ResponseURL == "" {
log.Debug("need to call archive.org api for ", archive.RequestURL)

// This will always try to archive the page if not found.
url, err := goarchive.GetLatestURL(archive.RequestURL, sc.RetryAttempts,
sc.AlwaysArchiveFirst || newSnapshot, bot.Config.Cookie)
// This will always try to archive the page if not found
url, err := goarchive.GetLatestURL(archive.RequestURL, uint(sc.RetryAttempts.Int32),
sc.AlwaysArchiveFirst.Bool || newSnapshot, bot.Config.Cookie)
if err != nil {
log.Errorf("error archiving url: %v", err)
url = fmt.Sprint("%w", errors.Unwrap(err))
Expand All @@ -174,7 +174,7 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne
archivedLinks = append(archivedLinks, url)
} else {
// We have a response URL, so add that to the links to be used
// in the message.
// in the message
archivedLinks = append(archivedLinks, archive.ResponseURL)
}
}
Expand Down Expand Up @@ -235,7 +235,7 @@ func (bot *ArchiverBot) handleArchiveRequest(r *discordgo.MessageReactionAdd, ne
}

if link != "" {
if sc.ShowDetails {
if sc.ShowDetails.Valid && sc.ShowDetails.Bool {
embeds[0].Fields = []*discordgo.MessageEmbedField{
{
Name: "Oldest Archived Copy",
Expand Down
45 changes: 27 additions & 18 deletions bot/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import (
"gorm.io/gorm"
)

// ArchiverBot is the main type passed around throughout the code
// It has many functions for overall bot management
type ArchiverBot struct {
DB *gorm.DB
DG *discordgo.Session
Config ArchiverBotConfig
StartingUp bool
}

// ArchiverBotConfig is attached to ArchiverBot so config settings can be
// accessed easily
type ArchiverBotConfig struct {
AdminIds []string `env:"ADMINISTRATOR_IDS"`
DBHost string `env:"DB_HOST"`
Expand All @@ -28,22 +32,25 @@ type ArchiverBotConfig struct {
Cookie string `env:"COOKIE"`
}

// BotReadyHandler is called when the bot is considered ready to use the Discord session.
// BotReadyHandler is called when the bot is considered ready to use the Discord session
func (bot *ArchiverBot) BotReadyHandler(s *discordgo.Session, r *discordgo.Ready) {
// Register all servers the bot is active in
for _, g := range r.Guilds {
err := bot.registerOrUpdateServer(g)
if err != nil {
log.Errorf("unable to register or update server: %v", err)
}
}

// Use this to clean up commands if IDs have changed.
bot.updateServerRegistrations(r.Guilds)

// Use this to clean up commands if IDs have changed
// TODO remove later if unnecessary
// log.Debug("removing all commands")
// bot.deleteAllCommands()
// globals.RegisteredCommands, err = bot.DG.ApplicationCommandBulkOverwrite(bot.DG.State.User.ID, "", globals.Commands)
log.Debug("registering slash commands")
var err error
// globals.RegisteredCommands, err = bot.DG.ApplicationCommandBulkOverwrite(bot.DG.State.User.ID, "", globals.Commands)
existingCommands, err := bot.DG.ApplicationCommands(bot.DG.State.User.ID, "")
for _, cmd := range globals.Commands {
for _, existingCmd := range existingCommands {
Expand Down Expand Up @@ -79,7 +86,7 @@ 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.
// connection to Discord
func (bot *ArchiverBot) GuildCreateHandler(s *discordgo.Session, gc *discordgo.GuildCreate) {
if gc.Guild.Unavailable {
return
Expand All @@ -92,7 +99,7 @@ func (bot *ArchiverBot) GuildCreateHandler(s *discordgo.Session, gc *discordgo.G
}

// This function will be called every time a new react is created on any message
// that the authenticated bot has access to.
// that the authenticated bot has access to
func (bot *ArchiverBot) MessageReactionAddHandler(s *discordgo.Session, r *discordgo.MessageReactionAdd) {
if r.MessageReaction.Emoji.Name == "🏛️" {
var m *discordgo.Message
Expand All @@ -104,7 +111,7 @@ func (bot *ArchiverBot) MessageReactionAddHandler(s *discordgo.Session, r *disco
return
}
// Create a fake message so that we can handle reacts
// and interactions.
// and interactions
m = &discordgo.Message{
ID: r.MessageReaction.MessageID,
Member: &discordgo.Member{
Expand All @@ -117,7 +124,7 @@ func (bot *ArchiverBot) MessageReactionAddHandler(s *discordgo.Session, r *disco
}
} else {
// Create a fake message so that we can handle reacts
// and interactions.
// and interactions
m = &discordgo.Message{
ID: r.MessageID,
Member: &discordgo.Member{
Expand Down Expand Up @@ -208,8 +215,8 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
guild.Name + "(" + guild.ID + ")"
} else {
log.Debug("handling stats DM request")
// We can be sure now the request was a direct message.
// Deny by default.
// We can be sure now the request was a direct message
// Deny by default
administrator := false

out:
Expand All @@ -220,7 +227,7 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
// This prevents us from checking all IDs now that
// we found a match but is a fairly ineffectual
// optimization since config.AdminIds will probably
// only have dozens of IDs at most.
// only have dozens of IDs at most
break out
}
}
Expand Down Expand Up @@ -259,7 +266,7 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
Embeds: []*discordgo.MessageEmbed{
{
Title: "🏛️ Archive.org Bot Stats",
Fields: structToPrettyDiscordFields(stats),
Fields: structToPrettyDiscordFields(stats, directMessage),
Color: globals.FrenchGray,
},
},
Expand All @@ -272,7 +279,7 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
},
globals.Settings: func(s *discordgo.Session, i *discordgo.InteractionCreate) {
log.Debug("handling settings request")
// This is a DM, so settings cannot be changed.
// This is a DM, so settings cannot be changed
if i.GuildID == "" {
err := bot.DG.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Expand All @@ -299,13 +306,14 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
})

sc := bot.getServerConfig(i.GuildID)
resp := bot.SettingsIntegrationResponse(sc)
err = bot.DG.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: bot.SettingsIntegrationResponse(sc),
Data: resp,
})

if err != nil {
log.Errorf("error responding to slash command"+globals.Settings+", err: %v", err)
log.Errorf("error responding to slash command "+globals.Settings+", err: %v", err)
}
}

Expand Down Expand Up @@ -401,7 +409,7 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
sc := bot.getServerConfig(i.GuildID)
var interactionErr error

inverse := !sc.ArchiveEnabled
inverse := sc.ArchiveEnabled.Valid && !sc.ArchiveEnabled.Bool
sc, ok := bot.updateServerSetting(i.GuildID, "archive_enabled", inverse)

guild, err := bot.DG.Guild(i.Interaction.GuildID)
Expand Down Expand Up @@ -440,7 +448,7 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
},
globals.Details: func(s *discordgo.Session, i *discordgo.InteractionCreate) {
sc := bot.getServerConfig(i.GuildID)
inverse := !sc.ShowDetails
inverse := sc.ShowDetails.Valid && !sc.ShowDetails.Bool
var interactionErr error
sc, ok := bot.updateServerSetting(i.GuildID, "show_details", inverse)

Expand Down Expand Up @@ -476,7 +484,7 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
},
globals.AlwaysArchiveFirst: func(s *discordgo.Session, i *discordgo.InteractionCreate) {
sc := bot.getServerConfig(i.GuildID)
inverse := !sc.AlwaysArchiveFirst
inverse := sc.AlwaysArchiveFirst.Valid && !sc.AlwaysArchiveFirst.Bool
var interactionErr error
sc, ok := bot.updateServerSetting(i.GuildID, "always_archive_first", inverse)

Expand Down Expand Up @@ -512,7 +520,8 @@ func (bot *ArchiverBot) InteractionHandler(s *discordgo.Session, i *discordgo.In
},
globals.RemoveRetry: func(s *discordgo.Session, i *discordgo.InteractionCreate) {
sc := bot.getServerConfig(i.GuildID)
inverse := !sc.RemoveRetries
// If the value isn't valid, the setting should end up disabled
inverse := sc.RemoveRetry.Valid && !sc.RemoveRetry.Bool
var interactionErr error
sc, ok := bot.updateServerSetting(i.GuildID, "remove_retry", inverse)

Expand Down
19 changes: 17 additions & 2 deletions bot/messaging.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package bot

import (
"strconv"
"strings"
"time"

"github.com/bwmarrin/discordgo"
Expand Down Expand Up @@ -57,7 +59,20 @@ func (bot *ArchiverBot) removeRetryButtonAfterSleep(message *discordgo.Message)
}

sc := bot.getServerConfig(guild.ID)
time.Sleep(time.Duration(sc.RemoveRetriesDelay))
var sleep int32
if sc.RemoveRetriesDelay.Valid {
sleep = sc.RemoveRetriesDelay.Int32
} else {
field := "RemoveRetriesDelay"
log.Debugf("%s was not set, getting gorm default", field)
gormDefault := getTagValue(ServerConfig{}, field, "gorm")
if value, err := strconv.ParseInt(strings.Split(gormDefault, ":")[1], 10, 32); err != nil {
log.Errorf("unable to get default gorm value for %s", field)
} else {
sleep = int32(value)
}
}
time.Sleep(time.Duration(sleep) * time.Second)
me := discordgo.MessageEdit{
// Remove the components (button)
Components: []discordgo.MessageComponent{},
Expand All @@ -67,7 +82,7 @@ func (bot *ArchiverBot) removeRetryButtonAfterSleep(message *discordgo.Message)
}

log.Debugf("removing reply button (waited %vs) for message ID %s in channel %s, guild: %s(%s)",
sc.RemoveRetriesDelay, message.ID, message.ChannelID, guild.Name, guild.ID)
sleep, message.ID, message.ChannelID, guild.Name, guild.ID)
_, err := bot.DG.ChannelMessageEditComplex(&me)
if err != nil {
log.Errorf("unable to remove retry button on message id %v, server: %s(%s): %v, ",
Expand Down
Loading

0 comments on commit 999c655

Please sign in to comment.