Skip to content

Commit

Permalink
[config, core] Started working on ConfigParser based config.py
Browse files Browse the repository at this point in the history
See issue #78 for details.

ATTENTION: This is far from being ready, do not use in production
  • Loading branch information
Elad Alfassa committed Sep 20, 2012
1 parent 97299a4 commit 1951a07
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 181 deletions.
6 changes: 3 additions & 3 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import signal

def run(config):
if hasattr(config, 'delay'):
delay = config.delay
if config.core.delay is not None:
delay = config.core.delay
else:
delay = 20
def signal_handler(sig, frame):
Expand All @@ -30,7 +30,7 @@ def signal_handler(sig, frame):
p = bot.Willie(config)
if hasattr(signal, 'SIGUSR1'):
signal.signal(signal.SIGUSR1, signal_handler)
p.run(config.host, config.port)
p.run(config.core.host, int(config.core.port))
except KeyboardInterrupt:
os._exit(0)
except Exception, e:
Expand Down
34 changes: 8 additions & 26 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def enumerate_modules(config):
if fn.endswith('.py') and not fn.startswith('_'):
filenames.append(os.path.join(modules_dir, fn))
else:
for fn in config.enable:
for fn in config.enable.split(','):
filenames.append(os.path.join(modules_dir, fn + '.py'))

if hasattr(config, 'extra') and config.extra is not None:
Expand All @@ -51,27 +51,7 @@ def enumerate_modules(config):

class Willie(irc.Bot):
def __init__(self, config):
if hasattr(config, "logchan_pm"):
lc_pm = config.logchan_pm
else:
lc_pm = None
if hasattr(config, 'use_ssl'):
use_ssl = config.use_ssl
else:
use_ssl = False
if hasattr(config, 'verify_ssl'):
verify_ssl = config.verify_ssl
else:
verify_ssl = False
if hasattr(config, 'ca_certs'):
ca_certs = config.ca_certs
else:
ca_certs = '/etc/pki/tls/cert.pem'
if hasattr(config, 'serverpass'):
serverpass = config.serverpass
else:
serverpass = None
irc.Bot.__init__(self, config.nick, config.name, config.channels, config.user, config.password, lc_pm, use_ssl, verify_ssl, ca_certs, serverpass)
irc.Bot.__init__(self, config.core)
self.config = config
"""The ``Config`` for the current Willie instance."""
self.doc = {}
Expand Down Expand Up @@ -278,14 +258,14 @@ def __new__(cls, text, origin, bytes, match, event, args, self):
setting ``mode -m`` on the channel ``#example``, args would be
``('#example', '-m')``
"""
s.admin = (origin.nick in self.config.admins) or origin.nick.lower() == self.config.owner.lower()
s.admin = (origin.nick in self.config.admins.split(',')) or origin.nick.lower() == self.config.owner.lower()
"""
True if the nick which triggered the command is in Willie's admin
list as defined in the config file.
"""

if s.admin == False:
for each_admin in self.config.admins:
for each_admin in self.config.admins.split(','):
re_admin = re.compile(each_admin)
if re_admin.findall(origin.host):
s.admin = True
Expand Down Expand Up @@ -359,8 +339,10 @@ def dispatch(self, origin, args):

willie = self.wrapped(origin, text)
trigger = self.Trigger(text, origin, bytes, match, event, args, self)
if trigger.nick in self.config.other_bots:
continue

if self.config.core.other_bots is not None:
if trigger.nick in self.config.other_bots.split(','):
continue

nick = (trigger.nick).lower()

Expand Down
176 changes: 52 additions & 124 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,18 @@
http://dft.ba/-williesource
"""

import os, sys, imp
import os, sys
import ConfigParser
import getpass
from textwrap import dedent as trim
from bot import enumerate_modules

class ConfigurationError(Exception):
""" Exception type for configuration errors """
def __init__(self, value):
self.value = value
def __str__(self):
return 'ConfigurationError: %s' % self.value

