Spending lots of time on useless discord bots makes to make the pain go away
This commit is contained in:
parent
82c62658bb
commit
5a1158048d
10 changed files with 162 additions and 125 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
24
lib/db.go
24
lib/db.go
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
105
lib/himbucks.go
105
lib/himbucks.go
|
@ -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
63
lib/processor.go
Normal 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
43
lib/users.go
Normal 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
|
||||||
|
}
|
9
main.go
9
main.go
|
@ -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
|
||||||
|
|
1
migrations/000003_create_guild_profiles_table.down.sql
Normal file
1
migrations/000003_create_guild_profiles_table.down.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE IF EXISTS guild_profiles;
|
14
migrations/000003_create_guild_profiles_table.up.sql
Normal file
14
migrations/000003_create_guild_profiles_table.up.sql
Normal 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)
|
||||||
|
);
|
|
@ -1 +0,0 @@
|
||||||
DROP TABLE IF EXISTS himbucks;
|
|
|
@ -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)
|
|
||||||
);
|
|
Loading…
Add table
Reference in a new issue