Files

177 lines
4.4 KiB
Go

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()
}