Skip to content
This repository was archived by the owner on Aug 15, 2022. It is now read-only.

Commit 6fbbf34

Browse files
authored
Merge pull request #62 from jammons/plugin_refactor
Refactor the Plugin architecture to make this work as a PyPi installable package
2 parents cccf2b5 + 0455787 commit 6fbbf34

File tree

12 files changed

+335
-154
lines changed

12 files changed

+335
-154
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
*.pyc
22
/rtmbot.conf
33
/plugins/**
4+
/new_plugins/**
45
/build/**
56
*.log
67
env
@@ -10,3 +11,8 @@ env
1011
tests/.cache
1112
.coverage
1213
.cache
14+
15+
# Packaging related
16+
dist/
17+
rtmbot.egg-info/
18+
MANIFEST

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include LICENSE
2+
include CHANGELOG.md
3+
include README.md
4+

README.md

Lines changed: 110 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ python-rtmbot
44
[![Build Status](https://travis-ci.org/slackhq/python-rtmbot.png)](https://travis-ci.org/slackhq/python-rtmbot)
55
[![Coverage Status](https://coveralls.io/repos/github/slackhq/python-rtmbot/badge.svg?branch=master)](https://coveralls.io/github/slackhq/python-rtmbot?branch=master)
66

7-
A Slack bot written in python that connects via the RTM API.
7+
A Slack bot written in Python that connects via the RTM API.
88

9-
Python-rtmbot is a callback based bot engine. The plugins architecture should be familiar to anyone with knowledge to the [Slack API](https://api.slack.com) and Python. The configuration file format is YAML.
9+
Python-rtmbot is a bot engine. The plugins architecture should be familiar to anyone with knowledge of the [Slack API](https://api.slack.com) and Python. The configuration file format is YAML.
10+
11+
This project is currently pre-1.0. As such, you should plan for it to have breaking changes from time to time. For any breaking changes, we will bump the minor version while we are pre-1.0. (e.g. 0.2.4 -> 0.3.0 implies breaking changes). If stabiilty is important, you'll likely want to lock in a specific minor version)
1012

1113
Some differences to webhooks:
1214

@@ -23,84 +25,149 @@ Dependencies
2325
Installation
2426
-----------
2527

26-
1. Download the python-rtmbot code
28+
1. Create your project
29+
30+
mkdir myproject
31+
cd myproject
32+
33+
2. Install rtmbot (ideally into a [virtualenv](https://virtualenv.readthedocs.io/en/latest/))
34+
35+
pip install rtmbot
2736

28-
git clone https://github.com/slackhq/python-rtmbot.git
29-
cd python-rtmbot
37+
3. Create an rtmbot.conf file and [create a bot for your team](https://api.slack.com/bot-users)
3038

31-
2. Install dependencies ([virtualenv](http://virtualenv.readthedocs.org/en/latest/) is recommended.)
39+
# Add the following to rtmbot.conf
40+
DEBUG: True # make this False in production
41+
SLACK_TOKEN: "xoxb-11111111111-222222222222222"
42+
ACTIVE_PLUGINS:
43+
- plugins.repeat.RepeatPlugin
3244

33-
pip install -r requirements.txt
45+
```DEBUG``` will adjust logging verbosity and cause the runner to exit on exceptions, generally making debugging more pleasant.
3446

35-
3. Configure rtmbot (https://api.slack.com/bot-users)
47+
```SLACK_TOKEN``` is needed to [authenticate with your Slack team.](https://api.slack.com/web#authentication)
3648

37-
cp docs/example-config/rtmbot.conf .
38-
vi rtmbot.conf
39-
SLACK_TOKEN: "xoxb-11111111111-222222222222222"
49+
```ACTIVE_PLUGINS``` RTMBot will attempt to import any Plugin specified in `ACTIVE_PLUGINS` (relative to your python path) and instantiate them as plugins. These specified classes should inherit from the core Plugin class.
4050

41-
*Note*: At this point rtmbot is ready to run, however no plugins are configured.
51+
For example, if your python path includes '/path/to/myproject' and you include `plugins.repeat.RepeatPlugin` in ACTIVE_PLUGINS, it will find the RepeatPlugin class within /path/to/myproject/plugins/repeat.py and instantiate it, then attach it to your running RTMBot.
52+
53+
A Word on Structure
54+
-------
55+
To give you a quick sense of how this library is structured, there is a RtmBot class which does the setup and handles input and outputs of messages. It will also search for and register Plugins within the specified directory(ies). These Plugins handle different message types with various methods and can also register periodic Jobs which will be executed by the Plugins.
56+
```
57+
RtmBot
58+
|--> Plugin
59+
|---> Job
60+
|---> Job
61+
|--> Plugin
62+
|--> Plugin
63+
|---> Job
64+
```
4265

4366
Add Plugins
4467
-------
68+
Plugins can live within any python module, but we recommend just putting them in ./plugins. (Don't forget to add an `__init__.py` file to your directory to make it a module -- use `touch __init__.py` within your plugin directory to create one)
69+
70+
To add a plugin, create a file within your plugin directory (./plugins is a good place for it).
71+
72+
mkdir plugins
73+
touch plugins/__init__.py
74+
cd plugins
75+
vi myplugin.py
76+
77+
Add your plugin content into this file. Here's an example that will just print all of the requests it receives to the console. See below for more information on available methods.
78+
79+
from future import print_function
80+
from rtmbot.core import Plugin
81+
82+
class MyPlugin(Plugin):
83+
84+
def catch_all(self, data):
85+
print(data)
86+
87+
You can install as many plugins as you like, and each will handle every event received by the bot indepentently.
88+
89+
To create an example 'repeat' plugin:
90+
91+
Open `plugins/repeat.py`
4592

46-
Plugins can be installed as .py files in the ```plugins/``` directory OR as a .py file in any first level subdirectory. If your plugin uses multiple source files and libraries, it is recommended that you create a directory. You can install as many plugins as you like, and each will handle every event received by the bot indepentently.
93+
Add the following:
4794

48-
To install the example 'repeat' plugin
95+
from __future__ import print_function
96+
from __future__ import unicode_literals
4997

50-
mkdir plugins/repeat
51-
cp docs/example-plugins/repeat.py plugins/repeat/
98+
from rtmbot.core import Plugin
5299

53-
The repeat plugin will now be loaded by the bot on startup.
54100

55-
./rtmbot.py
101+
class RepeatPlugin(Plugin):
102+
103+
def process_message(self, data):
104+
if data['channel'].startswith("D"):
105+
self.outputs.append(
106+
[data['channel'], 'from repeat1 "{}" in channel {}'.format(
107+
data['text'], data['channel']
108+
)]
109+
)
110+
111+
The repeat plugin will now be loaded by the bot on startup. Run `rtmbot` from console to start your RtmBot.
112+
113+
rtmbot
56114

57115
Create Plugins
58116
--------
59117

60118
####Incoming data
61-
Plugins are callback based and respond to any event sent via the rtm websocket. To act on an event, create a function definition called process_(api_method) that accepts a single arg. For example, to handle incoming messages:
119+
All events from the RTM websocket are sent to the registered plugins. To act on an event, create a function definition, inside your Plugin class, called process_(api_method) that accepts a single arg for data. For example, to handle incoming messages:
62120

63-
def process_message(data):
121+
def process_message(self, data):
64122
print data
65123

66124
This will print the incoming message json (dict) to the screen where the bot is running.
67125

68-
Plugins having a method defined as ```catch_all(data)``` will receive ALL events from the websocket. This is useful for learning the names of events and debugging.
126+
Plugins having a method defined as ```catch_all(self, data)``` will receive ALL events from the websocket. This is useful for learning the names of events and debugging.
127+
128+
For a list of all possible API Methods, look here: https://api.slack.com/rtm
69129

70130
Note: If you're using Python 2.x, the incoming data should be a unicode string, be careful you don't coerce it into a normal str object as it will cause errors on output. You can add `from __future__ import unicode_literals` to your plugin file to avoid this.
71131

72132
####Outgoing data
73-
Plugins can send messages back to any channel, including direct messages. This is done by appending a two item array to the outputs global array. The first item in the array is the channel ID and the second is the message text. Example that writes "hello world" when the plugin is started:
74133

75-
outputs = []
76-
outputs.append(["C12345667", "hello world"])
134+
#####RTM Output
135+
Plugins can send messages back to any channel or direct message. This is done by appending a two item array to the Plugin's output array (```myPluginInstance.output```). The first item in the array is the channel or DM ID and the second is the message text. Example that writes "hello world" when the plugin is started:
136+
137+
class myPlugin(Plugin):
138+
139+
def process_message(self, data):
140+
self.outputs.append(["C12345667", "hello world"])
141+
142+
#####SlackClient Web API Output
143+
Plugins also have access to the connected SlackClient instance for more complex output (or to fetch data you may need).
144+
145+
def process_message(self, data):
146+
self.slack_client.api_call(
147+
"chat.postMessage", channel="#general", text="Hello from Python! :tada:",
148+
username="pybot", icon_emoji=":robot_face:"
77149

78-
*Note*: you should always create the outputs array at the start of your program, i.e. ```outputs = []```
79150

80151
####Timed jobs
81-
Plugins can also run methods on a schedule. This allows a plugin to poll for updates or perform housekeeping during its lifetime. This is done by appending a two item array to the crontable array. The first item is the interval in seconds and the second item is the method to run. For example, this will print "hello world" every 10 seconds.
152+
Plugins can also run methods on a schedule. This allows a plugin to poll for updates or perform housekeeping during its lifetime. Jobs define a run() method and return any outputs to be sent to channels. They also have access to a SlackClient instance that allows them to make calls to the Slack Web API.
82153

83-
outputs = []
84-
crontable = []
85-
crontable.append([10, "say_hello"])
86-
def say_hello():
87-
outputs.append(["C12345667", "hello world"])
154+
For example, this will print "hello world" every 10 seconds. You can output multiple messages two the same or different channels by passing multiple pairs of [Channel, Message] combos.
155+
156+
from core import Plugin, Job
88157

89-
####Plugin misc
90-
The data within a plugin persists for the life of the rtmbot process. If you need persistent data, you should use something like sqlite or the python pickle libraries.
91158

92-
####Direct API Calls
93-
You can directly call the Slack web API in your plugins by including the following import:
159+
class myJob(Job):
94160

95-
from client import slack_client
161+
def run(self, slack_client):
162+
return [["C12345667", "hello world"]]
96163

97-
You can also rename the client on import so it can be easily referenced like shown below:
98164

99-
from client import slack_client as sc
165+
class myPlugin(Plugin):
100166

101-
Direct API calls can be called in your plugins in the following form:
102-
103-
sc.api_call("API.method", "parameters")
167+
def register_jobs(self):
168+
job = myJob(10, debug=True)
169+
self.jobs.append(job)
104170

105-
####Todo:
106-
Some rtm data should be handled upstream, such as channel and user creation. These should create the proper objects on-the-fly.
171+
172+
####Plugin misc
173+
The data within a plugin persists for the life of the rtmbot process. If you need persistent data, you should use something like sqlite or the python pickle libraries.

client.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
requests
2-
python-daemon
32
pyyaml
43
websocket-client
54
slackclient

rtmbot.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

rtmbot/bin/__init__.py

Whitespace-only changes.

rtmbot/bin/run_rtmbot.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env python
2+
from argparse import ArgumentParser
3+
import sys
4+
import os
5+
import yaml
6+
7+
from rtmbot import RtmBot
8+
9+
sys.path.append(os.getcwd())
10+
11+
12+
def parse_args():
13+
parser = ArgumentParser()
14+
parser.add_argument(
15+
'-c',
16+
'--config',
17+
help='Full path to config file.',
18+
metavar='path'
19+
)
20+
return parser.parse_args()
21+
22+
23+
def main(args=None):
24+
# load args with config path if not specified
25+
if not args:
26+
args = parse_args()
27+
28+
config = yaml.load(open(args.config or 'rtmbot.conf', 'r'))
29+
bot = RtmBot(config)
30+
try:
31+
bot.start()
32+
except KeyboardInterrupt:
33+
sys.exit(0)
34+
35+
if __name__ == "__main__":
36+
main()

0 commit comments

Comments
 (0)