interactive bots: Create DialogFlow bot.
MODIFY
tools/run-mypy
tools/run-mypy
@@ -69,6 +69,8 @@ force_include = [
"zulip_bots/zulip_bots/bots/wikipedia/test_wikipedia.py",
"zulip_bots/zulip_bots/bots/yoda/yoda.py",
"zulip_bots/zulip_bots/bots/yoda/test_yoda.py",
+ "zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py",
+ "zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py"
]
parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")

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

ADD
zulip_bots/zulip_bots/bots/dialogflow/__init__.py
zulip_bots/zulip_bots/bots/dialogflow/__init__.py
ADD
zulip_bots/zulip_bots/bots/dialogflow/dialogflow.conf
+[dialogflow]
+key=YOUR_PUBLIC_API_KEY_HERE
+bot_info=BOT_INFO_HERE

ADD
zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py
+# See readme.md for instructions on running this code.
+from __future__ import print_function
+import logging
+from six.moves.urllib import parse
+import json
+
+import apiai
+
+from typing import Dict, Any, List
+
+help_message = '''DialogFlow bot
+This bot will interact with dialogflow bots.
+Simply send this bot a message, and it will respond depending on the configured bot's behaviour.
+'''
+
+def get_bot_result(message_content: str, config: Dict[str, str], sender_id: str) -> str:
+ if message_content.strip() == '' or message_content.strip() == 'help':
+ return config['bot_info']
+ ai = apiai.ApiAI(config['key'])
+ try:
+ request = ai.text_request()
+ request.session_id = sender_id
+ request.query = message_content
+ response = request.getresponse()
+ res_str = response.read().decode('utf8', 'ignore')
+ res_json = json.loads(res_str)
+ if res_json['status']['errorType'] != 'success':
+ return 'Error {}: {}.'.format(res_json['status']['code'], res_json['status']['errorDetails'])
+ if res_json['result']['fulfillment']['speech'] == '':
+ if res_json['alternateResult']['fulfillment']['speech'] == '':
+ return 'Error. No result.'
+ return res_json['alternateResult']['fulfillment']['speech']
+ return res_json['result']['fulfillment']['speech']
+ except Exception as e:
+ logging.exception(str(e))
+ return 'Error. {}.'.format(str(e))
+
+class DialogFlowHandler(object):
+ '''
+ This plugin allows users to easily add their own
+ DialogFlow bots to zulip
+ '''
+
+ def initialize(self, bot_handler: Any) -> None:
+ self.config_info = bot_handler.get_config_info('dialogflow')
+
+ def usage(self) -> str:
+ return '''
+ This plugin will allow users to easily add their own
+ DialogFlow bots to zulip
+ '''
+
+ def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None:
+ result = get_bot_result(message['content'], self.config_info, message['sender_id'])
+ bot_handler.send_reply(message, result)
+
+handler_class = DialogFlowHandler

