Example #1
0
class BridgeBot:
    xmpp = None  # type: ClientXMPP
    matrix = None  # type: MatrixClient
    topic_room_id_map = None  # type: Dict[str, str]
    special_rooms = None  # type: Dict[str, MatrixRoom]
    special_room_names = None  # type: Dict[str, str]
    groupchat_flag = None  # type: str
    groupchat_jids = None  # type: List[str]
    restore_room_topic = True  # type: bool

    users_to_invite = None  # type: List[str]
    matrix_room_topics = None  # type: Dict[str, str]
    matrix_server = None  # type: Dict[str, str]
    matrix_login = None  # type: Dict[str, str]
    xmpp_server = None  # type: Tuple[str, int]
    xmpp_login = None  # type: Dict[str, str]
    xmpp_roster_options = None  # type: Dict[str, bool]
    xmpp_groupchat_nick = None  # type: str

    send_messages_to_all_chat = True  # type: bool
    disable_all_chat_room = False  # type: bool
    send_presences_to_control = True  # type: bool
    groupchat_mute_own_nick = True  # type: bool

    disabled_jids = set()  # type: Set[str]

    @property
    def bot_id(self) -> str:
        return self.matrix_login['username']

    def __init__(self, config_file: str = CONFIG_FILE):
        self.groupchat_jids = []
        self.topic_room_id_map = {}
        self.special_rooms = {
            'control': None,
            'all_chat': None,
        }
        self.special_room_names = {
            'control': 'XMPP Control Room',
            'all_chat': 'XMPP All Chat',
        }
        self.xmpp_roster_options = {}

        self.load_config(config_file)

        self.matrix = MatrixClient(**self.matrix_server)
        self.xmpp = ClientXMPP(**self.xmpp_login, **self.xmpp_roster_options)

        self.matrix.login_with_password(**self.matrix_login)

        if self.disable_all_chat_room:
            self.send_messages_to_all_chat = False  #should not be necessary (see load_config)
            if 'all_chat' in self.special_rooms:
                del self.special_rooms['all_chat']
            if 'all_chat' in self.special_room_names:
                del self.special_room_names['all_chat']

        # Prepare matrix special channels and their listeners
        for room in self.matrix.get_rooms().values():
            room.update_room_topic()
            topic = room.topic

            if topic in self.special_rooms.keys():
                logging.debug('Recovering special room: ' + topic)
                self.special_rooms[topic] = room

            elif topic.startswith(self.groupchat_flag):
                room_jid = topic[len(self.groupchat_flag):]
                self.groupchat_jids.append(room_jid)

        for topic, room in self.special_rooms.items():
            if room is None:
                room = self.matrix.create_room()
            self.setup_special_room(room, topic)

        self.special_rooms['control'].add_listener(self.matrix_control_message,
                                                   'm.room.message')
        if not self.disable_all_chat_room:
            self.special_rooms['all_chat'].add_listener(
                self.matrix_all_chat_message, 'm.room.message')

        # Invite users to special rooms
        for room in self.special_rooms.values():
            for user_id in self.users_to_invite:
                room.invite_user(user_id)

        # Prepare xmpp listeners
        self.xmpp.add_event_handler('roster_update', self.xmpp_roster_update)
        self.xmpp.add_event_handler('message', self.xmpp_message)
        self.xmpp.add_event_handler('presence_available',
                                    self.xmpp_presence_available)
        self.xmpp.add_event_handler('presence_unavailable',
                                    self.xmpp_presence_unavailable)
        self.xmpp.add_event_handler('groupchat_message',
                                    self.xmpp_groupchat_message)

        # Connect to XMPP and start processing XMPP events
        self.xmpp.connect(self.xmpp_server)
        self.xmpp.process(block=False)

        # Rejoin group chats
        logging.debug('Rejoining group chats')
        for room_jid in self.groupchat_jids:
            self.xmpp.plugin['xep_0045'].joinMUC(room_jid,
                                                 self.xmpp_groupchat_nick)

        logging.debug('Done with bot init')

    def load_config(self, path: str):
        with open(path, 'r') as conf_file:
            config = yaml.load(conf_file)

        self.users_to_invite = config['matrix']['users_to_invite']
        self.matrix_room_topics = config['matrix']['room_topics']
        self.groupchat_flag = config['matrix']['groupchat_flag']
        if 'restore_room_topic' in config[
                'matrix']:  # to be compatible to other config files
            self.restore_room_topic = config['matrix']['restore_room_topic']

        self.matrix_server = config['matrix']['server']
        self.matrix_login = config['matrix']['login']
        self.xmpp_server = (config['xmpp']['server']['host'],
                            config['xmpp']['server']['port'])
        self.xmpp_login = config['xmpp']['login']
        self.xmpp_groupchat_nick = config['xmpp']['groupchat_nick']

        self.send_presences_to_control = config['send_presences_to_control']
        self.send_messages_to_all_chat = config['send_messages_to_all_chat']
        if not self.send_messages_to_all_chat and 'disable_all_chat_room' in config:
            self.disable_all_chat_room = config['disable_all_chat_room']
        else:
            self.disable_all_chat_room = False

        self.groupchat_mute_own_nick = config['groupchat_mute_own_nick']

        self.xmpp_roster_options = config['xmpp']['roster_options']

        if 'disabled_jids' in config:
            self.disabled_jids = set(config['disabled_jids'])
            if 'xmpp_login_jid' in self.disabled_jids:
                self.disabled_jids.add(self.xmpp_login['jid'])
                self.disabled_jids.remove('xmpp_login_jid')

    def get_room_for_jid(self, jid: str) -> MatrixRoom:
        """
        Return the room corresponding to the given XMPP JID
        :param jid: bare XMPP JID, should not include the resource
        :return: Matrix room object for chatting with that JID
        """
        room_id = self.topic_room_id_map[jid]
        return self.matrix.get_rooms()[room_id]

    def get_unmapped_rooms(self) -> List[MatrixRoom]:
        """
        Returns a list of all Matrix rooms which are not a special room (e.g., the control room) and
        do not have a corresponding entry in the topic -> room map.
        :return: List of unmapped, non-special Matrix room objects.
        """
        special_room_ids = [r.room_id for r in self.special_rooms.values()]
        valid_room_ids = [v for v in self.topic_room_id_map.values()
                          ] + special_room_ids
        unmapped_rooms = [
            room for room_id, room in self.matrix.get_rooms().items()
            if room_id not in valid_room_ids
        ]
        return unmapped_rooms

    def get_empty_rooms(self) -> List[MatrixRoom]:
        """
        Returns a list of all Matrix rooms which are occupied by only one user
        (the bot itself).
        :return: List of Matrix rooms occupied by only the bot.
        """
        empty_rooms = [
            room for room in self.matrix.get_rooms().values()
            if len(room.get_joined_members()) < 2
        ]
        return empty_rooms

    def setup_special_room(self, room, topic: str):
        """
        Sets up a Matrix room with the requested topic and adds it to the self.special_rooms map.

        If a special room with that topic already exists, it is replaced in the special_rooms
         map by the new room.
        :param room: Room to set up
        :param topic: Topic for the room
        """
        room.set_room_topic(topic)
        room.set_room_name(self.special_room_names[topic])
        self.special_rooms[topic] = room

        logging.debug('Set up special room with topic {} and id'.format(
            str(room.topic), room.room_id))

    def create_mapped_room(self,
                           topic: str,
                           name: str = None) -> MatrixRoom or None:
        """
        Create a new room and add it to self.topic_room_id_map.

        :param topic: Topic for the new room
        :param name: (Optional) Name for the new room
        :return: Room which was created
        """
        if not name:  #room without name is shown as the bot's name in clients like riot
            name = topic

        if topic in self.groupchat_jids:
            logging.debug(
                'Topic {} is a groupchat without its flag, ignoring'.format(
                    topic))
            return None
        elif topic in self.topic_room_id_map.keys():
            room_id = self.topic_room_id_map[topic]
            room = self.matrix.get_rooms()[room_id]
            logging.debug('Room with topic {} already exists!'.format(topic))
        else:
            room = self.matrix.create_room()
            room.set_room_topic(topic)
            self.topic_room_id_map[topic] = room
            logging.info('Created mapped room with topic {} and id {}'.format(
                topic, str(room.room_id)))
            room.set_room_name(name)

        room.update_room_name(
        )  #room.name is not set automatically in all cases

        if self.restore_room_topic and room.name != name:
            room.set_room_name(name)

        return room

    def map_rooms_by_topic(self):
        """
        Add unmapped rooms to self.topic_room_id_map, and listen to messages from those rooms.

        Rooms whose topics are empty or do not contain an '@' symbol are assumed to be special
         rooms, and will not be mapped.
        """
        unmapped_rooms = self.get_unmapped_rooms()

        for room in unmapped_rooms:
            room.update_room_topic()

            logging.debug('Unmapped room {} ({}) [{}]'.format(
                room.room_id, room.name, room.topic))

            if room.topic is None or '@' not in room.topic:
                logging.debug(
                    'Leaving it as-is (special room, topic does not contain @)'
                )
            else:
                self.topic_room_id_map[room.topic] = room.room_id

                room.add_listener(self.matrix_message, 'm.room.message')

    def matrix_control_message(self, room: MatrixRoom, event: Dict):
        """
        Handle a message sent to the control room.

        Does nothing unless a valid command is received:
          refresh  Probes the presence of all XMPP contacts, and updates the roster.
          purge    Leaves any ((un-mapped and non-special) or empty) Matrix rooms.
          joinmuc [email protected]   Joins a muc
          leavemuc [email protected]  Leaves a muc

        :param room: Matrix room object representing the control room
        :param event: The Matrix event that was received. Assumed to be an m.room.message .
        """
        # Always ignore our own messages
        if event['sender'] == self.bot_id:
            return

        logging.debug('matrix_control_message: {}  {}'.format(
            room.room_id, str(event)))

        if event['content']['msgtype'] == 'm.text':
            message_body = event['content']['body']
            logging.info('Matrix received control message: ' + message_body)

            message_parts = message_body.split()
            if len(message_parts) > 0:
                message_parts[0] = message_parts[0].lower()
                # what about a empty body?
                if message_parts[0] == 'refresh':
                    for jid in self.topic_room_id_map.keys():
                        self.xmpp.send_presence(pto=jid, ptype='probe')

                    self.xmpp.send_presence()
                    self.xmpp.get_roster()

                elif message_parts[0] == 'purge':
                    self.special_rooms['control'].send_text(
                        'Purging unused rooms')

                    # Leave from unwanted rooms
                    for room in self.get_unmapped_rooms(
                    ) + self.get_empty_rooms():
                        logging.info(
                            'Leaving room {r.room_id} ({r.name}) [{r.topic}]'.
                            format(r=room))
                        if room.topic.startswith(self.groupchat_flag):
                            room_jid = room.topic[len(self.groupchat_flag):]
                            self.xmpp.plugin['xep_0045'].leaveMUC(room_jid)
                        room.leave()

                elif len(message_parts) > 1:
                    if message_parts[0] == 'joinmuc':
                        room_jid = message_parts[1]
                        logging.info('XMPP MUC join: {}'.format(room_jid))
                        self.create_groupchat_room(room_jid)
                        self.xmpp.plugin['xep_0045'].joinMUC(
                            room_jid, self.xmpp_groupchat_nick)
                    elif message_parts[0] == 'leavemuc':
                        room_jid = message_parts[1]
                        logging.info('XMPP MUC leave: {}'.format(room_jid))
                        self.xmpp.plugin['xep_0045'].leaveMUC(
                            room_jid, self.xmpp_groupchat_nick)
                        room = self.get_room_for_jid(self.groupchat_flag +
                                                     room_jid)
                        room.leave()

    def matrix_all_chat_message(self, room: MatrixRoom, event: Dict):
        """
        Handle a message sent to Matrix all-chat room.

        Currently just sends a warning that nobody will hear your message.

        :param room: Matrix room object representing the all-chat room
        :param event: The Matrix event that was received. Assumed to be an m.room.message .
        """
        # Always ignore our own messages
        if event['sender'] == self.bot_id:
            return

        logging.debug('matrix_all_chat_message: {}  {}'.format(
            room.room_id, str(event)))

        room.send_notice('Don\'t talk in here! Nobody gets your messages.')

    def matrix_message(self, room: MatrixRoom, event: Dict):
        """
        Handle a message sent to a mapped Matrix room.

        Sends the message to the xmpp handle specified by the room's topic.

        :param room: Matrix room object representing the room in which the message was received.
        :param event: The Matrix event that was received. Assumed to be an m.room.message .
        """
        if event['sender'] == self.bot_id:
            return

        if room.topic in self.special_rooms.keys():
            logging.error('matrix_message called on special channel')

        logging.debug('matrix_message: {}  {}'.format(room.room_id, event))

        if event['content']['msgtype'] == 'm.text':
            message_body = event['content']['body']

            if room.topic.startswith(self.groupchat_flag):
                jid = room.topic[len(self.groupchat_flag):]
                message_type = 'groupchat'
            else:
                jid = room.topic
                message_type = 'chat'

            name = self.xmpp.jid_nick_map[jid]

            logging.info('Matrix received message to {} : {}'.format(
                jid, message_body))
            self.xmpp.send_message(mto=jid,
                                   mbody=message_body,
                                   mtype=message_type)

            if self.send_messages_to_all_chat:
                self.special_rooms['all_chat'].send_notice('To {} : {}'.format(
                    name, message_body))

    def xmpp_message(self, message: Dict):
        """
        Handle a message received by the XMPP client.

        Sends the message to the relevant mapped Matrix room, as well as the Matrix all-chat room.

        :param message: The message that was received.
        :return:
        """
        logging.info('XMPP received {} : {}'.format(message['from'].full,
                                                    message['body']))

        if message['type'] in ('normal', 'chat'):
            from_jid = message['from'].bare
            from_name = self.xmpp.jid_nick_map[from_jid]

            if from_jid in self.groupchat_jids:
                logging.warning(
                    'Normal chat message from a groupchat, ignoring...')
                return

            room = self.get_room_for_jid(from_jid)
            room.send_text(message['body'])
            if self.send_messages_to_all_chat:
                self.special_rooms['all_chat'].send_text('From {}: {}'.format(
                    from_name, message['body']))

    def xmpp_groupchat_message(self, message: Dict):
        """
        Handle a groupchat message received by the XMPP client.

        Sends the message to the relevant mapped Matrix room, as well as the Matrix all-chat room.

        :param message: The message that was received.
        :return:
        """
        logging.info('XMPP MUC received {} : {}'.format(
            message['from'].full, message['body']))

        if message['type'] == 'groupchat':
            from_jid = message['from'].bare
            from_name = message['mucnick']

            if self.groupchat_mute_own_nick and from_name == self.xmpp_groupchat_nick:
                return

            room = self.get_room_for_jid(self.groupchat_flag + from_jid)
            room.send_text(from_name + ': ' + message['body'])
            if self.send_messages_to_all_chat:
                self.special_rooms['all_chat'].send_text(
                    'Room {}, from {}: {}'.format(from_jid, from_name,
                                                  message['body']))

    def create_groupchat_room(self, room_jid: str):
        room = self.create_mapped_room(topic=self.groupchat_flag + room_jid)
        if room_jid not in self.groupchat_jids:
            self.groupchat_jids.append(room_jid)
        for user_id in self.users_to_invite:
            room.invite_user(user_id)

    def xmpp_presence_available(self, presence: Dict):
        """
        Handle a presence of type "available".

        Sends a notice to the control channel.

        :param presence: The presence that was received.
        """
        logging.debug('XMPP received {} : (available)'.format(
            presence['from'].full))

        jid = presence['from'].bare
        if jid in self.disabled_jids:
            return

        if jid not in self.xmpp.jid_nick_map.keys():
            logging.error('JID NOT IN ROSTER!?')
            self.xmpp.get_roster()
            return

        if self.send_presences_to_control:
            name = self.xmpp.jid_nick_map[jid]
            self.special_rooms['control'].send_notice(
                '{} available ({})'.format(name, jid))

    def xmpp_presence_unavailable(self, presence):
        """
        Handle a presence of type "unavailable".

        Sends a notice to the control channel.

        :param presence: The presence that was received.
        """
        logging.debug('XMPP received {} : (unavailable)'.format(
            presence['from'].full))

        jid = presence['from'].bare
        if jid in self.disabled_jids:
            return

        if self.send_presences_to_control:
            name = self.xmpp.jid_nick_map[jid]
            self.special_rooms['control'].send_notice(
                '{} unavailable ({})'.format(name, jid))

    def xmpp_roster_update(self, _event):
        """
        Handle an XMPP roster update.

        Maps all existing Matrix rooms, creates a new mapped room for each JID in the roster
        which doesn't have one yet, and invites the users specified in the config in to all the rooms.

        :param _event: The received roster update event (unused).
        """
        logging.debug('######### ROSTER UPDATE ###########')

        rjids = [jid for jid in self.xmpp.roster]
        if len(rjids) > 1:
            raise Exception('Not sure what to do with more than one roster...')

        roster0 = self.xmpp.roster[rjids[0]]
        self.xmpp.roster_dict = {jid: roster0[jid] for jid in roster0}
        roster = self.xmpp.roster_dict

        self.map_rooms_by_topic()

        # Create new rooms where none exist
        for jid, info in roster.items():
            if '@' not in jid:
                logging.warning('Skipping fake jid in roster: ' + jid)
                continue
            if jid in self.disabled_jids:
                continue
            name = info['name']
            self.xmpp.jid_nick_map[jid] = name
            self.create_mapped_room(topic=jid, name=name)

        logging.debug('Sending invitations..')
        # Invite to all rooms
        for room in self.matrix.get_rooms().values():
            users_in_room = room.get_joined_members()
            for user_id in self.users_to_invite:
                if user_id not in users_in_room:
                    room.invite_user(user_id)

        logging.debug('######## Done with roster update #######')
