placeholders.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  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. )
  11. type subPlaceholderData struct {
  12. SubID string
  13. Context remarkContext
  14. HasCtx bool
  15. Escape bool
  16. }
  17. type renderedSubMetadata struct {
  18. Title string
  19. SupportURL string
  20. ProfileURL string
  21. Announce string
  22. }
  23. func renderSubPlaceholders(value string, data subPlaceholderData) string {
  24. if value == "" || !strings.Contains(value, "{") {
  25. return value
  26. }
  27. ctx := data.Context
  28. if !data.HasCtx {
  29. ctx = remarkContext{
  30. client: model.Client{
  31. SubID: data.SubID,
  32. },
  33. }
  34. }
  35. if ctx.client.SubID == "" {
  36. ctx.client.SubID = data.SubID
  37. }
  38. return strings.TrimSpace(expandSubMetadataVars(value, ctx, data.Escape))
  39. }
  40. var subMetadataTokens = map[string]bool{
  41. "EMAIL": true,
  42. "ID": true,
  43. "SHORT_ID": true,
  44. "TELEGRAM_ID": true,
  45. "SUB_ID": true,
  46. }
  47. func expandSubMetadataVars(template string, ctx remarkContext, escape bool) string {
  48. return remarkVarRe.ReplaceAllStringFunc(template, func(match string) string {
  49. token := match[2 : len(match)-2]
  50. if !subMetadataTokens[token] {
  51. return match
  52. }
  53. value := remarkVarValue(token, ctx)
  54. if escape {
  55. return url.QueryEscape(value)
  56. }
  57. return value
  58. })
  59. }
  60. func subMetadataUsesPlaceholders(values ...string) bool {
  61. for _, value := range values {
  62. if strings.Contains(value, "{") {
  63. return true
  64. }
  65. }
  66. return false
  67. }
  68. func (a *SUBController) metadataForSubRequest(getSubReq func() *SubService, subID string, fallbackProfileURL string) renderedSubMetadata {
  69. var context remarkContext
  70. var hasContext bool
  71. if subMetadataUsesPlaceholders(a.subTitle, a.subSupportUrl, a.subProfileUrl, a.subAnnounce) {
  72. var err error
  73. subReq := getSubReq()
  74. context, hasContext, err = subReq.subscriptionTemplateContextBySubID(subID)
  75. if err != nil {
  76. logger.Warning("sub: load template contexts for subscription metadata:", err)
  77. }
  78. }
  79. profileURL := a.subProfileUrl
  80. if profileURL == "" {
  81. profileURL = fallbackProfileURL
  82. } else {
  83. profileURL = renderSubPlaceholders(profileURL, subPlaceholderData{SubID: subID, Context: context, HasCtx: hasContext, Escape: true})
  84. }
  85. data := subPlaceholderData{SubID: subID, Context: context, HasCtx: hasContext}
  86. return renderedSubMetadata{
  87. Title: renderSubPlaceholders(a.subTitle, data),
  88. SupportURL: renderSubPlaceholders(a.subSupportUrl, subPlaceholderData{SubID: subID, Context: context, HasCtx: hasContext, Escape: true}),
  89. ProfileURL: profileURL,
  90. Announce: renderSubPlaceholders(a.subAnnounce, data),
  91. }
  92. }
  93. func (s *SubService) subscriptionTemplateContextBySubID(subID string) (remarkContext, bool, error) {
  94. if subID == "" {
  95. return remarkContext{}, false, nil
  96. }
  97. var rec model.ClientRecord
  98. err := database.GetDB().Where("sub_id = ?", subID).Order("id ASC").First(&rec).Error
  99. if errors.Is(err, gorm.ErrRecordNotFound) {
  100. return remarkContext{}, false, nil
  101. }
  102. if err != nil {
  103. return remarkContext{}, false, err
  104. }
  105. return remarkContext{client: *rec.ToClient()}, true, nil
  106. }