jabber bot definition
Bases: gozerbot.xmpp.core.XMLStream, gozerbot.botbase.BotBase
xmpp bot class.
send an action.
auth against the xmpp server.
broadcast txt to all joined channels.
connect the xmpp server.
disconnect handler.
dispatch an msg on the bot.
error handler .. calls the errorhandler set in the event.
exit the bot.
get topic.
iq handler .. overload this when needed.
message handler.
presence handler.
send initial string sequence to the xmpp server.
join conference.
join all already joined channels.
log on the xmpp server.
do output but don’t log it.
leave conference.
send unavailable presence.
reconnect to the server.
register the jid to the server.
request roster from xmpp server.
save bot’s state.
say txt to channel/JID.
say txt to channel/JID without calling callbacks/monitors.
convert dict to stanza and send it to the server.
send to server without calling callbacks/monitors.
send presence based on status and status text set by user.
compat function.
set topic.
check if topic is set.
wait for user response.
# gozerbot/xmpp/bot.py # # """ jabber bot definition """ __status__ = "seen"
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
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
import socket import logging import time import Queue import os import threading import thread import types import xml import re import hashlib
outlock = thread.allocate_lock() inlock = thread.allocate_lock() connectlock = thread.allocate_lock() outlocked = lockdec(outlock) inlocked = lockdec(inlock) connectlocked = lockdec(connectlock)
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)