bots: Create Wit.ai Bot.
Wit.ai Bot communicates with the Wit.ai API. It can be configured with
any Wit.ai token and allows for setting up a custom handler function to
handle Wit.ai responses.
MODIFY
tools/run-mypy
tools/run-mypy
@@ -67,6 +67,8 @@ force_include = [
"zulip_bots/zulip_bots/bots/chess/test_chess.py",
"zulip_bots/zulip_bots/bots/xkcd/xkcd.py",
"zulip_bots/zulip_bots/bots/xkcd/test_xkcd.py",
+ "zulip_bots/zulip_bots/bots/witai/witai.py",
+ "zulip_bots/zulip_bots/bots/witai/test_witai.py",
"zulip_bots/zulip_bots/bots/wikipedia/wikipedia.py",
"zulip_bots/zulip_bots/bots/wikipedia/test_wikipedia.py",
"zulip_bots/zulip_bots/bots/yoda/yoda.py",

MODIFY
zulip_bots/setup.py
zulip_bots/setup.py
@@ -54,6 +54,7 @@ setuptools_info = dict(
'lxml', # for bots/googlesearch
'requests', # for bots/link_shortener
'python-chess[engine,gaviota]', # for bots/chess
+ 'wit', # for bots/witai
'apiai' # for bots/dialogflow
],
)