Example #2
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
Example #3
0
def start(stdscr):
    global server, base_url, username, access_token, password
    global size, room, rooms, all_rooms, lastEventRoom, room_keys

    curses.curs_set(0)
    curses.use_default_colors()
    size = stdscr.getmaxyx()

    stdscr.addstr(0, 0, "loading...")
    stdscr.refresh()
    loadCredentials("./credentials.json")

    client = MatrixClient(base_url,
                          token=access_token,
                          user_id='@{}:{}'.format(username, server))
    if access_token is None:
        access_token = client.login_with_password(username, password, size[0])

    rooms = client.get_rooms()

    all_rooms = "all rooms"
    rooms[all_rooms] = all_rooms

    room_keys = list(rooms.keys())
    room = all_rooms

    curses.halfdelay(10)
    maxDisplayName = 24
    displayNamestartingPos = 20
    PAD_COMMENTS = True
    pause = False

    client.add_listener(processMessage)
    client.start_listener_thread()

    curses.echo()
    stdscr.keypad(True)
    inputBuffer = ""
    lastEventRoom = all_rooms
    the_room_to_post_to = None  # store the last room we saw before we started typing

    while (True):
        size = stdscr.getmaxyx()
        maxChars = size[1] - 1 - len(username) - 3

        stdscr.clear()

        line = "redpill v0.7"
        line += " · screen size: " + str(size)
        if isinstance(rooms[room], Room):
            line += " · chat size: " + str(len(rooms[room].events))
        line += " · room: "

        # we want NAME aka ALIAS[0] (ROOM_ID)
        # or 2nd choice: ALIAS[0] (ROOM_ID)
        # or fallback: ROOM_ID

        if room == all_rooms:
            line += str(room)
        elif rooms[room].name:
            if len(rooms[room].aliases) > 0 and rooms[room].aliases[0] != room:
                line += rooms[room].name + " aka " + getFirstRoomAlias(
                    rooms[room]) + " (" + str(room) + ")"
            elif rooms[room].name != room:
                line += rooms[room].name + " (" + str(room) + ")"
            else:
                line += str(room)
        elif len(rooms[room].aliases) > 0 and rooms[room].aliases[0] != room:
            line += rooms[room].aliases[0] + " (" + str(line) + ")"
        else:
            line += str(room)

        if isinstance(rooms[room], Room) and rooms[room].topic is not None:
            line += " · topic: " + rooms[room].topic

        line += " · variables: room: " + room + ", last: " + lastEventRoom

        stdscr.addstr(0, 0, line, curses.A_UNDERLINE)

        current = len(rooms[room].events) - 1 if isinstance(rooms[room],
                                                            Room) else -1

        if True:
            y = 1
            if current >= 0:

                # TODO: something when the first event is a typing event
                currentLine = size[0] - 1

                # input
                space = ""
                for i in range(size[1] - 1):
                    space += " "
                stdscr.addstr(currentLine, 0, space, curses.A_DIM)
                stdscr.addstr(currentLine, 0, "<" + username + ">",
                              curses.A_DIM)
                stdscr.addstr(currentLine - 1, 0, space, curses.A_UNDERLINE)

                for event in reversed(rooms[room].events):
                    log(event)
                    log("maxDisplayName: {}, size: {}, currentLine: {}".format(
                        maxDisplayName, size, currentLine))

                    if event["type"] == "m.typing":
                        #if True:
                        continue  # do something clever
                    elif event["type"] == "m.presence":
                        #if True:
                        continue  # do something clever

                    elif event["type"] == "m.roomchange":
                        room_id = event["room_id"]
                        #lin = (str(rooms[room_id].name) + " aka " + getFirstRoomAlias(rooms[room_id]) + " (" +
                        #    rooms[room_id].room_id + ")")
                        line = room_id
                        if line == all_rooms:
                            pass
                        elif rooms[line].name is None:
                            if len(
                                    rooms[room_id].aliases
                            ) > 0 and rooms[room_id].aliases[0] != room_id:
                                line = rooms[room_id].aliases[
                                    0] + " (" + line + ")"
                        else:
                            if len(
                                    rooms[room_id].aliases
                            ) > 0 and rooms[room_id].aliases[0] != room_id:
                                line = rooms[
                                    room_id].name + " aka " + getFirstRoomAlias(
                                        rooms[room_id]) + " (" + line + ")"
                            else:
                                if rooms[room_id].name != room_id:
                                    line = rooms[
                                        room_id].name + " (" + line + ")"

                        #if rooms[room].topic is not None:
                        #    line += " · topic: " + rooms[room].topic

                        currentLine -= 1
                        stdscr.addstr(currentLine, 0, "Event(s) from " + line,
                                      curses.A_DIM)

                    else:
                        #currentLine = size[0] - y
                        #currentLine -= 1

                        if currentLine < 3:  # how many lines we want to reserve
                            break
                        #if currentLine == 5:
                        #    currentLine -= 1
                        y += 1
                        if "origin_server_ts" in event:
                            convertedDate = datetime.datetime.fromtimestamp(
                                int(event["origin_server_ts"] /
                                    1000)).strftime('%Y-%m-%d %H:%M:%S')

                        # assumption: body == normal message
                        length = len(
                            event["sender"]) if "sender" in event else 0
                        log("length: {}, currentLine: {}".format(
                            length, currentLine))

                        if "body" in event["content"]:

                            rawText = event["content"][
                                "body"]  # .encode('utf-8')

                            if event["content"]["msgtype"] == "m.emote":
                                if len(rawText) > 0 and rawText[0] == " ":
                                    rawText = rawText[1:]

                            linesNeeded = 0

                            buf = ""
                            lineByLineText = []
                            first = True
                            bufSinceLastWord = ""
                            for char in rawText:
                                if True:  #for char in line:

                                    bufSinceLastWord += char

                                    if char == '\n':
                                        linesNeeded += 1
                                        buf += bufSinceLastWord

                                        if PAD_COMMENTS or first:
                                            linesNeeded += int(
                                                (displayNamestartingPos +
                                                 maxDisplayName + 3 + len(buf))
                                                / size[1])
                                        else:
                                            linesNeeded += int(
                                                len(buf) / size[1])

                                        first = False
                                        lineByLineText.append(buf)
                                        buf = ""
                                        bufSinceLastWord = ""
                                    else:
                                        if ((PAD_COMMENTS and
                                             (displayNamestartingPos +
                                              maxDisplayName + 3 +
                                              len(buf + bufSinceLastWord))
                                             == size[1] - 1) or
                                            (not PAD_COMMENTS and
                                             (len(buf + bufSinceLastWord))
                                             == size[1] - 1)):

                                            #if (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1:
                                            if len(buf) == 0:
                                                buf += bufSinceLastWord
                                                bufSinceLastWord = ""

                                            if char.isspace():
                                                buf += bufSinceLastWord
                                                lineByLineText.append(buf)
                                                bufSinceLastWord = ""
                                                buf = ""
                                            else:
                                                lineByLineText.append(buf)
                                                buf = bufSinceLastWord
                                                bufSinceLastWord = ""
                                            linesNeeded += 1

                                    if char.isspace():
                                        buf += bufSinceLastWord
                                        bufSinceLastWord = ""


