diff --git a/README.md b/README.md index 9d853404..30f3a474 100644 --- a/README.md +++ b/README.md @@ -249,12 +249,6 @@ Script enables users to programmatically select a policy for the packets with mo ```yaml mode: script -rules: - # the rule GEOSITE just as a rule provider in mode script - - GEOSITE,category-ads-all,Whatever - - GEOSITE,youtube,Whatever - - GEOSITE,geolocation-cn,Whatever - script: code: | def main(ctx, metadata): diff --git a/config/config.go b/config/config.go index cf7f2012..3fef5b67 100644 --- a/config/config.go +++ b/config/config.go @@ -315,9 +315,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) { config.Proxies = proxies config.Providers = providers - if err = parseScript(rawCfg.Script); err != nil { + rawRules, err := parseScript(rawCfg.Script, rawCfg.Rule) + if err != nil { return nil, err } + rawCfg.Rule = rawRules rules, ruleProviders, err := parseRules(rawCfg, proxies) if err != nil { @@ -512,10 +514,10 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin var ( rules []C.Rule providerNames []string + foundRP bool ruleProviders = map[string]C.Rule{} rulesConfig = cfg.Rule - mode = cfg.Mode ) // parse rules @@ -550,10 +552,16 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin target = rule[l-1] params = rule[l:] - if _, ok := proxies[target]; !ok && (mode != T.Script || ruleName != "GEOSITE") { + if _, ok := proxies[target]; !ok && ruleName != "GEOSITE" && target != C.ScriptRuleGeoSiteTarget { return nil, nil, fmt.Errorf("rules[%d] [%s] error: proxy [%s] not found", idx, line, target) } + pvName := "geosite:" + strings.ToLower(payload) + _, foundRP = ruleProviders[pvName] + if ruleName == "GEOSITE" && target == C.ScriptRuleGeoSiteTarget && foundRP { + continue + } + params = trimArr(params) parsed, parseErr := R.ParseRule(ruleName, payload, target, params) @@ -561,8 +569,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin return nil, nil, fmt.Errorf("rules[%d] [%s] error: %s", idx, line, parseErr.Error()) } - if ruleName == "GEOSITE" { - pvName := "geosite:" + strings.ToLower(payload) + if ruleName == "GEOSITE" && !foundRP { providerNames = append(providerNames, pvName) ruleProviders[pvName] = parsed } @@ -845,7 +852,7 @@ func parseAuthentication(rawRecords []string) []auth.AuthUser { return users } -func parseScript(script Script) error { +func parseScript(script Script, rawRules []string) ([]string, error) { mainCode := script.MainCode shortcutsCode := script.ShortcutsCode @@ -890,20 +897,30 @@ time = ClashTime() err := os.WriteFile(C.Path.Script(), []byte(content), 0o644) if err != nil { - return fmt.Errorf("initialized script module failure, %w", err) + return nil, fmt.Errorf("initialized script module failure, %w", err) } if err = S.Py_Initialize(C.Path.GetExecutableFullPath(), C.Path.ScriptDir()); err != nil { - return fmt.Errorf("initialized script module failure, %w", err) + return nil, fmt.Errorf("initialized script module failure, %w", err) } if err = S.LoadMainFunction(); err != nil { - return fmt.Errorf("initialized script module failure, %w", err) + return nil, fmt.Errorf("initialized script module failure, %w", err) + } + + rpdArr := findRuleProvidersName(content) + for _, v := range rpdArr { + if !strings.HasPrefix(v, "geosite:") { + return nil, fmt.Errorf("initialized script module failure, rule provider name must be start with \"geosite:\"") + } + v = strings.ToLower(v) + rule := fmt.Sprintf("GEOSITE,%s,%s", strings.TrimPrefix(v, "geosite:"), C.ScriptRuleGeoSiteTarget) + rawRules = append(rawRules, rule) } log.Infoln("Start initial script module successful, version: %s", S.Py_GetVersion()) - return nil + return rawRules, nil } func cleanPyKeywords(code string) string { @@ -916,6 +933,23 @@ func cleanPyKeywords(code string) string { return code } +func findRuleProvidersName(s string) []string { + ruleProviderRegx := regexp.MustCompile("ctx.rule_providers\\[[\"'](\\S+)[\"']\\]\\.match|match_provider\\([\"'](\\S+)[\"']\\)") + arr := ruleProviderRegx.FindAllStringSubmatch(s, -1) + + var rpd []string + for _, rpdArr := range arr { + for i, v := range rpdArr { + if i == 0 || v == "" { + continue + } + rpd = append(rpd, v) + } + } + + return rpd +} + func parseMitm(rawMitm RawMitm) (*Mitm, error) { var ( req []C.Rewrite diff --git a/constant/rule_extra.go b/constant/rule_extra.go index a115319f..972e85da 100644 --- a/constant/rule_extra.go +++ b/constant/rule_extra.go @@ -7,6 +7,8 @@ import ( "github.com/Dreamacro/clash/component/geodata/router" ) +const ScriptRuleGeoSiteTarget = "__WhateverTarget__" + type RuleExtra struct { Network NetWork SourceIPs []*netip.Prefix diff --git a/rule/geosite.go b/rule/geosite.go index 22f39887..e336a5b8 100644 --- a/rule/geosite.go +++ b/rule/geosite.go @@ -55,6 +55,9 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { if recordsCount == 0 { cont = "from cache" } + if adapter == C.ScriptRuleGeoSiteTarget { + adapter = "Script" + } log.Infoln("Start initial GeoSite rule %s => %s, records: %s", country, adapter, cont) geoSite := &GEOSITE{