From 3ced968494cbbdbb6aae027e06f674d8e41d6726 Mon Sep 17 00:00:00 2001 From: Andrew Ferrazzutti Date: Tue, 5 Apr 2022 02:44:03 -0400 Subject: [PATCH] Inbound replies (and some kwarg cleanup) --- matrix_appservice_kakaotalk/portal.py | 127 ++++++++++++++++---------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/matrix_appservice_kakaotalk/portal.py b/matrix_appservice_kakaotalk/portal.py index 311922b..05623fb 100644 --- a/matrix_appservice_kakaotalk/portal.py +++ b/matrix_appservice_kakaotalk/portal.py @@ -31,7 +31,7 @@ import time from mautrix.appservice import IntentAPI from mautrix.bridge import BasePortal, NotificationDisabler, async_getter_lock -from mautrix.errors import MatrixError +from mautrix.errors import MatrixError, MForbidden, MNotFound, SessionNotFound from mautrix.types import ( AudioInfo, ContentURI, @@ -73,6 +73,7 @@ from .kt.types.chat.attachment import ( MediaAttachment, MultiPhotoAttachment, PhotoAttachment, + ReplyAttachment, VideoAttachment, ) @@ -190,6 +191,7 @@ class Portal(DBPortal, BasePortal): # TODO More cls._message_type_handler_map = { KnownChatType.TEXT: cls._handle_remote_text, + KnownChatType.REPLY: cls._handle_remote_reply, KnownChatType.PHOTO: cls._handle_remote_photo, KnownChatType.MULTIPHOTO: cls._handle_remote_multiphoto, KnownChatType.VIDEO: cls._handle_remote_video, @@ -953,6 +955,38 @@ class Portal(DBPortal, BasePortal): return False return True + async def _add_remote_reply( + self, content: MessageEventContent, reply_to: ReplyAttachment + ) -> None: + message = await DBMessage.get_by_ktid(reply_to.src_logId, self.kt_receiver) + if not message: + self.log.warning( + f"Couldn't find reply target {reply_to.src_logId} to bridge reply metadata to Matrix" + ) + return + + content.set_reply(message.mxid) + if not isinstance(content, TextMessageEventContent): + return + + try: + evt = await self.main_intent.get_event(message.mx_room, message.mxid) + except (MNotFound, MForbidden): + evt = None + if not evt: + return + + if evt.type == EventType.ROOM_ENCRYPTED: + try: + evt = await self.matrix.e2ee.decrypt(evt, wait_session_timeout=0) + except SessionNotFound: + return + + if isinstance(evt.content, TextMessageEventContent): + evt.content.trim_reply_fallback() + + content.set_reply(evt) + async def handle_remote_message( self, source: u.User, @@ -1039,52 +1073,58 @@ class Portal(DBPortal, BasePortal): type_str, f"text = {message_text}" if message_text is not None else "no text", ) + if message_text: + events = await self._handle_remote_text( + intent=intent, + timestamp=timestamp, + message_text=message_text, + ) + else: + events = [] content = TextMessageEventContent( msgtype=MessageType.NOTICE, body=f"\u26a0 Unbridgeable message ({type_str})", ) - # TODO Replies - return [await self._send_message(intent, content, timestamp=timestamp)] + if events: + content.set_reply(events[-1]) + events.append(await self._send_message(intent, content, timestamp=timestamp)) + return events async def _handle_remote_text( self, intent: IntentAPI, timestamp: int, - message_text: str, + message_text: str | None, **_ ) -> list[EventID]: # TODO Handle mentions properly content = await kakaotalk_to_matrix(message_text) - # TODO Replies return [await self._send_message(intent, content, timestamp=timestamp)] - def _handle_remote_photo( + async def _handle_remote_reply( self, - source: u.User, intent: IntentAPI, - attachment: PhotoAttachment, + attachment: ReplyAttachment, timestamp: int, - message_text: str | None, + message_text: str, **_ - ) -> Awaitable[list[EventID]]: - return asyncio.gather(self._handle_remote_uniphoto( - source, intent, attachment, timestamp, message_text - )) + ) -> list[EventID]: + content = await kakaotalk_to_matrix(message_text) + await self._add_remote_reply(content, attachment) + return [await self._send_message(intent, content, timestamp=timestamp)] + + def _handle_remote_photo(self, **kwargs) -> Awaitable[list[EventID]]: + return asyncio.gather(self._handle_remote_uniphoto(**kwargs)) async def _handle_remote_multiphoto( self, - source: u.User, - intent: IntentAPI, attachment: MultiPhotoAttachment, - timestamp: int, - message_text: str | None, - **_ + **kwargs ) -> Awaitable[list[EventID]]: # TODO Upload media concurrently, but post messages sequentially return [ await self._handle_remote_uniphoto( - source, intent, - PhotoAttachment( + attachment=PhotoAttachment( shout=attachment.shout, mentions=attachment.mentions, urls=attachment.urls, @@ -1099,22 +1139,18 @@ class Portal(DBPortal, BasePortal): cs=attachment.csl[i], mt=attachment.mtl[i], ), - timestamp, message_text, + **kwargs ) for i in range(len(attachment.imageUrls)) ] def _handle_remote_uniphoto( self, - source: u.User, - intent: IntentAPI, attachment: PhotoAttachment, - timestamp: int, - message_text: str | None, - **_ + **kwargs ) -> Awaitable[EventID]: return self._handle_remote_media( - source, intent, attachment, timestamp, message_text, + attachment, ImageInfo( mimetype=attachment.mt, size=attachment.s, @@ -1122,72 +1158,66 @@ class Portal(DBPortal, BasePortal): height=attachment.h, ), MessageType.IMAGE, + **kwargs ) def _handle_remote_video( self, - source: u.User, - intent: IntentAPI, attachment: VideoAttachment, - timestamp: int, - message_text: str | None, - **_ + **kwargs ) -> Awaitable[list[EventID]]: return asyncio.gather(self._handle_remote_media( - source, intent, attachment, timestamp, message_text, + attachment, VideoInfo( duration=attachment.d, width=attachment.w, height=attachment.h, ), MessageType.VIDEO, + **kwargs )) def _handle_remote_audio( self, - source: u.User, - intent: IntentAPI, attachment: AudioAttachment, - timestamp: int, - message_text: str | None, - **_ + **kwargs ) -> Awaitable[list[EventID]]: return asyncio.gather(self._handle_remote_media( - source, intent, attachment, timestamp, message_text, + attachment, AudioInfo( size=attachment.s, duration=attachment.d, ), MessageType.AUDIO, + **kwargs )) """ TODO Find what auth is required for reading file contents def _handle_remote_file( self, - source: u.User, - intent: IntentAPI, attachment: FileAttachment, - timestamp: int, - message_text: str | None, + **kwargs ) -> Awaitable[list[EventID]]: return asyncio.gather(self._handle_remote_media( - source, intent, attachment, timestamp, message_text, + attachment, FileInfo( size=attachment.size, ), MessageType.FILE, + **kwargs )) """ async def _handle_remote_media( self, - source: u.User, - intent: IntentAPI, attachment: MediaAttachment, - timestamp: int, - message_text: str | None, info: MediaInfo, msgtype: MessageType, + *, + source: u.User, + intent: IntentAPI, + timestamp: int, + message_text: str | None, **_ ) -> EventID: mxc, additional_info, decryption_info = await self._reupload_remote_file( @@ -1203,7 +1233,6 @@ class Portal(DBPortal, BasePortal): content = MediaMessageEventContent( url=mxc, file=decryption_info, msgtype=msgtype, body=message_text, info=info ) - # TODO Replies return await self._send_message(intent, content, timestamp=timestamp) # TODO Many more remote handlers