stack_overflow : Create StackOverflow bot.
(Use stackoverflow APIs to answer queries.)
This commit is contained in:
parent
fb228f13ff
commit
9c5eaa2f1b
41
zulip_bots/zulip_bots/bots/stack_overflow/doc.md
Normal file
41
zulip_bots/zulip_bots/bots/stack_overflow/doc.md
Normal file
|
@ -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 @\<stackoverflow-bot-name\>,
|
||||||
|
followed by the query:
|
||||||
|
|
||||||
|
```
|
||||||
|
@<stackoverflow-bot-name> <query>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Beyond the typical obtaining of the zuliprc file, no extra setup is required to use the StackOverflow Bot
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. ```@<stackoverflow-bot-name> <query>``` -
|
||||||
|
fetches the link to the appropriate StackOverflow questions.
|
||||||
|
|
||||||
|
* For example, `@<stackoverflow-bot-name> rest api`
|
||||||
|
will return the links having questions related to rest api.
|
||||||
|
<br>
|
||||||
|
|
||||||
|
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.`
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
3. If no query is provided, the bot will return the help text:
|
||||||
|
|
||||||
|
```Please enter your message after @mention-bot```
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
requests
|
77
zulip_bots/zulip_bots/bots/stack_overflow/stack_overflow.py
Normal file
77
zulip_bots/zulip_bots/bots/stack_overflow/stack_overflow.py
Normal file
|
@ -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 <search query>'''
|
||||||
|
|
||||||
|
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
|
54
zulip_bots/zulip_bots/bots/stack_overflow/test_stack_overflow.py
Executable file
54
zulip_bots/zulip_bots/bots/stack_overflow/test_stack_overflow.py
Executable file
|
@ -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)
|
Loading…
Reference in a new issue