HUUUUUUUUGE Configuration change.

This commit is contained in:
Jeremy Fincher 2004-01-18 07:58:26 +00:00
parent b18ad358ee
commit 6ca78924f3
59 changed files with 887 additions and 1091 deletions

View File

@ -1,3 +1,6 @@
* Removed Admin.setprefixchar, since it's unneeded with the new
configuration.
* Changed the reply method of the irc object given to plugins not * Changed the reply method of the irc object given to plugins not
to require a msg object. to require a msg object.

View File

@ -40,5 +40,11 @@ othersDir = os.path.join(installDir, 'others')
sys.path.insert(0, srcDir) sys.path.insert(0, srcDir)
sys.path.insert(0, othersDir) sys.path.insert(0, othersDir)
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: import registry
supybot = registry.Group()
supybot.setName('supybot')
supybot.registerGroup('plugins')
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -156,7 +156,7 @@ def makeNewAlias(name, alias):
class Alias(callbacks.Privmsg): class Alias(callbacks.Privmsg):
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
filename = os.path.join(conf.dataDir, 'Aliases.db') filename = os.path.join(conf.supybot.directories.data(), 'Aliases.db')
# Schema: {name: [alias, locked]} # Schema: {name: [alias, locked]}
self.aliases = structures.PersistentDictionary(filename) self.aliases = structures.PersistentDictionary(filename)
@ -207,7 +207,7 @@ class Alias(callbacks.Privmsg):
def addAlias(self, irc, name, alias, lock=False): def addAlias(self, irc, name, alias, lock=False):
if self._invalidCharsRe.search(name): if self._invalidCharsRe.search(name):
raise AliasError, 'Names cannot contain spaces or square brackets.' raise AliasError, 'Names cannot contain spaces or square brackets.'
if conf.enablePipeSyntax and '|' in name: if conf.supybot.pipeSyntax() and '|' in name:
raise AliasError, 'Names cannot contain pipes.' raise AliasError, 'Names cannot contain pipes.'
realName = callbacks.canonicalName(name) realName = callbacks.canonicalName(name)
if name != realName: if name != realName:

View File

@ -67,7 +67,7 @@ priorityKeys = ['p1', 'p2', 'p3', 'p4', 'p5', 'Low', 'Normal', 'High',
severityKeys = ['enhancement', 'trivial', 'minor', 'normal', 'major', severityKeys = ['enhancement', 'trivial', 'minor', 'normal', 'major',
'critical', 'blocker'] 'critical', 'blocker']
dbfilename = os.path.join(conf.dataDir, 'Bugzilla.db') dbfilename = os.path.join(conf.supybot.directories.data(), 'Bugzilla.db')
def makeDb(filename): def makeDb(filename):
if os.path.exists(filename): if os.path.exists(filename):

View File

@ -91,7 +91,8 @@ class ChannelLogger(irclib.IrcCallback):
return self.logs[channel] return self.logs[channel]
else: else:
try: try:
log = file(os.path.join(conf.logDir, '%s.log' % channel), 'a') logDir = conf.supybot.directories.log()
log = file(os.path.join(logDir, '%s.log' % channel), 'a')
self.logs[channel] = log self.logs[channel] = log
return log return log
except IOError: except IOError:
@ -99,7 +100,7 @@ class ChannelLogger(irclib.IrcCallback):
return StringIO() return StringIO()
def timestamp(self, log): def timestamp(self, log):
log.write(time.strftime(conf.logTimestampFormat)) log.write(time.strftime(conf.supybot.log.timestampFormat()))
log.write(' ') log.write(' ')
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):

View File

@ -71,8 +71,8 @@ class DCC(callbacks.Privmsg):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(60) sock.settimeout(60)
host = ircutils.hostFromHostmask(irc.prefix) host = ircutils.hostFromHostmask(irc.prefix)
if conf.externalIP is not None: if conf.supybot.externalIP():
ip = conf.externalIP ip = conf.supybot.externalIP()
else: else:
try: try:
ip = socket.gethostbyname(host) ip = socket.gethostbyname(host)

View File

@ -93,7 +93,7 @@ class Debian(callbacks.Privmsg,
'debian/dists/unstable/Contents-i386.gz', 'debian/dists/unstable/Contents-i386.gz',
604800, None) 604800, None)
} }
contents = os.path.join(conf.dataDir, 'Contents-i386.gz') contents = os.path.join(conf.supybot.directories.data(),'Contents-i386.gz')
configurables = configurable.Dictionary( configurables = configurable.Dictionary(
[('python-zegrep', configurable.BoolType, False, [('python-zegrep', configurable.BoolType, False,
"""An advanced option, mostly just for testing; uses a Python-coded """An advanced option, mostly just for testing; uses a Python-coded

View File

@ -51,7 +51,7 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>' 'plugin. Download it at <http://pysqlite.sf.net/>'
dbfilename = os.path.join(conf.dataDir, 'Dunno.db') dbfilename = os.path.join(conf.supybot.directories.data(), 'Dunno.db')
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
# This will be called by setup.py to configure this module. onStart and # This will be called by setup.py to configure this module. onStart and

View File

@ -172,7 +172,8 @@ class Ebay(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
return '; '.join(resp) return '; '.join(resp)
else: else:
raise EbayError, 'That doesn\'t appear to be a proper eBay ' \ raise EbayError, 'That doesn\'t appear to be a proper eBay ' \
'auction page. (%s)' % conf.replyPossibleBug 'auction page. (%s)' % \
conf.supybot.replies.possibleBug()
Class = Ebay Class = Ebay

View File

@ -134,7 +134,7 @@ class Enforcer(callbacks.Privmsg, configurable.Mixin):
irc.queueMsg(ircmsgs.topic(channel, self.topics[channel])) irc.queueMsg(ircmsgs.topic(channel, self.topics[channel]))
if self.configurables.get('revenge', channel): if self.configurables.get('revenge', channel):
irc.queueMsg(ircmsgs.kick(channel, msg.nick, irc.queueMsg(ircmsgs.kick(channel, msg.nick,
conf.replyNoCapability % conf.supybot.replies.noCapability() %
_chanCap(channel, 'topic'))) _chanCap(channel, 'topic')))
else: else:
self.topics[channel] = msg.args[1] self.topics[channel] = msg.args[1]

View File

@ -319,7 +319,7 @@ class Factoids(plugins.ChannelDBHandler,
counter = 0 counter = 0
for (added_by, added_at) in factoids: for (added_by, added_at) in factoids:
counter += 1 counter += 1
added_at = time.strftime(conf.humanTimestampFormat, added_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at))) time.localtime(int(added_at)))
L.append('#%s was added by %s at %s' % (counter,added_by,added_at)) L.append('#%s was added by %s at %s' % (counter,added_by,added_at))
factoids = '; '.join(L) factoids = '; '.join(L)

View File

@ -159,7 +159,7 @@ class Gameknot(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
raise callbacks.Error, 'No user %s exists.' % name raise callbacks.Error, 'No user %s exists.' % name
else: else:
raise callbacks.Error,'The format of the page was odd. %s' % \ raise callbacks.Error,'The format of the page was odd. %s' % \
conf.replyPossibleBug conf.supybot.replies.possibleBug()
except urllib2.URLError: except urllib2.URLError:
raise callbacks.Error, 'Couldn\'t connect to gameknot.com' raise callbacks.Error, 'Couldn\'t connect to gameknot.com'

View File

@ -63,11 +63,13 @@ class HeraldDB(object):
def __init__(self): def __init__(self):
self.heralds = {} self.heralds = {}
self.open() self.open()
dataDir = conf.supybot.directories.data()
self.filename = os.path.join(dataDir, 'Herald.db')
def open(self): def open(self):
filename = os.path.join(conf.dataDir, 'Herald.db') dataDir = conf.supybot.directories.data()
if os.path.exists(filename): if os.path.exists(self.filename):
fd = file(filename) fd = file(self.filename)
for line in fd: for line in fd:
line = line.rstrip() line = line.rstrip()
try: try:
@ -81,7 +83,7 @@ class HeraldDB(object):
fd.close() fd.close()
def close(self): def close(self):
fd = file(os.path.join(conf.dataDir, 'Herald.db'), 'w') fd = file(self.filename, 'w')
L = self.heralds.items() L = self.heralds.items()
L.sort() L.sort()
for ((id, channel), msg) in L: for ((id, channel), msg) in L:

View File

@ -52,7 +52,7 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>' 'plugin. Download it at <http://pysqlite.sf.net/>'
dbfilename = os.path.join(conf.dataDir, 'Infobot.db') dbfilename = os.path.join(conf.supybot.directories.data(), 'Infobot.db')
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
from questions import expect, anything, something, yn from questions import expect, anything, something, yn

View File

@ -64,13 +64,16 @@ def configure(onStart, afterConnect, advanced):
onStart.append('load Lookup') onStart.append('load Lookup')
print 'This module allows you to define commands that do a simple key' print 'This module allows you to define commands that do a simple key'
print 'lookup and return some simple value. It has a command "add"' print 'lookup and return some simple value. It has a command "add"'
### TODO: fix conf.dataDir here. I'm waiting until we rewrite this with
### a proper question.py print statement.
print 'that takes a command name and a file in conf.dataDir and adds a' print 'that takes a command name and a file in conf.dataDir and adds a'
print 'command with that name that responds with mapping from that file.' print 'command with that name that responds with mapping from that file.'
print 'The file itself should be composed of lines of the form key:value.' print 'The file itself should be composed of lines of the form key:value.'
while yn('Would you like to add a file?') == 'y': while yn('Would you like to add a file?') == 'y':
filename = something('What\'s the filename?') filename = something('What\'s the filename?')
try: try:
fd = file(os.path.join(conf.dataDir, filename)) dataDir = conf.supybot.directories.data()
fd = file(os.path.join(dataDir, filename))
except EnvironmentError, e: except EnvironmentError, e:
print 'I couldn\'t open that file: %s' % e print 'I couldn\'t open that file: %s' % e
continue continue
@ -97,7 +100,8 @@ class Lookup(callbacks.Privmsg):
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
self.lookupDomains = sets.Set() self.lookupDomains = sets.Set()
self.dbHandler = LookupDB(name=os.path.join(conf.dataDir, 'Lookup')) dataDir = conf.supybot.directories.data()
self.dbHandler = LookupDB(name=os.path.join(dataDir, 'Lookup'))
def _shrink(self, s): def _shrink(self, s):
return utils.ellipsisify(s, 50) return utils.ellipsisify(s, 50)
@ -132,8 +136,8 @@ class Lookup(callbacks.Privmsg):
Adds a lookup for <name> with the key/value pairs specified in the Adds a lookup for <name> with the key/value pairs specified in the
colon-delimited file specified by <filename>. <filename> is searched colon-delimited file specified by <filename>. <filename> is searched
for in conf.dataDir. If <name> is not singular, we try to make it for in conf.supybot.directories.data. If <name> is not singular, we
singular before creating the command. try to make it singular before creating the command.
""" """
(name, filename) = privmsgs.getArgs(args, required=2) (name, filename) = privmsgs.getArgs(args, required=2)
name = utils.depluralize(name) name = utils.depluralize(name)
@ -151,7 +155,8 @@ class Lookup(callbacks.Privmsg):
except sqlite.DatabaseError: except sqlite.DatabaseError:
# Good, there's no such database. # Good, there's no such database.
try: try:
filename = os.path.join(conf.dataDir, filename) dataDir = conf.supybot.directories.data()
filename = os.path.join(dataDir, filename)
fd = file(filename) fd = file(filename)
except EnvironmentError, e: except EnvironmentError, e:
irc.error('Could not open %s: %s' % (filename, e.args[1])) irc.error('Could not open %s: %s' % (filename, e.args[1]))

View File

@ -65,7 +65,7 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>' 'plugin. Download it at <http://pysqlite.sf.net/>'
dbfilename = os.path.join(conf.dataDir, 'MoobotFactoids') dbfilename = os.path.join(conf.supybot.directories.data(), 'MoobotFactoids')
def configure(onStart, afterConnect, advanced): def configure(onStart, afterConnect, advanced):
# This will be called by setup.py to configure this module. onStart and # This will be called by setup.py to configure this module. onStart and
@ -421,19 +421,19 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp):
# First, creation info. # First, creation info.
# Map the integer created_by to the username # Map the integer created_by to the username
creat_by = ircdb.users.getUser(created_by).name creat_by = ircdb.users.getUser(created_by).name
creat_at = time.strftime(conf.humanTimestampFormat, creat_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(created_at))) time.localtime(int(created_at)))
s += "Created by %s on %s." % (creat_by, creat_at) s += "Created by %s on %s." % (creat_by, creat_at)
# Next, modification info, if any. # Next, modification info, if any.
if modified_by is not None: if modified_by is not None:
mod_by = ircdb.users.getUser(modified_by).name mod_by = ircdb.users.getUser(modified_by).name
mod_at = time.strftime(conf.humanTimestampFormat, mod_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(modified_at))) time.localtime(int(modified_at)))
s += " Last modified by %s on %s." % (mod_by, mod_at) s += " Last modified by %s on %s." % (mod_by, mod_at)
# Next, last requested info, if any # Next, last requested info, if any
if last_requested_by is not None: if last_requested_by is not None:
last_by = last_requested_by # not an int user id last_by = last_requested_by # not an int user id
last_at = time.strftime(conf.humanTimestampFormat, last_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(last_requested_at))) time.localtime(int(last_requested_at)))
req_count = requested_count req_count = requested_count
times_str = utils.nItems('time', requested_count) times_str = utils.nItems('time', requested_count)
@ -441,7 +441,7 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp):
(last_by, last_at, times_str) (last_by, last_at, times_str)
# Last, locked info # Last, locked info
if locked_at is not None: if locked_at is not None:
lock_at = time.strftime(conf.humanTimestampFormat, lock_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(locked_at))) time.localtime(int(locked_at)))
lock_by = ircdb.users.getUser(locked_by).name lock_by = ircdb.users.getUser(locked_by).name
s += " Locked by %s on %s." % (lock_by, lock_at) s += " Locked by %s on %s." % (lock_by, lock_at)

View File

@ -139,14 +139,14 @@ class News(plugins.ChannelDBHandler, callbacks.Privmsg):
if int(expires_at) == 0: if int(expires_at) == 0:
s = '%s (Subject: "%s", added by %s on %s)' % \ s = '%s (Subject: "%s", added by %s on %s)' % \
(item, subject, added_by, (item, subject, added_by,
time.strftime(conf.humanTimestampFormat, time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at)))) time.localtime(int(added_at))))
else: else:
s = '%s (Subject: "%s", added by %s on %s, expires at %s)' % \ s = '%s (Subject: "%s", added by %s on %s, expires at %s)' % \
(item, subject, added_by, (item, subject, added_by,
time.strftime(conf.humanTimestampFormat, time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at))), time.localtime(int(added_at))),
time.strftime(conf.humanTimestampFormat, time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(expires_at)))) time.localtime(int(expires_at))))
irc.reply(s) irc.reply(s)

View File

@ -57,8 +57,6 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>' 'plugin. Download it at <http://pysqlite.sf.net/>'
dbfilename = os.path.join(conf.dataDir, 'Notes.db')
class NoteDb(plugins.DBHandler): class NoteDb(plugins.DBHandler):
def makeDb(self, filename): def makeDb(self, filename):
"create Notes database and tables" "create Notes database and tables"
@ -85,7 +83,8 @@ class NoteDb(plugins.DBHandler):
class Note(callbacks.Privmsg): class Note(callbacks.Privmsg):
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
self.dbHandler = NoteDb(name=os.path.join(conf.dataDir, 'Notes')) dataDir = conf.supybot.directories.data()
self.dbHandler = NoteDb(name=os.path.join(dataDir, 'Notes'))
def setAsRead(self, id): def setAsRead(self, id):
db = self.dbHandler.getDb() db = self.dbHandler.getDb()

View File

@ -256,7 +256,7 @@ class QuoteGrabs(plugins.ChannelDBHandler,
irc.error('No quotegrab for id %r' % id) irc.error('No quotegrab for id %r' % id)
return return
quote, hostmask, timestamp = cursor.fetchone() quote, hostmask, timestamp = cursor.fetchone()
time_str = time.strftime(conf.humanTimestampFormat, time_str = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(float(timestamp))) time.localtime(float(timestamp)))
irc.reply('%s (Said by: %s on %s)' % (quote, hostmask, time_str)) irc.reply('%s (Said by: %s on %s)' % (quote, hostmask, time_str))

View File

@ -220,7 +220,7 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
cursor.execute("""SELECT * FROM quotes WHERE id=%s""", id) cursor.execute("""SELECT * FROM quotes WHERE id=%s""", id)
if cursor.rowcount == 1: if cursor.rowcount == 1:
(id, added_by, added_at, quote) = cursor.fetchone() (id, added_by, added_at, quote) = cursor.fetchone()
timestamp = time.strftime(conf.humanTimestampFormat, timestamp = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at))) time.localtime(int(added_at)))
irc.reply('Quote %r added by %s at %s.' % irc.reply('Quote %r added by %s at %s.' %
(quote, added_by, timestamp)) (quote, added_by, timestamp))

View File

@ -30,7 +30,7 @@
### ###
""" """
Logs raw IRC messages to a file, conf.dataDir/raw.log Logs raw IRC messages to a file.
""" """
__revision__ = "$Id$" __revision__ = "$Id$"
@ -47,7 +47,8 @@ import irclib
### ###
class RawLogger(irclib.IrcCallback): class RawLogger(irclib.IrcCallback):
def __init__(self): def __init__(self):
self.fd = file(os.path.join(conf.logDir, 'raw.log'), 'a') logDir = conf.supybot.directories.log()
self.fd = file(os.path.join(logDir, 'raw.log'), 'a')
world.flushers.append(self.fd.flush) world.flushers.append(self.fd.flush)
def inFilter(self, irc, msg): def inFilter(self, irc, msg):

View File

@ -452,7 +452,7 @@ class Relay(callbacks.Privmsg, configurable.Mixin):
channels = utils.commaAndify(L) channels = utils.commaAndify(L)
if '317' in d: if '317' in d:
idle = utils.timeElapsed(d['317'].args[2]) idle = utils.timeElapsed(d['317'].args[2])
signon = time.strftime(conf.humanTimestampFormat, signon = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(float(d['317'].args[3]))) time.localtime(float(d['317'].args[3])))
else: else:
idle = '<unknown>' idle = '<unknown>'

View File

@ -170,7 +170,7 @@ class Sourceforge(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
resp = imap(lambda s: utils.ellipsisify(s, 50), resp) resp = imap(lambda s: utils.ellipsisify(s, 50), resp)
return '%s' % utils.commaAndify(resp) return '%s' % utils.commaAndify(resp)
raise callbacks.Error, 'No Trackers were found. (%s)' % \ raise callbacks.Error, 'No Trackers were found. (%s)' % \
conf.replyPossibleBug conf.supybot.replies.possibleBug()
except webutils.WebError, e: except webutils.WebError, e:
raise callbacks.Error, str(e) raise callbacks.Error, str(e)

View File

@ -61,7 +61,8 @@ def configure(onStart, afterConnect, advanced):
class UptimeDB(object): class UptimeDB(object):
def __init__(self, filename='uptimes'): def __init__(self, filename='uptimes'):
self.filename = os.path.join(conf.dataDir, filename) dataDir = conf.supybot.directories.data()
self.filename = os.path.join(dataDir, filename)
if os.path.exists(self.filename): if os.path.exists(self.filename):
fd = file(self.filename) fd = file(self.filename)
s = fd.read() s = fd.read()
@ -137,9 +138,9 @@ class Status(callbacks.Privmsg):
return return
def format((started, ended)): def format((started, ended)):
return '%s until %s; up for %s' % \ return '%s until %s; up for %s' % \
(time.strftime(conf.humanTimestampFormat, (time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(started)), time.localtime(started)),
time.strftime(conf.humanTimestampFormat, time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(ended)), time.localtime(ended)),
utils.timeElapsed(ended-started)) utils.timeElapsed(ended-started))
irc.reply(utils.commaAndify(imap(format, L))) irc.reply(utils.commaAndify(imap(format, L)))
@ -186,7 +187,7 @@ class Status(callbacks.Privmsg):
try: try:
r = os.popen('ps -o rss -p %s' % pid) r = os.popen('ps -o rss -p %s' % pid)
r.readline() # VSZ Header. r.readline() # VSZ Header.
mem = r.readline().strip() + ' kB' mem = r.readline().strip()
finally: finally:
r.close() r.close()
elif sys.platform.startswith('netbsd'): elif sys.platform.startswith('netbsd'):

