mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
255 lines
7.2 KiB
Go
255 lines
7.2 KiB
Go
package automation
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
defaultScriptEntryFile = "index.cjs"
|
|
defaultScriptPackageFormat = "ant-automation-script"
|
|
defaultScriptManifestVersion = 1
|
|
defaultScriptCreateNameTemplate = "${templateName}-${timestamp}"
|
|
defaultScriptPublicAPITimeoutMs = 300000
|
|
scriptStoreConfigFileName = "config"
|
|
scriptStoreLegacyConfigName = "manifest.json"
|
|
scriptPublicAPIBasePath = "/api/automation/hooks"
|
|
)
|
|
|
|
type ScriptSource struct {
|
|
Type string `json:"type"`
|
|
URI string `json:"uri"`
|
|
Ref string `json:"ref"`
|
|
Path string `json:"path"`
|
|
ImportedAt string `json:"importedAt"`
|
|
}
|
|
|
|
type ScriptTargetSelector struct {
|
|
Code string `json:"code"`
|
|
ProfileID string `json:"profileId"`
|
|
ProfileName string `json:"profileName"`
|
|
GroupID string `json:"groupId"`
|
|
Keywords []string `json:"keywords"`
|
|
Tags []string `json:"tags"`
|
|
}
|
|
|
|
type ScriptTargetConfig struct {
|
|
Mode string `json:"mode"`
|
|
Selector ScriptTargetSelector `json:"selector"`
|
|
TemplateSelector ScriptTargetSelector `json:"templateSelector"`
|
|
CreateNameTemplate string `json:"createNameTemplate"`
|
|
}
|
|
|
|
type ScriptPublicAPIConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
Method string `json:"method"`
|
|
Path string `json:"path"`
|
|
RequestMode string `json:"requestMode"`
|
|
ResponseMode string `json:"responseMode"`
|
|
TimeoutMs int `json:"timeoutMs"`
|
|
RequestBodyText string `json:"requestBodyText"`
|
|
ResponseBodyText string `json:"responseBodyText"`
|
|
Variables []ScriptPublicAPIVariable `json:"variables"`
|
|
}
|
|
|
|
type ScriptPublicAPIVariable struct {
|
|
Name string `json:"name"`
|
|
DefaultValue string `json:"defaultValue"`
|
|
Description string `json:"description"`
|
|
Required bool `json:"required"`
|
|
}
|
|
|
|
type ScriptRecord struct {
|
|
PackageFormat string `json:"packageFormat"`
|
|
ManifestVersion int `json:"manifestVersion"`
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
EntryFile string `json:"entryFile"`
|
|
Tags []string `json:"tags"`
|
|
SelectorText string `json:"selectorText"`
|
|
ParamsText string `json:"paramsText"`
|
|
ScriptText string `json:"scriptText"`
|
|
Notes string `json:"notes"`
|
|
TargetConfig ScriptTargetConfig `json:"targetConfig"`
|
|
PublicAPI ScriptPublicAPIConfig `json:"publicAPI"`
|
|
Source ScriptSource `json:"source"`
|
|
CreatedAt string `json:"createdAt"`
|
|
UpdatedAt string `json:"updatedAt"`
|
|
}
|
|
|
|
type ImportedBundleFile struct {
|
|
Path string
|
|
Content []byte
|
|
}
|
|
|
|
type ImportedBundle struct {
|
|
Record ScriptRecord
|
|
Files []ImportedBundleFile
|
|
}
|
|
|
|
type scriptStoreConfig struct {
|
|
PackageFormat string `json:"packageFormat"`
|
|
ManifestVersion int `json:"manifestVersion"`
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Type string `json:"type"`
|
|
Status string `json:"status"`
|
|
EntryFile string `json:"entryFile"`
|
|
Tags []string `json:"tags"`
|
|
SelectorText string `json:"selectorText"`
|
|
ParamsText string `json:"paramsText"`
|
|
Notes string `json:"notes"`
|
|
TargetConfig ScriptTargetConfig `json:"targetConfig"`
|
|
PublicAPI ScriptPublicAPIConfig `json:"publicAPI"`
|
|
Source ScriptSource `json:"source"`
|
|
CreatedAt string `json:"createdAt"`
|
|
UpdatedAt string `json:"updatedAt"`
|
|
}
|
|
|
|
type ScriptStore struct {
|
|
rootDir string
|
|
}
|
|
|
|
func NewScriptStore(rootDir string) *ScriptStore {
|
|
return &ScriptStore{
|
|
rootDir: filepath.Clean(strings.TrimSpace(rootDir)),
|
|
}
|
|
}
|
|
|
|
func (s *ScriptStore) List() ([]ScriptRecord, error) {
|
|
if err := s.ensureRoot(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
entries, err := os.ReadDir(s.rootDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return []ScriptRecord{}, nil
|
|
}
|
|
return nil, fmt.Errorf("read automation scripts dir failed: %w", err)
|
|
}
|
|
|
|
items := make([]ScriptRecord, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if !entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
record, err := s.readScriptDir(filepath.Join(s.rootDir, entry.Name()))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
items = append(items, record)
|
|
}
|
|
|
|
sort.Slice(items, func(i, j int) bool {
|
|
return parseRFC3339OrZero(items[i].UpdatedAt).After(parseRFC3339OrZero(items[j].UpdatedAt))
|
|
})
|
|
|
|
return items, nil
|
|
}
|
|
|
|
func (s *ScriptStore) Save(input ScriptRecord) (ScriptRecord, error) {
|
|
if err := s.ensureRoot(); err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
|
|
normalizedInput, err := normalizeScriptRecord(input, ScriptRecord{})
|
|
if err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
input.ID = normalizedInput.ID
|
|
|
|
dir, err := s.scriptDir(input.ID)
|
|
if err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
|
|
existing, _ := s.readScriptDir(dir)
|
|
record, err := normalizeScriptRecord(input, existing)
|
|
if err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
if err := s.validateRecord(record); err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
|
|
return s.writeRecord(dir, record, existing, nil)
|
|
}
|
|
|
|
func (s *ScriptStore) Get(scriptID string) (ScriptRecord, error) {
|
|
dir, err := s.scriptDir(scriptID)
|
|
if err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
return s.readScriptDir(dir)
|
|
}
|
|
|
|
func (s *ScriptStore) ExportBundle(scriptID string) (ImportedBundle, error) {
|
|
dir, err := s.scriptDir(scriptID)
|
|
if err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
|
|
record, err := s.readScriptDir(dir)
|
|
if err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
|
|
files, err := collectScriptStoreBundleFiles(dir)
|
|
if err != nil {
|
|
return ImportedBundle{}, err
|
|
}
|
|
|
|
return ImportedBundle{
|
|
Record: record,
|
|
Files: files,
|
|
}, nil
|
|
}
|
|
|
|
func (s *ScriptStore) Dir(scriptID string) (string, error) {
|
|
return s.scriptDir(scriptID)
|
|
}
|
|
|
|
func (s *ScriptStore) ImportBundle(bundle ImportedBundle) (ScriptRecord, error) {
|
|
if err := s.ensureRoot(); err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
|
|
record, err := normalizeScriptRecord(bundle.Record, ScriptRecord{})
|
|
if err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
record.ID = firstNonEmpty(record.ID, bundle.Record.ID)
|
|
|
|
dir, err := s.scriptDir(record.ID)
|
|
if err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
|
|
existing, _ := s.readScriptDir(dir)
|
|
if err := s.validateRecord(record); err != nil {
|
|
return ScriptRecord{}, err
|
|
}
|
|
return s.writeRecord(dir, record, existing, bundle.Files)
|
|
}
|
|
|
|
func (s *ScriptStore) Delete(scriptID string) error {
|
|
dir, err := s.scriptDir(scriptID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := os.RemoveAll(dir); err != nil {
|
|
return fmt.Errorf("delete automation script failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|