From 9a1b3723bc93899afa9a1ea33d967dd816dcc9bd Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Tue, 5 Nov 2024 00:19:36 -0600 Subject: [PATCH] =?UTF-8?q?Added=20=E2=9C=A8himbucks=E2=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- command/himbucks.go | 42 ++++++ lib/himbucks.go | 136 ++++++++++++++++++ main.go | 24 +++- .../000003_create_himbucks_table.down.sql | 1 + .../000003_create_himbucks_table.up.sql | 10 ++ 5 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 command/himbucks.go create mode 100644 lib/himbucks.go create mode 100644 migrations/000003_create_himbucks_table.down.sql create mode 100644 migrations/000003_create_himbucks_table.up.sql diff --git a/command/himbucks.go b/command/himbucks.go new file mode 100644 index 0000000..6e139dd --- /dev/null +++ b/command/himbucks.go @@ -0,0 +1,42 @@ +package command + +import ( + "fmt" + "himbot/lib" + "strings" + + "github.com/bwmarrin/discordgo" +) + +func BalanceCommand(s *discordgo.Session, i *discordgo.InteractionCreate) (string, error) { + user, err := lib.GetUser(i) + if err != nil { + return "", err + } + + balance, err := lib.GetBalance(user.ID, i.GuildID) + if err != nil { + return "", err + } + + return fmt.Sprintf("You have %d himbucks!", balance), nil +} + +func LeaderboardCommand(s *discordgo.Session, i *discordgo.InteractionCreate) (string, error) { + entries, err := lib.GetLeaderboard(i.GuildID, 10) + if err != nil { + return "", err + } + + if len(entries) == 0 { + return "No himbucks earned yet!", nil + } + + var sb strings.Builder + sb.WriteString("🏆 Himbucks Leaderboard 🏆\n\n") + for idx, entry := range entries { + sb.WriteString(fmt.Sprintf("%d. %s: %d himbucks\n", idx+1, entry.Username, entry.Balance)) + } + + return sb.String(), nil +} diff --git a/lib/himbucks.go b/lib/himbucks.go new file mode 100644 index 0000000..c532e99 --- /dev/null +++ b/lib/himbucks.go @@ -0,0 +1,136 @@ +package lib + +import ( + "database/sql" + "fmt" + "time" + + "github.com/bwmarrin/discordgo" +) + +const ( + HimbucksPerReward = 10 + MessageCountThreshold = 5 + CooldownPeriod = time.Minute +) + +type HimbucksEntry struct { + Username string + Balance int + MessageCount int +} + +func GetOrCreateHimbucksEntry(userID int, guildID string) error { + _, err := DBClient.Exec(` + INSERT INTO himbucks (user_id, guild_id, balance, message_count) + VALUES (?, ?, 0, 0) + ON CONFLICT (user_id, guild_id) DO NOTHING`, + userID, guildID) + return err +} + +func ProcessMessage(s *discordgo.Session, m *discordgo.MessageCreate) error { + // Ignore bot messages + if m.Author.Bot { + return nil + } + + // Get user from database + var userID int + err := DBClient.QueryRow("SELECT id FROM users WHERE discord_id = ?", m.Author.ID).Scan(&userID) + if err != nil { + return fmt.Errorf("failed to get user: %w", err) + } + + // Ensure himbucks entry exists + err = GetOrCreateHimbucksEntry(userID, m.GuildID) + if err != nil { + return fmt.Errorf("failed to create himbucks entry: %w", err) + } + + // Update message count and check for rewards + tx, err := DBClient.Begin() + if err != nil { + return fmt.Errorf("failed to start transaction: %w", err) + } + defer tx.Rollback() + + var messageCount int + var lastEarnedAt sql.NullTime + + err = tx.QueryRow(` + SELECT message_count, last_earned_at + FROM himbucks + WHERE user_id = ? AND guild_id = ?`, + userID, m.GuildID).Scan(&messageCount, &lastEarnedAt) + if err != nil { + return fmt.Errorf("failed to get message count: %w", err) + } + + messageCount++ + shouldReward := messageCount >= MessageCountThreshold && + (!lastEarnedAt.Valid || time.Since(lastEarnedAt.Time) >= CooldownPeriod) + + if shouldReward { + _, err = tx.Exec(` + UPDATE himbucks + SET balance = balance + ?, message_count = 0, last_earned_at = CURRENT_TIMESTAMP + WHERE user_id = ? AND guild_id = ?`, + HimbucksPerReward, userID, m.GuildID) + } else { + _, err = tx.Exec(` + UPDATE himbucks + SET message_count = ? + WHERE user_id = ? AND guild_id = ?`, + messageCount, userID, m.GuildID) + } + + if err != nil { + return fmt.Errorf("failed to update himbucks: %w", err) + } + + return tx.Commit() +} + +func GetBalance(discordID, guildID string) (int, error) { + var balance int + err := DBClient.QueryRow(` + SELECT h.balance + FROM himbucks h + JOIN users u ON h.user_id = u.id + WHERE u.discord_id = ? AND h.guild_id = ?`, + discordID, guildID).Scan(&balance) + if err == sql.ErrNoRows { + return 0, nil + } + if err != nil { + return 0, fmt.Errorf("failed to get balance: %w", err) + } + return balance, nil +} + +func GetLeaderboard(guildID string, limit int) ([]HimbucksEntry, error) { + rows, err := DBClient.Query(` + SELECT u.username, h.balance, h.message_count + FROM himbucks h + JOIN users u ON h.user_id = u.id + WHERE h.guild_id = ? + ORDER BY h.balance DESC + LIMIT ?`, + guildID, limit) + if err != nil { + return nil, fmt.Errorf("failed to get leaderboard: %w", err) + } + defer rows.Close() + + var entries []HimbucksEntry + for rows.Next() { + var entry HimbucksEntry + err := rows.Scan(&entry.Username, &entry.Balance, &entry.MessageCount) + if err != nil { + return nil, fmt.Errorf("failed to scan leaderboard entry: %w", err) + } + entries = append(entries, entry) + } + return entries, nil +} diff --git a/main.go b/main.go index 4545c4b..5e92840 100644 --- a/main.go +++ b/main.go @@ -43,12 +43,22 @@ var ( }, }, }, + { + Name: "balance", + Description: "Check your himbucks balance", + }, + { + Name: "leaderboard", + Description: "View the himbucks leaderboard", + }, } commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ - "ping": lib.HandleCommand("ping", 5*time.Second, command.PingCommand), - "hs": lib.HandleCommand("hs", 10*time.Second, command.HsCommand), - "markov": lib.HandleCommand("markov", 30*time.Second, command.MarkovCommand), + "ping": lib.HandleCommand("ping", 5*time.Second, command.PingCommand), + "hs": lib.HandleCommand("hs", 10*time.Second, command.HsCommand), + "markov": lib.HandleCommand("markov", 30*time.Second, command.MarkovCommand), + "balance": lib.HandleCommand("balance", 5*time.Second, command.BalanceCommand), + "leaderboard": lib.HandleCommand("leaderboard", 5*time.Second, command.LeaderboardCommand), } ) @@ -74,7 +84,13 @@ func main() { dg.AddHandler(ready) dg.AddHandler(interactionCreate) - dg.Identify.Intents = discordgo.IntentsGuilds + dg.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { + if err := lib.ProcessMessage(s, m); err != nil { + log.Printf("Error processing message for himbucks: %v", err) + } + }) + + dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages err = dg.Open() if err != nil { diff --git a/migrations/000003_create_himbucks_table.down.sql b/migrations/000003_create_himbucks_table.down.sql new file mode 100644 index 0000000..71a3db3 --- /dev/null +++ b/migrations/000003_create_himbucks_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS himbucks; diff --git a/migrations/000003_create_himbucks_table.up.sql b/migrations/000003_create_himbucks_table.up.sql new file mode 100644 index 0000000..3f39f0e --- /dev/null +++ b/migrations/000003_create_himbucks_table.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS himbucks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + guild_id TEXT NOT NULL, + balance INTEGER DEFAULT 0, + message_count INTEGER DEFAULT 0, + last_earned_at DATETIME, + FOREIGN KEY (user_id) REFERENCES users(id), + UNIQUE(user_id, guild_id) +);