View File

@ -87,7 +87,8 @@ class TodoDB(plugins.DBHandler):
class Todo(callbacks.Privmsg): class Todo(callbacks.Privmsg):
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
self.dbHandler = TodoDB(os.path.join(conf.dataDir, 'Todo')) dataDir = conf.supybot.directories.data()
self.dbHandler = TodoDB(os.path.join(dataDir, 'Todo'))
def die(self): def die(self):
self.dbHandler.die() self.dbHandler.die()
@ -170,7 +171,7 @@ class Todo(callbacks.Privmsg):
active = 'Inactive' active = 'Inactive'
if pri: if pri:
task += ', priority: %s' % pri task += ', priority: %s' % pri
added_at = time.strftime(conf.humanTimestampFormat, added_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at))) time.localtime(int(added_at)))
s = "%s todo for %s: %s (Added at %s)" % \ s = "%s todo for %s: %s (Added at %s)" % \
(active, name, task, added_at) (active, name, task, added_at)

View File

@ -260,7 +260,7 @@ class URL(callbacks.PrivmsgCommandAndRegexp,
return (tinyurl, updateDb) return (tinyurl, updateDb)
def _formatUrl(self, url, added, addedBy): def _formatUrl(self, url, added, addedBy):
when = time.strftime(conf.humanTimestampFormat, when = time.strftime(conf.supybot.supybot.humanTimestampFormat(),
time.localtime(int(added))) time.localtime(int(added)))
return '<%s> (added by %s at %s)' % (url, addedBy, when) return '<%s> (added by %s at %s)' % (url, addedBy, when)

View File

@ -160,9 +160,10 @@ class Words(callbacks.Privmsg, configurable.Mixin):
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
configurable.Mixin.__init__(self) configurable.Mixin.__init__(self)
self.dbHandler = WordsDB(os.path.join(conf.dataDir, 'Words')) dataDir = conf.supybot.directories.data()
self.dbHandler = WordsDB(os.path.join(dataDir, 'Words'))
try: try:
dictfile = os.path.join(conf.dataDir, 'dict') dictfile = os.path.join(dataDir, 'dict')
self.wordList = file(dictfile).readlines() self.wordList = file(dictfile).readlines()
self.gotDictFile = True self.gotDictFile = True
except IOError: except IOError:
@ -367,31 +368,7 @@ class Words(callbacks.Privmsg, configurable.Mixin):
Class = Words Class = Words
### TODO: Write a script to make the database.
if __name__ == '__main__':
import sys, log
if len(sys.argv) < 2:
fd = sys.stdin
else:
try:
fd = file(sys.argv[1])
except EnvironmentError, e:
sys.stderr.write(str(e) + '\n')
sys.exit(-1)
db = WordsDB(os.path.join(conf.dataDir, 'Words')).getDb()
cursor = db.cursor()
cursor.execute("""PRAGMA cache_size=20000""")
lineno = 0
for line in fd:
lineno += 1
line = line.rstrip()
try:
addWord(db, line)
except KeyboardInterrupt:
sys.exit(-1)
except Exception, e:
sys.stderr.write('Error on line %s: %s\n' % (lineno, e))
db.commit()
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -38,6 +38,7 @@ __revision__ = "$Id$"
import re import re
import os import os
import sys import sys
import atexit
if sys.version_info < (2, 3, 0): if sys.version_info < (2, 3, 0):
sys.stderr.write('This program requires Python >= 2.3.0\n') sys.stderr.write('This program requires Python >= 2.3.0\n')
@ -105,6 +106,10 @@ if __name__ == '__main__':
parser.add_option('-p', '--password', action='store', parser.add_option('-p', '--password', action='store',
dest='password', default='', dest='password', default='',
help='server password the bot should use') help='server password the bot should use')
parser.add_option('', '--enable-eval', action='store_true',
dest='allowEval',
help='Determines whether the bot will '
'allow the evaluation of arbitrary Python code.')
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
@ -128,12 +133,12 @@ if __name__ == '__main__':
log.info('Finished writing registry file.') log.info('Finished writing registry file.')
atexit.register(closeRegistry) atexit.register(closeRegistry)
nick = options.nick or conf.supybot.nick.get() nick = options.nick or conf.supybot.nick()
user = options.user or conf.supybot.user.get() user = options.user or conf.supybot.user()
ident = options.ident or conf.supybot.ident.get() ident = options.ident or conf.supybot.ident()
password = options.password or conf.supybot.password.get() password = options.password or conf.supybot.password()
server = options.server or conf.supybot.server.get() server = options.server or conf.supybot.server()
if ':' in server: if ':' in server:
serverAndPort = server.split(':', 1) serverAndPort = server.split(':', 1)
serverAndPort[1] = int(serverAndPort[1]) serverAndPort[1] = int(serverAndPort[1])
@ -150,12 +155,15 @@ if __name__ == '__main__':
except ImportError: except ImportError:
log.warning('Psyco isn\'t installed, cannot -OO.') log.warning('Psyco isn\'t installed, cannot -OO.')
if not os.path.exists(conf.supybot.directories.log.get()): if options.allowEval:
os.mkdir(conf.supybot.directories.log.get()) conf.allowEval = True
if not os.path.exists(conf.supybot.directories.conf.get()):
os.mkdir(conf.supybot.directories.conf.get()) if not os.path.exists(conf.supybot.directories.log()):
if not os.path.exists(conf.supybot.directories.data.get()): os.mkdir(conf.supybot.directories.log())
os.mkdir(conf.supybot.directories.data.get()) if not os.path.exists(conf.supybot.directories.conf()):
os.mkdir(conf.supybot.directories.conf())
if not os.path.exists(conf.supybot.directories.data()):
os.mkdir(conf.supybot.directories.data())
import irclib import irclib
import ircmsgs import ircmsgs

View File

@ -89,7 +89,8 @@ setup(
'supybot.others.unum': os.path.join('others', 'unum'), 'supybot.others.unum': os.path.join('others', 'unum'),
'supybot.others.unum.units': 'supybot.others.unum.units':
os.path.join(os.path.join('others', 'unum'), 'units')}, os.path.join(os.path.join('others', 'unum'), 'units')},
scripts=['scripts/supybot-wizard', scripts=['scripts/supybot',
'scripts/supybot-wizard',
'scripts/supybot-adduser', 'scripts/supybot-adduser',
'scripts/supybot-newplugin'] 'scripts/supybot-newplugin']
) )

View File

@ -61,6 +61,10 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
privmsgs.CapabilityCheckingPrivmsg.__init__(self) privmsgs.CapabilityCheckingPrivmsg.__init__(self)
self.joins = {} self.joins = {}
def do376(self, irc, msg):
irc.queueMsg(ircmsgs.joins(conf.supybot.channels()))
do422 = do377 = do376
def do471(self, irc, msg): def do471(self, irc, msg):
try: try:
channel = msg.args[1] channel = msg.args[1]
@ -107,7 +111,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
def doInvite(self, irc, msg): def doInvite(self, irc, msg):
if msg.args[1] not in irc.state.channels: if msg.args[1] not in irc.state.channels:
if conf.alwaysJoinOnInvite: if conf.supybot.alwaysJoinOnInvite():
irc.queueMsg(ircmsgs.join(msg.args[1])) irc.queueMsg(ircmsgs.join(msg.args[1]))
else: else:
if ircdb.checkCapability(msg.prefix, 'admin'): if ircdb.checkCapability(msg.prefix, 'admin'):
@ -182,16 +186,14 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
if command in ('enable', 'identify'): if command in ('enable', 'identify'):
irc.error('You can\'t disable %s!' % command) irc.error('You can\'t disable %s!' % command)
else: else:
# This has to know that defaultCapabilties gets turned into a
# dictionary.
try: try:
capability = ircdb.makeAntiCapability(command) capability = ircdb.makeAntiCapability(command)
except ValueError: except ValueError:
irc.error('%r is not a valid command.' % command) irc.error('%r is not a valid command.' % command)
return return
if command in conf.defaultCapabilities: if command in conf.supybot.defaultCapabilities():
conf.defaultCapabilities.remove(command) conf.supybot.defaultCapabilities().remove(command)
conf.defaultCapabilities.add(capability) conf.supybot.defaultCapabilities().add(capability)
irc.replySuccess() irc.replySuccess()
def enable(self, irc, msg, args): def enable(self, irc, msg, args):
@ -205,8 +207,8 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
except ValueError: except ValueError:
irc.error('%r is not a valid command.' % command) irc.error('%r is not a valid command.' % command)
return return
if anticapability in conf.defaultCapabilities: if anticapability in conf.supybot.defaultCapabilities():
conf.defaultCapabilities.remove(anticapability) conf.supybot.defaultCapabilities().remove(anticapability)
irc.replySuccess() irc.replySuccess()
else: else:
irc.error('That command wasn\'t disabled.') irc.error('That command wasn\'t disabled.')
@ -228,14 +230,14 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
# Thus, the owner capability can't be given in the bot. Admin users # Thus, the owner capability can't be given in the bot. Admin users
# can only give out capabilities they have themselves (which will # can only give out capabilities they have themselves (which will
# depend on both conf.defaultAllow and conf.defaultCapabilities), but # depend on both conf.supybot.defaultAllow and
# generally means they can't mess with channel capabilities. # conf.supybot.defaultCapabilities), but generally means they can't
# mess with channel capabilities.
(name, capability) = privmsgs.getArgs(args, required=2) (name, capability) = privmsgs.getArgs(args, required=2)
if capability == 'owner': if capability == 'owner':
irc.error('The "owner" capability can\'t be added in the bot.' irc.error('The "owner" capability can\'t be added in the bot. '
' Use the supybot-adduser program (or edit the ' 'Use the supybot-adduser program (or edit the '
'users.conf file yourself) to add an owner ' 'users.conf file yourself) to add an owner capability.')
'capability.')
return return
if ircdb.checkCapability(msg.prefix, capability) or \ if ircdb.checkCapability(msg.prefix, capability) or \
'-' in capability: '-' in capability:
@ -292,7 +294,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
except KeyError: except KeyError:
irc.error('I can\'t find a hostmask for %s' % arg) irc.error('I can\'t find a hostmask for %s' % arg)
return return
conf.ignores.append(hostmask) conf.supybot.ignores().append(hostmask)
irc.replySuccess() irc.replySuccess()
def unignore(self, irc, msg, args): def unignore(self, irc, msg, args):
@ -311,38 +313,23 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
irc.error('I can\'t find a hostmask for %s' % arg) irc.error('I can\'t find a hostmask for %s' % arg)
return return
try: try:
conf.ignores.remove(hostmask) conf.supybot.ignores().remove(hostmask)
while hostmask in conf.ignores: while hostmask in conf.supybot.ignores():
conf.ignores.remove(hostmask) conf.supybot.ignores().remove(hostmask)
irc.replySuccess() irc.replySuccess()
except ValueError: except ValueError:
irc.error('%s wasn\'t in conf.ignores.' % hostmask) irc.error('%s wasn\'t in conf.supybot.ignores.' % hostmask)
def ignores(self, irc, msg, args): def ignores(self, irc, msg, args):
"""takes no arguments """takes no arguments
Returns the hostmasks currently being globally ignored. Returns the hostmasks currently being globally ignored.
""" """
if conf.ignores: if conf.supybot.ignores():
irc.reply(utils.commaAndify(imap(repr, conf.ignores))) irc.reply(utils.commaAndify(imap(repr, conf.supybot.ignores())))
else: else:
irc.reply('I\'m not currently globally ignoring anyone.') irc.reply('I\'m not currently globally ignoring anyone.')
def setprefixchar(self, irc, msg, args):
"""<prefixchars>
Sets the prefix chars by which the bot can be addressed.
"""
s = privmsgs.getArgs(args)
for c in s:
if c not in conf.validPrefixChars:
s = 'PrefixChars must be something in %r'%conf.validPrefixChars
irc.error(s)
return
else:
conf.prefixChars = s
irc.replySuccess()
def reportbug(self, irc, msg, args): def reportbug(self, irc, msg, args):
"""<description> """<description>

View File

@ -37,6 +37,8 @@ import plugins
import conf import conf
import utils import utils
import ircdb
import ircutils
import registry import registry
import privmsgs import privmsgs
import callbacks import callbacks
@ -50,7 +52,7 @@ class InvalidRegistryName(callbacks.Error):
pass pass
def getWrapper(name): def getWrapper(name):
parts = name.split() parts = name.split('.')
if not parts or parts[0] != 'supybot': if not parts or parts[0] != 'supybot':
raise InvalidRegistryName, name raise InvalidRegistryName, name
group = conf.supybot group = conf.supybot
@ -62,13 +64,25 @@ def getWrapper(name):
raise InvalidRegistryName, name raise InvalidRegistryName, name
return group return group
def getCapability(name):
capability = 'owner' # Default to requiring the owner capability.
parts = name.split('.')
while parts:
part = parts.pop()
if ircutils.isChannel(part):
# If a registry value has a channel in it, it requires a channel.op
# capability, or so we assume. We'll see if we're proven wrong.
capability = ircdb.makeChannelCapability(part, 'op')
### Do more later, for specific capabilities/sections.
return capability
class Config(callbacks.Privmsg): class Config(callbacks.Privmsg):
def callCommand(self, method, irc, msg, *L): def callCommand(self, method, irc, msg, *L):
try: try:
callbacks.Privmsg.callCommand(method, irc, msg, *L) callbacks.Privmsg.callCommand(self, method, irc, msg, *L)
except InvalidRegistryName, e: except InvalidRegistryName, e:
irc.error('%r is not a valid configuration variable.' % e) irc.error('%r is not a valid configuration variable.' % e.args[0])
except registry.InvalidRegistryValue, e: except registry.InvalidRegistryValue, e:
irc.error(str(e)) irc.error(str(e))
@ -81,8 +95,11 @@ class Config(callbacks.Privmsg):
name = privmsgs.getArgs(args) name = privmsgs.getArgs(args)
group = getWrapper(name) group = getWrapper(name)
if hasattr(group, 'getValues'): if hasattr(group, 'getValues'):
try:
L = zip(*group.getValues())[0] L = zip(*group.getValues())[0]
irc.reply(utils.commaAndify(L)) irc.reply(utils.commaAndify(L))
except TypeError:
irc.error('There don\'t seem to be any values in %r' % name)
else: else:
irc.error('%r is not a valid configuration group.' % name) irc.error('%r is not a valid configuration group.' % name)
@ -101,9 +118,13 @@ class Config(callbacks.Privmsg):
Sets the current value of the configuration variable <name> to <value>. Sets the current value of the configuration variable <name> to <value>.
""" """
(name, value) = privmsgs.getArgs(args, required=2) (name, value) = privmsgs.getArgs(args, required=2)
capability = getCapability(name)
if ircdb.checkCapability(msg.prefix, capability):
wrapper = getWrapper(name) wrapper = getWrapper(name)
wrapper.set(value) wrapper.set(value)
irc.replySuccess() irc.replySuccess()
else:
irc.errorNoCapability(capability)
def help(self, irc, msg, args): def help(self, irc, msg, args):
"""<name> """<name>
@ -112,7 +133,7 @@ class Config(callbacks.Privmsg):
""" """
name = privmsgs.getArgs(args) name = privmsgs.getArgs(args)
wrapper = getWrapper(name) wrapper = getWrapper(name)
irc.reply(msg, wrapper.help) irc.reply(wrapper.help)
def reset(self, irc, msg, args): def reset(self, irc, msg, args):
"""<name> """<name>
@ -120,9 +141,13 @@ class Config(callbacks.Privmsg):
Resets the configuration variable <name> to its original value. Resets the configuration variable <name> to its original value.
""" """
name = privmsgs.getArgs(args) name = privmsgs.getArgs(args)
capability = getCapability(name)
if ircdb.checkCapability(msg.prefix, capability):
wrapper = getWrapper(name) wrapper = getWrapper(name)
wrapper.reset() wrapper.reset()
irc.replySuccess() irc.replySuccess()
else:
irc.errorNoCapability(capability)
def default(self, irc, msg, args): def default(self, irc, msg, args):
"""<name> """<name>

View File

@ -54,7 +54,7 @@ class Misc(callbacks.Privmsg):
priority = sys.maxint priority = sys.maxint
def invalidCommand(self, irc, msg, tokens): def invalidCommand(self, irc, msg, tokens):
self.log.debug('Misc.invalidCommand called (tokens %s)', tokens) self.log.debug('Misc.invalidCommand called (tokens %s)', tokens)
if conf.replyWhenNotCommand: if conf.supybot.reply.whenNotCommand():
command = tokens and tokens[0] or '' command = tokens and tokens[0] or ''
irc.error('%r is not a valid command.' % command) irc.error('%r is not a valid command.' % command)
else: else:
@ -146,7 +146,7 @@ class Misc(callbacks.Privmsg):
cb = irc.getCallback(args[0]) cb = irc.getCallback(args[0])
if cb is not None: if cb is not None:
command = callbacks.canonicalName(privmsgs.getArgs(args[1:])) command = callbacks.canonicalName(privmsgs.getArgs(args[1:]))
command = command.lstrip(conf.prefixChars) command = command.lstrip(conf.supybot.prefixChars())
name = ' '.join(args) name = ' '.join(args)
if hasattr(cb, 'isCommand') and cb.isCommand(command): if hasattr(cb, 'isCommand') and cb.isCommand(command):
method = getattr(cb, command) method = getattr(cb, command)
@ -158,7 +158,7 @@ class Misc(callbacks.Privmsg):
return return
command = callbacks.canonicalName(privmsgs.getArgs(args)) command = callbacks.canonicalName(privmsgs.getArgs(args))
# Users might expect "@help @list" to work. # Users might expect "@help @list" to work.
command = command.lstrip(conf.prefixChars) command = command.lstrip(conf.supybot.prefixChars())
cbs = callbacks.findCallbackForCommand(irc, command) cbs = callbacks.findCallbackForCommand(irc, command)
if len(cbs) > 1: if len(cbs) > 1:
tokens = [command] tokens = [command]
@ -235,7 +235,7 @@ class Misc(callbacks.Privmsg):
except: except:
self.log.exception('Couldn\'t get id string: %r', s) self.log.exception('Couldn\'t get id string: %r', s)
names = {} names = {}
dirs = map(os.path.abspath, conf.pluginDirs) dirs = map(os.path.abspath, conf.supybot.directories.plugins())
for (name, module) in sys.modules.items(): # Don't use iteritems. for (name, module) in sys.modules.items(): # Don't use iteritems.
if hasattr(module, '__revision__'): if hasattr(module, '__revision__'):
if 'supybot' in module.__file__: if 'supybot' in module.__file__:
@ -268,7 +268,8 @@ class Misc(callbacks.Privmsg):
return return
filenameArg = os.path.basename(filenameArg) filenameArg = os.path.basename(filenameArg)
ret = [] ret = []
for (dirname, _, filenames) in os.walk(conf.logDir): dirname = conf.supybot.directories.log()
for (dirname,_,filenames) in os.walk(dirname):
if filenameArg: if filenameArg:
if filenameArg in filenames: if filenameArg in filenames:
filename = os.path.join(dirname, filenameArg) filename = os.path.join(dirname, filenameArg)
@ -284,13 +285,6 @@ class Misc(callbacks.Privmsg):
else: else:
irc.error('I couldn\'t find any logfiles.') irc.error('I couldn\'t find any logfiles.')
def getprefixchar(self, irc, msg, args):
"""takes no arguments
Returns the prefix character(s) the bot is currently using.
"""
irc.reply(repr(conf.prefixChars))
def plugin(self, irc, msg, args): def plugin(self, irc, msg, args):
"""<command> """<command>

