subController.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. package sub
  2. import (
  3. "encoding/base64"
  4. "fmt"
  5. "strings"
  6. "github.com/mhsanaei/3x-ui/v2/config"
  7. "github.com/gin-gonic/gin"
  8. )
  9. // SUBController handles HTTP requests for subscription links and JSON configurations.
  10. type SUBController struct {
  11. subTitle string
  12. subPath string
  13. subJsonPath string
  14. jsonEnabled bool
  15. subEncrypt bool
  16. updateInterval string
  17. subService *SubService
  18. subJsonService *SubJsonService
  19. }
  20. // NewSUBController creates a new subscription controller with the given configuration.
  21. func NewSUBController(
  22. g *gin.RouterGroup,
  23. subPath string,
  24. jsonPath string,
  25. jsonEnabled bool,
  26. encrypt bool,
  27. showInfo bool,
  28. rModel string,
  29. update string,
  30. jsonFragment string,
  31. jsonNoise string,
  32. jsonMux string,
  33. jsonRules string,
  34. subTitle string,
  35. ) *SUBController {
  36. sub := NewSubService(showInfo, rModel)
  37. a := &SUBController{
  38. subTitle: subTitle,
  39. subPath: subPath,
  40. subJsonPath: jsonPath,
  41. jsonEnabled: jsonEnabled,
  42. subEncrypt: encrypt,
  43. updateInterval: update,
  44. subService: sub,
  45. subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
  46. }
  47. a.initRouter(g)
  48. return a
  49. }
  50. // initRouter registers HTTP routes for subscription links and JSON endpoints
  51. // on the provided router group.
  52. func (a *SUBController) initRouter(g *gin.RouterGroup) {
  53. gLink := g.Group(a.subPath)
  54. gLink.GET(":subid", a.subs)
  55. if a.jsonEnabled {
  56. gJson := g.Group(a.subJsonPath)
  57. gJson.GET(":subid", a.subJsons)
  58. }
  59. }
  60. // subs handles HTTP requests for subscription links, returning either HTML page or base64-encoded subscription data.
  61. func (a *SUBController) subs(c *gin.Context) {
  62. subId := c.Param("subid")
  63. scheme, host, hostWithPort, hostHeader := a.subService.ResolveRequest(c)
  64. subs, lastOnline, traffic, err := a.subService.GetSubs(subId, host)
  65. if err != nil || len(subs) == 0 {
  66. c.String(400, "Error!")
  67. } else {
  68. result := ""
  69. for _, sub := range subs {
  70. result += sub + "\n"
  71. }
  72. // If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here
  73. accept := c.GetHeader("Accept")
  74. if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") {
  75. // Build page data in service
  76. subURL, subJsonURL := a.subService.BuildURLs(scheme, hostWithPort, a.subPath, a.subJsonPath, subId)
  77. if !a.jsonEnabled {
  78. subJsonURL = ""
  79. }
  80. page := a.subService.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, subURL, subJsonURL)
  81. c.HTML(200, "subpage.html", gin.H{
  82. "title": "subscription.title",
  83. "cur_ver": config.GetVersion(),
  84. "host": page.Host,
  85. "base_path": page.BasePath,
  86. "sId": page.SId,
  87. "download": page.Download,
  88. "upload": page.Upload,
  89. "total": page.Total,
  90. "used": page.Used,
  91. "remained": page.Remained,
  92. "expire": page.Expire,
  93. "lastOnline": page.LastOnline,
  94. "datepicker": page.Datepicker,
  95. "downloadByte": page.DownloadByte,
  96. "uploadByte": page.UploadByte,
  97. "totalByte": page.TotalByte,
  98. "subUrl": page.SubUrl,
  99. "subJsonUrl": page.SubJsonUrl,
  100. "result": page.Result,
  101. })
  102. return
  103. }
  104. // Add headers
  105. header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
  106. a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
  107. if a.subEncrypt {
  108. c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
  109. } else {
  110. c.String(200, result)
  111. }
  112. }
  113. }
  114. // subJsons handles HTTP requests for JSON subscription configurations.
  115. func (a *SUBController) subJsons(c *gin.Context) {
  116. subId := c.Param("subid")
  117. _, host, _, _ := a.subService.ResolveRequest(c)
  118. jsonSub, header, err := a.subJsonService.GetJson(subId, host)
  119. if err != nil || len(jsonSub) == 0 {
  120. c.String(400, "Error!")
  121. } else {
  122. // Add headers
  123. a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle)
  124. c.String(200, jsonSub)
  125. }
  126. }
  127. // ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title.
  128. func (a *SUBController) ApplyCommonHeaders(c *gin.Context, header, updateInterval, profileTitle string) {
  129. c.Writer.Header().Set("Subscription-Userinfo", header)
  130. c.Writer.Header().Set("Profile-Update-Interval", updateInterval)
  131. c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle)))
  132. }