コード例 #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("")
コード例 #2
0
ファイル: test_lib.py プロジェクト: jmandel/python-zulip-api
 def setUp(self):
     # type: () -> None
     # Mocking ExternalBotHandler
     self.patcher = patch('zulip_bots.lib.ExternalBotHandler', autospec=True)
     self.MockClass = self.patcher.start()
     self.mock_bot_handler = self.MockClass(None, None)
     self.mock_bot_handler.storage = StateHandler()
     self.message_handler = self.get_bot_message_handler()
コード例 #3
0
    def test_state_handler_by_mock(self):
        client = MagicMock()

        state_handler = StateHandler(client)
        client.get_storage.assert_not_called()

        client.update_storage = MagicMock(return_value=dict(result='success'))
        state_handler.put('key', [1, 2, 3])
        client.update_storage.assert_called_with(
            dict(storage=dict(key='[1, 2, 3]')))

        val = state_handler.get('key')
        client.get_storage.assert_not_called()
        self.assertEqual(val, [1, 2, 3])

        # force us to get non-cached values
        client.get_storage = MagicMock(return_value=dict(
            result='success', storage=dict(non_cached_key='[5]')))
        val = state_handler.get('non_cached_key')
        client.get_storage.assert_called_with({'keys': ['non_cached_key']})
        self.assertEqual(val, [5])

        # value must already be cached
        client.get_storage = MagicMock()
        val = state_handler.get('non_cached_key')
        client.get_storage.assert_not_called()
        self.assertEqual(val, [5])
コード例 #4
0
    def test_state_handler_by_mock(self):
        client = MagicMock()

        state_handler = StateHandler(client)
        client.get_storage.assert_not_called()

        client.update_storage = MagicMock(return_value=dict(result="success"))
        state_handler.put("key", [1, 2, 3])
        client.update_storage.assert_called_with(dict(storage=dict(key="[1, 2, 3]")))

        val = state_handler.get("key")
        client.get_storage.assert_not_called()
        self.assertEqual(val, [1, 2, 3])

        # force us to get non-cached values
        client.get_storage = MagicMock(
            return_value=dict(result="success", storage=dict(non_cached_key="[5]"))
        )
        val = state_handler.get("non_cached_key")
        client.get_storage.assert_called_with({"keys": ["non_cached_key"]})
        self.assertEqual(val, [5])

        # value must already be cached
        client.get_storage = MagicMock()
        val = state_handler.get("non_cached_key")
        client.get_storage.assert_not_called()
        self.assertEqual(val, [5])
コード例 #5
0
    def test_state_handler(self):
        client = FakeClient()

        state_handler = StateHandler(client)
        state_handler.put('key', [1, 2, 3])
        val = state_handler.get('key')
        self.assertEqual(val, [1, 2, 3])

        # force us to get non-cached values
        state_handler = StateHandler(client)
        val = state_handler.get('key')
        self.assertEqual(val, [1, 2, 3])
コード例 #6
0
 def setUp(self):
     # type: () -> None
     # Mocking ExternalBotHandler
     self.patcher = patch('zulip_bots.lib.ExternalBotHandler', autospec=True)
     self.MockClass = self.patcher.start()
     self.mock_bot_handler = self.MockClass(None, None, None, None)
     self.mock_client = MagicMock()
     self.mock_client.get_storage.return_value = {'result': 'success', 'storage': {}}
     self.mock_client.update_storage.return_value = {'result': 'success'}
     self.mock_bot_handler.storage = StateHandler(self.mock_client)
     self.message_handler = get_bot_message_handler(self.bot_name)
