owobot/internal/systems/plugins/init.go

179 lines
4.4 KiB
Go

package plugins
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"sync"
"github.com/bwmarrin/discordgo"
"github.com/dop251/goja"
"go.elara.ws/logger/log"
"go.elara.ws/owobot/internal/db"
"go.elara.ws/owobot/internal/systems/commands"
"go.elara.ws/owobot/internal/systems/plugins/builtins"
"go.elara.ws/owobot/internal/util"
)
func Init(s *discordgo.Session) error {
if err := loadEnabled(); err != nil {
return err
}
commands.Register(s, pluginCmd, &discordgo.ApplicationCommand{
Name: "plugin",
Description: "Interact with the plugins on this server",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "run",
Description: "Run a plugin command",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "cmd",
Description: "The plugin command to run",
Required: true,
Autocomplete: true,
},
},
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "help",
Description: "See how to use a plugin command",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "cmd",
Description: "The plugin command to help with",
Required: true,
Autocomplete: true,
},
},
},
},
})
commands.Register(s, pluginadmCmd, &discordgo.ApplicationCommand{
Name: "pluginadm",
Description: "Manage dynamic plugins for your server",
DefaultMemberPermissions: util.Pointer[int64](discordgo.PermissionManageServer),
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "list",
Description: "List all available plugins",
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "enable",
Description: "Enable a plugin in this guild",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "plugin",
Description: "The name of the plugin to enable",
Required: true,
},
},
},
{
Type: discordgo.ApplicationCommandOptionSubCommand,
Name: "disable",
Description: "Disable a plugin in this guild",
Options: []*discordgo.ApplicationCommandOption{
{
Type: discordgo.ApplicationCommandOptionString,
Name: "plugin",
Description: "The name of the plugin to disable",
Required: true,
},
},
},
},
})
s.AddHandler(handleAutocomplete)
s.AddHandler(handlePluginEvent)
return nil
}
// Load recursively loads plugins from the given directory.
func Load(dir string) error {
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || filepath.Ext(path) != ".js" {
return nil
}
data, err := os.ReadFile(path)
if err != nil {
return err
}
vm := lockableRuntime{&sync.Mutex{}, goja.New()}
vm.SetFieldNameMapper(lowerCamelNameMapper{})
api := &owobotAPI{vm: vm, path: path}
err = errors.Join(
vm.GlobalObject().Set("owobot", api),
vm.GlobalObject().Set("discord", builtins.Constants),
vm.GlobalObject().Set("print", fmt.Println),
)
if err != nil {
return err
}
_, err = vm.RunScript(path, string(data))
if err != nil {
return err
}
if !api.PluginInfo.IsValid() {
log.Warn("Plugin info not provided, skipping.").Str("path", path).Send()
return nil
}
prev, _ := db.GetPlugin(api.PluginInfo.Name)
err = db.AddPlugin(api.PluginInfo)
if err != nil {
return err
}
err = builtins.Register(vm.Runtime, api.PluginInfo.Name, api.PluginInfo.Version)
if err != nil {
return err
}
Plugins = append(Plugins, Plugin{
Info: api.PluginInfo,
Commands: api.Commands,
VM: vm,
api: api,
})
if api.Init != nil {
callableInit, ok := goja.AssertFunction(api.Init)
if !ok {
log.Warn("Init value is not callable, ignoring.").Str("plugin", api.PluginInfo.Name).Send()
return nil
}
_, err = callableInit(vm.ToValue(api), vm.ToValue(prev))
if err != nil {
return fmt.Errorf("%s init: %w", api.PluginInfo.Name, err)
}
}
return nil
})
}