Spending lots of time on useless discord bots makes to make the pain go away

This commit is contained in:
Atridad Lahiji 2024-11-05 18:01:58 -06:00
parent 82c62658bb
commit 5a1158048d
Signed by: atridad
SSH key fingerprint: SHA256:LGomp8Opq0jz+7kbwNcdfTcuaLRb5Nh0k5AchDDb438
10 changed files with 162 additions and 125 deletions

View file

@ -1,7 +1,6 @@
package lib package lib
import ( import (
"log"
"time" "time"
"github.com/bwmarrin/discordgo" "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) { func HandleCommand(commandName string, cooldownDuration time.Duration, handler CommandFunc) func(s *discordgo.Session, i *discordgo.InteractionCreate) {
return func(s *discordgo.Session, i *discordgo.InteractionCreate) { return func(s *discordgo.Session, i *discordgo.InteractionCreate) {
// Get user information first if !CheckAndApplyCooldown(s, i, commandName, cooldownDuration) {
user, userErr := GetUser(i)
if userErr != nil {
RespondWithError(s, i, "Error processing command: "+userErr.Error())
return return
} }
// Store user in database // Get or create user and guild profile
dbErr := StoreUser(user.ID, user.Username) _, createUserError := GetOrCreateUserWithGuild(i.Member.User.ID, i.Member.User.Username, i.GuildID)
if dbErr != nil {
// Log the error but don't stop command execution
log.Printf("Error storing user: %v", dbErr)
}
if !CheckAndApplyCooldown(s, i, commandName, cooldownDuration) { if createUserError != nil {
RespondWithError(s, i, "Error creating user profile: "+createUserError.Error())
return return
} }

View file

@ -154,27 +154,3 @@ func runMigrations() error {
log.Println("Database migrations completed successfully") log.Println("Database migrations completed successfully")
return nil 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
}

View file

@ -20,92 +20,48 @@ type HimbucksEntry struct {
MessageCount int MessageCount int
} }
func ProcessMessage(s *discordgo.Session, m *discordgo.MessageCreate) error { func ProcessHimbucks(s *discordgo.Session, m *discordgo.MessageCreate, ctx *ProcessContext) error {
// Ignore bot messages
if m.Author.Bot {
return nil
}
// Start transaction for user creation/lookup and himbucks entry
tx, err := DBClient.Begin() tx, err := DBClient.Begin()
if err != nil { if err != nil {
return fmt.Errorf("failed to start transaction: %w", err) return fmt.Errorf("failed to start transaction: %w", err)
} }
defer tx.Rollback() defer tx.Rollback()
// Get user from database, create if doesn't exist // Get current state
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 messageCount int
var lastEarnedAt sql.NullTime var lastRewardAt sql.NullTime
err = tx.QueryRow(` err = tx.QueryRow(`
SELECT message_count, last_earned_at SELECT message_count, last_reward_at
FROM himbucks FROM guild_profiles
WHERE user_id = ? AND guild_id = ?`, WHERE user_id = ? AND guild_id = ?`,
userID, m.GuildID).Scan(&messageCount, &lastEarnedAt) ctx.UserID, ctx.GuildID).Scan(&messageCount, &lastRewardAt)
if err != nil { if err != nil {
return fmt.Errorf("failed to get message count: %w", err) return fmt.Errorf("failed to get message count: %w", err)
} }
messageCount++ messageCount++
shouldReward := messageCount >= MessageCountThreshold && shouldReward := messageCount >= MessageCountThreshold &&
(!lastEarnedAt.Valid || time.Since(lastEarnedAt.Time) >= CooldownPeriod) (!lastRewardAt.Valid || time.Since(lastRewardAt.Time) >= CooldownPeriod)
if shouldReward { if shouldReward {
_, err = tx.Exec(` _, err = tx.Exec(`
UPDATE himbucks UPDATE guild_profiles
SET balance = balance + ?, message_count = 0, last_earned_at = CURRENT_TIMESTAMP SET currency_balance = currency_balance + ?,
message_count = 0,
last_reward_at = CURRENT_TIMESTAMP
WHERE user_id = ? AND guild_id = ?`, WHERE user_id = ? AND guild_id = ?`,
HimbucksPerReward, userID, m.GuildID) HimbucksPerReward, ctx.UserID, ctx.GuildID)
} else { } else {
_, err = tx.Exec(` _, err = tx.Exec(`
UPDATE himbucks UPDATE guild_profiles
SET message_count = ? SET message_count = ?
WHERE user_id = ? AND guild_id = ?`, WHERE user_id = ? AND guild_id = ?`,
messageCount, userID, m.GuildID) messageCount, ctx.UserID, ctx.GuildID)
} }
if err != nil { if err != nil {
return fmt.Errorf("failed to update himbucks: %w", err) return fmt.Errorf("failed to update profile: %w", err)
} }
return tx.Commit() return tx.Commit()
@ -113,25 +69,24 @@ func ProcessMessage(s *discordgo.Session, m *discordgo.MessageCreate) error {
func GetBalance(discordID, guildID string) (int, error) { func GetBalance(discordID, guildID string) (int, error) {
var balance int var balance int
err := DBClient.QueryRow(`
queryError := DBClient.QueryRow(` SELECT gp.currency_balance
SELECT h.balance FROM guild_profiles gp
FROM himbucks h JOIN users u ON gp.user_id = u.id
JOIN users u ON h.user_id = u.id WHERE u.discord_id = ? AND gp.guild_id = ?`,
WHERE u.discord_id = ? AND h.guild_id = ?`,
discordID, guildID).Scan(&balance) discordID, guildID).Scan(&balance)
if queryError == sql.ErrNoRows { if err == sql.ErrNoRows {
return 0, nil return 0, nil
} }
if queryError != nil { if err != nil {
return 0, fmt.Errorf("failed to get balance: %w", queryError) return 0, fmt.Errorf("failed to get balance: %w", err)
} }
return balance, nil return balance, nil
} }
func SyncBalance() error { func SyncBalance() error {
// First perform the regular sync
_, syncError := DBConnector.Sync() _, syncError := DBConnector.Sync()
if syncError != nil { if syncError != nil {
fmt.Println("Error syncing database:", syncError) fmt.Println("Error syncing database:", syncError)
return syncError return syncError
@ -142,12 +97,12 @@ func SyncBalance() error {
func GetLeaderboard(guildID string, limit int) ([]HimbucksEntry, error) { func GetLeaderboard(guildID string, limit int) ([]HimbucksEntry, error) {
rows, err := DBClient.Query(` rows, err := DBClient.Query(`
SELECT u.username, h.balance, h.message_count SELECT u.username, gp.currency_balance, gp.message_count
FROM himbucks h FROM guild_profiles gp
JOIN users u ON h.user_id = u.id JOIN users u ON gp.user_id = u.id
WHERE h.guild_id = ? WHERE gp.guild_id = ?
ORDER BY h.balance DESC ORDER BY gp.currency_balance DESC
LIMIT ?`, LIMIT ?`,
guildID, limit) guildID, limit)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get leaderboard: %w", err) return nil, fmt.Errorf("failed to get leaderboard: %w", err)

63
lib/processor.go Normal file
View file

@ -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())
}
}
}

43
lib/users.go Normal file
View file

@ -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
}

View file

@ -89,10 +89,13 @@ func main() {
dg.AddHandler(ready) dg.AddHandler(ready)
dg.AddHandler(interactionCreate) dg.AddHandler(interactionCreate)
processorManager := lib.NewMessageProcessorManager()
// Register processors
processorManager.RegisterProcessor(lib.ProcessHimbucks)
dg.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) { dg.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
if err := lib.ProcessMessage(s, m); err != nil { processorManager.ProcessMessage(s, m)
log.Printf("Error processing message for himbucks: %v", err)
}
}) })
dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages

View file

@ -0,0 +1 @@
DROP TABLE IF EXISTS guild_profiles;

View file

@ -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)
);

View file

@ -1 +0,0 @@
DROP TABLE IF EXISTS himbucks;

View file

@ -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)
);