コード例 #7
0
def main():
    # type: () -> None
    args = parse_args()
    if os.path.isfile(args.bot):
        bot_path = os.path.abspath(args.bot)
        bot_name = os.path.splitext(os.path.basename(bot_path))[0]
    else:
        bot_path = os.path.abspath(
            os.path.join(current_dir, 'bots', args.bot, args.bot + '.py'))
        bot_name = args.bot
    bot_dir = os.path.dirname(bot_path)
    if args.provision:
        provision_bot(os.path.dirname(bot_path), args.force)
    lib_module = import_module_from_source(bot_path, bot_name)

    message = {'content': args.message, 'sender_email': '*****@*****.**'}
    message_handler = lib_module.handler_class()

    with patch('zulip.Client') as mock_client:
        mock_bot_handler = ExternalBotHandler(mock_client, bot_dir)
        mock_bot_handler.send_reply = MagicMock()
        mock_bot_handler.send_message = MagicMock()
        mock_bot_handler.update_message = MagicMock()
        if hasattr(message_handler, 'initialize') and callable(
                message_handler.initialize):
            message_handler.initialize(mock_bot_handler)
        message_handler.handle_message(message=message,
                                       bot_handler=mock_bot_handler,
                                       state_handler=StateHandler())
        print("On sending {} bot the message \"{}\"".format(
            bot_name, args.message))
        # send_reply and send_message have slightly arguments; the
        # following takes that into account.
        #   send_reply(original_message, response)
        #   send_message(response_message)
        if mock_bot_handler.send_reply.called:
            output_message = list(mock_bot_handler.send_reply.call_args)[0][1]
        elif mock_bot_handler.send_message.called:
            output_message = list(
                mock_bot_handler.send_message.call_args)[0][0]
        elif mock_bot_handler.update_message.called:
            output_message = list(
                mock_bot_handler.update_message.call_args)[0][0]['content']
            print(
                "the bot updates a message with the following text (in quotes):\n\"{}\""
                .format(output_message))
            sys.exit()
        else:
            print("the bot sent no reply.")
            sys.exit()
        print(
            "the bot gives the following output message (in quotes):\n\"{}\"".
            format(output_message))
コード例 #8
0
ファイル: test_lib.py プロジェクト: Juljan/python-zulip-api
    def test_state_handler(self):
        client = MagicMock()

        state_handler = StateHandler(client)
        client.get_storage.assert_not_called()

        client.update_storage = MagicMock(return_value=dict(result='success'))
        state_handler.put('key', [1, 2, 3])
        client.update_storage.assert_called_with(dict(storage=dict(key='[1, 2, 3]')))

        val = state_handler.get('key')
        client.get_storage.assert_not_called()
        self.assertEqual(val, [1, 2, 3])

        # force us to get non-cached values
        client.get_storage = MagicMock(return_value=dict(
            result='success',
            storage=dict(non_cached_key='[5]')))
        val = state_handler.get('non_cached_key')
        client.get_storage.assert_called_with(keys=('non_cached_key',))
        self.assertEqual(val, [5])

        # value must already be cached
        client.get_storage = MagicMock()
        val = state_handler.get('non_cached_key')
        client.get_storage.assert_not_called()
        self.assertEqual(val, [5])
コード例 #9
0
ファイル: test_lib.py プロジェクト: neiljp/python-zulip-api
    def call_request(self, message, expected_method, response):
        # type: (Dict[str, Any], str, Dict[str, Any]) -> None
        # Send message to the concerned bot
        self.message_handler.handle_message(message, self.MockClass(),
                                            StateHandler())

        # Check if the bot is sending a message via `send_message` function.
        # Where response is a dictionary here.
        instance = self.MockClass.return_value
        if expected_method == "send_message":
            instance.send_message.assert_called_with(response)
        else:
            instance.send_reply.assert_called_with(message,
                                                   response['content'])
コード例 #10
0
ファイル: test_lib.py プロジェクト: Juljan/python-zulip-api
    def test_state_handler(self):
        client = FakeClient()

        state_handler = StateHandler(client)
        state_handler.put('key', [1, 2, 3])
        val = state_handler.get('key')
        self.assertEqual(val, [1, 2, 3])

        # force us to get non-cached values
        state_handler = StateHandler(client)
        val = state_handler.get('key')
        self.assertEqual(val, [1, 2, 3])
