fix(discord): preserve native document and video attachment support
Salvaged from PR #1115 onto current main by reusing the shared Discord file-attachment helper for local video and document sends, including file_name support for documents and regression coverage.
This commit is contained in:
@@ -669,6 +669,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||||||
chat_id: str,
|
chat_id: str,
|
||||||
file_path: str,
|
file_path: str,
|
||||||
caption: Optional[str] = None,
|
caption: Optional[str] = None,
|
||||||
|
file_name: Optional[str] = None,
|
||||||
) -> SendResult:
|
) -> SendResult:
|
||||||
"""Send a local file as a Discord attachment."""
|
"""Send a local file as a Discord attachment."""
|
||||||
if not self._client:
|
if not self._client:
|
||||||
@@ -680,7 +681,7 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||||||
if not channel:
|
if not channel:
|
||||||
return SendResult(success=False, error=f"Channel {chat_id} not found")
|
return SendResult(success=False, error=f"Channel {chat_id} not found")
|
||||||
|
|
||||||
filename = os.path.basename(file_path)
|
filename = file_name or os.path.basename(file_path)
|
||||||
with open(file_path, "rb") as fh:
|
with open(file_path, "rb") as fh:
|
||||||
file = discord.File(fh, filename=filename)
|
file = discord.File(fh, filename=filename)
|
||||||
msg = await channel.send(content=caption if caption else None, file=file)
|
msg = await channel.send(content=caption if caption else None, file=file)
|
||||||
@@ -1142,6 +1143,41 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||||||
)
|
)
|
||||||
return await super().send_image(chat_id, image_url, caption, reply_to)
|
return await super().send_image(chat_id, image_url, caption, reply_to)
|
||||||
|
|
||||||
|
async def send_video(
|
||||||
|
self,
|
||||||
|
chat_id: str,
|
||||||
|
video_path: str,
|
||||||
|
caption: Optional[str] = None,
|
||||||
|
reply_to: Optional[str] = None,
|
||||||
|
metadata: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> SendResult:
|
||||||
|
"""Send a local video file natively as a Discord attachment."""
|
||||||
|
try:
|
||||||
|
return await self._send_file_attachment(chat_id, video_path, caption)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return SendResult(success=False, error=f"Video file not found: {video_path}")
|
||||||
|
except Exception as e: # pragma: no cover - defensive logging
|
||||||
|
logger.error("[%s] Failed to send local video, falling back to base adapter: %s", self.name, e, exc_info=True)
|
||||||
|
return await super().send_video(chat_id, video_path, caption, reply_to, metadata=metadata)
|
||||||
|
|
||||||
|
async def send_document(
|
||||||
|
self,
|
||||||
|
chat_id: str,
|
||||||
|
file_path: str,
|
||||||
|
caption: Optional[str] = None,
|
||||||
|
file_name: Optional[str] = None,
|
||||||
|
reply_to: Optional[str] = None,
|
||||||
|
metadata: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> SendResult:
|
||||||
|
"""Send an arbitrary file natively as a Discord attachment."""
|
||||||
|
try:
|
||||||
|
return await self._send_file_attachment(chat_id, file_path, caption, file_name=file_name)
|
||||||
|
except FileNotFoundError:
|
||||||
|
return SendResult(success=False, error=f"File not found: {file_path}")
|
||||||
|
except Exception as e: # pragma: no cover - defensive logging
|
||||||
|
logger.error("[%s] Failed to send document, falling back to base adapter: %s", self.name, e, exc_info=True)
|
||||||
|
return await super().send_document(chat_id, file_path, caption, file_name, reply_to, metadata=metadata)
|
||||||
|
|
||||||
async def send_typing(self, chat_id: str, metadata=None) -> None:
|
async def send_typing(self, chat_id: str, metadata=None) -> None:
|
||||||
"""Send typing indicator."""
|
"""Send typing indicator."""
|
||||||
if self._client:
|
if self._client:
|
||||||
|
|||||||
@@ -199,6 +199,57 @@ class TestDiscordSendImageFile:
|
|||||||
assert result.message_id == "99"
|
assert result.message_id == "99"
|
||||||
mock_channel.send.assert_awaited_once()
|
mock_channel.send.assert_awaited_once()
|
||||||
|
|
||||||
|
def test_send_document_uploads_file_attachment(self, adapter, tmp_path):
|
||||||
|
"""send_document should upload a native Discord attachment."""
|
||||||
|
pdf = tmp_path / "sample.pdf"
|
||||||
|
pdf.write_bytes(b"%PDF-1.4\n%\xe2\xe3\xcf\xd3\n")
|
||||||
|
|
||||||
|
mock_channel = MagicMock()
|
||||||
|
mock_msg = MagicMock()
|
||||||
|
mock_msg.id = 100
|
||||||
|
mock_channel.send = AsyncMock(return_value=mock_msg)
|
||||||
|
adapter._client.get_channel = MagicMock(return_value=mock_channel)
|
||||||
|
|
||||||
|
with patch.object(discord_mod_ref, "File", MagicMock()) as file_cls:
|
||||||
|
result = _run(
|
||||||
|
adapter.send_document(
|
||||||
|
chat_id="67890",
|
||||||
|
file_path=str(pdf),
|
||||||
|
file_name="renamed.pdf",
|
||||||
|
metadata={"thread_id": "123"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.success
|
||||||
|
assert result.message_id == "100"
|
||||||
|
assert "file" in mock_channel.send.call_args.kwargs
|
||||||
|
assert file_cls.call_args.kwargs["filename"] == "renamed.pdf"
|
||||||
|
|
||||||
|
def test_send_video_uploads_file_attachment(self, adapter, tmp_path):
|
||||||
|
"""send_video should upload a native Discord attachment."""
|
||||||
|
video = tmp_path / "clip.mp4"
|
||||||
|
video.write_bytes(b"\x00\x00\x00\x18ftypmp42" + b"\x00" * 50)
|
||||||
|
|
||||||
|
mock_channel = MagicMock()
|
||||||
|
mock_msg = MagicMock()
|
||||||
|
mock_msg.id = 101
|
||||||
|
mock_channel.send = AsyncMock(return_value=mock_msg)
|
||||||
|
adapter._client.get_channel = MagicMock(return_value=mock_channel)
|
||||||
|
|
||||||
|
with patch.object(discord_mod_ref, "File", MagicMock()) as file_cls:
|
||||||
|
result = _run(
|
||||||
|
adapter.send_video(
|
||||||
|
chat_id="67890",
|
||||||
|
video_path=str(video),
|
||||||
|
metadata={"thread_id": "123"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.success
|
||||||
|
assert result.message_id == "101"
|
||||||
|
assert "file" in mock_channel.send.call_args.kwargs
|
||||||
|
assert file_cls.call_args.kwargs["filename"] == "clip.mp4"
|
||||||
|
|
||||||
def test_returns_error_when_file_missing(self, adapter):
|
def test_returns_error_when_file_missing(self, adapter):
|
||||||
result = _run(
|
result = _run(
|
||||||
adapter.send_image_file(chat_id="67890", image_path="/nonexistent.png")
|
adapter.send_image_file(chat_id="67890", image_path="/nonexistent.png")
|
||||||
|
|||||||
Reference in New Issue
Block a user