fixed todo merge

This commit is contained in:
Nathan Fritz 2010-08-19 16:09:47 -07:00
commit d150b35464
81 changed files with 5839 additions and 2547 deletions

View file

@ -6,3 +6,6 @@ python3 setup.py install
Root install: Root install:
sudo python3 setup.py install sudo python3 setup.py install
To test:
python example.py -v -j [USER@example.com] -p [PASSWORD]

View file

@ -1,4 +1,4 @@
Copyright (c) 2010 ICRL Copyright (c) 2010 Nathanael C. Fritz
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,39 +0,0 @@
setup.py
sleekxmpp/__init__.py
sleekxmpp/basexmpp.py
sleekxmpp/clientxmpp.py
sleekxmpp/example.py
sleekxmpp/plugins/__init__.py
sleekxmpp/plugins/base.py
sleekxmpp/plugins/gmail_notify.py
sleekxmpp/plugins/xep_0004.py
sleekxmpp/plugins/xep_0009.py
sleekxmpp/plugins/xep_0030.py
sleekxmpp/plugins/xep_0045.py
sleekxmpp/plugins/xep_0050.py
sleekxmpp/plugins/xep_0060.py
sleekxmpp/plugins/xep_0078.py
sleekxmpp/plugins/xep_0086.py
sleekxmpp/plugins/xep_0092.py
sleekxmpp/plugins/xep_0199.py
sleekxmpp/stanza/__init__.py
sleekxmpp/stanza/iq.py
sleekxmpp/stanza/message.py
sleekxmpp/stanza/presence.py
sleekxmpp/xmlstream/__init__.py
sleekxmpp/xmlstream/stanzabase.py
sleekxmpp/xmlstream/statemachine.py
sleekxmpp/xmlstream/test.py
sleekxmpp/xmlstream/testclient.py
sleekxmpp/xmlstream/xmlstream.py
sleekxmpp/xmlstream/handler/__init__.py
sleekxmpp/xmlstream/handler/base.py
sleekxmpp/xmlstream/handler/callback.py
sleekxmpp/xmlstream/handler/waiter.py
sleekxmpp/xmlstream/handler/xmlcallback.py
sleekxmpp/xmlstream/handler/xmlwaiter.py
sleekxmpp/xmlstream/matcher/__init__.py
sleekxmpp/xmlstream/matcher/base.py
sleekxmpp/xmlstream/matcher/many.py
sleekxmpp/xmlstream/matcher/xmlmask.py
sleekxmpp/xmlstream/matcher/xpath.py

5
README
View file

@ -4,6 +4,11 @@ Hosted at http://wiki.github.com/fritzy/SleekXMPP/
Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre Featured in examples in XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre
If you're coming here from The Definitive Guide, please read http://wiki.github.com/fritzy/SleekXMPP/xmpp-the-definitive-guide If you're coming here from The Definitive Guide, please read http://wiki.github.com/fritzy/SleekXMPP/xmpp-the-definitive-guide
Requirements:
We try to keep requirements to a minimum, but we suggest that you install http://dnspython.org although it isn't strictly required.
If you do not install this library, you may need to specify the server/port for services that use SRV records (like GTalk).
"sudo pip install dnspython" on a *nix system with pip installed.
SleekXMPP has several design goals/philosophies: SleekXMPP has several design goals/philosophies:
- Low number of dependencies. - Low number of dependencies.
- Every XEP as a plugin. - Every XEP as a plugin.

View file

@ -1,3 +1,4 @@
#!/usr/bin/env python
# coding=utf8 # coding=utf8
import sleekxmpp import sleekxmpp
@ -32,16 +33,21 @@ if __name__ == '__main__':
optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO) optp.add_option('-q','--quiet', help='set logging to ERROR', action='store_const', dest='loglevel', const=logging.ERROR, default=logging.INFO)
optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO) optp.add_option('-d','--debug', help='set logging to DEBUG', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO) optp.add_option('-v','--verbose', help='set logging to COMM', action='store_const', dest='loglevel', const=5, default=logging.INFO)
optp.add_option("-c","--config", dest="configfile", default="config.xml", help="set config file to use") optp.add_option("-j","--jid", dest="jid", help="JID to use")
optp.add_option("-p","--password", dest="password", help="password to use")
opts,args = optp.parse_args() opts,args = optp.parse_args()
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s') logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
xmpp = Example('user@gmail.com/sleekxmpp', 'password') xmpp = Example(opts.jid, opts.password)
xmpp.registerPlugin('xep_0030') xmpp.registerPlugin('xep_0030')
xmpp.registerPlugin('xep_0004') xmpp.registerPlugin('xep_0004')
xmpp.registerPlugin('xep_0060') xmpp.registerPlugin('xep_0060')
xmpp.registerPlugin('xep_0199') xmpp.registerPlugin('xep_0199')
if xmpp.connect(('talk.google.com', 5222)):
# use this if you don't have pydns, and want to
# talk to GoogleTalk (e.g.)
# if xmpp.connect(('talk.google.com', 5222)):
if xmpp.connect():
xmpp.process(threaded=False) xmpp.process(threaded=False)
print("done") print("done")
else: else:

View file

@ -42,12 +42,8 @@ packages = [ 'sleekxmpp',
'sleekxmpp/stanza', 'sleekxmpp/stanza',
'sleekxmpp/xmlstream', 'sleekxmpp/xmlstream',
'sleekxmpp/xmlstream/matcher', 'sleekxmpp/xmlstream/matcher',
'sleekxmpp/xmlstream/handler' ] 'sleekxmpp/xmlstream/handler',
'sleekxmpp/xmlstream/tostring']
if sys.version_info < (3, 0):
packages.append('sleekxmpp/xmlstream/tostring26')
else:
packages.append('sleekxmpp/xmlstream/tostring')
setup( setup(
name = "sleekxmpp", name = "sleekxmpp",

View file

@ -1,11 +1,11 @@
#!/usr/bin/python2.5 #!/usr/bin/env python
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from . basexmpp import basexmpp from . basexmpp import basexmpp
@ -68,6 +68,7 @@ class ClientXMPP(basexmpp, XMLStream):
self.sessionstarted = False self.sessionstarted = False
self.bound = False self.bound = False
self.bindfail = False self.bindfail = False
self.is_component = False
self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True))
self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True))
#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True)) #self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True))
@ -145,15 +146,21 @@ class ClientXMPP(basexmpp, XMLStream):
def updateRoster(self, jid, name=None, subscription=None, groups=[]): def updateRoster(self, jid, name=None, subscription=None, groups=[]):
"""Add or change a roster item.""" """Add or change a roster item."""
iq = self.Iq().setValues({'type': 'set'}) iq = self.Iq().setStanzaValues({'type': 'set'})
iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}} iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}}
#self.send(iq, self.Iq().setValues({'id': iq['id']})) #self.send(iq, self.Iq().setValues({'id': iq['id']}))
r = iq.send() r = iq.send()
return r['type'] == 'result' return r['type'] == 'result'
def delRosterItem(self, jid):
iq = self.Iq()
iq['type'] = 'set'
iq['roster']['items'] = {jid: {'subscription': 'remove'}}
return iq.send()['type'] == 'result'
def getRoster(self): def getRoster(self):
"""Request the roster be sent.""" """Request the roster be sent."""
iq = self.Iq().setValues({'type': 'get'}).enable('roster').send() iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send()
self._handleRoster(iq, request=True) self._handleRoster(iq, request=True)
def _handleStreamFeatures(self, features): def _handleStreamFeatures(self, features):
@ -169,7 +176,7 @@ class ClientXMPP(basexmpp, XMLStream):
def handler_starttls(self, xml): def handler_starttls(self, xml):
if not self.authenticated and self.ssl_support: if not self.authenticated and self.ssl_support:
self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, instream=True) self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, name='TLS Proceed', instream=True)
self.sendXML(xml) self.sendXML(xml)
return True return True
else: else:
@ -185,8 +192,8 @@ class ClientXMPP(basexmpp, XMLStream):
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
return False return False
logging.debug("Starting SASL Auth") logging.debug("Starting SASL Auth")
self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=True) self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, name='SASL Sucess', instream=True)
self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, instream=True) self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, name='SASL Failure', instream=True)
sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism')
if len(sasl_mechs): if len(sasl_mechs):
for sasl_mech in sasl_mechs: for sasl_mech in sasl_mechs:
@ -215,7 +222,9 @@ class ClientXMPP(basexmpp, XMLStream):
def handler_bind_resource(self, xml): def handler_bind_resource(self, xml):
logging.debug("Requesting resource: %s" % self.resource) logging.debug("Requesting resource: %s" % self.resource)
xml.clear()
iq = self.Iq(stype='set') iq = self.Iq(stype='set')
if self.resource:
res = ET.Element('resource') res = ET.Element('resource')
res.text = self.resource res.text = self.resource
xml.append(res) xml.append(res)
@ -248,5 +257,5 @@ class ClientXMPP(basexmpp, XMLStream):
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
self.roster[jid].update(iq['roster']['items'][jid]) self.roster[jid].update(iq['roster']['items'][jid])
if iq['type'] == 'set': if iq['type'] == 'set':
self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster')) self.send(self.Iq().setStanzaValues({'type': 'result', 'id': iq['id']}).enable('roster'))
self.event("roster_update", iq) self.event("roster_update", iq)

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from __future__ import with_statement, unicode_literals from __future__ import with_statement, unicode_literals
@ -16,6 +16,7 @@ from . xmlstream.handler.xmlcallback import XMLCallback
from . xmlstream.handler.xmlwaiter import XMLWaiter from . xmlstream.handler.xmlwaiter import XMLWaiter
from . xmlstream.handler.waiter import Waiter from . xmlstream.handler.waiter import Waiter
from . xmlstream.handler.callback import Callback from . xmlstream.handler.callback import Callback
from . xmlstream.stanzabase import registerStanzaPlugin
from . import plugins from . import plugins
from . stanza.message import Message from . stanza.message import Message
from . stanza.iq import Iq from . stanza.iq import Iq
@ -24,9 +25,11 @@ from . stanza.roster import Roster
from . stanza.nick import Nick from . stanza.nick import Nick
from . stanza.htmlim import HTMLIM from . stanza.htmlim import HTMLIM
from . stanza.error import Error from . stanza.error import Error
from sleekxmpp.xmlstream.tostring import tostring
import logging import logging
import threading import threading
import copy
import sys import sys
@ -34,12 +37,6 @@ if sys.version_info < (3,0):
reload(sys) reload(sys)
sys.setdefaultencoding('utf8') sys.setdefaultencoding('utf8')
def stanzaPlugin(stanza, plugin):
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
class basexmpp(object): class basexmpp(object):
def __init__(self): def __init__(self):
self.id = 0 self.id = 0
@ -61,13 +58,9 @@ class basexmpp(object):
self.registerStanza(Message) self.registerStanza(Message)
self.registerStanza(Iq) self.registerStanza(Iq)
self.registerStanza(Presence) self.registerStanza(Presence)
self.stanzaPlugin(Iq, Roster) registerStanzaPlugin(Iq, Roster)
self.stanzaPlugin(Message, Nick) registerStanzaPlugin(Message, Nick)
self.stanzaPlugin(Message, HTMLIM) registerStanzaPlugin(Message, HTMLIM)
def stanzaPlugin(self, stanza, plugin):
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
def Message(self, *args, **kwargs): def Message(self, *args, **kwargs):
return Message(self, *args, **kwargs) return Message(self, *args, **kwargs)
@ -126,15 +119,17 @@ class basexmpp(object):
self.id += 1 self.id += 1
return self.getId() return self.getId()
def add_handler(self, mask, pointer, disposable=False, threaded=False, filter=False, instream=False): def add_handler(self, mask, pointer, name=None, disposable=False, threaded=False, filter=False, instream=False):
#logging.warning("Deprecated add_handler used for %s: %s." % (mask, pointer)) # threaded is no longer needed, but leaving it for backwards compatibility for now
self.registerHandler(XMLCallback('add_handler_%s' % self.getNewId(), MatchXMLMask(mask), pointer, threaded, disposable, instream)) if name is None:
name = 'add_handler_%s' % self.getNewId()
self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer, threaded, disposable, instream))
def getId(self): def getId(self):
return "%x".upper() % self.id return "%x".upper() % self.id
def sendXML(self, data, mask=None, timeout=10): def sendXML(self, data, mask=None, timeout=10):
return self.send(self.tostring(data), mask, timeout) return self.send(tostring(data), mask, timeout)
def send(self, data, mask=None, timeout=10): def send(self, data, mask=None, timeout=10):
#logging.warning("Deprecated send used for \"%s\"" % (data,)) #logging.warning("Deprecated send used for \"%s\"" % (data,))
@ -152,26 +147,26 @@ class basexmpp(object):
return waitfor.wait(timeout) return waitfor.wait(timeout)
def makeIq(self, id=0, ifrom=None): def makeIq(self, id=0, ifrom=None):
return self.Iq().setValues({'id': id, 'from': ifrom}) return self.Iq().setStanzaValues({'id': str(id), 'from': ifrom})
def makeIqGet(self, queryxmlns = None): def makeIqGet(self, queryxmlns = None):
iq = self.Iq().setValues({'type': 'get'}) iq = self.Iq().setStanzaValues({'type': 'get'})
if queryxmlns: if queryxmlns:
iq.append(ET.Element("{%s}query" % queryxmlns)) iq.append(ET.Element("{%s}query" % queryxmlns))
return iq return iq
def makeIqResult(self, id): def makeIqResult(self, id):
return self.Iq().setValues({'id': id, 'type': 'result'}) return self.Iq().setStanzaValues({'id': id, 'type': 'result'})
def makeIqSet(self, sub=None): def makeIqSet(self, sub=None):
iq = self.Iq().setValues({'type': 'set'}) iq = self.Iq().setStanzaValues({'type': 'set'})
if sub != None: if sub != None:
iq.append(sub) iq.append(sub)
return iq return iq
def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None): def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None):
iq = self.Iq().setValues({'id': id}) iq = self.Iq().setStanzaValues({'id': id})
iq['error'].setValues({'type': type, 'condition': condition, 'text': text}) iq['error'].setStanzaValues({'type': type, 'condition': condition, 'text': text})
return iq return iq
def makeIqQuery(self, iq, xmlns): def makeIqQuery(self, iq, xmlns):
@ -205,12 +200,13 @@ class basexmpp(object):
def event(self, name, eventdata = {}): # called on an event def event(self, name, eventdata = {}): # called on an event
for handler in self.event_handlers.get(name, []): for handler in self.event_handlers.get(name, []):
handlerdata = copy.copy(eventdata)
if handler[1]: #if threaded if handler[1]: #if threaded
#thread.start_new(handler[0], (eventdata,)) #thread.start_new(handler[0], (eventdata,))
x = threading.Thread(name="Event_%s" % str(handler[0]), target=handler[0], args=(eventdata,)) x = threading.Thread(name="Event_%s" % str(handler[0]), target=handler[0], args=(handlerdata,))
x.start() x.start()
else: else:
handler[0](eventdata) handler[0](handlerdata)
if handler[2]: #disposable if handler[2]: #disposable
with self.lock: with self.lock:
self.event_handlers[name].pop(self.event_handlers[name].index(handler)) self.event_handlers[name].pop(self.event_handlers[name].index(handler))

View file

@ -1,11 +1,11 @@
#!/usr/bin/python2.6 #!/usr/bin/env python
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from __future__ import absolute_import from __future__ import absolute_import
from . basexmpp import basexmpp from . basexmpp import basexmpp
@ -52,6 +52,7 @@ class ComponentXMPP(basexmpp, XMLStream):
self.server_port = port self.server_port = port
self.set_jid(jid) self.set_jid(jid)
self.secret = secret self.secret = secret
self.is_component = True
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
def __getitem__(self, key): def __getitem__(self, key):

View file

@ -3,11 +3,40 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
class XMPPError(Exception): class XMPPError(Exception):
def __init__(self, condition='undefined-condition', text=None, etype=None, extension=None, extension_ns=None, extension_args=None):
"""
A generic exception that may be raised while processing an XMPP stanza
to indicate that an error response stanza should be sent.
The exception method for stanza objects extending RootStanza will create
an error stanza and initialize any additional substanzas using the
extension information included in the exception.
Meant for use in SleekXMPP plugins and applications using SleekXMPP.
"""
def __init__(self, condition='undefined-condition', text=None, etype=None,
extension=None, extension_ns=None, extension_args=None):
"""
Create a new XMPPError exception.
Extension information can be included to add additional XML elements
to the generated error stanza.
Arguments:
condition -- The XMPP defined error condition.
text -- Human readable text describing the error.
etype -- The XMPP error type, such as cancel or modify.
extension -- Tag name of the extension's XML content.
extension_ns -- XML namespace of the extensions' XML content.
extension_args -- Content and attributes for the extension
element. Same as the additional arguments to
the ET.Element constructor.
"""
self.condition = condition self.condition = condition
self.text = text self.text = text
self.etype = etype self.etype = etype

View file

@ -1,20 +1,8 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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
""" """
__all__ = ['xep_0004', 'xep_0030', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060'] __all__ = ['xep_0004', 'xep_0030', 'xep_0033', 'xep_0045', 'xep_0050', 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', 'xep_0060']

View file

@ -1,22 +1,12 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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
""" """
class base_plugin(object): class base_plugin(object):
def __init__(self, xmpp, config): def __init__(self, xmpp, config):

View file

@ -1,57 +1,146 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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 . import base
import logging import logging
from xml.etree import cElementTree as ET from . import base
import traceback from .. xmlstream.handler.callback import Callback
import time from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.iq import Iq
class GmailQuery(ElementBase):
namespace = 'google:mail:notify'
name = 'query'
plugin_attrib = 'gmail'
interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
def getSearch(self):
return self['q']
def setSearch(self, search):
self['q'] = search
def delSearch(self):
del self['q']
class MailBox(ElementBase):
namespace = 'google:mail:notify'
name = 'mailbox'
plugin_attrib = 'mailbox'
interfaces = set(('result-time', 'total-matched', 'total-estimate',
'url', 'threads', 'matched', 'estimate'))
def getThreads(self):
threads = []
for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
MailThread.name)):
threads.append(MailThread(xml=threadXML, parent=None))
return threads
def getMatched(self):
return self['total-matched']
def getEstimate(self):
return self['total-estimate'] == '1'
class MailThread(ElementBase):
namespace = 'google:mail:notify'
name = 'mail-thread-info'
plugin_attrib = 'thread'
interfaces = set(('tid', 'participation', 'messages', 'date',
'senders', 'url', 'labels', 'subject', 'snippet'))
sub_interfaces = set(('labels', 'subject', 'snippet'))
def getSenders(self):
senders = []
sendersXML = self.xml.find('{%s}senders' % self.namespace)
if sendersXML is not None:
for senderXML in sendersXML.findall('{%s}sender' % self.namespace):
senders.append(MailSender(xml=senderXML, parent=None))
return senders
class MailSender(ElementBase):
namespace = 'google:mail:notify'
name = 'sender'
plugin_attrib = 'sender'
interfaces = set(('address', 'name', 'originator', 'unread'))
def getOriginator(self):
return self.xml.attrib.get('originator', '0') == '1'
def getUnread(self):
return self.xml.attrib.get('unread', '0') == '1'
class NewMail(ElementBase):
namespace = 'google:mail:notify'
name = 'new-mail'
plugin_attrib = 'new-mail'
class gmail_notify(base.base_plugin): class gmail_notify(base.base_plugin):
"""
Google Talk: Gmail Notifications
"""
def plugin_init(self): def plugin_init(self):
self.description = 'Google Talk Gmail Notification' self.description = 'Google Talk: Gmail Notifications'
self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True)
self.emails = []
def handler_gmailcheck(self, payload): self.xmpp.registerHandler(
#TODO XEP 30 should cache results and have getFeature Callback('Gmail Result',
result = self.xmpp['xep_0030'].getInfo(self.xmpp.server) MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
features = [] MailBox.namespace,
for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'): MailBox.name)),
features.append(feature.get('var')) self.handle_gmail))
if 'google:mail:notify' in features:
logging.debug("Server supports Gmail Notify")
self.xmpp.add_handler("<iq type='set' xmlns='%s'><new-mail xmlns='google:mail:notify' /></iq>" % self.xmpp.default_ns, self.handler_notify)
self.getEmail()
def handler_notify(self, xml): self.xmpp.registerHandler(
logging.info("New Gmail recieved!") Callback('Gmail New Mail',
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
NewMail.namespace,
NewMail.name)),
self.handle_new_mail))
registerStanzaPlugin(Iq, GmailQuery)
registerStanzaPlugin(Iq, MailBox)
registerStanzaPlugin(Iq, NewMail)
self.last_result_time = None
def handle_gmail(self, iq):
mailbox = iq['mailbox']
approx = ' approximately' if mailbox['estimated'] else ''
logging.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched']))
self.last_result_time = mailbox['result-time']
self.xmpp.event('gmail_messages', iq)
def handle_new_mail(self, iq):
logging.info("Gmail: New emails received!")
self.xmpp.event('gmail_notify') self.xmpp.event('gmail_notify')
self.checkEmail()
def getEmail(self): def getEmail(self, query=None):
iq = self.xmpp.makeIqGet() return self.search(query)
iq.attrib['from'] = self.xmpp.fulljid
iq.attrib['to'] = self.xmpp.jid def checkEmail(self):
self.xmpp.makeIqQuery(iq, 'google:mail:notify') return self.search(newer=self.last_result_time)
emails = iq.send()
mailbox = emails.find('{google:mail:notify}mailbox') def search(self, query=None, newer=None):
total = int(mailbox.get('total-matched', 0)) if query is None:
logging.info("%s New Gmail Messages" % total) logging.info("Gmail: Checking for new emails")
else:
logging.info('Gmail: Searching for emails matching: "%s"' % query)
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['to'] = self.xmpp.jid
iq['gmail']['q'] = query
iq['gmail']['newer-than-time'] = newer
return iq.send()

View file

