From 46cac040c77e505f83341ef9fa07cacb1d3cbfe2 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 1 Mar 2018 21:06:23 +0200 Subject: [PATCH] Add room unbridge command --- ROADMAP.md | 2 + mautrix_appservice/state_store.py | 6 +-- mautrix_telegram/commands/clean_rooms.py | 2 +- mautrix_telegram/commands/meta.py | 6 ++- mautrix_telegram/commands/portal.py | 69 +++++++++++++++++++----- mautrix_telegram/portal.py | 11 ++-- 6 files changed, 75 insertions(+), 21 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 91dfcf6f..e0584dd7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -85,6 +85,8 @@ * [x] Upgrading the chat of a portal room into a supergroup (`upgrade`) * [x] Change username of supergroup/channel (`group-name`) * [x] Getting the Telegram invite link to a Matrix room (`invite-link`) + * [ ] Bridging existing Matrix rooms to existing Telegram chats (`bridge`) + * [ ] Unbridging Matrix rooms from Telegram chats (`unbridge`) * Bridge administration * [x] Clean up and forget a portal room (`delete-portal`) * [x] Find and clean up old portal rooms (`clean-rooms`) diff --git a/mautrix_appservice/state_store.py b/mautrix_appservice/state_store.py index b5421cd5..f376426e 100644 --- a/mautrix_appservice/state_store.py +++ b/mautrix_appservice/state_store.py @@ -127,10 +127,10 @@ class StateStore: def get_power_levels(self, room): return self.power_levels[room] - def has_power_level(self, room, user, event, is_state_event=False): + def has_power_level(self, room, user, event, is_state_event=False, default=None): room_levels = self.power_levels.get(room, {}) - default_required = (room_levels.get("state_default", 50) if is_state_event - else room_levels.get("events_default", 0)) + default_required = default or (room_levels.get("state_default", 50) if is_state_event + else room_levels.get("events_default", 0)) required = room_levels.get("events", {}).get(event, default_required) has = room_levels.get("users", {}).get(user, room_levels.get("users_default", 0)) return has >= required diff --git a/mautrix_telegram/commands/clean_rooms.py b/mautrix_telegram/commands/clean_rooms.py index 06ebb428..5c515645 100644 --- a/mautrix_telegram/commands/clean_rooms.py +++ b/mautrix_telegram/commands/clean_rooms.py @@ -162,7 +162,7 @@ async def execute_room_cleanup(evt, rooms_to_clean): await room.cleanup_and_delete() cleaned += 1 elif isinstance(room, str): - await po.Portal.cleanup_room(evt.az.intent, room, type="Room") + await po.Portal.cleanup_room(evt.az.intent, room, message="Room deleted") cleaned += 1 evt.sender.command_status = None await evt.reply(f"{cleaned} rooms cleaned up successfully.") diff --git a/mautrix_telegram/commands/meta.py b/mautrix_telegram/commands/meta.py index 9171f9ce..5d2f38df 100644 --- a/mautrix_telegram/commands/meta.py +++ b/mautrix_telegram/commands/meta.py @@ -69,8 +69,10 @@ def help(evt): #### Portal management **upgrade** - Upgrade a normal Telegram group to a supergroup. **invite-link** - Get a Telegram invite link to the current chat. -**delete-portal** - Forget the current portal room. Only works for group chats; to delete - a private chat portal, simply leave the room. +**delete-portal** - Remove all users from the current portal room and forget the portal. + Only works for group chats; to delete a private chat portal, simply + leave the room. +**unbridge** - Remove puppets from the current portal room and forget the portal. **group-name** <_name_|`-`> - Change the username of a supergroup/channel. To disable, use a dash (`-`) as the name. **clean-rooms** - Clean up unused portal/management rooms. diff --git a/mautrix_telegram/commands/portal.py b/mautrix_telegram/commands/portal.py index 04f2e5ca..c3e9340f 100644 --- a/mautrix_telegram/commands/portal.py +++ b/mautrix_telegram/commands/portal.py @@ -15,6 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . from telethon.errors import * +from mautrix_appservice import MatrixRequestError from .. import portal as po from . import command_handler @@ -38,34 +39,78 @@ async def invite_link(evt): return await evt.reply("You don't have the permission to create an invite link.") -@command_handler(needs_admin=True) -async def delete_portal(evt): - room_id = evt.args[0] if len(evt.args) > 0 else evt.room_id +async def _has_access_to(room, intent, sender, event, default=50): + if sender.is_admin: + return True + # Make sure the state store contains the power levels. + try: + await intent.get_power_levels(room) + except MatrixRequestError: + return False + return intent.state_store.has_power_level(room, sender.mxid, + event=f"net.maunium.telegram.{event}", + default=default) + + +async def _get_portal_and_check_permission(evt, permission, action=None, allow_that=False): + room_id = evt.args[0] if len(evt.args) > 0 and allow_that else evt.room_id portal = po.Portal.get_by_mxid(room_id) if not portal: that_this = "This" if room_id == evt.room_id else "That" - return await evt.reply(f"{that_this} is not a portal room.") + return await evt.reply(f"{that_this} is not a portal room."), False + if not _has_access_to(portal.mxid, portal.main_intent, evt.sender, permission): + action = action or f"{permission.replace('_', ' ')}s" + return await evt.reply(f"You do not have the permissions to {action}."), False + return portal, True + + +def _get_portal_murder_function(action, room_id, function, command, completed_message): async def post_confirm(confirm): - evt.sender.command_status = None - if len(confirm.args) > 0 and confirm.args[0] == "confirm-delete": - await portal.cleanup_and_delete() + confirm.sender.command_status = None + if len(confirm.args) > 0 and confirm.args[0] == f"confirm-{command}": + await function() if confirm.room_id != room_id: - return await confirm.reply("Portal successfully deleted.") + return await confirm.reply(completed_message) else: - return await confirm.reply("Portal deletion cancelled.") + return await confirm.reply(f"{action} cancelled.") - evt.sender.command_status = { + return { "next": post_confirm, - "action": "Portal deletion", + "action": action, } + + +@command_handler() +async def delete_portal(evt): + portal, ok = await _get_portal_and_check_permission(evt, "delete_portal") + if not ok: + return + + evt.sender.command_status = _get_portal_murder_function("Portal deletion", portal.mxid, + portal.cleanup_and_delete, "delete", + "Portal successfully deleted.") return await evt.reply("Please confirm deletion of portal " - f"[{room_id}](https://matrix.to/#/{room_id}) " + f"[{portal.alias or portal.mxid}](https://matrix.to/#/{portal.mxid}) " f"to Telegram chat \"{portal.title}\" " "by typing `$cmdprefix+sp confirm-delete`") +@command_handler() +async def unbridge(evt): + portal, ok = await _get_portal_and_check_permission(evt, "unbridge_room", allow_that=False) + if not ok: + return + + evt.sender.command_status = _get_portal_murder_function("Room unbridging", portal.mxid, + portal.unbridge, "unbridge", + "Room successfully unbridged.") + return await evt.reply(f"Please confirm unbridging chat \"{portal.title}\" from room " + f"[{portal.alias or portal.mxid}](https://matrix.to/#/{portal.mxid}) " + "by typing `$cmdprefix+sp confirm-unbridge`") + + async def _get_initial_state(evt): state = await evt.az.intent.get_room_state(evt.room_id) title = None diff --git a/mautrix_telegram/portal.py b/mautrix_telegram/portal.py index ad2986be..9f18eec7 100644 --- a/mautrix_telegram/portal.py +++ b/mautrix_telegram/portal.py @@ -484,19 +484,24 @@ class Portal: return authenticated @staticmethod - async def cleanup_room(intent, room_id, type="Portal"): + async def cleanup_room(intent, room_id, message="Portal deleted", puppets_only=False): try: members = await intent.get_room_members(room_id) except MatrixRequestError: members = [] for user in members: - if user != intent.mxid: + is_puppet = p.Puppet.get_id_from_mxid(user) + if user != intent.mxid and (not puppets_only or is_puppet): try: - await intent.kick(room_id, user, f"{type} deleted.") + await intent.kick(room_id, user, message) except (MatrixRequestError, IntentError): pass await intent.leave_room(room_id) + async def unbridge(self): + await self.cleanup_room(self.main_intent, self.mxid, "Room unbridged", puppets_only=True) + self.delete() + async def cleanup_and_delete(self): await self.cleanup_room(self.main_intent, self.mxid) self.delete()