Example #1
0
class Bot:
    def __init__(self, homeserver, userID, password, ownerID,
                 dialogflow_project):
        self.ownerID = ownerID
        self.userID = userID
        self.client = MatrixClient(homeserver)

        token = self.client.login(username=userID, password=password)

        self.project = dialogflow_project
        rooms = self.client.get_rooms()
        for name, room in self.client.get_rooms().items():
            room.add_listener(self.onEvent)

    def report_mainloop_error(self):
        pass

    def run(self):
        self.client.add_invite_listener(self.accept_invite)
        while True:
            try:
                self.client.listen_forever()
            except KeyboardInterrupt:
                raise
            except:
                self.report_mainloop_error()

    def accept_invite(self, room_id, state):
        self.client.join_room(room_id)
        session_client = dialogflow.SessionsClient()
        session = session_client.session_path(self.project, room_id)
        query_input = dialogflow.types.QueryInput(
            event=dialogflow.types.EventInput(name='WELCOME',
                                              language_code='en'))

    def onEvent(self, room, event):
        if event['sender'] != self.userID:
            print("New event in room {} : {}".format(room, event))
            if event['type'] == "m.room.message" and event['content'][
                    'msgtype'] == "m.text":
                if event['sender'] == self.ownerID:  # admin commands
                    leaveCommand = re.match("!leave (.+)",
                                            event['content']['body'])
                    if leaveCommand:
                        self.client.get_rooms()[leaveCommand.group(1)].leave()
                response = detect_intent(self.project, room.room_id,
                                         event['content']['body'], 'en')
                print("Got DialogFlow response: {}".format(response))
                for message in response:
                    for text in message.text.text:
                        room.send_text("{}".format(text))
def test_get_rooms():
    client = MatrixClient("http://example.com")
    rooms = client.get_rooms()
    assert isinstance(rooms, dict)
    assert len(rooms) == 0

    client = MatrixClient("http://example.com")

    client._mkroom("!abc:matrix.org")
    client._mkroom("!def:matrix.org")
    client._mkroom("!ghi:matrix.org")

    rooms = client.get_rooms()
    assert isinstance(rooms, dict)
    assert len(rooms) == 3
class MatrixBackend(ErrBot):
    def __init__(self, config):
        super().__init__(config)

        if not hasattr(config, 'MATRIX_HOMESERVER'):
            log.fatal("""
            You need to specify a homeserver to connect to in
            config.MATRIX_HOMESERVER.

            For example:
            MATRIX_HOMESERVER = "https://matrix.org"
            """)
            sys.exit(1)

        self._homeserver = config.MATRIX_HOMESERVER
        self._username = config.BOT_IDENTITY['username']
        self._password = config.BOT_IDENTITY['password']

    def serve_once(self):
        self.connect_callback()

        try:
            self._client = MatrixClient(self._homeserver)
            self._token = self._client.register_with_password(
                self._username,
                self._password,
            )
        except MatrixRequestError:
            log.fatal("""
            Incorrect username or password specified in
            config.BOT_IDENTITY['username'] or config.BOT_IDENTITY['password'].
            """)
            sys.exit(1)

        try:
            while True:
                time.sleep(2)
        except KeyboardInterrupt:
            log.info("Interrupt received, shutting down...")
            return True
        finally:
            self.disconnect_callback()

    def rooms(self):
        rooms = []
        raw_rooms = self._client.get_rooms()

        for rid, robject in raw_rooms:
            # TODO: Get the canonical alias rather than the first one from
            #       `Room.aliases`.
            log.debug('Found room %s (aka %s)' % (rid, rid.aliases[0]))

    def send_message(self, mess):
        super().send_message(mess)

    def connect_callback(self):
        super().connect_callback()
Example #4
0
def main(matrix_c: MatrixConfig, influx_dsn: str):
    """Listen for events happening in a Matrix room."""
    matrix = MatrixClient(matrix_c.hs, token=matrix_c.access_token)
    influxdb = InfluxDBClient.from_DSN(influx_dsn)

    my_room = list(
        filter(lambda x: x[0] == matrix_c.room_id,
               matrix.get_rooms().items()))[0][1]

    my_room.add_listener(lambda x: print(x))
    my_room.add_listener(lambda x: influxdb.write_points(
        [transform_matrix_to_influxdb(x)], time_precision='ms'))

    matrix.listen_forever()
Example #5
0
class Bot:
    def __init__(self, hs_url, username, password):
        self.cli = MatrixClient(hs_url)
        self.cli.login_with_password(username=username, password=password)
        self.shelf = shelve.open(data_file, writeback=True)
        signal.signal(signal.SIGTERM, self.close_shelf)
        signal.signal(signal.SIGINT, self.close_shelf)
        self.cli.add_invite_listener(self.on_invite)
        self.joined_rooms = self.cli.get_rooms()
        logger.info(
            f'Joined to {[r.display_name for r in self.joined_rooms.values()]}'
        )
        self.add_room_listeners()

    def run(self):
        self.cli.listen_forever(exception_handler=self.sync_exception_handler)
        logger.info('Bot started.')

    def add_room_listeners(self):
        for room in self.joined_rooms.values():
            self.add_local_bot(room)

    def on_invite(self, room_id, state):
        room = self.cli.join_room(room_id)
        # Force a sync in order not to process previous room messages
        self.cli._sync()
        self.add_local_bot(room)
        self.joined_rooms[room_id] = room
        room.send_notice(
            f'Hi! I\'m a list keeping bot. Send {LocalBot.prefix}help'
            ' to learn how to use me.')
        logger.info(
            f'Received an invite for room {room.display_name}, and joined.')

    def add_local_bot(self, room):
        lbot = LocalBot(room, self.cli.api, self.shelf)
        room.add_listener(lbot.on_message, event_type='m.room.message')

    def close_shelf(self, *args):
        logger.info('Closing shelf...')
        self.shelf.close()
        logger.info('Shelf is closed.')
        sys.exit()

    @staticmethod
    def sync_exception_handler(exception):
        logger.warning(exception)