@ -0,0 +1,417 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from . import base
import logging
from xml.etree import cElementTree as ET
import copy
import logging
#TODO support item groups and results
class old_0004(base.base_plugin):
def plugin_init(self):
self.xep = '0004'
self.description = '*Deprecated Data Forms'
self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form')
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
logging.warning("This implementation of XEP-0004 is deprecated.")
def handler_message_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("message_form", object)
def handler_presence_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("presence_form", object)
def handle_form(self, xml):
xmlform = xml.find('{jabber:x:data}x')
object = self.buildForm(xmlform)
self.xmpp.event("message_xform", object)
return object
def buildForm(self, xml):
form = Form(ftype=xml.attrib['type'])
form.fromXML(xml)
return form
def makeForm(self, ftype='form', title='', instructions=''):
return Form(self.xmpp, ftype, title, instructions)
class FieldContainer(object):
def __init__(self, stanza = 'form'):
self.fields = []
self.field = {}
self.stanza = stanza
def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
self.field[var] = FormField(var, ftype, label, desc, required, value)
self.fields.append(self.field[var])
return self.field[var]
def buildField(self, xml):
self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
self.fields.append(self.field[xml.get('var', '__unnamed__')])
self.field[xml.get('var', '__unnamed__')].buildField(xml)
def buildContainer(self, xml):
self.stanza = xml.tag
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
def getXML(self, ftype):
container = ET.Element(self.stanza)
for field in self.fields:
container.append(field.getXML(ftype))
return container
class Form(FieldContainer):
types = ('form', 'submit', 'cancel', 'result')
def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
if not ftype in self.types:
raise ValueError("Invalid Form Type")
FieldContainer.__init__(self)
self.xmpp = xmpp
self.type = ftype
self.title = title
self.instructions = instructions
self.reported = []
self.items = []
def merge(self, form2):
form1 = Form(ftype=self.type)
form1.fromXML(self.getXML(self.type))
for field in form2.fields:
if not field.var in form1.field:
form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
else:
form1.field[field.var].value = field.value
for option, label in field.options:
if (option, label) not in form1.field[field.var].options:
form1.fields[field.var].addOption(option, label)
return form1
def copy(self):
newform = Form(ftype=self.type)
newform.fromXML(self.getXML(self.type))
return newform
def update(self, form):
values = form.getValues()
for var in values:
if var in self.fields:
self.fields[var].setValue(self.fields[var])
def getValues(self):
result = {}
for field in self.fields:
value = field.value
if len(value) == 1:
value = value[0]
result[field.var] = value
return result
def setValues(self, values={}):
for field in values:
if field in self.field:
if isinstance(values[field], list) or isinstance(values[field], tuple):
for value in values[field]:
self.field[field].setValue(value)
else:
self.field[field].setValue(values[field])
def fromXML(self, xml):
self.buildForm(xml)
def addItem(self):
newitem = FieldContainer('item')
self.items.append(newitem)
return newitem
def buildItem(self, xml):
newitem = self.addItem()
newitem.buildContainer(xml)
def addReported(self):
reported = FieldContainer('reported')
self.reported.append(reported)
return reported
def buildReported(self, xml):
reported = self.addReported()
reported.buildContainer(xml)
def setTitle(self, title):
self.title = title
def setInstructions(self, instructions):
self.instructions = instructions
def setType(self, ftype):
self.type = ftype
def getXMLMessage(self, to):
msg = self.xmpp.makeMessage(to)
msg.append(self.getXML())
return msg
def buildForm(self, xml):
self.type = xml.get('type', 'form')
if xml.find('{jabber:x:data}title') is not None:
self.setTitle(xml.find('{jabber:x:data}title').text)
if xml.find('{jabber:x:data}instructions') is not None:
self.setInstructions(xml.find('{jabber:x:data}instructions').text)
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
for reported in xml.findall('{jabber:x:data}reported'):
self.buildReported(reported)
for item in xml.findall('{jabber:x:data}item'):
self.buildItem(item)
#def getXML(self, tostring = False):
def getXML(self, ftype=None):
if ftype:
self.type = ftype
form = ET.Element('{jabber:x:data}x')
form.attrib['type'] = self.type
if self.title and self.type in ('form', 'result'):
title = ET.Element('{jabber:x:data}title')
title.text = self.title
form.append(title)
if self.instructions and self.type == 'form':
instructions = ET.Element('{jabber:x:data}instructions')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXML(self.type))
for reported in self.reported:
form.append(reported.getXML('{jabber:x:data}reported'))
for item in self.items:
form.append(item.getXML(self.type))
#if tostring:
# form = self.xmpp.tostring(form)
return form
def getXHTML(self):
form = ET.Element('{http://www.w3.org/1999/xhtml}form')
if self.title:
title = ET.Element('h2')
title.text = self.title
form.append(title)
if self.instructions:
instructions = ET.Element('p')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXHTML())
for field in self.reported:
form.append(field.getXHTML())
for field in self.items:
form.append(field.getXHTML())
return form
def makeSubmit(self):
self.setType('submit')
class FormField(object):
types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
lbtypes = ('fixed', 'text-multi')
def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
if not ftype in self.types:
raise ValueError("Invalid Field Type")
self.type = ftype
self.var = var
self.label = label
self.desc = desc
self.options = []
self.required = False
self.value = []
if self.type in self.listtypes:
self.islist = True
else:
self.islist = False
if self.type in self.lbtypes:
self.islinebreak = True
else:
self.islinebreak = False
if value:
self.setValue(value)
def addOption(self, value, label):
if self.islist:
self.options.append((value, label))
else:
raise ValueError("Cannot add options to non-list type field.")
def setTrue(self):
if self.type == 'boolean':
self.value = [True]
def setFalse(self):
if self.type == 'boolean':
self.value = [False]
def require(self):
self.required = True
def setDescription(self, desc):
self.desc = desc
def setValue(self, value):
if self.type == 'boolean':
if value in ('1', 1, True, 'true', 'True', 'yes'):
value = True
else:
value = False
if self.islinebreak and value is not None:
self.value += value.split('\n')
else:
if len(self.value) and (not self.islist or self.type == 'list-single'):
self.value = [value]
else:
self.value.append(value)
def delValue(self, value):
if type(self.value) == type([]):
try:
idx = self.value.index(value)
if idx != -1:
self.value.pop(idx)
except ValueError:
pass
else:
self.value = ''
def setAnswer(self, value):
self.setValue(value)
def buildField(self, xml):
self.type = xml.get('type', 'text-single')
self.label = xml.get('label', '')
for option in xml.findall('{jabber:x:data}option'):
self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
for value in xml.findall('{jabber:x:data}value'):
self.setValue(value.text)
if xml.find('{jabber:x:data}required') is not None:
self.require()
if xml.find('{jabber:x:data}desc') is not None:
self.setDescription(xml.find('{jabber:x:data}desc').text)
def getXML(self, ftype):
field = ET.Element('{jabber:x:data}field')
if ftype != 'result':
field.attrib['type'] = self.type
if self.type != 'fixed':
if self.var:
field.attrib['var'] = self.var
if self.label:
field.attrib['label'] = self.label
if ftype == 'form':
for option in self.options:
optionxml = ET.Element('{jabber:x:data}option')
optionxml.attrib['label'] = option[1]
optionval = ET.Element('{jabber:x:data}value')
optionval.text = option[0]
optionxml.append(optionval)
field.append(optionxml)
if self.required:
required = ET.Element('{jabber:x:data}required')
field.append(required)
if self.desc:
desc = ET.Element('{jabber:x:data}desc')
desc.text = self.desc
field.append(desc)
for value in self.value:
valuexml = ET.Element('{jabber:x:data}value')
if value is True or value is False:
if value:
valuexml.text = '1'
else:
valuexml.text = '0'
else:
valuexml.text = value
field.append(valuexml)
return field
def getXHTML(self):
field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
if self.label:
label = ET.Element('p')
label.text = "%s: " % self.label
else:
label = ET.Element('p')
label.text = "%s: " % self.var
field.append(label)
if self.type == 'boolean':
formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
if len(self.value) and self.value[0] in (True, 'true', '1'):
formf.attrib['checked'] = 'checked'
elif self.type == 'fixed':
formf = ET.Element('p')
try:
formf.text = ', '.join(self.value)
except:
pass
field.append(formf)
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type == 'hidden':
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type in ('jid-multi', 'list-multi'):
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
optf.text = option[1]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(option)
elif self.type in ('jid-single', 'text-single'):
formf = ET.Element('input', {'type': 'text', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
elif self.type == 'list-single':
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0]})
optf.text = option[1]
if not optf.text:
optf.text = option[0]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(optf)
elif self.type == 'text-multi':
formf = ET.Element('textarea', {'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
if not formf.text:
formf.text = ' '
elif self.type == 'text-private':
formf = ET.Element('input', {'type': 'password', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
label.append(formf)
return field

View file

@ -1,4 +1,4 @@
from .. xmlstream.stanzabase import ElementBase, ET, JID from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.iq import Iq from .. stanza.iq import Iq
from .. stanza.message import Message from .. stanza.message import Message
from .. basexmpp import basexmpp from .. basexmpp import basexmpp
@ -6,9 +6,6 @@ from .. xmlstream.xmlstream import XMLStream
import logging import logging
from . import xep_0004 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 PubsubState(ElementBase): class PubsubState(ElementBase):
namespace = 'http://jabber.org/protocol/psstate' namespace = 'http://jabber.org/protocol/psstate'
@ -30,7 +27,7 @@ class PubsubState(ElementBase):
for child in self.xml.getchildren(): for child in self.xml.getchildren():
self.xml.remove(child) self.xml.remove(child)
stanzaPlugin(Iq, PubsubState) registerStanzaPlugin(Iq, PubsubState)
class PubsubStateEvent(ElementBase): class PubsubStateEvent(ElementBase):
namespace = 'http://jabber.org/protocol/psstate#event' namespace = 'http://jabber.org/protocol/psstate#event'
@ -40,8 +37,8 @@ class PubsubStateEvent(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(Message, PubsubStateEvent) registerStanzaPlugin(Message, PubsubStateEvent)
stanzaPlugin(PubsubStateEvent, PubsubState) registerStanzaPlugin(PubsubStateEvent, PubsubState)
class Pubsub(ElementBase): class Pubsub(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -51,7 +48,7 @@ class Pubsub(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(Iq, Pubsub) registerStanzaPlugin(Iq, Pubsub)
class PubsubOwner(ElementBase): class PubsubOwner(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -61,7 +58,7 @@ class PubsubOwner(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(Iq, PubsubOwner) registerStanzaPlugin(Iq, PubsubOwner)
class Affiliation(ElementBase): class Affiliation(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -86,7 +83,7 @@ class Affiliations(ElementBase):
self.xml.append(affiliation.xml) self.xml.append(affiliation.xml)
return self.iterables.append(affiliation) return self.iterables.append(affiliation)
stanzaPlugin(Pubsub, Affiliations) registerStanzaPlugin(Pubsub, Affiliations)
class Subscription(ElementBase): class Subscription(ElementBase):
@ -103,7 +100,7 @@ class Subscription(ElementBase):
def getjid(self): def getjid(self):
return jid(self._getattr('jid')) return jid(self._getattr('jid'))
stanzaPlugin(Pubsub, Subscription) registerStanzaPlugin(Pubsub, Subscription)
class Subscriptions(ElementBase): class Subscriptions(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -114,7 +111,7 @@ class Subscriptions(ElementBase):
plugin_tag_map = {} plugin_tag_map = {}
subitem = (Subscription,) subitem = (Subscription,)
stanzaPlugin(Pubsub, Subscriptions) registerStanzaPlugin(Pubsub, Subscriptions)
class OptionalSetting(object): class OptionalSetting(object):
interfaces = set(('required',)) interfaces = set(('required',))
@ -147,7 +144,7 @@ class SubscribeOptions(ElementBase, OptionalSetting):
plugin_tag_map = {} plugin_tag_map = {}
interfaces = set(('required',)) interfaces = set(('required',))
stanzaPlugin(Subscription, SubscribeOptions) registerStanzaPlugin(Subscription, SubscribeOptions)
class Item(ElementBase): class Item(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -178,7 +175,7 @@ class Items(ElementBase):
plugin_tag_map = {} plugin_tag_map = {}
subitem = (Item,) subitem = (Item,)
stanzaPlugin(Pubsub, Items) registerStanzaPlugin(Pubsub, Items)
class Create(ElementBase): class Create(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -188,7 +185,7 @@ class Create(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(Pubsub, Create) registerStanzaPlugin(Pubsub, Create)
#class Default(ElementBase): #class Default(ElementBase):
# namespace = 'http://jabber.org/protocol/pubsub' # namespace = 'http://jabber.org/protocol/pubsub'
@ -203,7 +200,7 @@ stanzaPlugin(Pubsub, Create)
# if not t: t == 'leaf' # if not t: t == 'leaf'
# return t # return t
# #
#stanzaPlugin(Pubsub, Default) #registerStanzaPlugin(Pubsub, Default)
class Publish(Items): class Publish(Items):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -214,7 +211,7 @@ class Publish(Items):
plugin_tag_map = {} plugin_tag_map = {}
subitem = (Item,) subitem = (Item,)
stanzaPlugin(Pubsub, Publish) registerStanzaPlugin(Pubsub, Publish)
class Retract(Items): class Retract(Items):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -224,7 +221,7 @@ class Retract(Items):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(Pubsub, Retract) registerStanzaPlugin(Pubsub, Retract)
class Unsubscribe(ElementBase): class Unsubscribe(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -254,13 +251,13 @@ class Subscribe(ElementBase):
def getJid(self): def getJid(self):
return JID(self._getAttr('jid')) return JID(self._getAttr('jid'))
stanzaPlugin(Pubsub, Subscribe) registerStanzaPlugin(Pubsub, Subscribe)
class Configure(ElementBase): class Configure(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
name = 'configure' name = 'configure'
plugin_attrib = name plugin_attrib = name
interfaces = set(('node', 'type', 'config')) interfaces = set(('node', 'type'))
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
@ -269,22 +266,8 @@ class Configure(ElementBase):
if not t: t == 'leaf' if not t: t == 'leaf'
return t return t
def getConfig(self): registerStanzaPlugin(Pubsub, Configure)
config = self.xml.find('{jabber:x:data}x') registerStanzaPlugin(Configure, xep_0004.Form)
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, Configure)
class DefaultConfig(ElementBase): class DefaultConfig(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -297,27 +280,13 @@ class DefaultConfig(ElementBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
ElementBase.__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)
def getType(self): def getType(self):
t = self._getAttr('type') t = self._getAttr('type')
if not t: t = 'leaf' if not t: t = 'leaf'
return t return t
stanzaPlugin(PubsubOwner, DefaultConfig) registerStanzaPlugin(PubsubOwner, DefaultConfig)
registerStanzaPlugin(DefaultConfig, xep_0004.Form)
class Options(ElementBase): class Options(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub' namespace = 'http://jabber.org/protocol/pubsub'
@ -351,8 +320,8 @@ class Options(ElementBase):
def getJid(self): def getJid(self):
return JID(self._getAttr('jid')) return JID(self._getAttr('jid'))
stanzaPlugin(Pubsub, Options) registerStanzaPlugin(Pubsub, Options)
stanzaPlugin(Subscribe, Options) registerStanzaPlugin(Subscribe, Options)
class OwnerAffiliations(Affiliations): class OwnerAffiliations(Affiliations):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -366,7 +335,7 @@ class OwnerAffiliations(Affiliations):
self.xml.append(affiliation.xml) self.xml.append(affiliation.xml)
return self.affiliations.append(affiliation) return self.affiliations.append(affiliation)
stanzaPlugin(PubsubOwner, OwnerAffiliations) registerStanzaPlugin(PubsubOwner, OwnerAffiliations)
class OwnerAffiliation(Affiliation): class OwnerAffiliation(Affiliation):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -380,7 +349,7 @@ class OwnerConfigure(Configure):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(PubsubOwner, OwnerConfigure) registerStanzaPlugin(PubsubOwner, OwnerConfigure)
class OwnerDefault(OwnerConfigure): class OwnerDefault(OwnerConfigure):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -388,7 +357,7 @@ class OwnerDefault(OwnerConfigure):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(PubsubOwner, OwnerDefault) registerStanzaPlugin(PubsubOwner, OwnerDefault)
class OwnerDelete(ElementBase, OptionalSetting): class OwnerDelete(ElementBase, OptionalSetting):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -398,7 +367,7 @@ class OwnerDelete(ElementBase, OptionalSetting):
plugin_tag_map = {} plugin_tag_map = {}
interfaces = set(('node',)) interfaces = set(('node',))
stanzaPlugin(PubsubOwner, OwnerDelete) registerStanzaPlugin(PubsubOwner, OwnerDelete)
class OwnerPurge(ElementBase, OptionalSetting): class OwnerPurge(ElementBase, OptionalSetting):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -407,7 +376,7 @@ class OwnerPurge(ElementBase, OptionalSetting):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(PubsubOwner, OwnerPurge) registerStanzaPlugin(PubsubOwner, OwnerPurge)
class OwnerRedirect(ElementBase): class OwnerRedirect(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -423,7 +392,7 @@ class OwnerRedirect(ElementBase):
def getJid(self): def getJid(self):
return JID(self._getAttr('jid')) return JID(self._getAttr('jid'))
stanzaPlugin(OwnerDelete, OwnerRedirect) registerStanzaPlugin(OwnerDelete, OwnerRedirect)
class OwnerSubscriptions(Subscriptions): class OwnerSubscriptions(Subscriptions):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -437,7 +406,7 @@ class OwnerSubscriptions(Subscriptions):
self.xml.append(subscription.xml) self.xml.append(subscription.xml)
return self.subscriptions.append(subscription) return self.subscriptions.append(subscription)
stanzaPlugin(PubsubOwner, OwnerSubscriptions) registerStanzaPlugin(PubsubOwner, OwnerSubscriptions)
class OwnerSubscription(ElementBase): class OwnerSubscription(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#owner' namespace = 'http://jabber.org/protocol/pubsub#owner'
@ -461,7 +430,7 @@ class Event(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(Message, Event) registerStanzaPlugin(Message, Event)
class EventItem(ElementBase): class EventItem(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event' namespace = 'http://jabber.org/protocol/pubsub#event'
@ -501,7 +470,7 @@ class EventItems(ElementBase):
plugin_tag_map = {} plugin_tag_map = {}
subitem = (EventItem, EventRetract) subitem = (EventItem, EventRetract)
stanzaPlugin(Event, EventItems) registerStanzaPlugin(Event, EventItems)
class EventCollection(ElementBase): class EventCollection(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event' namespace = 'http://jabber.org/protocol/pubsub#event'
@ -511,7 +480,7 @@ class EventCollection(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(Event, EventCollection) registerStanzaPlugin(Event, EventCollection)
class EventAssociate(ElementBase): class EventAssociate(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event' namespace = 'http://jabber.org/protocol/pubsub#event'
@ -521,7 +490,7 @@ class EventAssociate(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(EventCollection, EventAssociate) registerStanzaPlugin(EventCollection, EventAssociate)
class EventDisassociate(ElementBase): class EventDisassociate(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event' namespace = 'http://jabber.org/protocol/pubsub#event'
@ -531,7 +500,7 @@ class EventDisassociate(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(EventCollection, EventDisassociate) registerStanzaPlugin(EventCollection, EventDisassociate)
class EventConfiguration(ElementBase): class EventConfiguration(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event' namespace = 'http://jabber.org/protocol/pubsub#event'
@ -541,22 +510,8 @@ class EventConfiguration(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
def getConfig(self): registerStanzaPlugin(Event, EventConfiguration)
config = self.xml.find('{jabber:x:data}x') registerStanzaPlugin(EventConfiguration, xep_0004.Form)
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(Event, EventConfiguration)
class EventPurge(ElementBase): class EventPurge(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event' namespace = 'http://jabber.org/protocol/pubsub#event'
@ -566,7 +521,7 @@ class EventPurge(ElementBase):
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_tag_map = {} plugin_tag_map = {}
stanzaPlugin(Event, EventPurge) registerStanzaPlugin(Event, EventPurge)
class EventSubscription(ElementBase): class EventSubscription(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event' namespace = 'http://jabber.org/protocol/pubsub#event'
@ -582,4 +537,4 @@ class EventSubscription(ElementBase):
def getJid(self): def getJid(self):
return JID(self._getAttr('jid')) return JID(self._getAttr('jid'))
stanzaPlugin(Event, EventSubscription) registerStanzaPlugin(Event, EventSubscription)

View file

@ -1,427 +1,347 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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 . import base
import logging import logging
from xml.etree import cElementTree as ET
import copy import copy
#TODO support item groups and results from . import base
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.message import Message
class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values'))
sub_interfaces = set(('title',))
form_types = set(('cancel', 'form', 'result', 'submit'))
def setup(self, xml=None):
if ElementBase.setup(self, xml): #if we had to generate xml
self['type'] = 'form'
def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs):
kwtype = kwargs.get('type', None)
if kwtype is None:
kwtype = ftype
field = FormField(parent=self)
field['var'] = var
field['type'] = kwtype
field['label'] = label
field['desc'] = desc
field['required'] = required
field['value'] = value
if options is not None:
field['options'] = options
return field
def getXML(self):
logging.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
return self.xml
def fromXML(self, xml):
logging.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
n = Form(xml=xml)
return n
def addItem(self, values):
itemXML = ET.Element('{%s}item' % self.namespace)
self.xml.append(itemXML)
reported_vars = self['reported'].keys()
for var in reported_vars:
fieldXML = ET.Element('{%s}field' % FormField.namespace)
itemXML.append(fieldXML)
field = FormField(xml=fieldXML)
field['var'] = var
field['value'] = values.get(var, None)
def addReported(self, var, ftype=None, label='', desc='', **kwargs):
kwtype = kwargs.get('type', None)
if kwtype is None:
kwtype = ftype
reported = self.xml.find('{%s}reported' % self.namespace)
if reported is None:
reported = ET.Element('{%s}reported' % self.namespace)
self.xml.append(reported)
fieldXML = ET.Element('{%s}field' % FormField.namespace)
reported.append(fieldXML)
field = FormField(xml=fieldXML)
field['var'] = var
field['type'] = kwtype
field['label'] = label
field['desc'] = desc
return field
def cancel(self):
self['type'] = 'cancel'
def delFields(self):
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
for fieldXML in fieldsXML:
self.xml.remove(fieldXML)
def delInstructions(self):
instsXML = self.xml.findall('{%s}instructions')
for instXML in instsXML:
self.xml.remove(instXML)
def delItems(self):
itemsXML = self.xml.find('{%s}item' % self.namespace)
for itemXML in itemsXML:
self.xml.remove(itemXML)
def delReported(self):
reportedXML = self.xml.find('{%s}reported' % self.namespace)
if reportedXML is not None:
self.xml.remove(reportedXML)
def getFields(self, use_dict=False):
fields = {} if use_dict else []
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
for fieldXML in fieldsXML:
field = FormField(xml=fieldXML)
if use_dict:
fields[field['var']] = field
else:
fields.append((field['var'], field))
return fields
def getInstructions(self):
instructions = ''
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
return "\n".join([instXML.text for instXML in instsXML])
def getItems(self):
items = []
itemsXML = self.xml.findall('{%s}item' % self.namespace)
for itemXML in itemsXML:
item = {}
fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
for fieldXML in fieldsXML:
field = FormField(xml=fieldXML)
item[field['var']] = field['value']
items.append(item)
return items
def getReported(self):
fields = {}
fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
FormField.namespace))
for fieldXML in fieldsXML:
field = FormField(xml=fieldXML)
fields[field['var']] = field
return fields
def getValues(self):
values = {}
fields = self.getFields(use_dict=True)
for var in fields:
values[var] = fields[var]['value']
return values
def reply(self):
if self['type'] == 'form':
self['type'] = 'submit'
elif self['type'] == 'submit':
self['type'] = 'result'
def setFields(self, fields, default=None):
del self['fields']
for field_data in fields:
var = field_data[0]
field = field_data[1]
field['var'] = var
self.addField(**field)
def setInstructions(self, instructions):
del self['instructions']
if instructions in [None, '']:
return
instructions = instructions.split('\n')
for instruction in instructions:
inst = ET.Element('{%s}instructions' % self.namespace)
inst.text = instruction
self.xml.append(inst)
def setItems(self, items):
for item in items:
self.addItem(item)
def setReported(self, reported, default=None):
for var in reported:
field = reported[var]
field['var'] = var
self.addReported(var, **field)
def setValues(self, values):
fields = self.getFields(use_dict=True)
for field in values:
fields[field]['value'] = values[field]
class FormField(ElementBase):
namespace = 'jabber:x:data'
name = 'field'
plugin_attrib = 'field'
interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var'))
sub_interfaces = set(('desc',))
field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
'list-single', 'text-multi', 'text-private', 'text-single'))
multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi'))
multi_line_types = set(('hidden', 'text-multi'))
option_types = set(('list-multi', 'list-single'))
true_values = set((True, '1', 'true'))
def addOption(self, label='', value=''):
if self['type'] in self.option_types:
opt = FieldOption(parent=self)
opt['label'] = label
opt['value'] = value
else:
raise ValueError("Cannot add options to a %s field." % self['type'])
def delOptions(self):
optsXML = self.xml.findall('{%s}option' % self.namespace)
for optXML in optsXML:
self.xml.remove(optXML)
def delRequired(self):
reqXML = self.xml.find('{%s}required' % self.namespace)
if reqXML is not None:
self.xml.remove(reqXML)
def delValue(self):
valsXML = self.xml.findall('{%s}value' % self.namespace)
for valXML in valsXML:
self.xml.remove(valXML)
def getAnswer(self):
return self.getValue()
def getOptions(self):
options = []
optsXML = self.xml.findall('{%s}option' % self.namespace)
for optXML in optsXML:
opt = FieldOption(xml=optXML)
options.append({'label': opt['label'], 'value':opt['value']})
return options
def getRequired(self):
reqXML = self.xml.find('{%s}required' % self.namespace)
return reqXML is not None
def getValue(self):
valsXML = self.xml.findall('{%s}value' % self.namespace)
if len(valsXML) == 0:
return None
elif self['type'] == 'boolean':
return valsXML[0].text in self.true_values
elif self['type'] in self.multi_value_types:
values = []
for valXML in valsXML:
if valXML.text is None:
valXML.text = ''
values.append(valXML.text)
if self['type'] == 'text-multi':
values = "\n".join(values)
return values
else:
return valsXML[0].text
def setAnswer(self, answer):
self.setValue(answer)
def setFalse(self):
self.setValue(False)
def setOptions(self, options):
for value in options:
if isinstance(value, dict):
self.addOption(**value)
else:
self.addOption(value=value)
def setRequired(self, required):
exists = self.getRequired()
if not exists and required:
self.xml.append(ET.Element('{%s}required' % self.namespace))
elif exists and not required:
self.delRequired()
def setTrue(self):
self.setValue(True)
def setValue(self, value):
self.delValue()
valXMLName = '{%s}value' % self.namespace
if self['type'] == 'boolean':
if value in self.true_values:
valXML = ET.Element(valXMLName)
valXML.text = '1'
self.xml.append(valXML)
else:
valXML = ET.Element(valXMLName)
valXML.text = '0'
self.xml.append(valXML)
elif self['type'] in self.multi_value_types or self['type'] in ['', None]:
if self['type'] in self.multi_line_types and isinstance(value, str):
value = value.split('\n')
if not isinstance(value, list):
value = [value]
for val in value:
if self['type'] in ['', None] and val in self.true_values:
val = '1'
valXML = ET.Element(valXMLName)
valXML.text = val
self.xml.append(valXML)
else:
if isinstance(value, list):
raise ValueError("Cannot add multiple values to a %s field." % self['type'])
valXML = ET.Element(valXMLName)
valXML.text = value
self.xml.append(valXML)
class FieldOption(ElementBase):
namespace = 'jabber:x:data'
name = 'option'
plugin_attrib = 'option'
interfaces = set(('label', 'value'))
sub_interfaces = set(('value',))
class xep_0004(base.base_plugin): class xep_0004(base.base_plugin):
"""
XEP-0004: Data Forms
"""
def plugin_init(self): def plugin_init(self):
self.xep = '0004' self.xep = '0004'
self.description = 'Data Forms' self.description = 'Data Forms'
self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform)
self.xmpp.registerHandler(
Callback('Data Form',
MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns,
Form.namespace)),
self.handle_form))
registerStanzaPlugin(FormField, FieldOption)
registerStanzaPlugin(Form, FormField)
registerStanzaPlugin(Message, Form)
def post_init(self): def post_init(self):
base.base_plugin.post_init(self) base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
def handler_message_xform(self, xml): def handle_form(self, message):
object = self.handle_form(xml) self.xmpp.event("message_xform", message)
self.xmpp.event("message_form", object)
def handler_presence_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("presence_form", object)
def handle_form(self, xml):
xmlform = xml.find('{jabber:x:data}x')
object = self.buildForm(xmlform)
self.xmpp.event("message_xform", object)
return object
def buildForm(self, xml):
form = Form(ftype=xml.attrib['type'])
form.fromXML(xml)
return form
def makeForm(self, ftype='form', title='', instructions=''):
return Form(self.xmpp, ftype, title, instructions)
class FieldContainer(object):
def __init__(self, stanza = 'form'):
self.fields = []
self.field = {}
self.stanza = stanza
def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
self.field[var] = FormField(var, ftype, label, desc, required, value)
self.fields.append(self.field[var])
return self.field[var]
def buildField(self, xml):
self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
self.fields.append(self.field[xml.get('var', '__unnamed__')])
self.field[xml.get('var', '__unnamed__')].buildField(xml)
def buildContainer(self, xml):
self.stanza = xml.tag
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
def getXML(self, ftype):
container = ET.Element(self.stanza)
for field in self.fields:
container.append(field.getXML(ftype))
return container
class Form(FieldContainer):
types = ('form', 'submit', 'cancel', 'result')
def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
if not ftype in self.types:
raise ValueError("Invalid Form Type")
FieldContainer.__init__(self)
self.xmpp = xmpp
self.type = ftype
self.title = title
self.instructions = instructions
self.reported = []
self.items = []
def merge(self, form2):
form1 = Form(ftype=self.type)
form1.fromXML(self.getXML(self.type))
for field in form2.fields:
if not field.var in form1.field:
form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
else:
form1.field[field.var].value = field.value
for option, label in field.options:
if (option, label) not in form1.field[field.var].options:
form1.fields[field.var].addOption(option, label)
return form1
def copy(self):
newform = Form(ftype=self.type)
newform.fromXML(self.getXML(self.type))
return newform
def update(self, form):
values = form.getValues()
for var in values:
if var in self.fields:
self.fields[var].setValue(self.fields[var])
def getValues(self):
result = {}
for field in self.fields:
value = field.value
if len(value) == 1:
value = value[0]
result[field.var] = value
return result
def setValues(self, values={}):
for field in values:
if field in self.field:
if isinstance(values[field], list) or isinstance(values[field], tuple):
for value in values[field]:
self.field[field].setValue(value)
else:
self.field[field].setValue(values[field])
def fromXML(self, xml):
self.buildForm(xml)
def addItem(self):
newitem = FieldContainer('item')
self.items.append(newitem)
return newitem
def buildItem(self, xml):
newitem = self.addItem()
newitem.buildContainer(xml)
def addReported(self):
reported = FieldContainer('reported')
self.reported.append(reported)
return reported
def buildReported(self, xml):
reported = self.addReported()
reported.buildContainer(xml)
def setTitle(self, title):
self.title = title
def setInstructions(self, instructions):
self.instructions = instructions
def setType(self, ftype):
self.type = ftype
def getXMLMessage(self, to):
msg = self.xmpp.makeMessage(to)
msg.append(self.getXML())
return msg
def buildForm(self, xml):
self.type = xml.get('type', 'form')
if xml.find('{jabber:x:data}title') is not None:
self.setTitle(xml.find('{jabber:x:data}title').text)
if xml.find('{jabber:x:data}instructions') is not None:
self.setInstructions(xml.find('{jabber:x:data}instructions').text)
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
for reported in xml.findall('{jabber:x:data}reported'):
self.buildReported(reported)
for item in xml.findall('{jabber:x:data}item'):
self.buildItem(item)
#def getXML(self, tostring = False):
def getXML(self, ftype=None):
if ftype:
self.type = ftype
form = ET.Element('{jabber:x:data}x')
form.attrib['type'] = self.type
if self.title and self.type in ('form', 'result'):
title = ET.Element('{jabber:x:data}title')
title.text = self.title
form.append(title)
if self.instructions and self.type == 'form':
instructions = ET.Element('{jabber:x:data}instructions')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXML(self.type))
for reported in self.reported:
form.append(reported.getXML('{jabber:x:data}reported'))
for item in self.items:
form.append(item.getXML(self.type))
#if tostring:
# form = self.xmpp.tostring(form)
return form
def getXHTML(self):
form = ET.Element('{http://www.w3.org/1999/xhtml}form')
if self.title:
title = ET.Element('h2')
title.text = self.title
form.append(title)
if self.instructions:
instructions = ET.Element('p')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXHTML())
for field in self.reported:
form.append(field.getXHTML())
for field in self.items:
form.append(field.getXHTML())
return form
def makeSubmit(self):
self.setType('submit')
class FormField(object):
types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
lbtypes = ('fixed', 'text-multi')
def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
if not ftype in self.types:
raise ValueError("Invalid Field Type")
self.type = ftype
self.var = var
self.label = label
self.desc = desc
self.options = []
self.required = False
self.value = []
if self.type in self.listtypes:
self.islist = True
else:
self.islist = False
if self.type in self.lbtypes:
self.islinebreak = True
else:
self.islinebreak = False
if value:
self.setValue(value)
def addOption(self, value, label):
if self.islist:
self.options.append((value, label))
else:
raise ValueError("Cannot add options to non-list type field.")
def setTrue(self):
if self.type == 'boolean':
self.value = [True]
def setFalse(self):
if self.type == 'boolean':
self.value = [False]
def require(self):
self.required = True
def setDescription(self, desc):
self.desc = desc
def setValue(self, value):
if self.type == 'boolean':
if value in ('1', 1, True, 'true', 'True', 'yes'):
value = True
else:
value = False
if self.islinebreak and value is not None:
self.value += value.split('\n')
else:
if len(self.value) and (not self.islist or self.type == 'list-single'):
self.value = [value]
else:
self.value.append(value)
def delValue(self, value):
if type(self.value) == type([]):
try:
idx = self.value.index(value)
if idx != -1:
self.value.pop(idx)
except ValueError:
pass
else:
self.value = ''
def setAnswer(self, value):
self.setValue(value)
def buildField(self, xml):
self.type = xml.get('type', 'text-single')
self.label = xml.get('label', '')
for option in xml.findall('{jabber:x:data}option'):
self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
for value in xml.findall('{jabber:x:data}value'):
self.setValue(value.text)
if xml.find('{jabber:x:data}required') is not None:
self.require()
if xml.find('{jabber:x:data}desc') is not None:
self.setDescription(xml.find('{jabber:x:data}desc').text)
def getXML(self, ftype):
field = ET.Element('{jabber:x:data}field')
if ftype != 'result':
field.attrib['type'] = self.type
if self.type != 'fixed':
if self.var:
field.attrib['var'] = self.var
if self.label:
field.attrib['label'] = self.label
if ftype == 'form':
for option in self.options:
optionxml = ET.Element('{jabber:x:data}option')
optionxml.attrib['label'] = option[1]
optionval = ET.Element('{jabber:x:data}value')
optionval.text = option[0]
optionxml.append(optionval)
field.append(optionxml)
if self.required:
required = ET.Element('{jabber:x:data}required')
field.append(required)
if self.desc:
desc = ET.Element('{jabber:x:data}desc')
desc.text = self.desc
field.append(desc)
for value in self.value:
valuexml = ET.Element('{jabber:x:data}value')
if value is True or value is False:
if value:
valuexml.text = '1'
else:
valuexml.text = '0'
else:
valuexml.text = value
field.append(valuexml)
return field
def getXHTML(self):
field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
if self.label:
label = ET.Element('p')
label.text = "%s: " % self.label
else:
label = ET.Element('p')
label.text = "%s: " % self.var
field.append(label)
if self.type == 'boolean':
formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
if len(self.value) and self.value[0] in (True, 'true', '1'):
formf.attrib['checked'] = 'checked'
elif self.type == 'fixed':
formf = ET.Element('p')
try:
formf.text = ', '.join(self.value)
except:
pass
field.append(formf)
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type == 'hidden':
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type in ('jid-multi', 'list-multi'):
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
optf.text = option[1]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(option)
elif self.type in ('jid-single', 'text-single'):
formf = ET.Element('input', {'type': 'text', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
elif self.type == 'list-single':
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0]})
optf.text = option[1]
if not optf.text:
optf.text = option[0]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(optf)
elif self.type == 'text-multi':
formf = ET.Element('textarea', {'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
if not formf.text:
formf.text = ' '
elif self.type == 'text-private':
formf = ET.Element('input', {'type': 'password', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
label.append(formf)
return field

View file

@ -178,9 +178,12 @@ class xep_0009(base.base_plugin):
def plugin_init(self): def plugin_init(self):
self.xep = '0009' self.xep = '0009'
self.description = 'Jabber-RPC' self.description = 'Jabber-RPC'
self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", self._callMethod) self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",
self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", self._callResult) self._callMethod, name='Jabber RPC Call')
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", self._callError) self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",
self._callResult, name='Jabber RPC Result')
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",
self._callError, name='Jabber RPC Error')
self.entries = {} self.entries = {}
self.activeCalls = [] self.activeCalls = []

View file

@ -3,14 +3,14 @@
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permissio See the file LICENSE for copying permission.
""" """
import logging import logging
from . import base from . import base
from .. xmlstream.handler.callback import Callback from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import ElementBase, ET, JID from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.iq import Iq from .. stanza.iq import Iq
class DiscoInfo(ElementBase): class DiscoInfo(ElementBase):
@ -138,6 +138,9 @@ class DiscoNode(object):
self.info = DiscoInfo() self.info = DiscoInfo()
self.items = DiscoItems() self.items = DiscoItems()
self.info['node'] = name
self.items['node'] = name
# This is a bit like poor man's inheritance, but # This is a bit like poor man's inheritance, but
# to simplify adding information to the node we # to simplify adding information to the node we
# map node functions to either the info or items # map node functions to either the info or items
@ -201,8 +204,8 @@ class xep_0030(base.base_plugin):
DiscoInfo.namespace)), DiscoInfo.namespace)),
self.handle_info_query)) self.handle_info_query))
self.xmpp.stanzaPlugin(Iq, DiscoInfo) registerStanzaPlugin(Iq, DiscoInfo)
self.xmpp.stanzaPlugin(Iq, DiscoItems) registerStanzaPlugin(Iq, DiscoItems)
self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items)
self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info)
@ -290,21 +293,21 @@ class xep_0030(base.base_plugin):
# Older interface methods for backwards compatibility # Older interface methods for backwards compatibility
def getInfo(self, jid, node=''): def getInfo(self, jid, node='', dfrom=None):
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'get' iq['type'] = 'get'
iq['to'] = jid iq['to'] = jid
iq['from'] = self.xmpp.fulljid iq['from'] = dfrom
iq['disco_info']['node'] = node iq['disco_info']['node'] = node
iq.send() return iq.send()
def getItems(self, jid, node=''): def getItems(self, jid, node='', dfrom=None):
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
iq['type'] = 'get' iq['type'] = 'get'
iq['to'] = jid iq['to'] = jid
iq['from'] = self.xmpp.fulljid iq['from'] = dfrom
iq['disco_items']['node'] = node iq['disco_items']['node'] = node
iq.send() return iq.send()
def add_feature(self, feature, node='main'): def add_feature(self, feature, node='main'):
self.add_node(node) self.add_node(node)

View file

@ -0,0 +1,161 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from . import base
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.message import Message
class Addresses(ElementBase):
namespace = 'http://jabber.org/protocol/address'
name = 'addresses'
plugin_attrib = 'addresses'
interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False):
address = Address(parent=self)
address['type'] = atype
address['jid'] = jid
address['node'] = node
address['uri'] = uri
address['desc'] = desc
address['delivered'] = delivered
return address
def getAddresses(self, atype=None):
addresses = []
for addrXML in self.xml.findall('{%s}address' % Address.namespace):
# ElementTree 1.2.6 does not support [@attr='value'] in findall
if atype is None or addrXML.attrib.get('type') == atype:
addresses.append(Address(xml=addrXML, parent=None))
return addresses
def setAddresses(self, addresses, set_type=None):
self.delAddresses(set_type)
for addr in addresses:
addr = dict(addr)
# Remap 'type' to 'atype' to match the add method
if set_type is not None:
addr['type'] = set_type
curr_type = addr.get('type', None)
if curr_type is not None:
del addr['type']
addr['atype'] = curr_type
self.addAddress(**addr)
def delAddresses(self, atype=None):
if atype is None:
return
for addrXML in self.xml.findall('{%s}address' % Address.namespace):
# ElementTree 1.2.6 does not support [@attr='value'] in findall
if addrXML.attrib.get('type') == atype:
self.xml.remove(addrXML)
# --------------------------------------------------------------
def delBcc(self):
self.delAddresses('bcc')
def delCc(self):
self.delAddresses('cc')
def delNoreply(self):
self.delAddresses('noreply')
def delReplyroom(self):
self.delAddresses('replyroom')
def delReplyto(self):
self.delAddresses('replyto')
def delTo(self):
self.delAddresses('to')
# --------------------------------------------------------------
def getBcc(self):
return self.getAddresses('bcc')
def getCc(self):
return self.getAddresses('cc')
def getNoreply(self):
return self.getAddresses('noreply')
def getReplyroom(self):
return self.getAddresses('replyroom')
def getReplyto(self):
return self.getAddresses('replyto')
def getTo(self):
return self.getAddresses('to')
# --------------------------------------------------------------
def setBcc(self, addresses):
self.setAddresses(addresses, 'bcc')
def setCc(self, addresses):
self.setAddresses(addresses, 'cc')
def setNoreply(self, addresses):
self.setAddresses(addresses, 'noreply')
def setReplyroom(self, addresses):
self.setAddresses(addresses, 'replyroom')
def setReplyto(self, addresses):
self.setAddresses(addresses, 'replyto')
def setTo(self, addresses):
self.setAddresses(addresses, 'to')
class Address(ElementBase):
namespace = 'http://jabber.org/protocol/address'
name = 'address'
plugin_attrib = 'address'
interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri'))
address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def getDelivered(self):
return self.xml.attrib.get('delivered', False)
def setDelivered(self, delivered):
if delivered:
self.xml.attrib['delivered'] = "true"
else:
del self['delivered']
def setUri(self, uri):
if uri:
del self['jid']
del self['node']
self.xml.attrib['uri'] = uri
elif 'uri' in self.xml.attrib:
del self.xml.attrib['uri']
class xep_0030(base.base_plugin):
"""
XEP-0033: Extended Stanza Addressing
"""
def plugin_init(self):
self.xep = '0033'
self.description = 'Extended Stanza Addressing'
registerStanzaPlugin(Message, Addresses)
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace)

View file

@ -1,27 +1,15 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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 __future__ import with_statement
from . import base from . import base
import logging import logging
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from .. xmlstream.stanzabase import ElementBase, JID from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, JID
from .. stanza.presence import Presence from .. stanza.presence import Presence
from .. xmlstream.handler.callback import Callback from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream.matcher.xpath import MatchXPath
@ -125,7 +113,7 @@ class xep_0045(base.base_plugin):
self.xep = '0045' self.xep = '0045'
self.description = 'Multi User Chat' self.description = 'Multi User Chat'
# load MUC support in presence stanzas # load MUC support in presence stanzas
self.xmpp.stanzaPlugin(Presence, MUCPresence) registerStanzaPlugin(Presence, MUCPresence)
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence)) self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message)) self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
@ -134,7 +122,7 @@ class xep_0045(base.base_plugin):
""" """
if pr['muc']['room'] not in self.rooms.keys(): if pr['muc']['room'] not in self.rooms.keys():
return return
entry = pr['muc'].getValues() entry = pr['muc'].getStanzaValues()
if pr['type'] == 'unavailable': if pr['type'] == 'unavailable':
del self.rooms[entry['room']][entry['nick']] del self.rooms[entry['room']][entry['nick']]
else: else:
@ -166,13 +154,13 @@ class xep_0045(base.base_plugin):
return False return False
xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if xform is None: return False if xform is None: return False
form = self.xmpp.plugin['xep_0004'].buildForm(xform) form = self.xmpp.plugin['old_0004'].buildForm(xform)
return form return form
def configureRoom(self, room, form=None, ifrom=None): def configureRoom(self, room, form=None, ifrom=None):
if form is None: if form is None:
form = self.getRoomForm(room, ifrom=ifrom) form = self.getRoomForm(room, ifrom=ifrom)
#form = self.xmpp.plugin['xep_0004'].makeForm(ftype='submit') #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit')
#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
iq = self.xmpp.makeIqSet() iq = self.xmpp.makeIqSet()
iq['to'] = room iq['to'] = room
@ -274,7 +262,7 @@ class xep_0045(base.base_plugin):
form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x') form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if form is None: if form is None:
raise ValueError raise ValueError
return self.xmpp.plugin['xep_0004'].buildForm(form) return self.xmpp.plugin['old_0004'].buildForm(form)
def cancelConfig(self, room): def cancelConfig(self, room):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query') query = ET.Element('{http://jabber.org/protocol/muc#owner}query')

View file

@ -1,27 +1,14 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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 __future__ import with_statement
from . import base from . import base
import logging import logging
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
import traceback
import time import time
class xep_0050(base.base_plugin): class xep_0050(base.base_plugin):
@ -32,11 +19,11 @@ class xep_0050(base.base_plugin):
def plugin_init(self): def plugin_init(self):
self.xep = '0050' self.xep = '0050'
self.description = 'Ad-Hoc Commands' self.description = 'Ad-Hoc Commands'
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command) self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command) self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, threaded=True) self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True)
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel) self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete) self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete')
self.commands = {} self.commands = {}
self.sessions = {} self.sessions = {}
self.sd = self.xmpp.plugin['xep_0030'] self.sd = self.xmpp.plugin['xep_0030']
@ -83,7 +70,7 @@ class xep_0050(base.base_plugin):
in_command = xml.find('{http://jabber.org/protocol/commands}command') in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None) sessionid = in_command.get('sessionid', None)
pointer = self.sessions[sessionid]['next'] pointer = self.sessions[sessionid]['next']
results = self.xmpp.plugin['xep_0004'].makeForm('result') results = self.xmpp.plugin['old_0004'].makeForm('result')
results.fromXML(in_command.find('{jabber:x:data}x')) results.fromXML(in_command.find('{jabber:x:data}x'))
pointer(results,sessionid) pointer(results,sessionid)
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[])) self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[]))
@ -94,7 +81,7 @@ class xep_0050(base.base_plugin):
in_command = xml.find('{http://jabber.org/protocol/commands}command') in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None) sessionid = in_command.get('sessionid', None)
pointer = self.sessions[sessionid]['next'] pointer = self.sessions[sessionid]['next']
results = self.xmpp.plugin['xep_0004'].makeForm('result') results = self.xmpp.plugin['old_0004'].makeForm('result')
results.fromXML(in_command.find('{jabber:x:data}x')) results.fromXML(in_command.find('{jabber:x:data}x'))
form, npointer, next = pointer(results,sessionid) form, npointer, next = pointer(results,sessionid)
self.sessions[sessionid]['next'] = npointer self.sessions[sessionid]['next'] = npointer