コード例 #11
0
    def test_bot(self):
        help_txt = (
            '[email protected]:\n\nThis bot implements a virtual file system for a stream.\n'
            'The locations of text are persisted for the lifetime of the bot\n'
            'running, and if you rename a stream, you will lose the info.\n'
            'Example commands:\n\n```\n'
            '@mention-bot sample_conversation: sample conversation with the bot\n'
            '@mention-bot mkdir: create a directory\n'
            '@mention-bot ls: list a directory\n'
            '@mention-bot cd: change directory\n'
            '@mention-bot pwd: show current path\n'
            '@mention-bot write: write text\n'
            '@mention-bot read: read text\n'
            '@mention-bot rm: remove a file\n'
            '@mention-bot rmdir: remove a directory\n'
            '```\n'
            'Use commands like `@mention-bot help write` for more details on specific\ncommands.\n'
        )
        expected = {
            "cd /home": "[email protected]:\nERROR: invalid path",
            "mkdir home": "[email protected]:\ndirectory created",
            "pwd": "[email protected]:\n/",
            "help": help_txt,
            "help ls": "[email protected]:\nsyntax: ls <optional_path>",
            "": help_txt,
        }
        self.check_expected_responses(expected)

        expected = [
            ("help", help_txt),
            ("help ls", "[email protected]:\nsyntax: ls <optional_path>"),
            ("", help_txt),
            ("pwd", "[email protected]:\n/"),
            ("cd /home", "[email protected]:\nERROR: invalid path"),
            ("mkdir home", "[email protected]:\ndirectory created"),
            ("cd /home", "[email protected]:\nCurrent path: /home/"),
        ]

        state_handler = StateHandler()
        self.check_expected_responses(expected, state_handler=state_handler)
コード例 #12
0
ファイル: server.py プロジェクト: neiljp/python-zulip-api
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!"
コード例 #13
0
 def get_state_handler(self):
     # type: () -> StateHandler
     return StateHandler()
コード例 #14
0
    def test_bot(self):
        messages = [  # Template for message inputs to test, absent of message content
            {
                'type': 'stream',
                'display_recipient': 'some stream',
                'subject': 'some subject',
                'sender_email': '*****@*****.**',
            },
            {
                'type': 'private',
                'sender_email': '*****@*****.**',
            },
        ]
        private_response = {
            'type': 'private',
            'to': '*****@*****.**',
            'subject':
            '*****@*****.**',  # FIXME Requiring this in bot is a bug?
        }

        msg = dict(
            help=
            "*Help for Tic-Tac-Toe bot* \nThe bot responds to messages starting with @mention-bot.\n**@mention-bot new** will start a new game (but not if you're already in the middle of a game). You must type this first to start playing!\n**@mention-bot help** will return this help function.\n**@mention-bot quit** will quit from the current game.\n**@mention-bot <coordinate>** will make a move at the given coordinate.\nCoordinates are entered in a (row, column) format. Numbering is from top to bottom and left to right. \nHere are the coordinates of each position. (Parentheses and spaces are optional). \n(1, 1)  (1, 2)  (1, 3) \n(2, 1)  (2, 2)  (2, 3) \n(3, 1) (3, 2) (3, 3) \n",
            didnt_understand=
            "Hmm, I didn't understand your input. Type **@tictactoe help** or **@ttt help** to see valid inputs.",
            new_game=
            "Welcome to tic-tac-toe! You'll be x's and I'll be o's. Your move first!\nCoordinates are entered in a (row, column) format. Numbering is from top to bottom and left to right.\nHere are the coordinates of each position. (Parentheses and spaces are optional.) \n(1, 1)  (1, 2)  (1, 3) \n(2, 1)  (2, 2)  (2, 3) \n(3, 1) (3, 2) (3, 3) \n Your move would be one of these. To make a move, type @mention-bot followed by a space and the coordinate.",
            already_playing=
            "You're already playing a game! Type **@tictactoe help** or **@ttt help** to see valid inputs.",
            already_played_there='That space is already filled, sorry!',
            successful_quit="You've successfully quit the game.",
            after_1_1=("[ x _ _ ]\n[ _ _ _ ]\n[ _ _ _ ]\n"
                       "My turn:\n[ x _ _ ]\n[ _ o _ ]\n[ _ _ _ ]\n"
                       "Your turn! Enter a coordinate or type help."),
            after_2_1=("[ x _ _ ]\n[ x o _ ]\n[ _ _ _ ]\n"
                       "My turn:\n[ x _ _ ]\n[ x o _ ]\n[ o _ _ ]\n"
                       "Your turn! Enter a coordinate or type help."),
            after_1_3=("[ x _ x ]\n[ x o _ ]\n[ o _ _ ]\n"
                       "My turn:\n[ x o x ]\n[ x o _ ]\n[ o _ _ ]\n"
                       "Your turn! Enter a coordinate or type help."),
            after_3_2=("[ x o x ]\n[ x o _ ]\n[ o x _ ]\n"
                       "My turn:\n[ x o x ]\n[ x o o ]\n[ o x _ ]\n"
                       "Your turn! Enter a coordinate or type help."),
        )

        expected_send_message = [
            # Empty message
            ("", msg['didnt_understand']),
            # Non-command
            ("adboh", msg['didnt_understand']),
            # Help command
            ("help", msg['help']),
            # Command: quit not understood with no game
            ("quit", msg['didnt_understand']),
            # Can quit if new game and have state
            ("new", msg['new_game']),
            ("quit", msg['successful_quit']),
            # Quit not understood when no game FIXME improve response?
            ("quit", msg['didnt_understand']),
            # New right after new just restarts
            ("new", msg['new_game']),
            ("new", msg['new_game']),
            # Make a corner play
            ("(1,1)", msg['after_1_1']),
            # New while playing doesn't just restart
            ("new", msg['already_playing']),
            # User played in this location already
            ("(1,1)", msg['already_played_there']),
            # ... and bot played here
            ("(2,2)", msg['already_played_there']),
            ("quit", msg['successful_quit']),
            # Can't play without game FIXME improve response?
            ("(1,1)", msg['didnt_understand']),
            ("new", msg['new_game']),
            # Value out of range FIXME improve response?
            ("(1,5)", msg['didnt_understand']),
            # Value out of range FIXME improve response?
            ("0,1", msg['didnt_understand']),
            # Sequence of moves to show valid input formats:
            ("1,1", msg['after_1_1']),
            ("2, 1", msg['after_2_1']),
            ("(1,3)", msg['after_1_3']),
            # Can't test 'after_3_2' as it's random!
        ]
        for m in messages:
            state = StateHandler()
            for (mesg, resp) in expected_send_message:
                self.assert_bot_response(dict(m, content=mesg),
                                         dict(private_response, content=resp),
                                         'send_message', state)
