Ejemplo n.º 1
0
def send_msg(server_name, username, password, room_id, msg):
    client = MatrixClient(server_name)
    client.login(username=username, password=password)
    room = client.join_room(room_id)

    room.send_text(msg)
    client.logout()
Ejemplo n.º 2
0
class TinyMatrixtBot():
    def __init__(self, path_config):
        signal.signal(signal.SIGHUP, self.on_signal)
        signal.signal(signal.SIGINT, self.on_signal)
        signal.signal(signal.SIGTERM, self.on_signal)

        self.config = configparser.ConfigParser()
        self.config.read(path_config)

        _path_current = os.path.dirname(os.path.realpath(__file__))

        self.enabled_scripts = self.config.get("tiny-matrix-bot",
                                               "enabled_scripts",
                                               fallback="").strip()

        self.path_lib = self.config.get("tiny-matrix-bot",
                                        "lib",
                                        fallback=os.path.join(
                                            _path_current, "scripts")).strip()
        logger.debug("path_lib = {}".format(self.path_lib))
        if os.access(self.path_lib, os.R_OK):
            self.scripts = self.load_scripts(self.path_lib)
        else:
            logger.error("{} not readable".format(self.path_lib))
            sys.exit(1)

        self.path_var = self.config.get("tiny-matrix-bot",
                                        "var",
                                        fallback=os.path.join(
                                            _path_current, "data")).strip()
        logger.debug("path_var = {}".format(self.path_var))
        if os.access(self.path_var, os.W_OK):
            os.chdir(self.path_var)
        else:
            logger.error("{} not writeable".format(self.path_var))
            sys.exit(1)

        self.path_run = self.config.get("tiny-matrix-bot",
                                        "run",
                                        fallback=os.path.join(
                                            _path_current, "sockets")).strip()
        if os.access(self.path_run, os.W_OK):
            logger.debug("path_run = {}".format(self.path_run))
        else:
            logger.debug("path_run = {}".format(self.path_run) +
                         " (not writeable, disabling sockets)")
            self.path_run = False

        self.inviter_whitelist = self.config.get("tiny-matrix-bot",
                                                 "inviter_whitelist",
                                                 fallback="").strip()

        self.connect()
        self.user = self.client.get_user(self.client.user_id)
        self.user.set_display_name(self.config.get("tiny-matrix-bot", "name"))

        for room_id in self.client.get_rooms():
            self.join_room(room_id)

        self.client.start_listener_thread(
            exception_handler=self.listener_exception_handler)
        self.client.add_invite_listener(self.on_invite)
        self.client.add_leave_listener(self.on_leave)

        while True:
            sleep(1)

    def connect(self):
        _host = self.config.get("tiny-matrix-bot", "host")
        _user = self.config.get("tiny-matrix-bot", "user")
        _pass = self.config.get("tiny-matrix-bot", "pass")
        try:
            self.client = MatrixClient(_host)
            self.client.login(username=_user, password=_pass, limit=0)
            logger.info("connected to {}".format(_host))
        except Exception:
            logger.warning("connection to {} failed".format(_host) +
                           ", retrying in 5 seconds...")
            sleep(5)
            self.connect()

    def listener_exception_handler(self, e):
        self.connect()

    def on_signal(self, signal, frame):
        if signal == 1:
            self.scripts = self.load_scripts(self.path_lib)
        elif signal in [2, 15]:
            self.client.logout()
            sys.exit(0)

    def load_scripts(self, path):
        _scripts = {}
        for _script in os.listdir(path):
            _script_path = os.path.join(path, _script)
            if self.enabled_scripts:
                if _script not in self.enabled_scripts:
                    continue
            if (not os.access(_script_path, os.R_OK)
                    or not os.access(_script_path, os.X_OK)):
                continue
            _regex = subprocess.Popen(
                [_script_path],
                env={
                    "CONFIG": "1"
                },
                stdout=subprocess.PIPE,
                universal_newlines=True).communicate()[0].strip()
            if not _regex:
                continue
            _scripts[_regex] = _script_path
            logger.info("script load {}".format(_script))
            logger.debug("script regex {}".format(_regex))
        return _scripts

    def on_invite(self, room_id, state):
        _sender = "someone"
        for _event in state["events"]:
            if _event["type"] != "m.room.join_rules":
                continue
            _sender = _event["sender"]
            break
        logger.info("invited to {} by {}".format(room_id, _sender))
        if self.inviter_whitelist:
            if not re.search(self.inviter_whitelist, _sender, re.IGNORECASE):
                logger.info(
                    "no whitelist match, ignoring invite from {}".format(
                        _sender))
                return
        self.join_room(room_id)

    def join_room(self, room_id):
        logger.info("join {}".format(room_id))
        _room = self.client.join_room(room_id)
        _room.add_listener(self.on_room_event)
        if self.path_run is not False:
            _thread = Thread(target=self.create_socket, args=(_room, ))
            _thread.daemon = True
            _thread.start()

    def create_socket(self, room):
        _socket_name = re.search("^!([a-z]+):", room.room_id,
                                 re.IGNORECASE).group(1)
        _socket_path = os.path.join(self.path_run, _socket_name)
        try:
            os.remove(_socket_path)
        except OSError:
            pass
        _socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        _socket.bind(_socket_path)
        _socket.listen(1)
        logger.info("socket bind {}".format(_socket_path))
        while True:
            _conn, _addr = _socket.accept()
            _recv = _conn.recv(4096).decode('utf-8').strip()
            logger.debug("socket recv {} {}".format(_socket_path, _recv))
            room.send_text(_recv)

    def on_leave(self, room_id, state):
        _sender = "someone"
        for _event in state["timeline"]["events"]:
            if not _event["membership"]:
                continue
            _sender = _event["sender"]
        logger.info("kicked from {} by {}".format(room_id, _sender))

    def on_room_event(self, room, event):
        if event["sender"] == self.client.user_id:
            return
        if event["type"] != "m.room.message":
            return
        if event["content"]["msgtype"] != "m.text":
            return
        _args = event["content"]["body"].strip()
        for _regex, _script in self.scripts.items():
            if not re.search(_regex, _args, re.IGNORECASE):
                continue
            self.run_script(room, event, [_script, _args])

    def run_script(self, room, event, args):
        logger.debug("script room_id {}".format(event["room_id"]))
        logger.debug("script sender {}".format(event["sender"]))
        logger.debug("script run {}".format(args))
        _script = subprocess.Popen(args,
                                   env={
                                       "MXROOMID": event["room_id"],
                                       "MXSENDER": event["sender"]
                                   },
                                   stdout=subprocess.PIPE,
                                   universal_newlines=True)
        _output = _script.communicate()[0].strip()
        if _script.returncode != 0:
            logger.debug("script exit {}".format(_script.returncode))
            return
        sleep(0.5)
        for _p in _output.split("\n\n"):
            for _l in _p.split("\n"):
                logger.debug("script output {}".format(_l))
            room.send_text(_p)
            sleep(0.8)
