进一步查看前半部分对不对
Some checks failed
build / build (api, amd64, linux) (push) Failing after -46s
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-17 23:45:44 +08:00
parent 1a03d3d1fc
commit 4ba835b93f
4 changed files with 303 additions and 18 deletions

View File

@@ -29,7 +29,7 @@ func GenerateClashWithTemplate(templateName string, servers []model.Server, user
for _, s := range servers {
conf := service.BuildNodeConfig(&s)
password := service.GenerateServerPassword(&s, &user)
proxy := buildClashProxy(conf, password)
proxy := buildClashProxy(templateName, conf, password)
if proxy == nil {
continue
}
@@ -53,35 +53,88 @@ func GenerateClashWithTemplate(templateName string, servers []model.Server, user
return output, nil
}
func buildClashProxy(conf service.NodeServerConfig, password string) map[string]any {
func buildClashProxy(templateName string, conf service.NodeServerConfig, password string) map[string]any {
switch conf.Protocol {
case "shadowsocks":
cipher, _ := conf.Cipher.(string)
if templateName == "clash" {
switch cipher {
case "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305":
default:
return nil
}
}
return map[string]any{
"name": conf.Name,
"type": "ss",
"server": conf.RawHost,
"port": conf.ServerPort,
"port": conf.Port,
"cipher": cipher,
"password": password,
"udp": true,
}
case "vmess":
return map[string]any{
"name": conf.Name,
"type": "vmess",
"server": conf.RawHost,
"port": conf.ServerPort,
"port": conf.Port,
"uuid": password,
"alterId": 0,
"cipher": "auto",
"udp": true,
}
case "vless":
if templateName != "clashmeta" {
return nil
}
proxy := map[string]any{
"name": conf.Name,
"type": "vless",
"server": conf.RawHost,
"port": conf.Port,
"uuid": password,
"alterId": 0,
"cipher": "auto",
"udp": true,
"flow": toClashString(conf.Flow),
"encryption": "none",
"tls": false,
}
switch toClashInt(conf.Tls) {
case 1:
proxy["tls"] = true
if tlsSettings, ok := conf.TlsSettings.(map[string]any); ok {
proxy["skip-cert-verify"] = toClashBool(tlsSettings["allow_insecure"])
if serverName := toClashString(tlsSettings["server_name"]); serverName != "" {
proxy["servername"] = serverName
}
}
case 2:
proxy["tls"] = true
if tlsSettings, ok := conf.TlsSettings.(map[string]any); ok {
proxy["skip-cert-verify"] = toClashBool(tlsSettings["allow_insecure"])
if serverName := toClashString(tlsSettings["server_name"]); serverName != "" {
proxy["servername"] = serverName
}
proxy["reality-opts"] = map[string]any{
"public-key": toClashString(tlsSettings["public_key"]),
"short-id": toClashString(tlsSettings["short_id"]),
}
}
}
network := toClashString(conf.Network)
if network == "" {
network = "tcp"
}
proxy["network"] = network
return proxy
case "trojan":
return map[string]any{
"name": conf.Name,
"type": "trojan",
"server": conf.RawHost,
"port": conf.ServerPort,
"port": conf.Port,
"password": password,
"udp": true,
}
@@ -155,6 +208,54 @@ func appendUniqueAny(base []any, values ...any) []any {
return base
}
func toClashString(value any) string {
switch typed := value.(type) {
case string:
return typed
case nil:
return ""
default:
return fmt.Sprint(typed)
}
}
func toClashInt(value any) int {
switch typed := value.(type) {
case int:
return typed
case int64:
return int(typed)
case float64:
return int(typed)
case string:
if typed == "" {
return 0
}
var result int
fmt.Sscanf(typed, "%d", &result)
return result
default:
return 0
}
}
func toClashBool(value any) bool {
switch typed := value.(type) {
case bool:
return typed
case int:
return typed != 0
case int64:
return typed != 0
case float64:
return typed != 0
case string:
return typed == "1" || strings.EqualFold(typed, "true")
default:
return false
}
}
func generateClashFallback(servers []model.Server, user model.User) string {
var builder strings.Builder
@@ -163,7 +264,7 @@ func generateClashFallback(servers []model.Server, user model.User) string {
for _, s := range servers {
conf := service.BuildNodeConfig(&s)
password := service.GenerateServerPassword(&s, &user)
proxy := buildClashProxy(conf, password)
proxy := buildClashProxy("clash", conf, password)
if proxy == nil {
continue
}

View File

@@ -52,7 +52,7 @@ func buildShadowsocks(c service.NodeServerConfig, password string) string {
userInfo := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", cipher, password)))
userInfo = strings.TrimRight(strings.ReplaceAll(strings.ReplaceAll(userInfo, "+", "-"), "/", "_"), "=")
link := fmt.Sprintf("ss://%s@%s:%d", userInfo, wrapIPv6(c.RawHost), c.ServerPort)
link := fmt.Sprintf("ss://%s@%s:%d", userInfo, wrapIPv6(c.RawHost), c.Port)
params := url.Values{}
if plugin := toString(c.Plugin); plugin != "" {
opts := toString(c.PluginOpts)
@@ -69,7 +69,7 @@ func buildVmess(c service.NodeServerConfig, password string) string {
"v": "2",
"ps": c.Name,
"add": c.RawHost,
"port": fmt.Sprintf("%d", c.ServerPort),
"port": fmt.Sprintf("%d", c.Port),
"id": password,
"aid": "0",
"net": toString(c.Network),
@@ -145,7 +145,7 @@ func buildVless(c service.NodeServerConfig, password string) string {
}
}
return fmt.Sprintf("vless://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
return fmt.Sprintf("vless://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}
func buildTrojan(c service.NodeServerConfig, password string) string {
@@ -176,7 +176,7 @@ func buildTrojan(c service.NodeServerConfig, password string) string {
}
}
return fmt.Sprintf("trojan://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
return fmt.Sprintf("trojan://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}
func buildHysteria(c service.NodeServerConfig, password string) string {
@@ -188,7 +188,10 @@ func buildHysteria(c service.NodeServerConfig, password string) string {
params.Set("obfs", "salamander")
params.Set("obfs-password", toString(c.ObfsPassword))
}
return fmt.Sprintf("hysteria2://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
if c.Ports != "" {
params.Set("mport", c.Ports)
}
return fmt.Sprintf("hysteria2://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}
params.Set("protocol", "udp")
@@ -200,7 +203,7 @@ func buildHysteria(c service.NodeServerConfig, password string) string {
params.Set("downmbps", fmt.Sprintf("%v", c.DownMbps))
}
return fmt.Sprintf("hysteria://%s:%d?%s#%s", wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
return fmt.Sprintf("hysteria://%s:%d?%s#%s", wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}
func buildTuic(c service.NodeServerConfig, password string) string {
@@ -209,23 +212,23 @@ func buildTuic(c service.NodeServerConfig, password string) string {
params.Set("congestion_control", toString(c.CongestionControl))
params.Set("udp-relay-mode", "native")
return fmt.Sprintf("tuic://%s:%s@%s:%d?%s#%s", password, password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
return fmt.Sprintf("tuic://%s:%s@%s:%d?%s#%s", password, password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}
func buildAnyTLS(c service.NodeServerConfig, password string) string {
params := url.Values{}
params.Set("sni", toString(c.ServerName))
return fmt.Sprintf("anytls://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.ServerPort, params.Encode(), url.PathEscape(c.Name))
return fmt.Sprintf("anytls://%s@%s:%d?%s#%s", password, wrapIPv6(c.RawHost), c.Port, params.Encode(), url.PathEscape(c.Name))
}
func buildSocks(c service.NodeServerConfig, password string) string {
auth := base64.StdEncoding.EncodeToString([]byte(password + ":" + password))
return fmt.Sprintf("socks://%s@%s:%d#%s", auth, wrapIPv6(c.RawHost), c.ServerPort, url.PathEscape(c.Name))
return fmt.Sprintf("socks://%s@%s:%d#%s", auth, wrapIPv6(c.RawHost), c.Port, url.PathEscape(c.Name))
}
func buildHttp(c service.NodeServerConfig, password string) string {
auth := base64.StdEncoding.EncodeToString([]byte(password + ":" + password))
link := fmt.Sprintf("http://%s@%s:%d", auth, wrapIPv6(c.RawHost), c.ServerPort)
link := fmt.Sprintf("http://%s@%s:%d", auth, wrapIPv6(c.RawHost), c.Port)
if toInt(c.Tls) > 0 {
params := url.Values{}
params.Set("security", "tls")

View File

@@ -64,7 +64,7 @@ func buildSingBoxOutbound(conf service.NodeServerConfig, password string) map[st
outbound := map[string]any{
"tag": conf.Name,
"server": conf.RawHost,
"server_port": conf.ServerPort,
"server_port": conf.Port,
}
switch conf.Protocol {
@@ -76,6 +76,19 @@ func buildSingBoxOutbound(conf service.NodeServerConfig, password string) map[st
outbound["type"] = "vmess"
outbound["uuid"] = password
outbound["security"] = "auto"
case "vless":
outbound["type"] = "vless"
outbound["uuid"] = password
outbound["packet_encoding"] = "xudp"
if flow := singBoxString(conf.Flow); flow != "" {
outbound["flow"] = flow
}
if tls := buildSingBoxTLS(conf); tls != nil {
outbound["tls"] = tls
}
if transport := buildSingBoxTransport(conf); transport != nil {
outbound["transport"] = transport
}
case "trojan":
outbound["type"] = "trojan"
outbound["password"] = password
@@ -96,7 +109,7 @@ func generateSingBoxFallback(servers []model.Server, user model.User) (string, e
outbound := map[string]interface{}{
"tag": conf.Name,
"server": conf.RawHost,
"server_port": conf.ServerPort,
"server_port": conf.Port,
}
switch conf.Protocol {
@@ -108,6 +121,19 @@ func generateSingBoxFallback(servers []model.Server, user model.User) (string, e
outbound["type"] = "vmess"
outbound["uuid"] = password
outbound["security"] = "auto"
case "vless":
outbound["type"] = "vless"
outbound["uuid"] = password
outbound["packet_encoding"] = "xudp"
if flow := singBoxString(conf.Flow); flow != "" {
outbound["flow"] = flow
}
if tls := buildSingBoxTLS(conf); tls != nil {
outbound["tls"] = tls
}
if transport := buildSingBoxTransport(conf); transport != nil {
outbound["transport"] = transport
}
case "trojan":
outbound["type"] = "trojan"
outbound["password"] = password
@@ -133,3 +159,128 @@ func generateSingBoxFallback(servers []model.Server, user model.User) (string, e
data, err := json.MarshalIndent(config, "", " ")
return string(data), err
}
func buildSingBoxTLS(conf service.NodeServerConfig) map[string]any {
switch singBoxInt(conf.Tls) {
case 1:
tls := map[string]any{
"enabled": true,
}
if tlsSettings, ok := conf.TlsSettings.(map[string]any); ok {
tls["insecure"] = singBoxBool(tlsSettings["allow_insecure"])
if serverName := singBoxString(tlsSettings["server_name"]); serverName != "" {
tls["server_name"] = serverName
}
}
return tls
case 2:
tls := map[string]any{
"enabled": true,
}
if tlsSettings, ok := conf.TlsSettings.(map[string]any); ok {
tls["insecure"] = singBoxBool(tlsSettings["allow_insecure"])
if serverName := singBoxString(tlsSettings["server_name"]); serverName != "" {
tls["server_name"] = serverName
}
tls["reality"] = map[string]any{
"enabled": true,
"public_key": singBoxString(tlsSettings["public_key"]),
"short_id": singBoxString(tlsSettings["short_id"]),
}
}
return tls
default:
return nil
}
}
func buildSingBoxTransport(conf service.NodeServerConfig) map[string]any {
network := singBoxString(conf.Network)
settings, _ := conf.NetworkSettings.(map[string]any)
switch network {
case "ws":
transport := map[string]any{
"type": "ws",
}
if settings != nil {
if path := singBoxString(settings["path"]); path != "" {
transport["path"] = path
}
if headers, ok := settings["headers"].(map[string]any); ok {
if host := singBoxString(headers["Host"]); host != "" {
transport["headers"] = map[string]any{"Host": host}
}
}
}
return transport
case "grpc":
transport := map[string]any{
"type": "grpc",
}
if settings != nil {
if serviceName := singBoxString(settings["serviceName"]); serviceName != "" {
transport["service_name"] = serviceName
}
}
return transport
case "httpupgrade":
transport := map[string]any{
"type": "httpupgrade",
}
if settings != nil {
if path := singBoxString(settings["path"]); path != "" {
transport["path"] = path
}
if host := singBoxString(settings["host"]); host != "" {
transport["host"] = host
}
if headers, ok := settings["headers"].(map[string]any); ok && len(headers) > 0 {
transport["headers"] = headers
}
}
return transport
default:
return nil
}
}
func singBoxString(value any) string {
switch typed := value.(type) {
case string:
return typed
case nil:
return ""
default:
return ""
}
}
func singBoxInt(value any) int {
switch typed := value.(type) {
case int:
return typed
case int64:
return int(typed)
case float64:
return int(typed)
default:
return 0
}
}
func singBoxBool(value any) bool {
switch typed := value.(type) {
case bool:
return typed
case int:
return typed != 0
case int64:
return typed != 0
case float64:
return typed != 0
case string:
return typed == "1" || typed == "true"
default:
return false
}
}

View File

@@ -68,6 +68,8 @@ type NodeServerConfig struct {
Name string `json:"-"`
Protocol string `json:"protocol"`
RawHost string `json:"-"`
Port int `json:"-"`
Ports string `json:"-"`
ListenIP string `json:"listen_ip"`
ServerPort int `json:"server_port"`
Network any `json:"network"`
@@ -230,10 +232,13 @@ func CurrentRate(server *model.Server) float64 {
func BuildNodeConfig(node *model.Server) NodeServerConfig {
settings := parseObject(node.ProtocolSettings)
clientPort, portRange := resolveClientPort(node.Port, node.ServerPort)
response := NodeServerConfig{
Name: node.Name,
Protocol: node.Type,
RawHost: node.Host,
Port: clientPort,
Ports: portRange,
ListenIP: "0.0.0.0",
ServerPort: node.ServerPort,
Network: getMapAny(settings, "network"),
@@ -408,6 +413,31 @@ func uuidPrefixBase64(uuid string, size int) string {
return base64.StdEncoding.EncodeToString([]byte(uuid[:size]))
}
func resolveClientPort(rawPort string, fallback int) (int, string) {
rawPort = strings.TrimSpace(rawPort)
if rawPort == "" {
return fallback, ""
}
if strings.Contains(rawPort, "-") {
parts := strings.SplitN(rawPort, "-", 2)
start, errStart := strconv.Atoi(strings.TrimSpace(parts[0]))
end, errEnd := strconv.Atoi(strings.TrimSpace(parts[1]))
if errStart == nil && errEnd == nil && start > 0 && end >= start {
if start == end {
return start, rawPort
}
return start + int(time.Now().UnixNano()%int64(end-start+1)), rawPort
}
}
if port, err := strconv.Atoi(rawPort); err == nil && port > 0 {
return port, ""
}
return fallback, ""
}
func parseIntSlice(raw *string) []int {
if raw == nil || strings.TrimSpace(*raw) == "" {
return nil