feat: add parser manage command

This commit is contained in:
krau
2025-11-07 12:01:54 +08:00
parent f80ecae3cc
commit 450d32b2b7
4 changed files with 159 additions and 11 deletions

View File

@@ -6,9 +6,11 @@ import (
"fmt"
"os"
"path/filepath"
"sync"
"github.com/charmbracelet/log"
"github.com/dop251/goja"
"github.com/krau/SaveAny-Bot/config"
"github.com/krau/SaveAny-Bot/pkg/parser"
)
@@ -98,6 +100,7 @@ func newJSParser(vm *goja.Runtime, canHandleFunc, parseFunc goja.Value, metadata
return p
}
// 加载指定文件夹下的所有 JS 解析器插件
func LoadPlugins(ctx context.Context, dir string) error {
entries, err := os.ReadDir(dir)
if err != nil {
@@ -130,3 +133,40 @@ func LoadPlugins(ctx context.Context, dir string) error {
}
return nil
}
var (
pluginNameMu sync.Map
)
func AddPlugin(ctx context.Context, code string, name string) error {
value, _ := pluginNameMu.LoadOrStore(name, &sync.Mutex{})
mu := value.(*sync.Mutex)
mu.Lock()
defer mu.Unlock()
return addPlugin(ctx, code, name)
}
func addPlugin(ctx context.Context, code string, name string) error {
logger := log.FromContext(ctx).WithPrefix(fmt.Sprintf("[plugin|parser]/%s", name))
vm := goja.New()
vm.Set("registerParser", jsRegisterParser(vm))
vm.Set("console", jsConsole(logger))
vm.Set("ghttp", jsGhttp(vm))
vm.Set("playwright", jsPlaywright(vm, logger))
if _, err := vm.RunString(code); err != nil {
return fmt.Errorf("error loading plugin %s: %w", name, err)
}
dir := "plugins"
configuredDirs := config.C().Parser.PluginDirs
if len(configuredDirs) > 0 {
dir = configuredDirs[0]
}
if err := os.MkdirAll(dir, 0755); err == nil {
pluginPath := filepath.Join(dir, name)
if err := os.WriteFile(pluginPath, []byte(code), 0644); err != nil {
logger.Warn("Failed to save plugin file: " + err.Error())
}
}
return nil
}

View File

@@ -2,6 +2,7 @@ package parsers
import (
"encoding/json"
"errors"
"fmt"
"io"
"log/slog"
@@ -19,45 +20,44 @@ func jsRegisterParser(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value
return func(call goja.FunctionCall) goja.Value {
jsObj := call.Argument(0)
if jsObj == nil || goja.IsUndefined(jsObj) || goja.IsNull(jsObj) {
panic("registerParser expects an object { canHandle, parse }")
return vm.NewGoError(errors.New("registerParser expects an object { canHandle, parse }"))
}
obj := jsObj.ToObject(vm)
if obj == nil {
panic("registerParser: cannot convert argument to object")
return vm.NewGoError(errors.New("registerParser expects an object { canHandle, parse }"))
}
metaValue := obj.Get("metadata")
if metaValue == nil || goja.IsUndefined(metaValue) {
panic("parser must provide metadata")
return vm.NewGoError(errors.New("parser must provide metadata"))
}
var metadata PluginMeta
if exported := metaValue.Export(); exported != nil {
data, err := json.Marshal(exported)
if err != nil {
panic(fmt.Sprintf("failed to marshal metadata to JSON: %v", err))
return vm.NewGoError(fmt.Errorf("failed to marshal metadata to JSON: %w", err))
}
if err := json.Unmarshal(data, &metadata); err != nil {
panic(fmt.Sprintf("failed to unmarshal JSON to PluginMeta: %v", err))
return vm.NewGoError(fmt.Errorf("failed to unmarshal JSON to PluginMeta: %w", err))
}
} else {
panic("metadata cannot be null or undefined")
return vm.NewGoError(errors.New("metadata cannot be null or undefined"))
}
pluginV := semver.MustParse(metadata.Version)
if pluginV.LT(MinimumParserVersion) {
panic(fmt.Sprintf("parser version %s is not supported, must be at least %s", metadata.Version, MinimumParserVersion))
return vm.NewGoError(fmt.Errorf("parser version %s is not supported, must be at least %s", metadata.Version, MinimumParserVersion))
}
if pluginV.Major > LatestParserVersion.Major {
panic(fmt.Sprintf("parser major version %d is too new, latest supported major version is %d", pluginV.Major, LatestParserVersion.Major))
log.Printf("warning: parser major version %d is newer than latest supported major version %d", pluginV.Major, LatestParserVersion.Major)
}
handleFn := obj.Get("canHandle")
parseFn := obj.Get("parse")
if parseFn == nil || goja.IsUndefined(parseFn) {
panic("parser must provide a parse function")
return vm.NewGoError(errors.New("parser must provide a parse function"))
}
parsers = append(parsers, newJSParser(vm, handleFn, parseFn, metadata))
AddParser(newJSParser(vm, handleFn, parseFn, metadata))
return goja.Undefined()
}
}
@@ -74,6 +74,16 @@ var jsConsole = func(logger *log.Logger) map[string]any {
logger.Info(args[0])
}
},
"error": func(args ...any) {
if len(args) == 0 {
return
}
if len(args) > 1 {
logger.Error(fmt.Sprint(args[0]), args[1:]...)
} else {
logger.Error(fmt.Sprint(args[0]))
}
},
}
}