Files
trihuy-russian/sub/subController.go
T

245 lines
7.5 KiB
Go
Raw Normal View History

2023-05-22 18:06:34 +03:30
package sub
2023-04-09 23:13:18 +03:30
import (
"encoding/base64"
2025-09-14 23:08:09 +02:00
"fmt"
"strconv"
"strings"
2025-09-18 22:06:01 +02:00
2025-09-19 10:05:43 +02:00
"github.com/mhsanaei/3x-ui/v2/config"
2023-04-09 23:13:18 +03:30
"github.com/gin-gonic/gin"
)
2025-09-20 09:35:50 +02:00
// SUBController handles HTTP requests for subscription links and JSON configurations.
2023-04-09 23:13:18 +03:30
type SUBController struct {
subTitle string
subSupportUrl string
subProfileUrl string
subAnnounce string
subEnableRouting bool
subRoutingRules string
subPath string
subJsonPath string
2026-04-20 04:26:13 +08:00
subClashPath string
jsonEnabled bool
2026-04-20 04:26:13 +08:00
clashEnabled bool
subEncrypt bool
updateInterval string
2024-02-21 14:17:52 +03:30
2026-04-20 04:26:13 +08:00
subService *SubService
subJsonService *SubJsonService
subClashService *SubClashService
2023-04-09 23:13:18 +03:30
}
2025-09-20 09:35:50 +02:00
// NewSUBController creates a new subscription controller with the given configuration.
2024-02-21 14:17:52 +03:30
func NewSUBController(
g *gin.RouterGroup,
subPath string,
jsonPath string,
2026-04-20 04:26:13 +08:00
clashPath string,
2025-09-18 13:56:04 +02:00
jsonEnabled bool,
2026-04-20 04:26:13 +08:00
clashEnabled bool,
2024-02-21 14:17:52 +03:30
encrypt bool,
showInfo bool,
rModel string,
update string,
2024-03-11 01:01:24 +03:30
jsonFragment string,
2024-08-29 11:27:43 +02:00
jsonNoise string,
2024-03-12 19:44:51 +03:30
jsonMux string,
jsonRules string,
subTitle string,
subSupportUrl string,
subProfileUrl string,
subAnnounce string,
subEnableRouting bool,
subRoutingRules string,
2024-03-11 01:01:24 +03:30
) *SUBController {
2024-03-11 16:14:24 +03:30
sub := NewSubService(showInfo, rModel)
2024-02-21 14:17:52 +03:30
a := &SUBController{
subTitle: subTitle,
subSupportUrl: subSupportUrl,
subProfileUrl: subProfileUrl,
subAnnounce: subAnnounce,
subEnableRouting: subEnableRouting,
subRoutingRules: subRoutingRules,
subPath: subPath,
subJsonPath: jsonPath,
2026-04-20 04:26:13 +08:00
subClashPath: clashPath,
jsonEnabled: jsonEnabled,
2026-04-20 04:26:13 +08:00
clashEnabled: clashEnabled,
subEncrypt: encrypt,
updateInterval: update,
2026-04-20 04:26:13 +08:00
subService: sub,
subJsonService: NewSubJsonService(jsonFragment, jsonNoise, jsonMux, jsonRules, sub),
subClashService: NewSubClashService(sub),
2024-02-21 14:17:52 +03:30
}
2023-04-09 23:13:18 +03:30
a.initRouter(g)
return a
}
2025-09-20 09:35:50 +02:00
// initRouter registers HTTP routes for subscription links and JSON endpoints
// on the provided router group.
2023-04-09 23:13:18 +03:30
func (a *SUBController) initRouter(g *gin.RouterGroup) {
2024-02-21 14:17:52 +03:30
gLink := g.Group(a.subPath)
gLink.GET(":subid", a.subs)
2025-09-18 13:56:04 +02:00
if a.jsonEnabled {
gJson := g.Group(a.subJsonPath)
gJson.GET(":subid", a.subJsons)
}
2026-04-20 04:26:13 +08:00
if a.clashEnabled {
gClash := g.Group(a.subClashPath)
gClash.GET(":subid", a.subClashs)
}
2023-04-09 23:13:18 +03:30
}
2025-09-20 09:35:50 +02:00
// subs handles HTTP requests for subscription links, returning either HTML page or base64-encoded subscription data.
2023-04-09 23:13:18 +03:30
func (a *SUBController) subs(c *gin.Context) {
subId := c.Param("subid")
2025-09-14 01:22:42 +02:00
scheme, host, hostWithPort, hostHeader := a.subService.ResolveRequest(c)
2025-09-14 23:08:09 +02:00
subs, lastOnline, traffic, err := a.subService.GetSubs(subId, host)
2023-04-18 21:34:06 +03:30
if err != nil || len(subs) == 0 {
2023-04-09 23:13:18 +03:30
c.String(400, "Error!")
} else {
result := ""
for _, sub := range subs {
result += sub + "\n"
}
2023-04-18 21:34:06 +03:30
2025-09-14 01:22:42 +02:00
// If the request expects HTML (e.g., browser) or explicitly asked (?html=1 or ?view=html), render the info page here
accept := c.GetHeader("Accept")
if strings.Contains(strings.ToLower(accept), "text/html") || c.Query("html") == "1" || strings.EqualFold(c.Query("view"), "html") {
// Build page data in service
2026-04-20 04:26:13 +08:00
subURL, subJsonURL, subClashURL := a.subService.BuildURLs(scheme, hostWithPort, a.subPath, a.subJsonPath, a.subClashPath, subId)
2025-09-18 13:56:04 +02:00
if !a.jsonEnabled {
subJsonURL = ""
}
2026-04-20 04:26:13 +08:00
if !a.clashEnabled {
subClashURL = ""
}
2025-09-24 19:51:01 +02:00
// Get base_path from context (set by middleware)
basePath, exists := c.Get("base_path")
if !exists {
basePath = "/"
}
// Add subId to base_path for asset URLs
basePathStr := basePath.(string)
if basePathStr == "/" {
basePathStr = "/" + subId + "/"
} else {
// Remove trailing slash if exists, add subId, then add trailing slash
basePathStr = strings.TrimRight(basePathStr, "/") + "/" + subId + "/"
}
2026-04-20 04:26:13 +08:00
page := a.subService.BuildPageData(subId, hostHeader, traffic, lastOnline, subs, subURL, subJsonURL, subClashURL, basePathStr)
2025-09-18 12:20:21 +02:00
c.HTML(200, "subpage.html", gin.H{
2025-09-14 01:22:42 +02:00
"title": "subscription.title",
2025-09-14 19:44:26 +02:00
"cur_ver": config.GetVersion(),
2025-09-14 01:22:42 +02:00
"host": page.Host,
"base_path": page.BasePath,
"sId": page.SId,
"download": page.Download,
"upload": page.Upload,
"total": page.Total,
"used": page.Used,
"remained": page.Remained,
"expire": page.Expire,
"lastOnline": page.LastOnline,
"datepicker": page.Datepicker,
"downloadByte": page.DownloadByte,
"uploadByte": page.UploadByte,
"totalByte": page.TotalByte,
"subUrl": page.SubUrl,
"subJsonUrl": page.SubJsonUrl,
2026-04-20 04:26:13 +08:00
"subClashUrl": page.SubClashUrl,
2025-09-14 01:22:42 +02:00
"result": page.Result,
})
return
}
2023-04-18 21:34:06 +03:30
2025-09-14 19:44:26 +02:00
// Add headers
2025-09-14 23:08:09 +02:00
header := fmt.Sprintf("upload=%d; download=%d; total=%d; expire=%d", traffic.Up, traffic.Down, traffic.Total, traffic.ExpiryTime/1000)
profileUrl := a.subProfileUrl
2026-02-09 23:45:25 +03:00
if profileUrl == "" {
profileUrl = fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
}
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
2025-09-14 19:44:26 +02:00
2024-02-21 14:17:52 +03:30
if a.subEncrypt {
2023-08-26 16:54:01 +03:30
c.String(200, base64.StdEncoding.EncodeToString([]byte(result)))
} else {
c.String(200, result)
}
2023-04-09 23:13:18 +03:30
}
}
2024-02-21 14:17:52 +03:30
2025-09-20 09:35:50 +02:00
// subJsons handles HTTP requests for JSON subscription configurations.
2024-02-21 14:17:52 +03:30
func (a *SUBController) subJsons(c *gin.Context) {
subId := c.Param("subid")
2026-02-09 23:45:25 +03:00
scheme, host, hostWithPort, _ := a.subService.ResolveRequest(c)
2024-02-21 14:17:52 +03:30
jsonSub, header, err := a.subJsonService.GetJson(subId, host)
if err != nil || len(jsonSub) == 0 {
c.String(400, "Error!")
} else {
2026-02-09 23:45:25 +03:00
profileUrl := a.subProfileUrl
if profileUrl == "" {
profileUrl = fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
}
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
2024-02-21 14:17:52 +03:30
c.String(200, jsonSub)
}
}
2026-04-20 04:26:13 +08:00
func (a *SUBController) subClashs(c *gin.Context) {
subId := c.Param("subid")
scheme, host, hostWithPort, _ := a.subService.ResolveRequest(c)
clashSub, header, err := a.subClashService.GetClash(subId, host)
if err != nil || len(clashSub) == 0 {
c.String(400, "Error!")
} else {
profileUrl := a.subProfileUrl
if profileUrl == "" {
profileUrl = fmt.Sprintf("%s://%s%s", scheme, hostWithPort, c.Request.RequestURI)
}
a.ApplyCommonHeaders(c, header, a.updateInterval, a.subTitle, a.subSupportUrl, profileUrl, a.subAnnounce, a.subEnableRouting, a.subRoutingRules)
c.Data(200, "application/yaml; charset=utf-8", []byte(clashSub))
}
}
2025-09-20 09:35:50 +02:00
// ApplyCommonHeaders sets common HTTP headers for subscription responses including user info, update interval, and profile title.
func (a *SUBController) ApplyCommonHeaders(
c *gin.Context,
header,
updateInterval,
profileTitle string,
profileSupportUrl string,
profileUrl string,
profileAnnounce string,
profileEnableRouting bool,
profileRoutingRules string,
) {
2025-09-14 19:44:26 +02:00
c.Writer.Header().Set("Subscription-Userinfo", header)
c.Writer.Header().Set("Profile-Update-Interval", updateInterval)
//Basics
if profileTitle != "" {
c.Writer.Header().Set("Profile-Title", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileTitle)))
}
if profileSupportUrl != "" {
c.Writer.Header().Set("Support-Url", profileSupportUrl)
}
if profileUrl != "" {
c.Writer.Header().Set("Profile-Web-Page-Url", profileUrl)
}
if profileAnnounce != "" {
c.Writer.Header().Set("Announce", "base64:"+base64.StdEncoding.EncodeToString([]byte(profileAnnounce)))
}
//Advanced (Happ)
c.Writer.Header().Set("Routing-Enable", strconv.FormatBool(profileEnableRouting))
if profileRoutingRules != "" {
c.Writer.Header().Set("Routing", profileRoutingRules)
}
2025-09-14 19:44:26 +02:00
}