# mautrix-telegram - A Matrix-Telegram puppeting bridge # Copyright (C) 2019 Tulir Asokan # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from typing import List, NamedTuple, Tuple, Union from mautrix.appservice import IntentAPI from mautrix.errors import MatrixRequestError from mautrix.types import RoomID, UserID, EventID from . import command_handler, CommandEvent, SECTION_ADMIN from .. import puppet as pu, portal as po ManagementRoom = NamedTuple('ManagementRoom', room_id=RoomID, user_id=UserID) async def _find_rooms(intent: IntentAPI) -> Tuple[List[ManagementRoom], List[RoomID], List['po.Portal'], List['po.Portal']]: management_rooms: List[ManagementRoom] = [] unidentified_rooms: List[RoomID] = [] portals: List[po.Portal] = [] empty_portals: List[po.Portal] = [] rooms = await intent.get_joined_rooms() for room_id in rooms: portal = po.Portal.get_by_mxid(room_id) if not portal: try: members = await intent.get_room_members(room_id) except MatrixRequestError: members = [] if len(members) == 2: other_member = members[0] if members[0] != intent.mxid else members[1] if pu.Puppet.get_id_from_mxid(other_member): unidentified_rooms.append(room_id) else: management_rooms.append(ManagementRoom(room_id, other_member)) else: unidentified_rooms.append(room_id) else: members = await portal.get_authenticated_matrix_users() if len(members) == 0: empty_portals.append(portal) else: portals.append(portal) return management_rooms, unidentified_rooms, portals, empty_portals @command_handler(needs_admin=True, needs_auth=False, management_only=True, name="clean-rooms", help_section=SECTION_ADMIN, help_text="Clean up unused portal/management rooms.") async def clean_rooms(evt: CommandEvent) -> EventID: management_rooms, unidentified_rooms, portals, empty_portals = await _find_rooms(evt.az.intent) reply = ["#### Management rooms (M)"] reply += ([f"{n+1}. [M{n+1}](https://matrix.to/#/{room}) (with {other_member}" for n, (room, other_member) in enumerate(management_rooms)] or ["No management rooms found."]) reply.append("#### Active portal rooms (A)") reply += ([f"{n+1}. [A{n+1}](https://matrix.to/#/{portal.mxid}) " f"(to Telegram chat \"{portal.title}\")" for n, portal in enumerate(portals)] or ["No active portal rooms found."]) reply.append("#### Unidentified rooms (U)") reply += ([f"{n+1}. [U{n+1}](https://matrix.to/#/{room})" for n, room in enumerate(unidentified_rooms)] or ["No unidentified rooms found."]) reply.append("#### Inactive portal rooms (I)") reply += ([f"{n}. [I{n}](https://matrix.to/#/{portal.mxid}) " f"(to Telegram chat \"{portal.title}\")" for n, portal in enumerate(empty_portals)] or ["No inactive portal rooms found."]) reply += ["#### Usage", ("To clean the recommended set of rooms (unidentified & inactive portals), " "type `$cmdprefix+sp clean-recommended`"), "", ("To clean other groups of rooms, type `$cmdprefix+sp clean-groups ` " "where `letters` are the first letters of the group names (M, A, U, I)"), "", ("To clean specific rooms, type `$cmdprefix+sp clean-range ` " "where `range` is the range (e.g. `5-21`) prefixed with the first letter of" "the group name. (e.g. `I2-6`)"), "", ("Please note that you will have to re-run `$cmdprefix+sp clean-rooms` " "between each use of the commands above.")] evt.sender.command_status = { "next": lambda clean_evt: set_rooms_to_clean(clean_evt, management_rooms, unidentified_rooms, portals, empty_portals), "action": "Room cleaning", } return await evt.reply("\n".join(reply)) async def set_rooms_to_clean(evt, management_rooms: List[ManagementRoom], unidentified_rooms: List[RoomID], portals: List["po.Portal"], empty_portals: List["po.Portal"]) -> None: command = evt.args[0] rooms_to_clean: List[Union[po.Portal, RoomID]] = [] if command == "clean-recommended": rooms_to_clean += empty_portals rooms_to_clean += unidentified_rooms elif command == "clean-groups": if len(evt.args) < 2: return await evt.reply("**Usage:** `$cmdprefix+sp clean-groups [M][A][U][I]") groups_to_clean = evt.args[1].upper() if "M" in groups_to_clean: rooms_to_clean += [room_id for (room_id, user_id) in management_rooms] if "A" in groups_to_clean: rooms_to_clean += portals if "U" in groups_to_clean: rooms_to_clean += unidentified_rooms if "I" in groups_to_clean: rooms_to_clean += empty_portals elif command == "clean-range": try: clean_range = evt.args[1] group, clean_range = clean_range[0], clean_range[1:] start, end = clean_range.split("-") start, end = int(start), int(end) if group == "M": group = [room_id for (room_id, user_id) in management_rooms] elif group == "A": group = portals elif group == "U": group = unidentified_rooms elif group == "I": group = empty_portals else: raise ValueError("Unknown group") rooms_to_clean = group[start - 1:end] except (KeyError, ValueError): return await evt.reply( "**Usage:** `$cmdprefix+sp clean-groups <_M|A|U|I_>") else: return await evt.reply(f"Unknown room cleaning action `{command}`. " "Use `$cmdprefix+sp cancel` to cancel room " "cleaning.") evt.sender.command_status = { "next": lambda confirm: execute_room_cleanup(confirm, rooms_to_clean), "action": "Room cleaning", } await evt.reply(f"To confirm cleaning up {len(rooms_to_clean)} rooms, type" "`$cmdprefix+sp confirm-clean`.") async def execute_room_cleanup(evt, rooms_to_clean: List[Union[po.Portal, RoomID]]) -> None: if len(evt.args) > 0 and evt.args[0] == "confirm-clean": await evt.reply(f"Cleaning {len(rooms_to_clean)} rooms. " "This might take a while.") cleaned = 0 for room in rooms_to_clean: if isinstance(room, po.Portal): await room.cleanup_and_delete() cleaned += 1 else: 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.") else: await evt.reply("Room cleaning cancelled.")