Пример #1
0
def main(host, username, password):
    client = MatrixClient(host)
    rooms = client.get_rooms()

    try:
        db = sqlite3.connect('db')
        cursor = db.cursor()
        cursor.execute(
            '''CREATE TABLE IF NOT EXISTS messages(id INTEGER PRIMARY KEY, date TEXT, roomid TEXT, sender TEXT, message TEXT)'''
        )
        db.commit()
    except Exception as e:
        db.rollback()
        raise e
    finally:
        db.close()

    def on_invite(room_id, state):
        print("joining room " + room_id)
        room = client.join_room(room_id)
        room.add_listener(on_message)

    try:
        token = client.login_with_password(username, password)
        global matrix
        matrix = MatrixHttpApi(host, token)
    except MatrixRequestError as e:
        print(e)
        if e.code == 403:
            print("Bad username or password.")
            sys.exit(4)
        else:
            print("Check if server details are correct.")
            sys.exit(2)
    except MissingSchema as e:
        print("Bad URL format.")
        print(e)
        sys.exit(3)

    for room in rooms:
        try:
            roomname = matrix.get_room_name(room)
            print("Already in room " + roomname['name'])
            room_to_listen = client.join_room(room)
            room_to_listen.add_listener(on_message)
        except MatrixRequestError as e:
            print(e)
            if e.code == 400:
                print("Room ID/Alias in the wrong format")
                sys.exit(11)
            else:
                print("Couldn't find room.")
                sys.exit(12)

    client.add_invite_listener(on_invite)
    client.start_listener_thread()

    while True:
        time.sleep(30)
Пример #2
0
def main():
    global g_client
    print("Connecting to server: {}".format(botconfig.client_url))
    g_client = MatrixClient(botconfig.client_url)

    password = get_password()

    try:
        print("Logging in with username: {}".format(botconfig.username))
        g_client.login_with_password(botconfig.username, password)
    except MatrixRequestError as e:
        print(e)
        if e.code == 403:
            print("Bad username or password.")
            sys.exit(4)
        else:
            print("Check your sever details are correct.")
            sys.exit(2)
    except MissingSchema as e:
        print("Bad URL format.")
        print(e)
        sys.exit(3)

    # room dictionary that will be passed to the listener manager
    rooms = {}

    for room_address in botconfig.rooms:
        try:
            room = g_client.join_room(room_address)
            room.add_listener(on_message)
            rooms[room_address] = room
            print("Joined room: {}".format(room_address))
        except MatrixRequestError as e:
            print(e)
            if e.code == 400:
                print("{}: Room ID/Alias in the wrong format".format(
                    room_address))
                sys.exit(11)
            else:
                print("Couldn't find room: {}".format(room_address))
                sys.exit(12)

    g_client.add_invite_listener(on_invite)

    g_client.start_listener_thread()

    listener = ListenerManager(rooms, botconfig.listener_port)

    listener.start_listener_thread()

    try:
        while True:
            pass
    except KeyboardInterrupt:
        print("Shutting down.")
Пример #3
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))
Пример #4
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)
Пример #5
0
def run_bot(homeserver, authorize, username, password):
    allowed_users = authorize
    shell_env = os.environ.copy()
    shell_env['TERM'] = 'vt100'
    child_pid, master = pty.fork()
    if child_pid == 0:  # we are the child
        os.execlpe('sh', 'sh', shell_env)
    pin = os.fdopen(master, 'w')
    stop = threading.Event()

    client = MatrixClient(homeserver)
    client.login_with_password_no_sync(username, password)
    # listen for invites during initial event sync so we don't miss any
    client.add_invite_listener(lambda room_id, state: on_invite(
        client, room_id, state, allowed_users))
    client.listen_for_events()  # get rid of initial event sync
    client.add_listener(lambda event: on_message(event, pin, allowed_users),
                        event_type='m.room.message')

    shell_stdout_handler_thread = threading.Thread(target=shell_stdout_handler,
                                                   args=(master, client, stop))
    shell_stdout_handler_thread.start()

    while True:
        try:
            client.listen_forever()
        except KeyboardInterrupt:
            stop.set()
            sys.exit(0)
        except requests.exceptions.Timeout:
            logger.warn("timeout. Trying again in 5s...")
            time.sleep(5)
        except requests.exceptions.ConnectionError as e:
            logger.warn(repr(e))
            logger.warn("disconnected. Trying again in 5s...")
            time.sleep(5)
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 = []

        # Store dict with additional arguments to handlers
        # This allows to provide additional arguments to a specific handler
        # callback on registration.
        self.additional_arguments = {}

        # 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)
        else:
            # Add the message callback for all specified rooms
            for room in self.rooms:
                room.add_listener(self.handle_message)

    # Add a new handler to the bot. If arg is given, it is provided as a third
    # argument on every invocation of handler.
    def add_handler(self, handler, arg=None):
        self.handlers.append(handler)

        if arg:
            self.additional_arguments[handler] = arg

    def remove_handler(self, handler):
        try:
            self.handlers.remove(handler)
        except ValueError as e:
            return

        try:
            self.additional_arguments.pop(handler)
        except KeyError:
            return

    def get_handler(self, trigger):
        res = []

        for h in self.handlers:
            if h.triggers_on(trigger):
                res.append(h)

        return res

    def handle_message(self, room, event):
        # Make sure we didn't send this message
        if re.match("@" + self.username, event['sender']):
            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:
                    # If an additional argument is registered for the handler,
                    # call it with this argument
                    arg = self.additional_arguments[handler]
                    handler.handle_callback(room, event, arg)
                except KeyError:
                    # Otherwise leave it out
                    handler.handle_callback(room, event)

    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