Ejemplo n.º 3
0
class MatrigramClient(object):
    def __init__(self, server, tb, username):
        self.client = MatrixClient(server)
        self.tb = tb
        self.token = None
        self.server = server
        self.username = username
        self.focus_room_id = None
        self.msg_type_router = {
            'm.image': self.forward_image_to_tb,
            'm.audio': self.forward_voice_to_tb,
            'm.video': self.forward_video_to_tb,
            'm.emote': self.forward_emote_to_tb,
        }
        self.room_listener_uid = None
        self.ephemeral_listener_uid = None

    def login(self, username, password):
        try:
            self.token = self.client.login_with_password(username, password)
            logger.info('token = %s', self.token)
            rooms = self.get_rooms_aliases()
            if rooms:
                # set focus room to "first" room
                self.set_focus_room(rooms.keys()[0])

            self.client.add_invite_listener(self.on_invite_event)
            self.client.add_leave_listener(self.on_leave_event)
            self.client.start_listener_thread()
            return True, "OK"
        except MatrixRequestError:
            return False, "Failed to login"
        except ConnectionError:
            return False, "Server is offline"

    def logout(self):
        self.client.logout()

    def on_event(self, _, event):
        logger.debug('entered with message %s', pprint_json(event))
        sender = event['sender'].split(':')[0].encode('utf-8')

        # Prevent messages loopback
        if sender.startswith(u'@{}'.format(self.username)):
            return

        if event['type'] == "m.room.message":
            msgtype = event['content']['msgtype']

            if msgtype != 'm.text':
                callback = self.msg_type_router.get(msgtype)
                if callback:
                    callback(event)
                return

            content = event['content']['body'].encode('utf-8')
            self.tb.send_message(sender, content, self)

        elif event['type'] == "m.room.topic":
            topic = event['content']['topic'].encode('utf-8')
            self.tb.send_topic(sender, topic, self)

    def on_ephemeral_event(self, _, ee):
        logger.debug(pprint_json(ee))

        if ee['type'] == 'm.typing':
            if ee['content']['user_ids']:
                self.tb.start_typing_thread(self)
            else:
                self.tb.stop_typing_thread(self)

    def on_leave_event(self, room_id, le):
        logger.debug(pprint_json(le))

        if le['timeline']['events'][0]['sender'] != le['timeline']['events'][
                0]['state_key']:
            self.tb.send_kick(self._room_id_to_alias(room_id), self)

    def on_invite_event(self, _, ie):
        logger.debug('invite event %s', pprint_json(ie))
        room_name = None
        for event in ie['events']:
            if event['type'] == 'm.room.name':
                room_name = event['content']['name']

        if room_name:
            self.tb.send_invite(self, self._room_id_to_alias(room_name))

    def join_room(self, room_id_or_alias):
        try:
            self.client.join_room(room_id_or_alias)
            self.set_focus_room(room_id_or_alias)
            return True
        except MatrixRequestError:
            return False

    def leave_room(self, room_id_or_alias):
        room = self.get_room_obj(room_id_or_alias)
        if not room:
            logger.error('cant find room')
            return False

        if self.focus_room_id == room.room_id:
            rooms = self.get_rooms_aliases()
            room_id = self._room_alias_to_id(room_id_or_alias)

            del rooms[room_id]
            new_focus_room = rooms.keys()[0] if rooms else None
            self.set_focus_room(new_focus_room)

        return room.leave()

    def set_focus_room(self, room_id_or_alias):
        if self._room_alias_to_id(room_id_or_alias) == self.focus_room_id:
            return

        # remove current focus room listeners
        if self.focus_room_id is not None:
            room_obj = self.get_room_obj(self.focus_room_id)
            room_obj.remove_listener(self.room_listener_uid)
            self.room_listener_uid = None
            room_obj.remove_ephemeral_listener(self.ephemeral_listener_uid)
            self.ephemeral_listener_uid = None
            logger.info("remove focus room %s", self.focus_room_id)
            self.focus_room_id = None

        # set new room on focus
        if room_id_or_alias is not None:
            self.focus_room_id = self._room_alias_to_id(room_id_or_alias)
            room_obj = self.get_room_obj(self.focus_room_id)
            self.room_listener_uid = room_obj.add_listener(self.on_event)
            self.ephemeral_listener_uid = room_obj.add_ephemeral_listener(
                self.on_ephemeral_event)
            logger.info("set focus room to %s", self.focus_room_id)

    def get_focus_room_alias(self):
        return self._room_id_to_alias(self.focus_room_id)

    def have_focus_room(self):
        return self.focus_room_id is not None

    def get_members(self):
        room_obj = self.get_room_obj(self.focus_room_id)
        rtn = room_obj.get_joined_members()

        return [
            member['displayname'] for _, member in rtn.items()
            if member.get('displayname')
        ]

    def set_name(self, name):
        user = self.client.get_user(self.client.user_id)
        user.set_display_name(name)

    def emote(self, body):
        room_obj = self.get_room_obj(self.focus_room_id)
        room_obj.send_emote(body)

    def create_room(self, alias, is_public=False, invitees=()):
        try:
            room_obj = self.client.create_room(alias, is_public, invitees)
            room_obj.update_aliases()
            logger.debug('room_id %s', room_obj.room_id)
            logger.debug('room_alias %s', room_obj.aliases[0])
            return (room_obj.room_id, room_obj.aliases[0])
        except MatrixRequestError:
            logger.error('error creating room')
            return None, None

    def backfill_previous_messages(self, limit=10):
        room_obj = self.get_room_obj(self.focus_room_id)
        room_obj.backfill_previous_messages(limit=limit)

    def get_rooms_aliases(self):
        # returns a dict with id: room obj
        rooms = self._get_rooms_updated()
        if not rooms:
            return rooms

        logger.debug("rooms got from server are %s", rooms)

        # return dict with id: list of aliases or id (if no alias exists)
        return {
            key: val.aliases if val.aliases else [key]
            for (key, val) in rooms.items()
        }

    def get_room_obj(self, room_id_or_alias):
        """Get room object of specific id or alias.

        Args:
            room_id_or_alias (str): Room's id or alias.

        Returns (Room): Room object corresponding to room_id_or_alias.

        """
        rooms = self._get_rooms_updated()
        room_id = self._room_alias_to_id(room_id_or_alias)

        return rooms.get(room_id)

    def send_message(self, msg):
        room_obj = self.get_room_obj(self.focus_room_id)

        if not room_obj:
            logger.error('cant find room')
        else:
            room_obj.send_text(msg)

    def send_photo(self, path):
        with open(path, 'rb') as f:
            mxcurl = self.client.upload(f.read(),
                                        mimetypes.guess_type(path)[0])

            room_obj = self.get_room_obj(self.focus_room_id)

            if not room_obj:
                logger.error('cant find room')
            else:
                room_obj.send_image(mxcurl, os.path.split(path)[1])

    def send_voice(self, path):
        with open(path, 'rb') as f:
            mxcurl = self.client.upload(f.read(),
                                        mimetypes.guess_type(path)[0])
            room_obj = self.get_room_obj(self.focus_room_id)
            room_obj.send_audio(mxcurl, os.path.split(path)[1])

    def send_video(self, path):
        with open(path, 'rb') as f:
            mxcurl = self.client.upload(f.read(),
                                        mimetypes.guess_type(path)[0])
            room_obj = self.get_room_obj(self.focus_room_id)
            room_obj.send_video(mxcurl, os.path.split(path)[1])

    def discover_rooms(self):
        res = requests.get('{}/_matrix/client/r0/publicRooms?limit=20'.format(
            self.server))
        res_json = res.json()

        room_objs = res_json['chunk']
        return [
            room['aliases'][0] for room in room_objs if room.get('aliases')
        ]

    def download_from_event(self, event):
        mxcurl = event['content']['url']
        link = self.client.api.get_download_url(mxcurl)
        media_id = mxcurl.split('/')[3]
        media_type = event['content']['info']['mimetype'].split('/')[1]
        path = os.path.join(self.tb.config['media_dir'],
                            '{}.{}'.format(media_id, media_type))
        download_file(link, path)

        return path

    def forward_image_to_tb(self, event):
        sender = event['sender'].split(':')[0].encode('utf-8')
        path = self.download_from_event(event)
        self.tb.send_photo(sender, path, self)

    def forward_voice_to_tb(self, event):
        sender = event['sender'].split(':')[0].encode('utf-8')
        path = self.download_from_event(event)
        self.tb.send_voice(sender, path, self)

    def forward_video_to_tb(self, event):
        sender = event['sender'].split(':')[0].encode('utf-8')
        path = self.download_from_event(event)
        self.tb.send_video(sender, path, self)

    def forward_emote_to_tb(self, event):
        sender = event['sender'].split(':')[0].encode('utf-8')
        content = event['content']['body'].encode('utf-8')
        self.tb.send_emote(sender, content, self)

    def _room_id_to_alias(self, id):
        """Convert room id to alias.

        Args:
            id (str): Room id.

        Returns (str): Room alias.

        """
        if id is None:
            return None
        if id.startswith('#'):
            return id
        rooms = self.get_rooms_aliases()
        if id in rooms:
            return rooms[id][0]
        else:
            return None

    def _room_alias_to_id(self, alias):
        """Convert room alias to id.

        Args:
            alias (str): Room alias.

        Returns (str): Room id.

        """
        if alias is None:
            return None

        if not alias.startswith('#'):
            return alias

        return self.client.api.get_room_id(alias)

    def _get_rooms_updated(self):
        """Return rooms dictionary with updated aliases.

        Returns (dict): Return rooms dictionary with updated aliases.

        """
        rooms = self.client.get_rooms()
        for room in rooms.values():
            room.update_aliases()

        return rooms
