placeholders.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package sub
  2. import (
  3. "errors"
  4. "net/url"
  5. "strings"
  6. "gorm.io/gorm"
  7. "github.com/mhsanaei/3x-ui/v3/internal/database"
  8. "github.com/mhsanaei/3x-ui/v3/internal/database/model"
  9. "github.com/mhsanaei/3x-ui/v3/internal/logger"
  10. "github.com/mhsanaei/3x-ui/v3/internal/web/service"
  11. )
  12. type subPlaceholderData struct {
  13. SubID string
  14. Context remarkContext
  15. HasCtx bool
  16. Escape bool
  17. }
  18. type renderedSubMetadata struct {
  19. Title string
  20. SupportURL string
  21. ProfileURL string
  22. Announce string
  23. }
  24. func renderSubPlaceholders(value string, data subPlaceholderData) string {
  25. if value == "" || !strings.Contains(value, "{") {
  26. return value
  27. }
  28. ctx := data.Context
  29. if !data.HasCtx {
  30. ctx = remarkContext{
  31. client: model.Client{
  32. SubID: data.SubID,
  33. },
  34. }
  35. }
  36. if ctx.client.SubID == "" {
  37. ctx.client.SubID = data.SubID
  38. }
  39. return strings.TrimSpace(expandSubMetadataVars(value, ctx, data.Escape))
  40. }
  41. var subMetadataTokens = map[string]bool{
  42. "EMAIL": true,
  43. "ID": true,
  44. "SHORT_ID": true,
  45. "TELEGRAM_ID": true,
  46. "SUB_ID": true,
  47. }
  48. func expandSubMetadataVars(template string, ctx remarkContext, escape bool) string {
  49. return remarkVarRe.ReplaceAllStringFunc(template, func(match string) string {
  50. token := match[2 : len(match)-2]
  51. if !subMetadataTokens[token] {
  52. return match
  53. }
  54. value := remarkVarValue(token, ctx)
  55. if escape {
  56. return url.QueryEscape(value)
  57. }
  58. return value
  59. })
  60. }
  61. func subMetadataUsesPlaceholders(values ...string) bool {
  62. for _, value := range values {
  63. if strings.Contains(value, "{") {
  64. return true
  65. }
  66. }
  67. return false
  68. }
  69. func (a *SUBController) metadataForSubRequest(getSubReq func() *SubService, subID, builtinURL string) renderedSubMetadata {
  70. profileURL := ""
  71. switch a.subProfileMode {
  72. case service.SubProfileModeBuiltin:
  73. profileURL = builtinURL
  74. case service.SubProfileModeCustom:
  75. profileURL = strings.TrimSpace(a.subProfileUrl)
  76. }
  77. var context remarkContext
  78. var hasContext bool
  79. if subMetadataUsesPlaceholders(a.subTitle, a.subSupportUrl, profileURL, a.subAnnounce) {
  80. var err error
  81. subReq := getSubReq()
  82. context, hasContext, err = subReq.subscriptionTemplateContextBySubID(subID)
  83. if err != nil {
  84. logger.Warning("sub: load template contexts for subscription metadata:", err)
  85. }
  86. }
  87. // Disabled modes ignore the retained custom URL and never fall back to the request URL.
  88. profileURL = renderSubPlaceholders(profileURL, subPlaceholderData{SubID: subID, Context: context, HasCtx: hasContext, Escape: true})
  89. data := subPlaceholderData{SubID: subID, Context: context, HasCtx: hasContext}
  90. return renderedSubMetadata{
  91. Title: renderSubPlaceholders(a.subTitle, data),
  92. SupportURL: renderSubPlaceholders(a.subSupportUrl, subPlaceholderData{SubID: subID, Context: context, HasCtx: hasContext, Escape: true}),
  93. ProfileURL: profileURL,
  94. Announce: renderSubPlaceholders(a.subAnnounce, data),
  95. }
  96. }
  97. func (s *SubService) subscriptionTemplateContextBySubID(subID string) (remarkContext, bool, error) {
  98. if subID == "" {
  99. return remarkContext{}, false, nil
  100. }
  101. var rec model.ClientRecord
  102. err := database.GetDB().Where("sub_id = ?", subID).Order("id ASC").First(&rec).Error
  103. if errors.Is(err, gorm.ErrRecordNotFound) {
  104. return remarkContext{}, false, nil
  105. }
  106. if err != nil {
  107. return remarkContext{}, false, err
  108. }
  109. return remarkContext{client: *rec.ToClient()}, true, nil
  110. }