View file

@ -2,7 +2,7 @@ from __future__ import with_statement
from . import base from . import base
import logging import logging
#from xml.etree import cElementTree as ET #from xml.etree import cElementTree as ET
from .. xmlstream.stanzabase import ElementBase, ET from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
from . import stanza_pubsub from . import stanza_pubsub
class xep_0060(base.base_plugin): class xep_0060(base.base_plugin):

View file

@ -1,21 +1,9 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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 __future__ import with_statement
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET

View file

@ -0,0 +1,101 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permissio
"""
import logging
from . import base
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.message import Message
class ChatState(ElementBase):
namespace = 'http://jabber.org/protocol/chatstates'
plugin_attrib = 'chat_state'
interface = set(('state',))
states = set(('active', 'composing', 'gone', 'inactive', 'paused'))
def active(self):
self.setState('active')
def composing(self):
self.setState('composing')
def gone(self):
self.setState('gone')
def inactive(self):
self.setState('inactive')
def paused(self):
self.setState('paused')
def setState(self, state):
if state in self.states:
self.name = state
self.xml.tag = '{%s}%s' % (self.namespace, state)
else:
raise ValueError('Invalid chat state')
def getState(self):
return self.name
# In order to match the various chat state elements,
# we need one stanza object per state, even though
# they are all the same except for the initial name
# value. Do not depend on the type of the chat state
# stanza object for the actual state.
class Active(ChatState):
name = 'active'
class Composing(ChatState):
name = 'composing'
class Gone(ChatState):
name = 'gone'
class Inactive(ChatState):
name = 'inactive'
class Paused(ChatState):
name = 'paused'
class xep_0085(base.base_plugin):
"""
XEP-0085 Chat State Notifications
"""
def plugin_init(self):
self.xep = '0085'
self.description = 'Chat State Notifications'
handlers = [('Active Chat State', 'active'),
('Composing Chat State', 'composing'),
('Gone Chat State', 'gone'),
('Inactive Chat State', 'inactive'),
('Paused Chat State', 'paused')]
for handler in handlers:
self.xmpp.registerHandler(
Callback(handler[0],
MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns,
ChatState.namespace,
handler[1])),
self._handleChatState))
registerStanzaPlugin(Message, Active)
registerStanzaPlugin(Message, Composing)
registerStanzaPlugin(Message, Gone)
registerStanzaPlugin(Message, Inactive)
registerStanzaPlugin(Message, Paused)
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/chatstates')
def _handleChatState(self, msg):
state = msg['chat_state'].name
logging.debug("Chat State: %s, %s" % (state, msg['from'].jid))
self.xmpp.event('chatstate_%s' % state, msg)

View file

@ -1,21 +1,9 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
Copyright (C) 2007 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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 xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from . import base from . import base
@ -30,7 +18,7 @@ class xep_0092(base.base_plugin):
self.xep = "0092" self.xep = "0092"
self.name = self.config.get('name', 'SleekXMPP') self.name = self.config.get('name', 'SleekXMPP')
self.version = self.config.get('version', '0.1-dev') self.version = self.config.get('version', '0.1-dev')
self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version) self.xmpp.add_handler("<iq type='get' xmlns='%s'><query xmlns='jabber:iq:version' /></iq>" % self.xmpp.default_ns, self.report_version, name='Sofware Version')
def post_init(self): def post_init(self):
base.base_plugin.post_init(self) base.base_plugin.post_init(self)

View file

@ -0,0 +1,51 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from . import base
from .. xmlstream.handler.callback import Callback
from .. xmlstream.matcher.xpath import MatchXPath
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from .. stanza.iq import Iq
from . xep_0030 import DiscoInfo, DiscoItems
from . xep_0004 import Form
class xep_0128(base.base_plugin):
"""
XEP-0128 Service Discovery Extensions
"""
def plugin_init(self):
self.xep = '0128'
self.description = 'Service Discovery Extensions'
registerStanzaPlugin(DiscoInfo, Form)
registerStanzaPlugin(DiscoItems, Form)
def extend_info(self, node, data=None):
if data is None:
data = {}
node = self.xmpp['xep_0030'].nodes.get(node, None)
if node is None:
self.xmpp['xep_0030'].add_node(node)
info = node.info
info['form']['type'] = 'result'
info['form'].setFields(data, default=None)
def extend_items(self, node, data=None):
if data is None:
data = {}
node = self.xmpp['xep_0030'].nodes.get(node, None)
if node is None:
self.xmpp['xep_0030'].add_node(node)
items = node.items
items['form']['type'] = 'result'
items['form'].setFields(data, default=None)

View file

@ -1,22 +1,9 @@
""" """
SleekXMPP: The Sleek XMPP Library SleekXMPP: The Sleek XMPP Library
XEP-0199 (Ping) support Copyright (C) 2010 Nathanael C. Fritz
Copyright (C) 2007 Kevin Smith
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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 xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
from . import base from . import base
@ -29,7 +16,7 @@ class xep_0199(base.base_plugin):
def plugin_init(self): def plugin_init(self):
self.description = "XMPP Ping" self.description = "XMPP Ping"
self.xep = "0199" self.xep = "0199"
self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='http://www.xmpp.org/extensions/xep-0199.html#ns'/></iq>" % self.xmpp.default_ns, self.handler_ping) self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='http://www.xmpp.org/extensions/xep-0199.html#ns'/></iq>" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping')
self.running = False self.running = False
#if self.config.get('keepalive', True): #if self.config.get('keepalive', True):
#self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) #self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True)

View file

@ -3,6 +3,11 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
__all__ = ['presence']
from sleekxmpp.stanza.error import Error
from sleekxmpp.stanza.iq import Iq
from sleekxmpp.stanza.message import Message
from sleekxmpp.stanza.presence import Presence

View file

@ -1,4 +1,4 @@
from .. xmlstream.stanzabase import ElementBase, ET, JID from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
from xml.etree import cElementTree as ET from xml.etree import cElementTree as ET
class AtomEntry(ElementBase): class AtomEntry(ElementBase):

View file

@ -3,60 +3,131 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from .. xmlstream.stanzabase import ElementBase, ET
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin
from sleekxmpp.xmlstream.stanzabase import ElementBase, ET
class Error(ElementBase): class Error(ElementBase):
"""
XMPP stanzas of type 'error' should include an <error> stanza that
describes the nature of the error and how it should be handled.
Use the 'XEP-0086: Error Condition Mappings' plugin to include error
codes used in older XMPP versions.
Example error stanza:
<error type="cancel" code="404">
<item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
The item was not found.
</text>
</error>
Stanza Interface:
code -- The error code used in older XMPP versions.
condition -- The name of the condition element.
text -- Human readable description of the error.
type -- Error type indicating how the error should be handled.
Attributes:
conditions -- The set of allowable error condition elements.
condition_ns -- The namespace for the condition element.
types -- A set of values indicating how the error
should be treated.
Methods:
setup -- Overrides ElementBase.setup.
getCondition -- Retrieve the name of the condition element.
setCondition -- Add a condition element.
delCondition -- Remove the condition element.
getText -- Retrieve the contents of the <text> element.
setText -- Set the contents of the <text> element.
delText -- Remove the <text> element.
"""
namespace = 'jabber:client' namespace = 'jabber:client'
name = 'error' name = 'error'
plugin_attrib = 'error' plugin_attrib = 'error'
conditions = set(('bad-request', 'conflict', 'feature-not-implemented', 'forbidden', 'gone', 'internal-server-error', 'item-not-found', 'jid-malformed', 'not-acceptable', 'not-allowed', 'not-authorized', 'payment-required', 'recipient-unavailable', 'redirect', 'registration-required', 'remote-server-not-found', 'remote-server-timeout', 'resource-constraint', 'service-unavailable', 'subscription-required', 'undefined-condition', 'unexpected-request'))
interfaces = set(('code', 'condition', 'text', 'type')) interfaces = set(('code', 'condition', 'text', 'type'))
types = set(('cancel', 'continue', 'modify', 'auth', 'wait'))
sub_interfaces = set(('text',)) sub_interfaces = set(('text',))
conditions = set(('bad-request', 'conflict', 'feature-not-implemented',
'forbidden', 'gone', 'internal-server-error',
'item-not-found', 'jid-malformed', 'not-acceptable',
'not-allowed', 'not-authorized', 'payment-required',
'recipient-unavailable', 'redirect',
'registration-required', 'remote-server-not-found',
'remote-server-timeout', 'resource-constraint',
'service-unavailable', 'subscription-required',
'undefined-condition', 'unexpected-request'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas' condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
types = set(('cancel', 'continue', 'modify', 'auth', 'wait'))
def setup(self, xml=None): def setup(self, xml=None):
if ElementBase.setup(self, xml): #if we had to generate xml """
Populate the stanza object using an optional XML object.
Overrides ElementBase.setup.
Sets a default error type and condition, and changes the
parent stanza's type to 'error'.
Arguments:
xml -- Use an existing XML object for the stanza's values.
"""
if ElementBase.setup(self, xml):
#If we had to generate XML then set default values.
self['type'] = 'cancel' self['type'] = 'cancel'
self['condition'] = 'feature-not-implemented' self['condition'] = 'feature-not-implemented'
if self.parent is not None: if self.parent is not None:
self.parent()['type'] = 'error' self.parent()['type'] = 'error'
def getCondition(self): def getCondition(self):
"""Return the condition element's name."""
for child in self.xml.getchildren(): for child in self.xml.getchildren():
if "{%s}" % self.condition_ns in child.tag: if "{%s}" % self.condition_ns in child.tag:
return child.tag.split('}', 1)[-1] return child.tag.split('}', 1)[-1]
return '' return ''
def setCondition(self, value): def setCondition(self, value):
"""
Set the tag name of the condition element.
Arguments:
value -- The tag name of the condition element.
"""
if value in self.conditions: if value in self.conditions:
for child in self.xml.getchildren(): del self['condition']
if "{%s}" % self.condition_ns in child.tag: self.xml.append(ET.Element("{%s}%s" % (self.condition_ns, value)))
self.xml.remove(child)
condition = ET.Element("{%s}%s" % (self.condition_ns, value))
self.xml.append(condition)
return self return self
def delCondition(self): def delCondition(self):
"""Remove the condition element."""
for child in self.xml.getchildren():
if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:
self.xml.remove(child)
return self return self
def getText(self): def getText(self):
text = '' """Retrieve the contents of the <text> element."""
textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text") return self._getSubText('{%s}text' % self.condition_ns)
if textxml is not None:
text = textxml.text
return text
def setText(self, value): def setText(self, value):
self.delText() """
textxml = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}text') Set the contents of the <text> element.
textxml.text = value
self.xml.append(textxml) Arguments:
value -- The new contents for the <text> element.
"""
self._setSubText('{%s}text' % self.condition_ns, text=value)
return self return self
def delText(self): def delText(self):
textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text") """Remove the <text> element."""
if textxml is not None: self._delSub('{%s}text' % self.condition_ns)
self.xml.remove(textxml) return self

View file

@ -3,19 +3,58 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from .. xmlstream.stanzabase import ElementBase, ET
from sleekxmpp.stanza import Message
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin
from sleekxmpp.xmlstream.stanzabase import ElementBase, ET
class HTMLIM(ElementBase): class HTMLIM(ElementBase):
"""
XEP-0071: XHTML-IM defines a method for embedding XHTML content
within a <message> stanza so that lightweight markup can be used
to format the message contents and to create links.
Only a subset of XHTML is recommended for use with XHTML-IM.
See the full spec at 'http://xmpp.org/extensions/xep-0071.html'
for more information.
Example stanza:
<message to="user@example.com">
<body>Non-html message content.</body>
<html xmlns="http://jabber.org/protocol/xhtml-im">
<body xmlns="http://www.w3.org/1999/xhtml">
<p><b>HTML!</b></p>
</body>
</html>
</message>
Stanza Interface:
body -- The contents of the HTML body tag.
Methods:
getBody -- Return the HTML body contents.
setBody -- Set the HTML body contents.
delBody -- Remove the HTML body contents.
"""
namespace = 'http://jabber.org/protocol/xhtml-im' namespace = 'http://jabber.org/protocol/xhtml-im'
name = 'html' name = 'html'
plugin_attrib = 'html' interfaces = set(('body',))
interfaces = set(('html',)) plugin_attrib = name
plugin_attrib_map = set()
plugin_xml_map = set()
def setHtml(self, html): def setBody(self, html):
"""
Set the contents of the HTML body.
Arguments:
html -- Either a string or XML object. If the top level
element is not <body> with a namespace of
'http://www.w3.org/1999/xhtml', it will be wrapped.
"""
if isinstance(html, str): if isinstance(html, str):
html = ET.XML(html) html = ET.XML(html)
if html.tag != '{http://www.w3.org/1999/xhtml}body': if html.tag != '{http://www.w3.org/1999/xhtml}body':
@ -25,11 +64,17 @@ class HTMLIM(ElementBase):
else: else:
self.xml.append(html) self.xml.append(html)
def getHtml(self): def getBody(self):
"""Return the contents of the HTML body."""
html = self.xml.find('{http://www.w3.org/1999/xhtml}body') html = self.xml.find('{http://www.w3.org/1999/xhtml}body')
if html is None: return '' if html is None:
return ''
return html return html
def delHtml(self): def delBody(self):
"""Remove the HTML body contents."""
if self.parent is not None: if self.parent is not None:
self.parent().xml.remove(self.xml) self.parent().xml.remove(self.xml)
registerStanzaPlugin(Message, HTMLIM)

View file

@ -3,23 +3,78 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET from sleekxmpp.stanza import Error
from . error import Error from sleekxmpp.stanza.rootstanza import RootStanza
from .. xmlstream.handler.waiter import Waiter from sleekxmpp.xmlstream import RESPONSE_TIMEOUT
from .. xmlstream.matcher.id import MatcherId from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
from . rootstanza import RootStanza from sleekxmpp.xmlstream.handler import Waiter
from sleekxmpp.xmlstream.matcher import MatcherId
class Iq(RootStanza): class Iq(RootStanza):
"""
XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
requesting and modifying information, similar to HTTP's GET and
POST methods.
Each <iq> stanza must have an 'id' value which associates the
stanza with the response stanza. XMPP entities must always
be given a response <iq> stanza with a type of 'result' after
sending a stanza of type 'get' or 'set'.
Most uses cases for <iq> stanzas will involve adding a <query>
element whose namespace indicates the type of information
desired. However, some custom XMPP applications use <iq> stanzas
as a carrier stanza for an application-specific protocol instead.
Example <iq> Stanzas:
<iq to="user@example.com" type="get" id="314">
<query xmlns="http://jabber.org/protocol/disco#items" />
</iq>
<iq to="user@localhost" type="result" id="17">
<query xmlns='jabber:iq:roster'>
<item jid='otheruser@example.net'
name='John Doe'
subscription='both'>
<group>Friends</group>
</item>
</query>
</iq>
Stanza Interface:
query -- The namespace of the <query> element if one exists.
Attributes:
types -- May be one of: get, set, result, or error.
Methods:
__init__ -- Overrides StanzaBase.__init__.
unhandled -- Send error if there are no handlers.
setPayload -- Overrides StanzaBase.setPayload.
setQuery -- Add or modify a <query> element.
getQuery -- Return the namespace of the <query> element.
delQuery -- Remove the <query> element.
reply -- Overrides StanzaBase.reply
send -- Overrides StanzaBase.send
"""
namespace = 'jabber:client'
name = 'iq'
interfaces = set(('type', 'to', 'from', 'id', 'query')) interfaces = set(('type', 'to', 'from', 'id', 'query'))
types = set(('get', 'result', 'set', 'error')) types = set(('get', 'result', 'set', 'error'))
name = 'iq'
plugin_attrib = name plugin_attrib = name
namespace = 'jabber:client'
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""
Initialize a new <iq> stanza with an 'id' value.
Overrides StanzaBase.__init__.
"""
StanzaBase.__init__(self, *args, **kwargs) StanzaBase.__init__(self, *args, **kwargs)
if self['id'] == '': if self['id'] == '':
if self.stream is not None: if self.stream is not None:
@ -28,6 +83,11 @@ class Iq(RootStanza):
self['id'] = '0' self['id'] = '0'
def unhandled(self): def unhandled(self):
"""
Send a feature-not-implemented error if the stanza is not handled.
Overrides StanzaBase.unhandled.
"""
if self['type'] in ('get', 'set'): if self['type'] in ('get', 'set'):
self.reply() self.reply()
self['error']['condition'] = 'feature-not-implemented' self['error']['condition'] = 'feature-not-implemented'
@ -35,11 +95,25 @@ class Iq(RootStanza):
self.send() self.send()
def setPayload(self, value): def setPayload(self, value):
"""
Set the XML contents of the <iq> stanza.
Arguments:
value -- An XML object to use as the <iq> stanza's contents
"""
self.clear() self.clear()
StanzaBase.setPayload(self, value) StanzaBase.setPayload(self, value)
return self return self
def setQuery(self, value): def setQuery(self, value):
"""
Add or modify a <query> element.
Query elements are differentiated by their namespace.
Arguments:
value -- The namespace of the <query> element.
"""
query = self.xml.find("{%s}query" % value) query = self.xml.find("{%s}query" % value)
if query is None and value: if query is None and value:
self.clear() self.clear()
@ -48,6 +122,7 @@ class Iq(RootStanza):
return self return self
def getQuery(self): def getQuery(self):
"""Return the namespace of the <query> element."""
for child in self.xml.getchildren(): for child in self.xml.getchildren():
if child.tag.endswith('query'): if child.tag.endswith('query'):
ns = child.tag.split('}')[0] ns = child.tag.split('}')[0]
@ -56,18 +131,43 @@ class Iq(RootStanza):
return ns return ns
return '' return ''
def reply(self):
self['type'] = 'result'
StanzaBase.reply(self)
return self
def delQuery(self): def delQuery(self):
for child in self.getchildren(): """Remove the <query> element."""
for child in self.xml.getchildren():
if child.tag.endswith('query'): if child.tag.endswith('query'):
self.xml.remove(child) self.xml.remove(child)
return self return self
def send(self, block=True, timeout=10): def reply(self):
"""
Send a reply <iq> stanza.
Overrides StanzaBase.reply
Sets the 'type' to 'result' in addition to the default
StanzaBase.reply behavior.
"""
self['type'] = 'result'
StanzaBase.reply(self)
return self
def send(self, block=True, timeout=RESPONSE_TIMEOUT):
"""
Send an <iq> stanza over the XML stream.
The send call can optionally block until a response is received or
a timeout occurs. Be aware that using blocking in non-threaded event
handlers can drastically impact performance.
Overrides StanzaBase.send
Arguments:
block -- Specify if the send call will block until a response
is received, or a timeout occurs. Defaults to True.
timeout -- The length of time (in seconds) to wait for a response
before exiting the send call if blocking is used.
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
"""
if block and self['type'] in ('get', 'set'): if block and self['type'] in ('get', 'set'):
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
self.stream.registerHandler(waitfor) self.stream.registerHandler(waitfor)

