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

Commit 735d0d7

Browse files
committed
Merge pull request #37 from jammons/tzakrajs-master
Merge and fix PR breaking out RtmBot into its own Module
2 parents 1347107 + 84db35f commit 735d0d7

File tree

7 files changed

+130
-97
lines changed

7 files changed

+130
-97
lines changed

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
*.pyc
22
/rtmbot.conf
33
/plugins/**
4+
/build/**
5+
*.log
46
env
5-
.tox
7+
.tox
8+
*.un~
9+
0/
10+
tests/.cache

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Installation
3232
pip install -r requirements.txt
3333

3434
3. Configure rtmbot (https://api.slack.com/bot-users)
35-
35+
3636
cp doc/example-config/rtmbot.conf .
3737
vi rtmbot.conf
3838
SLACK_TOKEN: "xoxb-11111111111-222222222222222"
@@ -51,7 +51,7 @@ To install the example 'repeat' plugin
5151

5252
The repeat plugin will now be loaded by the bot on startup.
5353

54-
./rtmbot.py
54+
./start_rtmbot.py
5555

5656
Create Plugins
5757
--------
@@ -71,7 +71,7 @@ Plugins can send messages back to any channel, including direct messages. This i
7171

7272
outputs = []
7373
outputs.append(["C12345667", "hello world"])
74-
74+
7575
*Note*: you should always create the outputs array at the start of your program, i.e. ```outputs = []```
7676

7777
####Timed jobs

rtmbot/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from core import *
Lines changed: 80 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,53 @@
11
#!/usr/bin/env python
22
import sys
33
import glob
4-
import yaml
54
import os
65
import time
76
import logging
87

9-
from argparse import ArgumentParser
10-
118
from slackclient import SlackClient
129

1310
sys.dont_write_bytecode = True
1411

1512

16-
def dbg(debug_string):
17-
if debug:
18-
logging.info(debug_string)
19-
20-
2113
class RtmBot(object):
22-
def __init__(self, token):
14+
def __init__(self, config):
15+
# set the config object
16+
self.config = config
17+
18+
# set slack token
19+
self.token = config.get('SLACK_TOKEN')
20+
21+
# set working directory for loading plugins or other files
22+
working_directory = os.path.dirname(sys.argv[0])
23+
self.directory = self.config.get('BASE_PATH', working_directory)
24+
if not self.directory.startswith('/'):
25+
path = '{}/{}'.format(os.getcwd(), self.directory)
26+
self.directory = os.path.abspath(path)
27+
28+
# establish logging
29+
log_file = config.get('LOGFILE', 'rtmbot.log')
30+
logging.basicConfig(filename=log_file,
31+
level=logging.INFO,
32+
format='%(asctime)s %(message)s')
33+
logging.info(self.directory)
34+
self.debug = self.config.get('DEBUG', False)
35+
36+
# initialize stateful fields
2337
self.last_ping = 0
24-
self.token = token
2538
self.bot_plugins = []
2639
self.slack_client = None
2740

41+
def _dbg(self, debug_string):
42+
if self.debug:
43+
logging.info(debug_string)
44+
2845
def connect(self):
2946
"""Convenience method that creates Server instance"""
3047
self.slack_client = SlackClient(self.token)
3148
self.slack_client.rtm_connect()
3249

33-
def start(self):
50+
def _start(self):
3451
self.connect()
3552
self.load_plugins()
3653
while True:
@@ -41,6 +58,14 @@ def start(self):
4158
self.autoping()
4259
time.sleep(.1)
4360

61+
def start(self):
62+
if 'DAEMON' in self.config:
63+
if self.config.get('DAEMON'):
64+
import daemon
65+
with daemon.DaemonContext():
66+
self._start()
67+
self._start()
68+
4469
def autoping(self):
4570
# hardcode the interval to 3 seconds
4671
now = int(time.time())
@@ -51,7 +76,7 @@ def autoping(self):
5176
def input(self, data):
5277
if "type" in data:
5378
function_name = "process_" + data["type"]
54-
dbg("got {}".format(function_name))
79+
self._dbg("got {}".format(function_name))
5580
for plugin in self.bot_plugins:
5681
plugin.register_jobs()
5782
plugin.do(function_name, data)
@@ -74,59 +99,71 @@ def crons(self):
7499
plugin.do_jobs()
75100

76101
def load_plugins(self):
77-
for plugin in glob.glob(directory + '/plugins/*'):
102+
for plugin in glob.glob(self.directory + '/plugins/*'):
78103
sys.path.insert(0, plugin)
79-
sys.path.insert(0, directory + '/plugins/')
80-
for plugin in glob.glob(directory + '/plugins/*.py') + \
81-
glob.glob(directory + '/plugins/*/*.py'):
104+
sys.path.insert(0, self.directory + '/plugins/')
105+
for plugin in glob.glob(self.directory + '/plugins/*.py') + \
106+
glob.glob(self.directory + '/plugins/*/*.py'):
82107
logging.info(plugin)
83108
name = plugin.split('/')[-1][:-3]
84-
# try:
85-
self.bot_plugins.append(Plugin(name))
86-
# except:
87-
# print "error loading plugin %s" % name
109+
if name in self.config:
110+
logging.info("config found for: " + name)
111+
plugin_config = self.config.get(name, {})
112+
plugin_config['DEBUG'] = self.debug
113+
self.bot_plugins.append(Plugin(name, plugin_config))
88114

89115

90116
class Plugin(object):
91117

