package app import ( "encoding/json" "fmt" "os" "strings" ) func transportCollectBootstrapCandidates(client TransportClient) []string { candidates := make([]string, 0, 16) keys := []string{ "bootstrap_endpoint", "bootstrap_endpoints", "bootstrap_host", "bootstrap_hosts", "endpoint", "endpoint_host", "remote_host", "server", "host", "url", "uri", "link", "ssh_host", } for _, key := range keys { candidates = append(candidates, transportConfigValues(client.Config, key)...) } if client.Kind == TransportClientSingBox { candidates = append(candidates, transportCollectSingBoxConfigCandidates(client)...) } return dedupeStrings(candidates) } func transportCollectSingBoxConfigCandidates(client TransportClient) []string { path := transportSingBoxConfigPath(client) if path == "" { return nil } data, err := os.ReadFile(path) if err != nil || len(data) == 0 { return nil } var root map[string]any if err := json.Unmarshal(data, &root); err != nil { return nil } rawOutbounds, ok := root["outbounds"].([]any) if !ok || len(rawOutbounds) == 0 { return nil } out := make([]string, 0, 8) for _, raw := range rawOutbounds { transportCollectServerCandidatesRecursive(raw, &out) } return dedupeStrings(out) } func transportCollectServerCandidatesRecursive(node any, out *[]string) { switch v := node.(type) { case map[string]any: for key, val := range v { k := strings.ToLower(strings.TrimSpace(key)) if k == "server" || k == "address" || k == "host" { *out = append(*out, transportValueToStrings(val)...) } transportCollectServerCandidatesRecursive(val, out) } case []any: for _, item := range v { transportCollectServerCandidatesRecursive(item, out) } } } func transportConfigValues(cfg map[string]any, key string) []string { if cfg == nil { return nil } raw, ok := cfg[key] if !ok || raw == nil { return nil } return transportValueToStrings(raw) } func transportValueToStrings(raw any) []string { switch v := raw.(type) { case string: return transportSplitValue(v) case []string: out := make([]string, 0, len(v)) for _, it := range v { out = append(out, transportSplitValue(it)...) } return out case []any: out := make([]string, 0, len(v)) for _, it := range v { switch vv := it.(type) { case string: out = append(out, transportSplitValue(vv)...) default: out = append(out, transportSplitValue(fmt.Sprint(vv))...) } } return out default: return transportSplitValue(fmt.Sprint(v)) } } func transportSplitValue(raw string) []string { s := strings.TrimSpace(raw) if s == "" { return nil } if strings.ContainsAny(s, ",;\n\t") { repl := strings.NewReplacer(",", " ", ";", " ", "\n", " ", "\t", " ") fields := strings.Fields(repl.Replace(s)) out := make([]string, 0, len(fields)) for _, f := range fields { ff := strings.TrimSpace(f) if ff != "" { out = append(out, ff) } } return out } return []string{s} }