diff --git a/plugins/Bugzilla.py b/plugins/Bugzilla.py index 9534065b4..48f95ce58 100644 --- a/plugins/Bugzilla.py +++ b/plugins/Bugzilla.py @@ -87,12 +87,13 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): threaded = True regexps = ['bzSnarfer'] configurables = plugins.ConfigurableDictionary( - [('bug-snarfer', plugins.ConfigurableTypes.bool, True, + [('bug-snarfer', plugins.ConfigurableBoolType, True, """Determines whether the bug snarfer will be enabled, such that any Bugzilla URLs seen in the channel will have their information reported into the channel.""")] ) def __init__(self): + plugins.Configurable.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self) self.entre = re.compile('&(\S*?);') # Schema: {name, [url, description]} @@ -100,6 +101,7 @@ class Bugzilla(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): self.shorthand = utils.abbrev(self.db.keys()) def die(self): + plugins.Configurable.die(self) self.db.close() del self.db diff --git a/plugins/ChannelDB.py b/plugins/ChannelDB.py index aef30b456..80d6cfa92 100644 --- a/plugins/ChannelDB.py +++ b/plugins/ChannelDB.py @@ -59,23 +59,29 @@ frowns = (':|', ':-/', ':-\\', ':\\', ':/', ':(', ':-(', ':\'(') smileyre = re.compile('|'.join(map(re.escape, smileys))) frownre = re.compile('|'.join(map(re.escape, frowns))) -class ChannelDB(plugins.ChannelDBHandler, # Must be first (die). +class ChannelDB(plugins.ChannelDBHandler, plugins.Configurable, callbacks.Privmsg): noIgnore = True configurables = plugins.ConfigurableDictionary( - [('self-stats', plugins.ConfigurableTypes.bool, True, + [('self-stats', plugins.ConfigurableBoolType, True, """Determines whether the bot will keep channel statistics on itself, possibly skewing the channel stats (especially in cases where he's relaying between channels on a network.""")] ) def __init__(self): callbacks.Privmsg.__init__(self) + plugins.Configurable.__init__(self) plugins.ChannelDBHandler.__init__(self) self.lastmsg = None self.laststate = None self.outFiltering = False + def die(self): + callbacks.Privmsg.die(self) + plugins.Configurable.die(self) + plugins.ChannelDBHandler.die(self) + def makeDb(self, filename): if os.path.exists(filename): db = sqlite.connect(filename) diff --git a/plugins/Ebay.py b/plugins/Ebay.py index cd1571230..82e2ead3d 100644 --- a/plugins/Ebay.py +++ b/plugins/Ebay.py @@ -72,13 +72,18 @@ class Ebay(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): threaded = True regexps = ['ebaySnarfer'] configurables = plugins.ConfigurableDictionary( - [('snarfer', utils.safeEval, True, + [('snarfer', plugins.ConfigurableBoolType, True, """Determines whether the bot will automatically 'snarf' Ebay auction URLs and print information about them.""")] ) def __init__(self): + plugins.Configurable.__init__(self) callbacks.PrivmsgCommandAndRegexp.__init__(self) + def die(self): + plugins.Configurable.die(self) + callbacks.PrivmsgCommandAndRegexp.die(self) + _reopts = re.I | re.S _invalid = re.compile(r'(is invalid, still pending, or no longer in our '\ 'database)', _reopts) diff --git a/plugins/Enforcer.py b/plugins/Enforcer.py index 3fea64b98..d8388134a 100644 --- a/plugins/Enforcer.py +++ b/plugins/Enforcer.py @@ -61,19 +61,24 @@ def configure(onStart, afterConnect, advanced): _chanCap = ircdb.makeChannelCapability class Enforcer(callbacks.Privmsg, plugins.Configurable): configurables = plugins.ConfigurableDictionary( - [('auto-op', plugins.ConfigurableTypes.bool, False, + [('auto-op', plugins.ConfigurableBoolType, False, """Determines whether the bot will automatically op people with the .op capability when they join the channel."""), - ('auto-voice', plugins.ConfigurableTypes.bool, False, + ('auto-voice', plugins.ConfigurableBoolType, False, """Determines whether the bot will automatically voice people with the .voice capability when they join the channel."""), - ('auto-halfop', plugins.ConfigurableTypes.bool, False, + ('auto-halfop', plugins.ConfigurableBoolType, False, """Determines whether the bot will automatically halfop people with the .halfop capability when they join the channel."""),] ) started = False def __init__(self): callbacks.Privmsg.__init__(self) + plugins.Configurable.__init__(self) + + def die(self): + callbacks.Privmsg.die(self) + plugins.Configurable.die(self) def start(self, irc, msg, args): """[ ] diff --git a/plugins/Gameknot.py b/plugins/Gameknot.py index f2d52f519..1de9313ca 100644 --- a/plugins/Gameknot.py +++ b/plugins/Gameknot.py @@ -72,11 +72,11 @@ class Gameknot(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): threaded = True regexps = ['gameknotSnarfer', 'gameknotStatsSnarfer'] configurables = plugins.ConfigurableDictionary( - [('game-snarfer', plugins.ConfigurableTypes.bool, True, + [('game-snarfer', plugins.ConfigurableBoolType, True, """Determines whether the game URL snarfer is active; if so, the bot will reply to the channel with a summary of the game data when it sees a Gameknot game on the channel."""), - ('stats-snarfer', plugins.ConfigurableTypes.bool, True, + ('stats-snarfer', plugins.ConfigurableBoolType, True, """Determines whether the stats URL snarfer is active; if so, the bot will reply to the channel with a summary of the stats of any player whose stats URL is seen on the channel.""")] @@ -87,6 +87,14 @@ class Gameknot(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): '"#FFFF00">(\d+)') _gkteam = re.compile(r'Team:(<.*?>)+(?P.*?)') _gkseen = re.compile(r'(seen on GK:\s+([^[]+ago)|.*?is hiding.*?)') + def __init__(self): + plugins.Configurable.__init__(self) + callbacks.PrivmsgCommandAndRegexp.__init__(self) + + def die(self): + plugins.Configurable.die(self) + callbacks.PrivmsgCommandAndRegexp.die(self) + def getStats(self, name): gkprofile = 'http://www.gameknot.com/stats.pl?%s' % name try: diff --git a/plugins/Google.py b/plugins/Google.py index 03ed5cd50..61cf25b7a 100644 --- a/plugins/Google.py +++ b/plugins/Google.py @@ -123,21 +123,26 @@ class Google(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): threaded = True regexps = sets.Set(['googleSnarfer', 'googleGroups']) configurables = plugins.ConfigurableDictionary( - [('groups-snarfer', plugins.ConfigurableTypes.bool, True, + [('groups-snarfer', plugins.ConfigurableBoolType, True, """Determines whether the groups snarfer is enabled. If so, URLs at groups.google.com will be snarfed and their group/title messaged to the channel."""), - ('search-snarfer', plugins.ConfigurableTypes.bool, False, + ('search-snarfer', plugins.ConfigurableBoolType, False, """Determines whether the search snarfer is enabled. If so, messages (even unaddressed ones) beginning with the word 'google' will result in the first URL Google returns being sent to the channel.""")] ) def __init__(self): - super(Google, self).__init__() + plugins.Configurable.__init__(self) + callbacks.PrivmsgCommandAndRegexp.__init__(self) self.total = 0 self.totalTime = 0 self.last24hours = structures.queue() + def die(self): + plugins.Configurable.die(self) + callbacks.PrivmsgCommandAndRegexp.die(self) + def formatData(self, data): if isinstance(data, basestring): return data diff --git a/plugins/Python.py b/plugins/Python.py index 5f80c5908..5f237a4dd 100644 --- a/plugins/Python.py +++ b/plugins/Python.py @@ -75,12 +75,20 @@ class Python(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): threaded = True regexps = ['aspnRecipes'] configurables = plugins.ConfigurableDictionary( - [('aspn-snarfer', plugins.ConfigurableTypes.bool, True, + [('aspn-snarfer', plugins.ConfigurableBoolType, True, """Determines whether the ASPN Python recipe snarfer is enabled. If so, it will message the channel with the name of the recipe when it sees an ASPN Python recipe link on the channel.""")] ) + def __init__(self): + plugins.Configurable.__init__(self) + callbacks.PrivmsgCommandAndRegexp.__init__(self) + + def die(self): + plugins.Configurable.die(self) + callbacks.PrivmsgCommandAndRegexp.die(self) + def pydoc(self, irc, msg, args): """ diff --git a/plugins/QuoteGrabs.py b/plugins/QuoteGrabs.py index bbae857e3..993e6c932 100644 --- a/plugins/QuoteGrabs.py +++ b/plugins/QuoteGrabs.py @@ -66,7 +66,7 @@ class QuoteGrabs(plugins.ChannelDBHandler, plugins.Configurable, callbacks.Privmsg): configurables = plugins.ConfigurableDictionary( - [('random-grabber', plugins.ConfigurableTypes.bool, False, + [('random-grabber', plugins.ConfigurableBoolType, False, """Determines whether the bot will randomly grab possibly-suitable quotes for someone."""),] ) diff --git a/plugins/Relay.py b/plugins/Relay.py index 617d8c04a..e199b09e9 100644 --- a/plugins/Relay.py +++ b/plugins/Relay.py @@ -95,12 +95,13 @@ class Relay(callbacks.Privmsg, plugins.Configurable): noIgnore = True priority = sys.maxint configurables = plugins.ConfigurableDictionary( - [('color', plugins.ConfigurableTypes.bool, True, + [('color', plugins.ConfigurableBoolType, True, """Determines whether the bot will color relayed PRIVMSGs so as to make the messages easier to read."""),] ) def __init__(self): callbacks.Privmsg.__init__(self) + plugins.Configurable.__init__(self) self.ircs = {} self._color = 0 self._whois = {} @@ -122,6 +123,8 @@ class Relay(callbacks.Privmsg, plugins.Configurable): callbacks.Privmsg.__call__(self, irc, msg) def die(self): + callbacks.Privmsg.die(self) + plugins.Configurable.die(self) for irc in self.abbreviations: if irc != self.originalIrc: irc.callbacks[:] = [] diff --git a/plugins/Sourceforge.py b/plugins/Sourceforge.py index fbc57c505..14f689af3 100644 --- a/plugins/Sourceforge.py +++ b/plugins/Sourceforge.py @@ -98,14 +98,21 @@ class Sourceforge(callbacks.PrivmsgCommandAndRegexp, plugins.Configurable): _res =(_resolution, _assigned, _submitted, _priority, _status) configurables = plugins.ConfigurableDictionary( - [('tracker-snarfer', plugins.ConfigurableTypes.bool, True, + [('tracker-snarfer', plugins.ConfigurableBoolType, True, """Determines whether the bot will reply to SF.net Tracker URLs in the channel with a nice summary of the tracker item."""), - ('default-project', plugins.ConfigurableTypes.str, '', + ('default-project', plugins.ConfigurableStrType, '', """Sets the default project (used by the bugs/rfes commands in the case that no explicit project is given).""")] ) _projectURL = 'http://sourceforge.net/projects/' + def __init__(self): + plugins.Configurable.__init__(self) + callbacks.PrivmsgCommandAndRegexp.__init__(self) + + def die(self): + plugins.Configurable.die(self) + callbacks.PrivmsgCommandAndRegexp.die(self) def _formatResp(self, text, num=''): """ diff --git a/plugins/URL.py b/plugins/URL.py index 862bed05e..94e8a4bcd 100644 --- a/plugins/URL.py +++ b/plugins/URL.py @@ -64,18 +64,24 @@ def configure(onStart, afterConnect, advanced): class URL(callbacks.Privmsg, plugins.Configurable, plugins.ChannelDBHandler): configurables = plugins.ConfigurableDictionary( - [('tinyurl-snarfer', plugins.ConfigurableTypes.bool, True, + [('tinyurl-snarfer', plugins.ConfigurableBoolType, True, """Determines whether the bot will output shorter versions of URLs longer than the tinyurl-minimum-length config variable."""), - ('tinyurl-minimum-length', plugins.ConfigurableTypes.int, 46, + ('tinyurl-minimum-length', plugins.ConfigurableIntType, 46, """The minimum length a URL must be before the tinyurl-snarfer will snarf it and offer a tinyurl replacement."""),] ) def __init__(self): self.nextMsgs = {} callbacks.Privmsg.__init__(self) + plugins.Configurable.__init__(self) plugins.ChannelDBHandler.__init__(self) + def die(self): + callbacks.Privmsg.die(self) + plugins.Configurable.die(self) + plugins.ChannelDBHandler.die(self) + def makeDb(self, filename): if os.path.exists(filename): return sqlite.connect(filename) diff --git a/src/plugins.py b/src/plugins.py index 38d75ab97..29050e672 100644 --- a/src/plugins.py +++ b/src/plugins.py @@ -41,6 +41,7 @@ import types import random import urllib2 import threading +import cPickle as pickle import fix import cdb @@ -53,11 +54,6 @@ import ircutils import privmsgs import callbacks -__all__ = ['ChannelDBHandler', - 'PeriodicFileDownloader', - 'ConfigurableDictionary', - 'Configurable'] - class ChannelDBHandler(object): """A class to handle database stuff for individual channels transparently. """ @@ -244,41 +240,35 @@ class ConfigurableDictionary(object): def names(self): return self.originalNames - # XXX: Make persistent. - class ConfigurableTypeError(TypeError): pass -class _ConfigurableTypes(object): - def bool(self, s): - s = s.lower() - if s in ('true', 'enable', 'on'): - return True - elif s in ('false', 'disable', 'off'): - return False - else: - s = 'Value must be one of on/off/true/false/enable/disable.' - raise ConfigurableTypeError, s +def ConfigurableBoolType(s): + s = s.lower() + if s in ('true', 'enable', 'on'): + return True + elif s in ('false', 'disable', 'off'): + return False + else: + s = 'Value must be one of on/off/true/false/enable/disable.' + raise ConfigurableTypeError, s - def str(self, s): - if s and s[0] not in '\'"' and s[-1] not in '\'"': - s = repr(s) - try: - v = utils.safeEval(s) - if type(v) is not str: - raise ValueError - except ValueError: - raise ConfigurableTypeError, 'Value must be a string.' - return v +def ConfigurableStrType(s): + if s and s[0] not in '\'"' and s[-1] not in '\'"': + s = repr(s) + try: + v = utils.safeEval(s) + if type(v) is not str: + raise ValueError + except ValueError: + raise ConfigurableTypeError, 'Value must be a string.' + return v - def int(self, s): - try: - return int(s) - except ValueError: - raise ConfigurableTypeError, 'Value must be an int.' -ConfigurableTypes = _ConfigurableTypes() - -foobar = 'baz' +def ConfigurableIntType(s): + try: + return int(s) + except ValueError: + raise ConfigurableTypeError, 'Value must be an int.' class Configurable(object): """A mixin class to provide a "config" command that can be consistent @@ -292,6 +282,21 @@ class Configurable(object): variable should take on; help is a string that'll be returned to describe the purpose of the config variable. """ + def __init__(self): + className = self.__class__.__name__ + self.filename = os.path.join(conf.confDir,'%s-configurable'%className) + try: + if os.path.exists(self.filename): + configurables = pickle.load(file(self.filename, 'rb')) + if configurables.names() == self.configurables.names(): + self.configurables = configurables + except Exception, e: + debug.msg('%s raised when trying to unpickle %s configurables' % + (debug.exnToString(e), className)) + + def die(self): + pickle.dump(self.configurables, file(self.filename, 'wb'), -1) + def config(self, irc, msg, args): """[] [] [] @@ -318,7 +323,7 @@ class Configurable(object): return if not value: help = self.configurables.help(name) - value = self.configurables.get(name) + value = self.configurables.get(name, channel=channel) irc.reply(msg, '%s: %s (Current value: %r)' % (name, help, value)) return try: