interactive bots: Create Howdoi bot.

This bot allows users to search technical questions from Stack
Overflow.
This commit is contained in:
Tommy Ip 2016-12-24 14:35:56 +00:00 committed by showell
parent a654ba51e1
commit 5ef742638e
6 changed files with 186 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View file

@ -0,0 +1,56 @@
# Howdoi bot
This bot will allow users to get technical answers from
[StackOverflow](https://stackoverflow.com). It is build on top of the
python command line tool [howdoi](https://github.com/gleitz/howdoi) by
Benjamin Gleitzman.
## Usage
Simply prepend your questions with one of the following commands. The
answer will be formatted differently depending the chosen command.
| Command | Respond |
| ----------- | ------------------------------------------------------ |
| `@howdowe` | Concise answer to the same stream. |
| `@howdowe!` | Same as `@howdowe` but with full answer and URL of the solutions. |
| `@howdoi` | Concise answer replied to sender via private message. |
| `@howdoi!` | Same as `@howdoi` but with full answer and URL of the solutions. |
## Screenshots
#### Example 1
Question -> `@howdowe use supervisor in elixir`
![howdowe question](question_howdowe.png)
Answer -> Howdoi would try to **only** respond with the coding section
of the answer.
![howdowe answer](answer_howdowe.png)
#### Example 2
Question -> `@howdoi! stack vs heap`
![howdoi! question](question_howdoi_all.png)
Answer -> Howdoi would return the **full** stackoverflow answer via
**private message** to the original sender. The URL of the answer can be
seen at the bottom of the message.
![howdoi! answer](answer_howdoi_all.png)
**Note:**
* Line wrapped is enabled with a maximum line length of 85 characters.
This could be adjusted in the source code (`HowdoiHandler.MAX_LINE_LENGTH`).
* *Howdoi* generally perform better if you ask a question using keywords
instead of a complete sentences (eg: "How do i make a decorator in Python"
-> "python decorator").
* __[*Limitation*]__ If a answer contains multiple code blocks, the `@howdoi`
and `@howdowe` commands would only return the first coding section, use
`@howdo[we|i]!` in that case.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -0,0 +1,130 @@
"""
This bot uses the python library `howdoi` which is not a
dependency of Zulip. To use this module, you will have to
install it in your local machine. In your terminal, enter
the following command:
$ sudo pip install howdoi --upgrade
Note:
* You might have to use `pip3` if you are using python 3.
* The install command would also download any dependency
required by `howdoi`.
"""
import sys
import logging
from textwrap import fill
try:
from howdoi.howdoi import howdoi
except ImportError:
logging.error("Dependency missing!!\n%s" % (__doc__))
sys.exit(0)
class HowdoiHandler(object):
'''
This plugin facilitates searching Stack Overflow for
techanical answers based on the Python library `howdoi`.
To get the best possible answer, only include keywords
in your questions.
There are two possible commands:
* @howdowe > This would return the answer to the same
stream that it was called from.
* @howdoi > The bot would send a private message to the
user containing the answer.
By default, howdoi only returns the coding section of the
first search result if possible, to see the full answer
from Stack Overflow, append a '!' to the commands.
(ie '@howdoi!', '@howdowe!')
'''
MAX_LINE_LENGTH = 85
def usage(self):
return '''
This plugin will allow users to get techanical
answers from Stackoverflow. Users should preface
their questions with one of the following:
* @howdowe > Answer to the same stream
* @howdoi > Answer via private message
* @howdowe! OR @howdoi! > Full answer from SO
'''
def triage_message(self, message):
cmd_list = ['@howdowe', '@howdoi', '@howdowe!', '@howdoi!']
question = message['content']
# This next line of code is defensive, as we never want
# to get into an infinite loop of searching answers
# from Stackoverflow!
if message['sender_email'].startswith('howdoi'):
return False
is_howdoi = any([question.startswith(cmd) for cmd in cmd_list])
return is_howdoi
def line_wrap(self, string, length):
lines = string.split("\n")
wrapped = [(fill(line) if len(line) > length else line)
for line in lines]
return "\n".join(wrapped).strip()
def get_answer(self, command, query):
question = query[len(command):].strip()
result = howdoi(dict(
query=question,
num_answers=1,
pos=1,
all=command[-1] == '!',
color=False
))
_answer = self.line_wrap(result, HowdoiHandler.MAX_LINE_LENGTH)
answer = "Answer to '%s':\n```\n%s\n```" % (question, _answer)
return answer
def handle_message(self, message, client, state_handler):
question = message['content']
if question.startswith('@howdowe!'):
client.send_message(dict(
type='stream',
to=message['display_recipient'],
subject=message['subject'],
content=self.get_answer('@howdowe!', question)
))
elif question.startswith('@howdoi!'):
client.send_message(dict(
type='private',
to=message['sender_email'],
content=self.get_answer('@howdoi!', question)
))
elif question.startswith('@howdowe'):
client.send_message(dict(
type='stream',
to=message['display_recipient'],
subject=message['subject'],
content=self.get_answer('@howdowe', question)
))
elif question.startswith('@howdoi'):
client.send_message(dict(
type='private',
to=message['sender_email'],
content=self.get_answer('@howdoi', question)
))
handler_class = HowdoiHandler