diff --git a/gateway/config.py b/gateway/config.py index 6c8825298..ab0d7c118 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 c9da4c9bc..90812bb34 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")