Jelajahi Sumber

Add relaybot functionality for bridge user

a000a 4 tahun lalu
induk
melakukan
52312e46ed

+ 19 - 0
mautrix_signal/config.py

@@ -99,6 +99,10 @@ class Config(BaseBridgeConfig):
 
 
         copy_dict("bridge.permissions")
         copy_dict("bridge.permissions")
 
 
+        copy("bridge.relaybot.enable")
+        copy("bridge.relaybot.users")
+        copy_dict("bridge.relaybot.message_formats")
+
     def _get_permissions(self, key: str) -> Permissions:
     def _get_permissions(self, key: str) -> Permissions:
         level = self["bridge.permissions"].get(key, "")
         level = self["bridge.permissions"].get(key, "")
         admin = level == "admin"
         admin = level == "admin"
@@ -115,3 +119,18 @@ class Config(BaseBridgeConfig):
             return self._get_permissions(homeserver)
             return self._get_permissions(homeserver)
 
 
         return self._get_permissions("*")
         return self._get_permissions("*")
+
+    def get_relay_users(self, mxid: UserID) -> Permissions:
+        relay_users = self["bridge.relaybot.users"]
+        if not isinstance(relay_users, list):
+            return True
+        if len(relay_users) == 0:
+            return True
+        if mxid in relay_users:
+            return True
+
+        _, homeserver = Client.parse_user_id(mxid)
+        if homeserver in relay_users: 
+            return True
+
+        return False

+ 18 - 0
mautrix_signal/example-config.yaml

@@ -197,6 +197,24 @@ bridge:
         "example.com": "user"
         "example.com": "user"
         "@admin:example.com": "admin"
         "@admin:example.com": "admin"
 
 
+    relaybot:
+        enable: false
+        # The formats to use when sending messages to Signal via the relaybot.
+        # (markdown/html is not supported by signal yet.)
+        message_formats:
+            m.text: '*{{ .Sender.Displayname }}*: {{ .Message }}'
+            m.notice: '*{{ .Sender.Displayname }}*: {{ .Message }}'
+            m.emote: '*{{ .Sender.Displayname }}* {{ .Message }}'
+            m.file: '*{{ .Sender.Displayname }}* sent a file'
+            m.image: '*{{ .Sender.Displayname }}* sent an image'
+            m.audio: '*{{ .Sender.Displayname }}* sent an audio file'
+            m.video: '*{{ .Sender.Displayname }}* sent a video'
+            m.location: '*{{ .Sender.Displayname }}* sent a location'
+        # Users that may function as relaybot, means messages in signal are sent by these users.
+        # If empty all users may function as relaybot. Only the last reaction can be handled by signal.
+        users:
+            - '@signal_relaybot:example.com'
+
 
 
 # Python logging configuration.
 # Python logging configuration.
 #
 #

+ 3 - 0
mautrix_signal/matrix.py

@@ -162,3 +162,6 @@ class MatrixHandler(BaseMatrixHandler):
             await portal.handle_matrix_name(user, evt.content.name)
             await portal.handle_matrix_name(user, evt.content.name)
         elif evt.type == EventType.ROOM_AVATAR:
         elif evt.type == EventType.ROOM_AVATAR:
             await portal.handle_matrix_avatar(user, evt.content.url)
             await portal.handle_matrix_avatar(user, evt.content.url)
+
+    async def allow_bridging_message(self, user: 'BaseUser', portal: 'BasePortal') -> bool:
+        return self.config['bridge.relaybot.enable'] or await user.is_logged_in()

+ 53 - 0
mautrix_signal/portal.py

@@ -25,6 +25,10 @@ import os.path
 import time
 import time
 import os
 import os
 
 
+# for relaybot
+from html import escape as escape_html
+from string import Template
+
 from mausignald.types import (Address, MessageData, Reaction, Quote, Group, Contact, Profile,
 from mausignald.types import (Address, MessageData, Reaction, Quote, Group, Contact, Profile,
                               Attachment, GroupID, GroupV2ID, GroupV2, Mention, Sticker,
                               Attachment, GroupID, GroupV2ID, GroupV2, Mention, Sticker,
                               GroupAccessControl, AccessControlMode, GroupMemberRole)
                               GroupAccessControl, AccessControlMode, GroupMemberRole)
@@ -33,6 +37,7 @@ from mautrix.appservice import AppService, IntentAPI
 from mautrix.bridge import BasePortal, async_getter_lock
 from mautrix.bridge import BasePortal, async_getter_lock
 from mautrix.types import (EventID, MessageEventContent, RoomID, EventType, MessageType,
 from mautrix.types import (EventID, MessageEventContent, RoomID, EventType, MessageType,
                            MessageEvent, EncryptedEvent, ContentURI, MediaMessageEventContent,
                            MessageEvent, EncryptedEvent, ContentURI, MediaMessageEventContent,
+                           UserID, TextMessageEventContent, Format, #for relaybot
                            ImageInfo, VideoInfo, FileInfo, AudioInfo, PowerLevelStateEventContent)
                            ImageInfo, VideoInfo, FileInfo, AudioInfo, PowerLevelStateEventContent)
 from mautrix.errors import MatrixError, MForbidden, IntentError
 from mautrix.errors import MatrixError, MForbidden, IntentError
 
 