View File

@ -55,6 +55,7 @@ import irclib
import ircmsgs import ircmsgs
import drivers import drivers
import privmsgs import privmsgs
import registry
import callbacks import callbacks
class Deprecated(ImportError): class Deprecated(ImportError):
@ -63,7 +64,8 @@ class Deprecated(ImportError):
def loadPluginModule(name, ignoreDeprecation=False): def loadPluginModule(name, ignoreDeprecation=False):
"""Loads (and returns) the module for the plugin with the given name.""" """Loads (and returns) the module for the plugin with the given name."""
files = [] files = []
for dir in conf.pluginDirs: pluginDirs = conf.supybot.directories.plugins()
for dir in pluginDirs:
try: try:
files.extend(os.listdir(dir)) files.extend(os.listdir(dir))
except EnvironmentError: # OSError, IOError superclass. except EnvironmentError: # OSError, IOError superclass.
@ -74,7 +76,7 @@ def loadPluginModule(name, ignoreDeprecation=False):
name = os.path.splitext(files[index])[0] name = os.path.splitext(files[index])[0]
except ValueError: # We'd rather raise the ImportError, so we'll let go... except ValueError: # We'd rather raise the ImportError, so we'll let go...
pass pass
moduleInfo = imp.find_module(name, conf.pluginDirs) moduleInfo = imp.find_module(name, pluginDirs)
module = imp.load_module(name, *moduleInfo) module = imp.load_module(name, *moduleInfo)
if 'deprecated' in module.__dict__ and module.deprecated: if 'deprecated' in module.__dict__ and module.deprecated:
if ignoreDeprecation: if ignoreDeprecation:
@ -91,8 +93,14 @@ def loadPluginClass(irc, module):
callback = module.Class() callback = module.Class()
assert not irc.getCallback(callback.name()) assert not irc.getCallback(callback.name())
irc.addCallback(callback) irc.addCallback(callback)
if hasattr(callback, 'configure'):
callback.configure(irc) def registerPlugin(name, currentValue=None):
conf.supybot.plugins.registerGroup(name,
registry.GroupWithValue(registry.Boolean(False, """Determines
whether this plugin is loaded by default.""")))
if currentValue is not None:
conf.supybot.plugins.getChild(name).setValue(currentValue)
class Owner(privmsgs.CapabilityCheckingPrivmsg): class Owner(privmsgs.CapabilityCheckingPrivmsg):
# This plugin must be first; its priority must be lowest; otherwise odd # This plugin must be first; its priority must be lowest; otherwise odd
@ -107,6 +115,27 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
'capabilities': 'User', 'capabilities': 'User',
'addcapability': 'Admin', 'addcapability': 'Admin',
'removecapability': 'Admin'} 'removecapability': 'Admin'}
for (name, s) in registry.cache.iteritems():
if name.startswith('supybot.plugins'):
try:
(_, _, name) = name.split('.')
except ValueError: #unpack list of wrong size.
continue
registerPlugin(name)
def do001(self, irc, msg):
self.log.info('Loading other src/ plugins.')
for s in ('Admin', 'Channel', 'Config', 'Misc', 'User'):
self.log.info('Loading %s.' % s)
m = loadPluginModule(s)
loadPluginClass(irc, m)
for (name, value) in conf.supybot.plugins.getValues():
if value():
s = rsplit(name, '.', 1)[-1]
if not irc.getCallback(s):
self.log.info('Loading %s.' % s)
m = loadPluginModule(s)
loadPluginClass(irc, m)
def disambiguate(self, irc, tokens, ambiguousCommands=None): def disambiguate(self, irc, tokens, ambiguousCommands=None):
"""Disambiguates the given tokens based on the plugins loaded and """Disambiguates the given tokens based on the plugins loaded and
@ -227,7 +256,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
except Exception, e: except Exception, e:
irc.reply(utils.exnToString(e)) irc.reply(utils.exnToString(e))
else: else:
irc.error(conf.replyEvalNotAllowed) irc.error('You must enable conf.allowEval for that to work.')
def _exec(self, irc, msg, args): def _exec(self, irc, msg, args):
"""<statement> """<statement>
@ -242,77 +271,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
except Exception, e: except Exception, e:
irc.reply(utils.exnToString(e)) irc.reply(utils.exnToString(e))
else: else:
irc.error(conf.replyEvalNotAllowed) irc.error('You must enable conf.allowEval for that to work.')
def setconf(self, irc, msg, args):
"""[<name> [<value>]]
Lists adjustable variables in the conf-module by default, shows the
variable type with only the <name> argument and sets the value of the
variable to <value> when both arguments are given.
"""
(name, value) = privmsgs.getArgs(args, required=0, optional=2)
if name and value:
if conf.allowEval:
try:
value = eval(value)
except Exception, e:
irc.error(utils.exnToString(e))
return
setattr(conf, name, value)
irc.replySuccess()
else:
if name == 'allowEval':
irc.error('You can\'t set the value of allowEval.')
return
elif name not in conf.types:
irc.error('I can\'t set that conf variable.')
return
else:
converter = conf.types[name]
try:
value = converter(value)
except ValueError, e:
irc.error(str(e))
return
setattr(conf, name, value)
irc.replySuccess()
elif name:
typeNames = {conf.mystr: 'string',
conf.mybool: 'boolean',
float: 'float'}
try:
type = typeNames[conf.types[name]]
except KeyError:
irc.error('That configuration variable doesn\'t exist.')
return
try:
value = getattr(conf, name)
irc.reply('%s is a %s (%s).' % (name, type, value))
except KeyError:
irc.error('%s is of an unknown type.' % name)
else:
options = conf.types.keys()
options.sort()
irc.reply(', '.join(options))
def setdefaultcapability(self, irc, msg, args):
"""<capability>
Sets the default capability to be allowed for any command.
"""
capability = callbacks.canonicalName(privmsgs.getArgs(args))
conf.defaultCapabilities.add(capability)
irc.replySuccess()
def unsetdefaultcapability(self, irc, msg, args):
"""<capability>
Unsets the default capability for any command.
"""
capability = callbacks.canonicalName(privmsgs.getArgs(args))
conf.defaultCapabilities.remove(capability)
irc.replySuccess()
def ircquote(self, irc, msg, args): def ircquote(self, irc, msg, args):
"""<string to be sent to the server> """<string to be sent to the server>
@ -357,9 +316,9 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
"""[--deprecated] <plugin> """[--deprecated] <plugin>
Loads the plugin <plugin> from any of the directories in Loads the plugin <plugin> from any of the directories in
conf.pluginDirs; usually this includes the main installed directory conf.supybot.directories.plugins; usually this includes the main
and 'plugins' in the current directory. --deprecated is necessary installed directory and 'plugins' in the current directory.
if you wish to load deprecated plugins. --deprecated is necessary if you wish to load deprecated plugins.
""" """
(optlist, args) = getopt.getopt(args, '', ['deprecated']) (optlist, args) = getopt.getopt(args, '', ['deprecated'])
ignoreDeprecation = False ignoreDeprecation = False
@ -385,6 +344,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
irc.error(utils.exnToString(e)) irc.error(utils.exnToString(e))
return return
loadPluginClass(irc, module) loadPluginClass(irc, module)
registerPlugin(name, True)
irc.replySuccess() irc.replySuccess()
def reload(self, irc, msg, args): def reload(self, irc, msg, args):
@ -429,6 +389,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
callback.die() callback.die()
del callback del callback
gc.collect() gc.collect()
registerPlugin(name, False)
irc.replySuccess() irc.replySuccess()
else: else:
irc.error('There was no callback %s' % name) irc.error('There was no callback %s' % name)
@ -436,8 +397,8 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
def reconf(self, irc, msg, args): def reconf(self, irc, msg, args):
"""takes no arguments """takes no arguments
Reloads the configuration files in conf.dataDir: conf/users.conf and Reloads the configuration files for the user and channel databases:
conf/channels.conf, by default. conf/users.conf and conf/channels.conf, by default.
""" """
ircdb.users.reload() ircdb.users.reload()
ircdb.channels.reload() ircdb.channels.reload()

View File

@ -53,7 +53,7 @@ import callbacks
class User(callbacks.Privmsg): class User(callbacks.Privmsg):
def _checkNotChannel(self, irc, msg, password=' '): def _checkNotChannel(self, irc, msg, password=' '):
if password and ircutils.isChannel(msg.args[0]): if password and ircutils.isChannel(msg.args[0]):
raise callbacks.Error, conf.replyRequiresPrivacy raise callbacks.Error, conf.supybot.replies.requiresPrivacy()
def list(self, irc, msg, args): def list(self, irc, msg, args):
"""[<glob>] """[<glob>]
@ -136,7 +136,7 @@ class User(callbacks.Privmsg):
ircdb.users.delUser(id) ircdb.users.delUser(id)
irc.replySuccess() irc.replySuccess()
else: else:
irc.error(conf.replyIncorrectAuth) irc.error(conf.supybot.replies.incorrectAuthentication())
def changename(self, irc, msg, args): def changename(self, irc, msg, args):
"""<name> <new name> [<password>] """<name> <new name> [<password>]
@ -201,7 +201,7 @@ class User(callbacks.Privmsg):
ircdb.users.setUser(id, user) ircdb.users.setUser(id, user)
irc.replySuccess() irc.replySuccess()
else: else:
irc.error(conf.replyIncorrectAuth) irc.error(conf.supybot.replies.incorrectAuthentication())
return return
def removehostmask(self, irc, msg, args): def removehostmask(self, irc, msg, args):
@ -229,7 +229,7 @@ class User(callbacks.Privmsg):
ircdb.users.setUser(id, user) ircdb.users.setUser(id, user)
irc.replySuccess() irc.replySuccess()
else: else:
irc.error(conf.replyIncorrectAuth) irc.error(conf.supybot.replies.incorrectAuthentication())
return return
def setpassword(self, irc, msg, args): def setpassword(self, irc, msg, args):
@ -258,7 +258,7 @@ class User(callbacks.Privmsg):
ircdb.users.setUser(id, user) ircdb.users.setUser(id, user)
irc.replySuccess() irc.replySuccess()
else: else:
irc.error(conf.replyIncorrectAuth) irc.error(conf.supybot.replies.incorrectAuthentication())
def username(self, irc, msg, args): def username(self, irc, msg, args):
"""<hostmask|nick> """<hostmask|nick>
@ -345,7 +345,7 @@ class User(callbacks.Privmsg):
irc.error('Your secure flag is true and your hostmask ' irc.error('Your secure flag is true and your hostmask '
'doesn\'t match any of your known hostmasks.') 'doesn\'t match any of your known hostmasks.')
else: else:
irc.error(conf.replyIncorrectAuth) irc.error(conf.supybot.replies.incorrectAuthentication())
def unidentify(self, irc, msg, args): def unidentify(self, irc, msg, args):
"""takes no arguments """takes no arguments
@ -403,7 +403,7 @@ class User(callbacks.Privmsg):
ircdb.users.setUser(id, user) ircdb.users.setUser(id, user)
irc.reply('Secure flag set to %s' % value) irc.reply('Secure flag set to %s' % value)
else: else:
irc.error(conf.replyIncorrectAuth) irc.error(conf.supybot.replies.incorrectAuthentication())
Class = User Class = User

View File

