162 lines
4.9 KiB
Go
162 lines
4.9 KiB
Go
package automation
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// PrefixProxyNames rewrites every proxy's "name" field in data["proxies"]
|
|
// and every proxy-group's "name" field in data["proxy-groups"] — both are
|
|
// scoped per-subscription and would otherwise collide across subscriptions
|
|
// the same way proxy names do — prefixing them with subscriptionName. It
|
|
// also rewrites every reference to a renamed proxy or proxy-group inside
|
|
// each group's own "proxies" list and inside data["rules"]' rule targets
|
|
// (e.g. "DOMAIN,example.com,Proxy" or "MATCH,Auto"), since those are all
|
|
// resolved by name and would otherwise silently point at nothing after the
|
|
// rename. References to anything else (DIRECT, REJECT, a proxy-provider
|
|
// name, ...) are left untouched.
|
|
//
|
|
// Returns a ProxyRef per proxy, for later ssm matching/grouping.
|
|
func PrefixProxyNames(subscriptionName string, data map[string]interface{}) []ProxyRef {
|
|
// Original name (of either a proxy or a proxy-group) -> prefixed
|
|
// display name. Clash requires proxy and group names to share a single
|
|
// namespace within one config, so a single map is correct here too.
|
|
rename := make(map[string]string)
|
|
|
|
refs := renameProxies(subscriptionName, data, rename)
|
|
renameProxyGroups(subscriptionName, data, rename)
|
|
RewriteReferences(data, rename)
|
|
|
|
return refs
|
|
}
|
|
|
|
// RewriteReferences rewrites every reference to a renamed proxy or
|
|
// proxy-group — inside other groups' "proxies" lists and inside
|
|
// data["rules"]' rule targets — to the new name. Exposed separately from
|
|
// PrefixProxyNames so callers that rename groups after the fact (e.g.
|
|
// collapsing several subscriptions' default selector group into one) can
|
|
// reuse the same rewrite logic without re-running the proxy/group renaming.
|
|
func RewriteReferences(data map[string]interface{}, rename map[string]string) {
|
|
rewriteGroupProxyReferences(data, rename)
|
|
rewriteRuleTargets(data, rename)
|
|
}
|
|
|
|
func renameProxies(subscriptionName string, data map[string]interface{}, rename map[string]string) []ProxyRef {
|
|
proxiesRaw, ok := data["proxies"].([]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
refs := make([]ProxyRef, 0, len(proxiesRaw))
|
|
for _, item := range proxiesRaw {
|
|
proxy, ok := item.(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
name, ok := proxy["name"].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
display := fmt.Sprintf("%s | %s", subscriptionName, name)
|
|
proxy["name"] = display
|
|
rename[name] = display
|
|
|
|
refs = append(refs, ProxyRef{
|
|
OriginalName: name,
|
|
DisplayName: display,
|
|
Subscription: subscriptionName,
|
|
})
|
|
}
|
|
return refs
|
|
}
|
|
|
|
func renameProxyGroups(subscriptionName string, data map[string]interface{}, rename map[string]string) {
|
|
for _, group := range proxyGroupMaps(data) {
|
|
name, ok := group["name"].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
display := fmt.Sprintf("%s | %s", subscriptionName, name)
|
|
group["name"] = display
|
|
rename[name] = display
|
|
}
|
|
}
|
|
|
|
func rewriteGroupProxyReferences(data map[string]interface{}, rename map[string]string) {
|
|
for _, group := range proxyGroupMaps(data) {
|
|
proxiesRaw, ok := group["proxies"].([]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
for i, p := range proxiesRaw {
|
|
name, ok := p.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
if display, renamed := rename[name]; renamed {
|
|
proxiesRaw[i] = display
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// rewriteRuleTargets rewrites the target field of every rule in
|
|
// data["rules"] that references a renamed proxy or proxy-group. Clash rule
|
|
// lines are a comma-separated "TYPE,payload,TARGET[,no-resolve]" (or just
|
|
// "MATCH,TARGET" with no payload) — the target is always the last field,
|
|
// except for the handful of rule types that allow a trailing "no-resolve"
|
|
// modifier, where it's the second-to-last. Lines with no comma at all are
|
|
// left untouched (not valid rule syntax to begin with).
|
|
func rewriteRuleTargets(data map[string]interface{}, rename map[string]string) {
|
|
rulesRaw, ok := data["rules"].([]interface{})
|
|
if !ok {
|
|
return
|
|
}
|
|
for i, item := range rulesRaw {
|
|
line, ok := item.(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
rulesRaw[i] = rewriteRuleLine(line, rename)
|
|
}
|
|
}
|
|
|
|
func rewriteRuleLine(line string, rename map[string]string) string {
|
|
body := line
|
|
suffix := ""
|
|
|
|
if idx := strings.LastIndex(body, ","); idx != -1 && strings.EqualFold(body[idx+1:], "no-resolve") {
|
|
suffix = "," + body[idx+1:]
|
|
body = body[:idx]
|
|
}
|
|
|
|
idx := strings.LastIndex(body, ",")
|
|
if idx == -1 {
|
|
return line
|
|
}
|
|
|
|
target := body[idx+1:]
|
|
if display, ok := rename[target]; ok {
|
|
body = body[:idx+1] + display
|
|
}
|
|
|
|
return body + suffix
|
|
}
|
|
|
|
// proxyGroupMaps returns data["proxy-groups"]'s entries as maps, skipping
|
|
// anything malformed.
|
|
func proxyGroupMaps(data map[string]interface{}) []map[string]interface{} {
|
|
groupsRaw, ok := data["proxy-groups"].([]interface{})
|
|
if !ok {
|
|
return nil
|
|
}
|
|
groups := make([]map[string]interface{}, 0, len(groupsRaw))
|
|
for _, item := range groupsRaw {
|
|
if group, ok := item.(map[string]interface{}); ok {
|
|
groups = append(groups, group)
|
|
}
|
|
}
|
|
return groups
|
|
}
|