#!/usr/bin/env python3 """ usage: reaktor (get-config|run) [CONFIG] `get-config` writes the reaktor configuration to stdout if CONFIG is not given it will write the reaktor default config. use this for creating your own config """ import logging import logging.handlers import os import threading from asyncore import loop from os.path import dirname from re import match import reaktor from reaktor.getconf import make_getconf from reaktor.ircasy import asybot from reaktor.translate_colors import translate_colors default_config = dirname(reaktor.__file__)+'/config.py' log = logging.getLogger('Reaktor') class Reaktor(asybot): def __init__(self, config=default_config, getconf=None): self.config = config if not getconf: self.getconf = make_getconf(self.conf) else: self.getconf = getconf log.info("using config file %s" % (config)) asybot.__init__( self, getconf('irc_server'), getconf('irc_port'), getconf('irc_nickname'), getconf('irc_channels'), hammer_interval=getconf('irc_hammer_interval'), alarm_timeout=getconf('irc_alarm_timeout'), kill_timeout=getconf('irc_kill_timeout'), nickserv_password=getconf('nickserv_password'), ) def is_admin(self, prefix): try: with open(self.getconf('auth_file')) as f: for line in f: if line.strip() == prefix: return True except Exception as e: log.info(e) return False def on_join(self, prefix, command, params, rest): for command in self.getconf('on_join', []): thread = threading.Thread( target=self.execute_command, args=(command, None, prefix, params), ) thread.start() def on_ping(self, prefix, command, params, rest): for command in self.getconf('on_ping', []): prefix = '!' # => env = { _prefix: '!', _from: '' } # TODO why don't we get a list here and use ','.join() ? params = command.get('targets') thread = threading.Thread( target=self.execute_command, args=(command, None, prefix, params), ) thread.start() def on_privmsg(self, prefix, command, params, rest): if not (self.nickname == self.getconf('name')): # reload config if the name changed # TODO: this sucks, use another sidechannel to tell config the # new nickname log.debug("nickname differs ('{}' to '{}')".format( self.nickname, self.getconf('name'))) os.environ['REAKTOR_NICKNAME'] = self.nickname self.getconf = make_getconf(self.config) log.info('nickname changed to {}'.format(self.getconf('name'))) for command in self.getconf('commands'): y = match(command['pattern'], rest) if y: if not self.is_admin(prefix): self.PRIVMSG(params, 'unauthorized!') else: thread = threading.Thread( target=self.execute_command, args=(command, y, prefix, params), ) thread.start() for command in self.getconf('public_commands'): y = match(command['pattern'], rest) if y: thread = threading.Thread( target=self.execute_command, args=(command, y, prefix, params), ) thread.start() def execute_command(self, command, match, prefix, target): from os.path import realpath, dirname, join from subprocess import Popen as popen, PIPE from time import time # TODO: allow only commands below ./commands/ exe = join(dirname(realpath(dirname(__file__))), command['argv'][0]) myargv = [exe] + command['argv'][1:] try: if match and match.groupdict().get('args', None): myargv += [match.groupdict()['args']] except Exception as e: log.info("cannot parse args!: {}".format(e)) cwd = self.getconf('workdir') if not os.access(cwd, os.W_OK): log.error( "Workdir '%s' is not Writable! Falling back to root dir" % cwd) cwd = "/" env = {} env.update(os.environ) # first merge os.environ env.update(command.get('env', {})) # then env of cfg env['_prefix'] = prefix env['_from'] = prefix.split('!', 1)[0] log.debug('self:' + self.nickname) # when receiving /query, answer to the user, not to self if self.nickname in target: target.remove(self.nickname) target.append(env['_from']) log.debug('target:' + str(target)) start = time() try: log.debug("Running : %s" % str(myargv)) log.debug("Environ : %s" % (str(env))) p = popen(myargv, bufsize=1, stdout=PIPE, stderr=PIPE, env=env, cwd=cwd) except Exception as error: self.ME(target, 'brain damaged') log.error('OSError@%s: %s' % (myargv, error)) return pid = p.pid for line in iter(p.stdout.readline, ''.encode()): try: self.PRIVMSG(target, translate_colors(line.decode())) except Exception as error: log.error('no send: %s' % error) log.debug('%s stdout: %s' % (pid, line)) p.wait() elapsed = time() - start code = p.returncode log.info('command: %s -> %s in %d seconds' % (myargv, code, elapsed)) [log.debug('%s stderr: %s' % (pid, x)) for x in p.stderr.readlines()] if code != 0: self.ME(target, 'mimimi') def main(): from docopt import docopt args = docopt(__doc__) conf = args['CONFIG'] if args['CONFIG'] else default_config getconf = make_getconf(conf) logging.basicConfig(level=logging.DEBUG if getconf('debug') else logging.INFO) log.debug("Debug enabled") if args['run']: Reaktor(conf, getconf) loop() elif args['get-config']: print(open(conf).read()) if __name__ == "__main__": main()