From 4732e94c1bd71b48f46cbc4755e8b483e3689c86 Mon Sep 17 00:00:00 2001 From: jake Date: Tue, 24 Feb 2026 19:04:33 -0500 Subject: [PATCH] starting comment mountain --- README.md | 1 + bolt.go | 69 ++++++++++++++++++++++++++++++++----------------------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 9af7bb3..832f94c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ TODO pre-1. Break up msg handler method its insane +pre-2. Copy main into README 1. Read through code and ensure I didn't miss anything 2. do research on intents for 'admin' jobs 3. comments and README updates, things have changed diff --git a/bolt.go b/bolt.go index 97bd94a..320f79e 100644 --- a/bolt.go +++ b/bolt.go @@ -15,25 +15,35 @@ import ( ) const ( - //Environment variable name for discord token, this is the only required variable + //the name of the environment variable that should contain the token for the bot, it is + //required for bolt to run TOKEN_ENV_VAR = "DISCORD_TOKEN" - //bot defaults - DEFAULT_INDICATOR = "." - DEFAULT_MAX_GOROUTINES = 50 + //bot default command indicator, if messages begin with this substring they are processed + //through the command handler instead of the generic message handler + DEFAULT_INDICATOR = "." + //max amount of concurrent goroutines that bolt can use for events. A lower amount + //may lower the resource usage of bolt but may cause a delay in event handling + DEFAULT_MAX_GOROUTINES = 250 ) -// basic bot structure containing discordgo connection as well as the command map type bolt struct { - *dg.Session //holds discordgo internals - commands map[string]Command //maps trigger phrase to command struct for fast lookup - indicator string //the indicator used to detect whether a message is a command - logLvl LogLevel //determines how much the bot logs - wg sync.WaitGroup + //discordgo internals + *dg.Session + //maps trigger phrase to command struct for instant lookup + commands map[string]Command + //used to detect whether a message is a command + indicator string + //verbosity of logs bolt outputs + logLvl LogLevel + //waitgroup for event routines + wg sync.WaitGroup + //pool is a buffered channel used as a semaphore for event handler routines, it is limited to + //only spawn maxRoutines to handle events pool chan struct{} maxRoutines int + //generic message handler func msgHandlerf Payload - // admin bool } type Bolt interface { @@ -77,13 +87,15 @@ func New(opts ...Option) (Bolt, error) { opt(b) } - //options can change these fields so we must create post-opts + //options can change max routine number, so create after b.pool = make(chan struct{}, b.maxRoutines) - // b.tools = NewToolbox(b) return b, nil } +// Start applies the message event handler function to the bot and opens the initial websocket connection +// with Discord. Start is a blocking call that also handles safe shutdown, on Interrupt, bolt will give +// command routines a window to finish before closing the connection. func (b *bolt) Start() error { b.AddHandler(b.msgEventHandler) err := b.Open() @@ -117,21 +129,27 @@ func (b *bolt) Start() error { return b.stop() } -func (b *bolt) stop() error { - return b.Close() -} - -// adds commands to bot command map for use +// AddCommands registers command handlers, any messages that begin with the command indicator will be forwarded +// to a handler if the command string matches a trigger func (b *bolt) AddCommands(cmd ...Command) { for _, c := range cmd { b.commands[c.Trigger] = c } } +// AddMessageHandler registers the generic message handler, any messages that are not commands will be forwarded +// to the Payload func (b *bolt) AddMessageHandler(p Payload) { b.msgHandlerf = p } +// stop closes the websocket connection with Discord +func (b *bolt) stop() error { + return b.Close() +} + +// msgEventHandler is a beefy boy that handles message logging, command parsing, and executing payload functions. It needs cleanup then +// i'll worry about this comment func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) { //get server information server, err := s.Guild(msg.GuildID) @@ -148,14 +166,12 @@ func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) { //the bot will ignore it's own messages to prevent command loops if msg.Author.ID == s.State.User.ID { if b.logLvl != LogLevelErr && b.logLvl != LogLevelNone { - //log command responses log.Printf("< %s | %s | %s > %s\n", server.Name, channel.Name, msg.Author.Username, msg.Content) } return } if b.logLvl == LogLevelAll { - //log message log.Printf("< %s | %s | %s > %s\n", server.Name, channel.Name, msg.Author.Username, msg.Content) } @@ -172,7 +188,6 @@ func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) { ChannelID: channel.ID, Server: server.Name, ServerID: server.ID, - // sesh: b, } w := strings.Fields(msg.Content) @@ -203,21 +218,13 @@ func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) { m.Attachments = att } - //using a patter based on a stackoverflow comment I saw that mentioned the use of a buffered channel as a lock (semaphore) - //to limit the amount of goroutines used at once - - //could be an issue if the bot is used like a long-term calendar, not sure that is my concern we now have a timeout so it will only wait so long - lg := len(b.indicator) if msg.Content[:lg] == b.indicator { if b.logLvl == LogLevelCmd { - //log commands log.Printf("< %s | %s | %s > %s\n", m.Server, m.Channel, m.Author.Name, m.Content) } b.pool <- struct{}{} //'aquire' a routine - - //handled in its own goroutine to allow for async commands b.wg.Go(func() { err := b.handleCommand(&m, lg) if err != nil { @@ -237,6 +244,7 @@ func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) { } } +// handleMessage forwards the message data to the handler function, if one was set func (b *bolt) handleMessage(event *Message) error { if b.msgHandlerf != nil { return b.msgHandlerf(&Context{ @@ -248,6 +256,9 @@ func (b *bolt) handleMessage(event *Message) error { return nil } +// handleCommand maps the first word of the message to the command payload, if it exists. Checking the timeout +// and role restrictions before forwarding the message to the Command Payload. If restrictions have not been met +// a response is sent to the message func (b *bolt) handleCommand(msg *Message, lg int) error { run, ok := b.commands[msg.Words[0][lg:]] if !ok {