From 588a524fa5f039f0144a17518e2daca269e321a7 Mon Sep 17 00:00:00 2001 From: MHSanaei Date: Wed, 15 Jul 2026 04:54:41 +0200 Subject: [PATCH] fix(tgbot): stop auto-deleted messages from resetting wizard state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SendMsgToTgbotDeleteAfter spawns a goroutine that, after the display delay, deleted the transient message and then unconditionally cleared the chat's conversation state. Every caller that ends a wizard step already clears the state synchronously, so that call was redundant — and harmful: if within the delay the user advanced to the next step (a callback sets a fresh awaiting_* state), the late goroutine wiped it, and the user's next message fell through unrecognized, silently dropping their input. Move the delayed deletion into deleteMessageAfterDelay, which only removes the message and no longer touches the conversation state. Guard deleteMessageTgBot against a nil bot so the deletion path is unit-testable. --- .../service/tgbot/tgbot_delete_after_test.go | 22 +++++++++++++++++++ internal/web/service/tgbot/tgbot_send.go | 21 +++++++++++++----- 2 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 internal/web/service/tgbot/tgbot_delete_after_test.go diff --git a/internal/web/service/tgbot/tgbot_delete_after_test.go b/internal/web/service/tgbot/tgbot_delete_after_test.go new file mode 100644 index 000000000..be4aa2a02 --- /dev/null +++ b/internal/web/service/tgbot/tgbot_delete_after_test.go @@ -0,0 +1,22 @@ +package tgbot + +import "testing" + +// A transient "delete after N seconds" message must not reset the conversation +// state when its timer fires: the user may have advanced to the next wizard step +// (setting a fresh state) within that window, and clearing it would silently +// drop their next input. +func TestDeleteMessageAfterDelayKeepsUserState(t *testing.T) { + userStateMgr.reset() + t.Cleanup(userStateMgr.reset) + + const chatID = int64(4242) + userStateMgr.set(chatID, "awaiting_comment") + + tg := &Tgbot{} + tg.deleteMessageAfterDelay(chatID, 1, 0) + + if st, ok := userStateMgr.get(chatID); !ok || st != "awaiting_comment" { + t.Fatalf("delayed message deletion cleared the conversation state: got (%q, %v), want (%q, true)", st, ok, "awaiting_comment") + } +} diff --git a/internal/web/service/tgbot/tgbot_send.go b/internal/web/service/tgbot/tgbot_send.go index 4578619d0..b53aa6400 100644 --- a/internal/web/service/tgbot/tgbot_send.go +++ b/internal/web/service/tgbot/tgbot_send.go @@ -228,16 +228,25 @@ func (t *Tgbot) SendMsgToTgbotDeleteAfter(chatId int64, msg string, delayInSecon return } - // Delete the sent message after the specified number of seconds - go func() { - time.Sleep(time.Duration(delayInSeconds) * time.Second) // Wait for the specified delay - t.deleteMessageTgBot(chatId, sentMsg.MessageID) // Delete the message - userStateMgr.clear(chatId) - }() + // Delete the sent message after the specified number of seconds. + go t.deleteMessageAfterDelay(chatId, sentMsg.MessageID, delayInSeconds) +} + +// deleteMessageAfterDelay waits delayInSeconds and then removes the message. It +// deliberately does not touch the conversation state: every caller that ends a +// wizard step already clears the state synchronously, and clearing it here — up +// to several seconds later — would wipe a state the user set for the next step +// in the meantime, silently dropping their following input. +func (t *Tgbot) deleteMessageAfterDelay(chatId int64, messageID, delayInSeconds int) { + time.Sleep(time.Duration(delayInSeconds) * time.Second) + t.deleteMessageTgBot(chatId, messageID) } // deleteMessageTgBot deletes a message from the chat. func (t *Tgbot) deleteMessageTgBot(chatId int64, messageID int) { + if bot == nil { + return + } params := telego.DeleteMessageParams{ ChatID: tu.ID(chatId), MessageID: messageID,