@ -42,7 +42,6 @@ import asynchat
import log import log
import conf import conf
import repl
import ircdb import ircdb
import world import world
import drivers import drivers
@ -53,7 +52,7 @@ class AsyncoreRunnerDriver(drivers.IrcDriver):
def run(self): def run(self):
log.debug(repr(asyncore.socket_map)) log.debug(repr(asyncore.socket_map))
try: try:
asyncore.poll(conf.poll) asyncore.poll(conf.supybot.drivers.poll())
except: except:
log.exception('Uncaught exception:') log.exception('Uncaught exception:')
@ -75,7 +74,8 @@ class AsyncoreDriver(asynchat.async_chat, object):
def scheduleReconnect(self): def scheduleReconnect(self):
when = time.time() + 60 when = time.time() + 60
whenS = time.strftime(conf.logTimestampFormat, time.localtime(when)) whenS = time.strftime(conf.supybot.log.timestampFormat(),
time.localtime(when))
if not world.dying: if not world.dying:
log.info('Scheduling reconnect to %s at %s', self.server, whenS) log.info('Scheduling reconnect to %s at %s', self.server, whenS)
def makeNewDriver(): def makeNewDriver():
@ -121,116 +121,12 @@ class AsyncoreDriver(asynchat.async_chat, object):
log.info('Driver for %s dying.', self.irc) log.info('Driver for %s dying.', self.irc)
self.close() self.close()
class ReplListener(asyncore.dispatcher, object):
def __init__(self, port=conf.telnetPort):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(('', port))
self.listen(5)
def handle_accept(self):
(sock, addr) = self.accept()
log.info('Connection made to telnet-REPL: %s', addr)
Repl((sock, addr))
class Repl(asynchat.async_chat, object):
filename = 'repl'
def __init__(self, (sock, addr)):
asynchat.async_chat.__init__(self, sock)
self.buffer = ''
self.prompt = """SupyBot version %s.
Python %s
Type disconnect() to disconnect.
Name: """ % (world.version, sys.version.translate(string.ascii, '\r\n'))
self.u = None
self.authed = False
self.set_terminator('\r\n')
self.repl = repl.Repl(addr[0])
self.repl.namespace['disconnect'] = self.close
self.push(self.prompt)
self.tries = 0
_re = re.compile(r'(?<!\r)\n')
def push(self, data):
asynchat.async_chat.push(self, self._re.sub('\r\n', data))
def collect_incoming_data(self, data):
if self.tries >= 3:
self.close()
self.buffer += data
if len(self.buffer) > 1024:
self.close()
def handle_close(self):
self.close()
def handle_error(self):
self.close()
def found_terminator(self):
if self.u is None:
try:
name = self.buffer
self.buffer = ''
id = ircdb.users.getUserId(name)
self.u = ircdb.users.getUser(id)
self.prompt = 'Password: '
except KeyError:
self.push('Unknown user.\n')
self.tries += 1
self.prompt = 'Name: '
log.warning('Unknown user %s on telnet REPL.', name)
self.push(self.prompt)
elif self.u is not None and not self.authed:
password = self.buffer
self.buffer = ''
if self.u.checkPassword(password):
if self.u.checkCapability('owner'):
self.authed = True
self.prompt = '>>> '
else:
self.push('Only owners can use this feature.\n')
self.close()
msg = 'Attempted non-owner user %s on telnet REPL' % name
log.warning(msg)
else:
self.push('Incorrect Password.\n')
self.prompt = 'Name: '
self.u = None
msg = 'Invalid password for user %s on telnet REPL.' % name
log.warning(msg)
self.push(self.prompt)
elif self.authed:
log.info('Telnet REPL: %s', self.buffer)
ret = self.repl.addLine(self.buffer+'\r\n')
self.buffer = ''
if ret is not repl.NotYet:
if ret is not None:
s = self._re.sub('\r\n', str(ret))
self.push(s)
self.push('\r\n')
self.prompt = '>>> '
else:
self.prompt = '... '
self.push(self.prompt)
try: try:
ignore(poller) ignore(poller)
except NameError: except NameError:
poller = AsyncoreRunnerDriver() poller = AsyncoreRunnerDriver()
if conf.telnetEnable and __name__ != '__main__':
try:
ignore(_listener)
except NameError:
_listener = ReplListener()
Driver = AsyncoreDriver Driver = AsyncoreDriver
if __name__ == '__main__':
ReplListener()
asyncore.loop()
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -70,11 +70,11 @@ def addressed(nick, msg):
""" """
nick = ircutils.toLower(nick) nick = ircutils.toLower(nick)
if ircutils.nickEqual(msg.args[0], nick): if ircutils.nickEqual(msg.args[0], nick):
if msg.args[1][0] in conf.prefixChars: if msg.args[1][0] in conf.supybot.prefixChars():
return msg.args[1][1:].strip() return msg.args[1][1:].strip()
else: else:
return msg.args[1].strip() return msg.args[1].strip()
elif conf.replyWhenAddressedByNick and \ elif conf.supybot.reply.whenAddressedByNick() and \
ircutils.toLower(msg.args[1]).startswith(nick): ircutils.toLower(msg.args[1]).startswith(nick):
try: try:
(maybeNick, rest) = msg.args[1].split(None, 1) (maybeNick, rest) = msg.args[1].split(None, 1)
@ -86,9 +86,9 @@ def addressed(nick, msg):
return '' return ''
except ValueError: # split didn't work. except ValueError: # split didn't work.
return '' return ''
elif msg.args[1] and msg.args[1][0] in conf.prefixChars: elif msg.args[1] and msg.args[1][0] in conf.supybot.prefixChars():
return msg.args[1][1:].strip() return msg.args[1][1:].strip()
elif conf.replyWhenNotAddressed: elif conf.supybot.reply.whenNotAddressed():
return msg.args[1] return msg.args[1]
else: else:
return '' return ''
@ -112,7 +112,7 @@ def reply(msg, s, prefixName=True, private=False, notice=False, to=None):
s = ircutils.safeArgument(s) s = ircutils.safeArgument(s)
to = to or msg.nick to = to or msg.nick
if ircutils.isChannel(msg.args[0]) and not private: if ircutils.isChannel(msg.args[0]) and not private:
if notice or conf.replyWithPrivateNotice: if notice or conf.supybot.reply.withPrivateNotice():
m = ircmsgs.notice(to, s) m = ircmsgs.notice(to, s)
elif prefixName: elif prefixName:
m = ircmsgs.privmsg(msg.args[0], '%s: %s' % (to, s)) m = ircmsgs.privmsg(msg.args[0], '%s: %s' % (to, s))
@ -227,7 +227,7 @@ def tokenize(s):
try: try:
if s != _lastTokenized: if s != _lastTokenized:
_lastTokenized = s _lastTokenized = s
if conf.enablePipeSyntax: if conf.supybot.pipeSyntax():
tokens = '|' tokens = '|'
else: else:
tokens = '' tokens = ''
@ -263,7 +263,7 @@ def formatArgumentError(method, name=None):
if name is None: if name is None:
name = method.__name__ name = method.__name__
if hasattr(method, '__doc__') and method.__doc__: if hasattr(method, '__doc__') and method.__doc__:
if conf.showOnlySyntax: if conf.supybot.showSimpleSyntax():
return getSyntax(method, name=name) return getSyntax(method, name=name)
else: else:
return getHelp(method, name=name) return getHelp(method, name=name)
@ -281,7 +281,7 @@ def checkCommandCapability(msg, cb, command):
if ircdb.checkCapability(msg.prefix, antichancap): if ircdb.checkCapability(msg.prefix, antichancap):
log.info('Preventing because of antichancap: %s', msg.prefix) log.info('Preventing because of antichancap: %s', msg.prefix)
return False return False
return conf.defaultAllow or \ return conf.supybot.defaultAllow() or \
ircdb.checkCapability(msg.prefix, command) or \ ircdb.checkCapability(msg.prefix, command) or \
ircdb.checkCapability(msg.prefix, chancap) ircdb.checkCapability(msg.prefix, chancap)
@ -296,30 +296,38 @@ class RichReplyMethods(object):
return s return s
def replySuccess(self, s='', **kwargs): def replySuccess(self, s='', **kwargs):
self.reply(self.__makeReply(conf.replySuccess, s), **kwargs) self.reply(self.__makeReply(conf.supybot.replies.success(), s),
**kwargs)
def replyError(self, s='', **kwargs): def replyError(self, s='', **kwargs):
self.reply(self.__makeReply(conf.replyError, s), **kwargs) self.reply(self.__makeReply(conf.supybot.replies.error(), s),
**kwargs)
def errorNoCapability(self, capability, s='', **kwargs): def errorNoCapability(self, capability, s='', **kwargs):
log.warning('Denying %s for lacking %r capability', log.warning('Denying %s for lacking %r capability',
self.msg.prefix, capability) self.msg.prefix, capability)
s = self.__makeReply(conf.replyNoCapability % capability, s) noCapability = conf.supybot.replies.noCapability()
s = self.__makeReply(noCapability % capability, s)
self.error(s, **kwargs) self.error(s, **kwargs)
def errorPossibleBug(self, s='', **kwargs): def errorPossibleBug(self, s='', **kwargs):
if s: if s:
s += ' (%s)' % conf.replyPossibleBug s += ' (%s)' % conf.supybot.replies.possibleBug()
else:
s = conf.supybot.replies.possibleBug()
self.error(s, **kwargs) self.error(s, **kwargs)
def errorNotRegistered(self, s='', **kwargs): def errorNotRegistered(self, s='', **kwargs):
self.error(self.__makeReply(conf.replyNotRegistered, s), **kwargs) notRegistered = conf.supybot.replies.notRegistered()
self.error(self.__makeReply(notRegistered, s), **kwargs)
def errorNoUser(self, s='', **kwargs): def errorNoUser(self, s='', **kwargs):
self.error(self.__makeReply(conf.replyNoUser, s), **kwargs) noUser = conf.supybot.replies.noUser()
self.error(self.__makeReply(noUser, s), **kwargs)
def errorRequiresPrivacy(self, s='', **kwargs): def errorRequiresPrivacy(self, s='', **kwargs):
self.error(self.__makeReply(conf.replyRequiresPrivacy, s), **kwargs) requiresPrivacy = conf.supybot.replies.requiresPrivacy()
self.error(self.__makeReply(requiresPrivacy, s), **kwargs)
class IrcObjectProxy(RichReplyMethods): class IrcObjectProxy(RichReplyMethods):
@ -337,7 +345,7 @@ class IrcObjectProxy(RichReplyMethods):
self.notice = False self.notice = False
self.private = False self.private = False
self.finished = False self.finished = False
self.prefixName = conf.replyWithNickPrefix self.prefixName = conf.supybot.reply.withNickPrefix()
self.noLengthCheck = False self.noLengthCheck = False
if not args: if not args:
self.finalEvaled = True self.finalEvaled = True
@ -462,18 +470,18 @@ class IrcObjectProxy(RichReplyMethods):
to=<nick|channel>: The nick or channel the reply should go to. to=<nick|channel>: The nick or channel the reply should go to.
Defaults to msg.args[0] (or msg.nick if private) Defaults to msg.args[0] (or msg.nick if private)
""" """
# These use |= or &= based on whether or not they default to True or # These use and or or based on whether or not they default to True or
# False. Those that default to True use &=; those that default to # False. Those that default to True use and; those that default to
# False use |=. # False use or.
assert not isinstance(s, ircmsgs.IrcMsg), \ assert not isinstance(s, ircmsgs.IrcMsg), \
'Old code alert: there is no longer a "msg" argument to reply.' 'Old code alert: there is no longer a "msg" argument to reply.'
msg = self.msg msg = self.msg
self.action |= action self.action = action or self.action
self.notice |= notice self.notice = notice or self.notice
self.private |= private self.private = private or self.private
self.to = to or self.to self.to = to or self.to
self.prefixName &= prefixName self.prefixName = prefixName or self.prefixName
self.noLengthCheck |= noLengthCheck self.noLengthCheck = noLengthCheck or self.noLengthCheck
if self.finalEvaled: if self.finalEvaled:
if isinstance(self.irc, self.__class__): if isinstance(self.irc, self.__class__):
self.irc.reply(s, self.noLengthCheck, self.prefixName, self.irc.reply(s, self.noLengthCheck, self.prefixName,
@ -532,7 +540,7 @@ class IrcObjectProxy(RichReplyMethods):
self.irc.error(s, private) self.irc.error(s, private)
else: else:
s = 'Error: ' + s s = 'Error: ' + s
if private or conf.errorReplyPrivate: if private or conf.supybot.reply.errorInPrivate():
self.irc.queueMsg(ircmsgs.privmsg(self.msg.nick, s)) self.irc.queueMsg(ircmsgs.privmsg(self.msg.nick, s))
else: else:
self.irc.queueMsg(reply(self.msg, s)) self.irc.queueMsg(reply(self.msg, s))
@ -655,22 +663,6 @@ class Privmsg(irclib.IrcCallback):
dispatcher.__doc__ = docstring dispatcher.__doc__ = docstring
setattr(self.__class__, canonicalname, dispatcher) setattr(self.__class__, canonicalname, dispatcher)
def configure(self, irc):
fakeIrc = ConfigIrcProxy(irc)
for args in conf.commandsOnStart:
args = args[:]
command = canonicalName(args.pop(0))
if self.isCommand(command):
self.log.debug('%s: %r', command, args)
method = getattr(self, command)
line = '%s %s' % (command, ' '.join(imap(utils.dqrepr, args)))
msg = ircmsgs.privmsg(fakeIrc.nick, line, fakeIrc.prefix)
try:
world.startup = True
method(fakeIrc, msg, args)
finally:
world.startup = False
def __call__(self, irc, msg): def __call__(self, irc, msg):
if msg.command == 'PRIVMSG': if msg.command == 'PRIVMSG':
if self.noIgnore or not ircdb.checkIgnored(msg.prefix,msg.args[0]): if self.noIgnore or not ircdb.checkIgnored(msg.prefix,msg.args[0]):

View File

@ -35,307 +35,308 @@ import fix
import os import os
import sys import sys
import string
import sets import utils
import os.path import registry
import logging import ircutils
###
# Directions:
#
# Boolean values should be either True or False.
###
###
# Directories.
###
logDir = 'logs'
confDir = 'conf'
dataDir = 'data'
installDir = os.path.dirname(os.path.dirname(sys.modules[__name__].__file__)) installDir = os.path.dirname(os.path.dirname(sys.modules[__name__].__file__))
pluginDirs = [os.path.join(installDir, s) for s in ('src', 'plugins')] _srcDir = os.path.join(installDir, 'src')
_pluginsDir = os.path.join(installDir, 'plugins')
###
# Files.
###
userfile = 'users.conf'
channelfile = 'channels.conf'
###
# minimumLogPriority: The minimum priority that will be logged. Defaults to
# logging.INFO, which is probably a good value. Can also
# be usefully set to logging.{DEBUG,WARNING,ERROR,CRITICAL}
###
minimumLogPriority = logging.INFO
###
# stdoutLogging: Determines whether or not the bot logs to stdout.
###
stdoutLogging = True
###
# colorizedStdoutLogging: Determines whether or not the bot logs colored logs
# to stdout.
###
colorizedStdoutLogging = True
###
# logTimestampFormat: A format string defining how timestamps should be. Check
# the Python library reference for the "time" module to see
# what the various format specifiers mean.
###
logTimestampFormat = '[%d-%b-%Y %H:%M:%S]'
###
# humanTimestampFormat: A format string defining how timestamps should be
# formatted for human consumption. Check the Python
# library reference for the "time" module to see what the
# various format specifiers mean.
###
humanTimestampFormat = '%I:%M %p, %B %d, %Y'
###
# externalIP: A string that is the external IP of the bot. If this is None,
# the bot will attempt to find out its IP dynamically (though
# sometimes this doesn't work.)
###
externalIP = None
###
# throttleTime: A floating point number of seconds to throttle queued messages.
# (i.e., messages will not be sent faster than once per
# throttleTime units.)
###
throttleTime = 1.0
###
# snarfThrottle: A floating point number of seconds to throttle snarfed URLs,
# in order to prevent loops between two bots.
###
snarfThrottle = 10.0
### ###
# allowEval: True if the owner (and only the owner) should be able to eval # allowEval: True if the owner (and only the owner) should be able to eval
# arbitrary Python code. # arbitrary Python code. This is specifically *not* a registry
# variable because it shouldn't be modifiable in the bot.
### ###
allowEval = False allowEval = False
###
# replyWhenNotCommand: True if you want the bot reply when someone apparently supybot = registry.Group()
# addresses him but there is no command. Otherwise he'll supybot.setName('supybot')
# just remain silent. supybot.registerGroup('plugins') # This will be used by plugins, but not here.
###
replyWhenNotCommand = True class ValidNick(registry.String):
def set(self, s):
original = getattr(self, 'value', self.default)
registry.String.set(self, s)
if not ircutils.isNick(self.value):
self.value = original
raise registry.InvalidRegistryValue, 'Value must be a valid nick.'
supybot.register('nick', ValidNick('supybot',
"""Determines the bot's nick."""))
supybot.register('ident', ValidNick('supybot',
"""Determines the bot's ident."""))
supybot.register('user', registry.String('supybot', """Determines the user
the bot sends to the server."""))
supybot.register('password', registry.String('', """Determines the password to
be sent to the server if it requires one."""))
# TODO: Make this check for validity.
supybot.register('server', registry.String('irc.freenode.net', """Determines
what server the bot connects to."""))
supybot.register('channels', registry.CommaSeparatedListOfStrings('#supybot',
"""Determines what channels the bot will join when it connects to the server.
"""))
supybot.registerGroup('databases')
supybot.databases.registerGroup('users')
supybot.databases.registerGroup('channels')
supybot.databases.users.register('filename', registry.String('users.conf', """
Determines what filename will be used for the users database. This file will
go into the directory specified by the supybot.directories.conf
variable."""))
supybot.databases.channels.register('filename',registry.String('channels.conf',
"""Determines what filename will be used for the channels database. This file
will go into the directory specified by the supybot.directories.conf
variable."""))
supybot.registerGroup('directories')
supybot.directories.register('conf', registry.String('conf', """
Determines what directory configuration data is put into."""))
supybot.directories.register('data', registry.String('data', """
Determines what directory data is put into."""))
supybot.directories.register('plugins',
registry.CommaSeparatedListOfStrings(['plugins',_srcDir,_pluginsDir],
"""Determines what directories the bot will look for plugins in."""))
supybot.register('humanTimestampFormat', registry.String('%I:%M %p, %B %d, %Y',
"""Determines how timestamps printed for human reading should be formatted.
Refer to the Python documentation for the time module to see valid formatting
characteres for time formats."""))
class IP(registry.String):
def set(self, s):
original = getattr(self, 'value', self.default)
registry.String.set(self, s)
if self.value: # Empty string is alright.
if not (utils.isIP(self.value) or utils.isIPV6(self.value)):
raise registry.InvalidRegistryValue, \
'Value must be a valid IP.'
supybot.register('externalIP', IP('', """A string that is the external IP of
the bot. If this is the empty string, the bot will attempt to find out its IP
dynamically (though sometimes that doesn't work, hence this variable)."""))
# XXX Should this (and a few others) be made into a group 'network' or
# 'server' or something?
supybot.register('throttleTime', registry.Float(1.0, """A floating point
number of seconds to throttle queued messages -- that is, messages will not
be sent faster than once per throttleTime seconds."""))
supybot.register('snarfThrottle', registry.Float(10.0, """A floating point
number of seconds to throttle snarfed URLs, in order to prevent loops between
two bots snarfing the same URLs and having the snarfed URL in the output of
the snarf message."""))
### ###
# replyWithPrivateNotice: True if replies to a user in a channel should be # Reply/error tweaking.
# noticed to that user instead of sent to the channel
# itself.
### ###
replyWithPrivateNotice = False
# TODO: These should probably all be channel-specific.
supybot.registerGroup('reply')
supybot.reply.register('errorInPrivate', registry.Boolean(False, """
Determines whether the bot will send error messages to users in private."""))
supybot.reply.register('whenNotCommand', registry.Boolean(True, """
Determines whether the bot will reply with an error message when it is
addressed but not given a valid command. If this value is False, the bot
will remain silent."""))
supybot.reply.register('withPrivateNotice', registry.Boolean(False, """
Determines whether the bot will reply with a private notice to users rather
than sending a message to a channel. Private notices are particularly nice
because they don't generally cause IRC clients to open a new query window."""))
supybot.reply.register('withNickPrefix', registry.Boolean(True, """
Determines whether the bot will always prefix the user's nick to its reply to
that user's command."""))
supybot.reply.register('whenAddressedByNick', registry.Boolean(True, """
Determines whether the bot will reply when people address it by its nick,
rather than with a prefix character."""))
supybot.reply.register('whenNotAddressed', registry.Boolean(False, """
Determines whether the bot should attempt to reply to all messages even if they
don't address it (either via its nick or a prefix character). If you set this
to True, you almost certainly want to set supybot.reply.whenNotCommand to
False."""))
# XXX: Removed requireRegistration: it wasn't being used.
supybot.reply.register('requireChannelCommandsToBeSentInChannel',
registry.Boolean(False, """Determines whether the bot will allow you to send
channel-related commands outside of that channel. Sometimes people find it
confusing if a channel-related command (like Filter.outfilter) changes the
behavior of the channel but was sent outside the channel itself."""))
supybot.register('followIdentificationThroughNickChanges',
registry.Boolean(False, """Determines whether the bot will unidentify someone
when that person changes his or her nick. Setting this to True will cause the
bot to track such changes. It defaults to false for a little greater security.
"""))
supybot.register('alwaysJoinOnInvite', registry.Boolean(False, """Determines
whether the bot will always join a channel when it's invited. If this value
is False, the bot will only join a channel if the user inviting it has the
'admin' capability (or if it's explicitly told to join the channel using the
Admin.join command)"""))
supybot.register('pipeSyntax', registry.Boolean(False, """Supybot allows
nested commands; generally, commands are nested via square brackets. Supybot
can also provide a syntax more similar to UNIX pipes. The square bracket
nesting syntax is always enabled, but when this value is True, users can also
nest commands by saying 'bot: foo | bar' instead of 'bot: bar [foo]'."""))
supybot.register('showSimpleSyntax', registry.Boolean(False, """Supybot
normally replies with the full help whenever a user misuses a command. If this
value is set to True, the bot will only reply with the syntax of the command
(the first line of the docstring) rather than the full help."""))
supybot.register('defaultCapabilities',
registry.CommaSeparatedSetOfStrings(['-owner', '-admin', '-trusted'], """
These are the capabilities that are given to everyone by default. If they are
normal capabilities, then the user will have to have the appropriate
anti-capability if you want to override these capabilities; if they are
anti-capabilities, then the user will have to have the actual capability to
override these capabilities. See docs/CAPABILITIES if you don't understand
why these default to what they do."""))
### ###
# replyWithNickPrefix: True if the bot should always prefix the nick of the # Replies
# person giving the command to its reply.
### ###
replyWithNickPrefix = True # TODO: These should be channel-specific.
supybot.registerGroup('replies')
supybot.replies.register('error', registry.NormalizedString("""An error has
occurred and has been logged. Please contact this bot's administrator for more
information.""", """Determines what error message the bot gives when it wants
to be ambiguous."""))
supybot.replies.register('noCapability', registry.NormalizedString("""You
don\'t have the %r capability. If you think that you should have this
capability, be sure that you are identified before trying again. The 'whoami'
command can tell you if you're identified.""", """Determines what error message
is given when the bot is telling someone they aren't cool enough to use the
command they tried to use."""))
supybot.replies.register('success', registry.NormalizedString("""The operation
succeeded.""", """Determines what message the bot replies with when a command
succeeded."""))
supybot.replies.register('incorrectAuthentication',
registry.NormalizedString("""Your hostmask doesn't match or your password is
wrong.""", """Determines what message the bot replies wiwth when someone tries
to use a command that requires being identified or having a password and
neither credential is correct."""))
supybot.replies.register('noUser', registry.NormalizedString("""I can't find
that user in my user database.""", """Determines what error message the bot
replies with when someone tries to accessing some information on a user the
bot doesn't know about."""))
supybot.replies.register('notRegistered', registry.NormalizedString("""
You must be registered to use this command. If you are already registered, you
must either identify (using the identify command) or add a hostmask matching
your current hostmask (using the addhostmask command).""", """Determines what
error message the bot replies with when someone tries to do something that
requires them to be registered but they're not currently recognized."""))
# XXX: removed replyInvalidArgument.
supybot.replies.register('requiresPrivacy', registry.NormalizedString("""
That operation cannot be done in a channel.""", """Determines what error
messages the bot sends to people who try to do things in a channel that really
should be done in private."""))
supybot.replies.register('possibleBug', registry.NormalizedString("""This may
be a bug. If you think it is, please file a bug report at
<http://sourceforge.net/tracker/?func=add&group_id=58965&atid=489447>.""",
"""Determines what message the bot sends when it thinks you've encountered a
bug that the developers don't know about."""))
supybot.register('pingServer', registry.Boolean(True, """Determines whether
the bot will send PINGs to the server it's connected to in order to keep the
connection alive and discover earlier when it breaks. Really, this option
only exists for debugging purposes: you always should make it True unless
you're testing some strange server issues."""))
supybot.register('pingInterval', registry.Integer(120, """Determines the
number of seconds between sending pings to the server, if pings are being sent
to the server."""))
supybot.register('maxHistoryLength', registry.Integer(1000, """Determines
how many old messages the bot will keep around in its history. Changing this
variable will not take effect until the bot is restarted."""))
supybot.register('nickmods', registry.CommaSeparatedListOfStrings(
'__%s__,%s^,%s`,%s_,%s__,_%s,__%s,[%s]'.split(','),
"""A list of modifications to be made to a nick when the nick the bot tries
to get from the server is in use. There should be one %s in each string;
this will get replaced with the original nick."""))
supybot.register('defaultAllow', registry.Boolean(True, """Determines whether
the bot by default will allow users to run commands. If this is disabled, a
user will have to have the capability for whatever command he wishes to run.
"""))
supybot.register('defaultIgnore', registry.Boolean(False, """Determines
whether the bot will ignore unregistered users by default. Of course, that'll
make it particularly hard for those users to register with the bot, but that's
your problem to solve."""))
supybot.register('ignores', registry.CommaSeparatedListOfStrings('', """
A list of hostmasks ignored by the bot. Add people you don't like to here.
"""))
class ValidPrefixChars(registry.String):
def set(self, s):
registry.String.set(self, s)
if self.value.translate(string.ascii,
'`~!@#$%^&*()_-+=[{}]\\|\'";:,<.>/?'):
raise registry.InvalidRegistryValue, \
'Value must contain only ~!@#$%^&*()_-+=[{}]\\|\'";:,<.>/?'
supybot.register('prefixChars', ValidPrefixChars('@', """Determines what prefix
characters the bot will reply to. A prefix character is a single character
that the bot will use to determine what messages are addressed to it; when
there are no prefix characters set, it just uses its nick."""))
### ###
# replyWhenAddressedByNick: True if the bot should reply to messages of the # Driver stuff.
# form "botnick: foo" where "botnick" is the bot's
# nick.
### ###
replyWhenAddressedByNick = True supybot.registerGroup('drivers')
supybot.drivers.register('poll', registry.Float(1.0, """Determines the default
length of time a driver should block waiting for input."""))
### class ValidDriverModule(registry.String):
# replyWhenNotAddressed: True if the bot should reply to messages even if they def set(self, s):
# don't address it at all. If you have this on, you'll original = getattr(self, 'value', self.default)
# almost certainly want to make sure replyWhenNotCommand registry.String.set(self, s)
# is turned off. if self.value not in ('socketDrivers',
### 'twistedDrivers',
replyWhenNotAddressed = False 'asyncoreDrivers'):
self.value = original
raise registry.InvalidRegistryValue, \
'Value must be one of "socketDrivers", "asyncoreDrivers", ' \
'or twistedDrivers.'
else:
# TODO: check to make sure Twisted is available if it's set to
# twistedDrivers.
pass
### supybot.drivers.register('module', ValidDriverModule('socketDrivers', """
# requireRegistration: Oftentimes a plugin will want to record who added or Determines what driver module the bot will use. socketDrivers, a simple
# changed or messed with it last. Supybot's user database driver based on timeout sockets, is used by default because it's simple and
# is an excellent way to determine who exactly someone is. stable. asyncoreDrivers is a bit older (and less well-maintained) but allows
# You may, however, want something a little less you to integrate with asyncore-based applications. twistedDrivers is very
# "intrustive," so you can set this variable to False to stable and simple, and if you've got Twisted installed, is probably your best
# tell such plugins that they should use the hostmask when bet."""))
# the user isn't registered with the user database.
###
requireRegistration = False
###
# requireChannelCommandsToBeSentInChannel: Normally, you can send channel
# related commands in private or in
# another channel. Sometimes this
# can be confusing, though, if the
# command changes the behavior of
# the bot in the channel. Set this
# variable to True if you want to
# require such commands to be sent
# in the channel to which they apply.
###
requireChannelCommandsToBeSentInChannel = False
###
# followIdentificationThroughNickChanges: By default the bot will simply
# unidentify someone when he changes
# his nick. Setting this to True will
# cause the bot to track such changes.
###
followIdentificationThroughNickChanges = False
###
# alwaysJoinOnInvite: Causes the bot to always join a channel when it's
# invited. Defaults to False, in which case the bot will
# only join if the user inviting it has the 'admin'
# capability.
###
alwaysJoinOnInvite = False
###
# enablePipeSyntax: Supybot allows nested commands; generally, commands are
# nested via [square brackets]. Supybot can also use a
# syntax more similar to Unix pipes. What would be (and
# still can be; the pipe syntax doesn't disable the bracket
# syntax) "bot: bar [foo]" can now by "bot: foo | bar"
# This variable enables such syntax.
###
enablePipeSyntax = False
###
# showOnlySyntax : Supybot normally returns the full help whenever a user
# misuses a command. If this option is set to True, the bot
# will only return the syntax of the command (the first line
# of the docstring) rather than the full help.
###
showOnlySyntax = False
###
# defaultCapabilities: Capabilities allowed to everyone by default. You almost
# certainly want to have !owner and !admin in here.
###
defaultCapabilities = sets.Set(['-owner', '-admin', '-trusted'])
###
# reply%s: Stock replies for various reasons.
###
replyError = 'An error has occurred and has been logged. ' \
'Please contact this bot\'s administrator for more information.'
replyNoCapability = 'You don\'t have the "%s" capability. If you think ' \
'that you should have this capability, be sure that ' \
'you are identified via the "whoami" command.'
replySuccess = 'The operation succeeded.'
replyIncorrectAuth = 'Your hostmask doesn\'t match or your password is wrong.'
replyNoUser = 'I can\'t find that user in my database.'
replyNotRegistered = 'You must be registered to use this command. ' \
'If you are already registered, you must either ' \
'identify (using the identify command) or add a ' \
'hostmask matching your current hostmask (using ' \
'the addhostmask command).'
replyInvalidArgument = 'I can\'t send \\r, \\n, or \\0 (\\x00).'
replyRequiresPrivacy = 'That can\'t be done in a channel.'
replyEvalNotAllowed = 'You must enable conf.allowEval for that to work.'
replyPossibleBug = 'This may be a bug. If you think it is, please file a bug '\
'report at <http://sourceforge.net/tracker/?' \
'func=add&group_id=58965&atid=489447>'
###
# errorReplyPrivate: True if errors should be reported privately so as not to
# bother the channel.
###
errorReplyPrivate = False
###
# telnetEnable: A boolean saying whether or not to enable the telnet REPL.
# This will allow a user with the 'owner' capability to telnet
# into the bot and see how it's working internally. A lifesaver
# for development.
###
telnetEnable = False
telnetPort = 31337
###
# poll: the length of a polling term.
# If asyncore drivers are all you're using, feel free to make
# this arbitrarily large -- be warned, however, that all other
# drivers are just sitting around while asyncore waits during
# this poll period (including the schedule). It'll take more
# CPU, but you probably don't want to set this more than 0.01
# when you've got non-asyncore drivers to worry about.
###
poll = 1
###
# pingServer: Determines whether the bot will send PINGs to the server it's
# connected to in order to keep the connection alive. Sometimes
# this seems to result in instability.
###
pingServer = True
###
# maxHistory: Maximum number of messages kept in an Irc object's state.
###
maxHistory = 1000
###
# pingInterval: Number of seconds between PINGs to the server.
# 0 means not to ping the server.
###
pingInterval = 120
###
# nickmods: List of ways to 'spice up' a nick so the bot doesn't run out of
# nicks if all his normal ones are taken.
###
nickmods = ['%s^', '^%s^', '__%s__', '%s_', '%s__', '__%s', '^^%s^^', '{%s}',
'[%s]', '][%s][', '}{%s}{', '}{}%s', '^_^%s', '%s^_^', '^_^%s^_^']
###
# defaultAllow: Are commands allowed by default?
###
defaultAllow = True
###
# defaultIgnore: True if users should be ignored by default.
# It's a really easy way to make sure that people who want to
# talk to the bot register first. (Of course, they can't
# register if they're ignored. We'll work on that.)
###
defaultIgnore = False
###
# ignores: Hostmasks to ignore.
###
ignores = []
###
# prefixChars: A string of chars that are valid prefixes to address the bot.
###
prefixChars = '@'
###
# validPrefixChars: A string of chars that are allowed to be used as
# prefixChars.
###
validPrefixChars = '`~!@#$%^&*()_-+=[{}]\\|\'";:,<.>/?'
###
# detailedTracebacks: A boolean describing whether or not the bot will give
# *extremely* detailed tracebacks. Be cautioned, this eats
# a lot of log file space.
###
detailedTracebacks = True
###
# driverModule: A string that is the module where the default driver for the
# bot will be found.
###
driverModule = 'socketDrivers'
#driverModule = 'asyncoreDrivers'
#driverModule = 'twistedDrivers'
############################### ###############################
############################### ###############################
@ -346,71 +347,4 @@ driverModule = 'socketDrivers'
############################### ###############################
version ='0.76.1' version ='0.76.1'
commandsOnStart = []
# This is a dictionary mapping names to converter functions for use in the
# Owner.setconf command.
def mybool(s):
"""Converts a string read from the user into a bool, fuzzily."""
if s.capitalize() == 'False' or s == '0':
return False
elif s.capitalize() == 'True' or s == '1':
return True
else:
raise ValueError, 'invalid literal for mybool()'
def mystr(s):
"""Converts a string read from the user into a real string."""
while s and s[0] in "'\"" and s[0] == s[-1]:
s = s[1:-1]
return s
types = {
'logDir': mystr,
'confDir': mystr,
'dataDir': mystr,
#'pluginDirs': (list, str),
'userfile': mystr,
'channelfile': mystr,
'logTimestampFormat': mystr,
'humanTimestampFormat': mystr,
'throttleTime': float,
'snarfThrottle': float,
#'allowEval': mybool,
'replyWhenNotCommand': mybool,
'replyWithPrivateNotice': mybool,
'replyWithNickPrefix': mybool,
'replyWhenAddressedByNick': mybool,
'requireRegistration': mybool,
'enablePipeSyntax': mybool,
'replyError': mystr,
'replyNoCapability': mystr,
'replySuccess': mystr,
'replyIncorrectAuth': mystr,
'replyNoUser': mystr,
'replyNotRegistered': mystr,
'replyInvalidArgument': mystr,
'replyRequiresPrivacy': mystr,
'replyEvalNotAllowed': mystr,
'errorReplyPrivate': mybool,
#'telnetEnable': mybool,
#'telnetPort': int,
'poll': float,
#'maxHistory': int,
'pingInterval': float,
#'nickmods': (list, str),
'defaultAllow': mybool,
'defaultIgnore': mybool,
#'ignores': (list, str),
'prefixChars': mystr,
'detailedTracebacks': mybool,
'driverModule': mystr,
'showOnlySyntax': mybool,
'pingServer': mybool,
'followIdentificationThroughNickChanges': mybool
}
if os.name == 'nt':
colorizedStdoutLogging = False
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -210,7 +210,8 @@ class Mixin(object):
""" """
def __init__(self): def __init__(self):
className = self.__class__.__name__ className = self.__class__.__name__
self.filename = os.path.join(conf.confDir, '%s-configurable'%className) self.filename = os.path.join(conf.supybot.directories.conf(),
'%s-configurable'%className)
if os.path.exists(self.filename): if os.path.exists(self.filename):
fd = file(self.filename) fd = file(self.filename)
for line in fd: for line in fd:
@ -258,7 +259,7 @@ class Mixin(object):
flushDictionary(self.configurables) flushDictionary(self.configurables)
fd.close() fd.close()
def config(self, irc, msg, args): def configurableishnessify(self, irc, msg, args):
"""[<channel>] [<name>] [<value>] """[<channel>] [<name>] [<value>]
Sets the value of config variable <name> to <value> on <channel>. If Sets the value of config variable <name> to <value> on <channel>. If

View File

@ -148,8 +148,11 @@ def run():
del _drivers[name] del _drivers[name]
_drivers[name] = driver _drivers[name] = driver
def newDriver(server, irc, moduleName=conf.driverModule): def newDriver(server, irc, moduleName=None):
"""Returns a new driver for the given server using conf.driverModule.""" """Returns a new driver for the given server using the irc given and using
conf.supybot.driverModule to determine what driver to pick."""
if moduleName is None:
moduleName = conf.supybot.drivers.module()
driver = __import__(moduleName).Driver(server, irc) driver = __import__(moduleName).Driver(server, irc)
irc.driver = driver irc.driver = driver
return driver return driver

View File

@ -556,8 +556,11 @@ class ChannelsDictionary(utils.IterableMap):
### ###
# Later, I might add some special handling for botnet. # Later, I might add some special handling for botnet.
### ###
users = UsersDB(os.path.join(conf.confDir, conf.userfile)) confDir = conf.supybot.directories.conf()
channels = ChannelsDictionary(os.path.join(conf.confDir, conf.channelfile)) users = UsersDB(os.path.join(confDir,
conf.supybot.databases.users.filename()))
channels = ChannelsDictionary(os.path.join(confDir,
conf.supybot.databases.channels.filename()))
### ###
# Useful functions for checking credentials. # Useful functions for checking credentials.
@ -567,9 +570,9 @@ def checkIgnored(hostmask, recipient='', users=users, channels=channels):
Checks if the user is ignored by the recipient of the message. Checks if the user is ignored by the recipient of the message.
""" """
for ignore in conf.ignores: for ignore in conf.supybot.ignores():
if ircutils.hostmaskPatternEqual(ignore, hostmask): if ircutils.hostmaskPatternEqual(ignore, hostmask):
log.info('Ignoring %s due to conf.ignores.', hostmask) log.info('Ignoring %s due to conf.supybot.ignores.', hostmask)
return True return True
try: try:
id = users.getUserId(hostmask) id = users.getUserId(hostmask)
@ -584,8 +587,9 @@ def checkIgnored(hostmask, recipient='', users=users, channels=channels):
else: else:
return False return False
else: else:
if conf.defaultIgnore: if conf.supybot.defaultIgnore():
log.info('Ignoring %s due to conf.defaultIgnore', hostmask) log.info('Ignoring %s due to conf.supybot.defaultIgnore',
hostmask)
return True return True
else: else:
return False return False
@ -625,17 +629,17 @@ def _checkCapabilityForUnknownUser(capability, users=users, channels=channels):
return _x(capability, c.defaultAllow) return _x(capability, c.defaultAllow)
except KeyError: except KeyError:
pass pass
if capability in conf.defaultCapabilities: if capability in conf.supybot.defaultCapabilities():
return True return True
elif invertCapability(capability) in conf.defaultCapabilities: elif invertCapability(capability) in conf.supybot.defaultCapabilities():
return False return False
else: else:
return _x(capability, conf.defaultAllow) return _x(capability, conf.supybot.defaultAllow())
def checkCapability(hostmask, capability, users=users, channels=channels): def checkCapability(hostmask, capability, users=users, channels=channels):
"""Checks that the user specified by name/hostmask has the capabilty given. """Checks that the user specified by name/hostmask has the capabilty given.
""" """
if world.startup: if world.testing:
return _x(capability, True) return _x(capability, True)
try: try:
u = users.getUser(hostmask) u = users.getUser(hostmask)
@ -666,12 +670,13 @@ def checkCapability(hostmask, capability, users=users, channels=channels):
return c.checkCapability(capability) return c.checkCapability(capability)
else: else:
return _x(capability, c.defaultAllow) return _x(capability, c.defaultAllow)
if capability in conf.defaultCapabilities: defaultCapabilities = conf.supybot.defaultCapabilities()
if capability in defaultCapabilities:
return True return True
elif invertCapability(capability) in conf.defaultCapabilities: elif invertCapability(capability) in defaultCapabilities:
return False return False
else: else:
return _x(capability, conf.defaultAllow) return _x(capability, conf.supybot.defaultAllow())
def checkCapabilities(hostmask, capabilities, requireAll=False): def checkCapabilities(hostmask, capabilities, requireAll=False):

View File

@ -148,7 +148,6 @@ class IrcMsgQueue(object):
def enqueue(self, msg): def enqueue(self, msg):
"""Enqueues a given message.""" """Enqueues a given message."""
if msg in self.msgs: if msg in self.msgs:
if not world.startup:
log.info('Not adding msg %s to queue' % msg) log.info('Not adding msg %s to queue' % msg)
else: else:
self.msgs.add(msg) self.msgs.add(msg)
@ -257,7 +256,7 @@ class IrcState(IrcCommandDispatcher):
""" """
__slots__ = ('history', 'nicksToHostmasks', 'channels') __slots__ = ('history', 'nicksToHostmasks', 'channels')
def __init__(self): def __init__(self):
self.history = RingBuffer(conf.maxHistory) self.history = RingBuffer(conf.supybot.maxHistoryLength())
self.reset() self.reset()
def reset(self): def reset(self):
@ -407,7 +406,7 @@ class Irc(IrcCommandDispatcher):
world.ircs.append(self) world.ircs.append(self)
self.originalNick = intern(nick) self.originalNick = intern(nick)
self.nick = self.originalNick self.nick = self.originalNick
self.nickmods = cycle(conf.nickmods) self.nickmods = cycle(conf.supybot.nickmods())
self.password = password self.password = password
self.user = intern(user or nick) # Default to nick self.user = intern(user or nick) # Default to nick
self.ident = intern(ident or nick) # Ditto. self.ident = intern(ident or nick) # Ditto.
@ -486,13 +485,15 @@ class Irc(IrcCommandDispatcher):
if self.fastqueue: if self.fastqueue:
msg = self.fastqueue.dequeue() msg = self.fastqueue.dequeue()
elif self.queue: elif self.queue:
if not world.testing and now - self.lastTake <= conf.throttleTime: if not world.testing and now - self.lastTake <= \
conf.supybot.throttleTime():
log.debug('Irc.takeMsg throttling.') log.debug('Irc.takeMsg throttling.')
else: else:
self.lastTake = now self.lastTake = now
msg = self.queue.dequeue() msg = self.queue.dequeue()
elif conf.pingServer and \ elif conf.supybot.pingServer() and \
now > (self.lastping + conf.pingInterval) and self.afterConnect: now > (self.lastping + conf.supybot.pingInterval()) and \
self.afterConnect:
if self.outstandingPing: if self.outstandingPing:
s = 'Reconnecting to %s, ping not replied to.' % self.server s = 'Reconnecting to %s, ping not replied to.' % self.server
log.warning(s) log.warning(s)
@ -525,16 +526,6 @@ class Irc(IrcCommandDispatcher):
msg._len = len(str(msg)) msg._len = len(str(msg))
self.state.addMsg(self, msg) self.state.addMsg(self, msg)
log.debug('Outgoing message: ' + str(msg).rstrip('\r\n')) log.debug('Outgoing message: ' + str(msg).rstrip('\r\n'))
if msg.command == 'NICK':
# We don't want a race condition where the server's NICK
# back to us is lost and someone else steals our nick and uses
# it to abuse our 'owner' power we give to ourselves. Ergo, on
# outgoing messages that change our nick, we pre-emptively
# delete the 'owner' user we setup for ourselves.
user = ircdb.users.getUser(0)
user.unsetAuth()
user.hostmasks = []
ircdb.users.setUser(0, user)
return msg return msg
else: else:
return None return None
@ -542,7 +533,6 @@ class Irc(IrcCommandDispatcher):
def do001(self, msg): def do001(self, msg):
"""Does some logging.""" """Does some logging."""
log.info('Received 001 from the server.') log.info('Received 001 from the server.')
log.info('Hostmasks of user 0: %r', ircdb.users.getUser(0).hostmasks)
def do002(self, msg): def do002(self, msg):
"""Logs the ircd version.""" """Logs the ircd version."""
@ -577,21 +567,11 @@ class Irc(IrcCommandDispatcher):
"""Handles NICK messages.""" """Handles NICK messages."""
if msg.nick == self.nick: if msg.nick == self.nick:
newNick = intern(msg.args[0]) newNick = intern(msg.args[0])
user = ircdb.users.getUser(0)
user.unsetAuth()
user.hostmasks = []
try:
ircdb.users.getUser(newNick)
log.error('User already registered with name %s' % newNick)
except KeyError:
user.name = newNick
ircdb.users.setUser(0, user)
self.nick = newNick self.nick = newNick
(nick, user, domain) = ircutils.splitHostmask(msg.prefix) (nick, user, domain) = ircutils.splitHostmask(msg.prefix)
self.prefix = ircutils.joinHostmask(self.nick, user, domain) self.prefix = ircutils.joinHostmask(self.nick, user, domain)
self.prefix = intern(self.prefix) self.prefix = intern(self.prefix)
log.info('Changing user 0 hostmask to %r' % self.prefix) elif conf.supybot.followIdentificationThroughNickChanges():
elif conf.followIdentificationThroughNickChanges:
# We use elif here because this means it's someone else's nick # We use elif here because this means it's someone else's nick
# change, not our own. # change, not our own.
try: try:
@ -620,13 +600,7 @@ class Irc(IrcCommandDispatcher):
# This catches cases where we know our own nick (from sending it to the # This catches cases where we know our own nick (from sending it to the
# server) but we don't yet know our prefix. # server) but we don't yet know our prefix.
if msg.nick == self.nick and self.prefix != msg.prefix: if msg.nick == self.nick and self.prefix != msg.prefix:
log.info('Updating user 0 prefix: %r' % msg.prefix)
self.prefix = msg.prefix self.prefix = msg.prefix
user = ircdb.users.getUser(0)
user.hostmasks = []
user.name = self.nick
user.addHostmask(msg.prefix)
ircdb.users.setUser(0, user)
# This keeps our nick and server attributes updated. # This keeps our nick and server attributes updated.
if msg.command in self._nickSetters: if msg.command in self._nickSetters:

View File

@ -42,13 +42,57 @@ import logging
import ansi import ansi
import conf import conf
import registry
class LogLevel(registry.Value):
def set(self, s):
s = s.upper()
try:
self.value = getattr(logging, s)
except AttributeError:
s = 'Invalid log level: should be one of ' \
'DEBUG, INFO, WARNING, ERROR, or CRITICAL.'
raise registry.InvalidRegistryValue, s
def __str__(self):
return logging.getLevelName(self.value)
conf.supybot.directories.register('log', registry.String('logs', """Determines
what directory the bot will store its logfiles in."""))
conf.supybot.registerGroup('log')
conf.supybot.log.register('minimumPriority', LogLevel(logging.INFO,
"""Determines what the minimum priority logged will be. Valid values are
DEBUG, INFO, WARNING, ERROR, and CRITICAL, in order of increasing
priority."""))
conf.supybot.log.register('timestampFormat',
registry.String('[%d-%b-%Y %H:%M:%S]',
"""Determines the format string for timestamps in logfiles. Refer to the
Python documentation for the time module to see what formats are accepted."""))
conf.supybot.log.register('detailedTracebacks', registry.Boolean(True, """
Determines whether highly detailed tracebacks will be logged. While more
informative (and thus more useful for debugging) they also take a significantly
greater amount of space in the logs. Hopefully, however, such uncaught
exceptions aren't very common."""))
conf.supybot.log.registerGroup('stdout',
registry.GroupWithValue(registry.Boolean(True, """Determines whether the bot
will log to stdout.""")))
class BooleanRequiredFalseOnWindows(registry.Boolean):
def set(self, s):
registry.Boolean.set(self, s)
if self.value and os.name == 'nt':
raise InvalidRegistryValue, 'Value cannot be true on Windows.'
conf.supybot.log.stdout.register('colorized',
BooleanRequiredFalseOnWindows(False, """Determines whether the bot's logs to
stdout (if enabled) will be colorized with ANSI color."""))
deadlyExceptions = [KeyboardInterrupt, SystemExit] deadlyExceptions = [KeyboardInterrupt, SystemExit]
if not os.path.exists(conf.logDir): if not os.path.exists(conf.supybot.directories.log()):
os.mkdir(conf.logDir, 0755) os.mkdir(conf.supybot.directories.log(), 0755)
pluginLogDir = os.path.join(conf.logDir, 'plugins') pluginLogDir = os.path.join(conf.supybot.directories.log(), 'plugins')
if not os.path.exists(pluginLogDir): if not os.path.exists(pluginLogDir):
os.mkdir(pluginLogDir, 0755) os.mkdir(pluginLogDir, 0755)
@ -56,14 +100,14 @@ if not os.path.exists(pluginLogDir):
class Formatter(logging.Formatter): class Formatter(logging.Formatter):
def formatTime(self, record, datefmt=None): def formatTime(self, record, datefmt=None):
if datefmt is None: if datefmt is None:
datefmt = conf.logTimestampFormat datefmt = conf.supybot.log.timestampFormat()
return logging.Formatter.formatTime(self, record, datefmt) return logging.Formatter.formatTime(self, record, datefmt)
def formatException(self, (E, e, tb)): def formatException(self, (E, e, tb)):
for exn in deadlyExceptions: for exn in deadlyExceptions:
if issubclass(e.__class__, exn): if issubclass(e.__class__, exn):
raise raise
if conf.detailedTracebacks: if conf.supybot.log.detailedTracebacks():
try: try:
return cgitb.text((E, e, tb)).rstrip('\r\n') return cgitb.text((E, e, tb)).rstrip('\r\n')
except: except:
@ -120,7 +164,7 @@ class DailyRotatingHandler(BetterFileHandler):
class ColorizedFormatter(Formatter): class ColorizedFormatter(Formatter):
def formatException(self, (E, e, tb)): def formatException(self, (E, e, tb)):
if conf.colorizedStdoutLogging: if conf.supybot.log.stdout.colorized():
return ''.join([ansi.BOLD, ansi.RED, return ''.join([ansi.BOLD, ansi.RED,
Formatter.formatException(self, (E, e, tb)), Formatter.formatException(self, (E, e, tb)),
ansi.RESET]) ansi.RESET])
@ -128,7 +172,7 @@ class ColorizedFormatter(Formatter):
return Formatter.formatException(self, (E, e, tb)) return Formatter.formatException(self, (E, e, tb))
def format(self, record, *args, **kwargs): def format(self, record, *args, **kwargs):
if conf.colorizedStdoutLogging: if conf.supybot.log.stdout.colorized():
color = '' color = ''
if record.levelno == logging.CRITICAL: if record.levelno == logging.CRITICAL:
color = ansi.WHITE + ansi.BOLD color = ansi.WHITE + ansi.BOLD
@ -148,13 +192,14 @@ pluginFormatter = Formatter('%(levelname)s %(asctime)s %(name)s %(message)s')
# These are not. # These are not.
_logger = logging.getLogger('supybot') _logger = logging.getLogger('supybot')
_handler = BetterFileHandler(os.path.join(conf.logDir, 'misc.log')) _handler = BetterFileHandler(os.path.join(conf.supybot.directories.log(),
'misc.log'))
_handler.setFormatter(formatter) _handler.setFormatter(formatter)
_handler.setLevel(-1) _handler.setLevel(-1)
_logger.addHandler(_handler) _logger.addHandler(_handler)
_logger.setLevel(conf.minimumLogPriority) _logger.setLevel(conf.supybot.log.minimumPriority())
if conf.stdoutLogging: if conf.supybot.log.stdout():
_stdoutHandler = BetterStreamHandler(sys.stdout) _stdoutHandler = BetterStreamHandler(sys.stdout)
_formatString = '%(name)s: %(levelname)s %(message)s' _formatString = '%(name)s: %(levelname)s %(message)s'
_stdoutFormatter = ColorizedFormatter(_formatString) _stdoutFormatter = ColorizedFormatter(_formatString)
@ -178,7 +223,7 @@ def getPluginLogger(name):
if not log.handlers: if not log.handlers:
filename = os.path.join(pluginLogDir, '%s.log' % name) filename = os.path.join(pluginLogDir, '%s.log' % name)
handler = BetterFileHandler(filename) handler = BetterFileHandler(filename)
handler.setLevel(conf.minimumLogPriority) handler.setLevel(conf.supybot.log.minimumPriority())
handler.setFormatter(pluginFormatter) handler.setFormatter(pluginFormatter)
log.addHandler(handler) log.addHandler(handler)
return log return log

View File

@ -118,7 +118,7 @@ class ChannelDBHandler(object):
"""Override this to specialize the filenames of your databases.""" """Override this to specialize the filenames of your databases."""
channel = ircutils.toLower(channel) channel = ircutils.toLower(channel)
prefix = '%s-%s%s' % (channel, self.__class__.__name__, self.suffix) prefix = '%s-%s%s' % (channel, self.__class__.__name__, self.suffix)
return os.path.join(conf.dataDir, prefix) return os.path.join(conf.supybot.directories.data(), prefix)
def makeDb(self, filename): def makeDb(self, filename):
"""Override this to create your databases.""" """Override this to create your databases."""
@ -174,7 +174,7 @@ class PeriodicFileDownloader(object):
you want with this; you may want to build a database, take some stats, you want with this; you may want to build a database, take some stats,
or simply rename the file. You can pass None as your function and the or simply rename the file. You can pass None as your function and the
file with automatically be renamed to match the filename you have it listed file with automatically be renamed to match the filename you have it listed
under. It'll be in conf.dataDir, of course. under. It'll be in conf.supybot.directories.data, of course.
Aside from that dictionary, simply use self.getFile(filename) in any method Aside from that dictionary, simply use self.getFile(filename) in any method
that makes use of a periodically downloaded file, and you'll be set. that makes use of a periodically downloaded file, and you'll be set.
@ -187,7 +187,8 @@ class PeriodicFileDownloader(object):
self.downloadedCounter = {} self.downloadedCounter = {}
for filename in self.periodicFiles: for filename in self.periodicFiles:
if self.periodicFiles[filename][-1] is None: if self.periodicFiles[filename][-1] is None:
fullname = os.path.join(conf.dataDir, filename) fullname = os.path.join(conf.supybot.directories.data(),
filename)
if os.path.exists(fullname): if os.path.exists(fullname):
self.lastDownloaded[filename] = os.stat(fullname).st_ctime self.lastDownloaded[filename] = os.stat(fullname).st_ctime
else: else:
@ -206,7 +207,8 @@ class PeriodicFileDownloader(object):
self.log.warning('Error downloading %s', url) self.log.warning('Error downloading %s', url)
self.log.exception('Exception:') self.log.exception('Exception:')
return return
newFilename = os.path.join(conf.dataDir, utils.mktemp()) confDir = conf.supybot.directories.data()
newFilename = os.path.join(confDir, utils.mktemp())
outfd = file(newFilename, 'wb') outfd = file(newFilename, 'wb')
start = time.time() start = time.time()
s = infd.read(4096) s = infd.read(4096)
@ -220,7 +222,7 @@ class PeriodicFileDownloader(object):
self.downloadedCounter[filename] += 1 self.downloadedCounter[filename] += 1
self.lastDownloaded[filename] = time.time() self.lastDownloaded[filename] = time.time()
if f is None: if f is None:
toFilename = os.path.join(conf.dataDir, filename) toFilename = os.path.join(confDir, filename)
if os.name == 'nt': if os.name == 'nt':
# Windows, grrr... # Windows, grrr...
if os.path.exists(toFilename): if os.path.exists(toFilename):

View File

@ -57,7 +57,7 @@ def getChannel(msg, args):
removed). removed).
""" """
if args and ircutils.isChannel(args[0]): if args and ircutils.isChannel(args[0]):
if conf.requireChannelCommandsToBeSentInChannel: if conf.supybot.reply.requireChannelCommandsToBeSentInChannel():
if args[0] != msg.args[0]: if args[0] != msg.args[0]:
s = 'Channel commands must be sent in the channel to which ' \ s = 'Channel commands must be sent in the channel to which ' \
'they apply.' 'they apply.'
@ -145,22 +145,6 @@ def thread(f):
t.start() t.start()
return utils.changeFunctionName(newf, f.func_name, f.__doc__) return utils.changeFunctionName(newf, f.func_name, f.__doc__)
def name(f):
"""Makes sure a name is available based on conf.requireRegistration."""
def newf(self, irc, msg, args, *L):
try:
name = ircdb.users.getUser(msg.prefix).name
except KeyError:
if conf.requireRegistration:
irc.errorNotRegistered()
return
else:
name = msg.prefix
L = (name,) + L
ff = types.MethodType(f, self, self.__class__)
ff(irc, msg, args, *L)
return utils.changeFunctionName(newf, f.func_name, f.__doc__)
def channel(f): def channel(f):
"""Gives the command an extra channel arg as if it had called getChannel""" """Gives the command an extra channel arg as if it had called getChannel"""
def newf(self, irc, msg, args, *L): def newf(self, irc, msg, args, *L):
@ -175,7 +159,7 @@ def urlSnarfer(f):
f = _threadedWrapMethod(f) f = _threadedWrapMethod(f)
def newf(self, irc, msg, match, *L): def newf(self, irc, msg, match, *L):
now = time.time() now = time.time()
cutoff = now - conf.snarfThrottle cutoff = now - conf.supybot.snarfThrottle()
q = getattr(self, '_snarfedUrls', None) q = getattr(self, '_snarfedUrls', None)
if q is None: if q is None:
q = structures.smallqueue() q = structures.smallqueue()