def send_matrix_message(config_dict: dict, line: str, message: str,
                        config: str) -> str:
    from matrix_client.client import MatrixClient, Room, MatrixRequestError

    used_config = config_dict['matrix']['configs'][config]
    client = MatrixClient(used_config.url,
                          token=used_config.token,
                          user_id=used_config.user_id)

    log.debug('Sending message "%s" to room "%s" on server "%s"', message,
              used_config.room_id, used_config.url)

    try:
        room: Room = client.get_rooms()[used_config.room_id]
        return room.send_html(message)
    except MatrixRequestError:
        return None
Example #7
0
class Matrix:
    def __init__(self, credentials, rooms, send_to, transport):
        self.credentials = credentials
        self.rooms = rooms
        self.send_to = send_to
        self.transport = transport
        self.connect()

    def connect(self):
        self.client = MatrixClient(self.credentials.host,
                                   token=self.credentials.token,
                                   user_id=self.credentials.userid)

        # Listen for events in all configured rooms
        for room in self.rooms:
            joined_room = self.client.join_room(room)
            joined_room.add_listener(self.listener)

        self.client.start_listener_thread()
        self.print_rooms()
        self.runner()

    def print_rooms(self):
        rooms = self.client.get_rooms()
        for room_id, room in rooms.items():
            print("Matrix: {} ({})".format(room_id, room.display_name))

    def runner(self):
        worker_thread = threading.Timer(1.0, self.runner)
        worker_thread.daemon = True
        worker_thread.start()

        if not self.transport.to_matrix.empty():
            message = self.transport.to_matrix.get()
            self.client.api.send_message(message.destination,
                                         message.get_message())

    def listener(self, room, message):
        if self.send_to.get(room.room_id):
            if message.get('content', {}).get('msgtype') == 'm.text':
                if self.credentials.userid != message['sender']:
                    message = Message(self.send_to[room.room_id],
                                      message['sender'],
                                      message['content']['body'])
                    self.transport.send_hangouts(message)
Example #8
0
File: main.py Project: Xe/h2
# bootstrap the reloader
eval(compile(open(os.path.join('core', 'reload.py'), 'U').read(),
             os.path.join('core', 'reload.py'), 'exec'), globals())
reload(init=True)

print "matrix has u"

config = {}

with open("./config.yml", "r") as fin:
    config = load(fin.read())

client = MatrixClient(config["me"]["homeserver"])
token = client.login_with_password(username=config["me"]["user"], password=config["me"]["password"])

rooms = client.get_rooms()

def room_callback(event):
    room = rooms[event[u'room_id']]
    reload()
    if event[u'type'] == "m.room.message":
        print room.name, "<"+event[u'user_id']+">", event[u'content'][u'body']
        if event[u'user_id'] == config["me"]["user"]:
            return
        else:
            content = event[u'content']
            body = content[u'body']

            if body.startswith("."):
                body = body.replace(".", "", 1)
                splitstuff = body.split()
