фиксы
This commit is contained in:
@@ -45,7 +45,7 @@ type CreativeEditorFields struct {
|
||||
}
|
||||
|
||||
const inviteLinkPlaceholder = "{{invite_link}}"
|
||||
const inviteLinkPreviewURL = "https://t.me/J7_channel"
|
||||
const inviteLinkPreviewURL = "https://t.me/+nlOUmIsLGAlmZTMy" //ссылка приглашения для предпросмотра
|
||||
|
||||
// GetCreativeText возвращает текст для превью креатива
|
||||
func (e *CreativeEditorFields) GetCreativeText() string {
|
||||
@@ -64,8 +64,79 @@ func (e *CreativeEditorFields) GetCreativeText() string {
|
||||
</i>`
|
||||
}
|
||||
|
||||
// Заменяем пустую ссылку <a class="tg-link"></a> на валидную preview-ссылку
|
||||
text = strings.ReplaceAll(text, `<a class="tg-link"></a>`, `<a href="`+inviteLinkPreviewURL+`">👉 Подписаться</a>`)
|
||||
// Заменяем пригласительные ссылки с классом tg-link на preview-ссылку
|
||||
// Находим все вхождения <a class="tg-link">...</a> и заменяем на кликабельную ссылку
|
||||
replacementCount := 0
|
||||
for strings.Contains(text, `<a class="tg-link">`) {
|
||||
startIdx := strings.Index(text, `<a class="tg-link">`)
|
||||
if startIdx == -1 {
|
||||
break
|
||||
}
|
||||
endIdx := strings.Index(text[startIdx:], "</a>")
|
||||
if endIdx == -1 {
|
||||
log.Warn().Msg("Found <a class=\"tg-link\"> but no closing </a>")
|
||||
break
|
||||
}
|
||||
endIdx += startIdx
|
||||
|
||||
// Извлекаем текст между тегами
|
||||
linkText := text[startIdx+len(`<a class="tg-link">`) : endIdx]
|
||||
log.Info().
|
||||
Int("replacement_num", replacementCount).
|
||||
Str("linkText_before_sanitize", linkText).
|
||||
Msg("Processing tg-link")
|
||||
|
||||
// ВАЖНО: Удаляем все HTML-теги из текста ссылки для предотвращения неправильной вложенности
|
||||
// Это нужно для старых креативов с неправильным HTML
|
||||
tagsRemoved := 0
|
||||
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
|
||||
}
|
||||
// Удаляем тег
|
||||
removedTag := linkText[openTag : openTag+closeTag+1]
|
||||
linkText = linkText[:openTag] + linkText[openTag+closeTag+1:]
|
||||
tagsRemoved++
|
||||
log.Info().
|
||||
Str("removed_tag", removedTag).
|
||||
Str("linkText_after", linkText).
|
||||
Int("tags_removed_total", tagsRemoved).
|
||||
Msg("Removed HTML tag from link text")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Str("linkText_after_sanitize", linkText).
|
||||
Int("tags_removed", tagsRemoved).
|
||||
Str("replacement", replacement).
|
||||
Msg("Sanitized link text")
|
||||
|
||||
text = text[:startIdx] + replacement + text[endIdx+len("</a>"):]
|
||||
replacementCount++
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Int("replacements_made", replacementCount).
|
||||
Str("final_text", text).
|
||||
Msg("Finished processing tg-links")
|
||||
|
||||
// Также заменяем плейсхолдер {{invite_link}} на preview URL (как в кнопках)
|
||||
text = strings.ReplaceAll(text, inviteLinkPlaceholder, inviteLinkPreviewURL)
|
||||
@@ -409,18 +480,55 @@ func (e *CreativeEditorFields) GetFormattedText(message *echotron.Message) strin
|
||||
// Преобразуем текст в руны для правильной работы с UTF-8
|
||||
runes := []rune(text)
|
||||
|
||||
// Сначала находим все url entities, чтобы исключить перекрывающиеся форматирования
|
||||
urlRanges := make([][2]int, 0)
|
||||
// Также отмечаем диапазоны, которые нужно полностью исключить из текста (простые url ссылки)
|
||||
skipRanges := make(map[[2]int]bool)
|
||||
|
||||
for _, entity := range entities {
|
||||
if entity.Type == "url" || entity.Type == "text_link" {
|
||||
urlRanges = append(urlRanges, [2]int{entity.Offset, entity.Offset + entity.Length})
|
||||
// Для простых url (не text_link) с пригласительными ссылками - пропускаем текст URL
|
||||
// Создаем пустой <a class="tg-link"></a>, а сам URL не сохраняем
|
||||
if entity.Type == "url" {
|
||||
entityText := ""
|
||||
if entity.Offset+entity.Length <= len(runes) {
|
||||
entityText = string(runes[entity.Offset : entity.Offset+entity.Length])
|
||||
}
|
||||
if strings.Contains(entityText, "t.me/+") || strings.Contains(entityText, "t.me/joinchat/") {
|
||||
skipRanges[[2]int{entity.Offset, entity.Offset + entity.Length}] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем, находится ли entity внутри URL
|
||||
isInsideURL := func(offset, length int) bool {
|
||||
for _, urlRange := range urlRanges {
|
||||
// Если entity полностью внутри URL (но не сам URL)
|
||||
if offset > urlRange[0] && offset+length <= urlRange[1] {
|
||||
return true
|
||||
}
|
||||
// Если entity пересекается с URL
|
||||
if offset < urlRange[1] && offset+length > urlRange[0] && (offset != urlRange[0] || length != urlRange[1]-urlRange[0]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var spans []tagSpan
|
||||
for _, entity := range entities {
|
||||
// URL уже присутствует в сыром тексте, чтобы не дублировать - пропускаем entity.
|
||||
if entity.Type == "url" {
|
||||
continue
|
||||
}
|
||||
|
||||
entityText := ""
|
||||
if entity.Offset+entity.Length <= len(runes) {
|
||||
entityText = string(runes[entity.Offset : entity.Offset+entity.Length])
|
||||
}
|
||||
|
||||
// Пропускаем форматирование (bold, italic, etc), если оно находится внутри URL
|
||||
if entity.Type != "url" && entity.Type != "text_link" && isInsideURL(entity.Offset, entity.Length) {
|
||||
continue
|
||||
}
|
||||
|
||||
var openTag, closeTag string
|
||||
switch entity.Type {
|
||||
case "bold":
|
||||
@@ -435,10 +543,29 @@ func (e *CreativeEditorFields) GetFormattedText(message *echotron.Message) strin
|
||||
openTag, closeTag = "<code>", "</code>"
|
||||
case "pre":
|
||||
openTag, closeTag = "<pre>", "</pre>"
|
||||
case "url":
|
||||
// Конвертируем простые URL в HTML-ссылки
|
||||
// Для пригласительных ссылок (t.me/+ или t.me/joinchat/) используем пустой тег
|
||||
if strings.Contains(entityText, "t.me/+") || strings.Contains(entityText, "t.me/joinchat/") {
|
||||
// Пригласительная ссылка - создаем пустой <a class="tg-link"></a>
|
||||
// Текст ссылки будет пропущен при рендеринге (см. skipRanges)
|
||||
openTag = `<a class="tg-link">`
|
||||
closeTag = "</a>"
|
||||
} else {
|
||||
// Обычная ссылка - оставляем как есть (ссылка на себя)
|
||||
openTag = fmt.Sprintf("<a href=\"%s\">", entityText)
|
||||
closeTag = "</a>"
|
||||
}
|
||||
case "text_link":
|
||||
if entity.URL != "" {
|
||||
openTag = fmt.Sprintf("<a href=\"%s\">", entity.URL)
|
||||
closeTag = "</a>"
|
||||
// Проверяем, является ли URL пригласительной ссылкой
|
||||
if strings.Contains(entity.URL, "t.me/+") || strings.Contains(entity.URL, "t.me/joinchat/") {
|
||||
openTag = `<a class="tg-link">`
|
||||
closeTag = "</a>"
|
||||
} else {
|
||||
openTag = fmt.Sprintf("<a href=\"%s\">", entity.URL)
|
||||
closeTag = "</a>"
|
||||
}
|
||||
}
|
||||
default:
|
||||
_ = entityText
|
||||
@@ -462,6 +589,16 @@ func (e *CreativeEditorFields) GetFormattedText(message *echotron.Message) strin
|
||||
closes[span.end] = append(closes[span.end], span)
|
||||
}
|
||||
|
||||
// Функция для проверки, находится ли позиция в skipRanges
|
||||
isInSkipRange := func(pos int) bool {
|
||||
for skipRange := range skipRanges {
|
||||
if pos >= skipRange[0] && pos < skipRange[1] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Пишем текст один раз, вставляя HTML-теги по позициям
|
||||
// и экранируя HTML-спецсимволы
|
||||
var result strings.Builder
|
||||
@@ -479,6 +616,10 @@ func (e *CreativeEditorFields) GetFormattedText(message *echotron.Message) strin
|
||||
}
|
||||
}
|
||||
if i < len(runes) {
|
||||
// Пропускаем символы, которые находятся в skipRanges (текст пригласительных ссылок)
|
||||
if isInSkipRange(i) {
|
||||
continue
|
||||
}
|
||||
// Экранируем HTML-спецсимволы
|
||||
switch runes[i] {
|
||||
case '<':
|
||||
|
||||
Reference in New Issue
Block a user