diff --git a/ChangeLog b/ChangeLog
index 3b8f195fe..5b25998e5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
to require a msg object.
diff --git a/__init__.py b/__init__.py
index 859d86755..e33a1b18a 100644
--- a/__init__.py
+++ b/__init__.py
@@ -40,5 +40,11 @@ othersDir = os.path.join(installDir, 'others')
sys.path.insert(0, srcDir)
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:
diff --git a/plugins/Alias.py b/plugins/Alias.py
index 62eea9322..6e5483992 100644
--- a/plugins/Alias.py
+++ b/plugins/Alias.py
@@ -156,7 +156,7 @@ def makeNewAlias(name, alias):
class Alias(callbacks.Privmsg):
def __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]}
self.aliases = structures.PersistentDictionary(filename)
@@ -207,7 +207,7 @@ class Alias(callbacks.Privmsg):
def addAlias(self, irc, name, alias, lock=False):
if self._invalidCharsRe.search(name):
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.'
realName = callbacks.canonicalName(name)
if name != realName:
diff --git a/plugins/Bugzilla.py b/plugins/Bugzilla.py
index 5ae0b3f47..880994e18 100644
--- a/plugins/Bugzilla.py
+++ b/plugins/Bugzilla.py
@@ -67,7 +67,7 @@ priorityKeys = ['p1', 'p2', 'p3', 'p4', 'p5', 'Low', 'Normal', 'High',
severityKeys = ['enhancement', 'trivial', 'minor', 'normal', 'major',
'critical', 'blocker']
-dbfilename = os.path.join(conf.dataDir, 'Bugzilla.db')
+dbfilename = os.path.join(conf.supybot.directories.data(), 'Bugzilla.db')
def makeDb(filename):
if os.path.exists(filename):
diff --git a/plugins/ChannelLogger.py b/plugins/ChannelLogger.py
index 18cf3b1ef..15a7e90db 100644
--- a/plugins/ChannelLogger.py
+++ b/plugins/ChannelLogger.py
@@ -91,7 +91,8 @@ class ChannelLogger(irclib.IrcCallback):
return self.logs[channel]
else:
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
return log
except IOError:
@@ -99,7 +100,7 @@ class ChannelLogger(irclib.IrcCallback):
return StringIO()
def timestamp(self, log):
- log.write(time.strftime(conf.logTimestampFormat))
+ log.write(time.strftime(conf.supybot.log.timestampFormat()))
log.write(' ')
def doPrivmsg(self, irc, msg):
diff --git a/plugins/DCC.py b/plugins/DCC.py
index 70b06313a..883389c4e 100644
--- a/plugins/DCC.py
+++ b/plugins/DCC.py
@@ -71,8 +71,8 @@ class DCC(callbacks.Privmsg):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(60)
host = ircutils.hostFromHostmask(irc.prefix)
- if conf.externalIP is not None:
- ip = conf.externalIP
+ if conf.supybot.externalIP():
+ ip = conf.supybot.externalIP()
else:
try:
ip = socket.gethostbyname(host)
diff --git a/plugins/Debian.py b/plugins/Debian.py
index da99f1d9b..d0deee3e8 100644
--- a/plugins/Debian.py
+++ b/plugins/Debian.py
@@ -93,7 +93,7 @@ class Debian(callbacks.Privmsg,
'debian/dists/unstable/Contents-i386.gz',
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(
[('python-zegrep', configurable.BoolType, False,
"""An advanced option, mostly just for testing; uses a Python-coded
diff --git a/plugins/Dunno.py b/plugins/Dunno.py
index 46d6f835f..e4f3def30 100644
--- a/plugins/Dunno.py
+++ b/plugins/Dunno.py
@@ -51,7 +51,7 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at '
-dbfilename = os.path.join(conf.dataDir, 'Dunno.db')
+dbfilename = os.path.join(conf.supybot.directories.data(), 'Dunno.db')
def configure(onStart, afterConnect, advanced):
# This will be called by setup.py to configure this module. onStart and
diff --git a/plugins/Ebay.py b/plugins/Ebay.py
index fd7393fd6..8f048573d 100644
--- a/plugins/Ebay.py
+++ b/plugins/Ebay.py
@@ -172,7 +172,8 @@ class Ebay(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
return '; '.join(resp)
else:
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
diff --git a/plugins/Enforcer.py b/plugins/Enforcer.py
index dcbf8580c..b6eaf9a72 100644
--- a/plugins/Enforcer.py
+++ b/plugins/Enforcer.py
@@ -134,7 +134,7 @@ class Enforcer(callbacks.Privmsg, configurable.Mixin):
irc.queueMsg(ircmsgs.topic(channel, self.topics[channel]))
if self.configurables.get('revenge', channel):
irc.queueMsg(ircmsgs.kick(channel, msg.nick,
- conf.replyNoCapability %
+ conf.supybot.replies.noCapability() %
_chanCap(channel, 'topic')))
else:
self.topics[channel] = msg.args[1]
diff --git a/plugins/Factoids.py b/plugins/Factoids.py
index b2666c032..66d55bbfd 100644
--- a/plugins/Factoids.py
+++ b/plugins/Factoids.py
@@ -319,7 +319,7 @@ class Factoids(plugins.ChannelDBHandler,
counter = 0
for (added_by, added_at) in factoids:
counter += 1
- added_at = time.strftime(conf.humanTimestampFormat,
+ added_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at)))
L.append('#%s was added by %s at %s' % (counter,added_by,added_at))
factoids = '; '.join(L)
diff --git a/plugins/Gameknot.py b/plugins/Gameknot.py
index 6fbca7ffb..10abe0e01 100644
--- a/plugins/Gameknot.py
+++ b/plugins/Gameknot.py
@@ -159,7 +159,7 @@ class Gameknot(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
raise callbacks.Error, 'No user %s exists.' % name
else:
raise callbacks.Error,'The format of the page was odd. %s' % \
- conf.replyPossibleBug
+ conf.supybot.replies.possibleBug()
except urllib2.URLError:
raise callbacks.Error, 'Couldn\'t connect to gameknot.com'
diff --git a/plugins/Herald.py b/plugins/Herald.py
index c62b93712..f28fd7f0f 100644
--- a/plugins/Herald.py
+++ b/plugins/Herald.py
@@ -63,11 +63,13 @@ class HeraldDB(object):
def __init__(self):
self.heralds = {}
self.open()
+ dataDir = conf.supybot.directories.data()
+ self.filename = os.path.join(dataDir, 'Herald.db')
def open(self):
- filename = os.path.join(conf.dataDir, 'Herald.db')
- if os.path.exists(filename):
- fd = file(filename)
+ dataDir = conf.supybot.directories.data()
+ if os.path.exists(self.filename):
+ fd = file(self.filename)
for line in fd:
line = line.rstrip()
try:
@@ -81,7 +83,7 @@ class HeraldDB(object):
fd.close()
def close(self):
- fd = file(os.path.join(conf.dataDir, 'Herald.db'), 'w')
+ fd = file(self.filename, 'w')
L = self.heralds.items()
L.sort()
for ((id, channel), msg) in L:
diff --git a/plugins/Infobot.py b/plugins/Infobot.py
index ed43e38e4..c27da2385 100755
--- a/plugins/Infobot.py
+++ b/plugins/Infobot.py
@@ -52,7 +52,7 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at '
-dbfilename = os.path.join(conf.dataDir, 'Infobot.db')
+dbfilename = os.path.join(conf.supybot.directories.data(), 'Infobot.db')
def configure(onStart, afterConnect, advanced):
from questions import expect, anything, something, yn
diff --git a/plugins/Lookup.py b/plugins/Lookup.py
index 4cb70aff5..ed7a25d85 100644
--- a/plugins/Lookup.py
+++ b/plugins/Lookup.py
@@ -64,13 +64,16 @@ def configure(onStart, afterConnect, advanced):
onStart.append('load Lookup')
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"'
+ ### 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 '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.'
while yn('Would you like to add a file?') == 'y':
filename = something('What\'s the filename?')
try:
- fd = file(os.path.join(conf.dataDir, filename))
+ dataDir = conf.supybot.directories.data()
+ fd = file(os.path.join(dataDir, filename))
except EnvironmentError, e:
print 'I couldn\'t open that file: %s' % e
continue
@@ -97,7 +100,8 @@ class Lookup(callbacks.Privmsg):
def __init__(self):
callbacks.Privmsg.__init__(self)
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):
return utils.ellipsisify(s, 50)
@@ -132,8 +136,8 @@ class Lookup(callbacks.Privmsg):
Adds a lookup for with the key/value pairs specified in the
colon-delimited file specified by . is searched
- for in conf.dataDir. If is not singular, we try to make it
- singular before creating the command.
+ for in conf.supybot.directories.data. If is not singular, we
+ try to make it singular before creating the command.
"""
(name, filename) = privmsgs.getArgs(args, required=2)
name = utils.depluralize(name)
@@ -151,7 +155,8 @@ class Lookup(callbacks.Privmsg):
except sqlite.DatabaseError:
# Good, there's no such database.
try:
- filename = os.path.join(conf.dataDir, filename)
+ dataDir = conf.supybot.directories.data()
+ filename = os.path.join(dataDir, filename)
fd = file(filename)
except EnvironmentError, e:
irc.error('Could not open %s: %s' % (filename, e.args[1]))
diff --git a/plugins/MoobotFactoids.py b/plugins/MoobotFactoids.py
index 8640727f0..9a4e0d104 100644
--- a/plugins/MoobotFactoids.py
+++ b/plugins/MoobotFactoids.py
@@ -65,7 +65,7 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at '
-dbfilename = os.path.join(conf.dataDir, 'MoobotFactoids')
+dbfilename = os.path.join(conf.supybot.directories.data(), 'MoobotFactoids')
def configure(onStart, afterConnect, advanced):
# This will be called by setup.py to configure this module. onStart and
@@ -421,19 +421,19 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp):
# First, creation info.
# Map the integer created_by to the username
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)))
s += "Created by %s on %s." % (creat_by, creat_at)
# Next, modification info, if any.
if modified_by is not None:
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)))
s += " Last modified by %s on %s." % (mod_by, mod_at)
# Next, last requested info, if any
if last_requested_by is not None:
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)))
req_count = requested_count
times_str = utils.nItems('time', requested_count)
@@ -441,7 +441,7 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp):
(last_by, last_at, times_str)
# Last, locked info
if locked_at is not None:
- lock_at = time.strftime(conf.humanTimestampFormat,
+ lock_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(locked_at)))
lock_by = ircdb.users.getUser(locked_by).name
s += " Locked by %s on %s." % (lock_by, lock_at)
diff --git a/plugins/News.py b/plugins/News.py
index e25e59f45..cff2053cd 100644
--- a/plugins/News.py
+++ b/plugins/News.py
@@ -139,14 +139,14 @@ class News(plugins.ChannelDBHandler, callbacks.Privmsg):
if int(expires_at) == 0:
s = '%s (Subject: "%s", added by %s on %s)' % \
(item, subject, added_by,
- time.strftime(conf.humanTimestampFormat,
+ time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at))))
else:
s = '%s (Subject: "%s", added by %s on %s, expires at %s)' % \
(item, subject, added_by,
- time.strftime(conf.humanTimestampFormat,
+ time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at))),
- time.strftime(conf.humanTimestampFormat,
+ time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(expires_at))))
irc.reply(s)
diff --git a/plugins/Note.py b/plugins/Note.py
index 7fa002ec0..72582f31a 100644
--- a/plugins/Note.py
+++ b/plugins/Note.py
@@ -57,8 +57,6 @@ except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at '
-dbfilename = os.path.join(conf.dataDir, 'Notes.db')
-
class NoteDb(plugins.DBHandler):
def makeDb(self, filename):
"create Notes database and tables"
@@ -85,7 +83,8 @@ class NoteDb(plugins.DBHandler):
class Note(callbacks.Privmsg):
def __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):
db = self.dbHandler.getDb()
diff --git a/plugins/QuoteGrabs.py b/plugins/QuoteGrabs.py
index 387df55bb..8519eda4b 100644
--- a/plugins/QuoteGrabs.py
+++ b/plugins/QuoteGrabs.py
@@ -256,7 +256,7 @@ class QuoteGrabs(plugins.ChannelDBHandler,
irc.error('No quotegrab for id %r' % id)
return
quote, hostmask, timestamp = cursor.fetchone()
- time_str = time.strftime(conf.humanTimestampFormat,
+ time_str = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(float(timestamp)))
irc.reply('%s (Said by: %s on %s)' % (quote, hostmask, time_str))
diff --git a/plugins/Quotes.py b/plugins/Quotes.py
index 0d7760561..57d038f32 100644
--- a/plugins/Quotes.py
+++ b/plugins/Quotes.py
@@ -220,7 +220,7 @@ class Quotes(plugins.ChannelDBHandler, callbacks.Privmsg):
cursor.execute("""SELECT * FROM quotes WHERE id=%s""", id)
if cursor.rowcount == 1:
(id, added_by, added_at, quote) = cursor.fetchone()
- timestamp = time.strftime(conf.humanTimestampFormat,
+ timestamp = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at)))
irc.reply('Quote %r added by %s at %s.' %
(quote, added_by, timestamp))
diff --git a/plugins/RawLogger.py b/plugins/RawLogger.py
index 6ab6cab56..466f0c6cc 100644
--- a/plugins/RawLogger.py
+++ b/plugins/RawLogger.py
@@ -30,7 +30,7 @@
###
"""
-Logs raw IRC messages to a file, conf.dataDir/raw.log
+Logs raw IRC messages to a file.
"""
__revision__ = "$Id$"
@@ -47,7 +47,8 @@ import irclib
###
class RawLogger(irclib.IrcCallback):
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)
def inFilter(self, irc, msg):
diff --git a/plugins/Relay.py b/plugins/Relay.py
index d12d5ac0f..b647abbbd 100644
--- a/plugins/Relay.py
+++ b/plugins/Relay.py
@@ -452,7 +452,7 @@ class Relay(callbacks.Privmsg, configurable.Mixin):
channels = utils.commaAndify(L)
if '317' in d:
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])))
else:
idle = ''
diff --git a/plugins/Sourceforge.py b/plugins/Sourceforge.py
index fa4ce136a..5b0406c03 100644
--- a/plugins/Sourceforge.py
+++ b/plugins/Sourceforge.py
@@ -170,7 +170,7 @@ class Sourceforge(callbacks.PrivmsgCommandAndRegexp, configurable.Mixin):
resp = imap(lambda s: utils.ellipsisify(s, 50), resp)
return '%s' % utils.commaAndify(resp)
raise callbacks.Error, 'No Trackers were found. (%s)' % \
- conf.replyPossibleBug
+ conf.supybot.replies.possibleBug()
except webutils.WebError, e:
raise callbacks.Error, str(e)
diff --git a/plugins/Status.py b/plugins/Status.py
index 6c882eb1c..6f860404b 100644
--- a/plugins/Status.py
+++ b/plugins/Status.py
@@ -61,7 +61,8 @@ def configure(onStart, afterConnect, advanced):
class UptimeDB(object):
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):
fd = file(self.filename)
s = fd.read()
@@ -137,9 +138,9 @@ class Status(callbacks.Privmsg):
return
def format((started, ended)):
return '%s until %s; up for %s' % \
- (time.strftime(conf.humanTimestampFormat,
+ (time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(started)),
- time.strftime(conf.humanTimestampFormat,
+ time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(ended)),
utils.timeElapsed(ended-started))
irc.reply(utils.commaAndify(imap(format, L)))
@@ -186,7 +187,7 @@ class Status(callbacks.Privmsg):
try:
r = os.popen('ps -o rss -p %s' % pid)
r.readline() # VSZ Header.
- mem = r.readline().strip() + ' kB'
+ mem = r.readline().strip()
finally:
r.close()
elif sys.platform.startswith('netbsd'):
diff --git a/plugins/Todo.py b/plugins/Todo.py
index 7ebc7ef7d..2f8d6e3c4 100644
--- a/plugins/Todo.py
+++ b/plugins/Todo.py
@@ -87,7 +87,8 @@ class TodoDB(plugins.DBHandler):
class Todo(callbacks.Privmsg):
def __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):
self.dbHandler.die()
@@ -170,7 +171,7 @@ class Todo(callbacks.Privmsg):
active = 'Inactive'
if pri:
task += ', priority: %s' % pri
- added_at = time.strftime(conf.humanTimestampFormat,
+ added_at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(int(added_at)))
s = "%s todo for %s: %s (Added at %s)" % \
(active, name, task, added_at)
diff --git a/plugins/URL.py b/plugins/URL.py
index d91324995..659c9c343 100644
--- a/plugins/URL.py
+++ b/plugins/URL.py
@@ -260,7 +260,7 @@ class URL(callbacks.PrivmsgCommandAndRegexp,
return (tinyurl, updateDb)
def _formatUrl(self, url, added, addedBy):
- when = time.strftime(conf.humanTimestampFormat,
+ when = time.strftime(conf.supybot.supybot.humanTimestampFormat(),
time.localtime(int(added)))
return '<%s> (added by %s at %s)' % (url, addedBy, when)
diff --git a/plugins/Words.py b/plugins/Words.py
index 612551a22..52b481639 100644
--- a/plugins/Words.py
+++ b/plugins/Words.py
@@ -160,9 +160,10 @@ class Words(callbacks.Privmsg, configurable.Mixin):
def __init__(self):
callbacks.Privmsg.__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:
- dictfile = os.path.join(conf.dataDir, 'dict')
+ dictfile = os.path.join(dataDir, 'dict')
self.wordList = file(dictfile).readlines()
self.gotDictFile = True
except IOError:
@@ -367,31 +368,7 @@ class Words(callbacks.Privmsg, configurable.Mixin):
Class = Words
-
-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()
+### TODO: Write a script to make the database.
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
diff --git a/scripts/supybot b/scripts/supybot
index 7ba3efbde..bf6d43b2b 100755
--- a/scripts/supybot
+++ b/scripts/supybot
@@ -38,6 +38,7 @@ __revision__ = "$Id$"
import re
import os
import sys
+import atexit
if sys.version_info < (2, 3, 0):
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',
dest='password', default='',
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()
@@ -128,12 +133,12 @@ if __name__ == '__main__':
log.info('Finished writing registry file.')
atexit.register(closeRegistry)
- nick = options.nick or conf.supybot.nick.get()
- user = options.user or conf.supybot.user.get()
- ident = options.ident or conf.supybot.ident.get()
- password = options.password or conf.supybot.password.get()
+ nick = options.nick or conf.supybot.nick()
+ user = options.user or conf.supybot.user()
+ ident = options.ident or conf.supybot.ident()
+ 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:
serverAndPort = server.split(':', 1)
serverAndPort[1] = int(serverAndPort[1])
@@ -150,12 +155,15 @@ if __name__ == '__main__':
except ImportError:
log.warning('Psyco isn\'t installed, cannot -OO.')
- if not os.path.exists(conf.supybot.directories.log.get()):
- os.mkdir(conf.supybot.directories.log.get())
- 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.data.get()):
- os.mkdir(conf.supybot.directories.data.get())
+ if options.allowEval:
+ conf.allowEval = True
+
+ if not os.path.exists(conf.supybot.directories.log()):
+ os.mkdir(conf.supybot.directories.log())
+ 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 ircmsgs
diff --git a/setup.py b/setup.py
index ee14017e6..690403b85 100644
--- a/setup.py
+++ b/setup.py
@@ -89,7 +89,8 @@ setup(
'supybot.others.unum': os.path.join('others', 'unum'),
'supybot.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-newplugin']
)
diff --git a/src/Admin.py b/src/Admin.py
index 9178d059e..09eaa8e0b 100755
--- a/src/Admin.py
+++ b/src/Admin.py
@@ -60,6 +60,10 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
def __init__(self):
privmsgs.CapabilityCheckingPrivmsg.__init__(self)
self.joins = {}
+
+ def do376(self, irc, msg):
+ irc.queueMsg(ircmsgs.joins(conf.supybot.channels()))
+ do422 = do377 = do376
def do471(self, irc, msg):
try:
@@ -107,7 +111,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
def doInvite(self, irc, msg):
if msg.args[1] not in irc.state.channels:
- if conf.alwaysJoinOnInvite:
+ if conf.supybot.alwaysJoinOnInvite():
irc.queueMsg(ircmsgs.join(msg.args[1]))
else:
if ircdb.checkCapability(msg.prefix, 'admin'):
@@ -182,16 +186,14 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
if command in ('enable', 'identify'):
irc.error('You can\'t disable %s!' % command)
else:
- # This has to know that defaultCapabilties gets turned into a
- # dictionary.
try:
capability = ircdb.makeAntiCapability(command)
except ValueError:
irc.error('%r is not a valid command.' % command)
return
- if command in conf.defaultCapabilities:
- conf.defaultCapabilities.remove(command)
- conf.defaultCapabilities.add(capability)
+ if command in conf.supybot.defaultCapabilities():
+ conf.supybot.defaultCapabilities().remove(command)
+ conf.supybot.defaultCapabilities().add(capability)
irc.replySuccess()
def enable(self, irc, msg, args):
@@ -205,8 +207,8 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
except ValueError:
irc.error('%r is not a valid command.' % command)
return
- if anticapability in conf.defaultCapabilities:
- conf.defaultCapabilities.remove(anticapability)
+ if anticapability in conf.supybot.defaultCapabilities():
+ conf.supybot.defaultCapabilities().remove(anticapability)
irc.replySuccess()
else:
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
# can only give out capabilities they have themselves (which will
- # depend on both conf.defaultAllow and conf.defaultCapabilities), but
- # generally means they can't mess with channel capabilities.
+ # depend on both conf.supybot.defaultAllow and
+ # conf.supybot.defaultCapabilities), but generally means they can't
+ # mess with channel capabilities.
(name, capability) = privmsgs.getArgs(args, required=2)
if capability == 'owner':
- irc.error('The "owner" capability can\'t be added in the bot.'
- ' Use the supybot-adduser program (or edit the '
- 'users.conf file yourself) to add an owner '
- 'capability.')
+ irc.error('The "owner" capability can\'t be added in the bot. '
+ 'Use the supybot-adduser program (or edit the '
+ 'users.conf file yourself) to add an owner capability.')
return
if ircdb.checkCapability(msg.prefix, capability) or \
'-' in capability:
@@ -292,7 +294,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
except KeyError:
irc.error('I can\'t find a hostmask for %s' % arg)
return
- conf.ignores.append(hostmask)
+ conf.supybot.ignores().append(hostmask)
irc.replySuccess()
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)
return
try:
- conf.ignores.remove(hostmask)
- while hostmask in conf.ignores:
- conf.ignores.remove(hostmask)
+ conf.supybot.ignores().remove(hostmask)
+ while hostmask in conf.supybot.ignores():
+ conf.supybot.ignores().remove(hostmask)
irc.replySuccess()
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):
"""takes no arguments
Returns the hostmasks currently being globally ignored.
"""
- if conf.ignores:
- irc.reply(utils.commaAndify(imap(repr, conf.ignores)))
+ if conf.supybot.ignores():
+ irc.reply(utils.commaAndify(imap(repr, conf.supybot.ignores())))
else:
irc.reply('I\'m not currently globally ignoring anyone.')
- def setprefixchar(self, irc, msg, args):
- """
-
- 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):
"""
diff --git a/src/Config.py b/src/Config.py
index e35198995..52c7f2abe 100644
--- a/src/Config.py
+++ b/src/Config.py
@@ -37,6 +37,8 @@ import plugins
import conf
import utils
+import ircdb
+import ircutils
import registry
import privmsgs
import callbacks
@@ -50,7 +52,7 @@ class InvalidRegistryName(callbacks.Error):
pass
def getWrapper(name):
- parts = name.split()
+ parts = name.split('.')
if not parts or parts[0] != 'supybot':
raise InvalidRegistryName, name
group = conf.supybot
@@ -62,13 +64,25 @@ def getWrapper(name):
raise InvalidRegistryName, name
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):
def callCommand(self, method, irc, msg, *L):
try:
- callbacks.Privmsg.callCommand(method, irc, msg, *L)
+ callbacks.Privmsg.callCommand(self, method, irc, msg, *L)
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:
irc.error(str(e))
@@ -81,8 +95,11 @@ class Config(callbacks.Privmsg):
name = privmsgs.getArgs(args)
group = getWrapper(name)
if hasattr(group, 'getValues'):
- L = zip(*group.getValues())[0]
- irc.reply(utils.commaAndify(L))
+ try:
+ L = zip(*group.getValues())[0]
+ irc.reply(utils.commaAndify(L))
+ except TypeError:
+ irc.error('There don\'t seem to be any values in %r' % name)
else:
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 to .
"""
(name, value) = privmsgs.getArgs(args, required=2)
- wrapper = getWrapper(name)
- wrapper.set(value)
- irc.replySuccess()
+ capability = getCapability(name)
+ if ircdb.checkCapability(msg.prefix, capability):
+ wrapper = getWrapper(name)
+ wrapper.set(value)
+ irc.replySuccess()
+ else:
+ irc.errorNoCapability(capability)
def help(self, irc, msg, args):
"""
@@ -112,7 +133,7 @@ class Config(callbacks.Privmsg):
"""
name = privmsgs.getArgs(args)
wrapper = getWrapper(name)
- irc.reply(msg, wrapper.help)
+ irc.reply(wrapper.help)
def reset(self, irc, msg, args):
"""
@@ -120,9 +141,13 @@ class Config(callbacks.Privmsg):
Resets the configuration variable to its original value.
"""
name = privmsgs.getArgs(args)
- wrapper = getWrapper(name)
- wrapper.reset()
- irc.replySuccess()
+ capability = getCapability(name)
+ if ircdb.checkCapability(msg.prefix, capability):
+ wrapper = getWrapper(name)
+ wrapper.reset()
+ irc.replySuccess()
+ else:
+ irc.errorNoCapability(capability)
def default(self, irc, msg, args):
"""
diff --git a/src/Misc.py b/src/Misc.py
index 6ddc97563..d875707ae 100755
--- a/src/Misc.py
+++ b/src/Misc.py
@@ -54,7 +54,7 @@ class Misc(callbacks.Privmsg):
priority = sys.maxint
def invalidCommand(self, irc, msg, tokens):
self.log.debug('Misc.invalidCommand called (tokens %s)', tokens)
- if conf.replyWhenNotCommand:
+ if conf.supybot.reply.whenNotCommand():
command = tokens and tokens[0] or ''
irc.error('%r is not a valid command.' % command)
else:
@@ -146,7 +146,7 @@ class Misc(callbacks.Privmsg):
cb = irc.getCallback(args[0])
if cb is not None:
command = callbacks.canonicalName(privmsgs.getArgs(args[1:]))
- command = command.lstrip(conf.prefixChars)
+ command = command.lstrip(conf.supybot.prefixChars())
name = ' '.join(args)
if hasattr(cb, 'isCommand') and cb.isCommand(command):
method = getattr(cb, command)
@@ -158,7 +158,7 @@ class Misc(callbacks.Privmsg):
return
command = callbacks.canonicalName(privmsgs.getArgs(args))
# Users might expect "@help @list" to work.
- command = command.lstrip(conf.prefixChars)
+ command = command.lstrip(conf.supybot.prefixChars())
cbs = callbacks.findCallbackForCommand(irc, command)
if len(cbs) > 1:
tokens = [command]
@@ -235,7 +235,7 @@ class Misc(callbacks.Privmsg):
except:
self.log.exception('Couldn\'t get id string: %r', s)
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.
if hasattr(module, '__revision__'):
if 'supybot' in module.__file__:
@@ -268,7 +268,8 @@ class Misc(callbacks.Privmsg):
return
filenameArg = os.path.basename(filenameArg)
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 in filenames:
filename = os.path.join(dirname, filenameArg)
@@ -284,13 +285,6 @@ class Misc(callbacks.Privmsg):
else:
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):
"""
diff --git a/src/Owner.py b/src/Owner.py
index 4ced38946..8f37ee956 100644
--- a/src/Owner.py
+++ b/src/Owner.py
@@ -55,6 +55,7 @@ import irclib
import ircmsgs
import drivers
import privmsgs
+import registry
import callbacks
class Deprecated(ImportError):
@@ -63,7 +64,8 @@ class Deprecated(ImportError):
def loadPluginModule(name, ignoreDeprecation=False):
"""Loads (and returns) the module for the plugin with the given name."""
files = []
- for dir in conf.pluginDirs:
+ pluginDirs = conf.supybot.directories.plugins()
+ for dir in pluginDirs:
try:
files.extend(os.listdir(dir))
except EnvironmentError: # OSError, IOError superclass.
@@ -74,7 +76,7 @@ def loadPluginModule(name, ignoreDeprecation=False):
name = os.path.splitext(files[index])[0]
except ValueError: # We'd rather raise the ImportError, so we'll let go...
pass
- moduleInfo = imp.find_module(name, conf.pluginDirs)
+ moduleInfo = imp.find_module(name, pluginDirs)
module = imp.load_module(name, *moduleInfo)
if 'deprecated' in module.__dict__ and module.deprecated:
if ignoreDeprecation:
@@ -91,8 +93,14 @@ def loadPluginClass(irc, module):
callback = module.Class()
assert not irc.getCallback(callback.name())
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):
# This plugin must be first; its priority must be lowest; otherwise odd
@@ -107,6 +115,27 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
'capabilities': 'User',
'addcapability': '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):
"""Disambiguates the given tokens based on the plugins loaded and
@@ -227,7 +256,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
except Exception, e:
irc.reply(utils.exnToString(e))
else:
- irc.error(conf.replyEvalNotAllowed)
+ irc.error('You must enable conf.allowEval for that to work.')
def _exec(self, irc, msg, args):
"""
@@ -242,77 +271,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
except Exception, e:
irc.reply(utils.exnToString(e))
else:
- irc.error(conf.replyEvalNotAllowed)
-
- def setconf(self, irc, msg, args):
- """[ []]
-
- Lists adjustable variables in the conf-module by default, shows the
- variable type with only the argument and sets the value of the
- variable to 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):
- """
-
- 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):
- """
-
- Unsets the default capability for any command.
- """
- capability = callbacks.canonicalName(privmsgs.getArgs(args))
- conf.defaultCapabilities.remove(capability)
- irc.replySuccess()
+ irc.error('You must enable conf.allowEval for that to work.')
def ircquote(self, irc, msg, args):
"""
@@ -357,9 +316,9 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
"""[--deprecated]
Loads the plugin from any of the directories in
- conf.pluginDirs; usually this includes the main installed directory
- and 'plugins' in the current directory. --deprecated is necessary
- if you wish to load deprecated plugins.
+ conf.supybot.directories.plugins; usually this includes the main
+ installed directory and 'plugins' in the current directory.
+ --deprecated is necessary if you wish to load deprecated plugins.
"""
(optlist, args) = getopt.getopt(args, '', ['deprecated'])
ignoreDeprecation = False
@@ -385,6 +344,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
irc.error(utils.exnToString(e))
return
loadPluginClass(irc, module)
+ registerPlugin(name, True)
irc.replySuccess()
def reload(self, irc, msg, args):
@@ -429,6 +389,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
callback.die()
del callback
gc.collect()
+ registerPlugin(name, False)
irc.replySuccess()
else:
irc.error('There was no callback %s' % name)
@@ -436,8 +397,8 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
def reconf(self, irc, msg, args):
"""takes no arguments
- Reloads the configuration files in conf.dataDir: conf/users.conf and
- conf/channels.conf, by default.
+ Reloads the configuration files for the user and channel databases:
+ conf/users.conf and conf/channels.conf, by default.
"""
ircdb.users.reload()
ircdb.channels.reload()
diff --git a/src/User.py b/src/User.py
index 30794012b..5f1bab3dd 100755
--- a/src/User.py
+++ b/src/User.py
@@ -53,7 +53,7 @@ import callbacks
class User(callbacks.Privmsg):
def _checkNotChannel(self, irc, msg, password=' '):
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):
"""[]
@@ -136,7 +136,7 @@ class User(callbacks.Privmsg):
ircdb.users.delUser(id)
irc.replySuccess()
else:
- irc.error(conf.replyIncorrectAuth)
+ irc.error(conf.supybot.replies.incorrectAuthentication())
def changename(self, irc, msg, args):
""" []
@@ -201,7 +201,7 @@ class User(callbacks.Privmsg):
ircdb.users.setUser(id, user)
irc.replySuccess()
else:
- irc.error(conf.replyIncorrectAuth)
+ irc.error(conf.supybot.replies.incorrectAuthentication())
return
def removehostmask(self, irc, msg, args):
@@ -229,7 +229,7 @@ class User(callbacks.Privmsg):
ircdb.users.setUser(id, user)
irc.replySuccess()
else:
- irc.error(conf.replyIncorrectAuth)
+ irc.error(conf.supybot.replies.incorrectAuthentication())
return
def setpassword(self, irc, msg, args):
@@ -258,7 +258,7 @@ class User(callbacks.Privmsg):
ircdb.users.setUser(id, user)
irc.replySuccess()
else:
- irc.error(conf.replyIncorrectAuth)
+ irc.error(conf.supybot.replies.incorrectAuthentication())
def username(self, irc, msg, args):
"""
@@ -345,7 +345,7 @@ class User(callbacks.Privmsg):
irc.error('Your secure flag is true and your hostmask '
'doesn\'t match any of your known hostmasks.')
else:
- irc.error(conf.replyIncorrectAuth)
+ irc.error(conf.supybot.replies.incorrectAuthentication())
def unidentify(self, irc, msg, args):
"""takes no arguments
@@ -403,7 +403,7 @@ class User(callbacks.Privmsg):
ircdb.users.setUser(id, user)
irc.reply('Secure flag set to %s' % value)
else:
- irc.error(conf.replyIncorrectAuth)
+ irc.error(conf.supybot.replies.incorrectAuthentication())
Class = User
diff --git a/src/asyncoreDrivers.py b/src/asyncoreDrivers.py
index 0fbd56263..36dd05bea 100644
--- a/src/asyncoreDrivers.py
+++ b/src/asyncoreDrivers.py
@@ -42,7 +42,6 @@ import asynchat
import log
import conf
-import repl
import ircdb
import world
import drivers
@@ -53,7 +52,7 @@ class AsyncoreRunnerDriver(drivers.IrcDriver):
def run(self):
log.debug(repr(asyncore.socket_map))
try:
- asyncore.poll(conf.poll)
+ asyncore.poll(conf.supybot.drivers.poll())
except:
log.exception('Uncaught exception:')
@@ -75,7 +74,8 @@ class AsyncoreDriver(asynchat.async_chat, object):
def scheduleReconnect(self):
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:
log.info('Scheduling reconnect to %s at %s', self.server, whenS)
def makeNewDriver():
@@ -121,116 +121,12 @@ class AsyncoreDriver(asynchat.async_chat, object):
log.info('Driver for %s dying.', self.irc)
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'(?= 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:
ignore(poller)
except NameError:
poller = AsyncoreRunnerDriver()
-if conf.telnetEnable and __name__ != '__main__':
- try:
- ignore(_listener)
- except NameError:
- _listener = ReplListener()
-
Driver = AsyncoreDriver
-if __name__ == '__main__':
- ReplListener()
- asyncore.loop()
+
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
diff --git a/src/callbacks.py b/src/callbacks.py
index b12b8eba1..d61f04b6f 100644
--- a/src/callbacks.py
+++ b/src/callbacks.py
@@ -70,11 +70,11 @@ def addressed(nick, msg):
"""
nick = ircutils.toLower(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()
else:
return msg.args[1].strip()
- elif conf.replyWhenAddressedByNick and \
+ elif conf.supybot.reply.whenAddressedByNick() and \
ircutils.toLower(msg.args[1]).startswith(nick):
try:
(maybeNick, rest) = msg.args[1].split(None, 1)
@@ -86,9 +86,9 @@ def addressed(nick, msg):
return ''
except ValueError: # split didn't work.
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()
- elif conf.replyWhenNotAddressed:
+ elif conf.supybot.reply.whenNotAddressed():
return msg.args[1]
else:
return ''
@@ -112,7 +112,7 @@ def reply(msg, s, prefixName=True, private=False, notice=False, to=None):
s = ircutils.safeArgument(s)
to = to or msg.nick
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)
elif prefixName:
m = ircmsgs.privmsg(msg.args[0], '%s: %s' % (to, s))
@@ -227,7 +227,7 @@ def tokenize(s):
try:
if s != _lastTokenized:
_lastTokenized = s
- if conf.enablePipeSyntax:
+ if conf.supybot.pipeSyntax():
tokens = '|'
else:
tokens = ''
@@ -263,7 +263,7 @@ def formatArgumentError(method, name=None):
if name is None:
name = method.__name__
if hasattr(method, '__doc__') and method.__doc__:
- if conf.showOnlySyntax:
+ if conf.supybot.showSimpleSyntax():
return getSyntax(method, name=name)
else:
return getHelp(method, name=name)
@@ -281,7 +281,7 @@ def checkCommandCapability(msg, cb, command):
if ircdb.checkCapability(msg.prefix, antichancap):
log.info('Preventing because of antichancap: %s', msg.prefix)
return False
- return conf.defaultAllow or \
+ return conf.supybot.defaultAllow() or \
ircdb.checkCapability(msg.prefix, command) or \
ircdb.checkCapability(msg.prefix, chancap)
@@ -296,30 +296,38 @@ class RichReplyMethods(object):
return s
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):
- self.reply(self.__makeReply(conf.replyError, s), **kwargs)
+ self.reply(self.__makeReply(conf.supybot.replies.error(), s),
+ **kwargs)
def errorNoCapability(self, capability, s='', **kwargs):
log.warning('Denying %s for lacking %r 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)
def errorPossibleBug(self, s='', **kwargs):
if s:
- s += ' (%s)' % conf.replyPossibleBug
+ s += ' (%s)' % conf.supybot.replies.possibleBug()
+ else:
+ s = conf.supybot.replies.possibleBug()
self.error(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):
- 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):
- self.error(self.__makeReply(conf.replyRequiresPrivacy, s), **kwargs)
+ requiresPrivacy = conf.supybot.replies.requiresPrivacy()
+ self.error(self.__makeReply(requiresPrivacy, s), **kwargs)
class IrcObjectProxy(RichReplyMethods):
@@ -337,7 +345,7 @@ class IrcObjectProxy(RichReplyMethods):
self.notice = False
self.private = False
self.finished = False
- self.prefixName = conf.replyWithNickPrefix
+ self.prefixName = conf.supybot.reply.withNickPrefix()
self.noLengthCheck = False
if not args:
self.finalEvaled = True
@@ -462,18 +470,18 @@ class IrcObjectProxy(RichReplyMethods):
to=: The nick or channel the reply should go to.
Defaults to msg.args[0] (or msg.nick if private)
"""
- # These use |= or &= based on whether or not they default to True or
- # False. Those that default to True use &=; those that default to
- # False use |=.
+ # These use and or or based on whether or not they default to True or
+ # False. Those that default to True use and; those that default to
+ # False use or.
assert not isinstance(s, ircmsgs.IrcMsg), \
'Old code alert: there is no longer a "msg" argument to reply.'
msg = self.msg
- self.action |= action
- self.notice |= notice
- self.private |= private
+ self.action = action or self.action
+ self.notice = notice or self.notice
+ self.private = private or self.private
self.to = to or self.to
- self.prefixName &= prefixName
- self.noLengthCheck |= noLengthCheck
+ self.prefixName = prefixName or self.prefixName
+ self.noLengthCheck = noLengthCheck or self.noLengthCheck
if self.finalEvaled:
if isinstance(self.irc, self.__class__):
self.irc.reply(s, self.noLengthCheck, self.prefixName,
@@ -532,7 +540,7 @@ class IrcObjectProxy(RichReplyMethods):
self.irc.error(s, private)
else:
s = 'Error: ' + s
- if private or conf.errorReplyPrivate:
+ if private or conf.supybot.reply.errorInPrivate():
self.irc.queueMsg(ircmsgs.privmsg(self.msg.nick, s))
else:
self.irc.queueMsg(reply(self.msg, s))
@@ -655,22 +663,6 @@ class Privmsg(irclib.IrcCallback):
dispatcher.__doc__ = docstring
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):
if msg.command == 'PRIVMSG':
if self.noIgnore or not ircdb.checkIgnored(msg.prefix,msg.args[0]):
diff --git a/src/conf.py b/src/conf.py
index 07d001669..e5d14e8fe 100644
--- a/src/conf.py
+++ b/src/conf.py
@@ -35,307 +35,308 @@ import fix
import os
import sys
+import string
-import sets
-import os.path
-import logging
+import utils
+import registry
+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__))
-pluginDirs = [os.path.join(installDir, s) for s in ('src', '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
+_srcDir = os.path.join(installDir, 'src')
+_pluginsDir = os.path.join(installDir, 'plugins')
###
# 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
-###
-# replyWhenNotCommand: True if you want the bot reply when someone apparently
-# addresses him but there is no command. Otherwise he'll
-# just remain silent.
-###
-replyWhenNotCommand = True
+
+supybot = registry.Group()
+supybot.setName('supybot')
+supybot.registerGroup('plugins') # This will be used by plugins, but not here.
+
+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
-# noticed to that user instead of sent to the channel
-# itself.
+# Reply/error tweaking.
###
-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
-# person giving the command to its reply.
+# Replies
###
-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
+.""",
+"""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
-# form "botnick: foo" where "botnick" is the bot's
-# nick.
+# Driver stuff.
###
-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."""))
-###
-# replyWhenNotAddressed: True if the bot should reply to messages even if they
-# don't address it at all. If you have this on, you'll
-# almost certainly want to make sure replyWhenNotCommand
-# is turned off.
-###
-replyWhenNotAddressed = False
+class ValidDriverModule(registry.String):
+ def set(self, s):
+ original = getattr(self, 'value', self.default)
+ registry.String.set(self, s)
+ if self.value not in ('socketDrivers',
+ 'twistedDrivers',
+ '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
-###
-# requireRegistration: Oftentimes a plugin will want to record who added or
-# changed or messed with it last. Supybot's user database
-# is an excellent way to determine who exactly someone is.
-# You may, however, want something a little less
-# "intrustive," so you can set this variable to False to
-# tell such plugins that they should use the hostmask when
-# 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 '
-
-###
-# 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'
+supybot.drivers.register('module', ValidDriverModule('socketDrivers', """
+Determines what driver module the bot will use. socketDrivers, a simple
+driver based on timeout sockets, is used by default because it's simple and
+stable. asyncoreDrivers is a bit older (and less well-maintained) but allows
+you to integrate with asyncore-based applications. twistedDrivers is very
+stable and simple, and if you've got Twisted installed, is probably your best
+bet."""))
###############################
###############################
@@ -346,71 +347,4 @@ driverModule = 'socketDrivers'
###############################
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:
diff --git a/src/configurable.py b/src/configurable.py
index bfbd15754..16bef357b 100644
--- a/src/configurable.py
+++ b/src/configurable.py
@@ -210,7 +210,8 @@ class Mixin(object):
"""
def __init__(self):
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):
fd = file(self.filename)
for line in fd:
@@ -258,7 +259,7 @@ class Mixin(object):
flushDictionary(self.configurables)
fd.close()
- def config(self, irc, msg, args):
+ def configurableishnessify(self, irc, msg, args):
"""[] [] []
Sets the value of config variable to on . If
diff --git a/src/drivers.py b/src/drivers.py
index e27014e93..0e385ca86 100644
--- a/src/drivers.py
+++ b/src/drivers.py
@@ -148,8 +148,11 @@ def run():
del _drivers[name]
_drivers[name] = driver
-def newDriver(server, irc, moduleName=conf.driverModule):
- """Returns a new driver for the given server using conf.driverModule."""
+def newDriver(server, irc, moduleName=None):
+ """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)
irc.driver = driver
return driver
diff --git a/src/ircdb.py b/src/ircdb.py
index cda5a701d..646fde576 100644
--- a/src/ircdb.py
+++ b/src/ircdb.py
@@ -556,8 +556,11 @@ class ChannelsDictionary(utils.IterableMap):
###
# Later, I might add some special handling for botnet.
###
-users = UsersDB(os.path.join(conf.confDir, conf.userfile))
-channels = ChannelsDictionary(os.path.join(conf.confDir, conf.channelfile))
+confDir = conf.supybot.directories.conf()
+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.
@@ -567,9 +570,9 @@ def checkIgnored(hostmask, recipient='', users=users, channels=channels):
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):
- log.info('Ignoring %s due to conf.ignores.', hostmask)
+ log.info('Ignoring %s due to conf.supybot.ignores.', hostmask)
return True
try:
id = users.getUserId(hostmask)
@@ -584,8 +587,9 @@ def checkIgnored(hostmask, recipient='', users=users, channels=channels):
else:
return False
else:
- if conf.defaultIgnore:
- log.info('Ignoring %s due to conf.defaultIgnore', hostmask)
+ if conf.supybot.defaultIgnore():
+ log.info('Ignoring %s due to conf.supybot.defaultIgnore',
+ hostmask)
return True
else:
return False
@@ -625,17 +629,17 @@ def _checkCapabilityForUnknownUser(capability, users=users, channels=channels):
return _x(capability, c.defaultAllow)
except KeyError:
pass
- if capability in conf.defaultCapabilities:
+ if capability in conf.supybot.defaultCapabilities():
return True
- elif invertCapability(capability) in conf.defaultCapabilities:
+ elif invertCapability(capability) in conf.supybot.defaultCapabilities():
return False
else:
- return _x(capability, conf.defaultAllow)
+ return _x(capability, conf.supybot.defaultAllow())
def checkCapability(hostmask, capability, users=users, channels=channels):
"""Checks that the user specified by name/hostmask has the capabilty given.
"""
- if world.startup:
+ if world.testing:
return _x(capability, True)
try:
u = users.getUser(hostmask)
@@ -666,12 +670,13 @@ def checkCapability(hostmask, capability, users=users, channels=channels):
return c.checkCapability(capability)
else:
return _x(capability, c.defaultAllow)
- if capability in conf.defaultCapabilities:
+ defaultCapabilities = conf.supybot.defaultCapabilities()
+ if capability in defaultCapabilities:
return True
- elif invertCapability(capability) in conf.defaultCapabilities:
+ elif invertCapability(capability) in defaultCapabilities:
return False
else:
- return _x(capability, conf.defaultAllow)
+ return _x(capability, conf.supybot.defaultAllow())
def checkCapabilities(hostmask, capabilities, requireAll=False):
diff --git a/src/irclib.py b/src/irclib.py
index 10c9b90f9..25c395c12 100644
--- a/src/irclib.py
+++ b/src/irclib.py
@@ -148,8 +148,7 @@ class IrcMsgQueue(object):
def enqueue(self, msg):
"""Enqueues a given message."""
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:
self.msgs.add(msg)
if msg.command in _high:
@@ -257,7 +256,7 @@ class IrcState(IrcCommandDispatcher):
"""
__slots__ = ('history', 'nicksToHostmasks', 'channels')
def __init__(self):
- self.history = RingBuffer(conf.maxHistory)
+ self.history = RingBuffer(conf.supybot.maxHistoryLength())
self.reset()
def reset(self):
@@ -407,7 +406,7 @@ class Irc(IrcCommandDispatcher):
world.ircs.append(self)
self.originalNick = intern(nick)
self.nick = self.originalNick
- self.nickmods = cycle(conf.nickmods)
+ self.nickmods = cycle(conf.supybot.nickmods())
self.password = password
self.user = intern(user or nick) # Default to nick
self.ident = intern(ident or nick) # Ditto.
@@ -486,13 +485,15 @@ class Irc(IrcCommandDispatcher):
if self.fastqueue:
msg = self.fastqueue.dequeue()
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.')
else:
self.lastTake = now
msg = self.queue.dequeue()
- elif conf.pingServer and \
- now > (self.lastping + conf.pingInterval) and self.afterConnect:
+ elif conf.supybot.pingServer() and \
+ now > (self.lastping + conf.supybot.pingInterval()) and \
+ self.afterConnect:
if self.outstandingPing:
s = 'Reconnecting to %s, ping not replied to.' % self.server
log.warning(s)
@@ -525,16 +526,6 @@ class Irc(IrcCommandDispatcher):
msg._len = len(str(msg))
self.state.addMsg(self, msg)
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
else:
return None
@@ -542,7 +533,6 @@ class Irc(IrcCommandDispatcher):
def do001(self, msg):
"""Does some logging."""
log.info('Received 001 from the server.')
- log.info('Hostmasks of user 0: %r', ircdb.users.getUser(0).hostmasks)
def do002(self, msg):
"""Logs the ircd version."""
@@ -577,21 +567,11 @@ class Irc(IrcCommandDispatcher):
"""Handles NICK messages."""
if msg.nick == self.nick:
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
(nick, user, domain) = ircutils.splitHostmask(msg.prefix)
self.prefix = ircutils.joinHostmask(self.nick, user, domain)
self.prefix = intern(self.prefix)
- log.info('Changing user 0 hostmask to %r' % self.prefix)
- elif conf.followIdentificationThroughNickChanges:
+ elif conf.supybot.followIdentificationThroughNickChanges():
# We use elif here because this means it's someone else's nick
# change, not our own.
try:
@@ -620,13 +600,7 @@ class Irc(IrcCommandDispatcher):
# This catches cases where we know our own nick (from sending it to the
# server) but we don't yet know our prefix.
if msg.nick == self.nick and self.prefix != msg.prefix:
- log.info('Updating user 0 prefix: %r' % 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.
if msg.command in self._nickSetters:
diff --git a/src/log.py b/src/log.py
index 45bcc4791..7b97406fc 100644
--- a/src/log.py
+++ b/src/log.py
@@ -42,13 +42,57 @@ import logging
import ansi
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]
-if not os.path.exists(conf.logDir):
- os.mkdir(conf.logDir, 0755)
+if not os.path.exists(conf.supybot.directories.log()):
+ 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):
os.mkdir(pluginLogDir, 0755)
@@ -56,14 +100,14 @@ if not os.path.exists(pluginLogDir):
class Formatter(logging.Formatter):
def formatTime(self, record, datefmt=None):
if datefmt is None:
- datefmt = conf.logTimestampFormat
+ datefmt = conf.supybot.log.timestampFormat()
return logging.Formatter.formatTime(self, record, datefmt)
def formatException(self, (E, e, tb)):
for exn in deadlyExceptions:
if issubclass(e.__class__, exn):
raise
- if conf.detailedTracebacks:
+ if conf.supybot.log.detailedTracebacks():
try:
return cgitb.text((E, e, tb)).rstrip('\r\n')
except:
@@ -120,7 +164,7 @@ class DailyRotatingHandler(BetterFileHandler):
class ColorizedFormatter(Formatter):
def formatException(self, (E, e, tb)):
- if conf.colorizedStdoutLogging:
+ if conf.supybot.log.stdout.colorized():
return ''.join([ansi.BOLD, ansi.RED,
Formatter.formatException(self, (E, e, tb)),
ansi.RESET])
@@ -128,7 +172,7 @@ class ColorizedFormatter(Formatter):
return Formatter.formatException(self, (E, e, tb))
def format(self, record, *args, **kwargs):
- if conf.colorizedStdoutLogging:
+ if conf.supybot.log.stdout.colorized():
color = ''
if record.levelno == logging.CRITICAL:
color = ansi.WHITE + ansi.BOLD
@@ -148,13 +192,14 @@ pluginFormatter = Formatter('%(levelname)s %(asctime)s %(name)s %(message)s')
# These are not.
_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.setLevel(-1)
_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)
_formatString = '%(name)s: %(levelname)s %(message)s'
_stdoutFormatter = ColorizedFormatter(_formatString)
@@ -178,7 +223,7 @@ def getPluginLogger(name):
if not log.handlers:
filename = os.path.join(pluginLogDir, '%s.log' % name)
handler = BetterFileHandler(filename)
- handler.setLevel(conf.minimumLogPriority)
+ handler.setLevel(conf.supybot.log.minimumPriority())
handler.setFormatter(pluginFormatter)
log.addHandler(handler)
return log
diff --git a/src/plugins.py b/src/plugins.py
index 7442f7421..9e4c38aad 100644
--- a/src/plugins.py
+++ b/src/plugins.py
@@ -118,7 +118,7 @@ class ChannelDBHandler(object):
"""Override this to specialize the filenames of your databases."""
channel = ircutils.toLower(channel)
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):
"""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,
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
- 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
that makes use of a periodically downloaded file, and you'll be set.
@@ -187,7 +187,8 @@ class PeriodicFileDownloader(object):
self.downloadedCounter = {}
for filename in self.periodicFiles:
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):
self.lastDownloaded[filename] = os.stat(fullname).st_ctime
else:
@@ -206,7 +207,8 @@ class PeriodicFileDownloader(object):
self.log.warning('Error downloading %s', url)
self.log.exception('Exception:')
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')
start = time.time()
s = infd.read(4096)
@@ -220,7 +222,7 @@ class PeriodicFileDownloader(object):
self.downloadedCounter[filename] += 1
self.lastDownloaded[filename] = time.time()
if f is None:
- toFilename = os.path.join(conf.dataDir, filename)
+ toFilename = os.path.join(confDir, filename)
if os.name == 'nt':
# Windows, grrr...
if os.path.exists(toFilename):
diff --git a/src/privmsgs.py b/src/privmsgs.py
index d6dbb074d..b15ba53f6 100644
--- a/src/privmsgs.py
+++ b/src/privmsgs.py
@@ -57,7 +57,7 @@ def getChannel(msg, args):
removed).
"""
if args and ircutils.isChannel(args[0]):
- if conf.requireChannelCommandsToBeSentInChannel:
+ if conf.supybot.reply.requireChannelCommandsToBeSentInChannel():
if args[0] != msg.args[0]:
s = 'Channel commands must be sent in the channel to which ' \
'they apply.'
@@ -145,22 +145,6 @@ def thread(f):
t.start()
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):
"""Gives the command an extra channel arg as if it had called getChannel"""
def newf(self, irc, msg, args, *L):
@@ -175,7 +159,7 @@ def urlSnarfer(f):
f = _threadedWrapMethod(f)
def newf(self, irc, msg, match, *L):
now = time.time()
- cutoff = now - conf.snarfThrottle
+ cutoff = now - conf.supybot.snarfThrottle()
q = getattr(self, '_snarfedUrls', None)
if q is None:
q = structures.smallqueue()
diff --git a/src/registry.py b/src/registry.py
index 01547c509..5369cad02 100644
--- a/src/registry.py
+++ b/src/registry.py
@@ -32,13 +32,17 @@
__revision__ = "$Id$"
import copy
+import sets
+import types
import utils
-
class RegistryException(Exception):
pass
+class InvalidRegistryFile(RegistryException):
+ pass
+
class InvalidRegistryValue(RegistryException):
pass
@@ -50,43 +54,47 @@ def open(filename):
"""Initializes the module by loading the registry file into memory."""
cache.clear()
fd = utils.nonCommentNonEmptyLines(file(filename))
- for line in fd:
- line = line.rstrip()
- (key, value) = line.split(': ', 1)
- cache[key] = value
+ for (i, line) in enumerate(fd):
+ line = line.rstrip('\r\n')
+ try:
+ (key, value) = line.split(': ', 1)
+ except ValueError:
+ raise InvalidRegistryFile, 'Error unpacking line #%s' % (i+1)
+ cache[key.lower()] = value
def close(registry, filename):
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.close()
-
+
+
class Value(object):
def __init__(self, default, help):
- self.help = utils.normalizeWhitespace(help)
- self.value = self.default = default
+ self.default = default
+ self.help = utils.normalizeWhitespace(help.strip())
+ self.setValue(default)
+ self.set(str(self)) # This is needed.
def set(self, s):
"""Override this with a function to convert a string to whatever type
you want, and set it to .value."""
- # self.value = value
raise NotImplementedError
+
+ def setValue(self, v):
+ self.value = v
- def get(self):
- return self.value
-
- def default(self):
- return self.default
-
def reset(self):
- self.value = self.default
-
- def help(self):
- return self.help
+ self.setValue(self.default)
def __str__(self):
return repr(self.value)
+ # This is simply prettier than naming this function get(self)
+ def __call__(self):
+ return self.value
+
+
class Boolean(Value):
def set(self, s):
s = s.lower()
@@ -94,6 +102,8 @@ class Boolean(Value):
self.value = True
elif s in ('false', 'off', 'disabled'):
self.value = False
+ elif s == 'toggle':
+ self.value = not self.value
else:
raise InvalidRegistryValue, 'Value must be True or False.'
@@ -104,9 +114,16 @@ class Integer(Value):
except ValueError:
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):
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)
try:
v = utils.safeEval(s)
@@ -116,6 +133,11 @@ class String(Value):
except ValueError: # This catches utils.safeEval(s) errors too.
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):
def set(self, s):
String.set(self, s)
@@ -124,13 +146,25 @@ class StringSurroundedBySpaces(String):
if self.value.rstrip() == 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):
def __init__(self):
- self.__dict__['name'] = 'unset'
- self.__dict__['values'] = {}
- self.__dict__['children'] = {}
- self.__dict__['originals'] = {}
+ self.name = 'unset'
+ self.values = {}
+ self.children = {}
+ self.originals = {}
def __nonExistentEntry(self, attr):
s = '%s is not a valid entry in %s' % (attr, self.name)
@@ -140,56 +174,24 @@ class Group(object):
original = attr
attr = attr.lower()
if attr in self.values:
- return self.values[attr].get()
+ return self.values[attr]
elif attr in self.children:
return self.children[attr]
else:
self.__nonExistentEntry(original)
- def __setattr__(self, attr, s):
- original = attr
- 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 getChild(self, attr):
+ return self.children[attr.lower()]
- 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):
- self.__dict__['name'] = name
+ self.name = name
def getName(self):
- return self.__dict__['name']
+ return self.name
def register(self, name, value):
original = name
name = name.lower()
- if name in self.values:
- value.set(str(self.values[name]))
self.values[name] = value
self.originals[name] = original
if cache:
@@ -200,11 +202,10 @@ class Group(object):
def registerGroup(self, name, group=None):
original = name
name = name.lower()
+ if name in self.children:
+ return # Ignore redundant group inserts.
if group is None:
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.originals[name] = original
fullname = '%s.%s' % (self.name, name)
@@ -212,25 +213,52 @@ class Group(object):
if cache and fullname in cache:
group.set(cache[fullname])
- def getValues(self, askChildren=False):
+ def getValues(self, getChildren=False):
L = []
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:
- L.append(('%s.%s' % (self.getName(), name), str(value)))
- if askChildren:
+ L.append(('%s.%s' % (self.getName(), self.originals[name]), value))
+ if getChildren:
items = self.children.items()
utils.sortBy(lambda (k, _): (k.lower(), len(k), k), items)
for (_, child) in items:
- L.extend(child.getValues(askChildren))
+ L.extend(child.getValues(getChildren))
return L
-class GroupWithDefault(Group):
+class GroupWithValue(Group):
def __init__(self, value):
Group.__init__(self)
- self.__dict__['help'] = value.help
- self.__dict__['value'] = self.__dict__['default'] = value
+ self.value = 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):
v = copy.copy(self.value)
@@ -241,13 +269,7 @@ class GroupWithDefault(Group):
try:
return Group.__getattr__(self, attr)
except NonExistentRegistryEntry:
- return self.value.get()
-
- def __setattr__(self, attr, s):
- try:
- Group.__setattr__(self, attr, s)
- except NonExistentRegistryEntry:
- self.__makeChild(attr, s)
+ return self.value
def setName(self, name):
Group.setName(self, name)
@@ -256,25 +278,16 @@ class GroupWithDefault(Group):
(_, group) = rsplit(k, '.', 1)
self.__makeChild(group, v)
- def set(self, *args):
- if len(args) == 1:
- self.value.set(args[0])
- else:
- assert len(args) == 2
- (attr, s) = args
- self.__setattr__(attr, s)
-
- def getValues(self, askChildren=False):
- L = Group.getValues(self, askChildren)
- L.insert(0, (self.getName(), str(self.value)))
- return L
-
+ def setChild(self, attr, s):
+ self.__setattr__(attr, s)
if __name__ == '__main__':
+ import sys
+ sys.setrecursionlimit(40)
supybot = Group()
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.
"""))
supybot.registerGroup('plugins')
@@ -282,9 +295,19 @@ if __name__ == '__main__':
supybot.plugins.topic.registerGroup('separator',
GroupWithDefault(StringSurroundedBySpaces(' || ',
'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.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():
print '%s: %s' % (k, v)
@@ -292,11 +315,11 @@ if __name__ == '__main__':
print 'Asking children'
print
- for (k, v) in supybot.getValues(askChildren=True):
+ for (k, v) in supybot.getValues(getChildren=True):
print '%s: %s' % (k, v)
- print supybot.help('throttleTime')
- print supybot.plugins.topic.help('separator')
+ print supybot.throttleTime.help
+ print supybot.plugins.topic.separator.help
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
diff --git a/src/socketDrivers.py b/src/socketDrivers.py
index f039ba768..9a8389bcd 100644
--- a/src/socketDrivers.py
+++ b/src/socketDrivers.py
@@ -40,6 +40,7 @@ __revision__ ="$Id$"
import fix
import time
+import atexit
import socket
from itertools import imap
@@ -51,13 +52,17 @@ import ircmsgs
import schedule
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):
def __init__(self, (server, port), irc, reconnectWaits=(0, 60, 300)):
global instances
instances += 1
- conf.poll = originalPoll / instances
+ conf.supybot.drivers.poll.setValue(originalPoll / instances)
self.server = (server, port)
drivers.IrcDriver.__init__(self) # Must come after server is set.
self.irc = irc
@@ -88,7 +93,9 @@ class SocketDriver(drivers.IrcDriver):
def run(self):
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
self._sendIfMsgs()
try:
@@ -124,12 +131,13 @@ class SocketDriver(drivers.IrcDriver):
return
self.irc.reset()
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:
self.reconnectWaitsIndex += 1
try:
self.conn.connect(self.server)
- self.conn.settimeout(conf.poll)
+ self.conn.settimeout(conf.supybot.drivers.poll())
except socket.error, e:
if e.args[0] != 115:
log.warning('Error connecting to %s: %s', self.server, e)
@@ -144,7 +152,8 @@ class SocketDriver(drivers.IrcDriver):
def _scheduleReconnect(self):
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:
log.info('Scheduling reconnect to %s at %s', self.server, whenS)
schedule.addEvent(self.reconnect, when)
diff --git a/src/twistedDrivers.py b/src/twistedDrivers.py
index 35575e224..0635c0521 100644
--- a/src/twistedDrivers.py
+++ b/src/twistedDrivers.py
@@ -42,14 +42,13 @@ import drivers
import ircmsgs
from twisted.internet import reactor
-from twisted.manhole.telnet import Shell, ShellFactory
from twisted.protocols.basic import LineReceiver
from twisted.internet.protocol import ReconnectingClientFactory
class TwistedRunnerDriver(drivers.IrcDriver):
def run(self):
try:
- reactor.iterate(conf.poll)
+ reactor.iterate(conf.supybot.drivers.poll())
except:
log.exception('Uncaught exception outside reactor:')
@@ -104,26 +103,6 @@ class SupyReconnectingFactory(ReconnectingClientFactory):
def die(self):
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
try:
diff --git a/src/world.py b/src/world.py
index f1df509ea..220a18000 100644
--- a/src/world.py
+++ b/src/world.py
@@ -92,7 +92,8 @@ def upkeep():
log.debug('Pattern cache size: %s'%len(ircutils._patternCache))
log.debug('HostmaskPatternEqual cache size: %s' %
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
def makeDriversDie():
@@ -129,7 +130,6 @@ atexit.register(startDying)
##################################################
##################################################
##################################################
-startup = False
testing = False
dying = False
diff --git a/test/test.py b/test/test.py
index 8e47c8071..17bd45756 100755
--- a/test/test.py
+++ b/test/test.py
@@ -29,17 +29,30 @@
# POSSIBILITY OF SUCH DAMAGE.
###
+import os
+
import supybot
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
-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
@@ -62,16 +75,19 @@ if __name__ == '__main__':
import testsupport
import optparse
- if not os.path.exists(conf.dataDir):
- os.mkdir(conf.dataDir)
+ if not os.path.exists(conf.supybot.directories.data()):
+ os.mkdir(conf.supybot.directories.data())
- if not os.path.exists(conf.confDir):
- os.mkdir(conf.confDir)
+ if not os.path.exists(conf.supybot.directories.conf()):
+ os.mkdir(conf.supybot.directories.conf())
- if not os.path.exists(conf.logDir):
- os.mkdir(conf.logDir)
+ if not os.path.exists(conf.supybot.directories.log()):
+ os.mkdir(conf.supybot.directories.log())
- pluginLogDir = os.path.join(conf.logDir, 'plugins')
+ pluginLogDir = os.path.join(conf.supybot.directories.log(), 'plugins')
+ if not os.path.exists(pluginLogDir):
+ os.mkdir(pluginLogDir)
+
for filename in os.listdir(pluginLogDir):
os.remove(os.path.join(pluginLogDir, filename))
diff --git a/test/test_Admin.py b/test/test_Admin.py
index 7046b318a..e573325a9 100644
--- a/test/test_Admin.py
+++ b/test/test_Admin.py
@@ -51,7 +51,7 @@ class AdminTestCase(PluginTestCase, PluginDocumentation):
self.assertNotError('admin unignore foo!bar@baz')
self.assertError('admin unignore foo!bar@baz')
finally:
- conf.ignores = []
+ conf.supybot.ignores.set('')
def testIgnores(self):
try:
@@ -61,13 +61,7 @@ class AdminTestCase(PluginTestCase, PluginDocumentation):
self.assertNotError('admin ignore foo!bar@baz')
self.assertNotError('admin ignores')
finally:
- conf.ignores = []
-
- def testSetprefixchar(self):
- self.assertNotError('setprefixchar $')
- self.assertResponse('getprefixchar', "'$'")
- self.assertError('setprefixchar p')
- self.assertNoResponse(' ', 2) # make sure we return
+ conf.supybot.ignores.set('')
def testAddcapability(self):
self.assertError('addcapability sdlkfj foo')
diff --git a/test/test_Alias.py b/test/test_Alias.py
index ca37e5cee..20b0ac5e4 100644
--- a/test/test_Alias.py
+++ b/test/test_Alias.py
@@ -107,12 +107,12 @@ class AliasTestCase(ChannelPluginTestCase, PluginDocumentation):
self.assertError('alias add [] foo')
self.assertError('alias add "foo bar" foo')
try:
- conf.enablePipeSyntax = True
+ conf.supybot.pipeSyntax.setValue(True)
self.assertError('alias add "foo|bar" foo')
- conf.enablePipeSyntax = False
+ conf.supybot.pipeSyntax.setValue(False)
self.assertNotError('alias add "foo|bar" foo')
finally:
- conf.enablePipeSyntax = False
+ conf.supybot.pipeSyntax.setValue(False)
def testNotCannotNestRaised(self):
self.assertNotError('alias add mytell "tell $channel $1"')
diff --git a/test/test_Debian.py b/test/test_Debian.py
index 5fde8f1fc..6a3b2f37b 100644
--- a/test/test_Debian.py
+++ b/test/test_Debian.py
@@ -44,13 +44,14 @@ if network:
def setUp(self, nick='test'):
PluginTestCase.setUp(self)
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')):
pass
else:
print
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):
time.sleep(1)
print "Download complete"
diff --git a/test/test_Misc.py b/test/test_Misc.py
index a623fdcc4..e668a370b 100644
--- a/test/test_Misc.py
+++ b/test/test_Misc.py
@@ -42,29 +42,32 @@ class MiscTestCase(ChannelPluginTestCase, PluginDocumentation):
def testReplyWhenNotCommand(self):
try:
- conf.replyWhenNotCommand = True
+ original = str(conf.supybot.reply.whenNotCommand)
+ conf.supybot.reply.whenNotCommand.set('True')
self.prefix = 'somethingElse!user@host.domain.tld'
self.assertRegexp('foo bar baz', 'not.*command')
finally:
- conf.replyWhenNotCommand = False
+ conf.supybot.reply.whenNotCommand.set(original)
if network:
def testNotReplyWhenRegexpsMatch(self):
try:
- conf.replyWhenNotCommand = True
+ original = str(conf.supybot.reply.whenNotCommand)
+ conf.supybot.reply.whenNotCommand.set('True')
self.prefix = 'somethingElse!user@host.domain.tld'
self.assertNotError('http://gameknot.com/chess.pl?bd=1019508')
finally:
- conf.replyWhenNotCommand = False
+ conf.supybot.reply.whenNotCommand.set(original)
def testNotReplyWhenNotCanonicalName(self):
try:
- conf.replyWhenNotCommand = True
+ original = str(conf.supybot.reply.whenNotCommand)
+ conf.supybot.reply.whenNotCommand.set('True')
self.prefix = 'somethingElse!user@host.domain.tld'
self.assertNotRegexp('STrLeN foobar', 'command')
self.assertResponse('StRlEn foobar', '6')
finally:
- conf.repylWhenNotCommand = False
+ conf.supybot.reply.whenNotCommand.set(original)
def testHelp(self):
self.assertHelp('help list')
@@ -78,11 +81,11 @@ class MiscTestCase(ChannelPluginTestCase, PluginDocumentation):
def testHelpStripsPrefixChars(self):
try:
- original = conf.prefixChars
- conf.prefixChars = '@'
+ original = str(conf.supybot.prefixChars)
+ conf.supybot.prefixChars.set('@')
self.assertHelp('help @list')
finally:
- conf.prefixChars = original
+ conf.supybot.prefixChars.set(original)
def testHelpIsCaseInsensitive(self):
self.assertHelp('help LIST')
@@ -122,9 +125,6 @@ class MiscTestCase(ChannelPluginTestCase, PluginDocumentation):
self.assertNotError('upkeep')
self.assertNotError('logfilesize')
- def testGetprefixchar(self):
- self.assertNotError('getprefixchar')
-
def testPlugin(self):
self.assertResponse('plugin plugin', 'Misc')
diff --git a/test/test_Owner.py b/test/test_Owner.py
index c090d1c34..e49b7bc84 100644
--- a/test/test_Owner.py
+++ b/test/test_Owner.py
@@ -103,43 +103,6 @@ class OwnerTestCase(PluginTestCase, PluginDocumentation):
self.assertNotError('load 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):
def testLoadPluginModule(self):
diff --git a/test/test_Status.py b/test/test_Status.py
index b129add96..a28d30fa3 100644
--- a/test/test_Status.py
+++ b/test/test_Status.py
@@ -48,6 +48,7 @@ class StatusTestCase(PluginTestCase, PluginDocumentation):
def testCpustats(self):
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)
for s in ['linux', 'freebsd', 'openbsd', 'netbsd', 'darwin']:
if sys.platform.startswith(s):
diff --git a/test/test_callbacks.py b/test/test_callbacks.py
index 3f86c9060..9990a12bf 100644
--- a/test/test_callbacks.py
+++ b/test/test_callbacks.py
@@ -86,7 +86,7 @@ class TokenizerTestCase(unittest.TestCase):
def testPipe(self):
try:
- conf.enablePipeSyntax = True
+ conf.supybot.pipeSyntax.set('True')
self.assertRaises(SyntaxError, tokenize, '| foo')
self.assertRaises(SyntaxError, tokenize, 'foo ||bar')
self.assertRaises(SyntaxError, tokenize, 'bar |')
@@ -100,7 +100,7 @@ class TokenizerTestCase(unittest.TestCase):
self.assertEqual(tokenize('foo bar | baz quux'),
['baz', 'quux', ['foo', 'bar']])
finally:
- conf.enablePipeSyntax = False
+ conf.supybot.pipeSyntax.set('False')
def testBold(self):
s = '\x02foo\x02'
@@ -127,9 +127,9 @@ class FunctionsTestCase(unittest.TestCase):
self.assertEqual('foobar--', callbacks.canonicalName('foobar--'))
def testAddressed(self):
- oldprefixchars = conf.prefixChars
+ oldprefixchars = str(conf.supybot.prefixChars)
nick = 'supybot'
- conf.prefixChars = '~!@'
+ conf.supybot.prefixChars.set('~!@')
inChannel = ['~foo', '@foo', '!foo',
'%s: foo' % nick, '%s foo' % nick,
'%s: foo' % nick.capitalize(), '%s: foo' % nick.upper()]
@@ -142,7 +142,7 @@ class FunctionsTestCase(unittest.TestCase):
self.assertEqual('foo', callbacks.addressed(nick, msg), msg)
msg = ircmsgs.privmsg(nick, 'foo')
self.assertEqual('foo', callbacks.addressed(nick, msg))
- conf.prefixChars = oldprefixchars
+ conf.supybot.prefixChars.set(oldprefixchars)
msg = ircmsgs.privmsg('#foo', '%s::::: bar' % nick)
self.assertEqual('bar', callbacks.addressed(nick, msg))
msg = ircmsgs.privmsg('#foo', '%s: foo' % nick.upper())
@@ -155,12 +155,12 @@ class FunctionsTestCase(unittest.TestCase):
msg2 = ircmsgs.privmsg('#foo', 'bar')
self.assertEqual(callbacks.addressed('blah', msg1), 'bar')
try:
- original = conf.replyWhenNotAddressed
- conf.replyWhenNotAddressed = True
+ original = str(conf.supybot.reply.whenNotAddressed)
+ conf.supybot.reply.whenNotAddressed.set('True')
self.assertEqual(callbacks.addressed('blah', msg1), 'bar')
self.assertEqual(callbacks.addressed('blah', msg2), 'bar')
finally:
- conf.replyWhenNotAddressed = original
+ conf.supybot.reply.whenNotAddressed.set(original)
def testReply(self):
prefix = 'foo!bar@baz'
@@ -220,17 +220,17 @@ class PrivmsgTestCase(ChannelPluginTestCase):
def testErrorPrivateKwarg(self):
try:
- originalConfErrorReplyPrivate = conf.errorReplyPrivate
- conf.errorReplyPrivate = False
+ original = str(conf.supybot.reply.errorInPrivate)
+ conf.supybot.reply.errorInPrivate.set('False')
m = self.getMsg("eval irc.error('foo', private=True)")
self.failIf(ircutils.isChannel(m.args[0]))
finally:
- conf.errorReplyPrivate = originalConfErrorReplyPrivate
+ conf.supybot.reply.errorInPrivate.set(original)
def testErrorReplyPrivate(self):
try:
- originalConfErrorReplyPrivate = conf.errorReplyPrivate
- conf.errorReplyPrivate = False
+ original = str(conf.supybot.reply.errorInPrivate)
+ conf.supybot.reply.errorInPrivate.set('False')
# 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
# expect to error actually does so we don't go on a wild goose
@@ -239,11 +239,11 @@ class PrivmsgTestCase(ChannelPluginTestCase):
self.assertError(s)
m = self.getMsg(s)
self.failUnless(ircutils.isChannel(m.args[0]))
- conf.errorReplyPrivate = True
+ conf.supybot.reply.errorInPrivate.set('True')
m = self.getMsg(s)
self.failIf(ircutils.isChannel(m.args[0]))
finally:
- conf.errorReplyPrivate = originalConfErrorReplyPrivate
+ conf.supybot.reply.errorInPrivate.set(original)
# Now for stuff not based on the plugins.
class First(callbacks.Privmsg):
@@ -300,12 +300,12 @@ class PrivmsgTestCase(ChannelPluginTestCase):
def testEmptyNest(self):
try:
- conf.replyWhenNotCommand = True
+ conf.supybot.reply.whenNotCommand.set('True')
self.assertError('echo []')
- conf.replyWhenNotCommand = False
+ conf.supybot.reply.whenNotCommand.set('False')
self.assertResponse('echo []', '[]')
finally:
- conf.replyWhenNotCommand = False
+ conf.supybot.reply.whenNotCommand.set('False')
def testDispatcherHelp(self):
self.assertNotRegexp('help first', r'\(dispatcher')
@@ -317,18 +317,6 @@ class PrivmsgTestCase(ChannelPluginTestCase):
self.assertError('first blah')
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):
self.assertError('load [foo')
self.assertError('load foo]')
@@ -342,14 +330,14 @@ class PrivmsgTestCase(ChannelPluginTestCase):
def testInvalidCommandOneReplyOnly(self):
try:
- original = conf.replyWhenNotCommand
- conf.replyWhenNotCommand = True
+ original = str(conf.supybot.reply.whenNotCommand)
+ conf.supybot.reply.whenNotCommand.set('True')
self.assertRegexp('asdfjkl', 'not a valid command')
self.irc.addCallback(self.InvalidCommand())
self.assertResponse('asdfjkl', 'foo')
self.assertNoResponse(' ', 2)
finally:
- conf.replyWhenNotCommand = original
+ conf.supybot.reply.whenNotCommand.set(original)
class BadInvalidCommand(callbacks.Privmsg):
def invalidCommand(self, irc, msg, tokens):
@@ -358,12 +346,12 @@ class PrivmsgTestCase(ChannelPluginTestCase):
def testBadInvalidCommandDoesNotKillAll(self):
try:
- original = conf.replyWhenNotCommand
- conf.replyWhenNotCommand = True
+ original = str(conf.supybot.reply.whenNotCommand)
+ conf.supybot.reply.whenNotCommand.set('True')
self.irc.addCallback(self.BadInvalidCommand())
self.assertRegexp('asdfjkl', 'not a valid command')
finally:
- conf.replyWhenNotCommand = original
+ conf.supybot.reply.whenNotCommand.set(original)
class PrivmsgCommandAndRegexpTestCase(PluginTestCase):
diff --git a/test/test_ircdb.py b/test/test_ircdb.py
index 7b86e72d6..f797d6b48 100644
--- a/test/test_ircdb.py
+++ b/test/test_ircdb.py
@@ -35,10 +35,20 @@ import os
import unittest
import conf
+import world
import ircdb
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):
self.failIf(ircdb.isAntiCapability('foo'))
self.failIf(ircdb.isAntiCapability('#foo.bar'))
@@ -188,7 +198,7 @@ class UserCapabilitySetTestCase(unittest.TestCase):
## self.failUnless(s.check('owner'))
-class IrcUserTestCase(unittest.TestCase):
+class IrcUserTestCase(IrcdbTestCase):
def testCapabilities(self):
u = ircdb.IrcUser()
u.addCapability('foo')
@@ -259,7 +269,7 @@ class IrcUserTestCase(unittest.TestCase):
u = ircdb.IrcUser(capabilities=('foo',))
self.assertRaises(KeyError, u.removeCapability, 'bar')
-class IrcChannelTestCase(unittest.TestCase):
+class IrcChannelTestCase(IrcdbTestCase):
def testInit(self):
c = ircdb.IrcChannel()
self.failIf(c.checkCapability('op'))
@@ -302,8 +312,9 @@ class IrcChannelTestCase(unittest.TestCase):
c.removeBan(banmask)
self.failIf(c.checkIgnored(prefix))
-class UsersDBTestCase(unittest.TestCase):
- filename = os.path.join(conf.confDir, 'UsersDBTestCase.conf')
+class UsersDBTestCase(IrcdbTestCase):
+ filename = os.path.join(conf.supybot.directories.conf(),
+ 'UsersDBTestCase.conf')
def setUp(self):
try:
os.remove(self.filename)
@@ -352,8 +363,9 @@ class UsersDBTestCase(unittest.TestCase):
self.assertRaises(ValueError, self.users.setUser, id, u2)
-class CheckCapabilityTestCase(unittest.TestCase):
- filename = os.path.join(conf.confDir, 'CheckCapabilityTestCase.conf')
+class CheckCapabilityTestCase(IrcdbTestCase):
+ filename = os.path.join(conf.supybot.directories.conf(),
+ 'CheckCapabilityTestCase.conf')
owner = 'owner!owner@owner'
nothing = 'nothing!nothing@nothing'
justfoo = 'justfoo!justfoo@justfoo'
@@ -455,9 +467,9 @@ class CheckCapabilityTestCase(unittest.TestCase):
def testNothing(self):
self.assertEqual(self.checkCapability(self.nothing, self.cap),
- conf.defaultAllow)
+ conf.supybot.defaultAllow())
self.assertEqual(self.checkCapability(self.nothing, self.anticap),
- not conf.defaultAllow)
+ not conf.supybot.defaultAllow())
def testJustFoo(self):
self.failUnless(self.checkCapability(self.justfoo, self.cap))
@@ -503,11 +515,11 @@ class CheckCapabilityTestCase(unittest.TestCase):
u.setAuth(self.securefoo)
self.users.setUser(id, u)
try:
- originalConfDefaultAllow = conf.defaultAllow
- conf.defaultAllow = False
+ originalConfDefaultAllow = conf.supybot.defaultAllow()
+ conf.supybot.defaultAllow.set('False')
self.failIf(self.checkCapability('a' + self.securefoo, self.cap))
finally:
- conf.defaultAllow = originalConfDefaultAllow
+ conf.supybot.defaultAllow.set(str(originalConfDefaultAllow))
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
diff --git a/test/test_irclib.py b/test/test_irclib.py
index 93ab05a8d..88fc42f59 100644
--- a/test/test_irclib.py
+++ b/test/test_irclib.py
@@ -208,18 +208,20 @@ class IrcStateTestCase(unittest.TestCase):
prefix = 'nick!user@host'
irc = FakeIrc()
def testHistory(self):
- oldconfmaxhistory = conf.maxHistory
- conf.maxHistory = 10
+ oldconfmaxhistory = str(conf.supybot.maxHistoryLength)
+ conf.supybot.maxHistoryLength.set('10')
state = irclib.IrcState()
for msg in msgs:
try:
state.addMsg(self.irc, msg)
except Exception:
pass
- self.failIf(len(state.history) > conf.maxHistory)
- self.assertEqual(len(state.history), conf.maxHistory)
- self.assertEqual(list(state.history), msgs[len(msgs)-conf.maxHistory:])
- conf.maxHistory = oldconfmaxhistory
+ self.failIf(len(state.history)>conf.supybot.maxHistoryLength())
+ self.assertEqual(len(state.history),
+ conf.supybot.maxHistoryLength())
+ self.assertEqual(list(state.history),
+ msgs[len(msgs)-conf.supybot.maxHistoryLength():])
+ conf.supybot.maxHistoryLength.set(oldconfmaxhistory)
def testEmptyTopic(self):
state = irclib.IrcState()
@@ -392,8 +394,6 @@ class IrcCallbackTestCase(unittest.TestCase):
self.assertEqual(doCommandCatcher.L, commands)
def testFirstCommands(self):
- oldconfthrottle = conf.throttleTime
- conf.throttleTime = 0
nick = 'nick'
user = 'user any user'
password = 'password'
@@ -411,7 +411,6 @@ class IrcCallbackTestCase(unittest.TestCase):
msgs.pop()
expected.insert(0, ircmsgs.password(password))
self.assertEqual(msgs, expected)
- conf.throttleTime = oldconfthrottle
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
diff --git a/test/testsupport.py b/test/testsupport.py
index 728ff7ed5..45bcb314c 100644
--- a/test/testsupport.py
+++ b/test/testsupport.py
@@ -112,15 +112,17 @@ class PluginTestCase(unittest.TestCase):
if self.__class__ in (PluginTestCase, ChannelPluginTestCase):
# Necessary because there's a test in here that shouldn\'t run.
return
- conf.prefixChars = '@'
- conf.replyWhenNotCommand = False
+ conf.supybot.prefixChars.set('@')
+ conf.supybot.reply.whenNotCommand.setValue(False)
self.myVerbose = world.myVerbose
if self.cleanConfDir:
- for filename in os.listdir(conf.confDir):
- os.remove(os.path.join(conf.confDir, filename))
+ for filename in os.listdir(conf.supybot.directories.conf()):
+ os.remove(os.path.join(conf.supybot.directories.conf(),
+ filename))
if self.cleanDataDir:
- for filename in os.listdir(conf.dataDir):
- os.remove(os.path.join(conf.dataDir, filename))
+ for filename in os.listdir(conf.supybot.directories.data()):
+ os.remove(os.path.join(conf.supybot.directories.data(),
+ filename))
ircdb.users.reload()
ircdb.channels.reload()
if self.plugins is None:
@@ -130,10 +132,10 @@ class PluginTestCase(unittest.TestCase):
self.irc = irclib.Irc(nick)
while self.irc.takeMsg():
pass
- OwnerModule = Owner.loadPluginModule('Owner')
- MiscModule = OwnerModule.loadPluginModule('Misc')
- _ = OwnerModule.loadPluginClass(self.irc, OwnerModule)
- _ = OwnerModule.loadPluginClass(self.irc, MiscModule)
+ #OwnerModule = Owner.loadPluginModule('Owner')
+ MiscModule = Owner.loadPluginModule('Misc')
+ _ = Owner.loadPluginClass(self.irc, Owner)
+ _ = Owner.loadPluginClass(self.irc, MiscModule)
if isinstance(self.plugins, str):
self.plugins = [self.plugins]
else:
@@ -300,8 +302,8 @@ class ChannelPluginTestCase(PluginTestCase):
timeout = self.timeout
if self.myVerbose:
print # Newline, just like PluginTestCase.
- if query[0] not in conf.prefixChars:
- query = conf.prefixChars[0] + query
+ if query[0] not in conf.supybot.prefixChars():
+ query = conf.supybot.prefixChars()[0] + query
msg = ircmsgs.privmsg(to, query, prefix=frm)
if self.myVerbose:
print 'Feeding: %r' % msg