diff --git a/gateway/platforms/telegram.py b/gateway/platforms/telegram.py index cd836b030..1303fcdde 100644 --- a/gateway/platforms/telegram.py +++ b/gateway/platforms/telegram.py @@ -578,23 +578,26 @@ class TelegramAdapter(BasePlatformAdapter): image_path: str, caption: Optional[str] = None, reply_to: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, **kwargs, ) -> SendResult: """Send a local image file natively as a Telegram photo.""" if not self._bot: return SendResult(success=False, error="Not connected") - + try: import os if not os.path.exists(image_path): return SendResult(success=False, error=f"Image file not found: {image_path}") - + + _thread = metadata.get("thread_id") if metadata else None with open(image_path, "rb") as image_file: msg = await self._bot.send_photo( chat_id=int(chat_id), photo=image_file, caption=caption[:1024] if caption else None, reply_to_message_id=int(reply_to) if reply_to else None, + message_thread_id=int(_thread) if _thread else None, ) return SendResult(success=True, message_id=str(msg.message_id)) except Exception as e: @@ -613,6 +616,7 @@ class TelegramAdapter(BasePlatformAdapter): caption: Optional[str] = None, file_name: Optional[str] = None, reply_to: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, **kwargs, ) -> SendResult: """Send a document/file natively as a Telegram file attachment.""" @@ -624,6 +628,7 @@ class TelegramAdapter(BasePlatformAdapter): return SendResult(success=False, error=f"File not found: {file_path}") display_name = file_name or os.path.basename(file_path) + _thread = metadata.get("thread_id") if metadata else None with open(file_path, "rb") as f: msg = await self._bot.send_document( @@ -632,6 +637,7 @@ class TelegramAdapter(BasePlatformAdapter): filename=display_name, caption=caption[:1024] if caption else None, reply_to_message_id=int(reply_to) if reply_to else None, + message_thread_id=int(_thread) if _thread else None, ) return SendResult(success=True, message_id=str(msg.message_id)) except Exception as e: @@ -644,6 +650,7 @@ class TelegramAdapter(BasePlatformAdapter): video_path: str, caption: Optional[str] = None, reply_to: Optional[str] = None, + metadata: Optional[Dict[str, Any]] = None, **kwargs, ) -> SendResult: """Send a video natively as a Telegram video message.""" @@ -654,12 +661,14 @@ class TelegramAdapter(BasePlatformAdapter): if not os.path.exists(video_path): return SendResult(success=False, error=f"Video file not found: {video_path}") + _thread = metadata.get("thread_id") if metadata else None with open(video_path, "rb") as f: msg = await self._bot.send_video( chat_id=int(chat_id), video=f, caption=caption[:1024] if caption else None, reply_to_message_id=int(reply_to) if reply_to else None, + message_thread_id=int(_thread) if _thread else None, ) return SendResult(success=True, message_id=str(msg.message_id)) except Exception as e: diff --git a/tests/gateway/test_send_image_file.py b/tests/gateway/test_send_image_file.py index 847ede90e..25a841717 100644 --- a/tests/gateway/test_send_image_file.py +++ b/tests/gateway/test_send_image_file.py @@ -147,6 +147,26 @@ class TestTelegramSendImageFile: call_kwargs = adapter._bot.send_photo.call_args.kwargs assert len(call_kwargs["caption"]) == 1024 + def test_thread_id_forwarded(self, adapter, tmp_path): + """metadata thread_id is forwarded as message_thread_id (required for Telegram forum groups).""" + img = tmp_path / "shot.png" + img.write_bytes(b"\x89PNG" + b"\x00" * 50) + + mock_msg = MagicMock() + mock_msg.message_id = 43 + adapter._bot.send_photo = AsyncMock(return_value=mock_msg) + + _run( + adapter.send_image_file( + chat_id="12345", + image_path=str(img), + metadata={"thread_id": "789"}, + ) + ) + + call_kwargs = adapter._bot.send_photo.call_args.kwargs + assert call_kwargs["message_thread_id"] == 789 + # --------------------------------------------------------------------------- # Discord send_image_file tests diff --git a/tests/gateway/test_telegram_documents.py b/tests/gateway/test_telegram_documents.py index 6fe9a2453..0472bdbac 100644 --- a/tests/gateway/test_telegram_documents.py +++ b/tests/gateway/test_telegram_documents.py @@ -557,6 +557,25 @@ class TestSendDocument: call_kwargs = connected_adapter._bot.send_document.call_args[1] assert call_kwargs["reply_to_message_id"] == 50 + @pytest.mark.asyncio + async def test_send_document_thread_id(self, connected_adapter, tmp_path): + """metadata thread_id is forwarded as message_thread_id (required for Telegram forum groups).""" + test_file = tmp_path / "report.pdf" + test_file.write_bytes(b"%PDF-1.4 data") + + mock_msg = MagicMock() + mock_msg.message_id = 103 + connected_adapter._bot.send_document = AsyncMock(return_value=mock_msg) + + await connected_adapter.send_document( + chat_id="12345", + file_path=str(test_file), + metadata={"thread_id": "789"}, + ) + + call_kwargs = connected_adapter._bot.send_document.call_args[1] + assert call_kwargs["message_thread_id"] == 789 + class TestTelegramPhotoBatching: @pytest.mark.asyncio @@ -654,3 +673,22 @@ class TestSendVideo: assert result.success is False assert "Not connected" in result.error + + @pytest.mark.asyncio + async def test_send_video_thread_id(self, connected_adapter, tmp_path): + """metadata thread_id is forwarded as message_thread_id (required for Telegram forum groups).""" + test_file = tmp_path / "clip.mp4" + test_file.write_bytes(b"\x00\x00\x00\x1c" + b"ftyp" + b"\x00" * 100) + + mock_msg = MagicMock() + mock_msg.message_id = 201 + connected_adapter._bot.send_video = AsyncMock(return_value=mock_msg) + + await connected_adapter.send_video( + chat_id="12345", + video_path=str(test_file), + metadata={"thread_id": "789"}, + ) + + call_kwargs = connected_adapter._bot.send_video.call_args[1] + assert call_kwargs["message_thread_id"] == 789