All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m23s
319 lines
8.2 KiB
Go
319 lines
8.2 KiB
Go
package lib
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/bwmarrin/discordgo"
|
|
)
|
|
|
|
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
|
|
var multiplier float64
|
|
|
|
err = tx.QueryRow(`
|
|
SELECT message_count, last_reward_at, COALESCE(multiplier, 1.0)
|
|
FROM guild_profiles
|
|
WHERE user_id = ? AND guild_id = ?`,
|
|
ctx.UserID, ctx.GuildID).Scan(&messageCount, &lastRewardAt, &multiplier)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get message count: %w", err)
|
|
}
|
|
|
|
messageCount++
|
|
shouldReward := messageCount >= AppConfig.MessageCountThreshold &&
|
|
(!lastRewardAt.Valid || time.Since(lastRewardAt.Time) >= AppConfig.CooldownPeriod)
|
|
|
|
if shouldReward {
|
|
reward := int(float64(AppConfig.HimbucksPerReward) * multiplier)
|
|
_, 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 = ?`,
|
|
reward, 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 := stmtGetBalance.QueryRow(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 GetLeaderboard(guildID string, limit int) ([]HimbucksEntry, error) {
|
|
rows, err := stmtGetLeaderboard.Query(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
|
|
}
|
|
|
|
func BuyMultiplier(discordID, guildID string) (float64, int, int, error) {
|
|
tx, err := DBClient.Begin()
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("failed to start transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var userID int
|
|
err = tx.QueryRow("SELECT id FROM users WHERE discord_id = ?", discordID).Scan(&userID)
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("user not found: %w", err)
|
|
}
|
|
|
|
var balance int
|
|
var currentMultiplier float64
|
|
err = tx.QueryRow(`
|
|
SELECT currency_balance, COALESCE(multiplier, 1.0)
|
|
FROM guild_profiles
|
|
WHERE user_id = ? AND guild_id = ?`,
|
|
userID, guildID).Scan(&balance, ¤tMultiplier)
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("failed to get profile: %w", err)
|
|
}
|
|
|
|
cost := int(1000 * currentMultiplier)
|
|
if balance < cost {
|
|
return 0, 0, 0, fmt.Errorf("insufficient funds: have %d, need %d", balance, cost)
|
|
}
|
|
|
|
newBalance := balance - cost
|
|
newMultiplier := currentMultiplier + 0.5
|
|
_, err = tx.Exec(`
|
|
UPDATE guild_profiles
|
|
SET currency_balance = ?,
|
|
multiplier = ?
|
|
WHERE user_id = ? AND guild_id = ?`,
|
|
newBalance, newMultiplier, userID, guildID)
|
|
if err != nil {
|
|
return 0, 0, 0, fmt.Errorf("failed to update profile: %w", err)
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
return 0, 0, 0, fmt.Errorf("failed to commit: %w", err)
|
|
}
|
|
|
|
return newMultiplier, cost, newBalance, nil
|
|
}
|
|
|
|
func BuyPizza(discordID, guildID string) (int, int, error) {
|
|
tx, err := DBClient.Begin()
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("failed to start transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var userID int
|
|
err = tx.QueryRow("SELECT id FROM users WHERE discord_id = ?", discordID).Scan(&userID)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("user not found: %w", err)
|
|
}
|
|
|
|
var balance int
|
|
err = tx.QueryRow(`
|
|
SELECT currency_balance
|
|
FROM guild_profiles
|
|
WHERE user_id = ? AND guild_id = ?`,
|
|
userID, guildID).Scan(&balance)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("failed to get profile: %w", err)
|
|
}
|
|
|
|
cost := 100
|
|
if balance < cost {
|
|
return 0, 0, fmt.Errorf("insufficient funds: have %d, need %d", balance, cost)
|
|
}
|
|
|
|
newBalance := balance - cost
|
|
_, err = tx.Exec(`
|
|
UPDATE guild_profiles
|
|
SET currency_balance = ?
|
|
WHERE user_id = ? AND guild_id = ?`,
|
|
newBalance, userID, guildID)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("failed to update balance: %w", err)
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
return 0, 0, fmt.Errorf("failed to commit transaction: %w", err)
|
|
}
|
|
|
|
return cost, newBalance, nil
|
|
}
|
|
|
|
func GiveHimbucks(discordID, guildID string, amount int) error {
|
|
tx, err := DBClient.Begin()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to start transaction: %w", err)
|
|
}
|
|
defer tx.Rollback()
|
|
|
|
var userID int
|
|
// Get user ID
|
|
err = tx.QueryRow("SELECT id FROM users WHERE discord_id = ?", discordID).Scan(&userID)
|
|
if err != nil {
|
|
return fmt.Errorf("user not found: %w", err)
|
|
}
|
|
|
|
result, err := tx.Exec(`
|
|
UPDATE guild_profiles
|
|
SET currency_balance = currency_balance + ?
|
|
WHERE user_id = ? AND guild_id = ?`,
|
|
amount, userID, guildID)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update balance: %w", err)
|
|
}
|
|
|
|
rows, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to check rows affected: %w", err)
|
|
}
|
|
|
|
if rows == 0 {
|
|
_, err = tx.Exec(`
|
|
INSERT INTO guild_profiles (user_id, guild_id, currency_balance, message_count)
|
|
VALUES (?, ?, ?, 0)`,
|
|
userID, guildID, amount)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create profile: %w", err)
|
|
}
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|