refactor + fix добавление проекта

This commit is contained in:
Artem Tsyrulnikov
2026-01-20 15:16:06 +03:00
parent 7f6527652b
commit 4e4f9810d9
10 changed files with 251 additions and 235 deletions

View File

@@ -14,15 +14,40 @@ import (
"github.com/rs/zerolog/log"
)
var emptyKeyboard = echotron.InlineKeyboardMarkup{
InlineKeyboard: [][]echotron.InlineKeyboardButton{},
}
type exec struct {
handled bool // Событие обработано, дальше не идём
transitioned bool // Был SetState
}
var botUsername string
var botUsernameOnce sync.Once
func SetBotUsername(username string) { botUsername = username }
func BotUsername() string { return botUsername }
func ensureBotUsername(api echotron.API) {
if botUsername != "" {
return
}
botUsernameOnce.Do(func() {
me, err := api.GetMe()
if err != nil {
log.Error().Err(err).Msg("GetMe failed, bot username will be empty")
return
}
if me.Result != nil {
botUsername = me.Result.Username
}
})
}
type lastRender struct {
textHash string
kbHash string
isMedia bool
}
type Bot struct {
echotron.API
mu sync.Mutex
@@ -30,10 +55,8 @@ type Bot struct {
exec *exec
CurrentState State
Session *Session
LastMessageID int // ID последнего сообщения с inline кнопками
LastMessageIsMedia bool // True if LastMessageID points to media message
LastMessageTextHash string // Hash for last text/caption
LastMessageKBHash string // Hash for last inline keyboard
LastMessageID int // ID последнего сообщения с inline кнопками
lastRender lastRender
commandRouter func(cmd string) State // Роутер для команд (например, /start, /help)
globalCallbackRouter func(callbackData string) State // Роутер для глобальных callbacks из уведомлений
Backend *backend.Client
@@ -53,9 +76,12 @@ func NewBot(chatID int64, token string, commandRouter func(string) State, backen
panic("bot: backendClient cannot be nil")
}
api := echotron.NewAPI(token)
ensureBotUsername(api)
return &Bot{
ChatID: chatID,
API: echotron.NewAPI(token),
API: api,
Session: &Session{},
commandRouter: commandRouter,
Backend: backendClient,
@@ -140,7 +166,7 @@ func (b *Bot) Render(text string, keyboard echotron.InlineKeyboardMarkup, mode R
}
func (b *Bot) SetLastMessageIsMedia(isMedia bool) {
b.LastMessageIsMedia = isMedia
b.lastRender.isMedia = isMedia
}
func (b *Bot) DownloadFileBytes(fileID string) ([]byte, error) {
@@ -175,9 +201,9 @@ func (b *Bot) SendNew(text string, keyboard echotron.InlineKeyboardMarkup) {
}
b.LastMessageID = res.Result.ID
b.LastMessageIsMedia = false
b.LastMessageTextHash = hashString(text)
b.LastMessageKBHash = keyboardHash(keyboard)
b.lastRender.isMedia = false
b.lastRender.textHash = hashString(text)
b.lastRender.kbHash = keyboardHash(keyboard)
log.Info().Int("new_last_msg_id", b.LastMessageID).Msg("Updated LastMessageID in SendNew")
}
@@ -194,8 +220,8 @@ func (b *Bot) Edit(text string, keyboard echotron.InlineKeyboardMarkup) {
newTextHash := hashString(text)
newKBHash := keyboardHash(keyboard)
textChanged := newTextHash != b.LastMessageTextHash
kbChanged := newKBHash != b.LastMessageKBHash
textChanged := newTextHash != b.lastRender.textHash
kbChanged := newKBHash != b.lastRender.kbHash
if !textChanged && !kbChanged {
log.Info().Msg("Edit skipped: message not modified")
@@ -205,7 +231,7 @@ func (b *Bot) Edit(text string, keyboard echotron.InlineKeyboardMarkup) {
var err error
msgID := echotron.NewMessageID(b.ChatID, b.LastMessageID)
if b.LastMessageIsMedia {
if b.lastRender.isMedia {
_, err = b.EditMessageCaption(msgID, &echotron.MessageCaptionOptions{Caption: text, ParseMode: echotron.HTML, ReplyMarkup: keyboard})
} else {
_, err = b.EditMessageText(text, msgID, &echotron.MessageTextOptions{ReplyMarkup: keyboard, ParseMode: echotron.HTML})
@@ -213,10 +239,10 @@ func (b *Bot) Edit(text string, keyboard echotron.InlineKeyboardMarkup) {
if err == nil {
if textChanged {
b.LastMessageTextHash = newTextHash
b.lastRender.textHash = newTextHash
}
if kbChanged {
b.LastMessageKBHash = newKBHash
b.lastRender.kbHash = newKBHash
}
return
}
@@ -225,7 +251,7 @@ func (b *Bot) Edit(text string, keyboard echotron.InlineKeyboardMarkup) {
case strings.Contains(err.Error(), "message is not modified"):
log.Err(err).Msg("EditMessageText ignored: message is not modified")
case strings.Contains(err.Error(), "no text in the message to edit"):
b.LastMessageIsMedia = true
b.lastRender.isMedia = true
log.Err(err).Msg("EditMessageText ignored: message has no text")
default:
log.Error().Err(err).Msg("EditMessageText")
@@ -284,7 +310,10 @@ func (b *Bot) Update(u *echotron.Update) {
err := b.GetOrCreateJWT(u)
if err != nil || b.Session.JWT == "" {
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", emptyKeyboard)
b.Edit(
"❌ Ошибка авторизации",
echotron.InlineKeyboardMarkup{InlineKeyboard: [][]echotron.InlineKeyboardButton{{{Text: "↻ Обновить", CallbackData: "refresh"}}}},
)
return
}
@@ -307,11 +336,11 @@ func (b *Bot) Update(u *echotron.Update) {
case "/start login":
b.SetState(b.commandRouter("/start login"), NewMessage)
return
default:
b.CurrentState.HandleMessage(b, u)
if e.handled || e.transitioned {
return
}
}
b.CurrentState.HandleMessage(b, u)
if e.handled || e.transitioned {
return
}
}
@@ -319,11 +348,17 @@ func (b *Bot) Update(u *echotron.Update) {
if u.CallbackQuery != nil {
b.cleanupCallbackUI(u)
if u.CallbackQuery.Data == "empty" {
switch u.CallbackQuery.Data {
case "empty":
return
case "refresh":
if b.CurrentState != nil {
b.CurrentState.Enter(b, EditMessage)
e.handled = true
}
return
}
// Глобальные callbacks (из уведомлений)
if newState := b.globalCallbackRouter(u.CallbackQuery.Data); newState != nil {
b.SetState(newState, NewMessage)
return
@@ -358,6 +393,9 @@ func (b *Bot) Update(u *echotron.Update) {
}
func (b *Bot) cleanupKeyboard(messageID int) {
var emptyKeyboard = echotron.InlineKeyboardMarkup{
InlineKeyboard: [][]echotron.InlineKeyboardButton{},
}
_, err := b.EditMessageReplyMarkup(
echotron.NewMessageID(b.ChatID, messageID),
&echotron.MessageReplyMarkupOptions{ReplyMarkup: emptyKeyboard},