interactive bots: Create Front bot.

This commit is contained in:
Alena Volkova 2018-03-02 18:27:35 -05:00 committed by showell
parent 6c0151ab67
commit ea8393511a
12 changed files with 317 additions and 0 deletions

View file

@ -93,6 +93,8 @@ force_include = [
"zulip_bots/zulip_bots/bots/trello/test_trello.py", "zulip_bots/zulip_bots/bots/trello/test_trello.py",
"zulip_bots/zulip_bots/bots/susi/susi.py", "zulip_bots/zulip_bots/bots/susi/susi.py",
"zulip_bots/zulip_bots/bots/susi/test_susi.py", "zulip_bots/zulip_bots/bots/susi/test_susi.py",
"zulip_bots/zulip_bots/bots/front/front.py",
"zulip_bots/zulip_bots/bots/front/test_front.py"
] ]
parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.") parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View file

@ -0,0 +1,17 @@
# Front Bot
## Setup
1. Go to the `Setting` of your Front app.
2. Copy the `JSON Web Token` from `Plugins & API``API`.
3. Replace `<api_key>` in `zulip_bots/bots/front/front.conf` with `JSON
Web Token`.
## Usage
![](assets/usage.png)
The name of the topic, from which you call the bot, must contain the ID of
the corresponding Front conversation. If you have received notifications
from this conversation using Front incoming webhook, you can use the topic
it has created.

View file

@ -0,0 +1,21 @@
{
"request": {
"method": "PATCH",
"api_url": "https://api2.frontapp.com/conversations/cnv_kqatm2",
"headers": {
"Authorization": "Bearer TEST"
},
"json": {
"status": "archived"
}
},
"response": {
"error": {
"title": "Unauthenticated",
"message": "Provided token is not a JSON Web Token"
}
},
"response-headers": {
"Status": "401 Unauthorized"
}
}

View file

@ -0,0 +1,22 @@
{
"request": {
"method": "POST",
"api_url": "https://api2.frontapp.com/conversations/cnv_kqatm2/comments",
"headers": {
"Authorization": "Bearer TEST"
},
"json": {
"author_id": "alt:email:leela@planet-express.com",
"body": "@bender, I thought you were supposed to be cooking for this party."
}
},
"response": {
"error": {
"title": "Unauthenticated",
"message": "Provided token is not a JSON Web Token"
}
},
"response-headers": {
"Status": "401 Unauthorized"
}
}

View file

@ -0,0 +1,21 @@
{
"request": {
"method": "PATCH",
"api_url": "https://api2.frontapp.com/conversations/cnv_kqatm2",
"headers": {
"Authorization": "Bearer TEST"
},
"json": {
"status": "deleted"
}
},
"response": {
"error": {
"title": "Unauthenticated",
"message": "Provided token is not a JSON Web Token"
}
},
"response-headers": {
"Status": "401 Unauthorized"
}
}

View file

@ -0,0 +1,21 @@
{
"request": {
"method": "PATCH",
"api_url": "https://api2.frontapp.com/conversations/cnv_kqatm2",
"headers": {
"Authorization": "Bearer TEST"
},
"json": {
"status": "open"
}
},
"response": {
"error": {
"title": "Unauthenticated",
"message": "Provided token is not a JSON Web Token"
}
},
"response-headers": {
"Status": "401 Unauthorized"
}
}

View file

@ -0,0 +1,21 @@
{
"request": {
"method": "PATCH",
"api_url": "https://api2.frontapp.com/conversations/cnv_kqatm2",
"headers": {
"Authorization": "Bearer TEST"
},
"json": {
"status": "spam"
}
},
"response": {
"error": {
"title": "Unauthenticated",
"message": "Provided token is not a JSON Web Token"
}
},
"response-headers": {
"Status": "401 Unauthorized"
}
}

View file

@ -0,0 +1,2 @@
[front]
api_key = <api_key>

View file

