приватные каналы

This commit is contained in:
Artem Tsyrulnikov
2026-01-21 12:16:02 +03:00
parent b5695360c2
commit 2c0dcbef97
7 changed files with 59 additions and 89 deletions

View File

@@ -2,6 +2,7 @@ package screens
import (
"fmt"
"regexp"
"strings"
"github.com/NicoNex/echotron/v3"
@@ -49,6 +50,10 @@ const inviteLinkPlaceholder = "{{invite_link}}"
const inviteLinkPreviewURL = "https://t.me/+nlOUmIsLGAlmZTMy" //ссылка приглашения для предпросмотра
const buttonURLPlaceholder = "https://example.com"
var inviteLinkPattern = regexp.MustCompile(`https?://t\.me/(?:\+|joinchat/)[a-zA-Z0-9_-]+`)
var inviteLinkTagPattern = regexp.MustCompile(`<a\s+class="tg-link">\s*(.*?)\s*</a>`)
var htmlTagPattern = regexp.MustCompile(`<[^>]+>`)
const (
inputModeText = "text"
inputModeButtonText = "button_text"
@@ -90,55 +95,23 @@ func (e *CreativeEditorFields) GetCreativeText() string {
return msgCreativeDefault
}
// Заменяем пригласительные ссылки с классом tg-link на preview-ссылку
// Находим все вхождения <a class="tg-link">...</a> и заменяем на кликабельную ссылку
for strings.Contains(text, `<a class="tg-link">`) {
startIdx := strings.Index(text, `<a class="tg-link">`)
if startIdx == -1 {
break
// Заменяем плейсхолдеры tg-link на кликабельную preview-ссылку.
text = inviteLinkTagPattern.ReplaceAllStringFunc(text, func(match string) string {
sub := inviteLinkTagPattern.FindStringSubmatch(match)
inner := ""
if len(sub) > 1 {
inner = strings.TrimSpace(htmlTagPattern.ReplaceAllString(sub[1], ""))
}
endIdx := strings.Index(text[startIdx:], "</a>")
if endIdx == -1 {
log.Warn().Msg("Found <a class=\"tg-link\"> but no closing </a>")
break
if inner == "" {
inner = inviteLinkPreviewURL
}
endIdx += startIdx
return fmt.Sprintf(`<a href="%s">%s</a>`, inviteLinkPreviewURL, inner)
})
// Извлекаем текст между тегами
linkText := text[startIdx+len(`<a class="tg-link">`) : endIdx]
// ВАЖНО: Удаляем все HTML-теги из текста ссылки для предотвращения неправильной вложенности
// Это нужно для старых креативов с неправильным HTML
for {
openTag := strings.Index(linkText, "<")
if openTag == -1 {
break
}
closeTag := strings.Index(linkText[openTag:], ">")
if closeTag == -1 {
log.Warn().Str("linkText", linkText).Msg("Found < but no closing >")
break
}
// Удаляем тег
linkText = linkText[:openTag] + linkText[openTag+closeTag+1:]
}
// Заменяем пригласительные ссылки на preview URL только для отображения.
text = inviteLinkPattern.ReplaceAllString(text, inviteLinkPreviewURL)
linkText = strings.TrimSpace(linkText)
// Если текст ссылки не пустой, добавляем атрибут href с примером ссылки
// Если пустой, показываем просто текст примера ссылки (без тега)
var replacement string
if linkText != "" {
// Есть текст - добавляем href атрибут к тегу
replacement = fmt.Sprintf(`<a class="tg-link" href="%s">%s</a>`, inviteLinkPreviewURL, linkText)
} else {
// Пусто - показываем просто текст примера ссылки
replacement = fmt.Sprintf(`%s`, inviteLinkPreviewURL)
}
text = text[:startIdx] + replacement + text[endIdx+len("</a>"):]
}
// Также заменяем плейсхолдер {{invite_link}} на preview URL (как в кнопках)
// Также заменяем плейсхолдер {{invite_link}} на preview URL (как в кнопках).
text = strings.ReplaceAll(text, inviteLinkPlaceholder, inviteLinkPreviewURL)
return text

View File

@@ -2,7 +2,6 @@ package ui
import (
"fmt"
"regexp"
"sort"
"strings"
@@ -24,7 +23,6 @@ var simpleHTMLTags = map[echotron.MessageEntityType]htmlTag{
}
// FormatMessageHTML converts Telegram entities to HTML and escapes the rest.
// Invite links are normalized into <a class="tg-link"> placeholders for previews.
func FormatMessageHTML(message *echotron.Message) string {
if message == nil {
return ""
@@ -138,22 +136,12 @@ func FormatMessageHTML(message *echotron.Message) string {
openTag, closeTag = tag.open, tag.close
} else if entity.Type == echotron.UrlEntity {
entityText := string(runes[start:end])
if isInviteLink(entityText) {
openTag = `<a class="tg-link">`
closeTag = "</a>"
} else {
openTag = fmt.Sprintf("<a href=\"%s\">", entityText)
closeTag = "</a>"
}
openTag = fmt.Sprintf("<a href=\"%s\">", entityText)
closeTag = "</a>"
} else if entity.Type == echotron.TextLinkEntity {
if entity.URL != "" {
if isInviteLink(entity.URL) {
openTag = `<a class="tg-link">`
closeTag = "</a>"
} else {
openTag = fmt.Sprintf("<a href=\"%s\">", entity.URL)
closeTag = "</a>"
}
openTag = fmt.Sprintf("<a href=\"%s\">", entity.URL)
closeTag = "</a>"
}
}
@@ -204,12 +192,7 @@ func FormatMessageHTML(message *echotron.Message) string {
}
}
return normalizeInviteLinkTags(normalizeHTMLTags(result.String()))
}
// isInviteLink detects Telegram invite links used in creatives.
func isInviteLink(url string) bool {
return strings.Contains(url, "t.me/+") || strings.Contains(url, "t.me/joinchat/")
return normalizeHTMLTags(result.String())
}
func normalizeHTMLTags(input string) string {
@@ -291,15 +274,6 @@ func normalizeHTMLTags(input string) string {
return out.String()
}
func normalizeInviteLinkTags(input string) string {
if input == "" {
return input
}
re := regexp.MustCompile(`<a\s+class="tg-link">\s*https?://t\.me/(?:\+|joinchat/)[a-zA-Z0-9_-]+\s*</a>`)
return re.ReplaceAllString(input, `<a class="tg-link"></a>`)
}
// EscapeHTML escapes HTML special characters for safe output.
func EscapeHTML(s string) string {
s = strings.ReplaceAll(s, "&", "&amp;")