From cee117e1e82b69b66cc682144c3d427eee56c78a Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Mon, 27 Mar 2023 15:03:55 +0200 Subject: [PATCH 1/5] [sub] prepare database --- xray/client_traffic.go | 1 + 1 file changed, 1 insertion(+) diff --git a/xray/client_traffic.go b/xray/client_traffic.go index d1302da4..764bd6f0 100644 --- a/xray/client_traffic.go +++ b/xray/client_traffic.go @@ -9,4 +9,5 @@ type ClientTraffic struct { Down int64 `json:"down" form:"down"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` Total int64 `json:"total" form:"total"` + SubID string `json:"subId" from:"subId"` } From 9ae49ed562a9dc971e44ede94eddcbac2b85e6a4 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Mon, 27 Mar 2023 15:06:22 +0200 Subject: [PATCH 2/5] [sub] add backend --- web/controller/sub.go | 40 ++++ web/service/sub.go | 512 ++++++++++++++++++++++++++++++++++++++++++ web/web.go | 2 + 3 files changed, 554 insertions(+) create mode 100644 web/controller/sub.go create mode 100644 web/service/sub.go diff --git a/web/controller/sub.go b/web/controller/sub.go new file mode 100644 index 00000000..61a11836 --- /dev/null +++ b/web/controller/sub.go @@ -0,0 +1,40 @@ +package controller + +import ( + "x-ui/web/service" + + "github.com/gin-gonic/gin" +) + +type SUBController struct { + BaseController + + subService service.SubService +} + +func NewSUBController(g *gin.RouterGroup) *SUBController { + a := &SUBController{} + a.initRouter(g) + return a +} + +func (a *SUBController) initRouter(g *gin.RouterGroup) { + g = g.Group("/sub") + + g.GET("/:subid", a.subs) +} + +func (a *SUBController) subs(c *gin.Context) { + subId := c.Param("subid") + host := c.Request.Host + subs, err := a.subService.GetSubs(subId, host) + result := "" + for _, sub := range subs { + result += sub + "\n" + } + if err != nil { + c.String(400, "Error!") + } else { + c.String(200, result) + } +} diff --git a/web/service/sub.go b/web/service/sub.go new file mode 100644 index 00000000..da91f346 --- /dev/null +++ b/web/service/sub.go @@ -0,0 +1,512 @@ +package service + +import ( + "encoding/base64" + "fmt" + "net/url" + "strings" + "x-ui/database" + "x-ui/database/model" + "x-ui/xray" + + "github.com/goccy/go-json" + "gorm.io/gorm" +) + +type SubService struct { + address string +} + +func (s *SubService) GetSubs(subId string, host string) ([]string, error) { + s.address = host + var result []string + traffics, err := s.getTrafficsBySubId(subId) + if err != nil { + return nil, err + } + for _, traffic := range traffics { + inbound, err := s.getInbound(traffic.InboundId) + if err != nil { + return nil, err + } + result = append(result, s.getLink(inbound, traffic.Email)) + } + return result, nil +} + +func (s *SubService) getTrafficsBySubId(subId string) ([]*xray.ClientTraffic, error) { + db := database.GetDB() + var traffics []*xray.ClientTraffic + err := db.Model(xray.ClientTraffic{}).Where("sub_id = ?", subId).Find(&traffics).Error + if err != nil && err != gorm.ErrRecordNotFound { + return nil, err + } + return traffics, nil +} + +func (s *SubService) getInbound(id int) (*model.Inbound, error) { + db := database.GetDB() + inbound := &model.Inbound{} + err := db.Model(model.Inbound{}).First(inbound, id).Error + if err != nil { + return nil, err + } + return inbound, 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) + } + return "" +} + +func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { + address := s.address + if inbound.Protocol != model.VMess { + return "" + } + var stream map[string]interface{} + json.Unmarshal([]byte(inbound.StreamSettings), &stream) + network, _ := stream["network"].(string) + typeStr := "none" + host := "" + path := "" + sni := "" + fp := "" + var alpn []string + allowInsecure := false + switch network { + 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{}) + path = requestPath[0].(string) + headers, _ := request["headers"].(map[string]interface{}) + host = searchHost(headers) + } + case "kcp": + kcp, _ := stream["kcpSettings"].(map[string]interface{}) + header, _ := kcp["header"].(map[string]interface{}) + typeStr, _ = header["type"].(string) + path, _ = kcp["seed"].(string) + case "ws": + ws, _ := stream["wsSettings"].(map[string]interface{}) + path = ws["path"].(string) + headers, _ := ws["headers"].(map[string]interface{}) + host = searchHost(headers) + case "http": + network = "h2" + http, _ := stream["httpSettings"].(map[string]interface{}) + path, _ = http["path"].(string) + host = searchHost(http) + case "quic": + quic, _ := stream["quicSettings"].(map[string]interface{}) + header := quic["header"].(map[string]interface{}) + typeStr, _ = header["type"].(string) + host, _ = quic["security"].(string) + path, _ = quic["key"].(string) + case "grpc": + grpc, _ := stream["grpcSettings"].(map[string]interface{}) + path = grpc["serviceName"].(string) + } + + security, _ := stream["security"].(string) + if security == "tls" { + tlsSetting, _ := stream["tlsSettings"].(map[string]interface{}) + alpns, _ := tlsSetting["alpn"].([]interface{}) + for _, a := range alpns { + alpn = append(alpn, a.(string)) + } + tlsSettings, _ := searchKey(tlsSetting, "settings") + if tlsSetting != nil { + if sniValue, ok := searchKey(tlsSettings, "serverName"); ok { + sni, _ = sniValue.(string) + } + if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { + fp, _ = fpValue.(string) + } + if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok { + allowInsecure, _ = insecure.(bool) + } + } + serverName, _ := tlsSetting["serverName"].(string) + if serverName != "" { + address = serverName + } + } + + settings := map[string][]model.Client{} + json.Unmarshal([]byte(inbound.Settings), &settings) + clients := settings["clients"] + clientIndex := -1 + for i, client := range clients { + if client.Email == email { + clientIndex = i + break + } + } + + obj := map[string]interface{}{ + "v": "2", + "ps": email, + "add": address, + "port": inbound.Port, + "id": clients[clientIndex].ID, + "aid": clients[clientIndex].AlterIds, + "net": network, + "type": typeStr, + "host": host, + "path": path, + "tls": security, + "sni": sni, + "fp": fp, + "alpn": strings.Join(alpn, ","), + "allowInsecure": allowInsecure, + } + 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) + settings := map[string][]model.Client{} + json.Unmarshal([]byte(inbound.Settings), &settings) + clients := settings["clients"] + 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) + 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) + } + + 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, ",") + } + tlsSettings, _ := searchKey(tlsSetting, "settings") + if tlsSetting != nil { + if sniValue, ok := searchKey(tlsSettings, "serverName"); ok { + params["sni"], _ = sniValue.(string) + } + 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 + } + + serverName, _ := tlsSetting["serverName"].(string) + if serverName != "" { + address = serverName + } + } + + 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, ",") + } + + xtlsSettings, _ := searchKey(xtlsSetting, "settings") + if xtlsSetting != nil { + if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok { + params["sni"], _ = sniValue.(string) + } + 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 + } + + serverName, _ := xtlsSetting["serverName"].(string) + if serverName != "" { + address = serverName + } + } + + 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 = 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) + settings := map[string][]model.Client{} + json.Unmarshal([]byte(inbound.Settings), &settings) + clients := settings["clients"] + 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) + 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) + } + + 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, ",") + } + tlsSettings, _ := searchKey(tlsSetting, "settings") + if tlsSetting != nil { + if sniValue, ok := searchKey(tlsSettings, "serverName"); ok { + params["sni"], _ = sniValue.(string) + } + if fpValue, ok := searchKey(tlsSettings, "fingerprint"); ok { + params["fp"], _ = fpValue.(string) + } + if insecure, ok := searchKey(tlsSettings, "allowInsecure"); ok { + if insecure.(bool) { + params["allowInsecure"] = "1" + } + } + } + + serverName, _ := tlsSetting["serverName"].(string) + if serverName != "" { + address = serverName + } + } + + 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, ",") + } + + xtlsSettings, _ := searchKey(xtlsSetting, "settings") + if xtlsSetting != nil { + if sniValue, ok := searchKey(xtlsSettings, "serverName"); ok { + params["sni"], _ = sniValue.(string) + } + 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 + } + + serverName, _ := xtlsSetting["serverName"].(string) + if serverName != "" { + address = serverName + } + } + + 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 = email + return url.String() +} + +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{}) + return hosts[0].(string) + case interface{}: + return v.(string) + } + } + } + + return "" +} diff --git a/web/web.go b/web/web.go index 4880ff19..a86a0419 100644 --- a/web/web.go +++ b/web/web.go @@ -85,6 +85,7 @@ type Server struct { server *controller.ServerController xui *controller.XUIController api *controller.APIController + sub *controller.SUBController xrayService service.XrayService settingService service.SettingService @@ -208,6 +209,7 @@ func (s *Server) initRouter() (*gin.Engine, error) { s.server = controller.NewServerController(g) s.xui = controller.NewXUIController(g) s.api = controller.NewAPIController(g) + s.sub = controller.NewSUBController(g) return engine, nil } From 27de7b030b823bcbff1e052a29e293c35d623a6e Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Mon, 27 Mar 2023 15:19:53 +0200 Subject: [PATCH 3/5] fix initiation of expirytime --- web/assets/js/model/xray.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index 79186e32..ecc9c9d8 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -1431,7 +1431,7 @@ Inbound.VmessSettings = class extends Inbound.Settings { } }; Inbound.VmessSettings.Vmess = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(), totalGB=0, expiryTime='') { + constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(), totalGB=0, expiryTime=0) { super(); this.id = id; this.alterId = alterId; @@ -1514,7 +1514,7 @@ Inbound.VLESSSettings = class extends Inbound.Settings { }; Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime='') { + constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime=0) { super(); this.id = id; this.flow = flow; @@ -1627,7 +1627,7 @@ Inbound.TrojanSettings = class extends Inbound.Settings { } }; Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { - constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime='') { + constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime=0) { super(); this.password = password; this.flow = flow; From 28050aba233f15927856d7f840b837df006f2d95 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Tue, 28 Mar 2023 23:09:51 +0200 Subject: [PATCH 4/5] revert client traffic change --- xray/client_traffic.go | 1 - 1 file changed, 1 deletion(-) diff --git a/xray/client_traffic.go b/xray/client_traffic.go index 764bd6f0..d1302da4 100644 --- a/xray/client_traffic.go +++ b/xray/client_traffic.go @@ -9,5 +9,4 @@ type ClientTraffic struct { Down int64 `json:"down" form:"down"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` Total int64 `json:"total" form:"total"` - SubID string `json:"subId" from:"subId"` } From efd1b0659335931627a1c20dbfe969d3553e16c2 Mon Sep 17 00:00:00 2001 From: Alireza Ahmadi Date: Tue, 28 Mar 2023 23:42:14 +0200 Subject: [PATCH 5/5] [sub] frontend and adaptation --- database/model/model.go | 1 + web/assets/js/model/xray.js | 16 +++++++---- web/controller/sub.go | 3 +- web/html/xui/form/client.html | 3 ++ web/service/sub.go | 53 +++++++++++++++-------------------- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/database/model/model.go b/database/model/model.go index d0b8bcfc..e87e8b6b 100644 --- a/database/model/model.go +++ b/database/model/model.go @@ -74,4 +74,5 @@ type Client struct { Email string `json:"email"` TotalGB int64 `json:"totalGB" form:"totalGB"` ExpiryTime int64 `json:"expiryTime" form:"expiryTime"` + SubID string `json:"subId" from:"subId"` } diff --git a/web/assets/js/model/xray.js b/web/assets/js/model/xray.js index ecc9c9d8..6e552d15 100644 --- a/web/assets/js/model/xray.js +++ b/web/assets/js/model/xray.js @@ -1431,13 +1431,14 @@ Inbound.VmessSettings = class extends Inbound.Settings { } }; Inbound.VmessSettings.Vmess = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), alterId=0, email=RandomUtil.randomText(), totalGB=0, expiryTime=0) { + , subId='') { super(); this.id = id; this.alterId = alterId; this.email = email; this.totalGB = totalGB; this.expiryTime = expiryTime; + this.subId = subId; } static fromJson(json={}) { @@ -1447,7 +1448,7 @@ Inbound.VmessSettings.Vmess = class extends XrayCommonClass { json.email, json.totalGB, json.expiryTime, - + json.subId, ); } get _expiryTime() { @@ -1514,14 +1515,14 @@ Inbound.VLESSSettings = class extends Inbound.Settings { }; Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { - constructor(id=RandomUtil.randomUUID(), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime=0) { + , subId='') { super(); this.id = id; this.flow = flow; this.email = email; this.totalGB = totalGB; this.expiryTime = expiryTime; - + this.subId = subId; } static fromJson(json={}) { @@ -1531,6 +1532,7 @@ Inbound.VLESSSettings.VLESS = class extends XrayCommonClass { json.email, json.totalGB, json.expiryTime, + json.subId, ); } @@ -1627,13 +1629,14 @@ Inbound.TrojanSettings = class extends Inbound.Settings { } }; Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { - constructor(password=RandomUtil.randomSeq(10), flow='', email=RandomUtil.randomText(), totalGB=0, expiryTime=0) { + , subId='') { super(); this.password = password; this.flow = flow; this.email = email; this.totalGB = totalGB; this.expiryTime = expiryTime; + this.subId = subId; } toJson() { @@ -1643,6 +1646,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { email: this.email, totalGB: this.totalGB, expiryTime: this.expiryTime, + subId: this.subId, }; } @@ -1653,7 +1657,7 @@ Inbound.TrojanSettings.Trojan = class extends XrayCommonClass { json.email, json.totalGB, json.expiryTime, - + json.subId, ); } diff --git a/web/controller/sub.go b/web/controller/sub.go index 61a11836..38340f9b 100644 --- a/web/controller/sub.go +++ b/web/controller/sub.go @@ -1,6 +1,7 @@ package controller import ( + "encoding/base64" "x-ui/web/service" "github.com/gin-gonic/gin" @@ -35,6 +36,6 @@ func (a *SUBController) subs(c *gin.Context) { if err != nil { c.String(400, "Error!") } else { - c.String(200, result) + c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) } } diff --git a/web/html/xui/form/client.html b/web/html/xui/form/client.html index 3d92c54e..fe87734a 100644 --- a/web/html/xui/form/client.html +++ b/web/html/xui/form/client.html @@ -24,6 +24,9 @@ + + + {{ i18n "none" }} diff --git a/web/service/sub.go b/web/service/sub.go index da91f346..875a5b53 100644 --- a/web/service/sub.go +++ b/web/service/sub.go @@ -7,51 +7,50 @@ import ( "strings" "x-ui/database" "x-ui/database/model" - "x-ui/xray" + "x-ui/logger" "github.com/goccy/go-json" "gorm.io/gorm" ) type SubService struct { - address string + address string + inboundService InboundService } func (s *SubService) GetSubs(subId string, host string) ([]string, error) { s.address = host var result []string - traffics, err := s.getTrafficsBySubId(subId) + inbounds, err := s.getInboundsBySubId(subId) if err != nil { return nil, err } - for _, traffic := range traffics { - inbound, err := s.getInbound(traffic.InboundId) + for _, inbound := range inbounds { + clients, err := s.inboundService.getClients(inbound) if err != nil { - return nil, err + logger.Error("SubService - GetSub: Unable to get clients from inbound") + } + if clients == nil { + continue + } + for _, client := range clients { + if client.SubID == subId { + link := s.getLink(inbound, client.Email) + result = append(result, link) + } } - result = append(result, s.getLink(inbound, traffic.Email)) } return result, nil } -func (s *SubService) getTrafficsBySubId(subId string) ([]*xray.ClientTraffic, error) { +func (s *SubService) getInboundsBySubId(subId string) ([]*model.Inbound, error) { db := database.GetDB() - var traffics []*xray.ClientTraffic - err := db.Model(xray.ClientTraffic{}).Where("sub_id = ?", subId).Find(&traffics).Error + var inbounds []*model.Inbound + err := db.Model(model.Inbound{}).Where("settings like ?", fmt.Sprintf(`%%"subId": "%s"%%`, subId)).Find(&inbounds).Error if err != nil && err != gorm.ErrRecordNotFound { return nil, err } - return traffics, nil -} - -func (s *SubService) getInbound(id int) (*model.Inbound, error) { - db := database.GetDB() - inbound := &model.Inbound{} - err := db.Model(model.Inbound{}).First(inbound, id).Error - if err != nil { - return nil, err - } - return inbound, nil + return inbounds, nil } func (s *SubService) getLink(inbound *model.Inbound, email string) string { @@ -144,9 +143,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { } } - settings := map[string][]model.Client{} - json.Unmarshal([]byte(inbound.Settings), &settings) - clients := settings["clients"] + clients, _ := s.inboundService.getClients(inbound) clientIndex := -1 for i, client := range clients { if client.Email == email { @@ -183,9 +180,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { } var stream map[string]interface{} json.Unmarshal([]byte(inbound.StreamSettings), &stream) - settings := map[string][]model.Client{} - json.Unmarshal([]byte(inbound.Settings), &settings) - clients := settings["clients"] + clients, _ := s.inboundService.getClients(inbound) clientIndex := -1 for i, client := range clients { if client.Email == email { @@ -333,9 +328,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string } var stream map[string]interface{} json.Unmarshal([]byte(inbound.StreamSettings), &stream) - settings := map[string][]model.Client{} - json.Unmarshal([]byte(inbound.Settings), &settings) - clients := settings["clients"] + clients, _ := s.inboundService.getClients(inbound) clientIndex := -1 for i, client := range clients { if client.Email == email {