def __init__(self, jid, password, redis_config=None): super(EventBot, self).__init__(jid, password) self._events = collections.defaultdict(list) # Redis init if redis_config is not None: self.REDIS_CONFIG.update(redis_config) self._storage = DataStorage(**self.REDIS_CONFIG) self.add_event_handler('got_offline', self._user_got_offline)
class EventBot(XMPPBot): """ XMPP Bot with listeners and event support Available events: answer_received triggered after the eventbot received answer to particular question question_expired triggered when the question timeouted """ REDIS_CONFIG = { 'host': 'localhost', 'port': 6379, 'db': "" } def __init__(self, jid, password, redis_config=None): super(EventBot, self).__init__(jid, password) self._events = collections.defaultdict(list) # Redis init if redis_config is not None: self.REDIS_CONFIG.update(redis_config) self._storage = DataStorage(**self.REDIS_CONFIG) self.add_event_handler('got_offline', self._user_got_offline) def register_callback(self, event, callback): """ Register callback according to event name. Intended mainly for use by listeners answer_received question_expired groupchat_message_received """ self._events[event].append(callback) def send_question(self, to, text, question_id, timeout=0, **kwargs): """ Send question to the user. Supported additional kwargs: expire_on_offline if set to True the question expires when the user goes offline. If the user is already offline, the question expires immediately. postback_url used by http listener. If specified, the answer will be sent as HTTP POST to this address. only_if_status takes comma separated list of statuses. If the actual user status is not specified in this list then the question will be ignored. """ question = { 'to': to, 'text': text, 'id': question_id, 'expires': datetime.now() + timedelta(seconds=timeout) if timeout else None, 'sent': datetime.now() } question.update(**kwargs) self._storage.set_question(jid=to, question_id=question_id, data=question) # only_if_status checking try: statuses = question['only_if_status'].split(',') if self.get_user_status(jid=to) not in statuses: return except KeyError: pass # send question to the user self.send_chat_message(to, text) def log_chatgroup(self, room, nick=None, password=None): pass def stop_processing(self): self.stop.set() def _trigger_event(self, event_name, data): for callback in self._events[event_name]: gevent.spawn(callback, data) # spawn another greenlet to execute callback (do not care about result) def _user_got_offline(self, presence): # expire all questions which has `expire_on_offline` set to True jid = presence['from'].bare questions = self._storage.get_questions(jid) for question_id, question in questions.items(): try: if question['expire_on_offline']: self._handle_expired_question(question) except KeyError: pass def _remove_question(self, question): """Removes question from redis""" self._storage.delete_questions(question['to'], question['id']) @bot_command(name="reset_to_defaults", min_privilege='admin') def _flush_storage(self): self._storage.clear_database() return "Database reset, please restart bot application" def _handle_expired_question(self, question): self._trigger_event('question_expired', question) self._remove_question(question) def _handle_multiple_questions(self, jid, msg, questions): choice_table = "To which question are you answering?" answer = self._storage.load_answer(jid) # choices were already displayed if answer: mapping = self._storage.get_question_mapping(jid) try: question_number = msg['body'] question_id = mapping[question_number] saved_answer_text = self._storage.load_answer(jid) self._storage.delete_answer(jid) msg['body'] = unicode(saved_answer_text, encoding='utf-8') return self._handle_answer(question_id, questions[question_id], msg) except (KeyError, ValueError): if mapping is not None: choice_table = 'Wrong number received\n\n' + choice_table else: self._storage.save_answer(jid, msg['body']) # save question mapping mapping = {} num = 1 for question_id in questions.keys(): mapping[num] = question_id num += 1 # save mapping to database self._storage.set_questions_mapping(jid, mapping) def _generate_list(_mapping): output = "" for number, question_id in sorted(_mapping.items()): output += u"\n[%d] %s [%s]" % (number, questions[question_id]['text'], question_id) return output choice_table += _generate_list(mapping) msg.reply(choice_table).send() def _handle_answer(self, question_id, question, msg): # reply with confirm_text if present if 'confirm_text' in question: msg.reply(question['confirm_text']).send() # build answer answer = { 'type': 'answer', 'id': question_id, 'from': msg['from'].full, 'answered_after': datetime.now() - question['sent'], 'text': msg['body'], 'msg_thread': msg['id'] } self._trigger_event('answer_received', (question, answer)) self._remove_question(question) def _message_received(self, msg): # trigger event on received groupchat if msg['type'] == 'groupchat': self._trigger_event('groupchat_message_received', msg) # handle answers if msg['type'] in ('chat', 'normal'): jid = msg['from'].bare questions = self._storage.get_questions(jid) if questions: for question_id, question in questions.items(): # handle expired questions if question['expires'] is not None and question['expires'] < datetime.now(): self._handle_expired_question(question) continue if len(questions) > 1: return self._handle_multiple_questions(jid, msg, questions) else: # only one question present - handle answer question_id, question = questions.items()[0] self._handle_answer(question_id, question, msg) super(EventBot, self)._message_received(msg)