class Config(object):
def __init__(self, filename, load=True):
Expand All @@ -43,137 +50,58 @@ def __init__(self, filename, load=True):
"""
self.filename = filename
"""The config object's associated file, as noted above."""
self.prefix = r'\.'
"""
This indicates the prefix for commands. (i.e, the . in the .w
command.) Note that this is used in a regular expression, so regex
syntax and special characters apply.
"""
self.user = 'willie'
"""The user/ident the bot will use."""
self.name = 'Willie Embosbot, http://willie.dftba.net'
"""The "real name" used for the bot's whois."""
self.port = 6667
"""The port to connect on"""
self.password = None
"""The nickserv password"""
self.host = 'irc.example.net'
"""
The host to connect to. This is set to irc.example.net by default,
which serves as a sanity check to make sure that the bot has been
configured.
"""
if load:
module = imp.load_source('Config', filename)
for attrib in dir(module):
setattr(self, attrib, getattr(module, attrib))
self.parser = ConfigParser.SafeConfigParser(allow_no_value=True)
self.parser.read(filename)

#Sanity check for the configuration file:
if not self.parser.has_section('core'):
raise ConfigurationError('Core section missing!')
if not self.parser.has_option('core', 'nick'):
raise ConfigurationError('Bot IRC nick not defined, expected option `nick` in [core] section')
if not self.parser.has_option('core', 'owner'):
raise ConfigurationError('Bot owner not defined, expected option `owner` in [core] section')
if not self.parser.has_option('core', 'host'):
raise ConfigurationError('IRC server address not defined, expceted option `host` in [core] section')

#Setting defaults:
if not self.parser.has_option('core', 'port'):
self.parser.set('core', 'port', '6667')
if not self.parser.has_option('core', 'user'):
self.parser.set('core', 'user', 'willie')
if not self.parser.has_option('core', 'name'):
self.parser.set('core', 'name', 'Willie Embosbot, http://willie.dftba.net')
if not self.parser.has_option('core', 'prefix'):
self.parser.set('core', 'prefix', r'\.')
if not self.parser.has_option('core', 'admins'):
self.parser.set('core', 'admins', '')

class ConfigSection():
"""Represents a section of the config file, contains all keys in the section as attributes"""
def __init__(self, name, items):
self.name = name
for item in items:
setattr(self, item[0], item[1])
def __getattr__(self, name):
return None

def __getattr__(self, name):
""""""
if name in self.parser.sections():
items = self.parser.items(name)
return self.ConfigSection(name, items) #Return a section
elif self.parser.has_option('core', name):
return self.parser.get('core', name) #For backwards compatibility
else:
raise AttributeError("%r object has no attribute %r" % (type(self).__name__, name))

def set_attr(self, attr, value):
"""
Set attr to value. This will succeed regardless of whether the attribute
is already set, and regardless of whether the given and current values
are of the same type.
"""
setattr(self, attr, value)

def write(self):
"""
Writes the current configuration to the file from which the current
configuration is derived. Changes made through ``set_attr`` may not be
properly written by this function.
"""
f = open(self.filename, 'w')

if hasattr(self, 'password') and self.password:
password_line = "password = '"+self.password+"'"
else:
password_line = "# password = 'example'"
if hasattr(self, 'serverpass') and self.serverpass:
serverpass_line = "serverpass = '"+self.serverpass+"'"
else:
serverpass_line = "# serverpass = 'example'"
if hasattr(self, 'enable') and self.enable:
enable_line = "enable = "+str(self.enable)
else:
enable_line = "# enable = []"
extra = self.extra.append(os.getcwd() + '/modules/')
if hasattr(self, 'verify_ssl'):
verify_ssl_line = "verify_ssl = "+str(self.verify_ssl)
else:
verify_ssl_line = "# verify_ssl = True"

