Files
himbot/main.go
2025-05-26 20:35:50 -06:00

246 lines
6.9 KiB
Go

package main
import (
"fmt"
"himbot/command"
"himbot/lib"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/bwmarrin/discordgo"
"github.com/joho/godotenv"
)
var (
commands []*discordgo.ApplicationCommand
commandHandlers map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate)
)
func main() {
godotenv.Load(".env")
// Load configuration
config := lib.LoadConfig()
// Initialize commands and handlers with config
initCommands(config)
initCommandHandlers(config)
err := lib.InitDB()
if err != nil {
log.Fatalf("Failed to initialize database: %v", err)
}
if config.DiscordToken == "" {
log.Fatalln("No $DISCORD_TOKEN given.")
}
dg, err := discordgo.New("Bot " + config.DiscordToken)
if err != nil {
log.Fatalf("Error creating Discord session: %v", err)
}
dg.AddHandler(ready)
dg.AddHandler(interactionCreate)
processorManager := lib.NewMessageProcessorManager()
// Register processors
processorManager.RegisterProcessor(lib.ProcessHimbucks)
dg.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
processorManager.ProcessMessage(s, m)
})
dg.Identify.Intents = discordgo.IntentsGuilds | discordgo.IntentsGuildMessages
err = dg.Open()
if err != nil {
log.Fatalf("Error opening connection: %v", err)
}
log.Println("Bot is now running. Press CTRL-C to exit.")
registerCommands(dg)
sc := make(chan os.Signal, 1)
signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt)
<-sc
log.Println("Shutting down gracefully...")
if lib.DBClient != nil {
// Close prepared statements
lib.CleanupDB()
lib.DBClient.Close()
}
dg.Close()
}
func ready(s *discordgo.Session, event *discordgo.Ready) {
log.Printf("Logged in as: %v#%v", s.State.User.Username, s.State.User.Discriminator)
}
func interactionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) {
if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok {
h(s, i)
}
}
func registerCommands(s *discordgo.Session) {
log.Println("Checking command registration...")
existingCommands, err := s.ApplicationCommands(s.State.User.ID, "")
if err != nil {
log.Printf("Error fetching existing commands: %v", err)
return
}
// Create maps for easier comparison
existingMap := make(map[string]*discordgo.ApplicationCommand)
for _, cmd := range existingCommands {
existingMap[cmd.Name] = cmd
}
desiredMap := make(map[string]*discordgo.ApplicationCommand)
for _, cmd := range commands {
desiredMap[cmd.Name] = cmd
}
// Delete commands that no longer exist
for name, existingCmd := range existingMap {
if _, exists := desiredMap[name]; !exists {
log.Printf("Deleting removed command: %s", name)
err := s.ApplicationCommandDelete(s.State.User.ID, "", existingCmd.ID)
if err != nil {
log.Printf("Error deleting command %s: %v", name, err)
}
}
}
// Update or create commands
for _, desiredCmd := range commands {
if existingCmd, exists := existingMap[desiredCmd.Name]; exists {
// Check if command needs updating (simple comparison)
if !commandsEqual(existingCmd, desiredCmd) {
log.Printf("Updating command: %s", desiredCmd.Name)
_, err := s.ApplicationCommandEdit(s.State.User.ID, "", existingCmd.ID, desiredCmd)
if err != nil {
log.Printf("Error updating command %s: %v", desiredCmd.Name, err)
}
} else {
log.Printf("Command %s is up to date", desiredCmd.Name)
}
} else {
log.Printf("Creating new command: %s", desiredCmd.Name)
_, err := s.ApplicationCommandCreate(s.State.User.ID, "", desiredCmd)
if err != nil {
log.Printf("Error creating command %s: %v", desiredCmd.Name, err)
}
}
}
log.Println("Command registration completed")
}
// commandsEqual performs a basic comparison between two commands
func commandsEqual(existing, desired *discordgo.ApplicationCommand) bool {
if existing.Name != desired.Name ||
existing.Description != desired.Description ||
len(existing.Options) != len(desired.Options) {
return false
}
// Compare options (basic comparison)
for i, existingOpt := range existing.Options {
if i >= len(desired.Options) {
return false
}
desiredOpt := desired.Options[i]
if existingOpt.Name != desiredOpt.Name ||
existingOpt.Description != desiredOpt.Description ||
existingOpt.Type != desiredOpt.Type ||
existingOpt.Required != desiredOpt.Required {
return false
}
}
return true
}
// initCommands initializes command definitions with configuration
func initCommands(config *lib.Config) {
commands = []*discordgo.ApplicationCommand{
{
Name: "ping",
Description: "ping pong!",
},
{
Name: "hs",
Description: "This command was your nickname in highschool!",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "nickname",
Description: "Your nickname in highschool.",
Required: true,
},
},
},
{
Name: "markov",
Description: "Why did the Markov chain break up with its partner? Because it couldn't handle the past!",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "messages",
Description: fmt.Sprintf("Number of messages to use (default: %d, max: %d)", config.MarkovDefaultMessages, config.MarkovMaxMessages),
Required: false,
},
},
},
{
Name: "himbucks",
Description: "Check your himbucks balance",
},
{
Name: "himboard",
Description: "View the himbucks leaderboard",
},
{
Name: "sendbucks",
Description: "Send himbucks to another user",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionUser,
Name: "user",
Description: "The user to send himbucks to",
Required: true,
},
{
Type: discordgo.ApplicationCommandOptionInteger,
Name: "amount",
Description: "Amount of himbucks to send",
Required: true,
MinValue: &[]float64{1}[0],
},
},
},
}
}
// initCommandHandlers initializes command handlers with configuration
func initCommandHandlers(config *lib.Config) {
commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){
"ping": lib.HandleCommand("ping", time.Duration(config.PingCooldown)*time.Second, command.PingCommand),
"hs": lib.HandleCommand("hs", time.Duration(config.HsCooldown)*time.Second, command.HsCommand),
"markov": lib.HandleCommand("markov", time.Duration(config.MarkovCooldown)*time.Second, command.MarkovCommand),
"himbucks": lib.HandleCommand("himbucks", time.Duration(config.HimbucksCooldown)*time.Second, command.BalanceGetCommand),
"himboard": lib.HandleCommand("himboard", time.Duration(config.HimboardCooldown)*time.Second, command.LeaderboardCommand),
"sendbucks": lib.HandleCommand("sendbucks", time.Duration(config.SendbucksCooldown)*time.Second, command.BalanceSendCommand),
}
}