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 ProcessHimbucks(s *discordgo.Session, m *discordgo.MessageCreate, ctx *ProcessContext) error { tx, err := DBClient.Begin() if err != nil { return fmt.Errorf("failed to start transaction: %w", err) } defer tx.Rollback() // Get current state var messageCount int var lastRewardAt sql.NullTime err = tx.QueryRow(` SELECT message_count, last_reward_at FROM guild_profiles WHERE user_id = ? AND guild_id = ?`, ctx.UserID, ctx.GuildID).Scan(&messageCount, &lastRewardAt) if err != nil { return fmt.Errorf("failed to get message count: %w", err) } messageCount++ shouldReward := messageCount >= MessageCountThreshold && (!lastRewardAt.Valid || time.Since(lastRewardAt.Time) >= CooldownPeriod) if shouldReward { _, err = tx.Exec(` UPDATE guild_profiles SET currency_balance = currency_balance + ?, message_count = 0, last_reward_at = CURRENT_TIMESTAMP WHERE user_id = ? AND guild_id = ?`, HimbucksPerReward, ctx.UserID, ctx.GuildID) } else { _, err = tx.Exec(` UPDATE guild_profiles SET message_count = ? WHERE user_id = ? AND guild_id = ?`, messageCount, ctx.UserID, ctx.GuildID) } if err != nil { return fmt.Errorf("failed to update profile: %w", err) } return tx.Commit() } func GetBalance(discordID, guildID string) (int, error) { var balance int err := DBClient.QueryRow(` SELECT gp.currency_balance FROM guild_profiles gp JOIN users u ON gp.user_id = u.id WHERE u.discord_id = ? AND gp.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 SendBalance(fromDiscordID, toDiscordID, guildID string, amount int) error { // Start database transaction tx, err := DBClient.Begin() if err != nil { return fmt.Errorf("failed to start transaction: %w", err) } defer tx.Rollback() // Get sender's user ID var fromUserID string err = tx.QueryRow(` SELECT id FROM users WHERE discord_id = ?`, fromDiscordID).Scan(&fromUserID) if err != nil { return fmt.Errorf("sender not found: %w", err) } // Get recipient's user ID var toUserID string err = tx.QueryRow(` SELECT id FROM users WHERE discord_id = ?`, toDiscordID).Scan(&toUserID) if err != nil { return fmt.Errorf("recipient not found: %w", err) } // Check if sender has sufficient balance var senderBalance int err = tx.QueryRow(` SELECT currency_balance FROM guild_profiles WHERE user_id = ? AND guild_id = ?`, fromUserID, guildID).Scan(&senderBalance) if err != nil { return fmt.Errorf("failed to get sender balance: %w", err) } if senderBalance < amount { return fmt.Errorf("insufficient balance: have %d, trying to send %d", senderBalance, amount) } // Deduct from sender _, err = tx.Exec(` UPDATE guild_profiles SET currency_balance = currency_balance - ? WHERE user_id = ? AND guild_id = ?`, amount, fromUserID, guildID) if err != nil { return fmt.Errorf("failed to deduct from sender: %w", err) } // Add to recipient result, err := tx.Exec(` UPDATE guild_profiles SET currency_balance = currency_balance + ? WHERE user_id = ? AND guild_id = ?`, amount, toUserID, guildID) if err != nil { return fmt.Errorf("failed to add to recipient: %w", err) } // Check if recipient exists in guild_profiles rowsAffected, err := result.RowsAffected() if err != nil { return fmt.Errorf("failed to check rows affected: %w", err) } // If recipient doesn't have a profile in this guild, create one if rowsAffected == 0 { _, err = tx.Exec(` INSERT INTO guild_profiles (user_id, guild_id, currency_balance, message_count) VALUES (?, ?, ?, 0)`, toUserID, guildID, amount) if err != nil { return fmt.Errorf("failed to create recipient profile: %w", err) } } // Commit transaction if err = tx.Commit(); err != nil { return fmt.Errorf("failed to commit transaction: %w", err) } return nil } func SyncBalance() error { _, syncError := DBConnector.Sync() if syncError != nil { fmt.Println("Error syncing database:", syncError) return syncError } return nil } func GetLeaderboard(guildID string, limit int) ([]HimbucksEntry, error) { rows, err := DBClient.Query(` SELECT u.username, gp.currency_balance, gp.message_count FROM guild_profiles gp JOIN users u ON gp.user_id = u.id WHERE gp.guild_id = ? ORDER BY gp.currency_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 }