コード例 #15
0
def main():
    # type: () -> None
    options = parse_args()
    bot_name = options.name
    if options.path_to_bot:
        if options.provision:
            bot_dir = os.path.dirname(os.path.abspath(options.path_to_bot))
            provision_bot(bot_dir, options.force)
        lib_module = import_module_from_source(options.path_to_bot,
                                               name=bot_name)
    elif options.name:
        if options.provision:
            current_dir = os.path.dirname(os.path.abspath(__file__))
            bots_parent_dir = os.path.join(current_dir, "bots")
            bot_dir = os.path.join(bots_parent_dir, options.name)
            provision_bot(bot_dir, options.force)
        lib_module = import_module(
            'zulip_bots.bots.{bot}.{bot}'.format(bot=bot_name))

    message = {
        'content': options.message,
        'sender_email': '*****@*****.**'
    }
    message_handler = lib_module.handler_class()

    with patch('zulip_bots.lib.ExternalBotHandler') as mock_bot_handler:

        def get_config_info(bot_name, section=None, optional=False):
            # type: (str, Optional[str], Optional[bool]) -> Dict[str, Any]
            conf_file_path = os.path.realpath(
                os.path.join('zulip_bots', 'bots', bot_name,
                             bot_name + '.conf'))
            section = section or bot_name
            config = configparser.ConfigParser()
            try:
                with open(conf_file_path) as conf:
                    config.readfp(conf)  # type: ignore
            except IOError:
                if optional:
                    return dict()
                raise
            return dict(config.items(section))

        mock_bot_handler.get_config_info = get_config_info
        if hasattr(message_handler, 'initialize') and callable(
                message_handler.initialize):
            message_handler.initialize(mock_bot_handler)

        mock_bot_handler.send_reply = MagicMock()
        mock_bot_handler.send_message = MagicMock()
        message_handler.handle_message(message=message,
                                       bot_handler=mock_bot_handler,
                                       state_handler=StateHandler())
        print("On sending ", options.name, " bot the following message:\n\"",
              options.message, "\"")

        # send_reply and send_message have slightly arguments; the
        # following takes that into account.
        #   send_reply(original_message, response)
        #   send_message(response_message)
        if mock_bot_handler.send_reply.called:
            print("\nThe bot gives the following output message:\n\"",
                  list(mock_bot_handler.send_reply.call_args)[0][1], "\"")
        elif mock_bot_handler.send_message.called:
            print("\nThe bot sends the following output to zulip:\n\"",
                  list(mock_bot_handler.send_message.call_args)[0][0], "\"")
        else:
            print("\nThe bot sent no reply.")