diff --git a/contrib_bots/lib/readme-yoda-bot.md b/contrib_bots/lib/readme-yoda-bot.md new file mode 100644 index 0000000..f0616fd --- /dev/null +++ b/contrib_bots/lib/readme-yoda-bot.md @@ -0,0 +1,79 @@ +# Overview + +This is the documentation for how to set up and run the yoda_bot. (`yoda_bot.py`) + +This directory contains library code for running Zulip +bots that react to messages sent by users. + +This bot will allow users to translate a sentence into 'Yoda speak'. +It looks for messages starting with '@yoda'. You will need to have a +Mashape API key. Please see instructions for getting one below. + +## Setup +This bot uses the python library `unirest` 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 unirest --upgrade +Note: + +You might have to use `pip3` if you are using python 3. +The install command would also download any dependency +required by `unirest`. + +Before running this bot, make sure to get a Mashape API Key. +Go to this link: + +This is the API that powers the `yoda_bot`. You can read more about it +on this page. + +![yoda api overview](yoda-speak-api.png) + +Click on the **Sign Up Free** button at the top and create +an account. Then click on the **Documentation** tab. Scroll down to the +bottom, and click on the **Test Endpoint** button. +This will add the Yoda Speak API to your default application. You can +also add it to a different application if you wish. Now click on the +**Applications** tab at the top. Select the application that you added +the Yoda Speak API to. Click on the blue **GET THE KEYS** button. + +On the pop-up that comes up, click on the **COPY** button. +This is your Mashape API Key. It is used +to authenticate. Store it in the `yoda_api_key.txt` file. + +The `yoda_api_key.txt` file should be located at `~/yoda_api_key.txt`. + +Example input: + + @yoda You will learn how to speak like me someday. + +If you need help while the bot is running just input `@yoda help`. + +## Running the bot + +Here is an example of running the "yoda_bot" bot from +inside a Zulip repo: + + cd ~/zulip/contrib_bots + ./run.py lib/yoda_bot.py --config-file ~/.zuliprc-prod + +Once the bot code starts running, you will see a +message explaining how to use the bot, as well as +some log messages. You can use the `--quiet` option +to suppress some of the informational messages. + +The bot code will run continuously until you kill them with +control-C (or otherwise). + +### Configuration + +For this document we assume you have some prior experience +with using the Zulip API, but here is a quick review of +what a `.zuliprc` files looks like. You can connect to the +API as your own human user, or you can go into the Zulip settings +page to create a user-owned bot. + + [api] + email=someuser@example.com + key= + site=https://zulip.somewhere.com diff --git a/contrib_bots/lib/yoda-speak-api.png b/contrib_bots/lib/yoda-speak-api.png new file mode 100755 index 0000000..d887ab4 Binary files /dev/null and b/contrib_bots/lib/yoda-speak-api.png differ diff --git a/contrib_bots/lib/yoda_bot.py b/contrib_bots/lib/yoda_bot.py new file mode 100644 index 0000000..e4c65cd --- /dev/null +++ b/contrib_bots/lib/yoda_bot.py @@ -0,0 +1,166 @@ +# See readme-yoda-bot.md for instructions on running this code. + +""" +This bot uses the python library `unirest` 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 unirest --upgrade +Note: + * You might have to use `pip3` if you are using python 3. + * The install command would also download any dependency + required by `unirest`. +""" + +from __future__ import print_function +import os +import logging +import ssl +import sys + +try: + import unirest +except ImportError: + logging.error("Dependency missing!!\n%s" % (__doc__)) + sys.exit(0) + + +HELP_MESSAGE = ''' + This bot allows users to translate a sentence into + 'Yoda speak'. + Users should preface messages with '@yoda'. + + Before running this, make sure to get a Mashape Api token. + Instructions are in the 'readme-yoda-bot.md' file. + Store it in the 'yoda_api_key.txt' file. + The 'yoda_api_key.txt' file should be located at '~/yoda_api_key.txt'. + Example input: + @yoda You will learn how to speak like me someday. + ''' + + +class ApiKeyError(Exception): + '''raise this when there is an error with the Mashape Api Key''' + + +class YodaSpeakHandler(object): + ''' + This bot will allow users to translate a sentence into 'Yoda speak'. + It looks for messages starting with '@yoda'. + ''' + + def usage(self): + return ''' + This bot will allow users to translate a sentence into + 'Yoda speak'. + Users should preface messages with '@yoda'. + + Before running this, make sure to get a Mashape Api token. + Instructions are in the 'readme-yoda-bot.md' file. + Store it in the 'yoda_api_key.txt' file. + The 'yoda_api_key.txt' file should be located at '~/yoda_api_key.txt'. + Example input: + @yoda You will learn how to speak like me someday. + ''' + + def triage_message(self, message): + # return True iff we want to (possibly) response to this message + + original_content = message['content'] + + return original_content.startswith('@yoda') + + def handle_message(self, message, client, state_handler): + original_content = message['content'] + stream = message['display_recipient'] + subject = message['subject'] + + # this handles the message if its starts with @yoda + if original_content.startswith('@yoda'): + handle_input(client, original_content, stream, subject) + +handler_class = YodaSpeakHandler + + +def send_to_yoda_api(sentence, api_key): + # function for sending sentence to api + + response = unirest.get("https://yoda.p.mashape.com/yoda?sentence=" + sentence, + headers={ + "X-Mashape-Key": api_key, + "Accept": "text/plain" + } + ) + + if response.code == 200: + return response.body + if response.code == 403: + raise ApiKeyError + else: + error_message = response.body['message'] + logging.error(error_message) + error_code = response.code + error_message = error_message + 'Error code: ' + error_code +\ + ' Did you follow the instructions in the `readme-yoda-bot.md` file?' + return error_message + + +def format_input(original_content): + # replaces the '@yoda' with nothing, so that '@yoda' doesn't get sent to the api + message_content = original_content.replace('@yoda', '') + # gets rid of whitespace around the edges, so that they aren't a problem in the future + message_content = message_content.strip() + # replaces all spaces with '+' to be in the format the api requires + sentence = message_content.replace(' ', '+') + return sentence + + +def handle_input(client, original_content, stream, subject): + + if is_help(original_content): + send_message(client, HELP_MESSAGE, stream, subject) + + else: + sentence = format_input(original_content) + try: + reply_message = send_to_yoda_api(sentence, get_api_key()) + + except ssl.SSLError or TypeError: + reply_message = 'The service is temporarily unavailable, please try again.' + logging.error(reply_message) + + except ApiKeyError: + reply_message = 'Invalid Api Key. Did you follow the instructions in the ' \ + '`readme-yoda-bot.md` file?' + logging.error(reply_message) + + send_message(client, reply_message, stream, subject) + + +def get_api_key(): + # function for getting Mashape api key + home = os.path.expanduser('~') + with open(home + '/yoda_api_key.txt') as api_key_file: + api_key = api_key_file.read().strip() + return api_key + + +def send_message(client, message, stream, subject): + # function for sending a message + client.send_message(dict( + type='stream', + to=stream, + subject=subject, + content=message + )) + + +def is_help(original_content): + # replaces the '@yoda' with nothing, so that '@yoda' doesn't get sent to the api + message_content = original_content.replace('@yoda', '') + # gets rid of whitespace around the edges, so that they aren't a problem in the future + message_content = message_content.strip() + if message_content == 'help': + return True + else: + return False