ADD
zulip_bots/zulip_bots/bots/witai/doc.md
+# Wit.ai Bot
+
+Wit.ai Bot uses [wit.ai](https://wit.ai/) to parse natural language.
+
+## Usage
+
+ 1. Go to https://wit.ai/ and sign up.
+
+ 2. Create your Wit.ai app, or follow
+ [this quickstart guide](https://wit.ai/docs/quickstart).
+
+ 3. Create a `.conf` file containing a `token` field for your Wit.ai token,
+ and a `help_message` field for a message to display to confused users,
+ e.g.,
+
+ ```
+ [witai]
+ token = QWERTYUIOP1234
+ help_message = Ask me about my favorite food!
+ ```
+
+ 4. Create a new file named `witai_handler.py`, and inside of it, create a
+ function called `handle` with one parameter `response`. Inside of `handle`,
+ write code for whatever you want to do with the Wit.ai response. It should
+ return a `string` to respond to the user with. For example,
+
+ ```python
+ def handle(response):
+ if response['entities']['intent'][0]['value'] == 'favorite_food':
+ return 'pizza'
+ if response['entities']['intent'][0]['value'] == 'favorite_drink':
+ return 'coffee'
+ ```
+
+ 5. Add `witai_handler.py`'s location as `handler_location` in your
+ configuration file, e.g.,
+
+ ```
+ [witai]
+ token = QWERTYUIOP1234
+ handler_location = /Users/you/witai_handler_directory/witai_handler.py
+ ```
+
+ 6. Call
+
+ ```bash
+ zulip-run-bot witai \
+ --config-file <your zuliprc> \
+ --bot-config-file <the config file>
+ ```
+
+ to start the bot.

ADD
zulip_bots/zulip_bots/bots/witai/test_witai.py
+from mock import patch
+import sys
+from typing import Dict, Any, Optional
+from zulip_bots.test_lib import BotTestCase, get_bot_message_handler, StubBotHandler
+
+class TestWitaiBot(BotTestCase):
+ bot_name = 'witai'
+
+ MOCK_CONFIG_INFO = {
+ 'token': '12345678',
+ 'handler_location': '/Users/abcd/efgh',
+ 'help_message': 'Qwertyuiop!'
+ }
+
+ MOCK_WITAI_RESPONSE = {
+ '_text': 'What is your favorite food?',
+ 'entities': {
+ 'intent': [{
+ 'confidence': 1.0,
+ 'value': 'favorite_food'
+ }]
+ }
+ }
+
+ def test_normal(self) -> None:
+ with self.mock_config_info(self.MOCK_CONFIG_INFO):
+ get_bot_message_handler(self.bot_name).initialize(StubBotHandler())
+
+ with patch('wit.Wit.message') as message:
+ message.return_value = self.MOCK_WITAI_RESPONSE
+
+ with patch('zulip_bots.bots.witai.witai.get_handle') as handler:
+ handler.return_value = mock_handle
+
+ self.verify_reply(
+ 'What is your favorite food?',
+ 'pizza'
+ )
+
+ # This overrides the default one in `BotTestCase`.
+ def test_bot_responds_to_empty_message(self) -> None:
+ with self.mock_config_info(self.MOCK_CONFIG_INFO):
+ get_bot_message_handler(self.bot_name).initialize(StubBotHandler())
+
+ with patch('wit.Wit.message') as message:
+ message.return_value = self.MOCK_WITAI_RESPONSE
+
+ with patch('zulip_bots.bots.witai.witai.get_handle') as handler:
+ handler.return_value = mock_handle
+
+ self.verify_reply('', 'Qwertyuiop!')
+
+def mock_handle(res: Dict[str, Any]) -> Optional[str]:
+ if res['entities']['intent'][0]['value'] == 'favorite_food':
+ return 'pizza'
+ if res['entities']['intent'][0]['value'] == 'favorite_drink':
+ return 'coffee'
+
+ return None

ADD
zulip_bots/zulip_bots/bots/witai/witai.conf
+[witai]
+token = <your token>
+handler_location = <your handler location>
+help_message = <your help message>

ADD
zulip_bots/zulip_bots/bots/witai/witai.py
+# See readme.md for instructions on running this code.
+
+from typing import Dict, Any, Optional, Callable
+import wit
+import sys
+import importlib.util
+
+class WitaiHandler(object):
+ def usage(self) -> str:
+ return '''
+ Wit.ai bot uses pywit API to interact with Wit.ai. In order to use
+ Wit.ai bot, `witai.conf` must be set up. See `doc.md` for more details.
+ '''
+
+ def initialize(self, bot_handler: Any) -> None:
+ config = bot_handler.get_config_info('witai')
+
+ token = config.get('token')
+ if not token:
+ raise KeyError('No `token` was specified')
+
+ # `handler_location` should be the location of a module which contains
+ # the function `handle`. See `doc.md` for more details.
+ handler_location = config.get('handler_location')
+ if not handler_location:
+ raise KeyError('No `handler_location` was specified')
+ self.handle = get_handle(handler_location)
+
+ help_message = config.get('help_message')
+ if not help_message:
+ raise KeyError('No `help_message` was specified')
+ self.help_message = help_message
+
+ self.client = wit.Wit(token)
+
+ def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None:
+ if message['content'] == '' or message['content'] == 'help':
+ bot_handler.send_reply(message, self.help_message)
+ return
+
+ try:
+ res = self.client.message(message['content'])
+ message_for_user = self.handle(res)
+
+ if message_for_user:
+ bot_handler.send_reply(message, message_for_user)
+ except wit.wit.WitError:
+ bot_handler.send_reply(message, 'Sorry, I don\'t know how to respond to that!')
+ except Exception as e:
+ bot_handler.send_reply(message, 'Sorry, there was an internal error.')
+ print(e)
+ return
+
+handler_class = WitaiHandler
+
+def get_handle(location: str) -> Callable[[Dict[str, Any]], Optional[str]]:
+ '''Returns a function to be used when generating a response from Wit.ai
+ bot. This function is the function named `handle` in the module at the
+ given `location`. For an example of a `handle` function, see `doc.md`.
+
+ For example,
+
+ handle = get_handle('/Users/someuser/witai_handler.py') # Get the handler function.
+ res = witai_client.message(message['content']) # Get the Wit.ai response.
+ message_res = self.handle(res) # Handle the response and find what to show the user.
+ bot_handler.send_reply(message, message_res) # Send it to the user.
+
+ Parameters:
+ - location: The absolute path to the module to look for `handle` in.
+ '''
+ try:
+ spec = importlib.util.spec_from_file_location('module.name', location)
+ handler = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(handler)
+ return handler.handle # type: ignore
+ except Exception as e:
+ print(e)
+ return None

bots: Create Wit.ai Bot.

Wit.ai Bot communicates with the Wit.ai API. It can be configured with
any Wit.ai token and allows for setting up a custom handler function to
handle Wit.ai responses.