interactive bots: Create Mention bot.
MODIFY
tools/run-mypy
tools/run-mypy
@@ -74,7 +74,9 @@ force_include = [
"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"
+ "zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py",
+ "zulip_bots/zulip_bots/bots/mention/mention.py",
+ "zulip_bots/zulip_bots/bots/mention/test_mention.py"
]
parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")

ADD
zulip_bots/zulip_bots/bots/mention/__init__.py
zulip_bots/zulip_bots/bots/mention/__init__.py
ADD
zulip_bots/zulip_bots/bots/mention/assets/mentions_demo.png
zulip_bots/zulip_bots/bots/mention/assets/mentions_demo.png
Binary files /dev/null and b/zulip_bots/zulip_bots/bots/mention/assets/mentions_demo.png differ

ADD
zulip_bots/zulip_bots/bots/mention/doc.md
+# Mention bot
+
+The Mention bot is a Zulip bot that can fetch Mentions associated with
+a given keyword from the web using [Mention](https://mention.com/en/).
+
+To use the Mention bot, you can simply call it with `@<botname>` followed
+by a keyword, like so:
+
+```
+@Mention Apple
+```
+
+## Setup
+
+Before you can proceed further, you'll need to go to the
+[Mention Dev](https://dev.mention.com/login), and get a
+Mention API Access Token.
+
+1. Login.
+2. Enter the **App Name**, **Description**, **Website**, and **Redirect uris**. In this version, there
+is no actual use of the Redirect Uri and Website.
+3. After accepting the agreement, click on **Create New App**.
+4. And you're done! You should now have an Access Token.
+5. Open up `zulip_bots/bots/mention/mention.conf` in an editor and
+ change the value of the `<access_token>` attribute to the Access Token
+ you generated above.
+
+## Usage
+`@Mention <keyword>` - This command will fetch the most recent 20
+mentions of the keyword on the web (Limitations of a free account).
+Example:
+![](assets/mentions_demo.png)

ADD
zulip_bots/zulip_bots/bots/mention/fixtures/create_alert.json
+{
+ "alert": {
+ "id": "112233",
+ "name": "NASA and competitors",
+ "query": {
+ "type": "basic",
+ "included_keywords": [
+ "NASA",
+ "Arianespace",
+ "SpaceX",
+ "Pockocmoc"
+ ],
+ "required_keywords": [
+ "mars"
+ ],
+ "excluded_keywords": [
+ "nose",
+ "fil d'ariane"
+ ]
+ },
+ "languages": [
+ "en",
+ "fr",
+ "ru"
+ ],
+ "countries": [],
+ "sources": [
+ "twitter",
+ "news",
+ "web",
+ "blogs",
+ "videos",
+ "forums",
+ "images"
+ ],
+ "blocked_sites": [
+ "www.spaceoflovemagazine.com/"
+ ],
+ "role": "admin",
+ "stats": {
+ "mention_sources": {
+ "total": "0"
+ },
+ "mention_folders": {
+ "inbox": {
+ "total": "0"
+ },
+ "archive": {
+ "total": "0"
+ },
+ "spam": {
+ "total": "0"
+ },
+ "trash": {
+ "total": "0"
+ }
+ },
+ "unread_mentions": {
+ "total": "0"
+ },
+ "unseen_mentions": {
+ "total": "0"
+ },
+ "favorite_mentions": {
+ "total": "0"
+ },
+ "important_mentions": {
+ "total": "0"
+ },
+ "tasks": {
+ "total": "0"
+ },
+ "todo_tasks": {
+ "total": "0"
+ },
+ "done_tasks": {
+ "total": "0"
+ },
+ "logs": {
+ "total": "0"
+ }
+ },
+ "shares": [
+ {
+ "id": "THE_ACCOUNT_ID",
+ "account": {
+ "id": "THE_ACCOUNT_ID",
+ "name": "Doe",
+ "email": "john.doe@nasa.com",
+ "language_code": "en",
+ "created_at": "2014-09-30T10:03:54.0+00:00",
+ "updated_at": "2016-01-14T14:55:57.0+00:00",
+ "avatar_url": "https:\/\/d39qsljf883l.cloudfront.net\/f6415b89ef2ljkca5c0c7d464f1b82-088f3dsqlj12lj4.jpg",
+ "timezone": "Europe\/Berlin",
+ "grouped_email_notification": true,
+ "default_email_notification_frequency": "daily",
+ "default_desktop_notification_frequency": "hourly",
+ "default_push_notification_frequency": "hourly"
+ },
+ "role": "admin",
+ "permissions": {
+ "edit": true,
+ "delete": true
+ },
+ "created_at": "2016-01-14T15:31:42.0+00:00",
+ "weight": 88674800
+ }
+ ],
+ "noise_detection": true,
+ "created_at": "2016-01-14T15:31:42.0+00:00",
+ "updated_at": "2016-01-14T15:31:44.0+00:00",
+ "quota_used": 0,
+ "index_version": 2,
+ "permissions": {
+ "edit": true,
+ "share": true,
+ "list_tasks": true,
+ "list_logs": true
+ },
+ "description": "Monitor NASA press release.",
+ "color": "#05e363",
+ "connection_type": "related",
+ "connection_id": "12121212"
+ }
+}

ADD
zulip_bots/zulip_bots/bots/mention/fixtures/get_mentions.json
+{
+ "mentions": [
+ {...mention...},
+ {...mention...},
+ {...mention...}
+ ],
+ "_links": {
+ "more": {
+ "href": "...url to get older mentions..."
+ },
+ "pull": {
+ "href": "...url to get newer mentions..."
+ }
+ }
+}

ADD
zulip_bots/zulip_bots/bots/mention/fixtures/mention.json
+{
+ "id": "527849933",
+ "alert_id": 112233,
+ "title": "NASA image shows 'starburst spider' pattern on Mars",
+ "description": "USA TODAY NASA image shows 'starburst spider' pattern on Mars USA TODAY NASA's Mars Reconnaissance Orbiter captured a stunning image of a \"starburst\" pattern on Mars ' surface. The \u201cstarbursts\u201d can be seen each spring when Mars ' seasonal cap of carbon \u2026",
+ "original_url": "http:\/\/www.usatoday.com\/story\/tech\/nation-now\/2016\/01\/14\/nasa-image-shows-starburst-spider-pattern-mars\/78781074\/",
+ "clickable_url": "https:\/\/web.mention.com\/api\/url?token=eyJ0eXAiOiJKV1QiLChbGciOiJIUzI1NiJ9.eyJ1cmwiOJodHRwOlwvXC93d3cudXNhdG9kYkuY29tXC9zdG9eVwvdGVjaFwvbmF0a9uLW5vd1wvMjAxNlwvMDFcLzE0XC9uYXNLWltYWdlLXNob3dzLXN0YJidXJzdC1zcGlZXItcGF0dGVyb1tYXJzXC83ODc4MTA3NFwvIiwiYWNjb3VudF9pZI6NDM4NDA0LCJhbGVydF9pZI6MTE2NTk5NSwic291cmNlXlkIjo0LCJtZW50a9uX2lkIjoiNzU2NzM5MjMzMIifQ.XH7WJlYkOYTDFysZELmouro__7QVoe5pT9c1qeZw",
+ "displayable_url": "usatoday.com\/story\/tech\/nation-now\/2016\/01\/14\/nasa-image-shows-starburst-spider-pattern-mars\/78781074\/",
+ "unique_id": "http:\/\/www.usatoday.com\/story\/tech\/nation-now\/2016\/01\/14\/nasa-image-shows-starburst-spider-pattern-mars\/78781074\/",
+ "published_at": "2016-01-14T17:16:27.10090700+00:00",
+ "created_at": "2016-01-18T16:05:44.0+00:00",
+ "country": "US",
+ "updated_at": "2016-01-18T16:05:45.0+00:00",
+ "favorite": false,
+ "folder": "inbox",
+ "folder_set_by_user": false,
+ "read": false,
+ "tone": 0,
+ "source_type": "news",
+ "source_name": "USA TODAY",
+ "source_url": "http:\/\/www.usatoday.com\/",
+ "language_code": "en",
+ "tasks": [],
+ "logs": [],
+ "children": {
+ "children": [
+ {...mention...},
+ {...mention...},
+ {...mention...}
+ ],
+ "total": 42,
+ "_links": {
+ "more": {
+ "href": "/api/accounts/THE_ACCOUNT_ID/alerts/112233/mentions/527849937/children?limit=20&before_date=2014-03-20T18:10:37.53829200+00:00",
+ "params": {
+ "limit": 20,
+ "before_date": "2014-03-20T18:10:37.53829200+00:00"
+ }
+ }
+ }
+ },
+ "picture_url": "\/\/t3.gstatic.com\/images?q=tbn:ANd9GcQlW3QxiNh2YxxacyF0gR636ViYH6YS_0ONIRj9pf4OhiRZ8hHHyCQqdOYVgMuToZ1Iixhy",
+ "tags": [],
+ "offsets": {
+ "title": [0, 0, 4, 4, 47, 47, 4, 4],
+ "description": [10,10, 4, 4, 57, 57, 4, 4, 72, 72, 4, 4, 79, 79, 4, 4, 161, 161, 4, 4, 223, 223, 4, 4],
+ "url": [],
+ "source_name": [],
+ "source_url": []
+ },
+ "permissions": {
+ "favorite": true,
+ "change_folder": true,
+ "create_task": true
+ },
+ "author_influence": {
+ "id": "3153793048",
+ "alert_id": 112233,
+ "kind": "web",
+ "url": "http:\/\/www.usatoday.com",
+ "name": "usatoday.com",
+ "score": 83,
+ "scored_id": "usatoday.com"
+ },
+ "metadata": {
+ "twitter": {
+ "id_str": "800747234652340224",
+ "user": {
+ "id_str": "188302352"
+ }
+ }
+ }
+}

ADD
zulip_bots/zulip_bots/bots/mention/mention.conf
+[mention]
+access_token = <access_token>
+# create an app here and get the access token from it: https://dev.mention.com/login

ADD
zulip_bots/zulip_bots/bots/mention/mention.py
+# See readme.md for instructions on running this code.
+
+import requests
+from typing import Any, List
+
+class MentionHandler(object):
+ def initialize(self, bot_handler: Any) -> None:
+ self.config_info = bot_handler.get_config_info('mention')
+ self.access_token = self.config_info['access_token']
+ self.account_id = ''
+
+ def usage(self) -> str:
+ return '''
+ This is a Mention API Bot which will find mentions
+ of the given keyword throughout the web.
+ Version 1.00
+ '''
+
+ def handle_message(self, message: Any, bot_handler: Any) -> None:
+ message['content'] = message['content'].strip()
+
+ if message['content'].lower() == 'help':
+ bot_handler.send_reply(message, self.usage())
+ return
+
+ if message['content'] == '':
+ bot_handler.send_reply(message, 'Empty Mention Query')
+ return
+
+ keyword = message['content']
+ content = self.generate_response(keyword)
+ bot_handler.send_reply(message, content)
+
+ def get_account_id(self) -> str:
+ get_ac_id_header = {
+ 'Authorization': 'Bearer ' + self.access_token,
+ 'Accept-Version': '1.15',
+ }
+ response = requests.get(
+ 'https://api.mention.net/api/accounts/me', headers=get_ac_id_header)
+ data_json = response.json()
+ account_id = data_json['account']['id']
+ return account_id
+
+ def get_alert_id(self, keyword: str) -> str:
+ create_alert_header = {
+ 'Authorization': 'Bearer ' + self.access_token,
+ 'Content-Type': 'application/json',
+ 'Accept-Version': '1.15',
+ }
+
+ create_alert_data = {
+ 'name': keyword,
+ 'query': {
+ 'type': 'basic',
+ 'included_keywords': [keyword]
+ },
+ 'languages': ['en'],
+ 'sources': ['web']
+ } # type: Any
+
+ response = requests.post('https://api.mention.net/api/accounts/' + self.account_id +
+ '/alerts', data=create_alert_data, headers=create_alert_header)
+ data_json = response.json()
+ alert_id = data_json['alert']['id']
+ return alert_id
+
+ def get_mentions(self, alert_id: str) -> List[Any]:
+ get_mentions_header = {
+ 'Authorization': 'Bearer ' + self.access_token,
+ 'Accept-Version': '1.15',
+ }
+ response = requests.get('https://api.mention.net/api/accounts/' + self.account_id +
+ '/alerts/' + alert_id + '/mentions', headers=get_mentions_header)
+ data_json = response.json()
+ mentions = data_json['mentions']
+ return mentions
+
+ def generate_response(self, keyword: str) -> str:
+ if self.account_id == '':
+ self.account_id = self.get_account_id()
+
+ try:
+ alert_id = self.get_alert_id(keyword)
+ except (TypeError, KeyError):
+ # Usually triggered by invalid token or json parse error when account quote is finished.
+ raise MentionNoResponseException()
+
+ try:
+ mentions = self.get_mentions(alert_id)
+ except (TypeError, KeyError):
+ # Usually triggered by no response or json parse error when account quota is finished.
+ raise MentionNoResponseException()
+
+ reply = 'The most recent mentions of `' + keyword + '` on the web are: \n'
+ for mention in mentions:
+ reply += "[{title}]({id})\n".format(title=mention['title'], id=mention['original_url'])
+ return reply
+
+handler_class = MentionHandler
+
+class MentionNoResponseException(Exception):
+ pass

ADD
zulip_bots/zulip_bots/bots/mention/test_mention.py
+from zulip_bots.test_lib import BotTestCase
+
+class TestMentionBot(BotTestCase):
+ bot_name = "mention"
+
+ def test_bot_responds_to_empty_message(self) -> None:
+ # Offline query.
+ with self.mock_config_info({'access_token': '12345'}):
+ self.verify_reply('', 'Empty Mention Query')
+
+ def test_help_query(self) -> None:
+ # Offline query.
+ with self.mock_config_info({'access_token': '12345'}):
+ self.verify_reply('help', '''
+ This is a Mention API Bot which will find mentions
+ of the given keyword throughout the web.
+ Version 1.00
+ ''')
+
+ # Offline query.
+ with self.mock_config_info({'access_token': '12345'}):
+ self.verify_reply('hElp', '''
+ This is a Mention API Bot which will find mentions
+ of the given keyword throughout the web.
+ Version 1.00
+ ''')
+
+ # Offline query.
+ with self.mock_config_info({'access_token': '12345'}):
+ self.verify_reply('HELP', '''
+ This is a Mention API Bot which will find mentions
+ of the given keyword throughout the web.
+ Version 1.00
+ ''')

interactive bots: Create Mention bot.