Beispiel #1
0
class TinyMatrixtBot():
    def __init__(self, hostname, username, password, displayname):
        signal.signal(signal.SIGTERM, self.on_signal)
        signal.signal(signal.SIGHUP, self.on_signal)

        self.current_path = os.path.dirname(os.path.realpath(__file__))
        self.scripts_path = os.path.join(self.current_path, "scripts")
        self.sockets_path = os.path.join(self.current_path, "sockets")

        if not os.access(self.sockets_path, os.W_OK):
            self.sockets_path = None

        os.chdir(self.scripts_path)
        self.scripts = self.load_scripts(self.scripts_path)

        self.client = MatrixClient(hostname)
        self.client.login_with_password(username=username, password=password)

        self.user = self.client.get_user(self.client.user_id)
        self.user.set_display_name(displayname)

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

        self.client.start_listener_thread()
        self.client.add_invite_listener(self.on_invite)
        self.client.add_leave_listener(self.on_leave)

        while True:
            sleep(10)

    def on_signal(self, signal, frame):
        if signal == 1:
            self.scripts = self.load_scripts(self.scripts_path)
        if signal == 15:
            sys.exit()

    def load_scripts(self, path):
        scripts = {}
        for script in os.listdir(path):
            script = os.path.join(path, script)
            if not stat.S_IXUSR & os.stat(script)[stat.ST_MODE]:
                continue
            output = subprocess.Popen(
                [script],
                env={
                    "CONFIG": "1"
                },
                stdout=subprocess.PIPE,
                universal_newlines=True).communicate()[0].strip()
            if not output:
                continue
            scripts[output] = script
            print("script {} {}".format(output, script))
        return scripts

    def on_invite(self, room_id, state):
        print("invite {}".format(room_id))
        self.join_room(room_id)

    def join_room(self, room_id):
        room = self.client.join_room(room_id)
        room.add_listener(self.on_room_event)
        print("join {}".format(room_id))
        if self.sockets_path is not None:
            thread = Thread(target=self.create_socket, args=(room, ))
            thread.daemon = True
            thread.start()

    def create_socket(self, room):
        socket_name = re.search("^\!([a-z]+):", room.room_id,
                                re.IGNORECASE).group(1)
        socket_path = os.path.join(self.sockets_path, socket_name)
        try:
            os.remove(socket_path)
        except OSError:
            pass
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.bind(socket_path)
        sock.listen(1)
        print("bind {}".format(socket_path))
        while True:
            conn, addr = sock.accept()
            recv = conn.recv(4096).decode('utf-8').strip()
            print("recv {} {}".format(socket_path, recv))
            room.send_text(recv)

    def on_leave(self, room_id, state):
        print("leave {}".format(room_id))

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

    def run_script(self, room, script, args):
        print("run {} {}".format(script, args))
        output = subprocess.Popen(
            [script, args], stdout=subprocess.PIPE,
            universal_newlines=True).communicate()[0].strip()
        for line in output.split("\n\n"):
            sleep(1)
            print(line)
            room.send_text(line)
