package render import ( "strings" "github.com/peterqiu0516/sub-store/internal/model" "github.com/peterqiu0516/sub-store/internal/util" "gopkg.in/yaml.v3" ) // RenderEgernYaml renders proxies as an Egern YAML document. func RenderEgernYaml(proxies []model.ProxyNode) (string, error) { var list []map[string]any for _, p := range proxies { if e := ToEgernProxy(p); e != nil { list = append(list, e) } } if len(list) == 0 { return "", errNoNodes("egern") } data, err := yaml.Marshal(map[string]any{"proxies": list}) if err != nil { return "", err } return string(data), nil } // ToEgernProxy converts a proxy node to an Egern proxy map. // Returns nil for unsupported proxy types. func ToEgernProxy(proxy model.ProxyNode) map[string]any { common := util.StripUndefined(map[string]any{ "name": proxy["name"], "server": proxy["server"], "port": proxy["port"], "tfo": proxy["tfo"], "udp_relay": proxy["udp"], }) ptype := getString(proxy, "type") switch ptype { case "ss": return util.StripUndefined(mergeMap(common, map[string]any{ "type": "shadowsocks", "method": proxy["cipher"], "password": proxy["password"], })) case "vmess", "vless": security := "" if ptype == "vmess" { security = strOr(proxy, "cipher", "auto") } return util.StripUndefined(mergeMap(common, map[string]any{ "type": ptype, "uuid": proxy["uuid"], "alter_id": proxy["alterId"], "security": security, "flow": proxy["flow"], "tls": proxy["tls"], "sni": proxy["servername"], "network": proxy["network"], "ws_opts": egernWsOptions(proxy), "reality": egernRealityOptions(proxy), })) case "trojan", "anytls", "hysteria2": return util.StripUndefined(mergeMap(common, map[string]any{ "type": ptype, "password": proxy["password"], "sni": sniOr(proxy, "sni", "servername"), "skip_tls_verify": proxy["skip-cert-verify"], "obfs": proxy["obfs"], "obfs_password": proxy["obfs-password"], "reality": egernRealityOptions(proxy), })) case "http", "socks5": egernType := ptype if ptype == "http" && getBool(proxy, "tls") { egernType = "https" } else if ptype == "socks5" && getBool(proxy, "tls") { egernType = "socks5_tls" } return util.StripUndefined(mergeMap(common, map[string]any{ "type": egernType, "username": proxy["username"], "password": proxy["password"], "skip_tls_verify": proxy["skip-cert-verify"], })) case "tuic": return util.StripUndefined(mergeMap(common, map[string]any{ "type": "tuic", "uuid": proxy["uuid"], "password": proxy["password"], "sni": proxy["sni"], "skip_tls_verify": proxy["skip-cert-verify"], })) case "wireguard": return util.StripUndefined(mergeMap(common, map[string]any{ "type": "wireguard", "private_key": proxy["private-key"], "public_key": proxy["public-key"], "pre_shared_key": proxy["pre-shared-key"], "address": proxy["ip"], "ipv6_address": proxy["ipv6"], })) } return nil } func egernWsOptions(proxy model.ProxyNode) any { if getString(proxy, "network") != "ws" { return nil } wsOpts, _ := proxy["ws-opts"].(map[string]any) path := "/" if wsOpts != nil { if p, ok := wsOpts["path"].(string); ok && p != "" { path = p } } result := map[string]any{"path": path} if wsOpts != nil { if h, ok := wsOpts["headers"]; ok && h != nil { result["headers"] = h } } return result } func egernRealityOptions(proxy model.ProxyNode) any { realityOpts, _ := proxy["reality-opts"].(map[string]any) if realityOpts == nil { return nil } pubKey := realityOpts["public-key"] if pubKey == nil { return nil } return util.StripUndefined(map[string]any{ "public_key": pubKey, "short_id": realityOpts["short-id"], }) } func mergeMap(base, next map[string]any) map[string]any { result := make(map[string]any, len(base)+len(next)) for k, v := range base { result[k] = v } for k, v := range next { result[k] = v } return result } func errNoNodes(target string) error { return &noNodesError{target: target} } type noNodesError struct { target string } func (e *noNodesError) Error() string { var sb strings.Builder sb.WriteString("No supported nodes for ") sb.WriteString(e.target) sb.WriteString(" output") return sb.String() }