diff --git a/lib/command.go b/lib/command.go index 1b6ce07..845e91f 100644 --- a/lib/command.go +++ b/lib/command.go @@ -1,7 +1,6 @@ package lib import ( - "log" "time" "github.com/bwmarrin/discordgo" @@ -11,21 +10,15 @@ type CommandFunc func(s *discordgo.Session, i *discordgo.InteractionCreate) (str func HandleCommand(commandName string, cooldownDuration time.Duration, handler CommandFunc) func(s *discordgo.Session, i *discordgo.InteractionCreate) { return func(s *discordgo.Session, i *discordgo.InteractionCreate) { - // Get user information first - user, userErr := GetUser(i) - if userErr != nil { - RespondWithError(s, i, "Error processing command: "+userErr.Error()) + if !CheckAndApplyCooldown(s, i, commandName, cooldownDuration) { return } - // Store user in database - dbErr := StoreUser(user.ID, user.Username) - if dbErr != nil { - // Log the error but don't stop command execution - log.Printf("Error storing user: %v", dbErr) - } + // Get or create user and guild profile + _, createUserError := GetOrCreateUserWithGuild(i.Member.User.ID, i.Member.User.Username, i.GuildID) - if !CheckAndApplyCooldown(s, i, commandName, cooldownDuration) { + if createUserError != nil { + RespondWithError(s, i, "Error creating user profile: "+createUserError.Error()) return } diff --git a/lib/db.go b/lib/db.go index 7c14eb8..a896ef4 100644 --- a/lib/db.go +++ b/lib/db.go @@ -154,27 +154,3 @@ func runMigrations() error { log.Println("Database migrations completed successfully") return nil } - -func StoreUser(discordID, username string) error { - // Check if user exists - var exists bool - err := DBClient.QueryRow( - "SELECT EXISTS(SELECT 1 FROM users WHERE discord_id = ?)", - discordID).Scan(&exists) - if err != nil { - return fmt.Errorf("failed to check user existence: %w", err) - } - - // If user doesn't exist, insert them - if !exists { - _, err = DBClient.Exec( - "INSERT INTO users (discord_id, username) VALUES (?, ?)", - discordID, username) - if err != nil { - return fmt.Errorf("failed to store user: %w", err) - } - log.Printf("New user stored: %s (%s)", username, discordID) - } - - return nil -} diff --git a/lib/himbucks.go b/lib/himbucks.go index 3d1038a..4506513 100644 --- a/lib/himbucks.go +++ b/lib/himbucks.go @@ -20,92 +20,48 @@ type HimbucksEntry struct { 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 +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 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 + // Get current state var messageCount int - var lastEarnedAt sql.NullTime + var lastRewardAt sql.NullTime err = tx.QueryRow(` - SELECT message_count, last_earned_at - FROM himbucks + SELECT message_count, last_reward_at + FROM guild_profiles WHERE user_id = ? AND guild_id = ?`, - userID, m.GuildID).Scan(&messageCount, &lastEarnedAt) + ctx.UserID, ctx.GuildID).Scan(&messageCount, &lastRewardAt) if err != nil { return fmt.Errorf("failed to get message count: %w", err) } messageCount++ shouldReward := messageCount >= MessageCountThreshold && - (!lastEarnedAt.Valid || time.Since(lastEarnedAt.Time) >= CooldownPeriod) + (!lastRewardAt.Valid || time.Since(lastRewardAt.Time) >= CooldownPeriod) if shouldReward { _, err = tx.Exec(` - UPDATE himbucks - SET balance = balance + ?, message_count = 0, last_earned_at = CURRENT_TIMESTAMP + UPDATE guild_profiles + SET currency_balance = currency_balance + ?, + message_count = 0, + last_reward_at = CURRENT_TIMESTAMP WHERE user_id = ? AND guild_id = ?`, - HimbucksPerReward, userID, m.GuildID) + HimbucksPerReward, ctx.UserID, ctx.GuildID) } else { _, err = tx.Exec(` - UPDATE himbucks + UPDATE guild_profiles SET message_count = ? WHERE user_id = ? AND guild_id = ?`, - messageCount, userID, m.GuildID) + messageCount, ctx.UserID, ctx.GuildID) } if err != nil { - return fmt.Errorf("failed to update himbucks: %w", err) + return fmt.Errorf("failed to update profile: %w", err) } return tx.Commit() @@ -113,25 +69,24 @@ func ProcessMessage(s *discordgo.Session, m *discordgo.MessageCreate) error { func GetBalance(discordID, guildID string) (int, error) { var balance int - - 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 = ?`, + 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 queryError == sql.ErrNoRows { + if err == sql.ErrNoRows { return 0, nil } - if queryError != nil { - return 0, fmt.Errorf("failed to get balance: %w", queryError) + if err != nil { + return 0, fmt.Errorf("failed to get balance: %w", err) } return balance, nil } func SyncBalance() error { + // First perform the regular sync _, syncError := DBConnector.Sync() - if syncError != nil { fmt.Println("Error syncing database:", syncError) return syncError @@ -142,12 +97,12 @@ func SyncBalance() error { 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 ?`, + 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) diff --git a/lib/processor.go b/lib/processor.go new file mode 100644 index 0000000..89d0b8c --- /dev/null +++ b/lib/processor.go @@ -0,0 +1,63 @@ +package lib + +import ( + "fmt" + + "github.com/bwmarrin/discordgo" +) + +// Represents a function that processes a message +type MessageProcessor func(s *discordgo.Session, m *discordgo.MessageCreate, ctx *ProcessContext) error + +// Holds shared data between processors +type ProcessContext struct { + UserID int + GuildID string + DiscordUser *discordgo.User +} + +// Handles the registration and execution of message processors +type MessageProcessorManager struct { + processors []MessageProcessor +} + +// Creates a new MessageProcessorManager +func NewMessageProcessorManager() *MessageProcessorManager { + return &MessageProcessorManager{ + processors: make([]MessageProcessor, 0), + } +} + +// Adds a new message processor to the manager +func (pm *MessageProcessorManager) RegisterProcessor(processor MessageProcessor) { + pm.processors = append(pm.processors, processor) +} + +// Runs all registered processors on a message +func (pm *MessageProcessorManager) ProcessMessage(s *discordgo.Session, m *discordgo.MessageCreate) { + if m.Author.Bot { + return + } + + // Create processing context + ctx := &ProcessContext{ + GuildID: m.GuildID, + DiscordUser: m.Author, + } + + // Get or create user and guild profile together + userID, err := GetOrCreateUserWithGuild(m.Author.ID, m.Author.Username, m.GuildID) + if err != nil { + ThrowWithError("MessageProcessor", fmt.Sprintf("failed to handle user and guild profile: %v", err)) + return + } + + ctx.UserID = userID + + // Run each processor with the context + for _, processor := range pm.processors { + if err := processor(s, m, ctx); err != nil { + ThrowWithError("MessageProcessor", err.Error()) + } + } +} diff --git a/lib/users.go b/lib/users.go new file mode 100644 index 0000000..7c90bc9 --- /dev/null +++ b/lib/users.go @@ -0,0 +1,43 @@ +package lib + +import "fmt" + +func GetOrCreateUserWithGuild(discordID string, username string, guildID string) (int, error) { + var userID int + + tx, err := DBClient.Begin() + if err != nil { + return 0, fmt.Errorf("failed to start transaction: %w", err) + } + defer tx.Rollback() + + // First get or create the user + err = tx.QueryRow(` + INSERT INTO users (discord_id, username) + VALUES (?, ?) + ON CONFLICT (discord_id) + DO UPDATE SET username = excluded.username + RETURNING id`, + discordID, username).Scan(&userID) + + if err != nil { + return 0, fmt.Errorf("failed to get or create user: %w", err) + } + + // Then ensure guild profile exists for this user + _, err = tx.Exec(` + INSERT INTO guild_profiles (user_id, guild_id, currency_balance, message_count) + VALUES (?, ?, 0, 0) + ON CONFLICT (user_id, guild_id) DO NOTHING`, + userID, guildID) + + if err != nil { + return 0, fmt.Errorf("failed to create guild profile: %w", err) + } + + if err := tx.Commit(); err != nil { + return 0, fmt.Errorf("failed to commit transaction: %w", err) + } + + return userID, nil +} diff --git a/main.go b/main.go index a083394..11683e7 100644 --- a/main.go +++ b/main.go @@ -89,10 +89,13 @@ func main() { dg.AddHandler(ready) dg.AddHandler(interactionCreate) + processorManager := lib.NewMessageProcessorManager() + + // Register processors + processorManager.RegisterProcessor(lib.ProcessHimbucks) + 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) - } + processorManager.ProcessMessage(s, m) }) dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages diff --git a/migrations/000003_create_guild_profiles_table.down.sql b/migrations/000003_create_guild_profiles_table.down.sql new file mode 100644 index 0000000..53d3e5b --- /dev/null +++ b/migrations/000003_create_guild_profiles_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS guild_profiles; diff --git a/migrations/000003_create_guild_profiles_table.up.sql b/migrations/000003_create_guild_profiles_table.up.sql new file mode 100644 index 0000000..42e59a7 --- /dev/null +++ b/migrations/000003_create_guild_profiles_table.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS guild_profiles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + guild_id TEXT NOT NULL, + -- Himbucks-related fields + currency_balance INTEGER DEFAULT 0, + message_count INTEGER DEFAULT 0, + last_reward_at DATETIME, + -- Add other profile-related fields here as needed + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id), + UNIQUE(user_id, guild_id) +); diff --git a/migrations/000003_create_himbucks_table.down.sql b/migrations/000003_create_himbucks_table.down.sql deleted file mode 100644 index 71a3db3..0000000 --- a/migrations/000003_create_himbucks_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS himbucks; diff --git a/migrations/000003_create_himbucks_table.up.sql b/migrations/000003_create_himbucks_table.up.sql deleted file mode 100644 index 3f39f0e..0000000 --- a/migrations/000003_create_himbucks_table.up.sql +++ /dev/null @@ -1,10 +0,0 @@ -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) -);