Beispiel #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
Beispiel #3
0
class TinyMatrixtBot():
    def __init__(self, path_config):
        signal.signal(signal.SIGHUP, self.on_signal)
        signal.signal(signal.SIGINT, self.on_signal)
        signal.signal(signal.SIGTERM, self.on_signal)

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

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

        self.path_lib = self.config.get("tiny-matrix-bot",
                                        "lib",
                                        fallback=os.path.join(
                                            path_current, "scripts")).strip()
        print("SCRIPTS {}".format(self.path_lib))
        if os.access(self.path_lib, os.R_OK):
            self.scripts = self.load_scripts(self.path_lib)
        else:
            print("ERROR {} is not readable".format(self.path_lib))
            sys.exit(0)

        self.path_var = self.config.get("tiny-matrix-bot",
                                        "var",
                                        fallback=os.path.join(
                                            path_current, "data")).strip()
        print("DATA {}".format(self.path_var))
        if os.access(self.path_var, os.W_OK):
            os.chdir(self.path_var)
        else:
            print("ERROR {} is not writeable".format(self.path_var))
            sys.exit(0)

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

        self.client = MatrixClient(self.config.get("tiny-matrix-bot", "host"))
        self.client.login_with_password(
            username=self.config.get("tiny-matrix-bot", "user"),
            password=self.config.get("tiny-matrix-bot", "pass"))

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

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

        self.client.start_listener_thread()
        self.client.add_invite_listener(self.on_invite)
        self.client.add_leave_listener(self.on_leave)

        while True:
            sleep(1)

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

    def load_scripts(self, path):
        scripts = {}
        for script in os.listdir(path):
            script_path = os.path.join(path, script)
            if not os.access(script_path, os.R_OK) \
                or not os.access(script_path, os.X_OK):
                continue
            output = subprocess.Popen(
                [script_path],
                env={
                    "CONFIG": "1"
                },
                stdout=subprocess.PIPE,
                universal_newlines=True).communicate()[0].strip()
            if not output:
                continue
            scripts[output] = script_path
            print("LOAD {} {}".format(script, output))
        return scripts

    def on_invite(self, room_id, state):
        print("INVITE {}".format(room_id))
        self.join_room(room_id)

    def join_room(self, room_id):
        room = self.client.join_room(room_id)
        room.add_listener(self.on_room_event)
        print("JOIN {}".format(room_id))
        if self.path_run is not False:
            thread = Thread(target=self.create_socket, args=(room, ))
            thread.daemon = True
            thread.start()

    def create_socket(self, room):
        socket_name = re.search("^\!([a-z]+):", room.room_id,
                                re.IGNORECASE).group(1)
        socket_path = os.path.join(self.path_run, socket_name)
        try:
            os.remove(socket_path)
        except OSError:
            pass
        sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sock.bind(socket_path)
        sock.listen(1)
        print("SOCKET {}".format(socket_path))
        while True:
            conn, addr = sock.accept()
            recv = conn.recv(4096).decode('utf-8').strip()
            print("RECV {} {}".format(socket_path, recv))
            room.send_text(recv)

    def on_leave(self, room_id, state):
        print("LEAVE {}".format(room_id))

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

    def run_script(self, room, event, args):
        print("ROOM {}".format(event["room_id"]))
        print("SENDER {}".format(event["sender"]))
        print("RUN {}".format(args))
        output = subprocess.Popen(
            args,
            env={
                "MXROOMID": event["room_id"],
                "MXSENDER": event["sender"]
            },
            stdout=subprocess.PIPE,
            universal_newlines=True).communicate()[0].strip()
        sleep(0.5)
        for p in output.split("\n\n"):
            for l in p.split("\n"):
                print("OUTPUT {}".format(l))
            room.send_text(p)
            sleep(1)
Beispiel #4
0
class MatrixBackend(ErrBot):
    def __init__(self, config):
        super().__init__(config)
        identity = config.BOT_IDENTITY
        self.token = identity["token"]
        self.url = identity["url"]
        self.user = identity["user"]
        self._client = None

    def build_identifier(self, text_representation: str) -> None:
        """Return an object that idenfifies a matrix person or room."""
        pass

    @staticmethod
    def parse_identfier_pieces(regex: str, text_rep: str):
        m = re.match(regex, text_rep)
        if m:
            data, domain = m.group()
            return data, domain
        return None, None

    @staticmethod
    def parse_identfier(text_rep):
        """Parse matrix identifiers into usable types.
        Expected formats are as follows:
        !<room>:<domain>
        #<room>:<domain>
        @<user>:<domain>
        """

        room, domain, user = None, None, None

        room, domain = MatrixBackend.parse_identfier_pieces(
            r"[!#](.*):(.*)", text_rep)
        if not room or not domain:
            user, domain = MatrixBackend.parse_identfier_pieces(
                r"@:(.*):(.*)", text_rep)

        return room, domain, user

    def send_msg(self, room_id, msg):
        room = self._client.join_room(room_id)
        return room.send_text(msg)

    def send_file(self, room_id, file_path, filename):
        with open(file_path, "rb") as f:
            content = f.read()

        return self.send_stream_content(room_id, content, filename)

    def send_stream_content(self, room_id, content, filename):
        res = self._client.upload(content, 'application/octet-stream',
                                  filename)
        room = self._client.join_room(room_id)
        room.send_file(res, filename)
        return res

    def build_reply(self, msg, text=None, private=False, threaded=False):
        response = self.build_message(text)
        response.frm = self.bot_identifier
        self.send_msg(msg.extras['room_id'], text)
        if private:
            response.to = msg.frm
        else:
            response.to = msg.frm.room if isinstance(msg.frm,
                                                     RoomOccupant) else msg.frm
        return response

    def change_presence(self):
        pass

    def mode(self):
        pass

    def query_room(self):
        pass

    def rooms(self):
        pass

    def invite_callback(self, *args, **kwargs):
        print(args, kwargs)

    def ephemeral_callback(self, *args, **kwargs):
        print(args, kwargs)

    def leave_callback(self, *args, **kwargs):
        print(args, kwargs)

    def presence_callback(self, *args, **kwargs):
        print(args, kwargs)

    def callback(self, *events):
        for event in events:
            log.debug("Saw event %s.", event)
            if event["type"] == "m.room.message":
                content = event["content"]
                sender = event["sender"]
                if content["msgtype"] == "m.text":
                    msg = Message(content["body"],
                                  extras={'room_id': event["room_id"]})
                    msg.frm = MatrixPerson(self._client, sender)
                    msg.to = self.bot_identifier
                    self.callback_message(msg)

    def serve_once(self):
        self._client = MatrixClient(self.url,
                                    token=self.token,
                                    user_id=self.user)
        self._client.add_listener(self.callback)
        self._client.add_invite_listener(self.invite_callback)
        self._client.add_ephemeral_listener(self.ephemeral_callback)
        self._client.add_leave_listener(self.leave_callback)
        self._client.add_presence_listener(self.presence_callback)
        self.connect_callback()

        self.bot_identifier = MatrixPerson(self._client, self.user)

        self._client.listen_forever()
