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()
Пример #2
0
    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"])
Пример #3
0
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"
Пример #4
0
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)
Пример #5
0
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=[{
Пример #6
0
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)
Пример #7
0
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"])
Пример #9
0
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"])