gozerbot.xmpp.bot

jabber bot definition

class gozerbot.xmpp.bot.Bot(name, cfg={})

Bases: gozerbot.xmpp.core.XMLStream, gozerbot.botbase.BotBase

xmpp bot class.

action(printto, txt, fromm=None, groupchat=True)

send an action.

auth(jid, password, digest='')

auth against the xmpp server.

broadcast(txt)

broadcast txt to all joined channels.

connect(reconnect=True)

connect the xmpp server.

disconnectHandler(ex)

disconnect handler.

domsg(msg)

dispatch an msg on the bot.

errorHandler(event)

error handler .. calls the errorhandler set in the event.

exit()

exit the bot.

gettopic(channel)

get topic.

handle_iq(data)

iq handler .. overload this when needed.

handle_message(data)

message handler.

handle_presence(data)

presence handler.

initstream()

send initial string sequence to the xmpp server.

join(channel, password=None, nick='gozerbot')

join conference.

joinchannels()

join all already joined channels.

logon(user, password)

log on the xmpp server.

outputnolog(printto, what, how, who=None, fromm=None)

do output but don’t log it.

part(channel)

leave conference.

quit()

send unavailable presence.

reconnect()

reconnect to the server.

register(jid, password)

register the jid to the server.

requestroster()

request roster from xmpp server.

save()

save bot’s state.

say(printto, txt, fromm=None, groupchat=True, speed=5, type='normal', how='')

say txt to channel/JID.

saynocb(printto, txt, fromm=None, groupchat=True, speed=5, type='normal', how='')

say txt to channel/JID without calling callbacks/monitors.

send(what)

convert dict to stanza and send it to the server.

sendnocb(what)

send to server without calling callbacks/monitors.

sendpresence()

send presence based on status and status text set by user.

sendraw(data)

compat function.

settopic(channel, txt)

set topic.

topiccheck(msg)

check if topic is set.

userwait(msg, txt)

wait for user response.

CODE

# gozerbot/xmpp/bot.py
#
#

""" jabber bot definition """

__status__ = "seen"

gozerbot imports

from gozerbot.users import users
from gozerbot.utils.log import rlog
from gozerbot.utils.exception import handle_exception
from gozerbot.utils.trace import whichmodule
from gozerbot.utils.locking import lockdec
from gozerbot.utils.generic import waitforqueue, toenc, fromenc, jabberstrip, getrandomnick
from gozerbot.config import config
from gozerbot.persist.pdod import Pdod
from gozerbot.utils.dol import Dol
from gozerbot.datadir import datadir
from gozerbot.channels import Channels
from gozerbot.less import Less
from gozerbot.ignore import shouldignore
from gozerbot.callbacks import jcallbacks
from gozerbot.threads.thr import start_new_thread
from gozerbot.fleet import fleet
from gozerbot.botbase import BotBase

xmpp imports

from presence import Presence
from message import Message
from iq import Iq
from core import XMLStream
from monitor import xmppmonitor
from wait import XMPPWait, XMPPErrorWait
from jid import JID, InvalidJID

basic imports

import socket
import logging
import time
import Queue
import os
import threading
import thread
import types
import xml
import re
import hashlib

locks

outlock = thread.allocate_lock()
inlock = thread.allocate_lock()
connectlock = thread.allocate_lock()
outlocked = lockdec(outlock)
inlocked = lockdec(inlock)
connectlocked = lockdec(connectlock)

Bot class

