Add support for Matrix displaynames in relaybot messages
This commit is contained in:
+17
-8
@@ -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".
|
||||
|
||||
@@ -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
@@ -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
@@ -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"):
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user