@@ -187,12 +192,44 @@ class Portal(DBPortal, BasePortal):
             data = await self.main_intent.download_media(message.url)
             data = await self.main_intent.download_media(message.url)
         return self._write_outgoing_file(data)
         return self._write_outgoing_file(data)
 
 
+    async def get_displayname(self, user: 'u.User') -> str:
+        return await self.main_intent.get_room_displayname(self.mxid, user.mxid) or user.mxid
+
+    async def _apply_msg_format(self, sender: 'u.User', content: MessageEventContent
+                                ) -> None:
+        if not isinstance(content, TextMessageEventContent) or content.format != Format.HTML:
+            content.format = Format.HTML
+            content.formatted_body = escape_html(content.body).replace("\n", "<br/>")
+ 
+        tpl = (self.config[f"relaybot.message_formats.[{content.msgtype.value}]"]
+               or "*$sender_displayname*: $message")
+        displayname = await self.get_displayname(sender)
+        tpl_args = dict(sender_mxid=sender.mxid,
+                        sender_username=sender.mxid, #TODO
+                        sender_displayname=escape_html(displayname),
+                        message=content.formatted_body,
+                        body=content.body, formatted_body=content.formatted_body)             
+        content.formatted_body = Template(tpl).safe_substitute(tpl_args)
+        content.body = Template(tpl).safe_substitute(tpl_args)
+
     async def handle_matrix_message(self, sender: 'u.User', message: MessageEventContent,
     async def handle_matrix_message(self, sender: 'u.User', message: MessageEventContent,
                                     event_id: EventID) -> None:
                                     event_id: EventID) -> None:
         if ((message.get(self.bridge.real_user_content_key, False)
         if ((message.get(self.bridge.real_user_content_key, False)
              and await p.Puppet.get_by_custom_mxid(sender.mxid))):
              and await p.Puppet.get_by_custom_mxid(sender.mxid))):
             self.log.debug(f"Ignoring puppet-sent message by confirmed puppet user {sender.mxid}")
             self.log.debug(f"Ignoring puppet-sent message by confirmed puppet user {sender.mxid}")
             return
             return
+        if not await sender.is_logged_in() and self.config['bridge.relaybot.enable']:
+            self.log.trace(f"Message sent by non signal-user {sender.mxid}")
+            async for user in u.User.all_logged_in():
+                await self._apply_msg_format(sender, message)
+                if await user.is_in_portal(self) and user.is_relaybot:
+                    await self._handle_matrix_message(user, message, event_id, True)
+                    return
+        else:
+            await self._handle_matrix_message(sender, message, event_id)
+
+    async def _handle_matrix_message(self, sender: 'u.User', message: MessageEventContent,
+            event_id: EventID, relay_sender = None) -> None:
         request_id = int(time.time() * 1000)
         request_id = int(time.time() * 1000)
         self._msgts_dedup.appendleft((sender.address, request_id))
         self._msgts_dedup.appendleft((sender.address, request_id))
 
 
@@ -213,6 +250,9 @@ class Portal(DBPortal, BasePortal):
             attachment = self._make_attachment(message, attachment_path)
             attachment = self._make_attachment(message, attachment_path)
             attachments = [attachment]
             attachments = [attachment]
             text = None
             text = None
+            if relay_sender:
+                self.log.trace(f"Format text for relay")
+                text = message.body
             self.log.trace("Formed outgoing attachment %s", attachment)
             self.log.trace("Formed outgoing attachment %s", attachment)
         else:
         else:
             self.log.debug(f"Unknown msgtype {message.msgtype} in Matrix message {event_id}")
             self.log.debug(f"Unknown msgtype {message.msgtype} in Matrix message {event_id}")
@@ -243,6 +283,19 @@ class Portal(DBPortal, BasePortal):
             self.log.debug(f"Ignoring reaction to unknown event {reacting_to}")
             self.log.debug(f"Ignoring reaction to unknown event {reacting_to}")
             return
             return
 
 
+        if not await sender.is_logged_in() and self.config['bridge.relaybot.enable']:
+            self.log.trace(f"Reaction by non signal-user {sender.mxid}")
+            react_permitted = False
+            async for user in u.User.all_logged_in():
+                if await user.is_in_portal(self) and user.is_relaybot:
+                    self.log.trace(f"Set new sender to {user.mxid}")
+                    react_permitted = True
+                    sender=user
+                    break
+            if not react_permitted:
+                self.log.debug(f"Reaction not permitted.")
+                return
+
         existing = await DBReaction.get_by_signal_id(self.chat_id, self.receiver, message.sender,
         existing = await DBReaction.get_by_signal_id(self.chat_id, self.receiver, message.sender,
                                                      message.timestamp, sender.address)
                                                      message.timestamp, sender.address)
         if existing and existing.emoji == emoji:
         if existing and existing.emoji == emoji:

+ 1 - 0
mautrix_signal/user.py

@@ -64,6 +64,7 @@ class User(DBUser, BaseUser):
         self._connected = False
         self._connected = False
         perms = self.config.get_permissions(mxid)
         perms = self.config.get_permissions(mxid)
         self.is_whitelisted, self.is_admin, self.permission_level = perms
         self.is_whitelisted, self.is_admin, self.permission_level = perms
+        self.is_relaybot = self.config.get_relay_users(mxid)
 
 
     @classmethod
     @classmethod
     def init_cls(cls, bridge: 'SignalBridge') -> None:
     def init_cls(cls, bridge: 'SignalBridge') -> None: