Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
f2ab674906
20 changed files with 621 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
*.pyc
|
||||
/rtmbot.conf
|
||||
/plugins/**
|
||||
/build/**
|
||||
*.log
|
||||
env
|
||||
.tox
|
||||
*.un~
|
||||
0/
|
||||
tests/.cache
|
||||
.coverage
|
||||
.cache
|
||||
18
.travis.yml
Normal file
18
.travis.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
sudo: false
|
||||
language: python
|
||||
python:
|
||||
- "3.5"
|
||||
env:
|
||||
matrix:
|
||||
- TOX_ENV=py27
|
||||
- TOX_ENV=py34
|
||||
- TOX_ENV=py35
|
||||
- TOX_ENV=flake8
|
||||
cache: pip
|
||||
install:
|
||||
- "travis_retry pip install setuptools --upgrade"
|
||||
- "travis_retry pip install tox"
|
||||
script:
|
||||
- tox -e $TOX_ENV
|
||||
after_script:
|
||||
- cat .tox/$TOX_ENV/log/*.log
|
||||
17
LICENSE.txt
Normal file
17
LICENSE.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
91
README.md
91
README.md
|
|
@ -0,0 +1,91 @@
|
|||
python-rtmbot
|
||||
=============
|
||||
|
||||
[](https://travis-ci.org/slackhq/python-rtmbot)
|
||||
[](https://coveralls.io/github/slackhq/python-rtmbot?branch=master)
|
||||
|
||||
A Slack bot written in python that connects via the RTM API.
|
||||
|
||||
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.
|
||||
|
||||
Some differences to webhooks:
|
||||
|
||||
1. Doesn't require a webserver to receive messages
|
||||
2. Can respond to direct messages from users
|
||||
3. Logs in as a slack user (or bot)
|
||||
4. Bot users must be invited to a channel
|
||||
|
||||
Dependencies
|
||||
----------
|
||||
* websocket-client https://pypi.python.org/pypi/websocket-client/
|
||||
* python-slackclient https://github.com/slackhq/python-slackclient
|
||||
|
||||
Installation
|
||||
-----------
|
||||
|
||||
1. Download the python-rtmbot code
|
||||
|
||||
git clone https://github.com/slackhq/python-rtmbot.git
|
||||
cd python-rtmbot
|
||||
|
||||
2. Install dependencies ([virtualenv](http://virtualenv.readthedocs.org/en/latest/) is recommended.)
|
||||
|
||||
pip install -r requirements.txt
|
||||
|
||||
3. Configure rtmbot (https://api.slack.com/bot-users)
|
||||
|
||||
cp doc/example-config/rtmbot.conf .
|
||||
vi rtmbot.conf
|
||||
SLACK_TOKEN: "xoxb-11111111111-222222222222222"
|
||||
|
||||
*Note*: At this point rtmbot is ready to run, however no plugins are configured.
|
||||
|
||||
Add Plugins
|
||||
-------
|
||||
|
||||
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.
|
||||
|
||||
To install the example 'repeat' plugin
|
||||
|
||||
mkdir plugins/repeat
|
||||
cp doc/example-plugins/repeat.py plugins/repeat
|
||||
|
||||
The repeat plugin will now be loaded by the bot on startup.
|
||||
|
||||
./rtmbot.py
|
||||
|
||||
Create Plugins
|
||||
--------
|
||||
|
||||
####Incoming data
|
||||
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:
|
||||
|
||||
def process_message(data):
|
||||
print data
|
||||
|
||||
This will print the incoming message json (dict) to the screen where the bot is running.
|
||||
|
||||
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.
|
||||
|
||||
####Outgoing data
|
||||
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:
|
||||
|
||||
outputs = []
|
||||
outputs.append(["C12345667", "hello world"])
|
||||
|
||||
*Note*: you should always create the outputs array at the start of your program, i.e. ```outputs = []```
|
||||
|
||||
####Timed jobs
|
||||
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.
|
||||
|
||||
outputs = []
|
||||
crontable = []
|
||||
crontable.append([10, "say_hello"])
|
||||
def say_hello():
|
||||
outputs.append(["C12345667", "hello world"])
|
||||
|
||||
####Plugin misc
|
||||
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.
|
||||
|
||||
####Todo:
|
||||
Some rtm data should be handled upstream, such as channel and user creation. These should create the proper objects on-the-fly.
|
||||
3
doc/example-config/rtmbot.conf
Normal file
3
doc/example-config/rtmbot.conf
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
DEBUG: False
|
||||
|
||||
SLACK_TOKEN: "xoxb-111111111111-2222222222222222222"
|
||||
76
doc/example-init/rtmbot.init
Executable file
76
doc/example-init/rtmbot.init
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: exampledaemon
|
||||
# Required-Start: $local_fs $remote_fs $network $syslog
|
||||
# Required-Stop: $local_fs $remote_fs $network $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Example
|
||||
# Description: Example start-stop-daemon - Debian
|
||||
### END INIT INFO
|
||||
|
||||
NAME="rtmbot"
|
||||
PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
|
||||
APPDIR="/opt/rtmbot"
|
||||
APPBIN="rtmbot.py"
|
||||
APPARGS="--config ${APPDIR}/rtmbot.conf"
|
||||
USER="ubuntu"
|
||||
GROUP="ubuntu"
|
||||
|
||||
# Include functions
|
||||
set -e
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
start() {
|
||||
printf "Starting '$NAME'... "
|
||||
start-stop-daemon --start --background --chuid "$USER:$GROUP" --make-pidfile --pidfile /var/run/$NAME.pid --exec "$APPDIR/$APPBIN" -- $APPARGS || true
|
||||
printf "done\n"
|
||||
}
|
||||
|
||||
#We need this function to ensure the whole process tree will be killed
|
||||
killtree() {
|
||||
local _pid=$1
|
||||
local _sig=${2-TERM}
|
||||
for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
|
||||
killtree ${_child} ${_sig}
|
||||
done
|
||||
kill -${_sig} ${_pid}
|
||||
}
|
||||
|
||||
stop() {
|
||||
printf "Stopping '$NAME'... "
|
||||
[ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || \
|
||||
while test -d /proc/$(cat /var/run/$NAME.pid); do
|
||||
killtree $(cat /var/run/$NAME.pid) 15
|
||||
sleep 0.5
|
||||
done
|
||||
[ -z `cat /var/run/$NAME.pid 2>/dev/null` ] || rm /var/run/$NAME.pid
|
||||
printf "done\n"
|
||||
}
|
||||
|
||||
status() {
|
||||
status_of_proc -p /var/run/$NAME.pid "" $NAME && exit 0 || exit $?
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
status)
|
||||
status
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $NAME {start|stop|restart|status}" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
9
doc/example-plugins/canary.py
Normal file
9
doc/example-plugins/canary.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import time
|
||||
outputs = []
|
||||
|
||||
|
||||
def canary():
|
||||
# NOTE: you must add a real channel ID for this to work
|
||||
outputs.append(["D12345678", "bot started: " + str(time.time())])
|
||||
|
||||
canary()
|
||||
10
doc/example-plugins/counter.py
Normal file
10
doc/example-plugins/counter.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import time
|
||||
crontable = []
|
||||
outputs = []
|
||||
|
||||
crontable.append([5, "say_time"])
|
||||
|
||||
|
||||
def say_time():
|
||||
# NOTE: you must add a real channel ID for this to work
|
||||
outputs.append(["D12345678", time.time()])
|
||||
9
doc/example-plugins/repeat.py
Normal file
9
doc/example-plugins/repeat.py
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
crontable = []
|
||||
outputs = []
|
||||
|
||||
|
||||
def process_message(data):
|
||||
if data['channel'].startswith("D"):
|
||||
outputs.append([data['channel'], "from repeat1 \"{}\" in channel {}".format(
|
||||
data['text'], data['channel'])]
|
||||
)
|
||||
42
doc/example-plugins/todo.py
Normal file
42
doc/example-plugins/todo.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
from __future__ import print_function
|
||||
import os
|
||||
import pickle
|
||||
|
||||
outputs = []
|
||||
crontabs = []
|
||||
|
||||
tasks = {}
|
||||
|
||||
|
||||
FILE = "plugins/todo.data"
|
||||
if os.path.isfile(FILE):
|
||||
tasks = pickle.load(open(FILE, 'rb'))
|
||||
|
||||
|
||||
def process_message(data):
|
||||
global tasks
|
||||
channel = data["channel"]
|
||||
text = data["text"]
|
||||
# only accept tasks on DM channels
|
||||
if channel.startswith("D"):
|
||||
if channel not in tasks.keys():
|
||||
tasks[channel] = []
|
||||
# do command stuff
|
||||
if text.startswith("todo"):
|
||||
tasks[channel].append(text[5:])
|
||||
outputs.append([channel, "added"])
|
||||
if text == "tasks":
|
||||
output = ""
|
||||
counter = 1
|
||||
for task in tasks[channel]:
|
||||
output += "%i) %s\n" % (counter, task)
|
||||
counter += 1
|
||||
outputs.append([channel, output])
|
||||
if text == "fin":
|
||||
tasks[channel] = []
|
||||
if text.startswith("done"):
|
||||
num = int(text.split()[1]) - 1
|
||||
tasks[channel].pop(num)
|
||||
if text == "show":
|
||||
print(tasks)
|
||||
pickle.dump(tasks, open(FILE, "wb"))
|
||||
0
plugins/.gitkeep
Normal file
0
plugins/.gitkeep
Normal file
9
requirements-dev.txt
Normal file
9
requirements-dev.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
coveralls==1.1
|
||||
ipdb==0.9.3
|
||||
ipython==4.1.2
|
||||
pdbpp==0.8.3
|
||||
pytest>=2.8.2
|
||||
pytest-cov==2.2.1
|
||||
pytest-pythonpath>=0.3
|
||||
testfixtures==4.9.1
|
||||
tox>=1.8.0
|
||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
requests
|
||||
python-daemon
|
||||
pyyaml
|
||||
websocket-client
|
||||
slackclient
|
||||
26
rtmbot.py
Executable file
26
rtmbot.py
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python
|
||||
import sys
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import yaml
|
||||
from rtmbot import RtmBot
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-c',
|
||||
'--config',
|
||||
help='Full path to config file.',
|
||||
metavar='path'
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
# load args with config path
|
||||
args = parse_args()
|
||||
config = yaml.load(open(args.config or 'rtmbot.conf', 'r'))
|
||||
bot = RtmBot(config)
|
||||
try:
|
||||
bot.start()
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
1
rtmbot/__init__.py
Normal file
1
rtmbot/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .core import *
|
||||
225
rtmbot/core.py
Executable file
225
rtmbot/core.py
Executable file
|
|
@ -0,0 +1,225 @@
|
|||
#!/usr/bin/env python
|
||||
import sys
|
||||
import glob
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
|
||||
from slackclient import SlackClient
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
|
||||
class RtmBot(object):
|
||||
def __init__(self, config):
|
||||
'''
|
||||
Params:
|
||||
- config (dict):
|
||||
- SLACK_TOKEN: your authentication token from Slack
|
||||
- BASE_PATH (optional: defaults to execution directory) RtmBot will
|
||||
look in this directory for plugins.
|
||||
- LOGFILE (optional: defaults to rtmbot.log) The filename for logs, will
|
||||
be stored inside the BASE_PATH directory
|
||||
- DEBUG (optional: defaults to False) with debug enabled, RtmBot will
|
||||
break on errors
|
||||
'''
|
||||
# set the config object
|
||||
self.config = config
|
||||
|
||||
# set slack token
|
||||
self.token = config.get('SLACK_TOKEN')
|
||||
|
||||
# set working directory for loading plugins or other files
|
||||
working_directory = os.path.dirname(sys.argv[0])
|
||||
self.directory = self.config.get('BASE_PATH', working_directory)
|
||||
if not self.directory.startswith('/'):
|
||||
path = '{}/{}'.format(os.getcwd(), self.directory)
|
||||
self.directory = os.path.abspath(path)
|
||||
|
||||
# establish logging
|
||||
log_file = config.get('LOGFILE', 'rtmbot.log')
|
||||
logging.basicConfig(filename=log_file,
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s %(message)s')
|
||||
logging.info('Initialized in: {}'.format(self.directory))
|
||||
self.debug = self.config.get('DEBUG', False)
|
||||
|
||||
# initialize stateful fields
|
||||
self.last_ping = 0
|
||||
self.bot_plugins = []
|
||||
self.slack_client = None
|
||||
|
||||
def _dbg(self, debug_string):
|
||||
if self.debug:
|
||||
logging.info(debug_string)
|
||||
|
||||
def connect(self):
|
||||
"""Convenience method that creates Server instance"""
|
||||
self.slack_client = SlackClient(self.token)
|
||||
self.slack_client.rtm_connect()
|
||||
|
||||
def _start(self):
|
||||
self.connect()
|
||||
self.load_plugins()
|
||||
while True:
|
||||
for reply in self.slack_client.rtm_read():
|
||||
self.input(reply)
|
||||
self.crons()
|
||||
self.output()
|
||||
self.autoping()
|
||||
time.sleep(.1)
|
||||
|
||||
def start(self):
|
||||
if 'DAEMON' in self.config:
|
||||
if self.config.get('DAEMON'):
|
||||
import daemon
|
||||
with daemon.DaemonContext():
|
||||
self._start()
|
||||
self._start()
|
||||
|
||||
def autoping(self):
|
||||
# hardcode the interval to 3 seconds
|
||||
now = int(time.time())
|
||||
if now > self.last_ping + 3:
|
||||
self.slack_client.server.ping()
|
||||
self.last_ping = now
|
||||
|
||||
def input(self, data):
|
||||
if "type" in data:
|
||||
function_name = "process_" + data["type"]
|
||||
self._dbg("got {}".format(function_name))
|
||||
for plugin in self.bot_plugins:
|
||||
plugin.register_jobs()
|
||||
plugin.do(function_name, data)
|
||||
|
||||
def output(self):
|
||||
for plugin in self.bot_plugins:
|
||||
limiter = False
|
||||
for output in plugin.do_output():
|
||||
channel = self.slack_client.server.channels.find(output[0])
|
||||
if channel is not None and output[1] is not None:
|
||||
if limiter:
|
||||
time.sleep(.1)
|
||||
limiter = False
|
||||
message = output[1].encode('ascii', 'ignore')
|
||||
channel.send_message("{}".format(message))
|
||||
limiter = True
|
||||
|
||||
def crons(self):
|
||||
for plugin in self.bot_plugins:
|
||||
plugin.do_jobs()
|
||||
|
||||
def load_plugins(self):
|
||||
for plugin in glob.glob(self.directory + '/plugins/*'):
|
||||
sys.path.insert(0, plugin)
|
||||
sys.path.insert(0, self.directory + '/plugins/')
|
||||
for plugin in glob.glob(self.directory + '/plugins/*.py') + \
|
||||
glob.glob(self.directory + '/plugins/*/*.py'):
|
||||
logging.info(plugin)
|
||||
name = plugin.split('/')[-1][:-3]
|
||||
if name in self.config:
|
||||
logging.info("config found for: " + name)
|
||||
plugin_config = self.config.get(name, {})
|
||||
plugin_config['DEBUG'] = self.debug
|
||||
self.bot_plugins.append(Plugin(name, plugin_config))
|
||||
|
||||
|
||||
class Plugin(object):
|
||||
|
||||
def __init__(self, name, plugin_config=None):
|
||||
'''
|
||||
A plugin in initialized with:
|
||||
- name (str)
|
||||
- plugin config (dict) - (from the yaml config)
|
||||
Values in config:
|
||||
- DEBUG (bool) - this will be overridden if debug is set in config for this plugin
|
||||
'''
|
||||
if plugin_config is None:
|
||||
plugin_config = {}
|
||||
self.name = name
|
||||
self.jobs = []
|
||||
self.module = __import__(name)
|
||||
self.module.config = plugin_config
|
||||
self.debug = self.module.config.get('DEBUG', False)
|
||||
self.register_jobs()
|
||||
self.outputs = []
|
||||
if 'setup' in dir(self.module):
|
||||
self.module.setup()
|
||||
|
||||
def register_jobs(self):
|
||||
if 'crontable' in dir(self.module):
|
||||
for interval, function in self.module.crontable:
|
||||
self.jobs.append(Job(interval, eval("self.module." + function), self.debug))
|
||||
logging.info(self.module.crontable)
|
||||
self.module.crontable = []
|
||||
else:
|
||||
self.module.crontable = []
|
||||
|
||||
def do(self, function_name, data):
|
||||
if function_name in dir(self.module):
|
||||
if self.debug is True:
|
||||
# this makes the plugin fail with stack trace in debug mode
|
||||
eval("self.module." + function_name)(data)
|
||||
else:
|
||||
# otherwise we log the exception and carry on
|
||||
try:
|
||||
eval("self.module." + function_name)(data)
|
||||
except Exception:
|
||||
logging.exception("problem in module {} {}".format(function_name, data))
|
||||
if "catch_all" in dir(self.module):
|
||||
if self.debug is True:
|
||||
# this makes the plugin fail with stack trace in debug mode
|
||||
self.module.catch_all(data)
|
||||
else:
|
||||
try:
|
||||
self.module.catch_all(data)
|
||||
except Exception:
|
||||
logging.exception("problem in catch all: {} {}".format(self.module, data))
|
||||
|
||||
def do_jobs(self):
|
||||
for job in self.jobs:
|
||||
job.check()
|
||||
|
||||
def do_output(self):
|
||||
output = []
|
||||
while True:
|
||||
if 'outputs' in dir(self.module):
|
||||
if len(self.module.outputs) > 0:
|
||||
logging.info("output from {}".format(self.module))
|
||||
output.append(self.module.outputs.pop(0))
|
||||
else:
|
||||
break
|
||||
else:
|
||||
self.module.outputs = []
|
||||
return output
|
||||
|
||||
|
||||
class Job(object):
|
||||
def __init__(self, interval, function, debug):
|
||||
self.function = function
|
||||
self.interval = interval
|
||||
self.lastrun = 0
|
||||
self.debug = debug
|
||||
|
||||
def __str__(self):
|
||||
return "{} {} {}".format(self.function, self.interval, self.lastrun)
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
def check(self):
|
||||
if self.lastrun + self.interval < time.time():
|
||||
if self.debug is True:
|
||||
# this makes the plugin fail with stack trace in debug mode
|
||||
self.function()
|
||||
else:
|
||||
# otherwise we log the exception and carry on
|
||||
try:
|
||||
self.function()
|
||||
except Exception:
|
||||
logging.exception("Problem in job check: {}".format(self.function))
|
||||
self.lastrun = time.time()
|
||||
|
||||
|
||||
class UnknownChannel(Exception):
|
||||
pass
|
||||
13
setup.py
Executable file
13
setup.py
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
setup(
|
||||
name='rtmbot',
|
||||
version='0.10',
|
||||
description='A Slack bot written in python that connects via the RTM API.',
|
||||
author='Ryan Huber',
|
||||
author_email='rhuber@gmail.com',
|
||||
url='https://github.com/slackhq/python-rtmbot',
|
||||
packages=['rtmbot'],
|
||||
)
|
||||
2
tests/test_example.py
Normal file
2
tests/test_example.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
def test_example():
|
||||
assert True
|
||||
20
tests/test_rtmbot_core.py
Normal file
20
tests/test_rtmbot_core.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
from testfixtures import LogCapture
|
||||
from rtmbot.core import RtmBot
|
||||
|
||||
|
||||
def test_init():
|
||||
with LogCapture() as l:
|
||||
rtmbot = RtmBot({
|
||||
'SLACK_TOKEN': 'test-12345',
|
||||
'BASE_PATH': '/tmp/',
|
||||
'LOGFILE': '/tmp/rtmbot.log',
|
||||
'DEBUG': True
|
||||
})
|
||||
|
||||
assert rtmbot.token == 'test-12345'
|
||||
assert rtmbot.directory == '/tmp/'
|
||||
assert rtmbot.debug == True
|
||||
|
||||
l.check(
|
||||
('root', 'INFO', 'Initialized in: /tmp/')
|
||||
)
|
||||
33
tox.ini
Normal file
33
tox.ini
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
[tox]
|
||||
envlist=
|
||||
py{27,34,35},
|
||||
flake8
|
||||
skipsdist=true
|
||||
|
||||
[flake8]
|
||||
max-line-length= 100
|
||||
exclude= tests/*
|
||||
|
||||
[testenv]
|
||||
passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
|
||||
commands =
|
||||
py.test --cov-report= --cov=rtmbot {posargs:tests}
|
||||
coveralls
|
||||
|
||||
deps =
|
||||
-r{toxinidir}/requirements-dev.txt
|
||||
-r{toxinidir}/requirements.txt
|
||||
basepython =
|
||||
py27: python2.7
|
||||
py34: python3.4
|
||||
py35: python3.5
|
||||
|
||||
[testenv:flake8]
|
||||
basepython=python
|
||||
deps=flake8
|
||||
commands=
|
||||
flake8 \
|
||||
{toxinidir}/rtmbot.py \
|
||||
{toxinidir}/rtmbot/core.py \
|
||||
{toxinidir}/setup.py \
|
||||
{toxinidir}/doc/example-plugins
|
||||
Loading…
Add table
Add a link
Reference in a new issue