Ejemplo n.º 4
0
        add_result = db_helper.check_add_game(white, black, result, sender,
                                              comment)
        if add_result.startswith("fehler"):
            myroom.send_text(add_result)
        else:
            myroom.send_text(msg_result + "\n" + add_result)


myroom.add_listener(on_message)
client.start_listener_thread()
myroom.send_text("Naelob returns!")

try:
    get_input = raw_input
except NameError:
    get_input = input

#====== MAIN LOOP: ==========
while True:
    msg = get_input()
    #TEST
    #time.sleep(0.3)
    if msg == "/quit":
        break

client.stop_listener_thread()
myroom.send_text("Naelob verabschiedet sich und geht offline.")

client.logout()
Ejemplo n.º 5
0
Archivo: core.py Proyecto: Vzaa/navi
class Navi:
    """
    The Navi API
    """
    def __init__(self, host_url, user_id, password, target_users, quiet=False):
        """ Starts up the bot. Connects to the homeserver and logs in.

        Args:
            base_url: Server url, e.g. https://example.com:8448
            user_id: @user:example.com
            password: p4ssw0rd
            target_users: List of users (@user_id:example.com) to push messages
                to 
        """
        self.quiet = quiet
        self.target_users = target_users
        self.host_url = host_url
        self.user_id = user_id
        self.password = password

        try:
            self.client = MatrixClient(self.host_url)
            self._log("Connecting to {}...".format(self.host_url))
            self.token = self.client.login_with_password(
                self.user_id, self.password)
        except MatrixRequestError as e:
            Navi._handle_matrix_exception(e)

        self._fetch_rooms()
        self._log("Current rooms:\n\t{}".format("\n\t".join(
            self.rooms.keys())))

    def shutdown(self):
        """ Stops the bot """
        self.client.logout()
        self._log("Connection closed.")

    def push_msg(self, msg):
        """ Push a message

        Args:
            msg: The message to push
        """
        self._cleanup_rooms()
        self._create_rooms()

        self._log("Pushing message...")
        for r in self.rooms.values():
            r.send_text(msg)

    def push_media(self, filepath):
        """ Push an image or video

        Args:
            filepath: The file path
        """
        self._cleanup_rooms()
        self._create_rooms()

        self._log("Pushing media...")

        mime = filetype.guess(filepath).mime
        if "image" in mime:
            media_type = "image"
        elif "video" in mime:
            media_type = "video"
        else:
            return

        with open(filepath, "rb") as fld:
            contents = fld.read()
            mxc = self.client.upload(contents, mime)
            if media_type == "image":
                for r in self.rooms.values():
                    r.send_image(mxc, os.path.basename(filepath))
            elif media_type == "video":
                for r in self.rooms.values():
                    r.send_video(mxc, os.path.basename(filepath))

    def leave_all_rooms(self):
        """ Leaves all rooms """
        self._log("Leaving all rooms..")
        for r in self.rooms.values():
            r.leave()
        self._log("Left {} rooms".format(len(rooms)))

    def _cleanup_rooms(self):
        """ Leaves rooms that no target user is in """
        for room_id in self.rooms.keys():
            room = self.rooms[room_id]
            users = room.get_joined_members().keys()
            if any(u in users for u in self.target_users): continue
            self._log("Leaving room {} (Name: {})".format(room_id, room.name))
            room.leave()
        self._fetch_rooms()

    def _create_rooms(self):
        """ Create rooms for users not found in any current room """
        # Compile list of all users
        if self.rooms:
            current_users = reduce(
                lambda a, b: a & b,
                map(lambda r: set(r.get_joined_members().keys()),
                    self.rooms.values()))
        else:
            current_users = set()
        missing_users = self.target_users - current_users
        for u in missing_users:
            try:
                self._log("Creating room for user {}...".format(u))
                self.client.create_room(invitees=[u])
            except MatrixRequestError as e:
                Navi._handle_matrix_exception(e)

    def _fetch_rooms(self):
        """ Fetches list of rooms """
        self.rooms = self.client.get_rooms()

    def _log(self, msg):
        if not self.quiet:
            print(msg)

    @staticmethod
    def _handle_matrix_exception(e):
        """ Print exception and quit """
        sys.stderr.write("Server Error {}: {}\n".format(e.code, e.content))
        sys.exit(-1)