View File

@ -32,13 +32,17 @@
__revision__ = "$Id$" __revision__ = "$Id$"
import copy import copy
import sets
import types
import utils import utils
class RegistryException(Exception): class RegistryException(Exception):
pass pass
class InvalidRegistryFile(RegistryException):
pass
class InvalidRegistryValue(RegistryException): class InvalidRegistryValue(RegistryException):
pass pass
@ -50,43 +54,47 @@ def open(filename):
"""Initializes the module by loading the registry file into memory.""" """Initializes the module by loading the registry file into memory."""
cache.clear() cache.clear()
fd = utils.nonCommentNonEmptyLines(file(filename)) fd = utils.nonCommentNonEmptyLines(file(filename))
for line in fd: for (i, line) in enumerate(fd):
line = line.rstrip() line = line.rstrip('\r\n')
try:
(key, value) = line.split(': ', 1) (key, value) = line.split(': ', 1)
cache[key] = value except ValueError:
raise InvalidRegistryFile, 'Error unpacking line #%s' % (i+1)
cache[key.lower()] = value
def close(registry, filename): def close(registry, filename):
fd = file(filename, 'w') fd = file(filename, 'w')
for (name, value) in registry.getValues(askChildren=True): for (name, value) in registry.getValues(getChildren=True):
fd.write('%s: %s\n' % (name, value)) fd.write('%s: %s\n' % (name, value))
fd.close() fd.close()
class Value(object): class Value(object):
def __init__(self, default, help): def __init__(self, default, help):
self.help = utils.normalizeWhitespace(help) self.default = default
self.value = self.default = default self.help = utils.normalizeWhitespace(help.strip())
self.setValue(default)
self.set(str(self)) # This is needed.
def set(self, s): def set(self, s):
"""Override this with a function to convert a string to whatever type """Override this with a function to convert a string to whatever type
you want, and set it to .value.""" you want, and set it to .value."""
# self.value = value
raise NotImplementedError raise NotImplementedError
def get(self): def setValue(self, v):
return self.value self.value = v
def default(self):
return self.default
def reset(self): def reset(self):
self.value = self.default self.setValue(self.default)
def help(self):
return self.help
def __str__(self): def __str__(self):
return repr(self.value) return repr(self.value)
# This is simply prettier than naming this function get(self)
def __call__(self):
return self.value
class Boolean(Value): class Boolean(Value):
def set(self, s): def set(self, s):
s = s.lower() s = s.lower()
@ -94,6 +102,8 @@ class Boolean(Value):
self.value = True self.value = True
elif s in ('false', 'off', 'disabled'): elif s in ('false', 'off', 'disabled'):
self.value = False self.value = False
elif s == 'toggle':
self.value = not self.value
else: else:
raise InvalidRegistryValue, 'Value must be True or False.' raise InvalidRegistryValue, 'Value must be True or False.'
@ -104,9 +114,16 @@ class Integer(Value):
except ValueError: except ValueError:
raise InvalidRegistryValue, 'Value must be an integer.' raise InvalidRegistryValue, 'Value must be an integer.'
class Float(Value):
def set(self, s):
try:
self.value = float(s)
except ValueError:
raise InvalidRegistryValue, 'Value must be a float.'
class String(Value): class String(Value):
def set(self, s): def set(self, s):
if s and s[0] not in '\'"' and s[-1] not in '\'"': if not s or (s[0] not in '\'"' and s[-1] not in '\'"'):
s = repr(s) s = repr(s)
try: try:
v = utils.safeEval(s) v = utils.safeEval(s)
@ -116,6 +133,11 @@ class String(Value):
except ValueError: # This catches utils.safeEval(s) errors too. except ValueError: # This catches utils.safeEval(s) errors too.
raise InvalidRegistryValue, 'Value must be a string.' raise InvalidRegistryValue, 'Value must be a string.'
class NormalizedString(String):
def set(self, s):
s = utils.normalizeWhitespace(s.strip())
String.set(self, s)
class StringSurroundedBySpaces(String): class StringSurroundedBySpaces(String):
def set(self, s): def set(self, s):
String.set(self, s) String.set(self, s)
@ -124,13 +146,25 @@ class StringSurroundedBySpaces(String):
if self.value.rstrip() == self.value: if self.value.rstrip() == self.value:
self.value += ' ' self.value += ' '
class CommaSeparatedListOfStrings(String):
def set(self, s):
String.set(self, s)
self.value = map(str.strip, self.value.split(','))
def __str__(self):
return ','.join(self.value)
class CommaSeparatedSetOfStrings(CommaSeparatedListOfStrings):
def set(self, s):
CommaSeparatedListOfStrings.set(self, s)
self.value = sets.Set(self.value)
class Group(object): class Group(object):
def __init__(self): def __init__(self):
self.__dict__['name'] = 'unset' self.name = 'unset'
self.__dict__['values'] = {} self.values = {}
self.__dict__['children'] = {} self.children = {}
self.__dict__['originals'] = {} self.originals = {}
def __nonExistentEntry(self, attr): def __nonExistentEntry(self, attr):
s = '%s is not a valid entry in %s' % (attr, self.name) s = '%s is not a valid entry in %s' % (attr, self.name)
@ -140,56 +174,24 @@ class Group(object):
original = attr original = attr
attr = attr.lower() attr = attr.lower()
if attr in self.values: if attr in self.values:
return self.values[attr].get() return self.values[attr]
elif attr in self.children: elif attr in self.children:
return self.children[attr] return self.children[attr]
else: else:
self.__nonExistentEntry(original) self.__nonExistentEntry(original)
def __setattr__(self, attr, s): def getChild(self, attr):
original = attr return self.children[attr.lower()]
attr = attr.lower()
if attr in self.values:
self.values[attr].set(s)
elif attr in self.children and hasattr(self.children[attr], 'set'):
self.children[attr].set(s)
else:
self.__nonExistentEntry(original)
def get(self, attr):
return self.__getattr__(attr)
def help(self, attr):
original = attr
attr = attr.lower()
if attr in self.values:
return self.values[attr].help
elif attr in self.children and hasattr(self.children[attr], 'help'):
return self.children[attr].help
else:
self.__nonExistentEntry(original)
def default(self, attr):
original = attr
attr = attr.lower()
if attr in self.values:
return self.values[attr].default
elif attr in self.children and hasattr(self.children[attr], 'default'):
return self.children[attr].default
else:
self.__nonExistentEntry(original)
def setName(self, name): def setName(self, name):
self.__dict__['name'] = name self.name = name
def getName(self): def getName(self):
return self.__dict__['name'] return self.name
def register(self, name, value): def register(self, name, value):
original = name original = name
name = name.lower() name = name.lower()
if name in self.values:
value.set(str(self.values[name]))
self.values[name] = value self.values[name] = value
self.originals[name] = original self.originals[name] = original
if cache: if cache:
@ -200,11 +202,10 @@ class Group(object):
def registerGroup(self, name, group=None): def registerGroup(self, name, group=None):
original = name original = name
name = name.lower() name = name.lower()
if name in self.children:
return # Ignore redundant group inserts.
if group is None: if group is None:
group = Group() group = Group()
if name in self.children:
group.__dict__['values'] = self.children[name].values
group.__dict__['children'] = self.children[name].children
self.children[name] = group self.children[name] = group
self.originals[name] = original self.originals[name] = original
fullname = '%s.%s' % (self.name, name) fullname = '%s.%s' % (self.name, name)
@ -212,25 +213,52 @@ class Group(object):
if cache and fullname in cache: if cache and fullname in cache:
group.set(cache[fullname]) group.set(cache[fullname])
def getValues(self, askChildren=False): def getValues(self, getChildren=False):
L = [] L = []
items = self.values.items() items = self.values.items()
utils.sortBy(lambda (k, _): (k.lower(), len(k), k), items) for (name, child) in self.children.items():
if hasattr(child, 'value'):
items.append((name, child))
utils.sortBy(lambda (k, _): (len(k), k.lower(), k), items)
for (name, value) in items: for (name, value) in items:
L.append(('%s.%s' % (self.getName(), name), str(value))) L.append(('%s.%s' % (self.getName(), self.originals[name]), value))
if askChildren: if getChildren:
items = self.children.items() items = self.children.items()
utils.sortBy(lambda (k, _): (k.lower(), len(k), k), items) utils.sortBy(lambda (k, _): (k.lower(), len(k), k), items)
for (_, child) in items: for (_, child) in items:
L.extend(child.getValues(askChildren)) L.extend(child.getValues(getChildren))
return L return L
class GroupWithDefault(Group): class GroupWithValue(Group):
def __init__(self, value): def __init__(self, value):
Group.__init__(self) Group.__init__(self)
self.__dict__['help'] = value.help self.value = value
self.__dict__['value'] = self.__dict__['default'] = value self.help = value.help
def set(self, s):
self.value.set(s)
def setValue(self, v):
self.value.setValue(v)
def reset(self):
self.value.reset()
def __call__(self):
return self.value()
def __str__(self):
return str(self.value)
## def getValues(self, getChildren=False):
## L = Group.getValues(self, getChildren=False)
## L.insert(0, (self.getName(), str(self.value)))
## return L
class GroupWithDefault(GroupWithValue):
def __init__(self, value):
GroupWithValue.__init__(self, value)
def __makeChild(self, attr, s): def __makeChild(self, attr, s):
v = copy.copy(self.value) v = copy.copy(self.value)
@ -241,13 +269,7 @@ class GroupWithDefault(Group):
try: try:
return Group.__getattr__(self, attr) return Group.__getattr__(self, attr)
except NonExistentRegistryEntry: except NonExistentRegistryEntry:
return self.value.get() return self.value
def __setattr__(self, attr, s):
try:
Group.__setattr__(self, attr, s)
except NonExistentRegistryEntry:
self.__makeChild(attr, s)
def setName(self, name): def setName(self, name):
Group.setName(self, name) Group.setName(self, name)
@ -256,25 +278,16 @@ class GroupWithDefault(Group):
(_, group) = rsplit(k, '.', 1) (_, group) = rsplit(k, '.', 1)
self.__makeChild(group, v) self.__makeChild(group, v)
def set(self, *args): def setChild(self, attr, s):
if len(args) == 1:
self.value.set(args[0])
else:
assert len(args) == 2
(attr, s) = args
self.__setattr__(attr, s) self.__setattr__(attr, s)
def getValues(self, askChildren=False):
L = Group.getValues(self, askChildren)
L.insert(0, (self.getName(), str(self.value)))
return L
if __name__ == '__main__': if __name__ == '__main__':
import sys
sys.setrecursionlimit(40)
supybot = Group() supybot = Group()
supybot.setName('supybot') supybot.setName('supybot')
supybot.register('throttleTime', Integer(1, """Determines the minimum supybot.register('throttleTime', Float(1, """Determines the minimum
number of seconds the bot will wait between sending messages to the server. number of seconds the bot will wait between sending messages to the server.
""")) """))
supybot.registerGroup('plugins') supybot.registerGroup('plugins')
@ -282,9 +295,19 @@ if __name__ == '__main__':
supybot.plugins.topic.registerGroup('separator', supybot.plugins.topic.registerGroup('separator',
GroupWithDefault(StringSurroundedBySpaces(' || ', GroupWithDefault(StringSurroundedBySpaces(' || ',
'Determines what separator the bot uses to separate topic entries.'))) 'Determines what separator the bot uses to separate topic entries.')))
supybot.plugins.topic.separator.set('#supybot', ' |||| ') supybot.plugins.topic.separator.setChild('#supybot', ' |||| ')
supybot.plugins.topic.separator.set(' <> ') supybot.plugins.topic.separator.set(' <> ')
supybot.throttleTime.set(10)
supybot.registerGroup('log')
supybot.log.registerGroup('stdout',
GroupWithValue(Boolean(False,
"""Help for stdout.""")))
supybot.log.stdout.register('colorized', Boolean(False,
'Help colorized'))
supybot.log.stdout.setValue(True)
for (k, v) in supybot.getValues(): for (k, v) in supybot.getValues():
print '%s: %s' % (k, v) print '%s: %s' % (k, v)
@ -292,11 +315,11 @@ if __name__ == '__main__':
print 'Asking children' print 'Asking children'
print print
for (k, v) in supybot.getValues(askChildren=True): for (k, v) in supybot.getValues(getChildren=True):
print '%s: %s' % (k, v) print '%s: %s' % (k, v)
print supybot.help('throttleTime') print supybot.throttleTime.help
print supybot.plugins.topic.help('separator') print supybot.plugins.topic.separator.help
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -40,6 +40,7 @@ __revision__ ="$Id$"
import fix import fix
import time import time
import atexit
import socket import socket
from itertools import imap from itertools import imap
@ -51,13 +52,17 @@ import ircmsgs
import schedule import schedule
instances = 0 instances = 0
originalPoll = conf.poll originalPoll = conf.supybot.drivers.poll()
def resetPoll():
log.info('Resetting supybot.drivers.poll to %s', originalPoll)
conf.supybot.drivers.poll.setValue(originalPoll)
atexit.register(resetPoll)
class SocketDriver(drivers.IrcDriver): class SocketDriver(drivers.IrcDriver):
def __init__(self, (server, port), irc, reconnectWaits=(0, 60, 300)): def __init__(self, (server, port), irc, reconnectWaits=(0, 60, 300)):
global instances global instances
instances += 1 instances += 1
conf.poll = originalPoll / instances conf.supybot.drivers.poll.setValue(originalPoll / instances)
self.server = (server, port) self.server = (server, port)
drivers.IrcDriver.__init__(self) # Must come after server is set. drivers.IrcDriver.__init__(self) # Must come after server is set.
self.irc = irc self.irc = irc
@ -88,7 +93,9 @@ class SocketDriver(drivers.IrcDriver):
def run(self): def run(self):
if not self.connected: if not self.connected:
time.sleep(conf.poll) # Otherwise we might spin. # We sleep here because otherwise, if we're the only driver, we'll
# spin at 100% CPU while we're disconnected.
time.sleep(conf.supybot.drivers.poll())
return return
self._sendIfMsgs() self._sendIfMsgs()
try: try:
@ -124,12 +131,13 @@ class SocketDriver(drivers.IrcDriver):
return return
self.irc.reset() self.irc.reset()
self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.conn.settimeout(conf.poll*10) # Allow more time for connect. # We allow more time for the connect here, since it might take longer.
self.conn.settimeout(conf.supybot.drivers.poll()*10)
if self.reconnectWaitsIndex < len(self.reconnectWaits)-1: if self.reconnectWaitsIndex < len(self.reconnectWaits)-1:
self.reconnectWaitsIndex += 1 self.reconnectWaitsIndex += 1
try: try:
self.conn.connect(self.server) self.conn.connect(self.server)
self.conn.settimeout(conf.poll) self.conn.settimeout(conf.supybot.drivers.poll())
except socket.error, e: except socket.error, e:
if e.args[0] != 115: if e.args[0] != 115:
log.warning('Error connecting to %s: %s', self.server, e) log.warning('Error connecting to %s: %s', self.server, e)
@ -144,7 +152,8 @@ class SocketDriver(drivers.IrcDriver):
def _scheduleReconnect(self): def _scheduleReconnect(self):
when = time.time() + self.reconnectWaits[self.reconnectWaitsIndex] when = time.time() + self.reconnectWaits[self.reconnectWaitsIndex]
whenS = time.strftime(conf.logTimestampFormat, time.localtime(when)) whenS = time.strftime(conf.supybot.log.timestampFormat(),
time.localtime(when))
if not world.dying: if not world.dying:
log.info('Scheduling reconnect to %s at %s', self.server, whenS) log.info('Scheduling reconnect to %s at %s', self.server, whenS)
schedule.addEvent(self.reconnect, when) schedule.addEvent(self.reconnect, when)