Example #9
0
class MatrixBackend(ErrBot):
    def __init__(self, config):
        super().__init__(config)

        if not hasattr(config, 'MATRIX_HOMESERVER'):
            log.fatal("""
            You need to specify a homeserver to connect to in
            config.MATRIX_HOMESERVER.

            For example:
            MATRIX_HOMESERVER = "https://matrix.org"
            """)
            sys.exit(1)

        self._homeserver = config.MATRIX_HOMESERVER
        self._username = config.BOT_IDENTITY['username']
        self._password = config.BOT_IDENTITY['password']
        self._api = None
        self._token = None


    def serve_once(self):
        def dispatch_event(event):
            log.info("Received event: %s" % event)

            if event['type'] == "m.room.member":
                if event['membership'] == "invite" and event['state_key'] == self._client.user_id:
                    room_id = event['room_id']
                    self._client.join_room(room_id)
                    log.info("Auto-joined room: %s" % room_id)

            if event['type'] == "m.room.message" and event['sender'] != self._client.user_id:
                sender = event['sender']
                room_id = event['room_id']
                body = event['content']['body']
                log.info("Received message from %s in room %s" % (sender, room_id))

                # msg = Message(body)
                # msg.frm = MatrixPerson(self._client, sender, room_id)
                # msg.to = MatrixPerson(self._client, self._client.user_id, room_id)
                # self.callback_message(msg) 

                msg = self.build_message(body)
                room = MatrixRoom(room_id)
                msg.frm = MatrixRoomOccupant(self._api, room, sender)
                msg.to = room
                self.callback_message(msg) 

        self.reset_reconnection_count()
        self.connect_callback()

        self._client = MatrixClient(self._homeserver)

        try:
            self._token = self._client.register_with_password(self._username,
                                                              self._password,)
        except MatrixRequestError as e:
            if e.code == 400 or e.code == 403:
                try:
                    self._token = self._client.login_with_password(self._username,
                                                     self._password,)
                except MatrixRequestError:
                    log.fatal("""
                        Incorrect username or password specified in
                        config.BOT_IDENTITY['username'] or config.BOT_IDENTITY['password'].
                    """)
                    sys.exit(1)

        self._api = MatrixHttpApi(self._homeserver, self._token)

        self.bot_identifier = MatrixPerson(self._api)

        self._client.add_listener(dispatch_event)

        try:
            while True:
                self._client.listen_for_events()
        except KeyboardInterrupt:
            log.info("Interrupt received, shutting down...")
            return True
        finally:
            self.disconnect_callback()

    def rooms(self):
        rooms = []
        raw_rooms = self._client.get_rooms()

        for rid, robject in raw_rooms:
            # TODO: Get the canonical alias rather than the first one from
            #       `Room.aliases`.
            log.debug('Found room %s (aka %s)' % (rid, rid.aliases[0]))

    def send_message(self, mess):
        super().send_message(mess)

        room_id = mess.to.room.id
        text_content = item_url = mess.body

        if item_url.startswith("http://") or item_url.startswith("https://"):
            if item_url.endswith("gif"):
                self._api.send_content(room_id, item_url, "image", "m.image")
                return

        # text_content = Markdown().convert(mess.body)
        self._api.send_message(room_id, text_content)

    def connect_callback(self):
        super().connect_callback()

    def build_identifier(self, txtrep):
        raise Exception(
            "XXX"
        )

    def build_reply(self, mess, text=None, private=False):
        log.info("build_reply")

        response = self.build_message(text)
        response.frm = self.bot_identifier
        response.to = mess.frm
        return response

    def change_presence(self, status: str = '', message: str = ''):
        raise Exception(
            "XXX"
        )

    @property
    def mode(self):
        return 'matrix'

    def query_room(self, room):
        raise Exception(
            "XXX"
        )
Example #10
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 #11
0
class MatrixBackend(ErrBot):
    def __init__(self, config):
        super().__init__(config)

        if not hasattr(config, 'MATRIX_HOMESERVER'):
            log.fatal("""
            You need to specify a homeserver to connect to in
            config.MATRIX_HOMESERVER.

            For example:
            MATRIX_HOMESERVER = "https://matrix.org"
            """)
            sys.exit(1)

        self._homeserver = config.MATRIX_HOMESERVER
        self._username = config.BOT_IDENTITY['username']
        self._password = config.BOT_IDENTITY['password']
        self._api = None
        self._token = None

    def serve_once(self):
        def dispatch_event(event):
            log.info("Received event: %s" % event)

            if event['type'] == "m.room.member":
                if event['membership'] == "invite" and event[
                        'state_key'] == self._client.user_id:
                    room_id = event['room_id']
                    self._client.join_room(room_id)
                    log.info("Auto-joined room: %s" % room_id)

            if event['type'] == "m.room.message" and event[
                    'sender'] != self._client.user_id:
                sender = event['sender']
                room_id = event['room_id']
                body = event['content']['body']
                log.info("Received message from %s in room %s" %
                         (sender, room_id))

                # msg = Message(body)
                # msg.frm = MatrixPerson(self._client, sender, room_id)
                # msg.to = MatrixPerson(self._client, self._client.user_id, room_id)
                # self.callback_message(msg)

                msg = self.build_message(body)
                room = MatrixRoom(room_id)
                msg.frm = MatrixRoomOccupant(self._api, room, sender)
                msg.to = room
                self.callback_message(msg)

        self.reset_reconnection_count()
        self.connect_callback()

        self._client = MatrixClient(self._homeserver)

        try:
            self._token = self._client.register_with_password(
                self._username,
                self._password,
            )
        except MatrixRequestError as e:
            if e.code == 400:
                try:
                    self._token = self._client.login_with_password(
                        self._username,
                        self._password,
                    )
                except MatrixRequestError:
                    log.fatal("""
                        Incorrect username or password specified in
                        config.BOT_IDENTITY['username'] or config.BOT_IDENTITY['password'].
                    """)
                    sys.exit(1)

        self._api = MatrixHttpApi(self._homeserver, self._token)

        self.bot_identifier = MatrixPerson(self._api)

        self._client.add_listener(dispatch_event)

        try:
            while True:
                self._client.listen_for_events()
        except KeyboardInterrupt:
            log.info("Interrupt received, shutting down...")
            return True
        finally:
            self.disconnect_callback()

    def rooms(self):
        rooms = []
        raw_rooms = self._client.get_rooms()

        for rid, robject in raw_rooms:
            # TODO: Get the canonical alias rather than the first one from
            #       `Room.aliases`.
            log.debug('Found room %s (aka %s)' % (rid, rid.aliases[0]))

    def send_message(self, mess):
        super().send_message(mess)

        room_id = mess.to.room.id
        text_content = item_url = mess.body

        if item_url.startswith("http://") or item_url.startswith("https://"):
            if item_url.endswith("gif"):
                self._api.send_content(room_id, item_url, "image", "m.image")
                return

        # text_content = Markdown().convert(mess.body)
        self._api.send_message(room_id, text_content)

    def connect_callback(self):
        super().connect_callback()

    def build_identifier(self, txtrep):
        raise Exception("XXX")

    def build_reply(self, mess, text=None, private=False):
        log.info("build_reply")

        response = self.build_message(text)
        response.frm = self.bot_identifier
        response.to = mess.frm
        return response

    def change_presence(self, status: str = '', message: str = ''):
        raise Exception("XXX")

    @property
    def mode(self):
        return 'matrix'

    def query_room(self, room):
        raise Exception("XXX")