Ejemplo n.º 6
0
class MatrixBotLedger(threading.Thread):
    def __init__(self, homeserver: str, username: str, password: str,
                 allowed_users: str, kill_event: threading.Event):
        super().__init__()
        logger.debug(
            f"__init__({homeserver}, {username}, {password}, {allowed_users}, {kill_event})"
        )

        self.allowed_users = allowed_users.split(',')
        self.homeserver = homeserver
        self.kill_event = kill_event
        self.password = password
        self.sh_timeout = SH_TIMEOUT
        self.username = username

        self.client = MatrixClient(self.homeserver)
        self.allowed_users.append(self.username)
        logger.debug(f"allowed users: {self.allowed_users}")

    def run(self) -> None:
        logger.debug("run()")

        self.connect()

        for room_id in self.client.rooms:
            self.join_room(room_id)

        self.client.start_listener_thread(
            timeout_ms=5000, exception_handler=self.listener_exception_handler)
        self.client.add_invite_listener(self.on_invite)
        self.client.add_leave_listener(self.on_leave)

        self.kill_event.wait()
        logger.info("Logging out.")
        self.client.logout()

    def connect(self):
        logger.debug("connect()")

        if not self.kill_event.is_set():
            try:
                self.client.login(username=self.username,
                                  password=self.password,
                                  limit=0)
                logger.info(f"connected to {self.homeserver}")
            except MatrixError as me:
                logger.warning(
                    f"connection to {self.homeserver} failed, retrying in 5 seconds... ({me})"
                )
                time.sleep(5)
                self.connect()

    def listener_exception_handler(self, e):
        logger.debug(f"listener_exception_handler({e})")

        self.connect()

    def on_invite(self, room_id, state):
        logger.debug(f"on_invite({room_id}, {state}")

        sender = "someone"
        for event in state["events"]:
            if event["type"] != "m.room.join_rules":
                continue
            sender = event["sender"]
            break

        logger.info(f"Invited to {room_id} by {sender}.")

        if sender not in self.allowed_users:
            logger.info(f"no whitelist match, ignoring invite from {sender}")
            return

        self.join_room(room_id)

    def join_room(self, room_id):
        logger.debug(f"join_room({room_id})")

        room = self.client.join_room(room_id)
        room.add_listener(self.on_room_event)
        logger.info(f"Joined room {room.display_name}.")

    # TODO: debug this
    def on_leave(self, room_id, state):
        logger.debug(f"on_leave({room_id}, {state})")

        sender = "someone"
        for event in state["timeline"]["events"]:
            if not event["membership"]:
                continue
            sender = event["sender"]
        logger.info(f"Kicked from {room_id} by {sender}.")

    def on_room_event(self, room: Room, event):
        logger.debug(f"on_room_event({room}, {event}")

        if event["sender"] == self.client.user_id \
                or event["type"] != "m.room.message" \
                or event["content"]["msgtype"] != "m.text":
            return

        content_body = event["content"]["body"]
        body, html = None, None

        if content_body.startswith('!echo '):
            message = content_body[6:]
            logger.info(f"Echoing message '{message}' to room {room}.")
            body = message
        elif content_body.startswith('!sh '):
            body, html = self.run_local_command('!sh ', content_body)
        elif content_body.startswith('!ledger '):
            body, html = self.run_local_command('!ledger ',
                                                content_body,
                                                keep_prefix=True)

        if body:
            self.safe_send_message(room, body, html)

    def run_local_command(self,
                          prefix: str,
                          command: str,
                          keep_prefix: bool = False) -> tuple:
        logger.debug(f"run_local_command({prefix}, {command}, {keep_prefix})")

        command = command[len(prefix):]
        body, html = None, None
        if keep_prefix:
            command = prefix[1:] + command
        try:
            body = self.__sh(command)
            html = f"<pre>{body}</pre"
        except subprocess.CalledProcessError as cpe:
            logger.warning(cpe)
            body = f"command failed: {cpe}"
        except subprocess.TimeoutExpired as te:
            logger.warning(te)
            body = f"command timed out: {te}"

        return body, html

    def __sh(self, command) -> str:
        logger.debug(f"__sh({command})")

        cmd = ["/bin/sh", "-c", command]
        return subprocess.check_output(cmd, timeout=self.sh_timeout).decode()

    def safe_send_message(self, room: Room, body: str, html: str):
        logger.debug(f"safe_send_message({room}, {body}, {html})")

        members = room.get_joined_members()
        logger.debug(f"room joined members: {members}")
        for u in members:
            if u.user_id not in self.allowed_users:
                body = "I'm sorry, but not everyone in this room has clearance, so I'm not going to respond."
                html = None
                break
        try:
            room.send_html(html, body)
        except MatrixError as me:
            logger.error(me)