def handle_bot(bot): # type: (str) -> Union[str, BadRequest] lib_module = get_bot_lib_module(bot) if lib_module is None: return BadRequest( "Can't find the configuration or Bot Handler code for bot {}. " "Make sure that the `zulip_bots` package is installed, and " "that your flaskbotrc is set up correctly".format(bot)) client = Client(email=bots_config[bot]["email"], api_key=bots_config[bot]["key"], site=bots_config[bot]["site"]) try: bot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bots', bot) restricted_client = ExternalBotHandler(client, bot_dir) except SystemExit: return BadRequest( "Cannot fetch user profile for bot {}, make sure you have set up the flaskbotrc " "file correctly.".format(bot)) message_handler = lib_module.handler_class() # TODO: Handle stateful bots properly. state_handler = StateHandler() event = request.get_json(force=True) message_handler.handle_message(message=event["message"], bot_handler=restricted_client, state_handler=state_handler) return json.dumps("")
def load_bot_handlers(): # type: () -> Any for bot in available_bots: client = Client(email=bots_config[bot]["email"], api_key=bots_config[bot]["key"], site=bots_config[bot]["site"]) try: bot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bots', bot) # TODO: Figure out how to pass in third party config info. bot_handler = ExternalBotHandler( client, bot_dir, bot_details={}, bot_config_file=None ) bot_handlers[bot] = bot_handler lib_module = get_bot_lib_module(bot) message_handler = lib_module.handler_class() if hasattr(message_handler, 'validate_config'): config_data = bot_handlers[bot].get_config_info(bot) try: lib_module.handler_class.validate_config(config_data) except ConfigValidationError as e: print("There was a problem validating your config file:\n\n{}".format(e)) sys.exit(1) if hasattr(message_handler, 'initialize'): message_handler.initialize(bot_handler=bot_handler) except SystemExit: return BadRequest("Cannot fetch user profile for bot {}, make sure you have set up the flaskbotrc " "file correctly.".format(bot))
def run_message_handler_for_bot(lib_module, quiet, config_file): # Make sure you set up your ~/.zuliprc client = Client(config_file=config_file) restricted_client = RestrictedClient(client) message_handler = lib_module.handler_class() class StateHandler(object): def __init__(self): self.state = None def set_state(self, state): self.state = state def get_state(self): return self.state state_handler = StateHandler() if not quiet: print(message_handler.usage()) def handle_message(message): logging.info('waiting for next message') if message_handler.triage_message(message=message): message_handler.handle_message(message=message, client=restricted_client, state_handler=state_handler) logging.info('starting message handling...') client.call_on_each_message(handle_message)
def run_message_handler_for_bot(lib_module, quiet, config_file): # Make sure you set up your ~/.zuliprc client = Client(config_file=config_file) restricted_client = BotHandlerApi(client) message_handler = lib_module.handler_class() state_handler = StateHandler() if not quiet: print(message_handler.usage()) def extract_query_without_mention(message, client): """ If the bot is the first @mention in the message, then this function returns the message with the bot's @mention removed. Otherwise, it returns None. """ bot_mention = r'^@(\*\*{0}\*\*)'.format(client.full_name) start_with_mention = re.compile(bot_mention).match(message['content']) if start_with_mention is None: return None query_without_mention = message['content'][len(start_with_mention. group()):] return query_without_mention.lstrip() def is_private(message, client): # bot will not reply if the sender name is the same as the bot name # to prevent infinite loop if message['type'] == 'private': return client.full_name != message['sender_full_name'] return False def handle_message(message): logging.info('waiting for next message') # is_mentioned is true if the bot is mentioned at ANY position (not necessarily # the first @mention in the message). is_mentioned = message['is_mentioned'] is_private_message = is_private(message, restricted_client) # Strip at-mention botname from the message if is_mentioned: # message['content'] will be None when the bot's @-mention is not at the beginning. # In that case, the message shall not be handled. message['content'] = extract_query_without_mention( message=message, client=restricted_client) if message['content'] is None: return if is_private_message or is_mentioned: message_handler.handle_message(message=message, client=restricted_client, state_handler=state_handler) signal.signal(signal.SIGINT, exit_gracefully) logging.info('starting message handling...') client.call_on_each_message(handle_message)
def get_zulip_client(self): if hasattr(self._bot, "client"): return self._bot.client from zulip import Client config = dict(self.bot_config.BOT_IDENTITY) config["api_key"] = config.pop("key") client = Client(**config) self._bot.client = client return client
def __init__(self, name, stream): self._client = Client() self._name = name self._self_short_name = u'{name}-bot'.format(name=self._name) self._self_mention = self._format_mention(self._name) self._stream = stream self._topic_router = {} self._command_handlers = { 'hello': self._hello_command_handler, 'help': self._help_command_handler, }
def load_bot_handlers(): # type: () -> Any for bot in available_bots: client = Client(email=bots_config[bot]["email"], api_key=bots_config[bot]["key"], site=bots_config[bot]["site"]) try: bot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bots', bot) bot_handlers[bot] = ExternalBotHandler(client, bot_dir) except SystemExit: return BadRequest("Cannot fetch user profile for bot {}, make sure you have set up the flaskbotrc " "file correctly.".format(bot))
def run_message_handler_for_bot(lib_module, quiet, config_file, bot_name): # type: (Any, bool, str) -> Any # # lib_module is of type Any, since it can contain any bot's # handler class. Eventually, we want bot's handler classes to # inherit from a common prototype specifying the handle_message # function. # # Make sure you set up your ~/.zuliprc client = Client(config_file=config_file, client="Zulip{}Bot".format(bot_name.capitalize())) bot_dir = os.path.dirname(lib_module.__file__) restricted_client = ExternalBotHandler(client, bot_dir) message_handler = lib_module.handler_class() if hasattr(message_handler, 'initialize'): message_handler.initialize(bot_handler=restricted_client) state_handler = StateHandler() if not quiet: print(message_handler.usage()) def handle_message(message): # type: (Dict[str, Any]) -> None logging.info('waiting for next message') # is_mentioned is true if the bot is mentioned at ANY position (not necessarily # the first @mention in the message). is_mentioned = message['is_mentioned'] is_private_message = is_private_message_from_another_user( message, restricted_client.user_id) # Strip at-mention botname from the message if is_mentioned: # message['content'] will be None when the bot's @-mention is not at the beginning. # In that case, the message shall not be handled. message['content'] = extract_query_without_mention( message=message, client=restricted_client) if message['content'] is None: return if is_private_message or is_mentioned: message_handler.handle_message(message=message, bot_handler=restricted_client, state_handler=state_handler) signal.signal(signal.SIGINT, exit_gracefully) logging.info('starting message handling...') client.call_on_each_message(handle_message)
def __init__(self, config): super().__init__(config) config.MESSAGE_SIZE_LIMIT = ZULIP_MESSAGE_SIZE_LIMIT self.identity = config.BOT_IDENTITY for key in ('email', 'key', 'site'): if key not in self.identity: log.fatal( "You need to supply the key `{}` for me to use. `{key}` and its value " "can be found in your bot's `zuliprc` config file.".format( key)) sys.exit(1) compact = config.COMPACT_OUTPUT if hasattr(config, 'COMPACT_OUTPUT') else False enable_format('text', TEXT_CHRS, borders=not compact) self.client = Client(email=self.identity['email'], api_key=self.identity['key'], site=self.identity['site'])
def run_message_handler_for_bot(lib_module, quiet): # Make sure you set up your ~/.zuliprc client = Client() restricted_client = RestrictedClient(client) message_handler = lib_module.handler_class() if not quiet: print(message_handler.usage()) def handle_message(message): logging.info('waiting for next message') if message_handler.triage_message(message=message): message_handler.handle_message( message=message, client=restricted_client) logging.info('starting message handling...') client.call_on_each_message(handle_message)
def handle_bot(bot): # type: (str) -> Union[str, BadRequest] if bot not in available_bots: return BadRequest("requested bot service {} not supported".format(bot)) client = Client(email=bots_config[bot]["email"], api_key=bots_config[bot]["key"], site=bots_config[bot]["site"]) restricted_client = ExternalBotHandler(client) message_handler = bots_lib_module[bot].handler_class() # TODO: Handle stateful bots properly. state_handler = StateHandler() event = json.loads(request.data) message_handler.handle_message(message=event["message"], client=restricted_client, state_handler=state_handler) return "Success!"
def load_bot_handlers( available_bots: List[str], bots_config: Dict[str, Dict[str, str]], third_party_bot_conf: Optional[configparser.ConfigParser] = None, ) -> Dict[str, lib.ExternalBotHandler]: bot_handlers = {} for bot in available_bots: client = Client(email=bots_config[bot]["email"], api_key=bots_config[bot]["key"], site=bots_config[bot]["site"]) bot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bots', bot) bot_handler = lib.ExternalBotHandler( client, bot_dir, bot_details={}, bot_config_parser=third_party_bot_conf) bot_handlers[bot] = bot_handler return bot_handlers
def playlist(self, msg, stream, topic): """Create a new playlist from specific topic or stream.""" if msg.to._client is None: from zulip import Client config = dict(self.bot_config.BOT_IDENTITY) config["api_key"] = config.pop("key") client = Client(**config) else: client = self._bot.client if stream is None: stream = msg.to.title topic = msg.to.subject narrow = [ {"negated": False, "operator": "stream", "operand": stream}, {"negated": False, "operator": "topic", "operand": topic}, ] if topic is None: narrow = narrow[:1] request = { "apply_markdown": False, "num_before": 5000, "num_after": 0, "anchor": 10000000000000000, "narrow": narrow, } result = client.get_messages(request) if "messages" not in result: return "No messages in {} > {}".format(stream, topic) messages = result["messages"] content = [m["content"] for m in messages] text = "\n".join(content) return get_playlist(text)
def handle_bot(bot): # type: (str) -> Union[str, BadRequest] if bot not in available_bots: return BadRequest("requested bot service {} not supported".format(bot)) client = Client(email=bots_config[bot]["email"], api_key=bots_config[bot]["key"], site=bots_config[bot]["site"]) try: restricted_client = ExternalBotHandler(client) except SystemExit: return BadRequest( "Cannot fetch user profile for bot {}, make sure you have set up the flaskbotrc " "file correctly.".format(bot)) message_handler = bots_lib_module[bot].handler_class() # TODO: Handle stateful bots properly. state_handler = StateHandler() event = json.loads(request.data) message_handler.handle_message(message=event["message"], bot_handler=restricted_client, state_handler=state_handler) return "Success!"
def run_message_handler_for_bot( lib_module: Any, quiet: bool, config_file: str, bot_config_file: str, bot_name: str, bot_source: str, ) -> Any: """ lib_module is of type Any, since it can contain any bot's handler class. Eventually, we want bot's handler classes to inherit from a common prototype specifying the handle_message function. Set default bot_details, then override from class, if provided """ bot_details = { "name": bot_name.capitalize(), "description": "", } bot_details.update(getattr(lib_module.handler_class, "META", {})) # Make sure you set up your ~/.zuliprc client_name = f"Zulip{bot_name.capitalize()}Bot" try: client = Client(config_file=config_file, client=client_name) except configparser.Error as e: display_config_file_errors(str(e), config_file) sys.exit(1) bot_dir = os.path.dirname(lib_module.__file__) restricted_client = ExternalBotHandler(client, bot_dir, bot_details, bot_config_file) message_handler = prepare_message_handler(bot_name, restricted_client, lib_module) if not quiet: print("Running {} Bot (from {}):".format(bot_details["name"], bot_source)) if bot_details["description"] != "": print("\n\t{}".format(bot_details["description"])) if hasattr(message_handler, "usage"): print(message_handler.usage()) else: print( f"WARNING: {bot_name} is missing usage handler, please add one eventually" ) def handle_message(message: Dict[str, Any], flags: List[str]) -> None: logging.info("waiting for next message") # `mentioned` will be in `flags` if the bot is mentioned at ANY position # (not necessarily the first @mention in the message). is_mentioned = "mentioned" in flags is_private_message = is_private_message_but_not_group_pm( message, restricted_client) # Provide bots with a way to access the full, unstripped message message["full_content"] = message["content"] # Strip at-mention botname from the message if is_mentioned: # message['content'] will be None when the bot's @-mention is not at the beginning. # In that case, the message shall not be handled. message["content"] = extract_query_without_mention( message=message, client=restricted_client) if message["content"] is None: return if is_private_message or is_mentioned: message_handler.handle_message(message=message, bot_handler=restricted_client) signal.signal(signal.SIGINT, exit_gracefully) logging.info("starting message handling...") def event_callback(event: Dict[str, Any]) -> None: if event["type"] == "message": handle_message(event["message"], event["flags"]) client.call_on_each_event(event_callback, ["message"])
def run_message_handler_for_bot( lib_module: Any, quiet: bool, config_file: str, bot_config_file: str, bot_name: str, ) -> Any: """ lib_module is of type Any, since it can contain any bot's handler class. Eventually, we want bot's handler classes to inherit from a common prototype specifying the handle_message function. Set default bot_details, then override from class, if provided """ bot_details = { 'name': bot_name.capitalize(), 'description': "", } bot_details.update(getattr(lib_module.handler_class, 'META', {})) # Make sure you set up your ~/.zuliprc client_name = "Zulip{}Bot".format(bot_name.capitalize()) try: client = Client(config_file=config_file, client=client_name) except configparser.Error as e: display_config_file_errors(str(e), config_file) sys.exit(1) bot_dir = os.path.dirname(lib_module.__file__) restricted_client = ExternalBotHandler(client, bot_dir, bot_details, bot_config_file) message_handler = prepare_message_handler(bot_name, restricted_client, lib_module) if not quiet: print("Running {} Bot:".format(bot_details['name'])) if bot_details['description'] != "": print("\n\t{}".format(bot_details['description'])) print(message_handler.usage()) def handle_message(message: Dict[str, Any], flags: List[str]) -> None: logging.info('waiting for next message') # `mentioned` will be in `flags` if the bot is mentioned at ANY position # (not necessarily the first @mention in the message). is_mentioned = 'mentioned' in flags is_private_message = is_private_message_from_another_user(message, restricted_client.user_id) # Provide bots with a way to access the full, unstripped message message['full_content'] = message['content'] # Strip at-mention botname from the message if is_mentioned: # message['content'] will be None when the bot's @-mention is not at the beginning. # In that case, the message shall not be handled. message['content'] = extract_query_without_mention(message=message, client=restricted_client) if message['content'] is None: return if is_private_message or is_mentioned: message_handler.handle_message( message=message, bot_handler=restricted_client ) signal.signal(signal.SIGINT, exit_gracefully) logging.info('starting message handling...') def event_callback(event: Dict[str, Any]) -> None: if event['type'] == 'message': handle_message(event['message'], event['flags']) client.call_on_each_event(event_callback, ['message'])
def run_message_handler_for_bot(lib_module, quiet, config_file): # type: (Any, bool, str) -> Any # # lib_module is of type Any, since it can contain any bot's # handler class. Eventually, we want bot's handler classes to # inherit from a common prototype specifying the handle_message # function. # # Make sure you set up your ~/.zuliprc client = Client(config_file=config_file) restricted_client = ExternalBotHandler(client) message_handler = lib_module.handler_class() if hasattr(message_handler, 'initialize'): message_handler.initialize(bot_handler=restricted_client) state_handler = StateHandler() if not quiet: print(message_handler.usage()) def extract_query_without_mention(message, client): # type: (Dict[str, Any], ExternalBotHandler) -> str """ If the bot is the first @mention in the message, then this function returns the message with the bot's @mention removed. Otherwise, it returns None. """ bot_mention = r'^@(\*\*{0}\*\*)'.format(client.full_name) start_with_mention = re.compile(bot_mention).match(message['content']) if start_with_mention is None: return None query_without_mention = message['content'][len(start_with_mention. group()):] return query_without_mention.lstrip() def is_private(message, client): # type: (Dict[str, Any], ExternalBotHandler) -> bool # bot will not reply if the sender name is the same as the bot name # to prevent infinite loop if message['type'] == 'private': return client.full_name != message['sender_full_name'] return False def handle_message(message): # type: (Dict[str, Any]) -> None logging.info('waiting for next message') # is_mentioned is true if the bot is mentioned at ANY position (not necessarily # the first @mention in the message). is_mentioned = message['is_mentioned'] is_private_message = is_private(message, restricted_client) # Strip at-mention botname from the message if is_mentioned: # message['content'] will be None when the bot's @-mention is not at the beginning. # In that case, the message shall not be handled. message['content'] = extract_query_without_mention( message=message, client=restricted_client) if message['content'] is None: return if is_private_message or is_mentioned: message_handler.handle_message(message=message, bot_handler=restricted_client, state_handler=state_handler) signal.signal(signal.SIGINT, exit_gracefully) logging.info('starting message handling...') client.call_on_each_message(handle_message)
def run_message_handler_for_bot(lib_module, quiet, config_file, bot_name): # type: (Any, bool, str, str) -> Any # # lib_module is of type Any, since it can contain any bot's # handler class. Eventually, we want bot's handler classes to # inherit from a common prototype specifying the handle_message # function. # # Make sure you set up your ~/.zuliprc client = Client(config_file=config_file, client="Zulip{}Bot".format(bot_name.capitalize())) bot_dir = os.path.dirname(lib_module.__file__) restricted_client = ExternalBotHandler(client, bot_dir) message_handler = lib_module.handler_class() if hasattr(message_handler, 'initialize'): message_handler.initialize(bot_handler=restricted_client) # Set default bot_details, then override from class, if provided bot_details = { 'name': bot_name.capitalize(), 'description': "", } bot_details.update(getattr(lib_module.handler_class, 'META', {})) if not quiet: print("Running {} Bot:".format(bot_details['name'])) if bot_details['description'] != "": print("\n\t{}".format(bot_details['description'])) print(message_handler.usage()) def handle_message(message, flags): # type: (Dict[str, Any], List[str]) -> None logging.info('waiting for next message') # `mentioned` will be in `flags` if the bot is mentioned at ANY position # (not necessarily the first @mention in the message). is_mentioned = 'mentioned' in flags is_private_message = is_private_message_from_another_user( message, restricted_client.user_id) # Strip at-mention botname from the message if is_mentioned: # message['content'] will be None when the bot's @-mention is not at the beginning. # In that case, the message shall not be handled. message['content'] = extract_query_without_mention( message=message, client=restricted_client) if message['content'] is None: return if is_private_message or is_mentioned: message_handler.handle_message(message=message, bot_handler=restricted_client) signal.signal(signal.SIGINT, exit_gracefully) logging.info('starting message handling...') def event_callback(event): # type: (Dict[str, Any]) -> None if event['type'] == 'message': handle_message(event['message'], event['flags']) client.call_on_each_event(event_callback, ['message'])
def get_bot_api_client(self, user_profile): # type: (UserProfile) -> BotHandlerApi raw_client = Client(email=str(user_profile.email), api_key=str(user_profile.api_key), site=str(user_profile.realm.uri)) return BotHandlerApi(raw_client)