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()
class Bot: def __init__(self, homeserver, userID, password, ownerID, dialogflow_project): self.ownerID = ownerID self.userID = userID self.client = MatrixClient(homeserver) token = self.client.login(username=userID, password=password) self.project = dialogflow_project rooms = self.client.get_rooms() for name, room in self.client.get_rooms().items(): room.add_listener(self.onEvent) def report_mainloop_error(self): pass def run(self): self.client.add_invite_listener(self.accept_invite) while True: try: self.client.listen_forever() except KeyboardInterrupt: raise except: self.report_mainloop_error() def accept_invite(self, room_id, state): self.client.join_room(room_id) session_client = dialogflow.SessionsClient() session = session_client.session_path(self.project, room_id) query_input = dialogflow.types.QueryInput( event=dialogflow.types.EventInput(name='WELCOME', language_code='en')) def onEvent(self, room, event): if event['sender'] != self.userID: print("New event in room {} : {}".format(room, event)) if event['type'] == "m.room.message" and event['content'][ 'msgtype'] == "m.text": if event['sender'] == self.ownerID: # admin commands leaveCommand = re.match("!leave (.+)", event['content']['body']) if leaveCommand: self.client.get_rooms()[leaveCommand.group(1)].leave() response = detect_intent(self.project, room.room_id, event['content']['body'], 'en') print("Got DialogFlow response: {}".format(response)) for message in response: for text in message.text.text: room.send_text("{}".format(text))
def 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()
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)
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()
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'])
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'])
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)
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 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)
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")
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 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()
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()