Skip to content

Commit

Permalink
impr: (plugins.go) Use fixed plugin config dir, add support for toggl…
Browse files Browse the repository at this point in the history
…ing plugins in `config/plugins.json`

Closes #24. See #21 and #22.
  • Loading branch information
5HT2 committed Jul 31, 2022
1 parent 0e73e33 commit 666a904
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 34 deletions.
51 changes: 46 additions & 5 deletions bot/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ import (
)

var (
C Config
fileMode = os.FileMode(0700)
DefaultPrefix = "."
C Config
P PluginConfig

DefaultPrefix = "."
DefaultPlugins = []string{"base", "base-extra", "base-fun", "bookmarker", "leave-join-msg", "message-roles",
"role-menu", "spotifytoyoutube", "starboard", "remindme", "sys-stats", "suggest-topic", "tenor-delete"}

fileMode = os.FileMode(0700)
)

type configOperation func(*Config)
Expand Down Expand Up @@ -89,14 +94,20 @@ type GuildConfig struct {
Starboard StarboardConfig `json:"starboard_config"` // TODO: Migrate
}

// SetupConfigSaving will run SaveLocalInDatabase every 5 minutes with a ticker
type PluginConfig struct {
Mutex sync.Mutex `json:"-"` // not saved in DB
LoadedPlugins []string `json:"loaded_plugins"` // A list of plugins to load, overrides DefaultPlugins
}