#                                if (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1:
                                if ((PAD_COMMENTS and
                                     (displayNamestartingPos + maxDisplayName +
                                      3 + len(buf + bufSinceLastWord))
                                     == size[1] - 1)
                                        or (not PAD_COMMENTS and
                                            (len(buf + bufSinceLastWord))
                                            == size[1] - 1)):

                                    buf += bufSinceLastWord
                                    bufSinceLastWord = ""
                                    lineByLineText.append(buf)
                                    linesNeeded += 1
                                    buf = ""
                                    #elif char == ' ':   # skip all whitespace
                                    #    self.X += 1
                            buf += bufSinceLastWord
                            lineByLineText.append(buf)
                            linesNeeded += int(
                                (displayNamestartingPos + maxDisplayName + 3 +
                                 len(buf)) / size[1])
                            buf = ""

                            if currentLine - linesNeeded < 2:  # how many lines we want to reserve
                                break

                            if PAD_COMMENTS:
                                pad = displayNamestartingPos + maxDisplayName + 3

                                linesNeeded += 1
                                currentLine -= linesNeeded

                                for i in range(linesNeeded):
                                    buf = rawText[:size[1] - pad]
                                    rawText = rawText[size[1] - pad:]

                                    if currentLine + i == size[0] - 2:
                                        stdscr.addstr(
                                            currentLine + i,
                                            displayNamestartingPos +
                                            maxDisplayName + 3,
                                            lineByLineText[i],
                                            curses.A_BOLD + curses.A_UNDERLINE)
                                    else:
                                        try:
                                            stdscr.addstr(
                                                currentLine + i,
                                                displayNamestartingPos +
                                                maxDisplayName + 3,
                                                lineByLineText[i],
                                                curses.A_BOLD)
                                        except:
                                            e = sys.exc_info()[0]
                                            print(
                                                "Error: unable to start thread. "
                                                + str(e))
                                            stdscr.addstr(1, 0, str(e))

                            else:
                                # TODO: need to split this out to get proper underline

                                currentLine -= linesNeeded
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        maxDisplayName + 3, rawText,
                                        curses.A_BOLD + curses.A_UNDERLINE)
                                else:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        maxDisplayName + 3, rawText,
                                        curses.A_BOLD)

                            usern = event["sender"]

                            if length > maxDisplayName:
                                usern = usern[:maxDisplayName - 3] + "..."

                            if event["content"]["msgtype"] == "m.emote":
                                usern = "* " + usern
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        max(0, maxDisplayName - length),
                                        str(usern),
                                        curses.A_UNDERLINE + curses.A_BOLD)
                                else:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        max(0, maxDisplayName - length),
                                        str(usern), curses.A_BOLD)
                            else:
                                usern = "<" + usern + ">"
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        max(0, maxDisplayName - length),
                                        str(usern), curses.A_UNDERLINE)
                                else:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        max(0, maxDisplayName - length),
                                        str(usern))

                            if currentLine == size[0] - 2:
                                stdscr.addstr(currentLine, 0, convertedDate,
                                              curses.A_UNDERLINE)
                            else:
                                stdscr.addstr(currentLine, 0, convertedDate)

                            #if currentLine == size[1]:  # last line
                            #    stdscr.addstr(
                            #        currentLine, displayNamestartingPos +
                            #        maxDisplayName + 3, buf[:size[1] -
                            #        (displayNamestartingPos + maxDisplayName + 4)],
                            #         curses.A_BOLD
                            #    )
                            #else:
                            #    stdscr.addstr(
                            #        currentLine, displayNamestartingPos +
                            #        maxDisplayName + 3, buf,
                            #        curses.A_BOLD
                            #    )

                        # membership == join/leave events
                        elif "membership" in event["content"]:
                            buf = " invited someone"
                            if event["content"]["membership"] == "invite":
                                if "state_key" in event:
                                    buf = " invited " + event["state_key"]
                            elif event["content"]["membership"] == "join":
                                buf = " has joined"
                            elif event["content"]["membership"] == "leave":
                                buf = " has left"

                            currentLine -= 1
                            if length > maxDisplayName:
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + 1,
                                        str(event["sender"]),
                                        curses.A_DIM + curses.A_UNDERLINE)
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + length + 1,
                                        buf, curses.A_DIM + curses.A_UNDERLINE)
                                else:
                                    stdscr.addstr(currentLine,
                                                  displayNamestartingPos + 1,
                                                  str(event["sender"]),
                                                  curses.A_DIM)
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + length + 1,
                                        buf, curses.A_DIM)

                            else:
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        1 + maxDisplayName - length,
                                        str(event["sender"]),
                                        curses.A_DIM + curses.A_UNDERLINE)
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        maxDisplayName + 1, buf,
                                        curses.A_DIM + curses.A_UNDERLINE)
                                else:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        1 + maxDisplayName - length,
                                        str(event["sender"]), curses.A_DIM)
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        maxDisplayName + 1, buf, curses.A_DIM)

                    current -= 1
        if pause:
            stdscr.addstr(
                int(size[0] / 2) - 1, int(size[1] / 2), "          ",
                curses.A_REVERSE)
            stdscr.addstr(int(size[0] / 2), int(size[1] / 2), "  PAUSED  ",
                          curses.A_REVERSE)
            stdscr.addstr(
                int(size[0] / 2) + 1, int(size[1] / 2), "          ",
                curses.A_REVERSE)
        try:
            stdscr.addstr(size[0] - 1,
                          len(username) + 3, inputBuffer[-maxChars:])
        except:
            e = sys.exc_info()[0]
            print("Error: unable to start thread. " + str(e))
            stdscr.addstr(1, 0, str(e))

        stdscr.refresh()

        #       getInput(stdscr)

        #def getInput(stdscr):
        #   if True:
        try:

            c = stdscr.getch(size[0] - 1, len(username) + 3)
            #c = stdscr.getkey(size[0] - 1, len(username) + 3)

            #stri = stdscr.getstr(size[0] - 1, len(username) + 3, 10)
            if c == -1:
                stdscr.addstr(1, 0, "timeout")
            else:
                if c <= 256 and c != 10 and c != 9:  ## enter and tab
                    inputBuffer += chr(c)
                if len(inputBuffer) == 1:  # e.g. just started typing
                    if lastEventRoom != all_rooms:
                        the_room_to_post_to = lastEventRoom

            if c == 9:
                #stdscr.addstr(1, 0, "%s was pressed\n" % c)
                log("key pressed: 0x{:X}".format(c))
                room = room_keys[(room_keys.index(room) + 1) % len(rooms)]
                the_room_to_post_to = None
            elif c == 10:  # enter
                with open('redpill-sends.log', 'a') as the_file:
                    the_file.write("the_room_to_post_to:" +
                                   str(the_room_to_post_to) + "\n")
                    the_file.write("lastEventRoom: " + str(lastEventRoom) +
                                   "\n")
                    the_file.write("room: " + str(room) + "\n")
                    the_file.write("inputBuffer: " + str(inputBuffer) + "\n")
                    the_file.write("---\n")

                if inputBuffer.startswith("/invite"):
                    user_id = inputBuffer[7:].strip()
                    rooms[room].invite_user(user_id)
                elif inputBuffer.startswith("/kick"):
                    user_id = inputBuffer[5:].strip()
                    reason = "no reason..."
                    rooms[room].kick_user(user_id, reason)
                elif inputBuffer.startswith("/power"):
                    user_id = inputBuffer[7:].strip()
                    power_level = 50
                    rooms[room].set_power_level(user_id, power_level)
                elif inputBuffer.startswith("/op"):
                    user_id = inputBuffer[2:].strip()
                    rooms[room].set_power_level(user_id)
                elif inputBuffer.startswith("/ban"):  # reason
                    user_id = inputBuffer[4:].strip()
                    reason = "sux"  #FIXME
                    rooms[room].ban(user_id, reason)
                elif inputBuffer.startswith(
                        "/join"):  # there's a /join that supports aliases
                    room_alias = inputBuffer[5:].strip()
                    client.join_room(room_alias)
                elif inputBuffer.startswith("/j"):
                    room_alias = inputBuffer[2:].strip()
                    client.join_room(room_alias)
                elif inputBuffer.startswith("/leave"):
                    rooms[room].leave_room(room_id)
                elif inputBuffer.startswith("/create"):  # create a new room
                    is_public = True
                    invitees = ()
                    #     def create_room(self, alias=None, is_public=False, invitees=()):
                    room_alias = inputBuffer[7:].strip()
                    client.create_room(room_alias, is_public, invitees)
                elif inputBuffer.startswith("/topic"):  # get or set topic
                    new_topic = inputBuffer[6:].strip()
                    if len(new_topic) > 0:
                        rooms[room].topic = new_topic
                    else:
                        pass
                        #rooms[room].topic = "fail"
                else:
                    if room == all_rooms:
                        if the_room_to_post_to is None:
                            if lastEventRoom != all_rooms:
                                the_room_to_post_to = lastEventRoom
                            else:
                                stdscr.addstr(1, 0,
                                              "No idea what room to post to!")
                                stdscr.refresh()
                                inputBuffer = "No idea what room to post to!"
                                continue
                    else:
                        the_room_to_post_to = room

                    if inputBuffer.startswith("/me"):
                        rooms[the_room_to_post_to].send_emote(inputBuffer[3:])
                    else:
                        rooms[the_room_to_post_to].send_text(inputBuffer)

                inputBuffer = ""
                the_room_to_post_to = None
            elif c == curses.KEY_DC:
                inputBuffer = ""
                the_room_to_post_to = None
            elif c == curses.KEY_BACKSPACE:
                if len(inputBuffer) > 0:
                    inputBuffer = inputBuffer[:-1]
                if len(inputBuffer) == 0:
                    the_room_to_post_to = None
            elif c == curses.KEY_IC:
                pause = not (pause)
                if pause:
                    curses.nocbreak()
                    curses.cbreak()
                    stdscr.timeout(-1)
                    stdscr.addstr(
                        int(size[0] / 2) - 1, int(size[1] / 2), "          ",
                        curses.A_REVERSE)
                    stdscr.addstr(int(size[0] / 2), int(size[1] / 2),
                                  " PAUSING  ", curses.A_REVERSE)
                    stdscr.addstr(
                        int(size[0] / 2) + 1, int(size[1] / 2), "          ",
                        curses.A_REVERSE)
                    stdscr.refresh()
                else:
                    stdscr.addstr(
                        int(size[0] / 2) - 1, int(size[1] / 2), "          ",
                        curses.A_REVERSE)
                    stdscr.addstr(int(size[0] / 2), int(size[1] / 2),
                                  " RESUMING ", curses.A_REVERSE)
                    stdscr.addstr(
                        int(size[0] / 2) + 1, int(size[1] / 2), "          ",
                        curses.A_REVERSE)
                    stdscr.refresh()
                    curses.halfdelay(10)
                    stdscr.timeout(1)
            elif c == 27:  # need to test for alt combo or ESC
                curses.cbreak()
                curses.echo()
                #curses.curs_set(1)
                stdscr.keypad(0)
                curses.endwin()
                quit()
            elif c == curses.KEY_F2:
                PAD_COMMENTS = not PAD_COMMENTS

            #stdscr.addstr(2, 0, "time() == %s\n" % time.time())

        finally:
            do_nothing = True
