diff --git a/example-config.yaml b/example-config.yaml
index 2e697f97..cadd50c8 100644
--- a/example-config.yaml
+++ b/example-config.yaml
@@ -120,18 +120,27 @@ bridge:
# Telegram doesn't have built-in emotes, so the m.emote format is also used for non-relaybot users.
#
# Available variables:
- # $sender_display_name - The display name of the sender (e.g. Example User)
+ # $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 as HTML
message_formats:
- m.text: "<$sender_display_name> $message"
- m.emote: "* $sender_display_name $message"
- m.file: "$sender_display_name sent a file: $message"
- m.image: "$sender_display_name sent an image: $message"
- m.audio: "$sender_display_name sent an audio file: $message"
- m.video: "$sender_display_name sent a video: $message"
- m.location: "$sender_display_name sent a location: $message"
+ m.text: "$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"
+
+ # The format sto use when sending state events to Telegram via the relay bot.
+ #
+ # Variables from `message_formats` that have the `sender_` prefix are available without the prefix.
+ # In name_change events, `$prev_displayname` is the previous displayname.
+ state_event_formats:
+ join: "$displayname joined the room."
+ leave: "$displayname left the room."
+ name_change: "$prev_displayname changed their name to $displayname"
filter:
# Filter mode to use. Either "blacklist" or "whitelist".
diff --git a/mautrix_telegram/config.py b/mautrix_telegram/config.py
index a26dd8ef..ec922e13 100644
--- a/mautrix_telegram/config.py
+++ b/mautrix_telegram/config.py
@@ -182,6 +182,9 @@ class Config(DictWithRecursion):
copy("bridge.catch_up")
copy_dict("bridge.message_formats")
+ copy("bridge.state_event_formats.join")
+ copy("bridge.state_event_formats.leave")
+ copy("bridge.state_event_formats.name_change")
copy("bridge.filter.mode")
copy("bridge.filter.list")
diff --git a/mautrix_telegram/matrix.py b/mautrix_telegram/matrix.py
index 0a3bd34f..662cf48a 100644
--- a/mautrix_telegram/matrix.py
+++ b/mautrix_telegram/matrix.py
@@ -16,6 +16,7 @@
# along with this program. If not, see .
import logging
import asyncio
+import re
from mautrix_appservice import MatrixRequestError, IntentError
@@ -264,6 +265,15 @@ class MatrixHandler:
# All pinned events removed, remove pinned event in Telegram.
await portal.handle_matrix_pin(sender, None)
+ async def handle_name_change(self, room, user, displayname, prev_displayname, event_id):
+ portal = Portal.get_by_mxid(room)
+ if not portal or not portal.has_bot:
+ return
+
+ user = await User.get_by_mxid(user).ensure_started()
+ if await user.needs_relaybot(portal):
+ await portal.name_change_matrix(user, displayname, prev_displayname, event_id)
+
def filter_matrix_event(self, event):
return (event["sender"] == self.az.bot_mxid
or Puppet.get_id_from_mxid(event["sender"]) is not None)
@@ -273,36 +283,43 @@ class MatrixHandler:
return
self.log.debug("Received event: %s", evt)
type = evt["type"]
+ room_id = evt["room_id"]
+ event_id = evt["event_id"]
+ sender = evt["sender"]
content = evt.get("content", {})
if type == "m.room.member":
+ state_key = evt["state_key"]
prev_content = evt.get("unsigned", {}).get("prev_content", {})
membership = content.get("membership", "")
prev_membership = prev_content.get("membership", "leave")
if membership == prev_membership:
- # TODO handle displayname/avatar changes
- pass
+ match = re.compile("@(.+):(.+)").match(state_key)
+ localpart = match.group(1)
+ displayname = content.get("displayname", localpart)
+ prev_displayname = prev_content.get("displayname", localpart)
+ if displayname != prev_displayname:
+ await self.handle_name_change(room_id, state_key, displayname,
+ prev_displayname, event_id)
elif membership == "invite":
- await self.handle_invite(evt["room_id"], evt["state_key"], evt["sender"])
+ await self.handle_invite(room_id, state_key, sender)
elif prev_membership == "join" and membership == "leave":
- await self.handle_part(evt["room_id"], evt["state_key"], evt["sender"],
- evt["event_id"])
+ await self.handle_part(room_id, state_key, sender, event_id)
elif membership == "join":
- await self.handle_join(evt["room_id"], evt["state_key"], evt["event_id"])
+ await self.handle_join(room_id, state_key, event_id)
elif type in ("m.room.message", "m.sticker"):
if type != "m.room.message":
content["msgtype"] = type
- await self.handle_message(evt["room_id"], evt["sender"], content, evt["event_id"])
+ await self.handle_message(room_id, sender, content, event_id)
elif type == "m.room.redaction":
- await self.handle_redaction(evt["room_id"], evt["sender"], evt["redacts"])
+ await self.handle_redaction(room_id, sender, evt["redacts"])
elif type == "m.room.power_levels":
- await self.handle_power_levels(evt["room_id"], evt["sender"], evt["content"],
- evt["prev_content"])
+ await self.handle_power_levels(room_id, sender, evt["content"], evt["prev_content"])
elif type in ("m.room.name", "m.room.avatar", "m.room.topic"):
- await self.handle_room_meta(type, evt["room_id"], evt["sender"], evt["content"])
+ await self.handle_room_meta(type, room_id, sender, evt["content"])
elif type == "m.room.pinned_events":
new_events = set(evt["content"]["pinned"])
try:
old_events = set(evt["unsigned"]["prev_content"]["pinned"])
except KeyError:
old_events = set()
- await self.handle_room_pin(evt["room_id"], evt["sender"], new_events, old_events)
+ await self.handle_room_pin(room_id, sender, new_events, old_events)
diff --git a/mautrix_telegram/portal.py b/mautrix_telegram/portal.py
index dbb2dcfc..1459b3f4 100644
--- a/mautrix_telegram/portal.py
+++ b/mautrix_telegram/portal.py
@@ -618,11 +618,42 @@ class Portal:
else:
return ""
+ async def _get_state_change_message(self, event, user, arguments={}):
+ tpl = config[f"bridge.state_event_formats.{event}"]
+ displayname = await self.get_displayname(user)
+
+ tpl_args = dict(mxid=user.mxid,
+ username=user.mxid_localpart,
+ displayname=displayname)
+ tpl_args = {**tpl_args, **arguments}
+ message = Template(tpl).safe_substitute(tpl_args)
+ return {
+ "format": "org.matrix.custom.html",
+ "formatted_body": message,
+ }
+
+ async def name_change_matrix(self, user, displayname, prev_displayname, event_id):
+ async with self.require_send_lock(self.bot.tgid):
+ message = await self._get_state_change_message(
+ "name_change", user,
+ dict(displayname=displayname, prev_displayname=prev_displayname))
+ response = await self.bot.client.send_message(
+ self.peer, message,
+ parse_mode=self._matrix_event_to_entities)
+ space = self.tgid if self.peer_type == "channel" else self.bot.tgid
+ self.is_duplicate(response, (event_id, space))
+
+ async def get_displayname(self, user):
+ return (await self.main_intent.get_displayname(self.mxid, user.mxid)
+ or user.mxid_localpart)
+
async def leave_matrix(self, user, source, event_id):
if await user.needs_relaybot(self):
async with self.require_send_lock(self.bot.tgid):
+ message = await self._get_state_change_message("leave", user)
response = await self.bot.client.send_message(
- self.peer, f"__{user.displayname} left the room.__")
+ self.peer, message,
+ parse_mode=self._matrix_event_to_entities)
space = self.tgid if self.peer_type == "channel" else self.bot.tgid
self.is_duplicate(response, (event_id, space))
return
@@ -653,8 +684,10 @@ class Portal:
async def join_matrix(self, user, event_id):
if await user.needs_relaybot(self):
async with self.require_send_lock(self.bot.tgid):
+ message = await self._get_state_change_message("join", user)
response = await self.bot.client.send_message(
- self.peer, f"__{user.displayname} joined the room.__")
+ self.peer, message,
+ parse_mode=self._matrix_event_to_entities)
space = self.tgid if self.peer_type == "channel" else self.bot.tgid
self.is_duplicate(response, (event_id, space))
return
@@ -665,29 +698,28 @@ class Portal:
# We'll just assume the user is already in the chat.
pass
- @staticmethod
- def _apply_msg_format(sender, msgtype, message):
+ async def _apply_msg_format(self, sender, msgtype, message):
if "formatted_body" not in message:
message["format"] = "org.matrix.custom.html"
- message["formatted_body"] = escape_html(message["body"])
+ message["formatted_body"] = escape_html(message.get("body", ""))
body = message["formatted_body"]
tpl = config["bridge.message_formats"].get(msgtype,
"<$sender_display_name> $message")
+ displayname = await self.get_displayname(sender)
tpl_args = dict(sender_mxid=sender.mxid,
sender_username=sender.mxid_localpart,
- sender_display_name=sender.displayname,
+ sender_displayname=displayname,
message=body)
message["formatted_body"] = Template(tpl).safe_substitute(tpl_args)
- @classmethod
- def _preprocess_matrix_message(cls, sender, use_relaybot, message):
- msgtype = message["msgtype"]
+ async def _preprocess_matrix_message(self, sender, use_relaybot, message):
+ msgtype = message.get("msgtype", "m.text")
if msgtype == "m.emote":
- cls._apply_msg_format(sender, msgtype, message)
+ await self._apply_msg_format(sender, msgtype, message)
message["msgtype"] = "m.text"
elif use_relaybot:
- cls._apply_msg_format(sender, msgtype, message)
+ await self._apply_msg_format(sender, msgtype, message)
def _matrix_event_to_entities(self, event):
try:
@@ -792,7 +824,7 @@ class Portal:
reply_to = formatter.matrix_reply_to_telegram(message, space, room_id=self.mxid)
message["mxtg_filename"] = message["body"]
- self._preprocess_matrix_message(sender, not logged_in, message)
+ await self._preprocess_matrix_message(sender, not logged_in, message)
type = message["msgtype"]
if type == "m.text" or (self.bridge_notices and type == "m.notice"):
diff --git a/mautrix_telegram/user.py b/mautrix_telegram/user.py
index 8fa72233..06418203 100644
--- a/mautrix_telegram/user.py
+++ b/mautrix_telegram/user.py
@@ -68,9 +68,9 @@ class User(AbstractUser):
match = re.compile("@(.+):(.+)").match(self.mxid)
return match.group(1)
+ # TODO replace with proper displayname getting everywhere
@property
def displayname(self):
- # TODO show better display name
return self.mxid_localpart
@property