ADD
zulip_bots/zulip_bots/bots/dialogflow/doc.md
+# DialogFlow bot
+This bot allows users to easily add their own DialogFlow bots to zulip.
+
+## Setup
+To add your DialogFlow bot:
+Add the V1 Client access token from your agent's settings in the DialogFlow console to
+`dialogflow.conf`, and write a short sentence describing what your bot does in the same file
+as `bot_info`.
+
+## Usage
+
+Run this bot as described
+[here](https://zulipchat.com/api/running-bots#running-a-bot).
+
+Mention the bot in order to say things to it.
+
+For example: `@weather What is the weather today?`
+
+
+## Limitations
+When creating your DialogFlow bot, please consider these things:
+
+- Empty input will not be sent to the bot.
+- Only text can be sent to, and recieved from the bot.

ADD
zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_403.json
+{
+ "response": {
+ "status": {
+ "errorType": "fail",
+ "code": "403",
+ "errorDetails": "Access Denied"
+ }
+ },
+ "request": {
+ "query": "hello"
+ }
+ }

ADD
zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_alternate_result.json
+{
+ "response": {
+ "result": {
+ "fulfillment": {
+ "speech": ""
+ }
+ },
+ "alternateResult": {
+ "fulfillment": {
+ "speech": "alternate result"
+ }
+ },
+ "status": {
+ "errorType": "success"
+ }
+ },
+ "request": {
+ "query": "hello"
+ }
+ }

ADD
zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_empty_response.json
+{
+ "response": {
+ "result": {
+ "fulfillment": {
+ "speech": ""
+ }
+ },
+ "alternateResult": {
+ "fulfillment": {
+ "speech": ""
+ }
+ },
+ "status": {
+ "errorType": "success"
+ }
+ },
+ "request": {
+ "query": "hello"
+ }
+ }

ADD
zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_exception.json
+{
+ "response": {
+ "foo": {
+ "fulfillment": {
+ "speech": "how are you?"
+ }
+ },
+ "bar": {
+ "errorType": "success"
+ }
+ },
+ "request": {
+ "query": "hello"
+ }
+ }

ADD
zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_normal.json
+{
+ "response": {
+ "result": {
+ "fulfillment": {
+ "speech": "how are you?"
+ }
+ },
+ "status": {
+ "errorType": "success"
+ }
+ },
+ "request": {
+ "query": "hello"
+ }
+}

ADD
zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py
+#!/usr/bin/env python
+
+from zulip_bots.test_lib import StubBotTestCase, read_bot_fixture_data
+
+from contextlib import contextmanager
+
+from unittest.mock import patch
+
+from typing import Any, ByteString
+
+import json
+
+class MockTextRequest():
+ def __init__(self) -> None:
+ self.session_id = ""
+ self.query = ""
+ self.response = ""
+
+ def getresponse(self) -> Any:
+ return MockHttplibRequest(self.response)
+
+class MockHttplibRequest():
+ def __init__(self, response: str) -> None:
+ self.response = response
+
+ def read(self) -> ByteString:
+ return json.dumps(self.response).encode()
+
+@contextmanager
+def mock_dialogflow(test_name: str, bot_name: str) -> Any:
+ response_data = read_bot_fixture_data(bot_name, test_name)
+ df_request = response_data.get('request')
+ df_response = response_data.get('response')
+
+ with patch('apiai.ApiAI.text_request') as mock_text_request:
+ request = MockTextRequest()
+ request.response = df_response
+ mock_text_request.return_value = request
+ yield
+
+class TestDialogFlowBot(StubBotTestCase):
+ bot_name = 'dialogflow'
+
+ def _test(self, test_name: str, message: str, response: str) -> None:
+ with self.mock_config_info({'key': 'abcdefg', 'bot_info': 'bot info foo bar'}), \
+ mock_dialogflow(test_name, 'dialogflow'):
+ self.verify_reply(message, response)
+
+ def test_normal(self) -> None:
+ self._test('test_normal', 'hello', 'how are you?')
+
+ def test_403(self) -> None:
+ self._test('test_403', 'hello', 'Error 403: Access Denied.')
+
+ def test_empty_response(self) -> None:
+ self._test('test_empty_response', 'hello', 'Error. No result.')
+
+ def test_exception(self) -> None:
+ with patch('logging.exception'):
+ self._test('test_exception', 'hello', 'Error. \'status\'.')
+
+ def test_help(self) -> None:
+ self._test('test_normal', 'help', 'bot info foo bar')
+ self._test('test_normal', '', 'bot info foo bar')
+
+ def test_alternate_response(self) -> None:
+ self._test('test_alternate_result', 'hello', 'alternate result')
+
+ def test_bot_responds_to_empty_message(self) -> None:
+ with self.mock_config_info({'key': 'abcdefg', 'bot_info': 'bot info foo bar'}):
+ pass

MODIFY
zulip_bots/zulip_bots/test_lib.py
zulip_bots/zulip_bots/test_lib.py
@@ -114,6 +114,7 @@ class StubBotTestCase(TestCase):
display_recipient='foo_stream',
sender_email='foo@example.com',
sender_full_name='Foo Test User',
+ sender_id='123',
content=content,
)
return message

interactive bots: Create DialogFlow bot.