Beispiel #5
0
class MatrixBotLedger(threading.Thread):
    def __init__(self, homeserver: str, username: str, password: str,
                 allowed_users: str, kill_event: threading.Event):
        super().__init__()
        logger.debug(
            f"__init__({homeserver}, {username}, {password}, {allowed_users}, {kill_event})"
        )

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

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

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

        self.connect()

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

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

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

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

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

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

        self.connect()

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

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

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

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

        self.join_room(room_id)

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

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

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

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

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

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

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

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

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

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

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

        return body, html

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

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

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

        members = room.get_joined_members()
        logger.debug(f"room joined members: {members}")
        for u in members:
            if u.user_id not in self.allowed_users:
                body = "I'm sorry, but not everyone in this room has clearance, so I'm not going to respond."
                html = None
                break
        try:
            room.send_html(html, body)
        except MatrixError as me:
            logger.error(me)
Beispiel #6
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)
Beispiel #7
0
class RssBot:
    def __init__(self, url, user_id, token):
        self.feeds = {}
        self.room_configs = {}
        self._known_guids = set()

        self.client = MatrixClient(url, user_id=user_id, token=token)
        self.client.add_invite_listener(self._handle_invite)
        self.client.add_leave_listener(self._handle_leave)

        self._fetch_cond = Condition()
        self._fetch_thread = Thread(target=self._fetch_loop)

        self._fetch_account_data()
        for room in self.client.rooms.values():
            self._setup_room(room)

    def _fetch_account_data(self):
        account_data_filter = \
            '{"presence":{"types":[]},\
              "room":{"rooms":[]},\
              "account_data":{"types":["%s"]}\
             }'                % ACCOUNT_DATA_TYPE
        # FIXME: might want to get this upstream
        sync = self.client.api.sync(filter=account_data_filter)
        account_data = sync['account_data']
        for event in account_data['events']:
            if event['type'] == ACCOUNT_DATA_TYPE:
                known_guids = event['content']['known_guids']
                self._known_guids = set(known_guids)

    def _setup_room(self, room):
        room.add_listener(self._handle_message, event_type='m.room.message')

        def on_state(event):
            self._handle_room_config(room, event['content'])

        room.add_state_listener(on_state, event_type=ROOM_EVENT_TYPE)

        try:
            # FIXME: might want to get this upstream
            config = self.client.api._send(
                "GET", "/rooms/" + room.room_id + "/state/" + ROOM_EVENT_TYPE)
        except MatrixRequestError as e:
            if e.code != 404:
                raise e
            config = None
        if config:
            self._handle_room_config(room, config)

    def _handle_invite(self, roomId, state):
        room = self.client.join_room(roomId)
        self._setup_room(room)

    def _handle_leave(self, room_id, room):
        if room_id in self.room_configs:
            del self.room_configs[room_id]
            self._update_feeds_config()

    def _handle_message(self, room, event):
        msg = str(event['content']['body'])
        if msg.startswith('!rss'):
            pass  # maybe a command interface for easier use?

    def _handle_room_config(self, room, config):
        room_config = dict()
        for entry in config['feeds']:
            url = str(entry['url'])
            update_interval = int(entry['update_interval_secs'])
            room_config[url] = update_interval
        self.room_configs[room.room_id] = room_config
        self._update_feeds_config()

    def _update_feeds_config(self):
        feeds = dict()
        for room_config in self.room_configs.values():
            for url, update_interval in room_config.items():
                if url not in feeds or feeds[url] > update_interval:
                    feeds[url] = update_interval
        self.feeds = {k: [v, 0] for k, v in feeds.items()}
        with self._fetch_cond:
            self._fetch_cond.notify()

    def _fetch_loop(self):
        while True:
            now = time.time()
            for url, times in self.feeds.items():
                [interval, last_update] = times
                next_update = last_update + interval
                if next_update <= now:
                    self._fetch_feed(url)
                    times[1] = now
            with self._fetch_cond:
                if self.feeds:
                    now = time.time()
                    timeout = None
                    for url, [interval, last_update] in self.feeds.items():
                        feed_timeout = last_update + interval - now
                        if timeout is None or feed_timeout < timeout:
                            timeout = feed_timeout
                    if timeout > 0:
                        self._fetch_cond.wait(timeout)
                else:
                    # No feeds registered
                    self._fetch_cond.wait()

    def get_rooms_for_feed(self, url):
        return [
            self.client.rooms[room_id]
            for room_id, feeds in self.room_configs.items() if url in feeds
        ]

    def _fetch_feed(self, url):
        # FIXME: one site with slow response times can block all feeds
        print('Fetching updates from {}'.format(url))
        try:
            feed = feedparser.parse(url)
            feed_title = feed.feed.title
            to_be_sent = []
            any_knowns = False
            for entry in feed.entries:
                guid = entry.id
                if guid not in self._known_guids:
                    self._known_guids.add(guid)
                    to_be_sent.append(entry)
                else:
                    any_knowns = True

            if not to_be_sent:
                return

            self.client.api.set_account_data(
                self.client.user_id, ACCOUNT_DATA_TYPE,
                {'known_guids': list(self._known_guids)})

            if not any_knowns:
                return

            for entry in reversed(to_be_sent):
                html = '[<a href="{}">{}</a>] {}'\
                       .format(entry.link, feed_title, entry.title)
                raw = '[{}][{}] {}'\
                      .format(feed_title, entry.link, entry.title)
                print(raw)
                for room in self.get_rooms_for_feed(url):
                    room.send_html(html, raw, 'm.notice')
        except Exception:
            print('Failed to parse feed {}: {}'.format(url,
                                                       traceback.format_exc()))

    def run(self):
        self._fetch_thread.start()
        self.client.listen_forever()
