Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c0c8366b7b | |||
| ddff13518f | |||
| 310f417521 | |||
| d2f901e1b1 | |||
| 5662e3dbb8 | |||
| b059438179 | |||
| 4f533eac6b |
3
go.mod
3
go.mod
@@ -6,12 +6,13 @@ require (
|
||||
github.com/bwmarrin/discordgo v0.28.1
|
||||
github.com/caarlos0/env/v10 v10.0.0
|
||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/lestrrat-go/strftime v1.0.6
|
||||
github.com/pelletier/go-toml/v2 v2.1.0
|
||||
github.com/rivo/uniseg v0.4.4
|
||||
github.com/rqlite/sql v0.0.0-20240312185922-ffac88a740bd
|
||||
github.com/rqlite/sql v0.0.0-20241029220113-152a320b02f7
|
||||
github.com/valyala/fasttemplate v1.2.2
|
||||
go.elara.ws/logger v0.0.0-20230928062203-85e135cf02ae
|
||||
go.elara.ws/vercmp v0.0.0-20231003203944-671892886053
|
||||
|
||||
5
go.sum
5
go.sum
@@ -16,6 +16,7 @@ github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRP
|
||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d h1:wi6jN5LVt/ljaBG4ue79Ekzb12QfJ52L9Q98tl8SWhw=
|
||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d h1:W1n4DvpzZGOISgp7wWNtraLcHtnmnTwBlJidqtMIuwQ=
|
||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
@@ -69,8 +70,8 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rqlite/sql v0.0.0-20240312185922-ffac88a740bd h1:wW6BtayFoKaaDeIvXRE3SZVPOscSKlYD+X3bB749+zk=
|
||||
github.com/rqlite/sql v0.0.0-20240312185922-ffac88a740bd/go.mod h1:ib9zVtNgRKiGuoMyUqqL5aNpk+r+++YlyiVIkclVqPg=
|
||||
github.com/rqlite/sql v0.0.0-20241029220113-152a320b02f7 h1:Mnz6yd4FWtiD6bbH9WHFFHfrOM2OYTUTmwrsckRc4W8=
|
||||
github.com/rqlite/sql v0.0.0-20241029220113-152a320b02f7/go.mod h1:ib9zVtNgRKiGuoMyUqqL5aNpk+r+++YlyiVIkclVqPg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
||||
@@ -26,7 +26,7 @@ func Modify(stmt, prefix, suffix string) (string, error) {
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
// modify changes all the table, viee, trigger, and index names in a single statement
|
||||
// modify changes all the table, view, trigger, and index names in a single statement
|
||||
func modify(stmt any, prefix, suffix string) {
|
||||
switch stmt := stmt.(type) {
|
||||
case *sqlparser.SelectStatement:
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/eventloop"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/owobot/internal/db"
|
||||
"go.elara.ws/owobot/internal/util"
|
||||
)
|
||||
|
||||
type lockableRuntime struct {
|
||||
*sync.Mutex
|
||||
*goja.Runtime
|
||||
}
|
||||
|
||||
// Plugins is a list of plugins
|
||||
var Plugins []Plugin
|
||||
|
||||
@@ -22,7 +16,7 @@ var Plugins []Plugin
|
||||
type Plugin struct {
|
||||
Info db.PluginInfo
|
||||
Commands []Command
|
||||
VM lockableRuntime
|
||||
Loop *eventloop.EventLoop
|
||||
api *owobotAPI
|
||||
}
|
||||
|
||||
@@ -52,7 +46,7 @@ type owobotAPI struct {
|
||||
Commands []Command
|
||||
|
||||
path string
|
||||
vm lockableRuntime
|
||||
loop *eventloop.EventLoop
|
||||
}
|
||||
|
||||
func (oa *owobotAPI) Enabled(guildID string) bool {
|
||||
@@ -86,22 +80,21 @@ func (oa *owobotAPI) On(eventType string, fn goja.Value) {
|
||||
handlersMtx.Lock()
|
||||
defer handlersMtx.Unlock()
|
||||
|
||||
this := oa.vm.ToValue(oa)
|
||||
oa.loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
this := vm.ToValue(oa)
|
||||
|
||||
handlerMap[eventType] = append(handlerMap[eventType], Handler{
|
||||
PluginName: oa.PluginInfo.Name,
|
||||
Func: func(s *discordgo.Session, data any) {
|
||||
oa.vm.Lock()
|
||||
defer oa.vm.Unlock()
|
||||
|
||||
_, err := callable(this, oa.vm.ToValue(s), oa.vm.ToValue(data))
|
||||
if err != nil {
|
||||
log.Error("Exception thrown in plugin function").
|
||||
Str("plugin", oa.PluginInfo.Name).
|
||||
Str("event-type", eventType).
|
||||
Err(err).
|
||||
Send()
|
||||
}
|
||||
},
|
||||
handlerMap[eventType] = append(handlerMap[eventType], Handler{
|
||||
PluginName: oa.PluginInfo.Name,
|
||||
Func: func(s *discordgo.Session, data any) {
|
||||
_, err := callable(this, vm.ToValue(s), vm.ToValue(data))
|
||||
if err != nil {
|
||||
log.Error("Exception thrown in plugin function").
|
||||
Str("plugin", oa.PluginInfo.Name).
|
||||
Str("event-type", eventType).
|
||||
Err(err).
|
||||
Send()
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"go.elara.ws/owobot/internal/util"
|
||||
)
|
||||
|
||||
// pluginCmd handles the `/plugin` command and routes it to the correct subcommand.
|
||||
func pluginCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
// pluginadmCmd handles the `/plugin` command and routes it to the correct subcommand.
|
||||
func pluginadmCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
switch name := data.Options[0].Name; name {
|
||||
case "list":
|
||||
@@ -22,7 +22,7 @@ func pluginCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
case "disable":
|
||||
return disableCmd(s, i)
|
||||
default:
|
||||
return fmt.Errorf("unknown plugin subcommand: %s", name)
|
||||
return fmt.Errorf("unknown pluginadm subcommand: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,12 @@ func enableCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
return fmt.Errorf("onEnable value is not callable")
|
||||
}
|
||||
|
||||
_, err := callable(plugin.VM.ToValue(plugin.api), plugin.VM.ToValue(i.GuildID))
|
||||
if err != nil {
|
||||
errCh := make(chan error)
|
||||
plugin.Loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
_, err := callable(vm.ToValue(plugin.api), vm.ToValue(i.GuildID))
|
||||
errCh <- err
|
||||
})
|
||||
if err := <-errCh; err != nil {
|
||||
return fmt.Errorf("%s onEnable: %w", plugin.Info.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -95,11 +99,12 @@ func disableCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
return fmt.Errorf("onDisable value is not callable")
|
||||
}
|
||||
|
||||
plugin.VM.Lock()
|
||||
defer plugin.VM.Unlock()
|
||||
|
||||
_, err := callable(plugin.VM.ToValue(plugin.api), plugin.VM.ToValue(i.GuildID))
|
||||
if err != nil {
|
||||
errCh := make(chan error)
|
||||
plugin.Loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
_, err := callable(vm.ToValue(plugin.api), vm.ToValue(i.GuildID))
|
||||
errCh <- err
|
||||
})
|
||||
if err := <-errCh; err != nil {
|
||||
return fmt.Errorf("%s onDisable: %w", plugin.Info.Name, err)
|
||||
}
|
||||
}
|
||||
@@ -107,10 +112,22 @@ func disableCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
return util.RespondEphemeral(s, i.Interaction, fmt.Sprintf("Successfully disabled the %q plugin", pluginName))
|
||||
}
|
||||
|
||||
// phelpCmd handles the `/phelp` command.
|
||||
func phelpCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
func pluginCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
cmdStr := data.Options[0].StringValue()
|
||||
switch name := data.Options[0].Name; name {
|
||||
case "run":
|
||||
return pluginRunCmd(s, i)
|
||||
case "help":
|
||||
return pluginHelpCmd(s, i)
|
||||
default:
|
||||
return fmt.Errorf("unknown plugin subcommand: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
// pluginHelpCmd handles the `/phelp` command.
|
||||
func pluginHelpCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
cmdStr := data.Options[0].Options[0].StringValue()
|
||||
|
||||
args, err := shellquote.Split(cmdStr)
|
||||
if err != nil {
|
||||
@@ -176,10 +193,10 @@ func phelpCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
return fmt.Errorf("command not found: %q", args[0])
|
||||
}
|
||||
|
||||
// prunCmd handles the `/prunCmd` command.
|
||||
func prunCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
// pluginRunCmd handles the `/pluginRunCmd` command.
|
||||
func pluginRunCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
data := i.ApplicationCommandData()
|
||||
cmdStr := data.Options[0].StringValue()
|
||||
cmdStr := data.Options[0].Options[0].StringValue()
|
||||
|
||||
args, err := shellquote.Split(cmdStr)
|
||||
if err != nil {
|
||||
@@ -207,15 +224,17 @@ func prunCmd(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
return fmt.Errorf("value in onExec is not callable")
|
||||
}
|
||||
|
||||
plugin.VM.Lock()
|
||||
_, err = callable(
|
||||
plugin.VM.ToValue(cmd),
|
||||
plugin.VM.ToValue(s),
|
||||
plugin.VM.ToValue(i),
|
||||
plugin.VM.ToValue(newArgs),
|
||||
)
|
||||
plugin.VM.Unlock()
|
||||
return err
|
||||
errCh := make(chan error)
|
||||
plugin.Loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
_, err = callable(
|
||||
vm.ToValue(cmd),
|
||||
vm.ToValue(s),
|
||||
vm.ToValue(i),
|
||||
vm.ToValue(newArgs),
|
||||
)
|
||||
errCh <- err
|
||||
})
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
return fmt.Errorf("command not found: %q", args[0])
|
||||
|
||||
@@ -70,11 +70,11 @@ func handleAutocomplete(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
||||
}
|
||||
|
||||
data := i.ApplicationCommandData()
|
||||
if data.Name != "prun" && data.Name != "phelp" {
|
||||
if data.Name != "plugin" {
|
||||
return
|
||||
}
|
||||
|
||||
cmdStr := data.Options[0].StringValue()
|
||||
cmdStr := data.Options[0].Options[0].StringValue()
|
||||
|
||||
s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
||||
Type: discordgo.InteractionApplicationCommandAutocompleteResult,
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/eventloop"
|
||||
"go.elara.ws/logger/log"
|
||||
"go.elara.ws/owobot/internal/db"
|
||||
"go.elara.ws/owobot/internal/systems/commands"
|
||||
@@ -22,36 +22,43 @@ func Init(s *discordgo.Session) error {
|
||||
return err
|
||||
}
|
||||
|
||||
commands.Register(s, prunCmd, &discordgo.ApplicationCommand{
|
||||
Name: "prun",
|
||||
Description: "Run a plugin command",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "cmd",
|
||||
Description: "The plugin command to run",
|
||||
Required: true,
|
||||
Autocomplete: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
commands.Register(s, phelpCmd, &discordgo.ApplicationCommand{
|
||||
Name: "phelp",
|
||||
Description: "Display help for a plugin command",
|
||||
Options: []*discordgo.ApplicationCommandOption{
|
||||
{
|
||||
Type: discordgo.ApplicationCommandOptionString,
|
||||
Name: "cmd",
|
||||
Description: "The plugin command to display help for",
|
||||
Required: true,
|
||||
Autocomplete: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
commands.Register(s, pluginCmd, &discordgo.ApplicationCommand{
|
||||
Name: "plugin",
|
||||
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{
|
||||
@@ -95,7 +102,7 @@ func Init(s *discordgo.Session) error {
|
||||
}
|
||||
|
||||
// Load recursively loads plugins from the given directory.
|
||||
func Load(dir string) error {
|
||||
func Load(dir string, sess *discordgo.Session) error {
|
||||
return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -110,22 +117,33 @@ func Load(dir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
vm := lockableRuntime{&sync.Mutex{}, goja.New()}
|
||||
vm.SetFieldNameMapper(lowerCamelNameMapper{})
|
||||
loop := eventloop.NewEventLoop()
|
||||
|
||||
api := &owobotAPI{vm: vm, path: path}
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
vm.SetFieldNameMapper(lowerCamelNameMapper{})
|
||||
})
|
||||
|
||||
err = errors.Join(
|
||||
vm.GlobalObject().Set("owobot", api),
|
||||
vm.GlobalObject().Set("discord", builtins.Constants),
|
||||
vm.GlobalObject().Set("print", fmt.Println),
|
||||
)
|
||||
api := &owobotAPI{loop: loop, path: path}
|
||||
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
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 {
|
||||
loop.Start()
|
||||
errCh := make(chan error)
|
||||
|
||||
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
_, err := vm.RunScript(path, string(data))
|
||||
errCh <- err
|
||||
})
|
||||
if err := <-errCh; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -141,15 +159,18 @@ func Load(dir string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = builtins.Register(vm.Runtime, api.PluginInfo.Name, api.PluginInfo.Version)
|
||||
if err != nil {
|
||||
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
err := builtins.Register(vm, api.PluginInfo.Name, api.PluginInfo.Version)
|
||||
errCh <- err
|
||||
})
|
||||
if err := <-errCh; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Plugins = append(Plugins, Plugin{
|
||||
Info: api.PluginInfo,
|
||||
Commands: api.Commands,
|
||||
VM: vm,
|
||||
Loop: loop,
|
||||
api: api,
|
||||
})
|
||||
|
||||
@@ -160,8 +181,11 @@ func Load(dir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = callableInit(vm.ToValue(api), vm.ToValue(prev))
|
||||
if err != nil {
|
||||
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||
_, err := callableInit(vm.ToValue(api), vm.ToValue(prev), vm.ToValue(sess))
|
||||
errCh <- err
|
||||
})
|
||||
if err := <-errCh; err != nil {
|
||||
return fmt.Errorf("%s init: %w", api.PluginInfo.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,4 +37,4 @@ func onRoleButton(s *discordgo.Session, i *discordgo.InteractionCreate) error {
|
||||
}
|
||||
return util.RespondEphemeral(s, i.Interaction, fmt.Sprintf("Successfully assigned role <@&%s> to you", roleID))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
"go.elara.ws/logger/log"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
"go.elara.ws/logger/log"
|
||||
)
|
||||
|
||||
// onMemberLeave closes any tickets a user had open when they leave
|
||||
@@ -19,4 +19,4 @@ func onMemberLeave(s *discordgo.Session, gmr *discordgo.GuildMemberRemove) {
|
||||
log.Warn("Error removing ticket after user left").Err(err).Send()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user