Пример #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()
Пример #8
0
class MatrixHandler(object):
    """Handling matrix connection and bot integration"""

    __slots__ = [
        'client',
        'config',
        'db',
        'giphy_api_key',
        'hostname',
        'openweathermap_api_key',
        'password',
        'stored_msg',
        'sync_process',
        'uid',
        'username',
    ]

    def __init__(self, config: Dict[str, str]) -> None:
        self.config = config

        self.hostname = config['hostname']
        self.username = config['username']
        self.password = config['password']
        self.uid = config['uid']

        self.db = get_db()
        self.stored_msg = Query()

        self.giphy_api_key = config['giphy_api_key']
        self.openweathermap_api_key = config['openweathermap_api_key']

    def on_message(self, room: Room, event: Dict) -> None:
        """Callback for recieved messages

        Gets events and checks if something can be triggered.
        """
        logger.debug(event)

        logger.info('stores msg in db')
        self.store_msg(event)

        if event['content'].get('msgtype') == 'm.text' and event['sender'] != \
                self.uid:

            # add config to event
            event['config'] = self.config

            # gives event to mossbot and watching out for a return message
            msg = MOSS.serve(event)

            if msg and msg.data:

                if msg.type == 'text':
                    logger.info('sending text msg...')
                    room.send_text(msg.data)

                elif msg.type == 'notice':
                    logger.info('sending notice msg...')
                    room.send_notice(msg.data)

                elif msg.type == 'html':
                    logger.info('sending html msg...')
                    room.send_html(msg.data)

                elif msg.type == 'image':
                    logger.info('sending image msg...')
                    self.write_media('image', room, msg.data)

                else:
                    logger.error('could not recognize msg type "%s"', msg[0])

            elif msg and msg.type == 'skip':
                logger.info('skipping msg...')

            else:
                logger.debug('no matching in event')

    def on_invite(self, room_id, state):
        """Callback for recieving invites"""
        logger.info('got invite for room %s', room_id)
        self.client.join_room(room_id)

    def listen_forever(self, timeout_ms: int = 30000) -> None:
        """Loop to run _sync in a process"""
        while True:

            try:
                # pylint: disable=protected-access
                self.client._sync(timeout_ms)
            except BaseException as e:
                logger.exception('problem with sync: %s', e)
                time.sleep(10)

            time.sleep(0.1)

    def start_listener_process(self, timeout_ms: int = 30000) -> None:
        """Create sync process."""
        self.sync_process = Process(
            target=self.listen_forever,
            args=(timeout_ms, ),
            daemon=True,
        )
        self.sync_process.start()

    def connect(self) -> None:
        """Connection handler."""
        while True:

            try:

                logger.info('create matrix client')
                self.client = MatrixClient(self.hostname)

                logger.info('login with password')
                self.client.login_with_password(self.username, self.password)

                for room_id in self.client.get_rooms():
                    logger.info('join room %s', room_id)

                    room = self.client.join_room(room_id)
                    room.add_listener(self.on_message)

                self.client.add_invite_listener(self.on_invite)
                self.start_listener_process()

                start_time = pendulum.now()
                while True:

                    if pendulum.now() >= start_time.add(minutes=10):

                        logger.info('planed reconnect')
                        self.sync_process.terminate()

                        break

                    time.sleep(0.1)

            except KeyboardInterrupt:
                logger.info('GoodBye')
                self.sync_process.terminate()
                sys.exit()

            except BaseException as e:
                logger.exception('problem while try to connect: %s', e)
                time.sleep(10)

    def write_media(self, media_type: str, room: Room, url: str) -> None:
        """Get media, upload it and post to room
        """
        # image is the only media type supported right now
        if media_type != 'image':
            logger.error('%s as media type is not supported', media_type)
            return None

        # getting image and analyze it
        logger.info('download %s', url)
        image_data = get_image(url)
        logger.debug('got image_data: %s', image_data)

        if not image_data:
            logger.error('got no image_data')
            return

        # analyze image file and create image info dict
        media_info = {}  # type: Dict[str, Union[str, int, BytesIO, None]]

        # getting mimetype
        media_info['mimetype'] = image_data.get('content-type')
        if not media_info['mimetype']:
            media_info['mimetype'] = mimetypes.guess_type(url)[0]

        # getting name
        name = urlsplit(url).path.split('/')[-1]

        # image size
        media_info['h'] = image_data.get('height')
        media_info['w'] = image_data.get('width')

        logger.debug('media_info content: %s', media_info)

        # upload it to homeserver
        logger.info('upload file')
        uploaded = self.client.upload(image_data['image'],
                                      media_info['mimetype'])
        logger.debug('upload: %s', uploaded)

        # send image to room
        logger.info('send media: %s', name)
        room.send_image(uploaded, name, **media_info)

    def store_msg(self, event: Dict) -> None:
        """Store msgs in a db"""
        logger.debug('got event to store: %s', str(event))

        try:

            msgs_table = self.db.table('msgs')

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

                all_sender_msgs = msgs_table.search(
                    self.stored_msg.sender == event['sender'])

                while len(all_sender_msgs) >= 10:

                    msgs_table.remove(doc_ids=[all_sender_msgs[0].doc_id])

                    all_sender_msgs = msgs_table.search(
                        self.stored_msg.sender == event['sender'])

                msgs_table.insert({
                    'sender': event['sender'],
                    'body': event['content']['body'],
                })

        except BaseException:
            logger.exception('could not store msg')

            return None
