diff --git a/src/mab/bot/_client_sender.py b/src/mab/bot/_client_sender.py index b8daa2d..6da6e6f 100644 --- a/src/mab/bot/_client_sender.py +++ b/src/mab/bot/_client_sender.py @@ -1,4 +1,5 @@ import asyncio +from io import BytesIO import logging from html.parser import HTMLParser from pathlib import Path @@ -214,6 +215,69 @@ class ClientSender: } return (await self.send_content(room, content)).event_id + async def send_image_bytes(self, + room: MatrixRoom | str, + data: bytes, + filename: str, + *, + text: str | None = None, + is_html: bool | None = None, + timeout: float | None = 60 * 60) -> str: + """ + Send the image to `room`. Please note that formatted text is displayed + incorrectly in some clients as of September 8th, 2026 + + Args: + - room - the room to send the text to + - bytes - the image to send + - filename - filename to use for the file + - text - image caption to use (`None` to disable) + - is_html - whether the text is HTML-formatted (`None` for auto) + - timeout - upload timeout in seconds (`None` to disable) + + Returns: + - `event_id` of sent message on success + - Raises an exception on error + """ + # caption must not actually be empty + if text is None or not text.strip(): + text = filename + is_html = False + # check if the file is image + mime_type: str = magic.from_buffer(data, mime=True) + if not mime_type.startswith("image/"): + raise RuntimeError(f"Data has non-image mime-type") + # get image size + buffer = BytesIO(data) + with Image.open(buffer) as image: + width, height = image.size + buffer.seek(0) + # upload + async with asyncio.timeout(timeout): + upload_result = await self._uploader.upload_using_provider( + provider=buffer, + mime_type=mime_type, + filename=filename, + filesize=len(data)) + # prepare the content and send + content = { + "msgtype": "m.image", + "filename": filename, + **self._process_html_text(text, is_html), + "file": { + "url": upload_result.response.content_uri, + "mimetype": mime_type, + **upload_result.keys + }, + "info": { + "mimetype": mime_type, + "size": upload_result.filesize, + "w": width, + "h": height + } + } + return (await self.send_content(room, content)).event_id + async def send_video(self, room: MatrixRoom | str, path: Path | str, diff --git a/src/mab/bot/bot.py b/src/mab/bot/bot.py index b454e2a..4b472a2 100644 --- a/src/mab/bot/bot.py +++ b/src/mab/bot/bot.py @@ -177,6 +177,39 @@ class MatrixBot: timeout=timeout ) + async def send_image_bytes(self, + room: MatrixRoom | str, + data: bytes, + filename: str, + *, + text: str | None = None, + is_html: bool | None = None, + timeout: float | None = 60 * 60) -> str: + """ + Send the image to `room`. Please note that formatted text is displayed + incorrectly in some clients as of September 8th, 2026 + + Args: + - room - the room to send the text to + - bytes - the image to send + - filename - filename to use for the file + - text - image caption to use (`None` to disable) + - is_html - whether the text is HTML-formatted (`None` for auto) + - timeout - upload timeout in seconds (`None` to disable) + + Returns: + - `event_id` of sent message on success + - Raises an exception on error + """ + return await self._client_sender.send_image_bytes( + room=room, + data=data, + filename=filename, + text=text, + is_html=is_html, + timeout=timeout + ) + async def send_video(self, room: MatrixRoom | str, path: Path | str,