фиксы
This commit is contained in:
@@ -19,6 +19,13 @@ func Button(text, data string) echotron.InlineKeyboardButton {
|
||||
}
|
||||
}
|
||||
|
||||
func URLButton(text, url string) echotron.InlineKeyboardButton {
|
||||
return echotron.InlineKeyboardButton{
|
||||
Text: text,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
func Row(buttons ...echotron.InlineKeyboardButton) []echotron.InlineKeyboardButton { return buttons }
|
||||
|
||||
func Keyboard(rows ...[]echotron.InlineKeyboardButton) echotron.InlineKeyboardMarkup {
|
||||
@@ -44,28 +51,34 @@ type Bot struct {
|
||||
exec *Exec
|
||||
CurrentState State
|
||||
Session *Session
|
||||
LastMessageID int // ID последнего сообщения с inline кнопками
|
||||
DefaultStateFactory func() State
|
||||
LastMessageID int // ID последнего сообщения с inline кнопками
|
||||
commandRouter func(cmd string) State // Роутер для команд (например, /start, /help)
|
||||
globalCallbackRouter func(callbackData string) State // Роутер для глобальных callbacks из уведомлений
|
||||
Backend *backend.Client
|
||||
GlobalCallbackRouter func(callbackData string) State // Роутер для глобальных callbacks из уведомлений
|
||||
}
|
||||
|
||||
func NewBot(
|
||||
chatID int64,
|
||||
token string,
|
||||
defaultStateFactory func() State,
|
||||
backendClient *backend.Client,
|
||||
globalCallbackRouter func(string) State,
|
||||
) *Bot {
|
||||
bot := &Bot{
|
||||
func NewBot(chatID int64, token string, commandRouter func(string) State, backendClient *backend.Client, globalCallbackRouter func(string) State) *Bot {
|
||||
if commandRouter == nil {
|
||||
panic("bot: commandRouter cannot be nil")
|
||||
}
|
||||
if globalCallbackRouter == nil {
|
||||
panic("bot: globalCallbackRouter cannot be nil")
|
||||
}
|
||||
if commandRouter("/start") == nil {
|
||||
panic("bot: commandRouter requires /start state answer")
|
||||
}
|
||||
if backendClient == nil {
|
||||
panic("bot: backendClient cannot be nil")
|
||||
}
|
||||
|
||||
return &Bot{
|
||||
ChatID: chatID,
|
||||
API: echotron.NewAPI(token),
|
||||
Session: &Session{},
|
||||
DefaultStateFactory: defaultStateFactory,
|
||||
commandRouter: commandRouter,
|
||||
Backend: backendClient,
|
||||
GlobalCallbackRouter: globalCallbackRouter,
|
||||
globalCallbackRouter: globalCallbackRouter,
|
||||
}
|
||||
return bot
|
||||
}
|
||||
|
||||
// GetOrCreateJWT получает JWT токен, передавая актуальные данные пользователя из update
|
||||
@@ -129,19 +142,6 @@ func (b *Bot) SetState(s State, mode RenderMode) {
|
||||
s.Enter(b, mode)
|
||||
}
|
||||
|
||||
//func (b *Bot) RemoveLastKeyboard() {
|
||||
// if b.LastMessageID != 0 {
|
||||
// emptyKeyboard := Keyboard() // Пустая клавиатура
|
||||
// _, err := b.EditMessageReplyMarkup(
|
||||
// echotron.NewMessageID(b.ChatID, b.LastMessageID),
|
||||
// &echotron.MessageReplyMarkupOptions{ReplyMarkup: emptyKeyboard},
|
||||
// )
|
||||
// if err != nil {
|
||||
// log.Error().Err(err).Msg("RemoveLastKeyboard: b.EditMessageReplyMarkup")
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
func (b *Bot) Render(text string, keyboard echotron.InlineKeyboardMarkup, mode RenderMode) {
|
||||
if mode == EditMessage {
|
||||
b.Edit(text, keyboard)
|
||||
@@ -262,126 +262,104 @@ func (b *Bot) Update(u *echotron.Update) {
|
||||
Bool("has_edited_message", u.EditedMessage != nil).
|
||||
Msg("Update received")
|
||||
|
||||
// Обрабатываем системные события (добавление в канал, подписки и т.д.)
|
||||
// 1. Системные события (ChatJoinRequest, ChatMember, etc.) -> форвардим асинхронно
|
||||
if u.ChatJoinRequest != nil || u.ChatMember != nil || u.MyChatMember != nil {
|
||||
go func() {
|
||||
ctx := context.Background()
|
||||
if raw, err := json.Marshal(u); err == nil {
|
||||
log.Info().RawJSON("update", raw).Msg("System event payload")
|
||||
} else {
|
||||
log.Error().Err(err).Msg("Failed to marshal system event payload")
|
||||
raw, err := json.Marshal(u)
|
||||
if err != nil {
|
||||
log.Error().RawJSON("update", raw).Msg("Failed to marshal update")
|
||||
}
|
||||
err := b.Backend.ForwardTelegramUpdate(ctx, u)
|
||||
err = b.Backend.ForwardTelegramUpdate(ctx, u)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to forward system event to backend")
|
||||
return
|
||||
}
|
||||
log.Info().Msg("System event forwarded to backend")
|
||||
}()
|
||||
// Системные события не требуют ответа бота - выходим
|
||||
return
|
||||
}
|
||||
|
||||
// Игнорируем все события не из личных чатов (кроме уже обработанных системных)
|
||||
var chatType string
|
||||
// 2. Игнорируем события не из private чатов
|
||||
chatType := ""
|
||||
switch {
|
||||
case u.Message != nil:
|
||||
chatType = u.Message.Chat.Type
|
||||
case u.EditedMessage != nil:
|
||||
chatType = u.EditedMessage.Chat.Type
|
||||
case u.ChannelPost != nil:
|
||||
chatType = u.ChannelPost.Chat.Type
|
||||
case u.EditedChannelPost != nil:
|
||||
chatType = u.EditedChannelPost.Chat.Type
|
||||
case u.CallbackQuery != nil && u.CallbackQuery.Message != nil:
|
||||
chatType = u.CallbackQuery.Message.Chat.Type
|
||||
case u.EditedMessage != nil:
|
||||
chatType = u.EditedMessage.Chat.Type
|
||||
case u.ChannelPost != nil || u.EditedChannelPost != nil:
|
||||
return // Каналы игнорируем сразу
|
||||
}
|
||||
|
||||
if chatType != "" && chatType != "private" {
|
||||
log.Debug().Str("chat_type", chatType).Msg("Ignoring event from non-private chat")
|
||||
log.Debug().Str("chat_type", chatType).Msg("Ignoring non-private chat")
|
||||
return
|
||||
}
|
||||
|
||||
// Игнорируем edited_message обновления, чтобы не сбрасывать состояние после наших Edit операций.
|
||||
// 3. Игнорируем edited messages
|
||||
if u.EditedMessage != nil || u.EditedChannelPost != nil || u.EditedBusinessMessage != nil {
|
||||
log.Debug().Msg("Ignoring edited message update")
|
||||
log.Debug().Msg("Ignoring edited message")
|
||||
return
|
||||
}
|
||||
|
||||
// Получаем или создаем JWT, обновляя данные пользователя (username, first_name, last_name)
|
||||
if err := b.GetOrCreateJWT(u); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get or create JWT")
|
||||
// Не блокируем работу бота если JWT не удалось получить - это не критично для всех действий
|
||||
}
|
||||
_ = b.GetOrCreateJWT(u)
|
||||
|
||||
exec := &Exec{}
|
||||
|
||||
b.exec = exec
|
||||
defer func() { b.exec = nil }()
|
||||
|
||||
if b.CurrentState == nil && b.DefaultStateFactory != nil {
|
||||
b.CurrentState = b.DefaultStateFactory()
|
||||
if b.CurrentState == nil {
|
||||
b.CurrentState = b.commandRouter("/start")
|
||||
}
|
||||
|
||||
if u.Message != nil && u.Message.Text == "/start" {
|
||||
if b.DefaultStateFactory != nil {
|
||||
b.SetState(b.DefaultStateFactory(), NewMessage)
|
||||
}
|
||||
return
|
||||
}
|
||||
// === Обработка входящего события ===
|
||||
|
||||
if u.CallbackQuery != nil {
|
||||
log.Info().
|
||||
Str("callback_data", u.CallbackQuery.Data).
|
||||
Int("callback_msg_id", func() int {
|
||||
if u.CallbackQuery.Message != nil {
|
||||
return u.CallbackQuery.Message.ID
|
||||
}
|
||||
return 0
|
||||
}()).
|
||||
Int("last_msg_id", b.LastMessageID).
|
||||
Msg("Callback received")
|
||||
b.cleanupCallbackUI(u)
|
||||
if u.CallbackQuery.Data == "empty" || u.CallbackQuery.Data == "page_info" || u.CallbackQuery.Data == "noop" {
|
||||
exec.handled = true
|
||||
// 1. Команды (приоритетный маршрут)
|
||||
if u.Message != nil {
|
||||
switch u.Message.Text {
|
||||
case "/start":
|
||||
b.SetState(b.commandRouter("/start"), NewMessage)
|
||||
return
|
||||
}
|
||||
|
||||
if b.GlobalCallbackRouter != nil && u.CallbackQuery.Data != "" {
|
||||
if newState := b.GlobalCallbackRouter(u.CallbackQuery.Data); newState != nil {
|
||||
b.SetState(newState, NewMessage)
|
||||
case "/start login":
|
||||
b.SetState(b.commandRouter("/start login"), NewMessage)
|
||||
return
|
||||
default:
|
||||
b.CurrentState.HandleMessage(b, u)
|
||||
if exec.handled || exec.transitioned {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Callback запросы
|
||||
if u.CallbackQuery != nil {
|
||||
b.cleanupCallbackUI(u)
|
||||
|
||||
if u.CallbackQuery.Data == "empty" {
|
||||
return
|
||||
}
|
||||
|
||||
// Глобальные callbacks (из уведомлений)
|
||||
if newState := b.globalCallbackRouter(u.CallbackQuery.Data); newState != nil {
|
||||
b.SetState(newState, NewMessage)
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().Msg("Handling callback")
|
||||
b.CurrentState.HandleCallback(b, u)
|
||||
} else if u.Message != nil {
|
||||
log.Info().Msg("Handling message")
|
||||
b.CurrentState.HandleMessage(b, u)
|
||||
if exec.handled || exec.transitioned {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !exec.handled && !exec.transitioned {
|
||||
log.Info().
|
||||
Bool("has_callback", u.CallbackQuery != nil).
|
||||
Bool("has_message", u.Message != nil).
|
||||
Bool("handled", exec.handled).
|
||||
Bool("transitioned", exec.transitioned).
|
||||
Msg("No handler handled update, calling common handler")
|
||||
log.Info().Msg("Common handler")
|
||||
b.CurrentState.Handle(b, u)
|
||||
// 3. Универсальный обработчик состояния (если ничего не подошло)
|
||||
b.CurrentState.Handle(b, u)
|
||||
if exec.handled || exec.transitioned {
|
||||
return
|
||||
}
|
||||
|
||||
if !exec.handled && !exec.transitioned {
|
||||
log.Info().
|
||||
Bool("has_callback", u.CallbackQuery != nil).
|
||||
Bool("has_message", u.Message != nil).
|
||||
Bool("handled", exec.handled).
|
||||
Bool("transitioned", exec.transitioned).
|
||||
Msg("Default feedback triggered")
|
||||
log.Info().Msg("Default feedback")
|
||||
b.SetState(b.DefaultStateFactory(), NewMessage)
|
||||
}
|
||||
// 4. Fallback - сброс в начальное состояние
|
||||
b.SetState(b.commandRouter("/start"), NewMessage)
|
||||
}
|
||||
|
||||
func (b *Bot) cleanupKeyboard(messageID int) {
|
||||
|
||||
Reference in New Issue
Block a user