Пример #9
0
def main():
    global client
    global data
    global log
    global lock

    lock = threading.RLock()

    log.debug("try lock before main load_data()")
    with lock:
        log.debug("success lock before main load_data()")
        data = load_data()

    #FIXME отладка парсера
    #data["users"]["@progserega:matrix.org"]={}
    #data["users"]["@progserega:matrix.org"]["test"]={}
    #data["users"]["@progserega:matrix.org"]["test"]["alarms"]=[]
    #data["users"]["@progserega:matrix.org"]["test"]["lang"]="ru"
    #print(process_alarm_cmd("@progserega:matrix.org","test","напомни послезавтра после работы проверить звук в машине и подтёки масла, т.к. 11 июня закончится гарантия."))
    #sys.exit(1)

    log.info("try init matrix-client")
    client = MatrixClient(conf.server)
    log.info("success init matrix-client")

    try:
        log.info("try login matrix-client")
        client.login_with_password(username=conf.username,
                                   password=conf.password)
        log.info("success login matrix-client")
    except MatrixRequestError as e:
        print(e)
        log.debug(e)
        if e.code == 403:
            log.error("Bad username or password.")
            sys.exit(4)
        else:
            log.error("Check your sever details are correct.")
            sys.exit(2)
    except MissingSchema as e:
        log.error("Bad URL format.")
        print(e)
        log.debug(e)
        sys.exit(3)

    log.info("try init listeners")
    client.add_listener(on_message)
    client.add_ephemeral_listener(on_event)
    client.add_invite_listener(on_invite)
    #client.start_listener_thread()
    # Слушанье сокета и пересоединение в случае сбоя:
    #client.listen_forever(timeout_ms=30000, exception_handler=exception_handler,bad_sync_timeout=5)
    client.start_listener_thread(exception_handler=exception_handler)
    #client.listen_forever(timeout_ms=30000, exception_handler=exception_handler,bad_sync_timeout=5)
    #client.listen_forever()
    log.info("success init listeners")

    x = 0
    log.info("enter main loop")
    while True:
        # Проверяем уведомления:
        log.debug("try lock before main loop")
        with lock:
            log.debug("success lock before main loop")
            for user in data["users"]:
                for room in data["users"][user]:
                    for item in data["users"][user][room]["alarms"]:
                        alarm_timestamp = item["time"]
                        alarm_text = item["text"]
                        time_now = time.time()
                        if alarm_timestamp < time_now:
                            # Уведомляем:
                            html = "<p><strong>Напоминаю Вам:</strong></p>\n<ul>\n"
                            html += "<li>%s</li>\n" % alarm_text
                            html += "</ul>\n"
                            if send_html(room, html) == True:
                                data["users"][user][room]["alarms"].remove(
                                    item)
                                save_data(data)
                                break  # выходим из текущего цикла, т.к. изменили количество в маассиве (валится в корку) - следующей проверкой проверим оставшиеся
                            else:
                                log.error(
                                    "error send alarm at '%s' with text: '%s'"
                                    % (time.strftime(
                                        "%Y.%m.%d-%T",
                                        time.localtime(alarm_timestamp)),
                                       alarm_text))

        #print("step %d"%x)
        #x+=1
        time.sleep(10)
    log.info("exit main loop")
