mirror of
https://github.com/krau/SaveAny-Bot.git
synced 2026-05-11 22:29:41 +08:00
refactor: js plugin api
This commit is contained in:
@@ -1,19 +1,17 @@
|
|||||||
package parsers
|
package js
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/krau/SaveAny-Bot/common/utils/netutil"
|
"github.com/krau/SaveAny-Bot/common/utils/netutil"
|
||||||
"github.com/playwright-community/playwright-go"
|
"github.com/krau/SaveAny-Bot/parsers/parsers"
|
||||||
)
|
)
|
||||||
|
|
||||||
func jsRegisterParser(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
func jsRegisterParser(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value {
|
||||||
@@ -57,7 +55,7 @@ func jsRegisterParser(vm *goja.Runtime) func(call goja.FunctionCall) goja.Value
|
|||||||
if parseFn == nil || goja.IsUndefined(parseFn) {
|
if parseFn == nil || goja.IsUndefined(parseFn) {
|
||||||
return vm.NewGoError(errors.New("parser must provide a parse function"))
|
return vm.NewGoError(errors.New("parser must provide a parse function"))
|
||||||
}
|
}
|
||||||
AddParser(newJSParser(vm, handleFn, parseFn, metadata))
|
parsers.Add(newJSParser(vm, handleFn, parseFn, metadata))
|
||||||
return goja.Undefined()
|
return goja.Undefined()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,74 +171,3 @@ var jsGhttp = func(vm *goja.Runtime) *goja.Object {
|
|||||||
})
|
})
|
||||||
return ghttp
|
return ghttp
|
||||||
}
|
}
|
||||||
|
|
||||||
var jsPlaywright = func(vm *goja.Runtime, logger *log.Logger) *goja.Object {
|
|
||||||
pwObj := vm.NewObject()
|
|
||||||
var installOnce sync.Once
|
|
||||||
slogger := slog.New(logger)
|
|
||||||
pwObj.Set("get", func(call goja.FunctionCall) goja.Value {
|
|
||||||
url := call.Argument(0).String()
|
|
||||||
var installErr error
|
|
||||||
installOnce.Do(func() {
|
|
||||||
installErr = playwright.Install(&playwright.RunOptions{
|
|
||||||
Browsers: []string{"chromium"},
|
|
||||||
DriverDirectory: "./playwright",
|
|
||||||
Logger: slogger,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
if installErr != nil {
|
|
||||||
return vm.ToValue(map[string]any{
|
|
||||||
"error": fmt.Sprintf("failed to install playwright: %v", installErr),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pw, err := playwright.Run(&playwright.RunOptions{
|
|
||||||
DriverDirectory: "./playwright",
|
|
||||||
Logger: slogger,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return vm.ToValue(map[string]any{
|
|
||||||
"error": fmt.Sprintf("failed to start playwright: %v", err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
defer pw.Stop()
|
|
||||||
|
|
||||||
browser, err := pw.Chromium.Launch()
|
|
||||||
if err != nil {
|
|
||||||
return vm.ToValue(map[string]any{
|
|
||||||
"error": fmt.Sprintf("failed to launch browser: %v", err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
defer browser.Close()
|
|
||||||
|
|
||||||
page, err := browser.NewPage()
|
|
||||||
if err != nil {
|
|
||||||
return vm.ToValue(map[string]any{
|
|
||||||
"error": fmt.Sprintf("failed to create page: %v", err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := page.Goto(url, playwright.PageGotoOptions{
|
|
||||||
WaitUntil: playwright.WaitUntilStateNetworkidle,
|
|
||||||
Timeout: playwright.Float(60000),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return vm.ToValue(map[string]any{
|
|
||||||
"error": fmt.Sprintf("failed to navigate: %v", err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if resp != nil && resp.Status() >= 400 {
|
|
||||||
return vm.ToValue(map[string]any{
|
|
||||||
"error": fmt.Sprintf("bad status code: %d", resp.Status()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
content, err := page.Content()
|
|
||||||
if err != nil {
|
|
||||||
return vm.ToValue(map[string]any{
|
|
||||||
"error": fmt.Sprintf("failed to get page content: %v", err),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return vm.ToValue(content)
|
|
||||||
})
|
|
||||||
return pwObj
|
|
||||||
}
|
|
||||||
82
parsers/js/api_playwright.go
Normal file
82
parsers/js/api_playwright.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/playwright-community/playwright-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsPlaywright = func(vm *goja.Runtime, logger *log.Logger) *goja.Object {
|
||||||
|
pwObj := vm.NewObject()
|
||||||
|
var installOnce sync.Once
|
||||||
|
slogger := slog.New(logger)
|
||||||
|
pwObj.Set("get", func(call goja.FunctionCall) goja.Value {
|
||||||
|
url := call.Argument(0).String()
|
||||||
|
var installErr error
|
||||||
|
installOnce.Do(func() {
|
||||||
|
installErr = playwright.Install(&playwright.RunOptions{
|
||||||
|
Browsers: []string{"chromium"},
|
||||||
|
DriverDirectory: "./playwright",
|
||||||
|
Logger: slogger,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if installErr != nil {
|
||||||
|
return vm.ToValue(map[string]any{
|
||||||
|
"error": fmt.Sprintf("failed to install playwright: %v", installErr),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pw, err := playwright.Run(&playwright.RunOptions{
|
||||||
|
DriverDirectory: "./playwright",
|
||||||
|
Logger: slogger,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return vm.ToValue(map[string]any{
|
||||||
|
"error": fmt.Sprintf("failed to start playwright: %v", err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defer pw.Stop()
|
||||||
|
|
||||||
|
browser, err := pw.Chromium.Launch()
|
||||||
|
if err != nil {
|
||||||
|
return vm.ToValue(map[string]any{
|
||||||
|
"error": fmt.Sprintf("failed to launch browser: %v", err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
defer browser.Close()
|
||||||
|
|
||||||
|
page, err := browser.NewPage()
|
||||||
|
if err != nil {
|
||||||
|
return vm.ToValue(map[string]any{
|
||||||
|
"error": fmt.Sprintf("failed to create page: %v", err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := page.Goto(url, playwright.PageGotoOptions{
|
||||||
|
WaitUntil: playwright.WaitUntilStateNetworkidle,
|
||||||
|
Timeout: playwright.Float(60000),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return vm.ToValue(map[string]any{
|
||||||
|
"error": fmt.Sprintf("failed to navigate: %v", err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if resp != nil && resp.Status() >= 400 {
|
||||||
|
return vm.ToValue(map[string]any{
|
||||||
|
"error": fmt.Sprintf("bad status code: %d", resp.Status()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
content, err := page.Content()
|
||||||
|
if err != nil {
|
||||||
|
return vm.ToValue(map[string]any{
|
||||||
|
"error": fmt.Sprintf("failed to get page content: %v", err),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return vm.ToValue(content)
|
||||||
|
})
|
||||||
|
return pwObj
|
||||||
|
}
|
||||||
19
parsers/js/api_playwright_stub.go
Normal file
19
parsers/js/api_playwright_stub.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build no_playwright
|
||||||
|
|
||||||
|
package js
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsPlaywright = func(vm *goja.Runtime, _ *log.Logger) *goja.Object {
|
||||||
|
pwObj := vm.NewObject()
|
||||||
|
unsupported := vm.ToValue(map[string]any{
|
||||||
|
"error": "playwright is not supported in this build",
|
||||||
|
})
|
||||||
|
pwObj.Set("get", func(call goja.FunctionCall) goja.Value {
|
||||||
|
return unsupported
|
||||||
|
})
|
||||||
|
return pwObj
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package parsers
|
package js
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package parsers
|
package js
|
||||||
|
|
||||||
import "github.com/blang/semver"
|
import "github.com/blang/semver"
|
||||||
|
|
||||||
@@ -3,41 +3,16 @@ package parsers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/krau/SaveAny-Bot/config"
|
"github.com/krau/SaveAny-Bot/parsers/js"
|
||||||
"github.com/krau/SaveAny-Bot/parsers/kemono"
|
"github.com/krau/SaveAny-Bot/parsers/native/kemono"
|
||||||
"github.com/krau/SaveAny-Bot/parsers/twitter"
|
"github.com/krau/SaveAny-Bot/parsers/native/twitter"
|
||||||
|
"github.com/krau/SaveAny-Bot/parsers/parsers"
|
||||||
"github.com/krau/SaveAny-Bot/pkg/parser"
|
"github.com/krau/SaveAny-Bot/pkg/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
parsers []parser.Parser
|
|
||||||
parsersMu sync.Mutex
|
|
||||||
doConfig sync.Once
|
|
||||||
configParsers = func() {
|
|
||||||
if len(parsers) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, pser := range parsers {
|
|
||||||
if configurable, ok := pser.(parser.ConfigurableParser); ok {
|
|
||||||
cfg := config.C().GetParserConfigByName(configurable.Name())
|
|
||||||
if err := configurable.Configure(cfg); err != nil {
|
|
||||||
fmt.Printf("Error configuring parser %s: %v\n", configurable.Name(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func AddParser(p ...parser.Parser) {
|
|
||||||
parsersMu.Lock()
|
|
||||||
defer parsersMu.Unlock()
|
|
||||||
parsers = append(parsers, p...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
AddParser(new(twitter.TwitterParser), new(kemono.KemonoParser))
|
parsers.Add(new(twitter.TwitterParser), new(kemono.KemonoParser))
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -45,12 +20,11 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ParseWithContext(ctx context.Context, url string) (*parser.Item, error) {
|
func ParseWithContext(ctx context.Context, url string) (*parser.Item, error) {
|
||||||
doConfig.Do(configParsers)
|
|
||||||
ch := make(chan *parser.Item, 1)
|
ch := make(chan *parser.Item, 1)
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for _, pser := range parsers {
|
for _, pser := range parsers.Get() {
|
||||||
if !pser.CanHandle(url) {
|
if !pser.CanHandle(url) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -76,11 +50,18 @@ func ParseWithContext(ctx context.Context, url string) (*parser.Item, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CanHandle(url string) (bool, parser.Parser) {
|
func CanHandle(url string) (bool, parser.Parser) {
|
||||||
doConfig.Do(configParsers)
|
for _, pser := range parsers.Get() {
|
||||||
for _, pser := range parsers {
|
|
||||||
if pser.CanHandle(url) {
|
if pser.CanHandle(url) {
|
||||||
return true, pser
|
return true, pser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoadPlugins(ctx context.Context, dir string) error {
|
||||||
|
return js.LoadPlugins(ctx, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddPlugin(ctx context.Context, code string, name string) error {
|
||||||
|
return js.AddPlugin(ctx, code, name)
|
||||||
|
}
|
||||||
|
|||||||
44
parsers/parsers/parsers.go
Normal file
44
parsers/parsers/parsers.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package parsers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/krau/SaveAny-Bot/config"
|
||||||
|
"github.com/krau/SaveAny-Bot/pkg/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
parsers []parser.Parser
|
||||||
|
mu sync.Mutex
|
||||||
|
configOnce sync.Once
|
||||||
|
configParsers = func() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if len(parsers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, pser := range parsers {
|
||||||
|
if configurable, ok := pser.(parser.ConfigurableParser); ok {
|
||||||
|
cfg := config.C().GetParserConfigByName(configurable.Name())
|
||||||
|
if err := configurable.Configure(cfg); err != nil {
|
||||||
|
fmt.Printf("Error configuring parser %s: %v\n", configurable.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func Add(p ...parser.Parser) {
|
||||||
|
configOnce.Do(configParsers)
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
parsers = append(parsers, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get() []parser.Parser {
|
||||||
|
configOnce.Do(configParsers)
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
return parsers
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user