фиксы

This commit is contained in:
Artem Tsyrulnikov
2026-02-01 11:10:22 +03:00
parent 44713b4fb7
commit b212b6c22f
4 changed files with 181 additions and 136 deletions

View File

@@ -52,6 +52,20 @@ func New(cfg Config) (*Client, error) {
return fmt.Errorf("session not authorized") return fmt.Errorf("session not authorized")
} }
// Получаем информацию о текущем пользователе
self, err := tg.Self(ctx)
if err != nil {
log.Warn().Err(err).Msg("Failed to get user info")
} else {
log.Info().
Int64("id", self.ID).
Str("username", "@"+self.Username).
Str("first_name", self.FirstName).
Str("last_name", self.LastName).
Str("phone", self.Phone).
Msg("Authorized as user")
}
close(ready) close(ready)
<-ctx.Done() <-ctx.Done()

View File

@@ -146,6 +146,10 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode) {
text += `Для создания закупа сначала нужно создать креатив. text += `Для создания закупа сначала нужно создать креатив.
Вернитесь назад и создайте креатив в разделе <b>Креативы</b>` Вернитесь назад и создайте креатив в разделе <b>Креативы</b>`
buttons = append(buttons, Row(
Button("🎨 Перейти к креативам", "go_to_creatives"),
))
} else { } else {
text += fmt.Sprintf("Креативы%s\n\n", ui.FormatPageInfo(s.CreativePage, page.Pages)) text += fmt.Sprintf("Креативы%s\n\n", ui.FormatPageInfo(s.CreativePage, page.Pages))
@@ -219,6 +223,17 @@ func (s *AddPurchase) HandleCallback(b *bot.Bot, u *echotron.Update) {
s.ActivePicker = "" s.ActivePicker = ""
s.Enter(b, bot.EditMessage) s.Enter(b, bot.EditMessage)
case "go_to_creatives":
b.SetState(&Creatives{
CurrentPage: 0,
ProjectID: s.ProjectID,
ProjectTitle: s.ProjectTitle,
ProjectTelegramID: s.ProjectTelegramID,
ProjectUsername: s.ProjectUsername,
ProjectStatus: s.ProjectStatus,
BackState: s,
}, bot.EditMessage)
case "prev": case "prev":
if s.ActivePicker == "project_list" { if s.ActivePicker == "project_list" {
if s.ProjectPage > 0 { if s.ProjectPage > 0 {

View File

@@ -6,7 +6,6 @@ import (
"github.com/NicoNex/echotron/v3" "github.com/NicoNex/echotron/v3"
"github.com/TelegramExchange/tgex-backend/tg_bot/bot" "github.com/TelegramExchange/tgex-backend/tg_bot/bot"
"github.com/rs/zerolog/log"
) )
type Placements struct { type Placements struct {
@@ -18,72 +17,24 @@ type Placements struct {
BackState bot.State BackState bot.State
} }
const msgPlacements = `
<b>Размещения</b>
Управление размещениями для проекта.
`
func (s *Placements) Enter(b *bot.Bot, mode bot.RenderMode) { func (s *Placements) Enter(b *bot.Bot, mode bot.RenderMode) {
s.renderPurchases(b, mode) var rows [][]echotron.InlineKeyboardButton
}
func (s *Placements) renderPurchases(b *bot.Bot, mode bot.RenderMode) { rows = append(rows, Row(Button(" Создать размещение", "add_purchase")))
resp, err := b.Backend.GetPlacements(context.Background(), b.Session.JWT, b.Session.WorkspaceID, s.ProjectID) rows = append(rows, Row(Button("Размещения", "placements_list")))
if err != nil { rows = append(rows, Row(URLButton("План закупов", "https://example.com")))
log.Error().Err(err).Msg("Failed to get placements") rows = append(rows, Row(Button("← Назад", "back")))
b.SendNew("❌ Не удалось загрузить размещения", Keyboard())
return
}
text := "<b>Размещения</b>\n\n" keyboard := Keyboard(rows...)
b.Render(msgPlacements, keyboard, mode)
var buttons [][]echotron.InlineKeyboardButton updateProjectHeaderMedia(b, b.LastMessageID, msgPlacements, keyboard, s.ProjectTelegramID, s.ProjectTitle, s.ProjectUsername, s.ProjectStatus)
if len(resp.Placements) == 0 {
text += `Список размещений пуст.
<i>Размещение это план публикации рекламы в канале:</i>
Выбор креатива
Канал размещения
Стоимость и формат
Статус выполнения
Создайте первое размещение`
} else {
text += "Управление размещениями для проекта.\n\n"
text += "Выберите размещение для деталей:\n\n"
for i, placement := range resp.Placements {
statusSymbol := "●"
if placement.Status == "archived" {
statusSymbol = "■"
}
channelName := "Без названия"
if placement.Channel.Title != nil && *placement.Channel.Title != "" {
channelName = *placement.Channel.Title
} else if placement.Channel.Username != nil && *placement.Channel.Username != "" {
channelName = "@" + *placement.Channel.Username
}
buttonText := fmt.Sprintf("%s #%d %s", statusSymbol, i+1, channelName)
buttons = append(buttons, Row(
Button(buttonText, fmt.Sprintf("placement:%s", placement.ID)),
))
}
}
// Кнопка создания нового размещения
buttons = append(buttons, Row(
Button(" Создать размещение", "add_purchase"),
))
// Кнопка назад
buttons = append(buttons, Row(
Button("← Назад", "back"),
))
keyboard := Keyboard(buttons...)
b.Render(text, keyboard, mode)
messageID := b.LastMessageID
updateProjectHeaderMedia(b, messageID, text, keyboard, s.ProjectTelegramID, s.ProjectTitle, s.ProjectUsername, s.ProjectStatus)
} }
func (s *Placements) HandleCallback(b *bot.Bot, u *echotron.Update) { func (s *Placements) HandleCallback(b *bot.Bot, u *echotron.Update) {
@@ -110,6 +61,106 @@ func (s *Placements) HandleCallback(b *bot.Bot, u *echotron.Update) {
BackState: s, BackState: s,
}, bot.EditMessage) }, bot.EditMessage)
case "placements_list":
b.SetState(&PlacementsList{
ProjectID: s.ProjectID,
ProjectTitle: s.ProjectTitle,
ProjectTelegramID: s.ProjectTelegramID,
ProjectUsername: s.ProjectUsername,
ProjectStatus: s.ProjectStatus,
BackState: s,
}, bot.EditMessage)
default:
s.Enter(b, bot.NewMessage)
}
}
func (s *Placements) HandleMessage(_ *bot.Bot, _ *echotron.Update) { return }
func (s *Placements) Handle(_ *bot.Bot, _ *echotron.Update) { return }
func (s *Placements) Exit() {}
// PlacementsList - экран со списком размещений
type PlacementsList struct {
ProjectID string
ProjectTitle string
ProjectTelegramID int64
ProjectUsername string
ProjectStatus string
BackState bot.State
}
func (s *PlacementsList) Enter(b *bot.Bot, mode bot.RenderMode) {
s.render(b, mode)
}
func (s *PlacementsList) render(b *bot.Bot, mode bot.RenderMode) {
resp, err := b.Backend.GetPlacements(context.Background(), b.Session.JWT, b.Session.WorkspaceID, s.ProjectID)
if err != nil {
b.SendNew("❌ Не удалось загрузить размещения", Keyboard())
return
}
text := "<b>Список размещений</b>\n\n"
var buttons [][]echotron.InlineKeyboardButton
if len(resp.Placements) == 0 {
text += `Список размещений пуст.
<i>Размещение это план публикации рекламы в канале:</i>
Выбор креатива
Канал размещения
Стоимость и формат
Статус выполнения
Создайте первое размещение`
} else {
for i, placement := range resp.Placements {
statusSymbol := "●"
if placement.Status == "archived" {
statusSymbol = "■"
}
channelName := "Без названия"
if placement.Channel.Title != nil && *placement.Channel.Title != "" {
channelName = *placement.Channel.Title
} else if placement.Channel.Username != nil && *placement.Channel.Username != "" {
channelName = "@" + *placement.Channel.Username
}
buttonText := fmt.Sprintf("%s #%d %s", statusSymbol, i+1, channelName)
buttons = append(buttons, Row(
Button(buttonText, fmt.Sprintf("placement:%s", placement.ID)),
))
}
}
// Кнопка назад
buttons = append(buttons, Row(
Button("← Назад", "back"),
))
keyboard := Keyboard(buttons...)
b.Render(text, keyboard, mode)
}
func (s *PlacementsList) HandleCallback(b *bot.Bot, u *echotron.Update) {
if u.CallbackQuery == nil || u.CallbackQuery.Data == "" {
return
}
data := u.CallbackQuery.Data
switch data {
case "back":
if s.BackState != nil {
b.SetState(s.BackState, bot.EditMessage)
}
default: default:
// Проверяем, не нажали ли на конкретное размещение // Проверяем, не нажали ли на конкретное размещение
if len(data) > 10 && data[:10] == "placement:" { if len(data) > 10 && data[:10] == "placement:" {
@@ -125,8 +176,8 @@ func (s *Placements) HandleCallback(b *bot.Bot, u *echotron.Update) {
} }
} }
func (s *Placements) HandleMessage(_ *bot.Bot, _ *echotron.Update) { return } func (s *PlacementsList) HandleMessage(_ *bot.Bot, _ *echotron.Update) { return }
func (s *Placements) Handle(_ *bot.Bot, _ *echotron.Update) { return } func (s *PlacementsList) Handle(_ *bot.Bot, _ *echotron.Update) { return }
func (s *Placements) Exit() {} func (s *PlacementsList) Exit() {}

View File

@@ -14,13 +14,12 @@ import (
type ProjectDetails struct { type ProjectDetails struct {
Project *backend.Project Project *backend.Project
BackState bot.State BackState bot.State
confirmingLinkTypeChange bool // Флаг для двухшагового подтверждения смены типа ссылки
} }
func (s *ProjectDetails) Enter(b *bot.Bot, mode bot.RenderMode) { func (s *ProjectDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
if s.Project == nil { if s.Project == nil {
b.SendNew("❌ Проект не найден", Keyboard( b.SendNew("❌ Проект не найден", Keyboard(
Row(Button("← Назад", "back_to_projects")), Row(Button("← Назад", "back")),
)) ))
return return
} }
@@ -40,32 +39,25 @@ func (s *ProjectDetails) formatProjectDetails() string {
text := fmt.Sprintf("<b>%s</b>\n\n", p.Title) text := fmt.Sprintf("<b>%s</b>\n\n", p.Title)
// Информация о канале
if p.Username != nil && *p.Username != "" { if p.Username != nil && *p.Username != "" {
text += fmt.Sprintf("▸ Telegram: @%s\n", *p.Username) text += fmt.Sprintf("▸ Tg username: @%s\n", *p.Username)
} else { } else {
text += "▸ Telegram: <i>не привязан</i>\n" text += "▸ Tg username: <i>не привязан</i>\n"
} }
// Статус проекта // Статус проекта
var statusSymbol string var statusSymbol, statusText string
var statusText string
switch p.Status { switch p.Status {
case "active": case "active":
statusSymbol = "●" statusSymbol, statusText = "●", "Активный"
statusText = "Активный"
case "inactive": case "inactive":
statusSymbol = "○" statusSymbol, statusText = "○", "Неактивен"
statusText = "Неактивен"
case "archived": case "archived":
statusSymbol = "■" statusSymbol, statusText = "■", "Архивный"
statusText = "Архивный"
case "paused": case "paused":
statusSymbol = "◐" statusSymbol, statusText = "◐", "Приостановлен"
statusText = "Приостановлен"
default: default:
statusSymbol = "○" statusSymbol, statusText = "○", p.Status
statusText = p.Status
} }
text += fmt.Sprintf("▸ Статус: %s %s\n", statusSymbol, statusText) text += fmt.Sprintf("▸ Статус: %s %s\n", statusSymbol, statusText)
@@ -82,34 +74,20 @@ func (s *ProjectDetails) buildKeyboard() echotron.InlineKeyboardMarkup {
Button("Размещения", fmt.Sprintf("placements:%s", s.Project.ID)), Button("Размещения", fmt.Sprintf("placements:%s", s.Project.ID)),
)) ))
// Второй ряд: Тип вступления по умолчанию для закупов (с подтверждением) // Второй ряд: Тип вступления по умолчанию для закупов
var linkTypeButtonText string
if s.confirmingLinkTypeChange {
// Показываем подтверждение
var newType string
if s.Project.PurchaseInviteTypeDefault == "public" {
newType = "По заявке"
} else {
newType = "Свободное"
}
linkTypeButtonText = fmt.Sprintf("Изменить → %s", newType)
} else {
// Показываем текущий тип вступления
var currentType string var currentType string
if s.Project.PurchaseInviteTypeDefault == "public" { if s.Project.PurchaseInviteTypeDefault == "public" {
currentType = "Свободное" currentType = "открытая"
} else { } else {
currentType = "По заявке" currentType = "с заявками"
}
linkTypeButtonText = fmt.Sprintf("Вступление: %s", currentType)
} }
buttons = append(buttons, Row( buttons = append(buttons, Row(
Button(linkTypeButtonText, fmt.Sprintf("link_type:%s", s.Project.ID)), Button(fmt.Sprintf("Ссылка: %s", currentType), fmt.Sprintf("link_type:%s", s.Project.ID)),
)) ))
// Нижний ряд: Назад и Архивировать // Нижний ряд: Назад и Архивировать
buttons = append(buttons, Row( buttons = append(buttons, Row(
Button("← Назад", "back_to_projects"), Button("← Назад", "back"),
Button("≡ Архивировать", fmt.Sprintf("archive:%s", s.Project.ID)), Button("≡ Архивировать", fmt.Sprintf("archive:%s", s.Project.ID)),
)) ))
@@ -124,14 +102,12 @@ func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
data := u.CallbackQuery.Data data := u.CallbackQuery.Data
switch { switch {
case data == "back_to_projects", data == "back": case data == "back":
s.confirmingLinkTypeChange = false // Сбрасываем флаг подтверждения
if s.BackState != nil { if s.BackState != nil {
s.transitionWithNewMessage(b, s.BackState) s.transitionWithNewMessage(b, s.BackState)
} }
case strings.HasPrefix(data, "creatives:"): case strings.HasPrefix(data, "creatives:"):
s.confirmingLinkTypeChange = false // Сбрасываем флаг подтверждения
b.SetState(&Creatives{ b.SetState(&Creatives{
ProjectID: s.Project.ID, ProjectID: s.Project.ID,
ProjectTitle: s.Project.Title, ProjectTitle: s.Project.Title,
@@ -142,7 +118,6 @@ func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
}, bot.EditMessage) }, bot.EditMessage)
case strings.HasPrefix(data, "placements:"): case strings.HasPrefix(data, "placements:"):
s.confirmingLinkTypeChange = false // Сбрасываем флаг подтверждения
b.SetState(&Placements{ b.SetState(&Placements{
ProjectID: s.Project.ID, ProjectID: s.Project.ID,
ProjectTitle: s.Project.Title, ProjectTitle: s.Project.Title,
@@ -153,16 +128,9 @@ func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
}, bot.EditMessage) }, bot.EditMessage)
case data == "back_to_project": case data == "back_to_project":
s.confirmingLinkTypeChange = false // Сбрасываем флаг подтверждения
s.Enter(b, bot.EditMessage) s.Enter(b, bot.EditMessage)
case strings.HasPrefix(data, "link_type:"): case strings.HasPrefix(data, "link_type:"):
if !s.confirmingLinkTypeChange {
// Первое нажатие - включаем режим подтверждения
s.confirmingLinkTypeChange = true
s.Enter(b, bot.EditMessage)
} else {
// Второе нажатие - меняем тип ссылки
var newType string var newType string
if s.Project.PurchaseInviteTypeDefault == "public" { if s.Project.PurchaseInviteTypeDefault == "public" {
newType = "approval" newType = "approval"
@@ -179,11 +147,8 @@ func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
return return
} }
// Обновляем проект и сбрасываем флаг подтверждения
s.Project = updatedProject s.Project = updatedProject
s.confirmingLinkTypeChange = false
s.Enter(b, bot.EditMessage) s.Enter(b, bot.EditMessage)
}
default: default:
// Для остальных кнопок показываем заглушку // Для остальных кнопок показываем заглушку