feature/improvements #8
@@ -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
|
||||||
|
|||||||
65
bolt.go
65
bolt.go
@@ -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
|
||||||
|
//through the command handler instead of the generic message handler
|
||||||
DEFAULT_INDICATOR = "."
|
DEFAULT_INDICATOR = "."
|
||||||
DEFAULT_MAX_GOROUTINES = 50
|
//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
|
||||||
|
//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
|
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user