Example #12
0
class MatrixProtocol(Protocol):

    # called on bot init; the following are already created by __init__:
    #   self.bot = SibylBot instance
    #   self.log = the logger you should use
    def setup(self):
        self.connected = False
        self.rooms = {}
        self.bot.add_var("credentials", persist=True)

        # Incoming message queue - messageHandler puts messages in here and
        # process() looks here periodically to send them to sibyl
        self.msg_queue = Queue()

        # Create a client in setup() because we might use self.client before
        # connect() is called
        homeserver = self.opt('matrix.server')
        self.client = MatrixClient(homeserver)

    # @raise (ConnectFailure) if can't connect to server
    # @raise (AuthFailure) if failed to authenticate to server
    def connect(self):
        homeserver = self.opt('matrix.server')
        user = self.opt('matrix.username')
        pw = self.opt('matrix.password')

        self.log.debug("Connecting to %s" % homeserver)

        try:
            self.log.debug("Logging in as %s" % user)

            # Log in with the existing access token if we already have a token
            if (self.bot.credentials and self.bot.credentials[0] == user):
                self.client = MatrixClient(homeserver,
                                           user_id=user,
                                           token=self.bot.credentials[1])
            # Otherwise, log in with the configured username and password
            else:
                token = self.client.login_with_password(user, pw)
                self.bot.credentials = (user, token)

            self.rooms = self.client.get_rooms()
            self.log.debug("Already in rooms: %s" % self.rooms)

            # Connect to Sibyl's message callback
            self.client.add_listener(self.messageHandler)

            self.client.start_listener_thread()
            self.connected = True

        except MatrixRequestError as e:
            if (e.code == 403):
                self.log.debug(
                    "Credentials incorrect! Maybe your access token is outdated?"
                )
                raise AuthFailure
            else:
                self.log.debug("Failed to connect to homeserver!")
                raise ConnectFailure

    # @return (bool) True if we are connected to the server
    def is_connected(self):
        return self.connected

    # receive/process messages and call bot._cb_message()
    # must ignore msgs from myself and from users not in any of our rooms
    # @call bot._cb_message(Message) upon receiving a valid status or message
    # @raise (PingTimeout) if implemented
    # @raise (ConnectFailure) if disconnected
    # @raise (ServerShutdown) if server shutdown
    def process(self):
        while (not self.msg_queue.empty()):
            self.bot._cb_message(self.msg_queue.get())

    def messageHandler(self, msg):
        if (self.opt('matrix.debug')):
            self.log.debug(str(msg))

        try:
            # Create a new Message to send to Sibyl
            u = self.new_user(msg['sender'], Message.GROUP)
            r = self.new_room(msg['room_id'])

            msgtype = msg['content']['msgtype']

            if (msgtype == 'm.text'):
                m = Message(u,
                            msg['content']['body'],
                            room=r,
                            typ=Message.GROUP)
                self.log.debug('Handling m.text: ' + msg['content']['body'])
                self.msg_queue.put(m)

            elif (msgtype == 'm.emote'):
                m = Message(u,
                            msg['content']['body'],
                            room=r,
                            typ=Message.GROUP,
                            emote=True)
                self.log.debug('Handling m.emote: ' + msg['content']['body'])
                self.msg_queue.put(m)

            elif (msgtype == 'm.image' or msgtype == 'm.audio'
                  or msgtype == 'm.file' or msgtype == 'm.video'):
                media_url = urlparse(msg['content']['url'])
                http_url = self.client.api.base_url + "/_matrix/media/r0/download/{0}{1}".format(
                    media_url.netloc, media_url.path)
                if (msgtype == 'm.image'):
                    body = "{0} uploaded {1}: {2}".format(
                        msg['sender'], msg['content'].get('body', 'an image'),
                        http_url)
                elif (msgtype == 'm.audio'):
                    body = "{0} uploaded {1}: {2}".format(
                        msg['sender'],
                        msg['content'].get('body', 'an audio file'), http_url)
                elif (msgtype == 'm.video'):
                    body = "{0} uploaded {1}: {2}".format(
                        msg['sender'],
                        msg['content'].get('body', 'a video file'), http_url)
                elif (msgtype == 'm.file'):
                    body = "{0} uploaded {1}: {2}".format(
                        msg['sender'], msg['content'].get('body', 'a file'),
                        http_url)
                m = Message(u, body, room=r, typ=Message.GROUP)
                self.log.debug("Handling " + msgtype + ": " + body)
                self.msg_queue.put(m)

            elif (msgtype == 'm.location'):
                body = "{0} sent a location: {1}".format(
                    msg['sender'], msg['content']['geo_uri'])
                m = Message(u, body, room=r, typ=Message.GROUP)
                self.log.debug('Handling m.location: ' + body)
                self.msg_queue.put(m)

            else:
                self.log.debug('Not handling message, unknown msgtype')

        except KeyError as e:
            self.log.debug(
                "Incoming message did not have all required fields: " +
                e.message)

    # called when the bot is exiting for whatever reason
    # NOTE: sibylbot will already call part_room() on every room in get_rooms()
    def shutdown(self):
        pass

    # send a message to a user
    # @param mess (Message) message to be sent
    # @raise (ConnectFailure) if failed to send message
    # Check: get_emote()
    def send(self, mess):
        (text, to) = (mess.get_text(), mess.get_to())
        if (mess.get_emote()):
            to.room.send_emote(text)
        else:
            to.room.send_text(text)

    # send a message with text to every user in a room
    # optionally note that the broadcast was requested by a specific User
    # @param mess (Message) the message to broadcast
    # @return (str,unicode) the text that was actually sent
    # Check: get_user(), get_users()
    def broadcast(self, mess):
        """send a message to every user in a room"""

        (text, room, frm) = (mess.get_text(), mess.get_to(), mess.get_user())
        users = self.get_occupants(room) + (mess.get_users() or [])

        # Matrix has no built-in broadcast, so we'll just highlight everyone
        s = 'all: %s --- ' % text
        if frm:
            self.log.debug('Broadcast message from: ' + str(frm))
            s += frm.get_name() + ' --- '

        me = self.get_user()
        names = [
            u.get_name() for u in users if (u != me and (not frm or u != frm))
        ]
        s += ', '.join(set(names))

        self.send(Message(self.get_user(), s, to=room))
        return s

    # join the specified room using the specified nick and password
    # @param room (Room) the room to join
    # @call bot._cb_join_room_success(room) on successful join
    # @call bot._cb_join_room_failure(room,error) on failed join
    def join_room(self, room):
        try:
            res = self.client.join_room(room.room.room_id)
            self.bot._cb_join_room_success(room)
        except MatrixRequestError as e:
            self.bot._cb_join_room_failure(room, e.message)

    # part the specified room
    # @param room (Room) the room to leave
    def part_room(self, room):
        raise NotImplementedError

    # helper function for get_rooms() for protocol-specific flags
    # only needs to handle: FLAG_PARTED, FLAG_PENDING, FLAG_IN, FLAG_ALL
    # @param flag (int) one of Room.FLAG_* enums
    # @return (list of Room) rooms matching the flag
    def _get_rooms(self, flag):
        mxrooms = self.client.get_rooms()
        return [self.new_room(mxroom) for mxroom in mxrooms]

    # @param room (Room) the room to query
    # @return (list of User) the Users in the specified room
    def get_occupants(self, room):
        memberdict = room.room.get_joined_members()
        return [self.new_user(x) for x in memberdict]

    # @param room (Room) the room to query
    # @return (str) the nick name we are using in the specified room
    def get_nick(self, room):
        return self.get_user().get_name()  # TODO: per-room nicknames

    # @param room (Room) the room to query
    # @param nick (str) the nick to examine
    # @return (User) the "real" User behind the specified nick/room
    def get_real(self, room, nick):
        raise NotImplementedError

    # @return (User) our username
    def get_user(self):
        return MatrixUser(self, self.opt('matrix.username'), Message.GROUP)

    # @param user (str) a user id to parse
    # @param typ (int) either Message.GROUP or Message.PRIVATE
    # @param real (User) [self] the "real" user behind this user
    # @return (User) a new instance of this protocol's User subclass
    def new_user(self, user, typ=None, real=None):
        return MatrixUser(self, user, typ, real)

    # @param name (object) the identifier for this Room
    # @param nick (str) [None] the nick name to use in this Room
    # @param pword (str) [None] the password for joining this Room
    # @return (Room) a new instance of this protocol's Room subclass
    def new_room(self, room_id_or_alias, nick=None, pword=None):
        return MatrixRoom(self, room_id_or_alias, nick, pword)
