From bf134ab52bb5bae2bdf8033e9217d881359efefc Mon Sep 17 00:00:00 2001 From: Hassan Dashtizadeh <101067119+Wolf6470@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:14:51 +0330 Subject: [PATCH 1/6] Add files via upload --- sub/sub.html | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 sub/sub.html diff --git a/sub/sub.html b/sub/sub.html new file mode 100644 index 00000000..aeae5b21 --- /dev/null +++ b/sub/sub.html @@ -0,0 +1,85 @@ + + + + + + + + {{ .sId }} - Sub Info + + + + + + + + +
+

اطلاعات سابسکریپشن

+ +
شناسه اشتراک : {{ .sId }}
+
دانلود : {{ .download }}
+
آپلود : {{ .upload }}
+
تاریخ پایان :
+
حجم کلی : {{ .total }}
+
+ {{ range .result }} +
+ + {{ . }} +
+ {{ end }} +
+ +
+ + + +
+
+ + + + \ No newline at end of file From 590567bfa03a8c26576cddf7d5479ec2c4759c50 Mon Sep 17 00:00:00 2001 From: Hassan Dashtizadeh <101067119+Wolf6470@users.noreply.github.com> Date: Wed, 5 Mar 2025 23:15:23 +0400 Subject: [PATCH 2/6] Update subController.go --- sub/subController.go | 63 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/sub/subController.go b/sub/subController.go index 9afbc8da..110357dc 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -2,7 +2,10 @@ package sub import ( "encoding/base64" + "fmt" + "math" "net" + "strconv" "strings" "github.com/gin-gonic/gin" @@ -78,16 +81,42 @@ func (a *SUBController) subs(c *gin.Context) { for _, sub := range subs { result += sub + "\n" } + resultSlice := strings.Split(strings.TrimSpace(result), "\n") // Add headers c.Writer.Header().Set("Subscription-Userinfo", header) c.Writer.Header().Set("Profile-Update-Interval", a.updateInterval) c.Writer.Header().Set("Profile-Title", subId) - if a.subEncrypt { - c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) + acceptHeader := c.GetHeader("Accept") + headerMap := parseHeaderString(header) + expireValue := headerMap["expire"] + upValue := formatBytes(headerMap["upload"], 2) + downValue := formatBytes(headerMap["download"], 2) + totalValue := formatBytes(headerMap["total"], 2) + + currentURL := "https://" + c.Request.Host + c.Request.RequestURI + + if strings.Contains(acceptHeader, "text/html") { + if a.subEncrypt { + c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) + } else { + c.HTML(200, "sub.html", gin.H{ + "result": resultSlice, + "total": totalValue, + "expire": expireValue, + "upload": upValue, + "download": downValue, + "sId": subId, + "subUrl": currentURL, + }) + } } else { - c.String(200, result) + if a.subEncrypt { + c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) + } else { + c.String(200, result) + } } } } @@ -132,3 +161,31 @@ func getHostFromXFH(s string) (string, error) { } return s, nil } + +func parseHeaderString(header string) map[string]string { + headerMap := make(map[string]string) + pairs := strings.Split(header, ";") + for _, pair := range pairs { + kv := strings.Split(strings.TrimSpace(pair), "=") + if len(kv) == 2 { + headerMap[kv[0]] = kv[1] + } + } + return headerMap +} + +func formatBytes(sizeStr string, precision int) string { + // Convert the string input to a float64 + size, _ := strconv.ParseFloat(sizeStr, 64) + + if size == 0 { + return "0 B" + } + + // Calculate base and suffix + base := math.Log(size) / math.Log(1024) + suffixes := []string{"B", "K", "M", "G", "T"} + + value := math.Pow(1024, base-math.Floor(base)) + return fmt.Sprintf("%.*f %s", precision, value, suffixes[int(math.Floor(base))]) +} From c7ec62def139be19366cd4855c7e92162351c09d Mon Sep 17 00:00:00 2001 From: Hassan Dashtizadeh <101067119+Wolf6470@users.noreply.github.com> Date: Wed, 5 Mar 2025 23:16:48 +0400 Subject: [PATCH 3/6] Update sub.go --- sub/sub.go | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/sub/sub.go b/sub/sub.go index db582e8d..4ff7b8da 100644 --- a/sub/sub.go +++ b/sub/sub.go @@ -3,11 +3,14 @@ package sub import ( "context" "crypto/tls" + "embed" + "html/template" "io" + "io/fs" "net" "net/http" + "os" "strconv" - "x-ui/config" "x-ui/logger" "x-ui/util/common" @@ -15,9 +18,13 @@ import ( "x-ui/web/network" "x-ui/web/service" + "github.com/gin-contrib/gzip" "github.com/gin-gonic/gin" ) +//go:embed html/* +var htmlFS embed.FS + type Server struct { httpServer *http.Server listener net.Listener @@ -37,6 +44,48 @@ func NewServer() *Server { } } +func (s *Server) getHtmlFiles() ([]string, error) { + files := make([]string, 0) + dir, _ := os.Getwd() + err := fs.WalkDir(os.DirFS(dir), "sub/html", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + files = append(files, path) + return nil + }) + if err != nil { + return nil, err + } + return files, nil +} + +func (s *Server) getHtmlTemplate(funcMap template.FuncMap) (*template.Template, error) { + t := template.New("").Funcs(funcMap) + err := fs.WalkDir(htmlFS, "html", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + newT, err := t.ParseFS(htmlFS, path+"/*.html") + if err != nil { + // ignore + return nil + } + t = newT + } + return nil + }) + if err != nil { + return nil, err + } + return t, nil +} + func (s *Server) initRouter() (*gin.Engine, error) { if config.IsDebug() { gin.SetMode(gin.DebugMode) @@ -48,6 +97,33 @@ func (s *Server) initRouter() (*gin.Engine, error) { engine := gin.Default() + basePath, err := s.settingService.GetBasePath() + if err != nil { + return nil, err + } + engine.Use(gzip.Gzip(gzip.DefaultCompression, gzip.WithExcludedPaths([]string{basePath + "panel/API/"}))) + + engine.Use(func(c *gin.Context) { + c.Set("base_path", basePath) + }) + + // set static files and template + if config.IsDebug() { + // for development + files, err := s.getHtmlFiles() + if err != nil { + return nil, err + } + engine.LoadHTMLFiles(files...) + } else { + // for production + template, err := s.getHtmlTemplate(engine.FuncMap) + if err != nil { + return nil, err + } + engine.SetHTMLTemplate(template) + } + subDomain, err := s.settingService.GetSubDomain() if err != nil { return nil, err From 5cc04d03439d5166bc8d945e9551ffae4e1d8dc5 Mon Sep 17 00:00:00 2001 From: Hassan Dashtizadeh <101067119+Wolf6470@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:37:57 +0300 Subject: [PATCH 4/6] Rename sub/sub.html to sub/html/sub.html --- sub/{ => html}/sub.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename sub/{ => html}/sub.html (99%) diff --git a/sub/sub.html b/sub/html/sub.html similarity index 99% rename from sub/sub.html rename to sub/html/sub.html index aeae5b21..d0afdb20 100644 --- a/sub/sub.html +++ b/sub/html/sub.html @@ -82,4 +82,4 @@ - \ No newline at end of file + From d5199b614cbc49fdd4c9e7c41ef5ab78a61e031c Mon Sep 17 00:00:00 2001 From: Hassan Dashtizadeh <101067119+Wolf6470@users.noreply.github.com> Date: Thu, 6 Mar 2025 22:40:59 +0300 Subject: [PATCH 5/6] Update subController.go --- sub/subController.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/sub/subController.go b/sub/subController.go index 110357dc..22c43f18 100644 --- a/sub/subController.go +++ b/sub/subController.go @@ -102,13 +102,16 @@ func (a *SUBController) subs(c *gin.Context) { c.String(200, base64.StdEncoding.EncodeToString([]byte(result))) } else { c.HTML(200, "sub.html", gin.H{ - "result": resultSlice, - "total": totalValue, - "expire": expireValue, - "upload": upValue, - "download": downValue, - "sId": subId, - "subUrl": currentURL, + "result": resultSlice, + "total": totalValue, + "expire": expireValue, + "upload": upValue, + "download": downValue, + "totalByte": headerMap["total"], + "uploadByte": headerMap["upload"], + "downloadByte": headerMap["download"], + "sId": subId, + "subUrl": currentURL, }) } } else { From 915fd27d6180016ef358244c67aa6aab50155e30 Mon Sep 17 00:00:00 2001 From: Hassan Dashtizadeh <101067119+Wolf6470@users.noreply.github.com> Date: Thu, 6 Mar 2025 22:45:25 +0300 Subject: [PATCH 6/6] Update sub.html --- sub/html/sub.html | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/sub/html/sub.html b/sub/html/sub.html index d0afdb20..a1b61d82 100644 --- a/sub/html/sub.html +++ b/sub/html/sub.html @@ -1,4 +1,4 @@ - + @@ -8,10 +8,10 @@ {{ .sId }} - Sub Info + rel="stylesheet" type="text/css" /> + crossorigin="anonymous" /> @@ -20,17 +20,17 @@

اطلاعات سابسکریپشن

- +
شناسه اشتراک : {{ .sId }}
+
وضعیت اشتراک :
دانلود : {{ .download }}
آپلود : {{ .upload }}
تاریخ پایان :
-
حجم کلی : {{ .total }}
+
حجم کلی :
{{ range .result }}
- + {{ . }}
{{ end }} @@ -50,7 +50,27 @@ // Format the Jalali date const formattedJalaliDate = `${jalaliDate.jy}/${jalaliDate.jm}/${jalaliDate.jd}`; // Display the Jalali date in the HTML - humanDateElement.textContent = formattedJalaliDate; + if ('{{ .expire }}' === '0') { + humanDateElement.textContent = 'بدون انقضا'; + } else { + humanDateElement.textContent = formattedJalaliDate; + } + // Get the HTML element to display the status. + const statusElement = document.getElementById('status'); + if (timestamp >= Date.now() && '{{ .downloadByte}}' + '{{ .uploadByte }}' <= '{{ .totalByte }}') { + statusElement.textContent = 'فعال'; + } else { + if ('{{ .totalByte }}' === '0') { + statusElement.textContent = 'نامحدود'; + } else { + statusElement.textContent = 'غیرفعال'; + } + } + if ('{{ .totalByte }}' === '0') { + document.getElementById('total').textContent = '∞'; + } else { + document.getElementById('total').textContent = '{{ .total }}'; + } function copyToClipboard(text) { navigator.clipboard.writeText(text);