ubot2/rtmbot/core.py

226 lines
7.7 KiB
Python
Raw Normal View History

2014-12-08 15:31:55 -08:00
#!/usr/bin/env python
2014-11-17 21:17:37 -08:00
import sys
2014-11-17 17:07:52 -08:00
import glob
import os
import time
2014-12-11 16:25:41 -08:00
import logging
2014-11-17 17:07:52 -08:00
from slackclient import SlackClient
sys.dont_write_bytecode = True
2014-11-17 17:07:52 -08:00
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
2014-11-17 17:07:52 -08:00
self.bot_plugins = []
self.slack_client = None
def _dbg(self, debug_string):
if self.debug:
logging.info(debug_string)
2014-11-17 17:07:52 -08:00
def connect(self):
"""Convenience method that creates Server instance"""
self.slack_client = SlackClient(self.token)
self.slack_client.rtm_connect()
def _start(self):
2014-11-17 17:07:52 -08:00
self.connect()
self.load_plugins()
while True:
for reply in self.slack_client.rtm_read():
2014-11-17 17:07:52 -08:00
self.input(reply)
self.crons()
self.output()
self.autoping()
2014-11-17 17:07:52 -08:00
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
2014-11-17 17:07:52 -08:00
def input(self, data):
if "type" in data:
function_name = "process_" + data["type"]
self._dbg("got {}".format(function_name))
2014-11-17 17:07:52 -08:00
for plugin in self.bot_plugins:
plugin.register_jobs()
2014-11-17 17:07:52 -08:00
plugin.do(function_name, data)
2014-11-17 17:07:52 -08:00
def output(self):
for plugin in self.bot_plugins:
2014-12-02 18:34:52 -08:00
limiter = False
2014-11-17 17:07:52 -08:00
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]
channel.send_message("{}".format(message))
limiter = True
2014-11-17 17:07:52 -08:00
def crons(self):
for plugin in self.bot_plugins:
plugin.do_jobs()
2014-11-17 17:07:52 -08:00
def load_plugins(self):
for plugin in glob.glob(self.directory + '/plugins/*'):
2014-11-17 21:17:37 -08:00
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'):
2014-12-11 16:25:41 -08:00
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))
2014-11-17 17:07:52 -08:00
2014-11-17 17:07:52 -08:00
class Plugin(object):
def __init__(self, name, plugin_config=None):
2016-04-17 17:34:17 -07:00
'''
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:
2016-04-17 17:34:17 -07:00
plugin_config = {}
2014-11-17 17:07:52 -08:00
self.name = name
self.jobs = []
self.module = __import__(name)
self.module.config = plugin_config
self.debug = self.module.config.get('DEBUG', False)
2014-11-17 17:07:52 -08:00
self.register_jobs()
self.outputs = []
2014-12-11 16:25:41 -08:00
if 'setup' in dir(self.module):
self.module.setup()
2014-11-17 17:07:52 -08:00
def register_jobs(self):
if 'crontable' in dir(self.module):
for interval, function in self.module.crontable:
2016-04-17 17:34:17 -07:00
self.jobs.append(Job(interval, eval("self.module." + function), self.debug))
2014-12-11 16:25:41 -08:00
logging.info(self.module.crontable)
self.module.crontable = []
2014-11-18 09:55:23 -08:00
else:
self.module.crontable = []
2014-11-17 17:07:52 -08:00
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))
2014-11-17 17:07:52 -08:00
def do_jobs(self):
for job in self.jobs:
job.check()
2014-11-17 17:07:52 -08:00
def do_output(self):
output = []
while True:
if 'outputs' in dir(self.module):
if len(self.module.outputs) > 0:
2014-12-11 16:25:41 -08:00
logging.info("output from {}".format(self.module))
2014-11-17 17:07:52 -08:00
output.append(self.module.outputs.pop(0))
else:
break
2014-11-18 09:55:23 -08:00
else:
self.module.outputs = []
2014-11-17 17:07:52 -08:00
return output
2014-11-17 17:07:52 -08:00
class Job(object):
2016-04-17 17:34:17 -07:00
def __init__(self, interval, function, debug):
2014-11-17 17:07:52 -08:00
self.function = function
self.interval = interval
self.lastrun = 0
2016-04-17 17:34:17 -07:00
self.debug = debug
2014-11-17 17:07:52 -08:00
def __str__(self):
return "{} {} {}".format(self.function, self.interval, self.lastrun)
2014-11-17 17:07:52 -08:00
def __repr__(self):
return self.__str__()
2014-11-17 17:07:52 -08:00
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
2014-11-24 19:01:41 -08:00
try:
self.function()
except Exception:
logging.exception("Problem in job check: {}".format(self.function))
2014-11-17 17:07:52 -08:00
self.lastrun = time.time()
2014-11-21 13:35:15 -08:00
class UnknownChannel(Exception):
pass