def __init__(self, server_addr=None, server_port=443, access_token=None, server_protocol=None, suppress_exception=False): super().__init__(server_addr, server_port, access_token, server_protocol, suppress_exception) if server_addr is not None and access_token is not None: self.user = User(server_addr, server_port, access_token, server_protocol, suppress_exception) self.client = ClientAPI(server_addr, server_port, access_token, server_protocol, suppress_exception) else: self.user = User() self.client = ClientAPI() self._create_alias()
def admin_login(protocol: str, host: str, port: str, username: str = None, password: str = None, suppress_exception: bool = False, no_admin: bool = False) -> str: """Login and get an access token Args: protocol (str): "http://" or "https://". Defaults to None. # noqa: E501 host (str): homeserver address. Defaults to None. port (int): homeserver listening port. Defaults to None. username (str, optional): just username. Defaults to None. password (str, optional): just password. Defaults to None. suppress_exception (bool, optional): suppress exception or not, if not return False and the error in dict. Defaults to False. # noqa: E501 Returns: str: access token """ if username is None: username = input("Enter a username: "******"identifier": { "type": "m.id.user", "user": username }, "type": "m.login.password", "password": password, "initial_device_display_name": "matrix-synapse-admin" } http = Client() base_url = f"{protocol}{host}:{port}" while True: resp = http.post(f"{base_url}{ClientAPI.BASE_PATH}/login", json=login_data) data = resp.json() if "errcode" in data and data["errcode"] == "M_LIMIT_EXCEEDED": time.sleep(data["retry_after_ms"] / 1000) continue if resp.status_code == 200: access_token = data["access_token"] if not no_admin: resp = User(host, port, access_token, protocol).query(username) if "errcode" not in resp: return data["access_token"] else: data = resp else: return access_token if suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"])
def test_base_read_config(): user = User() user.config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../synapse_test/api.cfg") user.modify_config("invalid", 80, "invalid", "http://", True) user.read_config(user.config_path) assert user.server_addr == "invalid" assert user.server_port == 80 assert user.access_token == "invalid"
def test_base_create_config(): """TODO: interactive""" base_handler.config_path = config_path if os.path.isfile(config_path): os.remove(config_path) assert base_handler.create_config("http://", "localhost", 8008, admin_access_token, True) assert os.path.isfile(config_path) assert isinstance(User().lists(), Contents) os.remove(config_path) assert base_handler.create_config("http://", "localhost", 8008, admin_access_token, False) assert not os.path.isfile(config_path) assert base_handler.create_config("http://", "localhost", 8008, admin_access_token, True) assert os.path.isfile(config_path)
import time from synapse_admin import User, Room from synapse_admin.client import ClientAPI from synapse_admin.base import SynapseException, Utility from yaml import load, CLoader with open("synapse_test/admin.token", "r") as f: admin_access_token = f.read().replace("\n", "") with open("synapse_test/user.token", "r") as f: user_access_token = f.read().replace("\n", "") config = load(open("synapse_test/homeserver.yaml", "r"), Loader=CLoader) conn = ("localhost", 8008, admin_access_token, "http://") user_handler = User(*conn) def test_user_list(): exist_users = [] for user in user_handler.lists(): exist_users.append(user["name"][1:]) assert exist_users == ["admin1:localhost", "test1:localhost"] def test_user_create(): user_handler.create("test2", password="******", displayname="Test 2", admin=False, threepids=[{
def test_base_modify_config(): user = User() user.modify_config("invalid", 80, "invalid", "http://", False) with pytest.raises(ConnectError): user.lists() assert isinstance(User().lists(), Contents) user.modify_config("invalid", 80, "invalid", "http://", True) with pytest.raises(ConnectError): user.lists() User().lists() assert user.modify_config("localhost", 8008, admin_access_token, "http://") assert isinstance(user.lists(), Contents) assert isinstance(User().lists(), Contents)
import pytest import time from synapse_admin import Management, Room, User from synapse_admin.base import HTTPConnection, SynapseException, Utility from uuid import uuid4 with open("synapse_test/admin.token", "r") as f: admin_access_token = f.read().replace("\n", "") with open("synapse_test/user.token", "r") as f: user_access_token = f.read().replace("\n", "") conn = ("localhost", 8008, admin_access_token, "http://") mgt_handler = Management(*conn) room_handler = Room(*conn) user_handler = User(*conn) test1_conn = HTTPConnection("http://", "localhost", 8008, {"Authorization": f"Bearer {user_access_token}"}) test2_token = user_handler.login("test2") test2_conn = HTTPConnection("http://", "localhost", 8008, {"Authorization": f"Bearer {test2_token}"}) shared_variable = [] def test_management_announce(): """TODO: media announcement""" assert isinstance(mgt_handler.announce("test1", "This is a test"), str) assert isinstance(mgt_handler.announce("test1", "This is a test2"), str) assert isinstance(mgt_handler.announce("admin1", "This is a test"), str)
class Management(Admin): """ Wapper class for admin API for server management Reference: https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/server_notices.md https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.md https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/register_api.md https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/purge_history_api.md https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/delete_group.md https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/event_reports.md """ class SynapseVersion(NamedTuple): server: str python: str def __init__(self, server_addr=None, server_port=443, access_token=None, server_protocol=None, suppress_exception=False): super().__init__(server_addr, server_port, access_token, server_protocol, suppress_exception) if server_addr is not None and access_token is not None: self.user = User(server_addr, server_port, access_token, server_protocol, suppress_exception) self.client = ClientAPI(server_addr, server_port, access_token, server_protocol, suppress_exception) else: self.user = User() self.client = ClientAPI() self._create_alias() def _create_alias(self) -> None: """Create alias for some methods""" self.event_report = self.specific_event_report def _prepare_attachment( self, attachment: Union[str, bytes]) -> Tuple[str, str, str]: """Upload a media and return its information Args: attachment (Union[str, bytes]): the media, either in str or bytes Returns: Tuple[str, str, str]: media id, message type defined in matrix, file name # noqa: E501 """ mediaid, mime = self.client.client_upload_attachment(attachment) if isinstance(attachment, bytes): filename = "unknown" else: filename = os.path.basename(attachment) if "image/" in mime: msgtype = "m.image" elif "video/" in mime: msgtype = "m.video" elif "audio/" in mime: msgtype = "m.audio" else: msgtype = "m.file" return mediaid, msgtype, filename def announce(self, userid: Union[str, bool], announcement: str = None, attachment: Union[str, bytes] = None) -> Union[str, list]: """Send an announcement to a user or a batch of users Args: userid (Union[str, bool]): user you want to send them annoucement or set True to send the announcement to all users # noqa: E501 announcement (str, optional): a text-based announcement. Defaults to None. attachment (Union[str, bytes], optional): the media you want to send or to attach. Either provide a path to the file or the stream. Defaults to None. Returns: Union[str, list]: if either announcement or attachment is specified, return the event id if both announcement and attachment are specified, return a list which contains the event id for the attachment and the text """ if isinstance(userid, str): invoking_method = self._announce elif isinstance(userid, bool) and userid: invoking_method = self.announce_all raise ValueError("Argument must be a non-empty str or True") if announcement is None and attachment is None: raise ValueError("You must at least specify" "announcement or attachment") userid = self.validate_username(userid) if attachment is not None: mediaid, msgtype, filename = self._prepare_attachment(attachment) data = { "user_id": userid, "content": { "body": filename, "msgtype": msgtype, "url": mediaid } } if announcement is not None: event_ids = [] event_ids.append(invoking_method(userid, "", data)) event_ids.append(invoking_method(userid, announcement)) return event_ids else: announcement = "" elif announcement is not None: data = None return invoking_method(userid, announcement, data) def _announce(self, userid: str, announcement: str, data: dict = None) -> str: """Send an announcement to a specific user https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/server_notices.md#server-notices Args: userid (str): the user that the announcement should be delivered to announcement (str): the announcement Returns: str: event id of the announcement """ userid = self.validate_username(userid) if data is None: data = { "user_id": userid, "content": { "msgtype": "m.text", "body": announcement } } resp = self.connection.request("POST", self.admin_patterns( "/send_server_notice", 1), json=data) data = resp.json() if resp.status_code == 200: return data["event_id"] else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def announce_all(self, announcement: str, data: dict = None) -> dict: """Send an announcement to all local users Args: announcement (str): the announcement Returns: dict: a dict with user id as key and the event id as value """ events = {} for user in self.user.lists(): events[user["name"]] = self._announce(user["name"], announcement, data) return events def version(self) -> SynapseVersion: """Get the server and python version https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/version_api.md#version-api Returns: SynapseVersion: server: server version, python: python version # noqa: E501 """ resp = self.connection.request( "GET", self.admin_patterns("/server_version", 1)) data = resp.json() return Management.SynapseVersion(data["server_version"], data["python_version"]) def purge_history(self, roomid: str, event_id_ts: Union[str, int], include_local_event: bool = False) -> str: """Purge old events in a room from database https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/purge_history_api.md#purge-history-api Args: roomid (str): the room you want to perform the purging event_id_ts (Union[str, int]): purge up to an event id or timestamp include_local_event (bool, optional): whether to purge local events. Defaults to False. # noqa: E501 Returns: str: purge id """ roomid = self.validate_room(roomid) data = {"delete_local_events": include_local_event} if isinstance(event_id_ts, str): data["purge_up_to_event_id"] = event_id_ts elif isinstance(event_id_ts, int): data["purge_up_to_ts"] = event_id_ts resp = self.connection.request("POST", self.admin_patterns( f"/purge_history/{roomid}", 1), json=data) data = resp.json() if resp.status_code == 200: return data["purge_id"] else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def purge_history_status(self, purge_id: str) -> str: """Query the purge job status https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/purge_history_api.md#purge-status-query Args: purge_id (str): the purge id you want to query Returns: str: the status of the purge job """ resp = self.connection.request( "GET", self.admin_patterns(f"/purge_history_status/{purge_id}", 1)) data = resp.json() if resp.status_code == 200: return data["status"] else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def event_reports(self, limit: int = 100, _from: int = 0, recent_first: bool = True, userid: str = None, roomid: str = None) -> Contents: """Query all reported events https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/event_reports.md#show-reported-events Args: limit (int, optional): equivalent to "limit". Defaults to 100. _from (int, optional): equivalent to "from". Defaults to 0. recent_first (bool, optional): equivalent to "dir". True as "b" False as "f" Defaults to True. # noqa: E501 userid (str, optional): equivalent to "user_id". Defaults to None. roomid (str, optional): equivalent to "room_id". Defaults to None. Returns: Contents: list of reported events """ if recent_first: recent_first = "b" else: recent_first = "f" optional_str = "" if userid is not None: optional_str += f"&user_id={userid}" if roomid is not None: roomid = self.validate_room(roomid) optional_str += f"&room_id={roomid}" resp = self.connection.request( "GET", self.admin_patterns( f"/event_reports?from={_from}" f"&limit={limit}&dir={recent_first}" f"{optional_str}", 1)) data = resp.json() if data["total"] == 0: return None return Contents(data["event_reports"], data["total"], data.get("next_token", None)) def specific_event_report(self, reportid: int) -> dict: """Query specific event report https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/event_reports.md#show-details-of-a-specific-event-report Args: reportid (int): the report id Returns: dict: a dict with all details of the report """ resp = self.connection.request( "GET", self.admin_patterns(f"/event_reports/{reportid}", 1)) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def delete_group(self, groupid: str) -> bool: """Delete a local group https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/delete_group.md#delete-a-local-group Args: groupid (str): the group id you want to delete Returns: bool: the deletion is successful or not """ groupid = self.validate_group(groupid) resp = self.connection.request( "POST", self.admin_patterns(f"/delete_group/{groupid}", 1)) data = resp.json() if resp.status_code == 200: return data == {} else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def background_updates_get(self) -> Tuple[bool, dict]: """Get the current status of background updates https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/background_updates.md#status Returns: Tuple[bool, dict]: whether background updates is enabled, details of current updates # noqa: E501 """ resp = self.connection.request( "GET", self.admin_patterns("/background_updates/status", 1)) data = resp.json() if resp.status_code == 200: return data["enabled"], data["current_updates"] else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def background_updates_set(self, enabled: bool) -> bool: """Pause or resume background updates https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/background_updates.md#enabled Args: enabled (bool, optional): True to enable, False to disable background updates. # noqa: E501 Returns: bool: whether the background updates are now enabled or disabled. True means enabled, False means disabled. """ resp = self.connection.request("POST", self.admin_patterns( "/background_updates/enabled", 1), json={"enabled": enabled}) data = resp.json() if resp.status_code == 200: return data["enabled"] else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def background_updates_run(self, job_name: str): """Run a background update https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/background_updates.md#run Args: job_name (str): job name of the background update. # noqa: E501 """ jobs = {"populate_stats_process_rooms", "regenerate_directory"} if job_name not in jobs: raise ValueError( "Value of job_name can only be either" "populate_stats_process_rooms or regenerate_directory") resp = self.connection.request("POST", self.admin_patterns( "/background_updates/start_job", 1), json={"job_name": job_name}) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def federation_list(self, _from: int = 0, limit: int = 100, orderby: str = None, _dir: str = "f", destination: str = None) -> Contents: """List infomation of retrying timing for all remote servers https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#federation-api https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#list-of-destinations https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#destination-details-api Args: _from (int, optional): equivalent to "from". Defaults to 0. limit (int, optional): equivalent to "limit". Defaults to 100. order_by (int, optional): equivalent to "order_by". Defaults to None. # noqa: E501 _dir (str, optional): equivalent to "dir". Defaults to "f". destination (str, optional): show only the specified remote server. Defaults to None (no specification). Returns: Contents: list of timing information of current destination """ if isinstance(destination, str): return self._federation_list(destination) params = {"from": _from, "limit": limit, "dir": _dir} if orderby is not None: params["order_by"] = orderby resp = self.connection.request("GET", self.admin_patterns( "/federation/destinations", 1), params=params) data = resp.json() if resp.status_code == 200: return Contents(data["destinations"], data["total"], data.get("next_token", None)) else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def _federation_list(self, destination: str) -> dict: """Query the retry timing details of a specific remote server https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#destination-details-api Args: destination (str): show only the specified remote server. Returns: dict: details of retry timing """ resp = self.connection.request( "GET", self.admin_patterns(f"/federation/destinations/{destination}", 1)) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def reset_connection(self, destination: str) -> bool: """Reset the connection timeout for a specific destination https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#reset-connection-timeout Args: destination (str): the remote destination Returns: bool: whether or not the connection reset successfully """ resp = self.connection.request( "POST", self.admin_patterns( f"/federation/destinations/{destination}/reset_connection", 1), json={}) if resp.status_code == 200: return True else: data = resp.json() if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"]) def federation_room(self, destination: str, _from: int = 0, limit: int = 100, _dir: str = "f") -> Contents: """Fetch roms federated with remote destination https://github.com/matrix-org/synapse/blob/develop/docs/usage/administration/admin_api/federation.md#destination-rooms Args: destination (str): the remote destination _from (int, optional): equivalent to "from". Defaults to 0. limit (int, optional): equivalent to "limit". Defaults to 100. _dir (str, optional): equivalent to "dir". Defaults to "f". Returns: Contents: A list of federated room """ resp = self.connection.request( "GET", self.admin_patterns( f"/federation/destinations/{destination}/rooms", 1), params={ "from": _from, "limit": limit, "dir": _dir }) data = resp.json() if resp.status_code == 200: return Contents(data["rooms"], data["total"], data.get("next_token", None)) else: if self.suppress_exception: return False, data["errcode"], data["error"] else: raise SynapseException(data["errcode"], data["error"])
class Room(Admin): """ Wapper class for admin API for room management Reference: https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md https://github.com/matrix-org/synapse/blob/master/docs/admin_api/shutdown_room.md https://github.com/matrix-org/synapse/blob/master/docs/admin_api/purge_room.md """ order = { "alphabetical", "size", "name", "canonical_alias", "joined_members", "joined_local_members", "version", "creator", "encryption", "federatable", "public", "join_rules", "guest_access", "history_visibility", "state_events" } class RoomInformation(NamedTuple): roomid: str joined: list def __init__(self, server_addr: str = None, server_port: int = 443, access_token: str = None, server_protocol: str = None, suppress_exception: bool = False): super().__init__(server_addr, server_port, access_token, server_protocol, suppress_exception) if server_addr is not None and access_token is not None: self.user = User(server_addr, server_port, access_token, server_protocol, suppress_exception) self.client_api = ClientAPI(server_addr, server_port, access_token, server_protocol, suppress_exception) else: self.user = User() self.client_api = ClientAPI() self._create_alias() def _create_alias(self) -> None: """Create alias for some methods""" self.members = self.list_members def lists(self, _from: int = None, limit: int = None, orderby: str = None, recent_first: bool = True, search: str = None) -> Contents: """List all local rooms https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#list-room-api Args: _from (int, optional): equivalent to "from". Defaults to None. limit (int, optional): equivalent to "limit". Defaults to None. orderby (str, optional): equivalent to "order_by". Defaults to None. recent_first (bool, optional): equivalent to "dir", True to f False to b. Defaults to True. # noqa: E501 search (str, optional): equivalent to "search_term". Defaults to None. Returns: Contents: list of room """ if recent_first: optional_str = "dir=b" else: optional_str = "dir=f" if _from is not None: optional_str += f"&from={_from}" if limit is not None: optional_str += f"&limit={limit}" if orderby is not None: if not isinstance(orderby, str): raise TypeError("Argument 'orderby' should be a " f"str but not {type(orderby)}") elif orderby not in Room.order: raise ValueError( "Argument 'orderby' must be included in Room.order, " "for details please read documentation.") optional_str += f"&orderby={orderby}" if search: optional_str += f"&search_term={search}" resp = self.connection.request( "GET", self.admin_patterns(f"/rooms?{optional_str}", 1), ) data = resp.json() if resp.status_code == 200: return Contents(data["rooms"], data["total_rooms"], data.get("next_batch", None)) else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def details(self, roomid: str) -> dict: """Query a room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#room-details-api Args: roomid (str): the room you want to query Returns: dict: a dict containing the room's details """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}", 1), ) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def list_members(self, roomid: str) -> Contents: """List all members in the room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#room-members-api Args: roomid (str): the room you want to query Returns: Contents: a list of members """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/members", 1), ) data = resp.json() if resp.status_code == 200: return Contents(data["members"], data["total"]) else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def create(self, public: bool = False, *, alias: str = None, name: str = None, members: list = None, federation: bool = True, leave: bool = False, encrypted: bool = True) -> RoomInformation: """Create a room and force users to be a member Args: public (bool, optional): is the room public? Defaults to False. alias (str, optional): the alias of the room. Defaults to None. name (str, optional): the name of the room. Defaults to None. members (list, optional): a list of user that should be the members of the room. Defaults to None. # noqa: E501 federation (bool, optional): can the room be federated. Defaults to True. leave (bool, optional): whether to leave the room yourself after the creation. Defaults to False. Returns: RoomInformation: roomid: room id, joined: a list of joined users """ if members is None and leave: raise ValueError("You cannot create a room and leave" " the room immediately since you" " are the only member of the room") roomid = self.client_api.client_create(public, alias, name, federation=federation, encrypted=encrypted) joined = [] if members is not None: for member in members: userid = self.validate_username(member) if self.user.join_room(userid, roomid): joined.append(userid) if leave: self.client_api.client_leave(roomid) return Room.RoomInformation(roomid, joined) def delete(self, roomid: str, new_room_userid: str = None, room_name: str = None, message: str = None, block: bool = False, purge: bool = True) -> dict: """Delete a room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#version-1-old-version Args: roomid (str): the room you want to delete new_room_userid (str, optional): equivalent to "new_room_user_id". Defaults to None. # noqa: E501 room_name (str, optional): equivalent to "room_name". Defaults to None. message (str, optional): equivalent to "message". Defaults to None. block (bool, optional): equivalent to "block". Defaults to False. purge (bool, optional): whether or not to purge all information of the rooom from the database. Defaults to True. Returns: dict: a dict containing kicked_users, failed_tokick_users, local_aliases, new_room_id """ roomid = self.validate_room(roomid) data = {"block": block, "purge": purge} if new_room_userid is not None: new_room_userid = self.validate_username(new_room_userid) data["new_room_user_id"] = new_room_userid if room_name is not None: data["room_name"] = room_name if message is not None: data["message"] = message resp = self.connection.request( "DELETE", self.admin_patterns(f"/rooms/{roomid}", 1), json=data, ) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def delete_old(self, roomid: str, new_room_userid: str = None, new_room_name: str = None, message: str = None, block: bool = False, purge: bool = True): """Old room deletion method. Use the new one.""" roomid = self.validate_room(roomid) data = {"block": block, "purge": purge} if new_room_userid is not None: new_room_userid = self.validate_username(new_room_userid) data["new_room_user_id"] = new_room_userid if new_room_name is not None: data["room_name"] = new_room_name if message is not None: data["message"] = message resp = self.connection.request("POST", self.admin_patterns( f"/rooms/{roomid}/delete", 1), json=data) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def set_admin(self, roomid: str, userid: str = None) -> bool: """Set a member to be the admin in the room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#make-room-admin-api Args: roomid (str): the room you want to grant admin privilege to the user # noqa: E501 userid (str, optional): the user you wanted them as an admin in the room. Defaults to None (self). Returns: bool: The modification is successful or not """ roomid = self.validate_room(roomid) if userid is not None: userid = self.validate_username(userid) body = {"user_id": userid} else: body = {} resp = self.connection.request("POST", self.admin_patterns( f"/rooms/{roomid}/make_room_admin", 1), json=body) data = resp.json() if resp.status_code == 200: return True else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def purge_room(self, roomid): """Purge a room. Removed in Synapse 1.42.0 https://github.com/matrix-org/synapse/blob/3bcd525b46678ff228c4275acad47c12974c9a33/docs/admin_api/purge_room.md """ roomid = self.validate_room(roomid) resp = self.connection.request("POST", self.admin_patterns("/purge_room", 1), json={"room_id": roomid}) data = resp.json() if "errcode" in data and data["errcode"] == "M_UNRECOGNIZED": raise NotImplementedError( "This admin API has been removed in your homeserver") return data def shutdown_room(self, roomid, new_room_userid, new_room_name=None, message=None): """Shut down a room. Removed in Synapse 1.42.0 https://github.com/matrix-org/synapse/blob/3bcd525b46678ff228c4275acad47c12974c9a33/docs/admin_api/shutdown_room.md """ roomid = self.validate_room(roomid) new_room_userid = self.validate_username(new_room_userid) data = {"new_room_user_id": new_room_userid} if new_room_name is not None: data["room_name"] = new_room_name if message is not None: data["message"] = message resp = self.connection.request( "POST", self.admin_patterns(f"/shutdown_room/{roomid}", 1), json=data, ) data = resp.json() if "errcode" in data and data["errcode"] == "M_UNRECOGNIZED": raise NotImplementedError( "This admin API has been removed in your homeserver") return data def forward_extremities_check(self, roomid: str) -> Contents: """Query forward extremities in a room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#check-for-forward-extremities Args: roomid (str): the room you want to query Returns: Contents: a list of forward extremities """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/forward_extremities", 1)) data = resp.json() return Contents(data["results"], data["count"]) def forward_extremities_delete(self, roomid: str) -> int: """Delete forward extremities in a room (Do not use this method when writing automated script) https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#deleting-forward-extremities Args: roomid (str): the room you want the forward extremities to be deleted # noqa: E501 Returns: int: number of forward extremities deleted """ roomid = self.validate_room(roomid) resp = self.connection.request( "DELETE", self.admin_patterns(f"/rooms/{roomid}/forward_extremities", 1)) data = resp.json() if resp.status_code == 200: return data["deleted"] else: # Synapse bug: Internal server error # raise if the room does not exist if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def get_state(self, roomid: str) -> list: """Query the room state https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#room-state-api Args: roomid (str): the room you want to query Returns: list: a list of state """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/state", 1), ) data = resp.json() if resp.status_code == 200: return data["state"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def event_context(self, roomid: str, event_id: str) -> dict: """Query the context of an event https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#event-context-api Args: roomid (str): the room where the event exist event_id (str): the event you want to query Returns: dict: a dict with event context """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/context/{event_id}", 1), ) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def delete_async(self, roomid: str, new_room_userid: str = None, room_name: str = None, message: str = None, block: bool = False, purge: bool = True, force_purge: bool = None) -> str: """Delete a room asynchronously https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#version-2-new-version Args: roomid (str): the room you want to delete new_room_userid (str, optional): equivalent to "new_room_user_id". Defaults to None. # noqa: E501 room_name (str, optional): equivalent to "room_name". Defaults to None. message (str, optional): equivalent to "message". Defaults to None. block (bool, optional): equivalent to "block". Defaults to False. purge (bool, optional): whether or not to purge all information of the rooom from the database. Defaults to True. force_purge (bool, optional): equivalent to "force_purge". Defaults to None. Returns: dict: a dict containing kicked_users, failed_tokick_users, local_aliases, new_room_id """ roomid = self.validate_room(roomid) data = {"block": block, "purge": purge} if new_room_userid is not None: new_room_userid = self.validate_username(new_room_userid) data["new_room_user_id"] = new_room_userid if room_name is not None: data["room_name"] = room_name if message is not None: data["message"] = message if force_purge is not None: data["force_purge"] = force_purge resp = self.connection.request( "DELETE", self.admin_patterns(f"/rooms/{roomid}", 2), json=data, ) data = resp.json() if resp.status_code == 200: return data["delete_id"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def delete_status(self, *, roomid: str = None, deleteid: str = None) -> dict: """Query the deletion of room(s) https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#status-of-deleting-rooms Args: roomid (str): the room to be queried deleteid (str): the delete id to be queried Returns: dict: a dict with room deletion status """ if roomid is not None and deleteid is not None: raise ValueError("roomid and deleteid cannot " "be presented at the same time") if roomid is not None: return self.delete_status_room(roomid) if deleteid is not None: return self.delete_status_id(deleteid) raise ValueError("Either roomid or deleteid should be specified") def delete_status_room(self, roomid: str) -> dict: """Query the deletion of room by room id https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#query-by-room_id Args: roomid (str): the room to be queried Returns: dict: a dict with room deletion status """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/delete_status", 2), ) data = resp.json() if resp.status_code == 200: return data["results"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def delete_status_id(self, deleteid: str) -> dict: """Query the deletion of room by id https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#query-by-delete_id Args: deleteid (str): the delete id to be queried Returns: dict: a dict with room deletion status """ resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/delete_status/{deleteid}", 2), ) data = resp.json() if resp.status_code == 200: return data else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def block(self, roomid: str, blocked: bool = True) -> bool: """Block or unblock a room https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#block-or-unblock-a-room Args: roomid (str): the room to block or unblock blocked (bool, optional): whether the room should be blocked or unblocked, True to blocked, False to unblocked. Defaults to True. # noqa: E501 Returns: bool: whether the room is blocked or not """ roomid = self.validate_room(roomid) resp = self.connection.request("PUT", self.admin_patterns( f"/rooms/{roomid}/block", 1), json={"block": blocked}) data = resp.json() if resp.status_code == 200: return data["block"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"]) def block_status(self, roomid: str) -> bool: """Check if a room is blocked https://github.com/matrix-org/synapse/blob/develop/docs/admin_api/rooms.md#get-block-status Args: roomid (str): the room to be queried Returns: bool: whether the room is blocked or not """ roomid = self.validate_room(roomid) resp = self.connection.request( "GET", self.admin_patterns(f"/rooms/{roomid}/block", 1)) data = resp.json() if resp.status_code == 200: # TBD: whether return the user_id in the response. return data["block"] else: if self.suppress_exception: return False, data else: raise SynapseException(data["errcode"], data["error"])