class Bot(XMLStream, BotBase):

    """ xmpp bot class. """

    def __init__(self, name, cfg={}):
        BotBase.__init__(self, name, cfg)
        if not self.port: self.port = 5222
        if not self.host: raise Exception("host not set in %s xmpp bot" % self.name)
        self.username = self.user.split('@')[0]
        XMLStream.__init__(self, self.host, self.port, self.name)
        self.type = 'xmpp'
        self.outqueue = Queue.Queue()
        self.sock = None
        self.me = self.user
        self.jid = self.me
        self.lastin = None
        self.test = 0
        self.connecttime = 0
        self.connection = None
        self.privwait = XMPPWait()
        self.errorwait = XMPPErrorWait()
        self.monitor = xmppmonitor
        self.jabber = True
        self.connectok = threading.Event()
        self.jids = {}
        self.topics = {}
        self.timejoined = {}
        self.channels409 = []
        if not self.state.has_key('ratelimit'): self.state['ratelimit'] = 0.05
        if self.port == 0: self.port = 5222
        self.monitor.start()

    def _resumedata(self):
        """ return data needed for resuming. """
        return {self.name: [self.server, self.user, self.password, self.port]}

    def _outputloop(self):
        """ loop to take care of output to the server. """
        rlog(1, self.name, 'starting outputloop')
        lastsend = time.time()
        charssend = 0
        while not self.stopped:
            time.sleep(0.01)
            what = self.outqueue.get()
            if self.stopped or what == None: break
            if charssend + len(what) < 1000:
                try: self._raw(what)
                except Exception, ex:
                    self.error = str(ex)
                    handle_exception
                    continue
                lastsend = time.time()
                charssend += len(what)
            else:
                if time.time() - lastsend > 1:
                    try: self._raw(what)
                    except Exception, ex: handle_exception() ; continue
                    lastsend = time.time()
                    charssend = len(what)
                    continue
                charssend = 0
                sleeptime = self.cfg['jabberoutsleep'] or config['jabberoutsleep']
                if not sleeptime: sleeptime = 1
                rlog(10, self.name, 'jabberoutsleep .. sleeping %s seconds' % sleeptime)
                time.sleep(sleeptime)
                try: self._raw(what)
                except Exception, ex: handle_exception()
        rlog(1, self.name, 'stopping outputloop .. %s' % (self.error or 'no error set'))

    def _keepalive(self):
        """ keepalive method .. send empty string to self every 3 minutes. """
        nrsec = 0
        while not self.stopped:
            time.sleep(1)
            nrsec += 1
            if nrsec < 180: continue
            else: nrsec = 0
            self.sendpresence()

    def sendpresence(self):
        """ send presence based on status and status text set by user. """
        if self.state.has_key('status') and self.state['status']: status = self.state['status']
        else: status = ""
        if self.state.has_key('show') and self.state['show']: show = self.state['show']
        else: show = ""
        rlog(4, self.name, 'keepalive: %s - %s' % (show, status))
        if show and status: p = Presence({'to': self.me, 'show': show, 'status': status}, self)
        elif show: p = Presence({'to': self.me, 'show': show }, self)
        elif status: p = Presence({'to': self.me, 'status': status}, self)
        else: p = Presence({'to': self.me }, self)
        self.send(p)

    def _keepchannelsalive(self):
        """ channels keep alive method. """
        nrsec = 0
        p = Presence({'to': self.me, 'txt': '' }, self)
        while not self.stopped:
            time.sleep(1)
            nrsec += 1
            if nrsec < 600: continue
            else: nrsec = 0
            for chan in self.state['joinedchannels']:
                if chan not in self.channels409:
                    p = Presence({'to': chan}, self)
                    self.send(p)

    def connect(self, reconnect=True):
        """ connect the xmpp server. """

        if self.stopped:
            rlog(100, self.name, 'bot is stopped .. not connecting')
            return
        try:
            if not XMLStream.connect(self):
                rlog(10, self.name, 'connect to %s:%s failed' % (self.host, self.port))
                return
            else: rlog(10, self.name, 'connected')
            self.logon(self.user, self.password)
            start_new_thread(self._outputloop, ())
            start_new_thread(self._keepalive, ())
            #start_new_thread(self._keepchannelsalive, ())
            self.requestroster()
            self._raw("<presence/>")
            self.connectok.set()
            self.sock.settimeout(None)
            return True
        except socket.error, ex: rlog(10, 'xmpp', str(ex))
        except Exception, ex: handle_exception()
        if reconnect: self.reconnect()

    def logon(self, user, password):
        """ log on the xmpp server. """
        iq = self.initstream()
        if not self.auth(user, password, iq.id):
            self.exit()
            rlog(1000, 'xmpp', 'TAKE NOTE !! register required, please register an account for %s on the %s server' % (user, self.host))
            #if self.register(user, password): time.sleep(5) ; self.auth(user, password)
            return
        XMLStream.logon(self)

    def initstream(self):
        """ send initial string sequence to the xmpp server. """
        rlog(10, self.name, 'starting initial stream sequence')
        self._raw("""<stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams'>""" % (self.user.split('@')[1], ))
        result = self.connection.read()
        iq = self.loop_one(result)
        rlog(3, self.name, "initstream: %s" % result)
        return iq

    def register(self, jid, password):
        """ register the jid to the server. """
        try: resource = jid.split("/")[1]
        except IndexError: resource = "gozerbot"
        rlog(10, self.name, 'registering %s' % jid)
        self._raw("""<iq type='get'><query xmlns='jabber:iq:register'/><username>%s</username></iq>""" % jid.split("@")[0])
        #self._raw("""<iq type='get' id='reg1' />""")
        result = self.connection.read()
        rlog(10, self.name, 'register: %s' % result)
        iq = self.loop_one(result)
        if not iq: rlog(10, self.name, "unable to register") ; return
        rlog(3, self.name, 'register: %s' % str(iq))
        self._raw("""<iq type='set'><query xmlns='jabber:iq:register'><username>%s</username><resource>%s</resource><password>%s</password></query></iq>""" % (jid.split('@')[0], resource, password))
        result = self.connection.read()
        rlog(3, self.name, 'register: %s' % result)
        if not result: return False
        iq = self.loop_one(result)
        if not iq: rlog(10, self.name, "can't decode data: %s" % result) ; return False
        rlog(3, self.name, 'register: %s' % result)
        if iq.error:
            rlog(10, self.name, 'REGISTER FAILED: %s' % iq.error)
            if iq.error.code == "405":
                rlog(10, self.name, "this server doesn't allow registration by the bot, you need to create an account for it yourself")
            else: rlog(10, self.name, result)
            self.error = iq.error
            return False
        rlog(10, self.name, 'register ok')
        return True

    def auth(self, jid, password, digest=""):
        """ auth against the xmpp server. """
        rlog(10, self.name, 'authing %s' % jid)
        name = jid.split('@')[0]
        rsrc = self.cfg['resource'] or config['resource'] or 'gozerbot';
        self._raw("""<iq type='get'><query xmlns='jabber:iq:auth'><username>%s</username></query></iq>""" % name)
        result = self.connection.read()
        iq = self.loop_one(result)
        rlog(3, self.name, 'auth:' + result)
        if ('digest' in result) and digest:
            s = hashlib.new('SHA1')
            s.update(digest)
            s.update(password)
            d = s.hexdigest()
            self._raw("""<iq type='set'><query xmlns='jabber:iq:auth'><username>%s</username><digest>%s</digest><resource>%s</resource></query></iq>""" % (name, d, rsrc))
        else:
            self._raw("""<iq type='set'><query xmlns='jabber:iq:auth'><username>%s</username><resource>%s</resource><password>%s</password></query></iq>""" % (name, rsrc, password))
        result = self.connection.read()
        iq = self.loop_one(result)
        if not iq: rlog(10, self.name, 'auth failed: %s' % result) ; return False
        rlog(3, self.name, 'auth: ' + result)
        if iq.error:
            rlog(10, self.name, 'AUTH FAILED: %s' % iq.error)
            if iq.error.code == "401": rlog(10, self.name, "wrong user or password (or user not known)")
            else: rlog(10, self.name, result)
            self.error = iq.error
            return False
        rlog(10, self.name, 'auth ok')
        return True

    def requestroster(self):
        """ request roster from xmpp server. """
        self._raw("<iq type='get'><query xmlns='jabber:iq:roster'/></iq>")

    def disconnectHandler(self, ex):
        """ disconnect handler. """
        rlog(100, self.name, "disconnected: %s" % str(ex))
        if not self.stopped: self.reconnect()
        else: self.exit()

    def joinchannels(self):
        """ join all already joined channels. """

        for i in self.state['joinedchannels']:
            key = self.channels.getkey(i)
            nick = self.channels.getnick(i)
            result = self.join(i, key, nick)
            if result == 1: rlog(10, self.name, 'joined %s' % i)
            else: rlog(10, self.name, 'failed to join %s: %s' % (i, result))

    def broadcast(self, txt):
        """ broadcast txt to all joined channels. """
        for i in self.state['joinedchannels']: self.say(i, txt)

    def handle_iq(self, data):
        """ iq handler .. overload this when needed. """
        pass

    def handle_message(self, data):
        """ message handler. """
        if self.test: return
        m = Message(data, self)
        if data.type == 'groupchat' and data.subject:
            self.topiccheck(m)
            nm = Message(m, bot=self)
            jcallbacks.check(self, nm)
            return
        if data.get('x').xmlns == 'jabber:x:delay':
            rlog(5, self.name, "ignoring delayed message")
            return
        self.privwait.check(m)
        if m.isresponse: return
        if m.groupchat: m.msg = False
        jid = None
        m.origjid = m.jid
        for node in m.subelements:
            try: m.jid = node.x.item.jid
            except (AttributeError, TypeError): continue
        if self.me in m.fromm: return 0
        go = 0
        try: cc = self.channels[m.channel]['cc']
        except (TypeError, KeyError): cc = config['defaultcc'] or '!'
        try: channick = self.channels[m.channel]['nick']
        except (TypeError, KeyError): channick = self.nick
        if m.msg and not config['noccinmsg']: go = 1
        if not m.txt: go = 0
        elif m.txt[0] in cc: go = 1
        elif m.txt.startswith("%s: " % channick):
            m.txt = m.txt.replace("%s: " % channick, "")
            go = 1
        elif m.txt.startswith("%s, " % channick):
            m.txt = m.txt.replace("%s, " % channick, "")
            go = 1
        if m.txt and m.txt[0] in cc: m.txt = m.txt[1:]
        if go:
            try:
                from gozerbot.plugins import plugins
                if plugins.woulddispatch(self, m): m.usercmnd = True
                plugins.trydispatch(self, m)
            except: handle_exception()
        try:
            if m.type == 'error':
                if m.code: rlog(10, self.name + '.error', str(m))
                self.errorwait.check(m)
                self.errorHandler(m)
        except Exception, ex: handle_exception()
        mm = Message(m, self)
        jcallbacks.check(self, mm)

    def errorHandler(self, event):
        """ error handler .. calls the errorhandler set in the event. """
        try: event.errorHandler()
        except AttributeError: rlog(10, self.name, 'unhandled error: %s' % event)

    def handle_presence(self, data):
        """ presence handler. """
        p = Presence(data, self)
        frm = p.fromm
        nickk = ""
        nick = p.nick
        if self.me in p.userhost: return 0
        if nick:
            self.userhosts.data[nick] = str(frm)
            nickk = nick
        jid = None
        for node in p.subelements:
            try: jid = node.x.item.jid
            except (AttributeError, TypeError): continue
        if nickk and jid:
            channel = p.channel
            if not self.jids.has_key(channel): self.jids[channel] = {}
            self.jids[channel][nickk] = jid
            self.userhosts.data[nickk] = str(jid)
            rlog(0, self.name, 'setting jid of %s (%s) to %s' % (nickk, channel, jid))
        if p.type == 'subscribe':
            pres = Presence({'to': p.fromm, 'type': 'subscribed'}, self)
            self.send(pres)
            pres = Presence({'to': p.fromm, 'type': 'subscribe'}, self)
            self.send(pres)
        nick = p.resource
        if p.type != 'unavailable':
            self.userchannels.adduniq(nick, p.channel)
            p.joined = True
            p.type = 'available'
        elif self.me in p.userhost:
            try:
                del self.jids[p.channel]
                rlog(0, self.name, 'removed %s channel jids' % p.channel)
            except KeyError: pass
        else:
            try:
                del self.jids[p.channel][p.nick]
                rlog(0, self.name, 'removed %s jid' % p.nick)
            except KeyError: pass
        pp = Presence(p, self)
        jcallbacks.check(self, pp)
        if p.type == 'error':
            for node in p.subelements:
                try: err = node.error.code
                except (AttributeError, TypeError): err = 'no error set'
                try: txt = node.text.data
                except (AttributeError, TypeError): txt = ""
            if err: rlog(10, self.name + '.error', "%s: %s"  % (err, txt))
            self.errorwait.check(p)
            try:
                method = getattr(self,'handle_' + err)
                try: method(p)
                except: handle_exception()
            except AttributeError: pass

    def reconnect(self):
        """ reconnect to the server. """
        if self.stopped:
            rlog(100, self.name, 'bot is stopped .. not reconnecting')
            return
        rlog(100, self.name, 'reconnecting .. sleeping 15 seconds')
        self.exit()
        time.sleep(15)
        newbot = Bot(self.name, self.cfg)
        if newbot.connect():
            self.name += '.old'
            newbot.joinchannels()
            fleet.replace(self.name, newbot)
            return True
        return False


    def send(self, what):
        """ convert dict to stanza and send it to the server. """
        try: to = what['to']
        except (KeyError, TypeError):
            rlog(10, self.name, "can't determine where to send %s to" % what)
            return
        try: jid = JID(to)
        except (InvalidJID, AttributeError):
            rlog(10, self.name, "invalid jid %s .. %s" % (str(to), str(what)))
            return
        try: del what['from']
        except KeyError: pass
        try:
            xml = what.toxml()
            if not xml: raise Exception("can't convert %s to xml .. bot.send()" % what)
        except (AttributeError, TypeError): handle_exception() ; return
        self.outqueue.put(toenc(xml))
        self.monitor.put(self, what)

    def sendnocb(self, what):
        """ send to server without calling callbacks/monitors. """
        try: xml = what.toxml()
        except AttributeError: xml = what
        self.outqueue.put(toenc(xml))

    def action(self, printto, txt, fromm=None, groupchat=True):
        """ send an action. """
        txt = "/me " + txt
        if self.google: fromm = self.me
        if printto in self.state['joinedchannels'] and groupchat:
            message = Message({'to': printto, 'txt': txt, 'type': 'groupchat'}, self)
        else:
            message = Message({'to': printto, 'txt': txt}, self)
        if fromm: message.fromm = fromm
        self.send(message)

    def say(self, printto, txt, fromm=None, groupchat=True, speed=5, type="normal", how=''):
        """ say txt to channel/JID. """
        txt = jabberstrip(txt)
        if self.google: fromm = self.me
        if printto in self.state['joinedchannels'] and groupchat:
            message = Message({'to': printto, 'body': txt, 'type': 'groupchat'}, self)
        else:
            message = Message({'to': printto, 'body': txt, 'type': 'chat'}, self)
        if fromm: message.fromm = fromm
        else: message.fromm = self.me
        self.send(message)

    def saynocb(self, printto, txt, fromm=None, groupchat=True, speed=5, type="normal", how=''):
        """ say txt to channel/JID without calling callbacks/monitors. """
        txt = jabberstrip(txt)
        if printto in self.state['joinedchannels'] and groupchat:
            message = Message({'to': printto, 'body': txt, 'type': 'groupchat'}, self)
        else:
            message = Message({'to': printto, 'body': txt}, self)
        if fromm: message.fromm = fromm
        else: message.fromm = self.me
        self.send(message)

    def userwait(self, msg, txt):
        """ wait for user response. """
        msg.reply(txt)
        queue = Queue.Queue()
        self.privwait.register(msg, queue)
        result = queue.get()
        if result: return result.txt

    def save(self):
        """ save bot's state. """
        self.state.save()

    def quit(self):
        """ send unavailable presence. """
        presence = Presence({'type': 'unavailable'}, self)
        for i in self.state['joinedchannels']:
            presence.to = i
            self.send(presence)
        presence = Presence({'type': 'unavailable'}, self)
        presence['from'] = self.me
        self.send(presence)
        time.sleep(1)

    def exit(self):
        """ exit the bot. """
        self.quit()
        self.stopped = 1
        self.outqueue.put_nowait(None)
        self.save()
        time.sleep(3)
        rlog(10, self.name, 'exit')

    def join(self, channel, password=None, nick="gozerbot"):
        """ join conference. """
        if '#' in channel: return
        try:
            if not nick: nick = channel.split('/')[1]
        except IndexError: nick = self.nick
        channel = channel.split('/')[0]
        if not self.channels.has_key(channel): self.channels.setdefault(channel, {})
        q = Queue.Queue()
        self.errorwait.register("409", q, 3)
        self.errorwait.register("401", q, 3)
        self.errorwait.register("400", q, 3)
        presence = Presence({'to': channel + '/' + nick}, self)
        if password: presence.x.password = password
        self.send(presence)
        errorobj = waitforqueue(q, 3)
        if errorobj:
            err = errorobj[0].error
            rlog(100, self.name, 'error joining %s: %s' % (channel, err))
            if err >= '400':
                if channel not in self.channels409: self.channels409.append(channel)
            return err
        self.timejoined[channel] = time.time()
        chan = self.channels[channel]
        chan['nick'] = nick
        if password: chan['key'] = password
        if not chan.has_key('cc'): chan['cc'] = config['defaultcc'] or '!'
        if not chan.has_key('perms'): chan['perms'] = []
        self.channels.save()
        if channel not in self.state['joinedchannels']: self.state['joinedchannels'].append(channel)
        if channel in self.channels409: self.channels409.remove(channel)
        self.state.save()
        return 1

    def part(self, channel):
        """ leave conference. """
        if '#' in channel: return
        presence = Presence({'to': channel}, self)
        presence.type = 'unavailable'
        self.send(presence)
        if channel in self.state['joinedchannels']: self.state['joinedchannels'].remove(channel)
        self.state.save()
        return 1

    def outputnolog(self, printto, what, how, who=None, fromm=None):
        """ do output but don't log it. """
        if fromm and shouldignore(fromm): return
        self.saynocb(printto, what)

    def topiccheck(self, msg):
        """ check if topic is set. """
        if msg.groupchat:
            try:
                topic = msg.subject
                if not topic: return None
                self.topics[msg.channel] = (topic, msg.userhost, time.time())
                rlog(0, self.name, 'topic of %s set to %s' % (msg.channel, topic))
            except AttributeError: return None

    def settopic(self, channel, txt):
        """ set topic. """
        pres = Message({'to': channel, 'subject': txt}, self)
        pres.type = 'groupchat'
        self.send(pres)

    def gettopic(self, channel):
        """ get topic. """
        try:
            topic = self.topics[channel]
            return topic
        except KeyError: return None

    def domsg(self, msg):
        """ dispatch an msg on the bot. """
        from gozerbot.plugins import plugins
        plugins.trydispatch(self, msg)

    def sendraw(self, data):
        """ compat function. """
        self._raw(data)

Table Of Contents

Previous topic

gozerbot.xmpp

Next topic

gozerbot.xmpp.core

This Page