fix(telegram): handle expired callback queries gracefully

Parse Telegram HTTP error bodies so the real description is logged, fall back to a normal message when answerCallbackQuery rejects an expired callback ID, and remove inline buttons after a successful action.
This commit is contained in:
kairee committed 2026-10-31 21:59:04 +08:00
1 parent e2e4dc3336
commit 2d42d8ed55
3 files changed
+96 -8

No files matched your search

+51 -2
View File
@@ -12,6 +12,7 @@ sys.stdout.reconfigure(encoding="utf-8")
from reaction_board.notify.telegram import ( # noqa: E402
TelegramClient,
TelegramError,
format_duration,
)
from reaction_board.services.favorites import ActionResult # noqa: E402
@@ -94,20 +95,54 @@ def test_get_updates_and_answer():
assert transport.calls[0]["url"].endswith("/answerCallbackQuery")
assert transport.calls[0]["data"]["callback_query_id"] == "cb1"
transport = FakeTransport(result=True)
bot = TelegramClient("token", 42, transport=transport)
bot.edit_reply_markup(77)
assert transport.calls[0]["url"].endswith("/editMessageReplyMarkup")
assert transport.calls[0]["data"]["message_id"] == 77
assert json.loads(transport.calls[0]["data"]["reply_markup"]) == {
"inline_keyboard": []
}
def test_telegram_error_description_preserved():
class ErrorTransport:
def post(self, url, data, timeout=30):
return {"ok": False, "description": "query is too old"}
bot = TelegramClient("token", 42, transport=ErrorTransport())
try:
bot.answer_callback("cb1", "x")
except TelegramError as exc:
assert "too old" in str(exc)
else:
raise AssertionError("Telegram 业务错误应抛出描述")
class FakeBot:
chat_id = 42
def __init__(self, updates):
def __init__(self, updates, *, fail_answer=False):
self.updates = updates
self.fail_answer = fail_answer
self.answers = []
self.sent = []
self.edits = []
def get_updates(self, offset=None):
return list(self.updates)
def answer_callback(self, callback_id, text=""):
if self.fail_answer:
raise RuntimeError("HTTP Error 400: Bad Request")
self.answers.append((callback_id, text))
def send_text(self, text):
self.sent.append(text)
def edit_reply_markup(self, message_id):
self.edits.append(message_id)
class FakeActions:
def __init__(self):
@@ -133,7 +168,7 @@ def update(update_id, data, *, chat_id=42, from_id=42):
"id": f"cb{update_id}",
"data": data,
"from": {"id": from_id},
"message": {"chat": {"id": chat_id}},
"message": {"message_id": update_id * 10, "chat": {"id": chat_id}},
},
}
@@ -154,6 +189,18 @@ def test_poller_dispatch_and_authorization():
assert bot.answers[0] == ("cb1", "已加入收藏夹(位置 #3)")
assert bot.answers[3] == ("cb4", "无权限")
assert bot.answers[4] == ("cb5", "未知操作")
assert bot.edits == [10, 20, 30]
def test_answer_failure_falls_back_to_text():
bot = FakeBot([update(1, "fav:9")], fail_answer=True)
actions = FakeActions()
poller = TelegramPoller(bot, actions)
poller.run_once(None)
assert actions.calls == [("fav", 9)]
assert bot.answers == []
assert bot.sent == ["已加入收藏夹(位置 #3)"]
assert bot.edits == [10]
def main():
@@ -161,7 +208,9 @@ def main():
test_send_candidates_and_chunking()
test_send_candidates_dedupes_by_aid()
test_get_updates_and_answer()
test_telegram_error_description_preserved()
test_poller_dispatch_and_authorization()
test_answer_failure_falls_back_to_text()
print("OK: Telegram 消息、分片、长轮询与回调授权测试全部通过")