From 74b0072f8f9864fe2c0735d90ea3853b7927314e Mon Sep 17 00:00:00 2001 From: Alvaro Linares Date: Tue, 7 Apr 2026 07:34:56 -0300 Subject: [PATCH] feat(telegram): add message reactions on processing start/complete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the Discord reaction pattern for Telegram: - 👀 (eyes) when message processing begins - ✅ (check) on successful completion - ❌ (cross) on failure Controlled via TELEGRAM_REACTIONS env var or telegram.reactions in config.yaml (enabled by default, like Discord). Uses python-telegram-bot's Bot.set_message_reaction() API. Failures are caught and logged at debug level so they never break message processing. --- gateway/config.py | 2 ++ gateway/platforms/telegram.py | 54 +++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/gateway/config.py b/gateway/config.py index 6c8825298b..ab0d7c1186 100644 --- a/gateway/config.py +++ b/gateway/config.py @@ -582,6 +582,8 @@ def load_gateway_config() -> GatewayConfig: if isinstance(frc, list): frc = ",".join(str(v) for v in frc) os.environ["TELEGRAM_FREE_RESPONSE_CHATS"] = str(frc) + if "reactions" in telegram_cfg and not os.getenv("TELEGRAM_REACTIONS"): + os.environ["TELEGRAM_REACTIONS"] = str(telegram_cfg["reactions"]).lower() whatsapp_cfg = yaml_cfg.get("whatsapp", {}) if isinstance(whatsapp_cfg, dict): diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index c9da4c9bcc..90812bb345 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -2673,3 +2673,57 @@ class TelegramAdapter(BasePlatformAdapter): auto_skill=topic_skill, timestamp=message.date, ) + + # ── Message reactions (processing lifecycle) ────────────────────────── + + def _reactions_enabled(self) -> bool: + """Check if message reactions are enabled via config/env.""" + return os.getenv("TELEGRAM_REACTIONS", "true").lower() not in ("false", "0", "no") + + async def _set_reaction(self, chat_id: str, message_id: str, emoji: str) -> bool: + """Set a single emoji reaction on a Telegram message.""" + if not self._bot: + return False + try: + await self._bot.set_message_reaction( + chat_id=int(chat_id), + message_id=int(message_id), + reaction=emoji, + ) + return True + except Exception as e: + logger.debug("[%s] set_message_reaction failed (%s): %s", self.name, emoji, e) + return False + + async def _remove_reaction(self, chat_id: str, message_id: str) -> bool: + """Remove all reactions from a Telegram message.""" + if not self._bot: + return False + try: + await self._bot.set_message_reaction( + chat_id=int(chat_id), + message_id=int(message_id), + reaction=None, + ) + return True + except Exception as e: + logger.debug("[%s] remove_reaction failed: %s", self.name, e) + return False + + async def on_processing_start(self, event: MessageEvent) -> None: + """Add an in-progress reaction when message processing begins.""" + if not self._reactions_enabled(): + return + chat_id = getattr(event.source, "chat_id", None) + message_id = getattr(event, "message_id", None) + if chat_id and message_id: + await self._set_reaction(chat_id, message_id, "\U0001f440") + + async def on_processing_complete(self, event: MessageEvent, success: bool) -> None: + """Swap the in-progress reaction for a final success/failure reaction.""" + if not self._reactions_enabled(): + return + chat_id = getattr(event.source, "chat_id", None) + message_id = getattr(event, "message_id", None) + if chat_id and message_id: + await self._set_reaction(chat_id, message_id, "\u2705" if success else "\u274c")