starting comment mountain

This commit is contained in:
2026-02-24 19:04:33 -05:00
parent 01dd3633ef
commit 4732e94c1b
2 changed files with 41 additions and 29 deletions

View File

@@ -2,6 +2,7 @@
TODO TODO
pre-1. Break up msg handler method its insane 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 1. Read through code and ensure I didn't miss anything
2. do research on intents for 'admin' jobs 2. do research on intents for 'admin' jobs
3. comments and README updates, things have changed 3. comments and README updates, things have changed

69
bolt.go
View File

@@ -15,25 +15,35 @@ import (
) )
const ( 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" TOKEN_ENV_VAR = "DISCORD_TOKEN"
//bot defaults //bot default command indicator, if messages begin with this substring they are processed
DEFAULT_INDICATOR = "." //through the command handler instead of the generic message handler
DEFAULT_MAX_GOROUTINES = 50 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 { type bolt struct {
*dg.Session //holds discordgo internals //discordgo internals
commands map[string]Command //maps trigger phrase to command struct for fast lookup *dg.Session
indicator string //the indicator used to detect whether a message is a command //maps trigger phrase to command struct for instant lookup
logLvl LogLevel //determines how much the bot logs commands map[string]Command
wg sync.WaitGroup //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{} pool chan struct{}
maxRoutines int maxRoutines int
//generic message handler func
msgHandlerf Payload msgHandlerf Payload
// admin bool
} }
type Bolt interface { type Bolt interface {
@@ -77,13 +87,15 @@ func New(opts ...Option) (Bolt, error) {
opt(b) 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.pool = make(chan struct{}, b.maxRoutines)
// b.tools = NewToolbox(b)
return b, nil 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 { func (b *bolt) Start() error {
b.AddHandler(b.msgEventHandler) b.AddHandler(b.msgEventHandler)
err := b.Open() err := b.Open()
@@ -117,21 +129,27 @@ func (b *bolt) Start() error {
return b.stop() return b.stop()
} }
func (b *bolt) stop() error { // AddCommands registers command handlers, any messages that begin with the command indicator will be forwarded
return b.Close() // to a handler if the command string matches a trigger
}
// adds commands to bot command map for use
func (b *bolt) AddCommands(cmd ...Command) { func (b *bolt) AddCommands(cmd ...Command) {
for _, c := range cmd { for _, c := range cmd {
b.commands[c.Trigger] = c 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) { func (b *bolt) AddMessageHandler(p Payload) {
b.msgHandlerf = p 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) { func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) {
//get server information //get server information
server, err := s.Guild(msg.GuildID) 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 //the bot will ignore it's own messages to prevent command loops
if msg.Author.ID == s.State.User.ID { if msg.Author.ID == s.State.User.ID {
if b.logLvl != LogLevelErr && b.logLvl != LogLevelNone { 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) log.Printf("< %s | %s | %s > %s\n", server.Name, channel.Name, msg.Author.Username, msg.Content)
} }
return return
} }
if b.logLvl == LogLevelAll { if b.logLvl == LogLevelAll {
//log message
log.Printf("< %s | %s | %s > %s\n", server.Name, channel.Name, msg.Author.Username, msg.Content) 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, ChannelID: channel.ID,
Server: server.Name, Server: server.Name,
ServerID: server.ID, ServerID: server.ID,
// sesh: b,
} }
w := strings.Fields(msg.Content) w := strings.Fields(msg.Content)
@@ -203,21 +218,13 @@ func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) {
m.Attachments = att 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) lg := len(b.indicator)
if msg.Content[:lg] == b.indicator { if msg.Content[:lg] == b.indicator {
if b.logLvl == LogLevelCmd { if b.logLvl == LogLevelCmd {
//log commands
log.Printf("< %s | %s | %s > %s\n", m.Server, m.Channel, m.Author.Name, m.Content) log.Printf("< %s | %s | %s > %s\n", m.Server, m.Channel, m.Author.Name, m.Content)
} }
b.pool <- struct{}{} //'aquire' a routine b.pool <- struct{}{} //'aquire' a routine
//handled in its own goroutine to allow for async commands
b.wg.Go(func() { b.wg.Go(func() {
err := b.handleCommand(&m, lg) err := b.handleCommand(&m, lg)
if err != nil { 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 { func (b *bolt) handleMessage(event *Message) error {
if b.msgHandlerf != nil { if b.msgHandlerf != nil {
return b.msgHandlerf(&Context{ return b.msgHandlerf(&Context{
@@ -248,6 +256,9 @@ func (b *bolt) handleMessage(event *Message) error {
return nil 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 { func (b *bolt) handleCommand(msg *Message, lg int) error {
run, ok := b.commands[msg.Words[0][lg:]] run, ok := b.commands[msg.Words[0][lg:]]
if !ok { if !ok {