summaryrefslogtreecommitdiff
path: root/sleekxmpp/basexmpp.py
diff options
context:
space:
mode:
authorNathan Fritz <fritzy@netflint.net>2009-06-03 22:56:51 +0000
committerNathan Fritz <fritzy@netflint.net>2009-06-03 22:56:51 +0000
commit96b103b27599e5af247c1e3b95d62c80c1e32a63 (patch)
tree0527b1607b16adb020759ee9a944e1b22e3e0e6b /sleekxmpp/basexmpp.py
downloadslixmpp-96b103b27599e5af247c1e3b95d62c80c1e32a63.tar.gz
slixmpp-96b103b27599e5af247c1e3b95d62c80c1e32a63.tar.bz2
slixmpp-96b103b27599e5af247c1e3b95d62c80c1e32a63.tar.xz
slixmpp-96b103b27599e5af247c1e3b95d62c80c1e32a63.zip
moved seesmic branch to trunk
Diffstat (limited to 'sleekxmpp/basexmpp.py')
-rw-r--r--sleekxmpp/basexmpp.py422
1 files changed, 422 insertions, 0 deletions
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
new file mode 100644
index 00000000..ae2b063f
--- /dev/null
+++ b/sleekxmpp/basexmpp.py
@@ -0,0 +1,422 @@
+"""
+ SleekXMPP: The Sleek XMPP Library
+ Copyright (C) 2007 Nathanael C. Fritz
+ This file is part of SleekXMPP.
+
+ SleekXMPP is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ SleekXMPP is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SleekXMPP; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+"""
+from __future__ import with_statement
+from xml.etree import cElementTree as ET
+from . xmlstream.xmlstream import XMLStream
+from . xmlstream.matcher.xmlmask import MatchXMLMask
+from . xmlstream.matcher.many import MatchMany
+from . xmlstream.handler.xmlcallback import XMLCallback
+from . xmlstream.handler.xmlwaiter import XMLWaiter
+from . xmlstream.handler.callback import Callback
+from . import plugins
+
+import logging
+import threading
+
+class basexmpp(object):
+ def __init__(self):
+ self.id = 0
+ self.id_lock = threading.Lock()
+ self.stanza_errors = {
+ 'bad-request':False,
+ 'conflict':False,
+ 'feature-not-implemented':False,
+ 'forbidden':False,
+ 'gone':True,
+ 'internal-server-error':False,
+ 'item-not-found':False,
+ 'jid-malformed':False,
+ 'not-acceptable':False,
+ 'not-allowed':False,
+ 'payment-required':False,
+ 'recipient-unavailable':False,
+ 'redirect':True,
+ 'registration-required':False,
+ 'remote-server-not-found':False,
+ 'remote-server-timeout':False,
+ 'resource-constraint':False,
+ 'service-unavailable':False,
+ 'subscription-required':False,
+ 'undefined-condition':False,
+ 'unexpected-request':False}
+ self.stream_errors = {
+ 'bad-format':False,
+ 'bad-namespace-prefix':False,
+ 'conflict':False,
+ 'connection-timeout':False,
+ 'host-gone':False,
+ 'host-unknown':False,
+ 'improper-addressing':False,
+ 'internal-server-error':False,
+ 'invalid-from':False,
+ 'invalid-id':False,
+ 'invalid-namespace':False,
+ 'invalid-xml':False,
+ 'not-authorized':False,
+ 'policy-violation':False,
+ 'remote-connection-failed':False,
+ 'resource-constraint':False,
+ 'restricted-xml':False,
+ 'see-other-host':True,
+ 'system-shutdown':False,
+ 'undefined-condition':False,
+ 'unsupported-encoding':False,
+ 'unsupported-stanza-type':False,
+ 'unsupported-version':False,
+ 'xml-not-well-formed':False}
+ self.sentpresence = False
+ self.fulljid = ''
+ self.resource = ''
+ self.jid = ''
+ self.username = ''
+ self.server = ''
+ self.plugin = {}
+ self.auto_authorize = True
+ self.auto_subscribe = True
+ self.event_handlers = {}
+ self.roster = {}
+ self.registerHandler(Callback('IM', MatchMany((MatchXMLMask("<message xmlns='%s' type='chat'><body /></message>" % self.default_ns),MatchXMLMask("<message xmlns='%s' type='normal'><body /></message>" % self.default_ns),MatchXMLMask("<message xmlns='%s' type='__None__'><body /></message>" % self.default_ns))), self._handleMessage))
+ self.registerHandler(Callback('Presence', MatchMany((MatchXMLMask("<presence xmlns='%s' type='available'/>" % self.default_ns),MatchXMLMask("<presence xmlns='%s' type='__None__'/>" % self.default_ns),MatchXMLMask("<presence xmlns='%s' type='unavailable'/>" % self.default_ns))), self._handlePresence))
+ self.registerHandler(Callback('PresenceSubscribe', MatchMany((MatchXMLMask("<presence xmlns='%s' type='subscribe'/>" % self.default_ns),MatchXMLMask("<presence xmlns='%s' type='unsubscribed'/>" % self.default_ns))), self._handlePresenceSubscribe))
+
+ def set_jid(self, jid):
+ """Rip a JID apart and claim it as our own."""
+ self.fulljid = jid
+ self.resource = self.getjidresource(jid)
+ self.jid = self.getjidbare(jid)
+ self.username = jid.split('@', 1)[0]
+ self.server = jid.split('@',1)[-1].split('/', 1)[0]
+
+ def registerPlugin(self, plugin, pconfig = {}):
+ """Register a plugin not in plugins.__init__.__all__ but in the plugins
+ directory."""
+ # discover relative "path" to the plugins module from the main app, and import it.
+ __import__("%s.%s" % (globals()['plugins'].__name__, plugin))
+ # init the plugin class
+ self.plugin[plugin] = getattr(getattr(plugins, plugin), plugin)(self, pconfig) # eek
+ # all of this for a nice debug? sure.
+ xep = ''
+ if hasattr(self.plugin[plugin], 'xep'):
+ xep = "(XEP-%s) " % self.plugin[plugin].xep
+ logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description))
+
+ def register_plugins(self):
+ """Initiates all plugins in the plugins/__init__.__all__"""
+ if self.plugin_whitelist:
+ plugin_list = self.plugin_whitelist
+ else:
+ plugin_list = plugins.__all__
+ for plugin in plugin_list:
+ if plugin in plugins.__all__:
+ self.registerPlugin(plugin, self.plugin_config.get(plugin, {}))
+ else:
+ raise NameError("No plugin by the name of %s listed in plugins.__all__." % plugin)
+ # run post_init() for cross-plugin interaction
+ for plugin in self.plugin:
+ self.plugin[plugin].post_init()
+
+ def getNewId(self):
+ with self.id_lock:
+ self.id += 1
+ return self.getId()
+
+ def add_handler(self, mask, pointer, disposable=False, threaded=False, filter=False):
+ logging.warning("Deprecated add_handler used for %s: %s." % (mask, pointer))
+ self.registerHandler(XMLCallback('add_handler_%s' % self.getNewId(), MatchXMLMask(mask), pointer, threaded, disposable))
+
+ def getId(self):
+ return "%x".upper() % self.id
+
+ def send(self, data, mask=None, timeout=60):
+ #logging.warning("Deprecated send used for \"%s\"" % (data,))
+ if not type(data) == type(''):
+ data = self.tostring(data)
+ if mask is not None:
+ waitfor = XMLWaiter('SendWait_%s' % self.getNewId(), MatchXMLMask(mask))
+ self.registerHandler(waitfor)
+ self.sendRaw(data)
+ if mask is not None:
+ return waitfor.wait(timeout)
+
+ def makeIq(self, id=0, ifrom=None):
+ iq = ET.Element('{%s}iq' % self.default_ns)
+ if id == 0:
+ id = self.getNewId()
+ iq.set('id', str(id))
+ if ifrom is not None:
+ iq.attrib['from'] = ifrom
+ return iq
+
+ def makeIqGet(self, queryxmlns = None):
+ iq = self.makeIq()
+ iq.set('type', 'get')
+ if queryxmlns:
+ query = ET.Element("{%s}query" % queryxmlns)
+ iq.append(query)
+ return iq
+
+ def makeIqResult(self, id):
+ iq = self.makeIq(id)
+ iq.set('type', 'result')
+ return iq
+
+ def makeIqSet(self, sub=None):
+ iq = self.makeIq()
+ iq.set('type', 'set')
+ if sub != None:
+ iq.append(sub)
+ return iq
+
+ def makeIqError(self, id):
+ iq = self.makeIq(id)
+ iq.set('type', 'error')
+ return iq
+
+ def makeStanzaErrorCondition(self, condition, cdata=None):
+ if condition not in self.stanza_errors:
+ raise ValueError()
+ stanzaError = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}'+condition)
+ if cdata is not None:
+ if not self.stanza_errors[condition]:
+ raise ValueError()
+ stanzaError.text = cdata
+ return stanzaError
+
+
+ def makeStanzaError(self, condition, errorType, code=None, text=None, customElem=None):
+ if errorType not in ['auth', 'cancel', 'continue', 'modify', 'wait']:
+ raise ValueError()
+ error = ET.Element('error')
+ error.append(self.makeStanzaErrorCondition(condition))
+ error.set('type',errorType)
+ if code is not None:
+ error.set('code', code)
+ if text is not None:
+ textElem = ET.Element('text')
+ textElem.text = text
+ error.append(textElem)
+ if customElem is not None:
+ error.append(customElem)
+ return error
+
+ def makeStreamErrorCondition(self, condition, cdata=None):
+ if condition not in self.stream_errors:
+ raise ValueError()
+ streamError = ET.Element('{urn:ietf:params:xml:ns:xmpp-streams}'+condition)
+ if cdata is not None:
+ if not self.stream_errors[condition]:
+ raise ValueError()
+ textElem = ET.Element('text')
+ textElem.text = text
+ streamError.append(textElem)
+
+ def makeStreamError(self, errorElem, text=None):
+ error = ET.Element('error')
+ error.append(errorElem)
+ if text is not None:
+ textElem = ET.Element('text')
+ textElem.text = text
+ error.append(text)
+ return error
+
+ def makeIqQuery(self, iq, xmlns):
+ query = ET.Element("{%s}query" % xmlns)
+ iq.append(query)
+ return iq
+
+ def makeQueryRoster(self, iq=None):
+ query = ET.Element("{jabber:iq:roster}query")
+ if iq:
+ iq.append(query)
+ return query
+
+ def add_event_handler(self, name, pointer, threaded=False, disposable=False):
+ if not name in self.event_handlers:
+ self.event_handlers[name] = []
+ self.event_handlers[name].append((pointer, threaded, disposable))
+
+ def event(self, name, eventdata = {}): # called on an event
+ for handler in self.event_handlers.get(name, []):
+ if handler[1]: #if threaded
+ #thread.start_new(handler[0], (eventdata,))
+ x = threading.Thread(name="Event_%s" % str(handler[0]), target=handler[0], args=(eventdata,))
+ x.start()
+ else:
+ handler[0](eventdata)
+ if handler[2]: #disposable
+ with self.lock:
+ self.event_handlers[name].pop(self.event_handlers[name].index(handler))
+
+ def makeMessage(self, mto, mbody='', msubject=None, mtype=None, mhtml=None, mfrom=None):
+ message = ET.Element('{%s}message' % self.default_ns)
+ if mfrom is None:
+ message.attrib['from'] = self.fulljid
+ else:
+ message.attrib['from'] = mfrom
+ message.attrib['to'] = mto
+ if not mtype:
+ mtype='chat'
+ message.attrib['type'] = mtype
+ if mtype == 'none':
+ del message.attrib['type']
+ if mbody:
+ body = ET.Element('body')
+ body.text = mbody
+ message.append(body)
+ if mhtml :
+ html = ET.Element('{http://jabber.org/protocol/xhtml-im}html')
+ html_body = ET.XML('<body xmlns="http://www.w3.org/1999/xhtml">' + mhtml + '</body>')
+ html.append(html_body)
+ message.append(html)
+ if msubject:
+ subject = ET.Element('subject')
+ subject.text = msubject
+ message.append(subject)
+ return message
+
+ def makePresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None):
+ presence = ET.Element('{%s}presence' % self.default_ns)
+ if ptype:
+ presence.attrib['type'] = ptype
+ if pshow:
+ show = ET.Element('show')
+ show.text = pshow
+ presence.append(show)
+ if pstatus:
+ status = ET.Element('status')
+ status.text = pstatus
+ presence.append(status)
+ if ppriority:
+ priority = ET.Element('priority')
+ priority.text = str(ppriority)
+ presence.append(priority)
+ if pto:
+ presence.attrib['to'] = pto
+ if pfrom is None:
+ presence.attrib['from'] = self.fulljid
+ else:
+ presence.attrib['from'] = pfrom
+ return presence
+
+ def sendMessage(self, mto, mbody, msubject=None, mtype=None, mhtml=None, mfrom=None):
+ self.send(self.makeMessage(mto,mbody,msubject,mtype,mhtml,mfrom))
+
+ def sendPresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, pfrom=None):
+ self.send(self.makePresence(pshow,pstatus,ppriority,pto, pfrom=pfrom))
+ if not self.sentpresence:
+ self.event('sent_presence')
+ self.sentpresence = True
+
+ def sendPresenceSubscription(self, pto, pfrom=None, ptype='subscribe', pnick=None) :
+ presence = self.makePresence(ptype=ptype, pfrom=pfrom, pto=self.getjidbare(pto))
+ if pnick :
+ nick = ET.Element('{http://jabber.org/protocol/nick}nick')
+ nick.text = pnick
+ presence.append(nick)
+ self.send(presence)
+
+ def getjidresource(self, fulljid):
+ if '/' in fulljid:
+ return fulljid.split('/', 1)[-1]
+ else:
+ return ''
+
+ def getjidbare(self, fulljid):
+ return fulljid.split('/', 1)[0]
+
+ def _handleMessage(self, msg):
+ xml = msg.xml
+ mfrom = xml.attrib['from']
+ message = xml.find('{%s}body' % self.default_ns).text
+ subject = xml.find('{%s}subject' % self.default_ns)
+ if subject is not None:
+ subject = subject.text
+ else:
+ subject = ''
+ resource = self.getjidresource(mfrom)
+ mfrom = self.getjidbare(mfrom)
+ mtype = xml.attrib.get('type', 'normal')
+ name = self.roster.get('name', '')
+ self.event("message", {'jid': mfrom, 'resource': resource, 'name': name, 'type': mtype, 'subject': subject, 'message': message, 'to': xml.attrib.get('to', '')})
+
+ def _handlePresence(self, presence):
+ xml = presence.xml
+ """Update roster items based on presence"""
+ show = xml.find('{%s}show' % self.default_ns)
+ status = xml.find('{%s}status' % self.default_ns)
+ priority = xml.find('{%s}priority' % self.default_ns)
+ fulljid = xml.attrib['from']
+ to = xml.attrib['to']
+ resource = self.getjidresource(fulljid)
+ if not resource:
+ resouce = None
+ jid = self.getjidbare(fulljid)
+ if type(status) == type(None) or status.text is None:
+ status = ''
+ else:
+ status = status.text
+ if type(show) == type(None):
+ show = 'available'
+ else:
+ show = show.text
+ if xml.get('type', None) == 'unavailable':
+ show = 'unavailable'
+ if type(priority) == type(None):
+ priority = 0
+ else:
+ priority = int(priority.text)
+ wasoffline = False
+ oldroster = self.roster.get(jid, {}).get(resource, {})
+ if not jid in self.roster:
+ self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': False}
+ if not resource in self.roster[jid]['presence']:
+ wasoffline = True
+ self.roster[jid]['presence'][resource] = {'show': show, 'status': status, 'priority': priority}
+ else:
+ if self.roster[jid]['presence'][resource].get('show', None) == 'unavailable':
+ wasoffline = True
+ self.roster[jid]['presence'][resource] = {'show': show, 'status': status}
+ if priority:
+ self.roster[jid]['presence'][resource]['priority'] = priority
+ name = self.roster[jid].get('name', '')
+ eventdata = {'jid': jid, 'to': to, 'resource': resource, 'name': name, 'type': show, 'priority': priority, 'message': status}
+ if wasoffline and show in ('available', 'away', 'xa', 'na'):
+ self.event("got_online", eventdata)
+ elif not wasoffline and show == 'unavailable':
+ self.event("got_offline", eventdata)
+ elif oldroster != self.roster.get(jid, {'presence': {}})['presence'].get(resource, {}) and show != 'unavailable':
+ self.event("changed_status", eventdata)
+ name = ''
+ if name:
+ name = "(%s) " % name
+ logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, show,status))
+
+ def _handlePresenceSubscribe(self, presence):
+ """Handling subscriptions automatically."""
+ xml = presence.xml
+ if self.auto_authorize == True:
+ #self.updateRoster(self.getjidbare(xml.attrib['from']))
+ self.send(self.makePresence(ptype='subscribed', pto=self.getjidbare(xml.attrib['from'])))
+ if self.auto_subscribe:
+ self.send(self.makePresence(ptype='subscribe', pto=self.getjidbare(xml.attrib['from'])))
+ elif self.auto_authorize == False:
+ self.send(self.makePresence(ptype='unsubscribed', pto=self.getjidbare(xml.attrib['from'])))
+ elif self.auto_authorize == None:
+ pass