Example #4
0
class MatrixEngine(object):
    chatapi = ''
    chatclient = ''
    chattoken = ''
    listener_thread_id = 0
    new_msg_queue = []
    new_msg_senders = []

    def __init__(self, host=bothost, user=botuser, pwd=botpwd, room=botroom):
        print("Logging to matrix server...")
        self.chatclient = MatrixClient(host)
        try:
            self.chattoken = self.chatclient.login(username=user,
                                                   password=pwd,
                                                   sync=True)
        except MatrixRequestError as e:
            print(e)
            if e.code == 403:
                print("Bad username or password.")
                sys.exit(4)
            else:
                print("Check your sever details are correct.")
                sys.exit(2)
        except MissingSchema as e:
            print("Bad URL format.")
            print(e)
            sys.exit(3)
        self.chatapi = MatrixHttpApi(host, self.chattoken)
        print("Login OK")

        ### handle messages
        print("Setting up listener")
        try:
            room = self.chatclient.join_room(room)
        except MatrixRequestError as e:
            print(e)
            if e.code == 400:
                print("Room ID/Alias in the wrong format")
                sys.exit(11)
            else:
                print("Couldn't find room.")
                sys.exit(12)

        room.add_listener(self.on_message)
        self.listener_thread_id = self.chatclient.start_listener_thread()
        print('Listener set.. OK!')

    def sendmsg(self, msg, chatid=botroom):
        room = self.chatclient.join_room(chatid)
        response = self.chatapi.send_message(chatid, msg)

    def sendhtml(self, msg, chatid=botroom):
        room = self.chatclient.join_room(chatid)
        room.send_html(msg)

    def create_room(self, alias):
        ex_room = matrix_mongo.matrix_chats.find_one({"alias": alias})
        if ex_room:
            room_id = ex_room['room_id']
            room = self.chatclient.join_room(room_id)
            room.invite_user(ebuser)
        else:
            try:
                aldt = datetime.datetime.strftime(datetime.datetime.now(),
                                                  '%Y%m%d%H%M%S')
                new_room = self.chatclient.create_room(alias=alias + "_" +
                                                       aldt,
                                                       is_public=False,
                                                       invitees=[ebuser])
                room_id = new_room.room_id
                dtime = datetime.datetime.strftime(datetime.datetime.now(),
                                                   '%Y-%m-%d %H:%M:%S')
                chatdata = {
                    "created_date": dtime,
                    "alias": alias,
                    "room_alias": alias + "_" + aldt,
                    "room_id": room_id,
                    "room_data": room
                }

                recordID = matrix_mongo.matrix_chats.insert_one(chatdata)
            except MatrixRequestError as e:
                print(str(e))

        return room_id

    def sendfile(self, filename, chatid=botroom):
        room = self.chatclient.join_room(chatid)

        with open(filename, 'rb') as datafile:
            data = datafile.read()
            uri = self.chatclient.upload(content=data,
                                         content_type='image/jpg')
            print(uri)
            filedate = datetime.datetime.strftime(datetime.datetime.now(),
                                                  '%Y-%m-%d %H:%M:%S')
            room.send_image(
                uri, filedate + '_' + filename.replace('/', '_') + '.jpg')

    def on_message(self, room, event):
        try:
            # Called when a message is recieved.
            if event['type'] == "m.room.member":
                if event['membership'] == "join":
                    # print("{0} joined".format(event['content']['displayname']))
                    dtime = datetime.datetime.strftime(datetime.datetime.now(),
                                                       '%Y-%m-%d %H:%M:%S')
                    print(dtime + " new user joined to the room ")
            elif event['type'] == "m.room.message":
                if event['content']['msgtype'] == "m.text":
                    # print("{0}: {1}".format(event['sender'], event['content']['body']))
                    dtime = datetime.datetime.strftime(datetime.datetime.now(),
                                                       '%Y-%m-%d %H:%M:%S')
                    print(dtime + "|  '" + event['content']['body'] +
                          "' msg received from " + event['sender'])
                    if event['sender'] != "@mybot:matrix.org":
                        self.new_msg_senders.append(event['sender'])
                        self.new_msg_queue.append(event['content']['body'])
            else:
                print('new event in room:' + event['type'])
        except Exception as e:
            print('error @ on_message: ' + str(e))

    def parsemsg(self):
        for i in range(len(self.new_msg_queue)):
            origMsg = self.new_msg_queue[i]
            sender = self.new_msg_senders[i]

            msg = origMsg.lower()

            print("PARSER:  '" + origMsg + "' msg received from " + sender)

            def doshit():
                print('shit done')

            if msg == "test":
                doshit()
            elif msg == "msg":
                sendmsg("your msg responded!")
            elif msg == "html":
                sendhtml("your <b>html</b>  message <h1>responded</h1>!")
            else:
                print("message not understood")

            self.new_msg_queue.remove(origMsg)
            self.new_msg_senders.remove(sender)
Example #5
0
class Client:
    """
    A Matrix client implementation.

    :param server_url: The Matrix server URL
    :type server_url: str
    :param UI: The user interface object
    :type UI: :class:`.ui.base.BaseUI`
    """
    def __init__(self, server_url, UI):
        assert server_url, "Missing server URL"

        self.room = None

        self.client = MatrixClient(server_url)

        self.op_executor = OPExecutor(self._server_exception_handler)

        self.room_event_observer = RoomEventObserver(self)

        self.users = Users(self.client.api)

        commands = {}
        for name in dir(self):
            attr = getattr(self, name)
            if isinstance(attr, command.Command):
                commands[attr.cmd_type] = attr
        self.ui = UI(self.send_message, self.users, commands)

        self.users.set_modified_callback(self.ui.refresh_user_list)

    @property
    def connected(self):
        return self.client and self.client.should_listen

    def register(self, username, password):
        """
        Register a new user on the server.

        :param username: The username to register
        :type username: str
        :param password: The password to register
        :type password: str
        :raises RegistrationException:
        """
        assert username, "Missing username"
        assert password, "Missing password"

        LOG.info("Registering user {}".format(username))

        try:
            self.client.register_with_password(username=username,
                                               password=password)
        except MatrixRequestError as exc:
            LOG.exception(exc)

            try:
                content = json.loads(exc.content)
            except json.decoder.JSONDecodeError as json_exc:
                raise exceptions.RegistrationException(str(json_exc))

            try:
                if content["errcode"] in ("M_USER_IN_USE", "M_EXCLUSIVE"):
                    raise exceptions.UsernameTaken(username)

                if content["errcode"] == "M_INVALID_USERNAME":
                    raise exceptions.RegistrationException(content["error"])

                if content["errcode"] == "M_UNKNOWN":
                    if content["error"] == "Captcha is required.":
                        raise exceptions.CaptchaRequired()
            except KeyError:
                pass

            raise exceptions.RegistrationUnknownError(exc)

    def login(self, username, password):
        """
        Login to the server.

        If the login fails we try to register a new user using the same
        username and password.

        :param username: The username to login with
        :type username: str
        :param password: The password to login with
        :type password: str
        :raises LoginException:
        """
        assert username, "Missing username"
        assert password, "Missing password"

        LOG.info("Login with username {}".format(username))

        try:
            self.client.login_with_password(username=username,
                                            password=password)
        except MatrixRequestError as exc:
            LOG.exception(exc)

            try:
                content = json.loads(exc.content)
            except json.decoder.JSONDecodeError as json_exc:
                raise exceptions.LoginException(str(json_exc))

            try:
                if content["errcode"] == "M_FORBIDDEN":
                    raise exceptions.LoginFailed()
            except KeyError:
                pass

            raise exceptions.LoginUnknownError(exc)

    def create_room(self, room_alias):
        """
        Create a new room on the server.

        :param room_alias: The alias of the room to create
        :type room_alias: str
        """
        assert room_alias, "Missing room"

        LOG.info("Creating room {}".format(room_alias))
        """ #room:host -> room """
        room_alias_name = room_alias[1:].split(':')[0]

        self.room = self.client.create_room(room_alias_name)

    def join(self, room_alias):
        """
        Join a room.

        If the room does not already exist on the server we try to
        automatically create it.

        :param room_alias: The alias of the room to join
        :type room_alias: str
        :raises JoinRoomException:
        """
        assert room_alias, "Missing room"

        LOG.info("Joining room {}".format(room_alias))

        try:
            self.room = self.client.join_room(room_alias)
        except MatrixRequestError as exc:
            LOG.exception(exc)

            try:
                content = json.loads(exc.content)
            except json.decoder.JSONDecodeError as json_exc:
                exceptions.JoinRoomException(json_exc)

            try:
                if content["errcode"] == "M_NOT_FOUND":
                    raise exceptions.RoomNotFound()

                if content["errcode"] in ("M_FORBIDDEN", "M_UNKNOWN"):
                    raise exceptions.JoinRoomException(content["error"])
            except (KeyError, AttributeError):
                pass

            raise exceptions.JoinRoomUnknownError(exc)

    def run(self):
        """
        Run the client.
        """
        assert self.room, "You need to join a room before you run the client"

        self.room.add_listener(self.room_event_observer.on_room_event)

        self.op_executor.start()

        self.connect()

        self.ui.run()

    def stop(self):
        """
        Stop the client.
        """
        self.ui.stop()

        self.op_executor.stop()

        if self.connected:
            print("Waiting for server connection to close")
            print("Press ctrl+c to force stop")
            try:
                self.disconnect()
            except KeyboardInterrupt:
                pass

    def _server_exception_handler(self, exc):
        """
        Exception handler for Matrix server errors.
        """
        LOG.exception(exc)

        if isinstance(exc, ConnectionError):
            self.ui.draw_client_info("Server connection error")
        else:
            self.ui.draw_client_info("Unexpected server error: {}".format(exc))
            if not settings.debug:
                self.ui.draw_client_info(
                    "For more details enable debug mode, "
                    "reproduce the issue and check the logs. "
                    "Debug mode is enabled by setting the "
                    "MATRIX_DEBUG environment variable")

        self.disconnect()

    def _populate_room(self):
        # Clear the users model from old user data. To avoid duplicates when
        # for example reconnecting
        self.users.clear()

        # Temporarily disable UI user list refresh callback when doing the
        # initial population of the users model. Especially important for large
        # rooms which would cause an excessive amount of re-draws.
        modified_callback = self.users.modified_callback
        self.users.set_modified_callback(lambda: None)

        users = self.room.get_joined_members().items()
        for user_id, user in users:
            self.users.add_user(user_id, nick=user["displayname"])

        # Restore refresh callback
        self.users.set_modified_callback(modified_callback)

        # Execute an initial refresh
        modified_callback()

    @command.cmd(command.CONNECT, help_msg="Reconnect to the server")
    @op(require_connection=False)
    def connect(self):
        """
        Connect to the server.

        Before the client starts syncing events with the server it retrieves
        the users currently in the room and backfills previous messages. The
        number of previous messages backfilled are decidede by
        :const:`HISTORY_LIMIT`.
        """
        if self.connected:
            self.ui.draw_client_info("Already connected")
            return

        # Retrieve users currently in the room
        self._populate_room()

        # Get message history
        self.room.backfill_previous_messages(limit=HISTORY_LIMIT)

        self.client.start_listener_thread(
            timeout_ms=SERVER_TIMEOUT_MS,
            exception_handler=self._server_exception_handler)

        self.ui.draw_client_info("Connected to server")

    def disconnect(self):
        """
        Disconnect from the server.

        This also causes the user to logout when the sync thread has closed.
        """
        if self.connected:
            # Can't unfortunately cancel an in-flight request
            # https://github.com/kennethreitz/requests/issues/1353
            self.client.should_listen = False

            self.ui.draw_client_info("Disconnected")

            # Wait here for the matrix client sync thread to exit before
            # joining so that we can interrupt using signals.
            while self.client.sync_thread.isAlive():
                time.sleep(0.1)

        try:
            self.client.api.logout()
        except ConnectionError:
            pass

    @op
    def send_message(self, msg):
        """
        Send a message to the room.

        :param msg: Message to send
        :type msg: str
        """
        self.room.send_text(msg)

    @command.cmd(command.INVITE,
                 help_msg=("Invite a user to the room "
                           "(user_id syntax: @[mxid]:[server])"))
    @op
    def invite(self, user_id):
        """
        Invite a user to the room.

        :param user_id: The MXID of the user you want to invite
        :type user_id: str
        """
        try:
            self.client.api.invite_user(self.room.room_id, user_id)
            # self.room.invite_user(args[0])
        except MatrixRequestError as exc:
            try:
                error_msg = json.loads(exc.content)["error"]
            except:
                error_msg = str(exc)
            self.ui.draw_client_info("Invite error: {}".format(error_msg))

    @command.cmd(command.CHANGE_NICK, help_msg="Change nick")
    @op
    def change_nick(self, nick):
        """
        Change your nick.

        :param nick: The displayname you want to change to.
        :type nick: str
        """
        self.users.get_user(self.client.user_id).set_display_name(nick)

    @command.cmd(command.LEAVE, help_msg="Leave the room")
    def leave(self):
        """
        Leave the room.
        """
        # Stop listening for new events, when we leave the room we're forbidden
        # from interacting with the it anything anyway
        self.client.should_listen = False

        self.room.leave()

        self.ui.stop()

    @command.cmd(command.QUIT, help_msg="Exit the client")
    def quit(self):
        """
        Exit the client.
        """
        self.ui.stop()

    @command.cmd(command.HELP, help_msg="Show this")
    def show_help(self, cmd_type=None):
        """
        Show a help message explaining all the available commands.

        :param cmd_type: Use cmd_type to display the help message of a specific
                         command rather than all of them.
        :type cmd_type: int
        """
        self.ui.draw_help(cmd_type)
