diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/__init__.py b/zulip_bots/zulip_bots/bots/stack_overflow/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/doc.md b/zulip_bots/zulip_bots/bots/stack_overflow/doc.md new file mode 100644 index 0000000..4616753 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/doc.md @@ -0,0 +1,41 @@ +# StackOverflow Bot + +The StackOverflow bot is a Zulip bot that will search Stackoverflow +for a provided set of keywords or a question, and fetch a link to the associated +query. The link is returned to the same stream +it was @mentioned in + +The Stackoverflow bot uses the +[StackExchange API](http://api.stackexchange.com/docs) +to obtain the search results it returns + +Using the StackOverflow bot is as simple as mentioning @\, +followed by the query: + +``` +@ +``` + +## Setup + +Beyond the typical obtaining of the zuliprc file, no extra setup is required to use the StackOverflow Bot + +## Usage + +1. ```@ ``` - +fetches the link to the appropriate StackOverflow questions. + + * For example, `@ rest api` +will return the links having questions related to rest api. +
+ +2. If there are no questions related to the query, +the bot will respond with an error message: + + `I am sorry. The search query you provided is does not have any related results.` + +
+ +3. If no query is provided, the bot will return the help text: + + ```Please enter your message after @mention-bot``` diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_incorrect_query.json b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_incorrect_query.json new file mode 100644 index 0000000..7093910 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_incorrect_query.json @@ -0,0 +1,17 @@ +{ + "request": { + "api_url":"http://api.stackexchange.com/2.2/search/advanced?order=desc&sort=relevance&site=stackoverflow&title=narendra" + }, + "response": { + "data": { + "status_code":200 + }, + "items": [ + ] + }, + "response-headers":{ + "status":200, + "ok":true, + "content-type":"application/json; charset=utf-8" + } +} diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_multi_word.json b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_multi_word.json new file mode 100644 index 0000000..c20004a --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_multi_word.json @@ -0,0 +1,21 @@ +{ + "request": { + "api_url":"http://api.stackexchange.com/2.2/search/advanced?order=desc&sort=relevance&site=stackoverflow&title=what%20is%20flutter" + }, + "response": { + "data": { + "status_code":200 + }, + "items": [ + { + "title":"What is flutter/dart and what are its benefits over other tools?", + "link":"https://stackoverflow.com/questions/49023008/what-is-flutter-dart-and-what-are-its-benefits-over-other-tools" + } + ] + }, + "response-headers":{ + "status":200, + "ok":true, + "content-type":"application/json; charset=utf-8" + } +} diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_number_query.json b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_number_query.json new file mode 100644 index 0000000..ed6e291 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_number_query.json @@ -0,0 +1,29 @@ +{ + "request": { + "api_url":"http://api.stackexchange.com/2.2/search/advanced?order=desc&sort=relevance&site=stackoverflow&title=113" + }, + "response": { + "data": { + "status_code":200 + }, + "items": [ + { + "title":"INSTALL_FAILED_NO_MATCHING_ABIS res-113", + "link":"https://stackoverflow.com/questions/47117788/install-failed-no-matching-abis-res-113" + }, + { + "title":"com.sun.tools.xjc.reader.Ring.get(Ring.java:113)", + "link":"https://stackoverflow.com/questions/12848282/com-sun-tools-xjc-reader-ring-getring-java113" + }, + { + "title":"no route to host error 113", + "link":"https://stackoverflow.com/questions/10516222/no-route-to-host-error-113" + } + ] + }, + "response-headers":{ + "status":200, + "ok":true, + "content-type":"application/json; charset=utf-8" + } +} diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_single_word.json b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_single_word.json new file mode 100644 index 0000000..19d8942 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_single_word.json @@ -0,0 +1,29 @@ +{ + "request": { + "api_url":"http://api.stackexchange.com/2.2/search/advanced?order=desc&sort=relevance&site=stackoverflow&title=restful" + }, + "response": { + "data": { + "status_code":200 + }, + "items": [ + { + "title":"What exactly is RESTful programming?", + "link":"https://stackoverflow.com/questions/671118/what-exactly-is-restful-programming" + }, + { + "title":"RESTful Authentication", + "link":"https://stackoverflow.com/questions/319530/restful-authentication" + }, + { + "title":"RESTful URL design for search", + "link":"https://stackoverflow.com/questions/319530/restful-authentication" + } + ] + }, + "response-headers":{ + "status":200, + "ok":true, + "content-type":"application/json; charset=utf-8" + } +} diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_status_code.json b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_status_code.json new file mode 100644 index 0000000..34480ae --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/fixtures/test_status_code.json @@ -0,0 +1,16 @@ +{ + "request": { + "api_url":"http://api.stackexchange.com/2.2/search/advanced?order=desc&sort=relevance&site=stackoverflow&title=Zulip" + }, + "response": { + "data": { + "status_code":404 + }, + "items": [ + ] + }, + "response-headers":{ + "status":404, + "content-type":"text/html" + } +} diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/requirements.txt b/zulip_bots/zulip_bots/bots/stack_overflow/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/stack_overflow.py b/zulip_bots/zulip_bots/bots/stack_overflow/stack_overflow.py new file mode 100644 index 0000000..cd1e1e1 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/stack_overflow.py @@ -0,0 +1,77 @@ +import requests +import logging +import re +import urllib +from zulip_bots.lib import Any + +from typing import Optional, Any, Dict + +# See readme.md for instructions on running this code. + +class StackOverflowHandler(object): + ''' + This plugin facilitates searching Stack Overflow for a + specific query and returns the top 3 questions from the + search. It looks for messages starting with '@mention-bot' + + In this example, we write all Stack Overflow searches into + the same stream that it was called from. + ''' + + META = { + 'name': 'StackOverflow', + 'description': 'Searches Stack Overflow for a query and returns the top 3 articles.', + } + + def usage(self) -> str: + return ''' + This plugin will allow users to directly search + Stack Overflow for a specific query and get the top 3 + articles that are returned from the search. Users + should preface query with "@mention-bot". + @mention-bot ''' + + def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None: + bot_response = self.get_bot_stackoverflow_response(message, bot_handler) + bot_handler.send_reply(message, bot_response) + + def get_bot_stackoverflow_response(self, message: Dict[str, str], bot_handler: Any) -> Optional[str]: + '''This function returns the URLs of the requested topic.''' + + help_text = 'Please enter your query after @mention-bot to search StackOverflow' + + # Checking if the link exists. + query = message['content'] + if query == '' or query == 'help': + return help_text + + query_stack_link = ('http://api.stackexchange.com/2.2/search/advanced?' + 'order=desc&sort=relevance&site=stackoverflow&title=%s' + % (urllib.parse.quote(query),)) + try: + data = requests.get(query_stack_link) + + except requests.exceptions.RequestException: + logging.error('broken link') + return 'Uh-Oh ! Sorry ,couldn\'t process the request right now.:slightly_frowning_face:\n' \ + 'Please try again later.' + + # Checking if the bot accessed the link. + if data.status_code != 200: + logging.error('Page not found.') + return 'Uh-Oh ! Sorry ,couldn\'t process the request right now.:slightly_frowning_face:\n' \ + 'Please try again later.' + + new_content = 'For search term:' + query + '\n' + + # Checking if there is content for the searched term + if len(data.json()['items']) == 0: + new_content = 'I am sorry. The search term you provided is not found :slightly_frowning_face:' + else: + for i in range(min(3, len(data.json()['items']))): + search_string = data.json()['items'][i]['title'] + link = data.json()['items'][i]['link'] + new_content += str(i+1) + ' : ' + '[' + search_string + ']' + '(' + link + ')\n' + return new_content + +handler_class = StackOverflowHandler diff --git a/zulip_bots/zulip_bots/bots/stack_overflow/test_stack_overflow.py b/zulip_bots/zulip_bots/bots/stack_overflow/test_stack_overflow.py new file mode 100755 index 0000000..8bec785 --- /dev/null +++ b/zulip_bots/zulip_bots/bots/stack_overflow/test_stack_overflow.py @@ -0,0 +1,54 @@ +from zulip_bots.test_lib import BotTestCase +from zulip_bots.request_test_lib import mock_request_exception + +class TestStackoverflowBot(BotTestCase): + bot_name = "stack_overflow" + + def test_bot(self) -> None: + + # Single-word query + bot_request = 'restful' + bot_response = ('''For search term:restful +1 : [What exactly is RESTful programming?](https://stackoverflow.com/questions/671118/what-exactly-is-restful-programming) +2 : [RESTful Authentication](https://stackoverflow.com/questions/319530/restful-authentication) +3 : [RESTful URL design for search](https://stackoverflow.com/questions/319530/restful-authentication) +''') + with self.mock_http_conversation('test_single_word'): + self.verify_reply(bot_request, bot_response) + + # Multi-word query + bot_request = 'what is flutter' + bot_response = ('''For search term:what is flutter +1 : [What is flutter/dart and what are its benefits over other tools?](https://stackoverflow.com/questions/49023008/what-is-flutter-dart-and-what-are-its-benefits-over-other-tools) +''') + with self.mock_http_conversation('test_multi_word'): + self.verify_reply(bot_request, bot_response) + + # Number query + bot_request = '113' + bot_response = ('''For search term:113 +1 : [INSTALL_FAILED_NO_MATCHING_ABIS res-113](https://stackoverflow.com/questions/47117788/install-failed-no-matching-abis-res-113) +2 : [com.sun.tools.xjc.reader.Ring.get(Ring.java:113)](https://stackoverflow.com/questions/12848282/com-sun-tools-xjc-reader-ring-getring-java113) +3 : [no route to host error 113](https://stackoverflow.com/questions/10516222/no-route-to-host-error-113) +''') + with self.mock_http_conversation('test_number_query'): + self.verify_reply(bot_request, bot_response) + + # Incorrect word + bot_request = 'narendra' + bot_response = "I am sorry. The search term you provided is not found :slightly_frowning_face:" + with self.mock_http_conversation('test_incorrect_query'): + self.verify_reply(bot_request, bot_response) + + # 404 status code + bot_request = 'Zulip' + bot_response = 'Uh-Oh ! Sorry ,couldn\'t process the request right now.:slightly_frowning_face:\n' \ + 'Please try again later.' + + with self.mock_http_conversation('test_status_code'): + self.verify_reply(bot_request, bot_response) + + # Request Exception + bot_request = 'Z' + with mock_request_exception(): + self.verify_reply(bot_request, bot_response)