Пример #10
0
class JokeBot:
    bot_startcmd = '!joke'
    bot_display_name = 'JokeBot'
    auto_join_invited_rooms = True
    mcl = None
    init_done = False
    admin_ids = set()

    def __init__(self, filename=CONFIG_FILENAME):
        logging.debug('load config')
        config_dic = load_yaml_config(filename)
        matrix_server = config_dic['matrix_server']
        login_with_token = False
        if matrix_server.get('token', ''):
            if not matrix_server.get('user_id', ''):
                matrix_server['user_id'] = config_dic['matrix_user'][
                    'username']
            login_with_token = True
        else:
            matrix_user = config_dic['matrix_user']

        bot_startcmd = config_dic.get('bot_startcmd')
        if bot_startcmd:
            self.bot_startcmd = bot_startcmd
        bot_display_name = config_dic.get('bot_display_name')
        if bot_display_name:
            self.bot_display_name = bot_display_name
        self.auto_join_invited_rooms = config_dic.get(
            'auto_join_invited_rooms', True)
        self.admin_ids = set(config_dic.get('admin_ids', []))

        logging.debug('init bot')

        if login_with_token:
            logging.debug('init bot with token')
            self.mcl = MatrixClient(**matrix_server)
        else:
            logging.debug('init bot with password')
            self.mcl = MatrixClient(**matrix_server)
            self.mcl.login_with_password_no_sync(**matrix_user)

        m_user = self.mcl.get_user(self.mcl.user_id)
        if m_user.get_display_name() != self.bot_display_name:
            m_user.set_display_name(self.bot_display_name)

        self.mcl.add_invite_listener(self.process_invite)
        self.mcl.add_listener(self.process_message, 'm.room.message')

        self.init_done = True
        logging.info('bot initialization successful')

    def run(self):
        if self.init_done:
            logging.debug('run listen_forever')
            self.mcl.listen_forever()
        else:
            logging.warning('bot not initialized successful')

    def join_room(self, room_id):
        self.ignore_room_temporary(
            room_id
        )  # necessary while joining room because otherwise old messages would be processed
        try:
            logging.info('joining new room {}'.format(room_id))
            room = self.mcl.join_room(room_id)
            room.send_text(
                "Welcome! I'm a joke bot. Type '{}' and I will tell you a joke."
                .format(self.bot_startcmd))
            return True
        except:
            logging.exception(
                'Exception while joining room {}'.format(room_id))
            return False

    temp_ignored_rooms = set()

    def temp_ignore_room_thread(self, room_id):
        logging.debug('temporary ignoring room {}'.format(room_id))
        self.temp_ignored_rooms.add(room_id)
        time.sleep(10)
        self.temp_ignored_rooms.remove(room_id)
        logging.debug('not ignoring room {} any more'.format(room_id))

    def ignore_room_temporary(self, room_id):
        threading.Thread(target=self.temp_ignore_room_thread,
                         args=[room_id],
                         daemon=True).start()

    def leave_room(self, room_id):
        logging.debug('trying to leave room with id {}'.format(room_id))
        leave_room = self.mcl.get_rooms().get(room_id, '')
        if not leave_room:
            logging.debug('bot not in room with id {}'.format(room_id))
            return False
        if leave_room.leave():
            logging.debug('leaving room {} was successful'.format(room_id))
            return True
        else:
            logging.debug('failed to leave known room with id {}'.format(
                leave_room.room_id))
        return False

    def process_invite(self, room_id, state=None):
        logging.debug('received invitation of {}'.format(room_id))
        if self.auto_join_invited_rooms:
            self.join_room(room_id)

    def evaluate_bot_message(self, room, sender, msg):
        if msg.startswith('ctl'):
            logging.debug("received control message '{}' in room '{}'".format(
                msg, room.room_id))
            if sender not in self.admin_ids:
                logging.debug(
                    '{} has no permissions to send a ctl-message'.format(
                        sender))
                room.send_notice(
                    '{} has no permissions to send a ctl-message'.format(
                        sender))
                return
            data = msg.split(' ')[1:]
            if len(data) == 2:
                if data[0] == 'join':
                    if not self.join_room(data[1]):
                        room.send_notice(
                            'something went wrong while joining room')
                elif data[0] == 'leave':
                    if data[1] == 'this':
                        data[1] = room.room_id
                    if not self.leave_room(data[1]):
                        room.send_notice('room could not be left')
            return

        logging.info('sending joke to room {}'.format(room.room_id))
        answer = '...'
        data = msg.split(' ')[1:]  # remove first empty string
        if len(data) == 0:
            answer = get_joke()
        elif len(data) == 1:
            answer = get_joke(data[0])
        elif len(data) == 2:
            answer = get_joke(data[0], data[1])
        logging.debug('starting room send text')
        room.send_text(answer)
        logging.debug('done room send text')

    def process_message(self, roomchunk):
        if roomchunk['sender'] == self.mcl.user_id:
            return

        if roomchunk['room_id'] in self.temp_ignored_rooms:
            logging.debug('ignoring room {} temporary'.format(
                roomchunk['room_id']))
            return

        content = roomchunk['content']
        if content['msgtype'] == 'm.text':
            msg = content['body']
            if msg.startswith(self.bot_startcmd):
                room = self.mcl.get_rooms()[roomchunk['room_id']]
                msg = msg[len(self.bot_startcmd):]
                self.evaluate_bot_message(room, roomchunk['sender'], msg)
Пример #11
0
import time
from matrix_client.client import MatrixClient

host = "https://ansuddin.xyz"
user_id = "@ans:ansuddin.xyz"
password = "******"
#device_id = "device_id"
room_id = "!fLhiSuTZAupSQpcEXs:ansuddin.xyz"


client = MatrixClient(host)
token = client.login_with_password(username="******", password="******")

def on_invite(room_id, state):
        print("received invite");
        print ("joining room " + room_id)
        room = client.join_room(room_id)


client.add_invite_listener(on_invite)
client.start_listener_thread()

while(True):
	time.sleep(5)
Пример #12
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)
Пример #13
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())
    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)
      self.join_timestamps[room] = datetime.datetime.now(pytz.utc)
    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):
    if(room in self.room_occupants):
      return list(self.room_occupants[room])
    else:
      memberdict = room.room.get_joined_members()
      users = [ self.new_user(x) for x in memberdict ]
      self.room_occupants[room] = set(users)
      return users

  # @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)
Пример #14
0
class MatrixBot:
    def __init__(self, username, password, server, allowed_rooms=None):
        self.username = username
        self._username_re = re.compile('@{}'.format(re.escape(username)), re.I)
        self.server = server
        self.handlers = []
        self.rooms = {}
        self.client = MatrixClient(server)
        try:
            self.client.login_with_password(username, password)
        except MatrixRequestError as error:
            if error.code == 403:
                logger.error('Username and/or password mismatch')
            raise
        self._join_rooms(allowed_rooms)
        if not self.rooms:
            logger.info('No rooms given, listening for invitations')
            self.client.add_invite_listener(self.handle_invite)
            for room_id, room in self.client.rooms.items():
                room.add_listener(self.handle_message)
                self._add_room(room_id, room)

    def _add_room(self, room_id, room):
        logger.debug('Adding room (ID: %s): %s', room_id, room)
        self.rooms[room_id] = room

    def _join_rooms(self, room_ids):
        for room_id in room_ids:
            room = self.client.join_room(room_id)
            room.add_listener(self.handle_message)
            self._add_room(room_id, room)

    def send(self, msg, room_ids=None):
        rooms = room_ids or self.rooms.keys()
        for room_id in rooms:
            room = self.rooms.get(room_id)
            if room:
                room.send_text(msg)

    def handle_message(self, room, event):
        if self._username_re.match(event['sender']):
            # Don't handle own messages
            return
        for handler in self.handlers:
            assert isinstance(handler, BaseHandler)
            if handler.should_run(room, event):
                try:
                    handler(room, event)
                except Exception:
                    logger.exception('Error while calling handler function')

    def handle_invite(self, room_id, state):
        logger.info('Invitation received to: %s', room_id)
        logger.info('Trying to join now...')
        room = self.client.join_room(room_id)
        room.add_listener(self.handle_message)
        self.rooms[room_id] = room

    def start(self):
        """Starts listening for messages in a new thread. If the calling
        script has nothing more to do, it can simply do:
        >>> bot = MatrixBot(username, password, server, allowed_rooms)
        >>> bot_thread = bot.start()
        >>> bot_thread.join()
        This will run forever.
        """
        self.client.start_listener_thread()
        return self.client.sync_thread

    def stop(self):
        self.client.stop_listener_thread()