Example #13
0
class SparseManager(object):
    def __init__(self):
        self.active_room = None
        self.active_listener_id = None
        self.active_start = None

    def login(self, url, username, password):
        self.client = MatrixClient(url)

        # New user
        # token = client.register_with_password(username="******", password="******")

        # Existing user
        create_path(FILENAME)
        token = self.client.login_with_password(username=username,
                                                password=password)
        print("Logged in with token: {token}".format(token=token))

        data = {'token': token, 'user_id': self.client.user_id, 'url': url}

        with open(FILENAME, 'w') as f:
            json.dump(data, f, ensure_ascii=False)

        return data

    def login_with_token(self):
        with open(FILENAME, 'r') as f:
            data = json.load(f)

        self.client = MatrixClient(data["url"],
                                   user_id=data["user_id"],
                                   token=data["token"])
        return data

    def get_rooms(self):
        self.rooms = self.client.get_rooms()
        ids = [{
            "name": x.display_name,
            "topic": x.topic,
            "room_id": x.room_id,
            "has_unread_messages": x.has_unread_messages
        } for x in self.rooms.values()]
        return ids

    def enter_room(self, room_id):
        import threading
        print("Threads running: %s self: %s" %
              (len(threading.enumerate()), id(self)))
        if self.active_room and self.active_room.room_id == room_id:
            return
        if self.active_room:
            self.deactivate_room()

        # self.active_room = self.client.join_room(room_id)

        self.active_room = self.rooms[room_id]
        self.active_listener_id = self.active_room.add_listener(
            self.on_message)
        self.active_start = self.active_room.get_room_messages(limit=20)
        print("Message end: %s" % self.active_start)
        self.client.start_listener_thread()

    def get_next_messages(self):
        # TODO prepend at beginning
        self.active_start = self.active_room.get_room_messages(
            limit=20, start=self.active_start)
        return self.active_start

    def deactivate_room(self):
        self.active_room.remove_listener(self.active_listener_id)
        self.client.stop_listener_thread(blocking=False)
        self.active_room = None
        self.active_listener_id = None
        self.active_start = None

    def on_message(self, room, event):
        if event['type'] in ("m.room.message", "m.room.encrypted"):
            to_send = {}
            if "msgtype" in event["content"] and event["content"][
                    "msgtype"] == "m.image":
                to_send["image_url"] = self.client.api.get_download_url(
                    event["content"]["url"])
                to_send["msgtype"] = "image"
            if event["sender"] == self.client.user_id:
                user_id = self.client.user_id
            else:
                user_id = event["user_id"]
            if "body" in event["content"]:
                to_send["body"] = event["content"]["body"]
            elif "ciphertext" in event["content"]:
                to_send["body"] = "... encrypted ..."
            else:
                to_send["body"] = "... no message ..."
            if "redacted_because" in event:
                to_send["body"] = "... redacted ..."
            user = room._members.get(user_id)
            avatar_url = None
            displayname = None
            if user and user.avatar_url:
                avatar_url = user.avatar_url
            if user and user.displayname:
                displayname = user.displayname
            # XXX to expensive take up to 2 sec member and message
            # elif user and not user.avatar_url:
            #     avatar_url = user.get_avatar_url()
            to_send["origin_server_ts"] = event["origin_server_ts"]
            to_send["time"] = datetime.datetime.fromtimestamp(
                event["origin_server_ts"] / 1000, datetime.timezone.utc)
            to_send["avatar_url"] = avatar_url
            to_send["displayname"] = displayname if displayname else event[
                "sender"]
            pyotherside.send('r.room.message', {"event": to_send})
        else:
            pass
            # print(event["type"])
            # print(event)

    def send_text(self, text):
        self.active_room.send_text(text)