View file

@ -3,61 +3,141 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET from sleekxmpp.stanza import Error
from . error import Error from sleekxmpp.stanza.rootstanza import RootStanza
from . rootstanza import RootStanza from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
class Message(RootStanza): class Message(RootStanza):
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject', 'mucroom', 'mucnick'))
types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat')) """
sub_interfaces = set(('body', 'subject')) XMPP's <message> stanzas are a "push" mechanism to send information
name = 'message' to other XMPP entities without requiring a response.
plugin_attrib = name
Chat clients will typically use <message> stanzas that have a type
of either "chat" or "groupchat".
When handling a message event, be sure to check if the message is
an error response.
Example <message> stanzas:
<message to="user1@example.com" from="user2@example.com">
<body>Hi!</body>
</message>
<message type="groupchat" to="room@conference.example.com">
<body>Hi everyone!</body>
</message>
Stanza Interface:
body -- The main contents of the message.
subject -- An optional description of the message's contents.
mucroom -- (Read-only) The name of the MUC room that sent the message.
mucnick -- (Read-only) The MUC nickname of message's sender.
Attributes:
types -- May be one of: normal, chat, headline, groupchat, or error.
Methods:
chat -- Set the message type to 'chat'.
normal -- Set the message type to 'normal'.
reply -- Overrides StanzaBase.reply
getType -- Overrides StanzaBase interface
getMucroom -- Return the name of the MUC room of the message.
setMucroom -- Dummy method to prevent assignment.
delMucroom -- Dummy method to prevent deletion.
getMucnick -- Return the MUC nickname of the message's sender.
setMucnick -- Dummy method to prevent assignment.
delMucnick -- Dummy method to prevent deletion.
"""
namespace = 'jabber:client' namespace = 'jabber:client'
name = 'message'
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject',
'mucroom', 'mucnick'))
sub_interfaces = set(('body', 'subject'))
plugin_attrib = name
types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
def getType(self): def getType(self):
return self.xml.attrib.get('type', 'normal') """
Return the message type.
Overrides default stanza interface behavior.
Returns 'normal' if no type attribute is present.
"""
return self._getAttr('type', 'normal')
def chat(self): def chat(self):
"""Set the message type to 'chat'."""
self['type'] = 'chat' self['type'] = 'chat'
return self return self
def normal(self): def normal(self):
"""Set the message type to 'chat'."""
self['type'] = 'normal' self['type'] = 'normal'
return self return self
def reply(self, body=None): def reply(self, body=None):
"""
Create a message reply.
Overrides StanzaBase.reply.
Sets proper 'to' attribute if the message is from a MUC, and
adds a message body if one is given.
Arguments:
body -- Optional text content for the message.
"""
StanzaBase.reply(self) StanzaBase.reply(self)
if self['type'] == 'groupchat': if self['type'] == 'groupchat':
self['to'] = self['to'].bare self['to'] = self['to'].bare
del self['id'] del self['id']
if body is not None: if body is not None:
self['body'] = body self['body'] = body
return self return self
def getMucroom(self): def getMucroom(self):
"""
Return the name of the MUC room where the message originated.
Read-only stanza interface.
"""
if self['type'] == 'groupchat': if self['type'] == 'groupchat':
return self['from'].bare return self['from'].bare
else: else:
return '' return ''
def setMucroom(self, value):
pass
def delMucroom(self):
pass
def getMucnick(self): def getMucnick(self):
"""
Return the nickname of the MUC user that sent the message.
Read-only stanza interface.
"""
if self['type'] == 'groupchat': if self['type'] == 'groupchat':
return self['from'].resource return self['from'].resource
else: else:
return '' return ''
def setMucroom(self, value):
"""Dummy method to prevent modification."""
pass
def delMucroom(self):
"""Dummy method to prevent deletion."""
pass
def setMucnick(self, value): def setMucnick(self, value):
"""Dummy method to prevent modification."""
pass pass
def delMucnick(self): def delMucnick(self):
"""Dummy method to prevent deletion."""
pass pass

View file

@ -3,24 +3,70 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from .. xmlstream.stanzabase import ElementBase, ET
from sleekxmpp.stanza import Message, Presence
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin
from sleekxmpp.xmlstream.stanzabase import ElementBase, ET
class Nick(ElementBase): class Nick(ElementBase):
"""
XEP-0172: User Nickname allows the addition of a <nick> element
in several stanza types, including <message> and <presence> stanzas.
The nickname contained in a <nick> should be the global, friendly or
informal name chosen by the owner of a bare JID. The <nick> element
may be included when establishing communications with new entities,
such as normal XMPP users or MUC services.
The nickname contained in a <nick> element will not necessarily be
the same as the nickname used in a MUC.
Example stanzas:
<message to="user@example.com">
<nick xmlns="http://jabber.org/nick/nick">The User</nick>
<body>...</body>
</message>
<presence to="otheruser@example.com" type="subscribe">
<nick xmlns="http://jabber.org/nick/nick">The User</nick>
</presence>
Stanza Interface:
nick -- A global, friendly or informal name chosen by a user.
Methods:
getNick -- Return the nickname in the <nick> element.
setNick -- Add a <nick> element with the given nickname.
delNick -- Remove the <nick> element.
"""
namespace = 'http://jabber.org/nick/nick' namespace = 'http://jabber.org/nick/nick'
name = 'nick' name = 'nick'
plugin_attrib = 'nick' plugin_attrib = name
interfaces = set(('nick')) interfaces = set(('nick',))
plugin_attrib_map = set()
plugin_xml_map = set()
def setNick(self, nick): def setNick(self, nick):
"""
Add a <nick> element with the given nickname.
Arguments:
nick -- A human readable, informal name.
"""
self.xml.text = nick self.xml.text = nick
def getNick(self): def getNick(self):
"""Return the nickname in the <nick> element."""
return self.xml.text return self.xml.text
def delNick(self): def delNick(self):
"""Remove the <nick> element."""
if self.parent is not None: if self.parent is not None:
self.parent().xml.remove(self.xml) self.parent().xml.remove(self.xml)
registerStanzaPlugin(Message, Nick)
registerStanzaPlugin(Presence, Nick)

View file

@ -3,59 +3,142 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET from sleekxmpp.stanza import Error
from . error import Error from sleekxmpp.stanza.rootstanza import RootStanza
from . rootstanza import RootStanza from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
class Presence(RootStanza): class Presence(RootStanza):
interfaces = set(('type', 'to', 'from', 'id', 'status', 'priority'))
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe', 'subscribed', 'unsubscribe', 'unsubscribed'))
showtypes = set(('dnd', 'chat', 'xa', 'away'))
sub_interfaces = set(('status', 'priority'))
name = 'presence'
plugin_attrib = name
namespace = 'jabber:client'
def getShowElement(self): """
return self.xml.find("{%s}show" % self.namespace) XMPP's <presence> stanza allows entities to know the status of other
clients and components. Since it is currently the only multi-cast
stanza in XMPP, many extensions add more information to <presence>
stanzas to broadcast to every entry in the roster, such as
capabilities, music choices, or locations (XEP-0115: Entity Capabilities
and XEP-0163: Personal Eventing Protocol).
Since <presence> stanzas are broadcast when an XMPP entity changes
its status, the bulk of the traffic in an XMPP network will be from
<presence> stanzas. Therefore, do not include more information than
necessary in a status message or within a <presence> stanza in order
to help keep the network running smoothly.
Example <presence> stanzas:
<presence />
<presence from="user@example.com">
<show>away</show>
<status>Getting lunch.</status>
<priority>5</priority>
</presence>
<presence type="unavailable" />
<presence to="user@otherhost.com" type="subscribe" />
Stanza Interface:
priority -- A value used by servers to determine message routing.
show -- The type of status, such as away or available for chat.
status -- Custom, human readable status message.
Attributes:
types -- One of: available, unavailable, error, probe,
subscribe, subscribed, unsubscribe,
and unsubscribed.
showtypes -- One of: away, chat, dnd, and xa.
Methods:
reply -- Overrides StanzaBase.reply
setShow -- Set the value of the <show> element.
getType -- Get the value of the type attribute or <show> element.
setType -- Set the value of the type attribute or <show> element.
getPriority -- Get the value of the <priority> element.
setPriority -- Set the value of the <priority> element.
"""
namespace = 'jabber:client'
name = 'presence'
interfaces = set(('type', 'to', 'from', 'id', 'show',
'status', 'priority'))
sub_interfaces = set(('show', 'status', 'priority'))
plugin_attrib = name
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
'subscribed', 'unsubscribe', 'unsubscribed'))
showtypes = set(('dnd', 'chat', 'xa', 'away'))
def setShow(self, show):
"""
Set the value of the <show> element.
Arguments:
show -- Must be one of: away, chat, dnd, or xa.
"""
if show in self.showtypes:
self._setSubText('show', text=show)
return self
def setType(self, value): def setType(self, value):
show = self.getShowElement() """
Set the type attribute's value, and the <show> element
if applicable.
Arguments:
value -- Must be in either self.types or self.showtypes.
"""
if value in self.types: if value in self.types:
if show is not None: self['show'] = None
self.xml.remove(show)
if value == 'available': if value == 'available':
value = '' value = ''
self._setAttr('type', value) self._setAttr('type', value)
elif value in self.showtypes: elif value in self.showtypes:
if show is None: self['show'] = value
show = ET.Element("{%s}show" % self.namespace)
self.xml.append(show)
show.text = value
return self return self
def setPriority(self, value): def setPriority(self, value):
"""
Set the entity's priority value. Some server use priority to
determine message routing behavior.
Bot clients should typically use a priority of 0 if the same
JID is used elsewhere by a human-interacting client.
Arguments:
value -- An integer value greater than or equal to 0.
"""
self._setSubText('priority', text=str(value)) self._setSubText('priority', text=str(value))
def getPriority(self): def getPriority(self):
"""
Return the value of the <presence> element as an integer.
"""
p = self._getSubText('priority') p = self._getSubText('priority')
if not p: p = 0 if not p:
p = 0
return int(p) return int(p)
def getType(self): def getType(self):
"""
Return the value of the <presence> stanza's type attribute, or
the value of the <show> element.
"""
out = self._getAttr('type') out = self._getAttr('type')
if not out: if not out:
show = self.getShowElement() out = self['show']
if show is not None:
out = show.text
if not out or out is None: if not out or out is None:
out = 'available' out = 'available'
return out return out
def reply(self): def reply(self):
"""
Set the appropriate presence reply type.
Overrides StanzaBase.reply.
"""
if self['type'] == 'unsubscribe': if self['type'] == 'unsubscribe':
self['type'] = 'unsubscribed' self['type'] = 'unsubscribed'
elif self['type'] == 'subscribe': elif self['type'] == 'subscribe':

View file

@ -3,34 +3,64 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from .. xmlstream.stanzabase import StanzaBase
from xml.etree import cElementTree as ET import logging
from . error import Error
from .. exceptions import XMPPError
import traceback import traceback
import sys import sys
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.stanza import Error
from sleekxmpp.xmlstream.stanzabase import ET, StanzaBase, registerStanzaPlugin
class RootStanza(StanzaBase): class RootStanza(StanzaBase):
def exception(self, e): #called when a handler raises an exception """
A top-level XMPP stanza in an XMLStream.
The RootStanza class provides a more XMPP specific exception
handler than provided by the generic StanzaBase class.
Methods:
exception -- Overrides StanzaBase.exception
"""
def exception(self, e):
"""
Create and send an error reply.
Typically called when an event handler raises an exception.
The error's type and text content are based on the exception
object's type and content.
Overrides StanzaBase.exception.
Arguments:
e -- Exception object
"""
self.reply() self.reply()
if isinstance(e, XMPPError): # we raised this deliberately if isinstance(e, XMPPError):
# We raised this deliberately
self['error']['condition'] = e.condition self['error']['condition'] = e.condition
self['error']['text'] = e.text self['error']['text'] = e.text
if e.extension is not None: # extended error tag if e.extension is not None:
extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args) # Extended error tag
self['error'].xml.append(extxml) extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension),
e.extension_args)
self['error'].append(extxml)
self['error']['type'] = e.etype self['error']['type'] = e.etype
else: # we probably didn't raise this on purpose, so send back a traceback else:
# We probably didn't raise this on purpose, so send a traceback
self['error']['condition'] = 'undefined-condition' self['error']['condition'] = 'undefined-condition'
if sys.version_info < (3, 0): if sys.version_info < (3, 0):
self['error']['text'] = "SleekXMPP got into trouble." self['error']['text'] = "SleekXMPP got into trouble."
else: else:
self['error']['text'] = traceback.format_tb(e.__traceback__) self['error']['text'] = traceback.format_tb(e.__traceback__)
logging.exception('Error handling {%s}%s stanza' %
(self.namespace, self.name))
self.send() self.send()
# all jabber:client root stanzas should have the error plugin
RootStanza.plugin_attrib_map['error'] = Error registerStanzaPlugin(RootStanza, Error)
RootStanza.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error

View file

@ -3,19 +3,57 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from .. xmlstream.stanzabase import ElementBase, ET, JID
import logging from sleekxmpp.stanza import Iq
from sleekxmpp.xmlstream import JID
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin
from sleekxmpp.xmlstream.stanzabase import ET, ElementBase
class Roster(ElementBase): class Roster(ElementBase):
"""
Example roster stanzas:
<iq type="set">
<query xmlns="jabber:iq:roster">
<item jid="user@example.com" subscription="both" name="User">
<group>Friends</group>
</item>
</query>
</iq>
Stanza Inteface:
items -- A dictionary of roster entries contained
in the stanza.
Methods:
getItems -- Return a dictionary of roster entries.
setItems -- Add <item> elements.
delItems -- Remove all <item> elements.
"""
namespace = 'jabber:iq:roster' namespace = 'jabber:iq:roster'
name = 'query' name = 'query'
plugin_attrib = 'roster' plugin_attrib = 'roster'
interfaces = set(('items',)) interfaces = set(('items',))
sub_interfaces = set()
def setItems(self, items): def setItems(self, items):
"""
Set the roster entries in the <roster> stanza.
Uses a dictionary using JIDs as keys, where each entry is itself
a dictionary that contains:
name -- An alias or nickname for the JID.
subscription -- The subscription type. Can be one of 'to',
'from', 'both', 'none', or 'remove'.
groups -- A list of group names to which the JID
has been assigned.
Arguments:
items -- A dictionary of roster entries.
"""
self.delItems() self.delItems()
for jid in items: for jid in items:
ijid = str(jid) ijid = str(jid)
@ -23,7 +61,9 @@ class Roster(ElementBase):
if 'subscription' in items[jid]: if 'subscription' in items[jid]:
item.attrib['subscription'] = items[jid]['subscription'] item.attrib['subscription'] = items[jid]['subscription']
if 'name' in items[jid]: if 'name' in items[jid]:
item.attrib['name'] = items[jid]['name'] name = items[jid]['name']
if name is not None:
item.attrib['name'] = name
if 'groups' in items[jid]: if 'groups' in items[jid]:
for group in items[jid]['groups']: for group in items[jid]['groups']:
groupxml = ET.Element('{jabber:iq:roster}group') groupxml = ET.Element('{jabber:iq:roster}group')
@ -33,6 +73,16 @@ class Roster(ElementBase):
return self return self
def getItems(self): def getItems(self):
"""
Return a dictionary of roster entries.
Each item is keyed using its JID, and contains:
name -- An assigned alias or nickname for the JID.
subscription -- The subscription type. Can be one of 'to',
'from', 'both', 'none', or 'remove'.
groups -- A list of group names to which the JID has
been assigned.
"""
items = {} items = {}
itemsxml = self.xml.findall('{jabber:iq:roster}item') itemsxml = self.xml.findall('{jabber:iq:roster}item')
if itemsxml is not None: if itemsxml is not None:
@ -49,5 +99,11 @@ class Roster(ElementBase):
return items return items
def delItems(self): def delItems(self):
"""
Remove all <item> elements from the roster stanza.
"""
for child in self.xml.getchildren(): for child in self.xml.getchildren():
self.xml.remove(child) self.xml.remove(child)
registerStanzaPlugin(Iq, Roster)

View file

@ -1,19 +1,9 @@
""" """
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
SleekXMPP is free software; you can redistribute it and/or modify See the file LICENSE for copying permission.
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
""" """
import logging import logging
@ -34,9 +24,9 @@ class testps(sleekxmpp.ClientXMPP):
self.registerPlugin('xep_0030') self.registerPlugin('xep_0030')
self.registerPlugin('xep_0060') self.registerPlugin('xep_0060')
self.registerPlugin('xep_0092') self.registerPlugin('xep_0092')
self.add_handler("<message xmlns='jabber:client'><event xmlns='http://jabber.org/protocol/pubsub#event' /></message>", self.pubsubEventHandler, threaded=True) self.add_handler("<message xmlns='jabber:client'><event xmlns='http://jabber.org/protocol/pubsub#event' /></message>", self.pubsubEventHandler, name='Pubsub Event', threaded=True)
self.add_event_handler("session_start", self.start, threaded=True) self.add_event_handler("session_start", self.start, threaded=True)
self.add_handler("<iq type='error' />", self.handleError) self.add_handler("<iq type='error' />", self.handleError, name='Iq Error')
self.events = Queue.Queue() self.events = Queue.Queue()
self.default_config = None self.default_config = None
self.ps = self.plugin['xep_0060'] self.ps = self.plugin['xep_0060']

View file

@ -0,0 +1,11 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.jid import JID
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase
from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from socket import _fileobject from socket import _fileobject
import socket import socket

View file

@ -0,0 +1,12 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.handler.callback import Callback
from sleekxmpp.xmlstream.handler.waiter import Waiter
from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback
from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
class BaseHandler(object): class BaseHandler(object):

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from . import base from . import base
import logging import logging

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from . import base from . import base
try: try:

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
import threading import threading
from . callback import Callback from . callback import Callback

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from . waiter import Waiter from . waiter import Waiter

121
sleekxmpp/xmlstream/jid.py Normal file
View file

@ -0,0 +1,121 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
class JID(object):
"""
A representation of a Jabber ID, or JID.
Each JID may have three components: a user, a domain, and an optional
resource. For example: user@domain/resource
When a resource is not used, the JID is called a bare JID.
The JID is a full JID otherwise.
Attributes:
jid -- Alias for 'full'.
full -- The value of the full JID.
bare -- The value of the bare JID.
user -- The username portion of the JID.
domain -- The domain name portion of the JID.
server -- Alias for 'domain'.
resource -- The resource portion of the JID.
Methods:
reset -- Use a new JID value.
regenerate -- Recreate the JID from its components.
"""
def __init__(self, jid):
"""Initialize a new JID"""
self.reset(jid)
def reset(self, jid):
"""
Start fresh from a new JID string.
Arguments:
jid - The new JID value.
"""
self._full = self._jid = str(jid)
self._domain = None
self._resource = None
self._user = None
self._bare = None
def __getattr__(self, name):
"""
Handle getting the JID values, using cache if available.
Arguments:
name -- One of: user, server, domain, resource,
full, or bare.
"""
if name == 'resource':
if self._resource is None:
self._resource = self._jid.split('/', 1)[-1]
return self._resource
elif name == 'user':
if self._user is None:
if '@' in self._jid:
self._user = self._jid.split('@', 1)[0]
else:
self._user = self._user
return self._user
elif name in ('server', 'domain'):
if self._domain is None:
self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0]
return self._domain
elif name == 'full':
return self._jid
elif name == 'bare':
if self._bare is None:
self._bare = self._jid.split('/', 1)[0]
return self._bare
def __setattr__(self, name, value):
"""
Edit a JID by updating it's individual values, resetting the
generated JID in the end.
Arguments:
name -- The name of the JID part. One of: user, domain,
server, resource, full, jid, or bare.
value -- The new value for the JID part.
"""
if name in ('resource', 'user', 'domain'):
object.__setattr__(self, "_%s" % name, value)
self.regenerate()
elif name == 'server':
self.domain = value
elif name in ('full', 'jid'):
self.reset(value)
elif name == 'bare':
if '@' in value:
u, d = value.split('@', 1)
object.__setattr__(self, "_user", u)
object.__setattr__(self, "_domain", d)
else:
object.__setattr__(self, "_domain", value)
self.regenerate()
else:
object.__setattr__(self, name, value)
def regenerate(self):
"""Generate a new JID based on current values, useful after editing."""
jid = ""
if self.user:
jid = "%s@" % self.user
jid += self.domain
if self.resource:
jid += "/%s" % self.resource
self.reset(jid)
def __str__(self):
"""Use the full JID as the string value."""
return self.full

View file

@ -0,0 +1,13 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream.matcher.id import MatcherId
from sleekxmpp.xmlstream.matcher.many import MatchMany
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
class MatcherBase(object): class MatcherBase(object):

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from . import base from . import base

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from . import base from . import base
from xml.etree import cElementTree from xml.etree import cElementTree

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from . import base from . import base
from xml.etree import cElementTree from xml.etree import cElementTree

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from . import base from . import base
from xml.etree import cElementTree from xml.etree import cElementTree

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from . import base from . import base
from xml.etree import cElementTree from xml.etree import cElementTree

View file

@ -3,44 +3,37 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from xml.etree import cElementTree as ET
import copy
import logging import logging
import traceback
import sys import sys
import weakref import weakref
from xml.etree import cElementTree as ET
if sys.version_info < (3,0): from sleekxmpp.xmlstream import JID
from . import tostring26 as tostring from sleekxmpp.xmlstream.tostring import tostring
else:
from . import tostring
xmltester = type(ET.Element('xml'))
class JID(object): # Used to check if an argument is an XML object.
def __init__(self, jid): XML_TYPE = type(ET.Element('xml'))
self.jid = jid
def __getattr__(self, name):
if name == 'resource':
return self.jid.split('/', 1)[-1]
elif name == 'user':
if '@' in self.jid:
return self.jid.split('@', 1)[0]
else:
return ''
elif name == 'server':
return self.jid.split('@', 1)[-1].split('/', 1)[0]
elif name == 'full':
return self.jid
elif name == 'bare':
return self.jid.split('/', 1)[0]
def __str__(self): def registerStanzaPlugin(stanza, plugin):
return self.jid """
Associate a stanza object as a plugin for another stanza.
class ElementBase(tostring.ToString): Arguments:
stanza -- The class of the parent stanza.
plugin -- The class of the plugin stanza.
"""
tag = "{%s}%s" % (plugin.namespace, plugin.name)
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map[tag] = plugin
class ElementBase(object):
name = 'stanza' name = 'stanza'
plugin_attrib = 'plugin' plugin_attrib = 'plugin'
namespace = 'jabber:client' namespace = 'jabber:client'
@ -52,24 +45,229 @@ class ElementBase(tostring.ToString):
subitem = None subitem = None
def __init__(self, xml=None, parent=None): def __init__(self, xml=None, parent=None):
if parent is None: """
self.parent = None Create a new stanza object.
else:
self.parent = weakref.ref(parent) Arguments:
xml -- Initialize the stanza with optional existing XML.
parent -- Optional stanza object that contains this stanza.
"""
self.xml = xml self.xml = xml
self.plugins = {} self.plugins = {}
self.iterables = [] self.iterables = []
self.idx = 0 self.idx = 0
if not self.setup(xml): if parent is None:
self.parent = None
else:
self.parent = weakref.ref(parent)
if self.setup(xml):
# If we generated our own XML, then everything is ready.
return
# Initialize values using provided XML
for child in self.xml.getchildren(): for child in self.xml.getchildren():
if child.tag in self.plugin_tag_map: if child.tag in self.plugin_tag_map:
self.plugins[self.plugin_tag_map[child.tag].plugin_attrib] = self.plugin_tag_map[child.tag](xml=child, parent=self) plugin = self.plugin_tag_map[child.tag]
self.plugins[plugin.plugin_attrib] = plugin(child, self)
if self.subitem is not None: if self.subitem is not None:
for sub in self.subitem: for sub in self.subitem:
if child.tag == "{%s}%s" % (sub.namespace, sub.name): if child.tag == "{%s}%s" % (sub.namespace, sub.name):
self.iterables.append(sub(xml=child, parent=self)) self.iterables.append(sub(child, self))
break break
def setup(self, xml=None):
"""
Initialize the stanza's XML contents.
Will return True if XML was generated according to the stanza's
definition.
Arguments:
xml -- Optional XML object to use for the stanza's content
instead of generating XML.
"""
if self.xml is None:
self.xml = xml
if self.xml is None:
# Generate XML from the stanza definition
for ename in self.name.split('/'):
new = ET.Element("{%s}%s" % (self.namespace, ename))
if self.xml is None:
self.xml = new
else:
last_xml.append(new)
last_xml = new
if self.parent is not None:
self.parent().xml.append(self.xml)
# We had to generate XML
return True
else:
# We did not generate XML
return False
def enable(self, attrib):
"""
Enable and initialize a stanza plugin.
Alias for initPlugin.
Arguments:
attrib -- The stanza interface for the plugin.
"""
return self.initPlugin(attrib)
def initPlugin(self, attrib):
"""
Enable and initialize a stanza plugin.
Arguments:
attrib -- The stanza interface for the plugin.
"""
if attrib not in self.plugins:
plugin_class = self.plugin_attrib_map[attrib]
self.plugins[attrib] = plugin_class(parent=self)
return self
def getStanzaValues(self):
"""
Return a dictionary of the stanza's interface values.
Stanza plugin values are included as nested dictionaries.
"""
values = {}
for interface in self.interfaces:
values[interface] = self[interface]
for plugin, stanza in self.plugins.items():
values[plugin] = stanza.getStanzaValues()
if self.iterables:
iterables = []
for stanza in self.iterables:
iterables.append(stanza.getStanzaValues())
iterables[-1].update({
'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)
})
values['substanzas'] = iterables
return values
def setStanzaValues(self, values):
"""
Set multiple stanza interface values using a dictionary.
Stanza plugin values may be set using nested dictionaries.
Arguments:
values -- A dictionary mapping stanza interface with values.
Plugin interfaces may accept a nested dictionary that
will be used recursively.
"""
for interface, value in values.items():
if interface == 'substanzas':
for subdict in value:
if '__childtag__' in subdict:
for subclass in self.subitem:
child_tag = "{%s}%s" % (subclass.namespace,
subclass.name)
if subdict['__childtag__'] == child_tag:
sub = subclass(parent=self)
sub.setStanzaValues(subdict)
self.iterables.append(sub)
break
elif interface in self.interfaces:
self[interface] = value
elif interface in self.plugin_attrib_map:
if interface not in self.plugins:
self.initPlugin(interface)
self.plugins[interface].setStanzaValues(value)
return self
def __getitem__(self, attrib):
"""
Return the value of a stanza interface using dictionary-like syntax.
Example:
>>> msg['body']
'Message contents'
Stanza interfaces are typically mapped directly to the underlying XML
object, but can be overridden by the presence of a getAttrib method
(or getFoo where the interface is named foo, etc).
The search order for interface value retrieval for an interface
named 'foo' is:
1. The list of substanzas.
2. The result of calling getFoo.
3. The contents of the foo subelement, if foo is a sub interface.
4. The value of the foo attribute of the XML object.
5. The plugin named 'foo'
6. An empty string.
Arguments:
attrib -- The name of the requested stanza interface.
"""
if attrib == 'substanzas':
return self.iterables
elif attrib in self.interfaces:
get_method = "get%s" % attrib.title()
if hasattr(self, get_method):
return getattr(self, get_method)()
else:
if attrib in self.sub_interfaces:
return self._getSubText(attrib)
else:
return self._getAttr(attrib)
elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins:
self.initPlugin(attrib)
return self.plugins[attrib]
else:
return ''
def __setitem__(self, attrib, value):
"""
Set the value of a stanza interface using dictionary-like syntax.
Example:
>>> msg['body'] = "Hi!"
>>> msg['body']
'Hi!'
Stanza interfaces are typically mapped directly to the underlying XML
object, but can be overridden by the presence of a setAttrib method
(or setFoo where the interface is named foo, etc).
The effect of interface value assignment for an interface
named 'foo' will be one of:
1. Delete the interface's contents if the value is None.
2. Call setFoo, if it exists.
3. Set the text of a foo element, if foo is in sub_interfaces.
4. Set the value of a top level XML attribute name foo.
5. Attempt to pass value to a plugin named foo using the plugin's
foo interface.
6. Do nothing.
Arguments:
attrib -- The name of the stanza interface to modify.
value -- The new value of the stanza interface.
"""
if attrib in self.interfaces:
if value is not None:
if hasattr(self, "set%s" % attrib.title()):
getattr(self, "set%s" % attrib.title())(value,)
else:
if attrib in self.sub_interfaces:
return self._setSubText(attrib, text=value)
else:
self._setAttr(attrib, value)
else:
self.__delitem__(attrib)
elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins:
self.initPlugin(attrib)
self.plugins[attrib][attrib] = value
return self
@property @property
def attrib(self): #backwards compatibility def attrib(self): #backwards compatibility
@ -97,7 +295,7 @@ class ElementBase(tostring.ToString):
def append(self, item): def append(self, item):
if not isinstance(item, ElementBase): if not isinstance(item, ElementBase):
if type(item) == xmltester: if type(item) == XML_TYPE:
return self.appendxml(item) return self.appendxml(item)
else: else:
raise TypeError raise TypeError
@ -154,65 +352,6 @@ class ElementBase(tostring.ToString):
def findall(self, xpath): def findall(self, xpath):
return self.xml.findall(xpath) return self.xml.findall(xpath)
def setup(self, xml=None):
if self.xml is None:
self.xml = xml
if self.xml is None:
for ename in self.name.split('/'):
new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace})
if self.xml is None:
self.xml = new
else:
self.xml.append(new)
if self.parent is not None:
self.parent().xml.append(self.xml)
return True #had to generate XML
else:
return False
def enable(self, attrib):
self.initPlugin(attrib)
return self
def initPlugin(self, attrib):
if attrib not in self.plugins:
self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self)
def __getitem__(self, attrib):
if attrib == 'substanzas':
return self.iterables
elif attrib in self.interfaces:
if hasattr(self, "get%s" % attrib.title()):
return getattr(self, "get%s" % attrib.title())()
else:
if attrib in self.sub_interfaces:
return self._getSubText(attrib)
else:
return self._getAttr(attrib)
elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: self.initPlugin(attrib)
return self.plugins[attrib]
else:
return ''
def __setitem__(self, attrib, value):
if attrib in self.interfaces:
if value is not None:
if hasattr(self, "set%s" % attrib.title()):
getattr(self, "set%s" % attrib.title())(value,)
else:
if attrib in self.sub_interfaces:
return self._setSubText(attrib, text=value)
else:
self._setAttr(attrib, value)
else:
self.__delitem__(attrib)
elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins: self.initPlugin(attrib)
self.initPlugin(attrib)
self.plugins[attrib][attrib] = value
return self
def __delitem__(self, attrib): def __delitem__(self, attrib):
if attrib.lower() in self.interfaces: if attrib.lower() in self.interfaces:
if hasattr(self, "del%s" % attrib.title()): if hasattr(self, "del%s" % attrib.title()):
@ -230,7 +369,7 @@ class ElementBase(tostring.ToString):
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, ElementBase): if not isinstance(other, ElementBase):
return False return False
values = self.getValues() values = self.getStanzaValues()
for key in other: for key in other:
if key not in values or values[key] != other[key]: if key not in values or values[key] != other[key]:
return False return False
@ -246,69 +385,50 @@ class ElementBase(tostring.ToString):
if name in self.xml.attrib: if name in self.xml.attrib:
del self.xml.attrib[name] del self.xml.attrib[name]
def _getAttr(self, name): def _getAttr(self, name, default=''):
return self.xml.attrib.get(name, '') return self.xml.attrib.get(name, default)
def _getSubText(self, name): def _getSubText(self, name):
stanza = self.xml.find("{%s}%s" % (self.namespace, name)) if '}' not in name:
name = "{%s}%s" % (self.namespace, name)
stanza = self.xml.find(name)
if stanza is None or stanza.text is None: if stanza is None or stanza.text is None:
return '' return ''
else: else:
return stanza.text return stanza.text
def _setSubText(self, name, attrib={}, text=None): def _setSubText(self, name, attrib={}, text=None):
if '}' not in name:
name = "{%s}%s" % (self.namespace, name)
if text is None or text == '': if text is None or text == '':
return self.__delitem__(name) return self.__delitem__(name)
stanza = self.xml.find("{%s}%s" % (self.namespace, name)) stanza = self.xml.find(name)
if stanza is None: if stanza is None:
#self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib)) stanza = ET.Element(name)
stanza = ET.Element("{%s}%s" % (self.namespace, name))
self.xml.append(stanza) self.xml.append(stanza)
stanza.text = text stanza.text = text
return stanza return stanza
def _delSub(self, name): def _delSub(self, name):
if '}' not in name:
name = "{%s}%s" % (self.namespace, name)
for child in self.xml.getchildren(): for child in self.xml.getchildren():
if child.tag == "{%s}%s" % (self.namespace, name): if child.tag == name:
self.xml.remove(child) self.xml.remove(child)
def getValues(self):
out = {}
for interface in self.interfaces:
out[interface] = self[interface]
for pluginkey in self.plugins:
out[pluginkey] = self.plugins[pluginkey].getValues()
if self.iterables:
iterables = []
for stanza in self.iterables:
iterables.append(stanza.getValues())
iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)})
out['substanzas'] = iterables
return out
def setValues(self, attrib):
for interface in attrib:
if interface == 'substanzas':
for subdict in attrib['substanzas']:
if '__childtag__' in subdict:
for subclass in self.subitem:
if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name):
sub = subclass(parent=self)
sub.setValues(subdict)
self.iterables.append(sub)
break
elif interface in self.interfaces:
self[interface] = attrib[interface]
elif interface in self.plugin_attrib_map and interface not in self.plugins:
self.initPlugin(interface)
if interface in self.plugins:
self.plugins[interface].setValues(attrib[interface])
return self
def appendxml(self, xml): def appendxml(self, xml):
self.xml.append(xml) self.xml.append(xml)
return self return self
def __copy__(self):
return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
def __str__(self):
return tostring(self.xml, xmlns='', stanza_ns=self.namespace)
def __repr__(self):
return self.__str__()
#def __del__(self): #prevents garbage collection of reference cycle #def __del__(self): #prevents garbage collection of reference cycle
# if self.parent is not None: # if self.parent is not None:
# self.parent.xml.remove(self.xml) # self.parent.xml.remove(self.xml)
@ -357,7 +477,12 @@ class StanzaBase(ElementBase):
return self return self
def reply(self): def reply(self):
# if it's a component, use from
if self.stream and hasattr(self.stream, "is_component") and self.stream.is_component:
self['from'], self['to'] = self['to'], self['from'] self['from'], self['to'] = self['to'], self['from']
else:
self['to'] = self['from']
del self['from']
self.clear() self.clear()
return self return self
@ -381,8 +506,13 @@ class StanzaBase(ElementBase):
pass pass
def exception(self, e): def exception(self, e):
logging.error(traceback.format_tb(e)) logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name))
def send(self): def send(self):
self.stream.sendRaw(self.__str__()) self.stream.sendRaw(self.__str__())
def __copy__(self):
return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream)
def __str__(self):
return tostring(self.xml, xmlns='', stanza_ns=self.namespace, stream=self.stream)

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from __future__ import with_statement from __future__ import with_statement
import threading import threading

View file

@ -0,0 +1,139 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import with_statement
import threading
class StateError(Exception):
"""Raised whenever a state transition was attempted but failed."""
class StateManager(object):
"""
At the very core of SleekXMPP there is a need to track various
library configuration settings, XML stream features, and the
network connection status. The state manager is responsible for
tracking this information in a thread-safe manner.
State 'variables' store the current state of these items as simple
string values or booleans. Changing those values must be done
according to transitions defined when creating the state variable.
If a state variable is given a value that is not allowed according
to the transition definitions, a StateError is raised. When a
valid value is assigned an event is raised named:
_state_changed_nameofthestatevariable
The event carries a dictionary containing the previous and the new
state values.
"""
def __init__(self, event_func=None):
"""
Initialize the state manager. The parameter event_func should be
the event() method of a SleekXMPP object in order to enable
_state_changed_* events.
"""
self.main_lock = threading.Lock()
self.locks = {}
self.state_variables = {}
if event_func is not None:
self.event = event_func
else:
self.event = lambda name, data: None
def add(self, name, default=False, values=None, transitions=None):
"""
Create a new state variable.
When transitions is specified, only those defined state change
transitions will be allowed.
When values is specified (and not transitions), any state changes
between those values are allowed.
If neither values nor transitions are defined, then the state variable
will be a binary switch between True and False.
"""
if name in self.state_variables:
raise IndexError("State variable %s already exists" % name)
self.locks[name] = threading.Lock()
with self.locks[name]:
var = {'value': default,
'default': default,
'transitions': {}}
if transitions is not None:
for start in transitions:
var['transitions'][start] = set(transitions[start])
elif values is not None:
values = set(values)
for value in values:
var['transitions'][value] = values
elif values is None:
var['transitions'] = {True: [False],
False: [True]}
self.state_variables[name] = var
def addStates(self, var_defs):
"""
Create multiple state variables at once.
"""
for var, data in var_defs:
self.add(var,
default=data.get('default', False),
values=data.get('values', None),
transitions=data.get('transitions', None))
def force_set(self, name, val):
"""
Force setting a state variable's value by overriding transition checks.
"""
with self.locks[name]:
self.state_variables[name]['value'] = val
def reset(self, name):
"""
Reset a state variable to its default value.
"""
with self.locks[name]:
default = self.state_variables[name]['default']
self.state_variables[name]['value'] = default
def __getitem__(self, name):
"""
Get the value of a state variable if it exists.
"""
with self.locks[name]:
if name not in self.state_variables:
raise IndexError("State variable %s does not exist" % name)
return self.state_variables[name]['value']
def __setitem__(self, name, val):
"""
Attempt to set the value of a state variable, but raise StateError
if the transition is undefined.
A _state_changed_* event is triggered after a successful transition.
"""
with self.locks[name]:
if name not in self.state_variables:
raise IndexError("State variable %s does not exist" % name)
current = self.state_variables[name]['value']
if current == val:
return
if val in self.state_variables[name]['transitions'][current]:
self.state_variables[name]['value'] = val
self.event('_state_changed_%s' % name, {'from': current, 'to': val})
else:
raise StateError("Can not transition from '%s' to '%s'" % (str(current), str(val)))

View file

@ -1,60 +1,19 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
class ToString(object): See the file LICENSE for copying permission.
def __str__(self, xml=None, xmlns='', stringbuffer=''): """
if xml is None:
xml = self.xml
newoutput = [stringbuffer]
#TODO respect ET mapped namespaces
itag = xml.tag.split('}', 1)[-1]
if '}' in xml.tag:
ixmlns = xml.tag.split('}', 1)[0][1:]
else:
ixmlns = ''
nsbuffer = ''
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):
nsbuffer = """ xmlns="%s\"""" % ixmlns
newoutput.append("<%s" % itag)
newoutput.append(nsbuffer)
for attrib in xml.attrib:
if '{' not in attrib:
newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib])))
if len(xml) or xml.text or xml.tail:
newoutput.append(">")
if xml.text:
newoutput.append(self.xmlesc(xml.text))
if len(xml):
for child in xml.getchildren():
newoutput.append(self.__str__(child, ixmlns))
newoutput.append("</%s>" % (itag, ))
if xml.tail:
newoutput.append(self.xmlesc(xml.tail))
elif xml.text:
newoutput.append(">%s</%s>" % (self.xmlesc(xml.text), itag))
else:
newoutput.append(" />")
return ''.join(newoutput)
def xmlesc(self, text): import sys
text = list(text)
cc = 0 # Import the correct tostring and xml_escape functions based on the Python
matches = ('&', '<', '"', '>', "'") # version in order to properly handle Unicode.
for c in text:
if c in matches: if sys.version_info < (3, 0):
if c == '&': from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape
text[cc] = '&amp;'
elif c == '<':
text[cc] = '&lt;'
elif c == '>':
text[cc] = '&gt;'
elif c == "'":
text[cc] = '&apos;'
else: else:
text[cc] = '&quot;' from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape
cc += 1
return ''.join(text) __all__ = ['tostring', 'xml_escape']

