From 01b317484fc9612032419942254ab23ef20f3059 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Sun, 27 Oct 2019 13:55:34 +0200 Subject: [PATCH] Add support for formatted captions --- example-config.yaml | 25 ++++++++-------- mautrix_telegram/commands/handler.py | 12 ++++---- mautrix_telegram/commands/telegram/misc.py | 13 ++++---- mautrix_telegram/portal/matrix.py | 35 +++++++++++----------- 4 files changed, 43 insertions(+), 42 deletions(-) diff --git a/example-config.yaml b/example-config.yaml index 5cfed8ea..5d7e4fa8 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -196,20 +196,19 @@ bridge: # Text msgtypes (m.text, m.notice and m.emote) support HTML, media msgtypes don't. # # Available variables: - # $sender_displayname - The display name of the sender (e.g. Example User) - # $sender_username - The username (Matrix ID localpart) of the sender (e.g. exampleuser) - # $sender_mxid - The Matrix ID of the sender (e.g. @exampleuser:example.com) - # $body - The plaintext body (file name for media msgtypes) - # $formatted_body - The message content as HTML (for text msgtypes) + # $sender_displayname - The display name of the sender (e.g. Example User) + # $sender_username - The username (Matrix ID localpart) of the sender (e.g. exampleuser) + # $sender_mxid - The Matrix ID of the sender (e.g. @exampleuser:example.com) + # $message - The message content message_formats: - m.text: "$sender_displayname: $formatted_body" - m.notice: "$sender_displayname: $formatted_body" - m.emote: "* $sender_displayname $formatted_body" - m.file: "$sender_displayname sent a file: $body" - m.image: "$sender_displayname sent an image: $body" - m.audio: "$sender_displayname sent an audio file: $body" - m.video: "$sender_displayname sent a video: $body" - m.location: "$sender_displayname sent a location: $body" + m.text: "$sender_displayname: $message" + m.notice: "$sender_displayname: $message" + m.emote: "* $sender_displayname $message" + m.file: "$sender_displayname sent a file: $message" + m.image: "$sender_displayname sent an image: $message" + m.audio: "$sender_displayname sent an audio file: $message" + m.video: "$sender_displayname sent a video: $message" + m.location: "$sender_displayname sent a location: $message" # Telegram doesn't have built-in emotes, this field specifies how m.emote's from authenticated # users are sent to telegram. All fields in message_formats are supported. Additionally, the # Telegram user info is available in the following variables: diff --git a/mautrix_telegram/commands/handler.py b/mautrix_telegram/commands/handler.py index f50e59e5..cc728be6 100644 --- a/mautrix_telegram/commands/handler.py +++ b/mautrix_telegram/commands/handler.py @@ -18,7 +18,7 @@ from typing import Awaitable, Callable, List, Optional, NamedTuple, Any from telethon.errors import FloodWaitError -from mautrix.types import RoomID, EventID +from mautrix.types import RoomID, EventID, MessageEventContent from mautrix.bridge.commands import (HelpSection, CommandEvent as BaseCommandEvent, CommandHandler as BaseCommandHandler, CommandProcessor as BaseCommandProcessor, @@ -42,10 +42,10 @@ class CommandEvent(BaseCommandEvent): sender: u.User def __init__(self, processor: 'CommandProcessor', room_id: RoomID, event_id: EventID, - sender: u.User, command: str, args: List[str], is_management: bool, - is_portal: bool) -> None: - super().__init__(processor, room_id, event_id, sender, command, args, is_management, - is_portal) + sender: u.User, command: str, args: List[str], content: MessageEventContent, + is_management: bool, is_portal: bool) -> None: + super().__init__(processor, room_id, event_id, sender, command, args, content, + is_management, is_portal) self.bridge = processor.bridge self.tgbot = processor.tgbot self.config = processor.config @@ -69,7 +69,7 @@ class CommandHandler(BaseCommandHandler): def __init__(self, handler: Callable[[CommandEvent], Awaitable[EventID]], management_only: bool, name: str, help_text: str, help_args: str, help_section: HelpSection, needs_auth: bool, needs_puppeting: bool, - needs_matrix_puppeting: bool, needs_admin: bool,) -> None: + needs_matrix_puppeting: bool, needs_admin: bool) -> None: super().__init__(handler, management_only, name, help_text, help_args, help_section, needs_auth=needs_auth, needs_puppeting=needs_puppeting, needs_matrix_puppeting=needs_matrix_puppeting, needs_admin=needs_admin) diff --git a/mautrix_telegram/commands/telegram/misc.py b/mautrix_telegram/commands/telegram/misc.py index f6066ad3..6d9fde77 100644 --- a/mautrix_telegram/commands/telegram/misc.py +++ b/mautrix_telegram/commands/telegram/misc.py @@ -29,7 +29,7 @@ from telethon.tl.functions.messages import (ImportChatInviteRequest, CheckChatIn GetBotCallbackAnswerRequest, SendVoteRequest) from telethon.tl.functions.channels import JoinChannelRequest -from mautrix.types import EventID +from mautrix.types import EventID, Format from ... import puppet as pu, portal as po from ...abstract_user import AbstractUser @@ -45,11 +45,12 @@ async def caption(evt: CommandEvent) -> EventID: if len(evt.args) == 0: return await evt.reply("**Usage:** `$cmdprefix+sp caption `") - text = " ".join(evt.args) - evt.sender.command_status = {"caption": text} - quoted_text = "\n".join(f"> {row}" for row in text.split("\n")) - return await evt.reply("Your next image will be captioned with\n\n" - f"{quoted_text}\n\n" + prefix = f"{evt.command_prefix} caption " + if evt.content.format == Format.HTML: + evt.content.formatted_body = evt.content.formatted_body.replace(prefix, "", 1) + evt.content.body = evt.content.body.replace(prefix, "", 1) + evt.sender.command_status = {"caption": evt.content} + return await evt.reply("Your next image or file will be sent with that caption. " "Use `$cmdprefix+sp cancel` to cancel the caption.") diff --git a/mautrix_telegram/portal/matrix.py b/mautrix_telegram/portal/matrix.py index f59d2222..95c509e6 100644 --- a/mautrix_telegram/portal/matrix.py +++ b/mautrix_telegram/portal/matrix.py @@ -169,7 +169,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC): async def _apply_msg_format(self, sender: 'u.User', content: MessageEventContent ) -> None: - if isinstance(content, TextMessageEventContent) and content.format != Format.HTML: + if not isinstance(content, TextMessageEventContent) or content.format != Format.HTML: content.format = Format.HTML content.formatted_body = escape_html(content.body).replace("\n", "
") @@ -179,14 +179,9 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC): tpl_args = dict(sender_mxid=sender.mxid, sender_username=sender.mxid_localpart, sender_displayname=escape_html(displayname), - body=content.body) - if isinstance(content, TextMessageEventContent): - tpl_args["formatted_body"] = content.formatted_body - tpl_args["message"] = content.formatted_body - content.formatted_body = Template(tpl).safe_substitute(tpl_args) - else: - tpl_args["message"] = content.body - content.body = Template(tpl).safe_substitute(tpl_args) + message=content.formatted_body, + body=content.body, formatted_body=content.formatted_body) + content.formatted_body = Template(tpl).safe_substitute(tpl_args) async def _apply_emote_format(self, sender: 'u.User', content: TextMessageEventContent) -> None: @@ -248,7 +243,8 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC): async def _handle_matrix_file(self, sender_id: TelegramID, event_id: EventID, space: TelegramID, client: 'MautrixTelegramClient', - content: MediaMessageEventContent, reply_to: TelegramID) -> None: + content: MediaMessageEventContent, reply_to: TelegramID, + caption: TextMessageEventContent = None) -> None: mime = content.info.mimetype w, h = content.info.width, content.info.height file_name = content["net.maunium.telegram.internal.filename"] @@ -256,7 +252,7 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC): if config["bridge.parallel_file_transfer"]: file_handle, file_size = await parallel_transfer_to_telegram(client, self.main_intent, - content.url, 0) + content.url, sender_id) else: file = await self.main_intent.download_media(content.url) @@ -283,19 +279,19 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC): media = InputMediaUploadedDocument(file=file_handle, attributes=attributes, mime_type=mime or "application/octet-stream") - caption = content.body if content.body != file_name else None + caption, entities = self._matrix_event_to_entities(caption) if caption else (None, None) async with self.send_lock(sender_id): if await self._matrix_document_edit(client, content, space, caption, media, event_id): return try: response = await client.send_media(self.peer, media, reply_to=reply_to, - caption=caption) + caption=caption, entities=entities) except (PhotoInvalidDimensionsError, PhotoSaveFileInvalidError, PhotoExtInvalidError): media = InputMediaUploadedDocument(file=media.file, mime_type=mime, attributes=attributes) response = await client.send_media(self.peer, media, reply_to=reply_to, - caption=caption) + caption=caption, entities=entities) self._add_telegram_message_to_db(event_id, space, 0, response) async def _matrix_document_edit(self, client: 'MautrixTelegramClient', @@ -364,14 +360,18 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC): media = (MessageType.STICKER, MessageType.IMAGE, MessageType.FILE, MessageType.AUDIO, MessageType.VIDEO) + caption_content = None if content.msgtype in media: content["net.maunium.telegram.internal.filename"] = content.body try: - content.body = sender.command_status["caption"] + caption_content: MessageEventContent = sender.command_status["caption"] + caption_content.msgtype = content.msgtype + reply_to = reply_to or formatter.matrix_reply_to_telegram(caption_content, space, + room_id=self.mxid) sender.command_status = None except (KeyError, TypeError): pass - await self._pre_process_matrix_message(sender, not logged_in, content) + await self._pre_process_matrix_message(sender, not logged_in, caption_content or content) if content.msgtype == MessageType.NOTICE: bridge_notices = self.get_config("bridge_notices.default") @@ -385,7 +385,8 @@ class PortalMatrix(BasePortal, MautrixBasePortal, ABC): await self._handle_matrix_location(sender_id, event_id, space, client, content, reply_to) elif content.msgtype in media: - await self._handle_matrix_file(sender_id, event_id, space, client, content, reply_to) + await self._handle_matrix_file(sender_id, event_id, space, client, content, reply_to, + caption_content) else: self.log.debug(f"Unhandled Matrix event: {content}")