diff --git a/gateway/platforms/base.py b/gateway/platforms/base.py index 2e818b4ea..dcd97f309 100644 --- a/gateway/platforms/base.py +++ b/gateway/platforms/base.py @@ -425,6 +425,28 @@ class BasePlatformAdapter(ABC): text = f"{caption}\n{image_url}" if caption else image_url return await self.send(chat_id=chat_id, content=text, reply_to=reply_to) + async def send_animation( + self, + chat_id: str, + animation_url: str, + caption: Optional[str] = None, + reply_to: Optional[str] = None, + ) -> SendResult: + """ + Send an animated GIF natively via the platform API. + + Override in subclasses to send GIFs as proper animations + (e.g., Telegram send_animation) so they auto-play inline. + Default falls back to send_image. + """ + return await self.send_image(chat_id=chat_id, image_url=animation_url, caption=caption, reply_to=reply_to) + + @staticmethod + def _is_animation_url(url: str) -> bool: + """Check if a URL points to an animated GIF (vs a static image).""" + lower = url.lower().split('?')[0] # Strip query params + return lower.endswith('.gif') + @staticmethod def extract_images(content: str) -> Tuple[List[Tuple[str, str]], str]: """ @@ -636,11 +658,19 @@ class BasePlatformAdapter(ABC): if human_delay > 0: await asyncio.sleep(human_delay) try: - img_result = await self.send_image( - chat_id=event.source.chat_id, - image_url=image_url, - caption=alt_text if alt_text else None, - ) + # Route animated GIFs through send_animation for proper playback + if self._is_animation_url(image_url): + img_result = await self.send_animation( + chat_id=event.source.chat_id, + animation_url=image_url, + caption=alt_text if alt_text else None, + ) + else: + img_result = await self.send_image( + chat_id=event.source.chat_id, + image_url=image_url, + caption=alt_text if alt_text else None, + ) if not img_result.success: print(f"[{self.name}] Failed to send image: {img_result.error}") except Exception as img_err: diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index c37fde42c..076e97ff5 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -272,6 +272,30 @@ class TelegramAdapter(BasePlatformAdapter): # Fallback: send as text link return await super().send_image(chat_id, image_url, caption, reply_to) + async def send_animation( + self, + chat_id: str, + animation_url: str, + caption: Optional[str] = None, + reply_to: Optional[str] = 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: + 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, + ) + return SendResult(success=True, message_id=str(msg.message_id)) + except Exception as e: + print(f"[{self.name}] Failed to send animation, falling back to photo: {e}") + # 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: """Send typing indicator.""" if self._bot: