diff --git a/doc/example-plugins/canary.py b/doc/example-plugins/canary.py index 2c76784..b2cea10 100644 --- a/doc/example-plugins/canary.py +++ b/doc/example-plugins/canary.py @@ -1,8 +1,9 @@ import time outputs = [] + def canary(): - #NOTE: you must add a real channel ID for this to work + # NOTE: you must add a real channel ID for this to work outputs.append(["D12345678", "bot started: " + str(time.time())]) canary() diff --git a/doc/example-plugins/counter.py b/doc/example-plugins/counter.py index 84e9012..00fac1a 100644 --- a/doc/example-plugins/counter.py +++ b/doc/example-plugins/counter.py @@ -2,8 +2,9 @@ import time crontable = [] outputs = [] -crontable.append([5,"say_time"]) +crontable.append([5, "say_time"]) + def say_time(): - #NOTE: you must add a real channel ID for this to work + # NOTE: you must add a real channel ID for this to work outputs.append(["D12345678", time.time()]) diff --git a/doc/example-plugins/repeat.py b/doc/example-plugins/repeat.py index 54bc581..3106457 100644 --- a/doc/example-plugins/repeat.py +++ b/doc/example-plugins/repeat.py @@ -1,8 +1,9 @@ -import time crontable = [] outputs = [] + def process_message(data): if data['channel'].startswith("D"): - outputs.append([data['channel'], "from repeat1 \"{}\" in channel {}".format(data['text'], data['channel']) ]) - + outputs.append([data['channel'], "from repeat1 \"{}\" in channel {}".format( + data['text'], data['channel'])] + ) diff --git a/doc/example-plugins/todo.py b/doc/example-plugins/todo.py index 616be49..cd1db8d 100644 --- a/doc/example-plugins/todo.py +++ b/doc/example-plugins/todo.py @@ -1,3 +1,4 @@ +from __future__ import print_function import os import pickle @@ -6,19 +7,21 @@ crontabs = [] tasks = {} -FILE="plugins/todo.data" + +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 + # only accept tasks on DM channels if channel.startswith("D"): if channel not in tasks.keys(): tasks[channel] = [] - #do command stuff + # do command stuff if text.startswith("todo"): tasks[channel].append(text[5:]) outputs.append([channel, "added"]) @@ -35,5 +38,5 @@ def process_message(data): num = int(text.split()[1]) - 1 tasks[channel].pop(num) if text == "show": - print tasks - pickle.dump(tasks, open(FILE,"wb")) + print(tasks) + pickle.dump(tasks, open(FILE, "wb")) diff --git a/rtmbot.py b/rtmbot.py index 3bd4184..a275cc5 100755 --- a/rtmbot.py +++ b/rtmbot.py @@ -1,33 +1,35 @@ #!/usr/bin/env python - import sys -sys.dont_write_bytecode = True - import glob import yaml -import json import os -import sys 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): self.last_ping = 0 self.token = token self.bot_plugins = [] self.slack_client = None + 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() @@ -38,12 +40,14 @@ class RtmBot(object): self.output() self.autoping() time.sleep(.1) + def autoping(self): - #hardcode the interval to 3 seconds + # 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"] @@ -51,35 +55,43 @@ class RtmBot(object): 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 != None and output[1] != None: - if limiter == True: + if channel is not None and output[1] is not None: + if limiter: time.sleep(.1) limiter = False - message = output[1].encode('ascii','ignore') + 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(directory+'/plugins/*'): + for plugin in glob.glob(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, directory + '/plugins/') + for plugin in glob.glob(directory + '/plugins/*.py') + \ + glob.glob(directory + '/plugins/*/*.py'): logging.info(plugin) name = plugin.split('/')[-1][:-3] -# try: + # try: self.bot_plugins.append(Plugin(name)) -# except: -# print "error loading plugin %s" % name + # except: + # print "error loading plugin %s" % name + class Plugin(object): - def __init__(self, name, plugin_config={}): + + def __init__(self, name, plugin_config=None): + if plugin_config is None: + plugin_config = {} # TODO: is this variable necessary? self.name = name self.jobs = [] self.module = __import__(name) @@ -90,32 +102,36 @@ class Plugin(object): self.module.config = config[name] 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.jobs.append(Job(interval, eval("self.module." + function))) 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): - #this makes the plugin fail with stack trace in debug mode + # this makes the plugin fail with stack trace in debug mode if not debug: try: - eval("self.module."+function_name)(data) + eval("self.module." + function_name)(data) except: dbg("problem in module {} {}".format(function_name, data)) else: - eval("self.module."+function_name)(data) + 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") + def do_jobs(self): for job in self.jobs: job.check() + def do_output(self): output = [] while True: @@ -129,15 +145,19 @@ class Plugin(object): self.module.outputs = [] return output + class Job(object): def __init__(self, interval, function): self.function = function self.interval = interval self.lastrun = 0 + 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 not debug: @@ -150,13 +170,18 @@ class Job(object): self.lastrun = time.time() pass + 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.basicConfig( + filename=config["LOGFILE"], + level=logging.INFO, + format='%(asctime)s %(message)s' + ) logging.info(directory) try: bot.start() @@ -182,20 +207,20 @@ if __name__ == "__main__": directory = os.path.dirname(sys.argv[0]) if not directory.startswith('/'): directory = os.path.abspath("{}/{}".format(os.getcwd(), - directory - )) + directory + )) - config = yaml.load(file(args.config or 'rtmbot.conf', 'r')) + 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 config.has_key("DAEMON"): + if 'DAEMON' in config: if config["DAEMON"]: import daemon + with daemon.DaemonContext(): main_loop() main_loop() - diff --git a/tox.ini b/tox.ini index 0c59058..d8924e4 100644 --- a/tox.ini +++ b/tox.ini @@ -20,4 +20,4 @@ basepython = [testenv:flake8] basepython=python deps=flake8 -commands=flake8 {toxinidir}/rtmbot.py {toxinidir}/example-plugins \ No newline at end of file +commands=flake8 {toxinidir}/rtmbot.py {toxinidir}/doc/example-plugins \ No newline at end of file