From e84930df6d5589c9c692e900e50e7929e0b88cc2 Mon Sep 17 00:00:00 2001 From: Riley Date: Sat, 21 Aug 2021 19:08:40 -0700 Subject: [PATCH 1/3] added provider system --- core.go | 50 +++++++++++++---------------- fs-win.go => databases/fs/fs-win.go | 45 ++++++++++---------------- fs.go => databases/fs/fs.go | 49 ++++++++++++---------------- go.mod | 5 +++ guilds.go | 27 +++++++++++++++- util.go | 24 ++++++++++++++ 6 files changed, 114 insertions(+), 86 deletions(-) rename fs-win.go => databases/fs/fs-win.go (82%) rename fs.go => databases/fs/fs.go (80%) diff --git a/core.go b/core.go index a9e4b5d..296997d 100644 --- a/core.go +++ b/core.go @@ -1,12 +1,10 @@ package framework import ( - "fmt" "github.com/bwmarrin/discordgo" tlog "github.com/ubergeek77/tinylog" "os" "os/signal" - "runtime" "strconv" "strings" "syscall" @@ -15,9 +13,9 @@ import ( // core.go // This file contains the main code responsible for driving core bot functionality -// messageState +// MessageState // Tells discordgo the amount of messages to cache -var messageState = 500 +var MessageState = 500 // log // The logger for the core bot @@ -56,8 +54,20 @@ var ColorSuccess = 0x55F485 var ColorFailure = 0xF45555 // BotPresence +// Presence data to send when the bot is logging in var botPresence discordgo.GatewayStatusUpdate +// initProvider +// Stores and allows for the calling of the chosen GuildProvider +var initProvider func() GuildProvider + +// SetInitProvider +// Sets the init provider +func SetInitProvider(provider func() GuildProvider) { + initProvider = provider + return +} + // SetPresence // Sets the gateway field for bot presence func SetPresence(presence discordgo.GatewayStatusUpdate) { @@ -99,33 +109,15 @@ func IsCommand(trigger string) bool { return false } -// dgoLog -// Allows for discordgo to call tinylog -func dgoLog(msgL, caller int, format string, a ...interface{}) { - pc, file, line, _ := runtime.Caller(caller) - files := strings.Split(file, "/") - file = files[len(files)-1] - - name := runtime.FuncForPC(pc).Name() - fns := strings.Split(name, ".") - name = fns[len(fns)-1] - msg := fmt.Sprintf(format, a...) - switch msgL { - case discordgo.LogError: - dlog.Errorf("%s:%d:%s() %s", file, line, name, msg) - case discordgo.LogWarning: - dlog.Warningf("%s:%d:%s() %s", file, line, name, msg) - case discordgo.LogInformational: - dlog.Infof("%s:%d:%s() %s", file, line, name, msg) - case discordgo.LogDebug: - dlog.Debugf("%s:%d:%s() %s", file, line, name, msg) - } -} - // Start the bot. func Start() { discordgo.Logger = dgoLog + // Load all the guilds + if initProvider == nil { + log.Fatalf("You have not chosen a database provider. Please refer to the docs") + } + CurrentProvider = initProvider() loadGuilds() // We need a token @@ -141,12 +133,14 @@ func Start() { log.Fatalf("Failed to create Discord session: %s", err) } // Setup State specific variables - Session.State.MaxMessageCount = messageState + Session.State.MaxMessageCount = MessageState Session.LogLevel = discordgo.LogWarning Session.SyncEvents = false Session.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll) + // Set the bots status Session.Identify.Presence = botPresence + // Open the session log.Info("Connecting to Discord...") err = Session.Open() diff --git a/fs-win.go b/databases/fs/fs-win.go similarity index 82% rename from fs-win.go rename to databases/fs/fs-win.go index 7a21dee..a8908ba 100644 --- a/fs-win.go +++ b/databases/fs/fs-win.go @@ -1,10 +1,12 @@ //go:build windows // +build windows -package framework +package fs import ( "encoding/json" + "github.com/qpixel/framework" + tlog "github.com/ubergeek77/tinylog" "golang.org/x/sys/windows" "io/ioutil" "os" @@ -16,10 +18,12 @@ import ( // fs.go // This file contains functions that pertain to interacting with the filesystem, including mutex locking of files +var log = tlog.NewTaggedLogger("BotCore", tlog.NewColor("38;5;111")) + // GuildsDir // The directory to use for reading and writing guild .json files. Defaults to ./guilds // todo abstract this into a database module (being completed in feature/database) -var GuildsDir = "" +var GuildsDir = "./guilds" // saveLock // A map that stores mutexes for each guild, which will be locked every time that guild's data is written @@ -66,7 +70,7 @@ func loadGuilds() { // - Add up to at least 17 characters (it must be a Discord snowflake) // - Are all numbers guildId := strings.Split(fName, ".json")[0] - if len(guildId) < 17 || guildId != EnsureNumbers(guildId) { + if len(guildId) < 17 || guildId != framework.EnsureNumbers(guildId) { continue } @@ -88,7 +92,7 @@ func loadGuilds() { } // Unmarshal the json - var gInfo GuildInfo + var gInfo framework.GuildInfo err = json.Unmarshal(jsonBytes, &gInfo) if err != nil { log.Errorf("Failed to unmarshal \"%s\"; guild %s WILL NOT be loaded! (%s)", fPath, guildId, err) @@ -96,29 +100,29 @@ func loadGuilds() { } // Add the loaded guild to the map - Guilds[guildId] = &Guild{ + framework.Guilds[guildId] = &framework.Guild{ ID: guildId, Info: gInfo, } } - if len(Guilds) == 0 { + if len(framework.Guilds) == 0 { log.Warningf("There are no guilds to load; data for new guilds will be saved to \"%s\"", GuildsDir) return } // :) plural := "" - if len(Guilds) != 1 { + if len(framework.Guilds) != 1 { plural = "s" } - log.Infof("Loaded %d guild%s", len(Guilds), plural) + log.Infof("Loaded %d guild%s", len(framework.Guilds), plural) } // save // Save a given guild object to .json -func (g *Guild) save() { +func save(g *framework.Guild) { // See if a mutex exists for this guild, and create if not if _, ok := saveLock[g.ID]; !ok { saveLock[g.ID] = &sync.Mutex{} @@ -153,24 +157,9 @@ func (g *Guild) save() { } } -// ReadDefaults -// TODO: WRITE DOCUMENTATION FOR THIS LMAO -func ReadDefaults(filePath string) (result []string) { - fPath := path.Clean(filePath) - if _, existErr := os.Stat(fPath); os.IsNotExist(existErr) { - log.Errorf("Failed to find \"%s\"; File WILL NOT be loaded! (%s)", fPath, existErr) - return +func InitProvider() framework.GuildProvider { + return framework.GuildProvider{ + Save: save, + Load: loadGuilds, } - - jsonBytes, err := ioutil.ReadFile(fPath) - if err != nil { - log.Errorf("Failed to read \"%s\"; File WILL NOT be loaded! (%s)", fPath, err) - return - } - - err = json.Unmarshal(jsonBytes, &result) - if err != nil { - log.Errorf("Failed to unmarshal \"%s\"; File WILL NOT be loaded! (%s)", fPath, err) - } - return } diff --git a/fs.go b/databases/fs/fs.go similarity index 80% rename from fs.go rename to databases/fs/fs.go index 5df8cc9..2ca88be 100644 --- a/fs.go +++ b/databases/fs/fs.go @@ -1,10 +1,12 @@ //go:build darwin || linux // +build darwin linux -package framework +package fs import ( "encoding/json" + "github.com/qpixel/framework" + tlog "github.com/ubergeek77/tinylog" "golang.org/x/sys/unix" "io/ioutil" "os" @@ -16,10 +18,12 @@ import ( // fs.go // This file contains functions that pertain to interacting with the filesystem, including mutex locking of files +var log = tlog.NewTaggedLogger("BotCore", tlog.NewColor("38;5;111")) + // GuildsDir // The directory to use for reading and writing guild .json files. Defaults to ./guilds -// todo remind me to abstract this into a database -var GuildsDir = "" +// todo abstract this into a database module (being completed in feature/database) +var GuildsDir = "./guilds" // saveLock // A map that stores mutexes for each guild, which will be locked every time that guild's data is written @@ -66,7 +70,7 @@ func loadGuilds() { // - Add up to at least 17 characters (it must be a Discord snowflake) // - Are all numbers guildId := strings.Split(fName, ".json")[0] - if len(guildId) < 17 || guildId != EnsureNumbers(guildId) { + if len(guildId) < 17 || guildId != framework.EnsureNumbers(guildId) { continue } @@ -86,7 +90,7 @@ func loadGuilds() { } // Unmarshal the json - var gInfo GuildInfo + var gInfo framework.GuildInfo err = json.Unmarshal(jsonBytes, &gInfo) if err != nil { log.Errorf("Failed to unmarshal \"%s\"; guild %s WILL NOT be loaded! (%s)", fPath, guildId, err) @@ -94,29 +98,29 @@ func loadGuilds() { } // Add the loaded guild to the map - Guilds[guildId] = &Guild{ + framework.Guilds[guildId] = &framework.Guild{ ID: guildId, Info: gInfo, } } - if len(Guilds) == 0 { + if len(framework.Guilds) == 0 { log.Warningf("There are no guilds to load; data for new guilds will be saved to \"%s\"", GuildsDir) return } // :) plural := "" - if len(Guilds) != 1 { + if len(framework.Guilds) != 1 { plural = "s" } - log.Infof("Loaded %d guild%s", len(Guilds), plural) + log.Infof("Loaded %d guild%s", len(framework.Guilds), plural) } // save // Save a given guild object to .json -func (g *Guild) save() { +func save(g *framework.Guild) { // See if a mutex exists for this guild, and create if not if _, ok := saveLock[g.ID]; !ok { saveLock[g.ID] = &sync.Mutex{} @@ -151,24 +155,11 @@ func (g *Guild) save() { } } -// ReadDefaults -// TODO: WRITE DOCUMENTATION FOR THIS LMAO -func ReadDefaults(filePath string) (result []string) { - fPath := path.Clean(filePath) - if _, existErr := os.Stat(fPath); os.IsNotExist(existErr) { - log.Errorf("Failed to find \"%s\"; File WILL NOT be loaded! (%s)", fPath, existErr) - return +// InitProvider +// Inits the filesystem provider +func InitProvider() framework.GuildProvider { + return framework.GuildProvider{ + Save: save, + Load: loadGuilds, } - - jsonBytes, err := ioutil.ReadFile(fPath) - if err != nil { - log.Errorf("Failed to read \"%s\"; File WILL NOT be loaded! (%s)", fPath, err) - return - } - - err = json.Unmarshal(jsonBytes, &result) - if err != nil { - log.Errorf("Failed to unmarshal \"%s\"; File WILL NOT be loaded! (%s)", fPath, err) - } - return } diff --git a/go.mod b/go.mod index 1646b36..3b09091 100644 --- a/go.mod +++ b/go.mod @@ -9,3 +9,8 @@ require ( github.com/ubergeek77/tinylog v1.0.0 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 ) + +require ( + github.com/gorilla/websocket v1.4.2 // indirect + golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect +) diff --git a/guilds.go b/guilds.go index 7959672..6d02155 100644 --- a/guilds.go +++ b/guilds.go @@ -28,6 +28,14 @@ type GuildInfo struct { WhitelistIds []string `json:"whitelist_ids"` } +//GuildProvider +// Type that holds functions that can be easily modified to support a wide range +// of storage types +type GuildProvider struct { + Save func(guild *Guild) + Load func() +} + // Guild // The definition of a guild, which is simply its ID and Info type Guild struct { @@ -41,6 +49,11 @@ type Guild struct { // Otherwise, there will be information desync var Guilds = make(map[string]*Guild) +// CurrentProvider +// A reference to a function that provides the guild info system with a database +// Or similar system to save guild data. +var CurrentProvider GuildProvider + // getGuild // Return a Guild object corresponding to the given guildId // If the guild doesn't exist, initialize a new guild and save it before returning @@ -90,7 +103,7 @@ func getGuild(guildId string) *Guild { // Add the new guild to the map of guilds Guilds[guildId] = &newGuild - // Save the guild to .json + // Save the guild to database // A failed save is fatal, so we can count on this being successful newGuild.save() @@ -101,6 +114,18 @@ func getGuild(guildId string) *Guild { } } +// loadGuilds +// Load all known guilds from the database +func loadGuilds() { + CurrentProvider.Load() +} + +// save +// saves guild data to the database +func (g *Guild) save() { + CurrentProvider.Save(g) +} + // GetMember // Convenience function to get a member in this guild // This function handles cleaning of the string so you don't have to diff --git a/util.go b/util.go index 889479d..a1fc31e 100644 --- a/util.go +++ b/util.go @@ -6,6 +6,7 @@ import ( "github.com/bwmarrin/discordgo" "github.com/dlclark/regexp2" "regexp" + "runtime" "strconv" "strings" ) @@ -355,3 +356,26 @@ func FindAllString(re *regexp2.Regexp, s string) []string { } return matches } + +// dgoLog +// Allows for discordgo to call tinylog +func dgoLog(msgL, caller int, format string, a ...interface{}) { + pc, file, line, _ := runtime.Caller(caller) + files := strings.Split(file, "/") + file = files[len(files)-1] + + name := runtime.FuncForPC(pc).Name() + fns := strings.Split(name, ".") + name = fns[len(fns)-1] + msg := fmt.Sprintf(format, a...) + switch msgL { + case discordgo.LogError: + dlog.Errorf("%s:%d:%s() %s", file, line, name, msg) + case discordgo.LogWarning: + dlog.Warningf("%s:%d:%s() %s", file, line, name, msg) + case discordgo.LogInformational: + dlog.Infof("%s:%d:%s() %s", file, line, name, msg) + case discordgo.LogDebug: + dlog.Debugf("%s:%d:%s() %s", file, line, name, msg) + } +} From b4515ad957606fe91968cfabdcef470b58e501a1 Mon Sep 17 00:00:00 2001 From: Riley Date: Sat, 21 Aug 2021 19:16:39 -0700 Subject: [PATCH 2/3] privated some variables, minor clean up --- core.go | 4 ++-- databases/fs/fs.go | 14 ++++++++------ guilds.go | 12 ++++++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/core.go b/core.go index 296997d..c75886a 100644 --- a/core.go +++ b/core.go @@ -117,8 +117,8 @@ func Start() { if initProvider == nil { log.Fatalf("You have not chosen a database provider. Please refer to the docs") } - CurrentProvider = initProvider() - loadGuilds() + currentProvider = initProvider() + Guilds = loadGuilds() // We need a token if botToken == "" { diff --git a/databases/fs/fs.go b/databases/fs/fs.go index 2ca88be..a9ee6e6 100644 --- a/databases/fs/fs.go +++ b/databases/fs/fs.go @@ -32,7 +32,7 @@ var saveLock = make(map[string]*sync.Mutex) // loadGuilds // Load all known guilds from the filesystem, from inside GuildsDir -func loadGuilds() { +func loadGuilds() (guilds map[string]*framework.Guild) { // Check if the configured guild directory exists, and create it if otherwise if _, existErr := os.Stat(GuildsDir); os.IsNotExist(existErr) { mkErr := os.MkdirAll(GuildsDir, 0755) @@ -42,10 +42,11 @@ func loadGuilds() { log.Warningf("There are no Guilds to load; data for new Guilds will be saved to: %s", GuildsDir) // There are no guilds to load, so we can return early - return + return guilds } // Get a list of files in the directory + guilds = make(map[string]*framework.Guild) files, rdErr := ioutil.ReadDir(GuildsDir) if rdErr != nil { log.Fatalf("Failed to read guild directory: %s", rdErr) @@ -98,15 +99,15 @@ func loadGuilds() { } // Add the loaded guild to the map - framework.Guilds[guildId] = &framework.Guild{ + guilds[guildId] = &framework.Guild{ ID: guildId, Info: gInfo, } } - if len(framework.Guilds) == 0 { + if len(guilds) == 0 { log.Warningf("There are no guilds to load; data for new guilds will be saved to \"%s\"", GuildsDir) - return + return guilds } // :) @@ -115,7 +116,8 @@ func loadGuilds() { plural = "s" } - log.Infof("Loaded %d guild%s", len(framework.Guilds), plural) + log.Infof("Loaded %d guild%s", len(guilds), plural) + return guilds } // save diff --git a/guilds.go b/guilds.go index 6d02155..9bf4a2e 100644 --- a/guilds.go +++ b/guilds.go @@ -33,7 +33,7 @@ type GuildInfo struct { // of storage types type GuildProvider struct { Save func(guild *Guild) - Load func() + Load func() map[string]*Guild } // Guild @@ -49,10 +49,10 @@ type Guild struct { // Otherwise, there will be information desync var Guilds = make(map[string]*Guild) -// CurrentProvider +// currentProvider // A reference to a function that provides the guild info system with a database // Or similar system to save guild data. -var CurrentProvider GuildProvider +var currentProvider GuildProvider // getGuild // Return a Guild object corresponding to the given guildId @@ -116,14 +116,14 @@ func getGuild(guildId string) *Guild { // loadGuilds // Load all known guilds from the database -func loadGuilds() { - CurrentProvider.Load() +func loadGuilds() map[string]*Guild { + return currentProvider.Load() } // save // saves guild data to the database func (g *Guild) save() { - CurrentProvider.Save(g) + currentProvider.Save(g) } // GetMember From b3aad68fb6b73fc4d27a3832a0b1c4cd143b3ddd Mon Sep 17 00:00:00 2001 From: Riley Date: Sat, 21 Aug 2021 19:50:30 -0700 Subject: [PATCH 3/3] updated some packages. minor refactoring --- core.go | 6 +++--- guilds.go | 8 ++++---- handlers.go | 18 +++++++++--------- {databases => providers}/fs/fs-win.go | 0 {databases => providers}/fs/fs.go | 0 5 files changed, 16 insertions(+), 16 deletions(-) rename {databases => providers}/fs/fs-win.go (100%) rename {databases => providers}/fs/fs.go (100%) diff --git a/core.go b/core.go index c75886a..75c6cc0 100644 --- a/core.go +++ b/core.go @@ -149,13 +149,13 @@ func Start() { } // Add the commandHandler to the list of user-defined handlers - AddHandler(commandHandler) + AddDGOHandler(commandHandler) // Add the slash command handler to the list of user-defined handlers - AddHandler(handleInteraction) + AddDGOHandler(handleInteraction) // Add the handlers to the session - addHandlers() + addDGoHandlers() // Log that the login succeeded log.Infof("Bot logged in as \"" + Session.State.Ready.User.Username + "#" + Session.State.Ready.User.Discriminator + "\"") diff --git a/guilds.go b/guilds.go index 9bf4a2e..746a430 100644 --- a/guilds.go +++ b/guilds.go @@ -50,7 +50,7 @@ type Guild struct { var Guilds = make(map[string]*Guild) // currentProvider -// A reference to a function that provides the guild info system with a database +// A reference to a struct of functions that provides the guild info system with a database // Or similar system to save guild data. var currentProvider GuildProvider @@ -699,7 +699,7 @@ func (g *Guild) EnableCommandInChannel(command string, channelId string) error { // DisableCommandInChannel // Given a command and channel ID, add that command to that channel's list of blocked commands -func (g *Guild) DisableTriggerInChannel(command string, channelId string) error { +func (g *Guild) DisableCommandInChannel(command string, channelId string) error { cleanedId := CleanId(channelId) if cleanedId == "" { return errors.New("provided channel ID is invalid") @@ -741,7 +741,7 @@ func (g *Guild) SetResponseChannel(channelId string) error { } // Kick -// Kick a member +// Kicks a member func (g *Guild) Kick(userId string, reason string) error { // Make sure the member exists member, err := g.GetMember(userId) @@ -758,7 +758,7 @@ func (g *Guild) Kick(userId string, reason string) error { } // Ban -// Ban a user, who may not be a member +// Bans a user, who may not be a member func (g *Guild) Ban(userId string, reason string, deleteDays int) error { // Make sure the USER exists, because they may not be a member user, err := GetUser(userId) diff --git a/handlers.go b/handlers.go index 131a110..5a3e4e4 100644 --- a/handlers.go +++ b/handlers.go @@ -1,29 +1,29 @@ package framework // handlers.go -// Everything required for commands to pass their own handlers to discordgo +// Everything required for commands to pass their own handlers to discordgo and the framework itself. // handlers // This list stores all the handlers that can be added to the bot // It's basically a passthroughs for discordgo.AddHandler, but having a list // allows them to be collected ahead of time and then added all at once -var handlers []interface{} +var dGOHandlers []interface{} -// AddHandler -// This provides a way for commands to pass handler functions through to discorgo, +// AddDGOHandler +// This provides a way for commands to pass handler functions through to discordgo, // and have them added properly during bot startup -func AddHandler(handler interface{}) { - handlers = append(handlers, handler) +func AddDGOHandler(handler interface{}) { + dGOHandlers = append(dGOHandlers, handler) } // addHandlers // Given all the handlers that have been pre-added to the handlers list, add them to the discordgo session -func addHandlers() { - if len(handlers) == 0 { +func addDGoHandlers() { + if len(dGOHandlers) == 0 { return } - for _, handler := range handlers { + for _, handler := range dGOHandlers { Session.AddHandler(handler) } } diff --git a/databases/fs/fs-win.go b/providers/fs/fs-win.go similarity index 100% rename from databases/fs/fs-win.go rename to providers/fs/fs-win.go diff --git a/databases/fs/fs.go b/providers/fs/fs.go similarity index 100% rename from databases/fs/fs.go rename to providers/fs/fs.go