View file

@ -0,0 +1,95 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
"""
Serialize an XML object to a Unicode string.
If namespaces are provided using xmlns or stanza_ns, then elements
that use those namespaces will not include the xmlns attribute in
the output.
Arguments:
xml -- The XML object to serialize. If the value is None,
then the XML object contained in this stanza
object will be used.
xmlns -- Optional namespace of an element wrapping the XML
object.
stanza_ns -- The namespace of the stanza object that contains
the XML object.
stream -- The XML stream that generated the XML object.
outbuffer -- Optional buffer for storing serializations during
recursive calls.
"""
# Add previous results to the start of the output.
output = [outbuffer]
# Extract the element's tag name.
tag_name = xml.tag.split('}', 1)[-1]
# Extract the element's namespace if it is defined.
if '}' in xml.tag:
tag_xmlns = xml.tag.split('}', 1)[0][1:]
else:
tag_xmlns = ''
# Output the tag name and derived namespace of the element.
namespace = ''
if tag_xmlns not in ['', xmlns, stanza_ns]:
namespace = ' xmlns="%s"' % tag_xmlns
if stream and tag_xmlns in stream.namespace_map:
mapped_namespace = stream.namespace_map[tag_xmlns]
if mapped_namespace:
tag = "%s:%s" % (mapped_namespace, tag_name)
output.append("<%s" % tag_name)
output.append(namespace)
# Output escaped attribute values.
for attrib, value in xml.attrib.items():
if '{' not in attrib:
value = xml_escape(value)
output.append(' %s="%s"' % (attrib, value))
if len(xml) or xml.text:
# If there are additional child elements to serialize.
output.append(">")
if xml.text:
output.append(xml_escape(xml.text))
if len(xml):
for child in xml.getchildren():
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
output.append("</%s>" % tag_name)
elif xml.text:
# If we only have text content.
output.append(">%s</%s>" % (xml_escape(xml.text), tag_name))
else:
# Empty element.
output.append(" />")
if xml.tail:
# If there is additional text after the element.
output.append(xml_escape(xml.tail))
return ''.join(output)
def xml_escape(text):
"""
Convert special characters in XML to escape sequences.
Arguments:
text -- The XML text to convert.
"""
text = list(text)
escapes = {'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&apos;',
'"': '&quot;'}
for i, c in enumerate(text):
text[i] = escapes.get(c, c)
return ''.join(text)

View file

@ -0,0 +1,104 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import unicode_literals
import types
def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
"""
Serialize an XML object to a Unicode string.
If namespaces are provided using xmlns or stanza_ns, then elements
that use those namespaces will not include the xmlns attribute in
the output.
Arguments:
xml -- The XML object to serialize. If the value is None,
then the XML object contained in this stanza
object will be used.
xmlns -- Optional namespace of an element wrapping the XML
object.
stanza_ns -- The namespace of the stanza object that contains
the XML object.
stream -- The XML stream that generated the XML object.
outbuffer -- Optional buffer for storing serializations during
recursive calls.
"""
# Add previous results to the start of the output.
output = [outbuffer]
# Extract the element's tag name.
tag_name = xml.tag.split('}', 1)[-1]
# Extract the element's namespace if it is defined.
if '}' in xml.tag:
tag_xmlns = xml.tag.split('}', 1)[0][1:]
else:
tag_xmlns = u''
# Output the tag name and derived namespace of the element.
namespace = u''
if tag_xmlns not in ['', xmlns, stanza_ns]:
namespace = u' xmlns="%s"' % tag_xmlns
if stream and tag_xmlns in stream.namespace_map:
mapped_namespace = stream.namespace_map[tag_xmlns]
if mapped_namespace:
tag = u"%s:%s" % (mapped_namespace, tag_name)
output.append(u"<%s" % tag_name)
output.append(namespace)
# Output escaped attribute values.
for attrib, value in xml.attrib.items():
if '{' not in attrib:
value = xml_escape(value)
output.append(u' %s="%s"' % (attrib, value))
if len(xml) or xml.text:
# If there are additional child elements to serialize.
output.append(u">")
if xml.text:
output.append(xml_escape(xml.text))
if len(xml):
for child in xml.getchildren():
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
output.append(u"</%s>" % tag_name)
if xml.tail:
# If there is additional text after the element.
output.append(xml_escape(xml.tail))
elif xml.text:
# If we only have text content.
output.append(u">%s</%s>" % (xml_escape(xml.text), tag_name))
else:
# Empty element.
output.append(u" />")
if xml.tail:
# If there is additional text after the element.
output.append(xml_escape(xml.tail))
return u''.join(output)
def xml_escape(text):
"""
Convert special characters in XML to escape sequences.
Arguments:
text -- The XML text to convert.
"""
if type(text) != types.UnicodeType:
text = list(unicode(text, 'utf-8', 'ignore'))
else:
text = list(text)
escapes = {u'&': u'&amp;',
u'<': u'&lt;',
u'>': u'&gt;',
u"'": u'&apos;',
u'"': u'&quot;'}
for i, c in enumerate(text):
text[i] = escapes.get(c, c)
return u''.join(text)

View file

@ -1,65 +0,0 @@
import types
class ToString(object):
def __str__(self, xml=None, xmlns='', stringbuffer=''):
if xml is None:
xml = self.xml
newoutput = [stringbuffer]
#TODO respect ET mapped namespaces
itag = xml.tag.split('}', 1)[-1]
if '}' in xml.tag:
ixmlns = xml.tag.split('}', 1)[0][1:]
else:
ixmlns = ''
nsbuffer = ''
if xmlns != ixmlns and ixmlns != u'' and ixmlns != self.namespace:
if self.stream is not None and ixmlns in self.stream.namespace_map:
if self.stream.namespace_map[ixmlns] != u'':
itag = "%s:%s" % (self.stream.namespace_map[ixmlns], itag)
else:
nsbuffer = """ xmlns="%s\"""" % ixmlns
if ixmlns not in ('', xmlns, self.namespace):
nsbuffer = """ xmlns="%s\"""" % ixmlns
newoutput.append("<%s" % itag)
newoutput.append(nsbuffer)
for attrib in xml.attrib:
if '{' not in attrib:
newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib])))
if len(xml) or xml.text or xml.tail:
newoutput.append(u">")
if xml.text:
newoutput.append(self.xmlesc(xml.text))
if len(xml):
for child in xml.getchildren():
newoutput.append(self.__str__(child, ixmlns))
newoutput.append(u"</%s>" % (itag, ))
if xml.tail:
newoutput.append(self.xmlesc(xml.tail))
elif xml.text:
newoutput.append(">%s</%s>" % (self.xmlesc(xml.text), itag))
else:
newoutput.append(" />")
return u''.join(newoutput)
def xmlesc(self, text):
if type(text) != types.UnicodeType:
text = list(unicode(text, 'utf-8', 'ignore'))
else:
text = list(text)
cc = 0
matches = (u'&', u'<', u'"', u'>', u"'")
for c in text:
if c in matches:
if c == u'&':
text[cc] = u'&amp;'
elif c == u'<':
text[cc] = u'&lt;'
elif c == u'>':
text[cc] = u'&gt;'
elif c == u"'":
text[cc] = u'&apos;'
else:
text[cc] = u'&quot;'
cc += 1
return ''.join(text)

View file

@ -3,7 +3,7 @@
Copyright (C) 2010 Nathanael C. Fritz Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP. This file is part of SleekXMPP.
See the file license.txt for copying permission. See the file LICENSE for copying permission.
""" """
from __future__ import with_statement, unicode_literals from __future__ import with_statement, unicode_literals
@ -19,11 +19,13 @@ import logging
import socket import socket
import threading import threading
import time import time
import traceback
import types import types
import copy
import xml.sax.saxutils import xml.sax.saxutils
from . import scheduler from . import scheduler
from sleekxmpp.xmlstream.tostring import tostring
RESPONSE_TIMEOUT = 10
HANDLER_THREADS = 1 HANDLER_THREADS = 1
ssl_support = True ssl_support = True
@ -71,6 +73,7 @@ class XMLStream(object):
self.use_ssl = False self.use_ssl = False
self.use_tls = False self.use_tls = False
self.default_ns = ''
self.stream_header = "<stream>" self.stream_header = "<stream>"
self.stream_footer = "</stream>" self.stream_footer = "</stream>"
@ -194,14 +197,14 @@ class XMLStream(object):
return return
else: else:
self.state.set('processing', False) self.state.set('processing', False)
traceback.print_exc() logging.exception('Socket Error')
self.disconnect(reconnect=True) self.disconnect(reconnect=True)
except: except:
if not self.state.reconnect: if not self.state.reconnect:
return return
else: else:
self.state.set('processing', False) self.state.set('processing', False)
traceback.print_exc() logging.exception('Connection error. Reconnecting.')
self.disconnect(reconnect=True) self.disconnect(reconnect=True)
if self.state['reconnect']: if self.state['reconnect']:
self.reconnect() self.reconnect()
@ -257,8 +260,7 @@ class XMLStream(object):
logging.warning("Failed to send %s" % data) logging.warning("Failed to send %s" % data)
self.state.set('connected', False) self.state.set('connected', False)
if self.state.reconnect: if self.state.reconnect:
logging.error("Disconnected. Socket Error.") logging.exception("Disconnected. Socket Error.")
traceback.print_exc()
self.disconnect(reconnect=True) self.disconnect(reconnect=True)
def sendRaw(self, data): def sendRaw(self, data):
@ -303,21 +305,20 @@ class XMLStream(object):
def __spawnEvent(self, xmlobj): def __spawnEvent(self, xmlobj):
"watching xmlOut and processes handlers" "watching xmlOut and processes handlers"
#convert XML into Stanza #convert XML into Stanza
logging.debug("RECV: %s" % cElementTree.tostring(xmlobj)) logging.debug("RECV: %s" % tostring(xmlobj, xmlns=self.default_ns, stream=self))
xmlobj = self.incoming_filter(xmlobj) xmlobj = self.incoming_filter(xmlobj)
stanza = None stanza_type = StanzaBase
for stanza_class in self.__root_stanza: for stanza_class in self.__root_stanza:
if xmlobj.tag == "{%s}%s" % (self.default_ns, stanza_class.name): if xmlobj.tag == "{%s}%s" % (self.default_ns, stanza_class.name):
#if self.__root_stanza[stanza_class].match(xmlobj): stanza_type = stanza_class
stanza = stanza_class(self, xmlobj)
break break
if stanza is None:
stanza = StanzaBase(self, xmlobj)
unhandled = True unhandled = True
stanza = stanza_type(self, xmlobj)
for handler in self.__handlers: for handler in self.__handlers:
if handler.match(stanza): if handler.match(stanza):
handler.prerun(stanza) stanza_copy = stanza_type(self, copy.deepcopy(xmlobj))
self.eventqueue.put(('stanza', handler, stanza)) handler.prerun(stanza_copy)
self.eventqueue.put(('stanza', handler, stanza_copy))
if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler)) if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler))
unhandled = False unhandled = False
if unhandled: if unhandled:
@ -344,14 +345,14 @@ class XMLStream(object):
try: try:
handler.run(args[0]) handler.run(args[0])
except Exception as e: except Exception as e:
traceback.print_exc() logging.exception('Error processing event handler: %s' % handler.name)
args[0].exception(e) args[0].exception(e)
elif etype == 'schedule': elif etype == 'schedule':
try: try:
logging.debug(args) logging.debug(args)
handler(*args[0]) handler(*args[0])
except: except:
logging.error(traceback.format_exc()) logging.exception('Error processing scheduled task')
elif etype == 'quit': elif etype == 'quit':
logging.debug("Quitting eventRunner thread") logging.debug("Quitting eventRunner thread")
return False return False
@ -389,60 +390,6 @@ class XMLStream(object):
def removeStanzaExtension(self, stanza_class, stanza_extension): def removeStanzaExtension(self, stanza_class, stanza_extension):
stanza_extension[stanza_class].pop(stanza_extension) stanza_extension[stanza_class].pop(stanza_extension)
def tostring(self, xml, xmlns='', stringbuffer=''):
newoutput = [stringbuffer]
#TODO respect ET mapped namespaces
itag = xml.tag.split('}', 1)[-1]
if '}' in xml.tag:
ixmlns = xml.tag.split('}', 1)[0][1:]
else:
ixmlns = ''
nsbuffer = ''
if xmlns != ixmlns and ixmlns != '':
if ixmlns in self.namespace_map:
if self.namespace_map[ixmlns] != '':
itag = "%s:%s" % (self.namespace_map[ixmlns], itag)
else:
nsbuffer = """ xmlns="%s\"""" % ixmlns
newoutput.append("<%s" % itag)
newoutput.append(nsbuffer)
for attrib in xml.attrib:
newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib])))
if len(xml) or xml.text or xml.tail:
newoutput.append(">")
if xml.text:
newoutput.append(self.xmlesc(xml.text))
if len(xml):
for child in xml.getchildren():
newoutput.append(self.tostring(child, ixmlns))
newoutput.append("</%s>" % (itag, ))
if xml.tail:
newoutput.append(self.xmlesc(xml.tail))
elif xml.text:
newoutput.append(">%s</%s>" % (self.xmlesc(xml.text), itag))
else:
newoutput.append(" />")
return ''.join(newoutput)
def xmlesc(self, text):
text = list(text)
cc = 0
matches = ('&', '<', '"', '>', "'")
for c in text:
if c in matches:
if c == '&':
text[cc] = '&amp;'
elif c == '<':
text[cc] = '&lt;'
elif c == '>':
text[cc] = '&gt;'
elif c == "'":
text[cc] = '&apos;'
elif self.escape_quotes:
text[cc] = '&quot;'
cc += 1
return ''.join(text)
def start_stream_handler(self, xml): def start_stream_handler(self, xml):
"""Meant to be overridden""" """Meant to be overridden"""
pass pass

