Beispiel #1
0
def main(access_token: str, room_id: str, hs: str = 'https://matrix.org'):
    """Listen for events happening in a Matrix room."""
    client = MatrixClient(hs, token=access_token)

    my_room = list(
        filter(lambda x: x[0] == room_id,
               client.get_rooms().items()))[0][1]
    my_room.add_listener(lambda x: print(x))

    client.listen_forever()
Beispiel #2
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))
Beispiel #3
0
def main(matrix_c: MatrixConfig, influx_dsn: str):
    """Listen for events happening in a Matrix room."""
    matrix = MatrixClient(matrix_c.hs, token=matrix_c.access_token)
    influxdb = InfluxDBClient.from_DSN(influx_dsn)

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

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

    matrix.listen_forever()
Beispiel #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)
Beispiel #5
0
class MCat:
    def __init__(self, config):
        self.client = MatrixClient(config['server'])
        self.token = self.client.login_with_password(username=config['user'],
                                                     password=config['password'])
        self.room = self.client.join_room(config['room'])
        self.room_id = config['room']
        self.message_queue = []
        self.limited_until = None

    def dequeue(self):
        """Dequeue as many messages as the server lets us"""
        for message in self.message_queue:
            if time.time() * 1000 < self.limited_until:
                return

            try:
                self.room.send_html(message)
                self.message_queue.pop(0)
            except Exception as e:
                necessary_delay = json.loads(e.content)['retry_after_ms']
                sys.stderr.write("Sleeping for %s seconds... Queue depth %s\n" % (necessary_delay, len(self.message_queue)))
                sys.stderr.flush()
                self.limited_until = time.time() * 1000 + necessary_delay

    def enqueue(self, message):
        self.message_queue.append(message)

    def f_to_matrix(self, f):
        for line in f:
            self.enqueue(line)
            self.dequeue()
        while len(self.message_queue) > 0:
            self.dequeue()

    def matrix_to_f(self, f):
        def stdout_cb(chunk):
            if chunk[u'type'] != u"m.presence" and chunk[u'room_id'] == self.room_id:
                f.write(json.dumps(chunk))
                f.write('\n')
                f.flush()
        self.client.add_listener(stdout_cb)
        self.client.listen_forever()
Beispiel #6
0
class MatrixProtocol(Protocol):
    """
    A Matrix Protocol wrapper.
    """

    def __init__(self, username, password, room, server):
        super().__init__(username, password, room, server)
        self.username = username
        # Connect to Matrix
        self.client = MatrixClient(server)
        self.token = self.client.login_with_password(username=username,
                                                     password=password)

        self.room = self.client.join_room(room)
        self.room.add_listener(self.process_event)

    def listen_forever(self):
        self.room.send_text('My name is PyAstroBot! I seek the grail!')
        try:
            self.client.listen_forever()
        except KeyboardInterrupt:
            pass
        finally:
            self.room.send_text("PyAstroBot is sneaking away and buggering off...")

    def send_message(self, message):
        self.room.send_text(message)

    def send_emote(self, message):
        self.room.send_emote(message)

    def _is_message(self, event):
        if event['type'] == "m.room.message":
            if event['content']['msgtype'] == "m.text":
                # Ignore everything the bot says.
                if event['sender'] != self.username:
                    return event

    def process_event(self, room, event):
        if self._is_message(event):
            for call in self.event_callbacks:
                call(event['content']['body'], event['sender'])
Beispiel #7
0
class Connector:
    """A matrix connector for the Chattie bot framework."""
    def __init__(self, parser):
        """Connect to the matrix room."""
        self.parser = parser
        self.client = MatrixClient(MATRIX_URL)
        # Try to register if it fails try to log in.
        try:
            self.token = self.client.\
                         register_with_password(username=MATRIX_USERNAME,
                                                password=MATRIX_PASSWORD)
        except MatrixRequestError:
            self.token = self.client.\
                         login_with_password(username=MATRIX_USERNAME,
                                             password=MATRIX_PASSWORD)
        self.rooms = {}
        for room in MATRIX_ROOMS.split(","):
            room_name = room
            if not room.startswith("#"):
                room_name = "!" + room
            r = self.client.join_room(room_name)
            r.add_listener(self.parse_event)
            self.rooms[r.room_id] = r

    def listen(self):
        """Listen for messages on Matrix."""
        self.client.listen_forever()

    def send_message(self, room_id, msg):
        """Send a message to Matrix."""
        self.rooms[room_id].send_text(msg)

    def parse_event(self, room, incoming_event):
        """Transform Matrix incoming_event to text."""
        if incoming_event['type'] != 'm.room.message':
            return
        self.parser(incoming_event['room_id'],
                    incoming_event['content']['body'])
Beispiel #8
0
class Connector(connectors.Connector):
    """A matrix connector for the Chattie bot framework."""

    def __init__(self, bot):
        """Connect to the matrix room."""
        self.bot = bot

        self.client = MatrixClient(MATRIX_URL)
        # Try to register if it fails try to log in.
        try:
            self.token = self.client.\
                         register_with_password(username=MATRIX_USERNAME,
                                                password=MATRIX_PASSWORD)
        except MatrixRequestError:
            self.token = self.client.\
                         login_with_password(username=MATRIX_USERNAME,
                                             password=MATRIX_PASSWORD)
        for room in MATRIX_ROOMS.split(","):
            room_name = room
            if not room.startswith("#"):
                room_name = "!" + room
            r = self.client.join_room(room_name)
            r.add_listener(self.parse_event)

    def listen(self):
        """Listen for messages on Matrix."""
        self.client.listen_forever()

    def parse_event(self, room, event):
        """Transform Matrix incoming_event to text."""
        if event['type'] != 'm.room.message':
            return

        resp = self.bot.parse_message(event['content']['body'])

        for r in resp:
            room.send_text(r)
Beispiel #9
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)
Beispiel #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)
Beispiel #11
0
weather_intent = IntentBuilder("WeatherIntent")\
        .require("WeatherKeyword")\
        .optionally("WeatherType")\
        .require('Location')\
        .build()

hide_keyword = ['hide']
for hk in hide_keyword:
    engine.register_entity(hk, "HideKeyword")

hide_intent = IntentBuilder("HideIntent").require("HideKeyword").build()

engine.register_intent_parser(weather_intent)
engine.register_intent_parser(hide_intent)

# Existing user
token = client.login_with_password(username="******",
                                   password=password)

room = client.join_room("#apebot:matrix.org")
room.add_listener(on_message)
room.send_text("ApeBot reporting for duty")

try:
    client.listen_forever()
except KeyboardInterrupt:
    pass
finally:
    room.send_text("ApeBot going to sleep")
Beispiel #12
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))
Beispiel #13
0
class MatrixBackend(ErrBot):
    def __init__(self, config):
        super().__init__(config)
        identity = config.BOT_IDENTITY
        self.token = identity["token"]
        self.url = identity["url"]
        self.user = identity["user"]
        self._client = None

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

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

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

        room, domain, user = None, None, None

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

        return room, domain, user

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

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

        return self.send_stream_content(room_id, content, filename)

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

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

    def change_presence(self):
        pass

    def mode(self):
        pass

    def query_room(self):
        pass

    def rooms(self):
        pass

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

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

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

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

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

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

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

        self._client.listen_forever()
Beispiel #14
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()