package main import ( "embed" "encoding/json" "fmt" "log" "math/rand" "os" "os/signal" "syscall" "time" "github.com/bwmarrin/discordgo" "github.com/joho/godotenv" ) // Embed JSON files at build time //go:embed praises.json var praisesFile embed.FS //go:embed conspiracies.json var conspiraciesFile embed.FS var ( praises []string conspiracies []string ) const ( // TODO: update value after debugging sendMessageIntervalMin = 10 // TODO: update value after debugging sendMessageIntervalMax = 20 // TODO: update value after debugging sendMessageUnit = time.Second // TODO: update value after debugging deleteConspiracyDelay = 5 * time.Second ) func main() { // Load environment variables if err := godotenv.Load(); err != nil { log.Println("[!] No .env file found, using system environment variables.") } // Retrieve token and channel ID botToken, channelID := os.Getenv("DISCORD_BOT_TOKEN"), os.Getenv("DISCORD_CHANNEL_ID") if botToken == "" || channelID == "" { log.Fatal("[x] Missing DISCORD_BOT_TOKEN or DISCORD_CHANNEL_ID in .env") } // Load messages at build time if err := loadMessages(); err != nil { log.Fatalf("[x] Error loading messages: %v", err) } // Initialize bot session dg, err := discordgo.New("Bot " + botToken) if err != nil { log.Fatalf("[x] Error creating Discord session: %v", err) } // Open WebSocket connection dg.AddHandler(readyHandler) if err := dg.Open(); err != nil { log.Fatalf("[x] Error opening connection: %v", err) } defer dg.Close() // Start the message scheduler go startScheduler(dg, channelID) // Graceful shutdown handling log.Println("[✓] Swiftspiracy Bot is now running. Press CTRL+C to exit.") sc := make(chan os.Signal, 1) signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) <-sc log.Println("[↓] Shutting down bot gracefully...") } // loadMessages loads JSON files into memory at build time func loadMessages() error { var err error if praises, err = loadJSONFromEmbed(praisesFile, "praises.json"); err != nil { return fmt.Errorf("failed to load praises.json: %w", err) } if conspiracies, err = loadJSONFromEmbed(conspiraciesFile, "conspiracies.json"); err != nil { return fmt.Errorf("failed to load conspiracies.json: %w", err) } return nil } // loadJSONFromEmbed reads JSON from an embedded file system func loadJSONFromEmbed(fs embed.FS, filename string) ([]string, error) { data, err := fs.ReadFile(filename) if err != nil { return nil, err } var messages []string if err := json.Unmarshal(data, &messages); err != nil { return nil, err } return messages, nil } // readyHandler confirms the bot is online func readyHandler(s *discordgo.Session, r *discordgo.Ready) { log.Println("[✓] Swiftspiracy Bot is online and ready!") } // startScheduler handles sending messages at intervals func startScheduler(s *discordgo.Session, channelID string) { for { randomDuration := time.Duration(rand.Intn(sendMessageIntervalMin)+(sendMessageIntervalMax-sendMessageIntervalMin)) * sendMessageUnit log.Printf("[!] Next message in %v", randomDuration) // Wait for the randomized time before sending a message time.Sleep(randomDuration) sendMessage(praises, s, channelID) // Chance to send a conspiracy theory if rand.Float32() < 0.4 { msg := sendMessage(conspiracies, s, channelID) // Delete conspiracy after 5 minutes if msg != nil { go func(msgID string) { time.Sleep(deleteConspiracyDelay) err := s.ChannelMessageDelete(channelID, msgID) if err != nil { log.Printf("[!] Error deleting conspiracy: %v\n", err) } else { log.Println("[↓] Conspiracy deleted!") } }(msg.ID) } } } } // sendMessage sends a random message from a given list func sendMessage(messages []string, s *discordgo.Session, channelID string) *discordgo.Message { if len(messages) == 0 { log.Println("[!] No messages available.") return nil } message := messages[rand.Intn(len(messages))] discordMessage, err := s.ChannelMessageSend(channelID, message) if err != nil { log.Printf("[x] Error sending message: %v\n", err) return nil } return discordMessage }