.. _fbf.lib.eventbase: eventbase ~~~~~~~~~ .. automodule:: fbf.lib.eventbase :show-inheritance: :members: :undoc-members: CODE ---- :: # fbf/lib/eventbase.py # # """ base class of all events. """ .. _fbf.lib.eventbase_fbf_imports: fbf imports -------------- :: from .channelbase import ChannelBase from fbf.utils.lazydict import LazyDict from fbf.utils.generic import splittxt, stripped, waitforqueue from .errors import NoSuchUser, NoSuchCommand, RequireError from fbf.utils.opts import makeeventopts from fbf.utils.trace import whichmodule, whichplugin from fbf.utils.exception import handle_exception from fbf.utils.locking import lockdec from fbf.lib.config import Config, getmainconfig from fbf.lib.users import getusers from fbf.lib.commands import cmnds from fbf.lib.floodcontrol import floodcontrol from fbf.drivers.sleek.namespace import attributes, subelements .. _fbf.lib.eventbase_basic_imports: basic imports ---------------- :: from collections import deque from xml.sax.saxutils import unescape, escape import copy import logging import queue import types import socket import threading import time import _thread import urllib.request, urllib.parse, urllib.error import uuid .. _fbf.lib.eventbase_defines_: defines ---------- :: cpy = copy.deepcopy lock = _thread.allocate_lock() locked = lockdec(lock) .. _fbf.lib.eventbase_classes_: 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 = 5 self.nrout = 0 self.nodispatch = True self.isready = threading.Event() self.dontbind = self.bot and self.bot.cfg and self.bot.cfg.strict or False 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() self.setupdone = True 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.threaded and self.untildone: return 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.isready.set() self.finished.release() if not "TICK" in self.cbtype: logging.info("notified %s" % str(self)) def execwait(self, direct=False): from fbf.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() if self.threaded and self.untildone: logging.info("waiting until done") while 1: self.finished.wait(0.1) else: 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) join = wait 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 fbf.lib.commands import cmnds res = self if not self.setupdone: self.setup() 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 as ex: logging.error(str(ex)) except NoSuchCommand as ex: logging.error("we don't have a %s command" % str(ex)) ; self.ready() except NoSuchUser as ex: logging.error("we don't have user for %s" % str(ex)) except Exception as 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[0] in self.getcc(): t = ";" + t e = self.bot.make_event(self.userhost, self.channel, t) if self.sock: e.sock = self.sock 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 direct: self.bot.put(events[i]) else: events[i].execute(direct) self.ready() 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: 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) if not target: self.prepare(bot) ; 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 self.prepare(bot) 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. """ self.bot = bot or self.bot self.usercmnd = self.iscmnd() if self.usercmnd: self.execstr = self.usercmnd self.usercmnd = self.usercmnd.split()[0] self.nodispatch = False self.iscommand = True def reply(self, txt, result=[], event=None, origin="", dot=", ", 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, silent=False): """ tell the user we are done. """ if not silent: self.reply('done - %s' % (self.usercmnd or self.alias or self.txt), event=self) self.ready() 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" % str(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=", ", *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 = str(self.bot.cfg.nick + ":") if self.txt.startswith(matchnick): return self.txt[len(matchnick):] matchnick = str(self.bot.cfg.nick + ",") 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.debug("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) 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 += " %s='%s'" % (attribute, escape(res[attribute])) continue main += ">" if "xmlns" in res: main += "" % res["xmlns"] ; gotsub = True else: gotsub = False if 'html' in res: if res['html']: main += '%s' % res['html'] gotsub = True if 'txt' in res: if res['txt']: main += "%s" % escape(res['txt']) gotsub = True for subelement in subelements[elem]: if subelement == "body": continue if subelement == "thread": continue if subelement == "error": continue try: data = res[subelement] if data: try: main += "<%s>%s" % (subelement, escape(data), subelement) gotsub = True except AttributeError as ex: logging.warn("skipping %s" % subelement) except KeyError: pass if gotsub: main += "" % elem else: main = main[:-1] main += " />" return main def finish(self): if self.handler: self.handler.finish()