init commit
This commit is contained in:
parent
a172762edc
commit
e36e178f45
@ -1,3 +1,6 @@
|
||||
# bolt
|
||||
|
||||
Discord bot base framework
|
||||
Base Discord bot framework
|
||||
|
||||
## Introduction
|
||||
bolt is a wrapper for Discordgo to provide very quick and easy setup for simple Discord bots. The only code required to run bolt is the command handler functions, this provides developers with the ability to have text-based commands on a Discord server without all the bootstrapping and setup usually required.
|
160
bolt.go
Normal file
160
bolt.go
Normal file
@ -0,0 +1,160 @@
|
||||
package bolt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dg "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
const (
|
||||
TOKEN_ENV_VAR = "DISCORD_TOKEN" //label for token environment variable
|
||||
|
||||
BOT_INTENTS = dg.IntentGuildMembers | //discord permissions for the bot
|
||||
dg.IntentGuildPresences |
|
||||
dg.IntentMessageContent
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type Bolt interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
AddCommands(cmd Command)
|
||||
messageHandler(s *dg.Session, msg *dg.MessageCreate)
|
||||
createReply(content, message, channel, guild string) *dg.MessageSend
|
||||
}
|
||||
|
||||
// setup
|
||||
func init() {
|
||||
//validate environment variables
|
||||
_, check := os.LookupEnv(TOKEN_ENV_VAR)
|
||||
if !check {
|
||||
log.Fatalf("the %s environment variable must be set", TOKEN_ENV_VAR)
|
||||
}
|
||||
}
|
||||
|
||||
// create a new bolt interface
|
||||
func New() *bolt {
|
||||
bot, err := dg.New(fmt.Sprintf("Bot %s", os.Getenv(TOKEN_ENV_VAR)))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
bot.Identify.Intents = BOT_INTENTS
|
||||
|
||||
return &bolt{
|
||||
Session: bot,
|
||||
}
|
||||
}
|
||||
|
||||
// adds command handler and starts the bot
|
||||
func (b *bolt) Start() error {
|
||||
//register commands and open connection
|
||||
b.AddHandler(b.messageHandler)
|
||||
|
||||
return b.Open()
|
||||
}
|
||||
|
||||
// stops the bot
|
||||
func (b *bolt) Stop() error {
|
||||
return b.Close()
|
||||
}
|
||||
|
||||
// adds commands to bot command map for use
|
||||
func (b *bolt) AddCommands(cmd ...Command) {
|
||||
for _, c := range cmd {
|
||||
b.Commands[c.Trigger] = c
|
||||
}
|
||||
}
|
||||
|
||||
// handler function that parses message data and executes any command payloads
|
||||
func (b *bolt) messageHandler(s *dg.Session, msg *dg.MessageCreate) {
|
||||
//get server information
|
||||
server, err := s.Guild(msg.GuildID)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
channel, err := s.Channel(msg.ChannelID)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
//if there is no content it is likely an image or a GIF, updating message content for
|
||||
//better logging and to avoid confusion
|
||||
if len(msg.Content) == 0 {
|
||||
msg.Content = "GIF/IMAGE"
|
||||
}
|
||||
|
||||
//log message
|
||||
log.Printf("< %s | %s | %s > %s\n", server.Name, channel.Name, msg.Author.Username, msg.Content)
|
||||
|
||||
//the bot will ignore it's own messages to prevent command loops
|
||||
if msg.Author.ID == s.State.User.ID {
|
||||
return
|
||||
}
|
||||
|
||||
//does the message have the command indicator "."
|
||||
if msg.Content[:1] == "." {
|
||||
words := strings.Split(msg.Content, " ")
|
||||
run, ok := b.Commands[words[0]]
|
||||
if !ok {
|
||||
return //command doesn't exist, maybe log or respond to author
|
||||
}
|
||||
|
||||
//has command met its timeout requirements
|
||||
if !time.Now().After(run.LastRun.Add(run.Timeout)) {
|
||||
return //running too soon, maybe respond letting know time remaining
|
||||
}
|
||||
|
||||
//run command payload
|
||||
res, err := run.Payload(Message{
|
||||
Author: Author{
|
||||
Username: msg.Author.Username,
|
||||
Roles: msg.Member.Roles,
|
||||
},
|
||||
Words: words,
|
||||
Message: msg.Content,
|
||||
Channel: channel.Name,
|
||||
Server: server.Name,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
//if a reply is returned send back to Discord
|
||||
if res != "" {
|
||||
reply := b.createReply(res, msg.ID, msg.ChannelID, msg.GuildID)
|
||||
_, err := s.ChannelMessageSendComplex(msg.ChannelID, reply)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// basic wrapper function to create easy Discord responses
|
||||
func (b *bolt) createReply(content, message, channel, guild string) *dg.MessageSend {
|
||||
details := &dg.MessageReference{
|
||||
MessageID: message,
|
||||
ChannelID: channel,
|
||||
GuildID: guild,
|
||||
}
|
||||
|
||||
return &dg.MessageSend{
|
||||
Content: content,
|
||||
Reference: details,
|
||||
}
|
||||
}
|
30
command.go
Normal file
30
command.go
Normal file
@ -0,0 +1,30 @@
|
||||
package bolt
|
||||
|
||||
import "time"
|
||||
|
||||
type Command struct {
|
||||
Trigger string //.command that triggers payload NOT including the '.'
|
||||
Payload Payload //payload function to run when a command is detected
|
||||
Timeout time.Duration //the amount of time before command can run again
|
||||
LastRun time.Time //timestamp of last command run
|
||||
Roles []string //roles that can use command
|
||||
}
|
||||
|
||||
// payload function type handling commands. The returned error is parsed and, if no error,
|
||||
// is detected then the response string (res) will be sent in response to the command message
|
||||
type Payload func(msg Message) (res string, err error)
|
||||
|
||||
// contains the basic information needed for a message command
|
||||
type Message struct {
|
||||
Author Author
|
||||
Words []string //words from message split on whitespace
|
||||
Message string //entire message content
|
||||
Channel string //channel message was sent in
|
||||
Server string //guild message was sent in
|
||||
}
|
||||
|
||||
// username and roles of message authors
|
||||
type Author struct {
|
||||
Username string
|
||||
Roles []string
|
||||
}
|
11
go.mod
Normal file
11
go.mod
Normal file
@ -0,0 +1,11 @@
|
||||
module code.jakeyoungdev.com/jake/bolt
|
||||
|
||||
go 1.24.0
|
||||
|
||||
require github.com/bwmarrin/discordgo v0.29.0
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect
|
||||
)
|
12
go.sum
Normal file
12
go.sum
Normal file
@ -0,0 +1,12 @@
|
||||
github.com/bwmarrin/discordgo v0.29.0 h1:FmWeXFaKUwrcL3Cx65c20bTRW+vOb6k8AnaP+EgjDno=
|
||||
github.com/bwmarrin/discordgo v0.29.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
Loading…
x
Reference in New Issue
Block a user