diff --git a/contrib_bots/lib/HowdoiBot/answer_howdoi_all.png b/contrib_bots/lib/HowdoiBot/answer_howdoi_all.png new file mode 100644 index 0000000..ee71253 Binary files /dev/null and b/contrib_bots/lib/HowdoiBot/answer_howdoi_all.png differ diff --git a/contrib_bots/lib/HowdoiBot/answer_howdowe.png b/contrib_bots/lib/HowdoiBot/answer_howdowe.png new file mode 100644 index 0000000..35eaadd Binary files /dev/null and b/contrib_bots/lib/HowdoiBot/answer_howdowe.png differ diff --git a/contrib_bots/lib/HowdoiBot/docs.md b/contrib_bots/lib/HowdoiBot/docs.md new file mode 100644 index 0000000..4d01490 --- /dev/null +++ b/contrib_bots/lib/HowdoiBot/docs.md @@ -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. diff --git a/contrib_bots/lib/HowdoiBot/question_howdoi_all.png b/contrib_bots/lib/HowdoiBot/question_howdoi_all.png new file mode 100644 index 0000000..871f293 Binary files /dev/null and b/contrib_bots/lib/HowdoiBot/question_howdoi_all.png differ diff --git a/contrib_bots/lib/HowdoiBot/question_howdowe.png b/contrib_bots/lib/HowdoiBot/question_howdowe.png new file mode 100644 index 0000000..f779d6a Binary files /dev/null and b/contrib_bots/lib/HowdoiBot/question_howdowe.png differ diff --git a/contrib_bots/lib/howdoi_bot.py b/contrib_bots/lib/howdoi_bot.py new file mode 100644 index 0000000..3eb78c0 --- /dev/null +++ b/contrib_bots/lib/howdoi_bot.py @@ -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