diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..b73b5e5
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,8 @@
+Pre-requisites:
+Python 3.1 or 2.6
+
+Install:
+python3 setup.py install
+
+Root install:
+sudo python3 setup.py install
diff --git a/README b/README
new file mode 100644
index 0000000..a698909
--- /dev/null
+++ b/README
@@ -0,0 +1,37 @@
+SleekXMPP is an XMPP library written for Python 3.x (with 2.6 compatibility).
+Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre
+
+SleekXMPP has several design goals/philosophies:
+- Low number of dependencies.
+- Every XEP as a plugin.
+- Rewarding to work with.
+
+The goals for 1.0 include (and we're getting close):
+- Nearly Full test coverage of stanzas.
+- Wide range of functional tests.
+- Stanza objects for all interaction with the stream
+- Documentation on using and extending SleekXMPP.
+- Complete documentation on all implemented stanza objects
+- Documentation on all examples used in XMPP: The Definitive Guide
+
+1.1 will include:
+- More functional and unit tests
+- PEP-8 compliance
+- XEP-225 support
+
+Since 0.2, here's the Changelog:
+- MANY bugfixes
+- Re-implementation of handlers/threading to greatly simplify and remove bugs (no more spawning threads in handlers)
+- Stanza objects for jabber:client and all implemented XEPs
+- Raising XMPPError for jabber:client and extended errors in handlers
+- Robust error handling and better insurance of iq responses
+- Stanza objects have made life a lot easier!
+- Massive audit/cleanup.
+
+Credits
+----------------
+Main Author: Nathan Fritz fritz@netflint.net
+XEP-0045 original implementation: Kevin Smith
+Patches: Remko Tronçon
+
+Feel free to add fritzy@netflint.net to your roster for direct support and comments.
diff --git a/sleekxmpp/plugins/stanza_pubsub.py b/sleekxmpp/plugins/stanza_pubsub.py
index 900f3c8..58f9c89 100644
--- a/sleekxmpp/plugins/stanza_pubsub.py
+++ b/sleekxmpp/plugins/stanza_pubsub.py
@@ -58,7 +58,7 @@ class Subscription(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'subscription'
plugin_attrib = name
- interfaces = set(('jid', 'node', 'subscription'))
+ interfaces = set(('jid', 'node', 'subscription', 'subid'))
plugin_attrib_map = {}
plugin_tag_map = {}
@@ -207,9 +207,10 @@ class Publish(Items):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'publish'
plugin_attrib = name
- interfaces = set(('node'))
+ interfaces = set(('node',))
plugin_attrib_map = {}
plugin_tag_map = {}
+ subitem = Item
stanzaPlugin(Pubsub, Publish)
diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py
index 791038c..bff0dc1 100644
--- a/sleekxmpp/plugins/xep_0030.py
+++ b/sleekxmpp/plugins/xep_0030.py
@@ -53,7 +53,7 @@ class xep_0030(base.base_plugin):
def info_handler(self, xml):
logging.debug("Info request from %s" % xml.get('from', ''))
iq = self.xmpp.makeIqResult(xml.get('id', self.xmpp.getNewId()))
- iq.attrib['from'] = self.xmpp.fulljid
+ iq.attrib['from'] = xml.get('to')
iq.attrib['to'] = xml.get('from', self.xmpp.server)
query = xml.find('{http://jabber.org/protocol/disco#info}query')
node = query.get('node', 'main')
@@ -74,7 +74,7 @@ class xep_0030(base.base_plugin):
def item_handler(self, xml):
logging.debug("Item request from %s" % xml.get('from', ''))
iq = self.xmpp.makeIqResult(xml.get('id', self.xmpp.getNewId()))
- iq.attrib['from'] = self.xmpp.fulljid
+ iq.attrib['from'] = xml.get('to')
iq.attrib['to'] = xml.get('from', self.xmpp.server)
query = self.xmpp.makeIqQuery(iq, 'http://jabber.org/protocol/disco#items').find('{http://jabber.org/protocol/disco#items}query')
node = xml.find('{http://jabber.org/protocol/disco#items}query').get('node', 'main')
@@ -82,7 +82,7 @@ class xep_0030(base.base_plugin):
itemxml = ET.Element('item')
itemxml.attrib = item
if itemxml.attrib['jid'] is None:
- itemxml.attrib['jid'] = self.xmpp.fulljid
+ itemxml.attrib['jid'] = xml.get('to')
query.append(itemxml)
self.xmpp.send(iq)
diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py
index 4b181f9..8273742 100644
--- a/sleekxmpp/plugins/xep_0045.py
+++ b/sleekxmpp/plugins/xep_0045.py
@@ -223,12 +223,15 @@ class xep_0045(base.base_plugin):
return False
return True
- def setAffiliation(self, room, jid, affiliation='member'):
+ def setAffiliation(self, room, jid=None, nick=None, affiliation='member'):
""" Change room affiliation."""
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
raise TypeError
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
- item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})
+ if nick is not None:
+ item = ET.Element('item', {'affiliation':affiliation, 'nick':nick})
+ else:
+ item = ET.Element('item', {'affiliation':affiliation, 'jid':jid})
query.append(item)
iq = self.xmpp.makeIqSet(query)
iq['to'] = room
diff --git a/sleekxmpp/plugins/xep_0050.py b/sleekxmpp/plugins/xep_0050.py
index b75a675..0ca66dd 100644
--- a/sleekxmpp/plugins/xep_0050.py
+++ b/sleekxmpp/plugins/xep_0050.py
@@ -62,6 +62,7 @@ class xep_0050(base.base_plugin):
name, form, pointer, multi = self.commands[node]
self.sessions[sessionid] = {}
self.sessions[sessionid]['jid'] = xml.get('from')
+ self.sessions[sessionid]['to'] = xml.get('to')
self.sessions[sessionid]['past'] = [(form, None)]
self.sessions[sessionid]['next'] = pointer
npointer = pointer
@@ -133,6 +134,8 @@ class xep_0050(base.base_plugin):
command.append(xmlactions)
if not sessionid:
sessionid = self.getNewSession()
+ else:
+ iq.attrib['from'] = self.sessions[sessionid]['to']
command.attrib['sessionid'] = sessionid
if form is not None:
if hasattr(form,'getXML'):
diff --git a/sleekxmpp/stanza/iq.py b/sleekxmpp/stanza/iq.py
index e3eccfd..85165e1 100644
--- a/sleekxmpp/stanza/iq.py
+++ b/sleekxmpp/stanza/iq.py
@@ -20,10 +20,11 @@ class Iq(RootStanza):
self['id'] = '0'
def unhandled(self):
- self.reply()
- self['error']['condition'] = 'feature-not-implemented'
- self['error']['text'] = 'No handlers registered for this request.'
- self.send()
+ if self['type'] in ('get', 'set'):
+ self.reply()
+ self['error']['condition'] = 'feature-not-implemented'
+ self['error']['text'] = 'No handlers registered for this request.'
+ self.send()
def result(self):
self['type'] = 'result'
diff --git a/sleekxmpp/xmlstream/matcher/id.py b/sleekxmpp/xmlstream/matcher/id.py
index ec7597d..44fad15 100644
--- a/sleekxmpp/xmlstream/matcher/id.py
+++ b/sleekxmpp/xmlstream/matcher/id.py
@@ -3,4 +3,4 @@ from . import base
class MatcherId(base.MatcherBase):
def match(self, xml):
- return xml.get('id') == self._criteria
+ return xml['id'] == self._criteria
diff --git a/sleekxmpp/xmlstream/matcher/stanzapath.py b/sleekxmpp/xmlstream/matcher/stanzapath.py
new file mode 100644
index 0000000..d036d0b
--- /dev/null
+++ b/sleekxmpp/xmlstream/matcher/stanzapath.py
@@ -0,0 +1,7 @@
+from . import base
+from xml.etree import cElementTree
+
+class StanzaPath(base.MatcherBase):
+
+ def match(self, stanza):
+ return stanza.match(self._criteria)
diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py
index e8e4df0..e4a22fa 100644
--- a/sleekxmpp/xmlstream/matcher/xmlmask.py
+++ b/sleekxmpp/xmlstream/matcher/xmlmask.py
@@ -16,6 +16,8 @@ class MatchXMLMask(base.MatcherBase):
self.default_ns = ns
def match(self, xml):
+ if hasattr(xml, 'xml'):
+ xml = xml.xml
return self.maskcmp(xml, self._criteria, True)
def maskcmp(self, source, maskobj, use_ns=False, default_ns='__no_ns__'):
diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py
index 060d5df..fe18a65 100644
--- a/sleekxmpp/xmlstream/matcher/xpath.py
+++ b/sleekxmpp/xmlstream/matcher/xpath.py
@@ -6,6 +6,8 @@ ignore_ns = False
class MatchXPath(base.MatcherBase):
def match(self, xml):
+ if hasattr(xml, 'xml'):
+ xml = xml.xml
x = cElementTree.Element('x')
x.append(xml)
if not ignore_ns:
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
index 277882e..feb9b26 100644
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ b/sleekxmpp/xmlstream/stanzabase.py
@@ -2,6 +2,8 @@ from xml.etree import cElementTree as ET
import logging
import traceback
+xmltester = type(ET.Element('xml'))
+
class JID(object):
def __init__(self, jid):
self.jid = jid
@@ -62,7 +64,10 @@ class ElementBase(object):
def append(self, item):
if not isinstance(item, ElementBase):
- raise TypeError
+ if type(item) == xmltester:
+ return self.appendxml(item)
+ else:
+ raise TypeError
self.xml.append(item.xml)
self.iterables.append(item)
return self
@@ -86,11 +91,27 @@ class ElementBase(object):
out.append('substanzas')
return tuple(out)
- def find(self, item):
- return self.iterables.find(item)
-
- def match(self, xml):
- return xml.tag == self.tag
+ def match(self, matchstring):
+ if isinstance(matchstring, str):
+ nodes = matchstring.split('/')
+ else:
+ nodes = matchstring
+ tagargs = nodes[0].split('@')
+ if tagargs[0] not in (self.plugins, self.name): return False
+ founditerable = False
+ for iterable in self.iterables:
+ founditerable = iterable.match(nodes[1:])
+ if founditerable: break;
+ for evals in tagargs[1:]:
+ x,y = evals.split('=')
+ if self[x] != y: return False
+ if not founditerable and len(nodes) > 1:
+ next = nodes[1].split('@')[0]
+ if next in self.plugins:
+ return self.plugins[next].match(nodes[1:])
+ else:
+ return False
+ return True
def find(self, xpath): # for backwards compatiblity, expose elementtree interface
return self.xml.find(xpath)
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
index 0bf8b72..0cdfdf9 100644
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ b/sleekxmpp/xmlstream/xmlstream.py
@@ -279,7 +279,7 @@ class XMLStream(object):
stanza = StanzaBase(self, xmlobj)
unhandled = True
for handler in self.__handlers:
- if handler.match(xmlobj):
+ if handler.match(stanza):
handler.prerun(stanza)
self.eventqueue.put(('stanza', handler, stanza))
if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler))
diff --git a/tests/test_pubsubstanzas.py b/tests/test_pubsubstanzas.py
index b84a517..4c3737d 100644
--- a/tests/test_pubsubstanzas.py
+++ b/tests/test_pubsubstanzas.py
@@ -1,5 +1,6 @@
import unittest
from xml.etree import cElementTree as ET
+from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
class testpubsubstanzas(unittest.TestCase):
@@ -23,7 +24,8 @@ class testpubsubstanzas(unittest.TestCase):
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
- self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
+ self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3), "3 methods for creating stanza don't match")
+ self.failUnless(iq.match('iq@id=0/pubsub/affiliations/affiliation@node=testnode2@affiliation=publisher'), 'Match path failed')
def testSubscriptions(self):
"Testing iq/pubsub/subscriptions/subscription stanzas"
@@ -34,9 +36,10 @@ class testpubsubstanzas(unittest.TestCase):
sub2 = self.ps.Subscription()
sub2['node'] = 'testnode2'
sub2['jid'] = 'boogers@bork.top/bill'
+ sub2['subscription'] = 'subscribed'
iq['pubsub']['subscriptions'].append(sub1)
iq['pubsub']['subscriptions'].append(sub2)
- xmlstring = """"""
+ xmlstring = """"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
@@ -124,5 +127,30 @@ class testpubsubstanzas(unittest.TestCase):
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
+
+ def testPublish(self):
+ iq = self.ps.Iq()
+ iq['pubsub']['publish']['node'] = 'thingers'
+ payload = ET.fromstring("""""")
+ payload2 = ET.fromstring("""""")
+ item = self.ps.Item()
+ item['id'] = 'asdf'
+ item['payload'] = payload
+ item2 = self.ps.Item()
+ item2['id'] = 'asdf2'
+ item2['payload'] = payload2
+ iq['pubsub']['publish'].append(item)
+ iq['pubsub']['publish'].append(item2)
+ xmlstring = """ """
+ iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
+ iq3 = self.ps.Iq()
+ values = iq2.getValues()
+ iq3.setValues(values)
+ #print()
+ #print(xmlstring)
+ #print(iq)
+ #print(iq2)
+ #print(iq3)
+ self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
suite = unittest.TestLoader().loadTestsFromTestCase(testpubsubstanzas)