plugin's config is now in yaml

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2026-05-20 11:43:26 +02:00
parent b547a79d6e
commit 4251e4fb2a
10 changed files with 270 additions and 149 deletions
+51
View File
@@ -11,6 +11,7 @@ import (
"github.com/anotherhadi/spilltea/internal/db"
goproxy "github.com/lqqyt2423/go-mitmproxy/proxy"
lua "github.com/yuin/gopher-lua"
"gopkg.in/yaml.v3"
)
func newLuaState(mgr *Manager, p *Plugin) *lua.LState {
@@ -175,6 +176,27 @@ func registerUtilities(L *lua.LState, mgr *Manager, p *Plugin) {
return 0
}))
L.SetGlobal("get_config", L.NewFunction(func(L *lua.LState) int {
// p.mu is already held by the hook caller - do not lock again.
configText := p.ConfigText
if configText == "" {
L.Push(L.NewTable())
return 1
}
var data interface{}
if err := yaml.Unmarshal([]byte(configText), &data); err != nil || data == nil {
L.Push(L.NewTable())
return 1
}
lv := goToLuaValue(L, data)
if _, ok := lv.(*lua.LTable); !ok {
L.Push(L.NewTable())
return 1
}
L.Push(lv)
return 1
}))
L.SetGlobal("shell_pipe", L.NewFunction(func(L *lua.LState) int {
cmd := L.CheckString(1)
input := L.OptString(2, "")
@@ -201,6 +223,35 @@ func registerUtilities(L *lua.LState, mgr *Manager, p *Plugin) {
}))
}
func goToLuaValue(L *lua.LState, v interface{}) lua.LValue {
switch val := v.(type) {
case map[string]interface{}:
t := L.NewTable()
for k, v2 := range val {
L.SetField(t, k, goToLuaValue(L, v2))
}
return t
case []interface{}:
t := L.NewTable()
for i, v2 := range val {
L.RawSetInt(t, i+1, goToLuaValue(L, v2))
}
return t
case string:
return lua.LString(val)
case int:
return lua.LNumber(val)
case float64:
return lua.LNumber(val)
case bool:
if val {
return lua.LTrue
}
return lua.LFalse
}
return lua.LNil
}
func luaTableString(t *lua.LTable, key string) string {
v := t.RawGetString(key)
if s, ok := v.(lua.LString); ok {
+35 -50
View File
@@ -19,8 +19,9 @@ type Manager struct {
mu sync.RWMutex
plugins []*Plugin
db *db.DB
broker *intercept.Broker
db *db.DB
pluginsFile *PluginsFile
broker *intercept.Broker
Notifs chan PluginNotifMsg
Quit chan string
@@ -43,6 +44,10 @@ func (m *Manager) SetDB(d *db.DB) {
m.db = d
}
func (m *Manager) SetPluginsFile(pf *PluginsFile) {
m.pluginsFile = pf
}
func (m *Manager) LoadFromDir(dir string) error {
entries, err := os.ReadDir(dir)
if os.IsNotExist(err) {
@@ -52,17 +57,6 @@ func (m *Manager) LoadFromDir(dir string) error {
return err
}
var states map[string]db.PluginState
if m.db != nil {
list, err := m.db.LoadPluginStates()
if err == nil {
states = make(map[string]db.PluginState, len(list))
for _, s := range list {
states[s.Name] = s
}
}
}
for _, e := range entries {
if e.IsDir() || !strings.HasSuffix(e.Name(), ".lua") {
continue
@@ -73,9 +67,13 @@ func (m *Manager) LoadFromDir(dir string) error {
log.Printf("plugin load error %s: %v", path, err)
continue
}
if s, ok := states[p.Name]; ok {
p.Enabled = s.Enabled
p.ConfigText = s.ConfigText
if m.pluginsFile != nil {
if enabled, configText, found := m.pluginsFile.get(p.Name); found {
p.Enabled = enabled
p.ConfigText = configText
} else {
m.pluginsFile.register(p.Name, p.Enabled)
}
}
m.mu.Lock()
m.plugins = append(m.plugins, p)
@@ -131,12 +129,6 @@ func (m *Manager) loadPlugin(path string) (*Plugin, error) {
"on_response": false,
"on_history_entry": false,
}
// Fixed-sync hooks: always sync, not configurable.
fixedSyncHooks := map[string]struct{}{
"on_config": {},
"on_quit": {},
}
for hookName, defaultSync := range configurableHooks {
if tbl, ok := pluginTable.RawGetString(hookName).(*lua.LTable); ok {
p.hooks[hookName] = HookConfig{Sync: tbl.RawGetString("sync") == lua.LTrue}
@@ -146,9 +138,9 @@ func (m *Manager) loadPlugin(path string) (*Plugin, error) {
p.hooks[hookName] = HookConfig{Sync: defaultSync}
}
}
for hookName := range fixedSyncHooks {
if p.L.GetGlobal(hookName) != lua.LNil {
p.hooks[hookName] = HookConfig{Sync: true}
for _, fixedSync := range []string{"on_config", "on_quit"} {
if p.L.GetGlobal(fixedSync) != lua.LNil {
p.hooks[fixedSync] = HookConfig{Sync: true}
}
}
@@ -179,10 +171,9 @@ func (m *Manager) TogglePlugin(name string) {
found.mu.Lock()
found.Enabled = !found.Enabled
enabled := found.Enabled
configText := found.ConfigText
found.mu.Unlock()
if m.db != nil {
if err := m.db.SavePluginState(name, enabled, configText); err != nil {
if m.pluginsFile != nil {
if err := m.pluginsFile.setEnabled(name, enabled); err != nil {
log.Printf("plugin %s: save state: %v", name, err)
}
}
@@ -196,8 +187,8 @@ func (m *Manager) TogglePlugin(name string) {
disableIfFalse := func(p *Plugin, ret lua.LValue) {
if ret == lua.LFalse {
p.Enabled = false
if m.db != nil {
if err := m.db.SavePluginState(p.Name, false, p.ConfigText); err != nil {
if m.pluginsFile != nil {
if err := m.pluginsFile.setEnabled(p.Name, false); err != nil {
log.Printf("plugin %s: save state: %v", p.Name, err)
}
}
@@ -241,41 +232,35 @@ func (m *Manager) SaveConfig(name, configText string) {
}
found.mu.Lock()
found.ConfigText = configText
enabled := found.Enabled
_, hasOnConfig := found.hooks["on_config"]
found.mu.Unlock()
if m.db != nil {
if err := m.db.SavePluginState(name, enabled, configText); err != nil {
log.Printf("plugin %s: save state: %v", name, err)
if m.pluginsFile != nil {
if err := m.pluginsFile.setConfig(name, configText); err != nil {
log.Printf("plugin %s: save config: %v", name, err)
}
}
if !hasOnConfig {
if _, ok := found.hooks["on_config"]; !ok {
return
}
// on_config is always sync.
found.mu.Lock()
if _, err := callHook(found, "on_config", lua.LString(configText)); err != nil {
log.Printf("plugin %s on_config (config reload): %v", name, err)
if _, err := callHook(found, "on_config"); err != nil {
log.Printf("plugin %s on_config: %v", name, err)
}
found.mu.Unlock()
}
func (m *Manager) RunOnStart() {
// on_config runs first, always sync, for every enabled plugin that has it.
for _, p := range m.GetPlugins() {
if !p.Enabled {
continue
}
if _, ok := p.hooks["on_config"]; !ok {
continue
if _, ok := p.hooks["on_config"]; ok {
p.mu.Lock()
if _, err := callHook(p, "on_config"); err != nil {
log.Printf("plugin %s on_config: %v", p.Name, err)
}
p.mu.Unlock()
}
p.mu.Lock()
if _, err := callHook(p, "on_config", lua.LString(p.ConfigText)); err != nil {
log.Printf("plugin %s on_config: %v", p.Name, err)
}
p.mu.Unlock()
}
// on_start runs after, sync or async depending on plugin config.
for _, p := range m.GetPlugins() {
if !p.Enabled {
continue
@@ -287,8 +272,8 @@ func (m *Manager) RunOnStart() {
disableIfFalse := func(p *Plugin, ret lua.LValue) {
if ret == lua.LFalse {
p.Enabled = false
if m.db != nil {
if err := m.db.SavePluginState(p.Name, false, p.ConfigText); err != nil {
if m.pluginsFile != nil {
if err := m.pluginsFile.setEnabled(p.Name, false); err != nil {
log.Printf("plugin %s: save state: %v", p.Name, err)
}
}
+78
View File
@@ -0,0 +1,78 @@
package plugins
import (
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
type pluginFileEntry struct {
Enable bool `yaml:"enable"`
Config string `yaml:"config,omitempty"`
}
type pluginsFileData struct {
Plugins map[string]pluginFileEntry `yaml:"plugins"`
}
type PluginsFile struct {
path string
data pluginsFileData
}
func OpenPluginsFile(dbPath string) (*PluginsFile, error) {
path := filepath.Join(filepath.Dir(dbPath), "plugins.yaml")
pf := &PluginsFile{
path: path,
data: pluginsFileData{Plugins: make(map[string]pluginFileEntry)},
}
raw, err := os.ReadFile(path)
if os.IsNotExist(err) {
return pf, nil
}
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(raw, &pf.data); err != nil {
return nil, err
}
if pf.data.Plugins == nil {
pf.data.Plugins = make(map[string]pluginFileEntry)
}
return pf, nil
}
func (pf *PluginsFile) save() error {
raw, err := yaml.Marshal(&pf.data)
if err != nil {
return err
}
return os.WriteFile(pf.path, raw, 0o600)
}
func (pf *PluginsFile) get(name string) (enabled bool, config string, found bool) {
e, ok := pf.data.Plugins[name]
return e.Enable, e.Config, ok
}
func (pf *PluginsFile) register(name string, defaultEnabled bool) {
if _, ok := pf.data.Plugins[name]; !ok {
pf.data.Plugins[name] = pluginFileEntry{Enable: defaultEnabled}
_ = pf.save()
}
}
func (pf *PluginsFile) setEnabled(name string, enabled bool) error {
e := pf.data.Plugins[name]
e.Enable = enabled
pf.data.Plugins[name] = e
return pf.save()
}
func (pf *PluginsFile) setConfig(name string, configText string) error {
e := pf.data.Plugins[name]
e.Config = configText
pf.data.Plugins[name] = e
return pf.save()
}