Beispiel #1
0
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))
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
    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
Beispiel #6
0
 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,
     }
Beispiel #7
0
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))
Beispiel #8
0
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)
Beispiel #9
0
    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'])
Beispiel #10
0
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)
Beispiel #11
0
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!"
Beispiel #12
0
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)
Beispiel #14
0
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!"
Beispiel #15
0
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"])
Beispiel #16
0
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'])
Beispiel #17
0
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)
Beispiel #18
0
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'])
Beispiel #19
0
 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)