diff --git a/telegram_bot/screens/add_creative.go b/telegram_bot/screens/add_creative.go
index bba187a..fc9dfd5 100644
--- a/telegram_bot/screens/add_creative.go
+++ b/telegram_bot/screens/add_creative.go
@@ -24,8 +24,6 @@ type AddCreative struct {
}
const msgWaitCreative = `
-… › Креативы › Добавление
-
+ Добавить креатив
📄 Пришлите креатив который хотите добавить
diff --git a/telegram_bot/screens/add_project.go b/telegram_bot/screens/add_project.go
index bb46f1e..3f3460b 100644
--- a/telegram_bot/screens/add_project.go
+++ b/telegram_bot/screens/add_project.go
@@ -31,6 +31,7 @@ type AddProject struct {
func (s *AddProject) Enter(b *bot.Bot, mode bot.RenderMode) {
buttons := [][]echotron.InlineKeyboardButton{
Row(Button("← Назад", "back")),
+ Row(URLButton("Добавить", "tg://resolve?domain=chat_cruiser_bot&startchannel&admin=invite_users+post_messages+edit_messages+delete_messages")),
}
keyboard := Keyboard(buttons...)
diff --git a/telegram_bot/screens/add_purchase.go b/telegram_bot/screens/add_purchase.go
index 8375af1..ee3c738 100644
--- a/telegram_bot/screens/add_purchase.go
+++ b/telegram_bot/screens/add_purchase.go
@@ -40,7 +40,7 @@ func (s *AddPurchase) Enter(b *bot.Bot, mode bot.RenderMode) {
}
func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt string) {
- text := "… › Закупы › Создание\n\nСоздание закупа\n\n"
+ text := "Создание закупа\n\n"
text += "Выберите проект и креатив.\n\n"
var buttons [][]echotron.InlineKeyboardButton
diff --git a/telegram_bot/screens/creative_details.go b/telegram_bot/screens/creative_details.go
index 93d7d87..cbac406 100644
--- a/telegram_bot/screens/creative_details.go
+++ b/telegram_bot/screens/creative_details.go
@@ -24,15 +24,7 @@ type CreativeDetails struct {
}
func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
- jwt := b.Session.JWT
- if jwt == "" {
- log.Error().Msg("JWT is empty in session")
- b.SendNew("❌ Ошибка авторизации\n\nПопробуйте /start", Keyboard())
- return
- }
-
- // Загружаем креатив
- creative, err := b.Backend.GetCreative(context.Background(), jwt, s.WorkspaceID, s.CreativeID)
+ 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("❌ Не удалось загрузить креатив", Keyboard(
@@ -476,25 +468,9 @@ func (s *CreativeDetails) updateCreative(b *bot.Bot) {
}
func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
- // JWT уже создан в Bot.Update(), просто берем из сессии
- jwt := b.Session.JWT
- if jwt == "" {
- log.Error().Msg("JWT is empty in session")
- b.SendNew("❌ Ошибка авторизации\n\nПопробуйте /start", Keyboard())
- return
- }
-
- // Отправляем запрос на удаление
- err := b.Backend.DeleteCreative(
- context.Background(),
- jwt,
- s.WorkspaceID,
- s.CreativeID,
- )
+ err := b.Backend.DeleteCreative(context.Background(), b.Session.JWT, s.WorkspaceID, s.CreativeID)
if err != nil {
- log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to delete creative")
- // Показываем ошибку через панель управления
s.confirmingDelete = false // Сбрасываем флаг подтверждения
s.ShowControlPanel(b, "❌ Не удалось удалить креатив\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
Row(Button("← Назад", "back")),
@@ -503,9 +479,6 @@ func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
return
}
- log.Info().Str("creative_id", s.CreativeID).Msg("Creative deleted successfully")
-
- // Возвращаемся назад
if s.BackState != nil {
b.SendNew("✅ Креатив успешно удален!", Keyboard())
b.SetState(s.BackState, bot.EditMessage)
diff --git a/telegram_bot/screens/creatives.go b/telegram_bot/screens/creatives.go
index d56fd43..557f651 100644
--- a/telegram_bot/screens/creatives.go
+++ b/telegram_bot/screens/creatives.go
@@ -14,11 +14,10 @@ import (
const creativesPerPage = 6
type Creatives struct {
- CurrentPage int
- ProjectID string
- ProjectTitle string
- WorkspaceID string
- BackState bot.State
+ CurrentPage int
+ ProjectID string
+ WorkspaceID string
+ BackState bot.State
}
func (s *Creatives) Enter(b *bot.Bot, mode bot.RenderMode) {
@@ -52,16 +51,8 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
var text string
var buttons [][]echotron.InlineKeyboardButton
- // Навигация: 2 последних уровня
- navPath := "… › Креативы"
- if s.ProjectTitle != "" {
- navPath = fmt.Sprintf("… › %s › Креативы", s.ProjectTitle)
- }
-
if len(page.Items) == 0 {
- text = fmt.Sprintf(`%s
-
-Креативы
+ text = `Креативы
У вас пока нет креативов
@@ -71,16 +62,13 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
‣ Шаблон для автопостинга
‣ Варианты для A/B тестирования
-Начните с добавления первого креатива`, navPath)
+Начните с добавления первого креатива`
} else {
- text = fmt.Sprintf(`%s
-
-Креативы%s
+ text = fmt.Sprintf(`Креативы%s
Управление рекламными материалами
Выберите креатив`,
- navPath,
ui.FormatPageInfo(s.CurrentPage, page.Pages))
}
diff --git a/telegram_bot/screens/login.go b/telegram_bot/screens/login.go
index 03ad136..5bf6837 100644
--- a/telegram_bot/screens/login.go
+++ b/telegram_bot/screens/login.go
@@ -12,7 +12,7 @@ type Login struct{}
const msgHelloLogin = `
Привет, %s!
-👇Нажми кнопку для входа на сайт.
+Нажми кнопку для входа на сайт. 👇
`
func (s *Login) Enter(b *bot.Bot, mode bot.RenderMode) {
diff --git a/telegram_bot/screens/my_projects.go b/telegram_bot/screens/my_projects.go
index 93efc5c..ce10a7d 100644
--- a/telegram_bot/screens/my_projects.go
+++ b/telegram_bot/screens/my_projects.go
@@ -82,7 +82,6 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
var text string
var buttons [][]echotron.InlineKeyboardButton
- navTitle := "Главное меню › Мои проекты"
headerTitle := "Мои проекты"
emptyStateText := `У вас пока нет подключенных каналов.
@@ -96,24 +95,18 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
Начните с добавления первого проекта`
nonEmptyText := "Управление вашими Telegram-каналами.\n\nВыберите канал"
if s.OpenPurchases {
- navTitle = "Главное меню › Закупы"
headerTitle = "Закупы"
nonEmptyText = "Выберите проект для просмотра закупов"
}
if len(page.Items) == 0 {
- text = fmt.Sprintf(`%s
+ text = fmt.Sprintf(`%s
-%s
-
-%s`, navTitle, headerTitle, emptyStateText)
+%s`, headerTitle, emptyStateText)
} else {
- text = fmt.Sprintf(`%s
-
-%s%s
+ text = fmt.Sprintf(`%s%s
%s`,
- navTitle,
headerTitle,
ui.FormatPageInfo(s.CurrentPage, page.Pages),
nonEmptyText)
diff --git a/telegram_bot/screens/project_details.go b/telegram_bot/screens/project_details.go
index ea8c1bc..f6cb637 100644
--- a/telegram_bot/screens/project_details.go
+++ b/telegram_bot/screens/project_details.go
@@ -34,9 +34,7 @@ func (s *ProjectDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
func (s *ProjectDetails) formatProjectDetails() string {
p := s.Project
- // Хлебные крошки и заголовок
- text := fmt.Sprintf("… › Мои проекты › %s\n\n", p.Title)
- text += fmt.Sprintf("%s\n\n", p.Title)
+ text := fmt.Sprintf("%s\n\n", p.Title)
// Информация о канале
if p.Username != nil && *p.Username != "" {
@@ -132,10 +130,9 @@ func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
case strings.HasPrefix(data, "creatives:"):
s.confirmingLinkTypeChange = false // Сбрасываем флаг подтверждения
b.SetState(&Creatives{
- ProjectID: s.Project.ID,
- ProjectTitle: s.Project.Title,
- WorkspaceID: s.WorkspaceID,
- BackState: s,
+ ProjectID: s.Project.ID,
+ WorkspaceID: s.WorkspaceID,
+ BackState: s,
}, bot.EditMessage)
case strings.HasPrefix(data, "purchases:"):
diff --git a/telegram_bot/screens/purchase_details.go b/telegram_bot/screens/purchase_details.go
index f09abea..f5f648d 100644
--- a/telegram_bot/screens/purchase_details.go
+++ b/telegram_bot/screens/purchase_details.go
@@ -12,11 +12,10 @@ import (
)
type PurchaseDetails struct {
- WorkspaceID string
- ProjectID string
- ProjectTitle string
- PurchaseID string
- BackState bot.State
+ WorkspaceID string
+ ProjectID string
+ PurchaseID string
+ BackState bot.State
}
func (s *PurchaseDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
@@ -63,7 +62,7 @@ func (s *PurchaseDetails) renderDetails(b *bot.Bot, mode bot.RenderMode, jwt str
totalPlacements += len(items)
}
- text := "… › Закупы › Детали\n\nДетали закупа\n\n"
+ text := "Детали закупа\n\n"
// Информация о креативе
if creative != nil {
diff --git a/telegram_bot/screens/purchase_optional_details.go b/telegram_bot/screens/purchase_optional_details.go
index 0e77f9c..bdb8136 100644
--- a/telegram_bot/screens/purchase_optional_details.go
+++ b/telegram_bot/screens/purchase_optional_details.go
@@ -70,8 +70,7 @@ func (s *PurchaseOptionalDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
return
}
- text := "… › Создание › Дополнительно\n\n"
- text += "Страница создания закупа и необязательные составляющие"
+ text := "Страница создания закупа и необязательные составляющие"
text += "\n\n"
text += s.formatOptionalSummary()
@@ -129,7 +128,7 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
}
case "opt_payment_date":
- b.SetState(NewDateTimePicker(DateTimePickerConfig{
+ b.SetState(ui.NewDateTimePicker(ui.DateTimePickerConfig{
Title: "Дата оплаты",
Key: "payment_date",
IncludeTime: true,
@@ -380,8 +379,8 @@ func (s *PurchaseOptionalDetails) formatDateTime(value *time.Time) string {
if value == nil {
return "—"
}
- local := value.In(mskLocation)
- return fmt.Sprintf("%s %02d %s %s", weekdayName(local.Weekday()), local.Day(), monthShort(local.Month()), local.Format("15:04"))
+ local := value.In(ui.MskLocation)
+ return fmt.Sprintf("%s %02d %s %s", ui.WeekdayName(local.Weekday()), local.Day(), ui.MonthShort(local.Month()), local.Format("15:04"))
}
func (s *PurchaseOptionalDetails) formatText(value string) string {
@@ -500,7 +499,7 @@ func (s *PurchaseOptionalDetails) costBeforeDeleteRow() []echotron.InlineKeyboar
}
func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderMode) {
- text := "… › Создание › Дополнительно\n\n"
+ text := "Дополнительно\n\n"
switch s.InputMode {
case "type":
text += "Выберите тип закупа"
@@ -598,8 +597,7 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
}
func (s *PurchaseOptionalDetails) renderModeSelect(b *bot.Bot, mode bot.RenderMode) {
- text := "… › Создание › Дополнительно › Выбор режима\n\n"
- text += "⚙ Переключить режим параметра\n\n"
+ text := "⚙ Переключить режим параметра\n\n"
// Показываем текущую информацию о параметрах
text += s.formatOptionalSummary()
@@ -671,8 +669,7 @@ func (s *PurchaseOptionalDetails) renderParamScreens(b *bot.Bot, mode bot.Render
func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.RenderMode, param string) {
s.CurrentParam = param
- text := "… › Создание › Дополнительно\n\n"
- text += fmt.Sprintf("%s — по каналам\n\n", s.paramTitle(param))
+ text := fmt.Sprintf("%s — по каналам\n\n", s.paramTitle(param))
text += s.renderParamChannelSummary(param) + "\n\n"
const perPage = 5
@@ -866,7 +863,10 @@ func (s *PurchaseOptionalDetails) parseParamIndex(data, prefix string) (string,
return "", 0, false
}
param := parts[0]
- index := parseInt(parts[1])
+ index, err := strconv.Atoi(parts[1])
+ if err != nil {
+ return "", 0, false
+ }
return param, index, true
}
@@ -875,7 +875,7 @@ func (s *PurchaseOptionalDetails) openCommonEditor(b *bot.Bot, param string) {
case "placement":
s.InputMode = ""
s.ReturnMode = ""
- b.SetState(NewDateTimePicker(DateTimePickerConfig{
+ b.SetState(ui.NewDateTimePicker(ui.DateTimePickerConfig{
Title: "Дата и время размещения",
Key: "placement_datetime",
IncludeTime: true,
@@ -907,7 +907,7 @@ func (s *PurchaseOptionalDetails) openChannelEditor(b *bot.Bot, param, username
switch param {
case "placement":
s.InputMode = "placement_channels"
- b.SetState(NewDateTimePicker(DateTimePickerConfig{
+ b.SetState(ui.NewDateTimePicker(ui.DateTimePickerConfig{
Title: "Дата и время размещения",
Key: "placement_datetime:" + username,
IncludeTime: true,
@@ -968,7 +968,7 @@ func (s *PurchaseOptionalDetails) pasteParamValue(param, username string) {
s.PlacementByChannel[username] = nil
return
}
- value := s.PlacementCopy.In(mskLocation)
+ value := s.PlacementCopy.In(ui.MskLocation)
s.PlacementByChannel[username] = &value
case "cost":
if s.CostCopy == nil {
@@ -1467,11 +1467,10 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
}
b.SetState(&PurchaseDetails{
- WorkspaceID: s.WorkspaceID,
- ProjectID: s.ProjectID,
- ProjectTitle: s.ProjectTitle,
- PurchaseID: purchase.ID,
- BackState: s.BackState,
+ WorkspaceID: s.WorkspaceID,
+ ProjectID: s.ProjectID,
+ PurchaseID: purchase.ID,
+ BackState: s.BackState,
}, bot.NewMessage)
}
diff --git a/telegram_bot/screens/purchases.go b/telegram_bot/screens/purchases.go
index fc76e9c..0b85c7c 100644
--- a/telegram_bot/screens/purchases.go
+++ b/telegram_bot/screens/purchases.go
@@ -40,12 +40,7 @@ func (s *Purchases) renderPurchases(b *bot.Bot, mode bot.RenderMode, jwt string)
return
}
- // Навигация: 2 последних уровня
- navPath := "… › Закупы"
- if s.ProjectTitle != "" {
- navPath = fmt.Sprintf("… › %s › Закупы", s.ProjectTitle)
- }
- text := fmt.Sprintf("%s\n\nЗакупы\n\n", navPath)
+ text := "Закупы\n\n"
var buttons [][]echotron.InlineKeyboardButton
@@ -136,11 +131,10 @@ func (s *Purchases) HandleCallback(b *bot.Bot, u *echotron.Update) {
if len(data) > 9 && data[:9] == "purchase:" {
purchaseID := data[9:]
b.SetState(&PurchaseDetails{
- WorkspaceID: s.WorkspaceID,
- ProjectID: s.ProjectID,
- ProjectTitle: s.ProjectTitle,
- PurchaseID: purchaseID,
- BackState: s,
+ WorkspaceID: s.WorkspaceID,
+ ProjectID: s.ProjectID,
+ PurchaseID: purchaseID,
+ BackState: s,
}, bot.EditMessage)
} else {
s.Enter(b, bot.NewMessage)
diff --git a/telegram_bot/screens/select_channels_for_purchase.go b/telegram_bot/screens/select_channels_for_purchase.go
index 689d893..84f408b 100644
--- a/telegram_bot/screens/select_channels_for_purchase.go
+++ b/telegram_bot/screens/select_channels_for_purchase.go
@@ -38,8 +38,7 @@ func (s *SelectChannelsForPurchase) Enter(b *bot.Bot, mode bot.RenderMode) {
}
func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.RenderMode) {
- text := "… › Создание › Каналы\n\n"
- text += "Создание закупа\n\n"
+ text := "Создание закупа\n\n"
text += "Шаг 2/2: Добавление каналов\n\n"
var buttons [][]echotron.InlineKeyboardButton
@@ -268,11 +267,10 @@ func (s *SelectChannelsForPurchase) createPurchase(b *bot.Bot, jwt string) {
}
b.SetState(&PurchaseDetails{
- WorkspaceID: s.WorkspaceID,
- ProjectID: s.ProjectID,
- ProjectTitle: s.ProjectTitle,
- PurchaseID: purchase.ID,
- BackState: s.BackState,
+ WorkspaceID: s.WorkspaceID,
+ ProjectID: s.ProjectID,
+ PurchaseID: purchase.ID,
+ BackState: s.BackState,
}, bot.EditMessage)
}
diff --git a/telegram_bot/screens/date_time_picker.go b/telegram_bot/ui/date_time_picker.go
similarity index 77%
rename from telegram_bot/screens/date_time_picker.go
rename to telegram_bot/ui/date_time_picker.go
index 7cd515e..7e8e7b8 100644
--- a/telegram_bot/screens/date_time_picker.go
+++ b/telegram_bot/ui/date_time_picker.go
@@ -1,4 +1,4 @@
-package screens
+package ui
import (
"fmt"
@@ -18,7 +18,7 @@ const (
dtpCallbackPrefix = "dtp:"
)
-var mskLocation = time.FixedZone("MSK", 3*60*60)
+var MskLocation = time.FixedZone("MSK", 3*60*60)
type DateTimePickerConfig struct {
Title string
@@ -47,13 +47,13 @@ type DateTimePicker struct {
}
func NewDateTimePicker(cfg DateTimePickerConfig) *DateTimePicker {
- now := time.Now().In(mskLocation)
+ now := time.Now().In(MskLocation)
year := now.Year()
month := now.Month()
var selected *time.Time
if cfg.Selected != nil {
- value := cfg.Selected.In(mskLocation)
+ value := cfg.Selected.In(MskLocation)
selected = &value
year = value.Year()
month = value.Month()
@@ -72,9 +72,8 @@ func NewDateTimePicker(cfg DateTimePickerConfig) *DateTimePicker {
}
func (s *DateTimePicker) Enter(b *bot.Bot, mode bot.RenderMode) {
- text := fmt.Sprintf("… › %s\n\n", s.Title)
- text += fmt.Sprintf("%s\n", s.formatSelectedDateTime())
- text += "\n"
+ text := fmt.Sprintf("%s\n\n", s.Title)
+ text += fmt.Sprintf("%s\n\n", s.formatSelectedDateTime())
switch s.view {
case pickerViewCalendar:
text += s.renderCalendarTitle()
@@ -166,7 +165,7 @@ func (s *DateTimePicker) handleBack(b *bot.Bot) {
}
func (s *DateTimePicker) renderCalendarTitle() string {
- if s.year == time.Now().In(mskLocation).Year() {
+ if s.year == time.Now().In(MskLocation).Year() {
return "\n"
}
return fmt.Sprintf("%d\n\n", s.year)
@@ -175,43 +174,46 @@ func (s *DateTimePicker) renderCalendarTitle() string {
func (s *DateTimePicker) calendarKeyboard() echotron.InlineKeyboardMarkup {
var rows [][]echotron.InlineKeyboardButton
- rows = append(rows, Row(
+ rows = append(rows, []echotron.InlineKeyboardButton{
s.monthPrevButton(),
- Button(monthName(s.month), dtpCallbackPrefix+"open_months"),
- Button("→", dtpCallbackPrefix+"month_next"),
- ))
+ {Text: monthName(s.month), CallbackData: dtpCallbackPrefix + "open_months"},
+ {Text: "→", CallbackData: dtpCallbackPrefix + "month_next"},
+ })
- rows = append(rows, Row(
- Button("Пн", "empty"),
- Button("Вт", "empty"),
- Button("Ср", "empty"),
- Button("Чт", "empty"),
- Button("Пт", "empty"),
- Button("Сб", "empty"),
- Button("Вс", "empty"),
- ))
+ rows = append(rows, []echotron.InlineKeyboardButton{
+ {Text: "Пн", CallbackData: "empty"},
+ {Text: "Вт", CallbackData: "empty"},
+ {Text: "Ср", CallbackData: "empty"},
+ {Text: "Чт", CallbackData: "empty"},
+ {Text: "Пт", CallbackData: "empty"},
+ {Text: "Сб", CallbackData: "empty"},
+ {Text: "Вс", CallbackData: "empty"},
+ })
- firstOfMonth := time.Date(s.year, s.month, 1, 0, 0, 0, 0, mskLocation)
+ firstOfMonth := time.Date(s.year, s.month, 1, 0, 0, 0, 0, MskLocation)
weekday := int(firstOfMonth.Weekday())
if weekday == 0 {
weekday = 7
}
daysInMonth := daysInMonth(s.year, s.month)
- today := time.Now().In(mskLocation)
- todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, mskLocation)
+ today := time.Now().In(MskLocation)
+ todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, MskLocation)
var row []echotron.InlineKeyboardButton
for i := 1; i < weekday; i++ {
- row = append(row, Button(" ", "empty"))
+ row = append(row, echotron.InlineKeyboardButton{Text: " ", CallbackData: "empty"})
}
for day := 1; day <= daysInMonth; day++ {
- date := time.Date(s.year, s.month, day, 0, 0, 0, 0, mskLocation)
+ date := time.Date(s.year, s.month, day, 0, 0, 0, 0, MskLocation)
if date.Before(todayDate) {
- row = append(row, Button("-", "empty"))
+ row = append(row, echotron.InlineKeyboardButton{Text: "-", CallbackData: "empty"})
} else {
label := fmt.Sprintf("%d", day)
- row = append(row, Button(label, fmt.Sprintf("%sday:%04d-%02d-%02d", dtpCallbackPrefix, s.year, int(s.month), day)))
+ row = append(row, echotron.InlineKeyboardButton{
+ Text: label,
+ CallbackData: fmt.Sprintf("%sday:%04d-%02d-%02d", dtpCallbackPrefix, s.year, int(s.month), day),
+ })
}
if len(row) == 7 {
rows = append(rows, row)
@@ -221,17 +223,17 @@ func (s *DateTimePicker) calendarKeyboard() echotron.InlineKeyboardMarkup {
if len(row) > 0 {
for len(row) < 7 {
- row = append(row, Button(" ", "empty"))
+ row = append(row, echotron.InlineKeyboardButton{Text: " ", CallbackData: "empty"})
}
rows = append(rows, row)
}
- rows = append(rows, Row(
- Button("← Назад", "back"),
- Button("Отмена", "cancel"),
- ))
+ rows = append(rows, []echotron.InlineKeyboardButton{
+ {Text: "← Назад", CallbackData: "back"},
+ {Text: "Отмена", CallbackData: "cancel"},
+ })
- return Keyboard(rows...)
+ return echotron.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (s *DateTimePicker) monthsKeyboard() echotron.InlineKeyboardMarkup {
@@ -239,59 +241,61 @@ func (s *DateTimePicker) monthsKeyboard() echotron.InlineKeyboardMarkup {
months := []string{"Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"}
for i := 0; i < 12; i += 3 {
- rows = append(rows, Row(
- Button(months[i], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+1)),
- Button(months[i+1], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+2)),
- Button(months[i+2], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+3)),
- ))
+ rows = append(rows, []echotron.InlineKeyboardButton{
+ {Text: months[i], CallbackData: fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+1)},
+ {Text: months[i+1], CallbackData: fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+2)},
+ {Text: months[i+2], CallbackData: fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+3)},
+ })
}
- rows = append(rows, Row(
- Button("← Назад", "back"),
- ))
+ rows = append(rows, []echotron.InlineKeyboardButton{
+ {Text: "← Назад", CallbackData: "back"},
+ })
- return Keyboard(rows...)
+ return echotron.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (s *DateTimePicker) timeCombinedKeyboard() echotron.InlineKeyboardMarkup {
var rows [][]echotron.InlineKeyboardButton
- rows = append(rows, Row(Button("Часы", "empty")))
+ rows = append(rows, []echotron.InlineKeyboardButton{{Text: "Часы", CallbackData: "empty"}})
for i := 0; i < 24; i += 6 {
- rows = append(rows, Row(
+ rows = append(rows, []echotron.InlineKeyboardButton{
s.hourButton(i),
- s.hourButton(i+1),
- s.hourButton(i+2),
- s.hourButton(i+3),
- s.hourButton(i+4),
- s.hourButton(i+5),
- ))
+ s.hourButton(i + 1),
+ s.hourButton(i + 2),
+ s.hourButton(i + 3),
+ s.hourButton(i + 4),
+ s.hourButton(i + 5),
+ })
}
- rows = append(rows, Row(Button("Минуты", "empty")))
+ rows = append(rows, []echotron.InlineKeyboardButton{{Text: "Минуты", CallbackData: "empty"}})
minutes := []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}
for i := 0; i < len(minutes); i += 6 {
- rows = append(rows, Row(
+ rows = append(rows, []echotron.InlineKeyboardButton{
s.minuteButton(minutes[i]),
s.minuteButton(minutes[i+1]),
s.minuteButton(minutes[i+2]),
s.minuteButton(minutes[i+3]),
s.minuteButton(minutes[i+4]),
s.minuteButton(minutes[i+5]),
- ))
+ })
}
- rows = append(rows, Row(
- Button("← Назад", "back"),
- Button("Готово", dtpCallbackPrefix+"confirm"),
- ))
- return Keyboard(rows...)
+ rows = append(rows, []echotron.InlineKeyboardButton{
+ {Text: "← Назад", CallbackData: "back"},
+ {Text: "Готово", CallbackData: dtpCallbackPrefix + "confirm"},
+ })
+ return echotron.InlineKeyboardMarkup{InlineKeyboard: rows}
}
func (s *DateTimePicker) confirmKeyboard() echotron.InlineKeyboardMarkup {
- return Keyboard(
- Row(
- Button("← Назад", "back"),
- Button("Готово", dtpCallbackPrefix+"confirm"),
- ),
- )
+ return echotron.InlineKeyboardMarkup{
+ InlineKeyboard: [][]echotron.InlineKeyboardButton{
+ {
+ {Text: "← Назад", CallbackData: "back"},
+ {Text: "Готово", CallbackData: dtpCallbackPrefix + "confirm"},
+ },
+ },
+ }
}
func (s *DateTimePicker) renderSelectedDateTime() string {
@@ -306,7 +310,7 @@ func (s *DateTimePicker) renderSelectedDateTime() string {
if s.selectedMinute != nil {
minute = *s.selectedMinute
}
- value := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), hour, minute, 0, 0, mskLocation)
+ value := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), hour, minute, 0, 0, MskLocation)
return fmt.Sprintf("%s", formatCompactDateTime(value))
}
@@ -335,15 +339,15 @@ func (s *DateTimePicker) formatSelectedDateTime() string {
if s.selectedHour != nil && s.selectedMinute != nil {
timeLabel = fmt.Sprintf("%02d:%02d", *s.selectedHour, *s.selectedMinute)
}
- weekday := weekdayName(displayDate.Weekday())
- month := monthShort(displayDate.Month())
+ weekday := WeekdayName(displayDate.Weekday())
+ month := MonthShort(displayDate.Month())
return fmt.Sprintf("%s %02d %s %s %dг.", weekday, displayDate.Day(), month, timeLabel, displayDate.Year())
}
func (s *DateTimePicker) displayDate() (time.Time, bool) {
if s.selectedDate == nil {
if s.view == pickerViewCalendar || s.view == pickerViewMonths {
- return time.Date(s.year, s.month, 1, 0, 0, 0, 0, mskLocation), true
+ return time.Date(s.year, s.month, 1, 0, 0, 0, 0, MskLocation), true
}
return time.Time{}, false
}
@@ -355,15 +359,15 @@ func (s *DateTimePicker) displayDate() (time.Time, bool) {
if day > maxDay {
day = maxDay
}
- return time.Date(s.year, s.month, day, 0, 0, 0, 0, mskLocation), true
+ return time.Date(s.year, s.month, day, 0, 0, 0, 0, MskLocation), true
}
- return s.selectedDate.In(mskLocation), true
+ return s.selectedDate.In(MskLocation), true
}
func (s *DateTimePicker) handleDaySelect(payload string) {
datePart := strings.TrimPrefix(payload, "day:")
- parsed, err := time.ParseInLocation("2006-01-02", datePart, mskLocation)
+ parsed, err := time.ParseInLocation("2006-01-02", datePart, MskLocation)
if err != nil {
return
}
@@ -401,9 +405,12 @@ func (s *DateTimePicker) handleMinuteSelect(payload string) {
func (s *DateTimePicker) confirmSelection(b *bot.Bot) {
if s.selectedDate == nil {
+ s.Enter(b, bot.EditMessage)
return
}
if s.IncludeTime && (s.selectedHour == nil || s.selectedMinute == nil) {
+ s.view = pickerViewTimeCombined
+ s.Enter(b, bot.EditMessage)
return
}
hour := 0
@@ -414,7 +421,7 @@ func (s *DateTimePicker) confirmSelection(b *bot.Bot) {
if s.selectedMinute != nil {
minute = *s.selectedMinute
}
- value := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), hour, minute, 0, 0, mskLocation)
+ value := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), hour, minute, 0, 0, MskLocation)
if target, ok := s.BackState.(DateTimeSelectionTarget); ok {
target.SetDateTimeSelection(s.Key, value)
}
@@ -426,32 +433,38 @@ func (s *DateTimePicker) confirmSelection(b *bot.Bot) {
func (s *DateTimePicker) hourButton(hour int) echotron.InlineKeyboardButton {
label := fmt.Sprintf("%02d", hour)
if s.isHourDisabled(hour) {
- return Button("-", "empty")
+ return echotron.InlineKeyboardButton{Text: "-", CallbackData: "empty"}
}
if s.selectedHour != nil && *s.selectedHour == hour {
label = "● " + label
}
- return Button(label, fmt.Sprintf("%shour:%02d", dtpCallbackPrefix, hour))
+ return echotron.InlineKeyboardButton{
+ Text: label,
+ CallbackData: fmt.Sprintf("%shour:%02d", dtpCallbackPrefix, hour),
+ }
}
func (s *DateTimePicker) minuteButton(minute int) echotron.InlineKeyboardButton {
label := fmt.Sprintf("%02d", minute)
if s.isMinuteDisabled(minute) {
- return Button("-", "empty")
+ return echotron.InlineKeyboardButton{Text: "-", CallbackData: "empty"}
}
if s.selectedMinute != nil && *s.selectedMinute == minute {
label = "● " + label
}
- return Button(label, fmt.Sprintf("%smin:%02d", dtpCallbackPrefix, minute))
+ return echotron.InlineKeyboardButton{
+ Text: label,
+ CallbackData: fmt.Sprintf("%smin:%02d", dtpCallbackPrefix, minute),
+ }
}
func (s *DateTimePicker) isHourDisabled(hour int) bool {
if s.selectedDate == nil {
return false
}
- today := time.Now().In(mskLocation)
- date := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), 0, 0, 0, 0, mskLocation)
- todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, mskLocation)
+ today := time.Now().In(MskLocation)
+ date := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), 0, 0, 0, 0, MskLocation)
+ todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, MskLocation)
if date.After(todayDate) {
return false
}
@@ -462,9 +475,9 @@ func (s *DateTimePicker) isMinuteDisabled(minute int) bool {
if s.selectedDate == nil || s.selectedHour == nil {
return false
}
- today := time.Now().In(mskLocation)
- date := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), 0, 0, 0, 0, mskLocation)
- todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, mskLocation)
+ today := time.Now().In(MskLocation)
+ date := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), 0, 0, 0, 0, MskLocation)
+ todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, MskLocation)
if date.After(todayDate) {
return false
}
@@ -475,7 +488,7 @@ func (s *DateTimePicker) isMinuteDisabled(minute int) bool {
}
func (s *DateTimePicker) prevMonth() {
- now := time.Now().In(mskLocation)
+ now := time.Now().In(MskLocation)
if s.year == now.Year() && s.month == now.Month() {
return
}
@@ -497,15 +510,15 @@ func (s *DateTimePicker) nextMonth() {
}
func (s *DateTimePicker) monthPrevButton() echotron.InlineKeyboardButton {
- now := time.Now().In(mskLocation)
+ now := time.Now().In(MskLocation)
if s.year == now.Year() && s.month == now.Month() {
- return Button("-", "empty")
+ return echotron.InlineKeyboardButton{Text: "-", CallbackData: "empty"}
}
- return Button("←", dtpCallbackPrefix+"month_prev")
+ return echotron.InlineKeyboardButton{Text: "←", CallbackData: dtpCallbackPrefix + "month_prev"}
}
func daysInMonth(year int, month time.Month) int {
- return time.Date(year, month+1, 0, 0, 0, 0, 0, mskLocation).Day()
+ return time.Date(year, month+1, 0, 0, 0, 0, 0, MskLocation).Day()
}
func monthName(month time.Month) string {
@@ -598,10 +611,10 @@ func parseDateTimeInput(
text = strings.ReplaceAll(text, ",", " ")
text = strings.ReplaceAll(text, " ", " ")
- now := time.Now().In(mskLocation)
+ now := time.Now().In(MskLocation)
baseDate := now
if currentDate != nil {
- baseDate = currentDate.In(mskLocation)
+ baseDate = currentDate.In(MskLocation)
}
date, dateOk := parseDate(text, baseDate)
@@ -611,14 +624,14 @@ func parseDateTimeInput(
return time.Time{}, false, false
}
if !dateOk {
- date = time.Date(baseDate.Year(), baseDate.Month(), baseDate.Day(), 0, 0, 0, 0, mskLocation)
+ date = time.Date(baseDate.Year(), baseDate.Month(), baseDate.Day(), 0, 0, 0, 0, MskLocation)
}
if !timeOk {
hour = 0
minute = 0
}
- value := time.Date(date.Year(), date.Month(), date.Day(), hour, minute, 0, 0, mskLocation)
+ value := time.Date(date.Year(), date.Month(), date.Day(), hour, minute, 0, 0, MskLocation)
if value.Before(now) {
return time.Time{}, false, false
}
@@ -658,7 +671,7 @@ func parseNumericDate(text string) time.Time {
if match := reDMY.FindStringSubmatch(text); len(match) >= 3 {
day := parseInt(match[1])
month := parseInt(match[2])
- year := time.Now().In(mskLocation).Year()
+ year := time.Now().In(MskLocation).Year()
if len(match) == 4 && match[3] != "" {
year = parseInt(match[3])
if year < 100 {
@@ -705,7 +718,7 @@ func parseWordDate(text string) time.Time {
return time.Time{}
}
- year := time.Now().In(mskLocation).Year()
+ year := time.Now().In(MskLocation).Year()
if len(match) == 4 && match[3] != "" {
year = parseInt(match[3])
if year < 100 {
@@ -772,10 +785,10 @@ func safeDate(year, month, day int) time.Time {
if day < 1 || day > 31 {
return time.Time{}
}
- return time.Date(year, time.Month(month), day, 0, 0, 0, 0, mskLocation)
+ return time.Date(year, time.Month(month), day, 0, 0, 0, 0, MskLocation)
}
-func weekdayName(day time.Weekday) string {
+func WeekdayName(day time.Weekday) string {
switch day {
case time.Monday:
return "Пн"
@@ -797,12 +810,12 @@ func weekdayName(day time.Weekday) string {
}
func formatCompactDateTime(value time.Time) string {
- weekday := weekdayName(value.Weekday())
- month := monthShort(value.Month())
+ weekday := WeekdayName(value.Weekday())
+ month := MonthShort(value.Month())
return fmt.Sprintf("%s %02d %s %s", weekday, value.Day(), month, value.Format("15:04"))
}
-func monthShort(month time.Month) string {
+func MonthShort(month time.Month) string {
months := []string{
"янв", "фев", "мар", "апр", "май", "июн",
"июл", "авг", "сен", "окт", "ноя", "дек",