Пример #15
0
class MatrixBot:
    def connect(self):
        try:
            logger.info("connecting to {}".format(self.server))
            self.client = MatrixClient(self.server)
            self.client.login_with_password(self.username, self.password)
            logger.info("connection established")

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

            # listen for invites and automatically accept them
            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)

        except Exception:
            logger.warning("connection to {} failed".format(self.server) +
                           ", retrying in 5 seconds...")
            sleep(5)
            self.connect()

    # 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
        self.password = password
        self.server = server
        self.rooms = rooms

        self.connect()

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

    def handle_message(self, room, event):
        # Make sure we didn't send this message
        if re.match("@" + self.username, event['sender']):
            return

        try:
            if event['type'] == "m.room.message":
                rx_msg = event['content']['body']
                if re.search(r"#book", rx_msg):
                    book_url = self.get_book_url(rx_msg)
                    print('found book: ' + book_url)
                    if (book_url):
                        img_info = self.get_book_img(book_url)
                        img = requests.get(img_info['url'])
                        mxc_url = self.client.upload(img.content, 'image/jpeg')
                        #html = "<a href='"+book_url+"'><img src='"+mxc_url+"'/></a>"
                        #print(html)
                        room.send_image(mxc_url, img_info['title'])
                        room.send_text(book_url)
        except (TypeError):
            pass

    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)

        print("Joined!")

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

    def get_book_url(self, search_string):
        search_words = search_string.split()
        filtered_search_words = filter(lambda w: not re.search(r"#book", w),
                                       search_words)
        query = '+'.join(filtered_search_words)
        page = requests.get("https://www.goodreads.com/search?q=" + query)
        soup = BeautifulSoup(page.content, 'html.parser')
        results = soup.find_all('a', class_='bookTitle')
        if (len(results) == 0):
            return None
        url = 'https://goodreads.com' + results[0]['href'].split('-')[0].split(
            '.')[0]
        return url

    def get_book_img(self, book_url):
        page = requests.get(book_url)
        soup = BeautifulSoup(page.content, 'html.parser')
        img_tag = soup.find("img", {"id": "coverImage"})
        h1_tag = soup.find("h1", {"id": "bookTitle"})
        return {'title': h1_tag.text, 'url': img_tag['src']}

    def start_polling(self):
        # Starts polling for messages
        self.client.start_listener_thread(
            exception_handler=lambda e: self.connect())
        return self.client.sync_thread
Пример #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)
        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 handle_message(self, room, event):
        # Make sure we didn't send this message
        if re.match("@" + self.username, event['sender']):
            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):
        inviting_user = state['events'][1]['sender']
        print("Got invite to room: {} by {}.".format(str(room_id),
                                                     inviting_user))

        # Check if we're allowed to be invited by the whitelist
        with open('user_whitelist.txt') as file:
            users = file.readlines()

        if [user for user in users if re.match(inviting_user, user)]:
            print("Accepting invite from {}. Joining room.".format(
                inviting_user))
            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)
        else:
            print(
                "Not accepting invite from: {}. Add to user_whitelist.txt to enable access."
                .format(inviting_user))

    def set_matrix_avatar(self, url):
        avatar = MatrixClientUser.set_avatar_url(self, url)
        return avatar

    def start_polling(self):
        # Starts polling for messages
        self.client.start_listener_thread()
        return self.client.sync_thread
Пример #17
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)
Пример #18
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)
        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 handle_message(self, room, event):
        # Make sure we didn't send this message
        if re.match("@" + self.username, event['sender']):
            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
                handler.handle_callback(room, event)

    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()
Пример #19
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
Пример #20
0
def main():
    global client
    global config
    global github
    global repo
    global msc_labels
    global logger
    global room_specific_data

    # Retrieve login information from config file
    with open("config.toml", "r") as f:
        try:
            config = toml.loads(f.read())
        except:
            log_fatal("Error reading config file:")
            return

    # Configure logging
    # Determine whether we are using a logfile or not
    logging_format = "[%(levelname)s] %(asctime)s: %(message)s"
    if "logfile" in config["logging"]:
        logging.basicConfig(
            level=logging.INFO if config["logging"]["level"] != "DEBUG" else logging.DEBUG,
            format=logging_format,
            filename=config["logging"]["logfile"])
    else:
        logging.basicConfig(
            level=logging.INFO if config["logging"]["level"] != "DEBUG" else logging.DEBUG,
            format=logging_format)
    logger = logging.getLogger()

    # Retrieve room-specific data if config file exists
    if "data_filepath" in config["bot"]:
        data_filepath = config["bot"]["data_filepath"]
        if os.path.exists(config["bot"]["data_filepath"]):
            with open(data_filepath, 'r') as f:
                room_specific_data = json.loads(f.read())

    # Schedule daily summary messages per-room
    for room_id in room_specific_data.keys():
        # Check if summaries are enabled in this room
        if get_room_setting(room_id, "summary_enabled") == False:
            continue

        # Check if this room has a custom summary time
        if get_room_setting(room_id, "summary_time"):
            # Set a scheduler for that time
            # Tag with the room ID so we can easily cancel later if necessary
            schedule.every().day.at(config["bot"]["daily_summary_time"]).do(send_summary,
                                                                            room_id).tag(
                room_id)

    # Schedule daily summary messages to rooms that do not have a custom time
    set_up_default_summaries()

    # Login to Github
    github = Github(config["github"]["token"])
    repo = github.get_repo(config["github"]["repo"])
    log_info("Connected to Github")

    # Get MSC-related label objects from specified Github repository
    labels = config["github"]["labels"]
    msc_labels = {label.name: label for label in repo.get_labels() if label.name in labels}

    # Login to Matrix and listen for messages
    homeserver = "https://" + config["matrix"]["user_id"].split(":")[-1]
    client = MatrixClient(homeserver, user_id=config["matrix"]["user_id"],
                          token=config["matrix"]["token"])
    client.add_invite_listener(invite_received)
    client.add_listener(event_received, event_type="m.room.message")
    log_info("Connected to Matrix")

    # Sync continuously and check time for daily summary sending
    while True:
        try:
            client.listen_for_events()
        except:
            log_warn("Unable to contact /sync")
        schedule.run_pending()
        time.sleep(config["matrix"]["sync_interval"])  # Wait a few seconds between syncs
