From 3b81e7656ffa1c5e7dd17fbbb5fba7be6bef7fe4 Mon Sep 17 00:00:00 2001 From: phj233 <2780990934@qq.com> Date: Mon, 29 Jun 2026 00:38:39 +0800 Subject: [PATCH] fix(clash): inject default Proxy/Auto only when a custom group references them (#1151) * fix(clash): inject default Proxy/Auto only when a custom group references them Refines the proxy-group injection logic so default Proxy/Auto groups are only injected when needed, rather than skipping injection entirely when custom groups exist (#1110) or always injecting unconditionally (#1066). Two-part condition: 1. No group named Proxy already exists (user-defined Proxy is respected) 2. At least one existing group references Proxy in its proxies list When injection is needed, defaults are prepended before custom groups. Also avoids injecting a duplicate Auto group when the user already defines one. Added hasGroupNamed helper and comprehensive tests (15 cases) covering nil/empty/custom/no-ref/duplicate-Auto scenarios. * chore(clash): drop test file before merge --------- Co-authored-by: Alireza Ahmadi --- sub/clashService.go | 61 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/sub/clashService.go b/sub/clashService.go index bdb075e..653373e 100644 --- a/sub/clashService.go +++ b/sub/clashService.go @@ -389,14 +389,50 @@ func (s *ClashService) ConvertToClashMeta(outbounds *[]map[string]interface{}, b output["proxies"] = proxies } - if pg, ok := output["proxy-groups"].([]interface{}); !ok || len(pg) == 0 { + // Inject default Proxy + Auto unless user already has "Proxy", or + // no custom group references it (fully custom config: don't inject). + pgSlice, _ := output["proxy-groups"].([]interface{}) + inject := !hasGroupNamed(pgSlice, "Proxy") + if inject && len(pgSlice) > 0 { + inject = false + for _, item := range pgSlice { + if g, ok := item.(map[string]interface{}); ok { + if proxies, ok := g["proxies"].([]interface{}); ok { + for _, p := range proxies { + if name, ok := p.(string); ok && name == "Proxy" { + inject = true + break + } + } + } + } + if inject { + break + } + } + } + if inject { var proxyGroups []map[string]interface{} if err := yaml.Unmarshal([]byte(ProxyGroups), &proxyGroups); err != nil { - logger.Error(err.Error()) + return "", err } proxyGroups[1]["proxies"] = proxyTags proxyGroups[0]["proxies"] = append([]string{proxyGroups[1]["name"].(string)}, proxyTags...) - output["proxy-groups"] = proxyGroups + // Don't inject a duplicate "Auto" if the user already defines one. + if hasGroupNamed(pgSlice, "Auto") { + proxyGroups = proxyGroups[:1] + proxyGroups[0]["proxies"] = proxyTags + } + if pg, ok := output["proxy-groups"].([]interface{}); ok { + for _, item := range pg { + if g, ok := item.(map[string]interface{}); ok { + proxyGroups = append(proxyGroups, g) + } + } + output["proxy-groups"] = proxyGroups + } else { + output["proxy-groups"] = proxyGroups + } } result, err := yaml.Marshal(output) @@ -405,3 +441,22 @@ func (s *ClashService) ConvertToClashMeta(outbounds *[]map[string]interface{}, b } return string(result), nil } + +// hasGroupNamed checks whether a "proxy-groups" interface{} slice contains a group with the given name. +// Used to avoid injecting duplicate "Proxy" / "Auto" default groups when the user already defined their own. +func hasGroupNamed(pg interface{}, name string) bool { + list, ok := pg.([]interface{}) + if !ok { + return false + } + for _, item := range list { + group, ok := item.(map[string]interface{}) + if !ok { + continue + } + if gname, ok := group["name"].(string); ok && gname == name { + return true + } + } + return false +}