diff --git a/.gitignore b/.gitignore index 1e6aa92..71c0436 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.pyc /rtmbot.conf /plugins/** +/build/** +*.log env .tox \ No newline at end of file diff --git a/rtmbot/__init__.py b/rtmbot/__init__.py new file mode 100644 index 0000000..56b4646 --- /dev/null +++ b/rtmbot/__init__.py @@ -0,0 +1,3 @@ +from core import * + +site_config = {} diff --git a/rtmbot.py b/rtmbot/core.py similarity index 65% rename from rtmbot.py rename to rtmbot/core.py index a275cc5..482562c 100755 --- a/rtmbot.py +++ b/rtmbot/core.py @@ -1,36 +1,58 @@ #!/usr/bin/env python import sys import glob -import yaml import os import time import logging -from argparse import ArgumentParser - from slackclient import SlackClient sys.dont_write_bytecode = True -def dbg(debug_string): - if debug: - logging.info(debug_string) - - class RtmBot(object): - def __init__(self, token): + def __init__(self, config): + # set the config object + self.config = config + global site_config + site_config = self.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(self.directory) + if 'DEBUG' in self.config: + self.debug = self.config.get('DEBUG') + else: + self.debug = False + + # initialize stateful fields self.last_ping = 0 - self.token = token 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): + def _start(self): self.connect() self.load_plugins() while True: @@ -41,6 +63,14 @@ class RtmBot(object): 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()) @@ -51,7 +81,7 @@ class RtmBot(object): def input(self, data): if "type" in data: function_name = "process_" + data["type"] - dbg("got {}".format(function_name)) + self._dbg("got {}".format(function_name)) for plugin in self.bot_plugins: plugin.register_jobs() plugin.do(function_name, data) @@ -74,17 +104,18 @@ class RtmBot(object): plugin.do_jobs() def load_plugins(self): - for plugin in glob.glob(directory + '/plugins/*'): + for plugin in glob.glob(self.directory + '/plugins/*'): sys.path.insert(0, plugin) - sys.path.insert(0, directory + '/plugins/') - for plugin in glob.glob(directory + '/plugins/*.py') + \ - glob.glob(directory + '/plugins/*/*.py'): + 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] - # try: - self.bot_plugins.append(Plugin(name)) - # except: - # print "error loading plugin %s" % name + 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): @@ -95,11 +126,10 @@ class Plugin(object): self.name = name self.jobs = [] self.module = __import__(name) + self.module.config = plugin_config + self.debug = self.module.config.get('DEBUG') self.register_jobs() self.outputs = [] - if name in config: - logging.info("config found for: " + name) - self.module.config = config[name] if 'setup' in dir(self.module): self.module.setup() @@ -115,18 +145,18 @@ class Plugin(object): def do(self, function_name, data): if function_name in dir(self.module): # this makes the plugin fail with stack trace in debug mode - if not debug: + if not self.debug: try: eval("self.module." + function_name)(data) except: - dbg("problem in module {} {}".format(function_name, data)) + self._dbg("problem in module {} {}".format(function_name, data)) else: eval("self.module." + function_name)(data) if "catch_all" in dir(self.module): try: self.module.catch_all(data) except: - dbg("problem in catch all") + self._dbg("problem in catch all") def do_jobs(self): for job in self.jobs: @@ -160,11 +190,11 @@ class Job(object): def check(self): if self.lastrun + self.interval < time.time(): - if not debug: + if not debug: # TODO: This isn't in scope any more try: self.function() except: - dbg("problem") + self._dbg("problem") else: self.function() self.lastrun = time.time() @@ -173,54 +203,3 @@ class Job(object): class UnknownChannel(Exception): pass - - -def main_loop(): - if "LOGFILE" in config: - logging.basicConfig( - filename=config["LOGFILE"], - level=logging.INFO, - format='%(asctime)s %(message)s' - ) - logging.info(directory) - try: - bot.start() - except KeyboardInterrupt: - sys.exit(0) - except: - logging.exception('OOPS') - - -def parse_args(): - parser = ArgumentParser() - parser.add_argument( - '-c', - '--config', - help='Full path to config file.', - metavar='path' - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - directory = os.path.dirname(sys.argv[0]) - if not directory.startswith('/'): - directory = os.path.abspath("{}/{}".format(os.getcwd(), - directory - )) - - config = yaml.load(open(args.config or 'rtmbot.conf', 'r')) - debug = config["DEBUG"] - bot = RtmBot(config["SLACK_TOKEN"]) - site_plugins = [] - files_currently_downloading = [] - job_hash = {} - - if 'DAEMON' in config: - if config["DAEMON"]: - import daemon - - with daemon.DaemonContext(): - main_loop() - main_loop() diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..ef6cfc3 --- /dev/null +++ b/setup.py @@ -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'], + ) diff --git a/start_rtmbot.py b/start_rtmbot.py new file mode 100755 index 0000000..3857d63 --- /dev/null +++ b/start_rtmbot.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +import sys +import logging +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(file(args.config or 'rtmbot.conf', 'r')) +bot = RtmBot(config) +try: + bot.start() +except KeyboardInterrupt: + sys.exit(0) +except: + logging.exception('OOPS')