持续集成修复订阅错误的问题
Some checks failed
build / build (api, amd64, linux) (push) Failing after -51s
build / build (api, arm64, linux) (push) Failing after -51s
build / build (api.exe, amd64, windows) (push) Failing after -51s

This commit is contained in:
CN-JS-HuiBai
2026-04-18 00:08:19 +08:00
parent 4ba835b93f
commit cafab67dcc
4 changed files with 332 additions and 6 deletions

View File

@@ -104,6 +104,9 @@ func buildClashProxy(templateName string, conf service.NodeServerConfig, passwor
switch toClashInt(conf.Tls) {
case 1:
proxy["tls"] = true
if fp := tlsFingerprint(conf.UTLS); fp != "" {
proxy["client-fingerprint"] = fp
}
if tlsSettings, ok := conf.TlsSettings.(map[string]any); ok {
proxy["skip-cert-verify"] = toClashBool(tlsSettings["allow_insecure"])
if serverName := toClashString(tlsSettings["server_name"]); serverName != "" {
@@ -112,6 +115,9 @@ func buildClashProxy(templateName string, conf service.NodeServerConfig, passwor
}
case 2:
proxy["tls"] = true
if fp := tlsFingerprint(conf.UTLS); fp != "" {
proxy["client-fingerprint"] = fp
}
if tlsSettings, ok := conf.TlsSettings.(map[string]any); ok {
proxy["skip-cert-verify"] = toClashBool(tlsSettings["allow_insecure"])
if serverName := toClashString(tlsSettings["server_name"]); serverName != "" {
@@ -123,11 +129,83 @@ func buildClashProxy(templateName string, conf service.NodeServerConfig, passwor
}
}
}
network := toClashString(conf.Network)
if network == "" {
network = "tcp"
proxy["network"] = "tcp"
if settings, ok := conf.NetworkSettings.(map[string]any); ok {
switch toClashString(conf.Network) {
case "", "tcp":
if header, ok := settings["header"].(map[string]any); ok && toClashString(header["type"]) == "http" {
proxy["network"] = "http"
httpOpts := map[string]any{}
if request, ok := header["request"].(map[string]any); ok {
if headers, ok := request["headers"].(map[string]any); ok && len(headers) > 0 {
httpOpts["headers"] = headers
}
if paths := clashPathList(request["path"]); len(paths) > 0 {
httpOpts["path"] = paths
}
}
if len(httpOpts) > 0 {
proxy["http-opts"] = httpOpts
}
}
case "ws":
proxy["network"] = "ws"
wsOpts := map[string]any{}
if path := toClashString(settings["path"]); path != "" {
wsOpts["path"] = path
}
if headers, ok := settings["headers"].(map[string]any); ok {
if host := toClashString(headers["Host"]); host != "" {
wsOpts["headers"] = map[string]any{"Host": host}
}
}
if len(wsOpts) > 0 {
proxy["ws-opts"] = wsOpts
}
case "grpc":
proxy["network"] = "grpc"
if serviceName := toClashString(settings["serviceName"]); serviceName != "" {
proxy["grpc-opts"] = map[string]any{"grpc-service-name": serviceName}
}
case "h2":
proxy["network"] = "h2"
h2Opts := map[string]any{}
if path := toClashString(settings["path"]); path != "" {
h2Opts["path"] = path
}
if hosts := clashHostList(settings["host"]); len(hosts) > 0 {
h2Opts["host"] = hosts
}
if len(h2Opts) > 0 {
proxy["h2-opts"] = h2Opts
}
case "httpupgrade":
proxy["network"] = "ws"
wsOpts := map[string]any{
"v2ray-http-upgrade": true,
}
if path := toClashString(settings["path"]); path != "" {
wsOpts["path"] = path
}
host := toClashString(settings["host"])
if host == "" {
host = conf.RawHost
}
if host != "" {
wsOpts["headers"] = map[string]any{"Host": host}
}
proxy["ws-opts"] = wsOpts
default:
if network := toClashString(conf.Network); network != "" {
proxy["network"] = network
}
}
} else if network := toClashString(conf.Network); network != "" {
proxy["network"] = network
}
if smux := buildClashSmux(conf.Multiplex); smux != nil {
proxy["smux"] = smux
}
return proxy
case "trojan":
return map[string]any{
@@ -208,6 +286,80 @@ func appendUniqueAny(base []any, values ...any) []any {
return base
}
func clashPathList(value any) []string {
switch typed := value.(type) {
case []string:
return append([]string{}, typed...)
case []any:
result := make([]string, 0, len(typed))
for _, item := range typed {
text := toClashString(item)
if text != "" {
result = append(result, text)
}
}
return result
case string:
if typed == "" {
return nil
}
return []string{typed}
default:
return nil
}
}
func clashHostList(value any) []string {
switch typed := value.(type) {
case []string:
return append([]string{}, typed...)
case []any:
result := make([]string, 0, len(typed))
for _, item := range typed {
text := toClashString(item)
if text != "" {
result = append(result, text)
}
}
return result
case string:
if typed == "" {
return nil
}
return []string{typed}
default:
return nil
}
}
func buildClashSmux(value any) map[string]any {
settings, ok := value.(map[string]any)
if !ok || !toClashBool(settings["enabled"]) {
return nil
}
smux := map[string]any{
"enabled": true,
}
if protocol := toClashString(settings["protocol"]); protocol != "" {
smux["protocol"] = protocol
}
if maxConnections := toClashInt(settings["max_connections"]); maxConnections > 0 {
smux["max-connections"] = maxConnections
}
if toClashBool(settings["padding"]) {
smux["padding"] = true
}
if brutal, ok := settings["brutal"].(map[string]any); ok && toClashBool(brutal["enabled"]) {
smux["brutal-opts"] = map[string]any{
"enabled": true,
"up": toClashInt(brutal["up_mbps"]),
"down": toClashInt(brutal["down_mbps"]),
}
}
return smux
}
func toClashString(value any) string {
switch typed := value.(type) {
case string:

View File

@@ -107,6 +107,7 @@ func buildVmess(c service.NodeServerConfig, password string) string {
func buildVless(c service.NodeServerConfig, password string) string {
params := url.Values{}
params.Set("mode", "multi")
params.Set("encryption", "none")
if c.Flow != nil {
params.Set("flow", toString(c.Flow))
@@ -116,18 +117,26 @@ func buildVless(c service.NodeServerConfig, password string) string {
switch toInt(c.Tls) {
case 1:
security = "tls"
if fp := tlsFingerprint(c.UTLS); fp != "" {
params.Set("fp", fp)
}
if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
params.Set("sni", toString(tlsSettings["server_name"]))
if toString(tlsSettings["allow_insecure"]) == "1" {
if truthy(tlsSettings["allow_insecure"]) {
params.Set("allowInsecure", "1")
}
}
case 2:
security = "reality"
if fp := tlsFingerprint(c.UTLS); fp != "" {
params.Set("fp", fp)
}
if tlsSettings, ok := c.TlsSettings.(map[string]any); ok {
params.Set("pbk", toString(tlsSettings["public_key"]))
params.Set("sid", toString(tlsSettings["short_id"]))
params.Set("sni", toString(tlsSettings["server_name"]))
params.Set("servername", toString(tlsSettings["server_name"]))
params.Set("spx", "/")
}
}
params.Set("security", security)
@@ -142,6 +151,19 @@ func buildVless(c service.NodeServerConfig, password string) string {
}
case "grpc":
params.Set("serviceName", toString(settings["serviceName"]))
case "h2":
params.Set("type", "http")
params.Set("path", toString(settings["path"]))
if host := joinConfigValue(settings["host"]); host != "" {
params.Set("host", host)
}
case "httpupgrade":
params.Set("path", toString(settings["path"]))
host := toString(settings["host"])
if host == "" {
host = c.RawHost
}
params.Set("host", host)
}
}
@@ -244,6 +266,34 @@ func wrapIPv6(host string) string {
return host
}
func tlsFingerprint(value any) string {
settings, ok := value.(map[string]any)
if !ok || !truthy(settings["enabled"]) {
return ""
}
return toString(settings["fingerprint"])
}
func joinConfigValue(value any) string {
switch typed := value.(type) {
case string:
return typed
case []string:
return strings.Join(typed, ",")
case []any:
result := make([]string, 0, len(typed))
for _, item := range typed {
text := toString(item)
if text != "" {
result = append(result, text)
}
}
return strings.Join(result, ",")
default:
return ""
}
}
func toString(v any) string {
if s, ok := v.(string); ok {
return s
@@ -265,3 +315,22 @@ func toInt(v any) int {
}
return 0
}
func truthy(v any) bool {
switch typed := v.(type) {
case bool:
return typed
case int:
return typed != 0
case int64:
return typed != 0
case float64:
return typed != 0
case string:
switch strings.ToLower(strings.TrimSpace(typed)) {
case "1", "true", "yes", "on":
return true
}
}
return false
}

View File

@@ -89,6 +89,9 @@ func buildSingBoxOutbound(conf service.NodeServerConfig, password string) map[st
if transport := buildSingBoxTransport(conf); transport != nil {
outbound["transport"] = transport
}
if multiplex := buildSingBoxMultiplex(conf.Multiplex); multiplex != nil {
outbound["multiplex"] = multiplex
}
case "trojan":
outbound["type"] = "trojan"
outbound["password"] = password
@@ -134,6 +137,9 @@ func generateSingBoxFallback(servers []model.Server, user model.User) (string, e
if transport := buildSingBoxTransport(conf); transport != nil {
outbound["transport"] = transport
}
if multiplex := buildSingBoxMultiplex(conf.Multiplex); multiplex != nil {
outbound["multiplex"] = multiplex
}
case "trojan":
outbound["type"] = "trojan"
outbound["password"] = password
@@ -161,6 +167,7 @@ func generateSingBoxFallback(servers []model.Server, user model.User) (string, e
}
func buildSingBoxTLS(conf service.NodeServerConfig) map[string]any {
utlsConfig := buildSingBoxUTLS(conf.UTLS)
switch singBoxInt(conf.Tls) {
case 1:
tls := map[string]any{
@@ -172,6 +179,9 @@ func buildSingBoxTLS(conf service.NodeServerConfig) map[string]any {
tls["server_name"] = serverName
}
}
if utlsConfig != nil {
tls["utls"] = utlsConfig
}
return tls
case 2:
tls := map[string]any{
@@ -188,6 +198,9 @@ func buildSingBoxTLS(conf service.NodeServerConfig) map[string]any {
"short_id": singBoxString(tlsSettings["short_id"]),
}
}
if utlsConfig != nil {
tls["utls"] = utlsConfig
}
return tls
default:
return nil
@@ -223,6 +236,19 @@ func buildSingBoxTransport(conf service.NodeServerConfig) map[string]any {
}
}
return transport
case "h2":
transport := map[string]any{
"type": "http",
}
if settings != nil {
if path := singBoxString(settings["path"]); path != "" {
transport["path"] = path
}
if host := singBoxHostValue(settings["host"]); len(host) > 0 {
transport["host"] = host
}
}
return transport
case "httpupgrade":
transport := map[string]any{
"type": "httpupgrade",
@@ -239,6 +265,81 @@ func buildSingBoxTransport(conf service.NodeServerConfig) map[string]any {
}
}
return transport
case "quic":
return map[string]any{
"type": "quic",
}
default:
return nil
}
}
func buildSingBoxUTLS(value any) map[string]any {
settings, ok := value.(map[string]any)
if !ok || !singBoxBool(settings["enabled"]) {
return nil
}
utls := map[string]any{
"enabled": true,
}
if fingerprint := singBoxString(settings["fingerprint"]); fingerprint != "" {
utls["fingerprint"] = fingerprint
}
return utls
}
func buildSingBoxMultiplex(value any) map[string]any {
settings, ok := value.(map[string]any)
if !ok || !singBoxBool(settings["enabled"]) {
return nil
}
multiplex := map[string]any{
"enabled": true,
}
if protocol := singBoxString(settings["protocol"]); protocol != "" {
multiplex["protocol"] = protocol
}
if maxConnections := singBoxInt(settings["max_connections"]); maxConnections > 0 {
multiplex["max_connections"] = maxConnections
}
if minStreams := singBoxInt(settings["min_streams"]); minStreams > 0 {
multiplex["min_streams"] = minStreams
}
if maxStreams := singBoxInt(settings["max_streams"]); maxStreams > 0 {
multiplex["max_streams"] = maxStreams
}
if singBoxBool(settings["padding"]) {
multiplex["padding"] = true
}
if brutal, ok := settings["brutal"].(map[string]any); ok && singBoxBool(brutal["enabled"]) {
multiplex["brutal"] = map[string]any{
"enabled": true,
"up_mbps": singBoxInt(brutal["up_mbps"]),
"down_mbps": singBoxInt(brutal["down_mbps"]),
}
}
return multiplex
}
func singBoxHostValue(value any) []string {
switch typed := value.(type) {
case string:
if typed == "" {
return nil
}
return []string{typed}
case []string:
return append([]string{}, typed...)
case []any:
result := make([]string, 0, len(typed))
for _, item := range typed {
if text := singBoxString(item); text != "" {
result = append(result, text)
}
}
return result
default:
return nil
}
@@ -251,7 +352,7 @@ func singBoxString(value any) string {
case nil:
return ""
default:
return ""
return toString(value)
}
}

View File

@@ -84,6 +84,7 @@ type NodeServerConfig struct {
TlsSettings any `json:"tls_settings,omitempty"`
Flow any `json:"flow,omitempty"`
Multiplex any `json:"multiplex,omitempty"`
UTLS any `json:"utls,omitempty"`
UpMbps any `json:"up_mbps,omitempty"`
DownMbps any `json:"down_mbps,omitempty"`
Version any `json:"version,omitempty"`
@@ -265,10 +266,12 @@ func BuildNodeConfig(node *model.Server) NodeServerConfig {
case "vmess":
response.Tls = getMapInt(settings, "tls")
response.Multiplex = getMapAny(settings, "multiplex")
response.UTLS = getMapAny(settings, "utls")
case "trojan":
response.Host = node.Host
response.ServerName = getMapString(settings, "server_name")
response.Multiplex = getMapAny(settings, "multiplex")
response.UTLS = getMapAny(settings, "utls")
response.Tls = getMapInt(settings, "tls")
if getMapInt(settings, "tls") == 2 {
response.TlsSettings = getMapAny(settings, "reality_settings")
@@ -279,6 +282,7 @@ func BuildNodeConfig(node *model.Server) NodeServerConfig {
response.Tls = getMapInt(settings, "tls")
response.Flow = getMapString(settings, "flow")
response.Multiplex = getMapAny(settings, "multiplex")
response.UTLS = getMapAny(settings, "utls")
response.Decryption = nil
if encryption, ok := settings["encryption"].(map[string]any); ok {
if enabled, ok := encryption["enabled"].(bool); ok && enabled {