Пример #21
0
class MpyBot:
	def __init__(self, configfile, run=True):
		logger.debug('load config')
		config_dic = load_yaml_config(configfile)
		self.bot_startcmd = config_dic.get('bot_startcmd', STARTCMD)

		self._full_cmds = {}
		self._local_cmds = {}
		self._module_calls = {}
		for moduledic in config_dic.get('modules', []):
			self.add_module(moduledic)

		matrix_server = config_dic['matrix_server']

		logger.debug('init bot')
		self.mcl = MatrixClient(**matrix_server)

		self.auto_join_invited_rooms = config_dic.get('auto_join_invited_rooms', True)
		self.auto_join_servers = set(config_dic.get('auto_join_servers', []))

		self.admin_ids = set(config_dic.get('admin_ids', []))

		disp_name = config_dic.get('bot_display_name', '')
		if disp_name:
			user = self.mcl.get_user(self.mcl.user_id)
			if user.get_display_name() != disp_name:
				user.set_display_name(disp_name)

		self.mcl.add_invite_listener(self._process_invite)
		self.mcl.add_listener(self._process_message, 'm.room.message')

		logger.info('bot initialized')

		if run:
			self._run()

	def _run(self):
		logger.debug('run listen_forever')
		self.mcl.listen_forever()


	def join_room(self, room_id):
		try:
			logger.info('joining new room {}'.format(room_id))
			room = self.mcl.join_room(room_id)
			room.send_text("Welcome! Type {} to control me.".format(self.bot_startcmd))
			return True
		except MatrixError as e:
			logger.exception('{} while joining room {}'.format(repr(e), room_id))
			return False

	def leave_room(self, room_id):
		logger.info('trying to leave room with id {}'.format(room_id))
		leave_room = self.mcl.get_rooms().get(room_id, '')
		if not leave_room:
			logger.debug('bot not in room {}'.format(room_id))
			return False
		if leave_room.leave():
			logger.debug('left room {}'.format(room_id))
			return True
		else:
			logger.debug('failed to leave known room with id {}'.format(leave_room.room_id))
		return False


	def _process_invite(self, room_id, state=None):
		logger.debug('received invitation to {}, state: {}'.format(room_id, state))
		if self.auto_join_invited_rooms:
			if self.auto_join_servers and \
					room_id.split(':')[-1] not in self.auto_join_servers:
				return
			self.join_room(room_id)

	def _process_message(self, roomchunk):
		if roomchunk['sender'] == self.mcl.user_id:
			return

		age = roomchunk.get('unsigned', {}).get('age')
		if age is None:
			# fallback
			age = abs(time.time() - roomchunk['origin_server_ts']/1000)
		else:
			age /= 1000

		if age > 60:
			logger.debug('received old message in {}, event_id: {}'.format(roomchunk['room_id'], roomchunk['event_id']))
			return

		content = roomchunk['content']
		if content['msgtype'] == 'm.text':
			msg = content['body'].lstrip()
			if msg.startswith(self.bot_startcmd):
				room = self.mcl.get_rooms()[roomchunk['room_id']]
				msg = msg[len(self.bot_startcmd):].lstrip()
				self._evaluate_bot_message(room, roomchunk['sender'], msg)
			else:
				s_msg = msg.split(' ', 1)
				cmd = s_msg[0]
				msg = s_msg[1] if len(s_msg) > 1 else ''
				modulename = self._full_cmds.get(cmd)
				if modulename:
					room = self.mcl.get_rooms()[roomchunk['room_id']]
					self._call_module(modulename, room, roomchunk['sender'], msg)

	def _evaluate_bot_message(self, room, sender, msg):
		if msg.startswith('ctl'):
			logger.debug("received control message '{}' in room '{}'".format(msg, room.room_id))
			if sender not in self.admin_ids:
				logger.debug('{} has no permissions to send a ctl-message'.format(sender))
				room.send_notice('{} has no permissions to send a ctl-message'.format(sender))
				return
			data = msg.split()[1:]
			if len(data) == 2:
				if data[0] == 'join':
					if not self.join_room(data[1]):
						room.send_notice('something went wrong while joining room')
				elif data[0] == 'leave':
					if data[1] == 'this':
						data[1] = room.room_id
					if not self.leave_room(data[1]):
						room.send_notice('room could not be left')
			return

		elif msg.startswith('-'):
			msg = msg[1:].strip()
			if msg.startswith('help'):
				text = 'Available local commands:\n'
				for k in self._local_cmds:
					text += ' - ' + k + '\n'
				text += 'Available full commands:\n'
				for k in self._full_cmds:
					text += ' - ' + k + '\n'
				room.send_text(text)
			elif msg.startswith('time'):
				room.send_text('UTC: {:.0f}'.format(time.time()))

		s_msg = msg.split(' ', 1)
		cmd = s_msg[0]
		msg = s_msg[1] if len(s_msg) > 1 else ''

		modulename = self._local_cmds.get(cmd)
		if modulename:
			self._call_module(modulename, room, sender, msg)


	def add_module(self, moduledic):
		try:
			name = moduledic['name']
			module = importlib.import_module('modules.' + name)
			opt = moduledic.get('options')
			logging.debug('here')
			if opt:
				module.set_options(opt)

			self._module_calls[name] = module.msg_call

			cmd = moduledic.get('local_cmd')
			if cmd:
				self._local_cmds[cmd] = name
			cmd = moduledic.get('full_cmd')
			if cmd:
				self._full_cmds[cmd] = name

			logger.info('module {} added'.format(moduledic))
		except Exception as e:
			logger.exception('not possible to add module {}: {}'.format(moduledic, repr(e)))

	def _call_module(self, modulename, room, sender, msg):
		logger.debug("modulecall {} for message '{}' in room {}".format(modulename, msg, room.room_id))
		try:
			res = self._module_calls[modulename](room=room, sender=sender, msg=msg)
			if res and isinstance(res, str):
				room.send_text(res)
		except Exception as e:
			logger.exception('Failed to call module {}'.format(modulename))
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
    # accept_invites    - If true, accept invites to rooms
    def __init__(self,
                 username,
                 password,
                 server,
                 rooms=None,
                 accept_invites=True):
        self.username = username

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

        # Store allowed rooms. If rooms is None, store empty list
        # of rooms and add all rooms we're currently in
        if rooms:
            self.rooms = rooms
        else:
            self.rooms = []
            for room in self.client.rooms.values():
                self.rooms.append(room)
        print('Total rooms: ', len(self.rooms))
        # Store empty list of handlers
        self.handlers = []

        # we should listen for invites and automatically accept them
        if accept_invites:
            self.client.add_invite_listener(self.handle_invite)

        # Add handlers for all rooms
        for room in self.rooms:
            room.add_listener(self.handle_message)

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

    def handle_message(self, room, event):
        # Make sure we didn't send this message
        if re.match("@" + self.username, event['sender']):
            return

        # Loop through all installed handlers and see if they need to be called
        for handler in self.handlers:
            if handler.test_callback(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 send_message(self, message, room_id=None, room_alias=None):
        if not room_id:
            if not room_alias:  # send to all rooms if no room specified
                for room in self.rooms:
                    room.send_text(message)
                return True
            else:  # no ID but we have alias, so get room_id from it
                room_id = self.client.api.get_room_id(room_alias)
        room = self.client.rooms.get(room_id)
        if room and room in self.rooms:
            room.send_text(message)
            return True
        return False

    def start_polling(self):
        # Starts polling for messages
        self.client.start_listener_thread()
        return self.client.sync_thread
Пример #23
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
    # token    - Optional token, if set password is ignored and username is
    #            asumed to be a user_id (@<username>:<domain>)
    def __init__(self, username, password, server, rooms=None, token=None):
        self.username = username

        # Authenticate with given credentials
        self.client = MatrixClient(server, user_id=username, token=token)
        if token is None:
            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 empty list of handlers
        self.handlers = []

        # If rooms is None, we should listen for invites
        # and automatically accept them
        self.rooms = []

        # Store allowed room ids
        self.room_ids = []
        if rooms is None:
            self.client.add_invite_listener(self.handle_invite)

            # 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)
                self.room_ids.append(room_id)
        else:
            self.client.add_invite_listener(self.handle_invite)

            # Add the message callback for all specified rooms
            for room in rooms:
                try:
                    # If room is a string we assume it is a room_id
                    # Otherwise, we assume it a room object for backwards
                    # compatibility sake.
                    if isinstance(room, str):
                        _room = self.client.join_room(room)

                    else:
                        _room = room

                    _room.add_listener(self.handle_message)
                    self.rooms.append(_room)
                    self.room_ids.append(_room.room_id)

                except MatrixRequestError as error:
                    print(error)
                    if error.code == 403:
                        print('You likely need to invite the bot')

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

    def handle_message(self, room, event):
        # Make sure we didn't send this message
        if re.match("@" + self.username, event['sender']):
            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))
        if self.room_ids is None or room_id in self.room_ids:
            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)

        else:
            print("Room not in allowed rooms list")

    def start_polling(self):
        # Starts polling for messages
        self.client.start_listener_thread()
        return self.client.sync_thread
