gozerbot (0.99.1-2) direct (non packaging) changes

Summary

 build/lib/gozerbot/__init__.py              |   45 
 build/lib/gozerbot/admin.py                 |   19 
 build/lib/gozerbot/aliases.py               |  135 ++
 build/lib/gozerbot/botbase.py               |  394 ++++++
 build/lib/gozerbot/cache.py                 |   12 
 build/lib/gozerbot/callbacks.py             |  274 ++++
 build/lib/gozerbot/channels.py              |  136 ++
 build/lib/gozerbot/commands.py              |  579 +++++++++
 build/lib/gozerbot/compat/__init__.py       |   32 
 build/lib/gozerbot/compat/config.py         |  313 ++++
 build/lib/gozerbot/compat/dbusers.py        |  264 ++++
 build/lib/gozerbot/compat/karma.py          |  270 ++++
 build/lib/gozerbot/compat/pdod.py           |   75 +
 build/lib/gozerbot/compat/pdol.py           |   72 +
 build/lib/gozerbot/compat/persist.py        |   64 +
 build/lib/gozerbot/compat/persistconfig.py  |  420 ++++++
 build/lib/gozerbot/compat/quote.py          |  114 +
 build/lib/gozerbot/compat/rss.py            | 1124 +++++++++++++++++
 build/lib/gozerbot/compat/todo.py           |  172 ++
 build/lib/gozerbot/compat/users.py          |  246 +++
 build/lib/gozerbot/config.py                |  425 ++++++
 build/lib/gozerbot/contrib/rijndael.py      |  376 +++++
 build/lib/gozerbot/contrib/xmlstream.py     |  271 ++++
 build/lib/gozerbot/database/__init__.py     |    2 
 build/lib/gozerbot/database/alchemy.py      |  341 +++++
 build/lib/gozerbot/database/db.py           |  421 ++++++
 build/lib/gozerbot/database/gae.py          |   73 +
 build/lib/gozerbot/database/samodels.py     |  264 ++++
 build/lib/gozerbot/datadir.py               |   57 
 build/lib/gozerbot/dbusers.py               |  375 +++++
 build/lib/gozerbot/eggs.py                  |  188 ++
 build/lib/gozerbot/eventbase.py             |  602 +++++++++
 build/lib/gozerbot/eventhandler.py          |  192 +++
 build/lib/gozerbot/examples.py              |  113 +
 build/lib/gozerbot/exit.py                  |   73 +
 build/lib/gozerbot/fleet.py                 |  654 ++++++++++
 build/lib/gozerbot/generic.py               |   31 
 build/lib/gozerbot/gozerimport.py           |   97 +
 build/lib/gozerbot/ignore.py                |  126 ++
 build/lib/gozerbot/irc/bot.py               |  721 +++++++++++
 build/lib/gozerbot/irc/irc.py               | 1144 ++++++++++++++++++
 build/lib/gozerbot/irc/ircevent.py          |  484 +++++++
 build/lib/gozerbot/irc/monitor.py           |   41 
 build/lib/gozerbot/jsonusers.py             |  751 +++++++++++
 build/lib/gozerbot/less.py                  |  159 ++
 build/lib/gozerbot/monitor.py               |  168 ++
 build/lib/gozerbot/morphs.py                |  159 ++
 build/lib/gozerbot/partyline.py             |  365 +++++
 build/lib/gozerbot/periodical.py            |  567 +++++++++
 build/lib/gozerbot/persist/pdod.py          |   75 +
 build/lib/gozerbot/persist/pdol.py          |   72 +
 build/lib/gozerbot/persist/persist.py       |  156 ++
 build/lib/gozerbot/persist/persistconfig.py |  294 ++++
 build/lib/gozerbot/persist/persiststate.py  |   68 +
 build/lib/gozerbot/plughelp.py              |   73 +
 build/lib/gozerbot/plugins.py               | 1509 ++++++++++++++++++++++++
 build/lib/gozerbot/plugs/__init__.py        |   26 
 build/lib/gozerbot/plugs/admin.py           |   49 
 build/lib/gozerbot/plugs/alias.py           |  148 ++
 build/lib/gozerbot/plugs/all.py             |   70 +
 build/lib/gozerbot/plugs/at.py              |   96 +
 build/lib/gozerbot/plugs/chanperm.py        |  108 +
 build/lib/gozerbot/plugs/choice.py          |   43 
 build/lib/gozerbot/plugs/code.py            |   67 +
 build/lib/gozerbot/plugs/core.py            |  975 +++++++++++++++
 build/lib/gozerbot/plugs/count.py           |   32 
 build/lib/gozerbot/plugs/fleet.py           |  339 +++++
 build/lib/gozerbot/plugs/grep.py            |  103 +
 build/lib/gozerbot/plugs/ignore.py          |   83 +
 build/lib/gozerbot/plugs/inform.py          |   41 
 build/lib/gozerbot/plugs/irc.py             |  495 +++++++
 build/lib/gozerbot/plugs/jabber.py          |   99 +
 build/lib/gozerbot/plugs/job.py             |  130 ++
 build/lib/gozerbot/plugs/misc.py            |  169 ++
 build/lib/gozerbot/plugs/mysqlkeepalive.py  |   27 
 build/lib/gozerbot/plugs/nickcapture.py     |   32 
 build/lib/gozerbot/plugs/nickserv.py        |  272 ++++
 build/lib/gozerbot/plugs/not.py             |   74 +
 build/lib/gozerbot/plugs/plug.py            |  123 +
 build/lib/gozerbot/plugs/reload.py          |  106 +
 build/lib/gozerbot/plugs/rest.py            |   80 +
 build/lib/gozerbot/plugs/reverse.py         |   33 
 build/lib/gozerbot/plugs/size.py            |   47 
 build/lib/gozerbot/plugs/sort.py            |  111 +
 build/lib/gozerbot/plugs/stats.py           |   26 
 build/lib/gozerbot/plugs/tail.py            |   42 
 build/lib/gozerbot/plugs/tell.py            |   43 
 build/lib/gozerbot/plugs/test.py            |  250 +++
 build/lib/gozerbot/plugs/throttle.py        |  142 ++
 build/lib/gozerbot/plugs/to.py              |   53 
 build/lib/gozerbot/plugs/underauth.py       |   41 
 build/lib/gozerbot/plugs/uniq.py            |   45 
 build/lib/gozerbot/plugs/user.py            |  945 +++++++++++++++
 build/lib/gozerbot/plugs/userstate.py       |  139 ++
 build/lib/gozerbot/reboot.py                |   74 +
 build/lib/gozerbot/redispatcher.py          |  394 ++++++
 build/lib/gozerbot/rest/client.py           |  285 ++++
 build/lib/gozerbot/rest/server.py           |  265 ++++
 build/lib/gozerbot/runner.py                |  172 ++
 build/lib/gozerbot/sausers.py               | 1189 ++++++++++++++++++
 build/lib/gozerbot/stats.py                 |   67 +
 build/lib/gozerbot/tests.py                 |  326 +++++
 build/lib/gozerbot/threads/thr.py           |  163 ++
 build/lib/gozerbot/threads/threadloop.py    |  135 ++
 build/lib/gozerbot/users.py                 |   18 
 build/lib/gozerbot/utils/dol.py             |   63 +
 build/lib/gozerbot/utils/exception.py       |   89 +
 build/lib/gozerbot/utils/fileutils.py       |   52 
 build/lib/gozerbot/utils/generic.py         |  513 ++++++++
 build/lib/gozerbot/utils/lazydict.py        |   36 
 build/lib/gozerbot/utils/limlist.py         |   29 
 build/lib/gozerbot/utils/locking.py         |   88 +
 build/lib/gozerbot/utils/lockmanager.py     |   56 
 build/lib/gozerbot/utils/log.py             |  104 +
 build/lib/gozerbot/utils/name.py            |   24 
 build/lib/gozerbot/utils/nextid.py          |   63 +
 build/lib/gozerbot/utils/popen.py           |   83 +
 build/lib/gozerbot/utils/reboot.py          |   26 
 build/lib/gozerbot/utils/rsslist.py         |   40 
 build/lib/gozerbot/utils/statdict.py        |   45 
 build/lib/gozerbot/utils/textutils.py       |   34 
 build/lib/gozerbot/utils/timeutils.py       |  230 +++
 build/lib/gozerbot/utils/trace.py           |   56 
 build/lib/gozerbot/utils/url.py             |  259 ++++
 build/lib/gozerbot/utils/xmpp.py            |   36 
 build/lib/gozerbot/wait.py                  |  168 ++
 build/lib/gozerbot/xmpp/__init__.py         |    1 
 build/lib/gozerbot/xmpp/bot.py              | 1125 +++++++++++++++++
 build/lib/gozerbot/xmpp/core.py             |  677 ++++++++++
 build/lib/gozerbot/xmpp/iq.py               |   56 
 build/lib/gozerbot/xmpp/jid.py              |   36 
 build/lib/gozerbot/xmpp/message.py          |  277 ++++
 build/lib/gozerbot/xmpp/monitor.py          |   41 
 build/lib/gozerbot/xmpp/namespace.py        |   18 
 build/lib/gozerbot/xmpp/presence.py         |   50 
 build/lib/gozerbot/xmpp/wait.py             |   88 +
 build/lib/gplugs/8b.py                      |   48 
 build/lib/gplugs/__init__.py                |   25 
 build/lib/gplugs/alarm.py                   |  265 ++++
 build/lib/gplugs/alchemy/__init__.py        |   25 
 build/lib/gplugs/alchemy/birthday.py        |  215 +++
 build/lib/gplugs/alchemy/infoitem.py        |  356 +++++
 build/lib/gplugs/alchemy/karma.py           |  699 +++++++++++
 build/lib/gplugs/alchemy/lists.py           |  406 ++++++
 build/lib/gplugs/alchemy/quote.py           |  452 +++++++
 build/lib/gplugs/alchemy/todo.py            |  690 +++++++++++
 build/lib/gplugs/autoreply.py               |   45 
 build/lib/gplugs/autovoice.py               |   61 
 build/lib/gplugs/away.py                    |  108 +
 build/lib/gplugs/banner.py                  |   56 
 build/lib/gplugs/bash.py                    |   69 +
 build/lib/gplugs/beats.py                   |   26 
 build/lib/gplugs/bender.py                  |   80 +
 build/lib/gplugs/botsnack.py                |   88 +
 build/lib/gplugs/bugtracker.py              |  710 +++++++++++
 build/lib/gplugs/buzz.py                    |   61 
 build/lib/gplugs/chatlog.py                 |  402 ++++++
 build/lib/gplugs/country.py                 |  333 +++++
 build/lib/gplugs/dns.py                     |  152 ++
 build/lib/gplugs/dope.py                    |   66 +
 build/lib/gplugs/drinks.py                  |   79 +
 build/lib/gplugs/echo.py                    |   22 
 build/lib/gplugs/event.py                   |  227 +++
 build/lib/gplugs/eventnet.py                |  213 +++
 build/lib/gplugs/facts.py                   |  754 ++++++++++++
 build/lib/gplugs/fans.py                    |  101 +
 build/lib/gplugs/gcalc.py                   |   50 
 build/lib/gplugs/gcollect.py                |   92 +
 build/lib/gplugs/grab.py                    |   43 
 build/lib/gplugs/greeting.py                |  168 ++
 build/lib/gplugs/hello.py                   |   20 
 build/lib/gplugs/hex2ip.py                  |   39 
 build/lib/gplugs/hexjoin.py                 |   69 +
 build/lib/gplugs/httpwatch.py               |  159 ++
 build/lib/gplugs/identi.py                  | 1755 ++++++++++++++++++++++++++++
 build/lib/gplugs/identify.py                |  134 ++
 build/lib/gplugs/idle.py                    |   85 +
 build/lib/gplugs/ipcalc.py                  |  433 ++++++
 build/lib/gplugs/kickban.py                 |  128 ++
 build/lib/gplugs/lag.py                     |  101 +
 build/lib/gplugs/lart.py                    |   37 
 build/lib/gplugs/links.py                   |   72 +
 build/lib/gplugs/lns.py                     |   92 +
 build/lib/gplugs/log.py                     |  891 ++++++++++++++
 build/lib/gplugs/mail.py                    |  182 ++
 build/lib/gplugs/mailexceptions.py          |   47 
 build/lib/gplugs/markov.py                  |  543 ++++++++
 build/lib/gplugs/mpd.py                     |  346 +++++
 build/lib/gplugs/olddb/__init__.py          |   25 
 build/lib/gplugs/olddb/birthday.py          |  190 +++
 build/lib/gplugs/olddb/infoitem.py          |  306 ++++
 build/lib/gplugs/olddb/karma.py             |  773 ++++++++++++
 build/lib/gplugs/olddb/lists.py             |  359 +++++
 build/lib/gplugs/olddb/quote.py             |  463 +++++++
 build/lib/gplugs/olddb/todo.py              |  708 +++++++++++
 build/lib/gplugs/olddb/user.py              |  908 ++++++++++++++
 build/lib/gplugs/oneliner.py                |  234 +++
 build/lib/gplugs/ops.py                     |  175 ++
 build/lib/gplugs/pit.py                     |   85 +
 build/lib/gplugs/powernick.py               |   72 +
 build/lib/gplugs/probe.py                   |   42 
 build/lib/gplugs/projecttracker.py          |  490 +++++++
 build/lib/gplugs/rblcheck.py                |  651 ++++++++++
 build/lib/gplugs/register.py                |   70 +
 build/lib/gplugs/relay.py                   |  421 ++++++
 build/lib/gplugs/remind.py                  |  130 ++
 build/lib/gplugs/rss.py                     | 1363 +++++++++++++++++++++
 build/lib/gplugs/search.py                  |   51 
 build/lib/gplugs/sed.py                     |  121 +
 build/lib/gplugs/seen.py                    |  143 ++
 build/lib/gplugs/shakespear.py              |  229 +++
 build/lib/gplugs/shop.py                    |  285 ++++
 build/lib/gplugs/shoutcast.py               |  227 +++
 build/lib/gplugs/snarf.py                   |  263 ++++
 build/lib/gplugs/tcp.py                     |  250 +++
 build/lib/gplugs/tel.py                     |  143 ++
 build/lib/gplugs/timebomb.py                |  121 +
 build/lib/gplugs/timer.py                   |   37 
 build/lib/gplugs/tinyurl.py                 |   92 +
 build/lib/gplugs/topic.py                   |  239 +++
 build/lib/gplugs/trac.py                    |   23 
 build/lib/gplugs/translate.py               |  171 ++
 build/lib/gplugs/twitter.py                 | 1738 +++++++++++++++++++++++++++
 build/lib/gplugs/udp.py                     |  336 +++++
 build/lib/gplugs/umode.py                   |  120 +
 build/lib/gplugs/upgrade.py                 |  161 ++
 build/lib/gplugs/urban.py                   |   54 
 build/lib/gplugs/url.py                     |  110 +
 build/lib/gplugs/urlinfo.py                 |  177 ++
 build/lib/gplugs/watcher.py                 |  244 +++
 build/lib/gplugs/weather.py                 |  104 +
 build/lib/gplugs/whois.py                   |   56 
 build/lib/gplugs/wikipedia.py               |   93 +
 build/lib/gplugs/wikiquote.py               |   61 
 build/lib/gplugs/wisdom.py                  |  630 ++++++++++
 build/lib/gplugs/wowwiki.py                 |   90 +
 build/lib/gplugs/yahoo.py                   |  646 ++++++++++
 build/scripts-2.5/gozerbot                  |  292 ++++
 build/scripts-2.5/gozerbot-init             |   24 
 build/scripts-2.5/gozerbot-start            |   23 
 build/scripts-2.5/gozerbot-stop             |   18 
 build/scripts-2.5/gozerbot-udp              |  450 +++++++
 gozerbot.egg-info/PKG-INFO                  |    2 
 gozerbot.egg-info/SOURCES.txt               |    1 
 setup.cfg                                   |    1 
 245 files changed, 57111 insertions(+), 2 deletions(-)

    
download this patch

Patch contents

--- gozerbot-0.99.1.orig/setup.cfg
+++ gozerbot-0.99.1/setup.cfg
@@ -2,4 +2,3 @@
 tag_build = 
 tag_date = 0
 tag_svn_revision = 0
-
--- gozerbot-0.99.1.orig/gozerbot.egg-info/SOURCES.txt
+++ gozerbot-0.99.1/gozerbot.egg-info/SOURCES.txt
@@ -1,6 +1,7 @@
 Changelog
 MANIFEST.in
 README.txt
+setup.cfg
 setup.py
 bin/gozerbot
 bin/gozerbot-init
--- gozerbot-0.99.1.orig/gozerbot.egg-info/PKG-INFO
+++ gozerbot-0.99.1/gozerbot.egg-info/PKG-INFO
@@ -1,4 +1,4 @@
-Metadata-Version: 1.0
+Metadata-Version: 1.1
 Name: gozerbot
 Version: 0.99.1
 Summary: the irc bot and jabber bot in one
--- gozerbot-0.99.1.orig/build/lib/gozerbot/cache.py
+++ gozerbot-0.99.1/build/lib/gozerbot/cache.py
@@ -0,0 +1,12 @@
+# gozerbot.cache
+#
+#
+
+""" attempt to make a global usercache. not used yet. """
+
+# INIT SECTION
+
+# global userhost cache
+userhosts = {}
+
+# END SECTION
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/monitor.py
+++ gozerbot-0.99.1/build/lib/gozerbot/monitor.py
@@ -0,0 +1,168 @@
+# gozerbot/monitor.py
+#
+#
+
+""" `gozerbot.monitor` .. monitor the bots output
+
+This module contains the Monitor base class use to implement callbacks for 
+the bot's output. Used in logging plugins. The actual monitor objects live
+irc and xmpp submodules.
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+## IMPORT SECTION
+
+# gozerbot imports
+from gozerbot.config import config
+from gozerbot.stats import stats
+from utils.log import rlog
+from utils.exception import handle_exception
+from utils.trace import calledfrom
+from config import config
+from threads.threadloop import ThreadLoop
+from runner import waitrunners
+import threads.thr as thr
+
+# basic imports
+import Queue, sys
+
+## END IMPORT
+
+## LOCK SECTION
+
+# no locks
+
+## END LOCK
+
+class Monitor(ThreadLoop):
+
+    """ monitor base class. used as base class for jabber and irc 
+        output monitoting.
+
+        :param name: name of the monitor
+        :type name: string
+
+    """
+
+    def __init__(self, name="monitor"):
+        ThreadLoop.__init__(self, name)        
+        self.outs = []
+
+    def add(self, name, callback, pre, threaded=False):
+
+        """ add a monitoring callback.
+
+            :param name: name of the plugin using this monitor callback
+            :type name: string
+            :param callback: the callback to fire
+            :type callback: function
+            :param pre: precondition (function) to check if callback should fire
+            :type pre: function
+            :param threaded: whether callback should be called in its own thread
+            :type threaded: boolean
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/monitor.py
+                :pyobject: Monitor.add
+
+        """
+
+        name = name or calledfrom(sys._getframe(0))
+
+        if config['loadlist'] and name not in config['loadlist']:
+            return False
+
+        self.outs.append([name, callback, pre, threaded, False])
+        rlog(0, self.name, 'added monitor %s (%s)' % (name, str(callback)))
+        return True
+
+    def disable(self, name):
+        name = name.lower()
+
+        for i in range(len(self.outs)-1, -1, -1):
+            if self.outs[i][0] == name:
+                self.outs[i][4] = False
+
+    def activate(self, name):
+        name = name.lower()
+
+        for i in range(len(self.outs)-1, -1, -1):
+            if self.outs[i][0] == name:
+                self.outs[i][4] = True
+        
+    def unload(self, name):
+
+        """ delete monitor callback. 
+
+            :param name: name of the plugin which monitors need to be unloaded
+            :type name: string
+            :rtype: integer .. number of monitors removed
+
+            .. literalinclude:: ../../gozerbot/monitor.py
+                :pyobject: Monitor.unload
+
+        """
+
+        name = name.lower()
+        nr = 0
+
+        for i in range(len(self.outs)-1, -1, -1):
+            if self.outs[i][0] == name:
+                del self.outs[i]
+                nr += 1
+
+        return nr
+   
+    def handle(self, *args, **kwargs):
+
+        """ check if monitor callbacks need to be fired. 
+
+           :param args: arguments passed to the callback
+           :type args: list
+           :param kwargs: quoted arguments passed to the callback
+           :type kwargs: dict
+           :rtype: number of callbacks called 
+
+           .. literalinclude:: ../../gozerbot/monitor.py
+               :pyobject: Monitor.handle
+
+        """
+
+        nr = 0
+        for i in self.outs:
+
+            if not i[4]:
+                continue
+            # check if precondition is met
+            try:
+                if i[2]:
+                    stats.up('monitors', thr.getname(str(i[2])))
+                    rlog(-10, 'monitor', 'checking inloop %s' % str(i[2]))
+                    doit = i[2](*args, **kwargs)
+                else:
+                    doit = 1
+
+            except Exception, ex:
+                handle_exception()
+                doit = 0
+
+            if doit:
+                # run monitor callback in its own thread
+                rlog(0, 'monitor', 'excecuting monitor callback %s' % i[0])
+                stats.up('monitors', thr.getname(str(i[1])))
+                if not i[3]:
+                    waitrunners.put("monitor-%s" % i[0], i[1], *args)
+                else:
+                    thr.start_new_thread(i[1], args, kwargs)
+
+                nr += 1
+
+        return nr
+
+## INIT SECTION
+
+# no vars
+
+## END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/less.py
+++ gozerbot-0.99.1/build/lib/gozerbot/less.py
@@ -0,0 +1,159 @@
+# gozerbot/less.py
+#
+#
+
+""" maintain bot output cache. """
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+# gozerbot imports
+from utils.limlist import Limlist
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
+
+class Less(object):
+
+    """
+        output cache .. caches upto <nr> item of txt lines per nick.
+
+        :param nr: size of backlog
+        :type nr: integer
+
+    """
+
+    def __init__(self, nr):
+        self.data = {}
+        self.index = {}
+        self.nr = nr
+
+    def add(self, nick, listoftxt):
+
+        """
+            add listoftxt to nick's output .. set index for used by more 
+            commands.
+
+            :param nick: nick to add txt to cache for
+            :type nick: string
+            :param listoftxt: list of txt to cache
+            :type listoftxt: list
+
+            .. literalinclude:: ../../gozerbot/less.py
+                :pyobject: Less.add
+
+        """
+
+        nick = nick.lower()
+
+        # see if we already have cached output .. if not create limited list
+        if not self.data.has_key(nick):
+            self.data[nick] = Limlist(self.nr)
+
+        # add data
+        self.data[nick].insert(0, listoftxt)
+        self.index[nick] = 1
+
+    def get(self, nick, index1, index2):
+
+        """
+             return less entry.
+
+             entry is self.data[nick][index1][index2]
+
+             :param nick: nick to get data for
+             :type nick: string
+             :param index1: number of txtlines back
+             :type index1: integer
+             :param index2: index into the txtlines 
+             :type index2: integer
+             :rtype: string
+
+             .. literalinclude:: ../../gozerbot/less.py
+                 :pyobject: Less.get
+
+        """
+
+        nick = nick.lower()
+
+        try:
+            txt = self.data[nick][index1][index2]
+        except (KeyError, IndexError):
+            txt = None
+        return txt
+
+    def more(self, nick, index1):
+
+        """
+             return more entry pointed to by index .. increase index.
+
+             :param nick: nick to fetch data for
+             :type nick: string
+             :param index1: index into cache data
+             :type index1: integer
+             :rtype: tuple .. (txt, index)
+
+             .. literalinclude:: ../../gozerbot/less.py
+                 :pyobject: Less.more
+
+        """
+
+        nick = nick.lower()
+
+        try:
+            nr = self.index[nick]
+        except KeyError:
+            nr = 1
+
+        try:
+            txt = self.data[nick][index1][nr]
+            size = len(self.data[nick][index1])-nr
+            self.index[nick] = nr+1
+        except (KeyError, IndexError):
+            txt = None
+            size = 0
+
+        return (txt, size-1)
+
+    def size(self, nick):
+
+        """
+             return sizes of cached output.
+
+             :param nick: nick to get cache sizes for
+             :type nick: string
+             :rtype: list .. list of sizes
+
+             .. literalinclude:: ../../gozerbot/less.py
+                 :pyobject: Less.size
+
+        """
+
+        nick = nick.lower()
+        sizes = []
+
+        if not self.data.has_key(nick):
+            return sizes
+
+        for i in self.data[nick]:
+            sizes.append(len(i))
+
+        return sizes
+
+# ============
+# INIT SECTION
+
+# no vars
+
+# END INIT
+# ========
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/partyline.py
+++ gozerbot-0.99.1/build/lib/gozerbot/partyline.py
@@ -0,0 +1,365 @@
+# gozerbot/partyline.py
+#
+#
+
+""" provide partyline functionality .. manage dcc sockets. """
+
+
+__copyright__ = 'this file is in the public domain'
+__author__ = 'Aim'
+
+## IMPORT SECTION
+
+# gozerbot imports
+from utils.log import rlog
+from utils.exception import handle_exception
+from fleet import fleet
+from simplejson import load
+from threads.thr import start_new_thread
+
+# basic imports
+import thread, pickle, socket
+
+## END IMPORT
+
+## LOCK SECTION
+
+# no locks
+
+## END LOCK
+
+class PartyLine(object):
+
+    """
+        partyline can be used to talk through dcc chat connections.
+
+    """
+
+    def __init__(self):
+        self.socks = [] # partyline sockets list
+        self.jids = []
+        self.lock = thread.allocate_lock()
+
+    def _doresume(self, data, reto=None):
+
+        """
+            resume a party line connection after reboot.
+
+            :param data: resume data
+            :type data: dict .. see PartyLine._resumedata
+            :param reto: nick of user to reply to
+            :type reto: string
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+               :pyobject PartyLine._doresume
+
+        """
+
+        for i in data['partyline']:
+            bot = fleet.byname(i['botname'])
+            sock = socket.fromfd(i['fileno'], socket.AF_INET, socket.SOCK_STREAM)
+            sock.setblocking(1)
+            nick = i['nick']
+            userhost = i['userhost']
+            channel = i['channel']
+
+            if not bot:
+                rlog(10, 'partyline', "can't find %s bot in fleet" % i['botname'])
+                continue
+
+            self.socks.append({'bot': bot, 'sock': sock, 'nick': nick, 'userhost': userhost, 'channel': channel, 'silent': i['silent']})
+            bot._dccresume(sock, nick, userhost, channel)        
+
+            if reto:
+                self.say_nick(nick, 'rebooting done')
+
+    def _resumedata(self):
+
+        """
+             return data used for resume.
+
+             :rtype: list .. list of resumedata (dicts)
+
+             .. literalinclude:: ../../gozerbot/partyline.py
+                 :pyobject: PartyLine._resumedata
+        """
+
+        result = []
+
+        for i in self.socks:
+            result.append({'botname': i['bot'].name, 'fileno': i['sock'].fileno(), 'nick': i['nick'], 'userhost': i['userhost'], 'channel': i['channel'], 'silent': i['silent']})
+
+        return result
+
+    def resume(self, sessionfile):
+
+        """
+             resume from session file.
+
+             :param sessionfile: path to resume file
+             :type sessionfile: string
+
+             .. literalinclude:: ../../gozerbot/partyline.py
+                 :pyobject PartyLine.resume
+        """
+
+        session = load(open(sessionfile, 'r'))
+
+        try:
+            reto = session['channel']
+            self._doresume(session, reto)
+
+        except Exception, ex:
+            handle_exception()
+
+    def stop(self, bot):
+
+        """
+            stop all users on bot.
+
+            :param bot: bot to stop users on
+            :type bot: gozerbot.eventbase.EventBase
+            
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.stop
+        """
+
+        for i in self.socks:
+
+            if i['bot'] == bot:
+                try:
+                    i['sock'].shutdown(2)
+                    i['sock'].close()
+                except:
+                    pass
+                 
+    def stop_all(self):
+
+        """
+             stop every user on partyline.
+
+             .. literalinclude:: ../../gozerbot/partyline.py
+                 :pyobject: PartyLine.stop_all
+
+        """
+
+        for i in self.socks:
+            try:
+                i['sock'].shutdown(2)
+                i['sock'].close()
+            except:
+                pass
+
+    def loud(self, nick): 
+
+        """
+            enable broadcasting of txt for nick.
+
+            :param nick: nick to put into loud mode
+            :type nick: string
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.loud
+
+        """
+
+        for i in self.socks:
+
+            if i['nick'] == nick:
+                i['silent'] = False
+
+    def silent(self, nick):
+
+        """
+            disable broadcasting txt from/to nick.
+
+            :param nick: nick to put into silent mode
+            :type nick: string
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.disable
+
+        """
+
+        for i in self.socks:
+
+            if i['nick'] == nick:
+                i['silent'] = True
+
+    def add_party(self, bot, sock, nick, userhost, channel):
+
+        '''
+            add a socket with nick to the list.
+
+            :param bot: bot to add party on
+            :type bot: gozerbot.botbase.BotBase
+            :param sock: socket of party to add
+            :type sock: socket.socket
+            :param nick: nick of party to add
+            :type nick: string
+            :param userhost: userhost of party to add
+            :type userhost: string
+            :param channel: channel of party to add
+            :type channel: string
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.add_party
+
+        '''
+
+        for i in self.socks:
+
+            if i['sock'] == sock:
+                return            
+
+        self.socks.append({'bot': bot, 'sock': sock, 'nick': nick, \
+'userhost': userhost, 'channel': channel, 'silent': False})
+
+        rlog(1, 'partyline', 'added user %s on the partyline' % nick)
+
+    def del_party(self, nick):
+
+        '''
+            remove a socket with nick from the list.
+
+            :param nick: nick to remove
+            :type nick: string
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.del_party
+ 
+        '''
+
+        nick = nick.lower()
+        self.lock.acquire()
+
+        try:
+
+            for socknr in range(len(self.socks)-1, -1, -1):	
+
+                if self.socks[socknr]['nick'].lower() == nick:
+                    del self.socks[socknr]
+
+            rlog(1, 'partyline', 'removed user %s from the partyline' % nick)
+
+        finally:
+            self.lock.release()
+
+    def list_nicks(self):
+
+        '''
+            list all connected nicks.
+
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.list_nicks
+
+        '''
+
+        result = []
+
+        for item in self.socks:
+            result.append(item['nick'])
+
+        return result
+
+    def say_broadcast(self, txt):
+
+        '''
+            broadcast a message to all ppl on partyline.
+
+            :param txt: txt to broadcast
+            :type txt: string
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.say_broadcast
+
+        '''
+
+        for item in self.socks:
+
+            if not item['silent']:
+                item['sock'].send("%s\n" % txt)
+
+    def say_broadcast_notself(self, nick, txt):
+
+        '''
+             broadcast a message to all ppl on partyline, except the sender.
+
+            :param nick: nick to ignore
+            :type nick: string
+            :param txt: text to broadcast
+            :type txt: string
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+               :pyobject: PartyLine.say_broadcast_notself
+
+        '''
+
+        nick = nick.lower()
+
+        for item in self.socks:
+
+            if item['nick'] == nick:
+                continue
+
+            if not item['silent']:
+                item['sock'].send("%s\n" % txt)
+
+    def say_nick(self, nickto, msg):
+
+        '''
+            say a message on the partyline to an user.
+
+            :param nickto: nick to send txt to
+            :type nickto: string
+            :param msg: msg to send
+            :type msg: string
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.say_nick
+
+        '''
+
+        nickto = nickto.lower()
+
+        for item in self.socks:
+
+            if item['nick'].lower() == nickto:
+
+                if not '\n' in msg:
+                    msg += "\n"
+
+                item['sock'].send("%s" % msg)
+                return
+
+    def is_on(self, nick):
+
+        '''
+            checks if user an is on the partyline.
+
+            :param nick: nick to check
+            :type nick: string
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/partyline.py
+                :pyobject: PartyLine.is_on
+
+        '''
+
+        nick = nick.lower()
+
+        for item in self.socks:
+
+            if item['nick'].lower() == nick:
+                return True
+
+        return False
+
+## INIT SECTION
+
+# the partyline !
+partyline = PartyLine()
+
+## END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/plughelp.py
+++ gozerbot-0.99.1/build/lib/gozerbot/plughelp.py
@@ -0,0 +1,73 @@
+# gozerbot/plughelp.py
+#
+#
+
+"""
+   help about plugins.
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+## IMPORT SECTION
+
+# no imports
+
+## END IMPORT
+
+## LOCK SECTION
+
+# no locks
+
+## END LOCK
+
+class PlugHelp(dict):
+
+    """
+        dict holding plugins help string.
+
+    """ 
+
+    def add(self, item, descr):
+
+        """
+            add plugin help string.
+
+            :param item: the plugin to store description for
+            :type item: string
+            :param descr: the description of the plugin
+            :type descr: string
+
+            .. literalinclude ../../gozerbot/plughelp.py
+                :pyobject: PlugHelp.add
+
+        """
+
+        item = item.lower()
+        self[item] = descr
+
+    def get(self, item):
+
+        """
+            get plugin help string.
+
+            :param item: plugin to get description for
+            :type item: string
+            :rtype: string
+
+            .. literalinclude:: ../../gozerbot/plughelp.py
+                :pyobject: PlugHelp.get
+        """
+
+        item = item.lower()
+        try:
+            return self[item]
+        except KeyError:
+            return None
+
+## INIT SECTION
+
+# plughelp object
+plughelp = PlugHelp()
+
+# END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/generic.py
+++ gozerbot-0.99.1/build/lib/gozerbot/generic.py
@@ -0,0 +1,31 @@
+# generic compat stub
+#
+#
+
+""" utils stub for backwards compatibility. """
+
+# ==============
+# IMPORT SECTION
+
+from utils.log import *
+from utils.generic import *
+from utils.exception import *
+from utils.popen import *
+from utils.timeutils import *
+from utils.fileutils import *
+from utils.reboot import *
+from utils.trace import *
+from utils.locking import *
+from utils.rsslist import *
+from utils.url import *
+
+# END IMPORT
+# ==========
+
+# =========
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
--- gozerbot-0.99.1.orig/build/lib/gozerbot/plugins.py
+++ gozerbot-0.99.1/build/lib/gozerbot/plugins.py
@@ -0,0 +1,1509 @@
+# gozerbot/plugins.py
+#
+#
+
+""" provide plugin infrastructure """
+
+__copyright__ = 'this file is in the public domain'
+
+## gozerbot imports
+
+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
+
+# basic imports
+import os, os.path, thread, time, Queue, re, copy
+
+## END IMPORT
+
+## LOCK SECTION
+
+loadlock = thread.allocate_lock()
+loadlocked = lockdec(loadlock)
+
+## END LOCK
+
+class Plugins(object):
+
+    """
+        hold all the plugins.
+
+    """
+
+    def __init__(self):
+        self.plugs = {} # dict with the plugins
+        self.reloadlock = thread.allocate_lock()
+        # persisted data for deny of plugins (blacklist)
+        self.plugdeny = Persist(datadir + os.sep + 'plugdeny', init=False)
+        if not self.plugdeny.data:
+            self.plugdeny.data = []
+        # persisted data for allowing of plugins (whitelist)
+        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.
+
+            :param item: plugin to get attribute of
+            :type item: string
+            :param attr: attribute to fetch
+            :type attr: string
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.get
+
+        """
+
+        if self.plugs.has_key(item):
+            return getattr(self.plugs[item], attr)
+
+    def whatperms(self):
+
+        """
+            return what permissions are possible.
+
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.whatperms
+
+        """
+
+        result = []
+
+        # search RE callbacks before the commands
+        for i in rebefore.whatperms():
+            if not i in result:
+                result.append(i)
+
+        # search the commands
+        for i in cmnds.whatperms():
+            if not i in result:
+                result.append(i)
+
+        # search RE callbacks after commands
+        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.
+
+            :param name: name of plugin to check for
+            :type name: string
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.exist
+
+        """
+
+        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.
+
+            :param name: name of the plugin to disable
+            :type name: string
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.disable
+
+        """
+
+        try:
+            config['loadlist'].remove(name)
+            config.save()
+            self.plugdeny.data.append(name)
+            self.plugdeny.save()
+            
+        except:
+            pass
+
+    def enable(self, name):
+
+        """
+            enable plugin.
+
+            :param name: name of plugin to enable
+            :type name: string
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.enable
+
+        """
+
+        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.
+
+            :rtype: list .. list of plugin sizes
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.plugsizes
+        """
+
+        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.
+
+            :rtype: list .. list of enabled plugins
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.list
+
+        """
+
+        self.avail.sort()
+        return self.avail
+
+    def plugimport(self, mod, name):
+
+        """
+            import a plugin.
+
+            :param mod: module to import plugin from
+            :type mod: string
+            :param name: name of the plugin to import
+            :type name: string
+            :rtype: module .. the plugin
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.plugimport
+
+        """
+        
+        if name in config['loadlist']:
+            return self.load(mod, name)
+
+    def regplugin(self, mod, name):
+
+        """
+            register plugin.
+
+            :param mod: module to import plugin from
+            :type mod: string
+            :param name: name of the plugin to import
+            :type name: string
+            :rtype: module .. the plugin
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.regplugin
+
+        """
+
+        name = name.lower()
+        mod = mod.lower()
+        modname = mod + '.' + name
+
+        # see if plugin is in deny
+        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 plugin is already registered unload it
+        if self.plugs.has_key(name):
+            rlog(10, 'plugins', 'overloading %s plugin with %s version' % (name, mod))
+            self.unloadnosave(name)
+
+        # create the plugin data dir
+        if hasattr(os, 'mkdir'):
+            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)
+
+        # import the plugin
+        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.
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.showregistered
+
+        """
+
+        self.avail.sort()
+        rlog(10, 'plugins', 'registered %s' % ' .. '.join(self.avail))
+        self.overload()
+
+    def regdir(self, dirname, exclude=[]):
+
+        """
+            register a directory.
+
+            :param dirname: directory to import plugins from
+            :type dirname: string
+            :param exclude: plugins to exclude from importing
+            :type exclude: list .. list of plugin names
+            :rtype: list .. list of plugin names that are registered
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.regdir
+
+        """
+
+        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.
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.regcore
+
+        """
+
+        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
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.enableall
+
+        """
+
+        for name in self.available():
+            self.enable(name)
+
+    def regplugins(self):
+
+        """
+            register all plugins.
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.regplugins
+
+        """
+
+        self.regcore()
+        avail = []
+
+        # check for myplugs directory
+        if os.path.isdir('myplugs'):
+            avail.extend(self.regdir('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')
+
+        # check for gplugs package
+        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":
+            # check for gplugs package
+            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:
+            # check for gplugs package
+            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.
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.readoverload
+
+        """
+
+        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. 
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.overload
+
+        """
+        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.
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.available
+
+        """
+
+        self.ondisk.sort()
+        return self.ondisk
+
+    def saveplug(self, plugname):
+
+        """
+            call save() function of plugin.
+
+            :param plugname: name of the plugin to call save() on
+            :type plugname: string
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.saveplug
+        """
+
+        try:
+            self.plugs[plugname].save()
+
+        except AttributeError:
+            pass
+
+        except KeyError:
+            pass
+
+    def save(self):
+
+        """
+            call registered plugins save.
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: 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.
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.save_cfg
+
+        """
+
+        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.
+
+            :param name: name of the plugin to call cfg.save for
+            :type name: string
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.save_cfgname
+        """
+
+        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. 
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.exit
+
+        """
+
+        self.save()
+        threadlist = []
+
+        # call shutdown on all plugins
+        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)))
+
+        # join shutdown threads
+        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.
+
+            :param command: command name to get options of
+            :type command: string
+            :rtype: dict
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.getoptions
+
+        """
+
+        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().
+
+        """
+
+        # try to import the plugin
+        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
+
+        # check for the __depend__ attribute           
+        try: 
+            depends = plug.__depend__
+        except:
+            depends = []
+
+        return depends
+
+    def load(self, mod , name, enable=True):
+        #if name in config['plugdeny']:
+        #     return
+        # force an import of the plugin
+        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()
+            # call plugins init() function
+            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
+
+    #@loadlocked
+    def reload(self, mod, name, enable=True):
+
+        """
+            reload plugin.
+
+            :param mod: module to import plugin from
+            :type mod: string
+            :param name: name of the plugin to reload
+            :type name: string
+            :param enable: whether plugin should be enabled on reload
+            :type enable: boolean  
+            :rtype: list .. list of names of reloaded plugins
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.reload
+
+        """
+
+        # create the plugin data dir
+        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
+
+        # force an import of the plugin
+        plug = self.load(mod, name)
+
+        # recurse the reload function if plugin is a dir
+        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)
+
+        # recurse on plugins the depend on this plugin
+        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):
+        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()
+            return 0
+
+    def down(self, plugname):
+        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()
+            return 0
+
+    def unload(self, plugname):
+
+        """
+            unload plugin.
+
+            :param plugname: name of the plugin to unload
+            :type plugname: string
+            :rtype: list .. list of unloaded plugins
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.unload
+ 
+        """
+
+        # call plugins shutdown function if available
+        unloaded = [plugname, ]
+
+        # recurse if plugin is dir
+        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
+
+        # save and unload
+        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.
+
+            :param plugname: name of the plugin to unload
+            :type plugname: string
+            :rtype: list .. list of unloaded plugins
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.unloadnosave
+
+        """
+
+        # call shutdown function
+        try:
+            self.plugs[plugname].shutdown()
+            rlog(10, 'plugins', '%s shutdown called' % plugname)
+
+        except (AttributeError, KeyError):
+            pass
+
+        except Exception, ex:
+            handle_exception()
+
+        # remove from plugallow
+        try:
+            self.plugallow.data.remove(plugname)
+        except (KeyError, ValueError):
+            pass
+
+        # remove from avail list
+        try:
+            self.avail.remove(plugname)
+        except ValueError:
+            pass
+
+        # remove from initcalled list
+        try:
+            self.initcalled.remove(plugname)
+        except ValueError:
+            pass
+
+        # unload commands, RE callbacks, callbacks, monitorsetc.
+        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 command.
+
+            :param what: name of command to search
+            :type what: string
+            :rtype: string .. plugin the command is in
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.whereis
+
+        """
+
+        return cmnds.whereis(what)
+
+    def permoverload(self, funcname, perms):
+
+        """
+            overload permission of a function.
+
+            :param funcname: name of function to overload permissions of
+            :type funcname: string
+            :param perms: permissions to overload
+            :type perms: list .. list of permissions
+            :rtype: boolean: whether overload worked or not
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.permoverload
+
+        """
+
+        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.
+
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+            :rtype: boolean .. whether the dispatch should fire
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.woulddispatch
+
+        """
+        #self.needreloadcheck(bot, ievent)
+        (what, command) = self.dispatchtest(bot, ievent)
+
+        #if what and not what.activate:
+        #    return False
+
+        if what and command:
+            return True
+
+        return False
+
+    #@funclocked
+    def dispatchtest(self, bot, ievent, direct=False):
+
+        """
+            return (dispatcher, command) on which command should fire.
+
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+            :param direct: whether user permission should be checked
+            :type direct: boolean .. when set user permission is NOT checked
+            :rtype: tuple .. (dispatcher, command) tuples
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.dispatchtest
+        """
+
+        # check for ignore
+        if shouldignore(ievent.userhost):
+            return (None, None)
+
+        # check for throttle
+        if ievent.userhost in bot.throttle:
+            return (None, None)
+
+        # set target properly
+        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 = []
+
+        # first check for RE before commands dispatcher
+        com = rebefore.getcallback(target)
+
+        if com and not target.startswith('!'):
+            com.re = True
+            result = [rebefore, com]
+        else:
+
+            # try commands 
+            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:
+
+                # try RE after commands
+                com = reafter.getcallback(target)
+                if com:
+                    com.re = True
+                    result = [reafter, com]
+        if result:
+
+            # check for auto registration
+            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)
+
+            # check for anon access
+            if config['anon_enable'] and not 'OPER' in result[1].perms:
+                return result
+
+            # check if command is allowed (all-add command)
+            if com.name in bot.state['allowed'] or getname(com.func) in bot.state['allowed']:
+                return result
+
+            # check for channel permissions
+            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 is set dont check the user database
+            if direct:
+                return result
+
+            # use event.stripped in case of jabber 
+            if bot.jabber and ievent.jabber:
+                if not ievent.groupchat or ievent.jidchange:
+                    if users.allowed(ievent.stripped, result[1].perms):
+                        return result
+
+            # irc users check
+            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.
+
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+            :param timeout: number of seconds to wait for a result
+            :type timeout: integer
+            :param response: whether to notify user we are running the command
+            :type response: string
+            :rtype: list .. list of results
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.cmnd
+
+        """
+        if response:
+            ievent.reply('launching %s on %s bot' % (ievent.txt, bot.name))
+        #ii = self.clonedevent(bot, ievent)
+        #q = Queue.Queue()
+        #ii.queues.append(q)
+        #ii.onlyqueues = onlyqueues
+        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.
+
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+            :param direct: whether user permission should be checked
+            :type direct: boolean .. when set user permission is NOT checked
+            :rtype: boolean .. whether dispatch succeeded or not
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.waitdispatch
+
+        """
+        ievent.threaded = True
+        return self.trydispatch(bot, ievent, direct, wait=True)
+
+    def trydispatch(self, bot, ievent, direct=False, wait=False):
+
+        """
+            try to dispatch ievent.
+
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+            :param direct: whether user permission should be checked
+            :type direct: boolean .. when set user permission is NOT checked
+            :param wait: whether function should wait for results
+            :type wait: boolean
+            :rtype: boolean .. whether dispatch succeeded or not
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.trydispatch
+
+        """
+
+        # test for ignore
+        if shouldignore(ievent.userhost):
+            return 0
+
+        # set printto
+        if ievent.msg:
+            ievent.printto = ievent.nick
+        else:
+            ievent.printto = ievent.channel
+
+        # see if ievent would dispatch
+        # what is rebefore, cmnds of reafter, com is the command object
+        # check if redispatcher or commands object needs to be used
+        #self.needreloadcheck(bot, ievent)
+
+        (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.
+
+            :param what: the dispatcher to dispatch the command on
+            :type what: gozerbot.redispatcher.REdispatcher or gozerbot.commands.Commands
+            :param com: the command to dispatch
+            :type com: gozerbot.redispatcher.REcallback or gozerbot.commands.Command
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+            :param wait: whether function should wait for results
+            :type wait: boolean
+            :rtype: boolean .. whether dispatch succeeded or not
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.dispatch
+
+        """
+        if bot.stopped:
+            return False
+
+        # make command options
+        if com.options:
+            makeoptions(ievent, com.options)
+        else:
+            makeoptions(ievent)
+
+        # make arguments and rest
+        makeargrest(ievent)
+        ievent.usercmnd = True
+        rlog(10, 'plugins', 'dispatching %s for %s' % (ievent.command, ievent.userhost))
+
+        # call dispatch
+        what.dispatch(com, bot, ievent, wait)
+        return True
+
+    def clonedevent(self, bot, event):
+
+        """
+            clone a event.
+
+            
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+            :rtype: gozerbot.eventbase.EventBase
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.clonedevent
+
+        """
+
+        ie = copy.deepcopy(event)
+        return ie
+
+
+    def multiple(self, bot, ievent):
+
+        """
+            execute multiple commands.
+
+
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.multiple
+
+        """
+
+        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.
+
+            :param bot: bot on which command is given
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event triggering the command
+            :type ievent: gozerbot.eventbase.EventBase
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.splitpipe
+
+        """
+
+        origqueues = ievent.queues
+        ievent.queues = []
+        events = []
+        txt = ievent.txt.replace(' || ', ' ##')
+
+        # split commands
+        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)
+
+        # loop over events .. chain queues
+        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):
+
+        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.
+
+            :param pluglist: list of plugin names
+            :type pluglist: list
+            :rtype: list .. list of plugins where reload failed
+
+            .. literalinclude:: ../../gozerbot/plugins.py
+                :pyobject: Plugins.listreload
+
+        """
+
+        failed = []
+
+        # loop over the plugin list and reload them
+        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]
+
+            # reload the plugin
+            try:
+                self.reload(mod, plug)
+
+            except Exception, ex:
+                failed.append(what)
+
+        return failed
+
+## INIT SECTION
+
+# THE plugins object
+plugins = Plugins()
+
+## END INIT
+
--- gozerbot-0.99.1.orig/build/lib/gozerbot/users.py
+++ gozerbot-0.99.1/build/lib/gozerbot/users.py
@@ -0,0 +1,18 @@
+# gozerbot/users.py
+#
+#
+
+from gozerbot.config import config
+
+if config.get('db_driver') == "olddb":
+    try:
+        import gozerbot.database.db
+        import gozerbot.dbusers
+    except Exception, ex:
+        handle_exception()
+        rlog(100, 'users', 'an error has occured while trying to enable the mysql database')
+        die()
+    users = gozerbot.dbusers.Dbusers()
+else:
+    import gozerbot.sausers as sa
+    users = sa.DbUsers()
--- gozerbot-0.99.1.orig/build/lib/gozerbot/ignore.py
+++ gozerbot-0.99.1/build/lib/gozerbot/ignore.py
@@ -0,0 +1,126 @@
+# gozerbot/ignore.py
+#
+#
+
+""" ignore module. """
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+# gozerbot imports
+from persist.persist import Persist
+from datadir import datadir
+from periodical import interval
+
+# basic imports
+import time, os, thread
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
+
+def addignore(userhost, ttime):
+
+    """
+        add ignore based on userhost .. record time when ignore is set.
+
+        :param userhost: userhost to ignore
+        :type userhost: string
+        :param ttime: duration of the ignore
+        :type ttime: integer
+
+        .. literalinclude:: ../../gozerbot/ignore.py
+            :pyobject: addignore
+
+    """
+
+    global ignore
+
+    ignore[userhost] = int(ttime)
+    timeset[userhost] = time.time()
+    
+def delignore(userhost):
+
+    """
+        remove ignore.
+
+        :param userhost: userhost to remove ignore from
+        :type userhost: string
+        :rtype: boolean .. whether delete was succesfull
+
+        .. literalinclude:: ../../gozerbot/ignore.py
+            :pyobject: delignore
+
+    """
+
+    global ignore
+
+    try:
+        del ignore[userhost]
+        del timeset[userhost]
+        return True
+    except KeyError:
+        return False
+
+def shouldignore(userhost):
+
+    """
+        check if we should ignore.
+
+        :param userhost: userhost to check whether we should ignore it
+        :type userhost: string
+        :rtype: boolean
+
+        .. literalinclude:: ../../gozerbot/ignore.py
+            :pyobject: shouldignore
+
+    """
+
+    try:
+        ignoretime = ignore[userhost]
+        ignoreset = timeset[userhost]
+    except KeyError:
+        return False
+
+    if time.time() - ignoretime < ignoreset:
+        return True
+
+    return False
+
+
+@interval(60)
+def ignorecheck():
+
+    """
+        periodic function to remove users that no longer need to be ignored.
+
+        .. literalinclude:: ../../gozerbot/ignore.py
+            :pyobject: ignorecheck
+
+    """
+
+    for userhost in ignore.keys():
+        if not shouldignore(userhost):
+            delignore(userhost)
+
+
+# ============
+# INIT SECTION
+
+ignore = {}
+timeset = {}
+
+# first call to trigger interval
+ignorecheck()
+
+# END INIT
+# =======
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/wait.py
+++ gozerbot-0.99.1/build/lib/gozerbot/wait.py
@@ -0,0 +1,168 @@
+# gozerbot/wait.py
+#
+#
+
+""" wait for ircevent based on ircevent.CMND """
+
+__copyright__ = 'this file is in the public domain'
+
+# gozerbot imports
+from utils.log import rlog
+from utils.locking import lockdec
+import threads.thr as thr
+
+# basic imports
+import time, thread
+
+# locks
+waitlock = thread.allocate_lock()
+locked = lockdec(waitlock)
+
+class Wait(object):
+
+    """ lists of ircevents to wait for """
+
+    def __init__(self):
+        self.waitlist = []
+        self.ticket = 0
+
+    def register(self, cmnd, catch, queue, timeout=15):
+
+        """ register wait for cmnd. """
+
+        rlog(1, 'wait', 'registering for cmnd ' + cmnd)
+        self.ticket += 1
+        self.waitlist.insert(0, (cmnd, catch, queue, self.ticket))
+        if timeout:
+            # start timeout thread
+            thr.start_new_thread(self.dotimeout, (timeout, self.ticket))
+        return self.ticket
+
+    def check(self, ievent):
+
+        """ check if there are wait items for ievent .. check if 'catch' 
+            matches on ievent.postfix if so put ievent on queue. """
+
+        cmnd = ievent.cmnd
+        for item in self.waitlist:
+            if item[0] == cmnd:
+                if cmnd == "JOIN":
+	            catch = ievent.txt + ievent.postfix
+                else:
+                    catch = ievent.nick + ievent.postfix
+                if item[1] in catch:
+                    ievent.ticket = item[3]
+                    item[2].put_nowait(ievent)
+                    self.delete(ievent.ticket)
+                    rlog(1, 'wait', 'got response for %s' % item[0])
+                    ievent.isresponse = True
+
+    def dotimeout(self, timeout, ticket):
+
+        """ start timeout thread for wait with ticket nr. """
+
+        rlog(1, 'wait', 'starting timeouthread for %s' % str(ticket))
+        time.sleep(float(timeout))
+        self.delete(ticket)
+
+    @locked
+    def delete(self, ticket):
+
+        """ delete wait item with ticket nr. """
+
+        for itemnr in range(len(self.waitlist)-1, -1, -1):
+            if self.waitlist[itemnr][3] == ticket:
+                self.waitlist[itemnr][2].put_nowait(None)
+                del self.waitlist[itemnr]
+                rlog(1, 'wait', 'deleted ' + str(ticket))
+                return 1
+
+class Privwait(Wait):
+
+    """ wait for privmsg .. catch is on nick """
+
+    def register(self, catch, queue, timeout=15):
+
+        """ register wait for privmsg. """
+
+        rlog(1, 'privwait', 'registering for ' + catch)
+        return Wait.register(self, 'PRIVMSG', catch, queue, timeout)
+
+    def check(self, ievent):
+
+        """ check if there are wait items for ievent. """
+
+        for item in self.waitlist:
+            if item[0] == 'PRIVMSG':
+                if ievent.userhost == item[1]:
+                    ievent.ticket = item[3]
+                    item[2].put_nowait(ievent)
+                    self.delete(ievent.ticket)
+                    rlog(1, 'privwait', 'got response for %s' % item[0])
+                    ievent.isresponse = True
+
+class Jabberwait(Wait):
+
+    """ wait object for jabber messages. """
+
+    def register(self, catch, queue, timeout=15):
+
+        """ register wait for privmsg. """
+
+        rlog(1, 'jabberwait', 'registering for %s' % catch)
+        self.ticket += 1
+        self.waitlist.append((catch, queue, self.ticket))
+        if timeout:
+            thr.start_new_thread(self.dotimeout, (timeout, self.ticket))
+        return self.ticket
+
+    def check(self, msg):
+
+        """ check if <msg> is waited for. """
+
+        for teller in range(len(self.waitlist)-1, -1, -1):
+            i = self.waitlist[teller]
+            if i[0] == msg.userhost:
+                msg.ticket = i[2]
+                i[1].put_nowait(msg)
+                self.delete(msg.ticket)
+                rlog(10, 'jabberwait', 'got response for %s' % i[0])
+                msg.isresponse = 1
+
+    @locked
+    def delete(self, ticket):
+
+        """ delete wait item with ticket nr. """
+
+        for itemnr in range(len(self.waitlist)-1, -1, -1):
+            item = self.waitlist[itemnr]
+            if item[2] == ticket:
+                item[1].put_nowait(None)
+                try:
+                    del self.waitlist[itemnr]
+                    rlog(1, 'jabberwait', 'deleted ' + str(ticket))
+                except IndexError:
+                    pass
+                return 1
+
+class Jabbererrorwait(Jabberwait):
+
+    """ wait for jabber errors. """
+
+    def check(self, msg):
+
+        """ check if <msg> is waited for. """
+
+        if not msg.getType() == 'error':
+            return
+
+        errorcode = msg.getErrorCode()
+
+        for teller in range(len(self.waitlist)-1, -1, -1):
+            i = self.waitlist[teller]
+            if i[0] == 'ALL' or i[0] == errorcode:
+                msg.error = msg.getError()
+                msg.ticket = i[2]
+                i[1].put_nowait(msg)
+                self.delete(msg.ticket)
+                rlog(10,'jabbererrorwait','got error response for %s' % i[0])
--- gozerbot-0.99.1.orig/build/lib/gozerbot/stats.py
+++ gozerbot-0.99.1/build/lib/gozerbot/stats.py
@@ -0,0 +1,67 @@
+# gozerbot/stats.py
+#
+#
+
+""" maintain bot stats. """
+
+## IMPORT SECTION
+
+from gozerbot.utils.statdict import Statdict
+
+## END IMPORT
+
+## LOCK SECTION
+
+# no locks
+
+## END LOCK
+
+class GozerStats(object):
+
+    """ dict containing all gozerbot related stats. """
+
+    def __init__(self):
+        self.data = {}
+
+    def init(self, item):
+
+        """ initialize a stats item. """
+
+        self.data[item] = Statdict()
+
+    def up(self, item, issue):
+
+        """ up a stats item. """
+
+        if not self.data.has_key(item):
+            self.init(item)
+        self.data[item].upitem(issue)
+
+    def get(self, item):
+
+        """ return stats item. """
+
+        try:
+            return self.data[item]
+        except KeyError:
+            return 
+
+    def list(self, item):
+
+        """ list all stats belonging to item. """
+
+        if self.data.has_key(item):
+            return self.data[item].keys()
+
+    def all(self):
+
+        """ list all stats items. """
+
+        return self.data.keys()
+
+## INIT SECTION
+
+# the gozerbot stats object
+stats = GozerStats()
+
+## END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/botbase.py
+++ gozerbot-0.99.1/build/lib/gozerbot/botbase.py
@@ -0,0 +1,394 @@
+# gozerbot/botbase.py
+#
+#
+
+""" bot base class. provides data/methods common to all bots. """
+
+# IMPORT SECTION
+
+# gozerbot imports
+from threads.thr import start_new_thread
+from utils.log import rlog
+from less import Less
+from persist.pdol import Pdol
+from utils.dol import Dol
+from utils.lazydict import LazyDict
+from persist.persiststate import PersistState
+from channels import Channels
+from datadir import datadir
+from persist.pdod import Pdod
+from config import config, Config, fleetbotconfigtxt
+from runner import runners_start
+from monitor import Monitor
+from callbacks import callbacks, gn_callbacks
+from cache import userhosts
+from wait import Wait, Privwait
+from eventhandler import mainhandler
+from exit import globalshutdown
+from utils.exception import handle_exception
+from plugins import plugins
+
+# throttle support
+from gozerbot.plugs.throttle import state as throttlestate
+
+# basic imports
+import time, threading, os, types, sys, copy
+
+# END IMPORT
+
+cpy = copy.deepcopy
+
+class BotBase(object):
+
+    """ 
+
+            Base class for all bots. Inherit from this. 
+
+            :param cfg: configuration 
+            :type cfg: dict like
+
+    """
+
+    def __init__(self, name, cfg={}):
+        self.name = name
+        self.encoding = sys.getdefaultencoding()
+
+        # if cfg is not passed on create one
+        if not cfg:
+            cfg = Config(inittxt=fleetbotconfigtxt)
+
+        if not cfg.has_key('dir'):
+           cfg['dir'] = os.getcwd()
+
+        if not cfg.has_key('user'):
+           cfg['user'] = 'gozerbot'
+
+        if not cfg.has_key('type'):
+           cfg['type'] = 'gozernet'
+
+        # set attributes based on config 
+        self.__dict__.update(cfg)
+
+        # if name not set in config file use the directory name
+
+        if not cfg.has_key('name'):
+            if 'fleet' in cfg['dir']:
+                self.name = cfg.dir.split(os.sep)[-1]
+            else:
+                self.name = name
+
+        # default nick to gozerbot
+        if not cfg.has_key('nick'):
+            self.nick = 'gozerbot'
+
+        if not cfg.has_key('server'):
+            self.server = 'server not set'
+
+        try:
+            self.host = cfg['host']
+            if not self.host:
+                self.host = self.user.split('@')[1]
+        except (KeyError, IndexError):
+            try:
+                self.host = self.user.split('@')[1]
+            except (ValueError, IndexError):
+                self.host = 'host not set'
+
+
+        # default port to 0 (use default port)
+        if not cfg.has_key('port'):
+            self.port  = 0
+
+        if not cfg.has_key('ipv6'):
+            self.ipv6  = 0
+        else:
+            self.ipv6 = cfg['ipv6']
+        
+        # make sure bot name is not a directory
+        if '..' in self.name or '/' in self.name:
+            raise Exception('wrong bot name %s' % self.name)
+
+        # set datadir to datadir/fleet/<botname>
+        self.datadir = datadir + os.sep + 'fleet' + os.sep + self.name
+
+        # set datadir to datadir/fleet/<botname>
+        if hasattr(os, 'mkdir'):
+            if not os.path.exists(self.datadir):
+                os.mkdir(self.datadir)
+
+        # bot state
+        self.state = Pdod(self.datadir + os.sep + 'state') # bot state
+
+        # joined channels list .. used to join channels
+        if not self.state.has_key('joinedchannels'):
+            self.state['joinedchannels'] = []
+
+        # allowed commands on the bot
+        if not self.state.has_key('allowed'):
+            self.state['allowed'] = []
+
+        # channels we dont want ops in
+        if not self.state.has_key('no-op'):
+            self.state['no-op'] = []
+
+        # channels we are op in
+        if not self.state.has_key('opchan'):
+            self.state['opchan'] = []
+
+        # the time we joined a channel
+        self.timejoined = {}
+
+        # jabber type doesnt exists anymore .. set type to 'xmpp' instead
+        self.type = self.type or 'gozernet'
+
+        if self.type == 'jabber':
+            self.type = 'xmpp'
+
+        self.networkname = self.server
+        self.jid = "%s@%s" % (self.nick, self.server)
+        self.jids = {}
+        self.shutloop = False
+        self.cfg = cfg # the bots config
+        self.orignick = "" # original nick
+        self.blocking = 1 # use blocking sockets
+        self.lastoutput = 0 # time of last output
+        self.stopped = False # flag to set when bot is to be stopped
+        self.connected = False # conencted flag
+        self.connecting = False # connecting flag
+        self.connectok = threading.Event() # event set when bot has connected
+        self.waitingforconnect = False # flag to indicate we are waiting for connect
+        self.starttime = time.time() # start time of the bot
+        self.nrevents = 0 # number of events processed
+        self.gcevents = 0 # number of garbage collected events
+        self.less = Less(5) # output buffering
+        self.userchannels = Dol() # list of channels a user is in
+        self.channels = Channels(self.datadir + os.sep + 'channels') # channels
+        self.userhosts = PersistState(self.datadir + os.sep + 'userhosts') # userhosts cache
+        self.splitted = [] # list of splitted nicks
+        self.throttle = [] # list of nicks that need to be throttled
+        self.jabber = False # flag is set on jabber bots
+        self.google = False
+        if 'google' in self.host:
+            self.google = True # flag is set on google bots
+        try:
+            import google.appengine.ext 
+            self.google = True
+        except:
+            pass
+        self.callbacks = callbacks
+        self.monitor = Monitor()
+        self.wait = Wait()
+        self.privwait = Privwait()
+        self.error = None
+        
+        # start runners
+        runners_start()
+        #self.monitor.start()
+        
+    def ownercheck(self, ievent, txt=None):
+
+        """ 
+            check whether an event originated from the bot owner. 
+
+            :param ievent: event to check for owner with
+            :param txt: optional txt to report to user when check fails
+            :rtype: 1 or 0
+
+            .. literalinclude:: ../../gozerbot/botbase.py
+                :pyobject: BotBase.ownercheck
+
+        """
+
+        # use owner set in bot's config or else in global config
+        owner = self.cfg['owner'] or config['owner']
+
+        # check if event userhost in in owner .. check lists and string values
+        if type(owner) == types.ListType:           
+            if ievent.userhost in owner:            
+                return 1
+        elif owner == ievent.userhost:              
+            return 1    
+        else:
+            rlog(100, self.name, 'failed owner check %s should be in %s' % (ievent.userhost, owner))
+            if not txt:
+                ievent.reply("only owner (see config file) is allowed to perform this command")
+            else:
+                ievent.reply("only owner (see config file) %s" % txt)
+            return 0
+
+    def save(self):
+
+        """ save bot state. """
+
+        self.channels.save()
+        self.userhosts.save()
+        self.state.save()
+
+    def stop(self):
+
+        """ stop the bot. """
+
+        self.stopped = True
+        rlog(10, self.name, 'stopped')
+
+    def exit(self):
+
+        """ shutdown the bot. overload this. """
+
+        pass
+
+    def connect(self, reconnect=True):
+
+        """ connect the bot to the server. reconnects in the default case. """
+
+        pass
+
+    def say(self, printto, what, event=None, who=None, how='msg', fromm=None, speed=0, groupchat=False):
+        print what
+
+    def whois(self, nick):
+        pass
+
+    def sendraw(self, txt):
+        print txt
+
+    def voice(self, channel, txt):
+        pass
+
+    def action(self, channel, txt):
+        pass
+
+    def _raw(self, txt):
+        print txt
+
+    def settopic(self, channel, txt):
+        pass
+
+    def names(self, channel):
+        pass
+
+    def gettopic(self, channel):
+        pass
+
+    def _dcclisten(self, *args):
+        pass
+
+    def donick(self, nick, save=False, setorig=True):
+        pass
+
+    def fakein(self, txt):
+        pass
+
+    def part(self, channel):
+        pass
+
+    def serveforever(self):
+        self.stopped = False
+        self.shutloop = False
+
+        while not self.stopped and not self.shutloop:
+            try:
+                import asyncore
+                asyncore.poll(timeout=0.01)
+            except ImportError:
+                pass
+            except Exception, ex:
+                handle_exception()
+                globalshutdown()
+                os._exit(1)
+            time.sleep(0.01)
+            mainhandler.handle_one()
+
+    def join(self, channel, password=""):
+        pass
+
+    def joinchannels(self):
+
+        """ join all registered channels. overload this. """
+
+        pass
+
+    def connectwithjoin(self, reconnect=True):
+
+        """ connect to the server and join channels. """
+
+        self.connect(reconnect)
+        self.connectok.wait()
+        start_new_thread(self.joinchannels, ())
+
+    def broadcast(self):
+
+        """ announce a message to all channels. overload this"""
+
+        pass
+
+    def send(self, txt):
+
+        """ send txt to the server. overload this"""
+
+        pass
+
+    def shutdown(self):
+
+        """ close sockets of the bot. overload this"""
+
+        pass
+
+    def domsg(self, msg, response=False, wait=False):
+
+        """ 
+            excecute a message (txt line) on the bot.
+
+            :param msg: text line to execute
+            :type msg: string
+            :rtype: None
+
+            .. literalinclude:: ../../gozerbot/botbase.py
+                :pyobject: BotBase.domsg
+        """ 
+
+        if response:
+             msg.reply('executing %s (%s) on %s bot' % (msg.txt, msg.userhost, self.name))
+
+        from gozerbot.plugins import plugins
+        
+        if wait:
+            plugins.waitdispatch(self, msg)
+        else:
+            plugins.trydispatch(self, msg)
+
+    def ratelimit(self, userhost, cpm=20):
+        try:
+            throttlestate['level'][userhost] = cpm 
+            throttlestate.save()
+            rlog(10, self.name, '%s throttled to %s cpm' % (userhost, cpm))
+        except Exception, ex:
+            rlog(100, self.name, "can't set throttle of %s" % userhost)
+            handle_exception()
+
+    def remoteout(self, request, event):
+        request.wfile.write(event.tojson())
+
+    def doevent(self, event):
+
+        """ 
+            dispatch an event. 
+
+            :param event: event to dispatch.
+            :rtype: None
+
+        """
+
+        if not event:
+            return
+
+        e = cpy(event)
+
+        if event.isremote:
+            rlog(10, self.name, 'remote event .. calling gn_callbacks')
+            gn_callbacks.check(self, e)
+        else:
+            callbacks.check(self, e)
+
+        if event.remotecmnd:
+            plugins.trydispatch(self, event)
--- gozerbot-0.99.1.orig/build/lib/gozerbot/gozerimport.py
+++ gozerbot-0.99.1/build/lib/gozerbot/gozerimport.py
@@ -0,0 +1,97 @@
+# gozerbot/myimport.py
+#
+#
+
+""" use the imp module to import modules. """
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION 
+
+# basic import
+from gozerbot.utils.log import rlog
+from gozerbot.utils.locking import lockdec
+
+import time, sys, imp, os, thread
+
+# END IMPORT 
+# ==========
+
+# ============
+# LOCK SECTION
+
+gozerimportlock = thread.allocate_lock()
+locked = lockdec(gozerimportlock)
+
+# END LOCK
+# ========
+
+#@locked
+def gozer_import(name, path=None):
+
+    """
+        import module <name> with the imp module  .. will reload module is 
+        already in sys.modules.
+
+        :param name: name of the module to import (may contain dots)
+        :type name: string
+        :param path: optional path to search in
+        :type path: string
+        :rtype: module
+
+        .. literalinclude:: ../../gozerbot/gozerimport.py
+            :pyobject: gozer_import
+
+    """
+
+    rlog(1, 'gozerimport', 'importing %s' % name)
+
+    splitted = name.split('.')
+    
+    for plug in splitted:
+        fp, pathname, description = imp.find_module(plug, path)
+        try:
+           result = imp.load_module(plug, fp, pathname, description)
+           try:
+               path = result.__path__
+           except:
+               pass
+        finally:
+            if fp:
+                fp.close()
+
+    if result:
+        return result
+
+#@locked
+def force_import(name):
+
+    """
+        force import of module <name> by replacing it in sys.modules.
+
+        :param name: name of module to import
+        :type name: string
+        :rtype: module
+
+        .. literalinclude:: ../../gozerbot/gozerimport.py
+            :pyobject: force_import
+
+    """
+
+    try:
+        del sys.modules[name]
+    except KeyError:
+        pass
+    plug = gozer_import(name)
+    if plug:
+        sys.modules[name] = plug
+        return plug
+
+# ============
+# INIT SECTION
+
+# no vars
+
+# END INIT
+# ========
--- gozerbot-0.99.1.orig/build/lib/gozerbot/redispatcher.py
+++ gozerbot-0.99.1/build/lib/gozerbot/redispatcher.py
@@ -0,0 +1,394 @@
+# gozerbot/redispatcher.py
+#
+#
+
+""" implement RE (regular expression) dispatcher. """
+
+__copyright__ = 'this file is in the public domain'
+
+## IMPORT SECTION
+
+# gozerbot imports
+from config import config
+from utils.log import rlog
+from utils.trace import calledfrom
+from utils.exception import handle_exception
+from utils.locking import lockdec
+from runner import cmndrunners
+import threads.thr as thr
+
+# basic imports
+import sys, re, copy, types, thread
+
+## END IMPORT
+
+## LOCK SECTION
+
+# locks
+relock = thread.allocate_lock()
+locked = lockdec(relock)
+
+## END LOCK
+
+class RECallback(object):
+
+    """
+         a regular expression callback.
+
+         :param index: index into the callback list
+         :type index: integer
+         :param regex: the regex to match
+         :type regex: string
+         :param func: the callback function
+         :type func: function
+         :param perm: permissions of the callback
+         :type perm: list .. list of permissions
+         :param speed: speed at which the callback should be executed
+         :type speed: integer
+         :param threaded: whether the callback should executed in its own thread
+         :type threaded: boolean
+         :param allowqueue: whether this command is allowed in pipelines
+         :type allowqueue: boolean
+         :param options: options allowed for this command
+         :type options: dict
+
+    """
+
+    def __init__(self, index, regex, func, perm, plugname, speed=5, \
+threaded=True, allowqueue=True, options={}):
+        self.name = thr.getname(func) # name of the callback
+        self.index = index # index into the list
+        self.regex = regex # the RE to match
+        self.compiled = re.compile(regex) # compiled RE
+        self.func = func # the function to call if RE matches
+        # make sure perms is a list
+        if type(perm) == types.ListType:
+            self.perms = list(perm)
+        else:
+            self.perms = [perm, ]
+        # plug name
+        self.plugname = plugname # plugname where RE callbacks is registered
+        self.speed = copy.deepcopy(speed) # speed at which the function runs
+        self.threaded = copy.deepcopy(threaded) # set when run threaade
+        self.allowqueue = copy.deepcopy(allowqueue) # set when pipeline is allowed
+        self.options = dict(options) # options set on the callback
+        self.activate = True
+
+class REDispatcher(object):
+
+    """
+        this is were the regexs callbacks live.
+
+    """
+
+    def __init__(self):
+        self.relist = []
+        
+    def size(self):
+
+        """
+            nr of callbacks.
+
+        """
+
+        return len(self.relist)
+
+    def activate(self, plugname):
+        for i in self.relist:
+            if i.plugname == plugname:
+                i.activate = True
+
+    def disable(self, plugname):
+        for i in self.relist:
+            if i.plugname == plugname:
+                i.activate = False
+
+    def whatperms(self):
+
+        """
+            return possible permissions.
+
+            :rtype: list .. list of possible permissions
+
+            .. literalinclude:: ../../gozerbot/redispatcher.py
+                :pyobject: REDispatcher.whatperms
+
+        """
+
+        result = []
+
+        for i in self.relist:
+            for j in i.perms:
+                if j not in result:
+                    result.append(j)
+
+        return result
+
+    def list(self, perm):
+
+        """
+            list RECallbacks with permission perm.
+
+            :param perm: permission to check for
+            :type perm: string
+            :rtype: list .. list of RECallbacks 
+
+            .. literalinclude:: ../../gozerbot/redispatcher.py
+                :pyobject: REDispatcher.list
+        """
+
+        result = []
+        perm = perm.upper()
+
+        for recom in self.relist:
+            if perm in recom.perms:
+                result.append(recom)
+
+        return result
+
+    def getfuncnames(self, plug):
+
+        """
+            return function names in plugin.
+
+            :param plug: name of the plugin to get callbacks of
+            :type plug: string
+            :rtype: list .. list of function names
+
+            .. literalinclude:: ../../gozerbot/redispatcher.py
+                :pyobject: REDispatcher.getfuncnames
+
+        """
+
+        result = []
+        for i in self.relist:
+            if i.plugname == plug:
+                result.append(i.func.func_name)
+        return result
+        
+    def permoverload(self, funcname, perms):
+
+        """
+            overload permission of function with funcname.
+
+            :param funcname: name of the function to overload
+            :type funcname: string
+            :param perms: permission to overload
+            :type perms: list
+            :rtype: boolean .. whether the overload succeeded
+
+            .. literalinclude:: ../../gozerbot/redispatcher.py
+                :pyobject: REDispatcher.permoverload
+
+        """
+
+        perms = [perm.upper() for perm in perms]
+        got = 0
+
+        for nr in range(len(self.relist)):
+
+            try:
+                if self.relist[nr].func.func_name == funcname:
+                    self.relist[nr].perms = list(perms)
+                    rlog(0, 'redispatcher', '%s function overloaded with %s' \
+% (funcname, perms))
+                    got = 1
+
+            except AttributeError:
+                rlog(10, 'redispatcher', 'permoverload: no %s function' % \
+funcname)
+        if got:
+            return True
+
+        return False
+
+    def add(self, index, regex, func, perm, speed=5, threaded=True, allowqueue=True, options={}):
+
+        """
+            add a regular expression command.
+
+           :param index: index into the callback list
+           :type index: integer
+           :param regex: the regex to match
+           :type regex: string
+           :param func: the callback function
+           :type func: function
+           :param perm: permissions of the callback
+           :type perm: list .. list of permissions
+           :param speed: speed at which the callback should be executed
+           :type speed: integer
+           :param threaded: whether the callback should executed in its own thread
+           :type threaded: boolean
+           :param allowqueue: whether this command is allowed in pipelines
+           :type allowqueue: boolean
+           :param options: options allowed for this command
+           :type options: dict
+
+           .. literalinclude:: ../../gozerbot/redispatcher.py
+               :pyobject: REDispatcher.add
+
+        """
+
+        try:
+            # get plugin name from where callback is added
+            plugname = calledfrom(sys._getframe())
+
+            if config['loadlist'] and plugname not in config['loadlist']:
+                return
+            # add Recallback
+
+            self.relist.append(RECallback(index, regex, func, perm, plugname, \
+speed, threaded, allowqueue, options))
+            # sort of index number
+            self.relist.sort(lambda a, b: cmp(a.index, b.index))
+            rlog(0, 'redispatcher', 'added %s (%s) ' % (regex, plugname))
+
+        finally:
+            pass
+
+    def unload(self, plugname):
+
+        """
+            unload regex commands.
+
+            :param plugname: name of the plugins to unload callbacks from
+            :type plugname: string
+            :rtype: boolean .. whether the unloading succeeded
+
+            .. literalinclude:: ../../gozerbot/redispatcher.py
+                :pyobject: REDispatcher.unload
+
+        """
+
+        got = False
+        try:
+
+            for i in range(len(self.relist)-1, -1 , -1):
+                if self.relist[i].plugname == plugname:
+                    rlog(1, 'redispatcher', 'unloading %s (%s)' % \
+(self.relist[i].regex, plugname))
+                    del self.relist[i]
+                    got = True
+        finally:    
+            pass
+        return got
+
+    def getcallback(self, txt):
+
+        """
+            get re callback if txt matches.
+
+            :param txt: txt to match against the regular expressions
+            :type txt: string
+
+            .. literalinclude:: ../../gozerbot/redispatcher.py
+                :pyobject: REDispatcher.getcallback
+        """
+
+        for i in self.relist:
+
+            try:
+                result = re.search(i.compiled, txt)
+
+                if result:
+                    return i
+
+            except:
+                pass
+
+    def dispatch(self, callback, txt, wait=False):
+
+        """
+            try to dispatch callback on txt.
+ 
+            :param callback: the callback to fire
+            :type callback: RECallback
+            :param txt: txt to match the regular expression
+            :type txt: string
+            :param wait: whether to wait for the result
+            :type wait: boolean
+
+            .. literalinclude:: ../../gozerbot/redispatcher.py
+                :pyobject: REDispatcher.dispatch
+
+        """
+
+        try:
+            result = re.search(callback.compiled, txt)
+
+            if result:
+
+                if callback.threaded:
+                    thread = thr.start_new_thread(callback.func, (txt, result.groups()))
+
+                    if wait:
+                        thread.join()
+
+                else:
+                    cmndrunners.put(callback.plugname, callback.func, txt, \
+result.groups())
+
+                    return 1
+
+        except Exception, ex:
+            handle_exception()
+
+class BotREDispatcher(REDispatcher):
+
+    """
+        dispatcher on Event.
+
+    """
+
+    def dispatch(self, callback, bot, ievent, wait=False):
+
+        """
+            dispatch callback on ircevent.
+
+            :param callback: the callback to fire
+            :type callback: RECallback
+            :param bot: the bot on which the callback was triggered
+            :type bot: gozerbot.botbase.BotBase
+            :param ievent: the event that triggered the callback
+            :type ievent: gozerbot.eventbase.EventBase
+            :rtype: boolean .. whether the dispatch was succesful
+
+            .. literalinclude:: ../../gozerbot/redispatcher.py
+                :pyobject: BotREDispatcher.dispatch
+
+        """
+
+        if not self.activate:
+            return False
+
+        try:
+            result = re.search(callback.compiled, ievent.txt.strip())
+
+            if result:
+                ievent.groups = list(result.groups())                
+
+                if callback.threaded or ievent.threaded:
+                    thread = thr.start_bot_command(callback.func, (bot, ievent))
+
+                    if thread and wait:
+                        thread.join()
+
+                else:
+                    cmndrunners.put(callback.plugname, callback.func, bot, \
+ievent)
+                return True
+
+        except Exception, ex:
+            handle_exception(ievent)
+
+        return False
+
+## INIT SECTION
+
+# dispatcher before commands are checked
+rebefore = BotREDispatcher()
+
+# dispatcher after commands are checked
+reafter = BotREDispatcher()
+
+## END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/eventbase.py
+++ gozerbot-0.99.1/build/lib/gozerbot/eventbase.py
@@ -0,0 +1,602 @@
+# gozerbot/eventbase.py
+#
+#
+
+""" 
+    This module implements the EventBase class from which all other 
+    events inherit. 
+
+    :vars: defaultevent
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+# gozerbot imports
+from xmpp.core import XMLDict
+from utils.log import rlog
+from utils.generic import stripident, fix_format, fromenc, toenc
+from utils.lazydict import LazyDict
+from gozerbot.stats import stats
+from gozerbot.config import config
+
+# basic imports
+import time, re, types, copy
+
+from simplejson import loads, dumps
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
+
+## START 
+
+# used to copy data
+cpy = copy.deepcopy
+
+def makeargrest(event):
+
+    """
+        set arguments and rest attributes of an event.
+
+        :param event: event to manipulate
+
+    """
+
+    # arguments 
+    try:
+        event.args = event.txt.split()[1:]
+    except ValueError:
+        event.args = []
+
+    # txt after command
+    try:
+        cmnd, event.rest = event.txt.split(' ', 1)
+    except ValueError:
+        event.rest = ""   
+
+    # the command 
+    event.command = event.txt.split(' ')[0]
+
+class EventBase(XMLDict):
+
+    """ 
+        Base class for all events. Inherit from this. 
+
+        :param event: event to clone from (optional)
+        :type event: gozerbot.event.EventBase
+        :param bot: bot on which this event occured
+        :type bot: gozerbot.botbase.BotBase
+
+    """
+
+
+    def __init__(self, event={}, bot=None):
+        self.server = ""
+        XMLDict.__init__(self)
+        self.threaded = False
+        self.onlyqueues = False
+        self.orig = event # the original data .. SET BY HANDLER 
+        self.type = 'chat' # type of event
+        self.cbtype = '' # callback type
+        self.jabber = False # set if event is jabber event
+        self.groupchat = False # set if event is groupchat
+        self.botoutput = False # set if event is bot output
+        self.cmnd = None # the event command
+        self.prefix = u"" # txt before the command
+        self.postfix = u"" # txt after the command
+        self.target = u"" # target to give reponse to
+        self.arguments = [] # arguments of event
+        self.nick = u"" # nick of user originating the event 
+        self.user = u""  # user originating the event
+        self.ruserhost = u"" # real userhost 
+        self.userhost = u"" # userhost that might change
+        self.stripped = u"" # stripped JID 
+        self.resource = u"" # resource part of JID
+        self.origin = u"" # source of the event
+        self.origchannel = u"" # original channel
+        self.channel = u"" # channel .. might change
+        self.origtxt = u"" # original txt
+        self.txt = u"" # text .. might change
+        self.command = u"" # the bot command if any
+        self.remoteout = u""
+        self.remotecmnd = False
+        self.usercmnd = False
+        self.alias = u"" # set to alias if one is used
+        self.aliased = u"" # set if commadn is aliased
+        self.time = time.time() # event creation time
+        self.msg = False # set if event is a private message
+        self.args = [] # arguments of command
+        self.rest = u"" # txt following the command
+        self.usercmnd = 0 # set if event is a command
+        self.bot = bot # the bot where the event originated on
+        self.sock = None # socket (set in DCC chat)
+        self.allowqueue = True # allow event to be used in pipeline
+        self.closequeue = True # cloase event queues when execution has ended
+        self.inqueue = None # queue used as input in pipeline
+        self.queues = [] # output queues (only used when set)
+        self.printto = None # target to printto
+        self.speed = 0 # speed with which this event needs to be processed
+        self.groups = None # set if event triggers a RE callback
+        self.cc = u"" # control character
+        self.jid = None # JID of used originating the event
+        self.jidchange = None # set is event changes jid
+        self.conn = None # connection of bot originating the event
+        self.denied = False # set if command is denied
+        self.iscallback = False
+        self.isresponse = False # set if event is a reponse
+        self.isrelay = False
+        self.isremote = False
+        self.isdcc = False # set if event is DCC related
+        self.isctcp = False # set if event is an ACTION
+        self.options = LazyDict() # options dict on the event
+        self.optionset = [] # list of options set
+        self.filter = [] # filter list to use on output
+
+        # wave stuff
+        self.wave = False
+        self.properties = None
+        self.context = None
+        self.eventin = None
+        self.wavelet = None
+
+        # GAE stuff
+        self.google = False
+        self.xmppgae = False
+        self.response = None
+        self.request = None
+
+        # xmpp stuff
+        self.host = self.server
+        self.xml = u""
+        self.realjid = u""
+        self.fromm = u""
+        self.to = u""
+        self.type = u""
+        self.id = 0
+        self.subject = u""
+        self.body = u""
+        self.error = u""
+        self.errorcode = u""
+        self.html = u""
+        self.thread = u""
+        self.x = {}
+
+        if event:
+            self.copyin(event)
+
+        self.toirc() 
+
+        # stats
+        stats.up('events', 'created')
+
+    def __copy__(self):
+        return EventBase(self)
+
+    def __deepcopy__(self, bla):
+        return EventBase(self)
+
+    def toirc(self):
+
+        """
+            set ircevent compat attributes.
+
+            .. literalinclude:: ../../gozerbot/eventbase.py
+                :pyobject: EventBase.toirc
+        """
+
+        self.jidchange = False
+        self.cmnd = 'Message'
+
+        try:
+            self.resource = self.fromm.split('/')[1]
+        except IndexError:
+            pass
+
+        self.channel = self['fromm'].split('/')[0]
+        self.origchannel = self.channel
+        self.nick = self.resource
+
+        #try:
+        #    self.jid = bot.jids[self.channel][self.resource]
+        #    self.jidchange = True
+        #except KeyError:
+        #     pass
+
+        self.jid = self.fromm
+        self.ruserhost = self.jid
+        self.userhost = self.jid
+        self.stripped = self.jid.split('/')[0]
+        self.printto = self.channel
+        self.target = self.printto
+
+        for node in self.subelements:
+            try:
+                self.txt = node.body.data
+            except (AttributeError, ValueError):
+                continue
+
+        self.origtxt = self.txt
+        self.time = time.time()
+
+        if self.type == 'groupchat':
+            self.groupchat = True
+            if self.jidchange:
+                self.userhost = self.stripped
+        else:
+            self.groupchat = False
+            self.userhost = self.stripped
+
+        self.msg = not self.groupchat
+
+    def copyin(self, ievent):
+
+        """
+            copy in an event.
+
+            :param ievent: event to copy in
+            :type ievent: EventBase
+
+            .. literalinclude:: ../../gozerbot/eventbase.py
+                :pyobject: EventBase.copyin
+
+        """
+        self.update(ievent)
+
+        if ievent.has_key('options'):
+            self.options = dict(ievent.options)        
+
+        if ievent.has_key('queues'):
+            self.queues = list(ievent.queues)
+
+        return self
+
+    def filtered(self, txt):
+
+        """
+            see if txt if filtered on this event.
+
+            :param txt: text to check if its filtered
+            :type txt: string
+
+            .. literalinclude:: ../../gozerbot/eventbase.py
+                :pyobject: EventBase.filtered
+
+        """
+
+        if not self.filter:
+            return False
+
+        for filter in self.filter:
+            if filter in txt:
+                return False
+
+        return True
+
+    def parse(self, bot, rawstr):
+
+        """
+            parse raw string into event.  overload this.
+
+            :param bot: bot on which event is triggered
+            :type bot: gozerbot.botbase.BotBase
+            :param rawstr: string as recieved on the socket
+            :type rawstr: string
+
+        """
+
+        pass
+
+    def make_response(self, txt, result=None, nick=None, dot=False, nritems=False, nr=False, fromm=None, private=False, how=''):
+
+
+        # don't reply is result is empty list
+        if result == []:
+            return
+
+        # stats
+        stats.up('events', 'replies')
+        if not how:
+            try:
+                how = self.options['--how']        
+            except KeyError:
+                how = 'msg'
+
+        # init
+        restxt = ""
+        splitted = []
+
+        # make reply if result is a dict
+        if type(result) == types.DictType:
+            for i, j in result.iteritems():
+                if type(j) == types.ListType:
+                    try:
+                        z = ' .. '.join(j)
+                    except TypeError:
+                        z = unicode(j)
+                else:
+                    z = j
+                res = "%s: %s" % (i, z)
+                splitted.append(res)
+                if dot == True:
+                    restxt += "%s%s" % (res, ' .. ')
+                else:
+                    restxt += "%s %s" % (dot or ' ', res)
+            if restxt:
+                if dot == True:
+                    restxt = restxt[:-6]
+                elif dot:
+                    restxt = restxt[:-len(dot)]
+
+        lt = False # set if result is list
+
+        # set vars if result is a list
+        if type(txt) == types.ListType and not result:
+            result = txt
+            origtxt = u""
+            lt = True
+        else:
+            origtxt = txt
+
+        if result:
+            lt = True
+
+        # if queues are set write output to them
+        if self.queues:
+            for i in self.queues:
+                if splitted:
+                    for item in splitted:
+                        i.put_nowait(item)
+                elif restxt:
+                    i.put_nowait(restxt)
+                elif lt:
+                    for j in result:
+                        i.put_nowait(j)
+                else:
+                    i.put_nowait(txt)
+                if self.onlyqueues:
+                    return
+
+        # check if bot is set in event
+        if not self.bot:
+            rlog(10, 'event', 'no bot defined in event')
+            return "no bot is defined in this event"
+
+        # make response
+        pretxt = origtxt
+        if lt and not restxt:
+            res = []
+
+            # check if there are list in list
+
+            for i in result:
+                if type(i) == types.ListType or type(i) == types.TupleType:
+                    try:
+                        res.append(u' .. '.join(i))
+                    except TypeError:
+                        res.extend(i)
+                else:
+                    res.append(i)
+
+            # if nritems is set ..
+            result = res
+            if nritems:
+                if len(result) > 1:
+                    pretxt += "(%s items) .. " % len(result)
+            txtlist = result
+
+            # prepend item number for results
+            if not nr is False:
+                try:
+                    start = int(nr)
+                except ValueError:
+                    start = 0
+                txtlist2 = []
+                teller = start
+                for i in txtlist:
+                    txtlist2.append(u"%s) %s" % (teller, i))
+                    teller += 1
+                txtlist = txtlist2
+
+            # convert results to encoding
+            txtl = []
+            for item in txtlist:
+                txtl.append(toenc(item))
+            txtlist = txtl
+
+            # join result with dot 
+            if dot == True:
+                restxt = ' .. '.join(txtlist)
+            elif dot:
+                restxt = dot.join(txtlist)
+            else:
+                restxt = ' '.join(txtlist)
+
+        # see if txt needs to be prepended
+        if pretxt:
+            try:
+                restxt = pretxt + restxt
+            except TypeError:
+                rlog(10, 'eventbase', "can't add %s and %s" % (str(pretxt), str(restxt)))
+
+        # if txt in result is filtered ignore the reuslt
+        if self.filtered(restxt):
+            return
+
+        if restxt:
+            return restxt
+        else:
+            return "no data found"
+
+    def reply(self, *args, **kwargs):
+
+        """
+            reply to event. this version prints to stdout
+
+            :param txt: txt to reply
+            :type txt: string
+            :param result: result list .. list of items to reply (is prepended to txt)
+            :type result: string    
+            :param nick: nick to reply to
+            :type nick: string
+            :param dot: whether results of result list should be seperated with a dot with ' ..' or the txt passed in as dot argument
+            :type dot: boolean or string
+            :param nritems: whether results should be numbered
+            :type nritems: boolean
+            :param nr: the number to start numerbering the result with
+            :type nr: integer
+            :param fromm: the user sending the reply
+            :type fromm: nick or JID
+            :param private: whether the reply should be in private
+            :type private: boolean
+            :param how: how the reply should be made (msg, notice, ctcp)
+            :type how: string
+
+        """
+        resp = self.make_response(*args, **kwargs)
+
+        if not resp:
+            return
+
+        self.bot.say(self.channel, resp)
+
+    def missing(self, txt):
+
+        """
+            show what arguments are missing.
+
+            :param txt: txt to add to missing response
+            :type txt: string
+
+            .. literalinclude:: ../../gozerbot/eventbase.py
+                :pyobject: EventBase.missing
+
+        """
+
+        stats.up('events', 'missing')
+        if self.origtxt:
+            splitted = self.origtxt.split()
+            if self.bot.nick in splitted[0]:
+                try:
+                    cmnd = splitted[1]
+                except IndexError:
+                    cmnd = splitted[0]
+            elif 'cmnd' in splitted[0]:
+                try:
+                    cmnd = splitted[2]
+                except IndexError:
+                    cmnd = splitted[0]
+            else:
+                if self.msg:
+                    cmnd = splitted[0]
+                else:
+                    if self.aliased:
+                        cmnd = self.aliased
+                    else:
+                        cmnd = splitted[0][1:]
+            self.reply(cmnd + ' ' + txt)
+        else:
+            self.reply('missing origtxt: %s' % txt)
+
+    def ircstr(self):
+
+        """
+             old compat function. use str() now.
+
+        """
+
+        return str(self)
+
+    def handle_error(self, data):
+
+        """
+            function to handler errors. override this.
+
+            :param data: txt describing the error (can be different with inherited classes)
+            :tyoe data: string (can be different with inherited classes)
+
+        """
+
+        rlog(10, self.name.error, 'ERROR: %s' % str(data))
+
+    def done(self, txt=None):
+
+        """
+            reply with the txt 'done'.
+
+            :param txt: txt to prepend to 'done'
+            :type txt: string
+
+        """
+
+        if txt:
+            self.reply('%s done' % txt)
+        else:
+            self.reply('done')
+
+    def tojson(self):
+        new = cpy(self)
+        new.sock = 'setremote'
+        new.bot = 'setremote'
+        new.request = 'setremote'
+        new.response = 'setremote'
+        new.queues = []
+        result = dumps(new)
+        rlog(0, 'eventbase', 'tojson - %s' % str(result))
+        return result
+
+    def fromjsonstring(self, input):
+        temp = loads(input)
+        rlog(10, 'eventbase', str(temp))
+        self.update(temp)
+        return self
+
+    def checkqueues(self, resultlist):
+
+        """ 
+            check if resultlist is to be sent to the queues. if so do it
+
+            :param resultlist: list of results to send to queues
+            :type resultlist: list
+            :rtype: boolean
+
+        """
+
+        if self.queues:
+
+            for queue in self.queues:
+                for item in resultlist:
+                    queue.put_nowait(item)
+
+            return True
+
+    def subelement(self, name):
+       if self.subelements:
+            rlog(10, 'eventbase', "got subelements")
+            for node in self.subelements:
+                try:
+                    attr = getattr(node, name)
+                    rlog(10, 'eventbase', "got subelement %s" % (name, str(attr)))
+                    return attr.data
+                except (AttributeError, TypeError):
+                    continue   
+
+# ============
+# INIT SECTION
+                
+# default event used to initialise events
+defaultevent = EventBase()
+
+
+# END INIT
+# ========
--- gozerbot-0.99.1.orig/build/lib/gozerbot/dbusers.py
+++ gozerbot-0.99.1/build/lib/gozerbot/dbusers.py
@@ -0,0 +1,375 @@
+# gozerbot/dbusers.py
+#
+#
+
+""" bots users for mysql interface"""
+
+__copyright__ = 'this file is in the public domain'
+
+from gozerbot.utils.log import rlog
+from gozerbot.database.db import db
+import types
+
+class Dbusers(object):
+
+    """ users class """
+
+    def __init__(self):
+        self.db = db
+
+    def size(self):
+        """ return nr of users """
+        result = self.db.execute(""" SELECT DISTINCT COUNT(*) FROM userhosts """)
+        if result:
+            return result[0][0]
+
+    def getperms(self, userhost):
+        """ return permission of user"""
+        name = self.getname(userhost)
+        if not name:
+            return ['ANON', ]
+        result = self.db.execute(""" SELECT perm FROM perms WHERE name = %s """, name)
+        res = []
+        for i in result:
+            res.append(i[0])
+        return res
+
+    def exist(self, name):
+        """ see if user with <name> exists """
+        name = name.lower()
+        result = self.db.execute(""" SELECT name,userhost FROM userhosts WHERE name = %s """, name)
+        return result
+
+    def getname(self, userhost):
+        """ get name of user belonging to <userhost> """
+        result = self.db.execute(""" SELECT name FROM userhosts WHERE %s LIKE userhost """, userhost)
+        if result:
+            return result[0][0]
+
+    def add(self, name, userhosts, perms):
+        """ add an user """
+        if type(userhosts) != types.ListType:
+            rlog(10, 'dbusers', 'i need a list of userhosts')
+            return 0
+        for i in userhosts:
+            self.adduserhost(name, i)
+        for i in perms:
+            self.addperm(name, i)
+        rlog(10, 'users', '%s added to user database' % name)
+        return 1
+
+    def adduserhost(self, name, userhost):
+        """ add userhost """
+        name = name.lower()
+        res = None
+        result = self.db.execute(""" INSERT INTO userhosts(name, userhost) values(%s, %s) """, (name, userhost))
+        if result:
+            res = 1
+            rlog(10, 'users', '%s (%s) added to userhosts' % (name, userhost))
+        return res
+
+    def addperm(self, name, perm):
+        """ add permission """
+        name = name.lower()
+        perm = perm.upper()
+        res = None
+        result = self.db.execute(""" INSERT INTO perms(name, perm) values(%s, %s) """, (name, perm))
+        if result:
+            res = 1
+            rlog(10, 'users', '%s perm %s added' % (name, perm))
+        return res
+
+    def addstatus(self, name, status):
+        """ add permission """
+        name = name.lower()
+        status = status.upper()
+        res = None
+        result = self.db.execute(""" INSERT INTO statuses(name, status) values(%s, %s) """, (name, status))
+        if result:
+            res = 1
+            rlog(10, 'users', '%s status %s added' % (name, status))
+        return res
+
+    def addpermit(self, name, who, what):
+        """ add permission """
+        p = '%s %s' % (who, what)
+        res = None
+        result = self.db.execute(""" INSERT INTO permits(name, permit) values(%s, %s) """, (name, p))
+        if result:
+            res = 1
+            rlog(10, 'users', '%s permit %s added' % (name, p))
+        return res
+
+    def delperm(self, name, perm):
+        """ add permission """
+        name = name.lower()
+        perm = perm.upper()
+        result = self.db.execute(""" DELETE FROM perms WHERE name = %s AND perm = %s """, (name, perm))
+        if result:
+            rlog(10, 'users', '%s perm %s deleted' % (name, perm))
+            return result
+
+    def permitted(self, userhost, who, what):
+        """ check if (who,what) is in users permit list """
+        name = self.getname(userhost)
+        res = None
+        if name:
+            result = self.db.execute(""" SELECT permit FROM permits WHERE name = %s """, name)
+            if result:
+                for i in result:
+                    if "%s %s" % (who, what) == i[0]:
+                        res = 1
+        return res
+
+    def names(self):
+        """ get names of all users """
+        res = []
+        result = self.db.execute(""" SELECT DISTINCT name FROM userhosts """)
+        if result:
+            for i in result:
+                res.append(i[0])
+        return res
+
+    def merge(self, name, userhost):
+        """ add userhosts to user with name """
+        name = name.lower()
+        if not self.exist(name):
+            return 0
+        res = None
+        result = self.db.execute(""" INSERT INTO userhosts(userhost, name) VALUES (%s, %s) """, (userhost, name))
+        if result:
+            res = 1
+        return res
+
+    def deluserpermit(self, name, permit):
+        """ delete user with name """
+        name = name.lower()
+        res = None
+        p = '%s %s' % permit
+        return self.db.execute(""" DELETE FROM permits WHERE name = %s AND permit = %s """, (name, p))
+
+    def deluserhost(self, name, userhost):
+        """ delete user with name """
+        name = name.lower()
+        res = None
+        return self.db.execute(""" DELETE FROM userhosts WHERE name = %s AND userhost = %s """, (name, userhost))
+
+    def deluserperms(self, name, perm):
+        """ delete user with name """
+        name = name.lower()
+        res = None
+        return self.db.execute(""" DELETE FROM perms WHERE name = %s AND perm = %s """, (name, perm))
+
+    def deluserstatus(self, name, status):
+        """ delete user with name """
+        name = name.lower()
+        res = None
+        return self.db.execute(""" DELETE FROM statuses WHERE name = %s AND status = %s """, (name, status))
+
+    def deluserstatus(self, name, status):
+        """ delete user with name """
+        name = name.lower()
+        res = None
+        return self.db.execute(""" DELETE FROM statuses WHERE name = %s AND status = %s """, (name, status))
+
+    def delete(self, name):
+        """ delete user with name """
+        name = name.lower()
+        res = None
+        nr1 = self.db.execute(""" DELETE FROM userhosts WHERE name = %s """, name)
+        nr2 = self.db.execute(""" DELETE FROM perms WHERE name = %s """, name)
+        if nr1 and nr2:
+            res = 1
+        return res
+
+    def status(self, userhost, status):
+        """ check if user with <userhost> has <status> set """
+        name = self.getname(userhost)
+        res = None
+        if name:
+            status = status.upper()
+            result = self.db.execute(""" SELECT status FROM statuses WHERE name = %s """, name)
+            if result:
+                for i in result:
+                    if status == i[0]:
+                        res = 1
+        return res
+
+    def gotperm(self, name, perm):
+        """ check if user had permission """
+        name = name.lower()
+        perm = perm.upper()
+        result = self.db.execute(""" SELECT perm FROM perms WHERE name = %s """, name)
+        if result:
+            for i in result:
+                if i[0] == perm:
+                    return True
+
+    def getuserstatuses(self, name):
+        """ check if user had permission """
+        name = name.lower()
+        return self.db.execute(""" SELECT status FROM statuses WHERE name = %s """, name)
+
+    def getuseremail(self, name):
+        """ check if user had permission """
+        name = name.lower()
+        return self.db.execute(""" SELECT email FROM email WHERE name = %s """, name)
+
+    def getuserperms(self, name):
+        """ check if user had permission """
+        name = name.lower()
+        return self.db.execute(""" SELECT perm FROM perms WHERE name = %s """, name)
+
+    def getuserstatus(self, name):
+        """ check if user has status """
+        name = name.lower()
+        status = status.upper()
+        return self.db.execute(""" SELECT status FROM statuses WHERE name = %s """, name)
+
+    def gotstatus(self, name, status):
+        """ check if user has status """
+        name = name.lower()
+        status = status.upper()
+        result = self.db.execute(""" SELECT status FROM statuses WHERE name = %s """, name)
+        if result:
+            for i in result:
+                if status == i[0]:
+                    return True
+
+    def gotuserhost(self, name, userhost):
+        """ check if user has userhost """
+        name = name.lower()
+        result = self.db.execute(""" SELECT userhost FROM userhosts WHERE name = %s """, name)
+        if result:
+            for i in result:
+                if i[0] == userhost:
+                    return True
+
+    def getuserhosts(self, name):
+        """ check if user has userhost """
+        name = name.lower()
+        return self.db.execute(""" SELECT userhost FROM userhosts WHERE name = %s """, name)
+
+    gethosts = getuserhosts
+
+    def gotpermit(self, name, permit):
+        """ check if user permits something """
+        name = name.lower()
+        result = self.db.execute(""" SELECT permit FROM permits WHERE name = %s """, name)
+        if result:
+            for i in result:
+                if "%s %s" % permit == i[0]:
+                    return True
+
+    def getuserpermits(self, name):
+        """ check if user permits something """
+        name = name.lower()
+        return self.db.execute(""" SELECT permit FROM permits WHERE name = %s """, name)
+
+    def allowed(self, userhost, perms, log=True):
+        """ check if user with userhosts is allowed to execute perm command """
+        if not type(perms) == types.ListType:
+            perms = [perms, ]
+        if 'ANY' in perms:
+            return 1
+        res = None
+        name = self.getname(userhost)
+        if not name:
+            if log:
+                rlog(10, 'users', '%s userhost denied' % userhost)
+            return res
+        result = self.db.execute(""" SELECT perm FROM perms WHERE name = %s """, name)
+        if result:
+            for i in result:
+                if i[0] in perms:
+                    res = 1
+        if not res:
+            if log:
+                rlog(10, 'users', "%s perm %s denied" % (userhost, perms))
+        return res
+
+    def getemail(self, name):
+        """ get email of user """
+        name = name.lower()
+        email = None
+        email = self.db.execute(""" SELECT email FROM email WHERE name = %s """, name)
+        if email:
+            return email[0][0]
+
+    def setemail(self, name, email):
+        """ set email of user """
+        res = 0
+        try:
+            result = self.db.execute(""" INSERT INTO email(name, email) VALUES (%s, %s) """, (name, email))
+        except:
+            try:
+                result = self.db.execute(""" UPDATE email SET email = %s WHERE name = %s """, (email, name))
+            except:
+                pass
+        if result:
+            res = 1
+        return res
+
+    def getuserhosts(self, name):
+        """ check if user has userhost """
+        name = name.lower()
+        return self.db.execute(""" SELECT userhost FROM userhosts WHERE name = %s """, name)
+
+
+    def addpermall(self, perm): 
+        """ add permission to all users """
+        perm = perm.upper()
+        for i in self.names():
+            try:
+                self.addperm(i, perm)
+            except:
+                pass
+
+    def delpermall(self, perm):
+        """ delete permission from all users """
+        perm = perm.upper()
+        for i in self.names():
+            try:
+                self.delperm(i, perm)
+            except:
+                pass
+
+    def make_owner(self, userhosts):
+
+        """
+            see if owner already has a user account if not merge otherwise 
+            add.
+
+            :param userhosts: userhosts to inititalize owner with
+            :type userhosts: list or string
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.make_owner
+
+        """
+
+        owner = []
+
+        if type(userhosts) != types.ListType:
+            owner.append(userhosts)
+        else:
+            owner = userhosts
+
+        for userhost in owner:
+            username = self.getname(unicode(userhost))
+
+            if not username:
+                if not self.merge('owner', unicode(userhost)):
+                    self.add('owner', [unicode(userhost), ], ['USER', 'OPER'])
+            elif username != 'owner':
+                self.delete(username)
+                if not self.merge('owner', unicode(userhost)):
+                    self.add('owner', [unicode(userhost), ], ['USER', 'OPER'])
+
+    def usersearch(self, name):
+        name = name.lower()
+        res = []
+        for n in self.names():
+            if name in n.lower(): res.append(n)
+        return res
+ 
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/callbacks.py
+++ gozerbot-0.99.1/build/lib/gozerbot/callbacks.py
@@ -0,0 +1,274 @@
+# gozerbot/callbacks.py
+#
+#
+
+""" 
+    bot callbacks .. callbacks occure on registered events. a precondition 
+    function can optionaly be provided to see if the callback should fire. 
+    callback can be executed in a Runner (1 thread that executes more jobs)
+    or in a seperate thread. callbacks are now unified for bot irc and jabber 
+    events.
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# IMPORT SECTION
+
+# gozerbot imports
+from gozerbot.stats import stats
+from gozerbot.threads.thr import getname 
+from config import config
+from utils.log import rlog
+from utils.exception import handle_exception
+from utils.trace import calledfrom
+from utils.generic import makeargrest
+from utils.locking import lockdec
+from utils.dol import Dol
+from threads.thr import start_new_thread, getname
+from runner import cbrunners
+
+# basic imports
+import sys, copy, thread
+
+# END IMPORT
+
+# LOCK SECTION
+
+# locks
+callbacklock = thread.allocate_lock()
+locked = lockdec(callbacklock)
+
+# END LOCKS
+
+class Callback(object):
+
+    """ 
+        class representing a callback.
+
+        :param func: function to execute
+        :param prereq: prerequisite function 
+        :param plugname: plugin to register this callback with 
+        :param kwargs: dict to pass on to the callback 
+        :param threaded: whether the callback should be executed in its own thread
+        :param speed: determines which runnerspool to run this callback on
+
+    """
+
+    def __init__(self, func, prereq, plugname, kwargs, threaded=False, \
+speed=5):
+        self.func = func # the callback function
+        self.prereq = prereq # pre condition function
+        self.plugname = plugname # plugin name
+        self.kwargs = kwargs # kwargs to pass on to function
+        self.threaded = copy.deepcopy(threaded) # run callback in thread
+        self.speed = copy.deepcopy(speed) # speed to execute callback with
+        self.activate = False
+        stats.up('callbacks', 'created')
+
+class Callbacks(object):
+
+    """ 
+        dict of lists containing callbacks.  Callbacks object take care of 
+        dispatching the callbacks based on incoming events. see Callbacks.check()
+
+    """
+
+    def __init__(self):
+
+        # self.cbs holds the dict of list. entry value is the event (string)
+        self.cbs = Dol()
+
+    def size(self):
+
+        """ return number of callbacks. """
+
+        return len(self.cbs)
+
+    def add(self, what, func, prereq=None, kwargs=None, threaded=False, nr=False, speed=5):
+
+        """ 
+            add a callback. 
+
+            :param what: event to fire callback for
+            :param func: function to execute
+            :param prereq: prerequisite function 
+            :param plugname: plugin to register this callback with 
+            :param kwargs: dict to pass on to the callback 
+            :param threaded: whether the callback should be executed in its own thread
+            :param speed: determines which runnerspool to run this callback on
+
+        """
+
+        what = what.upper()
+
+        # get the plugin this callback was registered from
+        plugname = calledfrom(sys._getframe(0))
+
+        # check if plugname is in loadlist .. if not don't add callback
+        if config['loadlist'] and not plugname in config['loadlist']:
+            rlog(-1, plugname, 'not in loadlist .. not adding callback')
+            return
+
+        # see if kwargs is set if not init to {}
+        if not kwargs:
+            kwargs = {}
+
+        # add callback to the dict of lists
+        if nr != False:
+            self.cbs.insert(nr, what, Callback(func, prereq, plugname, kwargs, threaded, speed))
+        else:
+            self.cbs.add(what, Callback(func, prereq, plugname, kwargs, threaded, speed))
+
+        rlog(0, 'callbacks', 'added %s (%s)' % (what, plugname))
+
+    def unload(self, plugname):
+
+        """ unload all callbacks registered in a plugin. """
+
+        unload = []
+
+        # look for all callbacks in a plugin
+        for name, cblist in self.cbs.iteritems():
+            index = 0
+            for item in cblist:
+                if item.plugname == plugname:
+                    unload.append((name, index))
+                index += 1
+
+        # delete callbacks
+        for callback in unload[::-1]:
+            self.cbs.delete(callback[0], callback[1])
+            rlog(1, 'callbacks', 'unloaded %s' % callback[0])
+
+    def disable(self, plugname):
+
+        """ disable all callbacks registered in a plugin. """
+
+        unload = []
+
+        # look for all callbacks in a plugin
+        for name, cblist in self.cbs.iteritems():
+            index = 0
+            for item in cblist:
+                if item.plugname == plugname:
+                    item.activate = False
+
+    def activate(self, plugname):
+
+        """ activate all callbacks registered in a plugin. """
+
+        unload = []
+
+        # look for all callbacks in a plugin
+        for name, cblist in self.cbs.iteritems():
+            index = 0
+            for item in cblist:
+                if item.plugname == plugname:
+                    item.activate = True
+
+    def whereis(self, cmnd):
+
+        """ show where ircevent.CMND callbacks are registered """
+
+        result = []
+        cmnd = cmnd.upper()
+
+        # locate callbacks for CMND
+        for c, callback in self.cbs.iteritems():
+            if c == cmnd:
+                for item in callback:
+                    if not item.plugname in result:
+                        result.append(item.plugname)
+
+        return result
+
+    def list(self):
+
+        """ show all callbacks. """
+
+        result = []
+
+        # loop over callbacks and collect callback functions
+        for cmnd, callbacks in self.cbs.iteritems():
+            for cb in callbacks:
+                result.append(getname(cb.func))
+
+        return result
+
+    def check(self, bot, ievent):
+
+        """ 
+            check for callbacks to be fired. 
+
+            :param bot: bot where event originates from
+            :param ievent: event that needs to be checked
+
+            .. literalinclude:: ../../gozerbot/callbacks.py
+                :pyobject: Callbacks.check
+
+        """
+
+        # check for "ALL" callbacks
+        if self.cbs.has_key('ALL'):
+            for cb in self.cbs['ALL']:
+                stats.up('callbacks', 'ALL')
+                self.callback(cb, bot, ievent)
+
+        cmnd = ievent.cbtype or ievent.cmnd.upper()
+
+        # check for CMND callbacks
+        if self.cbs.has_key(cmnd):
+            for cb in self.cbs[cmnd]:
+                stats.up('callbacks', cmnd)
+                self.callback(cb, bot, ievent)
+
+    def callback(self, cb, bot, ievent):
+
+        """ 
+            do the actual callback with provided bot and ievent as arguments.
+
+            :param cb: the callback to fire
+            :param bot: bot to call the callback on
+            :param ievent: the ievent that triggered the callback
+
+            .. literalinclude:: ../../gozerbot/callbacks.py
+                :pyobject: Callbacks.callback
+
+        """
+
+        try:
+            if not cb.activate:
+                return
+            # see if the callback pre requirement succeeds
+            if cb.prereq:
+                rlog(0, 'callbacks', 'excecuting in loop %s' % str(cb.prereq))
+                if not cb.prereq(bot, ievent):
+                    return
+
+            # check if callback function is there
+            if not cb.func:
+                return
+
+            # log and stats
+            rlog(0, 'callbacks', 'excecuting callback %s' % str(cb.func))
+            stats.up('callbacks', getname(cb.func))
+            stats.up('callbacks', cb.plugname)
+
+            ievent.iscallback = True
+
+            # launcn the callback .. either threaded or dispatched at runners
+            if cb.threaded:
+                start_new_thread(cb.func, (bot, ievent), cb.kwargs)
+            else:
+                cbrunners.put(cb.plugname, cb.func, bot, ievent, **cb.kwargs)
+
+        except Exception, ex:
+            handle_exception()
+
+# INIT SECTION
+
+# callbacks object is the same for ICR and Jabber
+callbacks = jcallbacks = Callbacks()
+gn_callbacks = Callbacks()
+
+# END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/morphs.py
+++ gozerbot-0.99.1/build/lib/gozerbot/morphs.py
@@ -0,0 +1,159 @@
+# gozerbot/morphs.py
+#
+#
+
+""" convert input/output stream. """
+
+## IMPORT SECTION
+
+# gozerbot imports
+from utils.exception import handle_exception
+from utils.trace import calledfrom
+
+# basic imports
+import sys
+
+## END IMPORT
+
+## LOCK SECTION
+
+# no locks
+
+## END LOCK
+
+class Morph(object):
+
+    """
+        transform stream.
+
+        :param func: morphing function
+        :type func: function
+
+    """
+
+    def __init__(self, func):
+        self.plugname = calledfrom(sys._getframe(0))
+        self.func = func
+        self.activate = False
+
+    def do(self, *args, **kwargs):
+
+        """
+            do the morphing.
+
+            .. literalinclude:: ../../gozerbot/morphs.py
+                :pyobject: Morph.do
+        """
+        if not self.activate:
+            return
+
+        try:
+            return self.func(*args, **kwargs)
+        except Exception, ex:
+            handle_exception()
+
+class MorphList(list):
+
+    """ list of morphs. """
+
+    def add(self, func, index=None):
+
+        """
+            add morph.
+
+            :param func: morphing function
+            :type func: function
+            :param index: index into the morphlist
+            :type index: integer
+            :rtype: self
+
+            .. literalinclude:: ../../gozerbot/morphs.py
+                :pyobject: MorphList.add
+
+        """
+
+        if not index:
+            self.append(Morph(func))
+        else:
+            self.insert(index, Moprh(func))
+
+        return self
+
+    def do(self, input, *args, **kwargs):
+
+        """
+            call morphing chain.
+
+            :param input: data to do the morphing on
+            :type input: string
+
+            .. literalinclude:: ../../gozerbot/morphs.py
+                :pyobject: MorphList.do
+
+        """
+
+        for morph in self:
+            input = morph.do(input, *args, **kwargs) or input
+
+        return input
+
+    def unload(self, plugname):
+
+        """
+            unload morhps belonging to plug <plugname>.
+
+            :param plugname: the plugname to unload the morphs from
+            :type plugname: string
+
+            .. literalinclude:: ../../gozerbot/morphs.py
+                :pyobject: MorphList.unload
+
+        """
+
+        for index in range(len(self)-1, -1, -1):
+            if self[index].plugname == plugname:
+                del self[index]
+
+    def disable(self, plugname):
+
+        """
+            disable morhps belonging to plug <plugname>.
+
+            :param plugname: the plugname to unload the morphs from
+            :type plugname: string
+
+            .. literalinclude:: ../../gozerbot/morphs.py
+                :pyobject: MorphList.disable
+
+        """
+
+        for index in range(len(self)-1, -1, -1):
+            if self[index].plugname == plugname:
+                self[index].activate = False
+
+    def activate(self, plugname):
+
+        """
+            activate morhps belonging to plug <plugname>.
+
+            :param plugname: the plugname to unload the morphs from
+            :type plugname: string
+
+            .. literalinclude:: ../../gozerbot/morphs.py
+                :pyobject: MorphList.activate
+
+        """
+
+        for index in range(len(self)-1, -1, -1):
+            if self[index].plugname == plugname:
+                self[index].activate = False
+
+## INIT SECTION
+
+# moprhs used on input
+inputmorphs = MorphList()
+
+# morphs used on output
+outputmorphs = MorphList()
+
+## END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/jsonusers.py
+++ gozerbot-0.99.1/build/lib/gozerbot/jsonusers.py
@@ -0,0 +1,751 @@
+# gozerbot/jsonusers.py
+#
+#
+
+""" bot's users in JSON file.  NOT USED AT THE MOMENT. """
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+# gozerbot imports
+from gozerbot.datadir import datadir
+from gozerbot.utils.log import rlog
+from gozerbot.utils.generic import stripident, stripidents
+from gozerbot.utils.exception import handle_exception, exceptionmsg
+from gozerbot.utils.generic import die, stripped
+from gozerbot.persist.persist import Persist
+from gozerbot.utils.lazydict import LazyDict
+from gozerbot.config import config
+
+# basic imports
+import re, types, os, time
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
+
+
+class JsonUser(Persist):
+
+    """ LazyDict representing a user. """
+
+    def __init__(self, name="", userhosts=[], perms=[], permits=[], status=[], email=[]):
+        Persist.__init__(self, u"users" + os.sep + name)
+        self.data.name = self.data.name or name
+        self.data.userhosts = self.data.userhosts or userhosts
+        self.data.perms = self.data.perms or perms
+        self.data.permits = self.data.permits or permits
+        self.data.status = self.data.status or status
+        self.data.email = self.data.email or email
+
+class JsonUsers(Persist):
+
+    """ class representing all users. """
+
+    def __init__(self, filename, ddir=None):
+        self.datadir = ddir or datadir
+        Persist.__init__(self, filename)
+        if not self.data:
+            self.data = LazyDict()
+        self.data.names = self.data.names or {}
+
+    def all(self):
+
+        """ get all users. """
+
+        result = []
+        for name in self.data['names'].values():
+            result.append(JsonUser(name))
+        return result
+
+    ### Misc. Functions
+    def size(self):
+
+        """ return nr of users. """
+
+        return len(self.data['names'])
+
+    def names(self):
+
+        """ get names of all users. """
+
+        return self.data.names
+
+    def byname(self, name):
+        """ return user by name. """ 
+        try:
+            return JsonUser(name)
+        except KeyError:
+            return
+
+    def merge(self, name, userhost):
+        """ add userhosts to user with name """
+        user = self.byname(name)
+        if user:
+            user.userhosts.append(userhost)
+            self.save()
+            rlog(10, 'users', "%s merged with %s" % (userhost, name))
+            return 1
+
+    def usersearch(self, userhost):
+        """ search for users with a userhost like the one specified """
+
+        result = []
+
+        for u, name in self.data.names.iteritems():
+
+            if userhost in u:
+                result.append((name, u))
+
+        return result
+
+    def getuser(self, userhost):
+        try:
+            return JsonUser(self.data.names[userhost])
+        except KeyError:
+            return 
+
+    ### Check functions
+    def exist(self, name):
+        """ see if user with <name> exists """
+        return self.byname(name)
+
+    def allowed(self, userhost, perms, log=True):
+        """ check if user with userhosts is allowed to execute perm command """
+        if not type(perms) == types.ListType:
+            perms = [perms, ]
+        if 'ANY' in perms:
+            return 1
+        res = None
+        user = self.getuser(userhost)
+        if not user and log:
+            rlog(10, 'users', '%s userhost denied' % userhost)
+            return res
+        else:
+            uperms = set(user.perms)
+            sperms = set(perms)
+            intersection = sperms.intersection(uperms)
+            res = list(intersection) or None
+        if not res and log:
+            rlog(10, 'users', "%s perm %s denied" % (userhost, str(perms)))
+        return res
+
+    def permitted(self, userhost, who, what):
+        """ check if (who,what) is in users permit list """
+        user = self.getuser(userhost)
+        res = None
+        if user:
+            if '%s %s' % (who, what) in user.permits:
+                res = 1
+        return res
+
+    def status(self, userhost, status):
+        """ check if user with <userhost> has <status> set """
+        user = self.getuser(userhost)
+        res = None
+        if user:
+            if status.upper() in user.statuses:
+                res = 1
+        return res
+
+    def gotuserhost(self, name, userhost):
+        """ check if user has userhost """
+        user = self.byname(name)
+        return userhost in user.userhosts
+
+    def gotperm(self, name, perm):
+        """ check if user had permission """
+        user = self.byname(name)
+        if user:
+            return perm.upper() in user.perms
+
+    def gotpermit(self, name, permit):
+        """ check if user permits something.  permit is a (who, what) tuple """
+        user = self.byname(name)
+        if user:
+            return '%s %s' % permit in user.permits
+        
+    def gotstatus(self, name, status):
+        """ check if user has status """
+        user = self.byname(name)
+        return status.upper() in user.statuses
+
+    ### Get Functions
+    def getname(self, userhost):
+        """ get name of user belonging to <userhost> """
+        user = self.getuser(userhost)
+        if user:
+            return user.name
+
+    def gethosts(self, userhost):
+        """ return the userhosts of the user associated with the specified userhost """
+        user = self.getuser(userhost)
+        if user:
+            return user.userhosts
+    
+    def getemail(self, userhost):
+        """ return the email of the specified userhost """
+        user = self.getuser(userhost)
+        if user:
+            if user.email:
+                return user.email[0]
+
+    def getperms(self, userhost):
+        """ return permission of user"""
+        user = self.getuser(userhost)
+        if user:
+            return user.perms
+
+    def getpermits(self, userhost):
+        """ return permits of the specified userhost"""
+        user = self.getuser(userhost)
+        if user:
+            return user.permits
+
+    def getstatuses(self, userhost):
+
+        """
+            return the list of statuses for the specified userhost.
+
+        """
+
+        user = self.getuser(userhost)
+        if user:
+            return user.status
+
+    def getuserhosts(self, name):
+
+        """
+            return the userhosts associated with the specified user.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            return user.userhosts
+
+    def getuseremail(self, name):
+
+        """
+            get email of user.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            if user.email:
+                return user.email[0]
+
+    def getuserperms(self, name):
+
+        """
+            return permission of user.
+
+        """
+
+        user = self.byname(name)
+        if user:
+            return user.perms
+
+    def getuserpermits(self, name):
+
+        """
+            return permits of user.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            return user.permits
+
+    def getuserstatuses(self, name):
+
+        """
+            return the list of statuses for the specified user.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            return user.status
+
+    def getpermusers(self, perm):
+
+        """
+            return all users that have the specified perm.
+
+        """
+
+        result = []
+
+        for userdata in self.users.values():
+            user = JsonUser(d=userdata)
+
+            if perm.upper() in user.perms:
+                result.append(user.name)
+
+        return result
+
+    def getstatususers(self, status):
+
+        """
+            return all users that have the specified status.
+
+        """
+
+        result = []
+
+        for userdata in self.users.values():
+            user = JsonUser(d=userdata)
+
+            if status in user.status:
+                result.append(user.name)
+
+        return result
+
+    ### Set Functions
+    def setemail(self, name, email):
+
+        """ 
+            set email of user.
+
+        """
+
+        user = self.byname(name)
+        if user:
+            try:
+                user.email.remove(email)
+            except:
+                pass
+            user.email.insert(0, email)
+            self.save()
+            return True
+        return False
+
+    ### Add functions
+    def add(self, name, userhosts, perms):
+
+        """
+            add an user.
+
+        """
+
+        if type(userhosts) != types.ListType:
+            rlog(10, 'jsonusers', 'i need a list of userhosts')
+            return 0
+
+        if not os.path.isdir(self.datadir + os.sep + 'users'):
+            os.mkdir(self.datadir + os.sep + 'users')
+
+        if not os.path.isdir(self.datadir + os.sep + 'users' +  os.sep + name):
+            os.mkdir(self.datadir + os.sep + 'users' +  os.sep + name)
+
+        user = self.byname(name)
+
+        if not user:
+
+            try:
+                newuser = JsonUser(name=name)
+                newuser.userhosts.extend(userhosts)
+                newuser.perms.extend(perms)
+                self.users[name] = newuser
+                self.save()
+            except Exception, ex:
+                rlog(10, 'jsonusers', str(ex))
+                return False
+
+            rlog(10, 'users', '%s %s %s added to user database' % (name, \
+userhosts, perms))
+
+        return True
+
+    def addemail(self, userhost, email):
+
+        """
+            add an email address to the userhost.
+
+        """
+
+        user = self.getuser(userhost)
+
+        if user:
+            user.email.append(email)
+            self.save()
+            rlog(10, 'users', '%s (%s) added to email' % (email, userhost))
+            return 1
+
+    def addperm(self, userhost, perm):
+
+        """
+            add the specified perm to the userhost.
+
+        """
+
+        user = self.getuser(userhost)
+
+        if user:
+            user.perms.append(perm.upper())
+            self.save()
+            rlog(10, 'users', '%s perm %s added' % (userhost, perm))
+            return 1
+
+    def addpermit(self, userhost, permit):
+
+        """
+            add the given (who, what) permit to the given userhost.
+
+        """
+
+        user = self.getuser(userhost)
+
+        if user:
+            #p = '%s %s' % permit
+            user.permits.append(permit)
+            self.save()
+            rlog(10, 'users', '%s permit %s added' % (userhost, p))
+            return 1
+
+    def addstatus(self, userhost, status):
+
+        """
+            add status to given userhost.
+
+        """
+
+        user = self.getuser(userhost)
+
+        if user:
+            user.status.append(status.upper())
+            self.save()
+            rlog(10, 'users', '%s status %s added' % (name, status))
+            return 1
+
+    def adduserhost(self, name, userhost):
+
+        """
+           add userhost.
+
+        """
+
+        user = self.byname(name)
+
+        if not user:
+            user = self.users[name] = User(name=name)
+
+        user.userhosts.append(userhost)
+        self.save(user)
+        rlog(10, 'users', '%s (%s) added to userhosts' % (name, userhost))
+        return 1
+
+    def adduseremail(self, name, email):
+
+        """
+            add email to specified user.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            user.email.append(email)
+            self.save()
+            rlog(10, 'users', '%s email %s added' % (name, email))
+            return 1
+
+    def adduserperm(self, name, perm):
+
+        """ 
+           add permission.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            perm = perm.upper()
+            user.perms.append(perm)
+            self.save()
+            rlog(10, 'users', '%s perm %s added' % (name, perm))
+            return 1
+
+    def adduserpermit(self, name, permit):
+
+        """ 
+            add (who, what) permit tuple to sepcified user.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            p = '%s %s' % permit
+            user.permits.append(p)
+            self.save()
+            rlog(10, 'users', '%s permit %s added' % (name, p))
+            return 1
+
+    def adduserstatus(self, name, status):
+
+        """
+            add status to given user.
+
+        """
+
+        user = byname(name)
+
+        if user:
+            user.status.append(status.upper())
+            self.save()
+            rlog(10, 'users', '%s status %s added' % (name, status))
+            return 1
+
+    def addpermall(self, perm): 
+
+        """
+            add permission to all users.
+
+        """
+
+        for user in self.users.values():
+            user.perms.append(perm.upper())
+
+        self.save()
+
+    ### Delete functions
+
+    def delemail(self, userhost, email):
+
+        """
+            delete email from userhost.
+
+        """
+
+        user = self.getuser(userhost)
+
+        if user:
+
+            if email in user.emails:
+                user.emails.remove(email)
+                self.save()
+                return 1
+
+    def delperm(self, userhost, perm):
+
+        """
+            delete perm from userhost.
+
+        """
+
+        user = self.getuser(userhost)
+        if user:
+            p = perm.upper()
+            if p in user.perms:
+                user.perms.remove(p)
+                self.save()
+                return 1
+
+    def delpermit(self, userhost, permit):
+
+        """
+            delete permit from userhost.
+
+        """
+
+        user = self.getuser(userhost)
+
+        if user:
+            p = '%s %s' % permit
+
+            if p in user.permits:
+                user.permits.remove(p)
+                self.save()
+                return 1
+
+    def delstatus(self, userhost, status):
+
+        """
+            delete status from userhost.
+
+        """
+
+        user = self.getuser(userhost)
+
+        if user:
+            st = status.upper()
+
+            if st in user.status:
+                user.status.remove(st)
+                self.save()
+                return 1
+
+    def delete(self, name):
+
+        """
+            delete user with name.
+
+        """
+
+        del self.users[name]
+        self.save()
+        return 1
+
+    def deluserhost(self, name, userhost):
+
+        """
+            delete the userhost entry.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+
+            if userhost in user.userhosts:
+                user.userhosts.remove(userhost)
+                self.save()
+                rlog(10, 'users', '%s userhost %s deleted' % (name, userhost))
+                return 1
+
+    def deluseremail(self, name, email):
+
+        """
+            delete email.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+
+            if email in user.email:
+                user.email.remove(email)
+                self.save()
+                rlog(10, 'users', '%s email %s deleted' % (name, email))
+                return 1
+
+    def deluserperm(self, name, perm):
+
+        """
+            delete permission.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            p = perm.upper()
+
+            if p in user.perms:
+                user.perms.remove(p)
+                self.save()
+                rlog(10, 'users', '%s perm %s deleted' % (name, p))
+                return 1
+
+    def deluserpermit(self, name, permit):
+
+        """
+            delete permit.
+
+         """
+
+        user = self.byname(name)
+
+        if user:
+            p = '%s %s' % permit
+
+            if p in user.permits:
+                user.permits.remove(p)
+                self.save()
+                rlog(10, 'users', '%s permit %s deleted' % (name, p))
+                return 1
+
+    def deluserstatus(self, name, status):
+
+        """
+            delete the status from the given user.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            st = status.upper()
+
+            if st in user.status:
+                user.status.remove(status)
+                self.save()
+                rlog(10, 'users', '%s status %s deleted' % (name, st))
+                return 1
+
+    def delallemail(self, name):
+
+        """
+            Delete all emails for the specified user.
+
+        """
+
+        user = self.byname(name)
+
+        if user:
+            user.email = []
+            self.save()
+            rlog(10, 'users', '%s emails deleted' % (name, ))
+            return 1
+
+    def delpermall(self, perm):
+
+        """
+            delete permission from all users.
+
+        """
+
+        for user in self.users.values():
+            if user.name != 'owner':
+                del user.perms
+
+        self.save()
+        return 1
+
+    def make_owner(self, userhosts):
+
+        """
+            see if owner already has a user account if not merge otherwise add. 
+
+        """
+
+        assert(userhosts)
+        owner = []
+
+        if type(userhosts) != types.ListType:
+            owner.append(userhosts)
+        else:
+            owner = userhosts
+
+        for userhost in owner:
+            username = self.getname(unicode(userhost))
+
+            if not username:
+
+                if not self.merge('owner', unicode(userhost)):
+                    self.add('owner', [unicode(userhost), ], ['USER', 'OPER'])
+
+# ============
+# INIT SECTION
+
+# no vars
+
+# END INIT
+# ========
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/sausers.py
+++ gozerbot-0.99.1/build/lib/gozerbot/sausers.py
@@ -0,0 +1,1189 @@
+# gozerbot/users.py
+#
+#
+
+""" bot users """
+
+__copyright__ = 'this file is in the public domain'
+
+## IMPORT SECTION
+
+# gozerbot imports
+from gozerbot.datadir import datadir
+from gozerbot.utils.log import rlog
+from gozerbot.utils.locking import lockdec
+from gozerbot.utils.generic import stripident, stripidents
+from gozerbot.utils.exception import handle_exception, exceptionmsg
+from gozerbot.utils.generic import die, stripped
+from gozerbot.utils.name import stripname
+from gozerbot.persist.persist import Persist
+from gozerbot.config import config
+from gozerbot.jsonusers import JsonUsers
+
+# basic imports
+import re, types, os, time, thread, sqlalchemy
+
+## END IMPORT
+
+## LOCK SECTION
+
+deletelock = thread.allocate_lock()
+deletelocked = lockdec(deletelock)
+
+## END LOCK
+
+# see if we need to use the database or use the jsonusers
+
+if True:
+    if not config['nodb']:
+        from gozerbot.database.samodels import UserHost, User, Perms, Statuses
+        from gozerbot.database.alchemy import Session, query, getuser, byname, eagerload, dblocked
+
+    else:
+        def trans(func, *args, **kwargs):
+            def transaction(*args, **kwargs):
+                func(*args, **kwargs)
+            return transaction
+
+
+class DbUsers(object):
+
+    """
+         sqlalchemy backed users.
+
+    """
+
+    def __init__(self, ddir=None):
+        self.datadir = ddir or datadir
+
+    ### Misc. Functions
+    def size(self):
+
+        """
+            return nr of users.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.size
+        """
+
+        return int(query(User).count())
+        
+    def names(self):
+
+        """
+            get names of all users.
+
+            :rtype: list .. list of usernames
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.names
+
+        """
+        a = query(User).all()
+        return [n.name for n in a]
+
+    @dblocked
+    def merge(self, name, userhost):
+
+        """
+             add userhosts to user with name.
+
+             :param session: the session to act upon
+             :type session: sqllachemy.session.Session
+             :param name: name of the user to merge with
+             :type name: string
+             :param userhost: userhosts to merge 
+             :type userhost: list .. list of userhosts
+             :rtype: boolean .. whether the merge succeeded
+
+             .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.merge
+
+        """
+
+        user = byname(name)
+
+        if user:
+
+            if name not in user.userhosts:
+                #Session.begin()
+                user.userhosts.append(userhost)
+                #Session.commit()
+                #Session.refresh(user)
+                #Session.close()
+                rlog(10, 'users', "%s merged with %s" % (userhost, name))
+                return True
+
+        return False
+
+    def usersearch(self, userhost):
+
+        """
+            search for users with a userhost like the one specified.
+
+            :param userhost: userhost to search for
+            :type userhost: string
+            :rtype: list .. list of (user.name, user.userhost) tuples
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.usersearch
+
+        """
+
+        n = query(UserHost).filter(UserHost.userhost.like('%%%s%%' % userhost)).all()
+        return [(u.name, u.userhost) for u in n]
+
+    ### Check functions
+    def exist(self, name):
+
+        """
+            see if user with <name> exists.
+
+            :param name: name of the user
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.exist
+
+        """
+
+        a = byname(name)
+
+        if a:
+            return True
+
+        return False
+
+    def allowed(self, userhost, perms, log=True):
+
+        """
+            check if user with userhosts is allowed to execute perm command.
+
+            :param userhost: userhost to check
+            :type userhost: string
+            :param perms: the permissions to check            
+            :type perms: list
+            :param log: whether the check should be logged
+            :type log: boolean
+            :rtype: list .. list of matching permissions or None
+ 
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.allowed
+
+        """
+
+        if not type(perms) == types.ListType:
+            perms = [perms, ]
+
+        if 'ANY' in perms:
+            return ['ANY', ]
+
+        res = None
+        user = getuser(userhost)
+
+        if not user:
+
+            if log:
+                rlog(10, 'users', '%s userhost denied' % userhost)
+            return res
+
+        else:
+            uperms = set(user.perms)
+            sperms = set(perms)
+            intersection = sperms.intersection(uperms)
+            res = list(intersection) or None
+
+        if not res and log:
+            rlog(10, 'users', "%s perm %s denied" % (userhost, str(perms)))
+
+        if res and log:
+            rlog(10, 'users', 'allowed %s %s perm' % (userhost, str(perms)))
+
+        return res
+
+    def permitted(self, userhost, who, what):
+
+        """
+            check if (who,what) is in users permit list.
+
+            :param userhost: userhost to check permits for
+            :type userhost: string
+            :param who: who to permit
+            :type who: string
+            :param what: what to allow
+            :type what: string
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.permitted
+        """
+
+        user = getuser(userhost)
+        res = False
+
+        if user:
+            if '%s %s' % (who, what) in user.permits:
+                res = True
+
+        return res
+
+    def status(self, userhost, status):
+
+        """
+            check if user with <userhost> has <status> set.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.status
+
+        """
+
+        user = getuser(userhost)
+        res = False
+
+        if user:
+            if status.upper() in user.statuses:
+                res = True
+
+        return res
+
+    def gotuserhost(self, name, userhost):
+
+        """
+            check if user has userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.gotuserhost
+
+        """
+
+        user = byname(name)
+
+        if user:
+            return userhost in user.userhosts
+
+        return False
+
+    def gotperm(self, name, perm):
+
+        """
+            check if user has permission.
+
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.gotperm
+        """
+
+        user = byname(name)
+
+        if user:
+            return perm.upper() in user.perms
+
+        return False
+
+    def gotpermit(self, name, permit):
+
+        """
+            check if user permits something.  permit is a [who, what] list. 
+
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.gotpermit
+
+        """
+
+        user = byname(name)
+
+        if user:
+            return '%s %s' % permit in user.permits
+
+        return ""
+
+    def gotstatus(self, name, status):
+
+        """
+            check if user has status.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.gotstatus
+
+        """
+
+        user = byname(name)
+        if user:
+            return status.upper() in user.statuses
+
+        return False
+
+    ### Get Functions
+    def getname(self, userhost):
+
+        """
+            get name of user belonging to <userhost>.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getname
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            return str(user.name)
+
+        return ""
+
+    def gethosts(self, userhost):
+
+        """ 
+            return the userhosts of the user associated with the 
+            specified userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.gethosts
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            return list(user.userhosts)
+
+        return []
+    
+    def getemail(self, userhost):
+
+        """
+            return the email of the specified userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getemail
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            if user.email:
+                return str(user.email[0])
+
+        return ""
+
+    def getperms(self, userhost):
+
+        """
+            return permissions of user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getperms
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            return list(user.perms)
+
+        return []
+
+    def getpermits(self, userhost):
+
+        """
+            return permits of the specified userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getpermits
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            return list(user.permits)
+
+        return []
+
+    def getstatuses(self, userhost):
+
+        """
+            return the list of statuses for the specified userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getstatuses
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            return list(user.statuses)
+
+    def getuserhosts(self, name):
+
+        """
+            return the userhosts associated with the specified user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getuserhosts
+        """
+
+        user = byname(name)
+
+        if user:
+            return list(user.userhosts)
+
+    def getuseremail(self, name):
+
+        """
+            get email of user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getuseremail
+
+        """
+
+        user = byname(name)
+
+        if user:
+            if user.email:
+                return str(user.email[0])
+
+    def getuserperms(self, name):
+
+        """
+            return permission of user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getuserperms
+        """
+
+        user = byname(name)
+
+        if user:
+            return list(user.perms)
+
+    def getuserpermits(self, name):
+ 
+        """
+            return permits of user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getuserpermits
+        """
+
+        user = byname(name)
+
+        if user:
+            return list(user.permits)
+
+    def getuserstatuses(self, name):
+
+        """
+            return the list of statuses for the specified user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getuserstatuses
+        """
+
+        user = byname(name)
+
+        if user:
+            return list(user.statuses)
+
+    def getpermusers(self, perm):
+
+        """
+            return all users that have the specified perm.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getpermusers
+
+        """
+        n = query(Perms).filter(Perms.perm==perm.upper()).all()
+        return [user.name for user in n]
+
+    def getstatususers(self, status):
+
+        """
+            return all users that have the specified status.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.getstatususers
+
+        """
+
+        n = query(Statuses).filter(Statuses.status==status.upper()).all()
+        return [user.name for user in n]
+
+    ### Set Functions
+
+    @dblocked
+    def setemail(self, name, email):
+
+        """
+            set email of user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.setemail
+        """
+
+        user = byname(name)
+
+        if user:
+            #Session.begin()
+            try:
+                user.email.remove(email)
+            except:
+                pass
+            user.email.insert(0, email)
+            #Session.refresh(user)
+            #Session.commit()
+            #Session.close()
+            return True
+
+        return False
+
+    ### Add functions
+
+    @dblocked
+    def add(self, name, userhosts, perms):
+
+        """
+            add an user. session argument is provided by @trans
+
+            :param name: name of the user
+            :type name: string
+            :param userhosts: userhosts to add
+            :type userhosts: list .. list of userhosts
+            :param perms: the permissions to add
+            :type perms: list .. list of permissions
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.add
+
+        """
+
+        if type(userhosts) != types.ListType:
+            rlog(10, 'users', 'i need a list of userhosts')
+            return False
+
+        name = stripname(name.lower())
+
+        if not os.path.isdir(self.datadir + os.sep + 'users'):
+            os.mkdir(self.datadir + os.sep + 'users')
+
+        if not os.path.isdir(self.datadir + os.sep + 'users' +  os.sep + name):
+            os.mkdir(self.datadir + os.sep + 'users' +  os.sep + name)
+
+        user = byname(name)
+
+        if not user:
+
+            try:
+                #Session.begin()
+                newuser = User(name=name)
+                newuser.userhosts.extend(userhosts)
+                newuser.perms.extend(perms)
+                Session.add(newuser)
+                #Session.commit()
+                #Session.close()
+            except sqlalchemy.exc.IntegrityError, ex:
+                if 'not unique' in str(ex):
+                    rlog(10, 'users', '%s %s already exists' % (name, userhosts))
+                    Session.close()
+                    return True
+                else:
+                    raise
+            except Exception, ex:
+                raise
+
+            rlog(10, 'users', '%s %s %s added to user database' % (name, userhosts, perms))
+
+        return True
+
+    @dblocked
+    def addemail(self, userhost, email):
+
+        """
+            add an email address to the userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.addemail
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            #Session.begin()
+            user.email.append(email)
+            #Session.refresh(user)
+            #Session.commit()
+            #Session.close()
+            rlog(10, 'users', '%s (%s) added to email' % (email, userhost))
+            return True
+
+        return False
+
+    @dblocked
+    def addperm(self, userhost, perm):
+
+        """
+            add the specified perm to the userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.addperm
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            #Session.begin()
+            user.perms.append(perm.upper())
+            #Session.refresh(user)
+            #Session.commit()
+            #Session.close()
+            rlog(10, 'users', '%s perm %s added' % (userhost, perm))
+            return True
+
+        return False
+
+    @dblocked
+    def addpermit(self, userhost, permit):
+
+        """
+            add the given [who, what] permit to the given userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.addpermit
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            p = '%s %s' % permit
+            #Session.begin()
+            user.permits.append(p)
+            Session.refesh(user)
+            #Session.commit()
+            #Session.close()
+            rlog(10, 'users', '%s permit %s added' % (userhost, p))
+            return True
+
+        return False
+
+    @dblocked
+    def addstatus(self, userhost, status):
+
+        """
+            add status to given userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.addstatus
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            #Session.begin()
+            user.statuses.append(status.upper())
+            #Session.commit()
+            #Session.refresh(user)
+            #Session.close()
+            rlog(10, 'users', '%s status %s added' % (name, status))
+            return True
+
+        return False
+
+    @dblocked
+    def adduserhost(self, name, userhost):
+
+        """
+            add userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.adduserhost
+
+        """
+
+        user = byname(name)
+        #Session.begin()
+        if not user:
+            user = User(name=name)
+            Session.add(user)
+       
+        user.userhosts.append(userhost)
+        #Session.commit()
+        #Session.refresh(user)
+        #Session.close()
+        rlog(10, 'users', '%s (%s) added to userhosts' % (name, userhost))
+        return True
+
+    @dblocked
+    def adduseremail(self, name, email):
+
+        """
+            add email to specified user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.adduseremail
+
+        """
+
+        user = byname(name)
+
+        if user:
+            #Session.begin()
+            user.email.append(email)
+            #Session.commit()
+            #Session.refresh(user)
+            #Session.close()
+            rlog(10, 'users', '%s email %s added' % (name, email))
+            return True
+
+    @dblocked
+    def adduserperm(self, name, perm):
+
+        """
+            add permission.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.adduserperm
+
+        """
+
+        user = byname(name)
+
+        if user:
+            #Session.begin()
+            perm = perm.upper()
+            user.perms.append(perm)
+            #Session.commit()
+            #Session.refresh(user)
+            #Session.close()
+            rlog(10, 'users', '%s perm %s added' % (name, perm))
+            return True
+
+        return False
+
+    @dblocked
+    def adduserpermit(self, name, who, permit):
+
+        """ 
+            add (who, what) permit tuple to specified user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.adduserpermit
+
+        """
+
+        user = byname(name)
+
+        if user:
+            p = '%s %s' % (who, permit)
+            #Session.begin()
+            user.permits.append(p)
+            #Session.commit()
+            #Session.refresh(user)
+            #Session.close()
+            rlog(10, 'users', '%s permit %s added' % (name, p))
+            return True
+
+        return False
+
+    @dblocked
+    def adduserstatus(self, name, status):
+
+        """
+            add status to given user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.adduserstatus
+
+        """
+
+        user = byname(name)
+
+        if user:
+            #Session.begin()
+            user.statuses.append(status.upper())
+            #Session.commit()
+            #Session.refresh(user)
+            #Session.close()
+            rlog(10, 'users', '%s status %s added' % (name, status))
+            return True
+
+        return False
+
+    @dblocked
+    def addpermall(self, perm): 
+
+        """
+            add permission to all users.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.addpermall
+        """
+
+        users = query(User).all()
+
+        if users:
+            #Session.begin()
+            for user in users:
+                user.perms.append(perm.upper())
+                #Session.refresh(user)
+            #Session.commit()
+            #Session.close()
+
+    ### Delete functions
+
+    @dblocked
+    def delemail(self, userhost, email):
+
+        """
+            delete email from userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.delemail
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            if email in user.emails:
+                #Session.begin()
+                user.emails.remove(email)
+                #Session.commit()
+                #Session.refresh(user)
+                #Session.close()
+                return True
+
+        return False
+
+    @dblocked
+    def delperm(self, userhost, perm):
+
+        """
+            delete perm from userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.delperm
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            p = perm.upper()
+
+            if p in user.perms:
+                #Session.begin()
+                user.perms.remove(p)
+                #Session.commit()
+                #Session.refresh(user)
+                #Session.close()
+                return True
+
+        return False
+            
+    @dblocked
+    def delpermit(self, userhost, permit):
+
+        """
+            delete permit from userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.delpermit
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            p = '%s %s' % permit
+
+            if p in user.permits:
+                #Session.begin()
+                user.permits.remove(p)
+                #Session.commit()
+                #Session.refresh(user)
+                #Session.close()
+                return True
+
+        return False
+
+    @dblocked
+    def delstatus(self, userhost, status):
+
+        """
+            delete status from userhost.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.delstatus
+
+        """
+
+        user = getuser(userhost)
+
+        if user:
+            st = status.upper()
+
+            if st in user.statuses:
+                #Session.begin()
+                user.statuses.remove(st)
+                #Session.commit()
+                #Session.refresh(user)
+                #Session.close()
+                return True
+
+        return False
+
+    @dblocked
+    def delete(self, name):
+
+        """ 
+            delete user with name. 
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.delete
+
+        """
+        name = stripname(name)
+        user = byname(name)
+
+        if user:
+            rlog(10, 'users', "deleting %s" % name)
+            #Session.begin()
+            Session.delete(user)
+            #Session.commit()
+            #Session.close()
+            return True
+
+        return False
+
+    @dblocked
+    def deluserhost(self, name, userhost):
+
+        """
+            delete the userhost entry.
+
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.deluserhost
+        """
+
+        user = byname(name)
+
+        if user:
+            if userhost in user.userhosts:
+                #Session.begin()
+                user.userhosts.remove(userhost)
+                #Session.refresh(user)
+                #Session.commit()
+                #Session.close()
+                rlog(10, 'users', '%s userhost %s deleted' % (name, userhost))
+                return True
+
+        return False
+
+    @dblocked
+    def deluseremail(self, name, email):
+
+        """
+            delete email.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.deluseremail
+
+        """
+
+        user = byname(name)
+
+        if user:
+            if email in user.email:
+                #Session.begin()
+                user.email.remove(email)
+                #Session.refresh(user)
+                #Session.commit()
+                #Session.close()
+                rlog(10, 'users', '%s email %s deleted' % (name, email))
+                return True
+
+        return False
+
+    @dblocked
+    def deluserperm(self, name, perm):
+
+        """
+             delete permission.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.deluserperm
+
+        """
+
+        user = byname(name)
+
+        if user:
+            p = perm.upper()
+
+            if p in user.perms:
+                #Session.begin()
+                user.perms.remove(p)
+                #Session.refresh(user)
+                #Session.commit()
+                #Session.close()
+                rlog(10, 'users', '%s perm %s deleted' % (name, p))
+                return True
+
+        return False
+
+    @dblocked
+    def deluserpermit(self, name, permit):
+
+        """
+            delete permit.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.deluserpermit
+
+        """
+
+        user = byname(name)
+
+        if user:
+            p = '%s %s' % permit
+
+            if p in user.permits:
+                #Session.begin()
+                user.permits.remove(p)
+                #Session.refresh(user)
+                #Session.commit()
+                #Session.close()
+                rlog(10, 'users', '%s permit %s deleted' % (name, p))
+                return True
+
+        return False
+
+    @dblocked
+    def deluserstatus(self, name, status):
+
+        """
+            delete the status from the given user.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.deluserstatus
+
+        """
+
+        user = byname(name)
+
+        if user:
+            st = status.upper()
+
+            if st in user.statuses:
+                #Session.begin()
+                user.statuses.remove(status)
+                #Session.refresh(user)
+                #Session.commit()
+                #Session.close()
+                rlog(10, 'users', '%s status %s deleted' % (name, st))
+                return True
+
+        return False
+
+    @dblocked
+    def delallemail(self, name):
+
+        """
+            Delete all emails of the specified user.
+
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.delallemail
+
+        """
+
+        user = byname(name)
+
+        if user:
+            #Session.begin()
+            user.email = []
+            #Session.refresh(user)
+            #Session.commit()
+            #Session.close()
+            rlog(10, 'users', '%s emails deleted' % (name, ))
+            return True
+
+        return False
+
+    @dblocked
+    def delpermall(self, perm):
+
+        """
+            delete permission from all users.
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.delpermall
+
+        """
+
+        users = query(User).options(eagerload('perms')).all()
+
+        for user in users:
+
+            if user.name != 'owner':
+
+                try:
+                    #Session.begin()
+                    user.perms.remove(perm)
+                    #Session.refresh(user)
+                    #Session.commit()
+                    #Session.close()
+                except ValueError:
+                    pass
+
+        return True
+
+    def make_owner(self, userhosts):
+
+        """
+            see if owner already has a user account if not merge otherwise 
+            add.
+
+            :param userhosts: userhosts to inititalize owner with
+            :type userhosts: list or string
+
+            .. literalinclude:: ../../gozerbot/users.py
+                :pyobject: DbUsers.make_owner
+
+        """
+
+        owner = []
+
+        if type(userhosts) != types.ListType:
+            owner.append(userhosts)
+        else:
+            owner = userhosts
+
+        for userhost in owner:
+            username = self.getname(unicode(userhost))
+
+            if not username:
+                if not self.merge('owner', unicode(userhost)):
+                    self.add('owner', [unicode(userhost), ], ['USER', 'OPER'])
+            elif username != 'owner':
+                self.delete(username)
+                if not self.merge('owner', unicode(userhost)):
+                    self.add('owner', [unicode(userhost), ], ['USER', 'OPER'])
+               
+## INIT SECTION
+
+# default to database
+
+if not config['nodb']:
+    users = DbUsers()
+else:
+    # otheriwse use json users
+    users = JsonUsers(datadir + os.sep + (config['jsonuser'] or 'users.json'))
+
+## END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/periodical.py
+++ gozerbot-0.99.1/build/lib/gozerbot/periodical.py
@@ -0,0 +1,567 @@
+# gozerbot/periodical.py
+#
+#
+
+__author__ = "Wijnand 'tehmaze' Modderman - http://tehmaze.com"
+__license__ = "BSD License"
+
+## IMPORT SECTION
+
+# gozerbot imports
+
+from utils.exception import handle_exception
+from utils.trace import calledfrom, whichmodule
+from utils.locking import lockdec
+from utils.log import rlog
+from utils.timeutils import strtotime
+import threads.thr as thr
+
+# basic imorts
+import datetime, sys, time, thread, types
+
+## END IMPORT
+
+## LOCK SECTION
+# locks and vars
+plock    = thread.allocate_lock()
+locked   = lockdec(plock)
+pidcount = 0
+
+# END LOCK
+
+class JobError(Exception):
+
+    """
+        job error exception.
+
+    """
+
+    pass
+
+class Job(object):
+
+    """
+        job to be scheduled.
+
+    """
+
+    group = ''
+    pid   = -1
+
+    def __init__(self):
+        global pidcount
+        pidcount += 1
+        self.pid = pidcount
+
+    def id(self):
+
+        """
+            return job id.
+
+        """
+
+        return self.pid
+
+    def member(self, group):
+
+        """
+             check for group membership.
+
+             :param group: group to check for
+             :type group: string
+             :rtype: boolean
+
+        """
+
+        return self.group == group
+
+    def do(self):
+        # try the callback
+        try:
+            self.func(*self.args, **self.kw)
+        except Exception, ex:
+            handle_exception()
+
+class JobAt(Job):
+
+    """
+        job to run at a specific time/interval/repeat.
+
+        :param start: start time
+        :type start: int, float or string
+        :param interval: time between alarms
+        :type interval: integer
+        :param repeat: number of repeats
+        :type interval: integer
+        :param func: the function to execute
+        :type func: function
+
+    """
+
+    def __init__(self, start, interval, repeat, func, *args, **kw):
+        Job.__init__(self)
+        self.func = func
+        self.args = args
+        self.kw = kw
+        self.repeat = repeat
+        self.description = ""
+        self.counts = 0
+
+        # check start time format
+        if type(start) in [types.IntType, types.FloatType]:
+            self.next = float(start)
+        elif type(start) in [types.StringType, types.UnicodeType]:
+            d = strtotime(start)
+            if d and d > time.time():
+                self.next = d
+            else:
+                raise JobError("invalid date/time")
+
+        if type(interval) in [types.IntType]:
+            d = datetime.timedelta(days=interval)
+            self.delta = d.seconds
+        else:
+            self.delta = interval
+
+    def __repr__(self):
+
+        """
+            return a string representation of the JobAt object.
+
+        """
+
+        return '<JobAt instance next=%s, interval=%s, repeat=%d, function=%s>' % (str(self.next),
+            str(self.delta), self.repeat, str(self.func))
+
+    def check(self):
+
+        """
+            run check to see if job needs to be scheduled.
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+                :pyobject: JobAt.check
+
+        """
+
+        if self.next <= time.time():
+            rlog(0, 'periodical', 'running %s - %s' % (str(self.func), self.description))
+            self.func(*self.args, **self.kw)
+            self.next += self.delta
+            self.counts += 1
+            if self.repeat > 0 and self.counts >= self.repeat:
+                return False # remove this job
+        return True
+
+class JobInterval(Job):
+
+    """
+        job to be scheduled at certain interval.
+
+        :param interval: time between alarms
+        :type interval: integer
+        :param repeat: number of repeats
+        :type interval: integer
+        :param func: the function to execute
+        :type func: function
+
+    """
+
+    def __init__(self, interval, repeat, func, *args, **kw):
+        Job.__init__(self)
+        self.func = func
+        self.args = args
+        self.kw = kw
+        self.repeat = int(repeat)
+        self.counts = 0
+        self.interval = float(interval)
+        self.description = ""
+        self.next = time.time() + self.interval
+        self.group = None
+        rlog(-10, 'periodical', 'scheduled next run of %s in %d seconds' % \
+(str(self.func), self.interval))
+
+    def __repr__(self):
+        return '<JobInterval instance next=%s, interval=%s, repeat=%d, group=%s, \
+function=%s>' % (str(self.next), str(self.interval), self.repeat, self.group,
+str(self.func))
+
+    def check(self):
+
+        """
+            run check to see if job needs to be scheduled.
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+                :pyobject: JobInterval.check
+        """
+
+        if self.next <= time.time():
+            rlog(0, 'periodical', 'running %s - %s' % (str(self.func), self.description))
+            self.next = time.time() + self.interval
+            thr.start_new_thread(self.do, ())
+            self.counts += 1
+            if self.repeat > 0 and self.counts >= self.repeat:
+                return False # remove this job
+        return True
+
+
+class Periodical(object):
+
+    """
+         periodical scheduler.
+
+    """
+
+    SLEEPTIME = 1 # smallest interval possible
+
+    def __init__(self):
+        self.jobs = []
+        self.running = []
+        self.run = True
+
+    def start(self):
+
+        """
+            start the periodical scheduler.
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+                :pyobject: Periodical.start
+        """
+
+        thr.start_new_thread(self.checkloop, ())
+
+    def addjob(self, sleeptime, repeat, function, description="" , *args, **kw): 
+
+        """
+            add a periodical job.
+
+            :param sleeptime: sleeptime between intervals
+            :type sleeptime: float
+            :param repeat: number of times to repeat 
+            :type repeat: integer
+            :param function: function to execute
+            :type function: function
+            :param description: description of the periodical job
+            :type description: string
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+                 :pyobject: Periodical.addjob
+
+        """
+
+        job = JobInterval(sleeptime, repeat, function, *args, **kw)
+        job.group = calledfrom(sys._getframe())
+        job.description = str(description) or whichmodule()
+        self.jobs.append(job)
+        return job.pid
+
+    def changeinterval(self, pid, interval):
+ 
+        """
+             change interval of of peridical job.
+
+             :param pid: id op the periodical job
+             :type pid: integer
+             :param interval: interval to set 
+             :type interval: integer
+
+             .. literalinclude:: ../../gozerbot/periodical.py
+                 :pyobject: Periodical.changeinterval
+        """
+
+        for i in periodical.jobs:
+
+            if i.pid == pid:
+                i.interval = interval
+                i.next = time.time() + interval
+
+    def looponce(self):
+        for job in self.jobs:
+
+            if job.next <= time.time():
+                self.runjob(job)
+
+    def checkloop(self):
+
+        """
+            main loop of the periodical scheduler.
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+                :pyobject: Periodical.checkloop
+
+        """
+
+        while self.run:
+
+            for job in self.jobs:
+
+                if job.next <= time.time():
+                    self.runjob(job)
+
+            time.sleep(self.SLEEPTIME)
+
+    def runjob(self, job):
+
+        """
+            run a periodical job
+
+            :param job: the job to be runned 
+            :type job: Job
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+                :pyobject: Periodical.runjob
+
+        """
+
+        if not job.check():
+            self.killjob(job.id())
+        else:
+            self.running.append(job)
+
+    def kill(self):
+
+        '''
+            kill all jobs invoked by another module.
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+               :pyobject: Periodical.kill
+        '''
+
+        group = calledfrom(sys._getframe())
+        self.killgroup(group)
+
+    def killgroup(self, group):
+
+        '''
+            kill all jobs with the same group.
+
+            :param group: the group of jobs to kill
+            :type group: string
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+                :pyobject: Periodical.killgroup
+
+        '''
+
+        def shoot():
+
+            """ knock down all jobs belonging to group. """
+
+            deljobs = [job for job in self.jobs if job.member(group)]
+
+            for job in deljobs:
+                self.jobs.remove(job)
+
+                try:
+                    self.running.remove(job)
+                except ValueError:
+                    pass
+
+            rlog(5, 'periodical', 'killed %d jobs for %s' % (len(deljobs), \
+group))
+
+            del deljobs
+
+        shoot() # *pow* you're dead ;)
+
+    def killjob(self, jobId):
+
+        '''
+            kill one job by its id.
+
+            :param jobId: the id of the job to kill
+            :type jobId: integer
+            :rtype: integer .. number of jobs killed
+
+            .. literalinclude:: ../../gozerbot/periodical.py
+                :pyobject: Periodical.killjob
+        '''
+
+        def shoot():
+            deljobs = [x for x in self.jobs if x.id() == jobId]
+            numjobs = len(deljobs)
+            for job in deljobs:
+                self.jobs.remove(job)
+                try:
+                    self.running.remove(job)
+                except ValueError:
+                    pass
+            del deljobs
+            return numjobs
+
+        return shoot() # *pow* you're dead ;)
+
+
+def interval(sleeptime, repeat=0):
+
+    """
+        interval decorator.
+
+        :param sleeptime: time to sleep
+        :type sleeptime: integer
+        :param repeat: number of times to repeat the job
+        :type repeat: integet
+
+        .. literalinclude:: ../../gozerbot/periodical.py
+            :pyobject: interval
+
+    """
+
+    group = calledfrom(sys._getframe())
+
+    def decorator(function):
+        decorator.func_dict = function.func_dict
+
+        def wrapper(*args, **kw):
+            job = JobInterval(sleeptime, repeat, function, *args, **kw)
+            job.group = group
+            job.description = whichmodule()
+            periodical.jobs.append(job)
+            rlog(-15, 'periodical', 'new interval job %d with sleeptime %d' % \
+(job.id(), sleeptime))
+
+        return wrapper
+
+    return decorator
+
+def at(start, interval=1, repeat=1):
+
+    """
+        at decorator.
+
+        :param start: start time of the periodical job
+        :type start: integer, float or string
+        :param interval: time between jobs
+        :type sleeptime: integer
+        :param repeat: number of times to repeat the job
+        :type repeat: integet
+
+        .. literalinclude:: ../../gozerbot/periodical.py
+            :pyobject: at
+
+    """
+
+    group = calledfrom(sys._getframe())
+
+    def decorator(function):
+        decorator.func_dict = function.func_dict
+
+        def wrapper(*args, **kw):
+            job = JobAt(start, interval, repeat, function, *args, **kw)
+            job.group = group
+            job.description = whichmodule()
+            periodical.jobs.append(job)
+
+        wrapper.func_dict = function.func_dict
+        return wrapper
+
+    return decorator
+
+def persecond(function):
+
+    """
+        per second decorator.
+
+        :param function: function to execute every second
+        :type function: function
+
+        .. literalinclude:: ../../gozerbot/periodical.py
+            :pyobject: persecond
+    """
+
+    minutely.func_dict = function.func_dict
+    group = calledfrom(sys._getframe())
+
+    def wrapper(*args, **kw):
+        job = JobInterval(1, 0, function, *args, **kw)
+        job.group = group
+        job.description = whichmodule()
+        periodical.jobs.append(job)
+        rlog(-15, 'periodical', 'new interval job %d running per second' % \
+job.id())
+
+    return wrapper
+
+def minutely(function):
+
+    """
+        minute decorator.
+
+        :param function: function to execute every minute
+        :type function: function
+
+        .. literalinclude:: ../../gozerbot/periodical.py
+            :pyobject: minutely
+
+    """
+
+    minutely.func_dict = function.func_dict
+    group = calledfrom(sys._getframe())
+
+    def wrapper(*args, **kw):
+        job = JobInterval(60, 0, function, *args, **kw)
+        job.group = group
+        job.description = whichmodule()
+        periodical.jobs.append(job)
+        rlog(-15, 'periodical', 'new interval job %d running minutely' % \
+job.id())
+
+    return wrapper
+
+def hourly(function):
+
+    """
+        hour decorator.
+
+        :param function: function to execute every hour
+        :type function: function
+
+        .. literalinclude:: ../../gozerbot/periodical.py
+            :pyobject: hourly
+
+    """
+
+    rlog(-15, 'periodical', '@hourly(%s)' % str(function))
+    hourly.func_dict = function.func_dict
+    group = calledfrom(sys._getframe())
+
+    def wrapper(*args, **kw):
+        job = JobInterval(3600, 0, function, *args, **kw)
+        job.group = group
+        job.description = whichmodule()
+        rlog(-15, 'periodical', 'new interval job %d running hourly' % job.id())
+        periodical.jobs.append(job)
+
+    return wrapper
+
+def daily(function):
+
+    """
+        day decorator.
+
+        :param function: function to execute every hour
+        :type function: function
+
+        .. literalinclude:: ../../gozerbot/periodical.py
+            :pyobject: daily
+
+    """
+
+    rlog(-15, 'periodical', '@daily(%s)' % str(function))
+    daily.func_dict = function.func_dict
+    group = calledfrom(sys._getframe())
+
+    def wrapper(*args, **kw):
+        job = JobInterval(86400, 0, function, *args, **kw)
+        job.group =  group
+        job.description = whichmodule()
+        periodical.jobs.append(job)
+        rlog(-15, 'periodical', 'new interval job %d running daily' % job.id())
+
+    return wrapper
+
+## INIT SECTION
+
+# the periodical scheduler
+periodical = Periodical()
+
+## END INIT
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/exit.py
+++ gozerbot-0.99.1/build/lib/gozerbot/exit.py
@@ -0,0 +1,73 @@
+# gozerbot/exit.py
+#
+#
+
+""" gozerbot's finaliser """
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+
+# gozerbot imports
+from utils.log import rlog
+from utils.trace import whichmodule
+from eventhandler import mainhandler
+from plugins import plugins
+from fleet import fleet
+from persist.persist import saving
+from runner import runners_stop
+from gozerbot.config import config
+
+# basic imports
+import atexit, os, time, sys
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
+
+def globalshutdown():
+
+    """
+        shutdown the bot.
+
+        .. literalinclude:: ../../gozerbot/exit.py
+            :pyobject: globalshutdown
+
+    """
+
+    rlog(10, 'GOZERBOT', 'SHUTTING DOWN')
+
+    try:
+        os.remove('gozerbot.pid')
+    except:
+        pass
+
+    try:
+        runners_stop()
+        rlog(10, 'gozerbot', 'shutting down fleet')
+        fleet.exit()
+        rlog(10, 'gozerbot', 'shutting down plugins')
+        plugins.exit()
+        rlog(10, 'GOZERBOT', 'done')
+        os._exit(0)
+
+    except Exception, ex:
+        rlog(10, 'gozerbot.exit', 'exit error %s:' % str(ex))
+
+# ============
+# INIT SECTION
+
+# register shutdown function
+#atexit.register(globalshutdown)
+
+# END INIT
+# ========
--- gozerbot-0.99.1.orig/build/lib/gozerbot/channels.py
+++ gozerbot-0.99.1/build/lib/gozerbot/channels.py
@@ -0,0 +1,136 @@
+# gozerbot/channels.py
+#
+#
+
+""" 
+    channel related data. implemented with a persisted dict of dicts. 
+    
+    :example:
+
+        key = channels[event.channel]['key']
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# IMPORT SECTION
+# gozerbot imports
+from persist.pdod import Pdod
+
+# END IMPORT
+
+class Channels(Pdod):
+
+    """ 
+        channels class .. per channel data. 
+
+        :param fname: filename to persist the data to
+        :type fname: string
+
+    """
+
+    def __init__(self, fname):
+
+        # call base constructor
+        Pdod.__init__(self, fname)
+
+        # make sure attributes are initialised
+        for j in self.data.values():
+            if not j.has_key('perms'):
+                j['perms'] = []
+            if not j.has_key('autovoice'):
+                j['autovoice'] = 0
+
+    def __setitem__(self, a, b):
+
+        # if item is not in dict initialise it to empty dict
+        if not self.data.has_key(a):
+           self.data[a] = {}
+
+        # assign data
+        self.data[a] = b 
+
+    def getchannels(self):
+
+        """
+            return channels.
+
+        .. literalinclude:: ../../gozerbot/channels.py
+            :pyobject: Channels.getchannels
+
+        """
+
+        result = [] # list of channels found
+
+        # loop over channels
+        for channel in self.data.keys():
+            channel = channel.strip()
+            if channel not in result:
+                result.append(channel)
+
+        return result
+
+    def getchannelswithkeys(self):
+
+        """
+            return channels with keys.
+
+            .. literalinclude:: ../../gozerbot/channels.py
+                :pyobject: Channels.getchannelswithkeys
+
+        """
+
+        result = []
+
+        # loop over channels gathering channel name and key
+        for channel in self.data.keys():
+            channel = channel.strip()
+            try:
+                key = self.data[channel]['key']
+                if not channel + ' ' + key in result:
+                    result.append(channel + ' ' + key)
+            except KeyError:
+                if channel not in result:
+                    result.append(channel)
+
+        return result
+
+    def getkey(self, channel):
+
+        """ 
+            return key of channel if set.
+
+            :param channel: channel to get key from
+            :type channel: string
+
+            .. literalinclude:: ../../gozerbot/channels.py
+                :pyobject: Channels.getkey
+
+        """
+
+        try:
+            key = self.data[channel]['key']
+        except:
+            key = None
+
+        return key
+
+    def getnick(self, channel):
+
+        """ 
+            return bot nick of channel if set.
+
+            :param channel: channel to get key from
+            :type channel: string
+
+            .. literalinclude:: ../../gozerbot/channels.py
+                :pyobject: Channels.getnick
+
+        """
+
+        try:
+            nick = self.data[channel]['nick']
+        except:
+            nick = None
+
+        return nick
--- gozerbot-0.99.1.orig/build/lib/gozerbot/tests.py
+++ gozerbot-0.99.1/build/lib/gozerbot/tests.py
@@ -0,0 +1,326 @@
+# gozerbot/tests.py
+#
+#
+
+""" gozerbot tests framework. """
+
+## IMPORT SECTION
+
+# gozerbot imports
+from config import config
+from threads.thr import start_new_thread
+from utils.locking import lockdec
+from utils.log import rlog
+from utils.trace import calledfrom, whichmodule
+from utils.exception import exceptionmsg
+from utils.dol import Dol
+from eventbase import EventBase
+
+# basic imports
+import sys, re, thread, copy, time, threading, random
+
+## END IMPORT
+
+# used to copy attributes
+cpy = copy.deepcopy
+
+## LOCK SECTION
+
+# locks
+testlock = threading.RLock()
+locked = lockdec(testlock)
+
+## END LOCK
+
+class Test(object):
+
+    """ a test object. """
+
+    def __init__(self, execstring="", expect="", descr="", where="", fakein=""):
+        self.plugin = calledfrom(sys._getframe(1))
+        if not self.plugin:
+            self.plugin = calledfrom(sys._getframe(2))
+        self.descr = cpy(descr)
+        self.execstring = cpy(execstring)
+        self.expect = cpy(expect)
+        self.response = ""
+        self.groups = []
+        self.error = ""
+        self.where = cpy(where)
+        self.fakein = cpy(fakein)
+        self.start = None
+        self.end = None
+        self.prev = None
+        self.activate = False
+        
+    def __str__(self):
+        return "test %s (%s) %s ==> %s (%s)" % (self.descr, self.where, self.execstring, self.response, self.error)
+
+    def begin(self):
+        if self.start:
+            self.start()
+        return self
+
+    def run(self, bot, event):
+
+        """ run the test on bot with event. """
+
+        if not self.activate:
+            return
+
+        if config['loadlist'] and self.plugin not in config['loadlist']:
+            return
+
+        mevent = copy.deepcopy(event)
+        mevent.onlyqueues = False
+        mevent.channel = '#dunkbots'
+        bot.userhosts['bottest'] = 'bottest@test'
+        bot.userhosts['mtest'] = 'mekker@test'
+        bot.userhosts['exec'] = 'exec@gozerbot'
+        if 'exec@gozerbot' not in bot.state['joinedchannels']:
+            bot.state['joinedchannels'].append('exec@gozerbot')
+        bot.channels['exec@gozerbot'] = {}
+        if '#dunkbots' not in bot.state['joinedchannels']:
+            bot.state['joinedchannels'].append('#dunkbots')
+        bot.channels['#dunkbots'] = {'cc': '!'}
+        self.error = ""
+        self.response = ""
+        self.groups = []
+        origexec = self.execstring
+        origexpect = self.expect
+        if self.start:
+            self.start()
+            return self
+        if self.end:
+            self.end()
+            return self
+        if self.fakein:
+            bot.fakein(self.fakein)
+            return self
+        if self.prev and self.prev.groups:
+            try:
+                execstring = self.execstring % self.prev.groups
+                self.execstring = execstring
+            except TypeError:
+                pass
+            try:
+                expect = self.expect % self.prev.groups
+                self.expect = expect
+            except TypeError:
+                pass
+
+        self.execstring = self.execstring.replace('{{ me }}', mevent.nick)
+        mevent.txt = mevent.origtxt = str(self.execstring)
+        rlog(100, 'tests', 'launching %s' % mevent.txt)
+
+        from gozerbot.plugins import plugins
+        self.response = plugins.cmnd(bot, mevent)
+
+        if self.response and self.expect:
+            self.expect = self.expect.replace('{{ me }}', mevent.nick)
+            expects = self.expect.split('|')
+            got = False
+            for expect in expects:
+                regex = re.compile(expect)
+                result = regex.search(str(self.response))
+
+                if result:
+                    got = True
+                    break
+
+            if not got:
+                self.error = 'invalid response'
+            else:
+                self.groups = result.groups() 
+
+        self.execstring = origexec
+        self.expect = origexpect
+
+        return self
+
+class Tests(object):
+
+    """ collection of all tests. """
+
+    def __init__(self):
+        self.tests = []
+        self.err = Dol()
+        self.toolate = Dol()
+        self.teller = 0
+
+    #@locked
+    def add(self, execstr, expect=None, descr="", fakein=""):
+
+        """ add a test. """
+
+        where = whichmodule(1)
+        if not where:
+            where = whichmodule(2)
+        if not where:
+            where = whichmodule(3)
+        test = Test(execstr, expect, descr, where, fakein)
+        self.tests.append(test)
+        return self
+
+    #@locked
+    def fakein(self, execstr, expect=None, descr=""):
+
+        """ call bot.fakein(). """ 
+
+        where = whichmodule(1)
+        if not where:
+            where = whichmodule(2)
+        if not where:
+            where = whichmodule(3)
+        test = Test(execstr, expect, descr, where, execstr)
+        test.where = where
+        self.tests.append(test)
+        return self
+
+    def start(self, func):
+
+        """ optional start function. """
+        where = whichmodule(1)
+        if not where:
+            where = whichmodule(2)
+        if not where:
+            where = whichmodule(3)
+        test = Test()
+        test.start = func
+        test.where = where
+        test.execstring = 'start'
+        self.tests.append(test)
+        return self
+
+    def end(self, func):
+
+        """ optional end function. """
+
+        where = whichmodule(1)
+        if not where:
+            where = whichmodule(2)
+        if not where:
+            where = whichmodule(3)
+        test = Test()
+        test.end = func
+        test.where = where
+        test.execstring = 'end'
+        self.tests.append(test)
+        return self
+
+    def unload(self, plugname):
+
+        """ unload tests. """
+
+        for i in range(len(self.tests)-1, -1, -1):
+            if self.tests[i].plugin == plugname:
+                del self.tests[i]
+
+        return self
+
+    def activate(self, plugname):
+
+        """ activate tests. """
+
+        for i in range(len(self.tests)-1, -1, -1):
+            if self.tests[i].plugin == plugname:
+                self.tests[i].activate = True
+
+        return self
+
+    def disable(self, plugname):
+
+        """ unload tests. """
+
+        for i in range(len(self.tests)-1, -1, -1):
+            if self.tests[i].plugin == plugname:
+                self.tests[i].activate = False
+
+        return self
+
+    def dorun(self, bot, event, tests, where, plug=None):
+        teller = 0
+        err = {}
+        toolate = []
+        prev = None
+        for test in tests:
+            if event.rest and event.rest not in test.plugin:
+                continue
+            if prev: 
+                test.prev = prev
+            prev = test
+            if test.expect:
+                teller += 1
+            try:
+                starttime = time.time()
+                self.teller += 1
+                e = copy.deepcopy(event)
+                result = test.run(bot, e)
+                finished = time.time()
+                if finished - starttime > 10:
+                    self.toolate[test.execstring] = finished - starttime
+                if not result:
+                    continue
+                if not result.error:
+                    event.reply("OK %s (%s) ==> %s" % (test.execstring, test.where, result.response))
+                else:
+                    self.err[test.execstring] = test
+                    event.reply('ERROR %s (%s): %s ==> %s (%s)' % (test.error, test.where, test.execstring, test.response, test.expect))
+            except Exception, ex:
+                test.error = exceptionmsg()
+                self.err[test.execstring] = test
+                event.reply(test.error)
+
+        return self
+
+    def dotests(self, bot, event, threaded=False, plug=None):
+
+        """ fire all tests. """
+
+        #event = EventBase(eventin)
+        groups = Dol()
+
+        for test in self.tests:
+            groups.add(test.where, test)
+
+        threads = []
+        testlist = []
+
+        for where, tests in groups.iteritems():
+            testlist.append((where, tests))
+
+        random.shuffle(testlist)
+
+        for t in testlist:
+            where, tests = t
+            if plug and plug not in where:
+                continue
+            event.reply("running tests on %s" % where)
+            if threaded:
+                thread = start_new_thread(self.dorun, (bot, event, tests, where, plug))
+                threads.append(thread)
+            else:
+                self.dorun(bot, event, tests, where, plug)
+
+        for thread in threads:
+            thread.join()
+
+        event.done()
+
+    def sleep(self, seconds):
+
+        """ sleep nr of seconds. """
+
+        time.sleep(seconds)
+        return self
+
+## INIT SECTION
+
+# expect is for examples
+
+expect = {}
+
+# the tests
+tests = Tests()
+
+## END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/__init__.py
+++ gozerbot-0.99.1/build/lib/gozerbot/__init__.py
@@ -0,0 +1,45 @@
+# gozerbot package
+#
+#
+
+""" gozerbot core package. """
+
+__copyright__ = 'this file is in the public domain'
+
+__version__ = "0.99.1"
+
+__all__ = ['aliases', 'database', 'ignore', 'periodical', 'tests', \
+'botbase',  'datadir', 'persist', 'threads', \
+'cache', 'eventbase', 'irc', 'plughelp', 'users', \
+'callbacks', 'eventhandler', 'jabber', 'plugins', 'utils', \
+'channels', 'examples', 'jsonusers', 'plugs', 'wait', \
+'commands', 'exit', 'less', 'redispatcher', \
+'compat', 'fleet', 'monitor', 'rest', \
+'config', 'morphs', 'runner', 'wave', \
+'contrib', 'gozerimport', 'partyline', 'stats', 'utils', 'reboot', 'xmpp']
+
+# ==============
+# IMPORT SECTION
+
+from eggs import loadegg
+import os
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
+
+# ============
+# INIT SECTION
+
+loadegg('simplejson', [os.getcwd(), os.getcwd() + os.sep + 'gozernest'], log=False)
+loadegg('sqlalchemy', [os.getcwd(), os.getcwd() + os.sep + 'gozernest'], log=False)
+
+# END INIT
+# ========
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/commands.py
+++ gozerbot-0.99.1/build/lib/gozerbot/commands.py
@@ -0,0 +1,579 @@
+# gozerbot/commands.py
+#
+#
+
+""" 
+
+    This module implements the commands a user can give. It also contains 
+    the global cmnds object to which all commands are added.
+
+    example:
+
+    ::
+
+        cmnds.add('hello', handle_hello, 'USER')
+
+
+    :var cmnds: global commands object
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# IMPORT SECTION
+
+# gozerbot imports
+from gozerbot.stats import stats
+from utils.generic import makeoptions
+from eventbase import defaultevent
+from config import config
+from utils.log import rlog
+from utils.trace import calledfrom
+from utils.exception import handle_exception
+from utils.locking import lockdec
+from runner import cmndrunners
+from threads.thr import start_new_thread, start_bot_command
+
+# basic imports
+import sys, re, copy, types, thread
+
+# END IMPORT
+
+# LOCK SECTION
+
+# command lock
+commandlock = thread.allocate_lock()
+locked = lockdec(commandlock)
+
+# END LOCK
+
+class Command(object):
+
+    """
+        implements a command.
+
+        :param func: the function to call when a commands gets triggered
+        :param perm: permissions the command needs before it gets fired
+        :type perm: list
+        :param plugname: the plugin this commands is implemented in
+        :type param: string
+        :param threaded: determines if the command has to be run in its own thread
+        :type threaded: False or True
+        :param allowqueue: determines if output of this commands can be used in pipeline queues
+        :type threaded: False or True
+        :param options: options for this command
+        :type options: dict
+
+    """
+
+    def __init__(self, func, perm, plugname, speed=5, threaded=False, allowqueue=True, options={'--speed': 5, '--chan': '', '--filter': '', '--how': 'msg'}):
+
+        self.name = str(func) # function name is string representation of the function 
+        self.func = func # function to call
+
+        # make sure permission(s) are stored in a list
+        if type(perm) == types.ListType:
+            self.perms = list(perm)
+        else:
+            self.perms = [perm, ]
+
+        self.plugname = plugname # plugin name
+        self.speed = copy.deepcopy(speed) # speed to execute command with
+        self.threaded = copy.deepcopy(threaded) # set if threaded exec is required
+        self.allowqueue = copy.deepcopy(allowqueue) # set if command is allowed to be used in pipeline
+        self.options = dict(options) # options set in the command 
+        self.activate = True
+        
+class Commands(dict):
+
+    """ 
+        the commands object is a dict containing the commands. dict key is the 
+        command (1 word).
+
+    """
+
+    def __setitem__(self, name, value):
+
+        """ set command. """
+
+        dict.__setitem__(self, name, value)
+
+    def __delitem__(self, name):
+
+        """ delete command. """
+
+        dict.__delitem__(self, name)
+
+    def size(self):
+
+        """
+            nr of commands.
+
+            :rtype: integer
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.size
+
+        """
+
+        return len(self)
+
+    def activate(self, plugname):
+        cp = dict(self)
+        for i in cp.values():
+            if i.plugname == plugname:
+                i.activate = True
+
+    def disable(self, plugname):
+        cp = dict(self)
+        for i in cp.values():
+            if i.plugname == plugname:
+                i.activate = False
+
+    def whatperms(self):
+
+        """
+            return all possible permissions.
+
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.whatperms
+
+        """
+
+        result = []
+
+        # loop over the commands and collect all possible permissions
+        cp = dict(self)
+        for i in cp.values():
+            for j in i.perms:
+                if j not in result:
+                    result.append(j)
+
+        return result
+
+    def list(self, perm):
+
+        """
+            list commands with permission perm.
+
+            :param perm: the permission
+            :type perm: string
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.list
+
+        """
+
+        result = []
+
+        # make sure perm is a list
+        if type(perm) != types.ListType:
+            perm = perm.upper()
+            perms = [perm, ]
+        else:
+            perms = perm
+
+        # loop over commands collecting all command having permission
+        cp = dict(self)
+        for name, cmnd in cp.items():
+            for i in perms:
+                if i in cmnd.perms:
+                    result.append(name)
+
+        return result
+
+    def getfuncnames(self, plug):
+
+        """
+            get all function names of commands in a plugin.
+
+            :param plug: plugname
+            :type plug: string
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.getfuncnames
+
+        """
+
+        result = []
+
+        # collect function names 
+        cp = dict(self)
+        for i in cp.values():
+            if i.plugname == plug:
+                result.append(i.func.func_name)
+
+        return result
+
+    def getoptions(self, command):
+
+        """
+            get options of a command.
+
+            :param command: the command to get options for
+            :type command: string
+            :rtype: dict
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.getoptions
+
+        """
+
+        cp = dict(self)
+        for name, cmnd in cp.iteritems():
+            if name == command:
+                return makeoptions(defaultevent, cmnd.options)
+
+    #@locked
+    def permoverload(self, name, perms):
+
+        """
+             overload permission of function with funcname.
+
+            :param name: name of command to overload permission of
+            :type name: string
+            :param perms: permission to overload the command with
+            :type perms: list
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.permoverload
+
+        """
+
+        # make sure all perms are uppercase
+        perms = [perm.upper() for perm in perms]
+
+        # overload the command with given permissions
+        for com in self.values():
+            try:
+                if com.func.func_name == name:
+                    com.perms = perms
+                    return True
+            except AttributeError:
+                rlog(10, 'commands', "permoverload: no %s function" % name)
+        return False
+
+    #@locked
+    def add(self, cmnd, func, perm, speed=5, threaded=False, allowqueue=True, options={'--speed': 5, '--chan': '', '--filter': '', '--how': 'msg'}):
+	
+        """
+            add a command.
+
+            :param func: the function to call when a commands gets triggered
+            :param perm: permissions the command needs before it gets fired
+            :type perm: list
+            :param plugname: the plugin this commands is implemented in
+            :type param: string
+            :param threaded: determines if the command has to be run in its own thread
+            :type threaded: boolean
+            :param allowqueue: determines if output of this commands can be used in pipeline queues
+            :type threaded: boolean
+            :param options: options for this command
+            :type options: dict
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.add
+
+        """
+
+        # plugin where the command is added
+        plugname = calledfrom(sys._getframe(0))
+        # check if plugin is in loadlist .. if not dont register command. 
+        if config['loadlist'] and plugname not in config['loadlist']:
+            rlog(1, 'commands', 'NOT LOADING %s' % plugname)
+            return
+
+        rlog(-3, 'commands', 'added %s (%s) ' % (cmnd, plugname))
+
+        # add command
+        self[cmnd.lower()] = Command(func, perm, plugname, speed, threaded, allowqueue, options)
+        self[cmnd.lower()].name = cmnd.lower()
+
+    def apropos(self, txt, perms=[]):
+
+        """
+            search for command.
+
+            :param txt: txt to search commands with
+            :type txt: string
+            :param perms: contain permission that must match first
+            :type perms: list
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.apropos
+
+        """
+
+        result = []
+
+        #  loop over commands collecting all commands that contain given txt
+        cp = dict(self)
+        for name, cmnd in cp.iteritems():
+            if perms:
+                go = False
+                for i in perms:
+                    if i in cmnd.perms:
+                        go = True
+                if not go:
+                    continue                
+            if re.search(txt, name):
+                result.append(name)
+
+        return result
+
+    #@locked
+    def unload(self, plugname):
+
+        """
+            unload plugin commands.
+
+            :param plugname: name of the plugin that needs to be unloaded
+            :type plugname: string
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.unload
+
+        """
+
+        results = []
+
+        # look for the commands registerd in plugin
+        for name, cmnd in self.iteritems():
+            if cmnd.plugname == plugname:
+                results.append(name)
+
+        got = False
+
+        # remove commands
+        for name in results:
+
+            try:
+                del self[name]
+                rlog(-3, 'commands', 'unloaded %s (%s)' % (name, plugname))
+                got = True
+            except KeyError:
+                got = False
+
+        if got:
+            return True
+
+    def whereis(self, command):
+
+        """
+            locate plugin a command is registered in.
+
+            :param command: command to search for
+            :type command: string
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.whereis
+
+        """
+
+        result = []
+
+        # find plugin 
+        cp = dict(self)
+        for name, cmnd in cp.iteritems():
+            if name == command:
+                if not cmnd.plugname in result:
+                    result.append(cmnd.plugname)
+
+        return result
+
+    def perms(self, name):
+
+        """
+            get permission of command.
+
+            :param command: command to lookup permissions for
+            :type command: string
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.perms
+
+        """
+
+        name = name.lower()
+
+        if self.has_key(name):
+            return self[name].perms
+        else:
+            return []
+
+    #@locked
+    def setperm(self, command, perm):
+
+        """
+            set permission of command.
+
+            :param command: command to set permission of
+            :type command: string
+            :param perm: the permission
+            :type perm: string
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.setperm
+
+        """
+
+        command = command.lower()
+        perm = perm.upper()
+
+        if self.has_key(command):
+            if perm not in self[command].perms:
+                self[command].perms.append(perm)
+            return True
+        return False
+
+    def getcommand(self, txt):
+
+        """
+            return command matching txt.
+
+            :param txt: txt to match commands against
+            :type txt: string
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.getcommand
+
+        """
+
+        textlist = txt.split()
+
+        if not textlist:
+            return None
+
+        cmnd = textlist[0].lower()
+        if self.has_key(cmnd):
+            com = self[cmnd] # the command
+            return com
+        else:
+            return None
+
+    def options(self, command):
+
+        """
+            return options dict of command.
+
+            :param command: the command to get the options of
+            :type command: string
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.options
+
+        """
+
+        try:
+            return self[command].options
+        except KeyError:
+            return 
+
+    def getcommands(self, plugin):
+
+        """
+            get all commands of a plugin.
+
+            :param plugin: the plugin to get commands of
+            :type plugin: string
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.getcommands
+
+        """
+
+        result = []
+        
+        tmp = dict(self)
+        for name, cmnd in tmp.iteritems():
+            if cmnd.plugname == plugin:
+                result.append(name)
+
+        return result
+
+    #@locked
+    def dispatch(self, com, txt, wait=False):
+
+        """
+            dispatch command.
+
+            :param com: the command object to dispatch with
+            :type com: Command
+
+            .. literalinclude:: ../../gozerbot/commands.py
+                :pyobject: Commands.dispatch
+
+        """
+
+        if com.threaded:
+            thread = start_new_thread(com.func, (txt, ))
+            if wait:
+                thread.join()
+        else:
+            cmndrunners[10-com.speed].put(com.name, com.func, txt)
+        return 1
+
+class Botcommands(Commands):
+
+    """
+        commands for the bot .. dispatch with (bot, ircevent) as arguments. 
+
+    """
+
+    #@locked
+    def dispatch(self, com, bot, ievent, wait=False):
+
+        """
+             dispatch on event passing bot and ievent as arguments.
+
+             :param com: the command object
+             :type com: gozerbot.commands.Command
+             :param bot: the bot this command is given on
+             :type bot: gozerbot.botbase.BotBase
+             :param ievent: the event triggering this command
+             :rtype: boolean
+
+             .. literalinclude:: ../../gozerbot/commands.py
+                 :pyobject: Botcommands.dispatch
+
+        """
+
+        if not com.activate:
+            return False
+
+        if bot.stopped:
+            return False
+
+        #ievent = copy.deepcopy(ieventin)
+
+        # stats
+        stats.up('cmnds', com.name)
+        stats.up('cmnds', com.plugname)
+        stats.up('cmnds', 'speed%s' % com.speed)
+
+        # execute command
+        if com.threaded or ievent.threaded:
+            thread = start_bot_command(com.func, (bot, ievent))
+            #if thread and wait:
+            #    thread.join()
+        else:	
+            speed = ievent.speed or com.speed
+            ievent.speed = speed
+            cmndrunners.put(com.name, com.func, bot, ievent)
+        return True
+
+# INIT SECTION
+
+cmnds = Botcommands()
+
+# END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/eventhandler.py
+++ gozerbot-0.99.1/build/lib/gozerbot/eventhandler.py
@@ -0,0 +1,192 @@
+# gozerbot/eventhandler.py
+#
+#
+
+"""
+    event handler. use to dispatch function in main loop.
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+# gozerbot imports
+from utils.exception import handle_exception
+from utils.log import rlog
+from utils.locking import lockdec
+from threads.thr import start_new_thread
+
+# basic imports
+import Queue, thread
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# locks
+handlerlock = thread.allocate_lock()
+locked = lockdec(handlerlock)
+
+# END LOCK
+# ========
+
+## START
+
+class Eventhandler(object):
+
+    """
+        events are handled in 11 queues with different priorities:
+        queue0 is tried first queue10 last.
+
+    """
+
+    def __init__(self):
+        self.sortedlist = []
+        self.queues = {}
+
+        for i in range(11):
+            self.queues[i] = Queue.Queue()
+            self.sortedlist.append(i)
+
+        self.sortedlist.sort()
+        self.go = Queue.Queue()
+        self.stopped = False
+        self.running = False
+        self.nooutput = False
+
+    def start(self):
+
+        """
+            start the eventhandler thread.
+
+            .. literalinclude:: ../../gozerbot/eventhandler.py
+                   :pyobject: Eventhandler.start
+
+        """
+
+        self.stopped = False
+
+        if not self.running:
+            start_new_thread(self.handleloop, ())
+            self.running = True
+
+    def stop(self):
+
+        """
+            stop the eventhandler thread.
+
+            .. literalinclude:: ../../gozerbot/eventhandler.py
+                :pyobject: Eventhandler.stop
+
+        """
+
+        self.running = False
+        self.stopped = True
+        self.go.put('Yihaaa')
+
+    def put(self, speed, func, *args, **kwargs):
+
+        """
+             put item on the queue.
+
+             .. literalinclude:: ../../gozerbot/eventhandler.py
+                 :pyobject: Eventhandler.put
+
+        """
+
+        self.queues[10-speed].put_nowait((func, args, kwargs))
+        self.go.put('go')
+
+    def getready(self):
+
+        """
+            check queues from available functions to execute.
+
+            .. literalinclude:: ../../gozerbot/eventhandler.py
+                :pyobject: Eventhandler.getready
+
+        """
+
+        ready = []
+
+        for i in self.sortedlist:
+            if self.queues[i].qsize():
+                ready.append(i)
+                break
+
+        return ready
+
+    def handle_one(self):
+
+        """
+            do 1 loop over ready queues.
+
+            .. literalinclude:: ../../gozerbot/eventhandler.py
+                :pyobject: Eventhandler.handle_one
+
+        """
+
+        ready = self.getready()
+
+        for i in ready:
+            self.dispatch(self.queues[i])
+
+    def handleloop(self):
+
+        """
+            thread that polls the queues for items to dispatch.
+
+            .. literalinclude:: ../../gozerbot/eventhandler.py
+                :pyobject: Eventhandler.handleloop
+
+        """
+
+        rlog(0, 'eventhandler', 'starting handle thread')
+
+        while not self.stopped:
+            self.go.get()
+            self.handle_one()
+
+        rlog(0, 'eventhandler', 'stopping %s' % str(self))
+
+    def dispatch(self, queue):
+
+        """
+            dispatch functions from provided queue.
+
+            .. literalinclude:: ../../gozerbot/eventhandler.py
+                :pyobject: Eventhandler.dispatch
+
+        """
+
+
+        try:
+            todo = queue.get_nowait()
+        except Queue.Empty:
+            return
+
+        try:
+            (func, args, kwargs) = todo
+            func(*args, **kwargs)
+        except ValueError:
+            try:
+                (func, args) = todo
+                func(*args)
+            except ValueError:
+                (func, ) = todo
+                func()
+
+        except:
+            handle_exception()
+
+# INIT SECTION
+
+# handler to use in main prog
+mainhandler = Eventhandler()
+
+
+# END INIT
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/runner.py
+++ gozerbot-0.99.1/build/lib/gozerbot/runner.py
@@ -0,0 +1,172 @@
+# jsb/runner.py
+#
+#
+
+""" threads management to run jobs. """
+
+## jsb imports
+
+from gozerbot.threads.thr import getname, start_new_thread, start_bot_command
+from gozerbot.utils.generic import handle_exception
+from gozerbot.utils.trace import callstack
+from gozerbot.threads.threadloop import RunnerLoop
+from gozerbot.utils.log import rlog
+
+## basic imports
+
+import Queue
+import time
+import thread
+import random
+import logging
+import sys
+
+## Runner class
+
+class Runner(RunnerLoop):
+
+    """
+        a runner is a thread with a queue on which jobs can be pushed. 
+        jobs scheduled should not take too long since only one job can 
+        be executed in a Runner at the same time.
+
+    """
+
+    def __init__(self, name="runner", doready=True):
+        RunnerLoop.__init__(self, name)
+        self.working = False
+        self.starttime = time.time()
+        self.elapsed = self.starttime
+        self.finished = time.time()
+        self.doready = doready
+
+    def handle(self, descr, func, *args, **kwargs):
+        """ schedule a job. """
+        self.working = True
+        name = getname(str(func))
+        try:
+            #rlockmanager.acquire(getname(str(func)))
+            name = getname(str(func))
+            self.name = name
+            rlog(10, "runner", 'running %s: %s' % (descr, name))
+            self.starttime = time.time()
+            func(*args, **kwargs)
+            self.finished = time.time()
+            self.elapsed = self.finished - self.starttime
+            #if self.elapsed > 3:
+                #logging.debug('ALERT %s %s job taking too long: %s seconds' % (descr, str(func), self.elapsed))
+        except Exception, ex: handle_exception()
+        #finally: rlockmanager.release()
+        self.working = False
+
+## BotEventRunner class
+
+class BotEventRunner(Runner):
+
+    def handle(self, descr, func, bot, ievent, *args, **kwargs):
+        """ schedule a bot command. """
+        try:
+            self.starttime = time.time()
+            #lockmanager.acquire(getname(str(func)))
+            name = getname(str(func))
+            self.name = name
+            self.working = True
+            rlog(10, "runner", "now running %s" % name)
+            func(bot, ievent, *args, **kwargs)
+
+            for queue in ievent.queues:
+                queue.put_nowait(None)
+
+            self.finished = time.time()
+            self.elapsed = self.finished - self.starttime
+            #if self.elapsed > 3:
+            #    logging.info('ALERT %s %s job taking too long: %s seconds' % (descr, str(func), self.elapsed))
+            #if ievent.iscommand: ievent.ready()
+            #if not ievent.type == "OUTPUT" and not ievent.dontclose: ievent.ready()
+            #time.sleep(0.001)
+        except Exception, ex:
+            handle_exception(ievent)
+        #finally: lockmanager.release(getname(str(func)))
+        self.working = False
+        self.name = "finished"
+
+## Runners class
+
+class Runners(object):
+
+    """ runners is a collection of runner objects. """
+
+    def __init__(self, max=100, runnertype=Runner, doready=True):
+        self.max = max
+        self.runners = []
+        self.runnertype = runnertype
+        self.doready = doready
+
+    def runnersizes(self):
+        """ return sizes of runner objects. """
+        result = []
+        for runner in self.runners: result.append("%s - %s" % (runner.queue.qsize(), runner.name))
+        return result
+
+    def stop(self):
+        """ stop runners. """
+        for runner in self.runners: runner.stop()
+
+    def start(self):
+        """ overload this if needed. """
+        pass
+ 
+    def put(self, *data):
+        """ put a job on a free runner. """
+        #logging.debug("size is %s" % len(self.runners))
+        for runner in self.runners:
+            if not runner.queue.qsize():
+                runner.put(*data)
+                return
+        runner = self.makenew()
+        runner.put(*data)
+         
+    def running(self):
+        """ return list of running jobs. """
+        result = []
+        for runner in self.runners:
+            if runner.queue.qsize(): result.append(runner.nowrunning)
+        return result
+
+    def makenew(self):
+        """ create a new runner. """
+        runner = None
+        for i in self.runners:
+            if not i.queue.qsize(): return i
+        if len(self.runners) < self.max:
+            runner = self.runnertype(self.doready)
+            runner.start()
+            self.runners.append(runner)
+        else: runner = random.choice(self.runners)
+        return runner
+
+    def cleanup(self):
+        """ clean up idle runners. """
+        #if not len(self.runners): logging.debug("nothing to clean")
+        for index in range(len(self.runners)-1, -1, -1):
+            runner = self.runners[index]
+            #logging.debug("cleanup %s" % runner.name)
+            if not runner.queue.qsize():
+                try: runner.stop() ; del self.runners[index]
+                except IndexError: pass
+                except: handle_exception()
+            #else: logging.info("now running: %s" % runner.nowrunning)
+
+## global runners
+
+cmndrunners = defaultrunner = longrunner = Runners(50, BotEventRunner)
+cbrunners = Runners(100, BotEventRunner, doready=False)
+waitrunners = Runners(10, Runner)
+
+## runners_start
+
+def runners_start():
+    pass
+
+def runners_stop():
+    pass
--- gozerbot-0.99.1.orig/build/lib/gozerbot/config.py
+++ gozerbot-0.99.1/build/lib/gozerbot/config.py
@@ -0,0 +1,425 @@
+# gozerbot/config.py
+#
+#
+
+""" 
+    gozerbot config module. 
+
+    :var config: default config object
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# IMPORT SECTION
+
+# gozerbot imports
+from utils.exception import handle_exception
+from utils.log import rlog
+
+# simplejson imports
+
+from simplejson import loads, dumps
+
+# basic imports
+import os, types, thread, logging
+
+# END IMPORT
+
+class Config(dict):
+
+    """ 
+        config class is a dict containing json strings. is writable to file 
+        and human editable.
+
+        :param ddir: datadir where the config file lives
+        :type ddir: string
+        :param filename: filename of the config file
+        :type filename: string
+        :param inittxt: initial txt to fill the config file with
+        :type inittxt: string
+      
+    """
+
+    def __init__(self, ddir=None, filename=None, inittxt=None, verbose=False, *args, **kw):
+        dict.__init__(self, *args, **kw)
+        self.jsondb = None
+        self.dir = ddir or 'gozerdata'
+        self.filename = filename or 'config'
+        self.cfile = self.dir + os.sep + self.filename
+
+        if hasattr(os, 'mkdir'):
+            #  check if provided dir exists if not create it
+            if not os.path.exists(self.dir):
+                os.mkdir(self.dir)
+
+            # see if initial data has to be written
+            if inittxt and not os.path.exists(self.cfile):
+                self.write_init(inittxt)
+
+        self.configlist = []
+        self.lock = thread.allocate_lock()
+        self.load(verbose)
+
+    def __getitem__(self, item):
+        if not self.has_key(item):
+            return None
+        else:
+            return dict.__getitem__(self, item)
+
+    def set(self, item, value):
+        dict.__setitem__(self, item, value)
+        self.save()
+
+    def load(self, verbose=False):
+
+        """
+            load the config file.
+
+            .. literalinclude:: ../../gozerbot/config.py
+                :pyobject: Config.load
+
+        """
+
+        self.reload()
+
+        if self.filename == 'mainconfig':
+            self.setdefault('owner', [])
+            self.setdefault('loglevel', 10)
+            self.setdefault('loglist',  [])
+            self.setdefault('resource', 'gozerbot')
+            self.setdefault('quitmsg', "http://gozerbot.googlecode.com")
+            self.setdefault('mask', "700")
+            self.setdefault('debug', 0)
+            self.setdefault('nodb', 0)
+            self.setdefault('plugdeny', [])
+            self.setdefault('db_driver', 'olddb')
+            self.setdefault('dbtype', "sqlite")
+            self.setdefault('dbname', "db/main.db")
+            self.setdefault('dbhost', "localhost")
+            self.setdefault('dbuser', "bart")
+            self.setdefault('dbpasswd', "mekker2")
+            self.setdefault('loadlist', ["alias", "all", "at", "chanperm", "choice", "code", "core", "count", "fleet", "googletalk", "grep", "ignore", "irc", "jabber", "job", "misc", "nickcapture", "nickserv", "not", "quote", "reload", "rest", "reverse", "size", "sort", "tail", "tell", "to", "underauth", "uniq", "user", "userstate", "stats", "throttle"])
+            self.setdefault('dotchars',  " .. ")
+            self.setdefault('floodallow', 1)
+            self.setdefault('auto_register', 0)
+
+        elif 'fleet' in self.dir:
+            self.setdefault("enable", 1)
+            self.setdefault("type", "irc")
+            self.setdefault("owner", [])
+            self.setdefault("user", "")
+            self.setdefault("nick", "gozerbot")
+            self.setdefault("server", "")
+            self.setdefault("host", "")
+            self.setdefault("password", "")
+            self.setdefault("port", 0)
+            self.setdefault("ssl", 0)
+            self.setdefault("ipv6", 0)
+            self.setdefault("username", "gozerbot")
+            self.setdefault("realname", "GOZERBOT")
+            self.setdefault("defaultcc", "!")
+            self.setdefault("quitmsg", "http://gozerbot.googlecode.com")
+            self.setdefault("bindhost", "")
+            self.setdefault("nolimiter", 0)
+            self.setdefault("nickservpass", "")
+            self.setdefault("nickservtxt", ["set unfiltered on", ])
+            self.setdefault("auto_register", 0)
+            self.setdefault("stripident", 0)   
+            self.setdefault("loadlist", [])    
+
+        import gozerbot
+        self['version'] = "GOZERBOT %s " % gozerbot.__version__
+        self['version'] += "RELEASE"
+
+        if verbose:
+            rlog(10, 'config', str(self))
+
+    def save(self):
+
+        """
+            save the config file.
+
+            :rtype: integer .. number of lines saved
+
+            .. literalinclude:: ../../gozerbot/config.py
+                :pyobject: Config.save
+
+
+        """
+
+        written = []
+        curitem = None
+
+        try:
+
+            self.lock.acquire()
+
+            # read existing config file if available
+            try:
+                self.configlist = open(self.cfile, 'r').readlines()
+            except IOError:
+                self.configlist = []
+
+            # make temp file
+            configtmp = open(self.cfile + '.tmp', 'w')
+            teller = 0
+
+            # write header if not already there
+            if not self.configlist:
+                configtmp.write('# %s\n\n' % self.cfile)
+
+            # loop over original lines replacing updated data
+            for line in self.configlist:
+                teller += 1
+
+                # skip comment
+                if line.startswith('#'):
+                    configtmp.write(line)
+                    continue
+
+                # take part after the =
+                try:
+                    keyword = line.split('=')[0].strip()
+                    curitem = keyword
+                except IndexError:
+                    configtmp.write(line)
+                    continue
+
+                # write JSON string of data
+                if self.has_key(keyword):
+                    configtmp.write('%s = %s\n' % (keyword, dumps(self[keyword])))
+                    written.append(keyword)
+                else:
+                    configtmp.write(line)
+
+            # write data not found in original config file
+            for keyword, value in self.iteritems():
+                if keyword in written:
+                    continue
+                curitem = keyword
+                configtmp.write('%s = %s\n' % (keyword, dumps(value)))
+
+            # move temp file to original
+            configtmp.close()
+            try:
+                os.rename(self.cfile + '.tmp', self.cfile)
+            except WindowsError:
+                # no atomic operation supported on windows! error is thrown when destination exists
+                os.remove(self.cfile)
+                os.rename(self.cfile + '.tmp', self.cfile)
+            self.lock.release()
+            return teller
+
+        except Exception, ex:
+            print "ERROR WRITING %s CONFIG FILE: %s .. %s" % (self.cfile, str(ex), curitem)
+            try:
+                self.lock.release()
+            except:
+                pass
+            return
+
+    def reload(self):
+
+        """
+            reload the config file.
+
+            .. literalinclude:: ../../gozerbot/config.py
+                :pyobject: Config.reload
+
+
+        """
+
+        curline = ""
+
+        # read file and set config values to loaded JSON entries
+        try:
+
+            # open file
+            f = open(self.cfile, 'r')
+
+            # loop over data in config file            
+            for line in f:
+                curline = line
+                line = line.strip()
+                if not line or line.startswith('#'):
+                    continue
+                else:
+                    key, value = line.split('=', 1)
+                    self[key.strip()] = loads(unicode(value.strip()))
+
+        except IOError:
+            pass
+        except Exception, ex:
+            print "ERROR READING %s CONFIG FILE: %s .. %s" % (self.cfile, str(ex), curline)
+
+    def write_init(self, txt):
+
+        """ 
+            write initial version of the config file .. mainconfig or fleet
+            config.
+
+            :param txt: txt to write to config file
+            :type txt: string
+
+            .. literalinclude:: ../../gozerbot/config.py
+                :pyobject: Config.write_init
+
+
+        """
+        if hasattr(os, 'mkdir'):
+            # check if datadir is there .. if not create it
+            if not os.path.isdir(self.dir):
+                os.mkdir(self.dir)
+
+            # check if config file is already there .. if not init it 
+            if not os.path.isfile(self.cfile):
+                cfgfile = open(self.cfile, 'w')
+                cfgfile.write(txt)
+                cfgfile.close()
+
+
+mainconfigtxt = """# gozerbot config
+#
+#
+# TAKE NOTE THAT FORMAT IS IN JSON NOW SO USE " not '
+
+# GLOBAL OWNER
+
+# example: owner = ["~bart@127.0.0.1"] (include ident if needed)
+owner = []
+
+# logging level
+loglevel = 10
+
+# list of plugins to log
+loglist = []
+
+# quit message
+quitmsg = "http://gozerbot.googlecode.com"
+
+# botterdata dir mask
+mask = "700"
+
+# enable debugging
+debug = 0
+
+# database stuff
+
+nodb = 0
+db_driver = "olddb"
+dbtype = "sqlite"
+dbname = "db/main.db"
+dbhost = "localhost"  
+dbuser = "bart"
+dbpasswd = "mekker2"
+
+# json backstore
+
+#jsonuser = "users.json"
+
+# loadlist
+loadlist = ["alias", "all", "at", "chanperm", "choice", "code", "core", "count", "fleet", "googletalk", "grep", "ignore", "irc", "jabber", "job", "misc", "nickcapture", "nickserv", "not", "quote", "reload", "rest", "reverse", "size", "sort", "tail", "tell", "to", "underauth", "uniq", "user", "userstate", "plug", "test", "stats", "inform", "admin", "throttle", "mysqlkeepalive", "gozernet"]
+
+# chars used to seperate result
+dotchars = " .. "
+
+# nr of lines before flood protect kicks in
+
+floodallow = 2
+
+# auto register new users 
+
+auto_register = 0
+
+# resource used by xmpp bot (global)
+resource = "gozerbot"
+
+"""
+
+
+""" init txt for fleet config """
+
+
+fleetbotconfigtxt = """# gozerbot fleet bot config
+#
+#
+# TAKE NOTE THAT FORMAT IS IN JSON NOW SO USE " not '
+
+## MAIN STUFF
+
+# set to 0 to disable
+enable = 1
+
+# set type to jabber or irc
+type = "irc"
+
+# owner (irc/jabber)
+owner = []
+
+## CONNECTION STUFF
+
+# user (jabber) .. needs to be full JID because server is taken from it
+user = "botter@jsonbot.org"
+
+# nick (irc)
+nick = "gozerbot"
+
+# server (irc)
+server = ""
+
+# host (jabber) .. server to connect to
+host = ""
+
+# password (irc and jabber)
+password = ""
+
+# port (irc and jabber) .. 0 uses default value
+port = 0
+
+# ssl (irc)
+ssl = 0
+
+# use ipv6 (irc)
+ipv6 = 0
+
+# bots username (irc)
+username = "gozerbot"
+
+# bots realname (irc)
+realname = "GOZERBOT"
+
+## OTHER STUFF
+
+# default control character
+defaultcc = "!"
+
+# quit message
+quitmsg = "http://gozerbot.googlecode.com"
+
+# bindhost .. uncomment to enable
+bindhost = ""
+
+# disable limiter (irc)
+nolimiter = 0
+
+# nickserv .. set pass to enable nickserv ident (irc)
+nickservpass = ""
+nickservtxt = ["set unfiltered on"]
+
+# allow every command except OPER commands
+auto_register = 0
+
+# strip ident string
+stripident = 0
+
+loadlist = []
+
+"""
+
+# INIT SECTION
+
+# main config object
+config = Config(filename='mainconfig', inittxt=mainconfigtxt)
+
+# END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/examples.py
+++ gozerbot-0.99.1/build/lib/gozerbot/examples.py
@@ -0,0 +1,113 @@
+# gozerbot/examples.py
+#
+#
+
+"""
+    examples is a dict of example objects.
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+
+# basic imports
+import re
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
+
+class Example(object):
+
+    """
+        an example.
+
+        :param descr: description of the example
+        :type descr: string
+        :param ex: the example
+        :type ex: string
+
+    """
+
+    def __init__(self, descr, ex):
+        self.descr = descr
+        self.example = ex
+
+class Examples(dict):
+
+    """
+        examples object is a dict.
+
+    """
+
+    def add(self, name, descr, ex):
+
+        """
+            add description and example.
+
+            :param name: name of the example
+            :type name: string
+            :param descr: description of the example
+            :type descr: string
+            :param ex: the example
+            :type ex: string
+
+            .. literalinclude:: ../../gozerbot/examples.py
+                :pyobject: Examples.add
+
+        """
+
+        self[name.lower()] = Example(descr, ex)
+
+    def size(self):
+ 
+        """
+            return size of examples dict.
+
+            :rtype: integer
+
+            .. literalinclude:: ../../gozerbot/examples.py
+                :pyobject: Examples.size
+
+        """
+
+        return len(self.keys())
+
+    def getexamples(self):
+
+        """
+            get all examples in list.
+
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/examples.py
+                :pyobject: Examples.getexamples
+        """
+
+        result = []
+        for i in self.values():
+            ex = i.example.lower()
+            exampleslist = re.split('\d\)', ex)
+            for example in exampleslist:
+                if example:
+                    result.append(example.strip())
+        return result
+
+# ============
+# INIT SECTION
+
+# main examples object
+examples = Examples()
+
+# END INIT
+# ========
--- gozerbot-0.99.1.orig/build/lib/gozerbot/aliases.py
+++ gozerbot-0.99.1/build/lib/gozerbot/aliases.py
@@ -0,0 +1,135 @@
+# gozerbot/aliases.py
+#
+#
+
+""" 
+    command aliases. this module contains the aliases of commands. aliases
+    are stored in the gozerdata/aliases.new json file. 
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+### IMPORT SECTION
+
+# gozerbot imports
+from gozerbot.persist.persist import Persist
+from gozerbot.datadir import datadir
+from gozerbot.utils.locking import lockdec
+# basic import
+import os, thread
+
+### END IMPORT
+
+## LOCK SECTION
+
+aliaslock = thread.allocate_lock()
+aliaslocked = lockdec(aliaslock)
+
+#@aliaslocked
+def aliasreverse(what):
+
+    """ get the reverse of an alias. 
+
+        :param what: alias to get command for
+        :type what: string
+
+        .. literalinclude:: ../../gozerbot/aliases.py
+            :pyobject: aliasreverse
+    """
+
+    for i, j in aliases.data.iteritems():
+        if what == j:
+            return i
+
+#@aliaslocked
+def aliascheck(ievent):
+
+    """ check if alias is available.
+
+        :param ievent: event to check for aliases
+
+        .. literalinclude:: ../../gozerbot/aliases.py
+            :pyobject: aliascheck
+    """
+
+    try:
+
+        cmnd = ievent.txt.split()[0]
+        alias = aliases.data[cmnd]
+        ievent.txt = ievent.txt.replace(cmnd, alias, 1)
+        ievent.alias = alias
+        ievent.aliased = cmnd
+
+    except (IndexError, KeyError):
+        pass
+
+@aliaslocked
+def aliassave():
+
+    """ save aliases to json file. 
+
+        .. literalinclude:: ../../gozerbot/aliases.py
+            :pyobject: aliassave
+    """
+
+    aliases.save()
+
+@aliaslocked
+def aliasset(fromm, to):
+
+    """ set an alias. 
+
+        :param from: alias to set
+        :type from: string
+        :param to: command to alias 
+        :type to: string
+
+        .. literalinclude:: ../../gozerbot/aliases.py
+            :pyobject: aliasset
+    """
+
+    aliases.data[fromm] = to
+
+@aliaslocked
+def aliasdel(fromm):
+
+    """ delete an alias.
+
+        :param fromm: alias to delete
+        :type fromm: string
+
+        .. literalinclude:: ../../gozerbot/aliases.py
+            :pyobject: aliasdel
+    """
+
+    try:
+
+        del aliases.data[fromm]
+        return 1
+
+    except KeyError:
+        pass
+
+def aliasget(fromm):
+
+    """ retrieve an alias. 
+
+        :param fromm: alias to get command for
+        :type fromm: string
+
+        .. literalinclude:: ../../gozerbot/aliases.py
+            :pyobject: aliasget
+    """
+
+    if aliases.data.has_key(fromm): 
+        return aliases.data[fromm]
+
+### INIT SECTION
+
+# the aliases object
+aliases = Persist(datadir + os.sep + 'aliases.new', init=False)
+if not aliases.data:
+    aliases.data = {}
+
+### END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/admin.py
+++ gozerbot-0.99.1/build/lib/gozerbot/admin.py
@@ -0,0 +1,19 @@
+# gozerbot/admin.py
+#
+#
+
+""" gozerbot admin related funtions. """
+
+
+from simplejson import loads
+
+try:
+    cmndtable = loads(open('gozerdata' + os.sep + 'run' + os.sep + 'cmndtable').read())
+except:
+    cmndtable = {}
+
+try:
+    pluginlist = loads(open('gozerdata' + os.sep + 'run' + os.sep + 'pluginlist').read())
+except:
+    pluginlist = []
+
--- gozerbot-0.99.1.orig/build/lib/gozerbot/datadir.py
+++ gozerbot-0.99.1/build/lib/gozerbot/datadir.py
@@ -0,0 +1,57 @@
+# gozerbot/datadir.py
+# -*- coding: utf-8 -*-
+#
+
+""" 
+    :mod: `gozerbot.datadir` -- the datadir of the bot
+
+    .. data::
+        datadir .. points to the datadir of the bot
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# IMPORT SECTION
+
+# basic imports
+import re, os
+
+# END IMPORT
+
+def makedirs(ddir=None):
+
+    """
+        make subdirs in datadir. users, db, fleet, pgp, plugs and old.
+
+        .. literalinclude:: ../../gozerbot/datadir.py
+            :pyobject: makedirs
+
+    """
+
+    ddir = ddir or datadir
+    curdir = os.getcwd()
+
+    if not os.path.isdir(ddir):
+        os.mkdir(ddir)
+    if not os.path.isdir(ddir + '/run/'):
+        os.mkdir(ddir + '/run/')
+    if not os.path.isdir(ddir + '/users/'):
+        os.mkdir(ddir + '/users/')
+    if not os.path.isdir(ddir + '/db/'):
+        os.mkdir(ddir + '/db/')
+    if not os.path.isdir(ddir + '/fleet/'):
+        os.mkdir(ddir + '/fleet/')
+    if not os.path.isdir(ddir + '/pgp/'):
+        os.mkdir(ddir + '/pgp/')
+    if not os.path.isdir(ddir + '/plugs/'):
+        os.mkdir(ddir + '/plugs/')
+    if not os.path.isdir(ddir + '/old/'):
+        os.mkdir(ddir + '/old/')
+
+# INIT SECTION
+
+# the datadir
+datadir = 'gozerdata'
+
+# END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/reboot.py
+++ gozerbot-0.99.1/build/lib/gozerbot/reboot.py
@@ -0,0 +1,74 @@
+# gozerbot/utils/reboot.py
+#
+#
+
+"""
+     reboot code. 
+
+"""
+
+## IMPORT SECTION
+
+from gozerbot.fleet import fleet
+from gozerbot.config import config
+
+from simplejson import dump
+
+import os, sys, pickle, tempfile
+
+## END IMPORT 
+
+## LOCK SECTION
+
+# no locks
+
+## END LOCK
+
+def reboot():
+
+    """
+        reboot the bot.
+
+        .. literalinclude:: ../../gozerbot/reboot.py
+            :pyobject: reboot
+
+    """
+
+    fleet.exit()
+    os.execl(sys.argv[0], *sys.argv)
+
+def reboot_stateful(bot, ievent, fleet, partyline):
+    """
+        reboot the bot, but keep the connections.
+
+        :param bot: bot on which the reboot command is given
+        :type bot: gozerbot.botbase.BotBase	
+        :param ievent: event that triggered the reboot
+        :type ievent: gozerbot.eventbase. EventBase
+        :param fleet: the fleet of the bot
+        :type fleet: gozerbot.fleet.Fleet
+        :param partyline: partyline of the bot
+        :type partyline: gozerbot.partyline.PartyLine
+
+        .. literalinclude:: ../../gozerbot/reboot.py
+            :pyobject: reboot_stateful
+
+    """
+    config.reload()
+    session = {'bots': {}, 'name': bot.name, 'channel': ievent.channel, 'partyline': []}
+
+    for i in fleet.bots:
+        session['bots'].update(i._resumedata())
+
+    session['partyline'] = partyline._resumedata()
+    sessionfile = tempfile.mkstemp('-session', 'gozerbot-')[1]
+    dump(session, open(sessionfile, 'w'))
+    fleet.save()
+    fleet.exit(jabber=True)
+    os.execl(sys.argv[0], sys.argv[0], '-r', sessionfile)
+
+## INIT SECTION
+
+# no vars
+
+## END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/fleet.py
+++ gozerbot-0.99.1/build/lib/gozerbot/fleet.py
@@ -0,0 +1,654 @@
+# gozerbot/fleet.py
+#
+#
+
+""" fleet is a list of bots. """
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+# gozerbot imports
+
+from gozerbot.datadir import datadir
+from utils.exception import handle_exception
+from utils.generic import waitforqueue
+from utils.log import rlog
+from utils.locking import lockdec
+from threads.thr import start_new_thread, threaded
+from config import Config, fleetbotconfigtxt, config
+from users import users
+from plugins import plugins
+from simplejson import load
+
+# basic imports
+
+import Queue, os, types, threading, time, pickle, glob, logging, shutil, thread
+
+# END IMPORT
+
+# ============
+# LOCK SECTION
+
+fleetlock = thread.allocate_lock()
+fleetlocked = lockdec(fleetlock)
+
+# END LOCK
+# ========
+
+## START
+
+class FleetBotAlreadyExists(Exception):
+    pass
+
+
+class Fleet(object):
+
+    """
+        a fleet contains multiple bots (list of bots). used the datadir
+        set in gozerbot/datadir.py
+
+    """
+
+    def __init__(self):
+        self.datadir = datadir + os.sep + 'fleet'
+        if hasattr(os, 'mkdir'):
+            if not os.path.exists(self.datadir):
+                os.mkdir(self.datadir)
+        self.startok = threading.Event()
+        self.bots = []
+
+    def getfirstbot(self):
+
+        """
+            return the main bot of the fleet.
+
+            :rtype: gozerbot.botbase.BotBase
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.getfirstbot
+
+        """
+        self.startok.wait()
+        return self.bots[0]
+
+    def getfirstjabber(self):
+
+        """
+            return the first jabber bot of the fleet.
+
+            :rtype: gozerbot.botbase.BotBase
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.getfirstjabber
+
+        """
+
+        self.startok.wait()
+
+        for bot in self.bots:
+            if bot.type == 'xmpp':
+               return bot
+        
+    def size(self):
+
+        """
+             return number of bots in fleet.
+
+             :rtype: integer
+
+             .. literalinclude:: ../../gozerbot/fleet.py
+                 :pyobject: Fleet.size
+
+        """
+
+        return len(self.bots)
+
+    def resume(self, sessionfile):
+
+        """
+            resume bot from session file.
+
+            :param sessionfile: filename of the session data file
+            :type sessionfile: string
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.resume
+        """
+
+        # read JSON session file
+        session = load(open(sessionfile))
+
+        #  resume bots in session file
+        for name in session['bots'].keys():
+            reto = None
+            if name == session['name']:
+                reto = session['channel']
+            start_new_thread(self.resumebot, (name, session['bots'][name], reto))
+
+        # allow 5 seconds for bots to resurrect
+        time.sleep(5)
+
+        # set start event
+        self.startok.set()
+
+    def makebot(self, name, cfg=None):
+
+        """
+            create a bot .. use configuration if provided.
+
+            :param name: the name of the bot
+            :type name: string
+            :param cfg: configuration file for the bot
+            :type cfg: gozerbot.config.Config
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.makebot
+
+        """
+
+        if self.byname(name):
+            raise FleetBotAlreadyExists("there is already a %s bot in the fleet" % name)
+
+        rlog(10, 'fleet', 'making bot')
+        bot = None
+
+        # if not config create a default bot
+        if not cfg:
+            cfg = Config(self.datadir + os.sep + name, 'config', inittxt=fleetbotconfigtxt)
+            cfg.save()
+
+        # create bot based on type 
+        if cfg['type'] == 'irc':
+            from gozerbot.irc.bot import Bot
+            bot = Bot(name, cfg)
+        elif cfg['type'] == 'xmpp' or cfg['type'] == 'jabber':
+            from gozerbot.xmpp.bot import Bot
+            bot = Bot(name, cfg)
+        elif cfg['type'] == 'gozernet':
+            from gozerbot.gozernet.bot import GozerNetBot
+            bot = GozerNetBot(name, cfg)
+        else:
+            rlog(10, 'fleet', '%s .. unproper type: %s' % (cfg['name'], cfg['type']))
+
+        # set bot name and initialize bot
+        if bot:
+            cfg['name'] = bot.name = name
+            self.initbot(bot)
+            return bot
+
+        # failed to created the bot
+        raise Exception("can't make %s bot" % name)
+
+    def resumebot(self, botname, data={}, printto=None):
+
+        """
+            resume individual bot.
+
+            :param botname: name of the bot to resume
+            :type botname: string
+            :param data: resume data
+            :type data: dict
+            :param printto: whom to reply to that resuming is done
+            :type printto: nick or JID
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.resumebot
+
+        """
+
+        # see if we need to exit the old bot
+        oldbot = self.byname(botname)
+        if oldbot:
+            oldbot.exit()
+
+        # recreate config file of the bot
+        cfg = Config(datadir + os.sep + 'fleet' + os.sep + botname, 'config')
+
+        # make the bot and resume (IRC) or reconnect (Jabber)
+        bot = self.makebot(botname, cfg)
+        rlog(100, 'fleet', 'bot made: %s' % str(bot))
+
+        if bot:
+            if oldbot:
+                self.replace(oldbot, bot)
+            else:
+                self.bots.append(bot)
+
+            if not bot.jabber:
+                bot._resume(data, printto)
+            else:
+                start_new_thread(bot.connectwithjoin, ())
+
+    def start(self, botlist=[], enable=False):
+
+        """
+            startup the bots.
+
+            :param botlist: list of bots to start .. if not provided the bots in the gozerdata/fleet dir will be used
+            :type botlist: list
+            :param enable: whether the bot should be enabled 
+            :type enable: boolean
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.start
+
+        """
+
+        # scan the fleet datadir for bots
+        dirs = []
+        got = []
+        for bot in botlist:
+            dirs.append(self.datadir + os.sep + bot)
+
+        if not dirs:
+            dirs = glob.glob(self.datadir + os.sep + "*")
+
+        for fleetdir in dirs:
+
+            if fleetdir.endswith('fleet'):
+                continue
+
+            rlog(10, 'fleet', 'found bot: ' + fleetdir)
+            cfg = Config(fleetdir, 'config')
+
+            if not cfg:
+                rlog(10, 'fleet', "can't read %s config file" % fleetdir)
+                continue
+
+            name = fleetdir.split(os.sep)[-1]
+
+            if not name:
+                rlog(10, 'fleet', "can't read botname from %s config file" % \
+fleetdir)
+                continue
+
+            if not enable and not cfg['enable']:
+                rlog(10, 'fleet', '%s bot is disabled' % name)
+                continue
+            else:
+                rlog(10, 'fleet', '%s bot is enabled' % name)
+
+            if not name in fleetdir:
+                rlog(10, 'fleet', 'bot name in config file doesnt match dir name')
+                continue
+
+            try:
+                bot = self.makebot(name, cfg)
+            except FleetBotAlreadyExists:
+                rlog(10, 'fleet', 'there is already a fleetbot with the name %s' % name)
+                continue
+
+            if bot:
+                self.addbot(bot)
+                start_new_thread(bot.connectwithjoin, ())
+                got.append(bot)
+
+        # set startok event
+        self.startok.set()
+
+        return got
+
+    def save(self):
+
+        """
+            save fleet data and call save on all the bots.
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.save
+
+        """
+
+        for i in self.bots:
+
+            try:
+                i.save()
+            except Exception, ex:
+                handle_exception()
+
+    def avail(self):
+
+        """
+            show available fleet bots.
+
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.avail
+
+        """
+
+        return os.listdir(self.datadir)
+
+    def list(self):
+
+        """
+            return list of bot names.
+
+            :rtype: list
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.list
+
+        """
+
+        result = []
+
+        for i in self.bots:
+            result.append(i.name)
+
+        return result
+
+    def stopall(self):
+
+        """ 
+            call stop() on all fleet bots.
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.stopall
+
+        """
+
+        for i in self.bots:
+
+            try:
+                i.stop()
+            except:
+                pass
+
+    def byname(self, name):
+
+        """
+            return bot by name.
+
+            :param name: name of the bot
+            :type name: string
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.byname
+
+        """
+
+        for i in self.bots:
+            if name == i.name:
+                return i
+
+    def replace(self, name, bot):
+
+        """
+            replace bot with a new bot.
+
+            :param name: name of the bot to replace
+            :type name: string
+            :param bot: bot to replace old bot with
+            :type bot: gozerbot.botbase.BotBase
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                 :pyobject: Fleet.replace
+
+        """
+
+        for i in range(len(self.bots)):
+            if name == self.bots[i].name:
+                self.bots[i] = bot
+                return
+
+    def initbot(self, bot):
+
+        """
+            initialise a bot.
+
+            :param bot: bot to initialise
+            :type bot: gozerbot.botbase.BotBase
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.initbot
+
+        """
+
+        if bot not in self.bots:
+
+            if not os.path.exists(self.datadir + os.sep + bot.name):
+                os.mkdir(self.datadir + os.sep + bot.name)
+
+            if type(bot.cfg['owner']) == types.StringType or type(bot.cfg['owner']) == types.UnicodeType:
+                bot.cfg['owner'] = [bot.cfg['owner'], ]
+                bot.cfg.save()
+
+            users.make_owner(config['owner'] + bot.cfg['owner'])
+            rlog(10, 'fleet', 'added bot: ' + bot.name)
+
+    @fleetlocked
+    def addbot(self, bot):
+
+        """
+            add a bot to the fleet .. remove all existing bots with the 
+            same name.
+
+            :param bot: bot to add
+            :type bot: gozerbot.botbase.BotBase
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.addbot
+            
+        """
+
+        if bot:
+
+            for i in range(len(self.bots)-1, -1, -1):
+                if self.bots[i].name == bot.name:
+                    rlog(10, 'fleet', 'removing %s from fleet' % bot.name)
+                    del self.bots[i]
+
+            rlog(10, 'fleet', 'adding %s' % bot.name)
+            self.bots.append(bot)
+            return True
+
+        return False
+
+    def connect(self, name):
+
+        """
+            connect bot to the server.
+
+            :param name: name of the bot
+            :type name: string
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.connect
+
+        """
+
+        for i in self.bots:
+
+            if i.name == name:
+                got = i.connect()
+
+                if got:
+                    start_new_thread(i.joinchannels, ())
+                    return True
+                else:
+                    return False
+
+    @fleetlocked
+    def delete(self, name):
+
+        """
+            delete bot with name from fleet.
+
+            :param name: name of bot to delete
+            :type name: string
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.delete
+
+        """
+
+        for i in self.bots:
+
+            if i.name == name:
+                i.exit()
+                self.remove(i)
+                i.cfg['enable'] = 0
+                i.cfg.save()
+                rlog(10, 'fleet', '%s disabled' % i.name)
+                return True
+
+        return False
+
+
+    def remove(self, bot):
+
+        """
+            delete bot by object.
+
+            :param bot: bot to delete
+            :type bot: gozerbot.botbase.BotBase
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                 :pyobject: Fleet.remove
+
+        """
+
+        try:
+            self.bots.remove(bot)
+            return True
+        except ValueError:
+            return False
+
+    def exit(self, name=None, jabber=False):
+
+        """
+            call exit on all bots. if jabber=True only jabberbots will exit.
+
+            :param name: name of the bot to exit. if not provided all bots will exit.
+            :type name: string
+            :param jabber: flag to set when only jabberbots should exit
+            :type jabber: boolean
+            :rtype: boolean
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.exit
+
+        """
+        
+        if not name:
+            threads = []
+
+            for i in self.bots:
+                if jabber and not i.jabber:
+                    pass
+                else:
+                    threads.append(start_new_thread(i.exit, ()))
+
+            for thr in threads:
+                thr.join()
+
+            return
+
+
+        for i in self.bots:
+
+            if i.name == name:
+                try:
+                    i.exit()
+                except:
+                    handle_exception()
+                self.remove(i)
+                return True
+
+        return False
+
+    def cmnd(self, event, name, cmnd):
+ 
+        """
+            do command on a bot.
+
+            :param event: event to pass on to the dispatcher
+            :type event: gozerbot.event.EventBase
+            :param name: name of the bot to pass on to the dispatcher
+            :type name: string
+            :param cmnd: command to execute on the fleet bot
+            :type cmnd: string
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.cmnd
+
+        """
+
+        bot = self.byname(name)
+
+        if not bot:
+            return 0
+
+        from gozerbot.eventbase import EventBase
+        j = plugins.clonedevent(bot, event)
+        j.onlyqueues = True
+        j.txt = cmnd
+        q = Queue.Queue()
+        j.queues = [q]
+        j.speed = 3
+        start_new_thread(plugins.trydispatch, (bot, j))
+        result = waitforqueue(q)
+
+        if not result:
+            return
+
+        res = ["<%s>" % bot.name, ]
+        res += result
+        event.reply(res)
+
+    def cmndall(self, event, cmnd):
+
+        """
+            do a command on all bots.
+
+            :param event: event to pass on to dispatcher
+            :type event: gozerbot.eventbase.EventBase
+            :param cmnd: the command string to execute
+            :type cmnd: string
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.cmndall
+
+        """
+
+        threads = []
+
+        for i in self.bots:
+            thread = start_new_thread(self.cmnd, (event, i.name, cmnd))
+            threads.append(thread)
+
+        for i in threads:
+            i.join()
+
+    def broadcast(self, txt):
+
+        """
+            broadcast txt to all bots.
+
+            :param txt: text to broadcast on all bots
+            :type txt: string
+
+            .. literalinclude:: ../../gozerbot/fleet.py
+                :pyobject: Fleet.broadcast
+
+        """
+
+        for i in self.bots:
+            i.broadcast(txt)
+
+# ============
+# INIT SECTION
+
+
+# main fleet object
+fleet = Fleet()
+
+# END INIT
+# ========
--- gozerbot-0.99.1.orig/build/lib/gozerbot/eggs.py
+++ gozerbot-0.99.1/build/lib/gozerbot/eggs.py
@@ -0,0 +1,188 @@
+# gozerbot/eggs.py
+#
+#
+
+""" 
+
+    :mod: `gozerbot.eggs` -- eggs related functions
+
+    this module is used to load the eggs on which gozerbot depends from 
+    specified dir .. most of the time this is the gozernest dir.
+
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+# IMPORT SECTION
+
+import os, sys
+from utils.exception import handle_exception
+from utils.log import rlog
+
+# END IMPORT
+
+mainenv = None
+
+def init(eggdir, log=False):
+
+    """
+        make sure setuptools is available.
+
+        :param eggdir: directory to scan for eggs
+        :type eggdir: string
+        :param log: whether to log the registration of the setuptools egg
+        :type log: True or False
+
+        .. literalinclude:: ../../gozerbot/eggs.py
+            :pyobject: init
+
+    """
+
+    try:
+        import setuptools
+    except ImportError, ex:
+        try:
+            sys.path.insert(0, eggdir)
+            for egg in os.listdir(eggdir):
+                if egg.startswith('setuptools'):
+                    log and rlog(10, 'eggs', 'loaded %s' % egg)
+                    sys.path.insert(0, eggdir + os.sep + egg)
+        except OSError:
+            pass
+
+latest = {}
+    
+def enable_egg(env, egg, log=True):
+
+    """
+        search for the latest version of an  egg in the enviroment and put 
+        it on sys.path.
+
+        :param env: the environment to search the egg in
+        :type env: pkg_resources.Environment
+        :param egg: egg to load or find a newer version for
+        :param log: determine if we should log the enabling of the egg
+
+        .. literalinclude:: ../../gozerbot/eggs.py
+            :pyobject: enable_egg
+
+    """
+
+    try:
+        from pkg_resources import DistributionNotFound, VersionConflict, working_set, parse_requirements, require
+         
+        if not latest.has_key(egg.project_name):
+            latest[egg.project_name] = egg
+
+        req = egg.as_requirement()
+        reqstr = str(req)
+        reqq = parse_requirements([reqstr.replace('==', '>='), ])
+        for e in working_set.resolve(reqq, mainenv):
+            if e.location not in sys.path:
+                env.add(e)
+                working_set.add(e)
+                working_set.add_entry(e.location)
+                latest[egg.project_name] = e
+                sys.path.insert(0, egg.location)
+                log and rlog(3, 'eggs', 'loaded %s' % e)
+            else:
+                log and rlog(3, 'eggs', '%s already on path' % e)
+    except DistributionNotFound, ex:
+        env.add(egg)
+        working_set.add(egg)
+        working_set.add_entry(egg.location)
+        latest[egg.project_name] = egg
+        sys.path.insert(0, egg.location)
+        log and rlog(3, 'eggs', 'loaded %s' % egg)
+    except VersionConflict, ex:
+        if egg > ex[0]:
+            env.add(egg)
+            working_set.add_entry(egg.location)
+            working_set.add(egg)
+            latest[egg.project_name] = egg
+            sys.path.insert(0, egg.location)
+            log and rlog(3, 'eggs', 'override %s' % egg)
+
+def loadegg(name, eggdirs=['gozernest',], log=True):
+
+    """
+        scan eggdir for a egg matching `name`.
+
+        :param name: piece of txt which should be in the egg projectname
+        :type name: string
+        :param eggdirs: directories to search in
+        :type eggdirs: list
+        :param log: boolean which indicates whether loading should be logged
+        :type log: boolean
+
+        .. literalinclude:: ../../gozerbot/eggs.py
+            :pyobject: loadegg
+
+    """
+
+    try:
+        from pkg_resources import find_distributions, Environment
+        global mainenv
+
+        for eggdir in eggdirs:
+            if mainenv:
+                mainenv += Environment(eggdir)
+            else:
+                mainenv = Environment(eggdir)
+            eggs = find_distributions(eggdir)
+            for egg in eggs:
+               if name.lower() in egg.project_name.lower():
+                    enable_egg(mainenv, egg, log)
+
+    except ImportError: 
+        return
+    except Exception, ex:
+        handle_exception()
+
+def loadeggs(eggdir, log=True):
+
+    """
+        load all eggs in a directory.
+
+        :param eggdir: directory to load eggs from
+        :type eggdir: string
+
+        .. literalinclude:: ../../gozerbot/eggs.py
+            :pyobject: loadeggs
+
+    """
+
+    rlog(3, 'eggs', 'scanning %s' % eggdir)
+    try:
+        from pkg_resources import find_distributions, Environment
+        global mainenv
+
+        if mainenv:
+            mainenv += Environment(eggdir)
+        else:
+            mainenv = Environment(eggdir)
+        eggs = find_distributions(eggdir)
+        for egg in eggs:
+            if not egg.project_name.startswith('setuptools'):
+                enable_egg(mainenv, egg, log)
+
+    except ImportError:
+        return
+    except Exception, ex:
+        handle_exception()
+
+    res = []
+    for name, egg in latest.iteritems():
+        res.append("%s: %s" % (name, egg.version))
+
+    rlog(3, 'eggs', 'loaded: %s' % ' .. '.join(res))
+
+
+# INIT SECTION
+
+
+# first search for setuptools and load it
+init(os.getcwd())
+init(os.getcwd() + os.sep + 'gozernest')
+
+# END INIT
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/persistconfig.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/persistconfig.py
@@ -0,0 +1,420 @@
+# gozerbot/persistconfig.py
+#
+#
+
+""" allow data to be pickled to disk .. creating the persisted object
+    restores data
+    
+usage:
+    !plug-cfg			->	shows list of all config
+    !plug-cfg key value		->	sets value to key
+    !plug-cfg key		->	shows list of key
+    !plug-cfg key add value	->	adds value to list
+    !plug-cfg key remove value	->	removes value from list
+    !plug-cfg key clear		-> 	clears entire list
+    !plug-cfgsave		->	force save configuration to disk
+    
+todo:
+    - plugin api (more work needed?)
+    
+"""
+
+__copyright__ = 'this file is in the public domain'
+__author__ = 'Bas van Oostveen'
+
+from gozerbot.utils.log import rlog
+from gozerbot.utils.trace import calledfrom
+from gozerbot.compat.persist import Persist
+from gozerbot.commands import cmnds, Command
+from gozerbot.examples import examples
+from gozerbot.datadir import datadir
+import sys, os, types, time
+
+class Option(object):
+
+    def __init__(self, value, desc, perm, example, name, exposed):
+        assert isinstance(name, str), "Option.self.name must be a string"
+        self.value = value
+        self.desc = desc
+        self.perm = perm
+        self.example = example
+        self.name = name.lower()
+        self.exposed = exposed
+
+    def __casattr__(self, name, val):
+        # Update option if needed
+        if not hasattr(self, name):
+            setattr(self, name, val)
+            return True
+        else:
+            #if val and getattr(self, name)!=val: # old style
+            if val != None and type(getattr(self, name)) != type(val):
+                setattr(self, name, val)
+                return True
+            return False
+
+    def check(self, key, plugname, value, desc, perm, example, name, exposed):
+        upd = False
+        # maybe checking value is too much here
+        if self.__casattr__("value", value): upd = True
+        if self.__casattr__("example", example): upd = True
+        if self.__casattr__("name", name): upd = True
+        if self.__casattr__("perm", perm): upd = True
+        if self.__casattr__("exposed", exposed): upd = True
+        if self.name == None:
+            self.name = "%s-cfg-%s" % (plugname, str(key))
+            upd = True
+        return upd
+
+    def __lt__(self, other):
+        return self.value < other
+
+    def __le__(self, other):
+        return self.value <= other
+
+    def __eq__(self, other):
+        return self.value == other
+
+    def __ne__(self, other):
+        return self.value != other
+
+    def __gt__(self, other):
+        return self.value >= other
+
+    def __ge__(self, other):
+        return self.value >= other
+
+class LazyValueDict(object):
+
+    """ emulates the normal Persist.data (key, value) dict """
+
+    def __init__(self, cfg):
+        self.__cfg = cfg
+
+    def __len__(self):
+        return len(self.__persistData)
+
+    def __getitem__(self, key):
+        return self.__cfg.data[key].value
+
+    def __setitem__(self, key, value):
+        if not self.__cfg.data.has_key(key) or not \
+isinstance(self.__cfg.data[key], Option):
+            name = "%s-cfg-%s" % (self.__cfg.plugname, str(key))
+            self.__cfg.define(value, "", 'OPER', "", name, exposed=False)
+        self.__cfg.set(key, value)
+
+    def __delitem__(self, key):
+        raise Exception("Direct deletion not supported, use \
+persistConfig.undefine()")
+
+    def __iter__(self):
+        return self.__cfg.data.__iter__()
+
+    def iterkeys(self):
+        return self.__iter__()
+
+    def __contains__(self, item):
+        return self.__cfg.data.has_key(item)
+
+class PersistConfigError(Exception): pass
+
+class PersistConfig(Persist):
+
+    """ persist plugin configuration and create default handlers """
+
+    def __init__(self):
+        self.__basename__ = self.__class__.__name__
+        self.plugname = calledfrom(sys._getframe())
+        Persist.__init__(self, os.path.join(datadir, "%s-config" % \
+self.plugname), {})
+        self.__callbacks = {}
+        cmndname = "%s-cfg" % self.plugname
+        rlog(-3, 'persistconfig', 'added command %s (%s)' % (cmndname, \
+self.plugname))
+        cmnds[cmndname] = Command(self.cmnd_cfg, 'OPER', self.plugname, \
+threaded=True)	
+        examples.add(cmndname, "plugin configuration", cmndname)
+        cmndnamesave = cmndname + "save"
+        cmnds[cmndnamesave] = Command(self.cmnd_cfgsave, 'OPER', \
+self.plugname, threaded=True)	
+        examples.add(cmndnamesave, "save plugin configuration", cmndnamesave)
+
+    def __getattribute__(self, name):
+        # make sure the attribute data is not called from Persist or 
+        # PersistConfig returning a persist compatible (key, value) dict 
+        # instead of the rich persistconfig
+        cf = calledfrom(sys._getframe())
+        ## (key, option(value, ...)) is only for persistconfig internal usage.
+        if name == "data" and cf != "persistconfig" and cf != "persist" and \
+cf != self.__basename__:
+            # intercept data block, return a clean dict with lazy binding 
+            # to option.value
+            return LazyValueDict(self)
+        return super(PersistConfig, self).__getattribute__(name)
+
+    def handle_callback(self, event, key, value=None):
+        if self.__callbacks.has_key((key, event)):
+            cb, extra_data = self.__callbacks[(key, event)]
+            if callable(cb):
+                cb(key, value, event, extra_data)
+            else:
+                rlog(5, 'persistconfig', 'invalid callback for %s %s' % (key, \
+event))
+                del self.__callbacks[(key, event)]
+
+    ### cmnds
+
+    def show_cfg(self, bot, ievent):
+        s = []
+        for key, option in sorted(self.data.items()):
+            if not isinstance(option, Option):
+                rlog(5, 'persistconfig', 'Option %s is not a valid option' % \
+key)
+                continue
+            if not option.exposed:
+                continue
+            v = option.value
+            if type(v) in [str, unicode]:
+                v = '"'+v+'"'
+            v = str(v)
+            s.append("%s=%s" % (key, v))
+        ievent.reply("options: " + ' .. '.join(s))
+
+    def cmnd_cfgsave(self, bot, ievent):
+        self.save()
+        ievent.reply("config saved")
+	
+    def cmnd_cfg_edit(self, bot, ievent, args, key, option):
+        if type(option.value) == types.ListType:
+	    if args[0].startswith("[") and args[-1].endswith("]"):
+		values = []
+		for v in ' '.join(args)[1:-1].replace(", ", ",").split(","):
+		    if v[0]=='"' and v[-1]=='"':
+			# string
+			v = v.replace('"', '')
+		    elif v[0]=="'" and v[-1]=="'":
+			# string
+			v = v.replace("'", "")
+		    elif '.' in v:
+			# float
+			try:
+			    v = float(v)
+			except ValueError:
+			    ievent.reply("invalid long literal: %s" % v)
+			    return
+		    else:
+			# int
+			try:
+			    v = int(v)
+			except ValueError:
+			    ievent.reply("invalid int literal: %s" % v)
+			    return
+		    values.append(v)
+                self.set(key, values)
+                ievent.reply("%s set %s" % (key, values))
+		return
+            command = args[0]
+            value = ' '.join(args[1:])
+            if command == "clear":
+                self.clear(key)
+                ievent.reply("list empty")
+            elif command == "add":
+                self.append(key, value)
+                ievent.reply("%s added %s" % (key, value))
+            elif command == "remove" or command == "del":
+                try:
+                    self.remove(key, value)
+                    ievent.reply("%s removed" % str(value))
+                except ValueError:
+                    ievent.reply("%s is not in list" % str(value))
+            else:
+                ievent.reply("invalid command")
+            return
+        else:
+            value = ' '.join(args)
+            try:
+                value = type(option.value)(value)
+            except:
+                pass
+            if type(value) == type(option.value):
+                self.set(key, value)
+                ievent.reply("%s set" % key)
+            elif type(value) == types.LongType and \
+type(option.value) == types.IntType:
+                # allow upscaling from int to long
+                self.set(key, value)
+                ievent.reply("%s set" % key)
+            else:
+                ievent.reply("value %s (%s) is not of the same type as %s \
+(%s)" % (value, type(value), option.value, type(option.value)))
+    
+    def cmnd_cfg(self, bot, ievent):
+        if not ievent.args:
+            self.show_cfg(bot, ievent)
+            return
+        argc = len(ievent.args)
+        key = ievent.args[0]
+        try:
+            option = self.data[key]
+        except KeyError:
+            ievent.reply("%s option %s not found" % (self.plugname, key))
+            return
+        if not isinstance(option, Option):
+            rlog(5, 'persistconfig', 'Option %s is not a valid option' % key)
+            return
+        if not option.exposed:
+            return
+        if argc == 1:
+            ievent.reply(str(option.value))
+            return
+        self.cmnd_cfg_edit(bot, ievent, ievent.args[1:], key, option)
+
+    def generic_cmnd(self, key):
+        def func(bot, ievent):
+            try:
+                option = self.data[key]
+            except KeyError:
+                ievent.reply("%s not found" % key)
+                # need return ?
+            if not isinstance(option, Option):
+                rlog(5, 'persistconfig', 'Option %s is not a valid option' % \
+key)
+                return
+            if ievent.args:
+                value = ' '.join(ievent.args)
+                try:
+                    value = type(option.value)(value)
+                except:
+                    pass
+                self.cmnd_cfg_edit(bot, ievent, ievent.args, key, option)
+            else:
+                ievent.reply(str(option.value))
+        return func
+
+    ### plugin api
+
+    def define(self, key, value=None, desc="plugin option", perm='OPER', \
+example="", name=None, exposed=True):
+        if name:
+            name = name.lower()
+        if not self.data.has_key(key):
+            if name == None:
+                name = "%s-cfg-%s" % (self.plugname, str(key))
+            option = Option(value, desc, perm, example, name, exposed)
+            self.data[key] = option
+            self.save()
+        else:
+            option = self.data[key]
+            # if unpickled option is not of class Option recreate the option
+            # also if the type of value has changed recreate the option
+            # exception if value got upgraded from int to long, then nothing 
+            # has to be changed
+            if not isinstance(option, Option):
+                if name == None:
+                    name = "%s-cfg-%s" % (self.plugname, str(key))
+                if type(value) == type(option):
+                    value = option
+                option = Option(value, desc, perm, example, name, exposed)
+                self.data[key] = option
+                self.save()
+            else:
+                if type(option.value) == types.LongType and \
+type(value) == types.IntType:
+                    value = long(value)
+                if option.check(key, self.plugname, value, desc, perm, \
+example, name, exposed):
+                    self.data[key] = option
+                    self.save()
+	
+    def undefine(self, key, throw=False):
+        try:
+            option = self.data[key]
+            if option.exposed:
+                if cmnds.has_key(option.name):
+                    del cmnds[option.name]
+                if examples.has_key(option.name):
+                    del examples[option.name]
+            del self.data[key]
+            self.save()
+            return True
+        except KeyError, e:
+            if throw:
+                raise
+        return False
+
+    def checkoption(self, key):
+        if not isinstance(self.data[key], Option):
+            raise PersistConfigError("Option %s is not a valid option" % key)
+        return True
+
+    def set(self, key, value, throw=False):
+        if type(value)==unicode:
+            value = str(value)
+        try:
+            self.checkoption(key)
+        except (KeyError, PersistConfigError):
+            if throw:
+                raise
+            self.define(key, value)
+            self.handle_callback('change', key, value)
+        else:
+            self.data[key].value = value
+            self.handle_callback('change', key, value)
+            self.save()
+
+    def append(self, key, value):
+        self.checkoption(key)
+        self.data[key].value.append(value)
+        self.save()
+        self.handle_callback('change', key, value)
+        self.handle_callback('add', key, value)
+
+    def remove(self, key, value):
+        self.checkoption(key)
+        self.data[key].value.remove(value)
+        self.save()
+        self.handle_callback('change', key, value)
+        self.handle_callback('remove', key, value)
+
+    def clear(self, key):
+        self.checkoption(key)
+        self.data[key].value = []
+        self.save()
+        self.handle_callback('change', key, [])
+        self.handle_callback('clear', key)
+
+    def get(self, key, default=None):
+        try:
+            return self.data[key].value
+        except KeyError:
+            return default
+
+    def has_key(self, key):
+        return self.data.has_key(key)
+
+    def callback(self, key, event, callback, extra_data=None):
+        callbacks = ["change", "add", "remove", "clear"]
+        if not event in callbacks:
+            raise PersistConfigError("Unsupported callback event %s" % event)
+        self.__callbacks[(key, event)] = (callback, extra_data)
+    
+    def syncold(self, filename, remove=False):
+        """ sync with old config data """
+        if os.path.isfile(filename):
+            synckey = "persistconfig-syncold"
+            oldconfig = Persist(filename)
+            if not oldconfig.data.has_key(synckey):
+                rlog(10, 'persistconfig', "syncing old config %s with \
+persistconfig" % filename)
+                for i, j in oldconfig.data.iteritems():
+                    if i == synckey:
+                        continue
+                    if j and not self.get(i):
+                        self.set(i, oldconfig.data[i])
+                oldconfig.data[synckey] = time.localtime()
+                oldconfig.save()
+            del oldconfig
+            if remove:
+                os.unlink(filename)
+
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/persist.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/persist.py
@@ -0,0 +1,64 @@
+# gozerbot/persist.py
+#
+#
+
+""" allow data to be pickled to disk .. creating the persisted object
+    restores data
+"""
+
+__copyright__ = 'this file is in the public domain'
+
+from gozerbot.utils.log import rlog
+import cPickle, thread, os, copy
+
+saving = []
+stopsave = 0
+
+class Persist(object):
+
+    """ persist data attribute to pickle file """
+
+    def __init__(self, filename, default=None):
+        rlog(1, 'compat-persist', 'reading %s' % filename)
+        self.fn = filename
+        self.lock = thread.allocate_lock()
+        self.data = None
+        # load data from pickled file
+        try:
+            datafile = open(filename, 'r')
+        except IOError:
+            if default != None:
+                self.data = copy.deepcopy(default)
+            return
+        try:
+            self.data = cPickle.load(datafile)
+            datafile.close()
+        except:
+            if default != None:
+                self.data = copy.deepcopy(default)
+            else:
+                rlog(100, 'compat-persist', 'ERROR: %s' % filename)
+                raise
+
+    def save(self):
+        """ save persist data """
+        if stopsave:
+            rlog(100, 'compat-persist', 'stopping mode .. not saving %s' % self.fn)
+            return
+        try:
+            saving.append(str(self.fn))
+            self.lock.acquire()
+            # first save to temp file and when done rename
+            tmp = self.fn + '.tmp'
+            try:
+                datafile = open(tmp, 'w')
+            except IOError:
+                rlog(100, 'compat-persist', "can't save %s" % self.fn)
+                return
+            cPickle.dump(self.data, datafile)
+            datafile.close()
+            os.rename(tmp, self.fn)
+            rlog(10, 'compat-persist', '%s saved' % self.fn)
+        finally:
+            saving.remove(self.fn)
+            self.lock.release()
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/users.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/users.py
@@ -0,0 +1,246 @@
+# gozerbot/users.py
+#
+#
+
+""" bot's users """
+
+__copyright__ = 'this file is in the public domain'
+
+from gozerbot.datadir import datadir
+from gozerbot.utils.log import rlog
+from gozerbot.utils.generic import stripident, stripidents
+from gozerbot.utils.exception import handle_exception
+from gozerbot.utils.generic import die, stripped
+from gozerbot.compat.persist import Persist
+from gozerbot.config import config
+import re, types, os
+
+config.load()
+
+class User(object):
+
+    """ repesents a user """
+
+    def __init__(self, name, userhosts, perms):
+        self.name = str(name)
+        self.userhosts = list(userhosts)
+        self.perms = list(perms)
+        self.email = ""
+        self.permit = []
+        self.status = []
+        self.passwd = ""
+        self.allowed = []
+        self.notallowed = []
+        self.tempuserhosts = []
+        self.userdata = {}
+
+    def __str__(self):
+        return "name: %s userhosts: %s perms: %s email: %s status: %s \
+allowed: %s notallowed: %s tempusershosts: %s permit: %s" % (self.name, \
+self.userhosts, self.perms, self.email, self.status, self.allowed, \
+self.notallowed, self.tempuserhosts, self.permit)
+
+class Users(Persist):
+
+    """ holds multiple users """
+
+    def __init__(self, filename):
+        self.userhosts = {}
+        self.masks = {}
+        self.compiled = {}
+        Persist.__init__(self, filename)
+        if not self.data:
+            self.data = []
+        for i in self.data:
+            for j in i.userhosts:
+                self.adduserhost(j, i)
+
+    def adduserhost(self, userhost, user):
+        """ add userhost/mask """
+        if '?' in userhost or '*' in userhost:
+            tmp = re.escape(userhost)
+            tmp = tmp.replace('\?','.')
+            tmp = tmp.replace('\*','.*?')
+            regex = re.compile(tmp)
+            self.compiled[regex] = user
+            self.masks[userhost] = regex
+        else:
+            self.userhosts[userhost] = user
+
+    def deluserhost(self, userhost):
+        """ del userhost/mask """
+        try:
+            if '?' in userhost or '*' in userhost:
+                regex = self.masks[userhost]
+                del self.compiled[regex]
+                del self.masks[userhost]
+            else:
+                del self.userhosts[userhost]
+            return 1
+        except KeyError:
+            return 0
+            
+    def exist(self, name):
+        """ see if user with username exists """
+        name = name.lower()
+        for i in self.data:
+            if i.name == name:
+                return 1
+
+    def getperms(self, userhost):
+        """ get permissions """
+        user = self.getuser(userhost)
+        if user:
+            return user.perms
+        else:
+            return ['ANON', ]
+
+    def getuser(self, userhost):
+        """ get user for which userhost matches """
+        userhost = stripident(userhost)
+        if userhost in self.userhosts:
+            return self.userhosts[userhost]
+        else:
+            for i in self.compiled:
+                if re.search(i, userhost):
+                    return self.compiled[i]
+        for user in self.data:
+            for i in user.userhosts:
+                if i == userhost or i == stripped(userhost):
+                    return user
+        return None
+
+    def gotperm(self, name, perm):
+        user = self.byname(name)
+        if not user:
+            return 0
+        if perm in user.perms:
+            return 1
+
+    def size(self):
+        """ return nr of users """
+        return len(self.data)
+
+    def add(self, name, userhosts, perms):
+        """ add user """
+        self.addnosave(name, userhosts, perms)
+        self.save()
+        return 1
+
+    def addnosave(self, name, userhosts, perms):
+        """ add user without saving """
+        name = name.lower()
+        for item in self.data:
+            if item.name == name:
+                return 0
+        userhosts = stripidents(userhosts)
+        # add user
+        user = User(name, userhosts, perms)
+        self.data.append(user)
+        rlog(10, 'users', 'added user %s %s with perms %s' % (name, \
+userhosts, perms))
+        for i in userhosts:
+            self.adduserhost(i, user)
+        return 1
+
+    def permitted(self, userhost, who, what):
+        """ check if (who,what) is in users permit list """
+        user = self.getuser(userhost)
+        if not user:
+            return 0
+        if (who, what) in user.permit:
+            return 1
+
+    def names(self):
+        """ get names of all users """
+        result = []
+        for item in self.data:
+            result.append(item.name)
+        return result
+
+    def getname(self, userhost):
+        """ get name of user with userhost """
+        item = self.getuser(userhost)
+        if item:
+            return item.name
+        else:
+            return None
+
+    def byname(self, name):
+        """ return user with name """
+        name = name.lower()
+        for item in self.data:
+            if item.name.lower() == name:
+                return item
+        return None
+
+    def merge(self, name, userhost):
+        """ add userhosts to user with name """
+        name = name.lower()
+        for item in self.data:
+            if item.name == name:
+                userhost = stripident(userhost)
+                item.userhosts.append(userhost)
+                self.adduserhost(userhost, item)
+                self.save()
+                rlog(10, 'users', 'merged %s (%s) with %s' % (name, \
+userhost, item.name))
+                return 1
+        return None
+
+    def delete(self, name):
+        """ delete user with name """
+        data = self.data
+        name = name.lower()
+        got = 0
+        for itemnr in range(len(data)-1, -1, -1):
+            if data[itemnr].name == name:
+                for i in data[itemnr].userhosts:
+                    self.deluserhost(i)
+                del data[itemnr]
+                got = 1
+        if got:
+            self.save()
+            return 1
+        return None
+
+    def allowed(self, userhost, perms, log=True):
+        """ check if user with userhosts is allowed to execute perm command """
+        if type(perms) != types.ListType:
+            perms = [perms, ]
+        if 'ANY' in perms:
+            return 1
+        item = self.getuser(userhost)
+        if not item:
+            if log:
+                rlog(10, 'users', '%s userhost denied' % userhost)
+            return 0
+        for i in perms:
+            if i in item.perms:
+                return 1
+        if log:
+            rlog(10, 'users', '%s perm %s denied' % (userhost, perms))
+        return 0
+
+    def status(self, userhost, status):
+        """ check if user has status set """
+        status = status.upper()
+        item = self.getuser(userhost)
+        if not item:
+            return 0
+        if status in item.status:
+            return 1
+        return 0
+
+    def getemail(self, name):
+        """ return email of user """
+        user = self.byname(name)
+        return user.email
+        
+    def setemail(self, name, email):
+        """ set email of user """
+        user = self.byname(name)
+        user.email = email
+        self.save()
+        return 1
+        
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/karma.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/karma.py
@@ -0,0 +1,270 @@
+# plugs/karma.py
+#
+#
+
+""" karma plugin """
+
+__copyright__ = 'this file is in the public domain'
+
+from gozerbot.commands import cmnds
+from gozerbot.examples import examples
+from gozerbot.redispatcher import rebefore
+from gozerbot.datadir import datadir
+from gozerbot.utils.exception import handle_exception
+from gozerbot.utils.log import rlog
+from gozerbot.utils.locking import lockdec
+from gozerbot.utils.statdict import Statdict
+from gozerbot.aliases import aliases
+from gozerbot.plughelp import plughelp
+from gozerbot.config import config
+import thread, pickle, time, os
+
+plughelp.add('karma', 'maintain karma of items .. use ++ to raise karma by 1 \
+or use -- to lower by 1 .. reason might be given after a "#"')
+
+savelist = []
+
+class Karma:
+
+    """ holds karma data """
+
+    def __init__(self, ddir):
+        rlog(0, 'karma', 'reading %s' % datadir + os.sep + 'karma')
+        self.datadir = ddir
+        self.lock = thread.allocate_lock()
+        try:
+            karmafile = open(ddir + os.sep + 'karma', 'r')
+            self.karma = pickle.load(karmafile)
+            karmafile.close()
+        except:
+            self.karma = {}
+        try:
+            reasonupfile = open(ddir + os.sep + 'reasonup', 'r')
+            self.reasonup = pickle.load(reasonupfile)
+            reasonupfile.close()
+        except:
+            self.reasonup = {}
+        try:
+            reasondownfile = open(ddir + os.sep + 'reasondown', 'r')
+            self.reasondown = pickle.load(reasondownfile)
+            reasondownfile.close()
+        except:
+            self.reasondown = {}
+        try:
+            whoupfile = open(ddir + os.sep + 'whoup', 'r')
+            self.whoup = pickle.load(whoupfile)
+            whoupfile.close()
+        except:
+            self.whoup = {}
+        try:
+            whodownfile = open(ddir + os.sep + 'whodown', 'r')
+            self.whodown = pickle.load(whodownfile)
+            whodownfile.close()
+        except:
+            self.whodown = {}
+
+    def size(self):
+        return len(self.karma)
+        
+    def save(self):
+        """ save karma data """
+        try:
+            self.lock.acquire()
+            karmafile = open(self.datadir + os.sep + 'karma', 'w')
+            pickle.dump(self.karma, karmafile)
+            karmafile.close()
+            rlog(1, 'karma', '%s karma saved' % self.datadir)
+            reasonupfile = open(self.datadir + os.sep + 'reasonup', 'w')
+            pickle.dump(self.reasonup, reasonupfile)
+            reasonupfile.close()
+            rlog(1, 'karma', '%s reasonup saved' % self.datadir)
+            reasondownfile = open(self.datadir + os.sep + 'reasondown', 'w')
+            pickle.dump(self.reasondown, reasondownfile)
+            reasondownfile.close()
+            rlog(1, 'karma', '%s reasondown saved' % self.datadir)
+            whoupfile = open(self.datadir + os.sep + 'whoup', 'w')
+            pickle.dump(self.whoup, whoupfile)
+            whoupfile.close()
+            rlog(1, 'karma', '%s whoup saved' % self.datadir)
+            whodownfile = open(self.datadir + os.sep + 'whodown', 'w')
+            pickle.dump(self.whoup, whodownfile)
+            whodownfile.close()
+            rlog(1, 'karma', '%s whodown saved' % self.datadir)
+        finally:
+            self.lock.release()
+
+    def add(self, item, value):
+        """ set karma value of item """
+        self.karma[item.lower()] = value
+
+    def delete(self, item):
+        """ delete karma item """
+        item = item.lower()
+        try:
+            del self.karma[item]
+            return 1
+        except KeyError:
+            return 0
+
+    def get(self, item):
+        """ get karma of item """
+        item = item.lower()
+        if self.karma.has_key(item):
+            return self.karma[item]
+        else:
+            return None
+
+    def addwhy(self, item, updown, reason):
+        """ add why of karma up/down """
+        item = item.lower()
+        if not self.karma.has_key(item):
+            return 0
+        reason = reason.strip()
+        if updown == 'up':
+            if self.reasonup.has_key(item):
+                self.reasonup[item].append(reason)
+            else:
+                self.reasonup[item] = [reason]
+        elif updown == 'down':
+            if self.reasondown.has_key(item):
+                self.reasondown[item].append(reason)
+            else:
+                self.reasondown[item] = [reason]
+            
+    def upitem(self, item, reason=None):
+        """ up a karma item with/without reason """
+        item = item.lower()
+        if self.karma.has_key(item):
+            self.karma[item] += 1
+        else:
+            self.karma[item] = 1
+        if reason:
+            reason = reason.strip()
+            if self.reasonup.has_key(item):
+                self.reasonup[item].append(reason)
+            else:
+                self.reasonup[item] = [reason]
+
+    def down(self, item, reason=None):
+        """ lower a karma item with/without reason """
+        item = item.lower()
+        if self.karma.has_key(item):
+            self.karma[item] -= 1
+        else:
+            self.karma[item] = -1
+        if reason:
+            reason = reason.strip()
+            if self.reasondown.has_key(item):
+                self.reasondown[item].append(reason)
+            else:
+                self.reasondown[item] = [reason]
+
+    def whykarmaup(self, item):
+        """ get why of karma ups """
+        item = item.lower()
+        if self.reasonup.has_key(item):
+            return self.reasonup[item]
+
+    def whykarmadown(self, item):
+        """ get why of karma downs """
+        item = item.lower()
+        if self.reasondown.has_key(item):
+            return self.reasondown[item]
+
+    def setwhoup(self, item, nick):
+        """ set who upped a karma item """
+        item = item.lower()
+        if self.whoup.has_key(item):
+            self.whoup[item].append(nick)
+        else:
+            self.whoup[item] = [nick]
+
+    def setwhodown(self, item, nick):
+        """ set who lowered a karma item """
+        item = item.lower()
+        if self.whodown.has_key(item):
+            self.whodown[item].append(nick)
+        else:
+            self.whodown[item] = [nick]
+
+    def getwhoup(self, item):
+        """ get list of who upped a karma item """
+        item = item.lower()
+        try:
+            return self.whoup[item]
+        except KeyError:
+            return None
+
+    def getwhodown(self, item):
+        """ get list of who downed a karma item """
+        item = item.lower()
+        try:
+            return self.whodown[item]
+        except KeyError:
+            return None
+
+    def good(self, limit=10):
+        """ show top 10 of karma items """
+        statdict = Statdict()
+        for i in self.karma.keys():
+            if i.startswith('quote '):
+                continue
+            statdict.upitem(i, value=self.karma[i])
+        return statdict.top(limit=limit)
+
+    def bad(self, limit=10):
+        """ show lowest 10 of negative karma items """
+        statdict = Statdict()
+        for i in self.karma.keys():
+            if i.startswith('quote '):
+                continue
+            statdict.upitem(i, value=self.karma[i])
+        return statdict.down(limit=limit)
+
+    def quotegood(self, limit=10):
+        """ show top 10 of karma items """
+        statdict = Statdict()
+        for i in self.karma.keys():
+            if not i.startswith('quote '):
+                continue
+            statdict.upitem(i, value=self.karma[i])
+        return statdict.top(limit=limit)
+
+    def quotebad(self, limit=10):
+        """ show lowest 10 of negative karma items """
+        statdict = Statdict()
+        for i in self.karma.keys():
+            if not i.startswith('quote '):
+                continue
+            statdict.upitem(i, value=self.karma[i])
+        return statdict.down(limit=limit)
+
+    def search(self, item):
+        """ search karma items """
+        result = []
+        item = item.lower()
+        for i, j in self.karma.iteritems():
+            if item in i:
+                result.append((i, j))
+        return result
+
+    def whatup(self, nick):
+        """ show what items where upped by nick """
+        nick = nick.lower()
+        statdict = Statdict()
+        for i, j in self.whoup.iteritems():
+            for z in j:
+                if nick == z:
+                    statdict.upitem(i)
+        return statdict.top()
+
+    def whatdown(self, nick):
+        """ show what items where lowered by nick """
+        nick = nick.lower()
+        statdict = Statdict()
+        for i, j in self.whodown.iteritems():
+            for z in j:
+                if nick == z:
+                    statdict.upitem(i)
+        return statdict.top()
+
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/dbusers.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/dbusers.py
@@ -0,0 +1,264 @@
+# gozerbot/dbusers.py
+#
+#
+
+""" bots users for mysql interface"""
+
+__copyright__ = 'this file is in the public domain'
+
+from gozerbot.utils.log import rlog
+from gozerbot.database.db import Db
+import types
+
+class Dbusers(object):
+
+    """ users class """
+
+    def __init__(self):
+        self.db = Db()
+
+    def size(self):
+        """ return nr of users """
+        result = self.db.execute(""" SELECT DISTINCT COUNT(*) FROM userhosts \
+""")
+        if result:
+            return result[0][0]
+
+    def getperms(self, userhost):
+        """ return permission of user"""
+        name = self.getname(userhost)
+        if not name:
+            return ['ANON', ]
+        result = self.db.execute(""" SELECT perm FROM perms WHERE name = %s \
+""", name)
+        res = []
+        for i in result:
+            res.append(i[0])
+        return res
+
+    def exist(self, name):
+        """ see if user with <name> exists """
+        name = name.lower()
+        result = self.db.execute(""" SELECT name,userhost FROM userhosts WHERE \
+name = %s """, name)
+        return result
+
+    def getname(self, userhost):
+        """ get name of user belonging to <userhost> """
+        result = self.db.execute(""" SELECT name FROM userhosts WHERE \
+%s LIKE userhost """, userhost)
+        if result:
+            return result[0][0]
+
+    def add(self, name, userhosts, perms):
+        """ add an user """
+        if type(userhosts) != types.ListType:
+            rlog(10, 'dbusers', 'i need a list of userhosts')
+            return 0
+        for i in userhosts:
+            self.adduserhost(name, i)
+        for i in perms:
+            self.addperm(name, i)
+        rlog(10, 'users', '%s added to user database' % name)
+        return 1
+
+    def adduserhost(self, name, userhost):
+        """ add userhost """
+        name = name.lower()
+        res = None
+        result = self.db.execute(""" INSERT INTO userhosts(name, userhost) \
+values(%s, %s) """, (name, userhost))
+        if result:
+            res = 1
+            rlog(10, 'users', '%s (%s) added to userhosts' % (name, userhost))
+        return res
+
+    def addperm(self, name, perm):
+        """ add permission """
+        name = name.lower()
+        perm = perm.upper()
+        res = None
+        result = self.db.execute(""" INSERT INTO perms(name, perm) \
+values(%s, %s) """, (name, perm))
+        if result:
+            res = 1
+            rlog(10, 'users', '%s perm %s added' % (name, perm))
+        return res
+
+    def delperm(self, name, perm):
+        """ add permission """
+        name = name.lower()
+        perm = perm.upper()
+        result = self.db.execute(""" DELETE FROM perms WHERE name = %s AND \
+perm = %s """, (name, perm))
+        if result:
+            rlog(10, 'users', '%s perm %s deleted' % (name, perm))
+            return result
+
+    def permitted(self, userhost, who, what):
+        """ check if (who,what) is in users permit list """
+        name = self.getname(userhost)
+        res = None
+        if name:
+            result = self.db.execute(""" SELECT permit FROM permits WHERE \
+name = %s """, name)
+            if result:
+                for i in result:
+                    if "%s %s" % (who, what) == i[0]:
+                        res = 1
+        return res
+
+    def names(self):
+        """ get names of all users """
+        res = []
+        result = self.db.execute(""" SELECT DISTINCT name FROM userhosts """)
+        if result:
+            for i in result:
+                res.append(i[0])
+        return res
+
+    def merge(self, name, userhost):
+        """ add userhosts to user with name """
+        name = name.lower()
+        if not self.exist(name):
+            return 0
+        res = None
+        result = self.db.execute(""" INSERT INTO userhosts(userhost, name) \
+VALUES (%s, %s) """, (userhost, name))
+        if result:
+            res = 1
+        return res
+
+    def delete(self, name):
+        """ delete user with name """
+        name = name.lower()
+        res = None
+        nr1 = self.db.execute(""" DELETE FROM userhosts WHERE name = %s \
+""", name)
+        nr2 = self.db.execute(""" DELETE FROM perms WHERE name = %s \
+""", name)
+        if nr1 and nr2:
+            res = 1
+        return res
+
+    def status(self, userhost, status):
+        """ check if user with <userhost> has <status> set """
+        name = self.getname(userhost)
+        res = None
+        if name:
+            status = status.upper()
+            result = self.db.execute(""" SELECT status FROM statuses WHERE \
+name = %s """, name)
+            if result:
+                for i in result:
+                    if status == i[0]:
+                        res = 1
+        return res
+
+    def gotperm(self, name, perm):
+        """ check if user had permission """
+        name = name.lower()
+        perm = perm.upper()
+        result = self.db.execute(""" SELECT perm FROM perms WHERE \
+name = %s """, name)
+        if result:
+            for i in result:
+                if i[0] == perm:
+                    return True
+
+    def gotstatus(self, name, status):
+        """ check if user has status """
+        name = name.lower()
+        status = status.upper()
+        result = self.db.execute(""" SELECT status FROM statuses WHERE \
+name = %s """, name)
+        if result:
+            for i in result:
+                if status == i[0]:
+                    return True
+
+    def gotuserhost(self, name, userhost):
+        """ check if user has userhost """
+        name = name.lower()
+        result = self.db.execute(""" SELECT userhost FROM userhosts WHERE \
+name = %s """, name)
+        if result:
+            for i in result:
+                if i[0] == userhost:
+                    return True
+
+    def gotpermit(self, name, permit):
+        """ check if user permits something """
+        name = name.lower()
+        result = self.db.execute(""" SELECT permit FROM permits WHERE \
+name = %s """, name)
+        if result:
+            for i in result:
+                if "%s %s" % permit == i[0]:
+                    return True
+
+    def allowed(self, userhost, perms, log=True):
+        """ check if user with userhosts is allowed to execute perm command """
+        if not type(perms) == types.ListType:
+            perms = [perms, ]
+        if 'ANY' in perms:
+            return 1
+        res = None
+        name = self.getname(userhost)
+        if not name:
+            if log:
+                rlog(10, 'users', '%s userhost denied' % userhost)
+            return res
+        result = self.db.execute(""" SELECT perm FROM perms WHERE \
+name = %s """, name)
+        if result:
+            for i in result:
+                if i[0] in perms:
+                    res = 1
+        if not res:
+            if log:
+                rlog(10, 'users', "%s perm %s denied" % (userhost, perms))
+        return res
+
+    def getemail(self, name):
+        """ get email of user """
+        name = name.lower()
+        email = None
+        email = self.db.execute(""" SELECT email FROM email WHERE name = %s \
+""", name)
+        if email:
+            return email[0][0]
+
+    def setemail(self, name, email):
+        """ set email of user """
+        res = 0
+        try:
+            result = self.db.execute(""" INSERT INTO email(name, email) \
+VALUES (%s, %s) """, (name, email))
+        except:
+            try:
+                result = self.db.execute(""" UPDATE email SET email = %s \
+WHERE name = %s """, (email, name))
+            except:
+                pass
+        if result:
+            res = 1
+        return res
+
+    def addpermall(self, perm): 
+        """ add permission to all users """
+        perm = perm.upper()
+        for i in self.names():
+            try:
+                self.addperm(i, perm)
+            except:
+                pass
+
+    def delpermall(self, perm):
+        """ delete permission from all users """
+        perm = perm.upper()
+        for i in self.names():
+            try:
+                self.delperm(i, perm)
+            except:
+                pass
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/pdod.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/pdod.py
@@ -0,0 +1,75 @@
+# gozerbot/pdod.py
+#
+#
+
+""" pickled dicts of dicts """
+
+__copyright__ = 'this file is in the public domain'
+
+from gozerbot.utils.locking import lockdec
+from persist import Persist
+import thread
+
+pdodlock = thread.allocate_lock()
+locked = lockdec(pdodlock)
+ 
+class Pdod(Persist):
+
+    """ pickled dicts of dicts """
+
+    def __init__(self, filename):
+        Persist.__init__(self, filename)
+        if not self.data:
+            self.data = {}
+
+    def __getitem__(self, name):
+        """ return item with name """
+        if self.data.has_key(name):
+            return self.data[name]
+
+    @locked
+    def save(self):
+        Persist.save(self)
+
+    @locked
+    def __delitem__(self, name):
+        """ delete name item """
+        if self.data.has_key(name):
+            return self.data.__delitem__(name)
+
+    @locked
+    def __setitem__(self, name, item):
+        """ set name item """
+        self.data[name] = item
+
+    def __contains__(self, name):
+        return self.data.__contains__(name)
+
+    @locked
+    def setdefault(self, name, default):
+        """ set default of name """
+        return self.data.setdefault(name, default)
+
+    def has_key(self, name):
+        """ has name key """
+        return self.data.has_key(name)
+
+    def has_key2(self, name1, name2):
+        """ has [name1][name2] key """
+        if self.data.has_key(name1):
+            return self.data[name1].has_key(name2)
+
+    def get(self, name1, name2):
+        """ get data[name1][name2] """
+        try:
+            result = self.data[name1][name2]
+            return result
+        except KeyError:
+            return None
+
+    @locked
+    def set(self, name1, name2, item):
+        """ set name, name2 item """
+        if not self.data.has_key(name1):
+            self.data[name1] = {}
+        self.data[name1][name2] = item
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/__init__.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/__init__.py
@@ -0,0 +1,32 @@
+# gozerbot package
+#
+#
+
+""" gozerbot compatibility package. """
+
+__copyright__ = 'this file is in the public domain'
+
+# ==============
+# IMPORT SECTION
+
+from gozerbot.eggs import loadegg
+import os
+
+# END IMPORT
+# ==========
+
+# ============
+# LOCK SECTION
+
+# no locks
+
+# END LOCK
+# ========
+
+# ============
+# INIT SECTION
+
+loadegg('feedparser', [os.getcwd(), os.getcwd() + os.sep + 'gozernest'], log=False)
+
+# END INIT
+# ========
\ No newline at end of file
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/config.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/config.py
@@ -0,0 +1,313 @@
+# gozerbot/config.py
+#
+#
+
+""" this is where the config dict lives .. use pickle to persist config data
+    .. use this pickle on start until the config file has changed  """
+
+__copyright__ = 'this file is in the public domain'
+
+from gozerbot.datadir import datadir
+import os, pickle, subprocess
+
+# version string
+ver = 'GOZERBOT 0.8.1.1 RELEASE'
+
+def diffdict(dictfrom, dictto):
+    """ check for differences between two dicts """
+    temp = {}
+    for i in dictto.iteritems():
+        if dictfrom.has_key(i[0]):
+            if dictfrom[i[0]] != i[1]:
+                temp.setdefault(i[0], i[1])
+        else:
+            temp.setdefault(i[0], i[1])
+    return temp
+
+class Config(dict):
+
+    """ config object is a dict """
+
+    def __init__(self, ddir, *args, **kw):
+        dict.__init__(self, *args, **kw)
+        self.dir = str(ddir)
+        self['dbtype'] = 'mysql'
+
+    def __getitem__(self, item):
+        """ get config item .. return None if not available"""
+        if not self.has_key(item):
+            return None
+        else:
+            return dict.__getitem__(self, item)
+
+    def set(self, item, value):
+        """ set a config item """
+        dict.__setitem__(self, item, value)
+        self.save()
+
+    def load(self):
+        """ load the config file """
+        frompickle = {}
+        picklemodtime = None
+        # first do reload of the data/config file
+        self.reload()
+        # see if there is a configpickle
+        try:
+            picklemodtime = os.stat(self.dir + os.sep + 'configpickle')[8]
+            configpickle = open(self.dir + os.sep + 'configpickle', 'r')
+            frompickle = pickle.load(configpickle)
+            configpickle.close()
+        except OSError:
+            return
+        except:
+            pass
+        # see if data/config is more recent than the configpickle
+        configmodtime = os.stat(self.dir + os.sep + 'config')[8]
+        if picklemodtime and picklemodtime > configmodtime:
+            # if so update the config dict with the pickled data
+            # a = diffdict(self, frompickle)
+            self.update(frompickle)
+        # set version
+        if self['dbenable']:
+            self['version'] = ver + ' ' + self['dbtype'].upper()
+        else:
+            self['version'] = ver
+    
+    def save(self):
+        """ save config data to pickled file """
+        picklefile = open(self.dir + os.sep + 'configpickle', 'w')
+        pickle.dump(self, picklefile)
+        picklefile.close()
+
+    def reload(self):
+        """ use execfile to reload data/config """
+        try:
+            execfile(self.dir + os.sep + 'config', self)
+        except IOError:
+            self.defaultconfig()
+        # remove builtin data
+        try:
+            del self['__builtins__']
+        except:
+            pass
+        # set version
+        if self['dbenable']:
+            self['version'] = ver + ' ' + self['dbtype'].upper()
+        else:
+            self['version'] = ver
+
+    def defaultconfig(self):
+        """ init default config values if no config file is found """
+        self['loglevel'] = 100
+        self['jabberenable'] = 0
+        self['ircdisable'] = 0
+        self['stripident'] = 1
+        self['owneruserhost'] = ['bart@127.0.0.1', ]
+        self['nick'] = 'gozerbot'
+        self['server'] = 'localhost'
+        self['port'] = 6667
+        self['ipv6'] = 0
+        self['username'] = 'gozerbot'
+        self['realname'] = 'GOZERBOT'
+        self['defaultcc'] = "!"
+        self['nolimiter'] = 0
+        self['quitmsg'] = 'http://gozerbot.org'
+        self['dbenable'] = 0
+        self['udp'] = 0
+        self['partyudp'] = 0
+        self['mainbotname'] = 'main'
+        self['addonallow'] = 0
+        self['allowedchars'] = []
+
+configtxt = """# config
+#
+#
+
+__copyright__ = 'this file is in the public domain'
+
+# gozerdata dir umask
+umask = 0700
+
+# logging level .. the higher this value is the LESS the bot logs
+loglevel = 10
+
+## jabber section:
+
+jabberenable = 0
+jabberowner = 'bartholo@localhost'
+jabberhost = 'localhost'
+jabberuser = 'gozerbot@localhost'
+jabberpass = 'pass'
+jabberoutsleep = 0.1
+
+## irc section:
+
+ircdisable = 0
+
+# stripident .. enable stripping of ident from userhost
+stripident = 1
+
+# userhost of owner .. make sure this matches your client's userhost
+# if it doesn't match you will get an userhost denied message when you
+# try to send commands to the bot
+owneruserhost = ['bart@127.0.0.1', ]
+
+# the nick the bot tries to use, only used if no nick is set
+# otherwise the bot will use the last nick used by the !nick command
+nick = 'gozerbot'
+
+# alternick
+#alternick = 'gozerbot2'
+
+# server to connect to
+server = 'localhost'
+
+# irc port to connect to 
+port = 6667
+
+# ircd password for main bot
+#password = 'bla'
+
+# ipv6
+ipv6 = 0
+
+# bindhost .. uncomment and edit to use
+#bindhost = 'localhost'
+
+# bots username
+username = 'gozerbot'
+
+# realname
+realname = 'GOZERBOT'
+
+# default control character
+defaultcc = "!"
+
+# no limiter
+nolimiter = 0
+
+# quit message
+quitmsg = 'http://gozerbot.org'
+
+# nickserv .. set pass to enable nickserv ident
+nickservpass = ""
+nickservtxt = ['set unfiltered on', ]
+
+## if you want to use a database:
+
+dbenable = 0 # set to 1 to enable
+dbtype = 'mysql' # one of mysql or sqlite
+dbname = "gb_db"
+dbhost = "localhost"
+dbuser = "bart"
+dbpasswd = "mekker2"
+dboldstyle = False # set to True if mysql database is <= 4.1
+
+## if you want to use udp:
+
+# udp
+udp = 0 # set to 1 to enable
+partyudp = 0
+udpipv6 = 0
+udphost = 'localhost'
+udpport = 5500
+udpmasks = ['192.168*', ]
+udpallow = ['127.0.0.1', ]
+udpallowednicks = ['#dunkbots', 'dunker']
+udppassword = 'mekker'
+udpseed = "" # set this to 16 char wide string if you want to encrypt the data
+udpstrip = 1 # strip all chars < char(32)
+udpsleep = 0 # sleep in sendloop .. can be used to delay packet traffic
+
+# tcp
+tcp = 0 # set to 1 to enable
+partytcp = 0
+tcpipv6 = 0
+tcphost = 'localhost'
+tcpport = 5500 
+tcpmasks = ['192.168*', ]
+tcpallow = ['127.0.0.1', ]
+tcpallowednicks = ['#dunkbots', 'dunker', 'dunker@jabber.xs4all.nl']
+tcppassword = 'mekker'
+tcpseed = "bla1234567890bla"
+# set this to 16 char wide string if you want to encrypt the data
+tcpstrip = 1
+tcpsleep = 0   
+
+## other stuff:
+
+# plugin server 
+pluginserver = 'http://gozerbot.org'
+
+# upgradeurl .. only needed if mercurial repo changed
+#upgradeurl = 'http://gozerbot.org/hg/gozerbot'
+
+# mail related
+mailserver = None
+mailfrom = None
+
+# collective boot server
+collboot = "gozerbot.org:8088"
+
+# name of the main bot
+mainbotname = 'main'
+
+# allowed character for strippedtxt
+allowedchars = []
+
+# set to 1 to allow addons
+addonallow = 0
+
+# enable loadlist
+loadlist = 0
+"""
+
+def writeconfig():
+    """ wtite default config file to datadir/config """
+    if not os.path.isfile(datadir + os.sep + 'config'):
+        cfgfile = open(datadir + os.sep + 'config', 'w')
+        cfgfile.write(configtxt)
+        cfgfile.close()
+
+loadlist = """
+core
+misc
+irc
+not
+grep
+reverse
+count
+chanperm
+choice
+fleet
+ignore
+upgrade
+job
+reload
+rest
+tail
+user
+googletalk
+all
+at
+backup
+install
+reload
+tell
+reverse
+to
+underauth
+userstate
+alias
+nickserv
+"""
+
+def writeloadlist():
+    """ write loadlist to datadir """
+    if not os.path.isfile(datadir + os.sep + 'loadlist'):
+        cfgfile = open(datadir + os.sep + 'loadlist', 'w')
+        cfgfile.write(loadlist)
+        cfgfile.close()
+
+# create the config dict and load it from file
+#config = Config(datadir)
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/todo.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/todo.py
@@ -0,0 +1,172 @@
+# plugs/todo.py
+#
+#
+
+from gozerbot.compat.persist import Persist
+import time
+
+class Todoitem:
+
+    """ a todo item """
+
+    def __init__(self, name, descr, ttime=None, duration=None, warnsec=None, \
+priority=None, num=0):
+        self.name = name
+        self.time = ttime
+        self.duration = duration
+        self.warnsec = warnsec
+        self.descr = descr
+        self.priority = priority
+        self.num = num
+
+    def __str__(self):
+        return "name: %s num: %d time: %s duration: %s warnsec: %s \
+description: %s priority: %s" % (self.name, self.num, \
+time.ctime(self.time), self.duration, self.warnsec, self.descr, self.priority)
+
+class Todolist:
+
+    """ a dict faking list of todo items .. index is number """
+
+    def __init__(self):
+        self.max = 0
+        self.data = {}
+
+    def __len__(self):
+        return len(self.data)
+
+    def __getitem__(self, num):
+        return self.data[num]
+
+    def __delitem__(self, num):
+        del self.data[num]
+
+    def __iter__(self):
+        tmplist = self.data.values()
+        tmplist.sort(lambda x, y: cmp(x.priority, y.priority), reverse=True)
+        return tmplist.__iter__()
+
+    def append(self, item):
+        """ add todo item """
+        self.max += 1
+        item.num = self.max
+        self.data[self.max] = item
+
+    def __str__(self):
+        return str(self.data)
+
+
+class Todo(Persist):
+
+    """ Todoos """
+
+    def __init__(self, filename):
+        Persist.__init__(self, filename)
+        if not self.data:
+            return
+        for key in self.data.keys():
+            todoos = self.data[key]
+            for (k, v) in todoos.data.items():
+                v.num = k
+            newd = Todolist()
+            for i in todoos:
+                newd.append(i)
+            self.data[key] = newd
+
+    def size(self):
+        """ return number of todo entries """
+        return len(self.data)
+
+    def get(self, name):
+        """ get todoos of <name> """
+        if self.data.has_key(name):
+            return self.data[name]
+
+    def add(self, name, txt, ttime, warnsec=0):
+        """ add a todo """
+        name = name.lower()
+        if not self.data.has_key(name):
+            self.data[name] = Todolist()
+        self.data[name].append(Todoitem(name, txt.strip(), ttime, \
+warnsec=0-warnsec))
+        self.save()
+        return len(self.data[name])
+
+    def addnosave(self, name, txt, ttime):
+        """ add but don't save """
+        name = name.lower()
+        if not self.data.has_key(name):
+            self.data[name] = Todolist()
+        self.data[name].append(Todoitem(name, txt, ttime))
+
+    def reset(self, name):
+        name = name.lower()
+        if self.data.has_key(name):
+           self.data[name] = Todolist()
+        self.save()
+
+    def delete(self, name, nr):
+        """ delete todo item """
+        if not self.data.has_key(name):
+            return 0
+        todoos = self.data[name]
+        try:
+            if todoos[nr].warnsec:
+                alarmnr = 0 - todoos[nr].warnsec
+                if alarmnr > 0:
+                    alarms.delete(alarmnr)
+            del todoos[nr]
+        except KeyError:
+            return 0
+        self.save()
+        return 1
+
+    def toolate(self, name):
+        """ show if there are any time related todoos that are too late """
+        now = time.time()
+        teller = 0
+        for i in self.data[name]:
+            if i.time < now:
+                teller += 1
+        return teller
+
+    def timetodo(self, name):
+        """ show todoos with time field set """
+        result = []
+        if not self.data.has_key(name):
+            return result
+        for i in self.data[name]:
+            if i.time:
+                result.append(i)
+        return result
+
+    def withintime(self, name, time1, time2):
+        """ show todoos within time frame """
+        result = []
+        if not self.data.has_key(name):
+            return result
+        for i in self.data[name]:
+            if i.time >= time1 and i.time < time2:
+                result.append(i)
+        return result
+
+    def setprio(self, who, itemnr, prio):
+        """ set priority of todo item """
+        try:
+            todoitems = self.get(who)
+            todoitems[itemnr].priority = prio
+            self.save()
+            return 1
+        except (KeyError, TypeError):
+            pass
+
+    def settime(self, who, itemnr, ttime):
+        """ set time of todo item """
+        try:
+            todoitems = self.get(who)
+            todoitems[itemnr].time = ttime
+            self.save()
+            return 1
+        except (KeyError, TypeError):
+            pass
+
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/quote.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/quote.py
@@ -0,0 +1,114 @@
+# plugs/quote.py
+#
+#
+
+from gozerbot.compat.persist import Persist
+
+class Quoteitem(object):
+
+    """ object representing a quote """
+
+    def __init__(self, idnr, txt, nick=None, userhost=None, ttime=None):
+        self.id = idnr
+        self.txt = txt
+        self.nick = nick
+        self.userhost = userhost
+        self.time = ttime
+
+class Quotes(Persist):
+
+    """ list of quotes """
+
+    def __init__(self, fname):
+        Persist.__init__(self, fname)
+        if not self.data:
+            self.data = []
+
+    def size(self):
+        """ return nr of quotes """
+        return len(self.data)
+
+    def add(self, nick, userhost, quote):
+        """ add a quote """
+        id = nextid.next('quotes')
+        item = Quoteitem(id, quote, nick, userhost, \
+time.time())
+        self.data.append(item)
+        self.save()
+        return id
+
+    def addnosave(self, nick, userhost, quote, ttime):
+        """ add quote but don't call save """
+        id = nextid.next('quotes')
+        item = Quoteitem(nextid.next('quotes'), quote, nick, userhost, ttime)
+        self.data.append(item)
+        return id
+
+    def delete(self, quotenr):
+        """ delete quote with id == nr """
+        for i in range(len(self.data)):
+            if self.data[i].id == quotenr:
+                del self.data[i]
+                self.save()
+                return 1
+
+    def random(self):
+        """ get random quote """
+        if not self.data:
+            return None
+        quotenr = random.randint(0, len(self.data)-1)
+        return self.data[quotenr]
+
+    def idquote(self, quotenr):
+        """ get quote by id """
+        for i in self.data:
+            if i.id == quotenr:
+                return i
+
+    def whoquote(self, quotenr):
+        """ get who quoted the quote """
+        for i in self.data:
+            if i.id == quotenr:
+                return (i.nick, i.time)
+
+    def last(self, nr=1):
+        """ get last quote """
+        return self.data[len(self.data)-nr:]
+
+    def search(self, what):
+        """ search quotes """
+        if not self.data:
+            return []
+        result = []
+        andre = re.compile('and', re.I)
+        ands = re.split(andre, what)
+        got = 0
+        for i in self.data:
+            for item in ands:
+                if i.txt.find(item.strip()) == -1:
+                    got = 0
+                    break  
+                got = 1
+            if got:                  
+                result.append(i)
+        return result
+
+    def searchlast(self, what, nr=1):
+        """ search quotes backwards limit to 1"""
+        if not self.data:
+            return []
+        result = []
+        andre = re.compile('and', re.I)
+        ands = re.split(andre, what)
+        got = 0
+        for i in self.data[::-1]:
+            for item in ands:
+                if i.txt.find(item.strip()) == -1:
+                    got = 0
+                    break  
+                got = 1
+            if got:                  
+                result.append(i)
+                got = 0
+        return result
+
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/rss.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/rss.py
@@ -0,0 +1,1124 @@
+# plugs/rss.py
+#
+#
+
+"""the rss mantra is of the following:
+
+as OPER:
+
+. add a url with rss-add
+. start the watcher with rss-watch
+
+now the user can start the bot sending messages of the feed to him with
+rss-start. if an OPER gives a rss-start command in a channel data will be
+send to the channel.
+ """
+
+__copyright__ = 'this file is in the public domain'
+__gendocfirst__ = ['rss-add', 'rss-watch', 'rss-start']
+__gendocskip__ = ['rss-dump', ]
+
+from gozerbot.compat.persist import Persist
+from gozerbot.utils.url import geturl2
+from gozerbot.utils.exception import handle_exception
+from gozerbot.utils.log import rlog
+from gozerbot.utils.locking import lockdec
+from gozerbot.utils.generic import strippedtxt, fromenc
+from gozerbot.utils.url import striphtml, useragent
+from gozerbot.utils.rsslist import rsslist
+from gozerbot.utils.statdict import Statdict
+from gozerbot.fleet import fleet
+from gozerbot.commands import cmnds
+from gozerbot.examples import examples
+from gozerbot.datadir import datadir
+from gozerbot.utils.dol import Dol
+from gozerbot.compat.pdod import Pdod
+from gozerbot.compat.pdol import Pdol
+from gozerbot.plughelp import plughelp
+from gozerbot.periodical import periodical
+from gozerbot.aliases import aliasset
+from gozerbot.users import users
+import feedparser
+import gozerbot.threads.thr as thr
+import time, os, types, thread, socket, xml
+
+plughelp.add('rss', 'manage rss feeds')
+
+savelist = []
+
+def txtindicts(result, d):
+    """ return lowlevel values in (nested) dicts """
+    for j in d.values():
+        if type(j) == types.DictType:
+            txtindicts(result, j) 
+        else:
+            result.append(j)
+
+def checkfordate(data, date):
+    for item in data:
+        try:
+            d = item['updated']
+        except KeyError:
+            continue
+        if date == d:
+            return True
+    return False
+
+rsslock = thread.allocate_lock()
+locked = lockdec(rsslock)
+
+class RssException(Exception):
+    pass
+
+class Rss301(RssException):
+    pass
+
+class RssStatus(RssException):
+    pass
+
+class RssBozoException(RssException):
+    pass
+
+class RssNoSuchItem(RssException):
+    pass
+
+class Rssitem(object):
+
+    """ item that contains rss data """
+
+    def __init__(self, name, url, itemslist, watchchannels=[], \
+sleeptime=30*60):
+        self.name = name
+        self.url = url
+        self.itemslist = list(itemslist)
+        self.watchchannels = list(watchchannels)
+        self.sleeptime = int(sleeptime)
+        self.running = 0
+        self.stoprunning = 0
+        self.botname = None
+
+    def __str__(self):
+        return "name=%s url=%s itemslist=%s watchchannels=%s sleeptime=%s \
+running=%s" % (self.name, self.url, str(self.itemslist), \
+str(self.watchchannels), str(self.sleeptime), self.running)
+
+class Rssdict(Persist):
+
+    """ dict of rss entries """
+
+    def __init__(self, filename):
+        Persist.__init__(self, filename)
+        if not self.data:
+            self.data = {}
+        if self.data.has_key('itemslists'):
+            del self.data['itemslists']
+        self.itemslists = Pdol(filename + '.itemslists')
+        self.handlers = {}
+        self.results = {}
+        self.jobids = {}
+        self.rawresults = {}
+        self.results = Dol()
+        self.modified = {}
+        self.etag = {}
+        self.markup = Pdod(filename + '.markup')
+
+    def size(self):
+        """ return number of rss entries """
+        return len(self.data)
+
+    @locked
+    def add(self, name, url):
+        """ add rss item """
+        rlog(10, 'rss', 'adding %s %s' % (name, url))
+        self.data[name] = Rssitem(name, url, ['title', ])
+        self.save()
+
+    @locked
+    def delete(self, name):
+        """ delete rss item by name """
+        target = None
+        for j, i in self.data.iteritems():
+           if i.name == name:
+               target = i
+        if target:
+            try:
+                target.running = 0
+                del self.data[name]
+                self.save()
+            except:
+                pass
+
+    def byname(self, name):
+        """ return rss item by name """
+        try:
+            return self.data[name]
+        except:
+            return
+
+    def getdata(self, name):
+        """ get data of rss feed """
+        rssitem = self.byname(name)
+        if rssitem == None:
+            raise RssNoSuchItem("no %s rss item found" % name)
+        try:
+            modified = self.modified[name]
+        except KeyError:
+            modified = None
+        try:
+            etag = self.etag[name]
+        except KeyError:
+            etag = None
+        result = feedparser.parse(rssitem.url, modified=modified, etag=etag, \
+agent=useragent())
+        if result and result.has_key('bozo_exception'):
+            rlog(1, 'rss', '%s bozo_exception: %s' % (name, \
+result['bozo_exception']))
+            #raise RssStatus(result['bozo_exception'])
+        try:
+            status = result.status
+        except AttributeError:
+            status = 200
+        if status != 200 and status != 301 and status != 302 and status != 304:
+            raise RssStatus(status)
+        try:
+            self.modified[name] = result.modified
+        except AttributeError:
+            pass
+        try:
+            self.etag[name] = result.etag
+        except AttributeError:
+            pass
+        if status == 304:
+            return self.rawresults[name]
+        else:
+            self.rawresults[name] = result.entries
+        return result.entries
+
+class Rsswatcher(Rssdict):
+
+    """ rss watchers """ 
+
+    def __init__(self, filename):
+        Rssdict.__init__(self, filename)
+        
+    def sync(self, name):
+        result = self.getdata(name)
+        if result:
+            self.results[name] = result
+
+    def changeinterval(self, name, interval):
+        periodical.changeinterval(self.jobids[name], interval)
+
+    @locked
+    def startwatchers(self):
+        """ start watcher threads """
+        for j, i in self.data.iteritems():
+            if i.running:
+                thr.start_new_thread(self.watch, (i.name, ))
+
+    @locked
+    def stopwatchers(self):
+        """ stop all watcher threads """
+        for j, i in self.data.iteritems():
+            if i.running:
+                i.stoprunning = 1
+        periodical.killgroup('rss')
+
+    def dowatch(self, name, sleeptime=1800):
+        rssitem = self.byname(name)
+        if not rssitem:
+            rlog(10, 'rss', "no %s rss item available" % name)
+            return
+        while 1:
+            try:
+                self.watch(name)
+            except Exception, ex:
+                rlog(100, 'rss', '%s feed error: %s' % (name, str(ex)))
+                rlog(100, 'rss', '%s sleeping %s seconds for retry' % \
+(name, sleeptime))
+                time.sleep(sleeptime)
+                if not rssitem.running:
+                    break
+            else:
+                break
+
+    def makeresult(self, name, target, data):
+        res = []
+        for j in data:
+            tmp = {}
+            if not self.itemslists[(name, target)]:
+                return []
+            for i in self.itemslists[(name, target)]:
+                try:
+                    tmp[i] = unicode(j[i])
+                except KeyError:
+                    continue
+            res.append(tmp)
+        return res
+
+    def watch(self, name):
+        """ start a watcher thread """
+        # get basic data
+        rlog(10, 'rss', 'trying %s rss feed watcher' % name)
+        try:
+            result = self.getdata(name)
+        except RssException, ex:
+            rlog(10, 'rss', "%s error: %s" % (name, str(ex)))
+            result = []
+        rssitem = self.byname(name)
+        if not rssitem:
+            raise RssNoItem()
+        # poll every sleeptime seconds
+        self.results[name] = result
+        pid = periodical.addjob(rssitem.sleeptime, 0, self.peek, name, name)
+        self.jobids[name] = pid
+        rlog(10, 'rss', 'started %s rss watch' % name)
+
+    def makeresponse(self, name, res, channel, sep="\002||\002"):
+        # loop over result to make a response
+        result = ""
+        itemslist = self.itemslists[(name, channel)]
+        if not itemslist:
+            rssitem = self.byname(name)
+            if not rssitem:
+                return "no %s rss item" % name
+            else:
+                self.itemslists.extend((name, channel), rssitem.itemslist)
+                self.itemslists.save()
+        for j in res:
+            resultstr = ""
+            for i in self.itemslists[(name, channel)]:
+                try:
+                    item = unicode(j[i])
+                    if not item:
+                        continue
+                    if item.startswith('http://'):
+                        resultstr += "<%s> - " % item
+                    else:
+                        resultstr += "%s - " % striphtml(item)
+                except KeyError:
+                    continue
+            resultstr = resultstr[:-3]
+            if resultstr:
+                result += "%s %s " % (resultstr, sep)
+        return result[:-6]
+
+    def peek(self, name, *args):
+        rssitem = self.byname(name)
+        if not rssitem or not rssitem.running or rssitem.stoprunning:
+            return
+        try:
+            try:
+                res = self.getdata(name)
+            except socket.timeout:
+                rlog(10, 'rss', 'socket timeout of %s' % name)
+                return
+            except RssException, ex:
+                rlog(10, 'rss', '%s error: %s' % (name, str(ex)))
+                return
+            if not res:
+                return
+            res2 = []
+            for j in res:
+                try:
+                    d = j['date']
+                except KeyError:
+                    if j not in self.results[name]:
+                        self.results[name].append(j)
+                        res2.append(j)
+                else:
+                    if not checkfordate(self.results[name], d):
+                        self.results[name].append(j)
+                        res2.append(j)
+            if not res2:
+                return
+            for item in rssitem.watchchannels:
+                try:
+                    (botname, channel) = item
+                except:
+                    rlog(10, 'rss', '%s is not in the format \
+(botname,channel)' % str(item))
+                bot = fleet.byname(botname)
+                if not bot:
+                    continue
+                if self.markup.get((name, channel), 'all-lines'):
+                    for i in res2:
+                        response = self.makeresponse(name, [i, ], channel)
+                        bot.say(channel, "\002%s\002: %s" % \
+(rssitem.name, response), fromm=rssitem.name)
+                else:
+                    sep =  self.markup.get((name, channel), 'seperator')
+                    if sep:
+                        response = self.makeresponse(name, res2, channel, \
+sep=sep)
+                    else:
+                        response = self.makeresponse(name, res2, channel)
+                    bot.say(channel, "\002%s\002: %s" % (rssitem.name, \
+response), fromm=rssitem.name)
+        except Exception, ex:
+            handle_exception(txt=name)
+
+    @locked
+    def stopwatch(self, name):
+        """ stop watcher thread """
+        for i, j in self.data.iteritems():
+            if i == name:
+                j.running = 0
+                try:
+                    del self.results[name]
+                except KeyError:
+                    pass
+                self.save()
+                try:
+                    periodical.killjob(self.jobids[i])
+                except KeyError:
+                    pass
+                return 1
+
+    @locked
+    def list(self):
+        """ return of rss names """
+        feeds = self.data.keys()
+        return feeds
+
+    @locked
+    def runners(self):	
+        """ show names/channels of running watchers """
+        result = []
+        for j, i in self.data.iteritems():
+            if i.running == 1 and not i.stoprunning: 
+                result.append((i.name, i.watchchannels))
+        return result
+
+    @locked
+    def feeds(self, botname, channel):
+        """ show names/channels of running watcher """
+        result = []
+        for j, i in self.data.iteritems():
+            if (botname, channel) in i.watchchannels:
+                result.append(i.name)
+        return result
+
+    @locked
+    def url(self, name):
+        """ return url of rssitem """
+        for j, i in self.data.iteritems():
+            if i.name == name:
+                return i.url
+
+    def scan(self, name):
+        """ scan a rss url for used xml items """
+        try:
+            result = self.getdata(name)
+        except RssException, ex:
+            rlog(10, 'rss', '%s error: %s' % (name, str(ex)))
+            return
+        if not result:
+            return
+        keys = []
+        for item in self.rawresults[name]:
+             for key in item.keys():
+                 keys.append(key)            
+        statdict = Statdict()
+        for key in keys:
+            statdict.upitem(key)
+        return statdict.top()  
+
+    def search(self, name, item, search):
+        res = []
+        for result in self.rawresults[name]:
+            try:
+                title = result['title']
+                txt = result[item]
+            except KeyError:
+                continue
+            if search in title.lower():
+                if txt:
+                   res.append(txt)
+        return res
+
+    def searchall(self, item, search):
+        res = []
+        for name, results in self.rawresults.iteritems():
+            for result in results:
+                try:
+                    title = result['title']
+                    txt = result[item]
+                except KeyError:
+                    continue
+                if search in title.lower():
+                    if txt:
+                        res.append("%s: %s" % (name, txt))
+        return res
+
+    def all(self, name, item):
+        res = []
+        for result in self.rawresults[name]:
+            try:
+                txt = result[item]
+            except KeyError:
+                continue
+            if txt:
+                res.append(txt)
+        return res
+
+watcher = Rsswatcher(datadir + os.sep + 'old' + os.sep + 'rss')
+
+def init(): 
+    """ called after plugin import """
+    thr.start_new_thread(watcher.startwatchers, ())
+    return 1
+
+def size():
+    """ return number of watched rss entries """
+    return watcher.size()
+
+def shutdown():
+    """ called before plugin import """
+    watcher.stopwatchers()
+    return 1
+    
+def handle_rssadd(bot, ievent):
+    """ rss-add <name> <url> .. add a rss item """
+    try:
+        (name, url) = ievent.args
+    except ValueError:
+        ievent.missing('<name> <url>')
+        return
+    watcher.add(name, url)
+    ievent.reply('rss item added')
+
+cmnds.add('rss-add', handle_rssadd, 'OPER')
+examples.add('rss-add', 'rss-add <name> <url> to the rsswatcher', 'rss-add \
+gozerbot http://gozerbot.org/hg/gozerbot/?cmd=changelog;style=rss')
+
+def handle_rssdel(bot, ievent):
+    """ rss-del <name> .. delete a rss item """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    if watcher.byname(name):
+        watcher.stopwatch(name)
+        watcher.delete(name)
+        ievent.reply('rss item deleted')
+    else:
+        ievent.reply('there is no %s rss item' % name)
+
+cmnds.add('rss-del', handle_rssdel, 'OPER')
+examples.add('rss-del', 'rss-del <name> .. remove <name> from the \
+rsswatcher', 'rss-del mekker')
+
+def handle_rsswatch(bot, ievent):
+    """ rss-watch <name> .. start watcher thread """
+    if not ievent.channel:
+        ievent.reply('no channel provided')
+    try:
+        name, sleepsec = ievent.args
+    except ValueError:
+        try:
+            name = ievent.args[0]
+            sleepsec = 1800
+        except IndexError:
+            ievent.missing('<name> [secondstosleep]')
+            return
+    try:
+        sleepsec = int(sleepsec)
+    except ValueError:
+        ievent.reply("time to sleep needs to be in seconds")
+        return
+    rssitem = watcher.byname(name)
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss object" % name)
+        return
+    got = None
+    if not rssitem.running:
+        rssitem.sleeptime = sleepsec
+        rssitem.running = 1
+        rssitem.stoprunning = 0
+        got = True
+        watcher.save()
+        try:
+            watcher.watch(name)
+        except Exception, ex:
+            ievent.reply(str(ex))
+            return
+    if got:
+        watcher.save()
+        ievent.reply('watcher started')
+    else:
+        ievent.reply('already watching %s' % name)
+
+cmnds.add('rss-watch', handle_rsswatch, 'OPER')
+examples.add('rss-watch', 'rss-watch <name> [seconds to sleep] .. go \
+watching <name>', '1) rss-watch gozerbot 2) rss-watch gozerbot 600')
+
+def handle_rssstart(bot, ievent):
+    """ rss-start <name> .. start a rss feed to a user """
+    if not ievent.rest:
+       ievent.missing('<feed name>')
+       return
+    name = ievent.rest
+    rssitem = watcher.byname(name)
+    if bot.jabber:
+        target = ievent.userhost
+    else:
+        if users.allowed(ievent.userhost, ['OPER', ]) and not ievent.msg:
+            target = ievent.channel
+        else:
+            target = ievent.nick
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss object" % name)
+        return
+    if not rssitem.running:
+        ievent.reply('%s watcher is not running' % name)
+        return
+    if (bot.name, target) in rssitem.watchchannels:
+        ievent.reply('we are already monitoring %s on (%s,%s)' % \
+(name, bot.name, target))
+        return
+    rssitem.watchchannels.append((bot.name, target))
+    for item in rssitem.itemslist:
+        watcher.itemslists.adduniq((name, target), item)
+    watcher.save()
+    ievent.reply('%s started' % name)
+
+cmnds.add('rss-start', handle_rssstart, ['RSS', 'USER'])
+examples.add('rss-start', 'rss-start <name> .. start a rss feed \
+(per user/channel) ', 'rss-start gozerbot')
+
+def handle_rssstop(bot, ievent):
+    """ rss-start <name> .. start a rss feed to a user """
+    if not ievent.rest:
+       ievent.missing('<feed name>')
+       return
+    name = ievent.rest
+    rssitem = watcher.byname(name)
+    if bot.jabber:
+        target = ievent.userhost
+    else:
+        if users.allowed(ievent.userhost, ['OPER', ]) and not ievent.msg:
+            target = ievent.channel
+        else:
+            target = ievent.nick
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss feed" % name)
+        return
+    if not rssitem.running:
+        ievent.reply('%s watcher is not running' % name)
+        return
+    if not (bot.name, target) in rssitem.watchchannels:
+        ievent.reply('we are not monitoring %s on (%s,%s)' % \
+(name, bot.name, target))
+        return
+    rssitem.watchchannels.remove((bot.name, target))
+    watcher.save()
+    ievent.reply('%s stopped' % name)
+
+cmnds.add('rss-stop', handle_rssstop, ['RSS', 'USER'])
+examples.add('rss-stop', 'rss-stop <name> .. stop a rss feed \
+(per user/channel) ', 'rss-stop gozerbot')
+
+def handle_rsschannels(bot, ievent):
+    """ rss-channels <name> .. show channels of rss feed """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing("<name>") 
+        return
+    rssitem = watcher.byname(name)
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss object" % name)
+        return
+    if not rssitem.watchchannels:
+        ievent.reply('%s is not in watch mode' % name)
+        return
+    result = []
+    for i in rssitem.watchchannels:
+        result.append(str(i))
+    ievent.reply("channels of %s: " % name, result, dot=True)
+
+cmnds.add('rss-channels', handle_rsschannels, 'OPER')
+examples.add('rss-channels', 'rss-channels <name> .. show channels', \
+'rss-channels gozerbot')
+
+def handle_rssaddchannel(bot, ievent):
+    """ rss-addchannel <name> [<botname>] <channel> .. add a channel to \
+        rss item """
+    try:
+        (name, botname, channel) = ievent.args
+    except ValueError:
+        try:
+            (name, channel) = ievent.args
+            botname = bot.name
+        except ValueError:
+            try:
+                name = ievent.args[0]
+                botname = bot.name
+                channel = ievent.channel
+            except IndexError:
+                ievent.missing('<name> [<botname>] <channel>')
+                return
+    rssitem = watcher.byname(name)
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss object" % name)
+        return
+    if not rssitem.running:
+        ievent.reply('%s watcher is not running' % name)
+        return
+    if (botname, channel) in rssitem.watchchannels:
+        ievent.reply('we are already monitoring %s on (%s,%s)' % \
+(name, botname, channel))
+        return
+    rssitem.watchchannels.append((botname, channel))
+    watcher.save()
+    ievent.reply('%s added to %s rss item' % (channel, name))
+
+cmnds.add('rss-addchannel', handle_rssaddchannel, 'OPER')
+examples.add('rss-addchannel', 'rss-addchannel <name> [<botname>] <channel> \
+..add <channel> or <botname> <channel> to watchchannels of <name>', \
+'1) rss-addchannel gozerbot #dunkbots 2) rss-addchannel gozerbot main \
+#dunkbots')
+
+def handle_rssadditem(bot, ievent):
+    try:
+        (name, item) = ievent.args
+    except ValueError:
+        ievent.missing('<name> <item>')
+        return
+    if bot.jabber or users.allowed(ievent.userhost, ['OPER', ]):
+        target = ievent.channel.lower()
+    else:
+        target = ievent.nick.lower()
+    if not watcher.byname(name):
+        ievent.reply("we don't have a %s feed" % name)
+        return
+    watcher.itemslists.adduniq((name, target), item)
+    watcher.itemslists.save()
+    #watcher.sync(name)
+    ievent.reply('%s added to (%s,%s) itemslist' % (item, name, target))
+
+cmnds.add('rss-additem', handle_rssadditem, ['RSS', 'USER'])
+examples.add('rss-additem', 'add a token to the itemslist (per user/channel)',\
+ 'rss-additem gozerbot link')
+
+def handle_rssdelitem(bot, ievent):
+    try:
+        (name, item) = ievent.args
+    except ValueError:
+        ievent.missing('<name> <item>')
+        return
+    if users.allowed(ievent.userhost, ['OPER', 'RSS']):
+        target = ievent.channel.lower()
+    else:
+        target = ievent.nick.lower()
+    if not watcher.byname(name):
+        ievent.reply("we don't have a %s feed" % name)
+        return
+    try:
+        watcher.itemslists.remove((name, target), item)
+        watcher.itemslists.save()
+    except RssNoSuchItem:
+        ievent.reply("we don't have a %s rss feed" % name)
+        return
+    ievent.reply('%s removed from (%s,%s) itemslist' % (item, name, target))
+
+cmnds.add('rss-delitem', handle_rssdelitem, ['RSS', 'USER'])
+examples.add('rss-delitem', 'remove a token from the itemslist \
+(per user/channel)', 'rss-delitem gozerbot link')
+
+def handle_rssmarkup(bot, ievent):
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    if users.allowed(ievent.userhost, ['OPER', ]):
+        target = ievent.channel.lower()
+    else:
+        target = ievent.nick.lower()
+    try:
+        ievent.reply(str(watcher.markup[(name, target)]))
+    except KeyError:
+        pass
+
+cmnds.add('rss-markup', handle_rssmarkup, ['RSS', 'USER'])
+examples.add('rss-markup', 'show markup list for a feed (per user/channel)', \
+'rss-markup gozerbot')
+
+def handle_rssaddmarkup(bot, ievent):
+    try:
+        (name, item, value) = ievent.args
+    except ValueError:
+        ievent.missing('<name> <item> <value>')
+        return
+    if users.allowed(ievent.userhost, ['OPER', ]):
+        target = ievent.channel.lower()
+    else:
+        target = ievent.nick.lower()
+    try:
+        watcher.markup.set((name, target), item, value)
+        watcher.markup.save()
+        ievent.reply('%s added to (%s,%s) markuplist' % (item, name, target))
+    except KeyError:
+        ievent.reply("no (%s,%s) feed available" % (name, target))
+
+cmnds.add('rss-addmarkup', handle_rssaddmarkup, ['RSS', 'USER'])
+examples.add('rss-addmarkup', 'add a markup option to the markuplist \
+(per user/channel)', 'rss-addmarkup gozerbot noseperator 1')
+
+def handle_rssdelmarkup(bot, ievent):
+    try:
+        (name, item) = ievent.args
+    except ValueError:
+        ievent.missing('<name> <item>')
+        return
+    if users.allowed(ievent.userhost, ['OPER', 'RSS']):
+        target = ievent.channel.lower()
+    else:
+        target = ievent.nick.lower()
+    try:
+        del watcher.markup[(name, target)][item]
+    except (KeyError, TypeError):
+        ievent.reply("can't remove %s from %s feed's markup" %  (item, name))
+        return
+    watcher.markup.save()
+    ievent.reply('%s removed from (%s,%s) markuplist' % (item, name, target))
+
+cmnds.add('rss-delmarkup', handle_rssdelmarkup, ['RSS', 'USER'])
+examples.add('rss-delmarkup', 'remove a markup option from the markuplist \
+(per user/channel)', 'rss-delmarkup gozerbot noseperator')
+
+def handle_rssdelchannel(bot, ievent):
+    """ rss-delchannel <name> [<botname>] <channel> .. delete channel \
+        from rss item """
+    botname = None
+    try:
+        (name, botname, channel) = ievent.args
+    except ValueError:
+        try:
+            (name, channel) = ievent.args
+            botname = bot.name
+        except ValueError:
+            try:
+                name = ievent.args[0]
+                botname = bot.name
+                channel = ievent.channel
+            except IndexError:
+                ievent.missing('<name> [<botname>] [<channel>]')
+                return
+    rssitem = watcher.byname(name)
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss object" % name)
+        return
+    if (botname, channel) not in rssitem.watchchannels:
+        ievent.reply('we are not monitoring %s on (%s,%s)' % \
+(name, botname, channel))
+        return
+    rssitem.watchchannels.remove((botname, channel))
+    ievent.reply('%s removed from %s rss item' % (channel, name))
+    watcher.save()
+
+cmnds.add('rss-delchannel', handle_rssdelchannel, 'OPER')
+examples.add('rss-delchannel', 'rss-delchannel <name> [<botname>] \
+[<channel>] .. delete <channel> or <botname> <channel> from watchchannels of \
+<name>', '1) rss-delchannel gozerbot #dunkbots 2) rss-delchannel gozerbot \
+main #dunkbots')
+
+def handle_rssstopwatch(bot, ievent):
+    """ rss-stopwatch <name> .. stop a watcher thread """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    rssitem = watcher.byname(name)
+    if not rssitem:
+        ievent.reply("there is no %s rssitem" % name)
+        return
+    if not watcher.stopwatch(name):
+        ievent.reply("can't stop %s watcher" % name)
+        return
+    ievent.reply('stopped %s rss watch' % name)
+
+cmnds.add('rss-stopwatch', handle_rssstopwatch, 'OPER')
+examples.add('rss-stopwatch', 'rss-stopwatch <name> .. stop polling <name>', \
+'rss-stopwatch gozerbot')
+
+def handle_rsssleeptime(bot, ievent):
+    """ rss-sleeptime <name> .. get sleeptime of rss item """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    rssitem = watcher.byname(name)
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss item" % name)
+        return
+    try:
+        ievent.reply('sleeptime for %s is %s seconds' % (name, \
+str(rssitem.sleeptime)))
+    except AttributeError:
+        ievent.reply("can't get sleeptime for %s" % name)
+
+cmnds.add('rss-sleeptime', handle_rsssleeptime, 'OPER')
+examples.add('rss-sleeptime', 'rss-sleeptime <name> .. get sleeping time \
+for <name>', 'rss-sleeptime gozerbot')
+
+def handle_rsssetsleeptime(bot, ievent):
+    """ rss-setsleeptime <name> <seconds> .. set sleeptime of rss item """
+    try:
+        (name, sec) = ievent.args
+        sec = int(sec)
+    except ValueError:
+        ievent.missing('<name> <seconds>')
+        return
+    if sec < 60:
+        ievent.reply('min is 60 seconds')
+        return
+    rssitem = watcher.byname(name)
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss item" % name)
+        return
+    rssitem.sleeptime = sec
+    if rssitem.running:
+        watcher.changeinterval(name, sec)
+    watcher.save()
+    ievent.reply('sleeptime set')
+
+cmnds.add('rss-setsleeptime', handle_rsssetsleeptime, 'OPER')
+examples.add('rss-setsleeptime', 'rss-setsleeptime <name> <seconds> .. set \
+sleeping time for <name> .. min 60 sec', 'rss-setsleeptime gozerbot 600')
+
+def handle_rssget(bot, ievent):
+    """ rss-get <name> .. fetch rss data """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    rssitem = watcher.byname(name)
+    if rssitem == None:
+        ievent.reply("we don't have a %s rss item" % name)
+        return
+    try:
+        result = watcher.getdata(name)
+    except Exception, ex:
+        ievent.reply('%s error: %s' % (name, str(ex)))
+        return
+    response = watcher.makeresponse(name, result, ievent.channel)
+    if response:
+        ievent.reply("results of %s: %s" % (name, response))
+    else:
+        ievent.reply("can't match watcher data")
+
+cmnds.add('rss-get', handle_rssget, ['RSS', 'USER', 'ANON'])
+examples.add('rss-get', 'rss-get <name> .. get data from <name>', \
+'rss-get gozerbot')
+
+def handle_rssrunning(bot, ievent):
+    """ rss-running .. show which watchers are running """
+    result = watcher.runners()
+    resultlist = []
+    teller = 1
+    for i in result:
+        resultlist.append("%s %s" % (i[0], i[1]))
+    if resultlist:
+        ievent.reply("running rss watchers: ", resultlist, nr=1)
+    else:
+        ievent.reply('nothing running yet')
+
+cmnds.add('rss-running', handle_rssrunning, ['RSS', 'USER'])
+examples.add('rss-running', 'rss-running .. get running rsswatchers', \
+'rss-running')
+
+def handle_rsslist(bot, ievent):
+    """ rss-list .. return list of rss items """
+    result = watcher.list()
+    result.sort()
+    if result:
+        ievent.reply("rss items: ", result, dot=True)
+    else:
+        ievent.reply('no rss items yet')
+
+cmnds.add('rss-list', handle_rsslist, ['RSS', 'USER', 'ANON'])
+examples.add('rss-list', 'get list of rss items', 'rss-list')
+
+def handle_rssurl(bot, ievent):
+    """ rss-url <name> .. return url of rss item """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    result = watcher.url(name)
+    ievent.reply('url of %s: %s' % (name, result))
+
+cmnds.add('rss-url', handle_rssurl, ['RSS', 'USER', 'ANON'])
+examples.add('rss-url', 'rss-url <name> .. get url from rssitem with \
+<name>', 'rss-url gozerbot')
+
+def handle_rssitemslist(bot, ievent):
+    """ rss-itemslist <name> .. show itemslist of rss item """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    try:
+        itemslist = watcher.itemslists[(name, ievent.channel.lower())]
+    except KeyError:
+        ievent.reply("no itemslist set for (%s, %s)" % (name, \
+ievent.channel.lower()))
+        return
+    ievent.reply("itemslist of (%s, %s): " % (name, ievent.channel.lower()), \
+itemslist, dot=True)
+
+cmnds.add('rss-itemslist', handle_rssitemslist, ['RSS', 'USER'])
+examples.add('rss-itemslist', 'rss-itemslist <name> .. get itemslist of \
+<name> ', 'rss-itemslist gozerbot')
+
+def handle_rssscan(bot, ievent):
+    """ rss-scan <name> .. scan rss item for used xml items """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    if not watcher.byname(name):
+        ievent.reply('no %s feeds available' % name)
+        return
+    try:
+        result = watcher.scan(name)
+    except Exception, ex:
+        ievent.reply(str(ex))
+        return
+    if result == None:
+        ievent.reply("can't get data for %s" % name)
+        return
+    res = []
+    for i in result:
+        res.append("%s=%s" % i)
+    ievent.reply("tokens of %s: " % name, res)
+
+cmnds.add('rss-scan', handle_rssscan, 'OPER')
+examples.add('rss-scan', 'rss-scan <name> .. get possible items of <name> ', \
+'rss-scan gozerbot')
+
+def handle_rsssync(bot, ievent):
+    """ rss-sync <name> .. sync rss item data """
+    try:
+        name = ievent.args[0]
+    except IndexError:
+        ievent.missing('<name>')
+        return
+    try:
+        result = watcher.sync(name)
+        ievent.reply('%s synced' % name)
+    except Exception, ex:
+        ievent.reply("%s error: %s" % (name, str(ex)))
+
+cmnds.add('rss-sync', handle_rsssync, 'OPER')
+examples.add('rss-sync', 'rss-sync <name> .. sync data of <name>', \
+'rss-sync gozerbot')
+
+def handle_rssfeeds(bot, ievent):
+    """ rss-feeds <channel> .. show what feeds are running in a channel """
+    try:
+        channel = ievent.args[0]
+    except IndexError:
+        channel = ievent.printto
+    try:
+        result = watcher.feeds(bot.name, channel)
+        if result:
+            ievent.reply("feeds running in %s: " % channel, result, dot=True)
+        else:
+            ievent.reply('%s has no feeds running' % channel)
+    except Exception, ex:
+        ievent.reply("ERROR: %s" % str(ex))
+
+cmnds.add('rss-feeds', handle_rssfeeds, ['USER', 'RSS'])
+examples.add('rss-feeds', 'rss-feeds <name> .. show what feeds are running \
+in a channel', '1) rss-feeds 2) rss-feeds #dunkbots')
+
+def handle_rsslink(bot, ievent):
+    try:
+        feed, rest = ievent.rest.split(' ', 1)
+    except ValueError:
+        ievent.missing('<feed> <words to search>')
+        return
+    rest = rest.strip().lower()
+    try:
+        res = watcher.search(feed, 'link', rest)
+        if not res:
+            res = watcher.search(feed, 'feedburner:origLink', rest)
+        if res:
+            ievent.reply(res, dot=" \002||\002 ")
+    except KeyError:
+        ievent.reply('no %s feed data available' % feed)
+        return
+
+cmnds.add('rss-link', handle_rsslink, ['RSS', 'USER'])
+examples.add('rss-link', 'give link of item which title matches search key', \
+'rss-link gozerbot gozer')
+
+def handle_rssdescription(bot, ievent):
+    try:
+        feed, rest = ievent.rest.split(' ', 1)
+    except ValueError:
+        ievent.missing('<feed> <words to search>')
+        return
+    rest = rest.strip().lower()
+    res = ""
+    try:
+        ievent.reply(watcher.search(feed, 'description', rest), \
+dot=" \002||\002 ")
+    except KeyError:
+        ievent.reply('no %s feed data available' % feed)
+        return
+
+cmnds.add('rss-description', handle_rssdescription, ['RSS', 'USER'])
+examples.add('rss-description', 'give description of item which title \
+matches search key', 'rss-description gozerbot gozer')
+
+def handle_rssall(bot, ievent):
+    try:
+        feed = ievent.args[0]
+    except IndexError:
+        ievent.missing('<feed>')
+        return
+    try:
+        ievent.reply(watcher.all(feed, 'title'), dot=" \002||\002 ")
+    except KeyError:
+        ievent.reply('no %s feed data available' % feed)
+        return
+
+cmnds.add('rss-all', handle_rssall, ['RSS', 'USER'])
+examples.add('rss-all', "give titles of a feed", 'rss-all gozerbot')
+
+def handle_rsssearch(bot, ievent):
+    try:
+        txt = ievent.args[0]
+    except IndexError:
+        ievent.missing('<txt>')
+        return
+    try:        
+        ievent.reply(watcher.searchall('title', txt), dot=" \002||\002 ")
+    except KeyError:
+        ievent.reply('no %s feed data available' % feed)
+        return
+
+cmnds.add('rss-search', handle_rsssearch, ['RSS', 'USER'])
+examples.add('rss-search', "search titles of all current feeds", \
+'rss-search goz')
+
+def handle_rssdump(bot, ievent):
+    try:
+        ievent.reply(str(watcher.rawresults[ievent.rest]))
+    except Exception, ex:
+        ievent.reply(str(ex))
+
+cmnds.add('rss-dump', handle_rssdump, 'OPER')
+examples.add('rss-dump', 'dump cached rss data', 'rss-dump')
--- gozerbot-0.99.1.orig/build/lib/gozerbot/compat/pdol.py
+++ gozerbot-0.99.1/build/lib/gozerbot/compat/pdol.py
@@ -0,0 +1,72 @@
+# gozerbot/pdol.py
+#
+#
+
+""" pickled dict of lists """
+
+__copyright__ = 'this file is in the public domain'
+
+from persist import Persist
+ 
+class Pdol(Persist):
+
+    """ pickled dict of lists """
+
+    def __init__(self, fname):
+        Persist.__init__(self, fname)
+        if not self.data:
+            self.data = {}
+
+    def __iter__(self, name):
+        return self.data[name].__iter__()
+ 
+    def __getitem__(self, item):
+        if self.data.has_key(item):
+            return self.data[item]
+
+    def __delitem__(self, item):
+        if self.data.has_key(item):
+            self.data.__delitem__(item)
+            return 1
+
+    def __setitem__(self, item, what):
+        if self.data.has_key(item):
+            self.data[item].append(what)
+        else:
+            self.data[item] = [what]
+        return 1
+
+    def add(self, item, what):
+        """ add what to items list """
+        return self.__setitem__(item, what)
+
+    def adduniq(self, item, what):
+        """ add what to items list if item not yet added """
+        if not self.data.has_key(item):
+            self.new(item)
+        if what not in self.data[item]:
+            return self.__setitem__(item, what)
+
+    def get(self, item):
+        """ get items list """
+        return self.__getitem__(item)
+
+    def new(self, what):
+        """ reset list of what """
+        self.data[what] = []
+
+    def delete(self, item, what):
+        """ remove what from item's list """
+        del self.data[item][what]
+
+    def extend(self, item, what):
+        if not self.data.has_key(item):
+            self.new(item)
+        self.data[item].extend(what)
+
+    def remove(self, item, what):
+        try:
+            self.data[item].remove(what)
+            return 1
+        except (ValueError, KeyError):
+            return 0
--- gozerbot-0.99.1.orig/build/lib/gozerbot/threads/thr.py
+++ gozerbot-0.99.1/build/lib/gozerbot/threads/thr.py
@@ -0,0 +1,163 @@
+# gozerbot/thr.py
+#
+#
+
+""" own threading wrapper. """
+
+__copyright__ = 'this file is in the public domain'
+
+# gozerbot imports
+from gozerbot.stats import stats
+from gozerbot.utils.exception import handle_exception
+from gozerbot.utils.log import rlog
+from gozerbot.utils.locking import lockdec
+from gozerbot.config import config
+
+# basic imports
+import threading, re, time, thread
+
+dontshowthreads = ['Periodical.runjob', ]
+
+# regular expression to determine thread name
+methodre = re.compile('method\s+(\S+)', re.I)
+funcre = re.compile('function\s+(\S+)', re.I)
+
+threadlock = thread.allocate_lock()
+locked = lockdec(threadlock)
+
+class Botcommand(threading.Thread):
+
+    """ thread for running bot commands. """
+
+    def __init__(self, group, target, name, args, kwargs):
+        threading.Thread.__init__(self, None, target, name, args, kwargs)
+        self.name = name
+        self.ievent = args[1]
+        self.setDaemon(True)
+
+    def join(self):
+        threading.Thread.join(self)
+
+    def run(self):
+
+        """ run the bot command. """
+
+        try:
+            rlog(10, 'thr', 'running bot command thread %s' % self.name) 
+            stats.up('threads', self.name)
+            result = threading.Thread.run(self)
+
+            if self.ievent.closequeue:
+                rlog(4, 'thr', 'closing queue for %s' % self.ievent.userhost)
+                for i in self.ievent.queues:
+                    i.put_nowait(None)
+
+        except Exception, ex:
+            handle_exception(self.ievent)
+            time.sleep(0.1)
+
+class Thr(threading.Thread):
+
+    """ thread wrapper. """
+
+    def __init__(self, group, target, name, args, kwargs):
+        threading.Thread.__init__(self, None, target, name, args, kwargs)
+        rlog(-14, 'thr', 'running %s .. args: %s' % (name, args))
+        self.setDaemon(True)
+        self.name = name
+        time.sleep(0.001)
+
+    def join(self):
+        threading.Thread.join(self)
+
+    def run(self):
+        """ run the thread. """
+        try:
+            if self.name not in dontshowthreads:
+                rlog(-4, 'thr', 'running thread %s' % self.name) 
+            stats.up('threads', self.name)
+            threading.Thread.run(self)
+        except Exception, ex:
+            handle_exception()
+            time.sleep(1)
+
+def getname(func):
+
+    """ get name of function/method. """
+
+    name = ""
+
+    method = re.search(methodre, str(func))
+    if method:
+        name = method.group(1)
+    else: 
+        function = re.search(funcre, str(func))
+        if function:
+            name = function.group(1)
+        else:
+            name = str(func)
+    return name
+
+#@locked
+def start_new_thread(func, arglist, kwargs=None):
+
+    """ start a new thread .. set name to function/method name."""
+
+    if not kwargs:
+        kwargs = {}
+
+    try:
+        name = getname(func)
+
+        if not name:
+            name = 'noname'
+
+        thread = Thr(None, target=func, name=name, args=arglist, \
+kwargs=kwargs)
+
+        rlog(-1, 'thr', 'starting %s thread' % str(func))
+
+        thread.start()
+        return thread
+
+    except:
+        handle_exception()
+        time.sleep(1)
+
+#@locked
+def start_bot_command(func, arglist, kwargs={}):
+
+    """ start a new thread .. set name to function/method name. """
+
+    if not kwargs:
+        kwargs = {}
+
+    try:
+        name = getname(func)
+
+        if not name:
+            name = 'noname'
+
+        thread = Botcommand(group=None, target=func, name=name, args=arglist, \
+kwargs=kwargs)
+
+        rlog(-1, 'thr', 'starting %s thread' % str(func))
+
+        thread.start()
+        return thread
+
+    except:
+        handle_exception()
+        time.sleep(1)
+
+def threaded(func):
+
+    """ threading decorator. """
+
+    def threadedfunc(*args, **kwargs):
+        if config['PAE']:
+            func(*args, **kwargs)
+        else:
+            start_new_thread(func, args, kwargs)
+
+    return threadedfunc
--- gozerbot-0.99.1.orig/build/lib/gozerbot/threads/threadloop.py
+++ gozerbot-0.99.1/build/lib/gozerbot/threads/threadloop.py
@@ -0,0 +1,135 @@
+# gozerbot/threads/threadloop.py
+#
+#
+
+""" class to implement start/stoppable threads. """
+
+__copyright__ = 'this file is in the public domain'
+
+# gozerbot imports
+from gozerbot.utils.log import rlog
+from gozerbot.config import config
+from gozerbot.utils.exception import handle_exception
+
+# core thread import
+from thr import start_new_thread
+
+# basic imports
+import Queue, time
+
+class ThreadLoop(object):
+
+    """ implement startable/stoppable threads. """
+
+    def __init__(self, name="", queue=None):
+        self.name = name or 'idle'
+        self.stopped = False
+        self.running = False
+        self.outs = []
+        self.queue = queue or Queue.Queue()
+        self.nowrunning = "none"
+
+    def _loop(self):
+        rlog(0, "threadloop", 'starting threadloop')
+        while not self.stopped:
+            try:
+                self.running = True
+                try:
+                    data = self.queue.get_nowait()
+                except Queue.Empty:
+                    if self.stopped:
+                        break
+                    time.sleep(0.1)
+                    continue
+
+                if self.stopped:
+                    break
+
+                if not data:
+                    break
+                rlog(0, self.name, 'running %s' % str(data))
+                self.handle(*data)
+            except Exception, ex: handle_exception()
+
+        self.running = False
+        rlog(0, self.name, 'stopping threadloop')
+
+    def put(self, *data):
+
+        """ put data on task queue. """
+
+        self.queue.put_nowait(data)
+
+    def start(self):
+
+        """ start the thread. """
+
+        if not self.running:
+            start_new_thread(self._loop, ())
+
+    def stop(self):
+
+        """ stop the thread. """
+
+        self.stopped = True
+        self.running = False
+        self.queue.put(None)
+
+    def handle(self, *args, **kwargs):
+
+        """ overload this. """
+
+        pass
+
+class RunnerLoop(ThreadLoop):
+
+    """ dedicated threadloop for bot commands/callbacks. """
+
+
+    def _loop(self):
+        rlog(0, "runnerloop", 'starting threadloop')
+        self.running = True
+
+        while not self.stopped:
+
+            try:
+                data = self.queue.get()
+            except Queue.Empty:
+                if self.stopped:
+                    break
+                time.sleep(0.1)
+                continue
+
+            if self.stopped:
+                break
+
+            if not data:
+                break
+
+            self.nowrunning = data[0]
+            rlog(0, 'runnerloop', 'now running %s' % self.nowrunning)
+            self.handle(*data)
+
+        self.running = False
+        rlog(0, 'runnerloop', 'stopping threadloop')
+
+class ThreadSleeper(ThreadLoop):
+
+    def __init__(self, name="", timeout=1800):
+        ThreadLoop.__init__(self, name)
+        self.timeout = timeout
+
+    def _loop(self):
+        rlog(0, 'threadsleeper', 'starting threadloop')
+        self.running = True
+
+        while not self.stopped:
+            time.sleep(self.timeout)
+
+            if self.stopped:
+                break
+            rlog(-1, self.name, 'running')
+            self.handle()
+
+        self.running = False
+        rlog(0, 'threadsleeper', 'stopping threadloop')
--- gozerbot-0.99.1.orig/build/lib/gozerbot/xmpp/bot.py
+++ gozerbot-0.99.1/build/lib/gozerbot/xmpp/bot.py
@@ -0,0 +1,1125 @@
+# gozerbot/xmpp/bot.py
+#
+#
+
+""" jabber bot definition """
+
+__copyright__ = 'this file is in the public domain'
+
+## IMPORT SECTION
+
+# gozerbot imports
+from gozerbot.users import users
+from gozerbot.utils.log import rlog
+from gozerbot.utils.exception import handle_exception
+from gozerbot.utils.trace import whichmodule
+from gozerbot.utils.locking import lockdec
+from gozerbot.utils.generic import waitforqueue, toenc, fromenc, jabberstrip, getrandomnick
+from gozerbot.config import config
+from gozerbot.persist.pdod import Pdod
+from gozerbot.utils.dol import Dol
+from gozerbot.datadir import datadir
+from gozerbot.channels import Channels
+from gozerbot.less import Less
+from gozerbot.ignore import shouldignore
+from gozerbot.callbacks import jcallbacks
+from gozerbot.threads.thr import start_new_thread
+from gozerbot.fleet import fleet
+from gozerbot.botbase import BotBase
+
+# xmpp imports
+from presence import Presence
+from message import Message
+from iq import Iq
+from core import XMLStream
+from monitor import xmppmonitor
+from wait import XMPPWait, XMPPErrorWait
+from jid import JID, InvalidJID
+
+# basic imports
+import time, Queue, os, threading, thread, types, xml, re, hashlib
+
+## END IMPORT
+
+## LOCK SECTION
+
+# locks
+outlock = thread.allocate_lock()
+inlock = thread.allocate_lock()
+connectlock = thread.allocate_lock()
+outlocked = lockdec(outlock)
+inlocked = lockdec(inlock)
+connectlocked = lockdec(connectlock)
+
+## END LOCK
+
+class Bot(XMLStream, BotBase):
+
+    """
+        xmpp bot class.
+
+        :param name: name of the xmpp bot
+        :type name: string
+        :param cfg: config file for the bot
+        :type config: gozerbot.config.Config
+        :rtype: None
+
+    """
+
+    def __init__(self, name, cfg={}):
+        BotBase.__init__(self, name, cfg)
+
+        if not self.port:
+            self.port = 5222
+
+        if not self.host:
+            raise Exception("host not set in %s xmpp bot" % self.name)
+
+        self.username = self.user.split('@')[0]
+        XMLStream.__init__(self, self.host, self.port, self.name)   
+        self.type = 'xmpp'
+        self.outqueue = Queue.Queue()
+        self.sock = None
+        self.me = self.user
+        self.jid = self.me
+        self.lastin = None
+        self.test = 0
+        self.connecttime = 0
+        self.connection = None
+        self.privwait = XMPPWait()
+        self.errorwait = XMPPErrorWait()
+        self.monitor = xmppmonitor
+        self.jabber = True
+        self.connectok = threading.Event()
+        self.jids = {}
+        self.topics = {}
+        self.timejoined = {}
+        self.channels409 = []
+
+        if not self.state.has_key('ratelimit'):
+            self.state['ratelimit'] = 0.05
+
+        if self.port == 0:
+            self.port = 5222
+
+        self.monitor.start()
+    
+    def _resumedata(self):
+
+        """
+             return data needed for resuming.
+
+             .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                 :pyobject: Bot._resumedata
+
+        """
+
+        return {self.name: [self.server, self.user, self.password, self.port]}
+
+    def _outputloop(self):
+
+        """
+            loop to take care of output to the server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot._outputloop
+
+        """
+
+        rlog(10, self.name, 'starting outputloop')
+        lastsend = time.time()
+        charssend = 0
+
+        while not self.stopped:
+            time.sleep(0.01)
+            what = self.outqueue.get()
+
+            if self.stopped or what == None:
+                 break
+
+            if charssend + len(what) < 1000:
+
+                try:
+                    self._raw(what)
+                except Exception, ex:
+                    self.error = str(ex)
+                    handle_exception
+                    continue
+
+                lastsend = time.time()
+                charssend += len(what)
+
+            else:
+
+                if time.time() - lastsend > 1:
+
+                    try:
+                        self._raw(what)
+                    except Exception, ex:
+                        handle_exception()
+                        continue
+
+                    lastsend = time.time()
+                    charssend = len(what) 
+                    continue
+
+                charssend = 0
+                sleeptime = self.cfg['jabberoutsleep'] or config['jabberoutsleep']
+
+                if not sleeptime:
+                    sleeptime = 1
+
+                rlog(10, self.name, 'jabberoutsleep .. sleeping %s seconds' % sleeptime)
+                time.sleep(sleeptime)
+
+                try:
+                    self._raw(what)
+                except Exception, ex:
+                    handle_exception()
+
+        rlog(10, self.name, 'stopping outputloop .. %s' % (self.error or 'no error set'))
+
+    def _keepalive(self):
+
+        """ 
+             keepalive method .. send empty string to self every 3 minutes.
+
+             .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                 :pyobject: Bot._keepalive
+
+        """
+
+        nrsec = 0
+        
+
+
+        while not self.stopped:
+            time.sleep(1)
+            nrsec += 1
+
+            if nrsec < 180:
+                continue
+            else:
+                nrsec = 0
+
+            self.sendpresence()
+
+    def sendpresence(self):
+        """
+            send presence based on status and status text
+            set by user
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                 :pyobject: Bot.sendpresence
+
+        """
+
+        if self.state.has_key('status') and self.state['status']:
+            status = self.state['status']
+        else:
+            status = ""
+        if self.state.has_key('show') and self.state['show']:
+            show = self.state['show']
+        else:
+            show = ""
+        rlog(4, self.name, 'keepalive: %s - %s' % (show, status))
+        if show and status:
+            p = Presence({'to': self.me, 'show': show, 'status': status}, self)
+        elif show:
+            p = Presence({'to': self.me, 'show': show }, self)
+        elif status:
+            p = Presence({'to': self.me, 'status': status}, self)
+        else:
+            p = Presence({'to': self.me }, self)
+
+        self.send(p)
+
+    def _keepchannelsalive(self):
+
+        """
+            channels keep alive method.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot._keepchannelsalive
+
+        """
+
+        nrsec = 0
+        p = Presence({'to': self.me, 'txt': '' }, self)
+
+        while not self.stopped:
+            time.sleep(1)
+            nrsec += 1
+
+            if nrsec < 600:
+                continue
+            else:
+                nrsec = 0
+
+            for chan in self.state['joinedchannels']:
+
+                if chan not in self.channels409:
+                    p = Presence({'to': chan}, self)
+                    self.send(p)
+
+    def connect(self, reconnect=True):
+
+        """
+            connect the xmpp server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.connect
+
+        """
+
+
+        if self.stopped:
+            rlog(100, self.name, 'bot is stopped .. not connecting')
+            return
+        try:
+            if not XMLStream.connect(self):
+                rlog(10, self.name, 'connect to %s:%s failed' % (self.host, self.port))
+                return
+            else:
+                rlog(10, self.name, 'connected')
+            self.logon(self.user, self.password)
+            start_new_thread(self._outputloop, ())
+            start_new_thread(self._keepalive, ())
+            #start_new_thread(self._keepchannelsalive, ())
+            self.requestroster()
+            self._raw("<presence/>")
+            self.connectok.set()
+            self.sock.settimeout(None)
+            return True
+        except Exception, ex:
+            handle_exception()
+            if reconnect:
+                self.reconnect()
+
+    def logon(self, user, password):
+
+        """
+            logon the xmpp server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.logon
+
+        """
+
+        iq = self.initstream()
+        if not self.auth(user, password, iq.id):
+            if self.register(user, password):
+                time.sleep(5)
+                self.auth(user, password)
+            else:
+                self.exit()
+                return
+        XMLStream.logon(self)
+ 
+    def initstream(self):
+
+        """
+             send initial string sequence to the xmpp server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.initstream
+
+        """
+
+
+        rlog(10, self.name, 'starting initial stream sequence')
+        self._raw("""<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>""" % (self.user.split('@')[1], )) 
+        result = self.connection.read()
+        iq = self.loop_one(result)
+        rlog(3, self.name, "initstream: %s" % result)
+        return iq
+
+    def register(self, jid, password):
+
+        """
+            register the jid to the server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.register
+
+        """
+
+        try:
+            resource = jid.split("/")[1]
+        except IndexError:
+            resource = "gozerbot"
+        rlog(10, self.name, 'registering %s' % jid)
+        self._raw("""<iq type='get'><query xmlns='jabber:iq:register'/><username>%s</username></iq>""" % jid.split("@")[0])
+        #self._raw("""<iq type='get' id='reg1' />""")
+        result = self.connection.read()
+        #rlog(3, self.name, 'register: %s' % result)
+        iq = self.loop_one(result)
+        if not iq:
+            rlog(10, self.name, "unable to register")
+            return
+        rlog(3, self.name, 'register: %s' % str(iq))
+        self._raw("""<iq type='set'><query xmlns='jabber:iq:register'><username>%s</username><resource>%s</resource><password>%s</password></query></iq>""" % (jid.split('@')[0], resource, password))
+        result = self.connection.read()
+        rlog(3, self.name, 'register: %s' % result)
+        if not result:
+            return False
+        iq = self.loop_one(result)
+        if not iq:
+            rlog(10, self.name, "can't decode data: %s" % result)
+            return False
+        rlog(3, self.name, 'register: %s' % result)
+        if iq.error:
+            rlog(10, self.name, 'REGISTER FAILED: %s' % iq.error)
+            if iq.error.code == "405":
+                rlog(10, self.name, "this server doesn't allow registration by the bot, you need to create an account for it yourself")
+            else:
+                rlog(10, self.name, result)
+            self.error = iq.error
+            return False
+        rlog(10, self.name, 'register ok')
+        return True
+
+    def auth(self, jid, password, digest=""):
+
+        """
+            auth against the xmpp server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.auth
+
+        """
+
+        rlog(10, self.name, 'authing %s' % jid)
+        name = jid.split('@')[0]
+        rsrc = self.cfg['resource'] or config['resource'] or 'gozerbot';
+        self._raw("""<iq type='get'><query xmlns='jabber:iq:auth'><username>%s</username></query></iq>""" % name)
+        result = self.connection.read()
+        iq = self.loop_one(result)
+        rlog(3, self.name, 'auth:' + result)
+        if ('digest' in result) and digest:
+            s = hashlib.new('SHA1')
+            s.update(digest)
+            s.update(password)
+            d = s.hexdigest()
+            self._raw("""<iq type='set'><query xmlns='jabber:iq:auth'><username>%s</username><digest>%s</digest><resource>%s</resource></query></iq>""" % (name, d, rsrc))
+        else:
+            self._raw("""<iq type='set'><query xmlns='jabber:iq:auth'><username>%s</username><resource>%s</resource><password>%s</password></query></iq>""" % (name, rsrc, password))
+        result = self.connection.read()
+        iq = self.loop_one(result)
+        if not iq:
+            rlog(10, self.name, 'auth failed: %s' % result)
+            return False        
+        rlog(3, self.name, 'auth: ' + result)
+        if iq.error:
+            rlog(10, self.name, 'AUTH FAILED: %s' % iq.error)
+            if iq.error.code == "401":
+                rlog(10, self.name, "wrong user or password (or user not known)")
+            else:
+                rlog(10, self.name, result)
+            self.error = iq.error
+            return False
+        rlog(10, self.name, 'auth ok')
+        return True
+
+    def requestroster(self):
+
+        """
+            request roster from xmpp server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.requestroster
+
+        """
+
+        self._raw("<iq type='get'><query xmlns='jabber:iq:roster'/></iq>")
+
+    def disconnectHandler(self, ex):
+
+        """
+            disconnect handler.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.disconnectHandler
+
+        """
+
+        rlog(100, self.name, "disconnected: %s" % str(ex)) 
+
+        if not self.stopped:
+            self.reconnect()
+        else:
+            self.exit()
+
+    def joinchannels(self):
+
+        """
+            join all already joined channels.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.joinchannels
+
+        """
+
+
+        for i in self.state['joinedchannels']:
+            key = self.channels.getkey(i)
+            nick = self.channels.getnick(i)
+            result = self.join(i, key, nick)
+            if result == 1:
+                rlog(10, self.name, 'joined %s' % i)
+            else:
+                rlog(10, self.name, 'failed to join %s: %s' % (i, result))
+
+    def broadcast(self, txt):
+
+        """
+            broadcast txt to all joined channels.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.broadcast
+
+        """
+
+        for i in self.state['joinedchannels']:
+            self.say(i, txt)
+
+    def handle_iq(self, data):
+
+        """
+            iq handler .. overload this when needed.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.handle_iq
+
+        """
+
+        pass
+
+    def handle_message(self, data):
+
+        """
+            message handler.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.handle_message
+
+        """
+
+        if self.test:
+            return
+ 
+        m = Message(data, self)
+
+        if data.type == 'groupchat' and data.subject:
+            self.topiccheck(m)
+            nm = Message(m, bot=self)
+            jcallbacks.check(self, nm)
+            return
+
+        if data.get('x').xmlns == 'jabber:x:delay':
+            rlog(5, self.name, "ignoring delayed message")
+            return
+
+        self.privwait.check(m)
+
+        if m.isresponse:
+            return
+
+        if m.groupchat:
+            m.msg = False
+
+        jid = None
+        m.origjid = m.jid
+
+        for node in m.subelements:
+            try:
+                m.jid = node.x.item.jid 
+            except (AttributeError, TypeError):
+                continue
+
+        #if not m.txt:
+        #    return
+        if self.me in m.fromm:
+            return 0
+
+        #if m.groupchat and self.nick == m.resource:
+        #    return 0
+        go = 0
+
+        try:
+            cc = self.channels[m.channel]['cc']
+        except (TypeError, KeyError):
+            cc = config['defaultcc'] or '!'
+
+        try:
+            channick = self.channels[m.channel]['nick']
+        except (TypeError, KeyError):
+            channick = self.nick
+
+        if m.msg and not config['noccinmsg']:
+            go = 1
+        if not m.txt:
+            go = 0
+        elif m.txt[0] in cc:
+            go = 1
+        elif m.txt.startswith("%s: " % channick):
+            m.txt = m.txt.replace("%s: " % channick, "")
+            go = 1
+        elif m.txt.startswith("%s, " % channick):
+            m.txt = m.txt.replace("%s, " % channick, "")
+            go = 1
+
+        if m.txt and m.txt[0] in cc:
+            m.txt = m.txt[1:]
+
+        if go:
+            try:
+                from gozerbot.plugins import plugins
+                if plugins.woulddispatch(self, m):
+                    m.usercmnd = True
+                plugins.trydispatch(self, m)
+            except:
+                handle_exception()
+
+        try:
+            if m.type == 'error':
+                if m.code:
+                    rlog(10, self.name + '.error', str(m))
+                self.errorwait.check(m)
+                self.errorHandler(m)
+        except Exception, ex:
+            handle_exception()
+
+        mm = Message(m, self)
+        jcallbacks.check(self, mm)
+
+    def errorHandler(self, event):
+
+        """
+            error handler .. calls the errorhandler set in the event.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.errorHandler
+
+        """
+
+        try:
+            event.errorHandler()
+        except AttributeError:
+            rlog(10, self.name, 'unhandled error: %s' % event)
+
+    def handle_presence(self, data):
+
+        """
+            presence handler.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.handle_presence
+
+        """
+
+        p = Presence(data, self)
+        frm = p.fromm
+        nickk = ""
+        nick = p.nick
+
+        if self.me in p.userhost:
+            return 0
+
+        if nick:
+            self.userhosts.data[nick] = str(frm)
+            nickk = nick
+
+        jid = None
+
+        for node in p.subelements:
+            try:
+                jid = node.x.item.jid 
+            except (AttributeError, TypeError):
+                continue
+
+        if nickk and jid:
+            channel = p.channel
+            if not self.jids.has_key(channel):
+                self.jids[channel] = {}
+            self.jids[channel][nickk] = jid
+            self.userhosts.data[nickk] = str(jid)
+            rlog(0, self.name, 'setting jid of %s (%s) to %s' % (nickk, \
+ channel, jid))
+
+        if p.type == 'subscribe':
+            pres = Presence({'to': p.fromm, 'type': 'subscribed'}, self)
+            self.send(pres)
+            pres = Presence({'to': p.fromm, 'type': 'subscribe'}, self)
+            self.send(pres)
+
+        nick = p.resource
+
+        if p.type != 'unavailable':
+            self.userchannels.adduniq(nick, p.channel)
+            p.joined = True
+            p.type = 'available'
+        elif self.me in p.userhost:
+            try:
+                del self.jids[p.channel]
+                rlog(0, self.name, 'removed %s channel jids' % p.channel)
+            except KeyError:
+                pass
+        else:
+            try:
+                del self.jids[p.channel][p.nick]
+                rlog(0, self.name, 'removed %s jid' % p.nick)
+            except KeyError:
+                pass
+
+        pp = Presence(p, self)
+        jcallbacks.check(self, pp)
+
+        if p.type == 'error':
+            for node in p.subelements:
+                try:
+                    err = node.error.code
+                except (AttributeError, TypeError):
+                    err = 'no error set'
+                try:
+                    txt = node.text.data
+                except (AttributeError, TypeError):
+                    txt = ""
+            if err:
+                rlog(10, self.name + '.error', "%s: %s"  % (err, txt))
+
+            self.errorwait.check(p)
+
+            try:
+                method = getattr(self,'handle_' + err)
+                # try to call method
+                try:
+                    method(p)
+                except:
+                    handle_exception()
+            except AttributeError:
+                # no command method to handle event
+                pass
+
+    def reconnect(self):
+
+        """
+            reconnect to the server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.reconnect
+
+        """
+
+        if self.stopped:
+            rlog(100, self.name, 'bot is stopped .. not reconnecting')
+            return
+
+        rlog(100, self.name, 'reconnecting .. sleeping 15 seconds')
+        self.exit()
+        time.sleep(15)
+        newbot = Bot(self.name, self.cfg)
+
+        if newbot.connect():
+            self.name += '.old'
+            newbot.joinchannels()
+            fleet.replace(self.name, newbot)
+            return True
+
+        return False
+
+
+    def send(self, what):
+
+        """
+            send stanza to the server.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.send
+
+        """
+
+        try:
+            to = what['to']
+        except (KeyError, TypeError):
+            rlog(10, self.name, "can't determine where to send %s to" % what)
+            return
+
+        try:
+            jid = JID(to)
+        except (InvalidJID, AttributeError):
+            rlog(10, self.name, "invalid jid %s .. %s" % (str(to), str(what)))
+            return
+
+        try:
+            del what['from']
+            #del what['fromm']
+        except KeyError:
+            pass
+
+        try:
+            xml = what.toxml()
+            if not xml:
+                raise Exception("can't convert %s to xml .. bot.send()" % what) 
+        except (AttributeError, TypeError):
+            handle_exception()
+            return
+            #raise Exception("can't convert %s to xml .. bot.send()" % what) 
+           
+        self.outqueue.put(toenc(xml))
+        self.monitor.put(self, what)
+
+    def sendnocb(self, what):
+
+        """
+            send to server without calling callbacks/monitors.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.sendnocb
+
+        """
+
+        try:
+            xml = what.toxml()
+        except AttributeError:
+            xml = what
+        self.outqueue.put(toenc(xml))
+
+    def action(self, printto, txt, fromm=None, groupchat=True):
+
+        """
+            send an action.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.action
+
+        """
+
+        txt = "/me " + txt
+
+        if self.google:
+            fromm = self.me
+
+        if printto in self.state['joinedchannels'] and groupchat:
+            message = Message({'to': printto, 'txt': txt, 'type': 'groupchat'}, self)
+        else:
+            message = Message({'to': printto, 'txt': txt}, self)
+
+        if fromm:
+            message.fromm = fromm
+
+        self.send(message)
+        
+    def say(self, printto, txt, fromm=None, groupchat=True, speed=5, type="normal", how=''):
+
+        """
+            say txt to channel/JID.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.say
+
+        """
+
+        txt = jabberstrip(txt)
+
+        if self.google:
+            fromm = self.me
+
+        if printto in self.state['joinedchannels'] and groupchat:
+            message = Message({'to': printto, 'body': txt, 'type': 'groupchat'}, self)
+        else:
+            message = Message({'to': printto, 'body': txt, 'type': 'chat'}, self)
+
+        if fromm:
+            message.fromm = fromm
+        else:
+            message.fromm = self.me
+
+        self.send(message)
+
+    def saynocb(self, printto, txt, fromm=None, groupchat=True, speed=5, type="normal", how=''):
+
+        """
+             say txt to channel/JID without calling callbacks/monitors.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.saynocb
+
+        """
+
+        txt = jabberstrip(txt)
+
+        #if self.google:
+        #    fromm = self.me
+
+        if printto in self.state['joinedchannels'] and groupchat:
+            message = Message({'to': printto, 'body': txt, 'type': 'groupchat'}, self)
+        else:
+            message = Message({'to': printto, 'body': txt}, self)
+
+        if fromm:
+            message.fromm = fromm
+        else:
+            message.fromm = self.me
+
+        self.send(message)
+
+    def userwait(self, msg, txt):
+
+        """
+            wait for user response.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.userwait
+
+        """
+
+        msg.reply(txt)
+        queue = Queue.Queue()
+        self.privwait.register(msg, queue)
+        result = queue.get()
+
+        if result:
+            return result.txt
+
+    def save(self):
+
+        """
+            save bot's state.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.save
+
+        """
+
+        self.state.save()
+
+    def quit(self):
+
+        """
+            send unavailable presence.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.quit
+
+        """
+
+        presence = Presence({'type': 'unavailable'}, self)
+
+        for i in self.state['joinedchannels']:
+            presence.to = i
+            self.send(presence)
+
+        presence = Presence({'type': 'unavailable'}, self)
+        presence['from'] = self.me
+        self.send(presence)
+        time.sleep(1)
+        
+    def exit(self):
+
+        """
+            exit the bot.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.exit
+
+        """
+
+        self.quit()
+        self.stopped = 1
+        self.outqueue.put_nowait(None)
+        self.save()
+        time.sleep(3)
+        rlog(10, self.name, 'exit')
+
+    def join(self, channel, password=None, nick=None):
+
+        """
+            join conference.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.join
+
+        """
+
+        if '#' in channel:
+            return
+
+        try:
+            if not nick:
+                nick = channel.split('/')[1]
+        except IndexError:
+            nick = self.nick
+
+        channel = channel.split('/')[0]
+
+        if not self.channels.has_key(channel):
+            # init channel data
+            self.channels.setdefault(channel, {})
+
+        # setup error wait
+        q = Queue.Queue()
+        self.errorwait.register("409", q, 3)
+        self.errorwait.register("401", q, 3)
+        self.errorwait.register("400", q, 3)
+
+        # do the actual join
+        presence = Presence({'to': channel + '/' + nick}, self)
+
+        if password:
+             presence.x.password = password             
+#            passnode = Node('password')
+#            passnode.addData(password)
+#            presence.addChild(name='x', namespace='http://jabber.org/protocol/muc', \
+#payload=[passnode, ])
+
+        self.send(presence)
+        errorobj = waitforqueue(q, 3)
+
+        if errorobj:
+            err = errorobj[0].error
+            rlog(100, self.name, 'error joining %s: %s' % (channel, err))
+            if err >= '400':
+                if channel not in self.channels409:
+                    self.channels409.append(channel)
+            return err
+
+        self.timejoined[channel] = time.time()
+        chan = self.channels[channel]
+        # if password is provided set it
+        chan['nick'] = nick
+
+        if password:
+            chan['key'] = password
+
+        # check for control char .. if its not there init to !
+        if not chan.has_key('cc'):
+            chan['cc'] = config['defaultcc'] or '!'
+
+        if not chan.has_key('perms'):
+            chan['perms'] = []
+
+        self.channels.save()
+
+        if channel not in self.state['joinedchannels']:
+            self.state['joinedchannels'].append(channel)
+
+        if channel in self.channels409:
+            self.channels409.remove(channel)
+
+        self.state.save()
+        return 1
+
+    def part(self, channel):
+ 
+        """
+            leave conference.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.part
+
+        """
+
+        if '#' in channel:
+            return
+
+        presence = Presence({'to': channel}, self)
+        presence.type = 'unavailable'
+        self.send(presence)
+
+        if channel in self.state['joinedchannels']:
+            self.state['joinedchannels'].remove(channel)
+
+        self.state.save()
+        return 1
+
+    def outputnolog(self, printto, what, how, who=None, fromm=None):
+
+        """
+            do output but don't log it.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.outputnolog
+
+        """
+
+        if fromm and shouldignore(fromm):
+            return
+
+        self.saynocb(printto, what)
+
+    def topiccheck(self, msg):
+
+        """
+            check if topic is set.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.topiccheck
+
+        """
+
+        if msg.groupchat:
+            try:
+                topic = msg.subject
+                if not topic:
+                    return None
+                self.topics[msg.channel] = (topic, msg.userhost, time.time())
+                rlog(0, self.name, 'topic of %s set to %s' % \
+(msg.channel, topic))
+            except AttributeError:
+                return None
+
+    def settopic(self, channel, txt):
+
+        """
+            set topic.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.settopic
+
+        """
+
+        pres = Message({'to': channel, 'subject': txt}, self)
+        pres.type = 'groupchat'
+        self.send(pres)
+
+    def gettopic(self, channel):
+
+        """
+            get topic.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.gettopic
+
+        """
+
+        try:
+            topic = self.topics[channel]
+            return topic
+        except KeyError:
+            return None
+
+    def domsg(self, msg):
+
+        """
+            dispatch an msg on the bot.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.domsg
+
+        """
+
+        from gozerbot.plugins import plugins
+        plugins.trydispatch(self, msg)
+
+    def sendraw(self, data):
+
+        """
+             compat function.
+
+            .. literalinclude:: ../../../gozerbot/xmpp/bot.py
+                :pyobject: Bot.sendraw
+
+        """
+
+        self._raw(data)
--- gozerbot-0.99.1.orig/build/lib/gozerbot/xmpp/monitor.py
+++ gozerbot-0.99.1/build/lib/gozerbot/xmpp/monitor.py
@@ -0,0 +1,41 @@
+# gozerbot/xmpp/monitor.py
+#
+#
+
+""" monitors .. call callback on bot output. """
+
+__copyright__ = 'this file is in the public domain'
+
+# gozerbot imports
+from gozerbot.utils.exception import handle_exception
+from gozerbot.monitor import Monitor
+from gozerbot.config import config
+from gozerbot.eventbase import EventBase
+from message import Message
+
+class XMPPmonitor(Monitor):
+
+    """ monitor jabber output """
+
+    def handle(self, bot, event):
+
+        """ fire jabber monitor callbacks. """
+
+        try:
+            #event.fromm = event['from'] = event.to
+            e = Message()
+            e.copyin(event)
+            e.isreply = True
+            e.iscmnd = True
+            e.remotecmnd = True
+            e.remoteout = bot.jid
+            e.channel = event.to
+            e.fromm = e['from'] = bot.jid
+            e.nick = bot.nick
+            e.botoutput = True
+            Monitor.handle(self, bot, e)
+        except AttributeError:
+            handle_exception()
+
+# xmpp monitor
+xmppmonitor = XMPPmonitor('xmppmonitor')
--- gozerbot-0.99.1.orig/build/lib/gozerbot/xmpp/core.py
+++ gozerbot-0.99.1/build/lib/gozerbot/xmpp/core.py
@@ -0,0 +1,677 @@
+# gozerbot/xmpp/core.py
+#
+#
+
+"""
+    this module contains the core xmpp handling functions.
+
+"""
+
+
+## IMPORT SECTION
+
+# gozerbot imports
+from gozerbot.datadir import datadir
+from gozerbot.config import Config, config
+from gozerbot.utils.generic import toenc, jabberstrip
+from gozerbot.utils.log import rlog
+from gozerbot.utils.lazydict import LazyDict
+from gozerbot.utils.exception import handle_exception
+from gozerbot.utils.locking import lockdec
+from gozerbot.threads.thr import start_new_thread
+from gozerbot.utils.trace import whichmodule
+
+# dom imports
+from gozerbot.contrib.xmlstream import NodeBuilder, XMLescape
+
+# for exceptions
+import  xml.parsers.expat
+
+from namespace import attributes, subelements
+
+# basic imports
+import socket, os, time, copy, logging, thread
+
+## END IMPORT
+
+## LOCK SECTION
+
+# locks
+outlock = thread.allocate_lock()   
+inlock = thread.allocate_lock()
+connectlock = thread.allocate_lock()
+outlocked = lockdec(outlock)
+inlocked = lockdec(inlock)  
+connectlocked = lockdec(connectlock) 
+
+## END LOCK
+
+class XMLDict(LazyDict):
+
+
+    """
+        dictionairy to store xml stanza attributes.
+
+        :params input: optional dict to init with
+        :type input: dict
+
+    """
+
+    def __init__(self, input={}):
+
+        if input == None:
+            LazyDict.__init__(self)
+        else:
+            LazyDict.__init__(self, input)
+
+        try:
+            self['fromm'] = self['from']
+        except (KeyError, TypeError):
+            self['fromm'] = ''
+
+    def __getattr__(self, name):
+
+        """
+            override getattribute so nodes in payload can be accessed.
+
+        """
+
+        if not self.has_key(name) and self.has_key('subelements'):
+
+            for i in self['subelements']:
+
+                if name in i:
+                    return i[name]
+
+        return LazyDict.__getattr__(self, name, default="")
+
+    def get(self, name):
+
+        """
+            get a attribute by name.
+
+            :param name: name of the attribute
+            :type name: string
+
+            .. literalinclude:: ../../../gozerbot/xmpp/core.py
+                :pyobject: XMLDict.get