Example #6
0
def start(stdscr):
    global size, room, data, rooms, access_token, endTime, rooms, all_rooms, lastEventRoom, room_keys

    curses.curs_set(0)
    curses.use_default_colors()
    size = stdscr.getmaxyx()

    stdscr.addstr(0, 0, "loading...")
    stdscr.refresh()
    loadCredentials("./credentials.json")

    client = MatrixClient(server)
    access_token = client.login_with_password(
        username,
        password,
        size[0])

    rooms = client.get_rooms()

    all_rooms = "all rooms"
    rooms[all_rooms] = Room(client, all_rooms)

    rooms[all_rooms].events = []
    room_keys = list(rooms.keys())
    room = all_rooms  #room_keys[1] # "all_rooms"
    nextRoom = 1
    endTime = client.end

    curses.halfdelay(10)
    maxDisplayName = 24
    displayNamestartingPos = 20
    PAD_COMMENTS = True
    pause = False

    client.add_listener(processMessage)
    client.start_listener_thread()

    curses.echo()
    stdscr.keypad(True)
    inputBuffer = ""
    lastEventRoom = all_rooms
    the_room_to_post_to = None  # store the last room we saw before we started typing

    while(True):
        size = stdscr.getmaxyx()
        maxChars = size[1] - 1 - len(username) - 3

        stdscr.clear()

        # we want NAME aka ALIAS[0] (ROOM_ID)
        # or 2nd choice: ALIAS[0] (ROOM_ID)
        # or fallback: ROOM_ID
        line = str(room)

        if line == all_rooms:
            pass
        elif rooms[room].name is None:
            if len(rooms[room].aliases) > 0 and rooms[room].aliases[0] != room:
                line = rooms[room].aliases[0] + " (" + line + ")"
        else:
            if len(rooms[room].aliases) > 0 and rooms[room].aliases[0] != room:
                line = rooms[room].name + " aka " + getFirstRoomAlias(rooms[room]) + " (" + line + ")"
            else:
                if rooms[room].name != room:
                    line = rooms[room].name + " (" + line + ")"

        #line.encode("utf-8")
        if rooms[room].topic is not None:
            line += " · topic: " + rooms[room].topic

        stdscr.addstr(
            0, 0, (
                "redpill v0.7 · screen size: " + str(size) + " · chat size: "
                + str(len(rooms[room].events)) + " · room: " + str(line) + " the variables: room: " + room + " last: "
                + lastEventRoom
            ), curses.A_UNDERLINE
        )

        current = len(rooms[room].events) - 1

        if True:
            y = 1
            if current >= 0:

                # TODO: something when the first event is a typing event
                currentLine = size[0] - 1

                # input
                space = ""
                for i in range(size[1] - 1):
                    space += " "
                stdscr.addstr(currentLine, 0, space, curses.A_DIM)
                stdscr.addstr(currentLine, 0, "<" + username + ">", curses.A_DIM)
                stdscr.addstr(currentLine - 1, 0, space, curses.A_UNDERLINE)

                for event in reversed(rooms[room].events):
                    if event["type"] == "m.typing":
                    #if True:
                        continue  # do something clever
                    elif event["type"] == "m.presence":
                    #if True:
                        continue  # do something clever

                    elif event["type"] == "m.roomchange":
                        room_id = event["room_id"]
                        #lin = (str(rooms[room_id].name) + " aka " + getFirstRoomAlias(rooms[room_id]) + " (" +
                        #    rooms[room_id].room_id + ")")
                        line = room_id
                        if line == all_rooms:
                            pass
                        elif rooms[line].name is None:
                            if len(rooms[room_id].aliases) > 0 and rooms[room_id].aliases[0] != room_id:
                                line = rooms[room_id].aliases[0] + " (" + line + ")"
                        else:
                            if len(rooms[room_id].aliases) > 0 and rooms[room_id].aliases[0] != room_id:
                                line = rooms[room_id].name + " aka " + getFirstRoomAlias(rooms[room_id]) + " (" + line + ")"
                            else:
                                if rooms[room_id].name != room_id:
                                    line = rooms[room_id].name + " (" + line + ")"

                        #if rooms[room].topic is not None:
                        #    line += " · topic: " + rooms[room].topic

                        currentLine -= 1
                        stdscr.addstr(currentLine, 0, "Event(s) from " + line, curses.A_DIM)


                    else:
                        #currentLine = size[0] - y
                        currentLine -= 1

                        if currentLine < 3:  # how many lines we want to reserve
                            break
                        #if currentLine == 5:
                        #    currentLine -= 1
                        y += 1
                        if "origin_server_ts" in event:
                            convertedDate = datetime.datetime.fromtimestamp(
                                int(
                                    event["origin_server_ts"] / 1000)
                                ).strftime('%Y-%m-%d %H:%M:%S')

                        # assumption: body == normal message
                        length = 0
                        if "user_id" in event:
                            length = len(
                                event["user_id"]
                            )
                        if "body" in event["content"]:

                            rawText = event["content"]["body"].encode('utf-8')

                            if event["content"]["msgtype"] == "m.emote":
                                if len(rawText) > 0 and rawText[0] == " ":
                                    rawText = rawText[1:]

                            linesNeeded = (displayNamestartingPos + maxDisplayName + 3 + len(rawText)) / size[1]
                            lin = (displayNamestartingPos + maxDisplayName + 3 + len(rawText))

                            #if currentLine == size[0] - 2:
                            #    stdscr.addstr(currentLine, 0, str(lin) + " " + str(size[1]) + " " + str(linesNeeded) + "  ", curses.A_UNDERLINE)
                            #else:
                            #    stdscr.addstr(currentLine, 0, str(lin) + " " + str(size[1]) + " " + str(linesNeeded) + "  ")



                            linesNeeded = 0

                            buf = ""
                            lineByLineText = []
                            first = True
                            bufSinceLastWord = ""
                            for char in rawText:
                                if True: #for char in line:

                                    bufSinceLastWord += char

                                    if char == '\n':
                                        linesNeeded += 1
                                        buf += bufSinceLastWord

                                        if PAD_COMMENTS or first:
                                            linesNeeded += (displayNamestartingPos + maxDisplayName + 3 + len(buf)) / size[1]
                                        else:
                                            linesNeeded += len(buf) / size[1]

                                        first = False
                                        lineByLineText.append(buf)
                                        buf = ""
                                        bufSinceLastWord = ""
                                    else:
                                        if ((PAD_COMMENTS and (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1)
                                            or (not PAD_COMMENTS and (len(buf + bufSinceLastWord)) == size[1] - 1)):

                                        #if (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1:
                                            if len(buf) == 0:
                                                buf += bufSinceLastWord
                                                bufSinceLastWord = ""

                                            if char.isspace():
                                                buf += bufSinceLastWord
                                                lineByLineText.append(buf)
                                                bufSinceLastWord = ""
                                                buf = ""
                                            else:
                                                lineByLineText.append(buf)
                                                buf = bufSinceLastWord
                                                bufSinceLastWord = ""
                                            linesNeeded += 1

                                    if char.isspace():
                                        buf += bufSinceLastWord
                                        bufSinceLastWord = ""

#                                if (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1:
                                if ((PAD_COMMENTS and (displayNamestartingPos + maxDisplayName + 3 + len(buf + bufSinceLastWord)) == size[1] - 1)
                                   or (not PAD_COMMENTS and (len(buf + bufSinceLastWord)) == size[1] - 1)):

                                    buf += bufSinceLastWord
                                    bufSinceLastWord = ""
                                    lineByLineText.append(buf)
                                    linesNeeded += 1
                                    buf = ""
                                    #elif char == ' ':   # skip all whitespace
                                    #    self.X += 1
                            buf += bufSinceLastWord
                            lineByLineText.append(buf)
                            linesNeeded += (displayNamestartingPos + maxDisplayName + 3 + len(buf)) / size[1]
                            buf = ""

                            currentLine -= linesNeeded
                            if currentLine - linesNeeded < 2:  # how many lines we want to reserve
                                break

                            if currentLine == size[0] - 2:
                                stdscr.addstr(currentLine, 0, str(lin) + " " + str(size[1]) + " " + str(linesNeeded) + "  ", curses.A_UNDERLINE)
                            else:
                                stdscr.addstr(currentLine, 0, str(lin) + " " + str(size[1]) + " " + str(linesNeeded) + "  ")

                            #for i in range(linesNeeded):


                            if PAD_COMMENTS:
                                pad = displayNamestartingPos + maxDisplayName + 3


                                #if linesNeeded == 0:
                                linesNeeded += 1

                                for i in range(linesNeeded):
                                    buf = rawText[:size[1] - pad]
                                    rawText = rawText[size[1] - pad:]


                                    if currentLine + i == size[0] - 2:
                                        stdscr.addstr(
                                            currentLine + i, displayNamestartingPos +
                                            maxDisplayName + 3, lineByLineText[i],
                                            curses.A_BOLD + curses.A_UNDERLINE
                                        )
                                    else:
                                        try:
                                            stdscr.addstr(
                                                currentLine + i, displayNamestartingPos +
                                                maxDisplayName + 3, lineByLineText[i],
                                                curses.A_BOLD
                                            )
                                        except:
                                            e = sys.exc_info()[0]
                                            print("Error: unable to start thread. " + str(e))
                                            stdscr.addstr(1, 0, str(e))



                            else:
                                # TODO: need to split this out to get proper underline
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        maxDisplayName + 3, rawText,
                                        curses.A_BOLD + curses.A_UNDERLINE
                                    )
                                else:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos +
                                        maxDisplayName + 3, rawText,
                                        curses.A_BOLD
                                    )

                            usern = event["user_id"]

                            if length > maxDisplayName:
                                usern = usern[:maxDisplayName - 3] + "..."

                            if event["content"]["msgtype"] == "m.emote":

                                usern = "* " + usern
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos + max(0,  maxDisplayName - length),
                                        str(usern),
                                        curses.A_UNDERLINE + curses.A_BOLD
                                    )
                                else:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos + max(0,  maxDisplayName - length),
                                        str(usern),
                                        curses.A_BOLD
                                    )
                            else:
                                usern = "<" + usern + ">"
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos + max(0,  maxDisplayName - length),
                                        str(usern),
                                        curses.A_UNDERLINE
                                    )
                                else:
                                    stdscr.addstr(
                                        currentLine, displayNamestartingPos + max(0,  maxDisplayName - length),
                                        str(usern)
                                    )

                            if currentLine == size[0] - 2:
                                stdscr.addstr(currentLine, 0, convertedDate, curses.A_UNDERLINE)
                            else:
                                stdscr.addstr(currentLine, 0, convertedDate)

                            #if currentLine == size[1]:  # last line
                            #    stdscr.addstr(
                            #        currentLine, displayNamestartingPos +
                            #        maxDisplayName + 3, buf[:size[1] -
                            #        (displayNamestartingPos + maxDisplayName + 4)],
                            #         curses.A_BOLD
                            #    )
                            #else:
                            #    stdscr.addstr(
                            #        currentLine, displayNamestartingPos +
                            #        maxDisplayName + 3, buf,
                            #        curses.A_BOLD
                            #    )

                        # membership == join/leave events
                        elif "membership" in event["content"]:
                            buf = " invited someone"
                            if event["content"]["membership"] == "invite":
                                if "state_key" in event:
                                    buf = " invited " + event["state_key"]
                            elif event["content"]["membership"] == "join":
                                buf = " has joined"
                            elif event["content"]["membership"] == "leave":
                                buf = " has left"

                            if length > maxDisplayName:
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + 1,
                                        str(event["user_id"]),
                                        curses.A_DIM + curses.A_UNDERLINE
                                    )
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + length + 1,
                                        buf,
                                        curses.A_DIM + curses.A_UNDERLINE
                                    )
                                else:
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + 1,
                                        str(event["user_id"]),
                                        curses.A_DIM
                                    )
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + length + 1,
                                        buf,
                                        curses.A_DIM
                                    )

                            else:
                                if currentLine == size[0] - 2:
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + 1 +
                                        maxDisplayName - length,
                                        str(event["user_id"]),
                                        curses.A_DIM + curses.A_UNDERLINE
                                    )
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + maxDisplayName + 1,
                                        buf,
                                        curses.A_DIM + curses.A_UNDERLINE
                                    )
                                else:
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + 1 +
                                        maxDisplayName - length,
                                        str(event["user_id"]),
                                        curses.A_DIM
                                    )
                                    stdscr.addstr(
                                        currentLine,
                                        displayNamestartingPos + maxDisplayName + 1,
                                        buf,
                                        curses.A_DIM
                                    )

                    current -= 1
        if pause:
            stdscr.addstr(
                int(size[0] / 2) - 1,
                int(size[1] / 2),
                "          ",
                curses.A_REVERSE
            )
            stdscr.addstr(
                int(size[0] / 2),
                int(size[1] / 2),
                "  PAUSED  ",
                curses.A_REVERSE
            )
            stdscr.addstr(
                int(size[0] / 2) + 1,
                int(size[1] / 2),
                "          ",
                curses.A_REVERSE
            )
        try:
            stdscr.addstr(size[0] - 1, len(username) + 3, inputBuffer[-maxChars:])
        except:
            e = sys.exc_info()[0]
            print("Error: unable to start thread. " + str(e))
            stdscr.addstr(1, 0, str(e))

        stdscr.refresh()

 #       getInput(stdscr)