92118
def __init__(self, name, plugin_config=None):
119+
'''
120+
A plugin in initialized with:
121+
- name (str)
122+
- plugin config (dict) - (from the yaml config)
123+
Values in config:
124+
- DEBUG (bool) - this will be overridden if debug is set in config for this plugin
125+
'''
93126
if plugin_config is None:
94-
plugin_config = {} # TODO: is this variable necessary?
127+
plugin_config = {}
95128
self.name = name
96129
self.jobs = []
97130
self.module = __import__(name)
131+
self.module.config = plugin_config
132+
self.debug = self.module.config.get('DEBUG', False)
98133
self.register_jobs()
99134
self.outputs = []
100-
if name in config:
101-
logging.info("config found for: " + name)
102-
self.module.config = config[name]
103135
if 'setup' in dir(self.module):
104136
self.module.setup()
105137

106138
def register_jobs(self):
107139
if 'crontable' in dir(self.module):
108140
for interval, function in self.module.crontable:
109-
self.jobs.append(Job(interval, eval("self.module." + function)))
141+
self.jobs.append(Job(interval, eval("self.module." + function), self.debug))
110142
logging.info(self.module.crontable)
111143
self.module.crontable = []
112144
else:
113145
self.module.crontable = []
114146

115147
def do(self, function_name, data):
116148
if function_name in dir(self.module):
117-
# this makes the plugin fail with stack trace in debug mode
118-
if not debug:
149+
if self.debug is True:
150+
# this makes the plugin fail with stack trace in debug mode
151+
eval("self.module." + function_name)(data)
152+
else:
153+
# otherwise we log the exception and carry on
119154
try:
120155
eval("self.module." + function_name)(data)
121-
except:
122-
dbg("problem in module {} {}".format(function_name, data))
123-
else:
124-
eval("self.module." + function_name)(data)
156+
except Exception:
157+
logging.exception("problem in module {} {}".format(function_name, data))
125158
if "catch_all" in dir(self.module):
126-
try:
159+
if self.debug is True:
160+
# this makes the plugin fail with stack trace in debug mode
127161
self.module.catch_all(data)
128-
except:
129-
dbg("problem in catch all")
162+
else:
163+
try:
164+
self.module.catch_all(data)
165+
except Exception:
166+
logging.exception("problem in catch all: {} {}".format(self.module, data))
130167

131168
def do_jobs(self):
132169
for job in self.jobs:
@@ -147,10 +184,11 @@ def do_output(self):
147184

148185

149186
class Job(object):
150-
def __init__(self, interval, function):
187+
def __init__(self, interval, function, debug):
151188
self.function = function
152189
self.interval = interval
153190
self.lastrun = 0
191+
self.debug = debug
154192

155193
def __str__(self):
156194
return "{} {} {}".format(self.function, self.interval, self.lastrun)
@@ -160,67 +198,17 @@ def __repr__(self):
160198

161199
def check(self):
162200
if self.lastrun + self.interval < time.time():
163-
if not debug:
201+
if self.debug is True:
202+
# this makes the plugin fail with stack trace in debug mode
203+
self.function()
204+
else:
205+
# otherwise we log the exception and carry on
164206
try:
165207
self.function()
166-
except:
167-
dbg("problem")
168-
else:
169-
self.function()
208+
except Exception:
209+
logging.exception("Problem in job check: {}".format(self.function))
170210
self.lastrun = time.time()
171-
pass
172211

173212

174213
class UnknownChannel(Exception):
175214
pass
176-
177-
178-
def main_loop():
179-
if "LOGFILE" in config:
180-
logging.basicConfig(
181-
filename=config["LOGFILE"],
182-
level=logging.INFO,
183-
format='%(asctime)s %(message)s'
184-
)
185-
logging.info(directory)
186-
try:
187-
bot.start()
188-
except KeyboardInterrupt:
189-
sys.exit(0)
190-
except:
191-
logging.exception('OOPS')
192-
193-
194-
def parse_args():
195-
parser = ArgumentParser()
196-
parser.add_argument(
197-
'-c',
198-
'--config',
199-
help='Full path to config file.',
200-
metavar='path'
201-
)
202-
return parser.parse_args()
203-
204-
205-
if __name__ == "__main__":
206-
args = parse_args()
207-
directory = os.path.dirname(sys.argv[0])
208-
if not directory.startswith('/'):
209-
directory = os.path.abspath("{}/{}".format(os.getcwd(),
210-
directory
211-
))
212-
213-
config = yaml.load(open(args.config or 'rtmbot.conf', 'r'))
214-
debug = config["DEBUG"]
215-
bot = RtmBot(config["SLACK_TOKEN"])
216-
site_plugins = []
217-
files_currently_downloading = []
218-
job_hash = {}
219-
220-
if 'DAEMON' in config:
221-
if config["DAEMON"]:
222-
import daemon
223-
224-
with daemon.DaemonContext():
225-
main_loop()
226-
main_loop()

setup.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/usr/bin/env python
2+
3+
from distutils.core import setup
4+
5+
setup(
6+
name='rtmbot',
7+
version='0.10',
8+
description='A Slack bot written in python that connects via the RTM API.',
9+
author='Ryan Huber',
10+
author_email='[email protected]',
11+
url='https://github.com/slackhq/python-rtmbot',
12+
packages=['rtmbot'],
13+
)

start_rtmbot.py

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

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ basepython =
2020
[testenv:flake8]
2121
basepython=python
2222
deps=flake8
23-
commands=flake8 {toxinidir}/rtmbot.py {toxinidir}/doc/example-plugins
23+
commands=flake8 {toxinidir}/start_rtmbot.py {toxinidir}/rtmbot/core.py {toxinidir}/setup.py {toxinidir}/doc/example-plugins

0 commit comments

Comments
 (0)