View file

@ -1,4 +1,4 @@
#!/usr/bin/python2.6 #!/usr/bin/env python
import unittest import unittest
import logging import logging
import sys import sys
@ -21,7 +21,7 @@ class testoverall(unittest.TestCase):
self.failIf(tabnanny.check("." + os.sep + 'sleekxmpp')) self.failIf(tabnanny.check("." + os.sep + 'sleekxmpp'))
#raise "Help!" #raise "Help!"
def testMethodLength(self): def disabled_testMethodLength(self):
"""Testing for excessive method lengths""" """Testing for excessive method lengths"""
import re import re
dirs = os.walk(sys.path[0] + os.sep + 'sleekxmpp') dirs = os.walk(sys.path[0] + os.sep + 'sleekxmpp')

519
tests/sleektest.py Normal file
View file

@ -0,0 +1,519 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import unittest
import socket
try:
import queue
except ImportError:
import Queue as queue
import sleekxmpp
from sleekxmpp import ClientXMPP
from sleekxmpp.stanza import Message, Iq, Presence
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ET
from sleekxmpp.xmlstream.tostring import tostring
class TestSocket(object):
"""
A dummy socket that reads and writes to queues instead
of an actual networking socket.
Methods:
nextSent -- Return the next sent stanza.
recvData -- Make a stanza available to read next.
recv -- Read the next stanza from the socket.
send -- Write a stanza to the socket.
makefile -- Dummy call, returns self.
read -- Read the next stanza from the socket.
"""
def __init__(self, *args, **kwargs):
"""
Create a new test socket.
Arguments:
Same as arguments for socket.socket
"""
self.socket = socket.socket(*args, **kwargs)
self.recv_queue = queue.Queue()
self.send_queue = queue.Queue()
def __getattr__(self, name):
"""
Return attribute values of internal, dummy socket.
Some attributes and methods are disabled to prevent the
socket from connecting to the network.
Arguments:
name -- Name of the attribute requested.
"""
def dummy(*args):
"""Method to do nothing and prevent actual socket connections."""
return None
overrides = {'connect': dummy,
'close': dummy,
'shutdown': dummy}
return overrides.get(name, getattr(self.socket, name))
# ------------------------------------------------------------------
# Testing Interface
def nextSent(self, timeout=None):
"""
Get the next stanza that has been 'sent'.
Arguments:
timeout -- Optional timeout for waiting for a new value.
"""
args = {'block': False}
if timeout is not None:
args = {'block': True, 'timeout': timeout}
try:
return self.send_queue.get(**args)
except:
return None
def recvData(self, data):
"""
Add data to the receiving queue.
Arguments:
data -- String data to 'write' to the socket to be received
by the XMPP client.
"""
self.recv_queue.put(data)
# ------------------------------------------------------------------
# Socket Interface
def recv(self, *args, **kwargs):
"""
Read a value from the received queue.
Arguments:
Placeholders. Same as for socket.Socket.recv.
"""
return self.read(block=True)
def send(self, data):
"""
Send data by placing it in the send queue.
Arguments:
data -- String value to write.
"""
self.send_queue.put(data)
# ------------------------------------------------------------------
# File Socket
def makefile(self, *args, **kwargs):
"""
File socket version to use with ElementTree.
Arguments:
Placeholders, same as socket.Socket.makefile()
"""
return self
def read(self, block=True, timeout=None, **kwargs):
"""
Implement the file socket interface.
Arguments:
block -- Indicate if the read should block until a
value is ready.
timeout -- Time in seconds a block should last before
returning None.
"""
if timeout is not None:
block = True
try:
return self.recv_queue.get(block, timeout)
except:
return None
class SleekTest(unittest.TestCase):
"""
A SleekXMPP specific TestCase class that provides
methods for comparing message, iq, and presence stanzas.
Methods:
Message -- Create a Message stanza object.
Iq -- Create an Iq stanza object.
Presence -- Create a Presence stanza object.
checkMessage -- Compare a Message stanza against an XML string.
checkIq -- Compare an Iq stanza against an XML string.
checkPresence -- Compare a Presence stanza against an XML string.
streamStart -- Initialize a dummy XMPP client.
streamRecv -- Queue data for XMPP client to receive.
streamSendMessage -- Check that the XMPP client sent the given
Message stanza.
streamSendIq -- Check that the XMPP client sent the given
Iq stanza.
streamSendPresence -- Check taht the XMPP client sent the given
Presence stanza.
streamClose -- Disconnect the XMPP client.
fix_namespaces -- Add top-level namespace to an XML object.
compare -- Compare XML objects against each other.
"""
# ------------------------------------------------------------------
# Shortcut methods for creating stanza objects
def Message(self, *args, **kwargs):
"""
Create a Message stanza.
Uses same arguments as StanzaBase.__init__
Arguments:
xml -- An XML object to use for the Message's values.
"""
return Message(None, *args, **kwargs)
def Iq(self, *args, **kwargs):
"""
Create an Iq stanza.
Uses same arguments as StanzaBase.__init__
Arguments:
xml -- An XML object to use for the Iq's values.
"""
return Iq(None, *args, **kwargs)
def Presence(self, *args, **kwargs):
"""
Create a Presence stanza.
Uses same arguments as StanzaBase.__init__
Arguments:
xml -- An XML object to use for the Iq's values.
"""
return Presence(None, *args, **kwargs)
# ------------------------------------------------------------------
# Methods for comparing stanza objects to XML strings
def checkStanza(self, stanza_class, stanza, xml_string,
defaults=None, use_values=True):
"""
Create and compare several stanza objects to a correct XML string.
If use_values is False, test using getStanzaValues() and
setStanzaValues() will not be used.
Some stanzas provide default values for some interfaces, but
these defaults can be problematic for testing since they can easily
be forgotten when supplying the XML string. A list of interfaces that
use defaults may be provided and the generated stanzas will use the
default values for those interfaces if needed.
However, correcting the supplied XML is not possible for interfaces
that add or remove XML elements. Only interfaces that map to XML
attributes may be set using the defaults parameter. The supplied XML
must take into account any extra elements that are included by default.
Arguments:
stanza_class -- The class of the stanza being tested.
stanza -- The stanza object to test.
xml_string -- A string version of the correct XML expected.
defaults -- A list of stanza interfaces that have default
values. These interfaces will be set to their
defaults for the given and generated stanzas to
prevent unexpected test failures.
use_values -- Indicates if testing using getStanzaValues() and
setStanzaValues() should be used. Defaults to
True.
"""
xml = ET.fromstring(xml_string)
# Ensure that top level namespaces are used, even if they
# were not provided.
self.fix_namespaces(stanza.xml, 'jabber:client')
self.fix_namespaces(xml, 'jabber:client')
stanza2 = stanza_class(xml=xml)
if use_values:
# Using getStanzaValues() and setStanzaValues() will add
# XML for any interface that has a default value. We need
# to set those defaults on the existing stanzas and XML
# so that they will compare correctly.
default_stanza = stanza_class()
if defaults is None:
defaults = []
for interface in defaults:
stanza[interface] = stanza[interface]
stanza2[interface] = stanza2[interface]
# Can really only automatically add defaults for top
# level attribute values. Anything else must be accounted
# for in the provided XML string.
if interface not in xml.attrib:
if interface in default_stanza.xml.attrib:
value = default_stanza.xml.attrib[interface]
xml.attrib[interface] = value
values = stanza2.getStanzaValues()
stanza3 = stanza_class()
stanza3.setStanzaValues(values)
debug = "Three methods for creating stanzas do not match.\n"
debug += "Given XML:\n%s\n" % tostring(xml)
debug += "Given stanza:\n%s\n" % tostring(stanza.xml)
debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml)
debug += "Second generated stanza:\n%s\n" % tostring(stanza3.xml)
result = self.compare(xml, stanza.xml, stanza2.xml, stanza3.xml)
else:
debug = "Two methods for creating stanzas do not match.\n"
debug += "Given XML:\n%s\n" % tostring(xml)
debug += "Given stanza:\n%s\n" % tostring(stanza.xml)
debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml)
result = self.compare(xml, stanza.xml, stanza2.xml)
self.failUnless(result, debug)
def checkMessage(self, msg, xml_string, use_values=True):
"""
Create and compare several message stanza objects to a
correct XML string.
If use_values is False, the test using getStanzaValues() and
setStanzaValues() will not be used.
Arguments:
msg -- The Message stanza object to check.
xml_string -- The XML contents to compare against.
use_values -- Indicates if the test using getStanzaValues
and setStanzaValues should be used. Defaults
to True.
"""
return self.checkStanza(Message, msg, xml_string,
defaults=['type'],
use_values = use_values)
def checkIq(self, iq, xml_string, use_values=True):
"""
Create and compare several iq stanza objects to a
correct XML string.
If use_values is False, the test using getStanzaValues() and
setStanzaValues() will not be used.
Arguments:
iq -- The Iq stanza object to check.
xml_string -- The XML contents to compare against.
use_values -- Indicates if the test using getStanzaValues
and setStanzaValues should be used. Defaults
to True.
"""
return self.checkStanza(Iq, iq, xml_string, use_values=use_values)
def checkPresence(self, pres, xml_string, use_values=True):
"""
Create and compare several presence stanza objects to a
correct XML string.
If use_values is False, the test using getStanzaValues() and
setStanzaValues() will not be used.
Arguments:
iq -- The Iq stanza object to check.
xml_string -- The XML contents to compare against.
use_values -- Indicates if the test using getStanzaValues
and setStanzaValues should be used. Defaults
to True.
"""
return self.checkStanza(Presence, pres, xml_string,
defaults=['priority'],
use_values=use_values)
# ------------------------------------------------------------------
# Methods for simulating stanza streams.
def streamStart(self, mode='client', skip=True):
"""
Initialize an XMPP client or component using a dummy XML stream.
Arguments:
mode -- Either 'client' or 'component'. Defaults to 'client'.
skip -- Indicates if the first item in the sent queue (the
stream header) should be removed. Tests that wish
to test initializing the stream should set this to
False. Otherwise, the default of True should be used.
"""
if mode == 'client':
self.xmpp = ClientXMPP('tester@localhost', 'test')
self.xmpp.setSocket(TestSocket())
self.xmpp.state.set('reconnect', False)
self.xmpp.state.set('is client', True)
self.xmpp.state.set('connected', True)
# Must have the stream header ready for xmpp.process() to work
self.xmpp.socket.recvData(self.xmpp.stream_header)
self.xmpp.connectTCP = lambda a, b, c, d: True
self.xmpp.startTLS = lambda: True
self.xmpp.process(threaded=True)
if skip:
# Clear startup stanzas
self.xmpp.socket.nextSent(timeout=0.01)
def streamRecv(self, data):
"""
Pass data to the dummy XMPP client as if it came from an XMPP server.
Arguments:
data -- String stanza XML to be received and processed by the
XMPP client or component.
"""
data = str(data)
self.xmpp.socket.recvData(data)
def streamSendMessage(self, data, use_values=True, timeout=.1):
"""
Check that the XMPP client sent the given stanza XML.
Extracts the next sent stanza and compares it with the given
XML using checkMessage.
Arguments:
data -- The XML string of the expected Message stanza,
or an equivalent stanza object.
use_values -- Modifies the type of tests used by checkMessage.
timeout -- Time in seconds to wait for a stanza before
failing the check.
"""
if isinstance(data, str):
data = self.Message(xml=ET.fromstring(data))
sent = self.xmpp.socket.nextSent(timeout)
self.checkMessage(data, sent, use_values)
def streamSendIq(self, data, use_values=True, timeout=.1):
"""
Check that the XMPP client sent the given stanza XML.
Extracts the next sent stanza and compares it with the given
XML using checkIq.
Arguments:
data -- The XML string of the expected Iq stanza,
or an equivalent stanza object.
use_values -- Modifies the type of tests used by checkIq.
timeout -- Time in seconds to wait for a stanza before
failing the check.
"""
if isinstance(data, str):
data = self.Iq(xml=ET.fromstring(data))
sent = self.xmpp.socket.nextSent(timeout)
self.checkIq(data, sent, use_values)
def streamSendPresence(self, data, use_values=True, timeout=.1):
"""
Check that the XMPP client sent the given stanza XML.
Extracts the next sent stanza and compares it with the given
XML using checkPresence.
Arguments:
data -- The XML string of the expected Presence stanza,
or an equivalent stanza object.
use_values -- Modifies the type of tests used by checkPresence.
timeout -- Time in seconds to wait for a stanza before
failing the check.
"""
if isinstance(data, str):
data = self.Presence(xml=ET.fromstring(data))
sent = self.xmpp.socket.nextSent(timeout)
self.checkPresence(data, sent, use_values)
def streamClose(self):
"""
Disconnect the dummy XMPP client.
Can be safely called even if streamStart has not been called.
Must be placed in the tearDown method of a test class to ensure
that the XMPP client is disconnected after an error.
"""
if hasattr(self, 'xmpp') and self.xmpp is not None:
self.xmpp.disconnect()
self.xmpp.socket.recvData(self.xmpp.stream_footer)
# ------------------------------------------------------------------
# XML Comparison and Cleanup
def fix_namespaces(self, xml, ns):
"""
Assign a namespace to an element and any children that
don't have a namespace.
Arguments:
xml -- The XML object to fix.
ns -- The namespace to add to the XML object.
"""
if xml.tag.startswith('{'):
return
xml.tag = '{%s}%s' % (ns, xml.tag)
for child in xml.getchildren():
self.fix_namespaces(child, ns)
def compare(self, xml, *other):
"""
Compare XML objects.
Arguments:
xml -- The XML object to compare against.
*other -- The list of XML objects to compare.
"""
if not other:
return False
# Compare multiple objects
if len(other) > 1:
for xml2 in other:
if not self.compare(xml, xml2):
return False
return True
other = other[0]
# Step 1: Check tags
if xml.tag != other.tag:
return False
# Step 2: Check attributes
if xml.attrib != other.attrib:
return False
# Step 3: Recursively check children
for child in xml:
child2s = other.findall("%s" % child.tag)
if child2s is None:
return False
for child2 in child2s:
if self.compare(child, child2):
break
else:
return False
# Everything matches
return True

111
tests/test_addresses.py Normal file
View file

@ -0,0 +1,111 @@
from . sleektest import *
import sleekxmpp.plugins.xep_0033 as xep_0033
class TestAddresses(SleekTest):
def setUp(self):
registerStanzaPlugin(Message, xep_0033.Addresses)
def testAddAddress(self):
"""Testing adding extended stanza address."""
msg = self.Message()
msg['addresses'].addAddress(atype='to', jid='to@header1.org')
self.checkMessage(msg, """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address jid="to@header1.org" type="to" />
</addresses>
</message>
""")
msg = self.Message()
msg['addresses'].addAddress(atype='replyto',
jid='replyto@header1.org',
desc='Reply address')
self.checkMessage(msg, """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address jid="replyto@header1.org" type="replyto" desc="Reply address" />
</addresses>
</message>
""")
def testAddAddresses(self):
"""Testing adding multiple extended stanza addresses."""
xmlstring = """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address jid="replyto@header1.org" type="replyto" desc="Reply address" />
<address jid="cc@header2.org" type="cc" />
<address jid="bcc@header2.org" type="bcc" />
</addresses>
</message>
"""
msg = self.Message()
msg['addresses'].setAddresses([
{'type':'replyto',
'jid':'replyto@header1.org',
'desc':'Reply address'},
{'type':'cc',
'jid':'cc@header2.org'},
{'type':'bcc',
'jid':'bcc@header2.org'}])
self.checkMessage(msg, xmlstring)
msg = self.Message()
msg['addresses']['replyto'] = [{'jid':'replyto@header1.org',
'desc':'Reply address'}]
msg['addresses']['cc'] = [{'jid':'cc@header2.org'}]
msg['addresses']['bcc'] = [{'jid':'bcc@header2.org'}]
self.checkMessage(msg, xmlstring)
def testAddURI(self):
"""Testing adding URI attribute to extended stanza address."""
msg = self.Message()
addr = msg['addresses'].addAddress(atype='to',
jid='to@header1.org',
node='foo')
self.checkMessage(msg, """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address node="foo" jid="to@header1.org" type="to" />
</addresses>
</message>
""")
addr['uri'] = 'mailto:to@header2.org'
self.checkMessage(msg, """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address type="to" uri="mailto:to@header2.org" />
</addresses>
</message>
""")
def testDelivered(self):
"""Testing delivered attribute of extended stanza addresses."""
xmlstring = """
<message>
<addresses xmlns="http://jabber.org/protocol/address">
<address %s jid="to@header1.org" type="to" />
</addresses>
</message>
"""
msg = self.Message()
addr = msg['addresses'].addAddress(jid='to@header1.org', atype='to')
self.checkMessage(msg, xmlstring % '')
addr['delivered'] = True
self.checkMessage(msg, xmlstring % 'delivered="true"')
addr['delivered'] = False
self.checkMessage(msg, xmlstring % '')
suite = unittest.TestLoader().loadTestsFromTestCase(TestAddresses)

44
tests/test_chatstates.py Normal file
View file

@ -0,0 +1,44 @@
from . sleektest import *
import sleekxmpp.plugins.xep_0085 as xep_0085
class TestChatStates(SleekTest):
def setUp(self):
registerStanzaPlugin(Message, xep_0085.Active)
registerStanzaPlugin(Message, xep_0085.Composing)
registerStanzaPlugin(Message, xep_0085.Gone)
registerStanzaPlugin(Message, xep_0085.Inactive)
registerStanzaPlugin(Message, xep_0085.Paused)
def testCreateChatState(self):
"""Testing creating chat states."""
xmlstring = """
<message>
<%s xmlns="http://jabber.org/protocol/chatstates" />
</message>
"""
msg = self.Message()
msg['chat_state'].active()
self.checkMessage(msg, xmlstring % 'active',
use_values=False)
msg['chat_state'].composing()
self.checkMessage(msg, xmlstring % 'composing',
use_values=False)
msg['chat_state'].gone()
self.checkMessage(msg, xmlstring % 'gone',
use_values=False)
msg['chat_state'].inactive()
self.checkMessage(msg, xmlstring % 'inactive',
use_values=False)
msg['chat_state'].paused()
self.checkMessage(msg, xmlstring % 'paused',
use_values=False)
suite = unittest.TestLoader().loadTestsFromTestCase(TestChatStates)

View file

