mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-24 03:00:15 +00:00
Merge branch 'master' of github.com:fritzy/SleekXMPP
This commit is contained in:
commit
65dd83d4e1
14 changed files with 134 additions and 21 deletions
8
INSTALL
Normal file
8
INSTALL
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
Pre-requisites:
|
||||||
|
Python 3.1 or 2.6
|
||||||
|
|
||||||
|
Install:
|
||||||
|
python3 setup.py install
|
||||||
|
|
||||||
|
Root install:
|
||||||
|
sudo python3 setup.py install
|
37
README
Normal file
37
README
Normal file
|
@ -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.
|
|
@ -58,7 +58,7 @@ class Subscription(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
name = 'subscription'
|
name = 'subscription'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
interfaces = set(('jid', 'node', 'subscription'))
|
interfaces = set(('jid', 'node', 'subscription', 'subid'))
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
|
@ -207,9 +207,10 @@ class Publish(Items):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
name = 'publish'
|
name = 'publish'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
interfaces = set(('node'))
|
interfaces = set(('node',))
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
subitem = Item
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Publish)
|
stanzaPlugin(Pubsub, Publish)
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ class xep_0030(base.base_plugin):
|
||||||
def info_handler(self, xml):
|
def info_handler(self, xml):
|
||||||
logging.debug("Info request from %s" % xml.get('from', ''))
|
logging.debug("Info request from %s" % xml.get('from', ''))
|
||||||
iq = self.xmpp.makeIqResult(xml.get('id', self.xmpp.getNewId()))
|
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)
|
iq.attrib['to'] = xml.get('from', self.xmpp.server)
|
||||||
query = xml.find('{http://jabber.org/protocol/disco#info}query')
|
query = xml.find('{http://jabber.org/protocol/disco#info}query')
|
||||||
node = query.get('node', 'main')
|
node = query.get('node', 'main')
|
||||||
|
@ -74,7 +74,7 @@ class xep_0030(base.base_plugin):
|
||||||
def item_handler(self, xml):
|
def item_handler(self, xml):
|
||||||
logging.debug("Item request from %s" % xml.get('from', ''))
|
logging.debug("Item request from %s" % xml.get('from', ''))
|
||||||
iq = self.xmpp.makeIqResult(xml.get('id', self.xmpp.getNewId()))
|
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)
|
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')
|
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')
|
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 = ET.Element('item')
|
||||||
itemxml.attrib = item
|
itemxml.attrib = item
|
||||||
if itemxml.attrib['jid'] is None:
|
if itemxml.attrib['jid'] is None:
|
||||||
itemxml.attrib['jid'] = self.xmpp.fulljid
|
itemxml.attrib['jid'] = xml.get('to')
|
||||||
query.append(itemxml)
|
query.append(itemxml)
|
||||||
self.xmpp.send(iq)
|
self.xmpp.send(iq)
|
||||||
|
|
||||||
|
|
|
@ -223,12 +223,15 @@ class xep_0045(base.base_plugin):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setAffiliation(self, room, jid, affiliation='member'):
|
def setAffiliation(self, room, jid=None, nick=None, affiliation='member'):
|
||||||
""" Change room affiliation."""
|
""" Change room affiliation."""
|
||||||
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
||||||
raise TypeError
|
raise TypeError
|
||||||
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
|
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)
|
query.append(item)
|
||||||
iq = self.xmpp.makeIqSet(query)
|
iq = self.xmpp.makeIqSet(query)
|
||||||
iq['to'] = room
|
iq['to'] = room
|
||||||
|
|
|
@ -62,6 +62,7 @@ class xep_0050(base.base_plugin):
|
||||||
name, form, pointer, multi = self.commands[node]
|
name, form, pointer, multi = self.commands[node]
|
||||||
self.sessions[sessionid] = {}
|
self.sessions[sessionid] = {}
|
||||||
self.sessions[sessionid]['jid'] = xml.get('from')
|
self.sessions[sessionid]['jid'] = xml.get('from')
|
||||||
|
self.sessions[sessionid]['to'] = xml.get('to')
|
||||||
self.sessions[sessionid]['past'] = [(form, None)]
|
self.sessions[sessionid]['past'] = [(form, None)]
|
||||||
self.sessions[sessionid]['next'] = pointer
|
self.sessions[sessionid]['next'] = pointer
|
||||||
npointer = pointer
|
npointer = pointer
|
||||||
|
@ -133,6 +134,8 @@ class xep_0050(base.base_plugin):
|
||||||
command.append(xmlactions)
|
command.append(xmlactions)
|
||||||
if not sessionid:
|
if not sessionid:
|
||||||
sessionid = self.getNewSession()
|
sessionid = self.getNewSession()
|
||||||
|
else:
|
||||||
|
iq.attrib['from'] = self.sessions[sessionid]['to']
|
||||||
command.attrib['sessionid'] = sessionid
|
command.attrib['sessionid'] = sessionid
|
||||||
if form is not None:
|
if form is not None:
|
||||||
if hasattr(form,'getXML'):
|
if hasattr(form,'getXML'):
|
||||||
|
|
|
@ -20,10 +20,11 @@ class Iq(RootStanza):
|
||||||
self['id'] = '0'
|
self['id'] = '0'
|
||||||
|
|
||||||
def unhandled(self):
|
def unhandled(self):
|
||||||
self.reply()
|
if self['type'] in ('get', 'set'):
|
||||||
self['error']['condition'] = 'feature-not-implemented'
|
self.reply()
|
||||||
self['error']['text'] = 'No handlers registered for this request.'
|
self['error']['condition'] = 'feature-not-implemented'
|
||||||
self.send()
|
self['error']['text'] = 'No handlers registered for this request.'
|
||||||
|
self.send()
|
||||||
|
|
||||||
def result(self):
|
def result(self):
|
||||||
self['type'] = 'result'
|
self['type'] = 'result'
|
||||||
|
|
|
@ -3,4 +3,4 @@ from . import base
|
||||||
class MatcherId(base.MatcherBase):
|
class MatcherId(base.MatcherBase):
|
||||||
|
|
||||||
def match(self, xml):
|
def match(self, xml):
|
||||||
return xml.get('id') == self._criteria
|
return xml['id'] == self._criteria
|
||||||
|
|
7
sleekxmpp/xmlstream/matcher/stanzapath.py
Normal file
7
sleekxmpp/xmlstream/matcher/stanzapath.py
Normal file
|
@ -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)
|
|
@ -16,6 +16,8 @@ class MatchXMLMask(base.MatcherBase):
|
||||||
self.default_ns = ns
|
self.default_ns = ns
|
||||||
|
|
||||||
def match(self, xml):
|
def match(self, xml):
|
||||||
|
if hasattr(xml, 'xml'):
|
||||||
|
xml = xml.xml
|
||||||
return self.maskcmp(xml, self._criteria, True)
|
return self.maskcmp(xml, self._criteria, True)
|
||||||
|
|
||||||
def maskcmp(self, source, maskobj, use_ns=False, default_ns='__no_ns__'):
|
def maskcmp(self, source, maskobj, use_ns=False, default_ns='__no_ns__'):
|
||||||
|
|
|
@ -6,6 +6,8 @@ ignore_ns = False
|
||||||
class MatchXPath(base.MatcherBase):
|
class MatchXPath(base.MatcherBase):
|
||||||
|
|
||||||
def match(self, xml):
|
def match(self, xml):
|
||||||
|
if hasattr(xml, 'xml'):
|
||||||
|
xml = xml.xml
|
||||||
x = cElementTree.Element('x')
|
x = cElementTree.Element('x')
|
||||||
x.append(xml)
|
x.append(xml)
|
||||||
if not ignore_ns:
|
if not ignore_ns:
|
||||||
|
|
|
@ -2,6 +2,8 @@ from xml.etree import cElementTree as ET
|
||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
xmltester = type(ET.Element('xml'))
|
||||||
|
|
||||||
class JID(object):
|
class JID(object):
|
||||||
def __init__(self, jid):
|
def __init__(self, jid):
|
||||||
self.jid = jid
|
self.jid = jid
|
||||||
|
@ -62,7 +64,10 @@ class ElementBase(object):
|
||||||
|
|
||||||
def append(self, item):
|
def append(self, item):
|
||||||
if not isinstance(item, ElementBase):
|
if not isinstance(item, ElementBase):
|
||||||
raise TypeError
|
if type(item) == xmltester:
|
||||||
|
return self.appendxml(item)
|
||||||
|
else:
|
||||||
|
raise TypeError
|
||||||
self.xml.append(item.xml)
|
self.xml.append(item.xml)
|
||||||
self.iterables.append(item)
|
self.iterables.append(item)
|
||||||
return self
|
return self
|
||||||
|
@ -86,11 +91,27 @@ class ElementBase(object):
|
||||||
out.append('substanzas')
|
out.append('substanzas')
|
||||||
return tuple(out)
|
return tuple(out)
|
||||||
|
|
||||||
def find(self, item):
|
def match(self, matchstring):
|
||||||
return self.iterables.find(item)
|
if isinstance(matchstring, str):
|
||||||
|
nodes = matchstring.split('/')
|
||||||
def match(self, xml):
|
else:
|
||||||
return xml.tag == self.tag
|
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
|
def find(self, xpath): # for backwards compatiblity, expose elementtree interface
|
||||||
return self.xml.find(xpath)
|
return self.xml.find(xpath)
|
||||||
|
|
|
@ -279,7 +279,7 @@ class XMLStream(object):
|
||||||
stanza = StanzaBase(self, xmlobj)
|
stanza = StanzaBase(self, xmlobj)
|
||||||
unhandled = True
|
unhandled = True
|
||||||
for handler in self.__handlers:
|
for handler in self.__handlers:
|
||||||
if handler.match(xmlobj):
|
if handler.match(stanza):
|
||||||
handler.prerun(stanza)
|
handler.prerun(stanza)
|
||||||
self.eventqueue.put(('stanza', handler, stanza))
|
self.eventqueue.put(('stanza', handler, stanza))
|
||||||
if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler))
|
if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import unittest
|
import unittest
|
||||||
from xml.etree import cElementTree as ET
|
from xml.etree import cElementTree as ET
|
||||||
|
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
|
||||||
|
|
||||||
class testpubsubstanzas(unittest.TestCase):
|
class testpubsubstanzas(unittest.TestCase):
|
||||||
|
|
||||||
|
@ -23,7 +24,8 @@ class testpubsubstanzas(unittest.TestCase):
|
||||||
iq3 = self.ps.Iq()
|
iq3 = self.ps.Iq()
|
||||||
values = iq2.getValues()
|
values = iq2.getValues()
|
||||||
iq3.setValues(values)
|
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):
|
def testSubscriptions(self):
|
||||||
"Testing iq/pubsub/subscriptions/subscription stanzas"
|
"Testing iq/pubsub/subscriptions/subscription stanzas"
|
||||||
|
@ -34,9 +36,10 @@ class testpubsubstanzas(unittest.TestCase):
|
||||||
sub2 = self.ps.Subscription()
|
sub2 = self.ps.Subscription()
|
||||||
sub2['node'] = 'testnode2'
|
sub2['node'] = 'testnode2'
|
||||||
sub2['jid'] = 'boogers@bork.top/bill'
|
sub2['jid'] = 'boogers@bork.top/bill'
|
||||||
|
sub2['subscription'] = 'subscribed'
|
||||||
iq['pubsub']['subscriptions'].append(sub1)
|
iq['pubsub']['subscriptions'].append(sub1)
|
||||||
iq['pubsub']['subscriptions'].append(sub2)
|
iq['pubsub']['subscriptions'].append(sub2)
|
||||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscriptions><subscription node="testnode" jid="steve@myserver.tld/someresource" /><subscription node="testnode2" jid="boogers@bork.top/bill" /></subscriptions></pubsub></iq>"""
|
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscriptions><subscription node="testnode" jid="steve@myserver.tld/someresource" /><subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" /></subscriptions></pubsub></iq>"""
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||||
iq3 = self.ps.Iq()
|
iq3 = self.ps.Iq()
|
||||||
values = iq2.getValues()
|
values = iq2.getValues()
|
||||||
|
@ -124,5 +127,30 @@ class testpubsubstanzas(unittest.TestCase):
|
||||||
values = iq2.getValues()
|
values = iq2.getValues()
|
||||||
iq3.setValues(values)
|
iq3.setValues(values)
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||||
|
|
||||||
|
def testPublish(self):
|
||||||
|
iq = self.ps.Iq()
|
||||||
|
iq['pubsub']['publish']['node'] = 'thingers'
|
||||||
|
payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""")
|
||||||
|
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""")
|
||||||
|
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 = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><publish node="thingers"><item id="asdf"><thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1"><child1 /><child2 foo="bar" normandy="cheese" /></thinger></item><item id="asdf2"><thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12"><child12 /><child22 foo="bar2" normandy="cheese2" /></thinger2></item></publish></pubsub></iq>"""
|
||||||
|
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)
|
suite = unittest.TestLoader().loadTestsFromTestCase(testpubsubstanzas)
|
||||||
|
|
Loading…
Reference in a new issue