Пример #24
0
def main():
  global client
  global data
  global log
  global lock

  lock = threading.RLock()

  log.debug("try lock before main load_data()")
  with lock:
    log.debug("success lock before main load_data()")
    data=load_data()
  log.debug("release lock() after access global data")

  log.info("try init matrix-client")
  client = MatrixClient(conf.server)
  log.info("success init matrix-client")

  try:
      log.info("try login matrix-client")
      token = client.login(username=conf.username, password=conf.password,device_id=conf.device_id)
      log.info("success login matrix-client")
  except MatrixRequestError as e:
      print(e)
      log.debug(e)
      if e.code == 403:
          log.error("Bad username or password.")
          sys.exit(4)
      else:
          log.error("Check your sever details are correct.")
          sys.exit(2)
  except MissingSchema as e:
      log.error("Bad URL format.")
      print(e)
      log.debug(e)
      sys.exit(3)

  log.info("try init listeners")
  client.add_listener(on_message)
  client.add_ephemeral_listener(on_event)
  client.add_invite_listener(on_invite)
  client.start_listener_thread(exception_handler=exception_handler)
  log.info("success init listeners")

  try:
    x=0
    log.info("enter main loop")
    while True:
      if conf.type_translate == "yandex_long":
        # check yandex_long_jobs:
        num_jobs=0
        for room_id in data["rooms"]:
          if "jobs" in data["rooms"][room_id]:
            for job in data["rooms"][room_id]["jobs"]:
              ret_value=False
              num_jobs+=1
              ret_value=check_long_yandex_job(log,room_id,data["rooms"][room_id]["jobs"],job)
              if ret_value==False:
                log.error("check_long_yandex_job(), room_id=%s, job_id=%s"%(room_id,job["id"]))
                result_string="error get result from yandex speech api - yandex api error"
                log.error(result_string)
                if send_notice(room_id,result_string)==False:
                  log.error("send_notice(%s)"%room_id)
        if num_jobs > 0:
          log.debug("len jobs list for all rooms = %d"%num_jobs)
      time.sleep(3)
  except Exception as e:
    log.error(get_exception_traceback_descr(e))
    log.error("exception at main loop check jobs: %s"%e)
    sys.exit(1)

  log.info("exit main loop")