View File

@ -42,14 +42,13 @@ import drivers
import ircmsgs import ircmsgs
from twisted.internet import reactor from twisted.internet import reactor
from twisted.manhole.telnet import Shell, ShellFactory
from twisted.protocols.basic import LineReceiver from twisted.protocols.basic import LineReceiver
from twisted.internet.protocol import ReconnectingClientFactory from twisted.internet.protocol import ReconnectingClientFactory
class TwistedRunnerDriver(drivers.IrcDriver): class TwistedRunnerDriver(drivers.IrcDriver):
def run(self): def run(self):
try: try:
reactor.iterate(conf.poll) reactor.iterate(conf.supybot.drivers.poll())
except: except:
log.exception('Uncaught exception outside reactor:') log.exception('Uncaught exception outside reactor:')
@ -104,26 +103,6 @@ class SupyReconnectingFactory(ReconnectingClientFactory):
def die(self): def die(self):
pass pass
class MyShell(Shell):
def checkUserAndPass(self, username, password):
try:
id = ircdb.users.getUserId(username)
u = ircdb.users.getUser(id)
if u.checkPassword(password) and u.checkCapability('owner'):
return True
else:
return False
except KeyError:
return False
class MyShellFactory(ShellFactory):
protocol = MyShell
if conf.telnetEnable and __name__ != '__main__':
reactor.listenTCP(conf.telnetPort, MyShellFactory())
Driver = SupyReconnectingFactory Driver = SupyReconnectingFactory
try: try:

