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)