Пример #25
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
Пример #26
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)
Пример #27
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)
Пример #28
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)
Пример #29
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()
Пример #30
0
        room = client.join_room(room_id)
        botinfo = info
        botinfo['room_id'] = room_id
        botinfo['room'] = room
        botinfo['state'] = state
        botinfo['caller'] = state['events'][0]['sender']
        bf[room_id] = BabelFish(botinfo)
        room.add_listener(handle_message)
        print('joined room %s' % room_id)
    else:
        print(
            'Unauthorized user access in room %s. I will not join this room.' %
            room_id)


# **** Main ****
if __name__ == '__main__':
    client = MatrixClient(SERVER)
    client.login_with_password(USERNAME, PASSWORD)
    print('Login as %s successful' % USERNAME)
    client.add_invite_listener(handle_invite)
    for room_id, room in client.get_rooms().items():
        botinfo = info
        botinfo['room_id'] = room_id
        botinfo['room'] = room
        bf[room_id] = BabelFish(botinfo)
        room.add_listener(handle_message)
    client.start_listener_thread()
    print('Listeners started successfully')
    while True:
        time.sleep(0.2)
Пример #31
0
class GfyrslfBot:
    def __init__(self, cfg='config.yml'):
        logging.debug("Opening config from: {}".format(cfg))
        with open(cfg, 'r') as cfgfile:
            self.cfg = yaml.load(cfgfile, Loader=yaml.Loader)

        # Add commands from config file
        self.commands = {}
        for cmdname in self.cfg['commands'].keys():
            self.add_command(cmdname)

        # Create client object and login to Matrix server
        self.client = MatrixClient(self.cfg['matrix']['server'])
        try:
            self.client.login(username=self.cfg['matrix']['username'],
                              password=self.cfg['matrix']['password'],
                              sync=True)
        except MatrixRequestError as e:
            logging.error(e)
            if e.code == 403:
                logging.error("Bad username/password")
        except Exception as e:
            logging.error("Invalid server URL: {}".format(e))
            traceback.print_exc()
            exit()

        # Set display name TODO: Clean up with try/except
        if 'display_name' in self.cfg['bot']:
            self.client.api.set_display_name(self.client.user_id,
                                             self.cfg['bot']['display_name'])

        # Set avatar image (if not set, or if changed)
        avatar_url = self.client.api.get_avatar_url(self.client.user_id)
        if avatar_url is not None:
            # TODO: check for differences
            pass
        else:
            if 'avatar_url' in self.cfg['bot']:
                self.client.api.set_avatar_url(self.client.user_id,
                                               self.cfg['bot']['avatar_url'])

        # Automatically accept invites
        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.rooms.items():
            room.add_listener(self.handle_message)
            self.rooms.append(room_id)

    def add_command(self, cmdname):
        # Add command modules
        cmdcfg = self.cfg['commands'][cmdname]
        module = str('gfyrslf.commands.' + cmdname)
        classname = cmdcfg['classname']
        logging.debug("Adding command class '{}' from module '{}'".format(
            classname, module))
        try:
            mod = __import__(module, fromlist=[classname])
            cls = getattr(mod, classname)
            inst = cls(cmdname, self.cfg)
            self.commands[cmdname] = inst
        except Exception as e:
            logging.error(
                "Failed to load command classname '{}' from module '{}': {}".
                format(classname, module, e))
            traceback.print_exc()

    def handle_exception(self, e):
        logging.debug("Got exception in listen thread: {}".format(e))
        pass

    def handle_message(self, room, event):
        # Make sure we didn't send this message
        if event["sender"] == self.client.user_id:
            return

        # Check commands for matches
        logging.debug("Got non-self event from {} in {}".format(
            event["sender"], event["room_id"]))
        for command in self.commands.values():
            if command.event_test(room, event):
                try:
                    command.event_handler(self, room, event)
                except:
                    traceback.print_exc()

    def handle_invite(self, room_id, state):
        logging.info("Got invite to room '{}', joining...".format(
            str(room_id)))
        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)

        # Announce
        if self.cfg['bot']['announce'] is True:
            room.send_text(
                str("Howdy, I'm {}.\n" +
                    "Type '!{} help' to find out how to (ab)use me.").format(
                        self.cfg['bot']['name'], self.cfg['bot']['name']))

    def run(self):
        # Starts listening for messages
        self.client.start_listener_thread(
            exception_handler=self.handle_exception)
        return self.client.sync_thread