diff --git a/mautrix_telegram/config.py b/mautrix_telegram/config.py index 5093f02c..4f516a6c 100644 --- a/mautrix_telegram/config.py +++ b/mautrix_telegram/config.py @@ -183,6 +183,7 @@ class Config(BaseBridgeConfig): del self["bridge.message_formats"] copy_dict("bridge.message_formats", override_existing_map=False) copy("bridge.emote_format") + copy("bridge.relay_user_distinguishers") copy("bridge.state_event_formats.join") copy("bridge.state_event_formats.leave") diff --git a/mautrix_telegram/example-config.yaml b/mautrix_telegram/example-config.yaml index 1789cfc5..1b516426 100644 --- a/mautrix_telegram/example-config.yaml +++ b/mautrix_telegram/example-config.yaml @@ -330,9 +330,12 @@ bridge: # List of user IDs for whom the previous flag is flipped. # e.g. if bridge_notices.default is false, notices from other users will not be bridged, but # notices from users listed here will be bridged. - exceptions: - - "@importantbot:example.com" + exceptions: [] + # An array of possible values for the $distinguisher variable in message formats. + # Each user gets one of the values here, based on a hash of their user ID. + # If the array is empty, the $distinguisher variable will also be empty. + relay_user_distinguishers: ["🟦", "🟣", "🟩", "⭕️", "🔶", "⬛️", "🔵", "🟢"] # The formats to use when sending messages to Telegram via the relay bot. # Text msgtypes (m.text, m.notice and m.emote) support HTML, media msgtypes don't. # @@ -340,16 +343,17 @@ bridge: # $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) + # $distinguisher - A random string from the options in the relay_user_distinguishers array. # $message - The message content message_formats: - 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" + m.text: "$distinguisher $sender_displayname: $message" + m.notice: "$distinguisher $sender_displayname: $message" + m.emote: "* $distinguisher $sender_displayname $message" + m.file: "$distinguisher $sender_displayname sent a file: $message" + m.image: "$distinguisher $sender_displayname sent an image: $message" + m.audio: "$distinguisher $sender_displayname sent an audio file: $message" + m.video: "$distinguisher $sender_displayname sent a video: $message" + m.location: "$distinguisher $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: @@ -365,9 +369,9 @@ bridge: # # Set format to an empty string to disable the messages for that event. state_event_formats: - join: "$displayname joined the room." - leave: "$displayname left the room." - name_change: "$prev_displayname changed their name to $displayname" + join: "$distinguisher $displayname joined the room." + leave: "$distinguisher $displayname left the room." + name_change: "$distinguisher $prev_displayname changed their name to $distinguisher $displayname" # Filter rooms that can/can't be bridged. Can also be managed using the `filter` and # `filter-mode` management commands. diff --git a/mautrix_telegram/portal.py b/mautrix_telegram/portal.py index 32138e9c..b8bcb942 100644 --- a/mautrix_telegram/portal.py +++ b/mautrix_telegram/portal.py @@ -1194,6 +1194,7 @@ class Portal(DBPortal, BasePortal): "mxid": user.mxid, "username": user.mxid_localpart, "displayname": escape_html(displayname), + "distinguisher": self._get_distinguisher(user.mxid), **kwargs, } return Template(tpl).safe_substitute(tpl_args) @@ -1410,6 +1411,28 @@ class Portal(DBPortal, BasePortal): # We'll just assume the user is already in the chat. pass + @staticmethod + def hash_user_id(val: UserID) -> int: + """ + A simple Matrix user ID hashing algorithm that matches what Element does. + + Args: + val: the Matrix user ID. + + Returns: + A 32-bit hash of the user ID. + """ + out = 0 + for char in val: + out = (out << 5) - out + ord(char) + # Emulate JS's 32-bit signed bitwise OR `hash |= 0` + out = (out & 2**31 - 1) - (out & 2**31) + return abs(out) + + def _get_distinguisher(self, user_id: UserID) -> str: + ruds = self.get_config("relay_user_distinguishers") or [] + return ruds[self.hash_user_id(user_id) % len(ruds)] if ruds else "" + 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 @@ -1427,6 +1450,7 @@ class Portal(DBPortal, BasePortal): message=content.formatted_body, body=content.body, formatted_body=content.formatted_body, + distinguisher=self._get_distinguisher(sender.mxid), ) content.formatted_body = Template(tpl).safe_substitute(tpl_args)