11#!/usr/bin/env python
22import sys
33import glob
4- import yaml
54import os
65import time
76import logging
87
9- from argparse import ArgumentParser
10-
118from slackclient import SlackClient
129
1310sys .dont_write_bytecode = True
1411
1512
16- def dbg (debug_string ):
17- if debug :
18- logging .info (debug_string )
19-
20-
2113class 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
90116class 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
149186class 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
174213class 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 ()
0 commit comments