648 lines
19 KiB
Go
648 lines
19 KiB
Go
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 AddCreative struct {
|
||
CreativeEditorFields // Встраивание общих полей
|
||
|
||
ProjectID string
|
||
WorkspaceID string
|
||
BackState bot.State
|
||
WaitingForCreative bool // Ждем пересылки сообщения с креативом
|
||
CreativeMessageSent bool // Креатив уже получен и показан
|
||
WaitingForTextEdit bool // Ждем редактирования текста креатива
|
||
}
|
||
|
||
func (s *AddCreative) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||
text := `<i>… › Креативы › Добавление</i>
|
||
|
||
<b>+ Добавить креатив</b>
|
||
|
||
📄 <b>Пришлите креатив который хотите добавить</b>
|
||
|
||
• Обрабатываем любое сообщение Telegram
|
||
• Отправляйте сразу оформленный пост
|
||
• На следующем шаге можно добавить кнопки или изменить контент`
|
||
|
||
buttons := [][]echotron.InlineKeyboardButton{
|
||
bot.Row(bot.Button("« Отмена", "cancel")),
|
||
}
|
||
|
||
keyboard := bot.Keyboard(buttons...)
|
||
s.WaitingForCreative = true
|
||
b.Render(text, keyboard, mode)
|
||
}
|
||
|
||
func (s *AddCreative) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||
if u.CallbackQuery == nil || u.CallbackQuery.Data == "" {
|
||
return
|
||
}
|
||
|
||
data := u.CallbackQuery.Data
|
||
|
||
switch {
|
||
case data == "cancel":
|
||
if s.BackState != nil {
|
||
b.SetState(s.BackState, bot.EditMessage)
|
||
}
|
||
|
||
case data == "edit_text":
|
||
// Начинаем редактирование текста
|
||
s.WaitingForTextEdit = true
|
||
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{
|
||
bot.Row(bot.Button("« Отмена", "cancel_edit")),
|
||
})
|
||
|
||
case data == "add_button":
|
||
// Начинаем процесс добавления кнопки
|
||
s.WaitingForButtonText = true
|
||
|
||
s.ShowControlPanel(b, `<b>+ Добавить кнопку</b>
|
||
|
||
Введите <b>текст кнопки:</b>
|
||
|
||
<i>Например:</i>
|
||
• Перейти на сайт
|
||
• Написать нам`, [][]echotron.InlineKeyboardButton{
|
||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||
})
|
||
|
||
case data == "add_media":
|
||
// Начинаем процесс добавления медиа
|
||
s.WaitingForMedia = true
|
||
|
||
s.ShowControlPanel(b, `<b>+ Добавить медиа</b>
|
||
|
||
Отправьте <b>фото, видео или GIF</b>
|
||
|
||
<i>Это медиа будет отображаться в креативе вместе с текстом</i>`, [][]echotron.InlineKeyboardButton{
|
||
bot.Row(bot.Button("« Отмена", "cancel_media")),
|
||
})
|
||
|
||
case data == "delete_media":
|
||
// Удаляем медиа
|
||
s.MediaType = ""
|
||
s.MediaFileID = ""
|
||
s.MediaChanged = true
|
||
s.UpdateCreativePreview(b)
|
||
|
||
if s.CreativeMessageSent {
|
||
s.showCreativeConfirmation(b)
|
||
} else {
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
|
||
case data == "cancel_add_button":
|
||
// Отменяем добавление кнопки
|
||
// Если мы уже создали кнопку с placeholder - удаляем её
|
||
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
|
||
|
||
if s.CreativeMessageSent {
|
||
s.showCreativeConfirmation(b)
|
||
} else {
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
|
||
case data == "cancel_edit":
|
||
// Отменяем редактирование текста
|
||
s.WaitingForTextEdit = false
|
||
if s.CreativeMessageSent {
|
||
s.showCreativeConfirmation(b)
|
||
} else {
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
|
||
case data == "cancel_media":
|
||
// Отменяем добавление медиа
|
||
s.WaitingForMedia = false
|
||
if s.CreativeMessageSent {
|
||
s.showCreativeConfirmation(b)
|
||
} else {
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
|
||
case data == "confirm_create":
|
||
// Создаём креатив
|
||
s.createCreative(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)
|
||
|
||
if s.CreativeMessageSent {
|
||
s.showCreativeConfirmation(b)
|
||
} else {
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
default:
|
||
s.Enter(b, bot.NewMessage)
|
||
}
|
||
}
|
||
|
||
func (s *AddCreative) HandleMessage(b *bot.Bot, u *echotron.Update) {
|
||
if u.Message == nil {
|
||
return
|
||
}
|
||
|
||
// Шаг 1: Ждем пересылки креатива (любое сообщение)
|
||
if s.WaitingForCreative {
|
||
// Извлекаем данные из сообщения
|
||
s.extractCreativeFromMessage(u.Message)
|
||
|
||
// Генерируем название из текста или дефолтное
|
||
if s.Name == nil {
|
||
name := s.generateCreativeName()
|
||
s.Name = &name
|
||
}
|
||
|
||
// Удаляем сообщение пользователя
|
||
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("Failed to delete user message")
|
||
}
|
||
|
||
s.WaitingForCreative = false
|
||
s.CreativeMessageSent = true
|
||
|
||
// Показываем превью + панель с предложением добавить
|
||
s.showCreativeConfirmation(b)
|
||
return
|
||
}
|
||
|
||
// Обработка медиа (когда явно просим добавить медиа)
|
||
if s.WaitingForMedia {
|
||
handled := false
|
||
|
||
if u.Message.Photo != nil && len(u.Message.Photo) > 0 {
|
||
photo := u.Message.Photo[len(u.Message.Photo)-1]
|
||
s.MediaType = "photo"
|
||
s.MediaFileID = photo.FileID
|
||
s.MediaChanged = true
|
||
handled = true
|
||
} else if u.Message.Video != nil {
|
||
s.MediaType = "video"
|
||
s.MediaFileID = u.Message.Video.FileID
|
||
s.MediaChanged = true
|
||
handled = true
|
||
} else if u.Message.Animation != nil {
|
||
s.MediaType = "animation"
|
||
s.MediaFileID = u.Message.Animation.FileID
|
||
s.MediaChanged = true
|
||
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.WaitingForMedia = false
|
||
s.UpdateCreativePreview(b)
|
||
|
||
if s.CreativeMessageSent {
|
||
s.showCreativeConfirmation(b)
|
||
} else {
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
return
|
||
}
|
||
}
|
||
|
||
// Обработка добавления кнопки - текст кнопки
|
||
if s.WaitingForButtonText {
|
||
if u.Message.Text == "" {
|
||
return
|
||
}
|
||
|
||
buttonText := u.Message.Text
|
||
s.CurrentButtonText = &buttonText
|
||
s.WaitingForButtonText = false
|
||
s.WaitingForButtonURL = true
|
||
|
||
// Удаляем сообщение пользователя
|
||
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("Failed to delete user message")
|
||
}
|
||
|
||
s.Buttons = append(s.Buttons, InlineButton{
|
||
Text: buttonText,
|
||
URL: "https://example.com",
|
||
})
|
||
|
||
s.UpdateCreativePreview(b)
|
||
|
||
s.ShowControlPanel(b, `<b>✧ Введите URL для кнопки:</b>
|
||
|
||
URL должен начинаться с <code>https://</code>
|
||
|
||
<i>Например:</i>
|
||
<code>https://example.com</code>
|
||
<code>https://t.me/yourchannel</code>`, [][]echotron.InlineKeyboardButton{
|
||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||
})
|
||
return
|
||
}
|
||
|
||
// Обработка добавления кнопки - URL
|
||
if s.WaitingForButtonURL {
|
||
if u.Message.Text == "" {
|
||
return
|
||
}
|
||
|
||
url := u.Message.Text
|
||
|
||
// Удаляем сообщение пользователя
|
||
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("Failed to delete user message")
|
||
}
|
||
|
||
if !strings.HasPrefix(url, "https://") {
|
||
s.ShowControlPanel(b, `<b>❌ Неверный формат URL</b>
|
||
|
||
URL должен начинаться с <code>https://</code>
|
||
|
||
Попробуйте ещё раз.`, [][]echotron.InlineKeyboardButton{
|
||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||
})
|
||
return
|
||
}
|
||
|
||
if len(s.Buttons) > 0 {
|
||
s.Buttons[len(s.Buttons)-1].URL = url
|
||
}
|
||
s.WaitingForButtonURL = false
|
||
s.CurrentButtonText = nil
|
||
|
||
s.UpdateCreativePreview(b)
|
||
|
||
if s.CreativeMessageSent {
|
||
s.showCreativeConfirmation(b)
|
||
} else {
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
return
|
||
}
|
||
|
||
// Если ждем редактирования текста креатива
|
||
if s.WaitingForTextEdit && u.Message.Text != "" {
|
||
text := s.GetFormattedText(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.WaitingForTextEdit = false
|
||
s.UpdateCreativePreview(b)
|
||
|
||
if s.CreativeMessageSent {
|
||
s.showCreativeConfirmation(b)
|
||
} else {
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
return
|
||
}
|
||
|
||
// Если креатив уже показан и пользователь вводит текст - это изменение названия
|
||
if s.CreativeMessageSent && u.Message.Text != "" {
|
||
newName := u.Message.Text
|
||
|
||
// Ограничение 200 символов
|
||
runes := []rune(newName)
|
||
if len(runes) > 200 {
|
||
newName = string(runes[:200])
|
||
}
|
||
|
||
s.Name = &newName
|
||
|
||
// Удаляем сообщение пользователя
|
||
_, err := b.DeleteMessage(b.ChatID, u.Message.ID)
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("Failed to delete user message")
|
||
}
|
||
|
||
// Обновляем панель с новым названием
|
||
s.showCreativeConfirmation(b)
|
||
return
|
||
}
|
||
}
|
||
|
||
func (s *AddCreative) Handle(_ *bot.Bot, _ *echotron.Update) { return }
|
||
|
||
// extractCreativeFromMessage извлекает данные креатива из сообщения
|
||
func (s *AddCreative) extractCreativeFromMessage(message *echotron.Message) {
|
||
// Извлекаем текст с форматированием (GetFormattedText теперь поддерживает и text и caption)
|
||
if message.Text != "" || message.Caption != "" {
|
||
text := s.GetFormattedText(message)
|
||
if text != "" {
|
||
s.Text = &text
|
||
}
|
||
}
|
||
|
||
// Извлекаем медиа
|
||
if message.Photo != nil && len(message.Photo) > 0 {
|
||
photo := message.Photo[len(message.Photo)-1]
|
||
s.MediaType = "photo"
|
||
s.MediaFileID = photo.FileID
|
||
s.MediaChanged = true
|
||
} else if message.Video != nil {
|
||
s.MediaType = "video"
|
||
s.MediaFileID = message.Video.FileID
|
||
s.MediaChanged = true
|
||
} else if message.Animation != nil {
|
||
s.MediaType = "animation"
|
||
s.MediaFileID = message.Animation.FileID
|
||
s.MediaChanged = true
|
||
}
|
||
|
||
// Извлекаем кнопки из ReplyMarkup
|
||
if message.ReplyMarkup != nil && len(message.ReplyMarkup.InlineKeyboard) > 0 {
|
||
for _, row := range message.ReplyMarkup.InlineKeyboard {
|
||
for _, button := range row {
|
||
// Берем только кнопки с URL
|
||
if button.URL != "" {
|
||
s.Buttons = append(s.Buttons, InlineButton{
|
||
Text: button.Text,
|
||
URL: button.URL,
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// generateCreativeName генерирует название из текста или дефолтное
|
||
func (s *AddCreative) generateCreativeName() string {
|
||
// Если есть текст, берем первые слова
|
||
if s.Text != nil && *s.Text != "" {
|
||
// Удаляем HTML теги для генерации названия
|
||
cleanText := strings.ReplaceAll(*s.Text, "<b>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "</b>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "<i>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "</i>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "<u>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "</u>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "<s>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "</s>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "<code>", "")
|
||
cleanText = strings.ReplaceAll(cleanText, "</code>", "")
|
||
|
||
// Берем первые 50 символов
|
||
runes := []rune(strings.TrimSpace(cleanText))
|
||
if len(runes) > 50 {
|
||
return string(runes[:50]) + "..."
|
||
}
|
||
if len(runes) > 0 {
|
||
return string(runes)
|
||
}
|
||
}
|
||
|
||
// Дефолтное название
|
||
return "Новый креатив"
|
||
}
|
||
|
||
// showCreativeConfirmation показывает превью креатива + панель с предложением добавить
|
||
func (s *AddCreative) showCreativeConfirmation(b *bot.Bot) {
|
||
|
||
// 1. Отправляем/обновляем превью креатива
|
||
if s.CreativeMessageID == nil {
|
||
s.SendCreativePreview(b)
|
||
} else {
|
||
s.UpdateCreativePreview(b)
|
||
}
|
||
|
||
// 2. Формируем текст панели управления
|
||
confirmText := fmt.Sprintf(`<b>➕ Добавляем этот креатив?</b>
|
||
|
||
<b>Название креатива:</b> %s
|
||
|
||
<i>Если нужно изменить название креатива, просто введите его здесь. Название автоматически сохранится. Лимит 200 символов.</i>`, *s.Name)
|
||
|
||
// 3. Показываем панель с кнопками редактирования + сохранения
|
||
var buttons [][]echotron.InlineKeyboardButton
|
||
|
||
// Кнопки редактирования
|
||
buttons = append(buttons, bot.Row(
|
||
bot.Button("✎ Текст", "edit_text"),
|
||
bot.Button("+ Медиа", "add_media"),
|
||
))
|
||
buttons = append(buttons, bot.Row(bot.Button("+ Добавить кнопку", "add_button")))
|
||
|
||
// Кнопка удаления медиа (если медиа добавлено)
|
||
if s.MediaFileID != "" {
|
||
buttons = append(buttons, bot.Row(
|
||
bot.Button("⌫ Убрать медиа", "delete_media"),
|
||
))
|
||
}
|
||
|
||
// Кнопки удаления добавленных кнопок
|
||
if len(s.Buttons) > 0 {
|
||
var row []echotron.InlineKeyboardButton
|
||
for i, btn := range s.Buttons {
|
||
row = append(row, bot.Button(
|
||
fmt.Sprintf(`⌫ Убрать «%s»`, btn.Text),
|
||
fmt.Sprintf("delete_button:%d", i),
|
||
))
|
||
|
||
if len(row) == 2 {
|
||
buttons = append(buttons, row)
|
||
row = nil
|
||
}
|
||
}
|
||
|
||
if len(row) > 0 {
|
||
buttons = append(buttons, row)
|
||
}
|
||
}
|
||
|
||
// Финальные кнопки
|
||
if s.Text != nil {
|
||
buttons = append(buttons, bot.Row(
|
||
bot.Button("← Отмена", "cancel"),
|
||
bot.Button("✓ Сохранить", "confirm_create"),
|
||
))
|
||
} else {
|
||
buttons = append(buttons, bot.Row(
|
||
bot.Button("← Отмена", "cancel"),
|
||
))
|
||
}
|
||
|
||
s.ShowControlPanel(b, confirmText, buttons)
|
||
}
|
||
|
||
// sendCreativeAndPanel отправляет превью креатива и панель управления
|
||
func (s *AddCreative) sendCreativeAndPanel(b *bot.Bot) {
|
||
|
||
// 1. Отправляем превью креатива
|
||
s.SendCreativePreview(b)
|
||
|
||
// 2. Отправляем панель управления
|
||
s.ShowManagementPanel(b, "✓ Сохранить", "confirm_create", "cancel")
|
||
}
|
||
|
||
func (s *AddCreative) createCreative(b *bot.Bot) {
|
||
// Получаем JWT
|
||
jwt, err := s.ensureJWT(b)
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("Failed to get JWT")
|
||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start login", bot.Keyboard())
|
||
return
|
||
}
|
||
|
||
var mediaData []byte
|
||
if s.MediaFileID != "" {
|
||
mediaData, err = b.DownloadFileBytes(s.MediaFileID)
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("Failed to download creative media")
|
||
b.SendNew("<b>❌ Не удалось загрузить медиа</b>", bot.Keyboard(
|
||
bot.Row(bot.Button("← Назад", "cancel")),
|
||
))
|
||
return
|
||
}
|
||
}
|
||
|
||
buttons := make([]backend.CreativeButton, 0, len(s.Buttons))
|
||
for _, button := range s.Buttons {
|
||
buttons = append(buttons, backend.CreativeButton{
|
||
Text: button.Text,
|
||
URL: button.URL,
|
||
})
|
||
}
|
||
|
||
// Логируем что отправляем
|
||
log.Info().
|
||
Str("name", *s.Name).
|
||
Str("text", *s.Text).
|
||
Int("buttons_count", len(buttons)).
|
||
Interface("buttons", buttons).
|
||
Str("media_type", s.MediaType).
|
||
Msg("Creating creative with data")
|
||
|
||
// Создаём креатив через API
|
||
creative, err := b.Backend.CreateCreative(
|
||
context.Background(),
|
||
jwt,
|
||
s.WorkspaceID,
|
||
s.ProjectID,
|
||
backend.CreateCreativeInput{
|
||
Name: *s.Name,
|
||
Text: *s.Text,
|
||
MediaType: s.MediaType,
|
||
MediaFileID: s.MediaFileID,
|
||
MediaData: mediaData,
|
||
Buttons: buttons,
|
||
},
|
||
)
|
||
|
||
if err != nil {
|
||
log.Error().Err(err).Msg("Failed to create creative")
|
||
|
||
// Проверяем, это ошибка валидации инвайт-ссылки
|
||
errMsg := err.Error()
|
||
var userMsg string
|
||
|
||
if strings.Contains(errMsg, "Creative text must contain one invite link") {
|
||
userMsg = `<b>❌ Ошибка валидации</b>
|
||
|
||
Текст должен содержать <b>ОДНУ</b> инвайт-ссылку вашего канала!
|
||
|
||
<b>Формат ссылки:</b>
|
||
<code>https://t.me/+xxx</code>
|
||
|
||
<b>Пример правильного текста:</b>
|
||
<i>Присоединяйтесь к нашему каналу!
|
||
https://t.me/+AbCdEfGhIjKlMn</i>`
|
||
} else if strings.Contains(errMsg, "Creative text must contain only one invite link") {
|
||
userMsg = `<b>❌ Слишком много ссылок</b>
|
||
|
||
Текст должен содержать <b>ТОЛЬКО ОДНУ</b> инвайт-ссылку.
|
||
У вас их несколько. Удалите лишние.`
|
||
} else {
|
||
userMsg = `<b>❌ Ошибка создания</b>
|
||
|
||
Не удалось создать креатив.
|
||
Попробуйте ещё раз или вернитесь назад.`
|
||
}
|
||
|
||
b.SendNew(userMsg, bot.Keyboard(
|
||
bot.Row(bot.Button("← Назад", "back_from_preview")),
|
||
))
|
||
return
|
||
}
|
||
|
||
// Успех! Показываем сообщение и возвращаемся
|
||
successText := `<b>✅ Креатив успешно создан!</b>
|
||
|
||
📄 <b>Название:</b> ` + creative.Name
|
||
|
||
if len(s.Buttons) > 0 {
|
||
successText += fmt.Sprintf(`
|
||
🔘 <b>Кнопок добавлено:</b> %d`, len(s.Buttons))
|
||
}
|
||
|
||
b.SendNew(successText, bot.Keyboard())
|
||
|
||
// Возвращаемся на экран креативов
|
||
if s.BackState != nil {
|
||
b.SetState(s.BackState, bot.NewMessage)
|
||
}
|
||
}
|
||
|
||
func (s *AddCreative) 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
|
||
}
|