[chore] cleaning

- removing old or commented code
- removing unused or deprecated types
- commenting everything, probably too much
- split msgEventHandler logic into methods
- readme update, removed most docs the pkg site looks good
- adjusted intents option, no need for a type
This commit is contained in:
2026-02-25 13:24:27 -05:00
parent 4732e94c1b
commit b9c26c6319
6 changed files with 191 additions and 215 deletions

135
bolt.go
View File

@@ -25,6 +25,14 @@ const (
//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
//minimum intents for bots to function, intents can be changed with options
DEFAULT_INTENTS = dg.IntentGuilds |
dg.IntentGuildMembers |
dg.IntentGuildPresences |
dg.IntentMessageContent |
dg.IntentsGuildMessages |
dg.IntentGuildMessageReactions
)
type bolt struct {
@@ -53,6 +61,7 @@ type Bolt interface {
//filtered methods
stop() error
msgEventHandler(s *dg.Session, msg *dg.MessageCreate)
mapEventToMsg(msg *dg.MessageCreate, channel *dg.Channel, server *dg.Guild) Message
handleCommand(msgEvent *Message, lg int) error
handleMessage(event *Message) error
createReply(content, message, channel, guild string) *dg.MessageSend
@@ -61,6 +70,7 @@ type Bolt interface {
timeoutCheck(msgID, channelID, guildID string, s *dg.Session, run Command) (bool, error)
}
// New creates a new bolt instance and applies any supplied options
func New(opts ...Option) (Bolt, error) {
_, check := os.LookupEnv(TOKEN_ENV_VAR)
if !check {
@@ -73,16 +83,15 @@ func New(opts ...Option) (Bolt, error) {
}
b := &bolt{
Session: bot,
commands: make(map[string]Command, 0),
logLvl: LogLevelAll,
indicator: DEFAULT_INDICATOR,
wg: sync.WaitGroup{},
// admin: false,
Session: bot,
commands: make(map[string]Command, 0),
logLvl: LogLevelAll,
indicator: DEFAULT_INDICATOR,
wg: sync.WaitGroup{},
maxRoutines: DEFAULT_MAX_GOROUTINES,
}
//apply options
b.Identify.Intents = DEFAULT_INTENTS
for _, opt := range opts {
opt(b)
}
@@ -109,7 +118,7 @@ func (b *bolt) Start() error {
signal.Notify(sigChannel, os.Interrupt)
<-sigChannel
//move this to an option, maybe?
//give handler routines a 5 second window to finish processes before closing connection
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
closeChan := make(chan struct{}, 0)
@@ -129,8 +138,8 @@ func (b *bolt) Start() error {
return b.stop()
}
// 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
// AddCommands registers command handlers. Any messages that begin with the command indicator will be forwarded
// to the handler
func (b *bolt) AddCommands(cmd ...Command) {
for _, c := range cmd {
b.commands[c.Trigger] = c
@@ -138,18 +147,22 @@ func (b *bolt) AddCommands(cmd ...Command) {
}
// AddMessageHandler registers the generic message handler, any messages that are not commands will be forwarded
// to the Payload
// to the handler
func (b *bolt) AddMessageHandler(p Payload) {
b.msgHandlerf = p
}
// stop closes the websocket connection with Discord
// stop closes the websocket connection to 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
// msgEventHandler handles the routing of messages to either the command or message handlers. If LogLvl is set, logging is handled here before
// the event is mapped to the Message struct and forwarded to the handlers. Each message is handled in its own goroutine, the max allowed goroutines
// is set as a default and can be altered using options for better performance.
func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) {
//get server information
server, err := s.Guild(msg.GuildID)
@@ -165,7 +178,7 @@ 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 {
if b.logLvl != LogLevelErr {
log.Printf("< %s | %s | %s > %s\n", server.Name, channel.Name, msg.Author.Username, msg.Content)
}
return
@@ -175,7 +188,35 @@ func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) {
log.Printf("< %s | %s | %s > %s\n", server.Name, channel.Name, msg.Author.Username, msg.Content)
}
//this hsould be moved to a parseMessageEvent method
m := b.mapEventToMsg(msg, channel, server)
lg := len(b.indicator)
if msg.Content[:lg] == b.indicator {
if b.logLvl == LogLevelCmd {
log.Printf("< %s | %s | %s > %s\n", m.Server, m.Channel, m.Author.Name, m.Content)
}
b.pool <- struct{}{} //'aquire' a routine
b.wg.Go(func() {
err := b.handleCommand(&m, lg)
if err != nil {
log.Println(err)
}
<-b.pool //release routine
})
} else {
b.pool <- struct{}{} //'aquire' a routine
b.wg.Go(func() {
err := b.handleMessage(&m)
if err != nil {
log.Println(err)
}
<-b.pool //release routine
})
}
}
// mapEventToMsg maps the Discord event message struct into bolt's user-friendly Message struct
func (b *bolt) mapEventToMsg(msg *dg.MessageCreate, channel *dg.Channel, server *dg.Guild) Message {
m := Message{
Author: Author{
Name: msg.Author.Username,
@@ -218,33 +259,10 @@ func (b *bolt) msgEventHandler(s *dg.Session, msg *dg.MessageCreate) {
m.Attachments = att
}
lg := len(b.indicator)
if msg.Content[:lg] == b.indicator {
if b.logLvl == LogLevelCmd {
log.Printf("< %s | %s | %s > %s\n", m.Server, m.Channel, m.Author.Name, m.Content)
}
b.pool <- struct{}{} //'aquire' a routine
b.wg.Go(func() {
err := b.handleCommand(&m, lg)
if err != nil {
log.Println(err)
}
<-b.pool //release routine
})
} else {
b.pool <- struct{}{} //'aquire' a routine
b.wg.Go(func() {
err := b.handleMessage(&m)
if err != nil {
log.Println(err)
}
<-b.pool //release routine
})
}
return m
}
// handleMessage forwards the message data to the handler function, if one was set
// handleMessage forwards the message data to the handler function, if one is set
func (b *bolt) handleMessage(event *Message) error {
if b.msgHandlerf != nil {
return b.msgHandlerf(&Context{
@@ -256,31 +274,35 @@ 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
// handleCommand maps the first word of the message to the command payload, if it exists. It then forwards the message
// data to the handler after checking the timeout and role restrictions. If restrictions have not been met a generic
// response is sent to the message
// TODO: accept a string for timeout/role rejection messages to allow customization
func (b *bolt) handleCommand(msg *Message, lg int) error {
run, ok := b.commands[msg.Words[0][lg:]]
if !ok {
return nil //command doesn't exist, maybe log or respond to author
return nil //command doesn't exist
}
//has command met its timeout requirements
//has command met its timeout requirements, if timeout has not expired a response is sent to the message
//from the timeoutCheck method
tc, err := b.timeoutCheck(msg.ID, msg.ChannelID, msg.ServerID, b.Session, run)
if err != nil {
return fmt.Errorf("failed to calculate timeout for %s\n%e", run.Trigger, err)
}
//method handles sending a response, so we do not need to send another here, simply return
if !tc {
return nil
}
//does user have correct permissions
//does user have correct permissions to run this command
if run.Roles != nil {
check, err := b.roleCheck(msg.ServerID, msg.Author.Roles, b.Session, run)
if err != nil {
return fmt.Errorf("failed to perform permission checks for %s\n%e", run.Trigger, err)
}
if !check {
//this alert should probably be moved into the roleCheck function to match the pattern of the timeoutCheck method
reply := b.createReply("you do not have permissions to run that command", msg.ID, msg.ChannelID, msg.ServerID)
_, err := b.Session.ChannelMessageSendComplex(msg.ChannelID, reply)
if err != nil {
@@ -290,6 +312,7 @@ func (b *bolt) handleCommand(msg *Message, lg int) error {
}
}
//execute handler func with message context
err = run.Payload(&Context{
Message: msg,
bolt: b,
@@ -304,7 +327,7 @@ func (b *bolt) handleCommand(msg *Message, lg int) error {
return nil
}
// basic wrapper function to create easy Discord responses
// createReploy is a basic wrapper function to map message data to the actual MessageSend struct
func (b *bolt) createReply(content, message, channel, guild string) *dg.MessageSend {
details := &dg.MessageReference{
MessageID: message,
@@ -318,7 +341,8 @@ func (b *bolt) createReply(content, message, channel, guild string) *dg.MessageS
}
}
// used to calculate the remaining time left in a timeout and returning it in a human-readable format
// remainingTimeout calculates the amount of time left before a command can be run again. Returning a string
// representing a readable version of the time left, example: 1h (1 hour)
func (b *bolt) remainingTimeout(timeout time.Time) string {
r := time.Until(timeout)
var (
@@ -336,14 +360,18 @@ func (b *bolt) remainingTimeout(timeout time.Time) string {
}
}
//provide a user-friendly time string to send back to the user
return fmt.Sprintf("%d%s", timeLeft, metric)
}
// checks if the author of msg has the correct role to run the requested command
// roleCheck loops through the provided user role ID's grabs the role "name" and ensures the role is present
// in the commands whitelist. If the role exists in the Command whitelist a true response is returned
func (b *bolt) roleCheck(guild string, roles []string, s *dg.Session, run Command) (bool, error) {
var found bool
//loop thru author roles, there may be a better way to check for this UNION
//TODO: improve role search performance to support bigger lists
//this needs a bit of love, looping through roles could get messy as server and role lists grow on big servers
//especially with the Role() call inside of the loop
for _, r := range roles {
//get role name from ID
n, err := s.State.Role(guild, r)
@@ -358,14 +386,11 @@ func (b *bolt) roleCheck(guild string, roles []string, s *dg.Session, run Comman
}
}
//can't find role, don't run command
if !found {
return false, nil
}
return true, nil
return found, nil
}
// timeoutCheck determines if a Command timeout has expired, if it hasn't expired a response is sent back to the message alerting the user
// of the remaining time
func (b *bolt) timeoutCheck(msgID, channelID, guildID string, s *dg.Session, run Command) (bool, error) {
wait := run.lastRun.Add(run.Timeout)
now := time.Now()