From 12d40257520639a43cfaa3b6c03f45282615d09a Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Tue, 20 Feb 2018 20:43:05 +0200 Subject: [PATCH] Implement Telegram->Matrix deletion bridging. Fixes #63 --- ROADMAP.md | 2 +- example-config.yaml | 3 ++ mautrix_appservice/intent_api.py | 10 ++++++ mautrix_telegram/abstract_user.py | 51 ++++++++++++++++++++++++++++++- 4 files changed, 64 insertions(+), 2 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 23b1f0e3..78b38cc4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -46,7 +46,7 @@ * [x] Audio messages * [x] Video messages * [x] Documents - * [ ] Message deletions (no way to tell difference between user-specific deletion and global deletion) + * [x] Message deletions * [ ] Message edits (not supported in Matrix) * [x] Avatars * [x] Presence diff --git a/example-config.yaml b/example-config.yaml index 221ff539..90166e14 100644 --- a/example-config.yaml +++ b/example-config.yaml @@ -69,6 +69,9 @@ bridge: edits_as_replies: False # Whether or not Matrix bot messages (type m.notice) should be bridged. bridge_notices: False + # The maximum number of simultaneous Telegram deletions to handle. + # A large number of simultaneous redactions could put strain on your homeserver. + max_telegram_delete: 10 # The prefix for commands. Only required in non-management rooms. command_prefix: "!tg" diff --git a/mautrix_appservice/intent_api.py b/mautrix_appservice/intent_api.py index 94b894b0..f011f3c0 100644 --- a/mautrix_appservice/intent_api.py +++ b/mautrix_appservice/intent_api.py @@ -425,6 +425,16 @@ class IntentAPI: return self.send_state_event(room_id, "m.room.member", body, state_key=user_id) + def redact(self, room_id, event_id, reason=None, txn_id=None): + txn_id = txn_id or str(self.client.txn_id) + str(int(time() * 1000)) + self.client.txn_id += 1 + content = {} + if reason: + content["reason"] = reason + return self.client.request("PUT", + f"/rooms/{quote(room_id)}/redact/{quote(event_id)}/{txn_id}", + content) + @staticmethod def _get_event_url(room_id, event_type, txn_id): if not room_id: diff --git a/mautrix_telegram/abstract_user.py b/mautrix_telegram/abstract_user.py index 260cc504..f93d1b92 100644 --- a/mautrix_telegram/abstract_user.py +++ b/mautrix_telegram/abstract_user.py @@ -18,12 +18,15 @@ import platform import os from telethon.tl.types import * +from mautrix_appservice import MatrixRequestError from .tgclient import MautrixTelegramClient from .db import Message as DBMessage from . import portal as po, puppet as pu, __version__ config = None +# Value updated from config in init() +MAX_DELETIONS = 10 class AbstractUser: @@ -106,6 +109,10 @@ class AbstractUser: (UpdateShortChatMessage, UpdateShortMessage, UpdateNewChannelMessage, UpdateNewMessage, UpdateEditMessage, UpdateEditChannelMessage)): await self.update_message(update) + elif isinstance(update, UpdateDeleteMessages): + await self.delete_message(update) + elif isinstance(update, UpdateDeleteChannelMessages): + await self.delete_channel_message(update) elif isinstance(update, (UpdateChatUserTyping, UpdateUserTyping)): await self.update_typing(update) elif isinstance(update, UpdateUserStatus): @@ -206,6 +213,47 @@ class AbstractUser: return update, None, None return update, sender, portal + @staticmethod + async def _try_redact(portal, message): + if not portal: + return + try: + await portal.main_intent.redact(message.mx_room, message.mxid) + except MatrixRequestError: + pass + + async def delete_message(self, update): + if len(update.messages) > MAX_DELETIONS: + return + + for message in update.messages: + message = DBMessage.query.get((message, self.tgid)) + if not message: + continue + self.db.delete(message) + number_left = DBMessage.query.filter(DBMessage.mxid == message.mxid, + DBMessage.mx_room == message.mx_room).count() + if number_left == 0: + portal = po.Portal.get_by_mxid(message.mx_room) + await self._try_redact(portal, message) + self.db.commit() + + async def delete_channel_message(self, update): + if len(update.messages) > MAX_DELETIONS: + return + + portal = po.Portal.get_by_tgid(update.channel_id) + if not portal: + return + + for message in update.messages: + message = DBMessage.query.get((message, portal.tgid)) + if not message: + continue + self.db.delete(message) + await self._try_redact(portal, message) + self.db.commit() + def update_message(self, original_update): update, sender, portal = self.get_message_details(original_update) @@ -231,5 +279,6 @@ class AbstractUser: def init(context): - global config + global config, MAX_DELETIONS AbstractUser.az, AbstractUser.db, config, AbstractUser.loop, _ = context + MAX_DELETIONS = config.get("bridge.max_telegram_delete", 10)