diff --git a/zulip_bots/setup.py b/zulip_bots/setup.py index 2c7b8dc..3026fbc 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 0000000..e69de29 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 0000000..0982157 --- /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 0000000..9c1d87b --- /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 0000000..c3bacb4 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/link_shortener/link_shortener.conf @@ -0,0 +1,2 @@ +[link_shortener] +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 0000000..04450b8 --- /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 0000000..fcff3ba --- /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' + )