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

+28 -2
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import html
import json
import urllib.error
import urllib.parse
import urllib.request
from typing import Any, Mapping, Protocol, Sequence
@@ -27,8 +28,18 @@ class UrllibTelegramTransport:
data=urllib.parse.urlencode(data).encode("utf-8"),
method="POST",
)
with urllib.request.urlopen(request, timeout=timeout) as response:
return json.loads(response.read().decode("utf-8", "replace"))
try:
with urllib.request.urlopen(request, timeout=timeout) as response:
return json.loads(response.read().decode("utf-8", "replace"))
except urllib.error.HTTPError as exc:
# Telegram 对业务错误也返回 4xx + JSON 描述;保留描述便于定位。
body = exc.read().decode("utf-8", "replace")
try:
return json.loads(body)
except json.JSONDecodeError as parse_error:
raise TelegramError(
f"Telegram HTTP {exc.code}: {body[:200]}"
) from parse_error
def format_duration(seconds: int) -> str:
@@ -142,3 +153,18 @@ class TelegramClient:
"answerCallbackQuery",
{"callback_query_id": callback_id, "text": text[:200]},
)
def edit_reply_markup(
self, message_id: int, *, reply_markup: Mapping[str, Any] | None = None
) -> Any:
"""移除或替换原消息的按钮;空 keyboard 表示移除。"""
return self._call(
"editMessageReplyMarkup",
{
"chat_id": self.chat_id,
"message_id": int(message_id),
"reply_markup": json.dumps(
reply_markup or {"inline_keyboard": []}, ensure_ascii=False
),
},
)
+17 -4
View File
@@ -67,13 +67,26 @@ class TelegramPoller:
action=action, aid=aid, ok=result.ok, state=result.state,
position=result.position,
)
self._answer(callback_id, text)
if not self._answer(callback_id, text):
# 回调 ID 过期时 answerCallbackQuery 会 400;改用普通消息告知结果。
try:
self.bot.send_text(text)
except Exception as exc: # noqa: BLE001
self._log("warning", "telegram.fallback_failed", error=str(exc))
message_id = message.get("message_id")
if isinstance(message_id, int) and not isinstance(message_id, bool):
try:
self.bot.edit_reply_markup(message_id)
except Exception as exc: # noqa: BLE001
self._log("warning", "telegram.edit_failed", error=str(exc))
def _answer(self, callback_id: str, text: str) -> None:
def _answer(self, callback_id: str, text: str) -> bool:
try:
self.bot.answer_callback(callback_id, text)
except Exception as exc: # noqa: BLE001
self._log("error", "telegram.answer_failed", error=str(exc))
return True
except Exception as exc: # noqa: BLE001 过期回调不应升级为错误
self._log("warning", "telegram.answer_failed", error=str(exc))
return False
def run_forever(self, stop_event=None) -> None:
offset: int | None = None