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) + } +}