This commit is contained in:
Artem Tsyrulnikov
2026-01-15 22:02:16 +03:00
parent 6ec26c96e3
commit 049024ebe8
72 changed files with 2417 additions and 2054 deletions

View File

@@ -7,6 +7,7 @@ import (
"example.com/m/adapter/backend"
"example.com/m/bot"
"example.com/m/ui"
"github.com/NicoNex/echotron/v3"
"github.com/rs/zerolog/log"
)
@@ -36,7 +37,8 @@ func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
// Заполняем поля из загруженного креатива
s.Name = &creative.Name
if creative.Text != "" {
s.Text = &creative.Text
sanitizedText := ui.SanitizeHTML(creative.Text)
s.Text = &sanitizedText
log.Info().Str("creative_text", creative.Text).Msg("Set creative text from backend")
} else {
log.Info().Msg("Creative text is empty from backend")
@@ -48,6 +50,7 @@ func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
s.Buttons = append(s.Buttons, InlineButton{Text: btn.Text, URL: btn.URL})
}
s.MediaChanged = false
s.ClearInputMode()
log.Info().
Str("creative_id", s.CreativeID).
@@ -77,47 +80,17 @@ func (s *CreativeDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
}
case data == "edit_text":
s.ShowControlPanel(b, `<b>✎ Введите новый текст креатива:</b>
⚠ <b>Важно:</b> Текст должен содержать <b>ОДНУ</b> инвайт-ссылку вашего канала
<b>Формат ссылки:</b>
<code>https://t.me/+xxx</code>
<b>Пример:</b>
<i>Присоединяйтесь к нашему каналу!
https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
Row(Button("« Отмена редактирования", "cancel_edit")),
})
s.InputMode = inputModeText
s.ShowTextEditPanel(b)
case data == "add_button":
s.WaitingForButtonText = true
s.InputMode = inputModeButtonText
s.PendingButtonType = "invite"
s.ShowControlPanel(b, `<b> Добавить кнопку</b>
Введите <b>текст кнопки:</b>
<i>Например:</i>
• Перейти на сайт
• Написать нам
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
Row(
Button("✓ Ссылка на канал", "button_type_invite"),
Button("Своя ссылка", "button_type_custom"),
),
Row(Button("« Отмена", "cancel_add_button")),
})
s.ShowAddButtonPanel(b)
case data == "add_media":
s.WaitingForMedia = true
s.ShowControlPanel(b, `<b> Добавить медиа</b>
Отправьте <b>фото, видео или GIF</b>
<i>Это медиа будет отображаться в креативе вместе с текстом</i>`, [][]echotron.InlineKeyboardButton{
Row(Button("« Отмена", "cancel_media")),
})
s.InputMode = inputModeMedia
s.ShowMediaPanel(b)
case data == "delete_media":
s.MediaType = ""
@@ -128,57 +101,21 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
case data == "cancel_add_button":
// Отменяем добавление кнопки
if s.WaitingForButtonURL && len(s.Buttons) > 0 {
lastButton := s.Buttons[len(s.Buttons)-1]
if lastButton.URL == "https://example.com" || lastButton.URL == inviteLinkPlaceholder {
s.Buttons = s.Buttons[:len(s.Buttons)-1]
s.UpdateCreativePreview(b)
}
}
s.WaitingForButtonText = false
s.WaitingForButtonURL = false
s.PendingButtonType = ""
s.CurrentButtonText = nil
s.CancelPendingButton(b)
s.showManagementPanelWithDelete(b)
case data == "button_type_invite":
s.PendingButtonType = "invite"
s.ShowControlPanel(b, `<b> Добавить кнопку</b>
Введите <b>текст кнопки:</b>
<i>Например:</i>
• Перейти на сайт
• Написать нам
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
Row(
Button("✓ Ссылка на канал", "button_type_invite"),
Button("Своя ссылка", "button_type_custom"),
),
Row(Button("« Отмена", "cancel_add_button")),
})
s.InputMode = inputModeButtonText
s.ShowAddButtonPanel(b)
case data == "button_type_custom":
s.PendingButtonType = "custom"
s.ShowControlPanel(b, `<b> Добавить кнопку</b>
Введите <b>текст кнопки:</b>
<i>Например:</i>
• Перейти на сайт
• Написать нам
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
Row(
Button("Ссылка на канал", "button_type_invite"),
Button("✓ Своя ссылка", "button_type_custom"),
),
Row(Button("« Отмена", "cancel_add_button")),
})
s.InputMode = inputModeButtonText
s.ShowAddButtonPanel(b)
case data == "cancel_edit", data == "cancel_media":
s.WaitingForMedia = false
s.ClearInputMode()
s.showManagementPanelWithDelete(b)
case data == "confirm_save":
@@ -225,146 +162,80 @@ func (s *CreativeDetails) HandleMessage(b *bot.Bot, u *echotron.Update) {
return
}
// Обрабатываем добавление медиа
if s.WaitingForMedia {
handled := false
if u.Message.Photo != nil && len(u.Message.Photo) > 0 {
photos := u.Message.Photo
largestPhoto := photos[len(photos)-1]
s.MediaType = "photo"
s.MediaFileID = largestPhoto.FileID
s.MediaChanged = true
s.WaitingForMedia = false
handled = true
} else if u.Message.Video != nil {
s.MediaType = "video"
s.MediaFileID = u.Message.Video.FileID
s.MediaChanged = true
s.WaitingForMedia = false
handled = true
} else if u.Message.Animation != nil {
s.MediaType = "animation"
s.MediaFileID = u.Message.Animation.FileID
s.MediaChanged = true
s.WaitingForMedia = false
handled = true
}
if handled {
// Удаляем сообщение пользователя
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
if err != nil {
log.Error().Err(err).Msg("Failed to delete user message")
}
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
switch s.InputMode {
case inputModeMedia:
if !s.SetMediaFromMessage(u.Message) {
return
}
}
// Обрабатываем ввод текста кнопки
if s.WaitingForButtonText && u.Message.Text != "" {
buttonText := u.Message.Text
s.CurrentButtonText = &buttonText
s.WaitingForButtonText = false
// Удаляем сообщение пользователя
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
if err != nil {
log.Error().Err(err).Msg("Failed to delete user message")
s.DeleteUserMessage(b, u.Message.ID)
s.UpdateCreativePreview(b)
s.ClearInputMode()
s.showManagementPanelWithDelete(b)
return
case inputModeButtonText:
if u.Message.Text == "" {
return
}
buttonText := u.Message.Text
s.DeleteUserMessage(b, u.Message.ID)
// Создаём кнопку с placeholder URL
if s.PendingButtonType == "custom" {
s.WaitingForButtonURL = true
s.Buttons = append(s.Buttons, InlineButton{
Text: buttonText,
URL: "https://example.com",
})
s.AddCustomButtonPlaceholder(buttonText)
s.UpdateCreativePreview(b)
s.ShowControlPanel(b, `<b>✧ Введите URL для кнопки:</b>
<i>Например:</i>
<code>https://example.com</code>`, [][]echotron.InlineKeyboardButton{
Row(Button("« Отмена", "cancel_add_button")),
})
s.InputMode = inputModeButtonURL
s.ShowButtonURLPanel(b)
} else {
s.Buttons = append(s.Buttons, InlineButton{
Text: buttonText,
URL: inviteLinkPlaceholder,
})
s.AddInviteButton(buttonText)
s.UpdateCreativePreview(b)
s.PendingButtonType = ""
s.ClearInputMode()
s.showManagementPanelWithDelete(b)
}
return
}
// Обрабатываем ввод URL кнопки
if s.WaitingForButtonURL && u.Message.Text != "" {
url := strings.TrimSpace(u.Message.Text)
// Удаляем сообщение пользователя
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
if err != nil {
log.Error().Err(err).Msg("Failed to delete user message")
case inputModeButtonURL:
if u.Message.Text == "" {
return
}
url := strings.TrimSpace(u.Message.Text)
s.DeleteUserMessage(b, u.Message.ID)
// Простая валидация URL
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
s.ShowControlPanel(b, `<b>❌ Неверный формат URL</b>
URL должен начинаться с <code>http://</code> или <code>https://</code>
Попробуйте ещё раз:`, [][]echotron.InlineKeyboardButton{
Row(Button("« Отмена", "cancel_add_button")),
})
if !s.IsValidButtonURL(url, true) {
s.ShowInvalidButtonURLPanel(b)
return
}
// Обновляем URL последней кнопки
if len(s.Buttons) > 0 {
s.Buttons[len(s.Buttons)-1].URL = url
}
s.UpdateLastButtonURL(url)
s.WaitingForButtonURL = false
s.CurrentButtonText = nil
s.ClearInputMode()
s.PendingButtonType = ""
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
return
}
// Обрабатываем редактирование текста креатива
if !s.WaitingForButtonText && !s.WaitingForButtonURL && !s.WaitingForMedia && u.Message.Text != "" {
text := s.GetFormattedText(u.Message)
case inputModeText:
if u.Message.Text == "" {
return
}
text := ui.FormatMessageHTML(u.Message)
s.Text = &text
// Удаляем сообщение пользователя
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
if err != nil {
log.Error().Err(err).Msg("Failed to delete user message")
}
s.DeleteUserMessage(b, u.Message.ID)
s.UpdateCreativePreview(b)
s.ClearInputMode()
s.showManagementPanelWithDelete(b)
return
}
// Обрабатываем имя креатива (если нет других состояний)
if u.Message.Text != "" {
text := s.GetFormattedText(u.Message)
text := ui.FormatMessageHTML(u.Message)
s.Text = &text
// Удаляем сообщение пользователя
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
if err != nil {
log.Error().Err(err).Msg("Failed to delete user message")
}
s.DeleteUserMessage(b, u.Message.ID)
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
@@ -373,6 +244,8 @@ URL должен начинаться с <code>http://</code> или <code>https
func (s *CreativeDetails) Handle(_ *bot.Bot, _ *echotron.Update) { return }
func (s *CreativeDetails) Exit() {}
func (s *CreativeDetails) showManagementPanelWithDelete(b *bot.Bot) {
var confirmText, confirmCallback string