diff --git a/cardinal.py b/cardinal.py index eb4749f..03a63e0 100755 --- a/cardinal.py +++ b/cardinal.py @@ -12,139 +12,153 @@ from cardinal.bot import CardinalBotFactory -def setup_logging(config=None): - if config is None: - logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' - ) - else: - logging.config.dictConfig(config) - - return logging.getLogger(__name__) +class Cardinal(object): + def __init__(self): + # Will be set later + self.logging_config = None + + # Define the config + config_spec = ConfigSpec() + config_spec.add_option('nickname', basestring, 'Cardinal') + config_spec.add_option('password', basestring, None) + config_spec.add_option('username', basestring, None) + config_spec.add_option('realname', basestring, None) + config_spec.add_option('network', basestring, 'irc.freenode.net') + config_spec.add_option('port', int, 6667) + config_spec.add_option('server_password', basestring, None) + config_spec.add_option('ssl', bool, False) + config_spec.add_option('storage', basestring, os.path.join( + os.path.dirname(os.path.realpath(sys.argv[0])), + 'storage' + )) + config_spec.add_option('channels', list, ['#bots']) + config_spec.add_option('plugins', list, [ + 'wikipedia', + 'ping', + 'help', + 'admin', + 'join_on_invite', + 'urls', + 'calculator', + 'lastfm', + 'remind', + 'weather', + 'youtube', + 'urbandict' + ]) + config_spec.add_option('logging', dict, None) + + self.config_spec = config_spec + + # Configure ArgParser + arg_parser = argparse.ArgumentParser(description=""" + Cardinal IRC bot + + A Twisted IRC bot designed to be simple to use and and easy to extend. + + https://github.com/JohnMaguire/Cardinal + """, formatter_class=argparse.RawDescriptionHelpFormatter) + arg_parser.add_argument('config', nargs='+', + help='config file location') + + self.arg_parser = arg_parser + + @property + def logger(self): + if not hasattr(self, '_logger'): + if self.logging_config: + logging.config.dictConfig(self.logging_config) + else: + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - ' + '%(message)s' + ) + + self._logger = logging.getLogger(__name__) + return self._logger + + def main(self): + args = self.arg_parser.parse_args() + for config_file in args.config: + parser = ConfigParser(self.config_spec) + + # Load config file + try: + config = parser.load_config(config_file) + except Exception: + # Need to setup a logger early + self.logger.exception("Unable to load config: {}".format( + config_file)) + os.exit(1) + + # Config loaded, setup the logger + self.logging_config = config['logging'] + self.logger.info("Config loaded: {}".format(config_file)) + + # Determine storage directory + storage_path = None + if config['storage'] is not None: + if config['storage'].startswith('/'): + storage_path = config['storage'] + else: + storage_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), + config['storage'] + ) + + self.logger.info("Storage path: {}".format(storage_path)) + + directories = [ + os.path.join(storage_path, 'database'), + os.path.join(storage_path, 'logs'), + ] + + for directory in directories: + if not os.path.exists(directory): + self.logger.info( + "Initializing storage directory: {}".format( + directory)) + os.makedirs(directory) + + # If no username is supplied, default to nickname + if config['username'] is None: + config['username'] = config['nickname'] + + # Instance a new factory, and connect with/without SSL + self.logger.debug("Instantiating CardinalBotFactory") + factory = CardinalBotFactory(config['network'], + config['server_password'], + config['channels'], + config['nickname'], + config['password'], + config['username'], + config['realname'], + config['plugins'], + storage_path) + + if not config['ssl']: + self.logger.info( + "Connecting over plaintext to %s:%d" % + (config['network'], config['port']) + ) + + reactor.connectTCP(config['network'], config['port'], factory) + else: + self.logger.info( + "Connecting over SSL to %s:%d" % + (config['network'], config['port']) + ) + + # For SSL, we need to import the SSL module from Twisted + from twisted.internet import ssl + reactor.connectSSL(config['network'], config['port'], factory, + ssl.ClientContextFactory()) + + # Run the Twisted reactor + reactor.run() if __name__ == "__main__": - # Create a new instance of ArgumentParser with a description about Cardinal - arg_parser = argparse.ArgumentParser(description=""" -Cardinal IRC bot - -A Twisted IRC bot designed to be simple to use and and easy to extend. - -https://github.com/JohnMaguire/Cardinal -""", formatter_class=argparse.RawDescriptionHelpFormatter) - - arg_parser.add_argument('config', metavar='config', - help='custom config location') - - # Parse command-line arguments - args = arg_parser.parse_args() - config_file = args.config - - # Define the config spec and create a parser for our internal config - spec = ConfigSpec() - spec.add_option('nickname', basestring, 'Cardinal') - spec.add_option('password', basestring, None) - spec.add_option('username', basestring, None) - spec.add_option('realname', basestring, None) - spec.add_option('network', basestring, 'irc.freenode.net') - spec.add_option('port', int, 6667) - spec.add_option('server_password', basestring, None) - spec.add_option('ssl', bool, False) - spec.add_option('storage', basestring, os.path.join( - os.path.dirname(os.path.realpath(sys.argv[0])), - 'storage' - )) - spec.add_option('channels', list, ['#bots']) - spec.add_option('plugins', list, [ - 'wikipedia', - 'ping', - 'help', - 'admin', - 'join_on_invite', - 'urls', - 'calculator', - 'lastfm', - 'remind', - 'weather', - 'youtube', - 'urbandict' - ]) - spec.add_option('logging', dict, None) - - parser = ConfigParser(spec) - - # Load config file - try: - config = parser.load_config(config_file) - except Exception: - # Need to setup a logger early - logger = setup_logging() - logger.exception("Unable to load config: {}".format(config_file)) - os.exit(1) - - # Config loaded, setup the logger - logger = setup_logging(config['logging']) - - logger.info("Config loaded: {}".format(config_file)) - - # Determine storage directory - storage_path = None - if config['storage'] is not None: - if config['storage'].startswith('/'): - storage_path = config['storage'] - else: - storage_path = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - config['storage'] - ) - - logger.info("Storage path: {}".format(storage_path)) - - directories = [ - os.path.join(storage_path, 'database'), - os.path.join(storage_path, 'logs'), - ] - - for directory in directories: - if not os.path.exists(directory): - logger.info( - "Initializing storage directory: {}".format(directory)) - os.makedirs(directory) - - # If no username is supplied, default to nickname - if config['username'] is None: - config['username'] = config['nickname'] - - # Instance a new factory, and connect with/without SSL - logger.debug("Instantiating CardinalBotFactory") - factory = CardinalBotFactory(config['network'], - config['server_password'], - config['channels'], - config['nickname'], - config['password'], - config['username'], - config['realname'], - config['plugins'], - storage_path) - - if not config['ssl']: - logger.info( - "Connecting over plaintext to %s:%d" % - (config['network'], config['port']) - ) - - reactor.connectTCP(config['network'], config['port'], factory) - else: - logger.info( - "Connecting over SSL to %s:%d" % - (config['network'], config['port']) - ) - - # For SSL, we need to import the SSL module from Twisted - from twisted.internet import ssl - reactor.connectSSL(config['network'], config['port'], factory, - ssl.ClientContextFactory()) - - # Run the Twisted reactor - reactor.run() + cardinal = Cardinal() + cardinal.main() diff --git a/cardinal/bot.py b/cardinal/bot.py index 6504bdb..2c4de68 100644 --- a/cardinal/bot.py +++ b/cardinal/bot.py @@ -579,8 +579,6 @@ def clientConnectionLost(self, connector, reason): "Disconnected successfully (%s), quitting." % reason ) - self.reactor.stop() - def clientConnectionFailed(self, connector, reason): """Called when a connection attempt fails.