@ -1,96 +1,118 @@
import unittest from . sleektest import *
from xml.etree import cElementTree as ET import sleekxmpp.plugins.xep_0030 as xep_0030
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
from . import xmlcompare
import sleekxmpp.plugins.xep_0030 as sd
def stanzaPlugin(stanza, plugin): class TestDisco(SleekTest):
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
class testdisco(unittest.TestCase):
def setUp(self): def setUp(self):
self.sd = sd registerStanzaPlugin(Iq, xep_0030.DiscoInfo)
stanzaPlugin(self.sd.Iq, self.sd.DiscoInfo) registerStanzaPlugin(Iq, xep_0030.DiscoItems)
stanzaPlugin(self.sd.Iq, self.sd.DiscoItems)
def try3Methods(self, xmlstring, iq):
iq2 = self.sd.Iq(None, self.sd.ET.fromstring(xmlstring))
values = iq2.getValues()
iq3 = self.sd.Iq()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3), str(iq)+"3 methods for creating stanza don't match")
def testCreateInfoQueryNoNode(self): def testCreateInfoQueryNoNode(self):
"""Testing disco#info query with no node.""" """Testing disco#info query with no node."""
iq = self.sd.Iq() iq = self.Iq()
iq['id'] = "0" iq['id'] = "0"
iq['disco_info']['node'] = '' iq['disco_info']['node'] = ''
xmlstring = """<iq id="0"><query xmlns="http://jabber.org/protocol/disco#info" /></iq>"""
self.try3Methods(xmlstring, iq) self.checkIq(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#info" />
</iq>
""")
def testCreateInfoQueryWithNode(self): def testCreateInfoQueryWithNode(self):
"""Testing disco#info query with a node.""" """Testing disco#info query with a node."""
iq = self.sd.Iq() iq = self.Iq()
iq['id'] = "0" iq['id'] = "0"
iq['disco_info']['node'] = 'foo' iq['disco_info']['node'] = 'foo'
xmlstring = """<iq id="0"><query xmlns="http://jabber.org/protocol/disco#info" node="foo" /></iq>"""
self.try3Methods(xmlstring, iq) self.checkIq(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#info" node="foo" />
</iq>
""")
def testCreateInfoQueryNoNode(self): def testCreateInfoQueryNoNode(self):
"""Testing disco#items query with no node.""" """Testing disco#items query with no node."""
iq = self.sd.Iq() iq = self.Iq()
iq['id'] = "0" iq['id'] = "0"
iq['disco_items']['node'] = '' iq['disco_items']['node'] = ''
xmlstring = """<iq id="0"><query xmlns="http://jabber.org/protocol/disco#items" /></iq>"""
self.try3Methods(xmlstring, iq) self.checkIq(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#items" />
</iq>
""")
def testCreateItemsQueryWithNode(self): def testCreateItemsQueryWithNode(self):
"""Testing disco#items query with a node.""" """Testing disco#items query with a node."""
iq = self.sd.Iq() iq = self.Iq()
iq['id'] = "0" iq['id'] = "0"
iq['disco_items']['node'] = 'foo' iq['disco_items']['node'] = 'foo'
xmlstring = """<iq id="0"><query xmlns="http://jabber.org/protocol/disco#items" node="foo" /></iq>"""
self.try3Methods(xmlstring, iq) self.checkIq(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#items" node="foo" />
</iq>
""")
def testInfoIdentities(self): def testInfoIdentities(self):
"""Testing adding identities to disco#info.""" """Testing adding identities to disco#info."""
iq = self.sd.Iq() iq = self.Iq()
iq['id'] = "0" iq['id'] = "0"
iq['disco_info']['node'] = 'foo' iq['disco_info']['node'] = 'foo'
iq['disco_info'].addIdentity('conference', 'text', 'Chatroom') iq['disco_info'].addIdentity('conference', 'text', 'Chatroom')
xmlstring = """<iq id="0"><query xmlns="http://jabber.org/protocol/disco#info" node="foo"><identity category="conference" type="text" name="Chatroom" /></query></iq>"""
self.try3Methods(xmlstring, iq) self.checkIq(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#info" node="foo">
<identity category="conference" type="text" name="Chatroom" />
</query>
</iq>
""")
def testInfoFeatures(self): def testInfoFeatures(self):
"""Testing adding features to disco#info.""" """Testing adding features to disco#info."""
iq = self.sd.Iq() iq = self.Iq()
iq['id'] = "0" iq['id'] = "0"
iq['disco_info']['node'] = 'foo' iq['disco_info']['node'] = 'foo'
iq['disco_info'].addFeature('foo') iq['disco_info'].addFeature('foo')
iq['disco_info'].addFeature('bar') iq['disco_info'].addFeature('bar')
xmlstring = """<iq id="0"><query xmlns="http://jabber.org/protocol/disco#info" node="foo"><feature var="foo" /><feature var="bar" /></query></iq>"""
self.try3Methods(xmlstring, iq) self.checkIq(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#info" node="foo">
<feature var="foo" />
<feature var="bar" />
</query>
</iq>
""")
def testItems(self): def testItems(self):
"""Testing adding features to disco#info.""" """Testing adding features to disco#info."""
iq = self.sd.Iq() iq = self.Iq()
iq['id'] = "0" iq['id'] = "0"
iq['disco_items']['node'] = 'foo' iq['disco_items']['node'] = 'foo'
iq['disco_items'].addItem('user@localhost') iq['disco_items'].addItem('user@localhost')
iq['disco_items'].addItem('user@localhost', 'foo') iq['disco_items'].addItem('user@localhost', 'foo')
iq['disco_items'].addItem('user@localhost', 'bar', 'Testing') iq['disco_items'].addItem('user@localhost', 'bar', 'Testing')
xmlstring = """<iq id="0"><query xmlns="http://jabber.org/protocol/disco#items" node="foo"><item jid="user@localhost" /><item node="foo" jid="user@localhost" /><item node="bar" jid="user@localhost" name="Testing" /></query></iq>"""
self.try3Methods(xmlstring, iq) self.checkIq(iq, """
<iq id="0">
<query xmlns="http://jabber.org/protocol/disco#items" node="foo">
<item jid="user@localhost" />
<item node="foo" jid="user@localhost" />
<item node="bar" jid="user@localhost" name="Testing" />
</query>
</iq>
""")
def testAddRemoveIdentities(self): def testAddRemoveIdentities(self):
"""Test adding and removing identities to disco#info stanza""" """Test adding and removing identities to disco#info stanza"""
ids = [('automation', 'commands', 'AdHoc'), ids = [('automation', 'commands', 'AdHoc'),
('conference', 'text', 'ChatRoom')] ('conference', 'text', 'ChatRoom')]
info = self.sd.DiscoInfo() info = xep_0030.DiscoInfo()
info.addIdentity(*ids[0]) info.addIdentity(*ids[0])
self.failUnless(info.getIdentities() == [ids[0]]) self.failUnless(info.getIdentities() == [ids[0]])
@ -110,7 +132,7 @@ class testdisco(unittest.TestCase):
"""Test adding and removing features to disco#info stanza""" """Test adding and removing features to disco#info stanza"""
features = ['foo', 'bar', 'baz'] features = ['foo', 'bar', 'baz']
info = self.sd.DiscoInfo() info = xep_0030.DiscoInfo()
info.addFeature(features[0]) info.addFeature(features[0])
self.failUnless(info.getFeatures() == [features[0]]) self.failUnless(info.getFeatures() == [features[0]])
@ -132,7 +154,7 @@ class testdisco(unittest.TestCase):
('user@localhost', 'foo', None), ('user@localhost', 'foo', None),
('user@localhost', 'bar', 'Test')] ('user@localhost', 'bar', 'Test')]
info = self.sd.DiscoItems() info = xep_0030.DiscoItems()
self.failUnless(True, ""+str(items[0])) self.failUnless(True, ""+str(items[0]))
info.addItem(*(items[0])) info.addItem(*(items[0]))
@ -151,5 +173,4 @@ class testdisco(unittest.TestCase):
self.failUnless(info.getItems() == []) self.failUnless(info.getItems() == [])
suite = unittest.TestLoader().loadTestsFromTestCase(TestDisco)
suite = unittest.TestLoader().loadTestsFromTestCase(testdisco)

193
tests/test_elementbase.py Normal file
View file

@ -0,0 +1,193 @@
from . sleektest import *
from sleekxmpp.xmlstream.stanzabase import ElementBase
class TestElementBase(SleekTest):
def testExtendedName(self):
"""Test element names of the form tag1/tag2/tag3."""
class TestStanza(ElementBase):
name = "foo/bar/baz"
namespace = "test"
stanza = TestStanza()
self.checkStanza(TestStanza, stanza, """
<foo xmlns="test">
<bar>
<baz />
</bar>
</foo>
""")
def testGetStanzaValues(self):
"""Test getStanzaValues using plugins and substanzas."""
class TestStanzaPlugin(ElementBase):
name = "foo2"
namespace = "foo"
interfaces = set(('bar', 'baz'))
plugin_attrib = "foo2"
class TestSubStanza(ElementBase):
name = "subfoo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
subitem = set((TestSubStanza,))
registerStanzaPlugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
stanza['bar'] = 'a'
stanza['foo2']['baz'] = 'b'
substanza = TestSubStanza()
substanza['bar'] = 'c'
stanza.append(substanza)
values = stanza.getStanzaValues()
expected = {'bar': 'a',
'baz': '',
'foo2': {'bar': '',
'baz': 'b'},
'substanzas': [{'__childtag__': '{foo}subfoo',
'bar': 'c',
'baz': ''}]}
self.failUnless(values == expected,
"Unexpected stanza values:\n%s\n%s" % (str(expected), str(values)))
def testSetStanzaValues(self):
"""Test using setStanzaValues with substanzas and plugins."""
class TestStanzaPlugin(ElementBase):
name = "pluginfoo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
plugin_attrib = "plugin_foo"
class TestStanzaPlugin2(ElementBase):
name = "pluginfoo2"
namespace = "foo"
interfaces = set(('bar', 'baz'))
plugin_attrib = "plugin_foo2"
class TestSubStanza(ElementBase):
name = "subfoo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
subitem = set((TestSubStanza,))
registerStanzaPlugin(TestStanza, TestStanzaPlugin)
registerStanzaPlugin(TestStanza, TestStanzaPlugin2)
stanza = TestStanza()
values = {'bar': 'a',
'baz': '',
'plugin_foo': {'bar': '',
'baz': 'b'},
'plugin_foo2': {'bar': 'd',
'baz': 'e'},
'substanzas': [{'__childtag__': '{foo}subfoo',
'bar': 'c',
'baz': ''}]}
stanza.setStanzaValues(values)
self.checkStanza(TestStanza, stanza, """
<foo xmlns="foo" bar="a">
<pluginfoo baz="b" />
<pluginfoo2 bar="d" baz="e" />
<subfoo bar="c" />
</foo>
""")
def testGetItem(self):
"""Test accessing stanza interfaces."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz', 'qux'))
sub_interfaces = set(('baz',))
def getQux(self):
return 'qux'
class TestStanzaPlugin(ElementBase):
name = "foobar"
namespace = "foo"
plugin_attrib = "foobar"
interfaces = set(('fizz',))
TestStanza.subitem = (TestStanza,)
registerStanzaPlugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
substanza = TestStanza()
stanza.append(substanza)
stanza.setStanzaValues({'bar': 'a',
'baz': 'b',
'qux': 42,
'foobar': {'fizz': 'c'}})
# Test non-plugin interfaces
expected = {'substanzas': [substanza],
'bar': 'a',
'baz': 'b',
'qux': 'qux',
'meh': ''}
for interface, value in expected.items():
result = stanza[interface]
self.failUnless(result == value,
"Incorrect stanza interface access result: %s" % result)
# Test plugin interfaces
self.failUnless(isinstance(stanza['foobar'], TestStanzaPlugin),
"Incorrect plugin object result.")
self.failUnless(stanza['foobar']['fizz'] == 'c',
"Incorrect plugin subvalue result.")
def testSetItem(self):
"""Test assigning to stanza interfaces."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz', 'qux'))
sub_interfaces = set(('baz',))
def setQux(self, value):
pass
class TestStanzaPlugin(ElementBase):
name = "foobar"
namespace = "foo"
plugin_attrib = "foobar"
interfaces = set(('foobar',))
registerStanzaPlugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza()
stanza['bar'] = 'attribute!'
stanza['baz'] = 'element!'
stanza['qux'] = 'overridden'
stanza['foobar'] = 'plugin'
self.checkStanza(TestStanza, stanza, """
<foo xmlns="foo" bar="attribute!">
<baz>element!</baz>
<foobar foobar="plugin" />
</foo>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)

View file

@ -0,0 +1,56 @@
from . sleektest import *
class TestErrorStanzas(SleekTest):
def testSetup(self):
"""Test setting initial values in error stanza."""
msg = self.Message()
msg.enable('error')
self.checkMessage(msg, """
<message type="error">
<error type="cancel">
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
</message>
""")
def testCondition(self):
"""Test modifying the error condition."""
msg = self.Message()
msg['error']['condition'] = 'item-not-found'
self.checkMessage(msg, """
<message type="error">
<error type="cancel">
<item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
</message>
""")
self.failUnless(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.")
del msg['error']['condition']
self.checkMessage(msg, """
<message type="error">
<error type="cancel" />
</message>
""")
def testDelCondition(self):
"""Test that deleting error conditions doesn't remove extra elements."""
msg = self.Message()
msg['error']['text'] = 'Error!'
msg['error']['condition'] = 'internal-server-error'
del msg['error']['condition']
self.checkMessage(msg, """
<message type="error">
<error type="cancel">
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">Error!</text>
</error>
</message>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestErrorStanzas)

View file

@ -1,14 +1,11 @@
import unittest import sleekxmpp
from . sleektest import *
class testevents(unittest.TestCase):
def setUp(self): class TestEvents(SleekTest):
import sleekxmpp.stanza.presence as p
self.p = p
def testEventHappening(self): def testEventHappening(self):
"Test handler working" "Test handler working"
import sleekxmpp
c = sleekxmpp.ClientXMPP('crap@wherever', 'password') c = sleekxmpp.ClientXMPP('crap@wherever', 'password')
happened = [] happened = []
def handletestevent(event): def handletestevent(event):
@ -20,7 +17,6 @@ class testevents(unittest.TestCase):
def testDelEvent(self): def testDelEvent(self):
"Test handler working, then deleted and not triggered" "Test handler working, then deleted and not triggered"
import sleekxmpp
c = sleekxmpp.ClientXMPP('crap@wherever', 'password') c = sleekxmpp.ClientXMPP('crap@wherever', 'password')
happened = [] happened = []
def handletestevent(event): def handletestevent(event):
@ -32,4 +28,4 @@ class testevents(unittest.TestCase):
self.failUnless(happened == [True], "event did not get triggered the correct number of times") self.failUnless(happened == [True], "event did not get triggered the correct number of times")
suite = unittest.TestLoader().loadTestsFromTestCase(testevents) suite = unittest.TestLoader().loadTestsFromTestCase(TestEvents)

115
tests/test_forms.py Normal file
View file

@ -0,0 +1,115 @@
from . sleektest import *
import sleekxmpp.plugins.xep_0004 as xep_0004
class TestDataForms(SleekTest):
def setUp(self):
registerStanzaPlugin(Message, xep_0004.Form)
registerStanzaPlugin(xep_0004.Form, xep_0004.FormField)
registerStanzaPlugin(xep_0004.FormField, xep_0004.FieldOption)
def testMultipleInstructions(self):
"""Testing using multiple instructions elements in a data form."""
msg = self.Message()
msg['form']['instructions'] = "Instructions\nSecond batch"
self.checkMessage(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<instructions>Instructions</instructions>
<instructions>Second batch</instructions>
</x>
</message>
""")
def testAddField(self):
"""Testing adding fields to a data form."""
msg = self.Message()
form = msg['form']
form.addField(var='f1',
ftype='text-single',
label='Text',
desc='A text field',
required=True,
value='Some text!')
self.checkMessage(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="f1" type="text-single" label="Text">
<desc>A text field</desc>
<required />
<value>Some text!</value>
</field>
</x>
</message>
""")
form['fields'] = [('f1', {'type': 'text-single',
'label': 'Username',
'required': True}),
('f2', {'type': 'text-private',
'label': 'Password',
'required': True}),
('f3', {'type': 'text-multi',
'label': 'Message',
'value': 'Enter message.\nA long one even.'}),
('f4', {'type': 'list-single',
'label': 'Message Type',
'options': [{'label': 'Cool!',
'value': 'cool'},
{'label': 'Urgh!',
'value': 'urgh'}]})]
self.checkMessage(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="f1" type="text-single" label="Username">
<required />
</field>
<field var="f2" type="text-private" label="Password">
<required />
</field>
<field var="f3" type="text-multi" label="Message">
<value>Enter message.</value>
<value>A long one even.</value>
</field>
<field var="f4" type="list-single" label="Message Type">
<option label="Cool!">
<value>cool</value>
</option>
<option label="Urgh!">
<value>urgh</value>
</option>
</field>
</x>
</message>
""")
def testSetValues(self):
"""Testing setting form values"""
msg = self.Message()
form = msg['form']
form.setFields([
('foo', {'type': 'text-single'}),
('bar', {'type': 'list-multi'})])
form.setValues({'foo': 'Foo!',
'bar': ['a', 'b']})
self.checkMessage(msg, """
<message>
<x xmlns="jabber:x:data" type="form">
<field var="foo" type="text-single">
<value>Foo!</value>
</field>
<field var="bar" type="list-multi">
<value>a</value>
<value>b</value>
</field>
</x>
</message>""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestDataForms)

88
tests/test_gmail.py Normal file
View file

@ -0,0 +1,88 @@
from . sleektest import *
import sleekxmpp.plugins.gmail_notify as gmail
class TestGmail(SleekTest):
def setUp(self):
registerStanzaPlugin(Iq, gmail.GmailQuery)
registerStanzaPlugin(Iq, gmail.MailBox)
registerStanzaPlugin(Iq, gmail.NewMail)
def testCreateQuery(self):
"""Testing querying Gmail for emails."""
iq = self.Iq()
iq['type'] = 'get'
iq['gmail']['search'] = 'is:starred'
iq['gmail']['newer-than-time'] = '1140638252542'
iq['gmail']['newer-than-tid'] = '11134623426430234'
self.checkIq(iq, """
<iq type="get">
<query xmlns="google:mail:notify"
newer-than-time="1140638252542"
newer-than-tid="11134623426430234"
q="is:starred" />
</iq>
""")
def testMailBox(self):
"""Testing reading from Gmail mailbox result"""
# Use the example from Google's documentation at
# http://code.google.com/apis/talk/jep_extensions/gmail.html#notifications
xml = ET.fromstring("""
<iq type="result">
<mailbox xmlns="google:mail:notify"
result-time='1118012394209'
url='http://mail.google.com/mail'
total-matched='95'
total-estimate='0'>
<mail-thread-info tid='1172320964060972012'
participation='1'
messages='28'
date='1118012394209'
url='http://mail.google.com/mail?view=cv'>
<senders>
<sender name='Me' address='romeo@gmail.com' originator='1' />
<sender name='Benvolio' address='benvolio@gmail.com' />
<sender name='Mercutio' address='mercutio@gmail.com' unread='1'/>
</senders>
<labels>act1scene3</labels>
<subject>Put thy rapier up.</subject>
<snippet>Ay, ay, a scratch, a scratch; marry, 'tis enough.</snippet>
</mail-thread-info>
</mailbox>
</iq>
""")
iq = self.Iq(xml=xml)
mailbox = iq['mailbox']
self.failUnless(mailbox['result-time'] == '1118012394209', "result-time doesn't match")
self.failUnless(mailbox['url'] == 'http://mail.google.com/mail', "url doesn't match")
self.failUnless(mailbox['matched'] == '95', "total-matched incorrect")
self.failUnless(mailbox['estimate'] == False, "total-estimate incorrect")
self.failUnless(len(mailbox['threads']) == 1, "could not extract message threads")
thread = mailbox['threads'][0]
self.failUnless(thread['tid'] == '1172320964060972012', "thread tid doesn't match")
self.failUnless(thread['participation'] == '1', "thread participation incorrect")
self.failUnless(thread['messages'] == '28', "thread message count incorrect")
self.failUnless(thread['date'] == '1118012394209', "thread date doesn't match")
self.failUnless(thread['url'] == 'http://mail.google.com/mail?view=cv', "thread url doesn't match")
self.failUnless(thread['labels'] == 'act1scene3', "thread labels incorrect")
self.failUnless(thread['subject'] == 'Put thy rapier up.', "thread subject doesn't match")
self.failUnless(thread['snippet'] == "Ay, ay, a scratch, a scratch; marry, 'tis enough.", "snippet doesn't match")
self.failUnless(len(thread['senders']) == 3, "could not extract senders")
sender1 = thread['senders'][0]
self.failUnless(sender1['name'] == 'Me', "sender name doesn't match")
self.failUnless(sender1['address'] == 'romeo@gmail.com', "sender address doesn't match")
self.failUnless(sender1['originator'] == True, "sender originator incorrect")
self.failUnless(sender1['unread'] == False, "sender unread incorrectly True")
sender2 = thread['senders'][2]
self.failUnless(sender2['unread'] == True, "sender unread incorrectly False")
suite = unittest.TestLoader().loadTestsFromTestCase(TestGmail)

90
tests/test_iqstanzas.py Normal file
View file

@ -0,0 +1,90 @@
from . sleektest import *
from sleekxmpp.xmlstream.stanzabase import ET
class TestIqStanzas(SleekTest):
def tearDown(self):
"""Shutdown the XML stream after testing."""
self.streamClose()
def testSetup(self):
"""Test initializing default Iq values."""
iq = self.Iq()
self.checkIq(iq, """
<iq id="0" />
""")
def testPayload(self):
"""Test setting Iq stanza payload."""
iq = self.Iq()
iq.setPayload(ET.Element('{test}tester'))
self.checkIq(iq, """
<iq id="0">
<tester xmlns="test" />
</iq>
""", use_values=False)
def testUnhandled(self):
"""Test behavior for Iq.unhandled."""
self.streamStart()
self.streamRecv("""
<iq id="test" type="get">
<query xmlns="test" />
</iq>
""")
iq = self.Iq()
iq['id'] = 'test'
iq['error']['condition'] = 'feature-not-implemented'
iq['error']['text'] = 'No handlers registered for this request.'
self.streamSendIq(iq, """
<iq id="test" type="error">
<error type="cancel">
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
No handlers registered for this request.
</text>
</error>
</iq>
""")
def testQuery(self):
"""Test modifying query element of Iq stanzas."""
iq = self.Iq()
iq['query'] = 'query_ns'
self.checkIq(iq, """
<iq id="0">
<query xmlns="query_ns" />
</iq>
""")
iq['query'] = 'query_ns2'
self.checkIq(iq, """
<iq id="0">
<query xmlns="query_ns2" />
</iq>
""")
self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match")
del iq['query']
self.checkIq(iq, """
<iq id="0" />
""")
def testReply(self):
"""Test setting proper result type in Iq replies."""
iq = self.Iq()
iq['to'] = 'user@localhost'
iq['type'] = 'get'
iq.reply()
self.checkIq(iq, """
<iq id="0" type="result" />
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestIqStanzas)

26
tests/test_jid.py Normal file
View file

@ -0,0 +1,26 @@
from . sleektest import *
from sleekxmpp.xmlstream.jid import JID
class TestJIDClass(SleekTest):
def testJIDfromfull(self):
j = JID('user@someserver/some/resource')
self.assertEqual(j.user, 'user', "User does not match")
self.assertEqual(j.domain, 'someserver', "Domain does not match")
self.assertEqual(j.resource, 'some/resource', "Resource does not match")
self.assertEqual(j.bare, 'user@someserver', "Bare does not match")
self.assertEqual(j.full, 'user@someserver/some/resource', "Full does not match")
self.assertEqual(str(j), 'user@someserver/some/resource', "String does not match")
def testJIDchange(self):
j = JID('user1@someserver1/some1/resource1')
j.user = 'user'
j.domain = 'someserver'
j.resource = 'some/resource'
self.assertEqual(j.user, 'user', "User does not match")
self.assertEqual(j.domain, 'someserver', "Domain does not match")
self.assertEqual(j.resource, 'some/resource', "Resource does not match")
self.assertEqual(j.bare, 'user@someserver', "Bare does not match")
self.assertEqual(j.full, 'user@someserver/some/resource', "Full does not match")
self.assertEqual(str(j), 'user@someserver/some/resource', "String does not match")
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)

View file

@ -1,18 +1,16 @@
import unittest from . sleektest import *
from xml.etree import cElementTree as ET from sleekxmpp.stanza.message import Message
from sleekxmpp.stanza.htmlim import HTMLIM
class testmessagestanzas(unittest.TestCase):
class TestMessageStanzas(SleekTest):
def setUp(self): def setUp(self):
import sleekxmpp.stanza.message as m registerStanzaPlugin(Message, HTMLIM)
from sleekxmpp.basexmpp import stanzaPlugin
from sleekxmpp.stanza.htmlim import HTMLIM
stanzaPlugin(m.Message, HTMLIM)
self.m = m
def testGroupchatReplyRegression(self): def testGroupchatReplyRegression(self):
"Regression groupchat reply should be to barejid" "Regression groupchat reply should be to barejid"
msg = self.m.Message() msg = self.Message()
msg['to'] = 'me@myserver.tld' msg['to'] = 'me@myserver.tld'
msg['from'] = 'room@someservice.someserver.tld/somenick' msg['from'] = 'room@someservice.someserver.tld/somenick'
msg['type'] = 'groupchat' msg['type'] = 'groupchat'
@ -22,23 +20,38 @@ class testmessagestanzas(unittest.TestCase):
def testAttribProperty(self): def testAttribProperty(self):
"Test attrib property returning self" "Test attrib property returning self"
msg = self.m.Message() msg = self.Message()
msg.attrib.attrib.attrib['to'] = 'usr@server.tld' msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
self.failUnless(str(msg['to']) == 'usr@server.tld') self.failUnless(str(msg['to']) == 'usr@server.tld')
def testHTMLPlugin(self): def testHTMLPlugin(self):
"Test message/html/html stanza" "Test message/html/body stanza"
msgtxt = """<message to="fritzy@netflint.net/sleekxmpp" type="chat"><body>this is the plaintext message</body><html xmlns="http://jabber.org/protocol/xhtml-im"><body xmlns="http://www.w3.org/1999/xhtml"><p>This is the htmlim message</p></body></html></message>""" msg = self.Message()
msg = self.m.Message()
msg['to'] = "fritzy@netflint.net/sleekxmpp" msg['to'] = "fritzy@netflint.net/sleekxmpp"
msg['body'] = "this is the plaintext message" msg['body'] = "this is the plaintext message"
msg['type'] = 'chat' msg['type'] = 'chat'
p = ET.Element('{http://www.w3.org/1999/xhtml}p') p = ET.Element('{http://www.w3.org/1999/xhtml}p')
p.text = "This is the htmlim message" p.text = "This is the htmlim message"
msg['html']['html'] = p msg['html']['body'] = p
msg2 = self.m.Message() self.checkMessage(msg, """
values = msg.getValues() <message to="fritzy@netflint.net/sleekxmpp" type="chat">
msg2.setValues(values) <body>this is the plaintext message</body>
self.failUnless(msgtxt == str(msg) == str(msg2)) <html xmlns="http://jabber.org/protocol/xhtml-im">
<body xmlns="http://www.w3.org/1999/xhtml">
<p>This is the htmlim message</p>
</body>
</html>
</message>""")
suite = unittest.TestLoader().loadTestsFromTestCase(testmessagestanzas) def testNickPlugin(self):
"Test message/nick/nick stanza."
msg = self.Message()
msg['nick']['nick'] = 'A nickname!'
self.checkMessage(msg, """
<message>
<nick xmlns="http://jabber.org/nick/nick">A nickname!</nick>
</message>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestMessageStanzas)

View file

@ -1,31 +1,67 @@
import unittest import sleekxmpp
from . sleektest import *
from sleekxmpp.stanza.presence import Presence
class testpresencestanzas(unittest.TestCase):
def setUp(self): class TestPresenceStanzas(SleekTest):
import sleekxmpp.stanza.presence as p
self.p = p
def testPresenceShowRegression(self): def testPresenceShowRegression(self):
"Regression check presence['type'] = 'dnd' show value working" """Regression check presence['type'] = 'dnd' show value working"""
p = self.p.Presence() p = self.Presence()
p['type'] = 'dnd' p['type'] = 'dnd'
self.failUnless(str(p) == "<presence><show>dnd</show></presence>") self.checkPresence(p, "<presence><show>dnd</show></presence>")
def testPresenceType(self):
"""Test manipulating presence['type']"""
p = self.Presence()
p['type'] = 'available'
self.checkPresence(p, "<presence />")
self.failUnless(p['type'] == 'available',
"Incorrect presence['type'] for type 'available'")
for showtype in ['away', 'chat', 'dnd', 'xa']:
p['type'] = showtype
self.checkPresence(p, """
<presence><show>%s</show></presence>
""" % showtype)
self.failUnless(p['type'] == showtype,
"Incorrect presence['type'] for type '%s'" % showtype)
p['type'] = None
self.checkPresence(p, "<presence />")
def testPresenceUnsolicitedOffline(self): def testPresenceUnsolicitedOffline(self):
"Unsolicted offline presence does not spawn changed_status or update roster" """
p = self.p.Presence() Unsolicted offline presence does not spawn changed_status
or update the roster.
"""
p = self.Presence()
p['type'] = 'unavailable' p['type'] = 'unavailable'
p['from'] = 'bill@chadmore.com/gmail15af' p['from'] = 'bill@chadmore.com/gmail15af'
import sleekxmpp
c = sleekxmpp.ClientXMPP('crap@wherever', 'password') c = sleekxmpp.ClientXMPP('crap@wherever', 'password')
happened = [] happened = []
def handlechangedpresence(event): def handlechangedpresence(event):
happened.append(True) happened.append(True)
c.add_event_handler("changed_status", handlechangedpresence) c.add_event_handler("changed_status", handlechangedpresence)
c._handlePresence(p) c._handlePresence(p)
self.failUnless(happened == [], "changed_status event triggered for superfulous unavailable presence")
self.failUnless(c.roster == {}, "Roster updated for superfulous unavailable presence") self.failUnless(happened == [],
"changed_status event triggered for extra unavailable presence")
self.failUnless(c.roster == {},
"Roster updated for superfulous unavailable presence")
def testNickPlugin(self):
"""Test presence/nick/nick stanza."""
p = self.Presence()
p['nick']['nick'] = 'A nickname!'
self.checkPresence(p, """
<presence>
<nick xmlns="http://jabber.org/nick/nick">A nickname!</nick>
</presence>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(testpresencestanzas) suite = unittest.TestLoader().loadTestsFromTestCase(TestPresenceStanzas)

View file

@ -1,212 +1,378 @@
import unittest from . sleektest import *
from xml.etree import cElementTree as ET import sleekxmpp.plugins.xep_0004 as xep_0004
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath import sleekxmpp.plugins.stanza_pubsub as pubsub
from . import xmlcompare
class testpubsubstanzas(unittest.TestCase):
def setUp(self): class TestPubsubStanzas(SleekTest):
import sleekxmpp.plugins.stanza_pubsub as ps
self.ps = ps
def testAffiliations(self): def testAffiliations(self):
"Testing iq/pubsub/affiliations/affiliation stanzas" "Testing iq/pubsub/affiliations/affiliation stanzas"
iq = self.ps.Iq() iq = self.Iq()
aff1 = self.ps.Affiliation() aff1 = pubsub.Affiliation()
aff1['node'] = 'testnode' aff1['node'] = 'testnode'
aff1['affiliation'] = 'owner' aff1['affiliation'] = 'owner'
aff2 = self.ps.Affiliation() aff2 = pubsub.Affiliation()
aff2['node'] = 'testnode2' aff2['node'] = 'testnode2'
aff2['affiliation'] = 'publisher' aff2['affiliation'] = 'publisher'
iq['pubsub']['affiliations'].append(aff1) iq['pubsub']['affiliations'].append(aff1)
iq['pubsub']['affiliations'].append(aff2) iq['pubsub']['affiliations'].append(aff2)
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><affiliations><affiliation node="testnode" affiliation="owner" /><affiliation node="testnode2" affiliation="publisher" /></affiliations></pubsub></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <pubsub xmlns="http://jabber.org/protocol/pubsub">
values = iq2.getValues() <affiliations>
iq3.setValues(values) <affiliation node="testnode" affiliation="owner" />
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3), "3 methods for creating stanza don't match") <affiliation node="testnode2" affiliation="publisher" />
self.failUnless(iq.match('iq@id=0/pubsub/affiliations/affiliation@node=testnode2@affiliation=publisher'), 'Match path failed') </affiliations>
</pubsub>
</iq>""")
def testSubscriptions(self): def testSubscriptions(self):
"Testing iq/pubsub/subscriptions/subscription stanzas" "Testing iq/pubsub/subscriptions/subscription stanzas"
iq = self.ps.Iq() iq = self.Iq()
sub1 = self.ps.Subscription() sub1 = pubsub.Subscription()
sub1['node'] = 'testnode' sub1['node'] = 'testnode'
sub1['jid'] = 'steve@myserver.tld/someresource' sub1['jid'] = 'steve@myserver.tld/someresource'
sub2 = self.ps.Subscription() sub2 = pubsub.Subscription()
sub2['node'] = 'testnode2' sub2['node'] = 'testnode2'
sub2['jid'] = 'boogers@bork.top/bill' sub2['jid'] = 'boogers@bork.top/bill'
sub2['subscription'] = 'subscribed' 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" subscription="subscribed" /></subscriptions></pubsub></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <pubsub xmlns="http://jabber.org/protocol/pubsub">
values = iq2.getValues() <subscriptions>
iq3.setValues(values) <subscription node="testnode" jid="steve@myserver.tld/someresource" />
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) <subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" />
</subscriptions>
</pubsub>
</iq>""")
def testOptionalSettings(self): def testOptionalSettings(self):
"Testing iq/pubsub/subscription/subscribe-options stanzas" "Testing iq/pubsub/subscription/subscribe-options stanzas"
iq = self.ps.Iq() iq = self.Iq()
iq['pubsub']['subscription']['suboptions']['required'] = True iq['pubsub']['subscription']['suboptions']['required'] = True
iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas' iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas'
iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp" iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp"
iq['pubsub']['subscription']['subscription'] = 'unconfigured' iq['pubsub']['subscription']['subscription'] = 'unconfigured'
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured"><subscribe-options><required /></subscribe-options></subscription></pubsub></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <pubsub xmlns="http://jabber.org/protocol/pubsub">
values = iq2.getValues() <subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured">
iq3.setValues(values) <subscribe-options>
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) <required />
</subscribe-options>
</subscription>
</pubsub>
</iq>""")
def testItems(self): def testItems(self):
"Testing iq/pubsub/items stanzas" "Testing iq/pubsub/items stanzas"
iq = self.ps.Iq() iq = self.Iq()
iq['pubsub']['items'] iq['pubsub']['items']
payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""") payload = ET.fromstring("""
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""") <thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'>
item = self.ps.Item() <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 = pubsub.Item()
item['id'] = 'asdf' item['id'] = 'asdf'
item['payload'] = payload item['payload'] = payload
item2 = self.ps.Item() item2 = pubsub.Item()
item2['id'] = 'asdf2' item2['id'] = 'asdf2'
item2['payload'] = payload2 item2['payload'] = payload2
iq['pubsub']['items'].append(item) iq['pubsub']['items'].append(item)
iq['pubsub']['items'].append(item2) iq['pubsub']['items'].append(item2)
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><items><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></items></pubsub></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <pubsub xmlns="http://jabber.org/protocol/pubsub">
values = iq2.getValues() <items>
iq3.setValues(values) <item id="asdf">
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) <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>
</items>
</pubsub>
</iq>""")
def testCreate(self): def testCreate(self):
"Testing iq/pubsub/create&configure stanzas" "Testing iq/pubsub/create&configure stanzas"
from sleekxmpp.plugins import xep_0004 iq = self.Iq()
iq = self.ps.Iq()
iq['pubsub']['create']['node'] = 'mynode' iq['pubsub']['create']['node'] = 'mynode'
form = xep_0004.Form() iq['pubsub']['configure']['form'].addField('pubsub#title',
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') ftype='text-single',
iq['pubsub']['configure']['config'] = form value='This thing is awesome')
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><create node="mynode" /><configure><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></configure></pubsub></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <pubsub xmlns="http://jabber.org/protocol/pubsub">
values = iq2.getValues() <create node="mynode" />
iq3.setValues(values) <configure>
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) <x xmlns="jabber:x:data" type="form">
<field var="pubsub#title" type="text-single">
<value>This thing is awesome</value>
</field>
</x>
</configure>
</pubsub>
</iq>""")
def testState(self): def testState(self):
"Testing iq/psstate stanzas" "Testing iq/psstate stanzas"
from sleekxmpp.plugins import xep_0004 iq = self.Iq()
iq = self.ps.Iq()
iq['psstate']['node']= 'mynode' iq['psstate']['node']= 'mynode'
iq['psstate']['item']= 'myitem' iq['psstate']['item']= 'myitem'
pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed') pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
iq['psstate']['payload'] = pl iq['psstate']['payload'] = pl
xmlstring = """<iq id="0"><state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem"><claimed xmlns="http://andyet.net/protocol/pubsubqueue" /></state></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem">
values = iq2.getValues() <claimed xmlns="http://andyet.net/protocol/pubsubqueue" />
iq3.setValues(values) </state>
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) </iq>""")
def testDefault(self): def testDefault(self):
"Testing iq/pubsub_owner/default stanzas" "Testing iq/pubsub_owner/default stanzas"
from sleekxmpp.plugins import xep_0004 iq = self.Iq()
iq = self.ps.Iq()
iq['pubsub_owner']['default'] iq['pubsub_owner']['default']
iq['pubsub_owner']['default']['node'] = 'mynode' iq['pubsub_owner']['default']['node'] = 'mynode'
iq['pubsub_owner']['default']['type'] = 'leaf' iq['pubsub_owner']['default']['type'] = 'leaf'
form = xep_0004.Form() iq['pubsub_owner']['default']['form'].addField('pubsub#title',
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') ftype='text-single',
iq['pubsub_owner']['default']['config'] = form value='This thing is awesome')
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><default node="mynode" type="leaf"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></default></pubsub></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
values = iq2.getValues() <default node="mynode" type="leaf">
iq3.setValues(values) <x xmlns="jabber:x:data" type="form">
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) <field var="pubsub#title" type="text-single">
<value>This thing is awesome</value>
</field>
</x>
</default>
</pubsub>
</iq>""", use_values=False)
def testSubscribe(self): def testSubscribe(self):
"Testing iq/pubsub/subscribe stanzas" "testing iq/pubsub/subscribe stanzas"
from sleekxmpp.plugins import xep_0004 iq = self.Iq()
iq = self.ps.Iq()
iq['pubsub']['subscribe']['options'] iq['pubsub']['subscribe']['options']
iq['pubsub']['subscribe']['node'] = 'cheese' iq['pubsub']['subscribe']['node'] = 'cheese'
iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp' iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp'
iq['pubsub']['subscribe']['options']['node'] = 'cheese' iq['pubsub']['subscribe']['options']['node'] = 'cheese'
iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp' iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp'
form = xep_0004.Form() form = xep_0004.Form()
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') form.addField('pubsub#title', ftype='text-single', value='this thing is awesome')
iq['pubsub']['subscribe']['options']['options'] = form iq['pubsub']['subscribe']['options']['options'] = form
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp"><options node="cheese" jid="fritzy@netflint.net/sleekxmpp"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></options></subscribe></pubsub></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <pubsub xmlns="http://jabber.org/protocol/pubsub">
values = iq2.getValues() <subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp">
iq3.setValues(values) <options node="cheese" jid="fritzy@netflint.net/sleekxmpp">
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) <x xmlns="jabber:x:data" type="form">
<field var="pubsub#title" type="text-single">
<value>this thing is awesome</value>
</field>
</x>
</options>
</subscribe>
</pubsub>
</iq>""", use_values=False)
def testPublish(self): def testPublish(self):
"Testing iq/pubsub/publish stanzas" "Testing iq/pubsub/publish stanzas"
iq = self.ps.Iq() iq = self.Iq()
iq['pubsub']['publish']['node'] = 'thingers' 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>""") payload = ET.fromstring("""
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""") <thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'>
item = self.ps.Item() <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 = pubsub.Item()
item['id'] = 'asdf' item['id'] = 'asdf'
item['payload'] = payload item['payload'] = payload
item2 = self.ps.Item() item2 = pubsub.Item()
item2['id'] = 'asdf2' item2['id'] = 'asdf2'
item2['payload'] = payload2 item2['payload'] = payload2
iq['pubsub']['publish'].append(item) iq['pubsub']['publish'].append(item)
iq['pubsub']['publish'].append(item2) 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)) self.checkIq(iq, """
iq3 = self.ps.Iq() <iq id="0">
values = iq2.getValues() <pubsub xmlns="http://jabber.org/protocol/pubsub">
iq3.setValues(values) <publish node="thingers">
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) <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>""")
def testDelete(self): def testDelete(self):
"Testing iq/pubsub_owner/delete stanzas" "Testing iq/pubsub_owner/delete stanzas"
iq = self.ps.Iq() iq = self.Iq()
iq['pubsub_owner']['delete']['node'] = 'thingers' iq['pubsub_owner']['delete']['node'] = 'thingers'
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><delete node="thingers" /></pubsub></iq>""" self.checkIq(iq, """
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
iq3 = self.ps.Iq() <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
iq3.setValues(iq2.getValues()) <delete node="thingers" />
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) </pubsub>
</iq>""")
def testCreateConfigGet(self): def testCreateConfigGet(self):
"""Testing getting config from full create""" """Testing getting config from full create"""
xml = """<iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7"><pubsub xmlns="http://jabber.org/protocol/pubsub"><create node="testnode2" /><configure><x xmlns="jabber:x:data" type="submit"><field var="FORM_TYPE" type="hidden"><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var="pubsub#node_type" type="list-single" label="Select the node type"><value>leaf</value></field><field var="pubsub#title" type="text-single" label="A friendly name for the node" /><field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications"><value>1</value></field><field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications"><value>1</value></field><field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" /><field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" /><field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node"><value>1</value></field><field var="pubsub#notify_sub" type="boolean" label="Notify owners about new subscribers and unsubscribes" /><field var="pubsub#persist_items" type="boolean" label="Persist items in storage" /><field var="pubsub#max_items" type="text-single" label="Max # of items to persist"><value>10</value></field><field var="pubsub#subscribe" type="boolean" label="Whether to allow subscriptions"><value>1</value></field><field var="pubsub#access_model" type="list-single" label="Specify the subscriber model"><value>open</value></field><field var="pubsub#publish_model" type="list-single" label="Specify the publisher model"><value>publishers</value></field><field var="pubsub#send_last_published_item" type="list-single" label="Send last published item"><value>never</value></field><field var="pubsub#presence_based_delivery" type="boolean" label="Deliver notification only to available users" /></x></configure></pubsub></iq>""" iq = self.Iq()
iq = self.ps.Iq(None, self.ps.ET.fromstring(xml)) iq['to'] = 'pubsub.asdf'
config = iq['pubsub']['configure']['config'] iq['from'] = 'fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7'
self.failUnless(config.getValues() != {}) iq['type'] = 'set'
iq['id'] = 'E'
pub = iq['pubsub']
pub['create']['node'] = 'testnode2'
pub['configure']['form']['type'] = 'submit'
pub['configure']['form'].setFields([
('FORM_TYPE', {'type': 'hidden',
'value': 'http://jabber.org/protocol/pubsub#node_config'}),
('pubsub#node_type', {'type': 'list-single',
'label': 'Select the node type',
'value': 'leaf'}),
('pubsub#title', {'type': 'text-single',
'label': 'A friendly name for the node'}),
('pubsub#deliver_notifications', {'type': 'boolean',
'label': 'Deliver event notifications',
'value': True}),
('pubsub#deliver_payloads', {'type': 'boolean',
'label': 'Deliver payloads with event notifications',
'value': True}),
('pubsub#notify_config', {'type': 'boolean',
'label': 'Notify subscribers when the node configuration changes'}),
('pubsub#notify_delete', {'type': 'boolean',
'label': 'Notify subscribers when the node is deleted'}),
('pubsub#notify_retract', {'type': 'boolean',
'label': 'Notify subscribers when items are removed from the node',
'value': True}),
('pubsub#notify_sub', {'type': 'boolean',
'label': 'Notify owners about new subscribers and unsubscribes'}),
('pubsub#persist_items', {'type': 'boolean',
'label': 'Persist items in storage'}),
('pubsub#max_items', {'type': 'text-single',
'label': 'Max # of items to persist',
'value': '10'}),
('pubsub#subscribe', {'type': 'boolean',
'label': 'Whether to allow subscriptions',
'value': True}),
('pubsub#access_model', {'type': 'list-single',
'label': 'Specify the subscriber model',
'value': 'open'}),
('pubsub#publish_model', {'type': 'list-single',
'label': 'Specify the publisher model',
'value': 'publishers'}),
('pubsub#send_last_published_item', {'type': 'list-single',
'label': 'Send last published item',
'value': 'never'}),
('pubsub#presence_based_delivery', {'type': 'boolean',
'label': 'Deliver notification only to available users'}),
])
self.checkIq(iq, """
<iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7">
<pubsub xmlns="http://jabber.org/protocol/pubsub">
<create node="testnode2" />
<configure>
<x xmlns="jabber:x:data" type="submit">
<field var="FORM_TYPE" type="hidden">
<value>http://jabber.org/protocol/pubsub#node_config</value>
</field>
<field var="pubsub#node_type" type="list-single" label="Select the node type">
<value>leaf</value>
</field>
<field var="pubsub#title" type="text-single" label="A friendly name for the node" />
<field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications">
<value>1</value>
</field>
<field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications">
<value>1</value>
</field>
<field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" />
<field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" />
<field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node">
<value>1</value>
</field>
<field var="pubsub#notify_sub" type="boolean" label="Notify owners about new subscribers and unsubscribes" />
<field var="pubsub#persist_items" type="boolean" label="Persist items in storage" />
<field var="pubsub#max_items" type="text-single" label="Max # of items to persist">
<value>10</value>
</field>
<field var="pubsub#subscribe" type="boolean" label="Whether to allow subscriptions">
<value>1</value>
</field>
<field var="pubsub#access_model" type="list-single" label="Specify the subscriber model">
<value>open</value>
</field>
<field var="pubsub#publish_model" type="list-single" label="Specify the publisher model">
<value>publishers</value>
</field>
<field var="pubsub#send_last_published_item" type="list-single" label="Send last published item">
<value>never</value>
</field>
<field var="pubsub#presence_based_delivery" type="boolean" label="Deliver notification only to available users" />
</x>
</configure>
</pubsub>
</iq>""")
def testItemEvent(self): def testItemEvent(self):
"""Testing message/pubsub_event/items/item""" """Testing message/pubsub_event/items/item"""
msg = self.ps.Message() msg = self.Message()
item = self.ps.EventItem() item = pubsub.EventItem()
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
item['payload'] = pl item['payload'] = pl
item['id'] = 'abc123' item['id'] = 'abc123'
msg['pubsub_event']['items'].append(item) msg['pubsub_event']['items'].append(item)
msg['pubsub_event']['items']['node'] = 'cheese' msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal' msg['type'] = 'normal'
xmlstring = """<message type="normal"><event xmlns="http://jabber.org/protocol/pubsub#event"><items node="cheese"><item id="abc123"><test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /></item></items></event></message>""" self.checkMessage(msg, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <message type="normal">
msg3 = self.ps.Message() <event xmlns="http://jabber.org/protocol/pubsub#event">
msg3.setValues(msg2.getValues()) <items node="cheese">
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) <item id="abc123">
<test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" />
</item>
</items>
</event>
</message>""")
def testItemsEvent(self): def testItemsEvent(self):
"""Testing multiple message/pubsub_event/items/item""" """Testing multiple message/pubsub_event/items/item"""
msg = self.ps.Message() msg = self.Message()
item = self.ps.EventItem() item = pubsub.EventItem()
item2 = self.ps.EventItem() item2 = pubsub.EventItem()
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'}) pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
item2['payload'] = pl2 item2['payload'] = pl2
@ -217,21 +383,29 @@ class testpubsubstanzas(unittest.TestCase):
msg['pubsub_event']['items'].append(item2) msg['pubsub_event']['items'].append(item2)
msg['pubsub_event']['items']['node'] = 'cheese' msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal' msg['type'] = 'normal'
xmlstring = """<message type="normal"><event xmlns="http://jabber.org/protocol/pubsub#event"><items node="cheese"><item id="abc123"><test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /></item><item id="123abc"><test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" /></item></items></event></message>""" self.checkMessage(msg, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <message type="normal">
msg3 = self.ps.Message() <event xmlns="http://jabber.org/protocol/pubsub#event">
msg3.setValues(msg2.getValues()) <items node="cheese">
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) <item id="abc123">
<test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" />
</item>
<item id="123abc">
<test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" />
</item>
</items>
</event>
</message>""")
def testItemsEvent(self): def testItemsEvent(self):
"""Testing message/pubsub_event/items/item & retract mix""" """Testing message/pubsub_event/items/item & retract mix"""
msg = self.ps.Message() msg = self.Message()
item = self.ps.EventItem() item = pubsub.EventItem()
item2 = self.ps.EventItem() item2 = pubsub.EventItem()
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'}) pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
item2['payload'] = pl2 item2['payload'] = pl2
retract = self.ps.EventRetract() retract = pubsub.EventRetract()
retract['id'] = 'aabbcc' retract['id'] = 'aabbcc'
item['payload'] = pl item['payload'] = pl
item['id'] = 'abc123' item['id'] = 'abc123'
@ -241,75 +415,97 @@ class testpubsubstanzas(unittest.TestCase):
msg['pubsub_event']['items'].append(item2) msg['pubsub_event']['items'].append(item2)
msg['pubsub_event']['items']['node'] = 'cheese' msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal' msg['type'] = 'normal'
xmlstring = """<message type="normal"><event xmlns="http://jabber.org/protocol/pubsub#event"><items node="cheese"><item id="abc123"><test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /></item><retract id="aabbcc" /><item id="123abc"><test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" /></item></items></event></message>""" self.checkMessage(msg, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <message type="normal">
msg3 = self.ps.Message() <event xmlns="http://jabber.org/protocol/pubsub#event">
msg3.setValues(msg2.getValues()) <items node="cheese">
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) <item id="abc123">
<test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" />
</item><retract id="aabbcc" />
<item id="123abc">
<test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" />
</item>
</items>
</event>
</message>""")
def testCollectionAssociate(self): def testCollectionAssociate(self):
"""Testing message/pubsub_event/collection/associate""" """Testing message/pubsub_event/collection/associate"""
msg = self.ps.Message() msg = self.Message()
msg['pubsub_event']['collection']['associate']['node'] = 'cheese' msg['pubsub_event']['collection']['associate']['node'] = 'cheese'
msg['pubsub_event']['collection']['node'] = 'cheeseburger' msg['pubsub_event']['collection']['node'] = 'cheeseburger'
msg['type'] = 'headline' msg['type'] = 'headline'
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><associate node="cheese" /></collection></event></message>""" self.checkMessage(msg, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <message type="headline">
msg3 = self.ps.Message() <event xmlns="http://jabber.org/protocol/pubsub#event">
msg3.setValues(msg2.getValues()) <collection node="cheeseburger">
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) <associate node="cheese" />
</collection>
</event>
</message>""")
def testCollectionDisassociate(self): def testCollectionDisassociate(self):
"""Testing message/pubsub_event/collection/disassociate""" """Testing message/pubsub_event/collection/disassociate"""
msg = self.ps.Message() msg = self.Message()
msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese' msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese'
msg['pubsub_event']['collection']['node'] = 'cheeseburger' msg['pubsub_event']['collection']['node'] = 'cheeseburger'
msg['type'] = 'headline' msg['type'] = 'headline'
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><disassociate node="cheese" /></collection></event></message>""" self.checkMessage(msg, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <message type="headline">
msg3 = self.ps.Message() <event xmlns="http://jabber.org/protocol/pubsub#event">
msg3.setValues(msg2.getValues()) <collection node="cheeseburger">
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) <disassociate node="cheese" />
</collection>
</event>
</message>""")
def testEventConfiguration(self): def testEventConfiguration(self):
"""Testing message/pubsub_event/configuration/config""" """Testing message/pubsub_event/configuration/config"""
msg = self.ps.Message() msg = self.Message()
from sleekxmpp.plugins import xep_0004
form = xep_0004.Form()
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
msg['pubsub_event']['configuration']['node'] = 'cheese' msg['pubsub_event']['configuration']['node'] = 'cheese'
msg['pubsub_event']['configuration']['config'] = form msg['pubsub_event']['configuration']['form'].addField('pubsub#title',
ftype='text-single',
value='This thing is awesome')
msg['type'] = 'headline' msg['type'] = 'headline'
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><configuration node="cheese"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></configuration></event></message>""" self.checkMessage(msg, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <message type="headline">
msg3 = self.ps.Message() <event xmlns="http://jabber.org/protocol/pubsub#event">
msg3.setValues(msg2.getValues()) <configuration node="cheese">
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) <x xmlns="jabber:x:data" type="form">
<field var="pubsub#title" type="text-single">
<value>This thing is awesome</value>
</field>
</x>
</configuration>
</event>
</message>""")
def testEventPurge(self): def testEventPurge(self):
"""Testing message/pubsub_event/purge""" """Testing message/pubsub_event/purge"""
msg = self.ps.Message() msg = self.Message()
msg['pubsub_event']['purge']['node'] = 'pickles' msg['pubsub_event']['purge']['node'] = 'pickles'
msg['type'] = 'headline' msg['type'] = 'headline'
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><purge node="pickles" /></event></message>""" self.checkMessage(msg, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <message type="headline">
msg3 = self.ps.Message() <event xmlns="http://jabber.org/protocol/pubsub#event">
msg3.setValues(msg2.getValues()) <purge node="pickles" />
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) </event>
</message>""")
def testEventSubscription(self): def testEventSubscription(self):
"""Testing message/pubsub_event/subscription""" """Testing message/pubsub_event/subscription"""
msg = self.ps.Message() msg = self.Message()
msg['pubsub_event']['subscription']['node'] = 'pickles' msg['pubsub_event']['subscription']['node'] = 'pickles'
msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test' msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test'
msg['pubsub_event']['subscription']['subid'] = 'aabb1122' msg['pubsub_event']['subscription']['subid'] = 'aabb1122'
msg['pubsub_event']['subscription']['subscription'] = 'subscribed' msg['pubsub_event']['subscription']['subscription'] = 'subscribed'
msg['pubsub_event']['subscription']['expiry'] = 'presence' msg['pubsub_event']['subscription']['expiry'] = 'presence'
msg['type'] = 'headline' msg['type'] = 'headline'
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><subscription node="pickles" subid="aabb1122" jid="fritzy@netflint.net/test" subscription="subscribed" expiry="presence" /></event></message>""" self.checkMessage(msg, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <message type="headline">
msg3 = self.ps.Message() <event xmlns="http://jabber.org/protocol/pubsub#event">
msg3.setValues(msg2.getValues()) <subscription node="pickles" subid="aabb1122" jid="fritzy@netflint.net/test" subscription="subscribed" expiry="presence" />
self.failUnless(xmlcompare.comparemany([xmlstring, str(msg), str(msg2), str(msg3)])) </event>
</message>""")
suite = unittest.TestLoader().loadTestsFromTestCase(testpubsubstanzas) suite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubStanzas)

84
tests/test_roster.py Normal file
View file

@ -0,0 +1,84 @@
from . sleektest import *
from sleekxmpp.stanza.roster import Roster
class TestRosterStanzas(SleekTest):
def testAddItems(self):
"""Test adding items to a roster stanza."""
iq = self.Iq()
iq['roster'].setItems({
'user@example.com': {
'name': 'User',
'subscription': 'both',
'groups': ['Friends', 'Coworkers']},
'otheruser@example.com': {
'name': 'Other User',
'subscription': 'both',
'groups': []}})
self.checkIq(iq, """
<iq>
<query xmlns="jabber:iq:roster">
<item jid="user@example.com" name="User" subscription="both">
<group>Friends</group>
<group>Coworkers</group>
</item>
<item jid="otheruser@example.com" name="Other User"
subscription="both" />
</query>
</iq>
""")
def testGetItems(self):
"""Test retrieving items from a roster stanza."""
xml_string = """
<iq>
<query xmlns="jabber:iq:roster">
<item jid="user@example.com" name="User" subscription="both">
<group>Friends</group>
<group>Coworkers</group>
</item>
<item jid="otheruser@example.com" name="Other User"
subscription="both" />
</query>
</iq>
"""
iq = self.Iq(ET.fromstring(xml_string))
expected = {
'user@example.com': {
'name': 'User',
'subscription': 'both',
'groups': ['Friends', 'Coworkers']},
'otheruser@example.com': {
'name': 'Other User',
'subscription': 'both',
'groups': []}}
debug = "Roster items don't match after retrieval."
debug += "\nReturned: %s" % str(iq['roster']['items'])
debug += "\nExpected: %s" % str(expected)
self.failUnless(iq['roster']['items'] == expected, debug)
def testDelItems(self):
"""Test clearing items from a roster stanza."""
xml_string = """
<iq>
<query xmlns="jabber:iq:roster">
<item jid="user@example.com" name="User" subscription="both">
<group>Friends</group>
<group>Coworkers</group>
</item>
<item jid="otheruser@example.com" name="Other User"
subscription="both" />
</query>
</iq>
"""
iq = self.Iq(ET.fromstring(xml_string))
del iq['roster']['items']
self.checkIq(iq, """
<iq>
<query xmlns="jabber:iq:roster" />
</iq>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestRosterStanzas)

