package screens import ( "context" "fmt" "strings" "example.com/m/adapter/backend" "example.com/m/bot" "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) { // Получаем JWT jwt, err := s.ensureJWT(b) if err != nil { log.Error().Err(err).Msg("Failed to get JWT") b.SendNew("❌ Ошибка авторизации\n\nПопробуйте /start", bot.Keyboard()) return } // Загружаем креатив creative, err := b.Backend.GetCreative(context.Background(), jwt, s.WorkspaceID, s.CreativeID) if err != nil { log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to get creative") b.SendNew("❌ Не удалось загрузить креатив", bot.Keyboard( bot.Row(bot.Button("« Назад", "back")), )) return } // Заполняем поля из загруженного креатива s.Name = &creative.Name if creative.Text != "" { s.Text = &creative.Text } // Buttons, MediaType, MediaFileID остаются пустыми (не сохраняются в БД) // Отправляем превью и панель управления 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.ShowControlPanel(b, `✎ Введите новый текст креатива:Важно: Текст должен содержать ОДНУ инвайт-ссылку вашего канала Формат ссылки: https://t.me/+xxx Пример: Присоединяйтесь к нашему каналу! https://t.me/+AbCdEfGhIjKlMn`, [][]echotron.InlineKeyboardButton{ bot.Row(bot.Button("« Отмена редактирования", "cancel_edit")), }) case data == "add_button": s.WaitingForButtonText = true s.ShowControlPanel(b, `+ Добавить кнопку Введите текст кнопки: Например: • Перейти на сайт • Написать нам`, [][]echotron.InlineKeyboardButton{ bot.Row(bot.Button("« Отмена", "cancel_add_button")), }) case data == "add_media": s.WaitingForMedia = true s.ShowControlPanel(b, `⧉ Добавить медиа Отправьте фото, видео или GIF Это медиа будет отображаться в креативе вместе с текстом`, [][]echotron.InlineKeyboardButton{ bot.Row(bot.Button("« Отмена", "cancel_media")), }) case data == "delete_media": s.MediaType = "" s.MediaFileID = "" s.UpdateCreativePreview(b) s.showManagementPanelWithDelete(b) 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" { s.Buttons = s.Buttons[:len(s.Buttons)-1] s.UpdateCreativePreview(b) } } s.WaitingForButtonText = false s.WaitingForButtonURL = false s.CurrentButtonText = nil s.showManagementPanelWithDelete(b) case data == "cancel_edit", data == "cancel_media": s.WaitingForMedia = false 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 } // Обрабатываем добавление медиа if s.WaitingForMedia { 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.WaitingForMedia = false s.UpdateCreativePreview(b) s.showManagementPanelWithDelete(b) return } else if u.Message.Video != nil { s.MediaType = "video" s.MediaFileID = u.Message.Video.FileID s.WaitingForMedia = false s.UpdateCreativePreview(b) s.showManagementPanelWithDelete(b) return } else if u.Message.Animation != nil { s.MediaType = "animation" s.MediaFileID = u.Message.Animation.FileID s.WaitingForMedia = false s.UpdateCreativePreview(b) s.showManagementPanelWithDelete(b) return } } // Обрабатываем ввод текста кнопки if s.WaitingForButtonText && u.Message.Text != "" { buttonText := u.Message.Text s.CurrentButtonText = &buttonText s.WaitingForButtonText = false s.WaitingForButtonURL = true // Создаём кнопку с placeholder URL s.Buttons = append(s.Buttons, InlineButton{ Text: buttonText, URL: "https://example.com", }) s.UpdateCreativePreview(b) s.ShowControlPanel(b, `✧ Введите URL для кнопки: Например: https://example.com`, [][]echotron.InlineKeyboardButton{ bot.Row(bot.Button("« Отмена", "cancel_add_button")), }) return } // Обрабатываем ввод URL кнопки if s.WaitingForButtonURL && u.Message.Text != "" { url := strings.TrimSpace(u.Message.Text) // Простая валидация URL if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") { s.ShowControlPanel(b, `❌ Неверный формат URL URL должен начинаться с http:// или https:// Попробуйте ещё раз:`, [][]echotron.InlineKeyboardButton{ bot.Row(bot.Button("« Отмена", "cancel_add_button")), }) return } // Обновляем URL последней кнопки if len(s.Buttons) > 0 { s.Buttons[len(s.Buttons)-1].URL = url } s.WaitingForButtonURL = false s.CurrentButtonText = nil s.UpdateCreativePreview(b) s.showManagementPanelWithDelete(b) return } // Обрабатываем редактирование текста креатива if !s.WaitingForButtonText && !s.WaitingForButtonURL && !s.WaitingForMedia && u.Message.Text != "" { text := s.GetFormattedText(u.Message) s.Text = &text s.UpdateCreativePreview(b) s.showManagementPanelWithDelete(b) return } // Обрабатываем имя креатива (если нет других состояний) if u.Message.Text != "" { text := s.GetFormattedText(u.Message) s.Text = &text s.UpdateCreativePreview(b) s.showManagementPanelWithDelete(b) } } func (s *CreativeDetails) Handle(_ *bot.Bot, _ *echotron.Update) { return } func (s *CreativeDetails) showManagementPanelWithDelete(b *bot.Bot) { var confirmText, confirmCallback string if s.confirmingDelete { confirmText = "✖ Подтвердить удаление" confirmCallback = "confirm_delete" } else { confirmText = "✓ Сохранить" confirmCallback = "confirm_save" } // Добавляем кнопку удаления креатива deleteButton := bot.Row(bot.Button("⌦ Удалить креатив", "delete_creative")) s.ShowManagementPanel(b, confirmText, confirmCallback, "cancel", deleteButton) } func (s *CreativeDetails) updateCreative(b *bot.Bot) { // Получаем JWT jwt, err := s.ensureJWT(b) if err != nil { log.Error().Err(err).Msg("Failed to get JWT") b.SendNew("❌ Ошибка авторизации\n\nПопробуйте /start", bot.Keyboard()) return } // Подготавливаем данные для обновления input := backend.UpdateCreativeInput{ Name: s.Name, Text: s.Text, } // Отправляем запрос на обновление _, 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") b.SendNew("❌ Не удалось сохранить изменения", bot.Keyboard( bot.Row(bot.Button("« Назад", "back")), )) return } log.Info().Str("creative_id", s.CreativeID).Msg("Creative updated successfully") // Возвращаемся назад if s.BackState != nil { b.SendNew("✅ Креатив успешно обновлен!", bot.Keyboard()) b.SetState(s.BackState, bot.NewMessage) } } func (s *CreativeDetails) deleteCreative(b *bot.Bot) { // Получаем JWT jwt, err := s.ensureJWT(b) if err != nil { log.Error().Err(err).Msg("Failed to get JWT") b.SendNew("❌ Ошибка авторизации\n\nПопробуйте /start", bot.Keyboard()) return } // Отправляем запрос на удаление err = b.Backend.DeleteCreative( context.Background(), jwt, s.WorkspaceID, s.CreativeID, ) if err != nil { log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to delete creative") b.SendNew("❌ Не удалось удалить креатив", bot.Keyboard( bot.Row(bot.Button("« Назад", "back")), )) return } log.Info().Str("creative_id", s.CreativeID).Msg("Creative deleted successfully") // Возвращаемся назад if s.BackState != nil { b.SendNew("✅ Креатив успешно удален!", bot.Keyboard()) b.SetState(s.BackState, bot.NewMessage) } } func (s *CreativeDetails) ensureJWT(b *bot.Bot) (string, error) { // Проверяем есть ли JWT в сессии if b.Session.JWT != "" { return b.Session.JWT, nil } // Если нет, запрашиваем новый jwt, err := b.Backend.GetJWTByTelegramID(context.Background(), b.ChatID) if err != nil { return "", err } // Сохраняем в сессию b.Session.JWT = jwt return jwt, nil }