2016-12-20 20:16:02 +01:00
|
|
|
# Writing bots
|
2016-12-18 16:11:46 +01:00
|
|
|
**This feature is still experimental.**
|
|
|
|
|
|
|
|
The contrib_bots system is a new part of Zulip that allows
|
|
|
|
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
|
2017-01-07 23:16:43 +01:00
|
|
|
* intercept private messages (except for PMs that are sent to the bot)
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
|
|
|
|
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
|
2016-12-20 04:30:25 +01:00
|
|
|
that help with writing bots, please visit the [Zulip chat](https://chat.zulip.org), open an issue, or submit a pull request
|
2016-12-18 16:11:46 +01:00
|
|
|
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/`.
|
|
|
|
|
2016-12-20 03:25:35 +01:00
|
|
|
* This is the place where all Zulip bots are stored.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
2016-12-20 03:25:35 +01:00
|
|
|
* You can also test out bots that already exist in this directory.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
2017-01-17 02:26:22 +01:00
|
|
|
* Navigate to *Settings* -> *Your bots* -> *Add a New Bot*, fill
|
2017-01-17 02:26:42 +01:00
|
|
|
out the form and click on *Create bot*.
|
2017-01-17 02:26:22 +01:00
|
|
|
* A new bot should appear in the *Your bots* panel.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
4. Add the bot's configuration file on your Zulip server.
|
|
|
|
|
2017-01-17 02:26:22 +01:00
|
|
|
* In the *Your bots* panel, click on the green icon to download
|
2017-01-05 23:23:16 +01:00
|
|
|
its configuration file *.zuliprc* (the structure of this file is
|
|
|
|
explained [here](#configuration-file).
|
2016-12-20 03:25:35 +01:00
|
|
|
* Copy the file to a destination of your choice on your Zulip server, e.g. to `~/.zuliprc` or `~/zuliprc-test`.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
5. Subscribe the bot to the streams that the bot needs to read messages from or write messages to.
|
|
|
|
|
2017-01-05 23:23:16 +01:00
|
|
|
* 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).
|
2016-12-20 03:25:35 +01:00
|
|
|
* Now, the bot will do its job on the streams you subscribed it to.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
6. Run the bot.
|
|
|
|
|
2016-12-20 03:25:35 +01:00
|
|
|
* On your Zulip server (and outside the Vagrant environment), navigate to `~/zulip/contrib_bots/`
|
2017-01-05 23:23:16 +01:00
|
|
|
* Run `python run.py ~/zulip/contrib_bots/lib/<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
|
|
|
|
```
|
|
|
|
|
2016-12-20 03:25:35 +01:00
|
|
|
* Congrats! Now, your bot should be ready to test on the streams you've subscribed it to.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
### Test the `followup.py` bot
|
2017-01-05 23:23:16 +01:00
|
|
|
|
2016-12-18 16:11:46 +01:00
|
|
|
1. Do the previous steps for the `followup.py` bot.
|
|
|
|
2. Create the *followup* stream.
|
2017-01-05 23:23:16 +01:00
|
|
|
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.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
## How to develop a bot
|
|
|
|
|
2017-01-05 23:23:16 +01:00
|
|
|
The tutorial below explains the structure of a bot `<my-bot>.py`. You
|
|
|
|
can use this as boilerplate code for developing your own bot.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
Every bot is built upon this structure:
|
2017-01-05 23:23:16 +01:00
|
|
|
|
2016-12-18 16:11:46 +01:00
|
|
|
```
|
|
|
|
class MyBotHandler(object):
|
|
|
|
'''
|
|
|
|
A docstring documenting this bot.
|
|
|
|
'''
|
|
|
|
|
|
|
|
def usage(self):
|
|
|
|
return '''Your description of the bot'''
|
|
|
|
|
2016-12-27 00:05:11 +01:00
|
|
|
def triage_message(self, message, client):
|
2016-12-18 16:11:46 +01:00
|
|
|
#add your code here
|
|
|
|
|
|
|
|
def handle_message(self, message, client, state_handler):
|
|
|
|
# add your code here
|
|
|
|
|
|
|
|
handler_class = MyBotHandler
|
|
|
|
```
|
2017-01-05 23:23:16 +01:00
|
|
|
|
|
|
|
* 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.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
* Every bot needs to implement the functions
|
2016-12-20 03:25:35 +01:00
|
|
|
* `usage(self)`
|
2016-12-27 00:05:11 +01:00
|
|
|
* `triage_message(self, message, client)`
|
2016-12-20 03:25:35 +01:00
|
|
|
* `handle_message(self, message, client)`
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
* These functions are documented in the [next section](#bot-api).
|
|
|
|
|
|
|
|
## Bot API
|
2017-01-05 23:23:16 +01:00
|
|
|
|
|
|
|
This section documents the functions every bot needs to implement and
|
|
|
|
the structure of the bot's config file.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
### usage
|
|
|
|
*usage(self)*
|
|
|
|
|
|
|
|
is called to retrieve information about the bot.
|
|
|
|
|
|
|
|
#### Arguments
|
|
|
|
* self - the instance the method is called on.
|
|
|
|
|
|
|
|
#### Return values
|
|
|
|
* A string describing the bot's functionality
|
|
|
|
|
|
|
|
#### Example implementation
|
|
|
|
```
|
|
|
|
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
|
2016-12-27 00:05:11 +01:00
|
|
|
*triage_message(self, message, client)*
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
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.
|
2016-12-20 03:25:35 +01:00
|
|
|
* 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)
|
2016-12-18 16:11:46 +01:00
|
|
|
|
2016-12-27 00:05:11 +01:00
|
|
|
* client - contains information about this bot
|
|
|
|
* client.full_name - name of the bot account
|
|
|
|
* client.email - email of the bot account
|
|
|
|
|
2016-12-18 16:11:46 +01:00
|
|
|
#### Return values
|
|
|
|
* True if the bot should react to this message
|
|
|
|
* False otherwise
|
|
|
|
|
|
|
|
#### Example implementation
|
|
|
|
```
|
2016-12-27 00:05:11 +01:00
|
|
|
def triage_message(self, message, client):
|
2016-12-18 16:11:46 +01:00
|
|
|
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.
|
|
|
|
|
|
|
|
#### Arguments
|
|
|
|
* self - the instance the method is called on.
|
|
|
|
|
2016-12-20 04:30:25 +01:00
|
|
|
* message - a dictionary describing a Zulip message
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
* client - used to interact with the server, e.g. to send a message
|
2016-12-20 03:25:35 +01:00
|
|
|
* use client.send_message(message) to send a message
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
* state_handler - used to save states/information of the bot **beta**
|
2016-12-20 03:25:35 +01:00
|
|
|
* 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
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
#### 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>
|
|
|
|
```
|
|
|
|
|
2017-01-05 23:23:16 +01:00
|
|
|
* key - the API key you created for the bot; this is how Zulip knows
|
|
|
|
the request is from an authorized user.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
* email - the email address of the bot, e.g. `some-bot@zulip.com`
|
|
|
|
|
2017-01-05 23:23:16 +01:00
|
|
|
* site - your development environment URL; if you are working on a
|
|
|
|
development environment hosted on your computer, use
|
|
|
|
`localhost:9991`
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
## Common problems
|
2017-01-05 23:23:16 +01:00
|
|
|
|
2016-12-18 16:11:46 +01:00
|
|
|
* I modified my bot's code, yet the changes don't seem to have an effect.
|
2016-12-20 03:25:35 +01:00
|
|
|
* Ensure that you restarted the `run.py` script.
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
* My bot won't start
|
2016-12-20 03:25:35 +01:00
|
|
|
* Ensure that your API config file is correct (download the config file from the server).
|
2016-12-25 14:52:09 +01:00
|
|
|
* Ensure that you bot script is located in zulip/contrib_bots/lib/
|
2016-12-18 16:11:46 +01:00
|
|
|
|
|
|
|
* My bot works only on some streams.
|
2016-12-20 03:25:35 +01:00
|
|
|
* Subscribe your bot to other streams, as described [here](#how-to-deploy-a-bot).
|