#def getInput(stdscr):
 #   if True:
        try:

            c = stdscr.getch(size[0] - 1, len(username) + 3)
            #c = stdscr.getkey(size[0] - 1, len(username) + 3)

            #stri = stdscr.getstr(size[0] - 1, len(username) + 3, 10)
            if c == -1:
                stdscr.addstr(1, 0, "timeout")
            else:
                if c <= 256 and c != 10 and c != 9: ## enter and tab
                    inputBuffer += chr(c)
                if len(inputBuffer) == 1:  # e.g. just started typing
                    if lastEventRoom != all_rooms:
                        the_room_to_post_to = lastEventRoom

            if c == 9:
                #stdscr.addstr(1, 0, "%s was pressed\n" % c)
                room = room_keys[nextRoom]
                nextRoom = (nextRoom + 1) % len(rooms)
                the_room_to_post_to = None
            elif c == 10: # enter
                with open('sends.log', 'a') as the_file:
                    the_file.write("the_room_to_post_to:" + str(the_room_to_post_to) + "\n")
                    the_file.write("lastEventRoom: " + str(lastEventRoom) + "\n")
                    the_file.write("room: " + str(room) + "\n")
                    the_file.write("inputBuffer: " + str(inputBuffer) + "\n")
                    the_file.write("---\n")

                if inputBuffer.startswith("/invite"):
                    user_id = inputBuffer[7:].strip()
                    rooms[room].invite_user(user_id)
                elif inputBuffer.startswith("/kick"):
                    user_id = inputBuffer[5:].strip()
                    reason = "no reason..."
                    rooms[room].kick_user(user_id, reason)
                elif inputBuffer.startswith("/power"):
                    user_id = inputBuffer[7:].strip()
                    power_level = 50
                    rooms[room].set_power_level(user_id, power_level)
                elif inputBuffer.startswith("/op"):
                    user_id = inputBuffer[2:].strip()
                    rooms[room].set_power_level(user_id)
                elif inputBuffer.startswith("/ban"): # reason
                    user_id = inputBuffer[4:].strip()
                    reason = "sux" #FIXME
                    rooms[room].ban(user_id, reason)
                elif inputBuffer.startswith("/join"):   # there's a /join that supports aliases
                    room_alias = inputBuffer[5:].strip()
                    client.join_room(room_alias)
                elif inputBuffer.startswith("/j"):
                    room_alias = inputBuffer[2:].strip()
                    client.join_room(room_alias)
                elif inputBuffer.startswith("/leave"):
                    rooms[room].leave_room(room_id)
                elif inputBuffer.startswith("/create"): # create a new room
                    is_public = True
                    invitees = ()
                    #     def create_room(self, alias=None, is_public=False, invitees=()):
                    room_alias = inputBuffer[7:].strip()
                    client.create_room(room_alias, is_public, invitees)
                elif inputBuffer.startswith("/topic"):   # get or set topic
                    new_topic = inputBuffer[6:].strip()
                    if len(new_topic) > 0:
                        rooms[room].topic = new_topic
                    else:
                        pass
                        #rooms[room].topic = "fail"
                else:
                    if room == all_rooms:
                        if the_room_to_post_to is None:
                            if lastEventRoom != all_rooms:
                                the_room_to_post_to = lastEventRoom
                            else:
                                stdscr.addstr(1, 0, "No idea what room to post to!")
                                stdscr.refresh()
                                inputBuffer = "No idea what room to post to!"
                                continue
                    else:
                        the_room_to_post_to = room

                    if inputBuffer.startswith("/me"):
                        rooms[the_room_to_post_to].send_emote(inputBuffer[3:])
                    else:
                        rooms[the_room_to_post_to].send_text(inputBuffer)

                inputBuffer = ""
                the_room_to_post_to = None
            elif c == curses.KEY_DC:
                inputBuffer = ""
                the_room_to_post_to = None
            elif c == curses.KEY_BACKSPACE:
                if len(inputBuffer) > 0:
                    inputBuffer = inputBuffer[:-1]
                if len(inputBuffer) == 0:
                    the_room_to_post_to = None
            elif c == curses.KEY_IC:
                pause = not(pause)
                if pause:
                    curses.nocbreak()
                    curses.cbreak()
                    stdscr.timeout(-1)
                    stdscr.addstr(
                        int(size[0] / 2) - 1,
                        int(size[1] / 2),
                        "          ",
                        curses.A_REVERSE
                    )
                    stdscr.addstr(
                        int(size[0] / 2),
                        int(size[1] / 2),
                        " PAUSING  ",
                        curses.A_REVERSE
                    )
                    stdscr.addstr(
                        int(size[0] / 2) + 1,
                        int(size[1] / 2),
                        "          ",
                        curses.A_REVERSE
                    )
                    stdscr.refresh()
                else:
                    stdscr.addstr(
                        int(size[0] / 2) - 1,
                        int(size[1] / 2),
                        "          ",
                        curses.A_REVERSE
                    )
                    stdscr.addstr(
                        int(size[0] / 2),
                        int(size[1] / 2),
                        " RESUMING ",
                        curses.A_REVERSE
                    )
                    stdscr.addstr(
                        int(size[0] / 2) + 1,
                        int(size[1] / 2),
                        "          ",
                        curses.A_REVERSE
                    )
                    stdscr.refresh()
                    curses.halfdelay(10)
                    stdscr.timeout(1)
            elif c == 27:  # need to test for alt combo or ESC
                curses.cbreak()
                curses.echo()
                #curses.curs_set(1)
                stdscr.keypad(0)
                curses.endwin()
                quit()
            elif c == curses.KEY_F2:
                PAD_COMMENTS = not PAD_COMMENTS

            #stdscr.addstr(2, 0, "time() == %s\n" % time.time())

        finally:
            do_nothing = True
Example #7
0
File: core.py Project: 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)
Example #8
0
def send_post(post):
    client = MatrixClient("http://localhost:8008")
    # Existing user
    token = client.login_with_password(username="******", password="******")
    room = client.create_room("my_room_alias")
    room.send_text(f"{post.title}\n\n{post.text}\n\n{post.owner}")
