Files
tgex-backend/telegram_bot/screens/creative_details.go
Artem Tsyrulnikov 049024ebe8 refactor
2026-01-15 22:02:16 +03:00

360 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package screens
import (
"context"
"fmt"
"strings"
"example.com/m/adapter/backend"
"example.com/m/bot"
"example.com/m/ui"
"github.com/NicoNex/echotron/v3"
"github.com/rs/zerolog/log"
)
type CreativeDetails struct {
CreativeEditorFields // Встраивание общих полей
CreativeID string
ProjectID string
WorkspaceID string
BackState bot.State
// Флаг подтверждения удаления
confirmingDelete bool
}
func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
creative, err := b.Backend.GetCreative(context.Background(), b.Session.JWT, s.WorkspaceID, s.CreativeID)
if err != nil {
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to get creative")
b.SendNew("<b>❌ Не удалось загрузить креатив</b>", Keyboard(
Row(Button("← Назад", "back")),
))
return
}
// Заполняем поля из загруженного креатива
s.Name = &creative.Name
if 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")
}
s.MediaType = creative.MediaType
s.MediaFileID = creative.MediaFileID
s.Buttons = nil
for _, btn := range creative.Buttons {
s.Buttons = append(s.Buttons, InlineButton{Text: btn.Text, URL: btn.URL})
}
s.MediaChanged = false
s.ClearInputMode()
log.Info().
Str("creative_id", s.CreativeID).
Str("name", creative.Name).
Bool("has_text", creative.Text != "").
Bool("s_text_is_nil", s.Text == nil).
Msg("Sending creative preview and panel")
// Отправляем превью и панель управления
s.SendCreativePreview(b)
s.showManagementPanelWithDelete(b)
}
func (s *CreativeDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
if u.CallbackQuery == nil || u.CallbackQuery.Data == "" {
return
}
data := u.CallbackQuery.Data
// Обрабатываем общие callbacks (edit_text, add_button, add_media, delete_button, delete_media)
switch {
case data == "cancel", data == "back":
// Отмена - возвращаемся назад
if s.BackState != nil {
b.SetState(s.BackState, bot.EditMessage)
}
case data == "edit_text":
s.InputMode = inputModeText
s.ShowTextEditPanel(b)
case data == "add_button":
s.InputMode = inputModeButtonText
s.PendingButtonType = "invite"
s.ShowAddButtonPanel(b)
case data == "add_media":
s.InputMode = inputModeMedia
s.ShowMediaPanel(b)
case data == "delete_media":
s.MediaType = ""
s.MediaFileID = ""
s.MediaChanged = true
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
case data == "cancel_add_button":
// Отменяем добавление кнопки
s.CancelPendingButton(b)
s.showManagementPanelWithDelete(b)
case data == "button_type_invite":
s.PendingButtonType = "invite"
s.InputMode = inputModeButtonText
s.ShowAddButtonPanel(b)
case data == "button_type_custom":
s.PendingButtonType = "custom"
s.InputMode = inputModeButtonText
s.ShowAddButtonPanel(b)
case data == "cancel_edit", data == "cancel_media":
s.ClearInputMode()
s.showManagementPanelWithDelete(b)
case data == "confirm_save":
s.updateCreative(b)
case data == "delete_creative":
if !s.confirmingDelete {
// Первое нажатие - просим подтверждение
s.confirmingDelete = true
s.showManagementPanelWithDelete(b)
} else {
// Второе нажатие - удаляем
s.deleteCreative(b)
}
case data == "confirm_delete":
// Это только для безопасности, главная логика в delete_creative
s.deleteCreative(b)
case strings.HasPrefix(data, "delete_button:"):
parts := strings.Split(data, ":")
if len(parts) == 2 {
var index int
if _, err := fmt.Sscanf(parts[1], "%d", &index); err == nil {
if index >= 0 && index < len(s.Buttons) {
s.Buttons = append(s.Buttons[:index], s.Buttons[index+1:]...)
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
}
}
}
default:
// Сбрасываем флаг подтверждения удаления при любом другом действии
if s.confirmingDelete {
s.confirmingDelete = false
s.showManagementPanelWithDelete(b)
}
}
}
func (s *CreativeDetails) HandleMessage(b *bot.Bot, u *echotron.Update) {
if u.Message == nil {
return
}
switch s.InputMode {
case inputModeMedia:
if !s.SetMediaFromMessage(u.Message) {
return
}
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.AddCustomButtonPlaceholder(buttonText)
s.UpdateCreativePreview(b)
s.InputMode = inputModeButtonURL
s.ShowButtonURLPanel(b)
} else {
s.AddInviteButton(buttonText)
s.UpdateCreativePreview(b)
s.PendingButtonType = ""
s.ClearInputMode()
s.showManagementPanelWithDelete(b)
}
return
case inputModeButtonURL:
if u.Message.Text == "" {
return
}
url := strings.TrimSpace(u.Message.Text)
s.DeleteUserMessage(b, u.Message.ID)
// Простая валидация URL
if !s.IsValidButtonURL(url, true) {
s.ShowInvalidButtonURLPanel(b)
return
}
// Обновляем URL последней кнопки
s.UpdateLastButtonURL(url)
s.ClearInputMode()
s.PendingButtonType = ""
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
return
case inputModeText:
if u.Message.Text == "" {
return
}
text := ui.FormatMessageHTML(u.Message)
s.Text = &text
s.DeleteUserMessage(b, u.Message.ID)
s.UpdateCreativePreview(b)
s.ClearInputMode()
s.showManagementPanelWithDelete(b)
return
}
// Обрабатываем имя креатива (если нет других состояний)
if u.Message.Text != "" {
text := ui.FormatMessageHTML(u.Message)
s.Text = &text
s.DeleteUserMessage(b, u.Message.ID)
s.UpdateCreativePreview(b)
s.showManagementPanelWithDelete(b)
}
}
func (s *CreativeDetails) Handle(_ *bot.Bot, _ *echotron.Update) { return }
func (s *CreativeDetails) Exit() {}
func (s *CreativeDetails) showManagementPanelWithDelete(b *bot.Bot) {
var confirmText, confirmCallback string
if s.confirmingDelete {
confirmText = "✖ Подтвердить удаление"
confirmCallback = "confirm_delete"
} else {
confirmText = "✓ Сохранить"
confirmCallback = "confirm_save"
}
// Добавляем кнопку удаления креатива
deleteButton := Row(Button("⌦ Удалить креатив", "delete_creative"))
s.ShowManagementPanel(b, confirmText, confirmCallback, "cancel", deleteButton)
}
func (s *CreativeDetails) updateCreative(b *bot.Bot) {
// JWT уже создан в Bot.Update(), просто берем из сессии
jwt := b.Session.JWT
if jwt == "" {
log.Error().Msg("JWT is empty in session")
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", Keyboard())
return
}
var err error
// Подготавливаем данные для обновления
input := backend.UpdateCreativeInput{
Name: s.Name,
Text: s.Text,
}
if s.MediaChanged {
mediaType := s.MediaType
mediaFileID := s.MediaFileID
input.MediaType = &mediaType
input.MediaFileID = &mediaFileID
if s.MediaFileID != "" {
mediaData, err := b.DownloadFileBytes(s.MediaFileID)
if err != nil {
log.Error().Err(err).Msg("Failed to download creative media")
// Показываем ошибку через панель управления
s.ShowControlPanel(b, "<b>❌ Не удалось загрузить медиа</b>\n\nПопробуйте удалить медиа и добавить заново, или нажмите \"Сохранить\" без изменения медиа.", [][]echotron.InlineKeyboardButton{
Row(Button("← Назад", "back")),
Row(Button("✓ Сохранить без медиа", "confirm_save")),
})
return
}
input.MediaData = &mediaData
} else {
emptyData := []byte{}
input.MediaData = &emptyData
}
}
buttons := make([]backend.CreativeButton, 0, len(s.Buttons))
for _, button := range s.Buttons {
buttons = append(buttons, backend.CreativeButton{
Text: button.Text,
URL: button.URL,
})
}
input.Buttons = &buttons
// Отправляем запрос на обновление
_, err = b.Backend.UpdateCreative(
context.Background(),
jwt,
s.WorkspaceID,
s.CreativeID,
input,
)
if err != nil {
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to update creative")
// Показываем ошибку через панель управления
s.ShowControlPanel(b, "<b>❌ Не удалось сохранить изменения</b>\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
Row(Button("← Назад", "back")),
Row(Button("✓ Попробовать снова", "confirm_save")),
})
return
}
log.Info().Str("creative_id", s.CreativeID).Msg("Creative updated successfully")
// Возвращаемся назад
if s.BackState != nil {
b.SendNew("✅ Креатив успешно обновлен!", Keyboard())
b.SetState(s.BackState, bot.EditMessage)
}
}
func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
err := b.Backend.DeleteCreative(context.Background(), b.Session.JWT, s.WorkspaceID, s.CreativeID)
if err != nil {
s.confirmingDelete = false // Сбрасываем флаг подтверждения
s.ShowControlPanel(b, "<b>❌ Не удалось удалить креатив</b>\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
Row(Button("← Назад", "back")),
Row(Button("⌦ Попробовать снова", "delete_creative")),
})
return
}
if s.BackState != nil {
b.SendNew("✅ Креатив успешно удален!", Keyboard())
b.SetState(s.BackState, bot.EditMessage)
}
}