provide plugin infrastructure
Bases: object
hold all the plugins.
activate a plugin.
available plugins not yet registered.
clone an event.
launch command and wait for result.
prevent plugin to be loaded. plugins does get imported but commands, callbacks, monitors etc are not enabled.
do the actual dispatch of event.
return (dispatcher, command) on which command should fire.
deactivate a plugin.
enable plugin.
enable all plugins.
see if plugin exists.
call shutdown on all registered plugins.
get attribute of plugin.
get plugins the plugin depends on. NOT USED ANYMORE ..
we use the __depending__ attribute now which checks for the reverse case ala what plugins depends on this plugin. code is in reload().
return options entry of a command.
list of registered plugins.
reload list of plugins.
execute multiple commands.
check if a plugin needs to be reloaded.
overload functions in self.overloads.
overload permission of a function.
import a plugin.
call the size() function in all plugins.
see if there is a permoverload file and if so use it to overload permissions based on function name.
register core plugins.
register a directory.
register plugin.
register all plugins.
reload plugin.
call registered plugins save.
call registered plugins configuration save.
save plugin persisted config data.
call save() function of plugin.
show registered plugins.
execute commands in a pipeline.
try to dispatch ievent.
unload plugin.
unload plugin without saving.
dispatch command and wait for results.
return what permissions are possible.
locate a command.
function to determine whether a event would dispatch.
# gozerbot/plugins.py # # """ provide plugin infrastructure """ __status__ = "seen"
from gozerbot.stats import stats from gozerbot.tests import tests from gozerbot.datadir import datadir from users import users from irc.monitor import outmonitor, saymonitor from xmpp.monitor import xmppmonitor from utils.log import rlog from utils.exception import handle_exception from utils.generic import checkchan from utils.locking import lockdec, funclocked, Locked from utils.generic import plugnames, waitforqueue, uniqlist, makeoptions, makeargrest, cleanpycfile from gozerimport import gozer_import, force_import from persist.persist import Persist from persist.persistconfig import PersistConfig from config import config from commands import cmnds from callbacks import callbacks, jcallbacks, gn_callbacks from redispatcher import rebefore, reafter from aliases import aliascheck, aliasget from ignore import shouldignore from threads.thr import start_new_thread, getname from persist.persiststate import PersistState from simplejson import loads from morphs import inputmorphs, outputmorphs from eventbase import EventBase from admin import cmndtable, pluginlist
import os import os.path import thread import time import Queue import re import copy
loadlock = thread.allocate_lock() loadlocked = lockdec(loadlock)
class Plugins(object): """ hold all the plugins. """ def __init__(self): self.plugs = {} # dict with the plugins self.reloadlock = thread.allocate_lock() self.plugdeny = Persist(datadir + os.sep + 'plugdeny', init=False) if not self.plugdeny.data: self.plugdeny.data = [] self.plugallow = Persist(datadir + os.sep + 'plugallow', init=False) if not self.plugallow.data: self.plugallow.data = [] self.avail = [] # available plugins self.ondisk = [] # plugisn available for reload self.initcalled = [] # plugins which init functions are called self.overloads = {} # plugins to overload self.activated = {} for plug in config['plugdeny']: self.disable(plug) def __getitem__(self, item): """ return plugin. """ if self.plugs.has_key(item): return self.plugs[item] else: return None def get(self, item, attr): """ get attribute of plugin. """ if self.plugs.has_key(item): return getattr(self.plugs[item], attr) def whatperms(self): """ return what permissions are possible. """ result = [] for i in rebefore.whatperms(): if not i in result: result.append(i) for i in cmnds.whatperms(): if not i in result: result.append(i) for i in reafter.whatperms(): if not i in result: result.append(i) result.sort() return result def exist(self, name): """ see if plugin exists. """ if self.plugs.has_key(name): return True return False def disable(self, name): """ prevent plugin to be loaded. plugins does get imported but commands, callbacks, monitors etc are not enabled. """ try: config['loadlist'].remove(name) config.save() self.plugdeny.data.append(name) self.plugdeny.save() except: pass def enable(self, name): """ enable plugin. """ try: if name not in config['loadlist']: config['loadlist'].append(name) config.save() except KeyError: pass try: self.plugdeny.data.remove(name) self.plugdeny.save() except ValueError: pass def plugsizes(self): """ call the size() function in all plugins. """ reslist = [] cp = dict(self.plugs) for i, j in cp.iteritems(): try: reslist.append("%s: %s" % (i, j.size())) except AttributeError: pass return reslist def list(self): """ list of registered plugins. """ self.avail.sort() return self.avail def plugimport(self, mod, name): """ import a plugin. """ if config.has_key('loadlist') and not name in config['loadlist']: logging.warn("%s is not in loadlist" % name) ; return return self.load(mod, name) def regplugin(self, mod, name): """ register plugin. """ name = name.lower() mod = mod.lower() modname = mod + '.' + name if name in self.avail: rlog(0, 'plugins', '%s already registered' % name) ; return if name in config['plugdeny']: rlog(0, 'plugins', '%s is in config.plugdeny .. not loading' % name) ; return if name in self.plugdeny.data: rlog(0, 'plugins', '%s.%s in deny .. not loading' % (mod, name)) ; return 0 if config.has_key('loadlist') and name not in config['loadlist'] and 'gplugs' in modname and name not in self.plugallow.data: rlog(9, 'plugins', 'not loading %s.%s' % (mod, name)) return 0 if self.plugs.has_key(name): rlog(10, 'plugins', 'overloading %s plugin with %s version' % (name, mod)) self.unloadnosave(name) if not os.path.isdir(datadir + os.sep + 'plugs'): os.mkdir(datadir + os.sep + 'plugs') if not os.path.isdir(datadir + os.sep + 'plugs' + os.sep + name): os.mkdir(datadir + os.sep + 'plugs' + os.sep + name) plug = self.plugimport(mod, name) if plug: rlog(0, 'plugins', "%s.%s registered" % (mod, name)) if name not in self.avail: self.avail.append(name) return plug else: rlog(10, 'plugins', "can't import %s.%s .. try plug-enable" % (mod, name)) def showregistered(self): """ show registered plugins. """ self.avail.sort() rlog(10, 'plugins', 'registered %s' % ' .. '.join(self.avail)) self.overload() def regdir(self, dirname, exclude=[]): """ register a directory. """ threads = [] plugs = [] for plug in plugnames(dirname): if plug in exclude or plug.startswith('.'): continue try: self.regplugin(dirname, plug) plugs.append(plug) except: handle_exception() self.ondisk.extend(plugs) return plugs def regcore(self): """ register core plugins. """ self.plugdeny.init([]) self.plugallow.init([]) avail = [] plugs = force_import('gozerbot.plugs') for i in plugs.__plugs__: if i not in avail: try: self.regplugin('gozerbot.plugs', i) except Exception, ex: handle_exception() else: avail.append(i) self.ondisk.extend(avail) def enableall(self): """ enable all plugins. """ for name in self.available(): self.enable(name) def regplugins(self): """ register all plugins. """ self.regcore() avail = [] avail.extend(self.regdir('myplugs')) if os.path.isdir("myplugs"): for i in os.listdir('myplugs'): if i.startswith('.'): continue if os.path.isdir('myplugs' + os.sep + i): avail.extend(self.regdir('myplugs' + os.sep + i)) else: rlog(10, 'plugins', 'no myplugs directory found') try: gplugs = gozer_import('gplugs') except ImportError: rlog(20, 'plugins', "no gplugs package found") ; gplugs = None if gplugs: for i in gplugs.__plugs__: try: self.regplugin('gplugs', i) avail.append(i) except Exception, ex: handle_exception() if config.get('db_driver') == "olddb": try: gplugs = gozer_import('gplugs.olddb') except ImportError: rlog(20, 'plugins', "no gplugs.old package found") ; gplugs = None if gplugs: for i in gplugs.__plugs__: try: self.regplugin('gplugs.olddb', i) avail.append(i) except Exception, ex: handle_exception() else: try: gplugs = gozer_import('gplugs.alchemy') except ImportError: rlog(20, 'plugins', "no gplugs.alchemy package found") ; gplugs = None if gplugs: for i in gplugs.__plugs__: try: self.regplugin('gplugs.alchemy', i) avail.append(i) except Exception, ex: handle_exception() self.ondisk.extend(avail) self.readoverload() start_new_thread(self.showregistered, ()) def readoverload(self): """ see if there is a permoverload file and if so use it to overload permissions based on function name. """ try: overloadfile = open(datadir + os.sep + 'permoverload', 'r') except IOError: return try: for i in overloadfile: i = i.strip() splitted = i.split(',') try: funcname = splitted[0].strip() perms = [] for j in splitted[1:]: perms.append(j.strip()) except IndexError: rlog(10, 'plugins', "permoverload: can't set perms of %s" % i) continue if not funcname: rlog(10, 'plugins', "permoverload: no function provided") continue if not perms: rlog(10, 'plugins', "permoverload: no permissions provided for %s" % funcname) continue self.overloads[funcname] = perms except Exception, ex: handle_exception() def overload(self): """ overload functions in self.overloads. """ for funcname, perms in self.overloads.iteritems(): if self.permoverload(funcname, perms): rlog(0, 'plugins', '%s permission set to %s' % (funcname, perms)) def available(self): """ available plugins not yet registered. """ self.ondisk.sort() return self.ondisk def saveplug(self, plugname): """ call save() function of plugin. """ try: self.plugs[plugname].save() except AttributeError: pass except KeyError: pass def save(self): """ call registered plugins save. """ for plug in self.plugs.values(): try: plug.save() except AttributeError: pass except Exception, ex: handle_exception() def save_cfg(self): """ call registered plugins configuration save. """ for plug in self.plugs.values(): try: cfg = getattr(plug, 'cfg') if isinstance(cfg, PersistConfig): try: cfg.save() except: handle_exception() except AttributeError: continue def save_cfgname(self, name): """ save plugin persisted config data. """ try: plug = self.plugs[name] cfg = getattr(plug, 'cfg') if isinstance(cfg, PersistConfig): try: cfg.save() except: handle_exception() except (AttributeError, KeyError): pass def exit(self): """ call shutdown on all registered plugins. """ self.save() threadlist = [] for name, plug in self.plugs.iteritems(): try: shutdown = getattr(plug, 'shutdown') thread = start_new_thread(shutdown, ()) threadlist.append((name, thread)) try: self.initcalled.remove(name) except ValueError: pass except AttributeError: continue except Exception, ex: rlog(10, 'plugins', 'error shutting down %s: %s' % (name, str(ex))) try: for name, thread in threadlist: thread.join() ; rlog(10, 'plugins', '%s shutdown finished' % name) except: handle_exception() def getoptions(self, command): """ return options entry of a command. """ return cmnds.getoptions(command) def getdepend(self, plugname): """ get plugins the plugin depends on. NOT USED ANYMORE .. we use the __depending__ attribute now which checks for the reverse case ala what plugins depends on this plugin. code is in reload(). """ if plugname in self.plugs: plug = self.plugs[plugname] else: for mod in ['gozerbot.plugs', 'gplugs', 'myplugs']: try: plug = gozer_import('%s.%s' % (mod, plugname)) except ImportError: continue try: depends = plug.__depend__ except: depends = [] return depends def load(self, mod , name, enable=True): if name in config['plugdeny']: rlog(10, 'plugins', '%s is in deny' % name) ; return modname = mod + '.' + name self.down(name) self.unload(name) if enable: self.enable(name) plug = self.plugs[name] = gozer_import(modname) plug.loadtime = time.time() if enable: self.enable(name) self.overload() try: rlog(0, 'plugins', 'calling %s init()' % modname) plug.init() self.initcalled.append(modname) except (AttributeError, KeyError): pass except Exception, ex: rlog(10, 'plugins', '%s module init failed' % name) ; raise rlog(0, 'plugins', 'enabled %s' % name) self.activate(name) return plug def reload(self, mod, name, enable=True): """ reload plugin. """ if not os.path.isdir(datadir + os.sep + 'plugs'): os.mkdir(datadir + os.sep + 'plugs') if not os.path.isdir(datadir + os.sep + 'plugs' + os.sep + name): os.mkdir(datadir + os.sep + 'plugs' + os.sep + name) reloaded = [] modname = mod + '.' + name plug = self.load(mod, name) try: for p in plug.__plugs__: self.load(modname, p) reloaded.append(p) except (KeyError, AttributeError): pass rlog(0, 'plugins', 'reloaded plugin %s' % modname) reloaded.append(name) self.plugallow.data.append(name) try: self.plugdeny.data.remove(name) except ValueError: pass if name not in self.avail: self.avail.append(name) try: depends = plug.__depending__ for plug in depends: rlog(10, 'plugins', 'loading depending plugin %s (%s)' % (plug, name)) self.load(mod, plug, False) reloaded.append(plug) except AttributeError: pass return reloaded def activate(self, plugname): """ activate a plugin. """ self.activated[plugname] = True try: cmnds.activate(plugname) callbacks.activate(plugname) gn_callbacks.activate(plugname) jcallbacks.activate(plugname) rebefore.activate(plugname) reafter.activate(plugname) saymonitor.activate(plugname) outmonitor.activate(plugname) xmppmonitor.activate(plugname) tests.activate(plugname) outputmorphs.activate(plugname) inputmorphs.activate(plugname) except Exception, ex: handle_exception() def down(self, plugname): """ deactivate a plugin. """ self.activated[plugname] = False try: cmnds.disable(plugname) callbacks.disable(plugname) gn_callbacks.disable(plugname) jcallbacks.disable(plugname) rebefore.disable(plugname) reafter.disable(plugname) saymonitor.disable(plugname) outmonitor.disable(plugname) xmppmonitor.disable(plugname) tests.disable(plugname) outputmorphs.disable(plugname) inputmorphs.disable(plugname) except Exception, ex: handle_exception() def unload(self, plugname): """ unload plugin. """ unloaded = [plugname, ] try: plug = self.plugs[plugname] for p in plug.__plugs__: if p == plug: raise Exception("same plugin name as dir name (%s)" % plugname) unloaded.extend(self.unload(p)) except (KeyError, AttributeError): pass for plugname in unloaded: self.saveplug(plugname) self.unloadnosave(plugname) try: self.avail.remove(plugname) except ValueError: pass return unloaded def unloadnosave(self, plugname): """ unload plugin without saving. """ try: self.plugs[plugname].shutdown() rlog(10, 'plugins', '%s shutdown called' % plugname) except (AttributeError, KeyError): pass except Exception, ex: handle_exception() try: self.plugallow.data.remove(plugname) except (KeyError, ValueError): pass try: self.avail.remove(plugname) except ValueError: pass try: self.initcalled.remove(plugname) except ValueError: pass try: cmnds.unload(plugname) callbacks.unload(plugname) gn_callbacks.unload(plugname) jcallbacks.unload(plugname) rebefore.unload(plugname) reafter.unload(plugname) saymonitor.unload(plugname) outmonitor.unload(plugname) xmppmonitor.unload(plugname) tests.unload(plugname) outputmorphs.unload(plugname) inputmorphs.unload(plugname) if self.plugs.has_key(plugname): del self.plugs[plugname] except Exception, ex: handle_exception() ; return 0 rlog(0, 'plugins', '%s unloaded' % plugname) return 1 def whereis(self, what): """ locate a command. """ return cmnds.whereis(what) def permoverload(self, funcname, perms): """ overload permission of a function. """ if not rebefore.permoverload(funcname, perms): if not cmnds.permoverload(funcname, perms): if not reafter.permoverload(funcname, perms): return False return True def woulddispatch(self, bot, ievent): """ function to determine whether a event would dispatch. """ (what, command) = self.dispatchtest(bot, ievent) if what and command: return True return False def dispatchtest(self, bot, ievent, direct=False): """ return (dispatcher, command) on which command should fire. """ if shouldignore(ievent.userhost): return (None, None) if ievent.userhost in bot.throttle: return (None, None) if ievent.txt.find(' | ') != -1: target = ievent.txt.split(' | ')[0] elif ievent.txt.find(' && ') != -1: target = ievent.txt.split(' && ')[0] else: target = ievent.txt result = [] com = rebefore.getcallback(target) if com and not target.startswith('!'): com.re = True result = [rebefore, com] else: if ievent.txt.startswith('!'): ievent.txt = ievent.txt[1:] aliascheck(ievent) com = cmnds.getcommand(ievent.txt) if com: com.re = False result = [cmnds, com] ievent.txt = ievent.txt.strip() else: com = reafter.getcallback(target) if com: com.re = True result = [reafter, com] if result: if config['auto_register'] and not users.getname(ievent.userhost): if bot.google: users.add(ievent.userhost , [ievent.userhost, ], ['USER', ]) elif not bot.jabber: users.add("%s!%s" % (ievent.nick, ievent.userhost) , [ievent.userhost, ], ['USER', ]) bot.ratelimit(ievent.userhost, 20) else: if ievent.groupchat: users.add(ievent.userhost , [ievent.userhost, ], ['USER', ]) bot.ratelimit(ievent.userhost) else: users.add(ievent.stripped , [ievent.stripped, ], ['USER', ]) bot.ratelimit(ievent.stripped, 20) if config['anon_enable'] and not 'OPER' in result[1].perms: return result if com.name in bot.state['allowed'] or getname(com.func) in bot.state['allowed']: return result try: chanperms = bot.channels[ievent.channel.lower()]['perms'] for i in result[1].perms: if i in chanperms and not ievent.msg: ievent.speed = 1 ; return result except (KeyError, TypeError): pass if direct: return result if bot.jabber and ievent.jabber: if not ievent.groupchat or ievent.jidchange: if users.allowed(ievent.stripped, result[1].perms): return result if users.allowed(ievent.userhost, result[1].perms): return result return (None, None) def cmnd(self, bot, ievent, timeout=15, response=False, onlyqueues=True): """ launch command and wait for result. """ if response: ievent.reply('launching %s on %s bot' % (ievent.txt, bot.name)) q = Queue.Queue() ievent.queues.append(q) self.trydispatch(bot, ievent) return waitforqueue(q, timeout) def waitdispatch(self, bot, ievent, direct=False): """ dispatch command and wait for results. """ ievent.threaded = True return self.trydispatch(bot, ievent, direct, wait=True) def trydispatch(self, bot, ievent, direct=False, wait=False): """ try to dispatch ievent. """ if shouldignore(ievent.userhost): return 0 if ievent.msg: ievent.printto = ievent.nick else: ievent.printto = ievent.channel (what, com) = self.dispatchtest(bot, ievent, direct) if what: if com.allowqueue: ievent.txt = ievent.txt.replace(' || ', ' | ') if ievent.txt.find(' | ') != -1: if ievent.txt[0] == '!': ievent.txt = ievent.txt[1:] else: self.splitpipe(bot, ievent) ; return elif ievent.txt.find(' && ') != -1: self.multiple(bot, ievent) ; return return self.dispatch(what, com, bot, ievent, wait) def dispatch(self, what, com, bot, ievent, wait=False): """ do the actual dispatch of event. """ if bot.stopped: return False if com.options: makeoptions(ievent, com.options) else: makeoptions(ievent) makeargrest(ievent) ievent.usercmnd = True rlog(10, 'plugins', 'dispatching %s for %s' % (ievent.command, ievent.userhost)) what.dispatch(com, bot, ievent, wait) return True def clonedevent(self, bot, event): """ clone an event. """ ie = copy.deepcopy(event) return ie def multiple(self, bot, ievent): """ execute multiple commands. """ for i in ievent.txt.split(' && '): ie = self.clonedevent(bot, ievent) ie.txt = i #self.needreloadcheck(bot, ievent) self.trydispatch(bot, ie) def splitpipe(self, bot, ievent): """ execute commands in a pipeline. """ origqueues = ievent.queues ievent.queues = [] events = [] txt = ievent.txt.replace(' || ', ' ##') # make the events for i in txt.split(' | '): item = i.replace(' ##', ' | ') ie = self.clonedevent(bot, ievent) ie.userhost = ievent.userhost ie.onlyqueues = True ie.txt = item.strip() #self.needreloadcheck(bot, ie) events.append(ie) prevq = None for i in events[:-1]: q = Queue.Queue() i.queues.append(q) if prevq: i.inqueue = prevq prevq = q events[-1].inqueue = prevq events[-1].onlyqueues = False if origqueues: events[-1].queues = origqueues # check if all commands would dispatch for i in events: if not self.woulddispatch(bot, i): ievent.reply("can't execute %s" % str(i.txt)) return # do the dispatch for i in events: (what, com) = self.dispatchtest(bot, i) if what: self.dispatch(what, com, bot, i) def needreloadcheck(self, bot, event, target=None): """ check if a plugin needs to be reloaded. """ if cmndtable: try: if target: rlog(10, 'plugins', 'target set: %s' % target) cmnd = 'target-set' plugin = target else: t = event.txt.split()[0] cmnd = aliasget(t) or t plugin = cmndtable[cmnd] rlog(10, 'plugins', 'cmnd: %s plugin: %s' % (cmnd, plugin)) if self.exist(plugin): return try: self.reload('gozerbot.plugs', plugin) except ImportError, ex: try: self.reload('gplugs', plugin) except ImportError, ex: if config.get('db_driver') == 'olddb': try: self.reload('gplugs.olddb', plugin) except ImportError, ex: pass else: try: self.reload('gplugs.alchemy', plugin) except ImportError, ex: pass try: self.reload('myplugs', plugin) except ImportError, ex: return rlog(100, 'plugins', 'reloaded %s' % plugin) except KeyError, ex: rlog(10, 'plugins', "can't find plugin to reload for %s" % event.txt.split()[0]) def listreload(self, pluglist): """ reload list of plugins. """ failed = [] for what in pluglist: splitted = what[:-3].split(os.sep) mod = '.'.join(splitted[:-1]) if not mod: if config.get('db_driver') == "olddb": mod = "gplugs.olddb" elif config.get("db_driver") == "alchemy": mod = "gplugs.alchemy" else: mod = 'gplugs' plug = splitted[-1] try: self.reload(mod, plug) except Exception, ex: failed.append(what) return failed
plugins = Plugins()