Example #9
0
class Application(QApplication):
    loggedIn = Signal(str)

    messageReceived = Signal(object, str, object, float)

    roomSwitched = Signal(object)
    roomJoined = Signal(object)
    roomUpdated = Signal(str, str, str)
    roomLeft = Signal(object)
    roomInvited = Signal(object)

    def __init__(self, *args, **kwargs):
        super(Application, self).__init__(*args, **kwargs)

        # setup the application name and icon
        self.setApplicationName('Qui')
        self.iconPath = os.path.dirname(__file__)
        self.iconPath = os.path.join(self.iconPath, 'icons/icon.png')
        self.setWindowIcon(QIcon(self.iconPath))

        # setup the tray icon
        self.showNotifications = False
        self.tray = QSystemTrayIcon(QIcon(self.iconPath))
        self.tray.show()
        self.tray.activated.connect(self.showWindow)

        # setup the tray icon menu
        self.menu = QMenu()
        self.menu.addAction('Quit').triggered.connect(self.quit)
        self.tray.setContextMenu(self.menu)

        # setup signals
        self.messageReceived.connect(self.receiveMessage)

        # show the window
        self.window = None
        self.showWindow(None)

        # try loading the login info
        self.client = None
        self.settings = QSettings('Qui', 'Qui')
        self.url = self.settings.value("url")
        self.token = self.settings.value("token")
        self.user = self.settings.value("user")
        invalid = self.url is None or self.url == "" or self.token is None or self.token == "" or self.user is None or self.user == ""
        if not invalid:
            try:
                self.client = MatrixClient(base_url=self.url,
                                           token=self.token,
                                           user_id=self.user)
                self.postLogin()
            except:
                invalid = True
        # show the login form if we can't login
        if invalid:
            self.loginForm = LoginForm()
            self.loginForm.loggedIn.connect(self.login)
            self.loginForm.show()

    def showWindow(self, reason):
        if self.window is None:
            # make a new window if we don't have one
            self.window = MainWindow()
            # setup signals
            self.loggedIn.connect(self.window.login)
            self.messageReceived.connect(self.window.receiveMessage)
            self.roomLeft.connect(self.window.roomLeft)
            self.roomJoined.connect(self.window.roomJoined)
            self.roomUpdated.connect(self.window.roomUpdated)
            self.window.createRoom.connect(self.createRoom)
            self.window.leaveRoom.connect(self.leaveRoom)
            self.window.joinRoom.connect(self.joinRoom)
            # show it
            self.window.show()
        else:
            # show it if it's minimized or something
            self.window.showNormal()

    def quit(self):
        sys.exit(0)

    def login(self, client, url):
        self.client = client
        self.settings.setValue('url', url)
        self.url = url
        self.settings.setValue('token', client.token)
        self.token = client.token
        self.settings.setValue('user', client.user_id)
        self.user = client.user_id
        self.postLogin()

    def postLogin(self):
        self.loggedIn.emit(self.user)
        #self.messages.userId = self.user
        self.client.add_listener(self.eventCallback)
        self.client.add_presence_listener(self.presenceCallback)
        self.client.add_invite_listener(self.inviteCallback)
        self.client.add_leave_listener(self.leaveCallback)
        self.client.start_listener_thread()
        for room, obj in self.client.get_rooms().items():
            self.roomJoined.emit(obj)
            for event in obj.events:
                self.eventCallback(event)
        self.showNotifications = True

    def eventCallback(self, event):
        if 'type' in event and 'room_id' in event and 'content' in event and event[
                'type'] == 'm.room.message':
            room = Room(self.client, event['room_id'])
            self.messageReceived.emit(room, event['sender'], event['content'],
                                      time.time() - event['unsigned']['age'])
        if 'type' in event and 'room_id' in event and 'content' in event and event[
                'type'] == 'm.room.canonical_alias':
            self.roomUpdated.emit(event['room_id'], 'canonical_alias',
                                  event['content']['alias'])

    def presenceCallback(self, event):
        return
        print('presence: {}'.format(event))

    def inviteCallback(self, roomId, state):
        return
        print('invite: {} {}'.format(roomId, state))

    def leaveCallback(self, roomId, room):
        return
        print('leave: {} {}'.format(roomId, room))

    def receiveMessage(self, room, sender, content, timestamp):
        if self.showNotifications:
            if self.window is None or not self.window.isActiveWindow():
                self.tray.showMessage(sender, content['body'])

    def leaveRoom(self, room):
        self.client.api.leave_room(room.room_id)
        self.roomLeft.emit(room)

    def joinRoom(self, roomId):
        room = self.client.join_room(roomId)
        self.roomJoined.emit(room)

    def createRoom(self, roomId):
        room = self.client.create_room(roomId)
        self.roomJoined.emit(room)