34
tests/test_stream.py Normal file
View file

@ -0,0 +1,34 @@
from . sleektest import *
import sleekxmpp.plugins.xep_0033 as xep_0033
class TestStreamTester(SleekTest):
"""
Test that we can simulate and test a stanza stream.
"""
def setUp(self):
self.streamStart()
def tearDown(self):
self.streamClose()
def testEcho(self):
def echo(msg):
msg.reply('Thanks for sending: %(body)s' % msg).send()
self.xmpp.add_event_handler('message', echo)
self.streamRecv("""
<message to="tester@localhost" from="user@localhost">
<body>Hi!</body>
</message>
""")
self.streamSendMessage("""
<message to="user@localhost">
<body>Thanks for sending: Hi!</body>
</message>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamTester)

104
tests/test_tostring.py Normal file
View file

@ -0,0 +1,104 @@
from . sleektest import *
from sleekxmpp.stanza import Message
from sleekxmpp.xmlstream.stanzabase import ET
from sleekxmpp.xmlstream.tostring import tostring, xml_escape
class TestToString(SleekTest):
"""
Test the implementation of sleekxmpp.xmlstream.tostring
"""
def tryTostring(self, original='', expected=None, message='', **kwargs):
"""
Compare the result of calling tostring against an
expected result.
"""
if not expected:
expected=original
if isinstance(original, str):
xml = ET.fromstring(original)
else:
xml=original
result = tostring(xml, **kwargs)
self.failUnless(result == expected, "%s: %s" % (message, result))
def testXMLEscape(self):
"""Test escaping XML special characters."""
original = """<foo bar="baz">'Hi & welcome!'</foo>"""
escaped = xml_escape(original)
desired = """&lt;foo bar=&quot;baz&quot;&gt;&apos;Hi"""
desired += """ &amp; welcome!&apos;&lt;/foo&gt;"""
self.failUnless(escaped == desired,
"XML escaping did not work: %s." % escaped)
def testEmptyElement(self):
"""Test converting an empty element to a string."""
self.tryTostring(
original='<bar xmlns="foo" />',
message="Empty element not serialized correctly")
def testEmptyElementWrapped(self):
"""Test converting an empty element inside another element."""
self.tryTostring(
original='<bar xmlns="foo"><baz /></bar>',
message="Wrapped empty element not serialized correctly")
def testEmptyElementWrappedText(self):
"""
Test converting an empty element wrapped with text
inside another element.
"""
self.tryTostring(
original='<bar xmlns="foo">Some text. <baz /> More text.</bar>',
message="Text wrapped empty element serialized incorrectly")
def testMultipleChildren(self):
"""Test converting multiple child elements to a Unicode string."""
self.tryTostring(
original='<bar xmlns="foo"><baz><qux /></baz><quux /></bar>',
message="Multiple child elements not serialized correctly")
def testXMLNS(self):
"""
Test using xmlns tostring parameter, which will prevent adding
an xmlns attribute to the serialized element if the element's
namespace is the same.
"""
self.tryTostring(
original='<bar xmlns="foo" />',
expected='<bar />',
message="The xmlns parameter was not used properly.",
xmlns='foo')
def testStanzaNs(self):
"""
Test using the stanza_ns tostring parameter, which will prevent
adding an xmlns attribute to the serialized element if the
element's namespace is the same.
"""
self.tryTostring(
original='<bar xmlns="foo" />',
expected='<bar />',
message="The stanza_ns parameter was not used properly.",
stanza_ns='foo')
def testStanzaStr(self):
"""
Test that stanza objects are serialized properly.
"""
utf8_message = '\xe0\xb2\xa0_\xe0\xb2\xa0'
if not hasattr(utf8_message, 'decode'):
# Python 3
utf8_message = bytes(utf8_message, encoding='utf-8')
msg = Message()
msg['body'] = utf8_message.decode('utf-8')
expected = '<message><body>\xe0\xb2\xa0_\xe0\xb2\xa0</body></message>'
result = msg.__str__()
self.failUnless(result == expected,
"Stanza Unicode handling is incorrect: %s" % result)
suite = unittest.TestLoader().loadTestsFromTestCase(TestToString)

View file

@ -1,28 +0,0 @@
from xml.etree import cElementTree as ET
def comparemany(xmls):
xml1 = xmls[0]
if type(xml1) == type(''):
xml1 = ET.fromstring(xml1)
for xml in xmls[1:]:
xml2 = xml
if type(xml2) == type(''):
xml2 = ET.fromstring(xml2)
if not compare(xml1, xml2): return False
return True
def compare(xml1, xml2):
if xml1.tag != xml2.tag:
return False
if xml1.attrib != xml2.attrib:
return False
for child in xml1:
child2s = xml2.findall("%s" % child.tag)
if child2s is None:
return False
found = False
for child2 in child2s:
found = compare(child, child2)
if found: break
if not found: return False
return True