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 ProcessMessage(s *discordgo.Session, m *discordgo.MessageCreate) error { // Ignore bot messages if m.Author.Bot { return nil } // Start transaction for user creation/lookup and himbucks entry tx, err := DBClient.Begin() if err != nil { return fmt.Errorf("failed to start transaction: %w", err) } defer tx.Rollback() // Get user from database, create if doesn't exist var userID int err = tx.QueryRow("SELECT id FROM users WHERE discord_id = ?", m.Author.ID).Scan(&userID) if err == sql.ErrNoRows { // Store user in database using existing StoreUser function within transaction result, err := tx.Exec( "INSERT INTO users (discord_id, username) VALUES (?, ?)", m.Author.ID, m.Author.Username) if err != nil { return fmt.Errorf("failed to store user: %w", err) } id, err := result.LastInsertId() if err != nil { return fmt.Errorf("failed to get new user ID: %w", err) } userID = int(id) // Immediately create corresponding himbucks entry _, err = tx.Exec(` INSERT INTO himbucks (user_id, guild_id, balance, message_count) VALUES (?, ?, 0, 0)`, userID, m.GuildID) if err != nil { return fmt.Errorf("failed to create initial himbucks entry: %w", err) } } else if err != nil { return fmt.Errorf("failed to query user: %w", err) } // For existing users, ensure himbucks entry exists _, err = tx.Exec(` INSERT INTO himbucks (user_id, guild_id, balance, message_count) VALUES (?, ?, 0, 0) ON CONFLICT (user_id, guild_id) DO NOTHING`, userID, m.GuildID) if err != nil { return fmt.Errorf("failed to create himbucks entry: %w", err) } // Process the message count and rewards 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 _, syncError := DBConnector.Sync() if syncError != nil { fmt.Println("Error syncing database:", syncError) } queryError := 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 queryError == sql.ErrNoRows { return 0, nil } if queryError != nil { return 0, fmt.Errorf("failed to get balance: %w", queryError) } 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 }