diff --git a/sub/subController.go b/sub/subController.go index 070a0906..5f7c69cf 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -27,9 +27,10 @@ func (a *SUBController) initRouter(g *gin.RouterGroup) { func (a *SUBController) subs(c *gin.Context) { subEncrypt, _ := a.settingService.GetSubEncrypt() + subShowInfo, _ := a.settingService.GetSubShowInfo() subId := c.Param("subid") host := strings.Split(c.Request.Host, ":")[0] - subs, headers, err := a.subService.GetSubs(subId, host) + subs, headers, err := a.subService.GetSubs(subId, host, subShowInfo) if err != nil || len(subs) == 0 { c.String(400, "Error!") } else { diff --git a/sub/subService.go b/sub/subService.go index 5de64d33..e97fe48f 100644 --- a/sub/subService.go +++ b/sub/subService.go @@ -5,9 +5,11 @@ import ( "fmt" "net/url" "strings" + "time" "x-ui/database" "x-ui/database/model" "x-ui/logger" + "x-ui/util/common" "x-ui/web/service" "x-ui/xray" @@ -16,12 +18,14 @@ import ( type SubService struct { address string + showInfo bool inboundService service.InboundService settingServics service.SettingService } -func (s *SubService) GetSubs(subId string, host string) ([]string, []string, error) { +func (s *SubService) GetSubs(subId string, host string, showInfo bool) ([]string, []string, error) { s.address = host + s.showInfo = showInfo var result []string var headers []string var traffic xray.ClientTraffic @@ -139,10 +143,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { if inbound.Protocol != model.VMess { return "" } - remark := fmt.Sprintf("%s-%s", inbound.Remark, email) obj := map[string]interface{}{ "v": "2", - "ps": remark, "add": s.address, "port": inbound.Port, "type": "none", @@ -241,7 +243,7 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { links := "" for index, d := range domains { domain := d.(map[string]interface{}) - obj["ps"] = remark + "-" + domain["remark"].(string) + obj["ps"] = s.genRemark(inbound, email, domain["remark"].(string)) obj["add"] = domain["domain"].(string) if index > 0 { links += "\n" @@ -252,6 +254,8 @@ func (s *SubService) genVmessLink(inbound *model.Inbound, email string) string { return links } + obj["ps"] = s.genRemark(inbound, email, "") + jsonStr, _ := json.MarshalIndent(obj, "", " ") return "vmess://" + base64.StdEncoding.EncodeToString(jsonStr) } @@ -407,13 +411,12 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { // Set the new query values on the URL url.RawQuery = q.Encode() - remark := fmt.Sprintf("%s-%s", inbound.Remark, email) if len(domains) > 0 { links := "" for index, d := range domains { domain := d.(map[string]interface{}) - url.Fragment = remark + "-" + domain["remark"].(string) + url.Fragment = s.genRemark(inbound, email, domain["remark"].(string)) url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port) if index > 0 { links += "\n" @@ -423,7 +426,7 @@ func (s *SubService) genVlessLink(inbound *model.Inbound, email string) string { return links } - url.Fragment = remark + url.Fragment = s.genRemark(inbound, email, "") return url.String() } @@ -572,13 +575,11 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string // Set the new query values on the URL url.RawQuery = q.Encode() - remark := fmt.Sprintf("%s-%s", inbound.Remark, email) - if len(domains) > 0 { links := "" for index, d := range domains { domain := d.(map[string]interface{}) - url.Fragment = remark + "-" + domain["remark"].(string) + url.Fragment = s.genRemark(inbound, email, domain["remark"].(string)) url.Host = fmt.Sprintf("%s:%d", domain["domain"].(string), port) if index > 0 { links += "\n" @@ -588,7 +589,7 @@ func (s *SubService) genTrojanLink(inbound *model.Inbound, email string) string return links } - url.Fragment = remark + url.Fragment = s.genRemark(inbound, email, "") return url.String() } @@ -671,12 +672,55 @@ func (s *SubService) genShadowsocksLink(inbound *model.Inbound, email string) st // Set the new query values on the URL url.RawQuery = q.Encode() - - remark := fmt.Sprintf("%s-%s", inbound.Remark, clients[clientIndex].Email) - url.Fragment = remark + url.Fragment = s.genRemark(inbound, email, "") return url.String() } +func (s *SubService) genRemark(inbound *model.Inbound, email string, extra string) string { + var remark []string + if len(email) > 0 { + if len(inbound.Remark) > 0 { + remark = append(remark, inbound.Remark) + } + remark = append(remark, email) + if len(extra) > 0 { + remark = append(remark, extra) + } + } else { + return inbound.Remark + } + + 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 "N/A" + } + 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: + remark = append(remark, fmt.Sprintf("%d%s⏳", (exp-now)/86400, "Days")) + case exp < 0: + remark = append(remark, fmt.Sprintf("%d%s⏳", exp/-86400, "Days")) + } + } + } + return strings.Join(remark, "-") +} + func searchKey(data interface{}, key string) (interface{}, bool) { switch val := data.(type) { case map[string]interface{}: diff --git a/web/assets/js/model/models.js b/web/assets/js/model/models.js index f0c51e06..49102a48 100644 --- a/web/assets/js/model/models.js +++ b/web/assets/js/model/models.js @@ -192,6 +192,7 @@ class AllSetting { this.subKeyFile = ""; this.subUpdates = 0; this.subEncrypt = true; + this.subShowInfo = false; this.timeLocation = "Asia/Tehran"; diff --git a/web/controller/setting.go b/web/controller/setting.go index 2bb8e9be..51d10d65 100644 --- a/web/controller/setting.go +++ b/web/controller/setting.go @@ -65,6 +65,7 @@ func (a *SettingController) getDefaultSettings(c *gin.Context) { "subKeyFile": func() (interface{}, error) { return a.settingService.GetSubKeyFile() }, "subCertFile": func() (interface{}, error) { return a.settingService.GetSubCertFile() }, "subEncrypt": func() (interface{}, error) { return a.settingService.GetSubEncrypt() }, + "subShowInfo": func() (interface{}, error) { return a.settingService.GetSubShowInfo() }, } result := make(map[string]interface{}) diff --git a/web/entity/entity.go b/web/entity/entity.go index f2e8fb71..e75048af 100644 --- a/web/entity/entity.go +++ b/web/entity/entity.go @@ -55,6 +55,7 @@ type AllSetting struct { SubKeyFile string `json:"subKeyFile" form:"subKeyFile"` SubUpdates int `json:"subUpdates" form:"subUpdates"` SubEncrypt bool `json:"subEncrypt" form:"subEncrypt"` + SubShowInfo bool `json:"subShowInfo" form:"subShowInfo"` } func (s *AllSetting) CheckValid() error { diff --git a/web/html/xui/settings.html b/web/html/xui/settings.html index 3c0b762d..b1312520 100644 --- a/web/html/xui/settings.html +++ b/web/html/xui/settings.html @@ -338,6 +338,7 @@ + diff --git a/web/service/setting.go b/web/service/setting.go index a686c669..77249d73 100644 --- a/web/service/setting.go +++ b/web/service/setting.go @@ -51,6 +51,7 @@ var defaultValueMap = map[string]string{ "subKeyFile": "", "subUpdates": "12", "subEncrypt": "true", + "subShowInfo": "false", } type SettingService struct { @@ -377,6 +378,10 @@ func (s *SettingService) GetSubEncrypt() (bool, error) { return s.getBool("subEncrypt") } +func (s *SettingService) GetSubShowInfo() (bool, error) { + return s.getBool("subShowInfo") +} + func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { if err := allSetting.CheckValid(); err != nil { return err diff --git a/web/translation/translate.en_US.toml b/web/translation/translate.en_US.toml index b940774e..1e14ead6 100644 --- a/web/translation/translate.en_US.toml +++ b/web/translation/translate.en_US.toml @@ -267,7 +267,8 @@ "subUpdatesDesc" = "Interval hours between updates in client application" "subEncrypt" = "Encrypt configs" "subEncryptDesc" = "Encrypt the returned configs in subscription" - +"subShowInfo" = "Show usage info" +"subShowInfoDesc" = "Show remianed traffic and date after config name" [pages.settings.templates] "title" = "Templates" diff --git a/web/translation/translate.fa_IR.toml b/web/translation/translate.fa_IR.toml index 78e48c1c..ed04ae7c 100644 --- a/web/translation/translate.fa_IR.toml +++ b/web/translation/translate.fa_IR.toml @@ -266,6 +266,8 @@ "subUpdatesDesc" = "ساعت های فاصله بین به روز رسانی در برنامه کاربر" "subEncrypt" = "رمزگذاری کانفیگ ها" "subEncryptDesc" = "رمزگذاری کانفیگ های بازگشتی سابسکریپشن" +"subShowInfo" = "نمایش اطلاعات مصرف" +"subShowInfoDesc" = "ترافیک و زمان باقیمانده را در هر کانفیگ نمایش میدهد" [pages.settings.templates] "title" = "الگوها" diff --git a/web/translation/translate.ru_RU.toml b/web/translation/translate.ru_RU.toml index 1aaaf40c..bf4e7a1a 100644 --- a/web/translation/translate.ru_RU.toml +++ b/web/translation/translate.ru_RU.toml @@ -267,6 +267,8 @@ "subUpdatesDesc" = "Часовой интервал между обновлениями в клиентском приложении" "subEncrypt" = "Шифрование конфигураций" "subEncryptDesc" = "Шифрование возвращаемых конфигураций в подписке" +"subShowInfo" = "Показать информацию об использовании" +"subShowInfoDesc" = "Показывать восстановленный трафик и дату после имени конфигурации" [pages.settings.templates] "title" = "Шаблоны" diff --git a/web/translation/translate.zh_Hans.toml b/web/translation/translate.zh_Hans.toml index 64848945..da0a6bd2 100644 --- a/web/translation/translate.zh_Hans.toml +++ b/web/translation/translate.zh_Hans.toml @@ -267,6 +267,8 @@ "subUpdatesDesc" = "客户端应用程序更新之间的间隔时间" "subEncrypt" = "加密配置" "subEncryptDesc" = "在订阅中加密返回的配置" +"subShowInfo" = "显示使用信息" +"subShowInfoDesc" = "在配置名称后显示剩余流量和日期" [pages.settings.templates] "title" = "模板"