diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 905e20d6f4..39267fba31 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -359,7 +359,7 @@ class DiscordAdapter(BasePlatformAdapter): print(f"[{self.name}] Failed to send image attachment, falling back to URL: {e}") return await super().send_image(chat_id, image_url, caption, reply_to) - async def send_typing(self, chat_id: str) -> None: + async def send_typing(self, chat_id: str, metadata=None) -> None: """Send typing indicator.""" if self._client: try: diff --git a/gateway/platforms/homeassistant.py b/gateway/platforms/homeassistant.py index a900ef3b77..930470608e 100644 --- a/gateway/platforms/homeassistant.py +++ b/gateway/platforms/homeassistant.py @@ -419,7 +419,7 @@ class HomeAssistantAdapter(BasePlatformAdapter): except Exception as e: return SendResult(success=False, error=str(e)) - async def send_typing(self, chat_id: str) -> None: + async def send_typing(self, chat_id: str, metadata=None) -> None: """No typing indicator for Home Assistant.""" pass diff --git a/gateway/platforms/slack.py b/gateway/platforms/slack.py index 020843d3ac..3449971f0b 100644 --- a/gateway/platforms/slack.py +++ b/gateway/platforms/slack.py @@ -185,7 +185,7 @@ class SlackAdapter(BasePlatformAdapter): except Exception as e: return SendResult(success=False, error=str(e)) - async def send_typing(self, chat_id: str) -> None: + async def send_typing(self, chat_id: str, metadata=None) -> None: """Slack doesn't have a direct typing indicator API for bots.""" pass diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index 2100ed6172..1ec64b4e09 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -289,6 +289,7 @@ class TelegramAdapter(BasePlatformAdapter): audio_path: str, caption: Optional[str] = None, reply_to: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, ) -> SendResult: """Send audio as a native Telegram voice message or audio file.""" if not self._bot: @@ -302,19 +303,23 @@ class TelegramAdapter(BasePlatformAdapter): with open(audio_path, "rb") as audio_file: # .ogg files -> send as voice (round playable bubble) if audio_path.endswith(".ogg") or audio_path.endswith(".opus"): + _voice_thread = metadata.get("thread_id") if metadata else None msg = await self._bot.send_voice( chat_id=int(chat_id), voice=audio_file, caption=caption[:1024] if caption else None, reply_to_message_id=int(reply_to) if reply_to else None, + message_thread_id=int(_voice_thread) if _voice_thread else None, ) else: # .mp3 and others -> send as audio file + _audio_thread = metadata.get("thread_id") if metadata else None msg = await self._bot.send_audio( chat_id=int(chat_id), audio=audio_file, caption=caption[:1024] if caption else None, reply_to_message_id=int(reply_to) if reply_to else None, + message_thread_id=int(_audio_thread) if _audio_thread else None, ) return SendResult(success=True, message_id=str(msg.message_id)) except Exception as e: @@ -355,6 +360,7 @@ class TelegramAdapter(BasePlatformAdapter): image_url: str, caption: Optional[str] = None, reply_to: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, ) -> SendResult: """Send an image natively as a Telegram photo. @@ -366,11 +372,13 @@ class TelegramAdapter(BasePlatformAdapter): try: # Telegram can send photos directly from URLs (up to ~5MB) + _photo_thread = metadata.get("thread_id") if metadata else None msg = await self._bot.send_photo( chat_id=int(chat_id), photo=image_url, caption=caption[:1024] if caption else None, # Telegram caption limit reply_to_message_id=int(reply_to) if reply_to else None, + message_thread_id=int(_photo_thread) if _photo_thread else None, ) return SendResult(success=True, message_id=str(msg.message_id)) except Exception as e: @@ -401,17 +409,20 @@ class TelegramAdapter(BasePlatformAdapter): animation_url: str, caption: Optional[str] = None, reply_to: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, ) -> SendResult: """Send an animated GIF natively as a Telegram animation (auto-plays inline).""" if not self._bot: return SendResult(success=False, error="Not connected") try: + _anim_thread = metadata.get("thread_id") if metadata else None msg = await self._bot.send_animation( chat_id=int(chat_id), animation=animation_url, caption=caption[:1024] if caption else None, reply_to_message_id=int(reply_to) if reply_to else None, + message_thread_id=int(_anim_thread) if _anim_thread else None, ) return SendResult(success=True, message_id=str(msg.message_id)) except Exception as e: @@ -419,13 +430,15 @@ class TelegramAdapter(BasePlatformAdapter): # Fallback: try as a regular photo return await self.send_image(chat_id, animation_url, caption, reply_to) - async def send_typing(self, chat_id: str) -> None: + async def send_typing(self, chat_id: str, metadata: Optional[Dict[str, Any]] = None) -> None: """Send typing indicator.""" if self._bot: try: + _typing_thread = metadata.get("thread_id") if metadata else None await self._bot.send_chat_action( chat_id=int(chat_id), - action="typing" + action="typing", + message_thread_id=int(_typing_thread) if _typing_thread else None, ) except Exception: pass # Ignore typing indicator failures diff --git a/gateway/platforms/whatsapp.py b/gateway/platforms/whatsapp.py index 285a89eef2..f2000add80 100644 --- a/gateway/platforms/whatsapp.py +++ b/gateway/platforms/whatsapp.py @@ -493,7 +493,7 @@ class WhatsAppAdapter(BasePlatformAdapter): file_name or os.path.basename(file_path), ) - async def send_typing(self, chat_id: str) -> None: + async def send_typing(self, chat_id: str, metadata=None) -> None: """Send typing indicator via bridge.""" if not self._running: return