diff --git a/src/internal/bot/auto_scrubber.go b/src/internal/bot/auto_scrubber.go deleted file mode 100644 index bbbfa61..0000000 --- a/src/internal/bot/auto_scrubber.go +++ /dev/null @@ -1,113 +0,0 @@ -package bot - -import ( - "log/slog" - "time" -) - -// scrubberInterval - How often we check -const scrubberInterval = 1 * time.Minute - -var ( - // scrubbers - Map of auto scrubbers to monitor - scrubbers map[string]map[string]time.Duration - - // Chan to use on shutdown - scrubberStop = make(chan struct{}) -) - -// initScrubber - Loads the scrubber configs from the DB -func initScrubber() error { - // Load config - allScrubbers, err := b.Db.GetAllAutoScrubbers() - if err != nil { - return err - } - - // map[GuildID][UserID]Interval - scrubbers = make(map[string]map[string]time.Duration) - for _, scrubber := range allScrubbers { - if _, ok := scrubbers[scrubber.GuildID]; !ok { - scrubbers[scrubber.GuildID] = make(map[string]time.Duration) - } - - scrubbers[scrubber.GuildID][scrubber.UserID] = scrubber.Duration - } - - runScrubber() - - return nil -} - -// runScrubber - Start the auto scrubber system -func runScrubber() { - ticker := time.NewTicker(scrubberInterval) - go func() { - for { - select { - case <-ticker.C: - // Loop through all scrubbers, clone for funsies - tmpScrub := scrubbers - for guildID, users := range tmpScrub { - for userID, interval := range users { - emojis, err := b.Db.GetRecentEmojisForUser(guildID, userID, 1) - if err != nil { - slog.Error("Error getting recent emojis for user", "guild_id", guildID, "user_id", userID, "err", err) - continue - } - - // If older creation time + interval is before now, remove it - for _, e := range emojis { - if e.Timestamp.Add(interval).Before(time.Now()) { - // Ignore errors here as it's likely bad data - emo := e.EmojiID - if emo == "" { - emo = e.EmojiName - } - err := b.DiscordSession.MessageReactionRemove(e.ChannelID, e.MessageID, emo, e.UserID) - if err != nil { - slog.Error("Error removing emoji reaction", "err", err, "emoji", emo, "user", e.UserID) - } - - // We care if we can't delete from our DB.. - err = b.Db.DeleteEmojiUsageById(e.ID) - if err != nil { - slog.Error("Error deleting emoji usage", "err", err, "emoji", e) - continue - } - } - } - } - } - // Handle shutdown - case <-scrubberStop: - ticker.Stop() - return - } - } - }() -} - -// startScrubbingUser - Start an auto scrubber for a user in a guild -func startScrubbingUser(guildID string, userID string, interval time.Duration) error { - err := b.Db.AddAutoScrubber(guildID, userID, interval) - if err != nil { - slog.Error("Failed to add auto scrubber", "err", err) - return err - } - - // Add to map - if _, ok := scrubbers[guildID]; !ok { - scrubbers[guildID] = make(map[string]time.Duration) - } - scrubbers[guildID][userID] = interval - - return nil -} - -// stopScrubbingUser - Stop an auto scrubber for a user in a guild -func stopScrubbingUser(guildID string, userID string) error { - // Remove from instant - delete(scrubbers[guildID], userID) - return b.Db.RemoveAutoScrubber(guildID, userID) -} diff --git a/src/internal/bot/commands.go b/src/internal/bot/commands.go index 8be5bd1..9a21cd1 100644 --- a/src/internal/bot/commands.go +++ b/src/internal/bot/commands.go @@ -5,7 +5,6 @@ import ( "log/slog" "sort" "strings" - "time" "github.com/bwmarrin/discordgo" ) @@ -47,8 +46,8 @@ var ( }, }, { - Name: "add-auto-scrubber", - Description: "Auto Scrub Emoijis after a set period", + Name: "add-magic-tool", + Description: "Runs a script on reaction for user", DefaultMemberPermissions: &defaultRunCommandPermissions, Options: []*discordgo.ApplicationCommandOption{ { @@ -57,17 +56,11 @@ var ( Description: "Select user", Required: true, }, - { - Type: discordgo.ApplicationCommandOptionInteger, - Name: "minutes", - Description: "Every X minutes", - Required: true, - }, }, }, { - Name: "remove-auto-scrubber", - Description: "Stop autoscrubbing emojis after a set period", + Name: "remove-magic-tool", + Description: "Stops running a script on reaction for user", DefaultMemberPermissions: &defaultRunCommandPermissions, Options: []*discordgo.ApplicationCommandOption{ { @@ -81,10 +74,10 @@ var ( } commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ - "show-top-emojis": showTopEmojis, - "show-top-users": showTopUsers, - "add-auto-scrubber": addAutoScrubber, - "remove-auto-scrubber": removeAutoScrubber, + "show-top-emojis": showTopEmojis, + "show-top-users": showTopUsers, + "add-magic-tool": addAutoScrubber, + "remove-magic-tool": removeAutoScrubber, } ) @@ -195,7 +188,7 @@ func showTopUsers(s *discordgo.Session, i *discordgo.InteractionCreate) { // Sort keys keys := make([]int, 0) - for k, _ := range top { + for k := range top { keys = append(keys, k) } sort.Ints(keys) @@ -260,29 +253,13 @@ func addAutoScrubber(s *discordgo.Session, i *discordgo.InteractionCreate) { return } - var minutes int64 - if opt, ok := optionMap["minutes"]; ok { - minutes = opt.IntValue() - } else { - slog.Error("Invalid time option provided") - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "No minutes specified", - AllowedMentions: &discordgo.MessageAllowedMentions{}, - }, - }) - return - } - - dur := time.Duration(minutes) * time.Minute - err := startScrubbingUser(i.GuildID, user.ID, dur) + err := startScrubbingUser(i.GuildID, user.ID) if err != nil { - slog.Error("Error starting auto scrubber", "err", err, "guild_id", i.GuildID, "user_id", user.ID, "duration", dur) + slog.Error("Error starting auto scrubber", "err", err, "guild_id", i.GuildID, "user_id", user.ID) s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("Error auto scrubbing user %s: %+v", user.Username, err.Error()), + Content: ":x:", AllowedMentions: &discordgo.MessageAllowedMentions{}, }, }) @@ -293,7 +270,7 @@ func addAutoScrubber(s *discordgo.Session, i *discordgo.InteractionCreate) { s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("Will remove %s's emojis after %d minutes", user.Username, minutes), + Content: ":white_check_mark:", AllowedMentions: &discordgo.MessageAllowedMentions{}, }, }) @@ -328,7 +305,7 @@ func removeAutoScrubber(s *discordgo.Session, i *discordgo.InteractionCreate) { s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("Error stopping scrub on %s: %+v", user.Username, err.Error()), + Content: ":x:", AllowedMentions: &discordgo.MessageAllowedMentions{}, }, }) @@ -339,7 +316,7 @@ func removeAutoScrubber(s *discordgo.Session, i *discordgo.InteractionCreate) { s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("Stopped scrubbing %s's emojis", user.Username), + Content: ":white_check_mark:", AllowedMentions: &discordgo.MessageAllowedMentions{}, }, }) diff --git a/src/internal/bot/main.go b/src/internal/bot/main.go index ba42ebe..23384ae 100644 --- a/src/internal/bot/main.go +++ b/src/internal/bot/main.go @@ -4,6 +4,7 @@ import ( "log/slog" "os" "os/signal" + "sync" "github.com/bwmarrin/discordgo" "github.com/idanoo/GoDiscMoji/internal/db" @@ -19,6 +20,10 @@ type Bot struct { registeredCommands []*discordgo.ApplicationCommand Db *db.Database + + // Scrub map[GuildID][UserID]bool + scrubs *map[string]map[string]bool + scrubsMutex sync.RWMutex } // New - Return new instance of *Bot @@ -69,8 +74,8 @@ func (bot *Bot) Start() error { bot.RegisterCommands() b = bot - // Add scrubber - initScrubber() + // Add scrubs + initScrub() // Keep running untill there is NO os interruption (ctrl + C) slog.Info("Bot is now running. Press CTRL-C to exit.") @@ -81,9 +86,6 @@ func (bot *Bot) Start() error { // Deregister any commands we created bot.DeregisterCommands() - // Stop scrubbin! - close(scrubberStop) - return nil } @@ -94,6 +96,15 @@ func (bot *Bot) HandleAddReaction(discord *discordgo.Session, reaction *discordg return } + if scrub.shouldScrub(reaction.GuildID, reaction.UserID) { + err := b.DiscordSession.MessageReactionRemove(reaction.ChannelID, reaction.MessageID, reaction.Emoji.ID, reaction.UserID) + if err != nil { + slog.Error("Error removing emoji reaction", "err", err, "reaction", reaction) + } + + return + } + err := bot.Db.LogEmojiUsage(reaction.GuildID, reaction.ChannelID, reaction.MessageID, reaction.UserID, reaction.Emoji.ID, reaction.Emoji.Name) if err != nil { slog.Error("Failed to log emoji usage", "err", err) diff --git a/src/internal/bot/scrub.go b/src/internal/bot/scrub.go new file mode 100644 index 0000000..3b2db5d --- /dev/null +++ b/src/internal/bot/scrub.go @@ -0,0 +1,84 @@ +package bot + +import ( + "log/slog" + "sync" +) + +type Scrubber struct { + scrubs map[string]map[string]bool + mutex sync.RWMutex +} + +var scrub *Scrubber + +// shouldScrub - Check if a user is scrubbing in a guild +func (s *Scrubber) shouldScrub(guildID string, userID string) bool { + s.mutex.RLock() + defer s.mutex.RUnlock() + + if _, ok := s.scrubs[guildID]; !ok { + return false + } + + if _, ok := s.scrubs[guildID][userID]; !ok { + return false + } + + return true +} + +// initScrub - Loads the scrubber configs from the DB +func initScrub() error { + s := Scrubber{ + scrubs: make(map[string]map[string]bool), + mutex: sync.RWMutex{}, + } + scrub = &s + + // Load config + allScrubbers, err := b.Db.GetAllScrubs() + if err != nil { + return err + } + + // map[GuildID][UserID]bool + scrubs := make(map[string]map[string]bool) + for _, scrubber := range allScrubbers { + if _, ok := scrubs[scrubber.GuildID]; !ok { + scrubs[scrubber.GuildID] = make(map[string]bool) + } + + scrubs[scrubber.GuildID][scrubber.UserID] = true + } + + scrub.scrubs = scrubs + + return nil +} + +// startScrubbingUser - Start an auto scrubber for a user in a guild +func (s *Scrubber) startScrubbingUser(guildID string, userID string) error { + err := b.Db.AddScrub(guildID, userID) + if err != nil { + slog.Error("Failed to add auto scrubber", "err", err) + return err + } + + // Add to map + if _, ok := s.scrubs[guildID]; !ok { + s.scrubs[guildID] = make(map[string]bool) + } + s.scrubs[guildID][userID] = true + + return nil +} + +// stopScrubbingUser - Stop an auto scrubber for a user in a guild +func (s *Scrubber) stopScrubbingUser(guildID string, userID string) error { + s.mutex.Lock() + defer s.mutex.Unlock() + + delete(s.scrubs[guildID], userID) + return b.Db.RemoveScrub(guildID, userID) +} diff --git a/src/internal/db/auto_scrubber.go b/src/internal/db/auto_scrubber.go deleted file mode 100644 index 1e1f38a..0000000 --- a/src/internal/db/auto_scrubber.go +++ /dev/null @@ -1,51 +0,0 @@ -package db - -import ( - "time" -) - -type AutoScrubber struct { - GuildID string `json:"guild_id"` - UserID string `json:"user_id"` - Duration time.Duration `json:"duration"` -} - -// AddAutoScrubber - Add an auto scrubber for a guild/user -func (db *Database) AddAutoScrubber(guildID, userID string, duration time.Duration) error { - _, err := db.db.Exec( - "INSERT INTO `auto_scrubber` (`guild_id`, `user_id`, `duration`) VALUES (?,?,?)", - guildID, userID, duration, - ) - - return err -} - -// RemoveAutoScrubber - Delete for guild/channel/message/user -func (db *Database) RemoveAutoScrubber(guildID, userID string) error { - _, err := db.db.Exec( - "DELETE FROM `auto_scrubber` WHERE `guild_id` = ? AND `user_id` = ?", - guildID, userID, - ) - - return err -} - -// DeleteEmojiAll - Delete for whole message -func (db *Database) GetAllAutoScrubbers() ([]AutoScrubber, error) { - data := make([]AutoScrubber, 0) - row, err := db.db.Query("SELECT guild_id, user_id, duration from `auto_scrubber`") - if err != nil { - return data, err - } - - defer row.Close() - for row.Next() { - var guildID string - var userID string - var duration time.Duration - row.Scan(&guildID, &userID, &duration) - data = append(data, AutoScrubber{GuildID: guildID, UserID: userID, Duration: duration}) - } - - return data, nil -} diff --git a/src/internal/db/database.go b/src/internal/db/database.go index 1b79096..6d62f0b 100644 --- a/src/internal/db/database.go +++ b/src/internal/db/database.go @@ -60,35 +60,26 @@ func (db *Database) runMigrations() (*Database, error) { return db, err } - _, err = db.db.Exec("CREATE TABLE IF NOT EXISTS `auto_scrubber` (" + + // Clean up old tables + _, err = db.db.Exec("DROP TABLE IF EXISTS `auto_scrubber`") + if err != nil { + return db, err + } + + _, err = db.db.Exec("CREATE TABLE IF NOT EXISTS `scrub` (" + "`id` INTEGER PRIMARY KEY AUTOINCREMENT, " + "`guild_id` TEXT, " + - "`user_id` TEXT, " + - "`duration` INT " + + "`user_id` TEXT " + ")") if err != nil { return db, err } - _, err = db.db.Exec("CREATE INDEX IF NOT EXISTS `idx_auto_scrubber_guild_user` ON `auto_scrubber` (`guild_id`, `user_id`)") + _, err = db.db.Exec("CREATE INDEX IF NOT EXISTS `idx_scrub_guildid_userid` ON `scrub` (`guild_id`, `user_id`)") if err != nil { return db, err } - // emojitest := map[string]string{ - // "pepe_analyze": "579431592624390147", - // "kekw": "1317987954102112347", - // "pepe_kek": "1300985103182336010", - // "KEKW": "649918246119669770", - // } - - // for name, id := range emojitest { - // _, err = db.db.Exec("UPDATE `emoji_usage` SET `emoji_id` = '" + id + "' WHERE `emoji_name` = '" + name + "'") - // if err != nil { - // return db, err - // } - // } - return db, nil } diff --git a/src/internal/db/scrub.go b/src/internal/db/scrub.go new file mode 100644 index 0000000..7fe320c --- /dev/null +++ b/src/internal/db/scrub.go @@ -0,0 +1,45 @@ +package db + +type Scrub struct { + GuildID string `json:"guild_id"` + UserID string `json:"user_id"` +} + +// AddScrub - Add an auto scrubber for a guild/user +func (db *Database) AddScrub(guildID, userID string) error { + _, err := db.db.Exec( + "INSERT INTO `scrub` (`guild_id`, `user_id`) VALUES (?,?)", + guildID, userID, + ) + + return err +} + +// RemoveScrub - Delete for guild/channel/message/user +func (db *Database) RemoveScrub(guildID, userID string) error { + _, err := db.db.Exec( + "DELETE FROM `scrub` WHERE `guild_id` = ? AND `user_id` = ?", + guildID, userID, + ) + + return err +} + +// GetAllScrubs - Get all scrubbers +func (db *Database) GetAllScrubs() ([]Scrub, error) { + data := make([]Scrub, 0) + row, err := db.db.Query("SELECT guild_id, user_id from `scrub`") + if err != nil { + return data, err + } + + defer row.Close() + for row.Next() { + var guildID string + var userID string + row.Scan(&guildID, &userID) + data = append(data, Scrub{GuildID: guildID, UserID: userID}) + } + + return data, nil +}