mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
225 lines
6.5 KiB
Go
225 lines
6.5 KiB
Go
package automation
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"io/fs"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
DualInstanceRuntimeScriptID = "dual-instance-runtime-switch"
|
|
NewsQueryTXTScriptID = "news-query-txt"
|
|
ProtonMailFirstMessageID = "proton-mail-first-message"
|
|
WebImageGenerateScriptID = "web-image-generate-download"
|
|
builtinScriptLibraryRoot = "demo-library"
|
|
)
|
|
|
|
var builtinScriptLibraryPackageDirs = map[string]string{
|
|
DualInstanceRuntimeScriptID: builtinScriptLibraryRoot + "/" + DualInstanceRuntimeScriptID,
|
|
NewsQueryTXTScriptID: builtinScriptLibraryRoot + "/" + NewsQueryTXTScriptID,
|
|
ProtonMailFirstMessageID: builtinScriptLibraryRoot + "/" + ProtonMailFirstMessageID,
|
|
WebImageGenerateScriptID: builtinScriptLibraryRoot + "/" + WebImageGenerateScriptID,
|
|
}
|
|
|
|
var builtinScriptLibraryDefaultOrder = []string{
|
|
DualInstanceRuntimeScriptID,
|
|
NewsQueryTXTScriptID,
|
|
ProtonMailFirstMessageID,
|
|
WebImageGenerateScriptID,
|
|
}
|
|
|
|
//go:embed demo-library
|
|
var builtinScriptLibraryFS embed.FS
|
|
|
|
func DefaultScriptBundles() ([]ImportedBundle, error) {
|
|
bundles := make([]ImportedBundle, 0, len(builtinScriptLibraryDefaultOrder))
|
|
for _, scriptID := range builtinScriptLibraryDefaultOrder {
|
|
dir, ok := builtinScriptLibraryPackageDirs[scriptID]
|
|
if !ok {
|
|
return nil, fmt.Errorf("内置脚本 %q 不存在", scriptID)
|
|
}
|
|
|
|
bundle, err := importBuiltinScriptLibraryBundle(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bundles = append(bundles, bundle)
|
|
}
|
|
return bundles, nil
|
|
}
|
|
|
|
func ImportBuiltinBundleFromSource(source ScriptSource) (ImportedBundle, error) {
|
|
dir, err := resolveBuiltinScriptLibraryDir(source)
|
|
if err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
|
|
bundle, err := importBuiltinScriptLibraryBundle(dir)
|
|
if err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
bundle.Record.Source.ImportedAt = time.Now().Format(time.RFC3339)
|
|
return bundle, nil
|
|
}
|
|
|
|
func resolveBuiltinScriptLibraryDir(source ScriptSource) (string, error) {
|
|
candidates := []string{
|
|
strings.TrimSpace(source.Path),
|
|
path.Base(strings.TrimSpace(strings.TrimPrefix(source.URI, "repo://"))),
|
|
path.Base(strings.TrimSpace(source.URI)),
|
|
}
|
|
|
|
for _, candidate := range candidates {
|
|
if candidate == "" {
|
|
continue
|
|
}
|
|
if dir, ok := builtinScriptLibraryPackageDirs[candidate]; ok {
|
|
return dir, nil
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case strings.TrimSpace(source.Path) != "":
|
|
return "", fmt.Errorf("内置脚本 %q 不存在", strings.TrimSpace(source.Path))
|
|
case strings.TrimSpace(source.URI) != "":
|
|
return "", fmt.Errorf("内置脚本来源 %q 不存在", strings.TrimSpace(source.URI))
|
|
default:
|
|
return "", fmt.Errorf("内置脚本来源缺失")
|
|
}
|
|
}
|
|
|
|
func importBuiltinScriptLibraryBundle(packageDir string) (ImportedBundle, error) {
|
|
manifestPath := path.Join(packageDir, scriptPackageManifestName)
|
|
manifestData, err := fs.ReadFile(builtinScriptLibraryFS, manifestPath)
|
|
if err != nil {
|
|
return ImportedBundle{}, fmt.Errorf("read built-in script manifest failed: %w", err)
|
|
}
|
|
|
|
descriptor, err := parseImportManifest(manifestData)
|
|
if err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
|
|
entryFile := normalizeScriptEntryFile(mapStringValueAny(descriptor, "entryFile"))
|
|
if entryFile == "" {
|
|
return ImportedBundle{}, fmt.Errorf("built-in script manifest is missing entryFile")
|
|
}
|
|
|
|
entryData, err := fs.ReadFile(builtinScriptLibraryFS, path.Join(packageDir, entryFile))
|
|
if err != nil {
|
|
return ImportedBundle{}, fmt.Errorf("read built-in script entry failed: %w", err)
|
|
}
|
|
|
|
files, err := collectImportedBundleFilesFromFS(builtinScriptLibraryFS, packageDir)
|
|
if err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
|
|
record, err := buildImportedRecord(scriptImportEnvelope{
|
|
Format: mapStringValueAny(descriptor, "format"),
|
|
PackageFormat: mapStringValueAny(descriptor, "packageFormat"),
|
|
ManifestVersion: mapIntValueAny(descriptor, "manifestVersion"),
|
|
ID: mapStringValueAny(descriptor, "id"),
|
|
Name: mapStringValueAny(descriptor, "name"),
|
|
Description: mapStringValueAny(descriptor, "description"),
|
|
Type: mapStringValueAny(descriptor, "type"),
|
|
Status: mapStringValueAny(descriptor, "status"),
|
|
EntryFile: entryFile,
|
|
Tags: mapStringSliceValue(descriptor, "tags"),
|
|
Selector: descriptor["selector"],
|
|
SelectorText: descriptor["selectorText"],
|
|
Params: descriptor["params"],
|
|
ParamsText: descriptor["paramsText"],
|
|
ScriptText: string(entryData),
|
|
Notes: mapStringValueAny(descriptor, "notes"),
|
|
TargetConfig: mapObjectValue(descriptor, "targetConfig"),
|
|
PublicAPI: mapObjectValue(descriptor, "publicAPI"),
|
|
Source: mapObjectValue(descriptor, "source"),
|
|
}, path.Base(packageDir), "")
|
|
if err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
if manifestID := strings.TrimSpace(mapStringValueAny(descriptor, "id")); manifestID != "" {
|
|
record.ID = manifestID
|
|
}
|
|
|
|
bundle := ImportedBundle{
|
|
Record: record,
|
|
Files: files,
|
|
}
|
|
if err := validateImportedBundle(bundle.Record, bundle.Files); err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
return bundle, nil
|
|
}
|
|
|
|
func collectImportedBundleFilesFromFS(fsys fs.FS, root string) ([]ImportedBundleFile, error) {
|
|
files := make([]ImportedBundleFile, 0, 8)
|
|
totalSize := 0
|
|
|
|
err := fs.WalkDir(fsys, root, func(current string, entry fs.DirEntry, walkErr error) error {
|
|
if walkErr != nil {
|
|
return walkErr
|
|
}
|
|
if current == root {
|
|
return nil
|
|
}
|
|
|
|
relativePath := strings.TrimPrefix(current, root)
|
|
relativePath = strings.TrimPrefix(relativePath, "/")
|
|
if relativePath == "." {
|
|
return nil
|
|
}
|
|
|
|
if entry.IsDir() {
|
|
name := strings.ToLower(strings.TrimSpace(entry.Name()))
|
|
if name == ".git" {
|
|
return fs.SkipDir
|
|
}
|
|
if name == "node_modules" {
|
|
return fmt.Errorf("script bundle must not include node_modules")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
normalizedPath, err := normalizeBundleFilePath(relativePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isImportManifestPath(normalizedPath) {
|
|
return nil
|
|
}
|
|
|
|
content, err := fs.ReadFile(fsys, current)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
totalSize += len(content)
|
|
if totalSize > maxImportedBundleBytes {
|
|
return fmt.Errorf("script bundle is too large")
|
|
}
|
|
if len(files) >= maxImportedBundleFiles {
|
|
return fmt.Errorf("script bundle contains too many files")
|
|
}
|
|
|
|
files = append(files, ImportedBundleFile{
|
|
Path: normalizedPath,
|
|
Content: content,
|
|
})
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("collect script bundle files failed: %w", err)
|
|
}
|
|
|
|
sort.Slice(files, func(i, j int) bool {
|
|
return files[i].Path < files[j].Path
|
|
})
|
|
return files, nil
|
|
}
|