Beispiel #8
0
class Matrix():
    def __init__(self):
        self.rooms = {}
        self.matrixClient = {}

    def login(self):
        global theBot
        config = bot.theBot.config

        #variables
        self.matrixClient = MatrixClient(config.matrix["clienturl"])

        if config.matrix["password"] == "":
            config.matrix["password"] = getpass.getpass(prompt='Password: '******'t find room:"+room)
                    sys.exit(12)

    def handleException( self, exception ):
        print ( exception )
        print ("An Error occured, ignoring")
        return

    def listen(self):
        global theBot
        config = bot.theBot.config

        #if success, start the command listener.
        for i in self.rooms:
            self.rooms[i].add_listener( self.listener )
        self.matrixClient.add_listener( self.clientEvent );
        self.matrixClient.add_leave_listener( self.leftRoom );
        self.matrixClient.start_listener_thread( exception_handler = self.handleException )

    def clientEvent( self, event ):
        if event["type"] == "m.room.power_levels":
            print("Damn it Hellbacon!")

    def leftRoom(self, room_id, event): #room_id, not to be mistaken for room name.
        #this shouldn't happen, so rejoin.
        room = ""
        time.sleep(10)
        try:
            for i in self.rooms:
                if self.rooms[i].room_id == room_id:
                    room = i
                    self.rooms[room].leave();#remove the room, before rejoining.
                    self.rooms[room] = self.matrixClient.join_room(room)
                    self.rooms[room].add_listener( self.listener )

        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:"+room)
                sys.exit(12)

    def listener(self, room, event):
        global theBot
        config = bot.theBot.config

        if event['type'] == "m.room.message":
            if event['content']['msgtype'] == "m.text":
                #print("{0}: {1}".format(event['sender'], event['content']['body']))

                # ignore anything the bot might send to itself
                if(event['sender'] == "@"+config.matrix["username"]+":cclub.cs.wmich.edu"):
                    return

                #built in auto response to mention.
                if ( config.matrix["username"] + ":" in event['content']['body']):
                    room.send_text("Hi! I am a bot. If you want to know my commands type \""+config.prefix+"commands\" for available commands")

                # split the string to commands
                input = event['content']['body'].split(" ")

                # create responses for messages starting with prefix
                if len(input) > 0:
                    if len(input[0]) > 0:
                        ep = commandcenter.EventPackage()
                        ep.body = input
                        ep.room_id = event["room_id"]
                        ep.sender = event["sender"]
                        ep.event = event
                        ep.command = input[0]
                        if ep.command == config.prefix+"purge":
                            self.purge(ep)
                        else:
                            output = bot.theBot.cc.run(ep)

                            # if the command is in our dictionary of functions, use it (from commands.py)
                            if len(output) > 0:
                                room.send_text(output)

        else:
            print(event['type'])

    def purge(self, event ):
        return