Files
sub-store/internal/render/surge.go
T

211 lines
5.1 KiB
Go

package render
import (
"github.com/peterqiu0516/sub-store/internal/model"
)
// RenderSurgeProxies renders proxies as Surge text lines.
func RenderSurgeProxies(proxies []model.ProxyNode) (string, error) {
return RenderTextProxyList(proxies, model.TargetSurge, func(p model.ProxyNode) string {
return ToSurgeProxyLine(p)
})
}
// RenderSurgeMacProxies renders proxies as Surge-Mac text lines (adds ssh/h2-connect/snell).
func RenderSurgeMacProxies(proxies []model.ProxyNode) (string, error) {
return RenderTextProxyList(proxies, model.TargetSurgeMac, func(p model.ProxyNode) string {
return ToSurgeMacProxyLine(p)
})
}
// ToSurgeProxyLine converts a single proxy node to a Surge proxy line.
// Returns "" for unsupported proxy types.
func ToSurgeProxyLine(proxy model.ProxyNode) string {
name := SanitizeTextProxyName(getString(proxy, "name"))
ptype := getString(proxy, "type")
server := getString(proxy, "server")
port := getInt(proxy, "port")
base := name + "=" + surgeType(proxy) + "," + server + "," + itoa(port)
entries := CommonTextOptions(proxy)
switch ptype {
case "ss":
entries = unshift(entries,
[2]any{"encrypt-method", strOr(proxy, "cipher", "none")},
[2]any{"password", proxy["password"]},
)
entries = AppendPluginOptions(entries, proxy, "surge")
return JoinTextProxy(base, entries)
case "vmess":
entries = unshift(entries,
[2]any{"username", proxy["uuid"]},
[2]any{"encrypt-method", strOr(proxy, "cipher", "auto")},
[2]any{"tls", proxy["tls"]},
[2]any{"sni", proxy["servername"]},
)
entries = AppendWsOptions(entries, proxy, "surge")
return JoinTextProxy(base, entries)
case "trojan":
entries = unshift(entries,
[2]any{"password", proxy["password"]},
[2]any{"sni", sniOr(proxy, "sni", "servername")},
)
return JoinTextProxy(base, entries)
case "http", "socks5":
entries = unshift(entries,
[2]any{"username", proxy["username"]},
[2]any{"password", proxy["password"]},
[2]any{"tls", proxy["tls"]},
[2]any{"sni", sniOr(proxy, "sni", "servername")},
)
return JoinTextProxy(base, entries)
case "hysteria2":
entries = unshift(entries,
[2]any{"password", proxy["password"]},
[2]any{"sni", proxy["sni"]},
)
if obfs := proxy["obfs"]; obfs != nil {
entries = append(entries, [2]any{"obfs", obfs}, [2]any{"obfs-password", proxy["obfs-password"]})
}
return JoinTextProxy(base, entries)
case "tuic":
entries = unshift(entries,
[2]any{"uuid", proxy["uuid"]},
[2]any{"password", proxy["password"]},
[2]any{"sni", proxy["sni"]},
)
return JoinTextProxy(name+"=tuic-v5,"+server+","+itoa(port), entries)
case "anytls":
entries = unshift(entries,
[2]any{"password", proxy["password"]},
[2]any{"sni", sniOr(proxy, "sni", "servername")},
)
return JoinTextProxy(base, entries)
case "snell":
entries = unshift(entries,
[2]any{"psk", pskOrPassword(proxy)},
[2]any{"version", versionOr3(proxy)},
)
return JoinTextProxy(name+"=snell,"+server+","+itoa(port), entries)
}
return ""
}
// ToSurgeMacProxyLine adds surge-mac specific types (ssh, h2-connect) on top
// of the base surge line.
func ToSurgeMacProxyLine(proxy model.ProxyNode) string {
name := SanitizeTextProxyName(getString(proxy, "name"))
ptype := getString(proxy, "type")
server := getString(proxy, "server")
port := getInt(proxy, "port")
switch ptype {
case "ssh":
entries := CommonTextOptions(proxy)
entries = unshift(entries,
[2]any{"username", proxy["username"]},
[2]any{"password", proxy["password"]},
[2]any{"private-key", proxy["private-key"]},
)
return JoinTextProxy(name+"=ssh,"+server+","+itoa(port), entries)
case "h2-connect":
entries := CommonTextOptions(proxy)
entries = unshift(entries,
[2]any{"username", proxy["username"]},
[2]any{"password", proxy["password"]},
[2]any{"tls", proxy["tls"]},
[2]any{"sni", sniOr(proxy, "sni", "servername")},
)
return JoinTextProxy(name+"=h2-connect,"+server+","+itoa(port), entries)
}
return ToSurgeProxyLine(proxy)
}
func surgeType(proxy model.ProxyNode) string {
ptype := getString(proxy, "type")
if ptype == "socks5" {
if getBool(proxy, "tls") {
return "socks5-tls"
}
return "socks5"
}
if ptype == "http" {
if getBool(proxy, "tls") {
return "https"
}
return "http"
}
return ptype
}
// --- small helpers ---
func unshift(entries [][2]any, front ...[2]any) [][2]any {
return append(front, entries...)
}
func strOr(proxy model.ProxyNode, key, def string) string {
s := getString(proxy, key)
if s == "" {
return def
}
return s
}
func sniOr(proxy model.ProxyNode, keys ...string) any {
for _, k := range keys {
if v := proxy[k]; v != nil {
return v
}
}
return nil
}
func pskOrPassword(proxy model.ProxyNode) any {
if v := proxy["psk"]; v != nil {
return v
}
return proxy["password"]
}
func versionOr3(proxy model.ProxyNode) any {
if v := proxy["version"]; v != nil {
return v
}
return 3
}
func itoa(n int) string {
if n == 0 {
return "0"
}
neg := false
if n < 0 {
neg = true
n = -n
}
var buf [20]byte
i := len(buf)
for n > 0 {
i--
buf[i] = byte('0' + n%10)
n /= 10
}
if neg {
i--
buf[i] = '-'
}
return string(buf[i:])
}