View File

@ -92,7 +92,8 @@ def upkeep():
log.debug('Pattern cache size: %s'%len(ircutils._patternCache)) log.debug('Pattern cache size: %s'%len(ircutils._patternCache))
log.debug('HostmaskPatternEqual cache size: %s' % log.debug('HostmaskPatternEqual cache size: %s' %
len(ircutils._hostmaskPatternEqualCache)) len(ircutils._hostmaskPatternEqualCache))
log.info('%s upkeep ran.', time.strftime(conf.logTimestampFormat)) log.info('%s upkeep ran.',
time.strftime(conf.supybot.log.timestampFormat()))
return collected return collected
def makeDriversDie(): def makeDriversDie():
@ -129,7 +130,6 @@ atexit.register(startDying)
################################################## ##################################################
################################################## ##################################################
################################################## ##################################################
startup = False
testing = False testing = False
dying = False dying = False

View File

@ -29,17 +29,30 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
import os
import supybot import supybot
import logging import logging
registryFilename = os.path.join('test-conf', 'test.conf')
fd = file(registryFilename, 'w')
fd.write("""
supybot.directories.data: test-data
supybot.directories.conf: test-conf
supybot.directories.log: test-log
supybot.reply.whenNotCommand: False
supybot.log.stdout: False
supybot.log.minimumPriority: DEBUG
supybot.log.detailedTracebacks: False
supybot.throttleTime: 0
""")
fd.close()
import registry
registry.open(registryFilename)
import log
import conf import conf
conf.dataDir = 'test-data'
conf.confDir = 'test-conf'
conf.logDir = 'test-log'
conf.replyWhenNotCommand = False
conf.stdoutLogging = False
conf.minimumLogPriority = logging.DEBUG
conf.detailedTracebacks = False # Bugs in cgitb can be bad.
import fix import fix
@ -62,16 +75,19 @@ if __name__ == '__main__':
import testsupport import testsupport
import optparse import optparse
if not os.path.exists(conf.dataDir): if not os.path.exists(conf.supybot.directories.data()):
os.mkdir(conf.dataDir) os.mkdir(conf.supybot.directories.data())
if not os.path.exists(conf.confDir): if not os.path.exists(conf.supybot.directories.conf()):
os.mkdir(conf.confDir) os.mkdir(conf.supybot.directories.conf())
if not os.path.exists(conf.logDir): if not os.path.exists(conf.supybot.directories.log()):
os.mkdir(conf.logDir) os.mkdir(conf.supybot.directories.log())
pluginLogDir = os.path.join(conf.supybot.directories.log(), 'plugins')
if not os.path.exists(pluginLogDir):
os.mkdir(pluginLogDir)
pluginLogDir = os.path.join(conf.logDir, 'plugins')
for filename in os.listdir(pluginLogDir): for filename in os.listdir(pluginLogDir):
os.remove(os.path.join(pluginLogDir, filename)) os.remove(os.path.join(pluginLogDir, filename))

View File

@ -51,7 +51,7 @@ class AdminTestCase(PluginTestCase, PluginDocumentation):
self.assertNotError('admin unignore foo!bar@baz') self.assertNotError('admin unignore foo!bar@baz')
self.assertError('admin unignore foo!bar@baz') self.assertError('admin unignore foo!bar@baz')
finally: finally:
conf.ignores = [] conf.supybot.ignores.set('')
def testIgnores(self): def testIgnores(self):
try: try:
@ -61,13 +61,7 @@ class AdminTestCase(PluginTestCase, PluginDocumentation):
self.assertNotError('admin ignore foo!bar@baz') self.assertNotError('admin ignore foo!bar@baz')
self.assertNotError('admin ignores') self.assertNotError('admin ignores')
finally: finally:
conf.ignores = [] conf.supybot.ignores.set('')
def testSetprefixchar(self):
self.assertNotError('setprefixchar $')
self.assertResponse('getprefixchar', "'$'")
self.assertError('setprefixchar p')
self.assertNoResponse(' ', 2) # make sure we return
def testAddcapability(self): def testAddcapability(self):
self.assertError('addcapability sdlkfj foo') self.assertError('addcapability sdlkfj foo')

View File

@ -107,12 +107,12 @@ class AliasTestCase(ChannelPluginTestCase, PluginDocumentation):
self.assertError('alias add [] foo') self.assertError('alias add [] foo')
self.assertError('alias add "foo bar" foo') self.assertError('alias add "foo bar" foo')
try: try:
conf.enablePipeSyntax = True conf.supybot.pipeSyntax.setValue(True)
self.assertError('alias add "foo|bar" foo') self.assertError('alias add "foo|bar" foo')
conf.enablePipeSyntax = False conf.supybot.pipeSyntax.setValue(False)
self.assertNotError('alias add "foo|bar" foo') self.assertNotError('alias add "foo|bar" foo')
finally: finally:
conf.enablePipeSyntax = False conf.supybot.pipeSyntax.setValue(False)
def testNotCannotNestRaised(self): def testNotCannotNestRaised(self):
self.assertNotError('alias add mytell "tell $channel $1"') self.assertNotError('alias add mytell "tell $channel $1"')

View File

@ -44,13 +44,14 @@ if network:
def setUp(self, nick='test'): def setUp(self, nick='test'):
PluginTestCase.setUp(self) PluginTestCase.setUp(self)
try: try:
if os.path.exists(os.path.join(conf.dataDir, dataDir = conf.supybot.directories.data()
if os.path.exists(os.path.join(dataDir,
'Contents-i386.gz')): 'Contents-i386.gz')):
pass pass
else: else:
print print
print "Downloading files, this may take awhile" print "Downloading files, this may take awhile"
filename = os.path.join(conf.dataDir, 'Contents-i386.gz') filename = os.path.join(dataDir, 'Contents-i386.gz')
while not os.path.exists(filename): while not os.path.exists(filename):
time.sleep(1) time.sleep(1)
print "Download complete" print "Download complete"

View File

@ -42,29 +42,32 @@ class MiscTestCase(ChannelPluginTestCase, PluginDocumentation):
def testReplyWhenNotCommand(self): def testReplyWhenNotCommand(self):
try: try:
conf.replyWhenNotCommand = True original = str(conf.supybot.reply.whenNotCommand)
conf.supybot.reply.whenNotCommand.set('True')
self.prefix = 'somethingElse!user@host.domain.tld' self.prefix = 'somethingElse!user@host.domain.tld'
self.assertRegexp('foo bar baz', 'not.*command') self.assertRegexp('foo bar baz', 'not.*command')
finally: finally:
conf.replyWhenNotCommand = False conf.supybot.reply.whenNotCommand.set(original)
if network: if network:
def testNotReplyWhenRegexpsMatch(self): def testNotReplyWhenRegexpsMatch(self):
try: try:
conf.replyWhenNotCommand = True original = str(conf.supybot.reply.whenNotCommand)
conf.supybot.reply.whenNotCommand.set('True')
self.prefix = 'somethingElse!user@host.domain.tld' self.prefix = 'somethingElse!user@host.domain.tld'
self.assertNotError('http://gameknot.com/chess.pl?bd=1019508') self.assertNotError('http://gameknot.com/chess.pl?bd=1019508')
finally: finally:
conf.replyWhenNotCommand = False conf.supybot.reply.whenNotCommand.set(original)
def testNotReplyWhenNotCanonicalName(self): def testNotReplyWhenNotCanonicalName(self):
try: try:
conf.replyWhenNotCommand = True original = str(conf.supybot.reply.whenNotCommand)
conf.supybot.reply.whenNotCommand.set('True')
self.prefix = 'somethingElse!user@host.domain.tld' self.prefix = 'somethingElse!user@host.domain.tld'
self.assertNotRegexp('STrLeN foobar', 'command') self.assertNotRegexp('STrLeN foobar', 'command')
self.assertResponse('StRlEn foobar', '6') self.assertResponse('StRlEn foobar', '6')
finally: finally:
conf.repylWhenNotCommand = False conf.supybot.reply.whenNotCommand.set(original)
def testHelp(self): def testHelp(self):
self.assertHelp('help list') self.assertHelp('help list')
@ -78,11 +81,11 @@ class MiscTestCase(ChannelPluginTestCase, PluginDocumentation):
def testHelpStripsPrefixChars(self): def testHelpStripsPrefixChars(self):
try: try:
original = conf.prefixChars original = str(conf.supybot.prefixChars)
conf.prefixChars = '@' conf.supybot.prefixChars.set('@')
self.assertHelp('help @list') self.assertHelp('help @list')
finally: finally:
conf.prefixChars = original conf.supybot.prefixChars.set(original)
def testHelpIsCaseInsensitive(self): def testHelpIsCaseInsensitive(self):
self.assertHelp('help LIST') self.assertHelp('help LIST')
@ -122,9 +125,6 @@ class MiscTestCase(ChannelPluginTestCase, PluginDocumentation):
self.assertNotError('upkeep') self.assertNotError('upkeep')
self.assertNotError('logfilesize') self.assertNotError('logfilesize')
def testGetprefixchar(self):
self.assertNotError('getprefixchar')
def testPlugin(self): def testPlugin(self):
self.assertResponse('plugin plugin', 'Misc') self.assertResponse('plugin plugin', 'Misc')

View File

@ -103,43 +103,6 @@ class OwnerTestCase(PluginTestCase, PluginDocumentation):
self.assertNotError('load ALIAS') self.assertNotError('load ALIAS')
self.assertNotError('unload ALIAS') self.assertNotError('unload ALIAS')
def testSetconf(self):
self.assertRegexp('setconf', 'confDir')
self.assertNotRegexp('setconf', 'allowEval')
self.assertResponse('setconf confDir',
'confDir is a string (%s).' % conf.confDir)
self.assertError('setconf whackyConfOption')
try:
originalConfAllowEval = conf.allowEval
conf.allowEval = False
self.assertError('setconf alsdkfj 100')
self.assertError('setconf poll "foo"')
try:
originalReplySuccess = conf.replySuccess
self.assertResponse('setconf replySuccess foo', 'foo')
self.assertResponse('setconf replySuccess "foo"', 'foo')
self.assertResponse('setconf replySuccess \'foo\'', 'foo')
finally:
conf.replySuccess = originalReplySuccess
try:
originalReplyWhenNotCommand = conf.replyWhenNotCommand
self.assertNotError('setconf replyWhenNotCommand True')
self.failUnless(conf.replyWhenNotCommand)
self.assertNotError('setconf replyWhenNotCommand False')
self.failIf(conf.replyWhenNotCommand)
self.assertNotError('setconf replyWhenNotCommand true')
self.failUnless(conf.replyWhenNotCommand)
self.assertNotError('setconf replyWhenNotCommand false')
self.failIf(conf.replyWhenNotCommand)
self.assertNotError('setconf replyWhenNotCommand 1')
self.failUnless(conf.replyWhenNotCommand)
self.assertNotError('setconf replyWhenNotCommand 0')
self.failIf(conf.replyWhenNotCommand)
finally:
conf.replyWhenNotCommand = originalReplyWhenNotCommand
finally:
conf.allowEval = originalConfAllowEval
class FunctionsTestCase(unittest.TestCase): class FunctionsTestCase(unittest.TestCase):
def testLoadPluginModule(self): def testLoadPluginModule(self):

View File

@ -48,6 +48,7 @@ class StatusTestCase(PluginTestCase, PluginDocumentation):
def testCpustats(self): def testCpustats(self):
m = self.assertNotError('status cpu') m = self.assertNotError('status cpu')
self.failIf('kB kB' in m.args[1])
self.failIf('None' in m.args[1], 'None in cpu output: %r.' % m) self.failIf('None' in m.args[1], 'None in cpu output: %r.' % m)
for s in ['linux', 'freebsd', 'openbsd', 'netbsd', 'darwin']: for s in ['linux', 'freebsd', 'openbsd', 'netbsd', 'darwin']:
if sys.platform.startswith(s): if sys.platform.startswith(s):