Example #10
0
File: main.py Project: m4tl0/mxpp
class BridgeBot:
    xmpp = None                # type: ClientXMPP
    matrix = None              # type: MatrixClient
    topic_room_id_map = None   # type: Dict[str, str]
    special_rooms = None       # type: Dict[str, MatrixRoom]
    special_room_names = None  # type: Dict[str, str]
    groupchat_flag = None      # type: str
    groupchat_jids = None      # type: List[str]

    users_to_invite = None      # type: List[str]
    matrix_room_topics = None   # type: Dict[str, str]
    matrix_server = None        # type: Dict[str, str]
    matrix_login = None         # type: Dict[str, str]
    xmpp_server = None          # type: Tuple[str, int]
    xmpp_login = None           # type: Dict[str, str]
    xmpp_roster_options = None  # type: Dict[str, bool]
    xmpp_groupchat_nick = None  # type: str

    default_actions = None          # type: Dict[str, bool]
    jid_actions = None              # type: Dict[str, Dict[str, bool]]
    groupchat_mute_own_nick = True                  # type: bool
    groupchat_send_messages_to_all_chat = True      # type: bool

    inbound_xmpp = None         # type: Queue

    exception = None            # type: Exception or None


    @property
    def bot_id(self) -> str:
        return self.matrix_login['username']

    def __init__(self, config_file: str=CONFIG_FILE):
        self.groupchat_jids = []
        self.topic_room_id_map = {}
        self.special_rooms = {
                'control': None,
                'all_chat': None,
                }
        self.special_room_names = {
                'control': 'XMPP Control Room',
                'all_chat': 'XMPP All Chat',
                }
        self.xmpp_roster_options = {}
        self.inbound_xmpp = Queue()

        self.load_config(config_file)

        self.matrix = MatrixClient(**self.matrix_server)
        self.xmpp = ClientXMPP(self.inbound_xmpp,
                               **self.xmpp_login,
                               **self.xmpp_roster_options)

        self.matrix.login_with_password(**self.matrix_login)

        # Recover existing matrix rooms
        for room in list(self.matrix.get_rooms().values()):
            room.update_room_topic()
            topic = room.topic

            if topic in self.special_rooms.keys():
                logger.debug('Recovering special room: ' + topic)
                self.special_rooms[topic] = room

            elif topic is None:
                room.leave()

            elif topic.startswith(self.groupchat_flag):
                room_jid = topic[len(self.groupchat_flag):]
                self.groupchat_jids.append(room_jid)

            elif not self.jid_actions.get(topic, self.default_actions)['send_messages_to_jid_rooms']:
                logger.info('Room ' + topic + ' is not needed due to send_messages_to_jid_rooms setting, leaving!')
                room.leave()

        # Prepare matrix special rooms and their listeners
        for topic, room in self.special_rooms.items():
            if room is None:
                room = self.matrix.create_room()
            self.setup_special_room(room, topic)

        self.special_rooms['control'].add_listener(self.matrix_control_message, 'm.room.message')
        self.special_rooms['all_chat'].add_listener(self.matrix_all_chat_message, 'm.room.message')

        # Invite users to special rooms
        for room in self.special_rooms.values():
            for user_id in self.users_to_invite:
                room.invite_user(user_id)

        # Connect to XMPP and start processing XMPP events
        self.xmpp.connect(self.xmpp_server)
        self.xmpp.process(block=False)

        # Rejoin group chats
        logger.debug('Rejoining group chats')
        for room_jid in self.groupchat_jids:
            self.xmpp.plugin['xep_0045'].joinMUC(room_jid, self.xmpp_groupchat_nick)

        # Listen for Matrix events
        def exception_handler(e: Exception):
            self.exception = e

        self.matrix.start_listener_thread(exception_handler=exception_handler)

        logger.debug('Done with bot init')

    def shutdown(self):
        self.matrix.stop_listener_thread()
        self.xmpp.disconnect()

    def handle_inbound_xmpp(self):
        while self.exception is None:
            event = self.inbound_xmpp.get()

            if isinstance(event, sleekxmpp.Presence):
                handler = {
                        'available': self.xmpp_presence_available,
                        'unavailable': self.xmpp_presence_unavailable,
                }.get(event.get_type(), self.xmpp_unrecognized_event)

            elif isinstance(event, sleekxmpp.Message):
                handler = {
                        'normal': self.xmpp_message,
                        'chat': self.xmpp_message,
                        'groupchat': self.xmpp_groupchat_message,
                }.get(event.get_type(), self.xmpp_unrecognized_event)

            elif isinstance(event, sleekxmpp.Iq) and event.get_query() == 'jabber:iq:roster':
                handler = self.xmpp_roster_update

            else:
                handler = self.xmpp_unrecognized_event

            handler(event)
        raise self.exception

    def load_config(self, path: str):
        with open(path, 'r') as conf_file:
            config = yaml.safe_load(conf_file)

        self.users_to_invite = config['matrix']['users_to_invite']
        self.matrix_room_topics = config['matrix']['room_topics']
        self.groupchat_flag = config['matrix']['groupchat_flag']

        self.matrix_server = config['matrix']['server']
        self.matrix_login = config['matrix']['login']
        self.xmpp_server = (config['xmpp']['server']['host'],
                            config['xmpp']['server']['port'])
        self.xmpp_login = config['xmpp']['login']
        self.xmpp_groupchat_nick = config['xmpp']['groupchat_nick']

        self.default_actions = {k: config[k] for k in ('send_messages_to_all_chat',
                                                       'send_messages_to_jid_rooms',
                                                       'send_presences_to_control')}
        self.jid_actions = {}
        for group in config['jid_groups']:
            group_data = group.copy()
            group_jids = group_data.pop('jids')
            for jid in group['jids']:
                self.jid_actions.setdefault(jid, self.default_actions.copy())
                self.jid_actions[jid].update(group_data)

        self.groupchat_mute_own_nick = config['groupchat_mute_own_nick']
        self.groupchat_send_messages_to_all_chat = config['groupchat_send_messages_to_all_chat']

        self.xmpp_roster_options = config['xmpp']['roster_options']

    def get_room_for_topic(self, jid: str) -> MatrixRoom:
        """
        Return the room corresponding to the given XMPP JID
        :param jid: bare XMPP JID, should not include the resource
        :return: Matrix room object for chatting with that JID
        """
        room_id = self.topic_room_id_map[jid]
        return self.matrix.get_rooms()[room_id]

    def get_unmapped_rooms(self) -> List[MatrixRoom]:
        """
        Returns a list of all Matrix rooms which are not a special room (e.g., the control room) and
        do not have a corresponding entry in the topic -> room map.
        :return: List of unmapped, non-special Matrix room objects.
        """
        special_room_ids = [r.room_id for r in self.special_rooms.values()]
        valid_room_ids = [v for v in self.topic_room_id_map.values()] + special_room_ids
        unmapped_rooms = [room for room_id, room in self.matrix.get_rooms().items()
                          if room_id not in valid_room_ids]
        return unmapped_rooms

    def get_empty_rooms(self) -> List[MatrixRoom]:
        """
        Returns a list of all Matrix rooms which are occupied by only one user
        (the bot itself).
        :return: List of Matrix rooms occupied by only the bot.
        """
        empty_rooms = [room for room in self.matrix.get_rooms().values()
                       if len(room.get_joined_members()) < 2]
        return empty_rooms

    def setup_special_room(self, room, topic: str):
        """
        Sets up a Matrix room with the requested topic and adds it to the self.special_rooms map.

        If a special room with that topic already exists, it is replaced in the special_rooms
         map by the new room.
        :param room: Room to set up
        :param topic: Topic for the room
        """
        room.set_room_topic(topic)
        room.set_room_name(self.special_room_names[topic])
        self.special_rooms[topic] = room

        logger.debug('Set up special room with topic {} and id'.format(
            str(room.topic), room.room_id))

    def create_mapped_room(self, topic: str, name: str=None) -> MatrixRoom or None:
        """
        Create a new room and add it to self.topic_room_id_map.

        :param topic: Topic for the new room
        :param name: (Optional) Name for the new room
        :return: Room which was created
        """
        if topic in self.groupchat_jids:
            logger.debug('Topic {} is a groupchat without its flag, ignoring'.format(topic))
            return None
        elif topic in self.topic_room_id_map.keys():
            room_id = self.topic_room_id_map[topic]
            room = self.matrix.get_rooms()[room_id]
            logger.debug('Room with topic {} already exists!'.format(topic))
        else:
            room = self.matrix.create_room()
            room.set_room_topic(topic)
            self.topic_room_id_map[topic] = room.room_id
            logger.info('Created mapped room with topic {} and id {}'.format(topic, str(room.room_id)))
            room.add_listener(self.matrix_message, 'm.room.message')

        if room.name != name:
            if name != "":
                room.set_room_name(name)
                room.set_user_profile(displayname=name)
            else:
                room.set_room_name(topic.split('@')[0])

        return room

    def leave_mapped_room(self, topic: str) -> bool:
        """
        Leave an existing, mapped room and remove it from self.topic_room_id_map.

        :param topic: Topic for room to leave
        :retrun: True if the room was left, False if the room was not found.
        """
        if topic in self.groupchat_jids:
            logger.debug('Topic {} is a groupchat without its flag, ignoring'.format(topic))
            return False

        if topic not in self.topic_room_id_map.keys():
            err_msg = 'Room with topic {} isn\'t mapped or doesn\'t exist'.format(topic)
            logger.warning(err_msg)
            return False

        if topic.startswith(self.groupchat_flag):
            # Leave the groupchat
            room_jid = topic[len(self.groupchat_flag):]
            if room_jid in self.groupchat_jids:
                self.groupchat_jids.remove(room_jid)
            logger.info('XMPP MUC leave: {}'.format(room_jid))
            self.xmpp.plugin['xep_0045'].leaveMUC(room_jid, self.xmpp_groupchat_nick)

        room = self.get_room_for_topic(topic)
        del self.topic_room_id_map[topic]
        room.leave()
        logger.info('Left mapped room with topic {}'.format(topic))
        return True

    def map_rooms_by_topic(self):
        """
        Add unmapped rooms to self.topic_room_id_map, and listen to messages from those rooms.

        Rooms whose topics are empty or do not contain an '@' symbol are assumed to be special
         rooms, and will not be mapped.
        """
        unmapped_rooms = self.get_unmapped_rooms()

        for room in unmapped_rooms:
            room.update_room_topic()

            logger.debug('Unmapped room {} ({}) [{}]'.format(room.room_id, room.name, room.topic))

            if room.topic is None or '@' not in room.topic:
                logger.debug('Leaving it as-is (special room, topic does not contain @)')
            else:
                self.topic_room_id_map[room.topic] = room.room_id
                room.add_listener(self.matrix_message, 'm.room.message')

    def matrix_control_message(self, room: MatrixRoom, event: Dict):
        """
        Handle a message sent to the control room.

        Does nothing unless a valid command is received:
          refresh  Probes the presence of all XMPP contacts, and updates the roster.
          purge    Leaves any ((un-mapped and non-special) or empty) Matrix rooms.
          joinmuc [email protected]   Joins a muc
          leavemuc [email protected]  Leaves a muc

        :param room: Matrix room object representing the control room
        :param event: The Matrix event that was received. Assumed to be an m.room.message .
        """
        # Always ignore our own messages
        if event['sender'] == self.bot_id:
            return

        logger.debug('matrix_control_message: {}  {}'.format(room.room_id, str(event)))

        if event['content']['msgtype'] == 'm.text':
            message_body = event['content']['body']
            logger.info('Matrix received control message: ' + message_body)

            message_parts = message_body.split()
            if len(message_parts) < 1:
                logger.warning('Received empty control message, ignoring')
                return

            if message_parts[0] == 'refresh':
                for jid in self.topic_room_id_map.keys():
                    self.xmpp.send_presence(pto=jid, ptype='probe')
                self.xmpp.send_presence()
                self.xmpp.get_roster()

            elif message_parts[0] == 'purge':
                self.special_rooms['control'].send_text('Purging unused rooms')

                # Leave from unwanted rooms
                for room in self.get_unmapped_rooms() + self.get_empty_rooms():
                    logger.info('Leaving room {r.room_id} ({r.name}) [{r.topic}]'.format(r=room))

                    if room.topic in self.topic_room_id_map.keys():
                        self.leave_mapped_room(room.topic)
                    else:
                        room.leave()

            elif message_parts[0] == 'joinmuc':
                if len(message_parts) < 2:
                    logger.warning('joinmuc command didn\'t specify a room, ignoring')
                    return

                room_jid = message_parts[1]
                logger.info('XMPP MUC join: {}'.format(room_jid))
                self.create_groupchat_room(room_jid)
                self.xmpp.plugin['xep_0045'].joinMUC(room_jid, self.xmpp_groupchat_nick)

            elif message_parts[0] == 'leavemuc':
                if len(message_parts) < 2:
                    logger.warning('leavemuc command didn\'t specify a room, ignoring')
                    return

                room_jid = message_parts[1]
                room_topic = self.groupchat_flag + room_jid

                success = self.leave_mapped_room(room_topic)
                if not success:
                    msg = 'Groupchat {} isn\'t mapped or doesn\'t exist'.format(room_jid)
                else:
                    msg = 'Left groupchat {}'.format(room_jid)
                self.special_rooms['control'].send_notice(msg)

    def matrix_all_chat_message(self, room: MatrixRoom, event: Dict):
        """
        Handle a message sent to Matrix all-chat room.

        Allows manual sending of xmpp messages: "/m target_jid your message here".
        Sends a notice with the expected format if it isn't there by default.

        :param room: Matrix room object representing the all-chat room
        :param event: The Matrix event that was received. Assumed to be an m.room.message .
        """
        # Always ignore our own messages
        if event['sender'] == self.bot_id:
            return

        logger.debug('matrix_all_chat_message: {}  {}'.format(room.room_id, str(event)))

        if event['content']['msgtype'] == 'm.text':
            message_body = event['content']['body']
            message_parts = message_body.split()
            if message_parts[0] == '/m':
                jid = message_parts[1]
                payload = message_body[message_body.find(jid) + len(jid) + 1:]
                logger.info('sending manual message to '+ jid + ' : ' + payload)
                self.xmpp.send_message(mto=jid, mbody=payload, mtype='chat')
            else:
                room.send_notice('Expected message format: "/m DEST_JID your message here"')

    def matrix_message(self, room: MatrixRoom, event: Dict):
        """
        Handle a message sent to a mapped Matrix room.

        Sends the message to the xmpp handle specified by the room's topic.

        :param room: Matrix room object representing the room in which the message was received.
        :param event: The Matrix event that was received. Assumed to be an m.room.message .
        """
        if event['sender'] == self.bot_id:
            return

        if room.topic in self.special_rooms.keys():
            logger.error('matrix_message called on special channel')

        logger.debug('matrix_message: {}  {}'.format(room.room_id, event))

        if event['content']['msgtype'] == 'm.text':
            message_body = event['content']['body']

            if room.topic.startswith(self.groupchat_flag):
                jid = room.topic[len(self.groupchat_flag):]
                message_type = 'groupchat'
            else:
                jid = room.topic
                message_type = 'chat'

            logger.info('Matrix received message to {} : {}'.format(jid, message_body))
            self.xmpp.send_message(mto=jid, mbody=message_body, mtype=message_type)

            # Possible that we're in a room that wasn't mapped
            if jid not in self.xmpp.jid_nick_map:
                logger.error('Received message in matrix room with topic {},'.format(jid) +
                              'which wasn\'t in the jid_nick_map')
            name = self.xmpp.jid_nick_map.get(jid, jid)

            send_message = self.jid_actions.get(jid, self.default_actions)['send_messages_to_all_chat']
            if send_message:
                self.special_rooms['all_chat'].send_notice('To {} : {}'.format(name, message_body))

    def xmpp_message(self, message: Dict):
        """
        Handle a message received by the XMPP client.

        Sends the message to the relevant mapped Matrix room, as well as the Matrix all-chat room.

        :param message: The message that was received.
        :return:
        """
        logger.info('XMPP received {} : {}'.format(message['from'].full, message['body']))

        if message['type'] in ('normal', 'chat'):
            from_jid = message['from'].bare
            from_name = self.xmpp.jid_nick_map.get(from_jid, from_jid)

            send_message2all = self.jid_actions.get(from_jid, self.default_actions)['send_messages_to_all_chat']
            if send_message2all:
                self.special_rooms['all_chat'].send_text('From  ({})\n{}: {}'.format(from_jid, from_name, message['body']))

            send_message2room  = self.jid_actions.get(from_jid, self.default_actions)['send_messages_to_jid_rooms']
            if send_message2room:
                if from_jid not in self.xmpp.jid_nick_map.keys():
                    logger.error('xmpp_message: JID {} NOT IN ROSTER!?'.format(from_jid))
                    self.xmpp.get_roster(block=True)

                room = self.get_room_for_topic(from_jid)
                room.send_text(message['body'])

    def xmpp_groupchat_message(self, message: Dict):
        """
        Handle a groupchat message received by the XMPP client.

        Sends the message to the relevant mapped Matrix room, as well as the Matrix all-chat room.

        :param message: The message that was received.
        :return:
        """
        logger.info('XMPP MUC received {} : {}'.format(message['from'].full, message['body']))

        if message['type'] == 'groupchat':
            from_jid = message['from'].bare
            from_name = message['mucnick']

            if self.groupchat_mute_own_nick and from_name == self.xmpp_groupchat_nick:
                return

            room = self.get_room_for_topic(self.groupchat_flag + from_jid)
            room.send_text(from_name + ': ' + message['body'])

            if self.groupchat_send_messages_to_all_chat:
                self.special_rooms['all_chat'].send_text(
                    'Room {}, from {}: {}'.format(from_jid, from_name, message['body']))

    def create_groupchat_room(self, room_jid: str):
        room = self.create_mapped_room(topic=self.groupchat_flag + room_jid)
        if room_jid not in self.groupchat_jids:
            self.groupchat_jids.append(room_jid)
        for user_id in self.users_to_invite:
            room.invite_user(user_id)

    def xmpp_presence_available(self, presence: Dict):
        """
        Handle a presence of type "available".

        Sends a notice to the control channel.

        :param presence: The presence that was received.
        """
        logger.debug('XMPP received {} : (available)'.format(presence['from'].full))

        jid = presence['from'].bare
        if jid not in self.xmpp.jid_nick_map.keys():
            logger.error('xmpp_presence_available: JID {} NOT IN ROSTER!?'.format(jid))
            self.xmpp.get_roster(block=True)

        send_presence = self.jid_actions.get(jid, self.default_actions)['send_presences_to_control']
        if send_presence:
            name = self.xmpp.jid_nick_map.get(jid, jid)
            self.special_rooms['control'].send_notice('{} available ({})'.format(name, jid))

    def xmpp_presence_unavailable(self, presence):
        """
        Handle a presence of type "unavailable".

        Sends a notice to the control channel.

        :param presence: The presence that was received.
        """
        logger.debug('XMPP received {} : (unavailable)'.format(presence['from'].full))

        jid = presence['from'].bare
        if jid not in self.xmpp.jid_nick_map.keys():
            logger.error('xmpp_presence_unavailable: JID {} NOT IN ROSTER!?'.format(jid))
            self.xmpp.get_roster(block=True)

        send_presence = self.jid_actions.get(jid, self.default_actions)['send_presences_to_control']
        if send_presence:
            name = self.xmpp.jid_nick_map.get(jid, jid)
            self.special_rooms['control'].send_notice('{} unavailable ({})'.format(name, jid))

    def xmpp_roster_update(self, _event):
        """
        Handle an XMPP roster update.

        Maps all existing Matrix rooms, creates a new mapped room for each JID in the roster
        which doesn't have one yet, and invites the users specified in the config in to all the rooms.

        :param _event: The received roster update event (unused).
        """
        logger.debug('######### ROSTER UPDATE ###########')

        rjids = [jid for jid in self.xmpp.roster]
        if len(rjids) > 1:
            raise Exception('Not sure what to do with more than one roster...')

        roster0 = self.xmpp.roster[rjids[0]]
        self.xmpp.roster_dict = {jid: roster0[jid] for jid in roster0}
        roster = self.xmpp.roster_dict

        self.map_rooms_by_topic()

        # Create new rooms where none exist
        for jid, info in roster.items():
            if '@' not in jid:
                logger.warning('Skipping fake jid in roster: ' + jid)
                continue

            name = info['name']
            self.xmpp.jid_nick_map[jid] = name

            # Check if we need to create a room
            if self.jid_actions.get(jid, self.default_actions)['send_messages_to_jid_rooms']:
                self.create_mapped_room(topic=jid, name=name)

        logger.debug('Sending invitations..')
        # Invite to all rooms
        for room in self.matrix.get_rooms().values():
            users_in_room = room.get_joined_members()
            for user_id in self.users_to_invite:
                if user_id not in users_in_room:
                    room.invite_user(user_id)

        logger.debug('######## Done with roster update #######')

    def xmpp_unrecognized_event(self, event):
        logger.error('Unrecognized event: {} || {}'.format(type(event), event))