Skip to content

Commit

Permalink
Begin work on multiple IRC server connections (#133)
Browse files Browse the repository at this point in the history
This will parse multiple configs and join multiple servers, but there
are some issues:

- It's unclear how logging is configured with multiple config files
- Quitting one instance called `reactor.stop()` which this commit
  removes -- bu now when all instances are shutdown, the reactor is not
  stopped

I think we need some global Cardinal object that keeps track of multiple
CardinalBot instances -- it may be possible to use a single
CardinalBotFactory for this as well, depending on how things are
structured.
  • Loading branch information
johnmaguire committed Sep 2, 2019
1 parent 6811611 commit a566318
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 135 deletions.
280 changes: 147 additions & 133 deletions cardinal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
2 changes: 0 additions & 2 deletions cardinal/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit a566318

Please sign in to comment.