@ -0,0 +1,123 @@
import requests
import re
from typing import Any, Dict, Optional
class FrontHandler(object):
FRONT_API = "https://api2.frontapp.com/conversations/{}"
COMMANDS = [
('archive', "Archive a conversation."),
('delete', "Delete a conversation."),
('spam', "Mark a conversation as spam."),
('open', "Restore a conversation."),
('comment <text>', "Leave a comment.")
]
CNV_ID_REGEXP = 'cnv_(?P<id>[0-9a-z]+)'
COMMENT_PREFIX = "comment "
def usage(self) -> str:
return '''
Front Bot uses the Front REST API to interact with Front. In order to use
Front Bot, `front.conf` must be set up. See `doc.md` for more details.
'''
def initialize(self, bot_handler: Any) -> None:
config = bot_handler.get_config_info('front')
api_key = config.get('api_key')
if not api_key:
raise KeyError("No API key specified.")
self.auth = "Bearer " + api_key
def help(self, bot_handler: Any) -> str:
response = ""
for command, description in self.COMMANDS:
response += "`{}` {}\n".format(command, description)
return response
def archive(self, bot_handler: Any) -> str:
response = requests.patch(self.FRONT_API.format(self.conversation_id),
headers={"Authorization": self.auth},
json={"status": "archived"})
if response.status_code not in (200, 204):
return "Something went wrong."
return "Conversation was archived."
def delete(self, bot_handler: Any) -> str:
response = requests.patch(self.FRONT_API.format(self.conversation_id),
headers={"Authorization": self.auth},
json={"status": "deleted"})
if response.status_code not in (200, 204):
return "Something went wrong."
return "Conversation was deleted."
def spam(self, bot_handler: Any) -> str:
response = requests.patch(self.FRONT_API.format(self.conversation_id),
headers={"Authorization": self.auth},
json={"status": "spam"})
if response.status_code not in (200, 204):
return "Something went wrong."
return "Conversation was marked as spam."
def restore(self, bot_handler: Any) -> str:
response = requests.patch(self.FRONT_API.format(self.conversation_id),
headers={"Authorization": self.auth},
json={"status": "open"})
if response.status_code not in (200, 204):
return "Something went wrong."
return "Conversation was restored."
def comment(self, bot_handler: Any, **kwargs: Any) -> str:
response = requests.post(self.FRONT_API.format(self.conversation_id) + "/comments",
headers={"Authorization": self.auth}, json=kwargs)
if response.status_code not in (200, 201):
return "Something went wrong."
return "Comment was sent."
def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None:
command = message['content']
result = re.search(self.CNV_ID_REGEXP, message['subject'])
if not result:
bot_handler.send_reply(message, "No coversation ID found. Please make "
"sure that the name of the topic "
"contains a valid coversation ID.")
return None
self.conversation_id = result.group()
if command == 'help':
bot_handler.send_reply(message, self.help(bot_handler))
elif command == 'archive':
bot_handler.send_reply(message, self.archive(bot_handler))
elif command == 'delete':
bot_handler.send_reply(message, self.delete(bot_handler))
elif command == 'spam':
bot_handler.send_reply(message, self.spam(bot_handler))
elif command == 'open':
bot_handler.send_reply(message, self.restore(bot_handler))
elif command.startswith(self.COMMENT_PREFIX):
kwargs = {
'author_id': "alt:email:" + message['sender_email'],
'body': command[len(self.COMMENT_PREFIX):]
}
bot_handler.send_reply(message, self.comment(bot_handler, **kwargs))
else:
bot_handler.send_reply(message, "Unknown command. Use `help` for instructions.")
handler_class = FrontHandler

View file

@ -0,0 +1,67 @@
from typing import Any, Dict
from zulip_bots.test_lib import BotTestCase
class TestFrontBot(BotTestCase):
bot_name = 'front'
def make_request_message(self, content: str) -> Dict[str, Any]:
message = super().make_request_message(content)
message['subject'] = "cnv_kqatm2"
message['sender_email'] = "leela@planet-express.com"
return message
def test_bot_responds_to_empty_message(self) -> None:
with self.mock_config_info({'api_key': "TEST"}):
self.verify_reply("", "Unknown command. Use `help` for instructions.")
def test_help(self) -> None:
with self.mock_config_info({'api_key': "TEST"}):
self.verify_reply('help', "`archive` Archive a conversation.\n"
"`delete` Delete a conversation.\n"
"`spam` Mark a conversation as spam.\n"
"`open` Restore a conversation.\n"
"`comment <text>` Leave a comment.\n")
def test_archive(self) -> None:
with self.mock_config_info({'api_key': "TEST"}):
with self.mock_http_conversation('archive'):
self.verify_reply('archive', "Conversation was archived.")
def test_delete(self) -> None:
with self.mock_config_info({'api_key': "TEST"}):
with self.mock_http_conversation('delete'):
self.verify_reply('delete', "Conversation was deleted.")
def test_spam(self) -> None:
with self.mock_config_info({'api_key': "TEST"}):
with self.mock_http_conversation('spam'):
self.verify_reply('spam', "Conversation was marked as spam.")
def test_restore(self) -> None:
with self.mock_config_info({'api_key': "TEST"}):
with self.mock_http_conversation('open'):
self.verify_reply('open', "Conversation was restored.")
def test_comment(self) -> None:
body = "@bender, I thought you were supposed to be cooking for this party."
with self.mock_config_info({'api_key': "TEST"}):
with self.mock_http_conversation('comment'):
self.verify_reply("comment " + body, "Comment was sent.")
class TestFrontBotWrongTopic(BotTestCase):
bot_name = 'front'
def make_request_message(self, content: str) -> Dict[str, Any]:
message = super().make_request_message(content)
message['subject'] = "kqatm2"
return message
def test_bot_responds_to_empty_message(self) -> None:
pass
def test_no_conversation_id(self) -> None:
with self.mock_config_info({'api_key': "TEST"}):
self.verify_reply('archive', "No coversation ID found. Please make "
"sure that the name of the topic "
"contains a valid coversation ID.")