Add support for Matrix displaynames in relaybot messages

This commit is contained in:
Tulir Asokan
2018-06-24 01:24:14 +03:00
parent f5c4b477e5
commit 4a0d00e74c
5 changed files with 94 additions and 33 deletions
+17 -8
View File
@@ -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: "<b>$sender_displayname</b>: $message"
m.emote: "* <b>$sender_displayname</b> $message"
m.file: "<b>$sender_displayname</b> sent a file: $message"
m.image: "<b>$sender_displayname</b> sent an image: $message"
m.audio: "<b>$sender_displayname</b> sent an audio file: $message"
m.video: "<b>$sender_displayname</b> sent a video: $message"
m.location: "<b>$sender_displayname</b> 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: "<b>$displayname</b> joined the room."
leave: "<b>$displayname</b> left the room."
name_change: "<b>$prev_displayname</b> changed their name to <b>$displayname</b>"
filter:
# Filter mode to use. Either "blacklist" or "whitelist".
+3
View File
@@ -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")
+29 -12
View File
@@ -16,6 +16,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
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)
+44 -12
View File
@@ -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,
"&lt;$sender_display_name&gt; $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"):
+1 -1
View File
@@ -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