jsb-0.84.4/0000775000175000017500000000000011733126034012271 5ustar bartbart00000000000000jsb-0.84.4/setup.py0000664000175000017500000001345611733124163014015 0ustar bartbart00000000000000#!/usr/bin/env python # # target = "jsb" # BHJTW change this to /var/cache/jsb on debian import os try: from setuptools import setup except: print "i need setuptools to properly install JSONBOT" ; os._exit(1) upload = [] def uploadfiles(dir): upl = [] if not os.path.isdir(dir): print "%s does not exist" % dir ; os._exit(1) for file in os.listdir(dir): if not file or file.startswith('.'): continue d = dir + os.sep + file if not os.path.isdir(d): if file.endswith(".pyc"): continue upl.append(d) return upl def uploadlist(dir): upl = [] for file in os.listdir(dir): if not file or file.startswith('.'): continue d = dir + os.sep + file if os.path.isdir(d): upl.extend(uploadlist(d)) else: if file.endswith(".pyc"): continue upl.append(d) return upl setup( name='jsb', version='0.84.4', url='http://jsonbot.googlecode.com/', download_url="http://code.google.com/p/jsonbot/downloads", author='Bart Thate', author_email='bthate@gmail.com', description='The bot for you!', license='MIT', include_package_data=True, zip_safe=False, scripts=['bin/jsb', 'bin/jsb-backup', 'bin/jsb-init', 'bin/jsb-irc', 'bin/jsb-fleet', 'bin/jsb-xmpp', 'bin/jsb-sed', 'bin/jsb-sleek', 'bin/jsb-stop', 'bin/jsb-tornado', 'bin/jsb-udp'], packages=['jsb', 'jsb.api', 'jsb.lib', 'jsb.lib.rest', 'jsb.db', 'jsb.drivers', 'jsb.drivers.console', 'jsb.drivers.convore', 'jsb.drivers.irc', 'jsb.drivers.sleek', 'jsb.drivers.tornado', 'jsb.drivers.xmpp', 'jsb.utils', 'jsb.plugs', 'jsb.plugs.db', 'jsb.plugs.core', 'jsb.plugs.common', 'jsb.plugs.socket', 'jsb.plugs.myplugs', 'jsb.plugs.myplugs.socket', 'jsb.plugs.myplugs.common', 'jsb.contrib', 'jsb.contrib.simplejson', 'jsb.contrib.tornado', 'jsb.contrib.tornado.test', 'jsb.contrib.tornado.platform', 'jsb.contrib.tweepy', 'jsb/contrib/sleekxmpp', 'jsb/contrib/sleekxmpp/stanza', 'jsb/contrib/sleekxmpp/test', 'jsb/contrib/sleekxmpp/roster', 'jsb/contrib/sleekxmpp/xmlstream', 'jsb/contrib/sleekxmpp/xmlstream/matcher', 'jsb/contrib/sleekxmpp/xmlstream/handler', 'jsb/contrib/sleekxmpp/plugins', 'jsb/contrib/sleekxmpp/plugins/xep_0004', 'jsb/contrib/sleekxmpp/plugins/xep_0004/stanza', 'jsb/contrib/sleekxmpp/plugins/xep_0009', 'jsb/contrib/sleekxmpp/plugins/xep_0009/stanza', 'jsb/contrib/sleekxmpp/plugins/xep_0030', 'jsb/contrib/sleekxmpp/plugins/xep_0030/stanza', 'jsb/contrib/sleekxmpp/plugins/xep_0050', 'jsb/contrib/sleekxmpp/plugins/xep_0059', 'jsb/contrib/sleekxmpp/plugins/xep_0060', 'jsb/contrib/sleekxmpp/plugins/xep_0060/stanza', 'jsb/contrib/sleekxmpp/plugins/xep_0066', 'jsb/contrib/sleekxmpp/plugins/xep_0078', 'jsb/contrib/sleekxmpp/plugins/xep_0085', 'jsb/contrib/sleekxmpp/plugins/xep_0086', 'jsb/contrib/sleekxmpp/plugins/xep_0092', 'jsb/contrib/sleekxmpp/plugins/xep_0128', 'jsb/contrib/sleekxmpp/plugins/xep_0199', 'jsb/contrib/sleekxmpp/plugins/xep_0202', 'jsb/contrib/sleekxmpp/plugins/xep_0203', 'jsb/contrib/sleekxmpp/plugins/xep_0224', 'jsb/contrib/sleekxmpp/plugins/xep_0249', 'jsb/contrib/sleekxmpp/features', 'jsb/contrib/sleekxmpp/features/feature_mechanisms', 'jsb/contrib/sleekxmpp/features/feature_mechanisms/stanza', 'jsb/contrib/sleekxmpp/features/feature_starttls', 'jsb/contrib/sleekxmpp/features/feature_bind', 'jsb/contrib/sleekxmpp/features/feature_session', 'jsb/contrib/sleekxmpp/thirdparty', 'jsb/contrib/sleekxmpp/thirdparty/suelta', 'jsb/contrib/sleekxmpp/thirdparty/suelta/mechanisms', ], long_description = """ JSONBOT is a remote event-driven framework for building bots that talk JSON to each other over XMPP. This distribution has IRC/Console/XMPP/WWW/Convore bots built on this framework. """, classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Environment :: Other Environment', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: Unix', 'Programming Language :: Python', 'Topic :: Communications :: Chat', 'Topic :: Software Development :: Libraries :: Python Modules'], data_files=[(target + os.sep + 'data', uploadfiles('jsb' + os.sep + 'data')), (target + os.sep + 'data' + os.sep + 'examples', uploadlist('jsb' + os.sep + 'data' + os.sep + 'examples')), (target + os.sep + 'data' + os.sep + 'static', uploadlist('jsb' + os.sep + 'data' + os.sep + 'static')), (target + os.sep + 'data' + os.sep + 'templates', uploadlist('jsb' + os.sep + 'data' + os.sep + 'templates')), (target + os.sep + 'contrib' + os.sep + 'tornado', ["jsb/contrib/tornado/ca-certificates.crt",])], package_data={'': ["*.crt"], }, ) jsb-0.84.4/MANIFEST.in0000664000175000017500000000025311733124163014030 0ustar bartbart00000000000000include MANIFEST.in include Changelog include jsb/contrib/tornado/ca-certificates.crt recursive-include files * recursive-include jsb/data * jsb-0.84.4/README0000664000175000017500000000362011733124163013153 0ustar bartbart00000000000000Welcome to JSONBOT ================== JSONBOT is a remote event-driven framework for building bots that talk JSON to each other over XMPP. This distribution provides bots built on this framework for console, IRC, XMPP, Convore and WWW on the shell. the jsb pakage contains the following programs: * jsb - console version of jsb * jsb-convore - Convore version of jsb * jsb-makecert - create keys and certifictes for web console SSL * jsb-init - create data directory and config examples, default ~/.jsb * jsb-irc - IRC version of jsb * jsb-fleet - mix IRC and XMPP bots * jsb-sed - sed a whole directory * jsb-stop - stop a running bot * jsb-tornado - a shell web server based on tornado * jsb-udp - send udp packets to the bot that will relay the data * jsb-udpdirect - a stand alone udp program, copy & edit * jsb-xmpp - XMPP version of jsb note: JSONBOT is in BETA stage right now and still subject to change of protocols and API. see http://jsonbot.googlecode.com. see https://jsonbot.org for documentation on the bot. if you want the GAE part of the bot install from mercurial repo. * "hg clone http://jsonbot.googlecode.com/hg mybot" license ~~~~~~~ JSONBOT is free code (MIT) and can be cloned where needed. contact the developer ~~~~~~~~~~~~~~~~~~~~~ * email: bthate@gmail.com * jabber/xmpp: bthate@gmail.com * IRC: botfather on #dunkbots irc.freenode.net * twitter: http://twitter.com/jsonbot urls ~~~~ * Source code: http://jsonbot.googlecode.com * web: http://jsonbot.org:10102 (web) * jabber: jsonbot@jsonbot.org running a development version of the bot ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ first checkout the main bot from the mercurial repository hg clone http://jsonbot.googlecode.com/hg mybot now you can run the programs in the bin directory with the ./bin/ command. try ./bin/jsb for the console app jsb-0.84.4/jsb/0000775000175000017500000000000011733126034013047 5ustar bartbart00000000000000jsb-0.84.4/jsb/imports.py0000664000175000017500000000366711733124163015133 0ustar bartbart00000000000000# jsb/imports.py # # """ provide a import wrappers for the contrib packages. """ ## lib imports from lib.jsbimport import _import ## basic imports import logging ## getdns function def getdns(): try: mod = _import("dns") except: mod = None logging.debug("imports - dns module is %s" % str(mod)) return mod def getwebapp2(): try: mod = _import("webapp2") except: mod = _import("jsb.contrib.webapp2") logging.debug("webapp2 module is %s" % str(mod)) return mod ## getjson function def getjson(): try: import wave #mod = _import("jsb.contrib.simplejson") mod = _import("json") except ImportError: try: mod = _import("json") except: try: mod = _import("simplejson") except: mod = _import("jsb.contrib.simplejson") logging.debug("json module is %s" % str(mod)) return mod ## getfeedparser function def getfeedparser(): try: mod = _import("feedparser") except: mod = _import("jsb.contrib.feedparser") logging.info("feedparser module is %s" % str(mod)) return mod def getoauth(): try: mod = _import("oauth") except: mod = _import("jsb.contrib.oauth") logging.info("oauth module is %s" % str(mod)) return mod def getrequests(): try: mod = _import("requests") except: mod = None logging.info("requests module is %s" % str(mod)) return mod def gettornado(): try: mod = _import("tornado") except: mod = _import("jsb.contrib.tornado") logging.info("tornado module is %s" % str(mod)) return mod def getBeautifulSoup(): try: mod = _import("BeautifulSoup") except: mod = _import("jsb.contrib.BeautifulSoup") logging.info("BeautifulSoup module is %s" % str(mod)) return mod def getsleek(): try: mod = _import("sleekxmpp") except: mod = _import("jsb.contrib.sleekxmpp") logging.info("sleek module is %s" % str(mod)) return mod jsb-0.84.4/jsb/lib/0000775000175000017500000000000011733126034013615 5ustar bartbart00000000000000jsb-0.84.4/jsb/lib/tasks.py0000664000175000017500000000303411733124163015315 0ustar bartbart00000000000000# jsb/tasks.py # # ## jsb imports from jsb.utils.trace import calledfrom from jsb.lib.plugins import plugs ## basic imports import logging import sys ## TaskManager class class TaskManager(object): def __init__(self): self.handlers = {} self.plugins = {} def size(self): return len(self.handlers) def add(self, taskname, func): """ add a task. """ logging.debug("tasks - added task %s - %s" % (taskname, func)) self.handlers[taskname] = func plugin = self.plugins[taskname] = calledfrom(sys._getframe()) plugs.load_mod(plugin) return True def unload(self, taskname): """ unload a task. """ logging.debug("tasks - unloading task %s" % taskname) try: del self.handlers[taskname] del self.plugins[taskname] return True except KeyError: return False def dispatch(self, taskname, *args, **kwargs): """ dispatch a task. """ try: plugin = self.plugins[taskname] except KeyError: logging.debug('tasks - no plugin for %s found' % taskname) return logging.debug('loading %s for taskmanager' % plugin) #plugs.load(plugin) try: handler = self.handlers[taskname] except KeyError: logging.debug('tasks - no handler for %s found' % taskname) return logging.warn("dispatching task %s - %s" % (taskname, str(handler))) return handler(*args, **kwargs) ## global task manager taskmanager = TaskManager() jsb-0.84.4/jsb/lib/partyline.py0000664000175000017500000001205211733124163016177 0ustar bartbart00000000000000# jsb/socklib/partyline.py # # """ provide partyline functionality .. manage dcc sockets. """ __copyright__ = 'this file is in the public domain' __author__ = 'Aim' ## jsb imports from jsb.lib.fleet import getfleet from jsb.utils.exception import handle_exception from jsb.lib.threads import start_new_thread from jsb.imports import getjson json = getjson() ## basic imports import thread import pickle import socket import logging ## classes 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 size(self): return len(self.socks) def resume(self, sessionfile): """ resume bot from session file. """ try: session = json.load(open(sessionfile, 'r')) self._resume(session) except: handle_exception() def _resume(self, data, reto=None): """ resume a party line connection after reboot. """ fleet = getfleet() for i in data['partyline']: logging.warn("partyline - resuming %s" % i) bot = fleet.byname(i['botname']) if not bot: logging.error("partyline - can't find bot") ; continue 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: logging.error("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. """ result = [] for i in self.socks: result.append({'botname': i['bot'].cfg.name, 'fileno': i['sock'].fileno(), 'nick': i['nick'], 'userhost': i['userhost'], 'channel': i['channel'], 'silent': i['silent']}) return result def stop(self, bot): """ stop all users on bot. """ 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. """ 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. """ for i in self.socks: if i['nick'] == nick: i['silent'] = False def silent(self, nick): """ disable broadcasting txt from/to nick. """ 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. ''' 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}) logging.warn("partyline - added user %s" % nick) def del_party(self, nick): ''' remove a socket with nick from the list. ''' 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] logging.debug('partyline - removed user %s' % nick) finally: self.lock.release() def list_nicks(self): ''' list all connected 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. ''' 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. ''' 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. ''' 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. ''' nick = nick.lower() for item in self.socks: if item['nick'].lower() == nick: return True return False ## global partyline object partyline = PartyLine() def size(): return partyline.size() jsb-0.84.4/jsb/lib/jsbimport.py0000664000175000017500000000156611733124163016211 0ustar bartbart00000000000000# jsb/jsbimport.py # # """ use the imp module to import modules. """ ## basic imports import time import sys import imp import os import thread import logging ## _import function def _import(name): """ do a import (full). """ mods = [] mm = "" for m in name.split('.'): mm += m mods.append(mm) mm += "." for mod in mods: imp = __import__(mod) logging.debug("jsbimport - got module %s" % sys.modules[name]) return sys.modules[name] ## force_import function def force_import(name): """ force import of module by replacing it in sys.modules. """ try: del sys.modules[name] except KeyError: pass plug = _import(name) return plug def _import_byfile(modname, filename): try: return imp.load_source(modname, filename) except NotImplementedError: return _import(filename[:-3].replace(os.sep, ".")) jsb-0.84.4/jsb/lib/wait.py0000664000175000017500000000726211733124163015143 0ustar bartbart00000000000000# jsb/lib/waiter.py # # """ wait for events. """ ## jsb imports from jsb.lib.runner import waitrunner from jsb.utils.trace import whichmodule from jsb.utils.exception import handle_exception ## basic imports import logging import copy import types import time import uuid ## defines cpy = copy.deepcopy ## Wait class class Wait(object): """ wait object contains a list of types to match and a list of callbacks to call, optional list of userhosts to match the event with can be given. """ def __init__(self, cbtypes, cbs=None, userhosts=None, modname=None, event=None, queue=None): self.created = time.time() if type(cbtypes) != types.ListType: cbtypes = [cbtypes, ] self.cbtypes = cbtypes self.userhosts = userhosts if cbs and type(cbs) != types.ListType: cbs = [cbs, ] self.cbs = cbs self.modname = modname self.origevent = event self.queue = queue def check(self, bot, event): """ check whether event matches this wait object. if so call callbacks. """ target = event.cmnd or event.cbtype logging.debug("waiter - checking for %s - %s" % (target, self.cbtypes)) if target not in self.cbtypes: return if event.channel and self.origevent and not event.channel == self.origevent.channel: logging.warn("waiter - %s and %s dont match" % (event.channel, self.origevent.channel)) return if self.userhosts and event.userhost and event.userhost not in self.userhosts: logging.warn("waiter - no userhost matched") return if self.queue: self.queue.put_nowait(event) self.docbs(bot, event) return event def docbs(self, bot, event): """ do the actual callback .. put callback on the waitrunner for execution. """ if not self.cbs: return logging.warn("%s - found wait match: %s" % (bot.cfg.name, event.dump())) for cb in self.cbs: try: cb(bot, event) #waitrunner.put(event.speed, (self.modname, cb, bot, event)) except Exception, ex: handle_exception() ## Waiter class class Waiter(object): """ list of wait object to match. """ def __init__(self): self.waiters = {} def size(self): return len(self.waiters) def register(self, cbtypes, cbs=None, userhosts=None, event=None, queue=None): """ add a wait object to the waiters dict. """ logging.warn("waiter - registering wait object: %s - %s" % (str(cbtypes), str(userhosts))) key = str(uuid.uuid4()) self.waiters[key] = Wait(cbtypes, cbs, userhosts, modname=whichmodule(), event=event, queue=queue) return key def ready(self, key): try: del self.waiters[key] except KeyError: logging.warn("wait - %s key is not in waiters" % key) def check(self, bot, event): """ scan waiters for possible wait object that match. """ matches = [] for wait in self.waiters.values(): result = wait.check(bot, event) if not wait.cbtypes: matches.append(wait) if matches: self.delete(matches) return matches def delete(self, removed): """ delete a list of wait items from the waiters dict. """ logging.debug("waiter - removing from waiters: %s" % str(removed)) for w in removed: try: del self.waiters[w] except KeyError: pass def remove(self, modname): """ remove all waiter registered by modname. """ removed = [] for wait in self.waiters.values(): if wait.modname == modname: removed.append(wait) if removed: self.delete(removed) ## the global waiter object waiter = Waiter() jsb-0.84.4/jsb/lib/periodical.py0000664000175000017500000002144211733124163016306 0ustar bartbart00000000000000# gozerbot/periodical.py # # """ provide a periodic structure. """ __author__ = "Wijnand 'tehmaze' Modderman - http://tehmaze.com" __license__ = "BSD License" ## jsb imports from jsb.utils.exception import handle_exception from jsb.utils.trace import calledfrom, whichmodule from jsb.utils.locking import lockdec from jsb.utils.timeutils import strtotime from jsb.lib.callbacks import callbacks import jsb.lib.threads as thr ## basic imorts import datetime import sys import time import thread import types import logging ## locks plock = thread.allocate_lock() locked = lockdec(plock) ## defines pidcount = 0 ## JobError class class JobError(Exception): """ job error exception. """ pass ## Job class 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. """ 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. """ 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 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 '' % (str(self.next), str(self.delta), self.repeat, str(self.func)) def check(self): """ run check to see if job needs to be scheduled. """ if self.next <= time.time(): logging.info('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 return True class JobInterval(Job): """ job to be scheduled at certain interval. """ 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 logging.warn('scheduled next run of %s in %d seconds' % (str(self.func), self.interval)) def __repr__(self): return '' % (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. """ if self.next <= time.time(): logging.info('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 return True class Periodical(object): """ periodical scheduler. """ def __init__(self): self.jobs = [] self.running = [] self.run = True def size(self): return len(self.jobs) def addjob(self, sleeptime, repeat, function, description="" , *args, **kw): """ add a periodical job. """ 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. """ for i in periodical.jobs: if i.pid == pid: i.interval = interval i.next = time.time() + interval def looponce(self, bot, event): """ loop over the jobs. """ for job in self.jobs: if job.next <= time.time(): self.runjob(job) def runjob(self, job): """ run a periodical job. """ if not job.check(): self.killjob(job.id()) else: self.running.append(job) def kill(self): """ kill all jobs invoked by another module. """ group = calledfrom(sys._getframe()) self.killgroup(group) def killgroup(self, group): """ kill all jobs with the same group. """ 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 logging.warn('killed %d jobs for %s' % (len(deljobs), group)) del deljobs return shoot() def killjob(self, jobId): """ kill one job by its id. """ 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() def interval(sleeptime, repeat=0): """ interval decorator. """ 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) logging.warn('new interval job %d with sleeptime %d' % (job.id(), sleeptime)) return wrapper return decorator def at(start, interval=1, repeat=1): """ at decorator. """ 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. """ 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) logging.debug('new interval job %d running per second' % job.id()) return wrapper def minutely(function): """ minute decorator. """ 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) logging.warn('new interval job %d running minutely' % job.id()) return wrapper def hourly(function): """ hour decorator. """ logging.warn('@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() logging.warn('new interval job %d running hourly' % job.id()) periodical.jobs.append(job) return wrapper def daily(function): """ day decorator. """ logging.warn('@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) logging.warb('new interval job %d running daily' % job.id()) return wrapper periodical = Periodical() callbacks.add("TICK", periodical.looponce) def size(): return periodical.size() jsb-0.84.4/jsb/lib/__init__.py0000664000175000017500000000016411733124163015730 0ustar bartbart00000000000000# jsb package # # """ jsb core package. """ __version__ = "0.84" import warnings warnings.simplefilter('ignore') jsb-0.84.4/jsb/lib/outputcache.py0000664000175000017500000000317011733124163016515 0ustar bartbart00000000000000# jsb/outputcache.py # # ## jsb imports from persist import Persist from jsb.utils.name import stripname from datadir import getdatadir from jsb.utils.timeutils import hourmin ## basic imports import os import logging import time ## clear function def clear(target): """ clear target's outputcache. """ cache = Persist(getdatadir() + os.sep + 'run' + os.sep + 'outputcache' + os.sep + stripname(target)) try: cache.data['msg'] = [] cache.save() except KeyError: pass return [] ## add function def add(target, txtlist): """ add list of txt to target entry. """ logging.warn("outputcache - adding %s lines" % len(txtlist)) t = [] for item in txtlist: t.append("[%s] %s" % (hourmin(time.time()), item)) cache = Persist(getdatadir() + os.sep + 'run' + os.sep + 'outputcache' + os.sep + stripname(target)) d = cache.data if not d.has_key('msg'): d['msg'] = [] d['msg'].extend(t) while len(d['msg']) > 10: d['msg'].pop(0) cache.save() ## set function def set(target, txtlist): """ set target entry to list. """ cache = Persist(getdatadir() + os.sep + 'run' + os.sep + 'outputcache' + os.sep + stripname(target)) if not cache.data.has_key('msg'): cache.data['msg'] = [] cache.data['msg'] = txtlist cache.save() ## get function def get(target): """ get output for target. """ logging.warn("get target is %s" % target) cache = Persist(getdatadir() + os.sep + 'run' + os.sep + 'outputcache' + os.sep + stripname(target)) try: result = cache.data['msg'] if result: return result except KeyError: pass return [] jsb-0.84.4/jsb/lib/gozerevent.py0000664000175000017500000000643311733124163016366 0ustar bartbart00000000000000# jsb/gozerevent.py # # """ basic event used in jsb. supports json dumping and loading plus toxml functionality. """ ## jsb imports from jsb.utils.url import striphtml from jsb.lib.eventbase import EventBase ## dom imports from jsb.contrib.xmlstream import NodeBuilder, XMLescape, XMLunescape ## for exceptions import xml.parsers.expat ## xmpp imports from jsb.drivers.xmpp.namespace import attributes, subelements ## basic imports import logging ## GozerEvent class class GozerEvent(EventBase): """ dictionairy to store xml stanza attributes. """ def __init__(self, input={}): if input == None: EventBase.__init__(self) else: EventBase.__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 EventBase.__getattr__(self, name, default="") def get(self, name): """ get a attribute by name. """ if self.has_key('subelements'): for i in self['subelements']: if name in i: return i[name] if self.has_key(name): return self[name] return EventBase() def tojabber(self): """ convert the dictionary to xml. """ res = dict(self) if not res: raise Exception("%s .. toxml() can't convert empty dict" % self.name) elem = self['element'] main = "<%s" % self['element'] for attribute in attributes[elem]: if attribute in res: if res[attribute]: main += u" %s='%s'" % (attribute, XMLescape(res[attribute])) continue main += ">" if res.has_key("xmlns"): main += "" % res["xmlns"] ; gotsub = True else: gotsub = False if res.has_key('html'): if res['html']: main += u'%s' % res['html'] gotsub = True if res.has_key('txt'): if res['txt']: main += u"%s" % XMLescape(res['txt']) gotsub = True for subelement in subelements[elem]: if subelement == "body": continue if subelement == "thread": continue try: data = res[subelement] if data: try: main += "<%s>%s" % (subelement, XMLescape(data), subelement) gotsub = True except AttributeError, ex: logging.warn("skipping %s" % subelement) except KeyError: pass if gotsub: main += "" % elem else: main = main[:-1] main += " />" return main toxml = tojabber def str(self): """ convert to string. """ result = "" elem = self['element'] for item, value in dict(self).iteritems(): if item in attributes[elem] or item in subelements[elem] or item == 'txt': result += "%s='%s' " % (item, value) return result jsb-0.84.4/jsb/lib/exit.py0000664000175000017500000000257711733124163015154 0ustar bartbart00000000000000# jsb/exit.py # # """ jsb's finaliser """ ## jsb imports from jsb.utils.locking import globallocked from jsb.utils.exception import handle_exception from jsb.utils.trace import whichmodule from jsb.memcached import killmcdaemon from jsb.lib.persist import cleanup from jsb.lib.boot import ongae from runner import defaultrunner, cmndrunner, callbackrunner, waitrunner ## basic imports import atexit import os import time import sys import logging ## functions @globallocked def globalshutdown(exit=True): """ shutdown the bot. """ try: try: sys.stdout.write("\n") except: pass logging.error('shutting down'.upper()) from fleet import getfleet fleet = getfleet() if fleet: logging.warn('shutting down fleet') fleet.exit() logging.warn('shutting down plugins') from jsb.lib.plugins import plugs plugs.exit() logging.warn("shutting down runners") cmndrunner.stop() callbackrunner.stop() waitrunner.stop() logging.warn("cleaning up any open files") while cleanup(): time.sleep(1) try: os.remove('jsb.pid') except: pass killmcdaemon() logging.warn('done') if not ongae: print "" if exit and not ongae: os._exit(0) except Exception, ex: print str(ex) if exit and not ongae: os._exit(1) jsb-0.84.4/jsb/lib/gatekeeper.py0000664000175000017500000000270011733124163016303 0ustar bartbart00000000000000# jsb/gatekeeper.py # # """ keep a whitelist of allowed entities based on userhost. """ ## jsb imports from jsb.lib.persist import Persist from jsb.lib.datadir import getdatadir ## basic imports import logging import os ## GateKeeper class class GateKeeper(Persist): """ keep a whitelist of allowed entities based on userhost. """ def __init__(self, name): self.name = name try: import waveapi except: if not os.path.exists(getdatadir() + os.sep +'gatekeeper'): os.mkdir(getdatadir() + os.sep + 'gatekeeper') Persist.__init__(self, getdatadir() + os.sep + 'gatekeeper' + os.sep + name) self.data.whitelist = self.data.whitelist or [] def isblocked(self, userhost): """ see if userhost is blocked. """ if not userhost: return False userhost = userhost.lower() if userhost in self.data.whitelist: logging.debug("%s - allowed %s" % (self.fn, userhost)) return False logging.warn("%s - denied %s" % (self.fn, userhost)) return True def allow(self, userhost): """ allow userhost. """ userhost = userhost.lower() if not userhost in self.data.whitelist: self.data.whitelist.append(userhost) self.save() def deny(self, userhost): """ deny access. """ userhost = userhost.lower() if userhost in self.data.whitelist: self.data.whitelist.remove(userhost) jsb-0.84.4/jsb/lib/reboot.py0000664000175000017500000000314011733124163015460 0ustar bartbart00000000000000# jsb/reboot.py # # """ reboot code. """ ## jsb imports from jsb.lib.fleet import getfleet from jsb.imports import getjson json = getjson() ## basic imports import os import sys import pickle import tempfile import logging import time ## reboot function def reboot(): """ reboot the bot. """ logging.warn("reboot - rebooting") os.execl(sys.argv[0], *sys.argv) ## reboot_stateful function def reboot_stateful(bot, ievent, fleet, partyline): """ reboot the bot, but keep the connections (IRC only). """ logging.warn("reboot - doing statefull reboot") session = {'bots': {}, 'name': bot.cfg.name, 'channel': ievent.channel, 'partyline': []} fleet = getfleet() for i in fleet.bots: logging.warn("reboot - updating %s" % i.cfg.name) data = i._resumedata() if not data: continue session['bots'].update(data) if i.type == "sxmpp": i.exit() ; continue if i.type == "convore": i.exit() ; continue if i.type == "tornado": i.exit() time.sleep(0.1) for socketlist in i.websockets.values(): for sock in socketlist: sock.stream.close() session['partyline'] = partyline._resumedata() sfile, sessionfile = tempfile.mkstemp('-session', 'jsb-', text=True) logging.warn("writing session file %s" % sessionfile) json.dump(session, open(sessionfile, "w")) args = [] skip = False for a in sys.argv[1:]: if skip: skip = False ; continue if a == "-r": skip = True ; continue args.append(a) os.execl(sys.argv[0], sys.argv[0], '-r', sessionfile, *args) jsb-0.84.4/jsb/lib/eventhandler.py0000664000175000017500000000464711733124163016662 0ustar bartbart00000000000000# jsb/eventhandler.py # # """ event handler. use to dispatch function in main loop. """ ## jsb imports from jsb.utils.exception import handle_exception from jsb.utils.locking import lockdec from threads import start_new_thread ## basic imports import Queue import thread import logging import time ## locks handlerlock = thread.allocate_lock() locked = lockdec(handlerlock) ## classes class EventHandler(object): """ events are handled in 11 queues with different priorities: queue0 is tried first queue10 last. """ def __init__(self): self.sortedlist = [] try: self.queue = Queue.PriorityQueue() except AttributeError: self.queue = Queue.Queue() self.stopped = False self.running = False self.nooutput = False def start(self): """ start the eventhandler thread. """ self.stopped = False if not self.running: start_new_thread(self.handleloop, ()) self.running = True def handle_one(self): try: speed, todo = self.queue.get_nowait() self.dispatch(todo) except Queue.Empty: pass def stop(self): """ stop the eventhandler thread. """ self.running = False self.stopped = True self.go.put('Yihaaa') def put(self, speed, func, *args, **kwargs): """ put item on the queue. """ self.queue.put_nowait((speed, (func, args, kwargs))) def handleloop(self): """ thread that polls the queues for items to dispatch. """ logging.warn('starting - %s ' % str(self)) while not self.stopped: try: (speed, todo) = self.queue.get() logging.warn("running at speed %s - %s" % (speed, str(todo))) self.dispatch(todo) except Queue.Empty: time.sleep(0.1) except Exception, ex: handle_exception() logging.warn('stopping - %s' % str(self)) runforever = handleloop def dispatch(self, todo): """ dispatch functions from provided queue. """ try: (func, args, kwargs) = todo func(*args, **kwargs) except ValueError: try: (func, args) = todo func(*args) except ValueError: (func, ) = todo func() except: handle_exception() ## handler to use in main prog mainhandler = EventHandler() jsb-0.84.4/jsb/lib/plugins.py0000664000175000017500000001744011733124163015657 0ustar bartbart00000000000000# jsb/plugins.py # # """ holds all the plugins. plugins are imported modules. """ ## jsb imports from thread import start_new_thread from commands import cmnds from callbacks import callbacks, remote_callbacks, first_callbacks, last_callbacks from eventbase import EventBase from persist import Persist from jsb.utils.lazydict import LazyDict from jsb.utils.exception import handle_exception from boot import cmndtable, plugin_packages, default_plugins from errors import NoSuchPlugin, URLNotEnabled, RequireError from jsb.utils.locking import lockdec from jsbimport import force_import, _import from morphs import outputmorphs, inputmorphs from wait import waiter from boot import plugblacklist ## basic imports import os import logging import Queue import copy import sys import thread import types import time from collections import deque ## defines cpy = copy.deepcopy ## locks loadlock = thread.allocate_lock() locked = lockdec(loadlock) ## Plugins class class Plugins(LazyDict): """ the plugins object contains all the plugins. """ loading = LazyDict() def size(self): return len(self) def exit(self): todo = cpy(self) for plugname in todo: self.unload(plugname) def reloadfile(self, filename, force=True): logging.info("reloading %s" % filename) mlist = filename.split(os.sep) mod = [] for m in mlist[::-1]: mod.insert(0, m) if m == "myplugs": break modname = ".".join(mod)[:-3] from boot import plugblacklist if modname in plugblacklist.data: logging.warn("%s is in blacklist .. not loading." % modname) ; return logging.debug("plugs - using %s" % modname) try: self.reload(modname, force) except RequireError, ex: logging.info(str(ex)) def loadall(self, paths=[], force=True): """ load all plugins from given paths, if force is true .. otherwise load all plugins for default_plugins list. """ if not paths: paths = plugin_packages imp = None old = self.keys() new = [] for module in paths: try: imp = _import(module) except ImportError, ex: #handle_exception() logging.warn("no %s plugin package found - %s" % (module, str(ex))) continue except Exception, ex: handle_exception() logging.debug("got plugin package %s" % module) try: for plug in imp.__plugs__: mod = "%s.%s" % (module, plug) try: self.reload(mod, force=force, showerror=True) except RequireError, ex: logging.info(str(ex)) ; continue except KeyError: logging.debug("failed to load plugin package %s" % module) ; continue except Exception, ex: handle_exception() ; continue new.append(mod) except AttributeError: logging.error("no plugins in %s .. define __plugs__ in __init__.py" % module) remove = [x for x in old if x not in new and x not in default_plugins] logging.info("unload list is: %s" % ", ".join(remove)) for mod in remove: self.unload(mod) return new def unload(self, modname): """ unload plugin .. remove related commands from cmnds object. """ logging.info("plugins - unloading %s" % modname) try: self[modname].shutdown() logging.debug('called %s shutdown' % modname) except KeyError: logging.debug("no %s module found" % modname) return False except AttributeError: pass try: cmnds.unload(modname) except KeyError: pass try: first_callbacks.unload(modname) except KeyError: pass try: callbacks.unload(modname) except KeyError: pass try: last_callbacks.unload(modname) except KeyError: pass try: remote_callbacks.unload(modname) except KeyError: pass try: outputmorphs.unload(modname) except: handle_exception() try: inputmorphs.unload(modname) except: handle_exception() try: waiter.remove(modname) except: handle_exception() return True def load_mod(self, modname, force=False, showerror=True, loaded=[]): """ load a plugin. """ if not modname: raise NoSuchPlugin(modname) if not force and modname in loaded: logging.warn("skipping %s" % modname) ; return loaded from boot import plugblacklist if plugblacklist and modname in plugblacklist.data: logging.warn("%s is in blacklist .. not loading." % modname) ; return loaded if self.has_key(modname): try: logging.debug("%s already loaded" % modname) if not force: return self[modname] self[modname] = reload(self[modname]) except Exception, ex: raise else: logging.debug("trying %s" % modname) mod = _import(modname) if not mod: return None try: self[modname] = mod except KeyError: logging.info("failed to load %s" % modname) raise NoSuchPlugin(modname) try: init = getattr(self[modname], 'init') except AttributeError: init = None try: threaded_init = getattr(self[modname], 'init_threaded') except AttributeError: threaded_init = None try: init and init() logging.debug('%s init called' % modname) except RequireError, ex: logging.info(str(ex)) ; return except URLNotEnabled: logging.error("URL fetching is disabled") except Exception, ex: raise try: threaded_init and start_new_thread(threaded_init, ()) logging.debug('%s threaded_init started' % modname) except Exception, ex: raise logging.warn("%s loaded" % modname) return self[modname] def loaddeps(self, modname, force=False, showerror=False, loaded=[]): if not "." in modname: modname = self.getmodule(modname) if not modname: logging.error("no modname found for %s" % modname) ; return [] try: deps = self[modname].__depending__ if deps: logging.warn("dependcies detected: %s" % deps) except (KeyError, AttributeError): deps = [] deps.insert(0, modname) for dep in deps: if dep not in loaded: self.loading[dep] = time.time() if self.has_key(dep): self.unload(dep) try: self.load_mod(dep, force, showerror, loaded) loaded.append(dep) except Exception, ex: del self.loading[dep] ; raise self.loading[dep] = 0 return loaded def reload(self, modname, force=False, showerror=False): """ reload a plugin. just load for now. """ if type(modname) == types.ListType: loadlist = modname else: loadlist = [modname, ] loaded = [] for modname in loadlist: modname = modname.replace("..", ".") loaded.extend(self.loaddeps(modname, force, showerror, [])) return loaded def fetch(self, plugname): mod = self.getmodule(plugname) if mod: self.reload(mod) ; return self.get(mod) def getmodule(self, plugname): for module in plugin_packages: try: imp = _import(module) except ImportError, ex: if "No module" in str(ex): logging.info("no %s plugin package found" % module) continue raise except Exception, ex: handle_exception() ; continue if plugname in imp.__plugs__: return "%s.%s" % (module, plugname) ## global plugins object plugs = Plugins() jsb-0.84.4/jsb/lib/boot.py0000664000175000017500000004110111733124163015130 0ustar bartbart00000000000000# jsb/boot.py # # """ admin related data and functions. """ ## jsb imports from jsb.utils.generic import checkpermissions, isdebian, botuser from jsb.lib.persist import Persist from jsb.lib.aliases import savealiases from jsb.utils.exception import handle_exception from jsb.lib.datadir import makedirs, getdatadir from jsb.lib.config import Config, getmainconfig from jsb.lib.jsbimport import _import from jsb.utils.lazydict import LazyDict from jsb.memcached import startmcdaemon ## basic imports import logging import os import sys import types import copy ## paths sys.path.insert(0, os.getcwd()) sys.path.insert(0, os.getcwd() + os.sep + '..') ## defines try: import waveapi ongae = True logging.warn("GAE detected") plugin_packages = ['jsb.plugs.core', 'jsb.plugs.common', 'jsb.plugs.gae', 'jsb.plugs.wave', 'myplugs'] except ImportError: ongae = False plugin_packages = ['jsb.plugs.core', 'jsb.plugs.common', 'jsb.plugs.socket', 'myplugs'] default_plugins = ['jsb.plugs.core.admin', 'jsb.plugs.core.dispatch', 'jsb.plugs.core.plug', 'jsb.lib.periodical'] logging.info("default plugins are %s" % str(default_plugins)) loaded = False cmndtable = None pluginlist = None callbacktable = None retable = None cmndperms = None shorttable = None timestamps = None plugwhitelist = None plugblacklist = None cpy = copy.deepcopy ## scandir function def scandir(d, dbenable=False): from jsb.lib.plugins import plugs changed = [] try: changed = checktimestamps(d, dbenable) mods = [] if changed: logging.debug("files changed %s" % str(changed)) for plugfile in changed: if not dbenable and os.sep + 'db' in plugfile: logging.warn("db not enabled .. skipping %s" % plugfile) ; continue if ongae and 'socket' in plugfile: logging.warn("on GAE .. skipping %s" % plugfile) ; continue if not ongae and ('gae' in plugfile or 'wave' in plugfile): logging.warn("not on GAE .. skipping %s" % plugfile) ; continue return changed except Exception, ex: logging.error("boot - can't read %s dir." % d) ; handle_exception() if changed: logging.debug("%s files changed -=- %s" % (len(changed), str(changed))) return changed ## boot function def boot(ddir=None, force=False, encoding="utf-8", umask=None, saveperms=True, fast=False, clear=False, loadall=False): """ initialize the bot. """ global plugin_packages if not ongae: try: if os.getuid() == 0: print "don't run the bot as root" os._exit(1) except AttributeError: pass logging.warn("starting!") from jsb.lib.datadir import getdatadir, setdatadir if ddir: setdatadir(ddir) origdir = ddir ddir = ddir or getdatadir() if not ddir: logging.error("can't determine datadir to boot from") ; raise Exception("can't determine datadir") if not ddir in sys.path: sys.path.append(ddir) makedirs(ddir) if os.path.isdir("/var/run/jsb") and botuser() == "jsb": rundir = "/var/run/jsb" else: rundir = ddir + os.sep + "run" try: k = open(rundir + os.sep + 'jsb.pid','w') k.write(str(os.getpid())) k.close() except IOError: pass try: if not ongae: reload(sys) sys.setdefaultencoding(encoding) except (AttributeError, IOError): pass if not ongae: try: if not umask: checkpermissions(getdatadir(), 0700) else: checkpermissions(getdatadir(), umask) except: handle_exception() from jsb.lib.plugins import plugs global loaded global cmndtable global retable global pluginlist global callbacktable global shorttable global cmndperms global timestamps global plugwhitelist global plugblacklist if not retable: retable = Persist(rundir + os.sep + 'retable') if clear: retable.data = {} if not cmndtable: cmndtable = Persist(rundir + os.sep + 'cmndtable') if clear: cmndtable.data = {} if not pluginlist: pluginlist = Persist(rundir + os.sep + 'pluginlist') if clear: pluginlist.data = [] if not callbacktable: callbacktable = Persist(rundir + os.sep + 'callbacktable') if clear: callbacktable.data = {} if not shorttable: shorttable = Persist(rundir + os.sep + 'shorttable') if clear: shorttable.data = {} if not timestamps: timestamps = Persist(rundir + os.sep + 'timestamps') #if clear: timestamps.data = {} if not plugwhitelist: plugwhitelist = Persist(rundir + os.sep + 'plugwhitelist') if not plugwhitelist.data: plugwhitelist.data = [] if not plugblacklist: plugblacklist = Persist(rundir + os.sep + 'plugblacklist') if not plugblacklist.data: plugblacklist.data = [] if not cmndperms: cmndperms = Config('cmndperms', ddir=ddir) changed = [] gotlocal = False dosave = clear or False maincfg = getmainconfig(ddir=ddir) logging.warn("mainconfig used is %s" % maincfg.cfile) if os.path.isdir('jsb'): gotlocal = True packages = find_packages("jsb" + os.sep + "plugs") if ongae: pluglist = [x for x in packages if not 'socket' in x and not 'db' in x] else: pluglist = [x for x in packages if not 'gae' in x and not 'wave' in x and not 'db' in x] for p in pluglist: if p not in plugin_packages: plugin_packages.append(p) for plug in default_plugins: plugs.reload(plug, showerror=True, force=True) changed = scandir(getdatadir() + os.sep + 'myplugs', dbenable=maincfg.dbenable) if changed: logging.debug("myplugs has changed -=- %s" % str(changed)) for plugfile in changed: try: plugs.reloadfile(plugfile, force=True) except Exception, ex: handle_exception() dosave = True configchanges = checkconfig() if configchanges: logging.info("there are configuration changes: %s" % str(configchanges)) for f in configchanges: if 'mainconfig' in f: force = True ; dosave = True if os.path.isdir('jsb'): corechanges = scandir("jsb" + os.sep + "plugs", dbenable=maincfg.dbenable) if corechanges: logging.debug("core changed -=- %s" % str(corechanges)) for plugfile in corechanges: if not maincfg.dbenable and "db" in plugfile: continue try: plugs.reloadfile(plugfile, force=True) except Exception, ex: handle_exception() dosave = True if not ongae and maincfg.dbenable: plugin_packages.append("jsb.plugs.db") try: from jsb.db import getmaindb from jsb.db.tables import tablestxt db = getmaindb() if db: db.define(tablestxt) except Exception, ex: logging.warn("could not initialize database %s" % str(ex)) else: logging.warn("db not enabled, set dbenable = 1 in %s to enable" % getmainconfig().cfile) try: plugin_packages.remove("jsb.plugs.db") except ValueError: pass if force or dosave or not cmndtable.data or len(cmndtable.data) < 100: logging.debug("using target %s" % str(plugin_packages)) plugs.loadall(plugin_packages, force=True) savecmndtable(saveperms=saveperms) savepluginlist() savecallbacktable() savealiases() logging.warn("ready") ## filestamps stuff def checkconfig(): if ongae: return [] changed = [] d = getdatadir() + os.sep + "config" for f in os.listdir(d): if os.path.isdir(d + os.sep + f): dname = d + os.sep + f changed.extend(checktimestamps(d + os.sep + f)) continue m = d + os.sep + f if os.path.isdir(m): continue if "__init__" in f: continue global timestamps try: t = os.path.getmtime(m) if t > timestamps.data[m]: changed.append(m) ; timestamps.data[m] = t ; except KeyError: timestamps.data[m] = os.path.getmtime(m) ; changed.append(m) if changed: timestamps.save() return changed def checktimestamps(d=None, dbenable=False): if ongae: return [] changed = [] for f in os.listdir(d): if os.path.isdir(d + os.sep + f): if f.startswith("."): logging.warn("skipping %s" % f) ; continue dname = d + os.sep + f if not dbenable and 'db' in dname: continue if ongae and 'socket' in dname: logging.info("on GAE .. skipping %s" % dname) ; continue if not ongae and ('gae' in dname or 'wave' in dname): logging.info("not on GAE .. skipping %s" % dname) ; continue splitted = dname.split(os.sep) target = [] for s in splitted[::-1]: target.append(s) if 'jsb' in s: break elif 'myplugs' in s: break package = ".".join(target[::-1]) if not "config" in dname and package not in plugin_packages: logging.warn("adding %s to plugin_packages" % package) ; plugin_packages.append(package) changed.extend(checktimestamps(d + os.sep + f)) if not f.endswith(".py"): continue m = d + os.sep + f global timestamps try: t = os.path.getmtime(m) if t > timestamps.data[m]: changed.append(m) ; timestamps.data[m] = t ; except KeyError: timestamps.data[m] = os.path.getmtime(m) ; changed.append(m) if changed: timestamps.save() return changed def find_packages(d=None): packages = [] for f in os.listdir(d): if os.path.isdir(d + os.sep + f): if f.startswith("."): logging.warn("skipping %s" % f) ; continue dname = d + os.sep + f splitted = dname.split(os.sep) target = [] for s in splitted[::-1]: target.append(s) if 'jsb' in s: break elif 'myplugs' in s: break package = ".".join(target[::-1]) if package not in plugin_packages: logging.info("adding %s to plugin_packages" % package) ; packages.append(package) packages.extend(find_packages(d + os.sep + f)) return packages ## commands related commands def savecmndtable(modname=None, saveperms=True): """ save command -> plugin list to db backend. """ global cmndtable if not cmndtable.data: cmndtable.data = {} if modname: target = LazyDict(cmndtable.data) else: target = LazyDict() global shorttable if not shorttable.data: shorttable.data = {} if modname: short = LazyDict(shorttable.data) else: short = LazyDict() global cmndperms from jsb.lib.commands import cmnds assert cmnds for cmndname, c in cmnds.iteritems(): if modname and c.modname != modname or cmndname == "subs": continue if cmndname and c: target[cmndname] = c.modname cmndperms[cmndname] = c.perms try: s = cmndname.split("-")[1] if not target.has_key(s): if not short.has_key(s): short[s] = [cmndname, ] if cmndname not in short[s]: short[s].append(cmndname) except (ValueError, IndexError): pass logging.warn("saving command table") assert cmndtable assert target cmndtable.data = target cmndtable.save() logging.warn("saving short table") assert shorttable assert short shorttable.data = short shorttable.save() logging.warn("saving RE table") for command in cmnds.regex: retable.data[command.regex] = command.modname assert retable retable.save() if saveperms: logging.warn("saving command perms") cmndperms.save() def removecmnds(modname): """ remove commands belonging to modname form cmndtable. """ global cmndtable assert cmndtable from jsb.lib.commands import cmnds assert cmnds for cmndname, c in cmnds.iteritems(): if c.modname == modname: del cmndtable.data[cmndname] cmndtable.save() def getcmndtable(): """ save command -> plugin list to db backend. """ global cmndtable if not cmndtable: boot() return cmndtable.data ## callbacks related commands def savecallbacktable(modname=None): """ save command -> plugin list to db backend. """ if modname: logging.warn("boot - module name is %s" % modname) global callbacktable assert callbacktable if not callbacktable.data: callbacktable.data = {} if modname: target = LazyDict(callbacktable.data) else: target = LazyDict() from jsb.lib.callbacks import first_callbacks, callbacks, last_callbacks, remote_callbacks for cb in [first_callbacks, callbacks, last_callbacks, remote_callbacks]: for type, cbs in cb.cbs.iteritems(): for c in cbs: if modname and c.modname != modname: continue if not target.has_key(type): target[type] = [] if not c.modname in target[type]: target[type].append(c.modname) logging.warn("saving callback table") assert callbacktable assert target callbacktable.data = target callbacktable.save() def removecallbacks(modname): """ remove callbacks belonging to modname form cmndtable. """ global callbacktable assert callbacktable from jsb.lib.callbacks import first_callbacks, callbacks, last_callbacks, remote_callbacks for cb in [first_callbacks, callbacks, last_callbacks, remote_callbacks]: for type, cbs in cb.cbs.iteritems(): for c in cbs: if not c.modname == modname: continue if not callbacktable.data.has_key(type): callbacktable.data[type] = [] if c.modname in callbacktable.data[type]: callbacktable.data[type].remove(c.modname) logging.warn("saving callback table") assert callbacktable callbacktable.save() def getcallbacktable(): """ save command -> plugin list to db backend. """ global callbacktable if not callbacktable: boot() return callbacktable.data ## plugin list related commands def savepluginlist(modname=None): """ save a list of available plugins to db backend. """ global pluginlist if not pluginlist.data: pluginlist.data = [] if modname: target = cpy(pluginlist.data) else: target = [] from jsb.lib.commands import cmnds assert cmnds for cmndname, c in cmnds.iteritems(): if modname and c.modname != modname: continue if c and not c.plugname: logging.info("boot - not adding %s to pluginlist" % cmndname) ; continue if c and c.plugname not in target and c.enable: target.append(c.plugname) assert target target.sort() logging.warn("saving plugin list") assert pluginlist pluginlist.data = target pluginlist.save() def remove_plugin(modname): removecmnds(modname) removecallbacks(modname) global pluginlist try: pluginlist.data.remove(modname.split(".")[-1]) ; pluginlist.save() except: pass def clear_tables(): global cmndtable global callbacktable global pluginlist cmndtable.data = {} ; cmndtable.save() callbacktable.data = {} ; callbacktable.save() pluginlist.data = [] ; pluginlist.save() def getpluginlist(): """ get the plugin list. """ global pluginlist if not pluginlist: boot() l = plugwhitelist.data or pluginlist.data result = [] denied = [] for plug in plugblacklist.data: denied.append(plug.split(".")[-1]) for plug in l: if plug not in denied: result.append(plug) return result ## update_mod command def update_mod(modname): """ update the tables with new module. """ savecallbacktable(modname) savecmndtable(modname, saveperms=False) savepluginlist(modname) def whatcommands(plug): tbl = getcmndtable() result = [] for cmnd, mod in tbl.iteritems(): if not mod: continue if plug in mod: result.append(cmnd) return result def getcmndperms(): return cmndperms def plugenable(mod): if plugwhitelist.data and not mod in plugwhitelist.data: plugwhitelist.data.append(mod) ; plugwhtelist.save() ; return if mod in plugblacklist.data: plugblacklist.data.remove(mod) ; plugblacklist.save() def plugdisable(mod): if plugwhitelist.data and mod in plugwhitelist.data: plugwhitelist.data.remove(mod) ; plugwhtelist.save() ; return if not mod in plugblacklist.data: plugblacklist.data.append(mod) ; plugblacklist.save() def size(): global cmndtable global pluginlist global callbacktable global cmndperms global timestamps global plugwhitelist global plugblacklist return "cmndtable: %s - pluginlist: %s - callbacks: %s - timestamps: %s - whitelist: %s - blacklist: %s" % (cmndtable.size(), pluginlist.size(), callbacktable.size(), timestamps.size(), plugwhitelist.size(), plugblacklist.size()) jsb-0.84.4/jsb/lib/threadloop.py0000664000175000017500000000651411733124163016337 0ustar bartbart00000000000000# jsb/threadloop.py # # """ class to implement start/stoppable threads. """ ## lib imports from jsb.utils.exception import handle_exception from threads import start_new_thread, getname ## basic imports import Queue import time import logging from collections import deque ## ThreadLoop class class ThreadLoop(object): """ implement startable/stoppable threads. """ def __init__(self, name="", queue=None): self.name = name self.stopped = False self.running = False self.outs = [] try: self.queue = queue or Queue.PriorityQueue() except AttributeError: self.queue = queue or Queue.Queue() self.nowrunning = "none" def _loop(self): """ the threadloops loop. """ logging.warn('starting %s' % getname(self)) self.running = True nrempty = 0 while not self.stopped: try: (speed, data) = self.queue.get() except IndexError: if self.stopped: break time.sleep(0.1) continue if self.stopped: break if not data: break try: self.handle(*data) except Exception, ex: handle_exception() self.running = False logging.warn('stopping %s' % getname(self)) def put(self, speed, *data): """ put data on task queue. """ self.queue.put((speed, data)) def start(self): """ start the thread. """ if not self.running and not self.stopped: return start_new_thread(self._loop, ()) def stop(self): """ stop the thread. """ self.stopped = True self.running = False self.put(0, None) def handle(self, *args, **kwargs): """ overload this. """ pass ## RunnerLoop class class RunnerLoop(ThreadLoop): """ dedicated threadloop for bot commands/callbacks. """ def put(self, speed, *data): """ put data on task queue. """ self.queue.put((speed, data)) def _loop(self): """ runner loop. """ logging.debug('%s - starting threadloop' % self.name) self.running = True while not self.stopped: try: speed, data = self.queue.get() if self.stopped: break if not data: break self.nowrunning = getname(data[1]) self.handle(speed, data) except IndexError: time.sleep(0.1) ; continue except Exception, ex: handle_exception() #self.nowrunning = getname(data[1]) + "-done" self.running = False logging.debug('%s - stopping threadloop' % self.name) class TimedLoop(ThreadLoop): """ threadloop that sleeps x seconds before executing. """ def __init__(self, name, sleepsec=300, *args, **kwargs): ThreadLoop.__init__(self, name, *args, **kwargs) self.sleepsec = sleepsec def _loop(self): """ timed loop. sleep a while. """ logging.warn('%s - starting timedloop (%s seconds)' % (self.name, self.sleepsec)) self.stopped = False self.running = True while not self.stopped: time.sleep(self.sleepsec) if self.stopped: break try: self.handle() except Exception, ex: handle_exception() self.running = False logging.warn('%s - stopping timedloop' % self.name) jsb-0.84.4/jsb/lib/persiststate.py0000664000175000017500000000371311733124163016726 0ustar bartbart00000000000000# jsb/persiststate.py # # """ persistent state classes. """ ## jsb imports from jsb.utils.name import stripname from jsb.utils.trace import calledfrom from persist import Persist from jsb.lib.datadir import getdatadir ## basic imports import types import os import sys import logging ## PersistState classes class PersistState(Persist): """ base persitent state class. """ def __init__(self, filename): Persist.__init__(self, filename) self.types = dict((i, type(j)) for i, j in self.data.iteritems()) def __getitem__(self, key): """ get state item. """ return self.data[key] def __setitem__(self, key, value): """ set state item. """ self.data[key] = value def define(self, key, value): """ define a state item. """ if not self.data.has_key(key) or type(value) != self.types[key]: if type(value) == types.StringType: value = unicode(value) if type(value) == types.IntType: value = long(value) self.data[key] = value class PlugState(PersistState): """ state for plugins. """ def __init__(self, *args, **kwargs): self.plugname = calledfrom(sys._getframe()) logging.debug('persiststate - initialising %s' % self.plugname) PersistState.__init__(self, getdatadir() + os.sep + 'state' + os.sep + 'plugs' + os.sep + self.plugname + os.sep + 'state') class ObjectState(PersistState): """ state for usage in constructors. """ def __init__(self, *args, **kwargs): PersistState.__init__(self, getdatadir() + os.sep + 'state' + os.sep + calledfrom(sys._getframe(1))+'.state') class UserState(PersistState): """ state for users. """ def __init__(self, username, filename="state", *args, **kwargs): assert username username = stripname(username) ddir = getdatadir() + os.sep + 'state' + os.sep + 'users' + os.sep + username PersistState.__init__(self, ddir + os.sep + filename) jsb-0.84.4/jsb/lib/examples.py0000664000175000017500000000202711733124163016007 0ustar bartbart00000000000000# jsb/examples.py # # """ examples is a dict of example objects. """ ## basic imports import re ## Example class class Example(object): """ an example. """ def __init__(self, descr, ex, url=False): self.descr = descr self.example = ex self.url = url ## Collection of exanples class Examples(dict): """ examples holds all the examples. """ def add(self, name, descr, ex, url=False): """ add description and example. """ self[name.lower()] = Example(descr, ex, url) def size(self): """ return size of examples dict. """ return len(self.keys()) def getexamples(self): """ get all examples in list. """ 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 ## global examples object examples = Examples() def size(): return examples.size() jsb-0.84.4/jsb/lib/users.py0000664000175000017500000003772211733124163015344 0ustar bartbart00000000000000# jsb/users.py # # """ bot's users in JSON file. NOT USED AT THE MOMENT. """ ## lib imports from jsb.utils.exception import handle_exception, exceptionmsg from jsb.utils.generic import stripped from jsb.utils.name import stripname from persiststate import UserState from persist import Persist from jsb.utils.lazydict import LazyDict from datadir import getdatadir from errors import NoSuchUser from config import Config, getmainconfig ## basic imports import re import types import os import time import logging import copy ## defines cpy = copy.deepcopy ## JsonUser class class JsonUser(Persist): """ LazyDict representing a user. """ def __init__(self, name, userhosts=[], perms=[], permits=[], status=[], email=[]): assert name name = stripname(name.lower()) Persist.__init__(self, getdatadir() + os.sep + 'users' + os.sep + name) self.data.datadir = self.data.datadir or getdatadir() self.data.name = self.data.name or name self.data.userhosts = self.data.userhosts or list(userhosts) self.data.perms = self.data.perms or list(perms) self.data.permits = self.data.permits or list(permits) self.data.status = self.data.status or list(status) self.data.email = self.data.email or list(email) self.state = UserState(name) ## Users class class Users(Persist): """ class representing all users. """ def __init__(self, ddir=None, filename=None): self.datadir = ddir or getdatadir() self.filename = filename or 'mainusers' Persist.__init__(self, self.datadir + os.sep + 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).lower()) 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.values() def byname(self, name): """ return user by name. """ try: name = name.lower() #name = stripname(name) user = JsonUser(name) if user.data.userhosts and not user.data.deleted: return user except KeyError: raise NoSuchUser(name) def merge(self, name, userhost): """ add userhosts to user with name """ name = name.lower() user = self.byname(name) if user: if not userhost in user.data.userhosts: user.data.userhosts.append(userhost) user.save() self.data.names[userhost] = name self.save() logging.warn("%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.lower(), u)) return result def getuser(self, userhost): """ get user based on userhost. """ try: user = self.byname(self.data.names[userhost]) if user: return user except KeyError: logging.debug("can't find %s in names cache" % userhost) ## Check functions def exist(self, name): """ see if user with exists """ return self.byname(name) def allowed(self, userhost, perms, log=True, bot=None): """ 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 if bot and bot.allowall: return 1 res = None user = self.getuser(userhost) if not user: logging.warn('%s userhost denied' % userhost) return res else: uperms = set(user.data.perms) sperms = set(perms) intersection = sperms.intersection(uperms) res = list(intersection) or None if not res and log: logging.warn("%s perm %s denied (%s)" % (userhost, str(perms), str(uperms))) 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.data.permits: res = 1 return res def status(self, userhost, status): """ check if user with has set """ user = self.getuser(userhost) res = None if user: if status.upper() in user.data.status: res = 1 return res def gotuserhost(self, name, userhost): """ check if user has userhost """ user = self.byname(name) return userhost in user.data.userhosts def gotperm(self, name, perm): """ check if user had permission """ user = self.byname(name) if user: return perm.upper() in user.data.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.data.permits def gotstatus(self, name, status): """ check if user has status """ user = self.byname(name) return status.upper() in user.data.status ## Get Functions def getname(self, userhost): """ get name of user belonging to """ user = self.getuser(userhost) if user and not user.data.deleted: return user.data.name.lower() def gethosts(self, userhost): """ return the userhosts of the user associated with the specified userhost """ user = self.getuser(userhost) if user: return user.data.userhosts def getemail(self, userhost): """ return the email of the specified userhost """ user = self.getuser(userhost) if user: if user.data.email: return user.data.email[0] def getperms(self, userhost): """ return permission of user""" user = self.getuser(userhost) if user: return user.data.perms def getpermits(self, userhost): """ return permits of the specified userhost""" user = self.getuser(userhost) if user: return user.data.permits def getstatuses(self, userhost): """ return the list of statuses for the specified userhost. """ user = self.getuser(userhost) if user: return user.data.status def getuserhosts(self, name): """ return the userhosts associated with the specified user. """ user = self.byname(name) if user: return user.data.userhosts def getuseremail(self, name): """ get email of user. """ user = self.byname(name) if user: if user.data.email: return user.data.email[0] def getuserperms(self, name): """ return permission of user. """ user = self.byname(name) if user: return user.data.perms def getuserpermits(self, name): """ return permits of user. """ user = self.byname(name) if user: return user.data.permits def getuserstatuses(self, name): """ return the list of statuses for the specified user. """ user = self.byname(name) if user: return user.data.status def getpermusers(self, perm): """ return all users that have the specified perm. """ result = [] names = cpy(self.data.names) for name in names: user = JsonUser(name) if perm.upper() in user.data.perms: result.append(user.data.name) return result def getstatususers(self, status): """ return all users that have the specified status. """ result = [] for name in self.data.names: user = JsonUser(name.lower()) if status in user.data.status: result.append(user.data.name.lower()) return result ## Set Functions def setemail(self, name, email): """ set email of user. """ user = self.byname(name) if user: try: user.data.email.remove(email) except: pass user.data.email.insert(0, email) user.save() return True return False ## Add functions def add(self, name, userhosts, perms): """ add an user. """ name = name.lower() newuser = JsonUser(name, userhosts, perms) for userhost in userhosts: self.data.names[userhost] = name newuser.save() self.save() logging.warn('%s added to user database - %s' % (name, ", ".join(perms))) return newuser def addguest(self, userhost, username=None): if username and self.byname(username): username = userhost if not self.getname(userhost): if getmainconfig().guestasuser: return self.add(username, [userhost, ], ["USER",]) else: return self.add(username, [userhost, ], ["GUEST",]) else: logging.warn("%s is already in database" % userhost) def addemail(self, userhost, email): """ add an email address to the userhost. """ user = self.getuser(userhost) if user: user.data.email.append(email) user.save() return 1 def addperm(self, userhost, perm): """ add the specified perm to the userhost. """ user = self.getuser(userhost) if user: user.data.perms.append(perm.upper()) user.save() return 1 def addpermit(self, userhost, permit): """ add the given (who, what) permit to the given userhost. """ user = self.getuser(userhost) if user: user.data.permits.append(permit) user.save() return 1 def addstatus(self, userhost, status): """ add status to given userhost. """ user = self.getuser(userhost) if user: user.data.status.append(status.upper()) user.save() return 1 def adduserhost(self, name, userhost): """ add userhost. """ name = name.lower() user = self.byname(name) if not user: user = self.users[name] = JsonUser(name=name) user.data.userhosts.append(userhost) user.save() self.data.names[userhost] = name self.save() return 1 def adduseremail(self, name, email): """ add email to specified user. """ user = self.byname(name) if user: user.data.email.append(email) user.save() return 1 def adduserperm(self, name, perm): """ add permission. """ user = self.byname(name) if user: perm = perm.upper() user.data.perms.append(perm) user.save() return 1 def adduserpermit(self, name, who, permit): """ add (who, what) permit tuple to sepcified user. """ user = self.byname(name) if user: p = '%s %s' % (who, permit) user.data.permits.append(p) user.save() return 1 def adduserstatus(self, name, status): """ add status to given user. """ user = self.byname(name) if user: user.data.status.append(status.upper()) user.save() return 1 def addpermall(self, perm): """ add permission to all users. """ for name in self.data.names: user = JsonUser(name.lower()) user.data.perms.append(perm.upper()) user.save() ## Delete functions def delemail(self, userhost, email): """ delete email from userhost. """ user = self.getuser(userhost) if user: if email in user.emails: user.data.emails.remove(email) user.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.data.perms.remove(p) user.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.data.permits.remove(p) user.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.data.status: user.data.status.remove(st) user.save() return 1 def delete(self, name): """ delete user with name. """ try: name = name.lower() user = JsonUser(name) logging.warn("deleting %s - %s" % (name, user)) user.data.deleted = True user.save() if user: for userhost in user.data.userhosts: try: del self.data.names[userhost] except KeyError: logging.warn("can't delete %s from names cache" % userhost) self.save() return True except NoSuchUser: pass def deluserhost(self, name, userhost): """ delete the userhost entry. """ user = self.byname(name) if user: if userhost in user.data.userhosts: user.data.userhosts.remove(userhost) user.save() try: del self.data.names[userhost] ; self.save() except KeyError: pass return 1 def deluseremail(self, name, email): """ delete email. """ user = self.byname(name) if user: if email in user.data.email: user.data.email.remove(email) user.save() return 1 def deluserperm(self, name, perm): """ delete permission. """ user = self.byname(name) if user: p = perm.upper() if p in user.data.perms: user.data.perms.remove(p) user.save() return 1 def deluserpermit(self, name, permit): """ delete permit. """ user = self.byname(name) if user: p = '%s %s' % permit if p in user.data.permits: user.data.permits.remove(p) user.save() 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.data.status: user.data.status.remove(status) user.save() return 1 def delallemail(self, name): """ delete all emails for the specified user. """ user = self.byname(name) if user: user.data.email = [] user.save() return 1 def make_owner(self, userhosts): """ see if owner already has a user account if not add it. """ if not userhosts: logging.info("no usershosts provided in make_owner") return owner = [] if type(userhosts) != types.ListType: owner.append(userhosts) else: owner = userhosts for userhost in owner: username = self.getname(unicode(userhost)) if not username or username != 'owner': if not self.merge('owner', unicode(userhost)): self.add('owner', [unicode(userhost), ], ['USER', 'OPER', 'GUEST']) ## global users object users = None ## users_boot function def users_boot(): """ initialize global users object. """ global users users = Users() return users def getusers(): global users if not users: users = users_boot() return users def size(): return getusers().size()jsb-0.84.4/jsb/lib/threads.py0000664000175000017500000000631711733124163015631 0ustar bartbart00000000000000# jsb/threads.py # # """ own threading wrapper. """ ## jsb imports from jsb.utils.exception import handle_exception ## basic imports import threading import re import time import thread import logging import uuid ## defines # RE to determine thread name methodre = re.compile('method\s+(\S+)', re.I) funcre = re.compile('function\s+(\S+)', re.I) objectre = re.compile('<(\S+)\s+object at (\S+)>', re.I) ## Botcommand class 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.bot = args[0] self.ievent = args[1] self.setDaemon(True) def run(self): """ run the bot command. """ try: self.bot.benice() result = threading.Thread.run(self) self.ievent.ready() except Exception, ex: handle_exception(self.ievent) time.sleep(1) ## Thr class class Thr(threading.Thread): """ thread wrapper. """ def __init__(self, group, target, name, args, kwargs): threading.Thread.__init__(self, None, target, name, args, kwargs) self.setDaemon(True) self.name = name def run(self): """ run the thread. """ try: logging.debug('threads - running thread %s' % self.name) threading.Thread.run(self) except Exception, ex: handle_exception() time.sleep(1) ## getname function def getname(func): """ get name of function/method. """ name = str(func) method = re.search(methodre, name) if method: name = method.group(1) else: function = re.search(funcre, name) if function: name = function.group(1) else: object = re.search(objectre, name) if object: name = "%s-%s" % (object.group(1), object.group(2)) else: name = str(func) return name ## start_new_thread function def start_new_thread(func, arglist, kwargs={}): """ start a new thread .. set name to function/method name.""" if not kwargs: kwargs = {} if not 'name' in kwargs: name = getname(func) if not name: name = str(func) else: name = kwargs['name'] try: thread = Thr(None, target=func, name=name, args=arglist, kwargs=kwargs) thread.start() return thread except Exception, ex: if "can't start" in str(ex): logging.error("threads - thread space is exhausted - can't start thread %s" % name) handle_exception() time.sleep(3) ## start_bot_cpmmand function 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) thread.start() return thread except: handle_exception() time.sleep(1) def threaded(func): """ threading decorator. """ def threadedfunc(*args, **kwargs): start_new_thread(func, args, kwargs) return threadedfunc jsb-0.84.4/jsb/lib/eventbase.py0000664000175000017500000003065011733124163016150 0ustar bartbart00000000000000# jsb/lib/eventbase.py # # """ base class of all events. """ ## jsb imports from channelbase import ChannelBase from jsb.utils.lazydict import LazyDict from jsb.utils.generic import splittxt, stripped, waitforqueue from errors import NoSuchUser, NoSuchCommand, RequireError from jsb.utils.opts import makeeventopts from jsb.utils.trace import whichmodule from jsb.utils.exception import handle_exception from jsb.utils.locking import lockdec from jsb.lib.config import Config, getmainconfig from jsb.lib.users import getusers from jsb.lib.commands import cmnds from jsb.lib.floodcontrol import floodcontrol ## basic imports from collections import deque from xml.sax.saxutils import unescape import copy import logging import Queue import types import socket import threading import time import thread import urllib import uuid ## defines cpy = copy.deepcopy lock = thread.allocate_lock() locked = lockdec(lock) ## classes class EventBase(LazyDict): """ basic event class. """ def __init__(self, input={}, bot=None): LazyDict.__init__(self) if bot: self.bot = bot self.ctime = time.time() self.speed = self.speed or 5 self.nrout = self.nrout or 0 if input: self.copyin(input) if not self.token: self.setup() def copyin(self, eventin): """ copy in an event. """ self.update(eventin) return self def setup(self): self.token = self.token or str(uuid.uuid4().hex) self.finished = threading.Condition() self.busy = deque() self.inqueue = deque() self.outqueue = deque() self.resqueue = deque() self.ok = threading.Event() return self def __deepcopy__(self, a): """ deepcopy an event. """ e = EventBase(self) return e def launched(self): logging.info(str(self)) self.ok.set() def startout(self): if not self.nodispatch and not self.token in self.busy: self.busy.append(self.token) def ready(self, what=None, force=False): """ signal the event as ready - push None to all queues. """ if self.nodispatch: return if not "TICK" in self.cbtype: logging.info(self.busy) try: self.busy.remove(self.token) except ValueError: pass if not self.busy or force: self.notify() def notify(self, p=None): self.finished.acquire() self.finished.notifyAll() self.finished.release() if not "TICK" in self.cbtype: logging.info("notified %s" % str(self)) def execwait(self, direct=False): from jsb.lib.commands import cmnds e = self.bot.put(self) if e: return e.wait() else: logging.info("no response for %s" % self.txt) ; return logging.info("%s wont dispatch" % self.txt) def wait(self, nr=1000): nr = int(nr) result = [] #if self.nodispatch: return if not self.busy: self.startout() self.finished.acquire() while nr > 0 and (self.busy and not self.dostop): self.finished.wait(0.1) ; nr -= 100 self.finished.release() if self.wait and self.thread: logging.warn("joining thread %s" % self.thread) ; self.thread.join(nr/1000) if not "TICK" in self.cbtype: logging.info(self.busy) if not self.resqueue: res = waitforqueue(self.resqueue, nr) else: res = self.resqueue return list(res) def waitandout(self, nr=1000): res = self.wait(nr) if res: for r in res: self.reply(r) def execute(self, direct=False, *args, **kwargs): """ dispatch event onto the cmnds object. this method needs both event.nodispatch = False amd event.iscommand = True set. """ logging.debug("execute %s" % self.cbtype) from jsb.lib.commands import cmnds res = self self.startout() self.bind(self.bot, force=True, dolog=True) if not self.pipelined and ' ! ' in self.txt: res = self.dopipe(direct, *args, **kwargs) else: try: res = cmnds.dispatch(self.bot, self, direct=direct, *args, **kwargs) except RequireError, ex: logging.error(str(ex)) except NoSuchCommand, ex: logging.error("we don't have a %s command" % str(ex)) except NoSuchUser, ex: logging.error("we don't have user for %s" % str(ex)) except Exception , ex: handle_exception() return res def dopipe(self, direct=False, *args, **kwargs): """ split cmnds, create events for them, chain the queues and dispatch. """ direct = True logging.warn("starting pipeline") origout = self.outqueue events = [] self.pipelined = True splitted = self.txt.split(" ! ") for i in range(len(splitted)): t = splitted[i].strip() if not t: continue if t[0] != ";": t = ";" + t e = self.bot.make_event(self.userhost, self.channel, t) e.outqueue = deque() e.busy = deque() e.prev = None e.pipelined = True e.dontbind = False if not e.woulddispatch(): raise NoSuchCommand(e.txt) events.append(e) prev = None for i in range(len(events)): if i > 0: events[i].inqueue = events[i-1].outqueue events[i].prev = events[i-1] events[-1].pipelined = False events[-1].dontclose = False for i in range(len(events)): if not self.bot.isgae and not direct: self.bot.put(events[i]) else: events[i].execute(direct) return events[-1] def prepare(self, bot=None): """ prepare the event for dispatch. """ if bot: self.bot = bot or self.bot assert(self.bot) self.origin = self.channel self.bloh() self.makeargs() if not self.nolog: logging.debug("%s - prepared event - %s" % (self.auth, self.cbtype)) return self def bind(self, bot=None, user=None, chan=None, force=False, dolog=None): """ bind event.bot event.user and event.chan to execute a command on it. """ #if self.nodispatch: logging.debug("nodispatch is set on event . .not binding"); return dolog = dolog or 'TICK' not in self.cbtype if dolog and not force and self.dontbind: logging.debug("dontbind is set on event . .not binding"); return if not force and self.bonded and (bot and not bot.isgae): logging.debug("already bonded") ; return dolog and logging.debug("starting bind on %s - %s" % (self.userhost, self.txt)) target = self.auth or self.userhost bot = bot or self.bot if not self.chan: if chan: self.chan = chan elif self.channel: self.chan = ChannelBase(self.channel, bot.cfg.name) elif self.userhost: self.chan = ChannelBase(self.userhost, bot.cfg.name) if self.chan: #self.debug = self.chan.data.debug or False dolog and logging.debug("channel bonded - %s" % self.chan.data.id) self.prepare(bot) if not target: self.bonded = True ; return if not self.user and target and not self.nodispatch: if user: u = user else: u = bot.users.getuser(target) if not u: cfg = getmainconfig() if cfg.auto_register and self.iscommand: u = bot.users.addguest(target, self.nick) if u: logging.warn("auto_register applied") else: logging.error("can't add %s to users database" % target) if u: msg = "!! %s -=- %s -=- %s -=- (%s) !!" % (u.data.name, self.usercmnd or "none", self.cbtype, self.bot.cfg.name) dolog and logging.warn(msg) self.user = u if self.user: dolog and logging.debug("user bonded from %s" % whichmodule()) if not self.user and target: dolog and self.iscommand and logging.warn("no %s user found" % target) ; self.nodispatch = True if self.bot: self.inchan = self.channel in self.bot.state.data.joinedchannels self.bonded = True return self def bloh(self, bot=None, *args, **kwargs): """ overload this. """ if not self.txt: return self.bot = bot or self.bot self.execstr = self.iscmnd() if self.execstr: self.usercmnd = self.execstr.split()[0] self.nodispatch = False self.iscommand = True else: logging.debug("can't detect a command on %s (%s)" % (self.txt, self.cbtype)) def reply(self, txt, result=[], event=None, origin="", dot=u", ", nr=375, extend=0, showall=False, *args, **kwargs): """ reply to this event """ try: target = self.channel or self.arguments[1] except (IndexError, TypeError): target = self.channel or "nochannel" if self.silent: self.msg = True self.bot.say(self.nick, txt, result, self.userhost, extend=extend, event=self, dot=dot, nr=nr, showall=showall, *args, **kwargs) elif self.isdcc: self.bot.say(self.sock, txt, result, self.userhost, extend=extend, event=self, dot=dot, nr=nr, showall=showall, *args, **kwargs) else: self.bot.say(target, txt, result, self.userhost, extend=extend, event=self, dot=dot, nr=nr, showall=showall, *args, **kwargs) return self def missing(self, txt): """ display missing arguments. """ if self.alias: l = len(self.alias.split()) - 1 else: l = 0 t = ' '.join(txt.split()[l:]) self.reply("%s %s" % (self.aliased or self.usercmnd, t), event=self) return self def done(self): """ tell the user we are done. """ self.reply('done - %s' % (self.usercmnd or self.alias or selt.txt), event=self) return self def leave(self): """ lower the time to leave. """ self.ttl -= 1 if self.ttl <= 0 : self.status = "done" def makeoptions(self): """ check the given txt for options. """ try: self.options = makeeventopts(self.txt) except: handle_exception() ; return if not self.options: return if self.options.channel: self.target = self.options.channel logging.debug("options - %s" % unicode(self.options)) self.txt = ' '.join(self.options.args) self.makeargs() def makeargs(self): """ make arguments and rest attributes from self.txt. """ if not self.execstr: self.args = [] self.rest = "" else: args = self.execstr.split() self.chantag = args[0] if len(args) > 1: self.args = args[1:] self.rest = ' '.join(self.args) else: self.args = [] self.rest = "" def makeresponse(self, txt, result, dot=u", ", *args, **kwargs): """ create a response from a string and result list. """ return self.bot.makeresponse(txt, result, dot, *args, **kwargs) def less(self, what, nr=365): """ split up in parts of chars overflowing on word boundaries. """ return self.bot.less(what, nr) def isremote(self): """ check whether the event is off remote origin. """ return self.txt.startswith('{"') or self.txt.startswith("{&") def iscmnd(self): """ check if event is a command. """ if not self.txt: return "" if not self.bot: return "" if self.txt[0] in self.getcc(): return self.txt[1:] matchnick = unicode(self.bot.cfg.nick + u":") if self.txt.startswith(matchnick): return self.txt[len(matchnick):] matchnick = unicode(self.bot.cfg.nick + u",") if self.txt.startswith(matchnick): return self.txt[len(matchnick):] if self.iscommand and self.execstr: return self.execstr return "" hascc = stripcc = iscmnd def gotcc(self): if not self.txt: return False return self.txt[0] in self.getcc() def getcc(self): if self.chan: cc = self.chan.data.cc else: cc = "" if not cc: cfg = getmainconfig() if cfg.globalcc and not cfg.globalcc in cc: cc += cfg.globalcc if not cc: cc = "!;" if not ";" in cc: cc += ";" logging.info("cc is %s" % cc) return cc def blocked(self): return floodcontrol.checkevent(self) def woulddispatch(self): cmnds.reloadcheck(self.bot, self) return cmnds.woulddispatch(self.bot, self) def wouldmatchre(self): return cmnds.wouldmatchre(self.bot, self) jsb-0.84.4/jsb/lib/datadir.py0000664000175000017500000001373611733124163015612 0ustar bartbart00000000000000# jsb/datadir.py # # """ the data directory of the bot. """ ## jsb imports from jsb.utils.source import getsource ## basic imports import re import os import shutil import logging import os.path import getpass ## the global datadir try: homedir = os.path.abspath(os.path.expanduser("~")) except: homedir = os.getcwd() isgae = False try: import waveapi logging.info("datadir - skipping makedirs") ; datadir = "data" ; isgae = True except ImportError: logging.info("datadir - shell detected") ; datadir = homedir + os.sep + ".jsb" ## helper functions def touch(fname): """ touch a file. """ fd = os.open(fname, os.O_WRONLY | os.O_CREAT) os.close(fd) def doit(ddir, mod, target=None): source = getsource(mod) if not source: raise Exception("can't find %s package" % mod) shutil.copytree(source, ddir + os.sep + (target or mod.replace(".", os.sep))) ## makedir function def makedirs(ddir=None): """ make subdirs in datadir. """ #if os.path.exists("/home/jsb/.jsb") and getpass.getuser() == 'jsb': ddir = "/home/jsb/.jsb" global datadir datadir = ddir or getdatadir() logging.warn("datadir - set to %s" % datadir) if isgae: return if not os.path.isdir(ddir): try: os.mkdir(ddir) except: raise Exception("can't make %s dir" % ddir) logging.info("making dirs in %s" % ddir) try: os.chmod(ddir, 0700) except: pass if ddir: setdatadir(ddir) last = datadir.split(os.sep)[-1] #if not os.path.isdir(ddir): doit(ddir, "jsb.data") try: doit(ddir, "jsb.plugs.myplugs") except: pass try: doit(ddir, "jsb.data.examples") except: pass try: doit(ddir, "jsb.data.static", "static") except: pass try: doit(ddir, "jsb.data.templates", "templates") except: pass try: touch(ddir + os.sep + "__init__.py") except: pass if not os.path.isdir(ddir + os.sep + "config"): os.mkdir(ddir + os.sep + "config") if not os.path.isfile(ddir + os.sep + 'config' + os.sep + "mainconfig"): source = getsource("jsb.data.examples") if not source: raise Exception("can't find jsb.data.examples package") try: shutil.copy(source + os.sep + 'mainconfig.example', ddir + os.sep + 'config' + os.sep + 'mainconfig') except (OSError, IOError), ex: logging.error("datadir - failed to copy jsb.data.config.mainconfig: %s" % str(ex)) if not os.path.isfile(ddir + os.sep + 'config' + os.sep + "credentials.py"): source = getsource("jsb.data.examples") if not source: raise Exception("can't find jsb.data.examples package") try: shutil.copy(source + os.sep + 'credentials.py.example', ddir + os.sep + 'config' + os.sep + 'credentials.py') except (OSError, IOError), ex: logging.error("datadir - failed to copy jsb.data.config: %s" % str(ex)) try: touch(ddir + os.sep + "config" + os.sep + "__init__.py") except: pass # myplugs initsource = getsource("jsb.plugs.myplugs") if not initsource: raise Exception("can't find jsb.plugs.myplugs package") initsource = initsource + os.sep + "__init__.py" if not os.path.isdir(ddir + os.sep + 'myplugs'): os.mkdir(ddir + os.sep + 'myplugs') if not os.path.isfile(ddir + os.sep + 'myplugs' + os.sep + "__init__.py"): try: shutil.copy(initsource, os.path.join(ddir, 'myplugs', '__init__.py')) except (OSError, IOError), ex: logging.error("datadir - failed to copy myplugs/__init__.py: %s" % str(ex)) # myplugs.common if not os.path.isdir(os.path.join(ddir, 'myplugs', 'common')): os.mkdir(os.path.join(ddir, 'myplugs', 'common')) if not os.path.isfile(os.path.join(ddir, 'myplugs', "common", "__init__.py")): try: shutil.copy(initsource, os.path.join(ddir, 'myplugs', 'common', '__init__.py')) except (OSError, IOError), ex: logging.error("datadir - failed to copy myplugs/common/__init__.py: %s" % str(ex)) # myplugs.gae if not os.path.isdir(os.path.join(ddir, 'myplugs', 'gae')): os.mkdir(os.path.join(ddir, 'myplugs', 'gae')) if not os.path.isfile(os.path.join(ddir, 'myplugs', "gae", "__init__.py")): try: shutil.copy(initsource, os.path.join(ddir, 'myplugs', 'gae', '__init__.py')) except (OSError, IOError), ex: logging.error("datadir - failed to copy myplugs/gae/__init__.py: %s" % str(ex)) # myplugs.socket if not os.path.isdir(os.path.join(ddir, 'myplugs', 'socket')): os.mkdir(os.path.join(ddir, 'myplugs', 'socket')) if not os.path.isfile(os.path.join(ddir, 'myplugs', 'socket', "__init__.py")): try: shutil.copy(initsource, os.path.join(ddir, 'myplugs', 'socket', '__init__.py')) except (OSError, IOError), ex: logging.error("datadir - failed to copy myplugs/socket/__init__.py: %s" % str(ex)) if not os.path.isdir(ddir + os.sep +'botlogs'): os.mkdir(ddir + os.sep + 'botlogs') 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 + '/channels/'): os.mkdir(ddir + '/channels/') 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/') if not os.path.isdir(ddir + '/containers/'): os.mkdir(ddir + '/containers/') if not os.path.isdir(ddir + '/chatlogs/'): os.mkdir(ddir + '/chatlogs/') if not os.path.isdir(ddir + '/botlogs/'): os.mkdir(ddir + '/botlogs/') if not os.path.isdir(ddir + '/spider/'): os.mkdir(ddir + '/spider/') if not os.path.isdir(ddir + '/spider/data/'): os.mkdir(ddir + '/spider/data') if os.path.isfile(ddir + '/globals'): try: os.rename(ddir + '/globals', ddir + '/globals.old') except: pass if not os.path.isdir(ddir + '/globals/'): os.mkdir(ddir + '/globals/') def getdatadir(): global datadir return datadir def setdatadir(ddir): global datadir datadir = ddir jsb-0.84.4/jsb/lib/callbacks.py0000664000175000017500000002012211733124163016104 0ustar bartbart00000000000000# jsb/callbacks.py # # """ bot callbacks .. callbacks take place on registered events. a precondition function can optionaly be provided to see if the callback should fire. """ ## jsb imports from threads import getname, start_new_thread from jsb.utils.locking import lockdec from jsb.utils.exception import handle_exception from jsb.utils.trace import calledfrom, whichplugin, callstack from jsb.utils.dol import Dol ## basic imports import sys import copy import thread import logging import time ## locks lock = thread.allocate_lock() locked = lockdec(lock) ## Callback class class Callback(object): """ class representing a callback. """ def __init__(self, modname, func, prereq, kwargs, threaded=False, speed=5): self.modname = modname self.plugname = self.modname.split('.')[-1] self.func = func # the callback function self.prereq = prereq # pre condition function 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 self.enable = True ## Callbacks class (holds multiple callbacks) 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 = Dol() def size(self): """ return number of callbacks. """ return self.cbs.size() def add(self, what, func, prereq=None, kwargs=None, threaded=False, nr=False, speed=5): """ add a callback. """ what = what.upper() modname = calledfrom(sys._getframe()) if not kwargs: kwargs = {} if nr != False: self.cbs.insert(nr, what, Callback(modname, func, prereq, kwargs, threaded, speed)) else: self.cbs.add(what, Callback(modname, func, prereq, kwargs, threaded, speed)) logging.debug('added %s (%s)' % (what, modname)) return self register = add def unload(self, modname): """ unload all callbacks registered in a plugin. """ unload = [] for name, cblist in self.cbs.iteritems(): index = 0 for item in cblist: if item.modname == modname: unload.append((name, index)) index += 1 for callback in unload[::-1]: self.cbs.delete(callback[0], callback[1]) logging.debug(' unloaded %s (%s)' % (callback[0], modname)) def disable(self, plugname): """ disable all callbacks registered in a plugin. """ unload = [] 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 = [] 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() 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 = [] for cmnd, callbacks in self.cbs.iteritems(): for cb in callbacks: result.append(getname(cb.func)) return result def check(self, bot, event): """ check for callbacks to be fired. """ self.reloadcheck(bot, event) type = event.cbtype or event.cmnd if self.cbs.has_key('ALL'): for cb in self.cbs['ALL']: self.callback(cb, bot, event) if self.cbs.has_key(type): target = self.cbs[type] for cb in target: self.callback(cb, bot, event) def callback(self, cb, bot, event): """ do the actual callback with provided bot and event as arguments. """ #if event.stop: logging.info("callbacks - event is stopped.") ; return event.calledfrom = cb.modname if not event.bonded: event.bind(bot) try: if event.status == "done": if not event.nolog: logging.debug("callback - event is done .. ignoring") return if event.chan and cb.plugname in event.chan.data.denyplug: logging.debug("%s denied in %s - %s" % (cb.modname, event.channel, event.auth)) return if cb.prereq: if not event.nolog: logging.debug('executing in loop %s' % str(cb.prereq)) if not cb.prereq(bot, event): return if not cb.func: return if event.isremote(): logging.info('%s - executing REMOTE %s - %s' % (bot.cfg.name, getname(cb.func), event.cbtype)) elif not event.nolog: logging.info('%s - executing %s - %s' % (bot.cfg.name, getname(cb.func), event.cbtype)) event.iscallback = True if not event.nolog: logging.debug("%s - %s - trail - %s" % (bot.cfg.name, getname(cb.func), callstack(sys._getframe())[::-1])) #if not event.direct and cb.threaded and not bot.isgae: start_new_thread(cb.func, (bot, event)) time.sleep(0.01) if cb.threaded and not bot.isgae: start_new_thread(cb.func, (bot, event)) else: if event.cbtype == "API": from runner import apirunner apirunner.put(event.speed or cb.speed, cb.modname, cb.func, bot, event) elif bot.isgae or event.direct: cb.func(bot, event) elif not event.dolong: from runner import callbackrunner callbackrunner.put(event.speed or cb.speed, cb.modname, cb.func, bot, event) else: from runner import longrunner longrunner.put(event.speed or cb.speed, cb.modname, cb.func, bot, event) return True except Exception, ex: handle_exception() def reloadcheck(self, bot, event, target=None): """ check if plugin need to be reloaded for callback, """ from boot import plugblacklist plugloaded = [] done = [] target = target or event.cbtype or event.cmnd if not event.nolog: logging.debug("%s - checking for %s events" % (bot.cfg.name, target)) try: from boot import getcallbacktable p = getcallbacktable()[target] except KeyError: if not event.nolog: logging.debug("can't find plugin to reload for %s" % event.cmnd) return if not event.nolog: logging.debug("found %s" % unicode(p)) for name in p: if name in bot.plugs: done.append(name) ; continue if plugblacklist and name in plugblacklist.data: logging.info("%s - %s is in blacklist" % (bot.cfg.name, name)) continue elif bot.cfg.loadlist and name not in bot.cfg.loadlist: logging.info("%s - %s is not in loadlist" % (bot.cfg.name, name)) continue if not event.nolog: logging.debug("%s - on demand reloading of %s" % (bot.cfg.name, name)) try: mod = bot.plugs.reload(name, force=True, showerror=False) if mod: plugloaded.append(mod) ; continue except Exception, ex: handle_exception(event) if done and not event.nolog: logging.debug("%s - %s is already loaded" % (bot.cfg.name, str(done))) return plugloaded ## global callbacks first_callbacks = Callbacks() callbacks = Callbacks() last_callbacks = Callbacks() remote_callbacks = Callbacks() api_callbacks = Callbacks() def size(): return "first: %s - callbacks: %s - last: %s - remote: %s" % (first_callbacks.size(), callbacks.size(), last_callbacks.size(), remote_callbacks.size(), api_callbacks.size()) jsb-0.84.4/jsb/lib/rest/0000775000175000017500000000000011733126034014572 5ustar bartbart00000000000000jsb-0.84.4/jsb/lib/rest/__init__.py0000664000175000017500000000000011733124163016672 0ustar bartbart00000000000000jsb-0.84.4/jsb/lib/rest/client.py0000664000175000017500000002367411733124163016437 0ustar bartbart00000000000000# jsb/rest/client.py # # """ Rest Client class """ ## jsb imports from jsb.utils.url import geturl4, posturl, deleteurl, useragent from jsb.utils.generic import toenc from jsb.utils.exception import handle_exception, exceptionmsg from jsb.utils.locking import lockdec from jsb.utils.lazydict import LazyDict from jsb.imports import getjson json = getjson() ## basic imports from urllib2 import HTTPError, URLError from httplib import InvalidURL from urlparse import urlparse import socket import asynchat import urllib import sys import thread import re import asyncore import time import logging ## defines restlock = thread.allocate_lock() locked = lockdec(restlock) ## RestResult class class RestResult(LazyDict): def __init__(self, url="", name=""): LazyDict.__init__(self) self.url = url self.name = name self.data = None self.error = None self.status = None self.reason = "" ## RestClient class class RestClient(object): """ Provide a REST client that works in sync mode. """ def __init__(self, url, keyfile=None, certfile=None, port=None): if not url.endswith('/'): url += '/' try: u = urlparse(url) splitted = u[1].split(':') if len(splitted) == 2: host, port = splitted else: host = splitted[0] port = port or 9999 path = u[2] except Exception, ex: raise self.host = host try: self.ip = socket.gethostbyname(self.host) except Exception, ex: handle_exception() self.path = path self.port = port self.url = url self.keyfile = keyfile self.certfile = certfile self.callbacks = [] def addcb(self, callback): """ add a callback. """ if not callback: return self.callbacks.append(callback) logging.debug('rest.client - added callback %s' % str(callback)) return self def delcb(self, callback): """ delete callback. """ try: del self.callbacks[callback] logging.debug('rest.client - deleted callback %s' % str(callback)) except ValueError: pass def do(self, func, url, *args, **kwargs): """ perform a rest request. """ result = RestResult(url) try: logging.info("rest.client - %s - calling %s" % (url, str(func))) res = func(url, {}, kwargs, self.keyfile, self.certfile, self.port) result.status = res.status result.reason = res.reason if result.status >= 400: result.error = result.status else: result.error = None if result.status == 200: r = res.read() result.data = json.loads(r) else: result.data = None logging.info("rest.client - %s - result: %s" % (url, str(result))) except Exception, ex: result.error = str(ex) result.data = None for cb in self.callbacks: try: cb(self, result) logging.info('rest.client - %s - called callback %s' % (url, str(cb))) except Exception, ex: handle_exception() return result def post(self, *args, **kwargs): """ do a POST request. """ return self.do(posturl, self.url, *args, **kwargs) def add(self, *args, **kwargs): """ add an REST item. """ return self.do(posturl, self.url, *args, **kwargs) def delete(self, nr=None): """ delete a REST item. """ if nr: return self.do(deleteurl, self.url + '/' + str(nr)) else: return self.do(deleteurl, self.url) def get(self, nr=None): """ get a REST item. """ if not nr: return self.do(geturl4, self.url) else: return self.do(geturl4, self.url + '/' + str(nr)) ## RestClientAsync class class RestClientAsync(RestClient, asynchat.async_chat): """ Async REST client. """ def __init__(self, url, name=""): RestClient.__init__(self, url) asynchat.async_chat.__init__(self) self.set_terminator("\r\n\r\n") self.reading_headers = True self.error = None self.buffer = '' self.name = name or self.url self.headers = {} self.status = None def handle_error(self): """ take care of errors. """ exctype, excvalue, tb = sys.exc_info() if exctype == socket.error: try: errno, errtxt = excvalue if errno in [11, 35, 9]: logging.error("res.client - %s - %s %s" % (self.url, errno, errtxt)) return except ValueError: pass self.error = str(excvalue) else: logging.error("%s - %s" % (self.name, exceptionmsg())) self.error = exceptionmsg() self.buffer = '' result = RestResult(self.url, self.name) result.error = self.error result.data = None for cb in self.callbacks: try: cb(self, result) logging.info('rest.client - %s - called callback %s' % (url, str(cb))) except Exception, ex: handle_exception() self.close() def handle_expt(self): """ handle an exception. """ handle_exception() def handle_connect(self): """ called after succesfull connect. """ logging.info('rest.client - %s - connected %s' % (self.url, str(self))) def start(self): """ start the client loop. """ assert(self.host) assert(int(self.port)) try: logging.info('rest.client - %s - starting client' % self.url) self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.connect((self.ip, int(self.port))) except socket.error, ex: self.error = str(ex) try: self.connect((self.ip, int(self.port))) except socket.error, ex: self.error = str(ex) except Exception, ex: self.error = str(ex) if self.error: self.warn("rest.client - %s - can't start %s" % (self.url, self.error)) else: return True @locked def found_terminator(self): """ called when terminator is found. """ logging.info('rest.client - %s - found terminator' % self.url) if self.reading_headers: self.reading_headers = False try: self.headers = self.buffer.split('\r\n') self.status = int(self.headers[0].split()[1]) except (ValueError, IndexError): logging.warn("rest.client - %s - can't parse headers %s" % (self.url, self.headers)) return self.set_terminator(None) self.buffer = '' logging.info('rest.client - %s - headers: %s' % (self.url, self.headers)) def collect_incoming_data(self, data): """ aggregate seperate data chunks. """ self.buffer = self.buffer + data def handle_close(self): """ called on connection close. """ self.reading_headers = False self.handle_incoming() logging.info('rest.client - %s - closed' % self.url) self.close() def handle_incoming(self): """ handle incoming data. """ logging.info("rest.client - %s - incoming: %s" % (self.url, self.buffer)) if not self.reading_headers: result = RestResult(self.url, self.name) if self.status >= 400: logging.warn('rest.client - %s - error status: %s' % (self.url, self.status)) result.error = self.status result.data = None elif self.error: result.error = self.error result.data = None elif self.buffer == "": result.data = "" result.error = None else: try: res = json.loads(self.buffer) if not res: self.buffer = '' return result.data = res result.error = None except ValueError, ex: logging.info("rest.client - %s - can't decode %s" % (self.url, self.buffer)) result.error = str(ex) except Exception, ex: logging.error("rest.client - %s - %s" % (self.url, exceptionmsg())) result.error = exceptionmsg() result.data = None for cb in self.callbacks: try: cb(self, result) logging.info('rest.client - %s - called callback %s' % (self.url, str(cb))) except Exception, ex: handle_exception() self.buffer = '' @locked def dorequest(self, method, path, postdata={}, headers={}): if postdata: postdata = urllib.urlencode(postdata) if headers: if not headers.has_key('Content-Length'): headers['Content-Length'] = len(postdata) headerstxt = "" for i,j in headers.iteritems(): headerstxt += "%s: %s\r\n" % (i.lower(), j) else: headerstxt = "" if method == 'POST': s = toenc("%s %s HTTP/1.0\r\n%s\r\n%s\r\n\r\n" % (method, path, headerstxt, postdata), 'ascii') else: s = toenc("%s %s HTTP/1.0\r\n\r\n" % (method, path), 'ascii') if self.start(): logging.info('rest.client - %s - sending %s' % (self.url, s)) self.push(s) def sendpost(self, postdata): headers = {'Content-Type': 'application/x-www-form-urlencoded', \ 'Accept': 'text/plain; text/html', 'User-Agent': useragent()} self.dorequest('POST', self.path, postdata, headers) def sendget(self): """ send a GET request. """ self.dorequest('GET', self.path) def post(self, *args, **kwargs): """ do a POST request. """ self.sendpost(kwargs) def get(self): """ call GET request. """ self.sendget() jsb-0.84.4/jsb/lib/rest/server.py0000664000175000017500000002315411733124163016460 0ustar bartbart00000000000000# jsb/socklib/rest/server.py # # ## jsb imports from jsb.utils.exception import handle_exception, exceptionmsg from jsb.utils.trace import calledfrom from jsb.lib.persiststate import ObjectState from jsb.lib.threads import start_new_thread from jsb.version import version ## basic imports from SocketServer import BaseServer, ThreadingMixIn from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from urllib import unquote_plus from asyncore import dispatcher from cgi import escape import time import sys import select import types import socket import logging ## RestServerBase class class RestServerBase(HTTPServer): """ REST web server """ allow_reuse_address = True daemon_thread = True def start(self): """ start the REST server. """ self.name = calledfrom(sys._getframe(0)) self.stop = False self.running = False self.handlers = {} self.webmods = {} self.state = ObjectState() self.state.define('whitelistenable', 0) self.state.define('whitelist', []) self.state.define('blacklist', []) self.state.define('disable', []) self.poll = select.poll() self.poll.register(self) start_new_thread(self.serve, ()) def shutdown(self): """ shutdown the REST server. """ try: self.stop = True time.sleep(0.2) self.server_close() except Exception, ex: handle_exception() def serve(self): """ serving loop. """ logging.warn('rest.server - starting') time.sleep(1) while not self.stop: self.running = True try: got = self.poll.poll(100) except Exception, ex: handle_exception() if got and not self.stop: try: self.handle_request() except Exception, ex: handle_exception() time.sleep(0.01) self.running = False logging.warn('rest.server - stopping') def entrypoint(self, request): """ check lists whether request should be allowed. """ ip = request.ip if not self.whitelistenable() and ip in self.blacklist(): logging.warn('rest.server - denied %s' % ip) request.send_error(401) return False if self.whitelistenable() and ip not in self.whitelist(): logging.warn('rest.server - denied %s' % ip) request.send_error(401) return False return True def whitelistenable(self): """ enable whitelist? """ return self.state['whitelistenable'] def whitelist(self): """ return the whitelist. """ return self.state['whitelist'] def blacklist(self): """ return the black list. """ return self.state['blacklist'] def addhandler(self, path, type, handler): """ add a web handler """ path = unquote_plus(path) splitted = [] for i in path.split('/'): if i: splitted.append(i) else: splitted.append("/") splitted = tuple(splitted) if not self.handlers.has_key(splitted): self.handlers[splitted] = {} self.handlers[splitted][type] = handler logging.info('rest.server - %s %s handler added' % (splitted[0], type)) def enable(self, what): """ enable an path. """ try: self.state['disable'].remove(what) logging.info('rest.server - enabled %s' % str(what)) except ValueError: pass def disable(self, what): """ disable an path. """ self.state['disable'].append(what) logging.info('rest.server - disabled %s' % str(what)) def do(self, request): """ do a request """ path = unquote_plus(request.path.strip()) path = path.split('?')[0] #if path.endswith('/'): path = path[:-1] splitted = [] for i in path.split('/'): if i: splitted.append(i) else: splitted.append("/") splitted = tuple(splitted) logging.warn("rest.server - incoming - %s" % str(splitted)) for i in self.state['disable']: if i in splitted: logging.warn('rest.server - %s - denied disabled %s' % (request.ip, i)) request.send_error(404) return request.splitted = splitted request.value = None type = request.command try: func = self.handlers[splitted][type] except (KeyError, ValueError): try: func = self.handlers[splitted][type] request.value = splitted[-1] except (KeyError, ValueError): logging.error("rest.server - no handler found for %s" % str(splitted)) request.send_error(404) return result = func(self, request) logging.info('rest.server - %s - result: %s' % (request.ip, str(result))) return result def handle_error(self, request, addr): """ log the error """ ip = request.ip exctype, excvalue, tb = sys.exc_info() if exctype == socket.timeout: logging.warn('rest.server - %s - socket timeout' % (ip, )) return if exctype == socket.error: logging.warn('rest.server - %s - socket error: %s' % (ip, excvalue)) return exceptstr = exceptionmsg() logging.warn('rest.server - %s - error %s %s => %s' % (ip, exctype, excvalue, exceptstr)) ## Mixin classes class RestServer(ThreadingMixIn, RestServerBase): pass class RestServerAsync(RestServerBase, dispatcher): pass ## RestReqeustHandler class class RestRequestHandler(BaseHTTPRequestHandler): """ timeserver request handler class """ def setup(self): """ called on each incoming request. """ BaseHTTPRequestHandler.setup(self) self.ip = self.client_address[0] self.name = self.ip self.size = 0 def writeheader(self, type='text/plain'): """ write headers to the client. """ self.send_response(200) self.send_header('Content-type', '%s; charset=%s ' % (type,sys.getdefaultencoding())) self.send_header('Server', version) self.end_headers() def sendresult(self): """ dispatch a call. """ try: result = self.server.do(self) if not result: return self.size = len(result) except Exception, ex: handle_exception() self.send_error(501) return self.writeheader() self.wfile.write(result) self.wfile.close() def handle_request(self): """ handle a REST request. """ if not self.server.entrypoint(self): return self.sendresult() do_DELETE = do_PUT = do_GET = do_POST = handle_request def log_request(self, code): """ log the request """ try: ua = self.headers['user-agent'] except: ua = "-" try: rf = self.headers['referer'] except: rf = "-" if hasattr(self, 'path'): logging.debug('rest.server - %s "%s %s %s" %s %s "%s" "%s"' % (self.address_string(), self.command, self.path, self.request_version, code, self.size, rf, ua)) else: logging.debug('rest.server - %s "%s %s %s" %s %s "%s" "%s"' % (self.address_string(), self.command, "none", self.request_version, code, self.size, rf, ua)) ## secure classes .. not working yet class SecureRestServer(RestServer): def __init__(self, server_address, HandlerClass, keyfile, certfile): from OpenSSL import SSL BaseServer.__init__(self, server_address, HandlerClass) ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.set_options(SSL.OP_NO_SSLv2) logging.warn("rest.server - loading private key from %s" % keyfile) ctx.use_privatekey_file (keyfile) logging.warn('rest.server - loading certificate from %s' % certfile) ctx.use_certificate_file(certfile) logging.info('rest.server - creating SSL socket on %s' % str(server_address)) self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type)) self.server_bind() self.server_activate() class SecureAuthRestServer(SecureRestServer): def __init__(self, server_address, HandlerClass, chain, serverkey, servercert): from OpenSSL import SSL BaseServer.__init__(self, server_address, HandlerClass) ctx = SSL.Context(SSL.SSLv23_METHOD) logging.warn("rest.server - loading private key from %s" % serverkey) ctx.use_privatekey_file (serverkey) logging.warn('rest.server - loading certificate from %s' % servercert) ctx.use_certificate_file(servercert) logging.warn('rest.server - loading chain of certifications from %s' % chain) ctx.set_verify_depth(2) ctx.load_client_ca(chain) #ctx.load_verify_locations(chain) logging.info('rest.server - creating SSL socket on %s' % str(server_address)) callback = lambda conn,cert,errno,depth,retcode: retcode ctx.set_verify(SSL.VERIFY_FAIL_IF_NO_PEER_CERT | SSL.VERIFY_PEER, callback) ctx.set_session_id('jsb') self.socket = SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type)) self.server_bind() self.server_activate() class SecureRequestHandler(RestRequestHandler): def setup(self): self.connection = self.request._sock self.request._sock.setblocking(1) self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) self.wfile = socket._fileobject(self.request, "wb", self.rbufsize) jsb-0.84.4/jsb/lib/config.py0000664000175000017500000005124411733124163015443 0ustar bartbart00000000000000# jsb/lib/config.py # # """ config module. config is stored as item = JSON pairs. """ ## jsb imports from jsb.utils.trace import whichmodule, calledfrom from jsb.utils.lazydict import LazyDict from jsb.utils.exception import handle_exception from jsb.utils.name import stripname from datadir import getdatadir from errors import CantSaveConfig, NoSuchFile from jsb.utils.locking import lockdec ## simplejson imports from jsb.imports import getjson json = getjson() ## basic imports import sys import os import types import thread import logging import uuid import thread import getpass import copy import time ## locks savelock = thread.allocate_lock() savelocked = lockdec(savelock) ## defines cpy = copy.deepcopy ## classes class Config(LazyDict): """ config class is a dict containing json strings. is writable to file and human editable. """ def __init__(self, filename, verbose=False, input={}, ddir=None, nolog=False, *args, **kw): assert filename LazyDict.__init__(self, input, *args, **kw) self.origname = filename self.origdir = ddir or getdatadir() self.setcfile(ddir, filename) self.jsondb = None if not self._comments: self._comments = {} try: import waveapi self.isdb = True self.isgae = True except ImportError: self.isgae = False self.isdb = False dodb = False try: logging.info("fromfile - %s from %s" % (self.origname, whichmodule(2))) self.fromfile(self.cfile) except IOError, ex: handle_exception() ; dodb = True if dodb or (self.isgae and not "mainconfig" in filename): try: from persist import Persist self.jsondb = Persist(self.cfile) if self.jsondb: self.merge(self.jsondb.data) logging.warn("fromdb - %s" % self.cfile) except ImportError: logging.warn("can't read config from %s - %s" % (self.cfile, str(ex))) self.init() if self.owner: logging.info("owner is %s" % self.owner) if not self.has_key("uuid"): self.setuuid() if not self.has_key("cfile"): self.cfile = self.setcfile(self.origdir, self.origname) assert self.cfile def setcfile(self, ddir, filename): self.filename = filename or 'mainconfig' self.datadir = ddir or getdatadir() self.dir = self.datadir + os.sep + 'config' self.cfile = self.dir + os.sep + filename def setuuid(self, save=True): logging.debug("setting uuid") self.uuid = str(uuid.uuid4()) if save: self.save() def __deepcopy__(self, a): """ accessor function. """ cfg = Config(self.filename, input=self, nolog=True) return cfg def __getitem__(self, item): """ accessor function. """ if not self.has_key(item): return None else: return LazyDict.__getitem__(self, item) def merge(self, cfg): """ merge in another cfg. """ self.update(cfg) def set(self, item, value): """ set item to value. """ LazyDict.__setitem__(self, item, value) def fromdb(self): """ read config from database. """ from jsb.lib.persist import Persist tmp = Persist(self.cfile) logging.debug("fromdb - %s - %s" % (self.cfile, tmp.data.tojson())) self.update(tmp.data) def todb(self): """ save config to database. """ cp = dict(self) del cp['jsondb'] if not self.jsondb: from jsb.lib.persist import Persist self.jsondb = Persist(self.cfile) self.jsondb.data = cp self.jsondb.save() def fromfile(self, filename=None): """ read config object from filename. """ curline = "" fname = filename or self.cfile if not fname: raise Exception(" %s - %s" % (self.cfile, self.dump())) if not os.path.exists(fname): logging.warn("config file %s doesn't exist yet" % fname) ; return False comment = "" for line in open(fname, 'r'): curline = line curline = curline.strip() if curline == "": continue if curline.startswith('#'): comment = curline; continue if True: try: key, value = curline.split('=', 1) kkey = key.strip() self[kkey] = json.loads(unicode(value.strip())) if comment: self._comments[kkey] = comment comment = "" except ValueError: logging.error("skipping line - unable to parse: %s" % line) #self.cfile = fname return def tofile(self, filename=None, stdout=False): """ save config object to file. """ if not filename: filename = self.cfile if not filename: raise Exception("no cfile found - %s" % whichmodule(3)) if self.isgae: logging.warn("can't save config file %s on GAE" % filename) ; return logging.warn("saving %s" % filename) if filename.startswith(os.sep): d = [os.sep,] else: d = [] for p in filename.split(os.sep)[:-1]: if not p: continue d.append(p) ddir = os.sep.join(d) if not os.path.isdir(ddir): logging.debug("persist - creating %s dir" % ddir) try: os.mkdir(ddir) except OSError, ex: logging.error("persist - not saving - failed to make %s - %s" % (ddir, str(ex))) return written = [] curitem = None later = [] try: if stdout: configtmp = sys.stdout else: configtmp = open(filename + '.tmp', 'w') configtmp.write('# ===========================================================\n#\n') configtmp.write("# JSONBOT CONFIGURATION FILE - %s\n" % filename) configtmp.write("#\n") configtmp.write('# last changed on %s\n#\n' % time.ctime(time.time())) configtmp.write("# This file contains configration data for the JSONBOT.\n") configtmp.write('# Variables are defined by "name = json value" pairs.\n') configtmp.write('# Make sure to use " in strings.\n#\n') configtmp.write('# The bot can edit this file!.\n#\n') configtmp.write('# ===========================================================\n\n') teller = 0 keywords = self.keys() keywords.sort() for keyword in keywords: value = self[keyword] if keyword in written: continue if keyword in ['isgae', 'origdir', 'origname', 'issaved', 'blacklist', 'whitelist', 'followlist', 'uuid', 'whitelist', 'datadir', 'name', 'createdfrom', 'cfile', 'filename', 'dir', 'isdb']: later.append(keyword) ; continue if keyword == 'jsondb': continue if keyword == 'optionslist': continue if keyword == 'gatekeeper': continue if keyword == "_comments": continue if self._comments and self._comments.has_key(keyword): configtmp.write(self._comments[keyword] + u"\n") curitem = keyword try: configtmp.write('%s = %s\n' % (keyword, json.dumps(value))) except TypeError: logging.error("%s - can't serialize %s" % (filename, keyword)) ; continue teller += 1 #configtmp.write("\n") configtmp.write('\n\n# ============================================================\n#\n') configtmp.write("# bot generated stuff.\n#\n") configtmp.write('# ============================================================\n\n') for keyword in later: if self._comments and self._comments.has_key(keyword): configtmp.write(self._comments[keyword] + u"\n") curitem = keyword value = self[keyword] try: configtmp.write(keyword + " = " + json.dumps(value) + "\n") except TypeError: logging.error("%s - can't serialize %s" % (filename, keyword)) ; continue teller += 1 #configtmp.write("\n") if not "mainconfig" in filename and self._comments: try: configtmp.write('\n\n# ============================================================\n#\n') configtmp.write("# possible other config variables.\n#\n") configtmp.write('# ============================================================\n\n') items = self._comments.keys() keys = self.keys() do = [] for var in items: if var not in keys: do.append(var) do.sort() for var in do: configtmp.write(u"# %s -=- %s\n" % (var, self._comments[var])) configtmp.write("\n\n") except Exception, ex: handle_exception() else: configtmp.write("\n\n# jsonbot can run multiple bots at once. see %s/config/fleet for their configurations.\n\n" % self.origdir) if not stdout: configtmp.close() os.rename(filename + '.tmp', filename) return teller except Exception, ex: handle_exception() logging.error("ERROR WRITING %s CONFIG FILE: %s .. %s" % (self.cfile, str(ex), curitem)) @savelocked def save(self): """ save the config. """ logging.info("save called from %s" % calledfrom(sys._getframe(2))) self.issaved = True if self.isdb: self.todb() else: self.tofile(self.cfile) def load_config(self, verbose=False): """ load the config file. """ if self.isdb: self.fromdb() else: self.fromfile(self.filename) self.init() if verbose: logging.debug('%s' % self.dump()) def init(self): """ initialize the config object. """ if not self._comments: self._comments = {} if self.filename == 'mainconfig': self._comments["whitelist"] = "# - whitelist used to allow ips .. bot maintains this" self.setdefault("whitelist", []) self._comments["blacklist"] = "# - blacklist used to deny ips .. bot maintains this" self.setdefault("blacklist", []) self.setdefault('owner', []) self._comments["loglist"] = "# - loglist .. maintained by the bot." self.setdefault('loglist', []) self._comments["loglevel"] = "# - loglevel of all bots" self.setdefault('loglevel', "warn") self._comments["loadlist"] = "# - loadlist .. not used yet." self.setdefault('loadlist', []) self._comments["quitmsg"] = "# - message to send on quit" self.setdefault('quitmsg', "http://jsonbot.googlecode.com") self._comments["dotchars"] = "# - characters to used as seperator." self.setdefault('dotchars', ", ") self._comments["floodallow"] = "# - whether the bot is allowed to flood." self.setdefault('floodallow', 1) self._comments["auto_register"] = "# - enable automatic registration of new users." self.setdefault('auto_register', 0) self._comments["guestasuser"] = "# - enable this to give new users the USER permission besides GUEST." self.setdefault('guestasuser', 0) self._comments["globalcc"] = "# - global control character" self.setdefault('globalcc', "") self._comments["app_id"] = "# - application id used by appengine." self.setdefault('app_id', "jsonbot") self._comments["appname"] = "# - application name as used by the bot." self.setdefault('appname', "JSONBOT") self._comments["domain"] = "# - domain .. used for WAVE." self.setdefault('domain', "") self._comments["color"] = "# - color used in the webconsole." self.setdefault('color', "") self._comments["colors"] = "# - enable colors in logging." self.setdefault('colors', "") self._comments["memcached"] = "# - enable memcached." self.setdefault('memcached', 0) self._comments["allowrc"] = "# - allow execution of rc files." self.setdefault('allowrc', 0) self._comments["allowremoterc"] = "# - allow execution of remote rc files." self.setdefault('allowremoterc', 0) self._comments['dbenable'] = "# - enable database support" self.setdefault('dbenable', 0) self._comments['dbtype'] = "# - type of database .. sqlite or mysql at this time." self.setdefault('dbtype', 'sqlite') self._comments['dbname'] = "# - database name" self.setdefault('dbname', "main.db") self._comments['dbhost'] = "# - database hostname" self.setdefault('dbhost', "localhost") self._comments['dbuser'] = "# - database user" self.setdefault('dbuser', "bart") self._comments['dbpasswd'] = "# - database password" self.setdefault('dbpasswd', "mekker2") self._comments['ticksleep'] = "# - nr of seconds to sleep before creating a TICK event." self.setdefault('ticksleep', 1) self._comments['bindhost'] = "# - host to bind to" self.setdefault("bindhost", "") self['createdfrom'] = whichmodule() if 'xmpp' in self.cfile: self.setdefault('fulljids', 1) if 'fleet' in self.cfile: self.setdefault('disable', 1) self.setdefault("owner", []) self.setdefault("user", "") self.setdefault("host", "") self.setdefault("server", "") self.setdefault("ssl", 0) self.setdefault("ipv6", 0) self.setdefault("channels", []) self.setdefault("port", "") self.setdefault("password", "") self._comments['datadir'] = "# - directory to store bot data in." self._comments["owner"] = "# - owner of the bot." self._comments["uuid"] = "# - bot generated uuid for this config file." self._comments["user"] = "# - user used to login on xmpp networks." self._comments["host"] = "# - host part of the user, derived from user var." self._comments["server"] = "# - server to connect to (only when different from users host)." self._comments["password"] = "# - password to use in authing the bot." self._comments["port"] = "# - port to connect to (IRC)." self._comments["ssl"] = "# - whether to enable ssl (set to 1 to enable)." self._comments["ipv6"] = "# - whether to enable ssl (set to 1 to enable)." self._comments["name"] = "# - the name of the bot." self._comments["disable"] = "# - set this to 0 to enable the bot." self._comments["followlist"] = "# - who to follow on the bot .. bot maintains this list." self._comments["networkname"] = "# - networkname .. not used right now." self._comments["type"] = "# - the bot's type." self._comments["nick"] = "# - the bot's nick." self._comments["channels"] = "# - channels to join." self._comments["cfile"] = "# - filename of this config file. edit this when you move this file." self._comments["createdfrom"] = "# - function that created this config file. bot generated" self._comments["dir"] = "# - directory in which this config file lives." self._comments["isdb"] = "# - whether this config file lives in the database and not on file." self._comments["filename"] = "# - filename of this config file." self._comments["username"] = "# - username of the bot." self._comments["fulljids"] = "# - use fulljids of bot users (used in non anonymous conferences." self._comments["servermodes"] = "# - string of modes to send to the server after connect." self._comments["realname"] = "# - name used in the ident of the bot." self._comments["onconnect"] = "# - string to send to server after connect." self._comments["onconnectmode"] = "# - MODE string to send to server after connect." self._comments["realname"] = "# - mode string to send to the server after connect." self._comments["issaved"] = "# - whether this config file has been saved. " self._comments["origdir"] = "# - original datadir for this configfile. " self._comments["origname"] = "# - displayable name of the config file name. " return self def reload(self): """ reload the config file. """ self.load_config() return self def ownercheck(userhost): """ check whether userhost is a owner. """ if not userhost: return False if userhost in cfg['owner']: return True return False mainconfig = None def getmainconfig(ddir=None): global mainconfig if not mainconfig: mainconfig = Config("mainconfig", ddir=ddir) if not mainconfig.has_key("issaved"): mainconfig.save() return mainconfig irctemplate = """# ===================================================== # # JSONBOT CONFIGURATION FILE - # # last changed on # # This file contains configration data for the JSONBOT. # Variables are defined by "name = json value" pairs. # Make sure to use " in strings. # The bot can edit this file! # # ===================================================== # - to enable put this to 0 disable = 1 # - the bot's nick. nick = "jsb" # - owner of the bot. owner = [] # - port to connect to (IRC). port = 6667 # - server to connect to (on jabber only when different that host. server = "localhost" # - the bot's type. type = "irc" # - username of the bot. username = "jsonbot" # - ssl enabled or not ssl = 0 # - ipv6 enabled or not ipv6 = 0 # - name use in ident of the bot realname = "jsonbot" # - string of modes send to the server on connect servermodes = "" # ===================================================== # # bot generated stuff. # # ===================================================== """ xmpptemplate = """# ===================================================== # # JSONBOT CONFIGURATION FILE - # # last changed on # # This file contains configration data for the JSONBOT. # Variables are defined by "name = json value" pairs. # Make sure to use " in strings. # The bot can edit this file! # # ===================================================== # - channels to join channels = [] # - to enable put this to 0 disable = 1 # - the bot's nick. nick = "jsb" # - owner of the bot. owner = [] # - use fulljids of bot users (used in non anonymous conferences. fulljids = 1 # password used to auth on the server. password = "" # - server to connect to (on jabber only when different that users host. server = "" # - the bot's type. type = "sxmpp" # - user used to login on xmpp networks. user = "" # ===================================================== # # bot generated stuff. # # ===================================================== """ sleektemplate = """# ===================================================== # # JSONBOT CONFIGURATION FILE - # # last changed on # # This file contains configration data for the JSONBOT. # Variables are defined by "name = json value" pairs. # Make sure to use " in strings. # The bot can edit this file! # # ===================================================== # - channels to join channels = [] # - to enable put this to 0 disable = 1 # - the bot's nick. nick = "jsb" # - owner of the bot. owner = [] # - use fulljids of bot users (used in non anonymous conferences. fulljids = 1 # password used to auth on the server. password = "" # - server to connect to (on jabber only when different that users host. server = "" # - the bot's type. type = "sleek" # - user used to login on xmpp networks. user = "" # ===================================================== # # bot generated stuff. # # ===================================================== """ def makedefaultconfig(type, ddir=None): filename = 'config' datadir = ddir or getdatadir() dir = datadir + os.sep + 'config' ttype = "default-%s" % type cfile = dir + os.sep + "fleet" + os.sep + ttype + os.sep + filename logging.warn("creating default config for type %s in %s" % (type, cfile)) splitted = cfile.split(os.sep) mdir = "" for i in splitted[:-1]: mdir += "%s%s" % (i, os.sep) if not os.path.isdir(mdir): os.mkdir(mdir) logging.debug("filename is %s" % cfile) f = open(cfile, "w") if type == "irc": f.write(irctemplate) ; f.close() elif type == "sxmpp": f.write(xmpptemplate) ; f.close() elif type == "sleek": f.write(sleektemplate) ; f.close() else: raise Exception("no such bot type: %s" % type) jsb-0.84.4/jsb/lib/tick.py0000664000175000017500000000173311733124163015126 0ustar bartbart00000000000000# jsb/tick.py # # """ provide system wide clock tick. """ ## jsb imports from jsb.lib.threadloop import TimedLoop from jsb.lib.eventbase import EventBase from jsb.lib.callbacks import callbacks from jsb.lib.config import getmainconfig ## TickLoop class class TickLoop(TimedLoop): def start(self, bot=None): """ start the loop. """ self.bot = bot self.counter = 0 TimedLoop.start(self) def handle(self): """ send TICK events to callback. """ self.counter += 1 event = EventBase() event.nolog = True event.nobind = True if self.counter % 60 == 0: event.type = event.cbtype = 'TICK60' callbacks.check(self.bot, event) maincfg = getmainconfig() t = maincfg.ticksleep or 1 if self.counter % t == 0: event.type = event.cbtype = 'TICK' callbacks.check(self.bot, event) ## global tick loop tickloop = TickLoop('tickloop', 1) jsb-0.84.4/jsb/lib/cache.py0000664000175000017500000000155711733124163015243 0ustar bartbart00000000000000# jsb/cache.py # # """ jsb cache provding get, set and delete functions. """ ## jsb imports from jsb.utils.lazydict import LazyDict ## basic imports import logging ## defines cache = LazyDict() ## functions def get(name, namespace=""): """ get data from the cache. """ global cache try: #logging.debug("cache - returning %s" % cache[name]) return cache[name] except KeyError: pass def set(name, item, timeout=0, namespace=""): """ set data in the cache. """ #logging.debug("cache - setting %s to %s" % (name, str(item))) global cache cache[name] = item def delete(name, namespace=""): """ delete data from the cache. """ try: global cache del cache[name] logging.warn("cache - deleted %s" % name) return True except KeyError: return False def size(): return len(cache) jsb-0.84.4/jsb/lib/floodcontrol.py0000664000175000017500000000460611733124163016702 0ustar bartbart00000000000000# jsb/lib/floodcontrol.py # # """ JSONBOT flood control. """ ## jsb imports from jsb.utils.statdict import StatDict from jsb.utils.lazydict import LazyDict from jsb.lib.config import getmainconfig ## basic imports import logging import time ## class FloodControl(object): def __init__(self): self.stats = StatDict() self.times = LazyDict() self.wait = LazyDict() self.warned = LazyDict() def reset(self, userhost): try: del self.times[userhost] except KeyError: pass try: del self.stats[userhost] except KeyError: pass try: del self.wait[userhost] except KeyError: pass try: del self.warned[userhost] except KeyError: pass def check(self, userhost, timetomonitor=60, threshold=10, wait=120, floodrate=1): u = userhost t = time.time() w = wait if self.times.has_key(u): if t - self.times[u] > w: self.reset(u) ; return False if (t - self.times[u] < timetomonitor): self.stats.upitem(u) if (self.stats.get(u) > threshold) or (t - self.times[u] < floodrate): self.wait[userhost] = wait ; return True else: self.times[u] = t ; return False if self.stats.get(u) <= threshold: return False return True def checkevent(self, event, dobind=True): if not event.iscommand: return False if getmainconfig().floodallow: return False if dobind: event.bind() if not event.user: got = False else: got = True t = got and event.user.data.floodtime or 60 if t < 60: t = 60 threshold = got and event.user.data.floodthreshold or 20 if threshold < 20: threshold = 20 wait = got and event.user.data.floodwait or 120 if wait < 120: wait = 120 floodrate = got and event.user.data.floodrate or 0.1 if floodrate < 0.1: floodrate = 0.1 if not self.check(event.userhost, t, threshold, wait, floodrate): return False if event.user and "OPER" in event.user.data.perms: return False logging.warn("floodcontrol block on %s" % event.userhost) if event.userhost not in self.warned: logging.warn("floodcontrol block on %s" % event.userhost) event.reply("floodcontrol enabled (%s seconds)" % wait) self.warned[event.userhost] = time.time() return True floodcontrol = FloodControl() jsb-0.84.4/jsb/lib/container.py0000664000175000017500000000342611733124163016157 0ustar bartbart00000000000000# jsb/container.py # # """ container for bot to bot communication. """ __version__ = "1" ## jsb imports from jsb.lib.persist import Persist from jsb.utils.name import stripname from jsb.lib.gozerevent import GozerEvent from jsb.imports import getjson ## xmpp import from jsb.contrib.xmlstream import NodeBuilder, XMLescape, XMLunescape ## basic imports import hmac import uuid import time import hashlib ## defines idattributes = ['createtime', 'origin', 'type', 'idtime', 'payload'] ## functions def getid(container): name = "" for attr in idattributes: try: name += str(container[attr]) except KeyError: pass return uuid.uuid3(uuid.NAMESPACE_URL, name).hex ## classes class Container(GozerEvent): """ Container for bot to bot communication. Provides a hmac id that can be checked. """ def __init__(self, origin=None, payload=None, type="event", key=None, how="direct"): GozerEvent.__init__(self) self.createtime = time.time() self.origin = origin self.type = str(type) self.payload = payload self.makeid() if key: self.makehmac(key) else: self.makehmac(self.id) def makeid(self): self.idtime = time.time() self.id = getid(self) def makehmac(self, key): self.hash = "sha512" self.hashkey = key self.digest = hmac.new(key, self.payload, hashlib.sha512).hexdigest() def save(self, attributes=[]): target = {} if attributes: for key in attributes: target[key] = self[key] else: target = cpy(self) targetfile = getdatadir() + os.sep + "containers" + os.sep + str(self.createtime) + "_" + stripname(self.origin) p = Persist(targetfile) p.data = getjson().dumps(target) p.save() jsb-0.84.4/jsb/lib/less.py0000664000175000017500000000357211733124163015145 0ustar bartbart00000000000000# jsb/less.py # # """ maintain bot output cache. """ # jsb imports from jsb.utils.exception import handle_exception from jsb.utils.limlist import Limlist ## google imports try: import waveapi from google.appengine.api.memcache import get, set, delete except ImportError: from jsb.lib.cache import get, set, delete ## basic imports import logging ## Less class class Less(object): """ output cache .. caches upto item of txt lines per channel. """ def clear(self, channel): """ clear outcache of channel. """ channel = unicode(channel).lower() try: delete(u"outcache-" + channel) except KeyError: pass def add(self, channel, listoftxt): """ add listoftxt to channel's output. """ channel = unicode(channel).lower() data = get("outcache-" + channel) if not data: data = [] data.extend(listoftxt) set(u"outcache-" + channel, data, 3600) def set(self, channel, listoftxt): """ set listoftxt to channel's output. """ channel = unicode(channel).lower() set(u"outcache-" + channel, listoftxt, 3600) def get(self, channel): """ return 1 item popped from outcache. """ channel = unicode(channel).lower() global get data = get(u"outcache-" + channel) if not data: txt = None else: try: txt = data.pop(0) ; set(u"outcache-" + channel, data, 3600) except (KeyError, IndexError): txt = None if data: size = len(data) else: size = 0 return (txt, size) def copy(self, channel): """ return 1 item popped from outcache. """ channel = unicode(channel).lower() global get return get(u"outcache-" + channel) def more(self, channel): """ return more entry and remaining size. """ return self.get(channel) outcache = Less()jsb-0.84.4/jsb/lib/persist.py0000664000175000017500000004772011733124163015673 0ustar bartbart00000000000000# jsb/persist.py # # """ allow data to be written to disk or BigTable in JSON format. creating the persisted object restores data. """ ## jsb imports from jsb.utils.trace import whichmodule, calledfrom, callstack, where from jsb.utils.lazydict import LazyDict from jsb.utils.exception import handle_exception from jsb.utils.name import stripname from jsb.utils.locking import lockdec from jsb.utils.timeutils import elapsedstring from jsb.lib.callbacks import callbacks from jsb.lib.errors import MemcachedCounterError, JSONParseError from datadir import getdatadir ## simplejson imports from jsb.imports import getjson json = getjson() ## basic imports from collections import deque import thread import logging import os import os.path import types import copy import sys import time ## defines cpy = copy.deepcopy ## locks persistlock = thread.allocate_lock() persistlocked = lockdec(persistlock) ## global list to keeptrack of what persist objects need to be saved needsaving = deque() def cleanup(bot=None, event=None): global needsaving todo = cpy(needsaving) r = [] for p in todo: try: p.dosave() ; r.append(p) ; logging.warn("saved on retry - %s" % p.fn) except (OSError, IOError), ex: logging.error("failed to save %s - %s" % (p, str(ex))) for p in r: try: needsaving.remove(p) except ValueError: pass return needsaving ## try google first try: from google.appengine.ext.db.metadata import Kind from google.appengine.ext import db import google.appengine.api.memcache as mc from google.appengine.api.datastore_errors import Timeout, TransactionFailedError from cache import get, set, delete logging.debug("using BigTable based Persist") ## JSONindb class class JSONindb(db.Model): """ model to store json files in. """ modtime = db.DateTimeProperty(auto_now=True, indexed=False) createtime = db.DateTimeProperty(auto_now_add=True, indexed=False) filename = db.StringProperty() content = db.TextProperty(indexed=False) ## Persist class class Persist(object): """ persist data attribute to database backed JSON file. """ def __init__(self, filename, default={}, type="cache"): self.cachtype = None self.plugname = calledfrom(sys._getframe()) if 'lib' in self.plugname: self.plugname = calledfrom(sys._getframe(1)) try: del self.fn except: pass self.fn = unicode(filename.strip()) # filename to save to self.logname = os.sep.join(self.fn.split(os.sep)[-1:]) self.countername = self.fn + "_" + "counter" self.mcounter = mc.get(self.countername) or mc.set(self.countername, "1") try: self.mcounter = int(self.mcounter) except ValueError: logging.warn("can't parse %s mcounter, setting to zero: %s" % (self.fn, self.mcounter)) ; self.mcounter = 0 self.data = None self.type = type self.counter = self.mcounter self.key = None self.obj = None self.size = 0 self.jsontxt = "" self.init(default) def init(self, default={}, filename=None): if self.checkmc(): self.jsontxt = self.updatemc() ; self.cachetype = "cache" else: tmp = get(self.fn) self.cachetype = "mem" if tmp != None: logging.warn("*%s* - loaded %s" % (self.cachetype, self.fn)) self.data = tmp if type(self.data) == types.DictType: self.data = LazyDict(self.data) return self.data if self.jsontxt == "": self.cachetype = "cache" ; self.jsontxt = mc.get(self.fn) if self.jsontxt == None: self.cachetype = "db" logging.debug("%s - loading from db" % self.fn) try: try: self.obj = JSONindb.get_by_key_name(self.fn) except Timeout: self.obj = JSONindb.get_by_key_name(self.fn) except Exception, ex: # bw compat sucks try: self.obj = JSONindb.get_by_key_name(self.fn) except Exception, ex: handle_exception() self.obj = None if self.obj == None: logging.warn("%s - no entry found, using default" % self.fn) self.jsontext = json.dumps(default) ; self.cachetype = "default" else: self.jsontxt = self.obj.content; self.cachetype = "db" if self.jsontxt: mc.set(self.fn, self.jsontxt) incr = mc.incr(self.countername) if incr: try: self.mcounter = self.counter = int(incr) except ValueError: logging.error("can't make counter out of %s" % incr) else: self.mcounter = 1 logging.debug("memcached counters for %s: %s" % (self.fn, self.mcounter)) if self.jsontxt == None: self.jsontxt = json.dumps(default) logging.warn('%s - jsontxt is %s' % (self.fn, self.jsontxt)) try: self.data = json.loads(self.jsontxt) except: raise JSONParseError(self.fn) if not self.data: self.data = default self.size = len(self.jsontxt) if type(self.data) == types.DictType: self.data = LazyDict(self.data) set(self.fn, self.data) logging.warn("*%s* - loaded %s (%s)" % (self.cachetype, self.fn, len(self.jsontxt))) def get(self): logging.debug("getting %s from local cache" % self.fn) a = get(self.fn) logging.debug("got %s from local cache" % type(a)) return a def sync(self): logging.debug("syncing %s" % self.fn) tmp = cpy(self.data) data = json.dumps(tmp) mc.set(self.fn, data) if type(self.data) == types.DictType: self.data = LazyDict(self.data) set(self.fn, self.data) return data def updatemc(self): tmp = mc.get(self.fn) if tmp != None: try: t = json.loads(tmp) if self.data: t.update(self.data) self.data = LazyDict(t) logging.warn("updated %s" % self.fn) except AttributeError, ex: logging.warn(str(ex)) return self.data def checkmc(self): try: self.mcounter = int(mc.get(self.countername)) or 0 except: self.mcounter = 0 logging.warn("mcounter for %s is %s (%s)" % (self.fn, self.mcounter, self.counter)) if (self.mcounter - self.counter) < 0: return True elif (self.mcounter - self.counter) > 0: return True return False def save(self): cleanup() global needsaving try: self.dosave() except (IOError, OSError, TransactionFailedError): handle_exception() logging.error("PUSHED ON RETRY QUEUE") self.sync() if self not in needsaving: needsaving.appendleft(self) @persistlocked def dosave(self, filename=None): """ save json data to database. """ if self.checkmc(): self.updatemc() fn = filename or self.fn bla = json.dumps(self.data) if filename or self.obj == None: self.obj = JSONindb(key_name=fn) self.obj.content = bla else: self.obj.content = bla self.obj.filename = fn from google.appengine.ext import db key = db.run_in_transaction(self.obj.put) logging.debug("transaction returned %s" % key) mc.set(fn, bla) if type(self.data) == types.DictType: self.data = LazyDict(self.data) set(fn, self.data) incr = mc.incr(self.countername) if incr: try: self.mcounter = self.counter = int(incr) except ValueError: logging.error("can't make counter out of %s" % incr) else: self.mcounter = 1 self.counter = self.mcounter logging.debug("memcached counters for %s: %s" % (fn, self.mcounter)) logging.warn('saved %s (%s)' % (fn, len(bla))) logging.debug('saved %s from %s' % (fn, where())) def upgrade(self, filename): self.init(self.data, filename=filename) ## findfilenames function def findfilenames(target, filter=[], skip=[]): res = [] targetkey = db.Key.from_path(JSONindb.kind(), target) targetkey2 = db.Key.from_path(JSONindb.kind(), target + "zzz") logging.warn("key for %s is %s" % (target, str(targetkey))) q = db.Query(JSONindb, keys_only=True) q.filter("__key__ >", targetkey) q.filter("__key__ <", targetkey2) for key in q: fname = key.name() if fname in skip: continue fname = str(fname) logging.warn("using %s" % fname) go = True for fil in filter: if fil not in fname.lower(): go = False ; break if not go: continue res.append(fname) return res def findnames(target, filter=[], skip=[]): res = [] for f in findfilenames(target, filter, skip): res.append(f.split(os.sep)[-1]) return res except ImportError: ## file based persist logging.debug("using file based Persist") ## imports for shell bots if True: got = False from jsb.memcached import getmc mc = getmc() if mc: status = mc.get_stats() if status: logging.warn("memcached uptime is %s" % elapsedstring(status[0][1]['uptime'])) got = True if got == False: logging.debug("no memcached found - using own cache") from cache import get, set, delete import fcntl ## classes class Persist(object): """ persist data attribute to JSON file. """ def __init__(self, filename, default=None, init=True, postfix=None): """ Persist constructor """ if postfix: self.fn = str(filename.strip()) + str("-%s" % postfix) else: self.fn = str(filename.strip()) self.lock = thread.allocate_lock() # lock used when saving) self.data = LazyDict(default=default) # attribute to hold the data try: res = [] target = getdatadir().split(os.sep)[-1] for i in self.fn.split(os.sep)[::-1]: if target in i: break res.append(i) self.logname = os.sep.join(res[::-1]) if not self.logname: self.logname = self.fn except: handle_exception() ; self.logname = self.fn self.countername = self.fn + "_" + "counter" if got: count = mc.get(self.countername) try: self.mcounter = self.counter = int(count) except (ValueError, TypeError): self.mcounter = self.counter = mc.set(self.countername, "1") or 0 else: self.mcounter = self.counter = 0 self.ssize = 0 self.jsontxt = "" self.dontsave = False if init: self.init(default) if default == None: default = LazyDict() def size(self): return "%s (%s)" % (len(self.data), len(self.jsontxt)) def init(self, default={}, filename=None): """ initialize the data. """ gotcache = False cachetype = "cache" try: logging.debug("using name %s" % self.fn) a = get(self.fn) if a: self.data = a else: self.data = None if self.data != None: logging.debug("got data from local cache") return self if got: self.jsontxt = mc.get(self.fn) ; cachetype = "cache" if not self.jsontxt: datafile = open(self.fn, 'r') self.jsontxt = datafile.read() datafile.close() self.ssize = len(self.jsontxt) cachetype = "file" if got: mc.set(self.fn, self.jsontxt) except IOError, ex: if not 'No such file' in str(ex): logging.error('failed to read %s: %s' % (self.fn, str(ex))) raise else: logging.debug("%s doesn't exist yet" % self.fn) self.jsontxt = json.dumps(default) try: if self.jsontxt: logging.debug(u"loading: %s" % type(self.jsontxt)) try: self.data = json.loads(str(self.jsontxt)) except Exception, ex: logging.error("couldn't parse %s" % self.jsontxt) ; self.data = None ; self.dontsave = True if not self.data: self.data = LazyDict() elif type(self.data) == types.DictType: logging.debug("converting dict to LazyDict") d = LazyDict() d.update(self.data) self.data = d set(self.fn, self.data) logging.debug("loaded %s - %s" % (self.logname, cachetype)) except Exception, ex: logging.error('ERROR: %s' % self.fn) raise def upgrade(self, filename): self.init(self.data, filename=filename) self.save(filename) def get(self): logging.debug("getting %s from local cache" % self.fn) a = get(self.fn) logging.debug("got %s from local cache" % type(a)) return a def sync(self): logging.debug("syncing %s" % self.fn) if got: mc.set(self.fn, json.dumps(self.data)) set(self.fn, self.data) return self def save(self): cleanup() global needsaving try: self.dosave() except (IOError, OSError): self.sync() if self not in needsaving: needsaving.append(self) @persistlocked def dosave(self): """ persist data attribute. """ try: if self.dontsave: logging.error("dontsave is set on %s - not saving" % self.fn) ; return fn = self.fn if got: self.mcounter = int(mc.incr(self.countername)) if got and (self.mcounter - self.counter) > 1: tmp = json.loads(mc.get(fn)) if tmp: try: tmp.update(self.data) ; self.data = LazyDict(tmp) ; logging.warn("updated %s" % fn) except AttributeError: pass self.counter = self.mcounter d = [] if fn.startswith(os.sep): d = [os.sep,] for p in fn.split(os.sep)[:-1]: if not p: continue d.append(p) pp = os.sep.join(d) if not os.path.isdir(pp): logging.warn("creating %s dir" % pp) os.mkdir(pp) tmp = fn + '.tmp' # tmp file to save to datafile = open(tmp, 'w') fcntl.flock(datafile, fcntl.LOCK_EX | fcntl.LOCK_NB) json.dump(self.data, datafile, indent=True) fcntl.flock(datafile, fcntl.LOCK_UN) datafile.close() try: os.rename(tmp, fn) except (IOError, OSError): os.remove(fn) os.rename(tmp, fn) jsontxt = json.dumps(self.data) logging.debug("setting cache %s - %s" % (fn, jsontxt)) self.jsontxt = jsontxt set(fn, self.data) if got: mc.set(fn, jsontxt) logging.info('%s saved' % self.logname) except IOError, ex: logging.error("not saving %s: %s" % (self.fn, str(ex))) ; raise except: raise finally: pass ## findfilenames function def findfilenames(target, filter=[], skip=[]): res = [] if not os.path.isdir(target): return res for f in os.listdir(target): if f in skip: continue fname = target + os.sep + f if os.path.isdir(fname): res.extend(findfilenames(fname, skip)) go = True for fil in filter: if fil not in fname.lower(): go = False ; break if not go: continue res.append(fname) return res def findnames(target, filter=[], skip=[]): res = [] for f in findfilenames(target, filter, skip): res.append(f.split(os.sep)[-1]) return res class PlugPersist(Persist): """ persist plug related data. data is stored in jsondata/plugs/{plugname}/{filename}. """ def __init__(self, filename, default={}, *args, **kwargs): plugname = calledfrom(sys._getframe()) Persist.__init__(self, getdatadir() + os.sep + 'plugs' + os.sep + stripname(plugname) + os.sep + stripname(filename), default=default, *args, **kwargs) class GlobalPersist(Persist): """ persist plug related data. data is stored in jsondata/plugs/{plugname}/{filename}. """ def __init__(self, filename, default={}, *args, **kwargs): if not filename: raise Exception("filename not set in GlobalPersist") logging.warn("filename is %s" % filename) Persist.__init__(self, getdatadir() + os.sep + 'globals' + os.sep + stripname(filename), default=default, *args, **kwargs) ## PersistCollection class class PersistCollection(object): """ maintain a collection of Persist objects. """ def __init__(self, path, *args, **kwargs): assert path self.path = path d = [os.sep, ] for p in path.split(os.sep): if not p: continue d.append(p) pp = os.sep.join(d) try: os.mkdir(pp) logging.warn("creating %s dir" % pp) except OSError, ex: if 'Errno 13' in str(ex) or 'Errno 2' in str(ex): continue logging.warn("can't make %s - %s" % (pp,str(ex))) ; continue def filenames(self, filter=[], path=None, skip=[], result=[]): target = path or self.path res = findfilenames(target, filter, skip) logging.info("filenames are %s" % str(res)) return res def names(self, filter=[], path=None, skip=[], result=[]): target = path or self.path res = findnames(target, filter, skip) return res def search(self, field, target): res = [] for obj in self.objects().values(): try: item = getattr(obj.data, field) except AttributeError: handle_exception() ; continue if not item: continue if target in item: res.append(obj) return res def objects(self, filter=[], path=None): if type(filter) != types.ListType: filter = [filter, ] res = {} target = path or self.path for f in self.filenames(filter, target): res[f] = Persist(f) return res ## PlugPersistCollection class class PlugPersistCollection(PersistCollection): def __init__(self): plugname = calledfrom(sys._getframe()) self.path = getdatadir() + os.sep + 'plugs' + os.sep + stripname(plugname) + os.sep PersistCollection.__init__(self, self.path) ## GlobalPersistCollection class class GlobalPersistCollection(PersistCollection): def __init__(self): self.path = getdatadir() + os.sep + 'globals' GlobalCollection(self, self.path) callbacks.add("TICK60", cleanup) jsb-0.84.4/jsb/lib/commands.py0000664000175000017500000002472511733124163016003 0ustar bartbart00000000000000# jsb/commands.py # # """ the commands module provides the infrastructure to dispatch commands. commands are the first word of a line. """ ## jsb imports from threads import start_new_thread, start_bot_command from jsb.utils.xmpp import stripped from jsb.utils.trace import calledfrom, whichmodule from jsb.utils.exception import handle_exception from jsb.utils.lazydict import LazyDict from errors import NoSuchCommand, NoSuchUser from persiststate import UserState from runner import cmndrunner from boot import getcmndperms from floodcontrol import floodcontrol from aliases import getaliases, aliascheck ## basic imports import logging import sys import types import os import copy import time import re ## defines cpy = copy.deepcopy ## Command class class Command(LazyDict): """ a command object. """ def __init__(self, modname, cmnd, func, perms=[], threaded=False, wait=False, orig=None, how=None, speed=None): LazyDict.__init__(self) if not modname: raise Exception("modname is not set - %s" % cmnd) self.modname = cpy(modname) self.plugname = self.modname.split('.')[-1] self.cmnd = cpy(cmnd) self.orig = cpy(orig) self.func = func if type(perms) == types.StringType: perms = [perms, ] self.perms = cpy(perms) self.plugin = self.plugname self.threaded = cpy(threaded) self.wait = cpy(wait) self.enable = True self.how = how or "overwrite" self.regex = None self.speed = speed class Commands(LazyDict): """ the commands object holds all commands of the bot. """ regex = [] def add(self, cmnd, func, perms, threaded=False, wait=False, orig=None, how=None, speed=None, regex=False, *args, **kwargs): """ add a command. """ modname = calledfrom(sys._getframe()) try: prev = self[cmnd] except KeyError: prev = None target = Command(modname, cmnd, func, perms, threaded, wait, orig, how, speed=speed) if regex: logging.info("regex command detected - %s" % cmnd) self.regex.append(target) target.regex = cmnd return self self[cmnd] = target try: p = cmnd.split('-')[0] if not self.pre: self.pre = LazyDict() if self.pre.has_key(p): if not self.pre[p]: self.pre[p] = [] if prev in self.pre[p]: self.pre[p].remove(prev) if target not in self.pre[p]: self.pre[p].append(target) else: self.pre[p] = [target, ] except IndexError: pass return self def checkre(self, bot, event): for r in self.regex: s = re.search(r.cmnd, event.stripcc().strip()) if s: logging.info("regex matches %s" % r.cmnd) event.groups = list(s.groups()) return r def wouldmatchre(self, bot, event, cmnd=""): groups = self.checkre(bot, event) if groups: return group def woulddispatch(self, bot, event): """ dispatch an event if cmnd exists and user is allowed to exec this command. """ event.bind(bot) try: cmnd = event.stripcc().split()[0] if event.execstr and not cmnd: cmnd = event.execstr.split()[0] if not cmnd: cmnd = event.txt.split()[0] except Exception, ex: logging.debug("can't determine command") ; return None try: a = event.chan.data.aliases[cmnd] if a: cmnd = a.split()[0] except (KeyError, TypeError): try: a = getaliases()[cmnd] if a: cmnd = a.split()[0] except (KeyError, TypeError): if not self.has_key(cmnd): try: from boot import shorttable if shorttable.data.has_key(cmnd): cmndlist = shorttable.data[cmnd] if len(cmndlist) == 1: cmnd = cmndlist[0] else: event.reply("choose one of: ", cmndlist) ; return except Exception, ex: handle_exception() logging.info("trying for %s" % cmnd) result = None try: result = self[cmnd] except KeyError: pass logging.debug("woulddispatch result: %s" % result) if result: event.bloh() ; event.makeargs() return result def dispatch(self, bot, event, direct=False): """ dispatch an event if cmnd exists and user is allowed to exec this command. """ if event.nodispatch: logging.info("nodispatch is set on event") ; return if event.groupchat and bot.cfg.fulljids: id = event.auth elif event.groupchat: id = event.auth = event.userhost else: id = event.auth if not event.user: event.bind(bot) if not event.user: raise NoSuchUser(event.userhost) self.reloadcheck(bot, event) c = self.woulddispatch(bot, event) if not c: c = self.checkre(bot, event) if not c: raise NoSuchCommand(event.usercmnd) if c.modname in bot.plugs.loading and bot.plugs.loading[c.modname]: event.reply("%s is loading" % c.modname) ; return if bot.cmndperms and bot.cmndperms[c.cmnd]: perms = bot.cmndperms[c.cmnd] else: perms = c.perms if bot.allowall: return self.doit(bot, event, c, direct) elif not bot.users or bot.users.allowed(id, perms, bot=bot): return self.doit(bot, event, c, direct) elif bot.users.allowed(id, perms, bot=bot): return self.doit(bot, event, c, direct) return event def doit(self, bot, event, target, direct=False): """ do the dispatching. """ if not target.enable: return if target.modname in event.chan.data.denyplug: logging.warn("%s is denied in channel %s - %s" % (target.plugname, event.channel, event.userhost)) return id = event.auth or event.userhost event.iscommand = True event.how = event.how or target.how or "overwrite" aliascheck(event) logging.warning('dispatching %s (%s)' % (event.usercmnd, bot.cfg.name)) try: if bot.isgae: if not event.notask and (target.threaded or event.threaded) and not event.nothreads: logging.warn("LAUNCHING AS TASK") from jsb.drivers.gae.tasks import start_botevent if target.threaded == "backend": start_botevent(bot, event, "backend") else: start_botevent(bot, event, target.speed or event.speed) event.reply("task started for %s" % event.auth) else: target.func(bot, event) else: if direct or event.direct: target.func(bot, event) elif target.threaded and not event.nothreads: logging.warning("launching thread for %s (%s)" % (event.usercmnd, bot.cfg.name)) t = start_bot_command(target.func, (bot, event)) event.thread = t else: event.dontclose = False; cmndrunner.put(target.speed or event.speed, target.modname, target.func, bot, event) except Exception, ex: logging.error('%s - error executing %s' % (whichmodule(), str(target.func))) raise return event def unload(self, modname): """ remove modname registered commands from store. """ delete = [] for name, cmnd in self.iteritems(): if not cmnd: continue if cmnd.modname == modname: delete.append(cmnd) for cmnd in delete: cmnd.enable = False return self def apropos(self, search): """ search existing commands for search term. """ result = [] from boot import getcmndtable for name, plugname in getcmndtable().iteritems(): if search in name: result.append(name) return result def perms(self, cmnd): """ show what permissions are needed to execute cmnd. """ try: return self[cmnd].perms except KeyError: return [] def whereis(self, cmnd): """ return plugin name in which command is implemented. """ from boot import getcmndtable try: return getcmndtable()[cmnd] except KeyError: return "" def gethelp(self, cmnd): """ get the docstring of a command. used for help. """ try: return self[cmnd].func.__doc__ except KeyError: pass def reloadcheck(self, bot, event, target=None): """ check if event requires a plugin to be reloaded. if so reload the plugin. """ from boot import getcmndtable from boot import plugblacklist plugloaded = None plugin = None target = target or event.usercmnd.lower() from jsb.lib.aliases import getaliases aliases = getaliases() try: target = aliases[target] except KeyError: try: target = event.chan.data.aliases[target] except (AttributeError, KeyError, TypeError): pass if not getcmndtable().has_key(target): try: from boot import shorttable if shorttable.data.has_key(target): cmndlist = shorttable.data[target] if len(cmndlist) == 1: target = cmndlist[0] except Exception, ex: handle_exception() if target: target = target.split()[0] logging.debug("checking for reload of %s" % target) try: plugin = getcmndtable()[target] except KeyError: try: from boot import retable for regex, mod in retable.data.iteritems(): if re.search(regex, event.stripcc() or event.txt): plugin = mod ; break except Exception, ex: handle_exception() logging.info("plugin is %s" % plugin) if not plugin: logging.debug("can't find plugin to reload for %s" % target) ; return if plugin in bot.plugs: logging.info(" %s already loaded" % plugin) ; return plugloaded elif plugin in plugblacklist.data: return plugloaded elif bot.cfg.loadlist and plugin not in bot.cfg.loadlist: logging.warn("plugin %s is blacklisted" % plugin) ; return plugloaded logging.info("loaded %s on demand" % plugin) plugloaded = bot.plugs.reload(plugin) return plugloaded ## global commands cmnds = Commands() def size(): return len(cmnds) jsb-0.84.4/jsb/lib/morphs.py0000664000175000017500000000367211733124163015510 0ustar bartbart00000000000000# gozerbot/morphs.py # # """ convert input/output stream. """ ## jsb imports from jsb.utils.exception import handle_exception from jsb.utils.trace import calledfrom ## basic imports import sys import logging ## Morph claas class Morph(object): """ transform stream. """ def __init__(self, func): self.modname = calledfrom(sys._getframe(1)) self.func = func self.activate = True def do(self, *args, **kwargs): """ do the morphing. """ if not self.activate: logging.warn("morphs - %s morph is not enabled" % str(self.func)) ; return #logging.warn("morphs - using morph function %s" % str(self.func)) 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. """ m = Morph(func) if not index: self.append(m) else: self.insert(index, m) logging.info("morphs - added morph function %s - %s" % (str(func), m.modname)) return self def do(self, input, *args, **kwargs): """ call morphing chain. """ for morph in self: input = morph.do(input, *args, **kwargs) or input return input def unload(self, modname): """ unload morhps belonging to plug . """ for index in range(len(self)-1, -1, -1): if self[index].modname == modname: del self[index] def disable(self, modname): """ disable morhps belonging to plug . """ for index in range(len(self)-1, -1, -1): if self[index].modname == modname: self[index].activate = False def activate(self, plugname): """ activate morhps belonging to plug . """ for index in range(len(self)-1, -1, -1): if self[index].modname == modname: self[index].activate = True ## global morphs inputmorphs = MorphList() outputmorphs = MorphList() jsb-0.84.4/jsb/lib/botbase.py0000664000175000017500000006417411733124163015623 0ustar bartbart00000000000000# jsb/botbase.py # # """ base class for all bots. """ ## jsb imports from jsb.utils.exception import handle_exception from runner import defaultrunner, callbackrunner, waitrunner from eventhandler import mainhandler from jsb.utils.lazydict import LazyDict from plugins import plugs as coreplugs from callbacks import callbacks, first_callbacks, last_callbacks, remote_callbacks from eventbase import EventBase from errors import NoSuchCommand, PlugsNotConnected, NoOwnerSet, NameNotSet, NoEventProvided from commands import Commands, cmnds from config import Config, getmainconfig from jsb.utils.pdod import Pdod from channelbase import ChannelBase from less import Less, outcache from boot import boot, getcmndperms, default_plugins from jsb.utils.locking import lockdec from exit import globalshutdown from jsb.utils.generic import splittxt, toenc, fromenc, waitforqueue, strippedtxt, waitevents, stripcolor from jsb.utils.trace import whichmodule from fleet import getfleet from aliases import getaliases from jsb.utils.name import stripname from tick import tickloop from threads import start_new_thread, threaded from morphs import inputmorphs, outputmorphs from gatekeeper import GateKeeper from wait import waiter from factory import bot_factory from jsb.lib.threads import threaded from jsb.utils.locking import lock_object, release_object from jsb.utils.url import decode_html_entities from jsb.lib.users import getusers try: import wave except ImportError: from jsb.imports import gettornado tornado = gettornado() import tornado.ioloop ## basic imports import time import logging import copy import sys import getpass import os import thread import types import threading import Queue import re import urllib from collections import deque ## defines cpy = copy.deepcopy ## locks reconnectlock = threading.RLock() reconnectlocked = lockdec(reconnectlock) lock = thread.allocate_lock() locked = lockdec(lock) ## classes class BotBase(LazyDict): """ base class for all bots. """ def __init__(self, cfg=None, usersin=None, plugs=None, botname=None, nick=None, bottype=None, *args, **kwargs): logging.debug("type is %s" % str(type(self))) if cfg: self.cfg = cfg ; botname = botname or self.cfg.name if not botname: botname = u"default-%s" % str(type(self)).split('.')[-1][:-2] if not botname: raise Exception("can't determine botname") self.fleetdir = u'fleet' + os.sep + stripname(botname) if not self.cfg: self.cfg = Config(self.fleetdir + os.sep + u'config') self.cfg.name = botname or self.cfg.name if not self.cfg.name: raise Exception("name is not set in %s config file" % self.fleetdir) logging.debug("name is %s" % self.cfg.name) LazyDict.__init__(self) logging.debug("created bot with config %s" % self.cfg.tojson(full=True)) self.ecounter = 0 self.ignore = [] self.ids = [] self.aliases = getaliases() self.reconnectcount = 0 self.plugs = coreplugs self.gatekeeper = GateKeeper(self.cfg.name) self.gatekeeper.allow(self.user or self.jid or self.cfg.server or self.cfg.name) try: import waveapi self.isgae = True logging.debug("bot is a GAE bot (%s)" % self.cfg.name) except ImportError: self.isgae = False logging.debug("bot is a shell bot (%s)" % self.cfg.name) self.starttime = time.time() self.type = bottype or "base" self.status = "init" self.networkname = self.cfg.networkname or self.cfg.name or "" from jsb.lib.datadir import getdatadir datadir = getdatadir() self.datadir = datadir + os.sep + self.fleetdir self.maincfg = getmainconfig() self.owner = self.cfg.owner if not self.owner: logging.debug(u"owner is not set in %s - using mainconfig" % self.cfg.cfile) self.owner = self.maincfg.owner self.users = usersin or getusers() logging.debug(u"owner is %s" % self.owner) self.users.make_owner(self.owner) self.outcache = outcache self.userhosts = LazyDict() self.nicks = LazyDict() self.connectok = threading.Event() self.reconnectcount = 0 self.cfg.nick = nick or self.cfg.nick or u'jsb' try: if not os.isdir(self.datadir): os.mkdir(self.datadir) except: pass self.setstate() self.outputlock = thread.allocate_lock() try: self.outqueue = Queue.PriorityQueue() self.eventqueue = Queue.PriorityQueue() except AttributeError: self.outqueue = Queue.Queue() self.eventqueue = Queue.Queue() self.encoding = self.cfg.encoding or "utf-8" self.cmndperms = getcmndperms() self.outputmorphs = outputmorphs self.inputmorphs = inputmorphs if not self.isgae: tickloop.start(self) def copyin(self, data): self.update(data) def _resume(self, data, botname, *args, **kwargs): pass def _resumedata(self): """ return data needed for resuming. """ try: self.cfg.fd = self.oldsock.fileno() except AttributeError: logging.warn("no oldsock found for %s" % self.cfg.name) return {self.cfg.name: dict(self.cfg)} def benice(self, event=None, sleep=0.005): if self.server and self.server.io_loop: logging.debug("i'm being nice") if event and self.server and event.handler: self.server.io_loop.add_callback(event.handler.async_callback(lambda: time.sleep(sleep))) elif self.server: self.server.io_loop.add_callback(lambda: time.sleep(sleep)) time.sleep(sleep) def do_enable(self, modname): """ enable plugin given its modulename. """ try: self.cfg.blacklist and self.cfg.blacklist.remove(modname) except ValueError: pass if self.cfg.loadlist and modname not in self.cfg.loadlist: self.cfg.loadlist.append(modname) self.cfg.save() def do_disable(self, modname): """ disable plugin given its modulename. """ if self.cfg.blacklist and modname not in self.cfg.blacklist: self.cfg.blacklist.append(modname) if self.cfg.loadlist and modname in self.cfg.loadlist: self.cfg.loadlist.remove(modname) self.cfg.save() #@locked def put(self, event, direct=False): """ put an event on the worker queue. """ if direct: self.doevent(event) elif self.isgae: from jsb.drivers.gae.tasks import start_botevent start_botevent(self, event, event.speed) else: if event: logging.debug("putted event on %s" % self.cfg.name) self.ecounter += 1 self.input(event.speed, event) else: self.input(0, None) return event def broadcast(self, txt): """ broadcast txt to all joined channels. """ for chan in self.state['joinedchannels']: self.say(chan, txt) def _eventloop(self): """ output loop. """ logging.debug('%s - starting eventloop' % self.cfg.name) self.stopeventloop = 0 while not self.stopped and not self.stopeventloop: try: res = self.eventqueue.get() if not res: break (prio, event) = res if not event: break logging.debug("%s - eventloop - %s - %s" % (self.cfg.name, event.cbtype, event.userhost)) event.speed = prio self.doevent(event) self.benice() except Queue.Empty: time.sleep(0.01) ; continue except Exception, ex: handle_exception() ; logging.warn("error in eventloop: %s" % str(ex)) logging.debug('%s - stopping eventloop' % self.cfg.name) def input(self, prio, event): """ put output onto one of the output queues. """ self.eventqueue.put(("%s-%s" % (prio, self.ecounter), event)) def _outloop(self): """ output loop. """ logging.debug('%s - starting output loop' % self.cfg.name) self.stopoutloop = 0 while not self.stopped and not self.stopoutloop: try: r = self.outqueue.get() if not r: continue (prio, res) = r logging.debug("%s - OUT - %s - %s" % (self.cfg.name, self.type, str(res))) if not res: continue self.out(*res) except Queue.Empty: time.sleep(0.1) ; continue except Exception, ex: handle_exception() logging.debug('%s - stopping output loop' % self.cfg.name) def _pingloop(self): """ output loop. """ logging.debug('%s - starting ping loop' % self.cfg.name) time.sleep(5) while not self.stopped: try: if self.status != "start" and not self.pingcheck(): self.reconnect() ; break except Exception, ex: logging.error(str(ex)) ; self.reconnect() ; break time.sleep(self.cfg.pingsleep or 60) logging.debug('%s - stopping ping loop' % self.cfg.name) def putonqueue(self, nr, *args): """ put output onto one of the output queues. """ self.outqueue.put((nr, args)) def outputsizes(self): """ return sizes of output queues. """ return (self.outqueue.qsize(), self.eventqueue.qsize()) def setstate(self, state=None): """ set state on the bot. """ self.state = state or Pdod(self.datadir + os.sep + 'state') if self.state and not 'joinedchannels' in self.state.data: self.state.data.joinedchannels = [] def setusers(self, users=None): """ set users on the bot. """ if users: self.users = users return import jsb.lib.users as u if not u.users: u.users_boot() self.users = u.users def loadplugs(self, packagelist=[]): """ load plugins from packagelist. """ self.plugs.loadall(packagelist) return self.plugs def joinchannels(self): """ join channels. """ time.sleep(getmainconfig().waitforjoin or 1) target = self.cfg.channels try: for i in self.state['joinedchannels']: if i not in target: target.append(i) except: pass if not target: target = self.state['joinedchannels'] for i in target: try: logging.debug("%s - joining %s" % (self.cfg.name, i)) channel = ChannelBase(i, self.cfg.name) if channel: key = channel.data.key else: key = None if channel.data.nick: self.ids.append("%s/%s" % (i, channel.data.nick)) start_new_thread(self.join, (i, key)) except Exception, ex: logging.warn('%s - failed to join %s: %s' % (self.cfg.name, i, str(ex))) handle_exception() time.sleep(3) def boot(self): logging.warn("booting %s bot" % self.cfg.name) if not self.cfg.type: self.cfg.type = self.type ; self.cfg.save() fleet = getfleet() fleet.addbot(self) fleet.addnametype(self.cfg.name, self.type) while 1: try: #self.exit(close=False, save=False) self.started = False if self.start(): break except Exception, ex: logging.error(str(ex)) logging.error("sleeping 15 seconds") time.sleep(15) def start(self, connect=True, join=True): """ start the mainloop of the bot. """ if self.started: logging.warn("%s - already started" % self.cfg.name) ; return self.stopped = False self.stopreadloop = False self.stopoutloop = False self.status = "start" if not self.isgae: start_new_thread(self._eventloop, ()) if connect: if not self.connect() : return False start_new_thread(self._readloop, ()) start_new_thread(self._outloop, ()) self.connectok.wait() if self.stopped: logging.warn("bot is stopped") ; return True if self.connectok.isSet(): logging.warn('%s - logged on !' % self.cfg.name) if join: start_new_thread(self.joinchannels, ()) elif self.type not in ["console", "base"]: logging.warn("%s - failed to logon - connectok is not set" % self.cfg.name) self.status == "started" self.started = True self.dostart(self.cfg.name, self.type) return True def doremote(self, event): """ dispatch an event. """ if not event: raise NoEventProvided() event.nodispatch = True event.forwarded = True event.dontbind = True event.prepare(self) self.status = "callback" starttime = time.time() msg = "%s - %s - %s - %s" % (self.cfg.name, event.auth, event.how, event.cbtype) logging.warn(msg) try: logging.debug("remote - %s" % event.dump()) except: pass if self.closed: if self.gatekeeper.isblocked(event.origin): return if event.status == "done": logging.debug("%s - event is done .. ignoring" % self.cfg.name) return e0 = cpy(event) e0.speed = 1 remote_callbacks.check(self, e0) return def doevent(self, event): """ dispatch an event. """ time.sleep(0.01) if not self.cfg: raise Exception("eventbase - cfg is not set .. can't handle event.") ; return if not event: raise NoEventProvided() self.ecounter += 1 try: if event.isremote(): self.doremote(event) ; return if event.type == "groupchat" and event.fromm in self.ids: logging.debug("%s - receiving groupchat from self (%s)" % (self.cfg.name, event.fromm)) return event.txt = self.inputmorphs.do(fromenc(event.txt, self.encoding), event) except UnicodeDecodeError: logging.warn("%s - got decode error in input .. ingoring" % self.cfg.name) ; return event.bind(self) try: logging.debug("%s - event dump: %s" % (self.cfg.name, event.dump())) except: pass self.status = "callback" starttime = time.time() if self.closed: if self.gatekeeper.isblocked(event.origin): return if event.status == "done": logging.debug("%s - event is done .. ignoring" % self.cfg.name) return if event.msg or event.isdcc: event.speed = 2 e1 = cpy(event) first_callbacks.check(self, e1) if not e1.stop: callbacks.check(self, e1) if not e1.stop: last_callbacks.check(self, e1) event.callbackdone = True waiter.check(self, event) return event def ownercheck(self, userhost): """ check if provided userhost belongs to an owner. """ if self.cfg and self.cfg.owner: if userhost in self.cfg.owner: return True logging.warn("failed ownercheck for %s" % userhost) return False def exit(self, stop=True, close=True, save=True, quit=False): """ exit the bot. """ logging.warn("%s - exit" % self.cfg.name) if stop: self.stopped = True self.stopreadloop = True self.connected = False self.started = False if close: self.putonqueue(1, None, "") self.put(None) self.shutdown() save and self.save() fleet = getfleet() fleet.remove(self) if quit and not fleet.bots: globalshutdown() def _raw(self, txt, *args, **kwargs): """ override this. outnocb() is used more though. """ logging.debug(u"%s - out - %s" % (self.cfg.name, txt)) print txt def makeoutput(self, printto, txt, result=[], nr=375, extend=0, dot=", ", origin=None, showall=False, *args, **kwargs): """ chop output in pieces and stored it for !more command. """ if not txt: return "" txt = self.makeresponse(txt, result, dot) if showall: return txt res1, nritems = self.less(origin or printto, txt, nr+extend) return res1 def out(self, printto, txt, how="msg", event=None, origin=None, *args, **kwargs): """ output method with OUTPUT event generated. """ self.outmonitor(origin, printto, txt, event=event) self.outnocb(printto, txt, how, event=event, origin=origin, *args, **kwargs) if event: event.ready() write = out def outnocb(self, printto, txt, how="msg", event=None, origin=None, *args, **kwargs): """ output function without callbacks called.. override this in your driver. """ self._raw(txt) writenocb = outnocb def say(self, channel, txt, result=[], how="msg", event=None, nr=375, extend=0, dot=", ", showall=False, *args, **kwargs): """ default method to send txt from the bot to a user/channel/jid/conference etc. """ logging.warn("saying to %s" % channel) if event: #event.busy.append(event.usercmnd) if event.userhost in self.ignore: logging.warn("%s - ignore on %s - no output done" % (self.cfg.name, event.userhost)) ; return if event.how == "msg" and self.type == "irc": target = event.nick else: target = channel if event.pipelined: for i in result: event.outqueue.append(i) return else: target = channel if showall or (event and event.showall): txt = self.makeresponse(txt, result, dot, *args, **kwargs) else: txt = self.makeoutput(channel, txt, result, nr, extend, dot, origin=target, *args, **kwargs) if txt: txt = decode_html_entities(txt) if event: event.nrout += 1 if event.displayname: txt = "[%s] %s" % (event.displayname, txt) if result: for i in result: event.outqueue.append(i) event.resqueue.append(txt) if event.nooutput: event.ready() ; return else: logging.info("not putting txt on queues") txt = self.outputmorphs.do(txt, event) self.out(target, txt, how, event=event, origin=target, *args, **kwargs) def saynocb(self, channel, txt, result=[], how="msg", event=None, nr=375, extend=0, dot=", ", showall=False, *args, **kwargs): logging.warn("saying to %s (without callbacks)" % channel) txt = self.makeoutput(channel, txt, result, nr, extend, dot, showall=showall, *args, **kwargs) if txt: if event: if self.cfg.name in event.path: event.path.append(self.cfg.name) for i in result: event.outqueue.append(i) event.resqueue.append(txt) txt = self.outputmorphs.do(txt, event) self.outnocb(channel, txt, how, event=event, origin=channel, *args, **kwargs) def less(self, printto, what, nr=365): """ split up in parts of chars overflowing on word boundaries. """ if type(what) == types.ListType: txtlist = what else: what = what.strip() txtlist = splittxt(what, nr) size = 0 if not txtlist: logging.debug("can't split txt from %s" % what) return ["", ""] res = txtlist[0] length = len(txtlist) if length > 1: logging.debug("addding %s lines to %s outcache (less)" % (len(txtlist), printto)) outcache.set(u"%s-%s" % (self.cfg.name, printto), txtlist[1:]) res += " - %s more" % (length - 1) return [res, length] def reconnect(self, start=False, close=False): """ reconnect to the server. """ if self.stopped: logging.warn("%s - bot is stopped .. not reconnecting" % self.cfg.name) ; return #self.reconnectcount = 0 time.sleep(2) while 1: self.reconnectcount += 1 sleepsec = self.reconnectcount * 5 if sleepsec > 301: sleepsec = 302 logging.warn('%s - reconnecting .. sleeping %s seconds' % (self.cfg.name, sleepsec)) if not start: time.sleep(sleepsec) #start = False try: if not start: self.exit(close=close) if self.doreconnect(): break except Exception, ex: logging.error(str(ex)) def doreconnect(self, start=False): self.started = False return self.start() def save(self, *args, **kwargs): """ save bot state if available. """ if self.state: self.state.save() def makeresponse(self, txt, result=[], dot=", ", *args, **kwargs): """ create a response from a string and result list. """ res = [] dres = [] if type(txt) == types.DictType or type(txt) == types.ListType: result = txt if type(result) == types.DictType: for key, value in result.iteritems(): dres.append(u"%s: %s" % (key, unicode(value))) if dres: target = dres else: target = result if target: txt = u"" + txt + u"" for i in target: if not i: continue if type(i) == types.DictType: for key, value in i.iteritems(): res.append(u"%s: %s" % (key, unicode(value))) else: res.append(unicode(i)) ret = "" if txt: ret = unicode(txt) + dot.join(res) elif res: ret = dot.join(res) if ret: return ret return "" def send(self, *args, **kwargs): pass def sendnocb(self, *args, **kwargs): pass def normalize(self, what): """ convert markup to IRC bold. """ if not what: return what txt = strippedtxt(what, ["\002", "\003"]) txt = re.sub("\s+", " ", what) txt = stripcolor(txt) txt = txt.replace("\002", "*") txt = txt.replace("", "") txt = txt.replace("", "") txt = txt.replace("", "") txt = txt.replace("", "") txt = txt.replace("<b>", "*") txt = txt.replace("</b>", "*") txt = txt.replace("<i>", "") txt = txt.replace("</i>", "") return txt def dostart(self, botname=None, bottype=None, *args, **kwargs): """ create an START event and send it to callbacks. """ e = EventBase() e.bot = self e.botname = botname or self.cfg.name e.bottype = bottype or self.type e.origin = e.botname e.userhost = self.cfg.name +'@' + self.cfg.uuid e.nolog = True e.channel = botname e.txt = "%s.%s - %s" % (e.botname, e.bottype, str(time.time())) e.cbtype = 'START' e.ttl = 1 e.nick = self.cfg.nick or self.cfg.name self.doevent(e) logging.debug("%s - START event send to callbacks" % self.cfg.name) def outmonitor(self, origin, channel, txt, event=None): """ create an OUTPUT event with provided txt and send it to callbacks. """ if event: e = cpy(event) else: e = EventBase() if e.status == "done": logging.debug("%s - outmonitor - event is done .. ignoring" % self.cfg.name) return e.bot = self e.origin = origin e.userhost = str(self.cfg.name) +'@' + str(self.cfg.uuid) e.auth = e.userhost e.channel = channel e.txt = txt e.cbtype = 'OUTPUT' e.nodispatch = True e.ttl = 1 e.nick = self.cfg.nick or self.cfg.name e.bonded = True e.isoutput = True e.dontbind = True first_callbacks.check(self, e) def make_event(self, origin, channel, txt, event=None, wait=0, showall=False, nooutput=False, cbtype=""): """ insert an event into the callbacks chain. """ if event: e = cpy(event) else: e = EventBase(bot=self) e.cbtype = cbtype or "CMND" e.origin = origin or "test@test" e.auth = e.origin e.userhost = e.origin e.channel = channel e.txt = unicode(txt) e.nick = e.userhost.split('@')[0] e.showall = showall e.nooutput = nooutput e.wait = wait e.closequeue = False e.bind(self) return e def execstr(self, origin, channel, txt, event=None, wait=0, showall=False, nooutput=False): e = self.make_event(origin, channel, txt, event, wait, showall, nooutput) return e.execwait() def docmnd(self, origin, channel, txt, event=None, wait=0, showall=False, nooutput=False): """ do a command. """ if event: e = cpy(event) else: e = EventBase() e.cbtype = "CMND" e.bot = self e.origin = origin e.auth = origin e.userhost = origin e.channel = channel e.txt = unicode(txt) e.nick = e.userhost.split('@')[0] e.usercmnd = e.txt.split()[0] e.allowqueues = True e.closequeue = True e.showall = showall e.nooutput = nooutput e.bind(self) if cmnds.woulddispatch(self, e) or e.txt[0] == "?": return self.doevent(e) def join(self, channel, password, *args, **kwargs): """ join a channel. """ pass def part(self, channel, *args, **kwargs): """ leave a channel. """ pass def action(self, channel, txt, event=None, *args, **kwargs): """ send action to channel. """ pass def doop(self, channel, who): """ give nick ops. """ pass def invite(self, *args, **kwargs): """ invite another user/bot. """ pass def donick(self, nick, *args, **kwargs): """ do a nick change. """ pass def shutdown(self, *args, **kwargs): """ shutdown the bot. """ pass def quit(self, reason="", *args, **kwargs): """ close connection with the server. """ pass def connect(self, reconnect=False, *args, **kwargs): """ connect to the server. """ pass def names(self, channel, *args, **kwargs): """ request all names of a channel. """ pass def settopic(self, channel, txt): pass def gettopic(self, channel): pass def pingcheck(self): return True jsb-0.84.4/jsb/lib/fleet.py0000664000175000017500000002604111733124163015272 0ustar bartbart00000000000000# jsb/lib/fleet.py # # """ fleet is a list of bots. """ ## jsb imports from jsb.utils.exception import handle_exception from jsb.utils.generic import waitforqueue from jsb.utils.trace import whichmodule from config import Config, getmainconfig from datadir import getdatadir from users import users from plugins import plugs from persist import Persist from errors import NoSuchBotType, BotNotEnabled from threads import start_new_thread from eventhandler import mainhandler from jsb.utils.name import stripname from jsb.lib.factory import BotFactory from jsb.utils.lazydict import LazyDict## simplejson imports from jsb.imports import getjson json = getjson() ## basic imports import Queue import os import types import time import glob import logging import threading import thread import copy ## defines cpy = copy.deepcopy ## classes class FleetBotAlreadyExists(Exception): pass ## locks from jsb.utils.locking import lockdec lock = thread.allocate_lock() locked = lockdec(lock) ## Fleet class class Fleet(Persist): """ a fleet contains multiple bots (list of bots). """ def __init__(self, datadir): Persist.__init__(self, datadir + os.sep + 'fleet' + os.sep + 'fleet.main') if not self.data.has_key('names'): self.data['names'] = [] if not self.data.has_key('types'): self.data['types'] = {} self.startok = threading.Event() self.bots = [] def addnametype(self, name, type): if name not in self.data['names']: self.data['names'].append(name) self.data['types'][name] = type self.save() return True def getenabled(self, exclude=[]): res = [] for name in self.data.names: cfg = Config("fleet" + os.sep + name + os.sep + "config") if cfg.type in exclude: continue if not cfg.disable: res.append(name) return res def loadall(self, names=[]): """ load all bots. """ target = names or self.data.names or [] threads = [] bots = [] for name in target: if not name: logging.warn("name is not set") ; continue try: bot = self.makebot(self.data.types[name], name) except KeyError: continue except BotNotEnabled: logging.info("%s is not enabled" % name) ; continue except KeyError: logging.error("no type know for %s bot" % name) except Exception, ex: handle_exception() if bot and bot not in bots: bots.append(bot) return bots def avail(self): """ return available bots. """ return self.data['names'] def getfirstbot(self, type="irc"): """ return the first bot in the fleet. """ for bot in self.bots: if type in bot.type: return bot def getfirstjabber(self, isgae=False): """ return the first jabber bot of the fleet. """ return self.getfirstbot("xmpp") def size(self): """ return number of bots in fleet. """ return len(self.bots) def settype(self, name, type): """ set the type of a bot. """ cfg = Config('fleet' + os.sep + stripname(name) + os.sep + 'config') cfg['name'] = name logging.debug("%s - setting type to %s" % (self.cfile, type)) cfg.type = type cfg.save() def makebot(self, type, name, config={}, domain="", showerror=False): """ create a bot .. use configuration if provided. """ if not name: logging.error(" name is not correct: %s" % name) ; return if not type: type = self.data.types.get(name) if not type: logging.error("no type found for %s bot" % name) ; return if config: logging.debug('making %s (%s) bot - %s - from: %s' % (type, name, config.tojson(), whichmodule())) bot = None if not config: cfg = Config('fleet' + os.sep + stripname(name) + os.sep + 'config') else: cfg = config cfg.init() if not cfg.name: cfg['name'] = name cfg['botname'] = cfg['name'] if cfg.disable: logging.info("%s bot is disabled. see %s" % (name, cfg.cfile)) raise BotNotEnabled(name) if not cfg.type and type: logging.debug("%s - setting type to %s" % (cfg.cfile, type)) cfg.type = type if not cfg['type']: try: self.data['names'].remove(name) self.save() except ValueError: pass raise Exception("no bot type specified") if not cfg.owner: logging.debug("%s - owner not set .. using global config." % cfg.name) cfg.owner = getmainconfig().owner if not cfg.domain and domain: cfg.domain = domain if not cfg: raise Exception("can't make config for %s" % name) #cfg.save() bot = BotFactory().create(type, cfg) return bot def save(self): """ save fleet data and call save on all the bots. """ Persist.save(self) for i in self.bots: try: i.save() except Exception, ex: handle_exception() def list(self): """ return list of bot names. """ result = [] for i in self.bots: result.append(i.cfg.name) return result def stopall(self): """ call stop() on all fleet bots. """ for i in self.bots: try: i.stop() except: handle_exception() def byname(self, name): """ return bot by name. """ for i in self.bots: if name == i.cfg.name: return i def replace(self, name, bot): """ replace bot with a new bot. """ for i in range(len(self.bots)): if name == self.bots[i].cfg.name: self.bots[i] = bot return True def enable(self, cfg): """ enable a bot baed of provided config. """ if cfg.name and cfg.name not in self.data['names']: self.data['names'].append(cfg.name) self.data['types'][cfg.name] = cfg.type self.save() return True def addbot(self, bot): """ add a bot to the fleet .. remove all existing bots with the same name. """ assert bot if not bot in self.bots: self.bots.append(bot) else: logging.error("%s bot is already in fleet" % bot.cfg.name) ; return False self.addnametype(bot.cfg.name, bot.type) logging.info('added %s' % bot.cfg.name) return True def delete(self, name): """ delete bot with name from fleet. """ for bot in self.bots: if bot.cfg.name == name: bot.exit() self.remove(bot) bot.cfg['disable'] = 1 bot.cfg.save() logging.debug('%s disabled' % bot.cfg.name) return True return False def remove(self, bot): """ delete bot by object. """ try: self.bots.remove(bot) return True except ValueError: return False def exit(self, name=None, jabber=False): """ call exit on all bots. """ if not name: threads = [] for bot in self.bots: if jabber and bot.type != 'sxmpp' and bot.type != 'jabber': continue threads.append(start_new_thread(bot.exit, ())) for thread in threads: thread.join() return for bot in self.bots: if bot.cfg.name == name: if jabber and bot.type != 'sxmpp' and bot.type != 'jabber': continue try: bot.exit() except: handle_exception() self.remove(bot) return True return False def cmnd(self, event, name, cmnd): """ do command on a bot. """ bot = self.byname(name) if not bot: return 0 j = cpy(event) j.bot = bot j.txt = cmnd j.displayname = j.bot.cfg.name j.execute() return j.wait() def cmndall(self, event, cmnd): """ do a command on all bots. """ for bot in self.bots: self.cmnd(event, bot.cfg.name, cmnd) def broadcast(self, txt): """ broadcast txt to all bots. """ for bot in self.bots: bot.broadcast(txt) def boot(self, botnames=[], exclude=[]): if not botnames: botnames = self.getenabled(exclude) bots = self.loadall(botnames) todo = [] done = [] for bot in bots: if not bot: continue logging.debug("%s bot type is %s" % (bot.cfg.name, bot.type)) if bot.type not in exclude: todo.append(bot) ; done.append(bot.cfg.name) if todo: start_new_thread(self.startall, (todo, )) if done: logging.warn("fleet bots are %s" % ", ".join(done)) else: logging.warn("no fleet bots are enabled, see %s" % getdatadir() + os.sep + "config" + os.sep +'fleet') def startall(self, bots, usethreads=True): threads = [] target = bots or self.bots for bot in target: logging.debug('starting %s bot (%s)' % (bot.cfg.name, bot.type)) if usethreads or bot.type in ["sleek", "tornado"]: threads.append(start_new_thread(bot.boot, ())) ; continue try: bot.boot() except Excepton, ex: handle_exception() time.sleep(1) if usethreads: for t in threads: t.join(15) time.sleep(5) self.startok.set() def resume(self, sessionfile, exclude=[]): """ resume bot from session file. """ session = json.load(open(sessionfile, 'r')) chan = session.get("channel") for name, cfg in session['bots'].iteritems(): dont = False for ex in exclude: if ex in name: dont = True if dont: continue cfg = LazyDict(cfg) #cfg = LazyDict(session['bots'][name]) try: if not cfg.disable: logging.info("resuming %s" % cfg) start_new_thread(self.resumebot, (cfg, chan)) except: handle_exception() ; return time.sleep(10) self.startok.set() def resumebot(self, botcfg, chan=None): """ resume single bot. """ botname = botcfg.name logging.warn("resuming %s bot" % botname) oldbot = self.byname(botname) if oldbot and botcfg['type'] in ["sxmpp", "convore"]: oldbot.exit() #cfg = Config('fleet' + os.sep + stripname(botname) + os.sep + 'config') #if cfg.disable: logging.warn("%s - bot is disabled .. not resuming it" % botname) ; return bot = self.makebot(botcfg.type, botname) bot._resume(botcfg, botname) bot.start(False) self.addbot(bot) if chan and chan in bot.state["joinedchannels"]: bot.say(chan, "done!") ## global fleet object fleet = None @locked def getfleet(datadir=None, new=False): if not datadir: from jsb.lib.datadir import getdatadir datadir = getdatadir() global fleet if not fleet or new: fleet = Fleet(datadir) fleet.loadall() return fleet def size(): return getfleet().size()jsb-0.84.4/jsb/lib/persistconfig.py0000664000175000017500000001644011733124163017054 0ustar bartbart00000000000000# gozerbot/persistconfig.py # # """ plugin related config file with commands added to the bot to config a plugin. 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 """ __copyright__ = 'this file is in the public domain' __author__ = 'Bas van Oostveen' ## jsb imports from jsb.utils.lazydict import LazyDict from jsb.utils.trace import calledfrom, whichplugin from jsb.lib.examples import examples from jsb.lib.persist import Persist from jsb.lib.config import Config from jsb.imports import getjson ## basic imports import sys import os import types import time import logging ## PersistConfigError exception class PersistConfigError(Exception): pass ## class Option .. is for gozerbot compat class Option(object): pass ## PersistConfig class class PersistConfig(Config): """ persist plugin configuration and create default handlers. """ def __init__(self): self.hide = [] modname = whichplugin() logging.debug("persistconfig - module name is %s" % modname) self.plugname = modname.split('.')[-1] Config.__init__(self, 'plugs' + os.sep + modname, "config") self.modname = modname cmndname = "%s-cfg" % self.plugname logging.debug('persistconfig - added command %s (%s)' % (cmndname, self.plugname)) from jsb.lib.commands import cmnds, Command cmnds[cmndname] = Command(self.modname, cmndname, self.cmnd_cfg, ['OPER', ]) examples.add(cmndname, "%s configuration" % self.plugname, cmndname) cmndnamesave = cmndname + "save" cmnds[cmndnamesave] = Command(self.modname, cmndname, self.cmnd_cfgsave, ['OPER',]) examples.add(cmndnamesave, "save %s configuration" % self.plugname, cmndnamesave) ## cmnds def show_cfg(self, bot, ievent): """ show config options. """ s = [] dumpstr = self.tojson() logging.warn(dumpstr) for key, optionvalue in sorted(getjson().loads(dumpstr).iteritems()): if key in self.hide: continue v = optionvalue 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): """ save config. """ self.save() ievent.reply("config saved") def cmnd_cfg_edit(self, bot, ievent, args, key, optionvalue): """ edit config values. """ if not self.has_key(key): ievent.reply('option %s is not defined' % key) return if key in self.hide: return if type(optionvalue) == 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]=='"': v = v.replace('"', '') elif v[0]=="'" and v[-1]=="'": v = v.replace("'", "") elif '.' in v: try: v = float(v) except ValueError: ievent.reply("invalid long literal: %s" % v) return else: try: v = int(v) except ValueError: ievent.reply("invalid int literal: %s" % v) return values.append(v) self.set(key, values) self.save() ievent.reply("%s set %s" % (key, values)) return command = args[0] value = ' '.join(args[1:]) if command == "clear": self.clear(key) self.save() ievent.reply("list empty") elif command == "add": self.append(key, value) self.save() ievent.reply("%s added %s" % (key, value)) elif command == "remove" or command == "del": try: self.remove(key, value) self.save() 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(optionvalue)(value) except: pass if type(value) == type(optionvalue): self.set(key, value) self.save() ievent.reply("%s set" % key) elif type(value) == types.LongType and type(option.value) == types.IntType: self.set(key, value) self.save() ievent.reply("%s set" % key) else: ievent.reply("value %s (%s) is not of the same type as %s (%s)" % (value, type(value), optionvalue, type(optionvalue))) def cmnd_cfg(self, bot, ievent): """ the config (cfg) command. """ if not ievent.args: self.show_cfg(bot, ievent) return argc = len(ievent.args) key = ievent.args[0] try: optionvalue = self[key] except KeyError: ievent.reply("%s option %s not found" % (self.plugname, key)) return if key in self.hide: return if argc == 1: ievent.reply(str(optionvalue)) return self.cmnd_cfg_edit(bot, ievent, ievent.args[1:], key, optionvalue) def generic_cmnd(self, key): """ command for editing config values. """ def func(bot, ievent): try: optionvalue = self[key] except KeyError: ievent.reply("%s not found" % key) return if not isinstance(option, Option): logging.warn('persistconfig - option %s is not a valid option' % key) return if ievent.args: value = ' '.join(ievent.args) try: value = type(optionvalue)(value) except: pass self.cmnd_cfg_edit(bot, ievent, ievent.args, key, optionvalue) else: ievent.reply(str(optionvalue)) return func ### plugin api def define(self, key, value=None, desc="plugin option", perm='OPER', example="", name=None, exposed=True): """ define initial value. """ if name: name = name.lower() if not exposed and not key in self.hide: self.hide.append(key) if not self.has_key(key): if name == None: name = "%s-cfg-%s" % (self.plugname, str(key)) self[key] = value def undefine(self, key, throw=False): """ remove a key. """ try: del self[key] return True except KeyError, e: if throw: raise self.save() return False def set(self, key, value, throw=False): """ set a key's value. """ self[key] = value def append(self, key, value): """ append a value. """ self[key].append(value) def remove(self, key, value): """ remove a value. """ self[key].remove(value) def clear(self, key): """ clear a value. """ self[key] = [] def get(self, key, default=None): """ get value of key. """ try: return self[key] except KeyError: return default jsb-0.84.4/jsb/lib/channelbase.py0000664000175000017500000000633511733124163016442 0ustar bartbart00000000000000# jsb/channelbase.py # # """ provide a base class for channels. """ ## jsb imports from jsb.utils.name import stripname from jsb.utils.lazydict import LazyDict from jsb.lib.persist import Persist from jsb.lib.datadir import getdatadir from jsb.utils.trace import whichmodule from jsb.lib.errors import NoChannelProvided, NoChannelSet ## basic imports import time import os import logging ## classes class ChannelBase(Persist): """ Base class for all channel objects. """ def __init__(self, id, botname=None, type="notset"): if not id: raise NoChannelSet() if not botname: Persist.__init__(self, getdatadir() + os.sep + 'channels' + os.sep + stripname(id)) else: Persist.__init__(self, getdatadir() + os.sep + 'fleet' + os.sep + stripname(botname) + os.sep + 'channels' + os.sep + stripname(id)) self.id = id self.type = type self.lastmodified = time.time() self.data.id = id self.data.enable = self.data.enable or False self.data.ops = self.data.ops or [] self.data.silentcommands = self.data.silentcommands or [] self.data.allowcommands = self.data.allowcommands or [] self.data.feeds = self.data.feeds or [] self.data.forwards = self.data.forwards or [] self.data.allowwatch = self.data.allowwatch or [] self.data.watched = self.data.watched or [] self.data.passwords = self.data.passwords or {} self.data.cc = self.data.cc or "" self.data.nick = self.data.nick or "jsb" self.data.key = self.data.key or "" self.data.denyplug = self.data.denyplug or [] self.data.createdfrom = whichmodule() self.data.cacheindex = 0 self.data.tokens = self.data.tokens or [] self.data.webchannels = self.data.webchannels or [] def setpass(self, type, key): """ set channel password based on type. """ self.data.passwords[type] = key self.save() def getpass(self, type='IRC'): """ get password based of type. """ try: return self.data.passwords[type] except KeyError: return def delpass(self, type='IRC'): """ delete password. """ try: del self.data.passwords[type] self.save() return True except KeyError: return def parse(self, event, wavelet=None): """ parse an event for channel related data and constuct the channel with it. Overload this. """ pass def gae_create(self): try: from google.appengine.api import channel id = self.id.split("_", 1)[0] except ImportError: return (None, None) #webchan = id + "_" + str(time.time()) webchan = id + "_" + str(time.time()) logging.warn("trying to create channel for %s" % webchan) token = channel.create_channel(webchan) #if token and token not in self.data.tokens: # self.data.tokens.insert(0, token) # self.data.tokens = self.data.tokens[:10] if webchan not in self.data.webchannels: self.data.webchannels.insert(0, webchan) self.data.webchannels = self.data.webchannels[:2] self.save() return (webchan, token) jsb-0.84.4/jsb/lib/runner.py0000664000175000017500000001634011733124163015505 0ustar bartbart00000000000000# jsb/runner.py # # """ threads management to run jobs. """ ## jsb imports from jsb.lib.threads import getname, start_new_thread, start_bot_command from jsb.utils.exception import handle_exception from jsb.utils.locking import locked, lockdec from jsb.utils.lockmanager import rlockmanager, lockmanager from jsb.utils.generic import waitevents from jsb.utils.trace import callstack, whichmodule from jsb.lib.threadloop import RunnerLoop from jsb.lib.callbacks import callbacks from jsb.lib.errors import URLNotEnabled ## 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 self.nowrunning = "" self.longrunning = [] self.shortrunning = [] def handle(self, descr, func, *args, **kwargs): """ schedule a job. """ self.working = True try: #rlockmanager.acquire(getname(str(func))) logging.debug('running %s: %s' % (descr, self.nowrunning)) self.starttime = time.time() func(*args, **kwargs) self.finished = time.time() self.elapsed = self.finished - self.starttime if self.elapsed > 5: 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 def done(self, event): try: int(event.cbtype) except ValueError: if event.cbtype not in ['TICK', 'PING', 'NOTICE', 'TICK60']: logging.warn(str(event.cbtype)) ## BotEventRunner class class BotEventRunner(Runner): def handle(self, speed, args): """ schedule a bot command. """ try: descr, func, bot, ievent = args self.starttime = time.time() #lockmanager.acquire(getname(str(func))) if not ievent.nolog: logging.debug("event handler is %s" % str(func)) if self.nowrunning in self.longrunning: logging.warn("putting %s on longrunner" % self.nowrunning) longrunner.put(ievent.speed or speed, descr, func, bot, ievent) return self.working = True try: func(bot, ievent) except URLNotEnabled: logging.warn("urls fetching is disabled (%s)" % ievent.usercmnd) ; return self.finished = time.time() self.elapsed = self.finished - self.starttime if self.elapsed > 5: if self.nowrunning not in self.longrunning: self.longrunning.append(self.nowrunning) if not ievent.nolog: logging.debug('ALERT %s %s job taking too long: %s seconds' % (descr, str(func), self.elapsed)) except Exception, ex: handle_exception() #finally: lockmanager.release(getname(str(func))) self.working = False class LongRunner(Runner): def handle(self, speed, args): """ schedule a bot command. """ try: descr, func, bot, ievent = args self.starttime = time.time() #lockmanager.acquire(getname(str(func))) #self.nowrunning = getname(func) if not ievent.nolog: logging.debug("long event handler is %s" % str(func)) self.working = True func(bot, ievent) self.elapsed = time.time() - self.starttime if self.elapsed < 1 and self.nowrunning not in self.shortrunning: self.shortrunning.append(self.nowrunning) except Exception, ex: handle_exception() #finally: lockmanager.release(getname(str(func))) self.working = False logging.debug("long finished - %s" % self.nowrunning) ## Runners class class Runners(object): """ runners is a collection of runner objects. """ def __init__(self, name, max=100, runnertype=Runner, doready=True): self.name = name self.max = max self.runners = [] self.runnertype = runnertype self.doready = doready def names(self): return [getname(runner.name) for runner in self.runners] def size(self): qsize = [runner.queue.qsize() for runner in self.runners] return "%s/%s" % (qsize, len(self.runners)) 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, speed, *data): """ put a job on a free runner. """ for runner in self.runners: if runner.queue.empty(): runner.put(speed, *data) return if self.runners: self.cleanup() runner = self.makenew() runner.put(speed, *data) def running(self): """ return list of running jobs. """ result = [] for runner in self.runners: if runner.working: result.append(runner.nowrunning) return result def makenew(self): """ create a new runner. """ runner = None if len(self.runners) < self.max: runner = self.runnertype(self.name + "-" + str(len(self.runners))) runner.start() self.runners.append(runner) else: runner = random.choice(self.runners) return runner def cleanup(self): """ clean up idle runners. """ r = [] for runner in self.runners: if runner.queue.empty(): r.append(runner) if not r: return for runner in r: runner.stop() for runner in r: try: self.runners.remove(runner) except ValueError: pass logging.debug("%s - cleaned %s" % (self.name, [item.name for item in r])) logging.debug("%s - now running: %s" % (self.name, self.size())) ## show runner status def runner_status(): print cmndrunner.runnersizes() print callbackrunner.runnersizes() ## global runners cmndrunner = defaultrunner = Runners("default", 100, BotEventRunner) longrunner = Runners("long", 80, LongRunner) callbackrunner = Runners("callback", 30, BotEventRunner) waitrunner = Runners("wait", 20, BotEventRunner) apirunner = Runners("api", 10, BotEventRunner) ## cleanup def runnercleanup(bot, event): cmndrunner.cleanup() longrunner.cleanup() callbackrunner.cleanup() waitrunner.cleanup() apirunner.cleanup() callbacks.add("TICK60", runnercleanup) def size(): return "cmnd: %s - callbacks: %s - wait: %s - long: %s - api: %s" % (cmndrunner.size(), callbackrunner.size(), waitrunner.size(), longrunner.size(), apirunner.size()) jsb-0.84.4/jsb/lib/nextid.py0000664000175000017500000000246311733124163015470 0ustar bartbart00000000000000# jsb/lib/nextid.py # # """ provide increasing counters """ ## basic imports import os ## Nextid class class Nextid(object): """ counters by name """ def __init__(self, fname): self.data = {} def get(self, item): """ get counter for item """ item = item.lower() try: result = self.data[item] except KeyError: return None return result def set(self, item, number): """ set counter of item to number """ item = item.lower() try: self.data[item] = int(number) except ValueError: return 0 self.save() return 1 def next(self, item): """ get increment of item counter """ item = item.lower() try: self.data[item] += 1 except KeyError: self.data[item] = 1 self.save() return self.data[item] def nextlist(self, item, nr): """ get increment of item counter """ item = item.lower() try: start = self.data[item] + 1 except KeyError: start = 1 stop = start + nr l = range(start, stop) self.data[item] = stop - 1 self.save() return l def save(self): pass nextid = Nextid('notused') jsb-0.84.4/jsb/lib/factory.py0000664000175000017500000000426211733124163015643 0ustar bartbart00000000000000# jsb/lib/factory.py # # """ Factory to produce instances of classes. """ ## jsb imports from jsb.utils.exception import handle_exception from jsb.lib.errors import NoSuchBotType, NoUserProvided ## basic imports import logging ## Factory base class class Factory(object): pass ## BotFactory class class BotFactory(Factory): def create(self, type=None, cfg={}): try: type = cfg['type'] or type or None except KeyError: pass try: if 'xmpp' in type: try: import waveapi from jsb.drivers.gae.xmpp.bot import XMPPBot bot = XMPPBot(cfg) except ImportError: from jsb.drivers.xmpp.bot import SXMPPBot bot = SXMPPBot(cfg) elif type == 'web': from jsb.drivers.gae.web.bot import WebBot bot = WebBot(cfg) elif type == 'wave': from jsb.drivers.gae.wave.bot import WaveBot bot = WaveBot(cfg, domain=cfg.domain) elif type == 'irc': from jsb.drivers.irc.bot import IRCBot bot = IRCBot(cfg) elif type == 'console': from jsb.drivers.console.bot import ConsoleBot bot = ConsoleBot(cfg) elif type == 'base': from jsb.lib.botbase import BotBase bot = BotBase(cfg) elif type == 'convore': from jsb.drivers.convore.bot import ConvoreBot bot = ConvoreBot(cfg) elif type == 'tornado': from jsb.drivers.tornado.bot import TornadoBot bot = TornadoBot(cfg) elif type == 'sleek': from jsb.drivers.sleek.bot import SleekBot bot = SleekBot(cfg) else: raise NoSuchBotType('%s bot .. unproper type %s' % (type, cfg.dump())) return bot except NoUserProvided, ex: logging.info("%s - %s" % (cfg.name, str(ex))) except AssertionError, ex: logging.warn("%s - assertion error: %s" % (cfg.name, str(ex))) except Exception, ex: handle_exception() bot_factory = BotFactory() jsb-0.84.4/jsb/lib/aliases.py0000664000175000017500000000304011733124163015606 0ustar bartbart00000000000000# jsb/lib/aliases.py # # """ global aliases. """ ## jsb imports from jsb.lib.datadir import getdatadir from jsb.utils.lazydict import LazyDict ## basic imports import os import logging ## defines aliases = LazyDict() ## getaliases function def getaliases(ddir=None, force=True): """ return global aliases. """ global aliases if not aliases or force: from jsb.lib.persist import Persist from jsb.utils.lazydict import LazyDict d = ddir or getdatadir() p = Persist(d + os.sep + "run" + os.sep + "aliases") if not p.data: p.data = LazyDict() aliases = p.data return aliases def savealiases(ddir=None): """ return global aliases. """ global aliases if aliases: logging.warn("saving aliases") from jsb.lib.persist import Persist from jsb.utils.lazydict import LazyDict d = ddir or getdatadir() p = Persist(d + os.sep + "run" + os.sep + "aliases") p.data = aliases p.save() return aliases def aliascheck(ievent): """ check if alias is available. """ if not ievent.execstr: return try: cmnd = ievent.execstr.split()[0] alias = aliases[cmnd] ievent.txt = ievent.txt.replace(cmnd, alias, 1) ievent.execstr = ievent.execstr.replace(cmnd, alias, 1) ievent.alias = alias ievent.aliased = cmnd ievent.prepare() except (IndexError, KeyError): pass def size(): return len(aliases) def setalias(first, second): global aliases aliases[first] = second jsb-0.84.4/jsb/lib/errors.py0000664000175000017500000000300511733124163015502 0ustar bartbart00000000000000# jsb/errors.py # # """ jsb exceptions. """ ## jsb imports from jsb.utils.trace import calledfrom ## basic imports import sys ## exceptions class JsonBotError(Exception): pass class CantLogon(JsonBotError): pass class URLNotEnabled(JsonBotError): pass class JSONParseError(JsonBotError): pass class StreamError(JsonBotError): pass class RequireError(JsonBotError): pass class CannotAuth(JsonBotError): pass class NotConnected(JsonBotError): pass class FeedAlreadyExists(JsonBotError): pass class MemcachedCounterError(JsonBotError): pass class NoSuchFile(JsonBotError): pass class BotNotEnabled(JsonBotError): pass class NoProperDigest(JsonBotError): pass class NoChannelProvided(JsonBotError): pass class NoInput(JsonBotError): pass class PropertyIgnored(JsonBotError): pass class BotNotSetInEvent(JsonBotError): pass class FeedProviderError(JsonBotError): pass class CantSaveConfig(JsonBotError): pass class NoOwnerSet(JsonBotError): pass class NameNotSet(JsonBotError): pass class NoSuchUser(JsonBotError): pass class NoUserProvided(JsonBotError): pass class NoSuchBotType(JsonBotError): pass class NoChannelSet(JsonBotError): pass class NoSuchWave(JsonBotError): pass class NoSuchCommand(JsonBotError): pass class NoSuchPlugin(JsonBotError): pass class NoOwnerSet(JsonBotError): pass class PlugsNotConnected(JsonBotError): pass class NoEventProvided(JsonBotError): pass jsb-0.84.4/jsb/__init__.py0000664000175000017500000000010711733124163015157 0ustar bartbart00000000000000__all__ = ["contrib", "db", "api", "drivers", "lib", "plugs", "utils"] jsb-0.84.4/jsb/contrib/0000775000175000017500000000000011733126034014507 5ustar bartbart00000000000000jsb-0.84.4/jsb/contrib/memcache.py0000664000175000017500000013173311733124163016634 0ustar bartbart00000000000000#!/usr/bin/env python """ client module for memcached (memory cache daemon) Overview ======== See U{the MemCached homepage} for more about memcached. Usage summary ============= This should give you a feel for how this module operates:: import memcache mc = memcache.Client(['127.0.0.1:11211'], debug=0) mc.set("some_key", "Some value") value = mc.get("some_key") mc.set("another_key", 3) mc.delete("another_key") mc.set("key", "1") # note that the key used for incr/decr must be a string. mc.incr("key") mc.decr("key") The standard way to use memcache with a database is like this:: key = derive_key(obj) obj = mc.get(key) if not obj: obj = backend_api.get(...) mc.set(key, obj) # we now have obj, and future passes through this code # will use the object from the cache. Detailed Documentation ====================== More detailed documentation is available in the L{Client} class. """ import sys import socket import time import os import re try: import cPickle as pickle except ImportError: import pickle from binascii import crc32 # zlib version is not cross-platform def cmemcache_hash(key): return((((crc32(key) & 0xffffffff) >> 16) & 0x7fff) or 1) serverHashFunction = cmemcache_hash def useOldServerHashFunction(): """Use the old python-memcache server hash function.""" global serverHashFunction serverHashFunction = crc32 try: from zlib import compress, decompress _supports_compress = True except ImportError: _supports_compress = False # quickly define a decompress just in case we recv compressed data. def decompress(val): raise _Error("received compressed data but I don't support compression (import error)") try: from cStringIO import StringIO except ImportError: from StringIO import StringIO # Original author: Evan Martin of Danga Interactive __author__ = "Sean Reifschneider " __version__ = "1.47" __copyright__ = "Copyright (C) 2003 Danga Interactive" # http://en.wikipedia.org/wiki/Python_Software_Foundation_License __license__ = "Python Software Foundation License" SERVER_MAX_KEY_LENGTH = 250 # Storing values larger than 1MB requires recompiling memcached. If you do, # this value can be changed by doing "memcache.SERVER_MAX_VALUE_LENGTH = N" # after importing this module. SERVER_MAX_VALUE_LENGTH = 1024*1024 class _Error(Exception): pass try: # Only exists in Python 2.4+ from threading import local except ImportError: # TODO: add the pure-python local implementation class local(object): pass class Client(local): """ Object representing a pool of memcache servers. See L{memcache} for an overview. In all cases where a key is used, the key can be either: 1. A simple hashable type (string, integer, etc.). 2. A tuple of C{(hashvalue, key)}. This is useful if you want to avoid making this module calculate a hash value. You may prefer, for example, to keep all of a given user's objects on the same memcache server, so you could use the user's unique id as the hash value. @group Setup: __init__, set_servers, forget_dead_hosts, disconnect_all, debuglog @group Insertion: set, add, replace, set_multi @group Retrieval: get, get_multi @group Integers: incr, decr @group Removal: delete, delete_multi @sort: __init__, set_servers, forget_dead_hosts, disconnect_all, debuglog,\ set, set_multi, add, replace, get, get_multi, incr, decr, delete, delete_multi """ _FLAG_PICKLE = 1<<0 _FLAG_INTEGER = 1<<1 _FLAG_LONG = 1<<2 _FLAG_COMPRESSED = 1<<3 _SERVER_RETRIES = 10 # how many times to try finding a free server. # exceptions for Client class MemcachedKeyError(Exception): pass class MemcachedKeyLengthError(MemcachedKeyError): pass class MemcachedKeyCharacterError(MemcachedKeyError): pass class MemcachedKeyNoneError(MemcachedKeyError): pass class MemcachedKeyTypeError(MemcachedKeyError): pass class MemcachedStringEncodingError(Exception): pass def __init__(self, servers, debug=0, pickleProtocol=0, pickler=pickle.Pickler, unpickler=pickle.Unpickler, pload=None, pid=None, server_max_key_length=SERVER_MAX_KEY_LENGTH, server_max_value_length=SERVER_MAX_VALUE_LENGTH): """ Create a new Client object with the given list of servers. @param servers: C{servers} is passed to L{set_servers}. @param debug: whether to display error messages when a server can't be contacted. @param pickleProtocol: number to mandate protocol used by (c)Pickle. @param pickler: optional override of default Pickler to allow subclassing. @param unpickler: optional override of default Unpickler to allow subclassing. @param pload: optional persistent_load function to call on pickle loading. Useful for cPickle since subclassing isn't allowed. @param pid: optional persistent_id function to call on pickle storing. Useful for cPickle since subclassing isn't allowed. """ local.__init__(self) self.debug = debug self.set_servers(servers) self.stats = {} self.cas_ids = {} # Allow users to modify pickling/unpickling behavior self.pickleProtocol = pickleProtocol self.pickler = pickler self.unpickler = unpickler self.persistent_load = pload self.persistent_id = pid self.server_max_key_length = server_max_key_length self.server_max_value_length = server_max_value_length # figure out the pickler style file = StringIO() try: pickler = self.pickler(file, protocol = self.pickleProtocol) self.picklerIsKeyword = True except TypeError: self.picklerIsKeyword = False def set_servers(self, servers): """ Set the pool of servers used by this client. @param servers: an array of servers. Servers can be passed in two forms: 1. Strings of the form C{"host:port"}, which implies a default weight of 1. 2. Tuples of the form C{("host:port", weight)}, where C{weight} is an integer weight value. """ self.servers = [_Host(s, self.debug) for s in servers] self._init_buckets() def get_stats(self, stat_args = None): '''Get statistics from each of the servers. @param stat_args: Additional arguments to pass to the memcache "stats" command. @return: A list of tuples ( server_identifier, stats_dictionary ). The dictionary contains a number of name/value pairs specifying the name of the status field and the string value associated with it. The values are not converted from strings. ''' data = [] for s in self.servers: if not s.connect(): continue if s.family == socket.AF_INET: name = '%s:%s (%s)' % ( s.ip, s.port, s.weight ) else: name = 'unix:%s (%s)' % ( s.address, s.weight ) if not stat_args: s.send_cmd('stats') else: s.send_cmd('stats ' + stat_args) serverData = {} data.append(( name, serverData )) readline = s.readline while 1: line = readline() if not line or line.strip() == 'END': break stats = line.split(' ', 2) serverData[stats[1]] = stats[2] return(data) def get_slabs(self): data = [] for s in self.servers: if not s.connect(): continue if s.family == socket.AF_INET: name = '%s:%s (%s)' % ( s.ip, s.port, s.weight ) else: name = 'unix:%s (%s)' % ( s.address, s.weight ) serverData = {} data.append(( name, serverData )) s.send_cmd('stats items') readline = s.readline while 1: line = readline() if not line or line.strip() == 'END': break item = line.split(' ', 2) #0 = STAT, 1 = ITEM, 2 = Value slab = item[1].split(':', 2) #0 = items, 1 = Slab #, 2 = Name if slab[1] not in serverData: serverData[slab[1]] = {} serverData[slab[1]][slab[2]] = item[2] return data def flush_all(self): 'Expire all data currently in the memcache servers.' for s in self.servers: if not s.connect(): continue s.send_cmd('flush_all') s.expect("OK") def debuglog(self, str): if self.debug: sys.stderr.write("MemCached: %s\n" % str) def _statlog(self, func): if func not in self.stats: self.stats[func] = 1 else: self.stats[func] += 1 def forget_dead_hosts(self): """ Reset every host in the pool to an "alive" state. """ for s in self.servers: s.deaduntil = 0 def _init_buckets(self): self.buckets = [] for server in self.servers: for i in range(server.weight): self.buckets.append(server) def _get_server(self, key): if isinstance(key, tuple): serverhash, key = key else: serverhash = serverHashFunction(key) for i in range(Client._SERVER_RETRIES): server = self.buckets[serverhash % len(self.buckets)] if server.connect(): #print "(using server %s)" % server, return server, key serverhash = serverHashFunction(str(serverhash) + str(i)) return None, None def disconnect_all(self): for s in self.servers: s.close_socket() def delete_multi(self, keys, time=0, key_prefix=''): ''' Delete multiple keys in the memcache doing just one query. >>> notset_keys = mc.set_multi({'key1' : 'val1', 'key2' : 'val2'}) >>> mc.get_multi(['key1', 'key2']) == {'key1' : 'val1', 'key2' : 'val2'} 1 >>> mc.delete_multi(['key1', 'key2']) 1 >>> mc.get_multi(['key1', 'key2']) == {} 1 This method is recommended over iterated regular L{delete}s as it reduces total latency, since your app doesn't have to wait for each round-trip of L{delete} before sending the next one. @param keys: An iterable of keys to clear @param time: number of seconds any subsequent set / update commands should fail. Defaults to 0 for no delay. @param key_prefix: Optional string to prepend to each key when sending to memcache. See docs for L{get_multi} and L{set_multi}. @return: 1 if no failure in communication with any memcacheds. @rtype: int ''' self._statlog('delete_multi') server_keys, prefixed_to_orig_key = self._map_and_prefix_keys(keys, key_prefix) # send out all requests on each server before reading anything dead_servers = [] rc = 1 for server in server_keys.iterkeys(): bigcmd = [] write = bigcmd.append if time != None: for key in server_keys[server]: # These are mangled keys write("delete %s %d\r\n" % (key, time)) else: for key in server_keys[server]: # These are mangled keys write("delete %s\r\n" % key) try: server.send_cmds(''.join(bigcmd)) except socket.error, msg: rc = 0 if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) dead_servers.append(server) # if any servers died on the way, don't expect them to respond. for server in dead_servers: del server_keys[server] for server, keys in server_keys.iteritems(): try: for key in keys: server.expect("DELETED") except socket.error, msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) rc = 0 return rc def delete(self, key, time=0): '''Deletes a key from the memcache. @return: Nonzero on success. @param time: number of seconds any subsequent set / update commands should fail. Defaults to 0 for no delay. @rtype: int ''' self.check_key(key) server, key = self._get_server(key) if not server: return 0 self._statlog('delete') if time != None: cmd = "delete %s %d" % (key, time) else: cmd = "delete %s" % key try: server.send_cmd(cmd) line = server.readline() if line and line.strip() in ['DELETED', 'NOT_FOUND']: return 1 self.debuglog('Delete expected DELETED or NOT_FOUND, got: %s' % repr(line)) except socket.error, msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) return 0 def incr(self, key, delta=1): """ Sends a command to the server to atomically increment the value for C{key} by C{delta}, or by 1 if C{delta} is unspecified. Returns None if C{key} doesn't exist on server, otherwise it returns the new value after incrementing. Note that the value for C{key} must already exist in the memcache, and it must be the string representation of an integer. >>> mc.set("counter", "20") # returns 1, indicating success 1 >>> mc.incr("counter") 21 >>> mc.incr("counter") 22 Overflow on server is not checked. Be aware of values approaching 2**32. See L{decr}. @param delta: Integer amount to increment by (should be zero or greater). @return: New value after incrementing. @rtype: int """ return self._incrdecr("incr", key, delta) def decr(self, key, delta=1): """ Like L{incr}, but decrements. Unlike L{incr}, underflow is checked and new values are capped at 0. If server value is 1, a decrement of 2 returns 0, not -1. @param delta: Integer amount to decrement by (should be zero or greater). @return: New value after decrementing. @rtype: int """ return self._incrdecr("decr", key, delta) def _incrdecr(self, cmd, key, delta): try: delta = int(delta) except ValueError: pass self.check_key(key) server, key = self._get_server(key) if not server: return 0 self._statlog(cmd) cmd = '%s %s %s' % (cmd, key, delta) try: server.send_cmd(cmd) line = server.readline() if line == None or line.strip() =='NOT_FOUND': return None return int(line) except socket.error, msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) return None def add(self, key, val, time = 0, min_compress_len = 0): ''' Add new key with value. Like L{set}, but only stores in memcache if the key doesn't already exist. @return: Nonzero on success. @rtype: int ''' return self._set("add", key, val, time, min_compress_len) def append(self, key, val, time=0, min_compress_len=0): '''Append the value to the end of the existing key's value. Only stores in memcache if key already exists. Also see L{prepend}. @return: Nonzero on success. @rtype: int ''' return self._set("append", key, val, time, min_compress_len) def prepend(self, key, val, time=0, min_compress_len=0): '''Prepend the value to the beginning of the existing key's value. Only stores in memcache if key already exists. Also see L{append}. @return: Nonzero on success. @rtype: int ''' return self._set("prepend", key, val, time, min_compress_len) def replace(self, key, val, time=0, min_compress_len=0): '''Replace existing key with value. Like L{set}, but only stores in memcache if the key already exists. The opposite of L{add}. @return: Nonzero on success. @rtype: int ''' return self._set("replace", key, val, time, min_compress_len) def set(self, key, val, time=0, min_compress_len=0): '''Unconditionally sets a key to a given value in the memcache. The C{key} can optionally be an tuple, with the first element being the server hash value and the second being the key. If you want to avoid making this module calculate a hash value. You may prefer, for example, to keep all of a given user's objects on the same memcache server, so you could use the user's unique id as the hash value. @return: Nonzero on success. @rtype: int @param time: Tells memcached the time which this value should expire, either as a delta number of seconds, or an absolute unix time-since-the-epoch value. See the memcached protocol docs section "Storage Commands" for more info on . We default to 0 == cache forever. @param min_compress_len: The threshold length to kick in auto-compression of the value using the zlib.compress() routine. If the value being cached is a string, then the length of the string is measured, else if the value is an object, then the length of the pickle result is measured. If the resulting attempt at compression yeilds a larger string than the input, then it is discarded. For backwards compatability, this parameter defaults to 0, indicating don't ever try to compress. ''' return self._set("set", key, val, time, min_compress_len) def cas(self, key, val, time=0, min_compress_len=0): '''Sets a key to a given value in the memcache if it hasn't been altered since last fetched. (See L{gets}). The C{key} can optionally be an tuple, with the first element being the server hash value and the second being the key. If you want to avoid making this module calculate a hash value. You may prefer, for example, to keep all of a given user's objects on the same memcache server, so you could use the user's unique id as the hash value. @return: Nonzero on success. @rtype: int @param time: Tells memcached the time which this value should expire, either as a delta number of seconds, or an absolute unix time-since-the-epoch value. See the memcached protocol docs section "Storage Commands" for more info on . We default to 0 == cache forever. @param min_compress_len: The threshold length to kick in auto-compression of the value using the zlib.compress() routine. If the value being cached is a string, then the length of the string is measured, else if the value is an object, then the length of the pickle result is measured. If the resulting attempt at compression yeilds a larger string than the input, then it is discarded. For backwards compatability, this parameter defaults to 0, indicating don't ever try to compress. ''' return self._set("cas", key, val, time, min_compress_len) def _map_and_prefix_keys(self, key_iterable, key_prefix): """Compute the mapping of server (_Host instance) -> list of keys to stuff onto that server, as well as the mapping of prefixed key -> original key. """ # Check it just once ... key_extra_len=len(key_prefix) if key_prefix: self.check_key(key_prefix) # server (_Host) -> list of unprefixed server keys in mapping server_keys = {} prefixed_to_orig_key = {} # build up a list for each server of all the keys we want. for orig_key in key_iterable: if isinstance(orig_key, tuple): # Tuple of hashvalue, key ala _get_server(). Caller is essentially telling us what server to stuff this on. # Ensure call to _get_server gets a Tuple as well. str_orig_key = str(orig_key[1]) server, key = self._get_server((orig_key[0], key_prefix + str_orig_key)) # Gotta pre-mangle key before hashing to a server. Returns the mangled key. else: str_orig_key = str(orig_key) # set_multi supports int / long keys. server, key = self._get_server(key_prefix + str_orig_key) # Now check to make sure key length is proper ... self.check_key(str_orig_key, key_extra_len=key_extra_len) if not server: continue if server not in server_keys: server_keys[server] = [] server_keys[server].append(key) prefixed_to_orig_key[key] = orig_key return (server_keys, prefixed_to_orig_key) def set_multi(self, mapping, time=0, key_prefix='', min_compress_len=0): ''' Sets multiple keys in the memcache doing just one query. >>> notset_keys = mc.set_multi({'key1' : 'val1', 'key2' : 'val2'}) >>> mc.get_multi(['key1', 'key2']) == {'key1' : 'val1', 'key2' : 'val2'} 1 This method is recommended over regular L{set} as it lowers the number of total packets flying around your network, reducing total latency, since your app doesn't have to wait for each round-trip of L{set} before sending the next one. @param mapping: A dict of key/value pairs to set. @param time: Tells memcached the time which this value should expire, either as a delta number of seconds, or an absolute unix time-since-the-epoch value. See the memcached protocol docs section "Storage Commands" for more info on . We default to 0 == cache forever. @param key_prefix: Optional string to prepend to each key when sending to memcache. Allows you to efficiently stuff these keys into a pseudo-namespace in memcache: >>> notset_keys = mc.set_multi({'key1' : 'val1', 'key2' : 'val2'}, key_prefix='subspace_') >>> len(notset_keys) == 0 True >>> mc.get_multi(['subspace_key1', 'subspace_key2']) == {'subspace_key1' : 'val1', 'subspace_key2' : 'val2'} True Causes key 'subspace_key1' and 'subspace_key2' to be set. Useful in conjunction with a higher-level layer which applies namespaces to data in memcache. In this case, the return result would be the list of notset original keys, prefix not applied. @param min_compress_len: The threshold length to kick in auto-compression of the value using the zlib.compress() routine. If the value being cached is a string, then the length of the string is measured, else if the value is an object, then the length of the pickle result is measured. If the resulting attempt at compression yeilds a larger string than the input, then it is discarded. For backwards compatability, this parameter defaults to 0, indicating don't ever try to compress. @return: List of keys which failed to be stored [ memcache out of memory, etc. ]. @rtype: list ''' self._statlog('set_multi') server_keys, prefixed_to_orig_key = self._map_and_prefix_keys(mapping.iterkeys(), key_prefix) # send out all requests on each server before reading anything dead_servers = [] notstored = [] # original keys. for server in server_keys.iterkeys(): bigcmd = [] write = bigcmd.append try: for key in server_keys[server]: # These are mangled keys store_info = self._val_to_store_info( mapping[prefixed_to_orig_key[key]], min_compress_len) if store_info: write("set %s %d %d %d\r\n%s\r\n" % (key, store_info[0], time, store_info[1], store_info[2])) else: notstored.append(prefixed_to_orig_key[key]) server.send_cmds(''.join(bigcmd)) except socket.error, msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) dead_servers.append(server) # if any servers died on the way, don't expect them to respond. for server in dead_servers: del server_keys[server] # short-circuit if there are no servers, just return all keys if not server_keys: return(mapping.keys()) for server, keys in server_keys.iteritems(): try: for key in keys: line = server.readline() if line == 'STORED': continue else: notstored.append(prefixed_to_orig_key[key]) #un-mangle. except (_Error, socket.error), msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) return notstored def _val_to_store_info(self, val, min_compress_len): """ Transform val to a storable representation, returning a tuple of the flags, the length of the new value, and the new value itself. """ flags = 0 if isinstance(val, str): pass elif isinstance(val, int): flags |= Client._FLAG_INTEGER val = "%d" % val # force no attempt to compress this silly string. min_compress_len = 0 elif isinstance(val, long): flags |= Client._FLAG_LONG val = "%d" % val # force no attempt to compress this silly string. min_compress_len = 0 else: flags |= Client._FLAG_PICKLE file = StringIO() if self.picklerIsKeyword: pickler = self.pickler(file, protocol = self.pickleProtocol) else: pickler = self.pickler(file, self.pickleProtocol) if self.persistent_id: pickler.persistent_id = self.persistent_id pickler.dump(val) val = file.getvalue() lv = len(val) # We should try to compress if min_compress_len > 0 and we could # import zlib and this string is longer than our min threshold. if min_compress_len and _supports_compress and lv > min_compress_len: comp_val = compress(val) # Only retain the result if the compression result is smaller # than the original. if len(comp_val) < lv: flags |= Client._FLAG_COMPRESSED val = comp_val # silently do not store if value length exceeds maximum if self.server_max_value_length != 0 and \ len(val) >= self.server_max_value_length: return(0) return (flags, len(val), val) def _set(self, cmd, key, val, time, min_compress_len = 0): self.check_key(key) server, key = self._get_server(key) if not server: return 0 self._statlog(cmd) store_info = self._val_to_store_info(val, min_compress_len) if not store_info: return(0) if cmd == 'cas': if key not in self.cas_ids: return self._set('set', key, val, time, min_compress_len) fullcmd = "%s %s %d %d %d %d\r\n%s" % ( cmd, key, store_info[0], time, store_info[1], self.cas_ids[key], store_info[2]) else: fullcmd = "%s %s %d %d %d\r\n%s" % ( cmd, key, store_info[0], time, store_info[1], store_info[2]) try: server.send_cmd(fullcmd) return(server.expect("STORED") == "STORED") except socket.error, msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) return 0 def _get(self, cmd, key): self.check_key(key) server, key = self._get_server(key) if not server: return None self._statlog(cmd) try: server.send_cmd("%s %s" % (cmd, key)) rkey = flags = rlen = cas_id = None if cmd == 'gets': rkey, flags, rlen, cas_id, = self._expect_cas_value(server) if rkey: self.cas_ids[rkey] = cas_id else: rkey, flags, rlen, = self._expectvalue(server) if not rkey: return None value = self._recv_value(server, flags, rlen) server.expect("END") except (_Error, socket.error), msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) return None return value def get(self, key): '''Retrieves a key from the memcache. @return: The value or None. ''' return self._get('get', key) def gets(self, key): '''Retrieves a key from the memcache. Used in conjunction with 'cas'. @return: The value or None. ''' return self._get('gets', key) def get_multi(self, keys, key_prefix=''): ''' Retrieves multiple keys from the memcache doing just one query. >>> success = mc.set("foo", "bar") >>> success = mc.set("baz", 42) >>> mc.get_multi(["foo", "baz", "foobar"]) == {"foo": "bar", "baz": 42} 1 >>> mc.set_multi({'k1' : 1, 'k2' : 2}, key_prefix='pfx_') == [] 1 This looks up keys 'pfx_k1', 'pfx_k2', ... . Returned dict will just have unprefixed keys 'k1', 'k2'. >>> mc.get_multi(['k1', 'k2', 'nonexist'], key_prefix='pfx_') == {'k1' : 1, 'k2' : 2} 1 get_mult [ and L{set_multi} ] can take str()-ables like ints / longs as keys too. Such as your db pri key fields. They're rotored through str() before being passed off to memcache, with or without the use of a key_prefix. In this mode, the key_prefix could be a table name, and the key itself a db primary key number. >>> mc.set_multi({42: 'douglass adams', 46 : 'and 2 just ahead of me'}, key_prefix='numkeys_') == [] 1 >>> mc.get_multi([46, 42], key_prefix='numkeys_') == {42: 'douglass adams', 46 : 'and 2 just ahead of me'} 1 This method is recommended over regular L{get} as it lowers the number of total packets flying around your network, reducing total latency, since your app doesn't have to wait for each round-trip of L{get} before sending the next one. See also L{set_multi}. @param keys: An array of keys. @param key_prefix: A string to prefix each key when we communicate with memcache. Facilitates pseudo-namespaces within memcache. Returned dictionary keys will not have this prefix. @return: A dictionary of key/value pairs that were available. If key_prefix was provided, the keys in the retured dictionary will not have it present. ''' self._statlog('get_multi') server_keys, prefixed_to_orig_key = self._map_and_prefix_keys(keys, key_prefix) # send out all requests on each server before reading anything dead_servers = [] for server in server_keys.iterkeys(): try: server.send_cmd("get %s" % " ".join(server_keys[server])) except socket.error, msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) dead_servers.append(server) # if any servers died on the way, don't expect them to respond. for server in dead_servers: del server_keys[server] retvals = {} for server in server_keys.iterkeys(): try: line = server.readline() while line and line != 'END': rkey, flags, rlen = self._expectvalue(server, line) # Bo Yang reports that this can sometimes be None if rkey is not None: val = self._recv_value(server, flags, rlen) retvals[prefixed_to_orig_key[rkey]] = val # un-prefix returned key. line = server.readline() except (_Error, socket.error), msg: if isinstance(msg, tuple): msg = msg[1] server.mark_dead(msg) return retvals def _expect_cas_value(self, server, line=None): if not line: line = server.readline() if line and line[:5] == 'VALUE': resp, rkey, flags, len, cas_id = line.split() return (rkey, int(flags), int(len), int(cas_id)) else: return (None, None, None, None) def _expectvalue(self, server, line=None): if not line: line = server.readline() if line and line[:5] == 'VALUE': resp, rkey, flags, len = line.split() flags = int(flags) rlen = int(len) return (rkey, flags, rlen) else: return (None, None, None) def _recv_value(self, server, flags, rlen): rlen += 2 # include \r\n buf = server.recv(rlen) if len(buf) != rlen: raise _Error("received %d bytes when expecting %d" % (len(buf), rlen)) if len(buf) == rlen: buf = buf[:-2] # strip \r\n if flags & Client._FLAG_COMPRESSED: buf = decompress(buf) if flags == 0 or flags == Client._FLAG_COMPRESSED: # Either a bare string or a compressed string now decompressed... val = buf elif flags & Client._FLAG_INTEGER: val = int(buf) elif flags & Client._FLAG_LONG: val = long(buf) elif flags & Client._FLAG_PICKLE: try: file = StringIO(buf) unpickler = self.unpickler(file) if self.persistent_load: unpickler.persistent_load = self.persistent_load val = unpickler.load() except Exception, e: self.debuglog('Pickle error: %s\n' % e) val = None else: self.debuglog("unknown flags on get: %x\n" % flags) return val def check_key(self, key, key_extra_len=0): """Checks sanity of key. Fails if: Key length is > SERVER_MAX_KEY_LENGTH (Raises MemcachedKeyLength). Contains control characters (Raises MemcachedKeyCharacterError). Is not a string (Raises MemcachedStringEncodingError) Is an unicode string (Raises MemcachedStringEncodingError) Is not a string (Raises MemcachedKeyError) Is None (Raises MemcachedKeyError) """ if isinstance(key, tuple): key = key[1] if not key: raise Client.MemcachedKeyNoneError("Key is None") if isinstance(key, unicode): raise Client.MemcachedStringEncodingError( "Keys must be str()'s, not unicode. Convert your unicode " "strings using mystring.encode(charset)!") if not isinstance(key, str): raise Client.MemcachedKeyTypeError("Key must be str()'s") if isinstance(key, basestring): if self.server_max_key_length != 0 and \ len(key) + key_extra_len > self.server_max_key_length: raise Client.MemcachedKeyLengthError("Key length is > %s" % self.server_max_key_length) for char in key: if ord(char) < 33 or ord(char) == 127: raise Client.MemcachedKeyCharacterError( "Control characters not allowed") class _Host(object): _DEAD_RETRY = 30 # number of seconds before retrying a dead server. _SOCKET_TIMEOUT = 3 # number of seconds before sockets timeout. def __init__(self, host, debug=0): self.debug = debug if isinstance(host, tuple): host, self.weight = host else: self.weight = 1 # parse the connection string m = re.match(r'^(?Punix):(?P.*)$', host) if not m: m = re.match(r'^(?Pinet):' r'(?P[^:]+)(:(?P[0-9]+))?$', host) if not m: m = re.match(r'^(?P[^:]+)(:(?P[0-9]+))?$', host) if not m: raise ValueError('Unable to parse connection string: "%s"' % host) hostData = m.groupdict() if hostData.get('proto') == 'unix': self.family = socket.AF_UNIX self.address = hostData['path'] else: self.family = socket.AF_INET self.ip = hostData['host'] self.port = int(hostData.get('port', 11211)) self.address = ( self.ip, self.port ) self.deaduntil = 0 self.socket = None self.buffer = '' def debuglog(self, str): if self.debug: sys.stderr.write("MemCached: %s\n" % str) def _check_dead(self): if self.deaduntil and self.deaduntil > time.time(): return 1 self.deaduntil = 0 return 0 def connect(self): if self._get_socket(): return 1 return 0 def mark_dead(self, reason): self.debuglog("MemCache: %s: %s. Marking dead." % (self, reason)) self.deaduntil = time.time() + _Host._DEAD_RETRY self.close_socket() def _get_socket(self): if self._check_dead(): return None if self.socket: return self.socket s = socket.socket(self.family, socket.SOCK_STREAM) if hasattr(s, 'settimeout'): s.settimeout(self._SOCKET_TIMEOUT) try: s.connect(self.address) except socket.timeout, msg: self.mark_dead("connect: %s" % msg) return None except socket.error, msg: if isinstance(msg, tuple): msg = msg[1] self.mark_dead("connect: %s" % msg[1]) return None self.socket = s self.buffer = '' return s def close_socket(self): if self.socket: self.socket.close() self.socket = None def send_cmd(self, cmd): self.socket.sendall(cmd + '\r\n') def send_cmds(self, cmds): """ cmds already has trailing \r\n's applied """ self.socket.sendall(cmds) def readline(self): buf = self.buffer recv = self.socket.recv while True: index = buf.find('\r\n') if index >= 0: break data = recv(4096) if not data: self.mark_dead('Connection closed while reading from %s' % repr(self)) self.buffer = '' return '' buf += data self.buffer = buf[index+2:] return buf[:index] def expect(self, text): line = self.readline() if line != text: self.debuglog("while expecting '%s', got unexpected response '%s'" % (text, line)) return line def recv(self, rlen): self_socket_recv = self.socket.recv buf = self.buffer while len(buf) < rlen: foo = self_socket_recv(max(rlen - len(buf), 4096)) buf += foo if not foo: raise _Error( 'Read %d bytes, expecting %d, ' 'read returned 0 length bytes' % ( len(buf), rlen )) self.buffer = buf[rlen:] return buf[:rlen] def __str__(self): d = '' if self.deaduntil: d = " (dead until %d)" % self.deaduntil if self.family == socket.AF_INET: return "inet:%s:%d%s" % (self.address[0], self.address[1], d) else: return "unix:%s%s" % (self.address, d) def _doctest(): import doctest, memcache servers = ["127.0.0.1:11211"] mc = Client(servers, debug=1) globs = {"mc": mc} return doctest.testmod(memcache, globs=globs) if __name__ == "__main__": failures = 0 print "Testing docstrings..." _doctest() print "Running tests:" print serverList = [["127.0.0.1:11211"]] if '--do-unix' in sys.argv: serverList.append([os.path.join(os.getcwd(), 'memcached.socket')]) for servers in serverList: mc = Client(servers, debug=1) def to_s(val): if not isinstance(val, basestring): return "%s (%s)" % (val, type(val)) return "%s" % val def test_setget(key, val): print "Testing set/get {'%s': %s} ..." % (to_s(key), to_s(val)), mc.set(key, val) newval = mc.get(key) if newval == val: print "OK" return 1 else: print "FAIL"; failures = failures + 1 return 0 class FooStruct(object): def __init__(self): self.bar = "baz" def __str__(self): return "A FooStruct" def __eq__(self, other): if isinstance(other, FooStruct): return self.bar == other.bar return 0 test_setget("a_string", "some random string") test_setget("an_integer", 42) if test_setget("long", long(1<<30)): print "Testing delete ...", if mc.delete("long"): print "OK" else: print "FAIL"; failures = failures + 1 print "Checking results of delete ..." if mc.get("long") == None: print "OK" else: print "FAIL"; failures = failures + 1 print "Testing get_multi ...", print mc.get_multi(["a_string", "an_integer"]) print "Testing get(unknown value) ...", print to_s(mc.get("unknown_value")) f = FooStruct() test_setget("foostruct", f) print "Testing incr ...", x = mc.incr("an_integer", 1) if x == 43: print "OK" else: print "FAIL"; failures = failures + 1 print "Testing decr ...", x = mc.decr("an_integer", 1) if x == 42: print "OK" else: print "FAIL"; failures = failures + 1 sys.stdout.flush() # sanity tests print "Testing sending spaces...", sys.stdout.flush() try: x = mc.set("this has spaces", 1) except Client.MemcachedKeyCharacterError, msg: print "OK" else: print "FAIL"; failures = failures + 1 print "Testing sending control characters...", try: x = mc.set("this\x10has\x11control characters\x02", 1) except Client.MemcachedKeyCharacterError, msg: print "OK" else: print "FAIL"; failures = failures + 1 print "Testing using insanely long key...", try: x = mc.set('a'*SERVER_MAX_KEY_LENGTH + 'aaaa', 1) except Client.MemcachedKeyLengthError, msg: print "OK" else: print "FAIL"; failures = failures + 1 print "Testing sending a unicode-string key...", try: x = mc.set(u'keyhere', 1) except Client.MemcachedStringEncodingError, msg: print "OK", else: print "FAIL",; failures = failures + 1 try: x = mc.set((u'a'*SERVER_MAX_KEY_LENGTH).encode('utf-8'), 1) except: print "FAIL",; failures = failures + 1 else: print "OK", import pickle s = pickle.loads('V\\u4f1a\np0\n.') try: x = mc.set((s*SERVER_MAX_KEY_LENGTH).encode('utf-8'), 1) except Client.MemcachedKeyLengthError: print "OK" else: print "FAIL"; failures = failures + 1 print "Testing using a value larger than the memcached value limit...", x = mc.set('keyhere', 'a'*SERVER_MAX_VALUE_LENGTH) if mc.get('keyhere') == None: print "OK", else: print "FAIL",; failures = failures + 1 x = mc.set('keyhere', 'a'*SERVER_MAX_VALUE_LENGTH + 'aaa') if mc.get('keyhere') == None: print "OK" else: print "FAIL"; failures = failures + 1 print "Testing set_multi() with no memcacheds running", mc.disconnect_all() errors = mc.set_multi({'keyhere' : 'a', 'keythere' : 'b'}) if errors != []: print "FAIL"; failures = failures + 1 else: print "OK" print "Testing delete_multi() with no memcacheds running", mc.disconnect_all() ret = mc.delete_multi({'keyhere' : 'a', 'keythere' : 'b'}) if ret != 1: print "FAIL"; failures = failures + 1 else: print "OK" if failures > 0: print '*** THERE WERE FAILED TESTS' sys.exit(1) sys.exit(0) # vim: ts=4 sw=4 et : jsb-0.84.4/jsb/contrib/__init__.py0000664000175000017500000000013111733124163016614 0ustar bartbart00000000000000import sys, os sys.path.append(os.sep.join(os.path.abspath(__file__).split(os.sep)[:-1]))jsb-0.84.4/jsb/contrib/sleekxmpp/0000775000175000017500000000000011733126034016517 5ustar bartbart00000000000000jsb-0.84.4/jsb/contrib/sleekxmpp/exceptions.py0000664000175000017500000000600711733124163021256 0ustar bartbart00000000000000# -*- coding: utf-8 -*- """ sleekxmpp.exceptions ~~~~~~~~~~~~~~~~~~~~ Part of SleekXMPP: The Sleek XMPP Library :copyright: (c) 2011 Nathanael C. Fritz :license: MIT, see LICENSE for more details """ class XMPPError(Exception): """ A generic exception that may be raised while processing an XMPP stanza to indicate that an error response stanza should be sent. The exception method for stanza objects extending :class:`~sleekxmpp.stanza.rootstanza.RootStanza` will create an error stanza and initialize any additional substanzas using the extension information included in the exception. Meant for use in SleekXMPP plugins and applications using SleekXMPP. Extension information can be included to add additional XML elements to the generated error stanza. :param condition: The XMPP defined error condition. Defaults to ``'undefined-condition'``. :param text: Human readable text describing the error. :param etype: The XMPP error type, such as ``'cancel'`` or ``'modify'``. Defaults to ``'cancel'``. :param extension: Tag name of the extension's XML content. :param extension_ns: XML namespace of the extensions' XML content. :param extension_args: Content and attributes for the extension element. Same as the additional arguments to the :class:`~xml.etree.ElementTree.Element` constructor. :param clear: Indicates if the stanza's contents should be removed before replying with an error. Defaults to ``True``. """ def __init__(self, condition='undefined-condition', text=None, etype='cancel', extension=None, extension_ns=None, extension_args=None, clear=True): if extension_args is None: extension_args = {} self.condition = condition self.text = text self.etype = etype self.clear = clear self.extension = extension self.extension_ns = extension_ns self.extension_args = extension_args class IqTimeout(XMPPError): """ An exception which indicates that an IQ request response has not been received within the alloted time window. """ def __init__(self, iq): super(IqTimeout, self).__init__( condition='remote-server-timeout', etype='cancel') #: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response #: did not arrive before the timeout expired. self.iq = iq class IqError(XMPPError): """ An exception raised when an Iq stanza of type 'error' is received after making a blocking send call. """ def __init__(self, iq): super(IqError, self).__init__( condition=iq['error']['condition'], text=iq['error']['text'], etype=iq['error']['type']) #: The :class:`~sleekxmpp.stanza.iq.Iq` error result stanza. self.iq = iq jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/0000775000175000017500000000000011733126034020200 5ustar bartbart00000000000000jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0203/0000775000175000017500000000000011733126034021440 5ustar bartbart00000000000000jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0203/__init__.py0000664000175000017500000000053211733124163023552 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ from sleekxmpp.plugins.xep_0203 import stanza from sleekxmpp.plugins.xep_0203.stanza import Delay from sleekxmpp.plugins.xep_0203.delay import xep_0203 jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0203/delay.py0000664000175000017500000000211311733124163023106 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ from sleekxmpp.stanza import Message, Presence from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.plugins.base import base_plugin from sleekxmpp.plugins.xep_0203 import stanza class xep_0203(base_plugin): """ XEP-0203: Delayed Delivery XMPP stanzas are sometimes withheld for delivery due to the recipient being offline, or are resent in order to establish recent history as is the case with MUCS. In any case, it is important to know when the stanza was originally sent, not just when it was last received. Also see . """ def plugin_init(self): """Start the XEP-0203 plugin.""" self.xep = '0203' self.description = 'Delayed Delivery' self.stanza = stanza register_stanza_plugin(Message, stanza.Delay) register_stanza_plugin(Presence, stanza.Delay) jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0203/stanza.py0000664000175000017500000000162711733124163023321 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ import datetime as dt from sleekxmpp.xmlstream import ElementBase from sleekxmpp.plugins import xep_0082 class Delay(ElementBase): """ """ name = 'delay' namespace = 'urn:xmpp:delay' plugin_attrib = 'delay' interfaces = set(('from', 'stamp', 'text')) def get_stamp(self): timestamp = self._get_attr('stamp') return xep_0082.parse(timestamp) def set_stamp(self, value): if isinstance(value, dt.datetime): value = xep_0082.format_datetime(value) self._set_attr('stamp', value) def get_text(self): return self.xml.text def set_text(self, value): self.xml.text = value def del_text(self): self.xml.text = '' jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/gmail_notify.py0000664000175000017500000001110011733124163023225 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ import logging from . import base from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq log = logging.getLogger(__name__) class GmailQuery(ElementBase): namespace = 'google:mail:notify' name = 'query' plugin_attrib = 'gmail' interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search')) def getSearch(self): return self['q'] def setSearch(self, search): self['q'] = search def delSearch(self): del self['q'] class MailBox(ElementBase): namespace = 'google:mail:notify' name = 'mailbox' plugin_attrib = 'mailbox' interfaces = set(('result-time', 'total-matched', 'total-estimate', 'url', 'threads', 'matched', 'estimate')) def getThreads(self): threads = [] for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, MailThread.name)): threads.append(MailThread(xml=threadXML, parent=None)) return threads def getMatched(self): return self['total-matched'] def getEstimate(self): return self['total-estimate'] == '1' class MailThread(ElementBase): namespace = 'google:mail:notify' name = 'mail-thread-info' plugin_attrib = 'thread' interfaces = set(('tid', 'participation', 'messages', 'date', 'senders', 'url', 'labels', 'subject', 'snippet')) sub_interfaces = set(('labels', 'subject', 'snippet')) def getSenders(self): senders = [] sendersXML = self.xml.find('{%s}senders' % self.namespace) if sendersXML is not None: for senderXML in sendersXML.findall('{%s}sender' % self.namespace): senders.append(MailSender(xml=senderXML, parent=None)) return senders class MailSender(ElementBase): namespace = 'google:mail:notify' name = 'sender' plugin_attrib = 'sender' interfaces = set(('address', 'name', 'originator', 'unread')) def getOriginator(self): return self.xml.attrib.get('originator', '0') == '1' def getUnread(self): return self.xml.attrib.get('unread', '0') == '1' class NewMail(ElementBase): namespace = 'google:mail:notify' name = 'new-mail' plugin_attrib = 'new-mail' class gmail_notify(base.base_plugin): """ Google Talk: Gmail Notifications """ def plugin_init(self): self.description = 'Google Talk: Gmail Notifications' self.xmpp.registerHandler( Callback('Gmail Result', MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, MailBox.namespace, MailBox.name)), self.handle_gmail)) self.xmpp.registerHandler( Callback('Gmail New Mail', MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, NewMail.namespace, NewMail.name)), self.handle_new_mail)) registerStanzaPlugin(Iq, GmailQuery) registerStanzaPlugin(Iq, MailBox) registerStanzaPlugin(Iq, NewMail) self.last_result_time = None def handle_gmail(self, iq): mailbox = iq['mailbox'] approx = ' approximately' if mailbox['estimated'] else '' log.info('Gmail: Received%s %s emails', approx, mailbox['total-matched']) self.last_result_time = mailbox['result-time'] self.xmpp.event('gmail_messages', iq) def handle_new_mail(self, iq): log.info("Gmail: New emails received!") self.xmpp.event('gmail_notify') self.checkEmail() def getEmail(self, query=None): return self.search(query) def checkEmail(self): return self.search(newer=self.last_result_time) def search(self, query=None, newer=None): if query is None: log.info("Gmail: Checking for new emails") else: log.info('Gmail: Searching for emails matching: "%s"', query) iq = self.xmpp.Iq() iq['type'] = 'get' iq['to'] = self.xmpp.boundjid.bare iq['gmail']['q'] = query iq['gmail']['newer-than-time'] = newer return iq.send() jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0128/0000775000175000017500000000000011733126034021446 5ustar bartbart00000000000000jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0128/__init__.py0000664000175000017500000000050211733124163023555 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ from sleekxmpp.plugins.xep_0128.static import StaticExtendedDisco from sleekxmpp.plugins.xep_0128.extended_disco import xep_0128 jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0128/static.py0000664000175000017500000000363611733124163023320 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ import logging import sleekxmpp from sleekxmpp.plugins.xep_0030 import StaticDisco log = logging.getLogger(__name__) class StaticExtendedDisco(object): """ Extend the default StaticDisco implementation to provide support for extended identity information. """ def __init__(self, static): """ Augment the default XEP-0030 static handler object. Arguments: static -- The default static XEP-0030 handler object. """ self.static = static def set_extended_info(self, jid, node, data): """ Replace the extended identity data for a JID/node combination. The data parameter may provide: data -- Either a single data form, or a list of data forms. """ self.del_extended_info(jid, node, data) self.add_extended_info(jid, node, data) def add_extended_info(self, jid, node, data): """ Add additional extended identity data for a JID/node combination. The data parameter may provide: data -- Either a single data form, or a list of data forms. """ self.static.add_node(jid, node) forms = data.get('data', []) if not isinstance(forms, list): forms = [forms] for form in forms: self.static.nodes[(jid, node)]['info'].append(form) def del_extended_info(self, jid, node, data): """ Replace the extended identity data for a JID/node combination. The data parameter is not used. """ if (jid, node) not in self.static.nodes: return info = self.static.nodes[(jid, node)]['info'] for form in info['substanza']: info.xml.remove(form.xml) jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0128/extended_disco.py0000664000175000017500000000645011733124163025007 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ import logging import sleekxmpp from sleekxmpp import Iq from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.plugins.base import base_plugin from sleekxmpp.plugins.xep_0004 import Form from sleekxmpp.plugins.xep_0030 import DiscoInfo from sleekxmpp.plugins.xep_0128 import StaticExtendedDisco class xep_0128(base_plugin): """ XEP-0128: Service Discovery Extensions Allow the use of data forms to add additional identity information to disco#info results. Also see . Attributes: disco -- A reference to the XEP-0030 plugin. static -- Object containing the default set of static node handlers. xmpp -- The main SleekXMPP object. Methods: set_extended_info -- Set extensions to a disco#info result. add_extended_info -- Add an extension to a disco#info result. del_extended_info -- Remove all extensions from a disco#info result. """ def plugin_init(self): """Start the XEP-0128 plugin.""" self.xep = '0128' self.description = 'Service Discovery Extensions' self._disco_ops = ['set_extended_info', 'add_extended_info', 'del_extended_info'] register_stanza_plugin(DiscoInfo, Form, iterable=True) def post_init(self): """Handle cross-plugin dependencies.""" base_plugin.post_init(self) self.disco = self.xmpp['xep_0030'] self.static = StaticExtendedDisco(self.disco.static) self.disco.set_extended_info = self.set_extended_info self.disco.add_extended_info = self.add_extended_info self.disco.del_extended_info = self.del_extended_info for op in self._disco_ops: self.disco._add_disco_op(op, getattr(self.static, op)) def set_extended_info(self, jid=None, node=None, **kwargs): """ Set additional, extended identity information to a node. Replaces any existing extended information. Arguments: jid -- The JID to modify. node -- The node to modify. data -- Either a form, or a list of forms to use as extended information, replacing any existing extensions. """ self.disco._run_node_handler('set_extended_info', jid, node, kwargs) def add_extended_info(self, jid=None, node=None, **kwargs): """ Add additional, extended identity information to a node. Arguments: jid -- The JID to modify. node -- The node to modify. data -- Either a form, or a list of forms to add as extended information. """ self.disco._run_node_handler('add_extended_info', jid, node, kwargs) def del_extended_info(self, jid=None, node=None, **kwargs): """ Remove all extended identity information to a node. Arguments: jid -- The JID to modify. node -- The node to modify. """ self.disco._run_node_handler('del_extended_info', jid, node, kwargs) jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/__init__.py0000664000175000017500000000074711733124163022322 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. See the file LICENSE for copying permission. """ __all__ = ['xep_0004', 'xep_0009', 'xep_0012', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050', 'xep_0060', 'xep_0066', 'xep_0082', 'xep_0085', 'xep_0086', 'xep_0092', 'xep_0128', 'xep_0199', 'xep_0203', 'xep_0224', 'xep_0249', 'gmail_notify'] # Don't automatically load xep_0078 jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0066/0000775000175000017500000000000011733126034021447 5ustar bartbart00000000000000jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0066/__init__.py0000664000175000017500000000054211733124163023562 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ from sleekxmpp.plugins.xep_0066 import stanza from sleekxmpp.plugins.xep_0066.stanza import OOB, OOBTransfer from sleekxmpp.plugins.xep_0066.oob import xep_0066 jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0066/oob.py0000664000175000017500000001235611733124163022610 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ import logging from sleekxmpp.stanza import Message, Presence, Iq from sleekxmpp.exceptions import XMPPError from sleekxmpp.xmlstream import register_stanza_plugin from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath from sleekxmpp.plugins.base import base_plugin from sleekxmpp.plugins.xep_0066 import stanza log = logging.getLogger(__name__) class xep_0066(base_plugin): """ XEP-0066: Out-of-Band Data Out-of-Band Data is a basic method for transferring files between XMPP agents. The URL of the resource in question is sent to the receiving entity, which then downloads the resource before responding to the OOB request. OOB is also used as a generic means to transmit URLs in other stanzas to indicate where to find additional information. Also see . Events: oob_transfer -- Raised when a request to download a resource has been received. Methods: send_oob -- Send a request to another entity to download a file or other addressable resource. """ def plugin_init(self): """Start the XEP-0066 plugin.""" self.xep = '0066' self.description = 'Out-of-Band Transfer' self.stanza = stanza self.url_handlers = {'global': self._default_handler, 'jid': {}} register_stanza_plugin(Iq, stanza.OOBTransfer) register_stanza_plugin(Message, stanza.OOB) register_stanza_plugin(Presence, stanza.OOB) self.xmpp.register_handler( Callback('OOB Transfer', StanzaPath('iq@type=set/oob_transfer'), self._handle_transfer)) def post_init(self): """Handle cross-plugin dependencies.""" base_plugin.post_init(self) self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace) self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace) def register_url_handler(self, jid=None, handler=None): """ Register a handler to process download requests, either for all JIDs or a single JID. Arguments: jid -- If None, then set the handler as a global default. handler -- If None, then remove the existing handler for the given JID, or reset the global handler if the JID is None. """ if jid is None: if handler is not None: self.url_handlers['global'] = handler else: self.url_handlers['global'] = self._default_handler else: if handler is not None: self.url_handlers['jid'][jid] = handler else: del self.url_handlers['jid'][jid] def send_oob(self, to, url, desc=None, ifrom=None, **iqargs): """ Initiate a basic file transfer by sending the URL of a file or other resource. Arguments: url -- The URL of the resource to transfer. desc -- An optional human readable description of the item that is to be transferred. ifrom -- Specifiy the sender's JID. block -- If true, block and wait for the stanzas' reply. timeout -- The time in seconds to block while waiting for a reply. If None, then wait indefinitely. callback -- Optional callback to execute when a reply is received instead of blocking and waiting for the reply. """ iq = self.xmpp.Iq() iq['type'] = 'set' iq['to'] = to iq['from'] = ifrom iq['oob_transfer']['url'] = url iq['oob_transfer']['desc'] = desc return iq.send(**iqargs) def _run_url_handler(self, iq): """ Execute the appropriate handler for a transfer request. Arguments: iq -- The Iq stanza containing the OOB transfer request. """ if iq['to'] in self.url_handlers['jid']: return self.url_handlers['jid'][jid](iq) else: if self.url_handlers['global']: self.url_handlers['global'](iq) else: raise XMPPError('service-unavailable') def _default_handler(self, iq): """ As a safe default, don't actually download files. Register a new handler using self.register_url_handler to screen requests and download files. Arguments: iq -- The Iq stanza containing the OOB transfer request. """ raise XMPPError('service-unavailable') def _handle_transfer(self, iq): """ Handle receiving an out-of-band transfer request. Arguments: iq -- An Iq stanza containing an OOB transfer request. """ log.debug('Received out-of-band data request for %s from %s:' % ( iq['oob_transfer']['url'], iq['from'])) self._run_url_handler(iq) iq.reply().send() jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0066/stanza.py0000664000175000017500000000121011733124163023314 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ from sleekxmpp.xmlstream import ElementBase class OOBTransfer(ElementBase): """ """ name = 'query' namespace = 'jabber:iq:oob' plugin_attrib = 'oob_transfer' interfaces = set(('url', 'desc', 'sid')) sub_interfaces = set(('url', 'desc')) class OOB(ElementBase): """ """ name = 'x' namespace = 'jabber:x:oob' plugin_attrib = 'oob' interfaces = set(('url', 'desc')) sub_interfaces = interfaces jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0202/0000775000175000017500000000000011733126034021437 5ustar bartbart00000000000000jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0202/__init__.py0000664000175000017500000000053611733124163023555 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. See the file LICENSE for copying permission. """ from sleekxmpp.plugins.xep_0202 import stanza from sleekxmpp.plugins.xep_0202.stanza import EntityTime from sleekxmpp.plugins.xep_0202.time import xep_0202 jsb-0.84.4/jsb/contrib/sleekxmpp/plugins/xep_0202/stanza.py0000664000175000017500000000715111733124163023316 0ustar bartbart00000000000000""" SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. See the file LICENSE for copying permission. """ import logging import datetime as dt from sleekxmpp.xmlstream import ElementBase from sleekxmpp.plugins import xep_0082 from sleekxmpp.thirdparty import tzutc, tzoffset class EntityTime(ElementBase): """ The