From 515b5ae9def4092e329da40db6b1a1a4ee742271 Mon Sep 17 00:00:00 2001 From: CN-JS-HuiBai Date: Sat, 18 Apr 2026 01:00:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DJSON=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/handler/node_api.go | 3 +- internal/service/node.go | 156 +++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/internal/handler/node_api.go b/internal/handler/node_api.go index 30196a7..020613f 100644 --- a/internal/handler/node_api.go +++ b/internal/handler/node_api.go @@ -96,7 +96,8 @@ func NodeConfig(c *gin.Context) { node := c.MustGet("node").(*model.Server) setNodeLastCheck(node) - config := service.BuildNodeConfig(node) + config := service.BuildNodeConfigPayload(node) + config["base_config"] = service.BuildNodeBaseConfigPayload() c.JSON(http.StatusOK, config) } diff --git a/internal/service/node.go b/internal/service/node.go index 947baf6..672ee83 100644 --- a/internal/service/node.go +++ b/internal/service/node.go @@ -351,6 +351,162 @@ func BuildNodeConfig(node *model.Server) NodeServerConfig { return response } +func BuildNodeConfigPayload(node *model.Server) map[string]any { + settings := parseObject(node.ProtocolSettings) + baseConfig := map[string]any{ + "protocol": node.Type, + "listen_ip": "0.0.0.0", + "server_port": node.ServerPort, + "network": getMapAny(settings, "network"), + "networkSettings": getMapAny(settings, "network_settings"), + } + + response := cloneNodePayload(baseConfig) + + switch node.Type { + case "shadowsocks": + response["cipher"] = getMapAny(settings, "cipher") + response["plugin"] = getMapAny(settings, "plugin") + response["plugin_opts"] = getMapAny(settings, "plugin_opts") + switch getMapString(settings, "cipher") { + case "2022-blake3-aes-128-gcm": + response["server_key"] = serverKey(node.CreatedAt, 16) + case "2022-blake3-aes-256-gcm": + response["server_key"] = serverKey(node.CreatedAt, 32) + default: + response["server_key"] = nil + } + case "vmess": + response["tls"] = getMapInt(settings, "tls") + response["multiplex"] = getMapAny(settings, "multiplex") + case "trojan": + response["host"] = node.Host + response["server_name"] = getMapAny(settings, "server_name") + response["multiplex"] = getMapAny(settings, "multiplex") + response["tls"] = getMapInt(settings, "tls") + if getMapInt(settings, "tls") == 2 { + response["tls_settings"] = getMapAny(settings, "reality_settings") + } else { + response["tls_settings"] = nil + } + case "vless": + response["tls"] = getMapInt(settings, "tls") + response["flow"] = getMapAny(settings, "flow") + response["multiplex"] = getMapAny(settings, "multiplex") + response["decryption"] = nil + if encryption, ok := settings["encryption"].(map[string]any); ok { + if enabled, ok := encryption["enabled"].(bool); ok && enabled { + response["decryption"] = encryption["decryption"] + } + } + if getMapInt(settings, "tls") == 2 { + response["tls_settings"] = getMapAny(settings, "reality_settings") + } else { + response["tls_settings"] = getMapAny(settings, "tls_settings") + } + case "hysteria": + tls, _ := settings["tls"].(map[string]any) + obfs, _ := settings["obfs"].(map[string]any) + bandwidth, _ := settings["bandwidth"].(map[string]any) + version := getMapInt(settings, "version") + response["version"] = version + response["host"] = node.Host + response["server_name"] = getMapAny(tls, "server_name") + response["up_mbps"] = mapAnyInt(bandwidth, "up") + response["down_mbps"] = mapAnyInt(bandwidth, "down") + switch version { + case 1: + response["obfs"] = getMapAny(obfs, "password") + case 2: + if open, ok := obfs["open"].(bool); ok && open { + response["obfs"] = getMapAny(obfs, "type") + } else { + response["obfs"] = nil + } + response["obfs-password"] = getMapAny(obfs, "password") + } + case "tuic": + tls, _ := settings["tls"].(map[string]any) + response["version"] = getMapInt(settings, "version") + response["server_name"] = getMapAny(tls, "server_name") + response["congestion_control"] = getMapAny(settings, "congestion_control") + response["tls_settings"] = getMapAny(settings, "tls_settings") + response["auth_timeout"] = "3s" + response["zero_rtt_handshake"] = false + response["heartbeat"] = "3s" + case "anytls": + tls, _ := settings["tls"].(map[string]any) + response["server_name"] = getMapAny(tls, "server_name") + response["padding_scheme"] = getMapAny(settings, "padding_scheme") + case "socks": + // Base config already matches the original XBoard payload for socks. + case "naive": + response["tls"] = getMapInt(settings, "tls") + response["tls_settings"] = getMapAny(settings, "tls_settings") + case "http": + response["tls"] = getMapInt(settings, "tls") + response["tls_settings"] = getMapAny(settings, "tls_settings") + case "mieru": + transport := getMapString(settings, "transport") + if strings.TrimSpace(transport) == "" { + transport = "TCP" + } + response["transport"] = transport + response["traffic_pattern"] = getMapAny(settings, "traffic_pattern") + } + + if routeIDs := parseIntSlice(node.RouteIDs); len(routeIDs) > 0 { + var routes []model.ServerRoute + if err := database.DB.Select("id", "`match`", "action", "action_value").Where("id IN ?", routeIDs).Find(&routes).Error; err == nil { + response["routes"] = routes + } + } + + if customOutbounds := parseGenericJSON(node.CustomOutbounds); hasNodePayloadValue(customOutbounds) { + response["custom_outbounds"] = customOutbounds + } + if customRoutes := parseGenericJSON(node.CustomRoutes); hasNodePayloadValue(customRoutes) { + response["custom_routes"] = customRoutes + } + if certConfig := parseObject(node.CertConfig); len(certConfig) > 0 && getMapString(certConfig, "cert_mode") != "none" { + response["cert_config"] = certConfig + } + + return response +} + +func BuildNodeBaseConfigPayload() map[string]any { + return map[string]any{ + "push_interval": MustGetInt("server_push_interval", 60), + "pull_interval": MustGetInt("server_pull_interval", 60), + } +} + +func cloneNodePayload(values map[string]any) map[string]any { + result := make(map[string]any, len(values)) + for key, value := range values { + result[key] = value + } + return result +} + +func hasNodePayloadValue(value any) bool { + switch typed := value.(type) { + case nil: + return false + case string: + return strings.TrimSpace(typed) != "" + case []any: + return len(typed) > 0 + case []map[string]any: + return len(typed) > 0 + case map[string]any: + return len(typed) > 0 + default: + return true + } +} + func ApplyTrafficDelta(userID int, node *model.Server, upload, download int64) { rate := CurrentRate(node) scaledUpload := int64(math.Round(float64(upload) * rate))