package sub import ( "encoding/base64" "fmt" "net/url" "strings" "time" "x-ui/database" "x-ui/database/model" "x-ui/logger" "x-ui/util/common" "x-ui/util/random" "x-ui/web/service" "x-ui/xray" "github.com/goccy/go-json" ) type SubService struct { address string showInfo bool remarkModel string datepicker string inboundService service.InboundService settingService service.SettingService } func NewSubService(showInfo bool, remarkModel string) *SubService { return &SubService{ showInfo: showInfo, remarkModel: remarkModel, } } func (s *SubService) GetSubs(subId string, host string) ([]string, string, error) { s.address = host var result []string var header string var traffic xray.ClientTraffic var clientTraffics []xray.ClientTraffic inbounds, err := s.getInboundsBySubId(subId) if err != nil { return nil, "", err } if len(inbounds) == 0 { return nil, "", common.NewError("No inbounds found with ", subId) } s.datepicker, err = s.settingService.GetDatepicker() if err != nil { s.datepicker = "gregorian" } for _, inbound := range inbounds { clients, err := s.inboundService.GetClients(inbound) if err != nil { logger.Error("SubService - GetClients: Unable to get clients from inbound") } if clients == nil { continue } if len(inbound.Listen) > 0 && inbound.Listen[0] == '@' { listen, port, streamSettings, err := s.getFallbackMaster(inbound.Listen, inbound.StreamSettings) if err == nil { inbound.Listen = listen inbound.Port = port inbound.StreamSettings = streamSettings } } for _, client := range clients { if client.Enable && client.SubID == subId { link := s.getLink(inbound, client.Email) result = append(result, link) clientTraffics = append(clientTraffics, s.getClientTraffics(inbound.ClientStats, client.Email)) } } } // Prepare statistics for index, clientTraffic := range clientTraffics { if index == 0 { traffic.Up = clientTraffic.Up traffic.Down = clientTraffic.Down traffic.Total = clientTraffic.Total if clientTraffic.ExpiryTime > 0 { traffic.ExpiryTime = clientTraffic.ExpiryTime } } else { traffic.Up += clientTraffic.Up traffic.Down += clientTraffic.Down if traffic.Total == 0 || clientTraffic.Total == 0 { traffic.Total = 0 } else { traffic.Total += clientTraffic.Total } if clientTraffic.ExpiryTime != traffic.ExpiryTime { traffic.ExpiryTime = 0 } } } header = fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000) return result, header, nil } func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { db := database.GetDB() var inbounds []*model.Inbound err := db.Model(model.Inbound{}).Preload("ClientStats").Where(`id in ( SELECT DISTINCT inbounds.id FROM inbounds, JSON_EACH(JSON_EXTRACT(inbounds.settings, '$.clients')) AS client WHERE protocol in ('vmess','vless','trojan','shadowsocks') AND JSON_EXTRACT(client.value, '$.subId') = ? AND enable = ? )`, subId, true).Find(&inbounds).Error if err != nil { return nil, err } return inbounds, nil } func (s *SubService) getClientTraffics(traffics []xray.ClientTraffic, email string) xray.ClientTraffic { for _, traffic := range traffics { if traffic.Email == email { return traffic } } return xray.ClientTraffic{} } func (s *SubService) getFallbackMaster(dest string, streamSettings string) (string, int, string, error) { db := database.GetDB() var inbound *model.Inbound err := db.Model(model.Inbound{}). Where("JSON_TYPE(settings, '$.fallbacks') = 'array'"). Where("EXISTS (SELECT * FROM json_each(settings, '$.fallbacks') WHERE json_extract(value, '$.dest') = ?)", dest). Find(&inbound).Error if err != nil { return "", 0, "", err } var stream map[string]interface{} json.Unmarshal([]byte(streamSettings), &stream) var masterStream map[string]interface{} json.Unmarshal([]byte(inbound.StreamSettings), &masterStream) stream["security"] = masterStream["security"] stream["tlsSettings"] = masterStream["tlsSettings"] stream["externalProxy"] = masterStream["externalProxy"] modifiedStream, _ := json.MarshalIndent(stream, "", " ") return inbound.Listen, inbound.Port, string(modifiedStream), nil } func (s *SubService) getLink(inbound *model.Inbound, email string) string { switch inbound.Protocol { case "vmess": return s.genVmessLink(inbound, email) case "vless": return s.genVlessLink(inbound, email) case "trojan": return s.genTrojanLink(inbound, email) case "shadowsocks": return s.genShadowsocksLink(inbound, email) } return "" } func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { if inbound.Protocol != model.VMess { return "" } obj := map[string]interface{}{ "v": "2", "add": s.address, "port": inbound.Port, "type": "none", } var stream map[string]interface{} json.Unmarshal([]byte(inbound.StreamSettings), &stream) network, _ := stream["network"].(string) obj["net"] = network switch network { case "tcp": tcp, _ := stream["tcpSettings"].(map[string]interface{}) header, _ := tcp["header"].(map[string]interface{}) typeStr, _ := header["type"].(string) obj["type"] = typeStr if typeStr == "http" { request := header["request"].(map[string]interface{}) requestPath, _ := request["path"].([]interface{}) obj["path"] = requestPath[0].(string) headers, _ := request["headers"].(map[string]interface{}) obj["host"] = searchHost(headers) } case "kcp": kcp, _ := stream["kcpSettings"].(map[string]interface{}) header, _ := kcp["header"].(map[string]interface{}) obj["type"], _ = header["type"].(string) obj["path"], _ = kcp["seed"].(string) case "ws": ws, _ := stream["wsSettings"].(map[string]interface{}) obj["path"] = ws["path"].(string) if host, ok := ws["host"].(string); ok && len(host) > 0 { obj["host"] = host } else { headers, _ := ws["headers"].(map[string]interface{}) obj["host"] = searchHost(headers) } case "http": obj["net"] = "h2" http, _ := stream["httpSettings"].(map[string]interface{}) obj["path"], _ = http["path"].(string) obj["host"] = searchHost(http) case "quic": quic, _ := stream["quicSettings"].(map[string]interface{}) header := quic["header"].(map[string]interface{}) obj["type"], _ = header["type"].(string) obj["host"], _ = quic["security"].(string) obj["path"], _ = quic["key"].(string) case "grpc": grpc, _ := stream["grpcSettings"].(map[string]interface{}) obj["path"] = grpc["serviceName"].(string) obj["authority"] = grpc["authority"].(string) if grpc["multiMode"].(bool) { obj["type"] = "multi" } case "httpupgrade": httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{}) obj["path"] = httpupgrade["path"].(string) if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 { obj["host"] = host } else { headers, _ := httpupgrade["headers"].(map[string]interface{}) obj["host"] = searchHost(headers) } case "splithttp": splithttp, _ := stream["splithttpSettings"].(map[string]interface{}) obj["path"] = splithttp["path"].(string) if host, ok := splithttp["host"].(string); ok && len(host) > 0 { obj["host"] = host } else { headers, _ := splithttp["headers"].(map[string]interface{}) obj["host"] = searchHost(headers) } } security, _ := stream["security"].(string) obj["tls"] = security if security == "tls" { tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) alpns, _ := tlsSetting["alpn"].([]interface{}) if len(alpns) > 0 { var alpn []string for _, a := range alpns { alpn = append(alpn, a.(string)) } obj["alpn"] = strings.Join(alpn, ",") } if sniValue, ok := searchKey(tlsSetting, "serverName"); ok { obj["sni"], _ = sniValue.(string) } tlsSettings, _ := searchKey(tlsSetting, "settings") if tlsSetting != nil { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { obj["fp"], _ = fpValue.(string) } if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok { obj["allowInsecure"], _ = insecure.(bool) } } } clients, _ := s.inboundService.GetClients(inbound) clientIndex := -1 for i, client := range clients { if client.Email == email { clientIndex = i break } } obj["id"] = clients[clientIndex].ID externalProxies, _ := stream["externalProxy"].([]interface{}) if len(externalProxies) > 0 { links := "" for index, externalProxy := range externalProxies { ep, _ := externalProxy.(map[string]interface{}) newSecurity, _ := ep["forceTls"].(string) newObj := map[string]interface{}{} for key, value := range obj { if !(newSecurity == "none" && (key == "alpn" || key == "sni" || key == "fp" || key == "allowInsecure")) { newObj[key] = value } } newObj["ps"] = s.genRemark(inbound, email, ep["remark"].(string)) newObj["add"] = ep["dest"].(string) newObj["port"] = int(ep["port"].(float64)) if newSecurity != "same" { newObj["tls"] = newSecurity } if index > 0 { links += "\n" } jsonStr, _ := json.MarshalIndent(newObj, "", " ") links += "vmess://" + base64.StdEncoding.EncodeToString(jsonStr) } return links } obj["ps"] = s.genRemark(inbound, email, "") jsonStr, _ := json.MarshalIndent(obj, "", " ") return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr) } func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { address := s.address if inbound.Protocol != model.VLESS { return "" } var stream map[string]interface{} json.Unmarshal([]byte(inbound.StreamSettings), &stream) clients, _ := s.inboundService.GetClients(inbound) clientIndex := -1 for i, client := range clients { if client.Email == email { clientIndex = i break } } uuid := clients[clientIndex].ID port := inbound.Port streamNetwork := stream["network"].(string) params := make(map[string]string) params["type"] = streamNetwork switch streamNetwork { case "tcp": tcp, _ := stream["tcpSettings"].(map[string]interface{}) header, _ := tcp["header"].(map[string]interface{}) typeStr, _ := header["type"].(string) if typeStr == "http" { request := header["request"].(map[string]interface{}) requestPath, _ := request["path"].([]interface{}) params["path"] = requestPath[0].(string) headers, _ := request["headers"].(map[string]interface{}) params["host"] = searchHost(headers) params["headerType"] = "http" } case "kcp": kcp, _ := stream["kcpSettings"].(map[string]interface{}) header, _ := kcp["header"].(map[string]interface{}) params["headerType"] = header["type"].(string) params["seed"] = kcp["seed"].(string) case "ws": ws, _ := stream["wsSettings"].(map[string]interface{}) params["path"] = ws["path"].(string) if host, ok := ws["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := ws["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } case "http": http, _ := stream["httpSettings"].(map[string]interface{}) params["path"] = http["path"].(string) params["host"] = searchHost(http) case "quic": quic, _ := stream["quicSettings"].(map[string]interface{}) params["quicSecurity"] = quic["security"].(string) params["key"] = quic["key"].(string) header := quic["header"].(map[string]interface{}) params["headerType"] = header["type"].(string) case "grpc": grpc, _ := stream["grpcSettings"].(map[string]interface{}) params["serviceName"] = grpc["serviceName"].(string) params["authority"], _ = grpc["authority"].(string) if grpc["multiMode"].(bool) { params["mode"] = "multi" } case "httpupgrade": httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{}) params["path"] = httpupgrade["path"].(string) if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := httpupgrade["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } case "splithttp": splithttp, _ := stream["splithttpSettings"].(map[string]interface{}) params["path"] = splithttp["path"].(string) if host, ok := splithttp["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := splithttp["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } } security, _ := stream["security"].(string) if security == "tls" { params["security"] = "tls" tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) alpns, _ := tlsSetting["alpn"].([]interface{}) var alpn []string for _, a := range alpns { alpn = append(alpn, a.(string)) } if len(alpn) > 0 { params["alpn"] = strings.Join(alpn, ",") } if sniValue, ok := searchKey(tlsSetting, "serverName"); ok { params["sni"], _ = sniValue.(string) } tlsSettings, _ := searchKey(tlsSetting, "settings") if tlsSetting != nil { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { params["fp"], _ = fpValue.(string) } if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok { if insecure.(bool) { params["allowInsecure"] = "1" } } } if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { params["flow"] = clients[clientIndex].Flow } } if security == "reality" { params["security"] = "reality" realitySetting, _ := stream["realitySettings"].(map[string]interface{}) realitySettings, _ := searchKey(realitySetting, "settings") if realitySetting != nil { if sniValue, ok := searchKey(realitySetting, "serverNames"); ok { sNames, _ := sniValue.([]interface{}) params["sni"] = sNames[random.Num(len(sNames))].(string) } if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok { params["pbk"], _ = pbkValue.(string) } if sidValue, ok := searchKey(realitySetting, "shortIds"); ok { shortIds, _ := sidValue.([]interface{}) params["sid"] = shortIds[random.Num(len(shortIds))].(string) } if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok { if fp, ok := fpValue.(string); ok && len(fp) > 0 { params["fp"] = fp } } params["spx"] = "/" + random.Seq(15) } if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { params["flow"] = clients[clientIndex].Flow } } if security == "xtls" { params["security"] = "xtls" xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{}) alpns, _ := xtlsSetting["alpn"].([]interface{}) var alpn []string for _, a := range alpns { alpn = append(alpn, a.(string)) } if len(alpn) > 0 { params["alpn"] = strings.Join(alpn, ",") } if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok { params["sni"], _ = sniValue.(string) } xtlsSettings, _ := searchKey(xtlsSetting, "settings") if xtlsSetting != nil { if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok { params["fp"], _ = fpValue.(string) } if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok { if insecure.(bool) { params["allowInsecure"] = "1" } } } if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { params["flow"] = clients[clientIndex].Flow } } if security != "tls" && security != "reality" && security != "xtls" { params["security"] = "none" } externalProxies, _ := stream["externalProxy"].([]interface{}) if len(externalProxies) > 0 { links := "" for index, externalProxy := range externalProxies { ep, _ := externalProxy.(map[string]interface{}) newSecurity, _ := ep["forceTls"].(string) dest, _ := ep["dest"].(string) port := int(ep["port"].(float64)) link := fmt.Sprintf("vless://%s@%s:%d", uuid, dest, port) if newSecurity != "same" { params["security"] = newSecurity } else { params["security"] = security } url, _ := url.Parse(link) q := url.Query() for k, v := range params { if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) { q.Add(k, v) } } // Set the new query values on the URL url.RawQuery = q.Encode() url.Fragment = s.genRemark(inbound, email, ep["remark"].(string)) if index > 0 { links += "\n" } links += url.String() } return links } link := fmt.Sprintf("vless://%s@%s:%d", uuid, address, port) url, _ := url.Parse(link) q := url.Query() for k, v := range params { q.Add(k, v) } // Set the new query values on the URL url.RawQuery = q.Encode() url.Fragment = s.genRemark(inbound, email, "") return url.String() } func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string { address := s.address if inbound.Protocol != model.Trojan { return "" } var stream map[string]interface{} json.Unmarshal([]byte(inbound.StreamSettings), &stream) clients, _ := s.inboundService.GetClients(inbound) clientIndex := -1 for i, client := range clients { if client.Email == email { clientIndex = i break } } password := clients[clientIndex].Password port := inbound.Port streamNetwork := stream["network"].(string) params := make(map[string]string) params["type"] = streamNetwork switch streamNetwork { case "tcp": tcp, _ := stream["tcpSettings"].(map[string]interface{}) header, _ := tcp["header"].(map[string]interface{}) typeStr, _ := header["type"].(string) if typeStr == "http" { request := header["request"].(map[string]interface{}) requestPath, _ := request["path"].([]interface{}) params["path"] = requestPath[0].(string) headers, _ := request["headers"].(map[string]interface{}) params["host"] = searchHost(headers) params["headerType"] = "http" } case "kcp": kcp, _ := stream["kcpSettings"].(map[string]interface{}) header, _ := kcp["header"].(map[string]interface{}) params["headerType"] = header["type"].(string) params["seed"] = kcp["seed"].(string) case "ws": ws, _ := stream["wsSettings"].(map[string]interface{}) params["path"] = ws["path"].(string) if host, ok := ws["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := ws["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } case "http": http, _ := stream["httpSettings"].(map[string]interface{}) params["path"] = http["path"].(string) params["host"] = searchHost(http) case "quic": quic, _ := stream["quicSettings"].(map[string]interface{}) params["quicSecurity"] = quic["security"].(string) params["key"] = quic["key"].(string) header := quic["header"].(map[string]interface{}) params["headerType"] = header["type"].(string) case "grpc": grpc, _ := stream["grpcSettings"].(map[string]interface{}) params["serviceName"] = grpc["serviceName"].(string) params["authority"], _ = grpc["authority"].(string) if grpc["multiMode"].(bool) { params["mode"] = "multi" } case "httpupgrade": httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{}) params["path"] = httpupgrade["path"].(string) if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := httpupgrade["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } case "splithttp": splithttp, _ := stream["splithttpSettings"].(map[string]interface{}) params["path"] = splithttp["path"].(string) if host, ok := splithttp["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := splithttp["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } } security, _ := stream["security"].(string) if security == "tls" { params["security"] = "tls" tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) alpns, _ := tlsSetting["alpn"].([]interface{}) var alpn []string for _, a := range alpns { alpn = append(alpn, a.(string)) } if len(alpn) > 0 { params["alpn"] = strings.Join(alpn, ",") } if sniValue, ok := searchKey(tlsSetting, "serverName"); ok { params["sni"], _ = sniValue.(string) } tlsSettings, _ := searchKey(tlsSetting, "settings") if tlsSetting != nil { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { params["fp"], _ = fpValue.(string) } if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok { if insecure.(bool) { params["allowInsecure"] = "1" } } } } if security == "reality" { params["security"] = "reality" realitySetting, _ := stream["realitySettings"].(map[string]interface{}) realitySettings, _ := searchKey(realitySetting, "settings") if realitySetting != nil { if sniValue, ok := searchKey(realitySetting, "serverNames"); ok { sNames, _ := sniValue.([]interface{}) params["sni"] = sNames[random.Num(len(sNames))].(string) } if pbkValue, ok := searchKey(realitySettings, "publicKey"); ok { params["pbk"], _ = pbkValue.(string) } if sidValue, ok := searchKey(realitySetting, "shortIds"); ok { shortIds, _ := sidValue.([]interface{}) params["sid"] = shortIds[random.Num(len(shortIds))].(string) } if fpValue, ok := searchKey(realitySettings, "fingerprint"); ok { if fp, ok := fpValue.(string); ok && len(fp) > 0 { params["fp"] = fp } } params["spx"] = "/" + random.Seq(15) } if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { params["flow"] = clients[clientIndex].Flow } } if security == "xtls" { params["security"] = "xtls" xtlsSetting, _ := stream["xtlsSettings"].(map[string]interface{}) alpns, _ := xtlsSetting["alpn"].([]interface{}) var alpn []string for _, a := range alpns { alpn = append(alpn, a.(string)) } if len(alpn) > 0 { params["alpn"] = strings.Join(alpn, ",") } if sniValue, ok := searchKey(xtlsSetting, "serverName"); ok { params["sni"], _ = sniValue.(string) } xtlsSettings, _ := searchKey(xtlsSetting, "settings") if xtlsSetting != nil { if fpValue, ok := searchKey(xtlsSettings, "fingerprint"); ok { params["fp"], _ = fpValue.(string) } if insecure, ok := searchKey(xtlsSettings, "allowInsecure"); ok { if insecure.(bool) { params["allowInsecure"] = "1" } } } if streamNetwork == "tcp" && len(clients[clientIndex].Flow) > 0 { params["flow"] = clients[clientIndex].Flow } } if security != "tls" && security != "reality" && security != "xtls" { params["security"] = "none" } externalProxies, _ := stream["externalProxy"].([]interface{}) if len(externalProxies) > 0 { links := "" for index, externalProxy := range externalProxies { ep, _ := externalProxy.(map[string]interface{}) newSecurity, _ := ep["forceTls"].(string) dest, _ := ep["dest"].(string) port := int(ep["port"].(float64)) link := fmt.Sprintf("trojan://%s@%s:%d", password, dest, port) if newSecurity != "same" { params["security"] = newSecurity } else { params["security"] = security } url, _ := url.Parse(link) q := url.Query() for k, v := range params { if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) { q.Add(k, v) } } // Set the new query values on the URL url.RawQuery = q.Encode() url.Fragment = s.genRemark(inbound, email, ep["remark"].(string)) if index > 0 { links += "\n" } links += url.String() } return links } link := fmt.Sprintf("trojan://%s@%s:%d", password, address, port) url, _ := url.Parse(link) q := url.Query() for k, v := range params { q.Add(k, v) } // Set the new query values on the URL url.RawQuery = q.Encode() url.Fragment = s.genRemark(inbound, email, "") return url.String() } func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) string { address := s.address if inbound.Protocol != model.Shadowsocks { return "" } var stream map[string]interface{} json.Unmarshal([]byte(inbound.StreamSettings), &stream) clients, _ := s.inboundService.GetClients(inbound) var settings map[string]interface{} json.Unmarshal([]byte(inbound.Settings), &settings) inboundPassword := settings["password"].(string) method := settings["method"].(string) clientIndex := -1 for i, client := range clients { if client.Email == email { clientIndex = i break } } streamNetwork := stream["network"].(string) params := make(map[string]string) params["type"] = streamNetwork switch streamNetwork { case "tcp": tcp, _ := stream["tcpSettings"].(map[string]interface{}) header, _ := tcp["header"].(map[string]interface{}) typeStr, _ := header["type"].(string) if typeStr == "http" { request := header["request"].(map[string]interface{}) requestPath, _ := request["path"].([]interface{}) params["path"] = requestPath[0].(string) headers, _ := request["headers"].(map[string]interface{}) params["host"] = searchHost(headers) params["headerType"] = "http" } case "kcp": kcp, _ := stream["kcpSettings"].(map[string]interface{}) header, _ := kcp["header"].(map[string]interface{}) params["headerType"] = header["type"].(string) params["seed"] = kcp["seed"].(string) case "ws": ws, _ := stream["wsSettings"].(map[string]interface{}) params["path"] = ws["path"].(string) if host, ok := ws["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := ws["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } case "http": http, _ := stream["httpSettings"].(map[string]interface{}) params["path"] = http["path"].(string) params["host"] = searchHost(http) case "quic": quic, _ := stream["quicSettings"].(map[string]interface{}) params["quicSecurity"] = quic["security"].(string) params["key"] = quic["key"].(string) header := quic["header"].(map[string]interface{}) params["headerType"] = header["type"].(string) case "grpc": grpc, _ := stream["grpcSettings"].(map[string]interface{}) params["serviceName"] = grpc["serviceName"].(string) params["authority"], _ = grpc["authority"].(string) if grpc["multiMode"].(bool) { params["mode"] = "multi" } case "httpupgrade": httpupgrade, _ := stream["httpupgradeSettings"].(map[string]interface{}) params["path"] = httpupgrade["path"].(string) if host, ok := httpupgrade["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := httpupgrade["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } case "splithttp": splithttp, _ := stream["splithttpSettings"].(map[string]interface{}) params["path"] = splithttp["path"].(string) if host, ok := splithttp["host"].(string); ok && len(host) > 0 { params["host"] = host } else { headers, _ := splithttp["headers"].(map[string]interface{}) params["host"] = searchHost(headers) } } security, _ := stream["security"].(string) if security == "tls" { params["security"] = "tls" tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) alpns, _ := tlsSetting["alpn"].([]interface{}) var alpn []string for _, a := range alpns { alpn = append(alpn, a.(string)) } if len(alpn) > 0 { params["alpn"] = strings.Join(alpn, ",") } if sniValue, ok := searchKey(tlsSetting, "serverName"); ok { params["sni"], _ = sniValue.(string) } tlsSettings, _ := searchKey(tlsSetting, "settings") if tlsSetting != nil { if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { params["fp"], _ = fpValue.(string) } if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok { if insecure.(bool) { params["allowInsecure"] = "1" } } } } encPart := fmt.Sprintf("%s:%s", method, clients[clientIndex].Password) if method[0] == '2' { encPart = fmt.Sprintf("%s:%s:%s", method, inboundPassword, clients[clientIndex].Password) } externalProxies, _ := stream["externalProxy"].([]interface{}) if len(externalProxies) > 0 { links := "" for index, externalProxy := range externalProxies { ep, _ := externalProxy.(map[string]interface{}) newSecurity, _ := ep["forceTls"].(string) dest, _ := ep["dest"].(string) port := int(ep["port"].(float64)) link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), dest, port) if newSecurity != "same" { params["security"] = newSecurity } else { params["security"] = security } url, _ := url.Parse(link) q := url.Query() for k, v := range params { if !(newSecurity == "none" && (k == "alpn" || k == "sni" || k == "fp" || k == "allowInsecure")) { q.Add(k, v) } } // Set the new query values on the URL url.RawQuery = q.Encode() url.Fragment = s.genRemark(inbound, email, ep["remark"].(string)) if index > 0 { links += "\n" } links += url.String() } return links } link := fmt.Sprintf("ss://%s@%s:%d", base64.StdEncoding.EncodeToString([]byte(encPart)), address, inbound.Port) url, _ := url.Parse(link) q := url.Query() for k, v := range params { q.Add(k, v) } // Set the new query values on the URL url.RawQuery = q.Encode() url.Fragment = s.genRemark(inbound, email, "") return url.String() } func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string { separationChar := string(s.remarkModel[0]) orderChars := s.remarkModel[1:] orders := map[byte]string{ 'i': "", 'e': "", 'o': "", } if len(email) > 0 { orders['e'] = email } if len(inbound.Remark) > 0 { orders['i'] = inbound.Remark } if len(extra) > 0 { orders['o'] = extra } var remark []string for i := 0; i < len(orderChars); i++ { char := orderChars[i] order, exists := orders[char] if exists && order != "" { remark = append(remark, order) } } if s.showInfo { statsExist := false var stats xray.ClientTraffic for _, clientStat := range inbound.ClientStats { if clientStat.Email == email { stats = clientStat statsExist = true break } } // Get remained days if statsExist { if !stats.Enable { return fmt.Sprintf("⛔️N/A%s%s", separationChar, strings.Join(remark, separationChar)) } if vol := stats.Total - (stats.Up + stats.Down); vol > 0 { remark = append(remark, fmt.Sprintf("%s%s", common.FormatTraffic(vol), "📊")) } now := time.Now().Unix() switch exp := stats.ExpiryTime / 1000; { case exp > 0: remainingSeconds := exp - now days := remainingSeconds / 86400 hours := (remainingSeconds % 86400) / 3600 minutes := (remainingSeconds % 3600) / 60 if days > 0 { if hours > 0 { remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours)) } else { remark = append(remark, fmt.Sprintf("%dD⏳", days)) } } else if hours > 0 { remark = append(remark, fmt.Sprintf("%dH⏳", hours)) } else { remark = append(remark, fmt.Sprintf("%dM⏳", minutes)) } case exp < 0: passedSeconds := now - exp days := passedSeconds / 86400 hours := (passedSeconds % 86400) / 3600 minutes := (passedSeconds % 3600) / 60 if days > 0 { if hours > 0 { remark = append(remark, fmt.Sprintf("%dD,%dH⏳", days, hours)) } else { remark = append(remark, fmt.Sprintf("%dD⏳", days)) } } else if hours > 0 { remark = append(remark, fmt.Sprintf("%dH⏳", hours)) } else { remark = append(remark, fmt.Sprintf("%dM⏳", minutes)) } } } } return strings.Join(remark, separationChar) } func searchKey(data interface{}, key string) (interface{}, bool) { switch val := data.(type) { case map[string]interface{}: for k, v := range val { if k == key { return v, true } if result, ok := searchKey(v, key); ok { return result, true } } case []interface{}: for _, v := range val { if result, ok := searchKey(v, key); ok { return result, true } } } return nil, false } func searchHost(headers interface{}) string { data, _ := headers.(map[string]interface{}) for k, v := range data { if strings.EqualFold(k, "host") { switch v.(type) { case []interface{}: hosts, _ := v.([]interface{}) if len(hosts) > 0 { return hosts[0].(string) } else { return "" } case interface{}: return v.(string) } } } return "" }