7 Commits

Author SHA1 Message Date
c0c8366b7b Fix typo
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-29 17:20:50 -07:00
ddff13518f Run formatter
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-29 15:52:37 -07:00
310f417521 Update rqlite/sql to latest commit 2024-10-29 15:52:24 -07:00
d2f901e1b1 Remove lockableRuntime as it's no longer needed
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-10-27 17:38:05 -07:00
5662e3dbb8 Integrate event loop for setTimeout, setInterval, etc.
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-10-27 17:34:30 -07:00
b059438179 Pass session to plugin init function
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-10-27 12:26:04 -07:00
4f533eac6b Rename /plugin to /pluginadm and make /prun and /phelp subcommands of /plugin instead
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2024-04-24 06:30:31 -07:00
10 changed files with 144 additions and 106 deletions

3
go.mod
View File

@@ -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
View File

@@ -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=

View File

@@ -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:

View File

@@ -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()
}
},
})
})
}

View File

@@ -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])

View File

@@ -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,

View File

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

View File

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

View File

@@ -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
}
}
}

View File

@@ -86,7 +86,7 @@ func main() {
}
}
err = plugins.Load(cfg.PluginDir)
err = plugins.Load(cfg.PluginDir, s)
if err != nil {
log.Error("Error running plugin file").Err(err).Send()
}