if hasattr(self, 'ca_certs'):
ca_cert_line = "ca_certs = '"+str(self.ca_certs)+"'"
else:
ca_cert_line = "# ca_certs = '/etc/pki/tls/cert.pem'"
if self.bind_host is not 'None':
bind_host_line = "bind_host = '%s'" % self.bind_host
else:
bind_host_line = "# bind_host = '0.0.0.0'"
output = trim("""\
nick = '"""+self.nick+"""'
user = '"""+self.user+"""'
host = '"""+self.host+"""'
port = """+str(self.port)+"""
channels = """+str(self.channels)+"""
owner = '"""+self.owner+"""'
name = '"""+self.name+"""'
use_ssl = """+str(self.use_ssl)+"""
"""+verify_ssl_line+"""
"""+ca_cert_line+"""
"""+bind_host_line+"""
# Channel where debug messages should be sent.
debug_target = '"""+self.debug_target+"""'
# Verbosity level for debug messages.
verbose = '"""+self.verbose+"""'
# List of other bots, whose outputs should be ignored
other_bots = """+str(self.other_bots)+"""
# password is the NickServ password, serverpass is the server password
"""+password_line+"""
"""+serverpass_line+"""
# The oper name and password, if the bot is allowed to /oper
"""+self.operline+"""
# These are people who will be able to use admin.py's functions...
admins = """+str(self.admins)+"""
# But admin.py is disabled by default, as follows:
exclude = """+str(self.exclude)+"""
# If you want to enumerate a list of modules rather than disabling
# some, use "enable = ['example']", which takes precedent over exclude
#
"""+enable_line+"""
# Directories to load user modules from
# e.g. /path/to/my/modules
extra = """+str(extra)+"""
# Services to load: maps channel names to white or black lists
#
# ?? Doesn't seem to do anything?
# external = {
# '#liberal': ['!'], # allow all
# '#conservative': [], # allow none
# '*': ['!'] # default whitelist, allow all
#}
""")+(self.settings_chunk+trim("""
#-----------------------MODULE SETTINGS-----------------------
""")+self.modules_chunk)+trim("""
# EOF
""")
print >> f, output
f.close()

pass


def interactive_add(self, attrib, prompt, default=None, ispass=False):
Expand Down
43 changes: 33 additions & 10 deletions irc.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,28 +78,48 @@ def log_raw(line):
f.close()

class Bot(asynchat.async_chat):
def __init__(self, nick, name, channels, user, password=None, logchan_pm=None, use_ssl = False, verify_ssl=False, ca_certs='', serverpass=None):
def __init__(self, config):
if config.use_ssl is not None:
use_ssl = config.use_ssl
else:
use_ssl = False
if config.verify_ssl is not None:
verify_ssl = config.verify_ssl
else:
verify_ssl = False
if config.ca_certs is not None:
ca_certs = config.ca_certs
else:
ca_certs = '/etc/pki/tls/cert.pem'
if config.serverpass is not None:
serverpass = config.serverpass
else:
serverpass = None
asynchat.async_chat.__init__(self)
self.set_terminator('\n')
self.buffer = ''

self.nick = nick
self.nick = config.nick
"""Willie's current nick. Changing this while Willie is running is untested."""
self.user = user
self.user = config.user
"""Willie's user/ident."""
self.name = name
self.name = config.name
"""Willie's "real name", as used for whois."""
self.password = password
self.password = config.password
"""Willie's NickServ password"""

self.verbose = True
"""True if Willie is running in verbose mode."""
self.channels = channels or []
self.channels = config.channels
"""The list of channels Willie joins on startup."""
if self.channels is not None:
self.channels = self.channels.split(',')
else:
self.channels = []

self.stack = []
self.logchan_pm = logchan_pm
self.serverpass = serverpass
self.logchan_pm = config.logchan_pm
self.serverpass = config.serverpass
self.verify_ssl = verify_ssl
self.ca_certs = ca_certs
self.use_ssl = use_ssl
Expand All @@ -120,6 +140,7 @@ def __init__(self, nick, name, channels, user, password=None, logchan_pm=None, u
#We need this to prevent error loops in handle_error
self.error_count = 0
self.last_error_timestamp = None


def safe(self, string):
'''Remove newlines from a string and make sure it is utf8'''
Expand Down Expand Up @@ -215,7 +236,7 @@ def handle_connect(self):
if not self.verify_ssl:
self.ssl = ssl.wrap_socket(self.socket, do_handshake_on_connect=False, suppress_ragged_eofs=True)
else:
verification = verify_ssl_cn(self.config.host, self.config.port)
verification = verify_ssl_cn(self.config.host, int(self.config.port))
if verification is 'NoCertFound':
stderr('Can\'t get server certificate, SSL might be disabled on the server.')
sys.exit(1)
Expand Down Expand Up @@ -317,7 +338,9 @@ def found_terminator(self):

if args[0] == 'PING':
self.write(('PONG', text))
if args[0] == '433':
elif args[0] == 'ERROR':
self.debug('IRC Server Error', text, 'always')
elif args[0] == '433':
stderr('Nickname already in use!')
self.hasquit = True
self.handle_close()
Expand Down
Loading

0 comments on commit 1951a07

Please sign in to comment.