package main import ( "log" "os" "os/signal" "syscall" "github.com/bwmarrin/discordgo" "ffxiv-bot/commands" "ffxiv-bot/config" "ffxiv-bot/db" "ffxiv-bot/partyfinder" "ffxiv-bot/reactionroles" "ffxiv-bot/roster" "ffxiv-bot/tomestone" ) func main() { cfg, err := config.Load("config.yaml") if err != nil { log.Fatalf("Failed to load config: %v", err) } if cfg.Bot.Token == "" { log.Fatal("Bot token is not set in config.yaml") } d, err := discordgo.New("Bot " + cfg.Bot.Token) if err != nil { log.Fatalf("Error creating Discord session: %v", err) } dbd, err := db.New(cfg.Database.Path) if err != nil { log.Fatalf("Error opening database: %v", err) } defer dbd.Close() // Open Discord connection first so d.State.User is available if err := d.Open(); err != nil { log.Fatalf("Error opening Discord connection: %v", err) } defer d.Close() log.Printf("Bot is running as %s", d.State.User.String()) // Register slash commands (must happen after Open, needs bot user ID) var rosterRefresh func() var tomestoneLookup func(firstName, lastName, server string) var tomestoneFetchAll func(fetchAll bool) var tomestoneFetchUser func(discordID string, fetchAll bool) if cfg.Bot.RosterChannelID != "" { rm := &roster.Manager{ DB: dbd, ChannelID: cfg.Bot.RosterChannelID, Session: d, ActivityIDToLabel: cfg.ActivityIDToLabel, } rosterRefresh = rm.Refresh tomestoneLookup = func(firstName, lastName, server string) { tomestone.LookupAndStoreCharacterID(dbd, firstName, lastName, server) } tomestoneFetchAll = func(fetchAll bool) { tomestone.FetchAllProgression(dbd, fetchAll) } tomestoneFetchUser = func(discordID string, fetchAll bool) { tomestone.FetchProgressionForUser(dbd, discordID, fetchAll) } } handler := &commands.Handler{ DB: dbd, GuildID: cfg.Bot.GuildID, AdminRoleID: cfg.Bot.AdminRoleID, RosterRefresh: rosterRefresh, TomestoneLookup: tomestoneLookup, TomestoneFetchAll: tomestoneFetchAll, TomestoneFetchUser: tomestoneFetchUser, } if err := handler.Register(d); err != nil { log.Fatalf("Error registering commands: %v", err) } d.AddHandler(handler.HandleInteraction) // Initial fetch progression and roster refresh on startup if tomestoneFetchAll != nil { go func() { tomestoneFetchAll(true) rosterRefresh() }() } // Register reaction roles if channel is configured if cfg.Bot.RoleReactChannelID != "" { rr := &reactionroles.Handler{ GuildID: cfg.Bot.GuildID, ChannelID: cfg.Bot.RoleReactChannelID, MsgRoleMap: cfg.DiscordMsgToDiscordRole, } rr.Register(d) } // Start party finder if channel is configured if cfg.Bot.PartyFinderChannelID != "" { pf := partyfinder.New(d, cfg.Bot.PartyFinderChannelID, dbd, cfg.Bot.ScrapeIntervalMin, cfg.EmojiIDs, cfg.RoleEmojiMap, cfg.GameRoleToDiscordRole, cfg.GameContentToDiscordRole) go pf.Run() } // Wait for interrupt signal stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) <-stop log.Println("Shutting down...") }