Example #14
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 #15
0
class MatrixProtocol(Protocol):

  # List of occupants in each room
  # Used to avoid having to re-request the list of members each time
  room_occupants = {}
  # Keep track of when we joined rooms this session
  # Used to filter out historical messages after accepting room invites
  join_timestamps = {}

  # called on bot init; the following are already created by __init__:
  #   self.bot = SibylBot instance
  #   self.log = the logger you should use
  def setup(self):
    self.rooms = {}
    self.bot.add_var("credentials",persist=True)

    # Incoming message queue - messageHandler puts messages in here and
    # process() looks here periodically to send them to sibyl
    self.msg_queue = Queue()

    # Create a client in setup() because we might use self.client before
    # connect() is called
    homeserver = self.opt('matrix.server')
    self.client = MatrixClient(homeserver)

  # @raise (ConnectFailure) if can't connect to server
  # @raise (AuthFailure) if failed to authenticate to server
  def connect(self):
    homeserver = self.opt('matrix.server')
    user = self.opt('matrix.username')
    pw = self.opt('matrix.password')

    self.log.debug("Connecting to %s" % homeserver)

    try:
      self.log.debug("Logging in as %s" % user)

      # Log in with the existing access token if we already have a token
      if(self.bot.credentials and self.bot.credentials[0] == user):
        self.client = MatrixClient(homeserver, user_id=user, token=self.bot.credentials[1])
      # Otherwise, log in with the configured username and password
      else:
        token = self.client.login_with_password(user,pw)
        self.bot.credentials = (user, token)

      self.rooms = self.client.get_rooms()
      self.log.debug("Already in rooms: %s" % self.rooms)

      # Connect to Sibyl's message callback
      self.client.add_listener(self.messageHandler)
      self.client.add_invite_listener(self.inviteHandler)

      self.log.debug("Starting Matrix listener thread")
      self.client.start_listener_thread(exception_handler=self._matrix_exception_handler)

    except MatrixRequestError as e:
      if(e.code in [401, 403]):
        self.log.debug("Credentials incorrect! Maybe your access token is outdated?")
        raise self.AuthFailure
      else:
        if(self.opt('matrix.debug')):
          tb = traceback.format_exc()
          self.log.debug(tb)
        self.log.debug("Failed to connect to homeserver!")
        raise self.ConnectFailure
    except MatrixHttpLibError as e:
      self.log.error("Failed to connect to homeserver!")
      self.log.debug("Received error:" + str(e))
      raise self.ConnectFailure

  def _matrix_exception_handler(self, e):
    self.msg_queue.put(e)

  # receive/process messages and call bot._cb_message()
  # must ignore msgs from myself and from users not in any of our rooms
  # @call bot._cb_message(Message) upon receiving a valid status or message
  # @raise (PingTimeout) if implemented
  # @raise (ConnectFailure) if disconnected
  # @raise (ServerShutdown) if server shutdown
  def process(self):
    while(not self.msg_queue.empty()):
      next = self.msg_queue.get()
      if(isinstance(next, Message)):
        self.log.debug("Placing message into queue: " + next.get_text())
        self.bot._cb_message(next)
      elif(isinstance(next, MatrixHttpLibError)):
        self.log.debug("Received error from Matrix SDK, stopping listener thread: " + str(next))
        self.client.stop_listener_thread()
        raise self.ConnectFailure("Connection error returned by requests library: " + str(next))


  def messageHandler(self, msg):
    if(self.opt('matrix.debug')):
      self.log.debug(str(msg))

    try:
      # Create a new Message to send to Sibyl
      u = self.new_user(msg['sender'], Message.GROUP)
      r = self.new_room(msg['room_id'])

      if(r in self.join_timestamps
         and datetime.datetime.fromtimestamp(msg['origin_server_ts']/1000, pytz.utc) < self.join_timestamps[r]):
        self.log.info('Message received in {} from before room join, ignoring'.format(msg['room_id']))
        return None

      if('msgtype' in msg['content']):
        msgtype = msg['content']['msgtype']

        if(msgtype == 'm.text'):
          m = Message(u, msg['content']['body'], room=r, typ=Message.GROUP)
          self.log.debug('Handling m.text: ' + msg['content']['body'])
          self.msg_queue.put(m)

        elif(msgtype == 'm.emote'):
          m = Message(u, msg['content']['body'], room=r, typ=Message.GROUP,
          emote=True)
          self.log.debug('Handling m.emote: ' + msg['content']['body'])
          self.msg_queue.put(m)

        elif(msgtype == 'm.image' or msgtype == 'm.audio' or msgtype == 'm.file' or msgtype == 'm.video'):
          media_url = urlparse(msg['content']['url'])
          http_url = self.client.api.base_url + "/_matrix/media/r0/download/{0}{1}".format(media_url.netloc, media_url.path)
          if(msgtype == 'm.image'):
            body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'an image'), http_url)
          elif(msgtype == 'm.audio'):
            body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'an audio file'), http_url)
          elif(msgtype == 'm.video'):
            body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'a video file'), http_url)
          elif(msgtype == 'm.file'):
            body = "{0} uploaded {1}: {2}".format(msg['sender'], msg['content'].get('body', 'a file'), http_url)
          m = Message(u, body, room=r, typ=Message.GROUP)
          self.log.debug("Handling " + msgtype + ": " + body)
          self.msg_queue.put(m)

        elif(msgtype == 'm.location'):
          body = "{0} sent a location: {1}".format(msg['sender'], msg['content']['geo_uri'])
          m = Message(u, body, room=r, typ=Message.GROUP)
          self.log.debug('Handling m.location: ' + body)
          self.msg_queue.put(m)


        else:
          self.log.debug('Not handling message, unknown msgtype')

      elif('membership' in msg):
        if(msg['membership'] == 'join'):
          self.room_occupants[r].add(self.new_user(msg['state_key'], Message.GROUP))
        elif(msg['membership'] == 'leave'):
          self.room_occupants[r].remove(self.new_user(msg['state_key'], Message.GROUP))

    except KeyError as e:
      self.log.debug("Incoming message did not have all required fields: " + e.message)


  def inviteHandler(self, room_id, state):
    join_on_invite = self.opt('matrix.join_on_invite')

    invite_events = [x for x in state['events'] if x['type'] == 'm.room.member'
                     and x['state_key'] == str(self.get_user())
                     and x['content']['membership'] == 'invite']
    if(len(invite_events) != 1):
      raise KeyError("Something's up, found more than one invite state event for " + room_id)

    inviter = invite_events[0]['sender']
    inviter_domain = inviter.split(':')[1]
    my_domain = str(self.get_user()).split(':')[1]

    if(join_on_invite == 'accept' or (join_on_invite == 'domain' and inviter_domain == my_domain)):
      self.log.debug('Joining {} on invite from {}'.format(room_id, inviter))
      self.join_room(MatrixRoom(self, room_id))

    elif(join_on_invite == 'domain' and inviter_domain != my_domain):
      self.log.debug("Received invite for {} but inviter {} is on a different homeserver").format(room_id, inviter)

    else:
      self.log.debug("Received invite for {} from {} but join_on_invite is disabled".format(room_id, inviter))


  # called when the bot is exiting for whatever reason
  # NOTE: sibylbot will already call part_room() on every room in get_rooms()
  def shutdown(self):
    pass

  # send a message to a user
  # @param mess (Message) message to be sent
  # @raise (ConnectFailure) if failed to send message
  # Check: get_emote()
  def send(self,mess):
    (text,to) = (mess.get_text(),mess.get_to())
    try:
      if(mess.get_emote()):
        to.room.send_emote(text)
      else:
        to.room.send_text(text)
    except MatrixError as e:
      raise self.ConnectFailure

  # send a message with text to every user in a room
  # optionally note that the broadcast was requested by a specific User
  # @param mess (Message) the message to broadcast
  # @return (str,unicode) the text that was actually sent
  # Check: get_user(), get_users()
  def broadcast(self,mess):
    """send a message to every user in a room"""

    (text,room,frm) = (mess.get_text(),mess.get_to(),mess.get_user())
    users = self.get_occupants(room)+(mess.get_users() or [])

    # Matrix has no built-in broadcast, so we'll just highlight everyone
    s = 'all: %s --- ' % text
    if frm:
      self.log.debug('Broadcast message from: ' + str(frm))
      s += frm.get_name()+' --- '

    me = self.get_user()
    names = [u.get_name() for u in users if (u!=me and (not frm or u!=frm))]
    s += ', '.join(set(names))

    self.send(Message(self.get_user(),s,to=room))
    return s

  # join the specified room using the specified nick and password
  # @param room (Room) the room to join
  # @call bot._cb_join_room_success(room) on successful join
  # @call bot._cb_join_room_failure(room,error) on failed join
  def join_room(self,room):
    try:
      res = self.client.join_room(room.room.room_id)
      self.bot._cb_join_room_success(room)
      self.join_timestamps[room] = datetime.datetime.now(pytz.utc)
    except MatrixError as e:
      self.bot._cb_join_room_failure(room, e.message)

  # part the specified room
  # @param room (Room) the room to leave
  def part_room(self,room):
    raise NotImplementedError

  # helper function for get_rooms() for protocol-specific flags
  # only needs to handle: FLAG_PARTED, FLAG_PENDING, FLAG_IN, FLAG_ALL
  # @param flag (int) one of Room.FLAG_* enums
  # @return (list of Room) rooms matching the flag
  def _get_rooms(self,flag):
    mxrooms = self.client.get_rooms()
    return [self.new_room(mxroom) for mxroom in mxrooms]


  # @param room (Room) the room to query
  # @return (list of User) the Users in the specified room
  def get_occupants(self,room):
    if(room in self.room_occupants):
      return list(self.room_occupants[room])
    else:
      try:
        memberdict = room.room.get_joined_members()
        users = [ self.new_user(x) for x in memberdict ]
        self.room_occupants[room] = set(users)
        return users
      except MatrixError as e:
        raise self.ConnectFailure

  # @param room (Room) the room to query
  # @return (str) the nick name we are using in the specified room
  def get_nick(self,room):
    return self.get_user().get_name() # TODO: per-room nicknames

  # @param room (Room) the room to query
  # @param nick (str) the nick to examine
  # @return (User) the "real" User behind the specified nick/room
  def get_real(self,room,nick):
    raise NotImplementedError

  # @return (User) our username
  def get_user(self):
    return MatrixUser(self,self.opt('matrix.username'),Message.GROUP)

  # @param user (str) a user id to parse
  # @param typ (int) either Message.GROUP or Message.PRIVATE
  # @param real (User) [self] the "real" user behind this user
  # @return (User) a new instance of this protocol's User subclass
  def new_user(self,user,typ=None,real=None):
    return MatrixUser(self,user,typ,real)

  # @param name (object) the identifier for this Room
  # @param nick (str) [None] the nick name to use in this Room
  # @param pword (str) [None] the password for joining this Room
  # @return (Room) a new instance of this protocol's Room subclass
  def new_room(self,room_id_or_alias,nick=None,pword=None):
    return MatrixRoom(self,room_id_or_alias,nick,pword)