View File

@ -86,7 +86,7 @@ class TokenizerTestCase(unittest.TestCase):
def testPipe(self): def testPipe(self):
try: try:
conf.enablePipeSyntax = True conf.supybot.pipeSyntax.set('True')
self.assertRaises(SyntaxError, tokenize, '| foo') self.assertRaises(SyntaxError, tokenize, '| foo')
self.assertRaises(SyntaxError, tokenize, 'foo ||bar') self.assertRaises(SyntaxError, tokenize, 'foo ||bar')
self.assertRaises(SyntaxError, tokenize, 'bar |') self.assertRaises(SyntaxError, tokenize, 'bar |')
@ -100,7 +100,7 @@ class TokenizerTestCase(unittest.TestCase):
self.assertEqual(tokenize('foo bar | baz quux'), self.assertEqual(tokenize('foo bar | baz quux'),
['baz', 'quux', ['foo', 'bar']]) ['baz', 'quux', ['foo', 'bar']])
finally: finally:
conf.enablePipeSyntax = False conf.supybot.pipeSyntax.set('False')
def testBold(self): def testBold(self):
s = '\x02foo\x02' s = '\x02foo\x02'
@ -127,9 +127,9 @@ class FunctionsTestCase(unittest.TestCase):
self.assertEqual('foobar--', callbacks.canonicalName('foobar--')) self.assertEqual('foobar--', callbacks.canonicalName('foobar--'))
def testAddressed(self): def testAddressed(self):
oldprefixchars = conf.prefixChars oldprefixchars = str(conf.supybot.prefixChars)
nick = 'supybot' nick = 'supybot'
conf.prefixChars = '~!@' conf.supybot.prefixChars.set('~!@')
inChannel = ['~foo', '@foo', '!foo', inChannel = ['~foo', '@foo', '!foo',
'%s: foo' % nick, '%s foo' % nick, '%s: foo' % nick, '%s foo' % nick,
'%s: foo' % nick.capitalize(), '%s: foo' % nick.upper()] '%s: foo' % nick.capitalize(), '%s: foo' % nick.upper()]
@ -142,7 +142,7 @@ class FunctionsTestCase(unittest.TestCase):
self.assertEqual('foo', callbacks.addressed(nick, msg), msg) self.assertEqual('foo', callbacks.addressed(nick, msg), msg)
msg = ircmsgs.privmsg(nick, 'foo') msg = ircmsgs.privmsg(nick, 'foo')
self.assertEqual('foo', callbacks.addressed(nick, msg)) self.assertEqual('foo', callbacks.addressed(nick, msg))
conf.prefixChars = oldprefixchars conf.supybot.prefixChars.set(oldprefixchars)
msg = ircmsgs.privmsg('#foo', '%s::::: bar' % nick) msg = ircmsgs.privmsg('#foo', '%s::::: bar' % nick)
self.assertEqual('bar', callbacks.addressed(nick, msg)) self.assertEqual('bar', callbacks.addressed(nick, msg))
msg = ircmsgs.privmsg('#foo', '%s: foo' % nick.upper()) msg = ircmsgs.privmsg('#foo', '%s: foo' % nick.upper())
@ -155,12 +155,12 @@ class FunctionsTestCase(unittest.TestCase):
msg2 = ircmsgs.privmsg('#foo', 'bar') msg2 = ircmsgs.privmsg('#foo', 'bar')
self.assertEqual(callbacks.addressed('blah', msg1), 'bar') self.assertEqual(callbacks.addressed('blah', msg1), 'bar')
try: try:
original = conf.replyWhenNotAddressed original = str(conf.supybot.reply.whenNotAddressed)
conf.replyWhenNotAddressed = True conf.supybot.reply.whenNotAddressed.set('True')
self.assertEqual(callbacks.addressed('blah', msg1), 'bar') self.assertEqual(callbacks.addressed('blah', msg1), 'bar')
self.assertEqual(callbacks.addressed('blah', msg2), 'bar') self.assertEqual(callbacks.addressed('blah', msg2), 'bar')
finally: finally:
conf.replyWhenNotAddressed = original conf.supybot.reply.whenNotAddressed.set(original)
def testReply(self): def testReply(self):
prefix = 'foo!bar@baz' prefix = 'foo!bar@baz'
@ -220,17 +220,17 @@ class PrivmsgTestCase(ChannelPluginTestCase):
def testErrorPrivateKwarg(self): def testErrorPrivateKwarg(self):
try: try:
originalConfErrorReplyPrivate = conf.errorReplyPrivate original = str(conf.supybot.reply.errorInPrivate)
conf.errorReplyPrivate = False conf.supybot.reply.errorInPrivate.set('False')
m = self.getMsg("eval irc.error('foo', private=True)") m = self.getMsg("eval irc.error('foo', private=True)")
self.failIf(ircutils.isChannel(m.args[0])) self.failIf(ircutils.isChannel(m.args[0]))
finally: finally:
conf.errorReplyPrivate = originalConfErrorReplyPrivate conf.supybot.reply.errorInPrivate.set(original)
def testErrorReplyPrivate(self): def testErrorReplyPrivate(self):
try: try:
originalConfErrorReplyPrivate = conf.errorReplyPrivate original = str(conf.supybot.reply.errorInPrivate)
conf.errorReplyPrivate = False conf.supybot.reply.errorInPrivate.set('False')
# If this doesn't raise an error, we've got a problem, so the next # If this doesn't raise an error, we've got a problem, so the next
# two assertions shouldn't run. So we first check that what we # two assertions shouldn't run. So we first check that what we
# expect to error actually does so we don't go on a wild goose # expect to error actually does so we don't go on a wild goose
@ -239,11 +239,11 @@ class PrivmsgTestCase(ChannelPluginTestCase):
self.assertError(s) self.assertError(s)
m = self.getMsg(s) m = self.getMsg(s)
self.failUnless(ircutils.isChannel(m.args[0])) self.failUnless(ircutils.isChannel(m.args[0]))
conf.errorReplyPrivate = True conf.supybot.reply.errorInPrivate.set('True')
m = self.getMsg(s) m = self.getMsg(s)
self.failIf(ircutils.isChannel(m.args[0])) self.failIf(ircutils.isChannel(m.args[0]))
finally: finally:
conf.errorReplyPrivate = originalConfErrorReplyPrivate conf.supybot.reply.errorInPrivate.set(original)
# Now for stuff not based on the plugins. # Now for stuff not based on the plugins.
class First(callbacks.Privmsg): class First(callbacks.Privmsg):
@ -300,12 +300,12 @@ class PrivmsgTestCase(ChannelPluginTestCase):
def testEmptyNest(self): def testEmptyNest(self):
try: try:
conf.replyWhenNotCommand = True conf.supybot.reply.whenNotCommand.set('True')
self.assertError('echo []') self.assertError('echo []')
conf.replyWhenNotCommand = False conf.supybot.reply.whenNotCommand.set('False')
self.assertResponse('echo []', '[]') self.assertResponse('echo []', '[]')
finally: finally:
conf.replyWhenNotCommand = False conf.supybot.reply.whenNotCommand.set('False')
def testDispatcherHelp(self): def testDispatcherHelp(self):
self.assertNotRegexp('help first', r'\(dispatcher') self.assertNotRegexp('help first', r'\(dispatcher')
@ -317,18 +317,6 @@ class PrivmsgTestCase(ChannelPluginTestCase):
self.assertError('first blah') self.assertError('first blah')
self.assertResponse('third foo bar baz', 'foo bar baz') self.assertResponse('third foo bar baz', 'foo bar baz')
def testConfigureHandlesNonCanonicalCommands(self):
# Note that this also ends up testing that the FakeIrc object in
# callbacks actually works.
try:
original = conf.commandsOnStart
tokens = callbacks.tokenize('Admin setprefixchar $')
conf.commandsOnStart = [tokens]
self.assertNotError('load Admin')
self.assertEqual(conf.prefixChars, '$')
finally:
conf.commandsOnStart = original
def testSyntaxErrorNotEscaping(self): def testSyntaxErrorNotEscaping(self):
self.assertError('load [foo') self.assertError('load [foo')
self.assertError('load foo]') self.assertError('load foo]')
@ -342,14 +330,14 @@ class PrivmsgTestCase(ChannelPluginTestCase):
def testInvalidCommandOneReplyOnly(self): def testInvalidCommandOneReplyOnly(self):
try: try:
original = conf.replyWhenNotCommand original = str(conf.supybot.reply.whenNotCommand)
conf.replyWhenNotCommand = True conf.supybot.reply.whenNotCommand.set('True')
self.assertRegexp('asdfjkl', 'not a valid command') self.assertRegexp('asdfjkl', 'not a valid command')
self.irc.addCallback(self.InvalidCommand()) self.irc.addCallback(self.InvalidCommand())
self.assertResponse('asdfjkl', 'foo') self.assertResponse('asdfjkl', 'foo')
self.assertNoResponse(' ', 2) self.assertNoResponse(' ', 2)
finally: finally:
conf.replyWhenNotCommand = original conf.supybot.reply.whenNotCommand.set(original)
class BadInvalidCommand(callbacks.Privmsg): class BadInvalidCommand(callbacks.Privmsg):
def invalidCommand(self, irc, msg, tokens): def invalidCommand(self, irc, msg, tokens):
@ -358,12 +346,12 @@ class PrivmsgTestCase(ChannelPluginTestCase):
def testBadInvalidCommandDoesNotKillAll(self): def testBadInvalidCommandDoesNotKillAll(self):
try: try:
original = conf.replyWhenNotCommand original = str(conf.supybot.reply.whenNotCommand)
conf.replyWhenNotCommand = True conf.supybot.reply.whenNotCommand.set('True')
self.irc.addCallback(self.BadInvalidCommand()) self.irc.addCallback(self.BadInvalidCommand())
self.assertRegexp('asdfjkl', 'not a valid command') self.assertRegexp('asdfjkl', 'not a valid command')
finally: finally:
conf.replyWhenNotCommand = original conf.supybot.reply.whenNotCommand.set(original)
class PrivmsgCommandAndRegexpTestCase(PluginTestCase): class PrivmsgCommandAndRegexpTestCase(PluginTestCase):

View File

@ -35,10 +35,20 @@ import os
import unittest import unittest
import conf import conf
import world
import ircdb import ircdb
import ircutils import ircutils
class FunctionsTestCase(unittest.TestCase): class IrcdbTestCase(unittest.TestCase):
def setUp(self):
world.testing = False
unittest.TestCase.setUp(self)
def tearDown(self):
world.testing = True
unittest.TestCase.tearDown(self)
class FunctionsTestCase(IrcdbTestCase):
def testIsAntiCapability(self): def testIsAntiCapability(self):
self.failIf(ircdb.isAntiCapability('foo')) self.failIf(ircdb.isAntiCapability('foo'))
self.failIf(ircdb.isAntiCapability('#foo.bar')) self.failIf(ircdb.isAntiCapability('#foo.bar'))
@ -188,7 +198,7 @@ class UserCapabilitySetTestCase(unittest.TestCase):
## self.failUnless(s.check('owner')) ## self.failUnless(s.check('owner'))
class IrcUserTestCase(unittest.TestCase): class IrcUserTestCase(IrcdbTestCase):
def testCapabilities(self): def testCapabilities(self):
u = ircdb.IrcUser() u = ircdb.IrcUser()
u.addCapability('foo') u.addCapability('foo')
@ -259,7 +269,7 @@ class IrcUserTestCase(unittest.TestCase):
u = ircdb.IrcUser(capabilities=('foo',)) u = ircdb.IrcUser(capabilities=('foo',))
self.assertRaises(KeyError, u.removeCapability, 'bar') self.assertRaises(KeyError, u.removeCapability, 'bar')
class IrcChannelTestCase(unittest.TestCase): class IrcChannelTestCase(IrcdbTestCase):
def testInit(self): def testInit(self):
c = ircdb.IrcChannel() c = ircdb.IrcChannel()
self.failIf(c.checkCapability('op')) self.failIf(c.checkCapability('op'))
@ -302,8 +312,9 @@ class IrcChannelTestCase(unittest.TestCase):
c.removeBan(banmask) c.removeBan(banmask)
self.failIf(c.checkIgnored(prefix)) self.failIf(c.checkIgnored(prefix))
class UsersDBTestCase(unittest.TestCase): class UsersDBTestCase(IrcdbTestCase):
filename = os.path.join(conf.confDir, 'UsersDBTestCase.conf') filename = os.path.join(conf.supybot.directories.conf(),
'UsersDBTestCase.conf')
def setUp(self): def setUp(self):
try: try:
os.remove(self.filename) os.remove(self.filename)
@ -352,8 +363,9 @@ class UsersDBTestCase(unittest.TestCase):
self.assertRaises(ValueError, self.users.setUser, id, u2) self.assertRaises(ValueError, self.users.setUser, id, u2)
class CheckCapabilityTestCase(unittest.TestCase): class CheckCapabilityTestCase(IrcdbTestCase):
filename = os.path.join(conf.confDir, 'CheckCapabilityTestCase.conf') filename = os.path.join(conf.supybot.directories.conf(),
'CheckCapabilityTestCase.conf')
owner = 'owner!owner@owner' owner = 'owner!owner@owner'
nothing = 'nothing!nothing@nothing' nothing = 'nothing!nothing@nothing'
justfoo = 'justfoo!justfoo@justfoo' justfoo = 'justfoo!justfoo@justfoo'
@ -455,9 +467,9 @@ class CheckCapabilityTestCase(unittest.TestCase):
def testNothing(self): def testNothing(self):
self.assertEqual(self.checkCapability(self.nothing, self.cap), self.assertEqual(self.checkCapability(self.nothing, self.cap),
conf.defaultAllow) conf.supybot.defaultAllow())
self.assertEqual(self.checkCapability(self.nothing, self.anticap), self.assertEqual(self.checkCapability(self.nothing, self.anticap),
not conf.defaultAllow) not conf.supybot.defaultAllow())
def testJustFoo(self): def testJustFoo(self):
self.failUnless(self.checkCapability(self.justfoo, self.cap)) self.failUnless(self.checkCapability(self.justfoo, self.cap))
@ -503,11 +515,11 @@ class CheckCapabilityTestCase(unittest.TestCase):
u.setAuth(self.securefoo) u.setAuth(self.securefoo)
self.users.setUser(id, u) self.users.setUser(id, u)
try: try:
originalConfDefaultAllow = conf.defaultAllow originalConfDefaultAllow = conf.supybot.defaultAllow()
conf.defaultAllow = False conf.supybot.defaultAllow.set('False')
self.failIf(self.checkCapability('a' + self.securefoo, self.cap)) self.failIf(self.checkCapability('a' + self.securefoo, self.cap))
finally: finally:
conf.defaultAllow = originalConfDefaultAllow conf.supybot.defaultAllow.set(str(originalConfDefaultAllow))
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -208,18 +208,20 @@ class IrcStateTestCase(unittest.TestCase):
prefix = 'nick!user@host' prefix = 'nick!user@host'
irc = FakeIrc() irc = FakeIrc()
def testHistory(self): def testHistory(self):
oldconfmaxhistory = conf.maxHistory oldconfmaxhistory = str(conf.supybot.maxHistoryLength)
conf.maxHistory = 10 conf.supybot.maxHistoryLength.set('10')
state = irclib.IrcState() state = irclib.IrcState()
for msg in msgs: for msg in msgs:
try: try:
state.addMsg(self.irc, msg) state.addMsg(self.irc, msg)
except Exception: except Exception:
pass pass
self.failIf(len(state.history) > conf.maxHistory) self.failIf(len(state.history)>conf.supybot.maxHistoryLength())
self.assertEqual(len(state.history), conf.maxHistory) self.assertEqual(len(state.history),
self.assertEqual(list(state.history), msgs[len(msgs)-conf.maxHistory:]) conf.supybot.maxHistoryLength())
conf.maxHistory = oldconfmaxhistory self.assertEqual(list(state.history),
msgs[len(msgs)-conf.supybot.maxHistoryLength():])
conf.supybot.maxHistoryLength.set(oldconfmaxhistory)
def testEmptyTopic(self): def testEmptyTopic(self):
state = irclib.IrcState() state = irclib.IrcState()
@ -392,8 +394,6 @@ class IrcCallbackTestCase(unittest.TestCase):
self.assertEqual(doCommandCatcher.L, commands) self.assertEqual(doCommandCatcher.L, commands)
def testFirstCommands(self): def testFirstCommands(self):
oldconfthrottle = conf.throttleTime
conf.throttleTime = 0
nick = 'nick' nick = 'nick'
user = 'user any user' user = 'user any user'
password = 'password' password = 'password'
@ -411,7 +411,6 @@ class IrcCallbackTestCase(unittest.TestCase):
msgs.pop() msgs.pop()
expected.insert(0, ircmsgs.password(password)) expected.insert(0, ircmsgs.password(password))
self.assertEqual(msgs, expected) self.assertEqual(msgs, expected)
conf.throttleTime = oldconfthrottle
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -112,15 +112,17 @@ class PluginTestCase(unittest.TestCase):
if self.__class__ in (PluginTestCase, ChannelPluginTestCase): if self.__class__ in (PluginTestCase, ChannelPluginTestCase):
# Necessary because there's a test in here that shouldn\'t run. # Necessary because there's a test in here that shouldn\'t run.
return return
conf.prefixChars = '@' conf.supybot.prefixChars.set('@')
conf.replyWhenNotCommand = False conf.supybot.reply.whenNotCommand.setValue(False)
self.myVerbose = world.myVerbose self.myVerbose = world.myVerbose
if self.cleanConfDir: if self.cleanConfDir:
for filename in os.listdir(conf.confDir): for filename in os.listdir(conf.supybot.directories.conf()):
os.remove(os.path.join(conf.confDir, filename)) os.remove(os.path.join(conf.supybot.directories.conf(),
filename))
if self.cleanDataDir: if self.cleanDataDir:
for filename in os.listdir(conf.dataDir): for filename in os.listdir(conf.supybot.directories.data()):
os.remove(os.path.join(conf.dataDir, filename)) os.remove(os.path.join(conf.supybot.directories.data(),
filename))
ircdb.users.reload() ircdb.users.reload()
ircdb.channels.reload() ircdb.channels.reload()
if self.plugins is None: if self.plugins is None:
@ -130,10 +132,10 @@ class PluginTestCase(unittest.TestCase):
self.irc = irclib.Irc(nick) self.irc = irclib.Irc(nick)
while self.irc.takeMsg(): while self.irc.takeMsg():
pass pass
OwnerModule = Owner.loadPluginModule('Owner') #OwnerModule = Owner.loadPluginModule('Owner')
MiscModule = OwnerModule.loadPluginModule('Misc') MiscModule = Owner.loadPluginModule('Misc')
_ = OwnerModule.loadPluginClass(self.irc, OwnerModule) _ = Owner.loadPluginClass(self.irc, Owner)
_ = OwnerModule.loadPluginClass(self.irc, MiscModule) _ = Owner.loadPluginClass(self.irc, MiscModule)
if isinstance(self.plugins, str): if isinstance(self.plugins, str):
self.plugins = [self.plugins] self.plugins = [self.plugins]
else: else:
@ -300,8 +302,8 @@ class ChannelPluginTestCase(PluginTestCase):
timeout = self.timeout timeout = self.timeout
if self.myVerbose: if self.myVerbose:
print # Newline, just like PluginTestCase. print # Newline, just like PluginTestCase.
if query[0] not in conf.prefixChars: if query[0] not in conf.supybot.prefixChars():
query = conf.prefixChars[0] + query query = conf.supybot.prefixChars()[0] + query
msg = ircmsgs.privmsg(to, query, prefix=frm) msg = ircmsgs.privmsg(to, query, prefix=frm)
if self.myVerbose: if self.myVerbose:
print 'Feeding: %r' % msg print 'Feeding: %r' % msg