Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add developer documentation for bots #2766

Merged
merged 1 commit into from Dec 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
232 changes: 232 additions & 0 deletions docs/bots-guide.md
@@ -0,0 +1,232 @@
# Developing bots
**This feature is still experimental.**

The contrib_bots system is a new part of Zulip that allows
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should insert a new line break here.

bot developers to write a large class of bots by simply reacting to messages.

With bots, you *can*

* intercept and view messages sent by users on Zulip
* send out new messages

With bots, you *cannot*

* modify an intercepted message (you have to send a new message)
* send messages on behalf of other users
* intercept private messages


On this page you'll find:

* A step-by-step [tutorial](#how-to-deploy-a-bot) on how to deploy a bot.
* A step-by-step [tutorial](#how-to-develop-a-bot) on how to develop a bot.
* A [documentation](#bot-api) of the bot API.
* Common [problems](#common-problems) when developing/deploying bots and their solutions.

Contributions to this guide are very welcome, so if you run into any
issues following these instructions or come up with any tips or tools
that help with writing bots, please visit the [Zulip chat](chat.zulip.org), open an issue, or submit a pull request
to share your ideas!

## How to deploy a bot
This guide will show you how to deploy a bot on your running Zulip server.
It presumes that you already have a fully implemented `<my-bot>.py` bot and now want to try it out.

1. Copy your bot `<my-bot>.py` to `~/zulip/contrib_bots/lib/`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add line breaks after each point and it's subpoints, to improve readability.


* This is the place where all Zulip bots are stored.

* You can also test out bots that already exist in this directory.

2. Run your Zulip server. Bots can only be deployed on running systems.

3. Register a new bot on your Zulip server's web interface.

* Navigate to *Settings* -> *Your Bots* -> *Add a New Bot*, fill out the form and click on *Create Bot*.

* A new bot should appear in the *Your Bots* panel.

4. Add the bot's configuration file on your Zulip server.

* In the *Your Bots* panel, click on the green icon to download its configuration file *.zuliprc*
(the structure of this file is explained [here](#configuration-file).

* Copy the file to a destination of your choice on your Zulip server, e.g. to `~/.zuliprc` or `~/zuliprc-test`.

5. Subscribe the bot to the streams that the bot needs to read messages from or write messages to.
* To subscribe your bot to streams, navigate to *Manage Streams*. Select a stream and
add your bot by its email address (the address you assigned in step 3).

* Now, the bot will do its job on the streams you subscribed it to.

6. Run the bot.
* On your Zulip server (and outside the Vagrant environment), navigate to `~/zulip/contrib_bots/`

* Run `python run.py ~/zulip/contrib_bots/<my-bot>.py --config-file ~/.zuliprc`. The `~/` before `.zuliprc`
should point to the directory containing the file (in this case, it is the home directory).

* Check the output of the command. It should start with the text the `usage` function returns,
followed by logging output similar to this:
```
INFO:root:starting message handling...
INFO:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): localhost
```
* Congrats! Now, your bot should be ready to test on the streams you've subscribed it to.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Describe what the output looks like when you start a bot. It shows usage then it shows HTTP connection stuff.

### Test the `followup.py` bot
1. Do the previous steps for the `followup.py` bot.
2. Create the *followup* stream.
3. Subscribe the bot to the newly created *followup* stream and a stream you want to use it from, e.g. *social*.
4. Send a message to the stream you've subscribed the bot to (other than *followup*). If everything works, a copy of the message should now pop up in the *followup* stream.

## How to develop a bot

The tutorial below explains the structure of a bot `<my-bot>.py`. You can use this as boilerplate code for developing your own bot.

Every bot is built upon this structure:
```
class MyBotHandler(object):
'''
A docstring documenting this bot.
'''

def usage(self):
return '''Your description of the bot'''

def triage_message(self, message):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a heads up that we may add another parameter here called "client." No need to address this now.

#add your code here

def handle_message(self, message, client, state_handler):
# add your code here

handler_class = MyBotHandler
```
* The class name (in this case *MyBotHandler*) can be defined by you and should match the name of your bot. To register your bot's class, adjust the last line `handler_class = MyBotHandler` to match your class name.

* Every bot needs to implement the functions
* `usage(self)`
* `triage_message(self, message)`
* `handle_message(self, message, client)`

* These functions are documented in the [next section](#bot-api).

## Bot API
This section documents the functions every bot needs to implement and the structure of the bot's config file.

### usage
*usage(self)*

is called to retrieve information about the bot.

#### Arguments
* self - the instance the method is called on.

#### Return values
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing double line break before this line.

* A string describing the bot's functionality

#### Example implementation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing double line break before this line.

```
def usage(self):
return '''
This plugin will allow users to flag messages
as being follow-up items. Users should preface
messages with "@followup".
Before running this, make sure to create a stream
called "followup" that your API user can send to.
'''
```

### triage_message
*triage_message(self, message)*

is called when a message was sent.

#### Arguments
* self - the instance the method is called on

* message - a dictionary containing information about the message, e.g.
* content - the content of the message
* content_type - the type of the content, e.g. *'text/x-markdown'* for normal messages
* display_recipient - the name of the stream the message is sent to (string)
* is_mentioned - is the bot pinged with an '@' in the message? (boolean)
* sender_email - email of the sender (string)
* sender_full_name - full name of the sender (string)
* subject - topic of the message (string)
* timestamp - when was the message sent (integer)

#### Return values
* True if the bot should react to this message
* False otherwise

#### Example implementation
```
def triage_message(self, message):
original_content = message['content']
if message['display_recipient'] == 'followup':
return False
is_follow_up = (original_content.startswith('@followup') or
original_content.startswith('@follow-up'))
return is_follow_up
```

### handle_message
*handle_message(self, message, client)*

is called when `triage_message` returns true, handles user message.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, same thing here, like usage.

#### Arguments
* self - the instance the method is called on.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superfluous full stop.

Also more spacing between * here.


* message - a dictionary describing a Zulip message (click [here](#triage_message) to see more about the message fields)

* client - used to interact with the server, e.g. to send a message
* use client.send_message(message) to send a message

* state_handler - used to save states/information of the bot **beta**
* use `state_handler.set_state(state)` to set a state (any object)
* use `state_handler.get_state()` to retrieve the state set; returns a `NoneType` object if no state is set

#### Return values
None.

#### Example implementation

```
def handle_message(self, message, client, state_handler):
original_content = message['content']
original_sender = message['sender_email']
new_content = original_content.replace('@followup',
'from %s:' % (original_sender,))

client.send_message(dict(
type='stream',
to='followup',
subject=message['sender_email'],
content=new_content,
))
```

### Configuration file

```
[api]
key=<api-key>
email=<email>
site=<dev-url>
```

* key - the API key you created for the bot; this is how Zulip knows the request is from an authorized user.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you actually create the API key? Don't you receive an API key for the bot?


* email - the email address of the bot, e.g. `some-bot@zulip.com`

* site - your development environment URL; if you are working on a development environment hosted on your computer, use `localhost:9991`

## Common problems
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The indentation and rendering here are off because of missing whitespace before this line.

* I modified my bot's code, yet the changes don't seem to have an effect.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple other problems are you forgot to store the host or you didn't create your API config correctly.

* Ensure that you restarted the `run.py` script.

* My bot won't start
* Ensure that your API config file is correct (download the config file from the server).

* My bot works only on some streams.
* Subscribe your bot to other streams, as described [here](#how-to-deploy-a-bot).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think My bot doesn't work is by far the most common problem.

I'd say check API keys, check server address and make sure your bot is subscribed to the streams you're using it in are the most basic answers.

1 change: 1 addition & 0 deletions docs/index.rst
Expand Up @@ -76,6 +76,7 @@ Contents:
:caption: Developer tutorials

integration-guide
bots-guide
new-feature-tutorial
writing-views
life-of-a-request
Expand Down