package protocol import ( "encoding/json" "xboard-go/internal/model" "xboard-go/internal/service" ) type SingBoxConfig struct { Outbounds []map[string]interface{} `json:"outbounds"` } func GenerateSingBox(servers []model.Server, user model.User) (string, error) { template := service.GetSubscribeTemplate("singbox") if template == "" { return generateSingBoxFallback(servers, user) } var config map[string]any if err := json.Unmarshal([]byte(template), &config); err != nil { return generateSingBoxFallback(servers, user) } outbounds := make([]any, 0, len(servers)) proxyTags := make([]string, 0, len(servers)) for _, s := range servers { conf := service.BuildNodeConfig(&s) outbound := buildSingBoxOutbound(conf, user) if outbound == nil { continue } outbounds = append(outbounds, outbound) proxyTags = append(proxyTags, conf.Name) } existingOutbounds := anySlice(config["outbounds"]) for index, item := range existingOutbounds { outbound, ok := item.(map[string]any) if !ok { continue } outboundType, _ := outbound["type"].(string) if outboundType != "selector" && outboundType != "urltest" { continue } outbound["outbounds"] = appendUniqueAny(anySlice(outbound["outbounds"]), stringsToAny(proxyTags)...) existingOutbounds[index] = outbound } config["outbounds"] = append(existingOutbounds, outbounds...) data, err := json.MarshalIndent(config, "", " ") if err != nil { return "", err } return string(data), nil } func buildSingBoxOutbound(conf service.NodeServerConfig, user model.User) map[string]any { outbound := map[string]any{ "tag": conf.Name, "server": conf.RawHost, "server_port": conf.ServerPort, } switch conf.Protocol { case "shadowsocks": outbound["type"] = "shadowsocks" outbound["method"] = conf.Cipher outbound["password"] = user.UUID case "vmess": outbound["type"] = "vmess" outbound["uuid"] = user.UUID outbound["security"] = "auto" case "trojan": outbound["type"] = "trojan" outbound["password"] = user.UUID default: return nil } return outbound } func generateSingBoxFallback(servers []model.Server, user model.User) (string, error) { outbounds := []map[string]interface{}{} proxyTags := []string{} for _, s := range servers { conf := service.BuildNodeConfig(&s) outbound := map[string]interface{}{ "tag": conf.Name, "server": conf.RawHost, "server_port": conf.ServerPort, } switch conf.Protocol { case "shadowsocks": outbound["type"] = "shadowsocks" outbound["method"] = conf.Cipher outbound["password"] = user.UUID case "vmess": outbound["type"] = "vmess" outbound["uuid"] = user.UUID outbound["security"] = "auto" case "trojan": outbound["type"] = "trojan" outbound["password"] = user.UUID default: continue } outbounds = append(outbounds, outbound) proxyTags = append(proxyTags, conf.Name) } selector := map[string]interface{}{ "type": "selector", "tag": "Proxy", "outbounds": proxyTags, } outbounds = append(outbounds, selector) config := SingBoxConfig{ Outbounds: outbounds, } data, err := json.MarshalIndent(config, "", " ") return string(data), err }