// SetupConfigSaving will run SaveConfig and SavePluginConfig every 5 minutes with a ticker
func SetupConfigSaving() {
ticker := time.NewTicker(5 * time.Minute)
go func() {
for {
select {
case <-ticker.C:
SaveConfig()
SavePluginConfig()
}
}
}()
Expand Down Expand Up @@ -139,7 +150,37 @@ func SaveConfig() {
if err != nil {
log.Printf("failed to write config: %v\n", err)
} else {
log.Printf("successfully saved taro config\n")
log.Printf("saved taro config\n")
}
}

func LoadPluginConfig() {
bytes, err := os.ReadFile("config/plugins.json")
if err != nil {
log.Printf("error loading plugin config: %v\n", err)
log.Printf("loading default config/plugins.json\n")

P = PluginConfig{LoadedPlugins: make([]string, 0)}
} else {
if err := json.Unmarshal(bytes, &P); err != nil {
log.Fatalf("error unmarshalling plugin config: %v\n", err)
}
}
}

func SavePluginConfig() {
bytes, err := json.MarshalIndent(&P, "", " ")

if err != nil {
log.Printf("failed to marshal plugin config: %v\n", err)
return
}

err = os.WriteFile("config/plugins.json", bytes, fileMode)
if err != nil {
log.Printf("failed to write plugin config: %v\n", err)
} else {
log.Printf("saved taro plugin config\n")
}
}

Expand Down
9 changes: 4 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,7 @@ import (
)

var (
defaultPlugins = "base base-extra base-fun bookmarker leave-join-msg message-roles role-menu spotifytoyoutube " +
"starboard remindme sys-stats suggest-topic tenor-delete"
pluginDir = flag.String("plugindir", "bin", "Default dir to search for plugins")
pluginList = flag.String("plugins", defaultPlugins, "List of plugins to load")
lastExitCode = flag.Int64("exited", 0, "Called by Dockerfile")
debugLog = flag.Bool("debug", false, "Debug messages and faster config saving")
debugLogFile = "/tmp/taro-bot.log"
Expand All @@ -34,8 +31,9 @@ func main() {
flag.Parse()
log.Printf("Running on Go version: %s\n", runtime.Version())

// Load config before anything else, as it will be needed
// Load configs before anything else, as it will be needed
bot.LoadConfig()
bot.LoadPluginConfig()
var token = bot.C.BotToken
if token == "" {
log.Fatalln("No bot_token given")
Expand Down Expand Up @@ -93,7 +91,7 @@ func main() {
util.RegisterHttpBashRequests()

// Call plugins after logging in with the bot, but before doing anything else at all
go plugins.RegisterAll(*pluginDir, *pluginList)
go plugins.RegisterAll(*pluginDir)

// Set up the bots status
go bot.LoadActivityStatus()
Expand All @@ -111,6 +109,7 @@ func main() {
log.Println("received signal, shutting down")

bot.SaveConfig()
bot.SavePluginConfig()
plugins.SaveConfig()
plugins.Shutdown()

Expand Down
64 changes: 40 additions & 24 deletions plugins/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@ import (
"path/filepath"
"plugin"
"reflect"
"regexp"
"strings"
"time"
)

var (
pathValidation = regexp.MustCompile(`[^a-z\d.]`)
fileMode = os.FileMode(0755)
plugins = make([]*Plugin, 0)
fileMode = os.FileMode(0755)
plugins = make([]*Plugin, 0)
)

type PluginInit struct {
ConfigDir string
}

type Plugin struct {
Name string // Name of the plugin to display to users
Description string // Description of what the plugin does
Version string // Version in semver, e.g.., 1.1.0
Config interface{} // Config is the Plugin's config, can be nil
ConfigDir string // ConfigDir is the name of the config directory
ConfigType reflect.Type // ConfigType is the type to validate parse the config with
Commands []bot.CommandInfo // Commands to register, could be none
Responses []bot.ResponseInfo // Responses to register, could be none
Expand All @@ -40,7 +40,7 @@ type Plugin struct {
}

func (p Plugin) String() string {
return fmt.Sprintf("[%s, %s, %v, %s, %s, %s, %s, %s]", p.Name, p.Description, p.Version, p.ConfigType, p.Commands, p.Responses, p.Handlers, p.Jobs)
return fmt.Sprintf("[%s, %s, %s, %s, %s, %s, %s, %s, %s]", p.Name, p.Description, p.Version, p.ConfigDir, p.ConfigType, p.Commands, p.Responses, p.Handlers, p.Jobs)
}

// Register will register a plugin's commands, responses and jobs to the bot
Expand All @@ -56,13 +56,17 @@ func (p *Plugin) Register() {
func (p *Plugin) LoadConfig() (i interface{}) {
defer util.LogPanic() // This code is unsafe, we should log if it panics

if p.ConfigDir == "" {
log.Fatalln("plugin config load failed: p.ConfigDir is unset!")
}

bytes, err := os.ReadFile(getConfigPath(p))
if err != nil {
log.Printf("plugin config reading failed (%s): %s\n", p.Name, err)
return i
}

obj, err := util.NewInterface(p.ConfigType, bytes)
obj, err := util.NewInterface(p.ConfigType, bytes) // unsafe
if err != nil {
log.Printf("plugin config unmarshalling failed (%s): %s\n", p.Name, err)
return i
Expand All @@ -73,13 +77,13 @@ func (p *Plugin) LoadConfig() (i interface{}) {
}

func (p *Plugin) SaveConfig() {
if p.Config == nil || p.ConfigType == nil {
if p.Config == nil || p.ConfigType == nil || p.ConfigDir == "" {
log.Printf("skipping saving %s\n", p.Name)
return
}

// This is faster than checking if it exists
_ = os.Mkdir("config/"+getConfigDir(p), fileMode)
_ = os.Mkdir("config/"+p.ConfigDir, fileMode)

if bytes, err := json.MarshalIndent(p.Config, "", " "); err != nil {
log.Printf("plugin config marshalling failed (%s): %s\n", p.Name, err)
Expand Down Expand Up @@ -121,16 +125,15 @@ func SetupConfigSaving() {
}()
}

// Load will load all the plugins from dir specified in pluginList
func Load(dir, pluginList string) {
// Load will load all the plugins
func Load(dir string) {
d, err := ioutil.ReadDir(dir)
if err != nil {
log.Printf("plugin loading failed: couldn't load dir: %s\n", err)
return
}

plugins := parsePluginsList(pluginList)
pluginInit := &PluginInit{}
plugins := parsePluginsList()

log.Printf("plugin list: [%s]\n", strings.Join(plugins, ", "))

Expand Down Expand Up @@ -159,7 +162,14 @@ func Load(dir, pluginList string) {
return
}

// Pass the ConfigDir to the PluginInit, so plugins can access it while loading their initial config.
// This requires an extra step on the user's part when writing a plugin, but the plugin loading will fail
// and let the user know if they forgot to do so. This isn't ideal, but it allows the renaming of plugin
// names, without breaking the config or relying on parsing to be consistent.
pluginInit := &PluginInit{ConfigDir: strings.TrimSuffix(entry.Name(), ".so")}
// Create the init function to execute, to attempt plugin registration.
initFn := fn.(func(manager *PluginInit) *Plugin)

if p := initFn(pluginInit); p != nil {
p.Register()
log.Printf("plugin registered: %s\n", p)
Expand Down Expand Up @@ -260,7 +270,7 @@ func RegisterHandlers() {
}

// RegisterAll will register all bot features, and then load plugins
func RegisterAll(dir, pluginList string) {
func RegisterAll(dir string) {
bot.Mutex.Lock()
defer bot.Mutex.Unlock()

Expand All @@ -276,7 +286,7 @@ func RegisterAll(dir, pluginList string) {

// This registers the plugins we have downloaded
// This does not build new plugins for us, which instead has to be done separately
Load(dir, pluginList)
Load(dir)

// This registers the new jobs that plugins have scheduled, and the handlers that they return
RegisterHandlers()
Expand All @@ -286,21 +296,27 @@ func RegisterAll(dir, pluginList string) {
SetupConfigSaving()
}

func parsePluginsList(pluginList string) []string {
func parsePluginsList() []string {
plugins := make([]string, 0)
for _, s := range strings.Split(pluginList, " ") {
p := strings.ToLower(s) + ".so"
if !util.SliceContains(plugins, p) {
plugins = append(plugins, p)

if len(bot.P.LoadedPlugins) > 0 {
for _, p := range bot.P.LoadedPlugins {
p += ".so"
if !util.SliceContains(plugins, p) {
plugins = append(plugins, p)
}
}
} else {
for _, p := range bot.DefaultPlugins {
p += ".so"
if !util.SliceContains(plugins, p) {
plugins = append(plugins, p)
}
}
}
return plugins
}

func getConfigPath(p *Plugin) string {
return fmt.Sprintf("config/%s/%s.json", getConfigDir(p), p.Version)
}

func getConfigDir(p *Plugin) string {
return pathValidation.ReplaceAllString(strings.ToLower(p.Name), "")
return fmt.Sprintf("config/%s/%s.json", p.ConfigDir, p.Version)
}

0 comments on commit 666a904

Please sign in to comment.