summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Fritz <fritzy@netflint.net>2010-01-05 21:56:48 +0000
committerNathan Fritz <fritzy@netflint.net>2010-01-05 21:56:48 +0000
commit093644ffbd6708121150c92359bce60408f924bb (patch)
treea7d6da57ff76a8e54b1ec3703aeffeed82f34fa1
parent805afa4bc1f44598d786fddc92c5129c62464227 (diff)
downloadslixmpp-093644ffbd6708121150c92359bce60408f924bb.tar.gz
slixmpp-093644ffbd6708121150c92359bce60408f924bb.tar.bz2
slixmpp-093644ffbd6708121150c92359bce60408f924bb.tar.xz
slixmpp-093644ffbd6708121150c92359bce60408f924bb.zip
* major stanza improvements
* raise XMPPError in handler to reply with error stanza * started work on pubsub stanzas
-rw-r--r--sleekxmpp/basexmpp.py5
-rw-r--r--sleekxmpp/exceptions.py8
-rw-r--r--sleekxmpp/plugins/stanza_pubsub.py177
-rw-r--r--sleekxmpp/plugins/xep_0004.py2
-rw-r--r--sleekxmpp/plugins/xep_0060.py3
-rw-r--r--sleekxmpp/stanza/iq.py18
-rw-r--r--sleekxmpp/stanza/message.py11
-rw-r--r--sleekxmpp/stanza/presence.py12
-rw-r--r--sleekxmpp/stanza/rootstanza.py25
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py22
-rw-r--r--sleekxmpp/xmlstream/xmlstream.py4
11 files changed, 244 insertions, 43 deletions
diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index 8f32c8f5..dd77bfb0 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -18,6 +18,8 @@
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
@@ -32,6 +34,7 @@ from . stanza.presence import Presence
from . stanza.roster import Roster
from . stanza.nick import Nick
from . stanza.htmlim import HTMLIM
+from . stanza.error import Error
import logging
import threading
@@ -91,6 +94,8 @@ class basexmpp(object):
"""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.
+ # TODO:
+ # gross, this probably isn't necessary anymore, especially for an installed module
__import__("%s.%s" % (globals()['plugins'].__name__, plugin))
# init the plugin class
self.plugin[plugin] = getattr(getattr(plugins, plugin), plugin)(self, pconfig) # eek
diff --git a/sleekxmpp/exceptions.py b/sleekxmpp/exceptions.py
new file mode 100644
index 00000000..9df51700
--- /dev/null
+++ b/sleekxmpp/exceptions.py
@@ -0,0 +1,8 @@
+class XMPPError(Exception):
+ def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None):
+ self.condition = condition
+ self.text = text
+ self.etype = etype
+ self.extension = extension
+ self.extension_ns = extension_ns
+ self.extension_args = extension_args
diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py
new file mode 100644
index 00000000..b407b799
--- /dev/null
+++ b/sleekxmpp/plugins/stanza_pubsub.py
@@ -0,0 +1,177 @@
+from .. xmlstream.stanzabase import ElementBase, ET
+from .. stanza.iq import Iq
+from .. basexmpp import basexmpp
+from .. xmlstream.xmlstream import XMLStream
+from . import xep_0004
+
+
+def stanzaPlugin(stanza, plugin):
+ stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
+ stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
+
+class Pubsub(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'pubsub'
+ plugin_attrib = 'pubsub'
+ interfaces = set((
+ 'create',
+ 'configure',
+ 'subscribe',
+ 'options',
+ 'default',
+ 'items',
+ 'publish',
+ 'retract',
+ 'subscription',
+ 'subscriptions',
+ 'unsubscribe',
+ ))
+
+stanzaPlugin(Iq, Pubsub)
+
+class Affiliations(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'affiliations'
+ plugin_attrib = 'affiliations'
+ interfaces = set(tuple())
+
+ def __init__(self, *args, **kwargs):
+ ElementBase.__init__(self, *args, **kwargs)
+ self.affiliations = []
+ self.idx = 0
+
+ def __iter__(self):
+ self.idx = 0
+ return self
+
+ def __next__(self):
+ self.idx += 1
+ if self.idx + 1 > len(self.affilations):
+ self.idx = 0
+ raise StopIteration
+ return self.affiliations[self.idx]
+
+ def __len__(self):
+ return len(self.affiliations)
+
+ def append(self, affiliation):
+ if not isinstance(affiliation, Affiliation):
+ raise TypeError
+ self.xml.append(affiliation.xml)
+ return self.affiliations.append(affiliation)
+
+ def pop(self, idx=0):
+ aff = self.affiliations.pop(idx)
+ self.xml.remove(aff.xml)
+ return aff
+
+ def find(self, affilation):
+ return self.affilations.find(affiliation)
+
+stanzaPlugin(Pubsub, Affiliations)
+
+class Affiliation(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'affiliation'
+ plugin_attrib = name
+ interfaces = set(('node', 'affiliation'))
+
+class Items(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'items'
+ plugin_attrib = 'items'
+ interfaces = set(tuple())
+
+ def __init__(self, *args, **kwargs):
+ ElementBase.__init__(self, *args, **kwargs)
+ self.items = []
+ self.idx = 0
+
+ def __iter__(self):
+ self.idx = 0
+ return self
+
+ def __next__(self):
+ self.idx += 1
+ if self.idx + 1 > len(self.items):
+ self.idx = 0
+ raise StopIteration
+ return self.items[self.idx]
+
+ def __len__(self):
+ return len(self.items)
+
+ def append(self, item):
+ if not isinstance(item, Item):
+ raise TypeError
+ self.xml.append(item.xml)
+ return self.items.append(item)
+
+ def pop(self, idx=0):
+ aff = self.items.pop(idx)
+ self.xml.remove(aff.xml)
+ return aff
+
+ def find(self, item):
+ return self.items.find(item)
+
+stanzaPlugin(Pubsub, Items)
+
+class Item(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'affiliation'
+ plugin_attrib = name
+ interfaces = set(('node', 'affiliation'))
+class DefaultConfig(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub'
+ name = 'default'
+ plugin_attrib = 'defaultconfig'
+ interfaces = set(('node', 'type', 'config'))
+
+ def __init__(self, *args, **kwargs):
+ ElementBase.__init__(self, *args, **kwargs)
+
+ def getConfig(self):
+ config = self.xml.find('{jabber:x:data}x')
+ form = xep_0004.Form()
+ if config is not None:
+ form.fromXML(config)
+ return form
+
+ def setConfig(self, value):
+ self.xml.append(value.getXML())
+ return self
+
+ def delConfig(self):
+ config = self.xml.find('{jabber:x:data}x')
+ self.xml.remove(config)
+
+stanzaPlugin(Pubsub, DefaultConfig)
+
+iq = Iq()
+aff1 = Affiliation()
+aff1['node'] = 'testnode'
+aff1['affiliation'] = 'owner'
+aff2 = Affiliation()
+aff2['node'] = 'testnode2'
+aff2['affiliation'] = 'publisher'
+iq['pubsub']['affiliations'].append(aff1)
+iq['pubsub']['affiliations'].append(aff2)
+print(iq)
+iq['pubsub']['affiliations'].pop(0)
+print(iq)
+
+iq = Iq()
+iq['pubsub']['defaultconfig']
+print(iq)
+
+class OwnerAffiliations(Affiliations):
+ pass
+
+class OwnerAffiation(Affiliation):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ interfaces = set(('node', 'affiliation', 'jid'))
+
+class PubSubOwner(ElementBase):
+ namespace = 'http://jabber.org/protocol/pubsub#owner'
+ nick = 'pubsubowner'
diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py
index abd5ceef..ec859252 100644
--- a/sleekxmpp/plugins/xep_0004.py
+++ b/sleekxmpp/plugins/xep_0004.py
@@ -48,7 +48,7 @@ class xep_0004(base.base_plugin):
return object
def buildForm(self, xml):
- form = Form(xml.attrib['type'])
+ form = Form(ftype=xml.attrib['type'])
form.fromXML(xml)
return form
diff --git a/sleekxmpp/plugins/xep_0060.py b/sleekxmpp/plugins/xep_0060.py
index 734dd3da..837d0ad4 100644
--- a/sleekxmpp/plugins/xep_0060.py
+++ b/sleekxmpp/plugins/xep_0060.py
@@ -1,7 +1,8 @@
from __future__ import with_statement
from . import base
import logging
-from xml.etree import cElementTree as ET
+#from xml.etree import cElementTree as ET
+from .. xmlstream.stanzabase import ElementBase, ET
class xep_0060(base.base_plugin):
"""
diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py
index a9c9c4be..cec0f8bc 100644
--- a/sleekxmpp/stanza/iq.py
+++ b/sleekxmpp/stanza/iq.py
@@ -3,8 +3,9 @@ from xml.etree import cElementTree as ET
from . error import Error
from .. xmlstream.handler.waiter import Waiter
from .. xmlstream.matcher.id import MatcherId
+from . rootstanza import RootStanza
-class Iq(StanzaBase):
+class Iq(RootStanza):
interfaces = set(('type', 'to', 'from', 'id','query'))
types = set(('get', 'result', 'set', 'error'))
name = 'iq'
@@ -13,13 +14,10 @@ class Iq(StanzaBase):
def __init__(self, *args, **kwargs):
StanzaBase.__init__(self, *args, **kwargs)
if self['id'] == '':
- self['id'] = self.stream.getNewId()
-
- def exception(self, text):
- self.reply()
- self['error']['condition'] = 'undefined-condition'
- self['error']['text'] = text
- self.send()
+ if self.stream is not None:
+ self['id'] = self.stream.getNewId()
+ else:
+ self['id'] = '0'
def unhandled(self):
self.reply()
@@ -84,7 +82,3 @@ class Iq(StanzaBase):
return waitfor.wait(timeout)
else:
return StanzaBase.send(self)
-
-
-Iq.plugin_attrib_map['error'] = Error
-Iq.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
diff --git a/sleekxmpp/stanza/message.py b/sleekxmpp/stanza/message.py
index d75421c8..c8d54f13 100644
--- a/sleekxmpp/stanza/message.py
+++ b/sleekxmpp/stanza/message.py
@@ -1,8 +1,9 @@
from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET
from . error import Error
+from . rootstanza import RootStanza
-class Message(StanzaBase):
+class Message(RootStanza):
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject'))
types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
sub_interfaces = set(('body', 'subject'))
@@ -27,11 +28,3 @@ class Message(StanzaBase):
self['body'] = body
return self
- def exception(self, text):
- self.reply()
- self['error']['condition'] = 'undefined-condition'
- self['error']['text'] = text
- self.send()
-
-Message.plugin_attrib_map['error'] = Error
-Message.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
diff --git a/sleekxmpp/stanza/presence.py b/sleekxmpp/stanza/presence.py
index cb6bd6ce..6a8247c5 100644
--- a/sleekxmpp/stanza/presence.py
+++ b/sleekxmpp/stanza/presence.py
@@ -1,8 +1,9 @@
from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET
from . error import Error
+from . rootstanza import RootStanza
-class Presence(StanzaBase):
+class Presence(RootStanza):
interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority'))
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed'))
showtypes = set(('dnd', 'ffc', 'xa', 'away'))
@@ -52,12 +53,3 @@ class Presence(StanzaBase):
elif self['type'] == 'subscribe':
self['type'] = 'subscribed'
return StanzaBase.reply(self)
-
- def exception(self, text):
- self.reply()
- self['error']['condition'] = 'undefined-condition'
- self['error']['text'] = text
- self.send()
-
-Presence.plugin_attrib_map['error'] = Error
-Presence.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py
new file mode 100644
index 00000000..72ba5ca9
--- /dev/null
+++ b/sleekxmpp/stanza/rootstanza.py
@@ -0,0 +1,25 @@
+from .. xmlstream.stanzabase import StanzaBase
+from xml.etree import cElementTree as ET
+from . error import Error
+from .. exceptions import XMPPError
+import traceback
+
+class RootStanza(StanzaBase):
+
+ def exception(self, e): #called when a handler raises an exception
+ self.reply()
+ if isinstance(e, XMPPError): # we raised this deliberately
+ self['error']['condition'] = e.condition
+ self['error']['text'] = e.text
+ if e.extension is not None: # extended error tag
+ extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args)
+ self['error'].xml.append(extxml)
+ self['error']['type'] = e.etype
+ else: # we probably didn't raise this on purpose, so send back a traceback
+ self['error']['condition'] = 'undefined-condition'
+ self['error']['text'] = traceback.format_tb(e.__traceback__)
+ self.send()
+
+# all jabber:client root stanzas should have the error plugin
+RootStanza.plugin_attrib_map['error'] = Error
+RootStanza.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index d941b8cb..30714dc0 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -1,5 +1,6 @@
from xml.etree import cElementTree as ET
import logging
+import traceback
class JID(object):
def __init__(self, jid):
@@ -31,6 +32,7 @@ class ElementBase(object):
plugin_tag_map = {}
def __init__(self, xml=None, parent=None):
+ self.attrib = self # backwards compatibility hack
self.parent = parent
self.xml = xml
self.plugins = {}
@@ -42,6 +44,9 @@ class ElementBase(object):
def match(self, xml):
return xml.tag == self.tag
+ def find(self, xpath): # for backwards compatiblity, expose elementtree interface
+ return self.xml.find(xpath)
+
def setup(self, xml=None):
if self.xml is None:
self.xml = xml
@@ -183,9 +188,8 @@ class StanzaBase(ElementBase):
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple()
- def __init__(self, stream, xml=None, stype=None, sto=None, sfrom=None, sid=None):
+ def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None):
self.stream = stream
- self.namespace = stream.default_ns
ElementBase.__init__(self, xml)
if stype is not None:
self['type'] = stype
@@ -193,7 +197,9 @@ class StanzaBase(ElementBase):
self['to'] = sto
if sfrom is not None:
self['from'] = sfrom
- self.tag = "{%s}%s" % (self.stream.default_ns, self.name)
+ if stream is not None:
+ self.namespace = stream.default_ns
+ self.tag = "{%s}%s" % (self.namespace, self.name)
def setType(self, value):
if value in self.types:
@@ -240,8 +246,8 @@ class StanzaBase(ElementBase):
def unhandled(self):
pass
- def exception(self, text):
- logging.error(text)
+ def exception(self, e):
+ logging.error(traceback.format_tb(e))
def send(self):
self.stream.sendRaw(str(self))
@@ -257,13 +263,13 @@ class StanzaBase(ElementBase):
else:
ixmlns = ''
nsbuffer = ''
- if xmlns != ixmlns and ixmlns != '' and ixmlns != self.stream.default_ns:
- if ixmlns in self.stream.namespace_map:
+ if xmlns != ixmlns and ixmlns != '' and ixmlns != self.namespace:
+ if self.stream is not None and ixmlns in self.stream.namespace_map:
if self.stream.namespace_map[ixmlns] != '':
itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag)
else:
nsbuffer = """ xmlns="%s\"""" % ixmlns
- if ixmlns not in (xmlns, self.namespace):
+ if ixmlns not in ('', xmlns, self.namespace):
nsbuffer = """ xmlns="%s\"""" % ixmlns
newoutput.append("<%s" % itag)
newoutput.append(nsbuffer)
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
index ee884504..cf61ba31 100644
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ b/sleekxmpp/xmlstream/xmlstream.py
@@ -281,8 +281,8 @@ class XMLStream(object):
if etype == 'stanza':
try:
handler.run(args[0])
- except:
- args[0].exception(traceback.format_exc())
+ except Exception as e:
+ args[0].exception(e)
elif etype == 'sched':
try:
handler.run(*args)