Example #16
0
class MatrixBotAPI:

    # username - Matrix username
    # password - Matrix password
    # server   - Matrix server url : port
    # rooms    - List of rooms ids to operate in, or None to accept all rooms
    def __init__(self, username, password, server, rooms=None):
        self.username = username

        # Authenticate with given credentials
        self.client = MatrixClient(server)
        try:
            self.client.login_with_password(username, password)
        except MatrixRequestError as e:
            print(e)
            if e.code == 403:
                print("Bad username/password")
        except Exception as e:
            print("Invalid server URL")
            traceback.print_exc()

        # Store allowed rooms
        self.rooms = rooms

        # Store empty list of handlers
        self.handlers = []

        # If rooms is None, we should listen for invites and automatically accept them
        if rooms is None:
            self.client.add_invite_listener(self.handle_invite)
            self.rooms = []

            # Add all rooms we're currently in to self.rooms and add their callbacks
            for room_id, room in self.client.get_rooms().items():
                room.add_listener(self.handle_message)
                self.rooms.append(room_id)
                #if room_id == "!tukvxnciRWDbYQTSdw:eaton.uk.net": #Startup Message Example
                #room.send_text("This is a startup message going out to my fellow bot Devs")
        else:
            # Add the message callback for all specified rooms
            for room in self.rooms:
                room.add_listener(self.handle_message)

    def add_handler(self, handler):
        self.handlers.append(handler)

    def CheckIgnoreSender(self, room, sender):
        allIgnored = IgnoreList.GetGlobalIgnoreList()
        roomExists = False
        for roomIgnore in allIgnored:  # find this room from all rooms
            if roomIgnore.roomID == room.room_id:
                roomExists = True
                for ignoredUser in roomIgnore.ignoredUsers:
                    if sender == ignoredUser:
                        return True
        if roomExists == False:  #if the room (and hence user) did not exist
            IgnoreList.AddNewRoom(room.room_id)  #create empty room entry
        return False  # we fell through the for loop looking for user, this is always false

    def handle_message(
            self, room,
            event):  # this is where the ignore check should really take place
        # Make sure we didn't send this message
        if re.match("@" + self.username, event['sender']):
            return
        if self.CheckIgnoreSender(room, event['sender']) == True:
            return

        # Loop through all installed handlers and see if they need to be called
        for handler in self.handlers:
            if handler.test_callback(room, event):
                # This handler needs to be called
                try:
                    handler.handle_callback(room, event)
                except:
                    traceback.print_exc()

    def handle_invite(self, room_id, state):
        print("Got invite to room: " + str(room_id))
        print("Joining...")
        room = self.client.join_room(room_id)

        # Add message callback for this room
        room.add_listener(self.handle_message)

        # Add room to list
        self.rooms.append(room)

    def start_polling(self):
        # Starts polling for messages
        self.client.start_listener_thread()
        return self.client.sync_thread