diff --git a/zulip_bots/setup.py b/zulip_bots/setup.py
index 2c7b8dc6..3026fbc6 100755
--- a/zulip_bots/setup.py
+++ b/zulip_bots/setup.py
@@ -52,6 +52,7 @@ setuptools_info = dict(
'html2text', # for bots/define
'BeautifulSoup4', # for bots/googlesearch
'lxml', # for bots/googlesearch
+ 'requests' # for bots/link_shortener
],
)
diff --git a/zulip_bots/zulip_bots/bots/link_shortener/__init__.py b/zulip_bots/zulip_bots/bots/link_shortener/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/zulip_bots/zulip_bots/bots/link_shortener/doc.md b/zulip_bots/zulip_bots/bots/link_shortener/doc.md
new file mode 100644
index 00000000..0982157c
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/link_shortener/doc.md
@@ -0,0 +1,17 @@
+# Link Shortener Bot
+
+Link Shortener Bot is a Zulip bot that will shorten URLs ("links") in a
+conversation. It uses the [goo.gl URL shortener API] to shorten its links.
+
+Links can be anywhere in the message, for example,
+
+ > @**Link Shortener Bot** @**Joe Smith** See
+ > https://github.com/zulip/python-zulip-api/tree/master/zulip_bots/zulip_bots/bots
+ > for a list of all Zulip bots.
+
+and LS Bot would respond
+
+ > https://github.com/zulip/python-zulip-api/tree/master/zulip_bots/zulip_bots/bots:
+ > **https://goo.gl/NjLZZH**
+
+[goo.gl URL shortener API]: https://goo.gl
diff --git a/zulip_bots/zulip_bots/bots/link_shortener/fixtures/test_normal.json b/zulip_bots/zulip_bots/bots/link_shortener/fixtures/test_normal.json
new file mode 100644
index 00000000..9c1d87be
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/link_shortener/fixtures/test_normal.json
@@ -0,0 +1,19 @@
+{
+ "request": {
+ "api_url": "https://www.googleapis.com/urlshortener/v1/url",
+ "method": "POST",
+ "params": {
+ "key": "qwertyuiop"
+ },
+ "json": {
+ "longUrl": "https://www.github.com/zulip/zulip"
+ }
+ },
+ "response": {
+ "id": "https://goo.gl/6uoWKb"
+ },
+ "response-headers": {
+ "status": 200,
+ "content-type": "application/json; charset=utf-8"
+ }
+}
diff --git a/zulip_bots/zulip_bots/bots/link_shortener/link_shortener.conf b/zulip_bots/zulip_bots/bots/link_shortener/link_shortener.conf
new file mode 100644
index 00000000..c3bacb44
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/link_shortener/link_shortener.conf
@@ -0,0 +1,2 @@
+[link_shortener]
+key = <your API key>
diff --git a/zulip_bots/zulip_bots/bots/link_shortener/link_shortener.py b/zulip_bots/zulip_bots/bots/link_shortener/link_shortener.py
new file mode 100644
index 00000000..04450b82
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/link_shortener/link_shortener.py
@@ -0,0 +1,80 @@
+import re
+import requests
+
+class LinkShortenerHandler(object):
+ '''A Zulip bot that will shorten URLs ("links") in a conversation using the
+ goo.gl URL shortener.
+ '''
+
+ def usage(self):
+ return (
+ 'Mention the link shortener bot in a conversation and then enter '
+ 'any URLs you want to shorten in the body of the message. \n\n'
+ '`key` must be set in `link_shortener.conf`.')
+
+ def initialize(self, bot_handler):
+ self.config_info = bot_handler.get_config_info('link_shortener')
+
+ def handle_message(self, message, bot_handler):
+ REGEX_STR = (
+ '('
+ '(?:http|https):\/\/' # This allows for the HTTP or HTTPS
+ # protocol.
+ '[^"<>#%\{\}|\\^~[\]` ]+' # This allows for any character except
+ # for certain non-URL-safe ones.
+ ')'
+ )
+
+ content = message['content']
+
+ if content.strip() == 'help':
+ bot_handler.send_reply(
+ message,
+ (
+ 'Mention the link shortener bot in a conversation and '
+ 'then enter any URLs you want to shorten in the body of '
+ 'the message.'
+ )
+ )
+ return
+
+ link_matches = re.findall(REGEX_STR, content)
+
+ shortened_links = [self.shorten_link(link) for link in link_matches]
+ link_pairs = [
+ (link_match + ': ' + shortened_link)
+ for link_match, shortened_link
+ in zip(link_matches, shortened_links)
+ if shortened_link != ''
+ ]
+ final_response = '\n'.join(link_pairs)
+
+ if final_response == '':
+ bot_handler.send_reply(
+ message,
+ 'No links found. Send "help" to see usage instructions.'
+ )
+ return
+
+ bot_handler.send_reply(message, final_response)
+
+ def shorten_link(self, long_url):
+ '''Shortens a link using goo.gl Link Shortener and returns it, or
+ returns an empty string if something goes wrong.
+
+ Parameters:
+ long_url (str): The original URL to shorten.
+ '''
+
+ body = {'longUrl': long_url}
+ params = {'key': self.config_info['key']}
+
+ request = requests.post(
+ 'https://www.googleapis.com/urlshortener/v1/url',
+ json=body,
+ params=params
+ )
+
+ return request.json().get('id', '')
+
+handler_class = LinkShortenerHandler
diff --git a/zulip_bots/zulip_bots/bots/link_shortener/test_link_shortener.py b/zulip_bots/zulip_bots/bots/link_shortener/test_link_shortener.py
new file mode 100644
index 00000000..fcff3ba5
--- /dev/null
+++ b/zulip_bots/zulip_bots/bots/link_shortener/test_link_shortener.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+from zulip_bots.test_lib import BotTestCase
+
+class TestLinkShortenerBot(BotTestCase):
+ bot_name = "link_shortener"
+
+ def test_bot(self):
+ MESSAGE = 'Shorten https://www.github.com/zulip/zulip please.'
+ RESPONSE = 'https://www.github.com/zulip/zulip: https://goo.gl/6uoWKb'
+
+ with self.mock_config_info({'key': 'qwertyuiop'}), \
+ self.mock_http_conversation('test_normal'):
+ self.initialize_bot()
+
+ self.assert_bot_response(
+ message = {'content': MESSAGE},
+ response = {'content': RESPONSE},
+ expected_method='send_reply'
+ )
+
+ def test_bot_empty(self):
+ MESSAGE = 'Shorten nothing please.'
+ RESPONSE = 'No links found. Send "help" to see usage instructions.'
+
+ # No `mock_http_conversation` is necessary because the bot will
+ # recognize that no links are in the message and won't make any HTTP
+ # requests.
+ with self.mock_config_info({'key': 'qwertyuiop'}):
+ self.initialize_bot()
+
+ self.assert_bot_response(
+ message = {'content': MESSAGE},
+ response = {'content': RESPONSE},
+ expected_method='send_reply'
+ )
+
+ def test_bot_help(self):
+ MESSAGE = 'help'
+ RESPONSE = (
+ 'Mention the link shortener bot in a conversation and then enter '
+ 'any URLs you want to shorten in the body of the message.'
+ )
+
+ # No `mock_http_conversation` is necessary because the bot will
+ # recognize that the message is 'help' and won't make any HTTP
+ # requests.
+ with self.mock_config_info({'key': 'qwertyuiop'}):
+ self.initialize_bot()
+
+ self.assert_bot_response(
+ message = {'content': MESSAGE},
+ response = {'content': RESPONSE},
+ expected_method='send_reply'
+ )