node build fixed
This commit is contained in:
276
seanime-2.9.10/internal/extension_repo/goja.go
Normal file
276
seanime-2.9.10/internal/extension_repo/goja.go
Normal file
@@ -0,0 +1,276 @@
|
||||
package extension_repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"seanime/internal/extension"
|
||||
goja_bindings "seanime/internal/goja/goja_bindings"
|
||||
"seanime/internal/library/anime"
|
||||
"seanime/internal/plugin"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/5rahim/habari"
|
||||
"github.com/dop251/goja"
|
||||
gojabuffer "github.com/dop251/goja_nodejs/buffer"
|
||||
gojarequire "github.com/dop251/goja_nodejs/require"
|
||||
gojaurl "github.com/dop251/goja_nodejs/url"
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cast"
|
||||
)
|
||||
|
||||
// GojaExtension is stored in the repository extension map, giving access to the VMs.
|
||||
// Current use: Kill the VM when the extension is unloaded.
|
||||
type GojaExtension interface {
|
||||
PutVM(*goja.Runtime)
|
||||
ClearInterrupt()
|
||||
GetExtension() *extension.Extension
|
||||
}
|
||||
|
||||
var cachedArrayOfTypes = plugin.NewStore[reflect.Type, reflect.Type](nil)
|
||||
|
||||
func BindUserConfig(vm *goja.Runtime, ext *extension.Extension, logger *zerolog.Logger) {
|
||||
vm.Set("$getUserPreference", func(call goja.FunctionCall) goja.Value {
|
||||
if ext.SavedUserConfig == nil {
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
key := call.Argument(0).String()
|
||||
value, ok := ext.SavedUserConfig.Values[key]
|
||||
if !ok {
|
||||
// Check if the field has a default value
|
||||
for _, field := range ext.UserConfig.Fields {
|
||||
if field.Name == key && field.Default != "" {
|
||||
return vm.ToValue(field.Default)
|
||||
}
|
||||
}
|
||||
|
||||
return goja.Undefined()
|
||||
}
|
||||
|
||||
return vm.ToValue(value)
|
||||
})
|
||||
}
|
||||
|
||||
// ShareBinds binds the shared bindings to the VM
|
||||
// This is called once per VM
|
||||
func ShareBinds(vm *goja.Runtime, logger *zerolog.Logger) {
|
||||
registry := new(gojarequire.Registry)
|
||||
registry.Enable(vm)
|
||||
|
||||
fm := goja_bindings.DefaultFieldMapper{}
|
||||
vm.SetFieldNameMapper(fm)
|
||||
// goja.TagFieldNameMapper("json", true)
|
||||
|
||||
bindings := []struct {
|
||||
name string
|
||||
fn func(*goja.Runtime) error
|
||||
}{
|
||||
{"url", func(vm *goja.Runtime) error { gojaurl.Enable(vm); return nil }},
|
||||
{"buffer", func(vm *goja.Runtime) error { gojabuffer.Enable(vm); return nil }},
|
||||
{"fetch", func(vm *goja.Runtime) error { goja_bindings.BindFetch(vm); return nil }},
|
||||
{"console", func(vm *goja.Runtime) error { goja_bindings.BindConsole(vm, logger); return nil }},
|
||||
{"formData", func(vm *goja.Runtime) error { goja_bindings.BindFormData(vm); return nil }},
|
||||
{"document", func(vm *goja.Runtime) error { goja_bindings.BindDocument(vm); return nil }},
|
||||
{"crypto", func(vm *goja.Runtime) error { goja_bindings.BindCrypto(vm); return nil }},
|
||||
{"torrentUtils", func(vm *goja.Runtime) error { goja_bindings.BindTorrentUtils(vm); return nil }},
|
||||
}
|
||||
|
||||
for _, binding := range bindings {
|
||||
if err := binding.fn(vm); err != nil {
|
||||
logger.Error().Err(err).Str("name", binding.name).Msg("failed to bind")
|
||||
}
|
||||
}
|
||||
|
||||
vm.Set("__isOffline__", plugin.GlobalAppContext.IsOffline())
|
||||
|
||||
vm.Set("$toString", func(raw any, maxReaderBytes int) (string, error) {
|
||||
switch v := raw.(type) {
|
||||
case io.Reader:
|
||||
if maxReaderBytes == 0 {
|
||||
maxReaderBytes = 32 << 20 // 32 MB
|
||||
}
|
||||
|
||||
limitReader := io.LimitReader(v, int64(maxReaderBytes))
|
||||
|
||||
bodyBytes, readErr := io.ReadAll(limitReader)
|
||||
if readErr != nil {
|
||||
return "", readErr
|
||||
}
|
||||
|
||||
return string(bodyBytes), nil
|
||||
default:
|
||||
str, err := cast.ToStringE(v)
|
||||
if err == nil {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// as a last attempt try to json encode the value
|
||||
rawBytes, _ := json.Marshal(raw)
|
||||
|
||||
return string(rawBytes), nil
|
||||
}
|
||||
})
|
||||
|
||||
vm.Set("$toBytes", func(raw any) ([]byte, error) {
|
||||
switch v := raw.(type) {
|
||||
case io.Reader:
|
||||
bodyBytes, readErr := io.ReadAll(v)
|
||||
if readErr != nil {
|
||||
return nil, readErr
|
||||
}
|
||||
|
||||
return bodyBytes, nil
|
||||
case string:
|
||||
return []byte(v), nil
|
||||
case []byte:
|
||||
return v, nil
|
||||
case []rune:
|
||||
return []byte(string(v)), nil
|
||||
default:
|
||||
// as a last attempt try to json encode the value
|
||||
rawBytes, _ := json.Marshal(raw)
|
||||
return rawBytes, nil
|
||||
}
|
||||
})
|
||||
|
||||
vm.Set("$toError", func(raw any) error {
|
||||
if err, ok := raw.(error); ok {
|
||||
return err
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v", raw)
|
||||
})
|
||||
|
||||
vm.Set("$sleep", func(milliseconds int64) {
|
||||
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
|
||||
})
|
||||
|
||||
vm.Set("$arrayOf", func(model any) any {
|
||||
mt := reflect.TypeOf(model)
|
||||
st := cachedArrayOfTypes.GetOrSet(mt, func() reflect.Type {
|
||||
return reflect.SliceOf(mt)
|
||||
})
|
||||
|
||||
return reflect.New(st).Elem().Addr().Interface()
|
||||
})
|
||||
|
||||
vm.Set("$unmarshal", func(data, dst any) error {
|
||||
raw, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(raw, &dst)
|
||||
})
|
||||
|
||||
vm.Set("$toPointer", func(data interface{}) interface{} {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
v := data
|
||||
return &v
|
||||
})
|
||||
|
||||
vm.Set("$Context", func(call goja.ConstructorCall) *goja.Object {
|
||||
var instance context.Context
|
||||
|
||||
oldCtx, ok := call.Argument(0).Export().(context.Context)
|
||||
if ok {
|
||||
instance = oldCtx
|
||||
} else {
|
||||
instance = context.Background()
|
||||
}
|
||||
|
||||
key := call.Argument(1).Export()
|
||||
if key != nil {
|
||||
instance = context.WithValue(instance, key, call.Argument(2).Export())
|
||||
}
|
||||
|
||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||
instanceValue.SetPrototype(call.This.Prototype())
|
||||
|
||||
return instanceValue
|
||||
})
|
||||
|
||||
//
|
||||
// Habari
|
||||
//
|
||||
habariObj := vm.NewObject()
|
||||
_ = habariObj.Set("parse", func(filename string) *habari.Metadata {
|
||||
return habari.Parse(filename)
|
||||
})
|
||||
vm.Set("$habari", habariObj)
|
||||
|
||||
//
|
||||
// Anime Utils
|
||||
//
|
||||
animeUtilsObj := vm.NewObject()
|
||||
_ = animeUtilsObj.Set("newLocalFileWrapper", func(lfs []*anime.LocalFile) *anime.LocalFileWrapper {
|
||||
return anime.NewLocalFileWrapper(lfs)
|
||||
})
|
||||
vm.Set("$animeUtils", animeUtilsObj)
|
||||
|
||||
vm.Set("$waitGroup", func() *sync.WaitGroup {
|
||||
return &sync.WaitGroup{}
|
||||
})
|
||||
|
||||
// Run a function in a new goroutine
|
||||
// The Goja runtime is not thread safe, so nothing related to the VM should be done in this goroutine
|
||||
// You can use the $waitGroup to wait for multiple goroutines to finish
|
||||
// You can use $store to communicate with the main thread
|
||||
vm.Set("$unsafeGoroutine", func(fn func()) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
logger.Error().Err(fmt.Errorf("%v", r)).Msg("goroutine panic")
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}()
|
||||
})
|
||||
}
|
||||
|
||||
// JSVMTypescriptToJS converts typescript to javascript
|
||||
func JSVMTypescriptToJS(ts string) (string, error) {
|
||||
result := api.Transform(ts, api.TransformOptions{
|
||||
Target: api.ES2018,
|
||||
Loader: api.LoaderTS,
|
||||
Format: api.FormatDefault,
|
||||
MinifyWhitespace: true,
|
||||
MinifySyntax: true,
|
||||
Sourcemap: api.SourceMapNone,
|
||||
})
|
||||
|
||||
if len(result.Errors) > 0 {
|
||||
var errMsgs []string
|
||||
for _, err := range result.Errors {
|
||||
errMsgs = append(errMsgs, err.Text)
|
||||
}
|
||||
return "", fmt.Errorf("typescript compilation errors: %v", errMsgs)
|
||||
}
|
||||
|
||||
return string(result.Code), nil
|
||||
}
|
||||
|
||||
// structToMap converts a struct to "JSON-like" map for Goja extensions
|
||||
// This is used to pass structs to Goja extensions
|
||||
func structToMap(obj interface{}) map[string]interface{} {
|
||||
// Convert the struct to a map
|
||||
jsonData, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var data map[string]interface{}
|
||||
err = json.Unmarshal(jsonData, &data)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
Reference in New Issue
Block a user