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
@ -8,41 +9,46 @@ import time
import sys import sys
if sys.version_info < (3,0): if sys.version_info < (3,0):
reload(sys) reload(sys)
sys.setdefaultencoding('utf8') sys.setdefaultencoding('utf8')
class Example(sleekxmpp.ClientXMPP): class Example(sleekxmpp.ClientXMPP):
def __init__(self, jid, password): def __init__(self, jid, password):
sleekxmpp.ClientXMPP.__init__(self, jid, password) sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.add_event_handler("session_start", self.start) self.add_event_handler("session_start", self.start)
self.add_event_handler("message", self.message) self.add_event_handler("message", self.message)
def start(self, event): def start(self, event):
self.getRoster() self.getRoster()
self.sendPresence() self.sendPresence()
def message(self, msg): def message(self, msg):
msg.reply("Thanks for sending\n%(body)s" % msg).send() msg.reply("Thanks for sending\n%(body)s" % msg).send()
if __name__ == '__main__': if __name__ == '__main__':
#parse command line arguements #parse command line arguements
optp = OptionParser() optp = OptionParser()
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")
opts,args = optp.parse_args() optp.add_option("-p","--password", dest="password", help="password to use")
opts,args = optp.parse_args()
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
xmpp = Example('user@gmail.com/sleekxmpp', 'password') logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
xmpp.registerPlugin('xep_0030') xmpp = Example(opts.jid, opts.password)
xmpp.registerPlugin('xep_0004') xmpp.registerPlugin('xep_0030')
xmpp.registerPlugin('xep_0060') xmpp.registerPlugin('xep_0004')
xmpp.registerPlugin('xep_0199') xmpp.registerPlugin('xep_0060')
if xmpp.connect(('talk.google.com', 5222)): xmpp.registerPlugin('xep_0199')
xmpp.process(threaded=False)
print("done") # use this if you don't have pydns, and want to
else: # talk to GoogleTalk (e.g.)
print("Unable to connect.") # if xmpp.connect(('talk.google.com', 5222)):
if xmpp.connect():
xmpp.process(threaded=False)
print("done")
else:
print("Unable to connect.")

View file

@ -16,13 +16,13 @@ import sys
# min_version = '0.6c6' # min_version = '0.6c6'
# else: # else:
# min_version = '0.6a9' # min_version = '0.6a9'
# #
# try: # try:
# use_setuptools(min_version=min_version) # use_setuptools(min_version=min_version)
# except TypeError: # except TypeError:
# # locally installed ez_setup won't have min_version # # locally installed ez_setup won't have min_version
# use_setuptools() # use_setuptools()
# #
# from setuptools import setup, find_packages, Extension, Feature # from setuptools import setup, find_packages, Extension, Feature
VERSION = '0.2.3.1' VERSION = '0.2.3.1'
@ -37,17 +37,13 @@ CLASSIFIERS = [ 'Intended Audience :: Developers',
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
] ]
packages = [ 'sleekxmpp', packages = [ 'sleekxmpp',
'sleekxmpp/plugins', 'sleekxmpp/plugins',
'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
@ -30,223 +30,232 @@ from . import plugins
#from . import stanza #from . import stanza
srvsupport = True srvsupport = True
try: try:
import dns.resolver import dns.resolver
except ImportError: except ImportError:
srvsupport = False srvsupport = False
#class PresenceStanzaType(object): #class PresenceStanzaType(object):
# #
# def fromXML(self, xml): # def fromXML(self, xml):
# self.ptype = xml.get('type') # self.ptype = xml.get('type')
class ClientXMPP(basexmpp, XMLStream): class ClientXMPP(basexmpp, XMLStream):
"""SleekXMPP's client class. Use only for good, not evil.""" """SleekXMPP's client class. Use only for good, not evil."""
def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True): def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True):
global srvsupport global srvsupport
XMLStream.__init__(self) XMLStream.__init__(self)
self.default_ns = 'jabber:client' self.default_ns = 'jabber:client'
basexmpp.__init__(self) basexmpp.__init__(self)
self.plugin_config = plugin_config self.plugin_config = plugin_config
self.escape_quotes = escape_quotes self.escape_quotes = escape_quotes
self.set_jid(jid) self.set_jid(jid)
self.plugin_whitelist = plugin_whitelist self.plugin_whitelist = plugin_whitelist
self.auto_reconnect = True self.auto_reconnect = True
self.srvsupport = srvsupport self.srvsupport = srvsupport
self.password = password self.password = password
self.registered_features = [] self.registered_features = []
self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.server,self.default_ns) self.stream_header = """<stream:stream to='%s' xmlns:stream='http://etherx.jabber.org/streams' xmlns='%s' version='1.0'>""" % (self.server,self.default_ns)
self.stream_footer = "</stream:stream>" self.stream_footer = "</stream:stream>"
#self.map_namespace('http://etherx.jabber.org/streams', 'stream') #self.map_namespace('http://etherx.jabber.org/streams', 'stream')
#self.map_namespace('jabber:client', '') #self.map_namespace('jabber:client', '')
self.features = [] self.features = []
#TODO: Use stream state here #TODO: Use stream state here
self.authenticated = False self.authenticated = False
self.sessionstarted = False self.sessionstarted = False
self.bound = False self.bound = False
self.bindfail = False self.bindfail = False
self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True)) self.is_component = False
self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True)) self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, 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', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True))
self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True) #self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True))
self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True) self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True)
self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource) self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True)
self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session) self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource)
self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session)
#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
#self.register_plugins() #self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
#self.register_plugins()
def __getitem__(self, key):
if key in self.plugin: def __getitem__(self, key):
return self.plugin[key] if key in self.plugin:
else: return self.plugin[key]
logging.warning("""Plugin "%s" is not loaded.""" % key) else:
return False logging.warning("""Plugin "%s" is not loaded.""" % key)
return False
def get(self, key, default):
return self.plugin.get(key, default) def get(self, key, default):
return self.plugin.get(key, default)
def connect(self, address=tuple()): def connect(self, address=tuple()):
"""Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses """Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses
the JID server.""" the JID server."""
if not address or len(address) < 2: if not address or len(address) < 2:
if not self.srvsupport: if not self.srvsupport:
logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using server hostname from JID.") logging.debug("Did not supply (address, port) to connect to and no SRV support is installed (http://www.dnspython.org). Continuing to attempt connection, using server hostname from JID.")
else: else:
logging.debug("Since no address is supplied, attempting SRV lookup.") logging.debug("Since no address is supplied, attempting SRV lookup.")
try: try:
answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV) answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV)
except dns.resolver.NXDOMAIN: except dns.resolver.NXDOMAIN:
logging.debug("No appropriate SRV record found. Using JID server name.") logging.debug("No appropriate SRV record found. Using JID server name.")
else: else:
# pick a random answer, weighted by priority # pick a random answer, weighted by priority
# there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway # there are less verbose ways of doing this (random.choice() with answer * priority), but I chose this way anyway
# suggestions are welcome # suggestions are welcome
addresses = {} addresses = {}
intmax = 0 intmax = 0
priorities = [] priorities = []
for answer in answers: for answer in answers:
intmax += answer.priority intmax += answer.priority
addresses[intmax] = (answer.target.to_text()[:-1], answer.port) addresses[intmax] = (answer.target.to_text()[:-1], answer.port)
priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort() priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort()
picked = random.randint(0, intmax) picked = random.randint(0, intmax)
for priority in priorities: for priority in priorities:
if picked <= priority: if picked <= priority:
address = addresses[priority] address = addresses[priority]
break break
if not address: if not address:
# if all else fails take server from JID. # if all else fails take server from JID.
address = (self.server, 5222) address = (self.server, 5222)
result = XMLStream.connect(self, address[0], address[1], use_tls=True) result = XMLStream.connect(self, address[0], address[1], use_tls=True)
if result: if result:
self.event("connected") self.event("connected")
else: else:
logging.warning("Failed to connect") logging.warning("Failed to connect")
self.event("disconnected") self.event("disconnected")
return result return result
# overriding reconnect and disconnect so that we can get some events # overriding reconnect and disconnect so that we can get some events
# should events be part of or required by xmlstream? Maybe that would be cleaner # should events be part of or required by xmlstream? Maybe that would be cleaner
def reconnect(self): def reconnect(self):
logging.info("Reconnecting") logging.info("Reconnecting")
self.event("disconnected") self.event("disconnected")
XMLStream.reconnect(self) XMLStream.reconnect(self)
def disconnect(self, init=True, close=False, reconnect=False): def disconnect(self, init=True, close=False, reconnect=False):
self.event("disconnected") self.event("disconnected")
XMLStream.disconnect(self, reconnect) XMLStream.disconnect(self, reconnect)
def registerFeature(self, mask, pointer, breaker = False): def registerFeature(self, mask, pointer, breaker = False):
"""Register a stream feature.""" """Register a stream feature."""
self.registered_features.append((MatchXMLMask(mask), pointer, breaker)) self.registered_features.append((MatchXMLMask(mask), pointer, breaker))
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 getRoster(self):
"""Request the roster be sent."""
iq = self.Iq().setValues({'type': 'get'}).enable('roster').send()
self._handleRoster(iq, request=True)
def _handleStreamFeatures(self, features):
self.features = []
for sub in features.xml:
self.features.append(sub.tag)
for subelement in features.xml:
for feature in self.registered_features:
if feature[0].match(subelement):
#if self.maskcmp(subelement, feature[0], True):
if feature[1](subelement) and feature[2]: #if breaker, don't continue
return True
def handler_starttls(self, xml):
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.sendXML(xml)
return True
else:
logging.warning("The module tlslite is required in to some servers, and has not been found.")
return False
def handler_tls_start(self, xml): def delRosterItem(self, jid):
logging.debug("Starting TLS") iq = self.Iq()
if self.startTLS(): iq['type'] = 'set'
raise RestartStream() iq['roster']['items'] = {jid: {'subscription': 'remove'}}
return iq.send()['type'] == 'result'
def handler_sasl_auth(self, xml):
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: def getRoster(self):
return False """Request the roster be sent."""
logging.debug("Starting SASL Auth") iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send()
self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=True) self._handleRoster(iq, request=True)
self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, instream=True)
sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism') def _handleStreamFeatures(self, features):
if len(sasl_mechs): self.features = []
for sasl_mech in sasl_mechs: for sub in features.xml:
self.features.append("sasl:%s" % sasl_mech.text) self.features.append(sub.tag)
if 'sasl:PLAIN' in self.features: for subelement in features.xml:
if sys.version_info < (3,0): for feature in self.registered_features:
self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8')) if feature[0].match(subelement):
else: #if self.maskcmp(subelement, feature[0], True):
self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8')) if feature[1](subelement) and feature[2]: #if breaker, don't continue
else: return True
logging.error("No appropriate login method.")
self.disconnect() def handler_starttls(self, xml):
#if 'sasl:DIGEST-MD5' in self.features: if not self.authenticated and self.ssl_support:
# self._auth_digestmd5() self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, name='TLS Proceed', instream=True)
return True self.sendXML(xml)
return True
def handler_auth_success(self, xml): else:
self.authenticated = True logging.warning("The module tlslite is required in to some servers, and has not been found.")
self.features = [] return False
raise RestartStream()
def handler_auth_fail(self, xml): def handler_tls_start(self, xml):
logging.info("Authentication failed.") logging.debug("Starting TLS")
self.disconnect() if self.startTLS():
self.event("failed_auth") raise RestartStream()
def handler_bind_resource(self, xml): def handler_sasl_auth(self, xml):
logging.debug("Requesting resource: %s" % self.resource) if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
iq = self.Iq(stype='set') return False
res = ET.Element('resource') logging.debug("Starting SASL Auth")
res.text = self.resource self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, name='SASL Sucess', instream=True)
xml.append(res) self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, name='SASL Failure', instream=True)
iq.append(xml) sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism')
response = iq.send() if len(sasl_mechs):
#response = self.send(iq, self.Iq(sid=iq['id'])) for sasl_mech in sasl_mechs:
self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text) self.features.append("sasl:%s" % sasl_mech.text)
self.bound = True if 'sasl:PLAIN' in self.features:
logging.info("Node set to: %s" % self.fulljid) if sys.version_info < (3,0):
if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail: self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username) + b'\x00' + bytes(self.password)).decode('utf-8'))
logging.debug("Established Session") else:
self.sessionstarted = True self.send("""<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>%s</auth>""" % base64.b64encode(b'\x00' + bytes(self.username, 'utf-8') + b'\x00' + bytes(self.password, 'utf-8')).decode('utf-8'))
self.event("session_start") else:
logging.error("No appropriate login method.")
def handler_start_session(self, xml): self.disconnect()
if self.authenticated and self.bound: #if 'sasl:DIGEST-MD5' in self.features:
iq = self.makeIqSet(xml) # self._auth_digestmd5()
response = iq.send() return True
logging.debug("Established Session")
self.sessionstarted = True def handler_auth_success(self, xml):
self.event("session_start") self.authenticated = True
else: self.features = []
#bind probably hasn't happened yet raise RestartStream()
self.bindfail = True
def handler_auth_fail(self, xml):
def _handleRoster(self, iq, request=False): logging.info("Authentication failed.")
if iq['type'] == 'set' or (iq['type'] == 'result' and request): self.disconnect()
for jid in iq['roster']['items']: self.event("failed_auth")
if not jid in self.roster:
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True} def handler_bind_resource(self, xml):
self.roster[jid].update(iq['roster']['items'][jid]) logging.debug("Requesting resource: %s" % self.resource)
if iq['type'] == 'set': xml.clear()
self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster')) iq = self.Iq(stype='set')
self.event("roster_update", iq) if self.resource:
res = ET.Element('resource')
res.text = self.resource
xml.append(res)
iq.append(xml)
response = iq.send()
#response = self.send(iq, self.Iq(sid=iq['id']))
self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text)
self.bound = True
logging.info("Node set to: %s" % self.fulljid)
if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail:
logging.debug("Established Session")
self.sessionstarted = True
self.event("session_start")
def handler_start_session(self, xml):
if self.authenticated and self.bound:
iq = self.makeIqSet(xml)
response = iq.send()
logging.debug("Established Session")
self.sessionstarted = True
self.event("session_start")
else:
#bind probably hasn't happened yet
self.bindfail = True
def _handleRoster(self, iq, request=False):
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
for jid in iq['roster']['items']:
if not jid in self.roster:
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
self.roster[jid].update(iq['roster']['items'][jid])
if iq['type'] == 'set':
self.send(self.Iq().setStanzaValues({'type': 'result', 'id': iq['id']}).enable('roster'))
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,14 +58,10 @@ 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)
@ -77,7 +70,7 @@ class basexmpp(object):
def Presence(self, *args, **kwargs): def Presence(self, *args, **kwargs):
return Presence(self, *args, **kwargs) return Presence(self, *args, **kwargs)
def set_jid(self, jid): def set_jid(self, jid):
"""Rip a JID apart and claim it as our own.""" """Rip a JID apart and claim it as our own."""
self.fulljid = jid self.fulljid = jid
@ -85,12 +78,12 @@ class basexmpp(object):
self.jid = self.getjidbare(jid) self.jid = self.getjidbare(jid)
self.username = jid.split('@', 1)[0] self.username = jid.split('@', 1)[0]
self.server = jid.split('@',1)[-1].split('/', 1)[0] self.server = jid.split('@',1)[-1].split('/', 1)[0]
def process(self, *args, **kwargs): def process(self, *args, **kwargs):
for idx in self.plugin: for idx in self.plugin:
if not self.plugin[idx].post_inited: self.plugin[idx].post_init() if not self.plugin[idx].post_inited: self.plugin[idx].post_init()
return super(basexmpp, self).process(*args, **kwargs) return super(basexmpp, self).process(*args, **kwargs)
def registerPlugin(self, plugin, pconfig = {}): def registerPlugin(self, plugin, pconfig = {}):
"""Register a plugin not in plugins.__init__.__all__ but in the plugins """Register a plugin not in plugins.__init__.__all__ but in the plugins
directory.""" directory."""
@ -105,7 +98,7 @@ class basexmpp(object):
if hasattr(self.plugin[plugin], 'xep'): if hasattr(self.plugin[plugin], 'xep'):
xep = "(XEP-%s) " % self.plugin[plugin].xep xep = "(XEP-%s) " % self.plugin[plugin].xep
logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description)) logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description))
def register_plugins(self): def register_plugins(self):
"""Initiates all plugins in the plugins/__init__.__all__""" """Initiates all plugins in the plugins/__init__.__all__"""
if self.plugin_whitelist: if self.plugin_whitelist:
@ -120,22 +113,24 @@ class basexmpp(object):
# run post_init() for cross-plugin interaction # run post_init() for cross-plugin interaction
for plugin in self.plugin: for plugin in self.plugin:
self.plugin[plugin].post_init() self.plugin[plugin].post_init()
def getNewId(self): def getNewId(self):
with self.id_lock: with self.id_lock:
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,))
#if not type(data) == type(''): #if not type(data) == type(''):
@ -150,41 +145,41 @@ class basexmpp(object):
self.sendRaw(data) self.sendRaw(data)
if mask is not None: if mask is not None:
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):
query = ET.Element("{%s}query" % xmlns) query = ET.Element("{%s}query" % xmlns)
iq.append(query) iq.append(query)
return iq return iq
def makeQueryRoster(self, iq=None): def makeQueryRoster(self, iq=None):
query = ET.Element("{jabber:iq:roster}query") query = ET.Element("{jabber:iq:roster}query")
if iq: if iq:
iq.append(query) iq.append(query)
return query return query
def add_event_handler(self, name, pointer, threaded=False, disposable=False): def add_event_handler(self, name, pointer, threaded=False, disposable=False):
if not name in self.event_handlers: if not name in self.event_handlers:
self.event_handlers[name] = [] self.event_handlers[name] = []
@ -194,27 +189,28 @@ class basexmpp(object):
"""Remove a handler for an event.""" """Remove a handler for an event."""
if not name in self.event_handlers: if not name in self.event_handlers:
return return
# Need to keep handlers that do not use # Need to keep handlers that do not use
# the given function pointer # the given function pointer
def filter_pointers(handler): def filter_pointers(handler):
return handler[0] != pointer return handler[0] != pointer
self.event_handlers[name] = filter(filter_pointers, self.event_handlers[name] = filter(filter_pointers,
self.event_handlers[name]) self.event_handlers[name])
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))
def makeMessage(self, mto, mbody=None, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None): def makeMessage(self, mto, mbody=None, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None):
message = self.Message(sto=mto, stype=mtype, sfrom=mfrom) message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
message['body'] = mbody message['body'] = mbody
@ -222,7 +218,7 @@ class basexmpp(object):
if mnick is not None: message['nick'] = mnick if mnick is not None: message['nick'] = mnick
if mhtml is not None: message['html']['html'] = mhtml if mhtml is not None: message['html']['html'] = mhtml
return message return message
def makePresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None): def makePresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None):
presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto) presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
if pshow is not None: presence['type'] = pshow if pshow is not None: presence['type'] = pshow
@ -231,10 +227,10 @@ class basexmpp(object):
presence['priority'] = ppriority presence['priority'] = ppriority
presence['status'] = pstatus presence['status'] = pstatus
return presence return presence
def sendMessage(self, mto, mbody, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None): def sendMessage(self, mto, mbody, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None):
self.send(self.makeMessage(mto,mbody,msubject,mtype,mhtml,mfrom,mnick)) self.send(self.makeMessage(mto,mbody,msubject,mtype,mhtml,mfrom,mnick))
def sendPresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, pfrom=None, ptype=None): def sendPresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, pfrom=None, ptype=None):
self.send(self.makePresence(pshow,pstatus,ppriority,pto, ptype=ptype, pfrom=pfrom)) self.send(self.makePresence(pshow,pstatus,ppriority,pto, ptype=ptype, pfrom=pfrom))
if not self.sentpresence: if not self.sentpresence:
@ -248,19 +244,19 @@ class basexmpp(object):
nick.text = pnick nick.text = pnick
presence.append(nick) presence.append(nick)
self.send(presence) self.send(presence)
def getjidresource(self, fulljid): def getjidresource(self, fulljid):
if '/' in fulljid: if '/' in fulljid:
return fulljid.split('/', 1)[-1] return fulljid.split('/', 1)[-1]
else: else:
return '' return ''
def getjidbare(self, fulljid): def getjidbare(self, fulljid):
return fulljid.split('/', 1)[0] return fulljid.split('/', 1)[0]
def _handleMessage(self, msg): def _handleMessage(self, msg):
self.event('message', msg) self.event('message', msg)
def _handlePresence(self, presence): def _handlePresence(self, presence):
"""Update roster items based on presence""" """Update roster items based on presence"""
self.event("presence_%s" % presence['type'], presence) self.event("presence_%s" % presence['type'], presence)
@ -301,7 +297,7 @@ class basexmpp(object):
if name: if name:
name = "(%s) " % name name = "(%s) " % name
logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, show,status)) logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, show,status))
def _handlePresenceSubscribe(self, presence): def _handlePresenceSubscribe(self, presence):
"""Handling subscriptions automatically.""" """Handling subscriptions automatically."""
if self.auto_authorize == True: if self.auto_authorize == True:

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
@ -30,59 +30,60 @@ from . import stanza
import hashlib import hashlib
srvsupport = True srvsupport = True
try: try:
import dns.resolver import dns.resolver
except ImportError: except ImportError:
srvsupport = False srvsupport = False
class ComponentXMPP(basexmpp, XMLStream): class ComponentXMPP(basexmpp, XMLStream):
"""SleekXMPP's client class. Use only for good, not evil.""" """SleekXMPP's client class. Use only for good, not evil."""
def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False): def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False):
XMLStream.__init__(self) XMLStream.__init__(self)
if use_jc_ns: if use_jc_ns:
self.default_ns = 'jabber:client' self.default_ns = 'jabber:client'
else: else:
self.default_ns = 'jabber:component:accept' self.default_ns = 'jabber:component:accept'
basexmpp.__init__(self) basexmpp.__init__(self)
self.auto_authorize = None self.auto_authorize = None
self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid self.stream_header = "<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' to='%s'>" % jid
self.stream_footer = "</stream:stream>" self.stream_footer = "</stream:stream>"
self.server_host = host self.server_host = host
self.server_port = port self.server_port = port
self.set_jid(jid) self.set_jid(jid)
self.secret = secret self.secret = secret
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake)) self.is_component = True
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
def __getitem__(self, key):
if key in self.plugin: def __getitem__(self, key):
return self.plugin[key] if key in self.plugin:
else: return self.plugin[key]
logging.warning("""Plugin "%s" is not loaded.""" % key) else:
return False logging.warning("""Plugin "%s" is not loaded.""" % key)
return False
def get(self, key, default):
return self.plugin.get(key, default) def get(self, key, default):
return self.plugin.get(key, default)
def incoming_filter(self, xmlobj):
if xmlobj.tag.startswith('{jabber:client}'): def incoming_filter(self, xmlobj):
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns) if xmlobj.tag.startswith('{jabber:client}'):
for sub in xmlobj: xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
self.incoming_filter(sub) for sub in xmlobj:
return xmlobj self.incoming_filter(sub)
return xmlobj
def start_stream_handler(self, xml): def start_stream_handler(self, xml):
sid = xml.get('id', '') sid = xml.get('id', '')
handshake = ET.Element('{jabber:component:accept}handshake') handshake = ET.Element('{jabber:component:accept}handshake')
if sys.version_info < (3,0): if sys.version_info < (3,0):
handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower() handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower()
else: else:
handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower() handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower()
self.sendXML(handshake) self.sendXML(handshake)
def _handleHandshake(self, xml): def _handleHandshake(self, xml):
self.event("session_start") self.event("session_start")
def connect(self): def connect(self):
logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port)) logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port))
return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port) return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port)

View file

@ -3,14 +3,43 @@
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):
self.condition = condition """
self.text = text A generic exception that may be raised while processing an XMPP stanza
self.etype = etype to indicate that an error response stanza should be sent.
self.extension = extension
self.extension_ns = extension_ns The exception method for stanza objects extending RootStanza will create
self.extension_args = extension_args 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.text = text
self.etype = etype
self.extension = extension
self.extension_ns = extension_ns
self.extension_args = extension_args

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):
"""
def plugin_init(self): Google Talk: Gmail Notifications
self.description = 'Google Talk Gmail Notification' """
self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True)
self.emails = [] def plugin_init(self):
self.description = 'Google Talk: Gmail Notifications'
def handler_gmailcheck(self, payload):
#TODO XEP 30 should cache results and have getFeature self.xmpp.registerHandler(
result = self.xmpp['xep_0030'].getInfo(self.xmpp.server) Callback('Gmail Result',
features = [] MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'): MailBox.namespace,
features.append(feature.get('var')) MailBox.name)),
if 'google:mail:notify' in features: self.handle_gmail))
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.xmpp.registerHandler(
self.getEmail() Callback('Gmail New Mail',
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
def handler_notify(self, xml): NewMail.namespace,
logging.info("New Gmail recieved!") NewMail.name)),
self.xmpp.event('gmail_notify') self.handle_new_mail))
def getEmail(self): registerStanzaPlugin(Iq, GmailQuery)
iq = self.xmpp.makeIqGet() registerStanzaPlugin(Iq, MailBox)
iq.attrib['from'] = self.xmpp.fulljid registerStanzaPlugin(Iq, NewMail)
iq.attrib['to'] = self.xmpp.jid
self.xmpp.makeIqQuery(iq, 'google:mail:notify') self.last_result_time = None
emails = iq.send()
mailbox = emails.find('{google:mail:notify}mailbox') def handle_gmail(self, iq):
total = int(mailbox.get('total-matched', 0)) mailbox = iq['mailbox']
logging.info("%s New Gmail Messages" % total) 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.checkEmail()
def getEmail(self, query=None):
return self.search(query)
def checkEmail(self):
return self.search(newer=self.last_result_time)
def search(self, query=None, newer=None):
if query is None:
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'
@ -296,28 +279,14 @@ 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):
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=''): def handle_form(self, message):
return Form(self.xmpp, ftype, title, instructions) self.xmpp.event("message_xform", message)
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.
See the file LICENSE for copying permission.
SleekXMPP is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
SleekXMPP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SleekXMPP; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
""" """
from 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):
namespace = 'jabber:client'
name = 'error' """
plugin_attrib = 'error' XMPP stanzas of type 'error' should include an <error> stanza that
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')) describes the nature of the error and how it should be handled.
interfaces = set(('code', 'condition', 'text', 'type'))
types = set(('cancel', 'continue', 'modify', 'auth', 'wait')) Use the 'XEP-0086: Error Condition Mappings' plugin to include error
sub_interfaces = set(('text',)) codes used in older XMPP versions.
condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
Example error stanza:
def setup(self, xml=None): <error type="cancel" code="404">
if ElementBase.setup(self, xml): #if we had to generate xml <item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
self['type'] = 'cancel' <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
self['condition'] = 'feature-not-implemented' The item was not found.
if self.parent is not None: </text>
self.parent()['type'] = 'error' </error>
def getCondition(self): Stanza Interface:
for child in self.xml.getchildren(): code -- The error code used in older XMPP versions.
if "{%s}" % self.condition_ns in child.tag: condition -- The name of the condition element.
return child.tag.split('}', 1)[-1] text -- Human readable description of the error.
return '' type -- Error type indicating how the error should be handled.
def setCondition(self, value): Attributes:
if value in self.conditions: conditions -- The set of allowable error condition elements.
for child in self.xml.getchildren(): condition_ns -- The namespace for the condition element.
if "{%s}" % self.condition_ns in child.tag: types -- A set of values indicating how the error
self.xml.remove(child) should be treated.
condition = ET.Element("{%s}%s" % (self.condition_ns, value))
self.xml.append(condition) Methods:
return self setup -- Overrides ElementBase.setup.
getCondition -- Retrieve the name of the condition element.
def delCondition(self): setCondition -- Add a condition element.
return self delCondition -- Remove the condition element.
getText -- Retrieve the contents of the <text> element.
def getText(self): setText -- Set the contents of the <text> element.
text = '' delText -- Remove the <text> element.
textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text") """
if textxml is not None:
text = textxml.text namespace = 'jabber:client'
return text name = 'error'
plugin_attrib = 'error'
def setText(self, value): interfaces = set(('code', 'condition', 'text', 'type'))
self.delText() sub_interfaces = set(('text',))
textxml = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}text') conditions = set(('bad-request', 'conflict', 'feature-not-implemented',
textxml.text = value 'forbidden', 'gone', 'internal-server-error',
self.xml.append(textxml) 'item-not-found', 'jid-malformed', 'not-acceptable',
return self 'not-allowed', 'not-authorized', 'payment-required',
'recipient-unavailable', 'redirect',
def delText(self): 'registration-required', 'remote-server-not-found',
textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text") 'remote-server-timeout', 'resource-constraint',
if textxml is not None: 'service-unavailable', 'subscription-required',
self.xml.remove(textxml) 'undefined-condition', 'unexpected-request'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
types = set(('cancel', 'continue', 'modify', 'auth', 'wait'))
def setup(self, xml=None):
"""
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['condition'] = 'feature-not-implemented'
if self.parent is not None:
self.parent()['type'] = 'error'
def getCondition(self):
"""Return the condition element's name."""
for child in self.xml.getchildren():
if "{%s}" % self.condition_ns in child.tag:
return child.tag.split('}', 1)[-1]
return ''
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:
del self['condition']
self.xml.append(ET.Element("{%s}%s" % (self.condition_ns, value)))
return 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
def getText(self):
"""Retrieve the contents of the <text> element."""
return self._getSubText('{%s}text' % self.condition_ns)
def setText(self, value):
"""
Set the contents of the <text> element.
Arguments:
value -- The new contents for the <text> element.
"""
self._setSubText('{%s}text' % self.condition_ns, text=value)
return self
def delText(self):
"""Remove the <text> element."""
self._delSub('{%s}text' % self.condition_ns)
return self

View file

@ -3,33 +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 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):
namespace = 'http://jabber.org/protocol/xhtml-im'
name = 'html'
plugin_attrib = 'html'
interfaces = set(('html',))
plugin_attrib_map = set()
plugin_xml_map = set()
def setHtml(self, html): """
if isinstance(html, str): XEP-0071: XHTML-IM defines a method for embedding XHTML content
html = ET.XML(html) within a <message> stanza so that lightweight markup can be used
if html.tag != '{http://www.w3.org/1999/xhtml}body': to format the message contents and to create links.
body = ET.Element('{http://www.w3.org/1999/xhtml}body')
body.append(html) Only a subset of XHTML is recommended for use with XHTML-IM.
self.xml.append(body) See the full spec at 'http://xmpp.org/extensions/xep-0071.html'
else: for more information.
self.xml.append(html)
Example stanza:
def getHtml(self): <message to="user@example.com">
html = self.xml.find('{http://www.w3.org/1999/xhtml}body') <body>Non-html message content.</body>
if html is None: return '' <html xmlns="http://jabber.org/protocol/xhtml-im">
return html <body xmlns="http://www.w3.org/1999/xhtml">
<p><b>HTML!</b></p>
def delHtml(self): </body>
if self.parent is not None: </html>
self.parent().xml.remove(self.xml) </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'
name = 'html'
interfaces = set(('body',))
plugin_attrib = name
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):
html = ET.XML(html)
if html.tag != '{http://www.w3.org/1999/xhtml}body':
body = ET.Element('{http://www.w3.org/1999/xhtml}body')
body.append(html)
self.xml.append(body)
else:
self.xml.append(html)
def getBody(self):
"""Return the contents of the HTML body."""
html = self.xml.find('{http://www.w3.org/1999/xhtml}body')
if html is None:
return ''
return html
def delBody(self):
"""Remove the HTML body contents."""
if self.parent is not None:
self.parent().xml.remove(self.xml)
registerStanzaPlugin(Message, HTMLIM)

View file

@ -3,75 +3,175 @@
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):
interfaces = set(('type', 'to', 'from', 'id','query'))
types = set(('get', 'result', 'set', 'error'))
name = 'iq'
plugin_attrib = name
namespace = 'jabber:client'
def __init__(self, *args, **kwargs): """
StanzaBase.__init__(self, *args, **kwargs) XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
if self['id'] == '': requesting and modifying information, similar to HTTP's GET and
if self.stream is not None: POST methods.
self['id'] = self.stream.getNewId()
else: Each <iq> stanza must have an 'id' value which associates the
self['id'] = '0' stanza with the response stanza. XMPP entities must always
be given a response <iq> stanza with a type of 'result' after
def unhandled(self): sending a stanza of type 'get' or 'set'.
if self['type'] in ('get', 'set'):
self.reply() Most uses cases for <iq> stanzas will involve adding a <query>
self['error']['condition'] = 'feature-not-implemented' element whose namespace indicates the type of information
self['error']['text'] = 'No handlers registered for this request.' desired. However, some custom XMPP applications use <iq> stanzas
self.send() as a carrier stanza for an application-specific protocol instead.
def setPayload(self, value): Example <iq> Stanzas:
self.clear() <iq to="user@example.com" type="get" id="314">
StanzaBase.setPayload(self, value) <query xmlns="http://jabber.org/protocol/disco#items" />
return self </iq>
def setQuery(self, value): <iq to="user@localhost" type="result" id="17">
query = self.xml.find("{%s}query" % value) <query xmlns='jabber:iq:roster'>
if query is None and value: <item jid='otheruser@example.net'
self.clear() name='John Doe'
query = ET.Element("{%s}query" % value) subscription='both'>
self.xml.append(query) <group>Friends</group>
return self </item>
</query>
def getQuery(self): </iq>
for child in self.xml.getchildren():
if child.tag.endswith('query'): Stanza Interface:
ns =child.tag.split('}')[0] query -- The namespace of the <query> element if one exists.
if '{' in ns:
ns = ns[1:] Attributes:
return ns types -- May be one of: get, set, result, or error.
return ''
Methods:
def reply(self): __init__ -- Overrides StanzaBase.__init__.
self['type'] = 'result' unhandled -- Send error if there are no handlers.
StanzaBase.reply(self) setPayload -- Overrides StanzaBase.setPayload.
return self setQuery -- Add or modify a <query> element.
getQuery -- Return the namespace of the <query> element.
def delQuery(self): delQuery -- Remove the <query> element.
for child in self.getchildren(): reply -- Overrides StanzaBase.reply
if child.tag.endswith('query'): send -- Overrides StanzaBase.send
self.xml.remove(child) """
return self
namespace = 'jabber:client'
def send(self, block=True, timeout=10): name = 'iq'
if block and self['type'] in ('get', 'set'): interfaces = set(('type', 'to', 'from', 'id', 'query'))
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id'])) types = set(('get', 'result', 'set', 'error'))
self.stream.registerHandler(waitfor) plugin_attrib = name
StanzaBase.send(self)
return waitfor.wait(timeout) def __init__(self, *args, **kwargs):
else: """
return StanzaBase.send(self) Initialize a new <iq> stanza with an 'id' value.
Overrides StanzaBase.__init__.
"""
StanzaBase.__init__(self, *args, **kwargs)
if self['id'] == '':
if self.stream is not None:
self['id'] = self.stream.getNewId()
else:
self['id'] = '0'
def unhandled(self):
"""
Send a feature-not-implemented error if the stanza is not handled.
Overrides StanzaBase.unhandled.
"""
if self['type'] in ('get', 'set'):
self.reply()
self['error']['condition'] = 'feature-not-implemented'
self['error']['text'] = 'No handlers registered for this request.'
self.send()
def 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()
StanzaBase.setPayload(self, value)
return self
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)
if query is None and value:
self.clear()
query = ET.Element("{%s}query" % value)
self.xml.append(query)
return self
def getQuery(self):
"""Return the namespace of the <query> element."""
for child in self.xml.getchildren():
if child.tag.endswith('query'):
ns = child.tag.split('}')[0]
if '{' in ns:
ns = ns[1:]
return ns
return ''
def delQuery(self):
"""Remove the <query> element."""
for child in self.xml.getchildren():
if child.tag.endswith('query'):
self.xml.remove(child)
return self
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'):
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
self.stream.registerHandler(waitfor)
StanzaBase.send(self)
return waitfor.wait(timeout)
else:
return StanzaBase.send(self)

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'))
name = 'message'
plugin_attrib = name
namespace = 'jabber:client'
def getType(self): """
return self.xml.attrib.get('type', 'normal') XMPP's <message> stanzas are a "push" mechanism to send information
to other XMPP entities without requiring a response.
def chat(self):
self['type'] = 'chat' Chat clients will typically use <message> stanzas that have a type
return self of either "chat" or "groupchat".
def normal(self): When handling a message event, be sure to check if the message is
self['type'] = 'normal' an error response.
return self
Example <message> stanzas:
def reply(self, body=None): <message to="user1@example.com" from="user2@example.com">
StanzaBase.reply(self) <body>Hi!</body>
if self['type'] == 'groupchat': </message>
self['to'] = self['to'].bare
del self['id'] <message type="groupchat" to="room@conference.example.com">
if body is not None: <body>Hi everyone!</body>
self['body'] = body </message>
return self
Stanza Interface:
def getMucroom(self): body -- The main contents of the message.
if self['type'] == 'groupchat': subject -- An optional description of the message's contents.
return self['from'].bare mucroom -- (Read-only) The name of the MUC room that sent the message.
else: mucnick -- (Read-only) The MUC nickname of message's sender.
return ''
Attributes:
def setMucroom(self, value): types -- May be one of: normal, chat, headline, groupchat, or error.
pass
Methods:
def delMucroom(self): chat -- Set the message type to 'chat'.
pass normal -- Set the message type to 'normal'.
reply -- Overrides StanzaBase.reply
def getMucnick(self): getType -- Overrides StanzaBase interface
if self['type'] == 'groupchat': getMucroom -- Return the name of the MUC room of the message.
return self['from'].resource setMucroom -- Dummy method to prevent assignment.
else: delMucroom -- Dummy method to prevent deletion.
return '' getMucnick -- Return the MUC nickname of the message's sender.
setMucnick -- Dummy method to prevent assignment.
def setMucnick(self, value): delMucnick -- Dummy method to prevent deletion.
pass """
def delMucnick(self): namespace = 'jabber:client'
pass 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):
"""
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):
"""Set the message type to 'chat'."""
self['type'] = 'chat'
return self
def normal(self):
"""Set the message type to 'chat'."""
self['type'] = 'normal'
return self
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)
if self['type'] == 'groupchat':
self['to'] = self['to'].bare
del self['id']
if body is not None:
self['body'] = body
return self
def getMucroom(self):
"""
Return the name of the MUC room where the message originated.
Read-only stanza interface.
"""
if self['type'] == 'groupchat':
return self['from'].bare
else:
return ''
def getMucnick(self):
"""
Return the nickname of the MUC user that sent the message.
Read-only stanza interface.
"""
if self['type'] == 'groupchat':
return self['from'].resource
else:
return ''
def setMucroom(self, value):
"""Dummy method to prevent modification."""
pass
def delMucroom(self):
"""Dummy method to prevent deletion."""
pass
def setMucnick(self, value):
"""Dummy method to prevent modification."""
pass
def delMucnick(self):
"""Dummy method to prevent deletion."""
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):
namespace = 'http://jabber.org/nick/nick'
name = 'nick'
plugin_attrib = 'nick'
interfaces = set(('nick'))
plugin_attrib_map = set()
plugin_xml_map = set()
def setNick(self, nick): """
self.xml.text = nick XEP-0172: User Nickname allows the addition of a <nick> element
in several stanza types, including <message> and <presence> stanzas.
def getNick(self):
return self.xml.text 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
def delNick(self): may be included when establishing communications with new entities,
if self.parent is not None: such as normal XMPP users or MUC services.
self.parent().xml.remove(self.xml)
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'
name = 'nick'
plugin_attrib = name
interfaces = set(('nick',))
def setNick(self, nick):
"""
Add a <nick> element with the given nickname.
Arguments:
nick -- A human readable, informal name.
"""
self.xml.text = nick
def getNick(self):
"""Return the nickname in the <nick> element."""
return self.xml.text
def delNick(self):
"""Remove the <nick> element."""
if self.parent is not None:
self.parent().xml.remove(self.xml)
registerStanzaPlugin(Message, Nick)
registerStanzaPlugin(Presence, Nick)

View file

@ -3,61 +3,144 @@
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).
def setType(self, value): Since <presence> stanzas are broadcast when an XMPP entity changes
show = self.getShowElement() its status, the bulk of the traffic in an XMPP network will be from
if value in self.types: <presence> stanzas. Therefore, do not include more information than
if show is not None: necessary in a status message or within a <presence> stanza in order
self.xml.remove(show) to help keep the network running smoothly.
if value == 'available':
value = ''
self._setAttr('type', value)
elif value in self.showtypes:
if show is None:
show = ET.Element("{%s}show" % self.namespace)
self.xml.append(show)
show.text = value
return self
def setPriority(self, value): Example <presence> stanzas:
self._setSubText('priority', text = str(value)) <presence />
def getPriority(self): <presence from="user@example.com">
p = self._getSubText('priority') <show>away</show>
if not p: p = 0 <status>Getting lunch.</status>
return int(p) <priority>5</priority>
</presence>
def getType(self):
out = self._getAttr('type') <presence type="unavailable" />
if not out:
show = self.getShowElement() <presence to="user@otherhost.com" type="subscribe" />
if show is not None:
out = show.text Stanza Interface:
if not out or out is None: priority -- A value used by servers to determine message routing.
out = 'available' show -- The type of status, such as away or available for chat.
return out status -- Custom, human readable status message.
def reply(self): Attributes:
if self['type'] == 'unsubscribe': types -- One of: available, unavailable, error, probe,
self['type'] = 'unsubscribed' subscribe, subscribed, unsubscribe,
elif self['type'] == 'subscribe': and unsubscribed.
self['type'] = 'subscribed' showtypes -- One of: away, chat, dnd, and xa.
return StanzaBase.reply(self)
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):
"""
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:
self['show'] = None
if value == 'available':
value = ''
self._setAttr('type', value)
elif value in self.showtypes:
self['show'] = value
return self
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))
def getPriority(self):
"""
Return the value of the <presence> element as an integer.
"""
p = self._getSubText('priority')
if not p:
p = 0
return int(p)
def getType(self):
"""
Return the value of the <presence> stanza's type attribute, or
the value of the <show> element.
"""
out = self._getAttr('type')
if not out:
out = self['show']
if not out or out is None:
out = 'available'
return out
def reply(self):
"""
Set the appropriate presence reply type.
Overrides StanzaBase.reply.
"""
if self['type'] == 'unsubscribe':
self['type'] = 'unsubscribed'
elif self['type'] == 'subscribe':
self['type'] = 'subscribed'
return StanzaBase.reply(self)

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 """
self.reply() A top-level XMPP stanza in an XMLStream.
if isinstance(e, XMPPError): # we raised this deliberately
self['error']['condition'] = e.condition
self['error']['text'] = e.text
if e.extension is not None: # extended error tag
extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension), e.extension_args)
self['error'].xml.append(extxml)
self['error']['type'] = e.etype
else: # we probably didn't raise this on purpose, so send back a traceback
self['error']['condition'] = 'undefined-condition'
if sys.version_info < (3,0):
self['error']['text'] = "SleekXMPP got into trouble."
else:
self['error']['text'] = traceback.format_tb(e.__traceback__)
self.send()
# all jabber:client root stanzas should have the error plugin The RootStanza class provides a more XMPP specific exception
RootStanza.plugin_attrib_map['error'] = Error handler than provided by the generic StanzaBase class.
RootStanza.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
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()
if isinstance(e, XMPPError):
# We raised this deliberately
self['error']['condition'] = e.condition
self['error']['text'] = e.text
if e.extension is not None:
# Extended error tag
extxml = ET.Element("{%s}%s" % (e.extension_ns, e.extension),
e.extension_args)
self['error'].append(extxml)
self['error']['type'] = e.etype
else:
# We probably didn't raise this on purpose, so send a traceback
self['error']['condition'] = 'undefined-condition'
if sys.version_info < (3, 0):
self['error']['text'] = "SleekXMPP got into trouble."
else:
self['error']['text'] = traceback.format_tb(e.__traceback__)
logging.exception('Error handling {%s}%s stanza' %
(self.namespace, self.name))
self.send()
registerStanzaPlugin(RootStanza, Error)

View file

@ -3,51 +3,107 @@
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):
namespace = 'jabber:iq:roster'
name = 'query'
plugin_attrib = 'roster'
interfaces = set(('items',))
sub_interfaces = set()
def setItems(self, items): """
self.delItems() Example roster stanzas:
for jid in items: <iq type="set">
ijid = str(jid) <query xmlns="jabber:iq:roster">
item = ET.Element('{jabber:iq:roster}item', {'jid': ijid}) <item jid="user@example.com" subscription="both" name="User">
if 'subscription' in items[jid]: <group>Friends</group>
item.attrib['subscription'] = items[jid]['subscription'] </item>
if 'name' in items[jid]: </query>
item.attrib['name'] = items[jid]['name'] </iq>
if 'groups' in items[jid]:
for group in items[jid]['groups']: Stanza Inteface:
groupxml = ET.Element('{jabber:iq:roster}group') items -- A dictionary of roster entries contained
groupxml.text = group in the stanza.
item.append(groupxml)
self.xml.append(item) Methods:
return self getItems -- Return a dictionary of roster entries.
setItems -- Add <item> elements.
def getItems(self): delItems -- Remove all <item> elements.
items = {} """
itemsxml = self.xml.findall('{jabber:iq:roster}item')
if itemsxml is not None: namespace = 'jabber:iq:roster'
for itemxml in itemsxml: name = 'query'
item = {} plugin_attrib = 'roster'
item['name'] = itemxml.get('name', '') interfaces = set(('items',))
item['subscription'] = itemxml.get('subscription', '')
item['groups'] = [] def setItems(self, items):
groupsxml = itemxml.findall('{jabber:iq:roster}group') """
if groupsxml is not None: Set the roster entries in the <roster> stanza.
for groupxml in groupsxml:
item['groups'].append(groupxml.text) Uses a dictionary using JIDs as keys, where each entry is itself
items[itemxml.get('jid')] = item a dictionary that contains:
return items name -- An alias or nickname for the JID.
subscription -- The subscription type. Can be one of 'to',
def delItems(self): 'from', 'both', 'none', or 'remove'.
for child in self.xml.getchildren(): groups -- A list of group names to which the JID
self.xml.remove(child) has been assigned.
Arguments:
items -- A dictionary of roster entries.
"""
self.delItems()
for jid in items:
ijid = str(jid)
item = ET.Element('{jabber:iq:roster}item', {'jid': ijid})
if 'subscription' in items[jid]:
item.attrib['subscription'] = items[jid]['subscription']
if 'name' in items[jid]:
name = items[jid]['name']
if name is not None:
item.attrib['name'] = name
if 'groups' in items[jid]:
for group in items[jid]['groups']:
groupxml = ET.Element('{jabber:iq:roster}group')
groupxml.text = group
item.append(groupxml)
self.xml.append(item)
return 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 = {}
itemsxml = self.xml.findall('{jabber:iq:roster}item')
if itemsxml is not None:
for itemxml in itemsxml:
item = {}
item['name'] = itemxml.get('name', '')
item['subscription'] = itemxml.get('subscription', '')
item['groups'] = []
groupsxml = itemxml.findall('{jabber:iq:roster}group')
if groupsxml is not None:
for groupxml in groupsxml:
item['groups'].append(groupxml.text)
items[itemxml.get('jid')] = item
return items
def delItems(self):
"""
Remove all <item> elements from the roster stanza.
"""
for child in self.xml.getchildren():
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,386 +3,516 @@
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):
def __init__(self, jid):
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):
return self.jid
class ElementBase(tostring.ToString):
name = 'stanza'
plugin_attrib = 'plugin'
namespace = 'jabber:client'
interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple()
plugin_attrib_map = {}
plugin_tag_map = {}
subitem = None
def __init__(self, xml=None, parent=None):
if parent is None:
self.parent = None
else:
self.parent = weakref.ref(parent)
self.xml = xml
self.plugins = {}
self.iterables = []
self.idx = 0
if not self.setup(xml):
for child in self.xml.getchildren():
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)
if self.subitem is not None:
for sub in self.subitem:
if child.tag == "{%s}%s" % (sub.namespace, sub.name):
self.iterables.append(sub(xml=child, parent=self))
break
@property # Used to check if an argument is an XML object.
def attrib(self): #backwards compatibility XML_TYPE = type(ET.Element('xml'))
return self
def __iter__(self):
self.idx = 0
return self
def __bool__(self): def registerStanzaPlugin(stanza, plugin):
return True """
Associate a stanza object as a plugin for another stanza.
def __next__(self):
self.idx += 1
if self.idx > len(self.iterables):
self.idx = 0
raise StopIteration
return self.iterables[self.idx - 1]
def next(self):
return self.__next__()
def __len__(self): Arguments:
return len(self.iterables) stanza -- The class of the parent stanza.
plugin -- The class of the plugin stanza.
def append(self, item): """
if not isinstance(item, ElementBase): tag = "{%s}%s" % (plugin.namespace, plugin.name)
if type(item) == xmltester: stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
return self.appendxml(item) stanza.plugin_tag_map[tag] = plugin
else:
raise TypeError
self.xml.append(item.xml)
self.iterables.append(item)
return self
def pop(self, idx=0):
aff = self.iterables.pop(idx)
self.xml.remove(aff.xml)
return aff
def get(self, key, defaultvalue=None):
value = self[key]
if value is None or value == '':
return defaultvalue
return value
def keys(self):
out = []
out += [x for x in self.interfaces]
out += [x for x in self.plugins]
if self.iterables:
out.append('substanzas')
return tuple(out)
def match(self, matchstring):
if isinstance(matchstring, str):
nodes = matchstring.split('/')
else:
nodes = matchstring
tagargs = nodes[0].split('@')
if tagargs[0] not in (self.plugins, self.plugin_attrib): return False
founditerable = False
for iterable in self.iterables:
if nodes[1:] == []:
break
founditerable = iterable.match(nodes[1:])
if founditerable: break;
for evals in tagargs[1:]:
x,y = evals.split('=')
if self[x] != y: return False
if not founditerable and len(nodes) > 1:
next = nodes[1].split('@')[0]
if next in self.plugins:
return self.plugins[next].match(nodes[1:])
else:
return False
return True
def find(self, xpath): # for backwards compatiblity, expose elementtree interface
return self.xml.find(xpath)
def findall(self, 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): class ElementBase(object):
self.initPlugin(attrib) name = 'stanza'
return self plugin_attrib = 'plugin'
namespace = 'jabber:client'
def initPlugin(self, attrib): interfaces = set(('type', 'to', 'from', 'id', 'payload'))
if attrib not in self.plugins: types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self) sub_interfaces = tuple()
plugin_attrib_map = {}
def __getitem__(self, attrib): plugin_tag_map = {}
if attrib == 'substanzas': subitem = None
return self.iterables
elif attrib in self.interfaces: def __init__(self, xml=None, parent=None):
if hasattr(self, "get%s" % attrib.title()): """
return getattr(self, "get%s" % attrib.title())() Create a new stanza object.
else:
if attrib in self.sub_interfaces: Arguments:
return self._getSubText(attrib) xml -- Initialize the stanza with optional existing XML.
else: parent -- Optional stanza object that contains this stanza.
return self._getAttr(attrib) """
elif attrib in self.plugin_attrib_map: self.xml = xml
if attrib not in self.plugins: self.initPlugin(attrib) self.plugins = {}
return self.plugins[attrib] self.iterables = []
else: self.idx = 0
return '' if parent is None:
self.parent = None
def __setitem__(self, attrib, value): else:
if attrib in self.interfaces: self.parent = weakref.ref(parent)
if value is not None:
if hasattr(self, "set%s" % attrib.title()): if self.setup(xml):
getattr(self, "set%s" % attrib.title())(value,) # If we generated our own XML, then everything is ready.
else: return
if attrib in self.sub_interfaces:
return self._setSubText(attrib, text=value) # Initialize values using provided XML
else: for child in self.xml.getchildren():
self._setAttr(attrib, value) if child.tag in self.plugin_tag_map:
else: plugin = self.plugin_tag_map[child.tag]
self.__delitem__(attrib) self.plugins[plugin.plugin_attrib] = plugin(child, self)
elif attrib in self.plugin_attrib_map: if self.subitem is not None:
if attrib not in self.plugins: self.initPlugin(attrib) for sub in self.subitem:
self.initPlugin(attrib) if child.tag == "{%s}%s" % (sub.namespace, sub.name):
self.plugins[attrib][attrib] = value self.iterables.append(sub(child, self))
return self break
def __delitem__(self, attrib): def setup(self, xml=None):
if attrib.lower() in self.interfaces: """
if hasattr(self, "del%s" % attrib.title()): Initialize the stanza's XML contents.
getattr(self, "del%s" % attrib.title())()
else: Will return True if XML was generated according to the stanza's
if attrib in self.sub_interfaces: definition.
return self._delSub(attrib)
else: Arguments:
self._delAttr(attrib) xml -- Optional XML object to use for the stanza's content
elif attrib in self.plugin_attrib_map: instead of generating XML.
if attrib in self.plugins: """
del self.plugins[attrib] if self.xml is None:
return self self.xml = xml
def __eq__(self, other): if self.xml is None:
if not isinstance(other, ElementBase): # Generate XML from the stanza definition
return False for ename in self.name.split('/'):
values = self.getValues() new = ET.Element("{%s}%s" % (self.namespace, ename))
for key in other: if self.xml is None:
if key not in values or values[key] != other[key]: self.xml = new
return False else:
return True last_xml.append(new)
last_xml = new
def _setAttr(self, name, value): if self.parent is not None:
if value is None or value == '': self.parent().xml.append(self.xml)
self.__delitem__(name)
else: # We had to generate XML
self.xml.attrib[name] = value return True
else:
def _delAttr(self, name): # We did not generate XML
if name in self.xml.attrib: return False
del self.xml.attrib[name]
def enable(self, attrib):
def _getAttr(self, name): """
return self.xml.attrib.get(name, '') Enable and initialize a stanza plugin.
def _getSubText(self, name): Alias for initPlugin.
stanza = self.xml.find("{%s}%s" % (self.namespace, name))
if stanza is None or stanza.text is None: Arguments:
return '' attrib -- The stanza interface for the plugin.
else: """
return stanza.text return self.initPlugin(attrib)
def _setSubText(self, name, attrib={}, text=None): def initPlugin(self, attrib):
if text is None or text == '': """
return self.__delitem__(name) Enable and initialize a stanza plugin.
stanza = self.xml.find("{%s}%s" % (self.namespace, name))
if stanza is None: Arguments:
#self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib)) attrib -- The stanza interface for the plugin.
stanza = ET.Element("{%s}%s" % (self.namespace, name)) """
self.xml.append(stanza) if attrib not in self.plugins:
stanza.text = text plugin_class = self.plugin_attrib_map[attrib]
return stanza self.plugins[attrib] = plugin_class(parent=self)
return self
def _delSub(self, name):
for child in self.xml.getchildren(): def getStanzaValues(self):
if child.tag == "{%s}%s" % (self.namespace, name): """
self.xml.remove(child) Return a dictionary of the stanza's interface values.
def getValues(self): Stanza plugin values are included as nested dictionaries.
out = {} """
for interface in self.interfaces: values = {}
out[interface] = self[interface] for interface in self.interfaces:
for pluginkey in self.plugins: values[interface] = self[interface]
out[pluginkey] = self.plugins[pluginkey].getValues() for plugin, stanza in self.plugins.items():
if self.iterables: values[plugin] = stanza.getStanzaValues()
iterables = [] if self.iterables:
for stanza in self.iterables: iterables = []
iterables.append(stanza.getValues()) for stanza in self.iterables:
iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)}) iterables.append(stanza.getStanzaValues())
out['substanzas'] = iterables iterables[-1].update({
return out '__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)
})
def setValues(self, attrib): values['substanzas'] = iterables
for interface in attrib: return values
if interface == 'substanzas':
for subdict in attrib['substanzas']: def setStanzaValues(self, values):
if '__childtag__' in subdict: """
for subclass in self.subitem: Set multiple stanza interface values using a dictionary.
if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name):
sub = subclass(parent=self) Stanza plugin values may be set using nested dictionaries.
sub.setValues(subdict)
self.iterables.append(sub) Arguments:
break values -- A dictionary mapping stanza interface with values.
elif interface in self.interfaces: Plugin interfaces may accept a nested dictionary that
self[interface] = attrib[interface] will be used recursively.
elif interface in self.plugin_attrib_map and interface not in self.plugins: """
self.initPlugin(interface) for interface, value in values.items():
if interface in self.plugins: if interface == 'substanzas':
self.plugins[interface].setValues(attrib[interface]) for subdict in value:
return self if '__childtag__' in subdict:
for subclass in self.subitem:
def appendxml(self, xml): child_tag = "{%s}%s" % (subclass.namespace,
self.xml.append(xml) subclass.name)
return self if subdict['__childtag__'] == child_tag:
sub = subclass(parent=self)
#def __del__(self): #prevents garbage collection of reference cycle sub.setStanzaValues(subdict)
# if self.parent is not None: self.iterables.append(sub)
# self.parent.xml.remove(self.xml) 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
def attrib(self): #backwards compatibility
return self
def __iter__(self):
self.idx = 0
return self
def __bool__(self):
return True
def __next__(self):
self.idx += 1
if self.idx > len(self.iterables):
self.idx = 0
raise StopIteration
return self.iterables[self.idx - 1]
def next(self):
return self.__next__()
def __len__(self):
return len(self.iterables)
def append(self, item):
if not isinstance(item, ElementBase):
if type(item) == XML_TYPE:
return self.appendxml(item)
else:
raise TypeError
self.xml.append(item.xml)
self.iterables.append(item)
return self
def pop(self, idx=0):
aff = self.iterables.pop(idx)
self.xml.remove(aff.xml)
return aff
def get(self, key, defaultvalue=None):
value = self[key]
if value is None or value == '':
return defaultvalue
return value
def keys(self):
out = []
out += [x for x in self.interfaces]
out += [x for x in self.plugins]
if self.iterables:
out.append('substanzas')
return tuple(out)
def match(self, matchstring):
if isinstance(matchstring, str):
nodes = matchstring.split('/')
else:
nodes = matchstring
tagargs = nodes[0].split('@')
if tagargs[0] not in (self.plugins, self.plugin_attrib): return False
founditerable = False
for iterable in self.iterables:
if nodes[1:] == []:
break
founditerable = iterable.match(nodes[1:])
if founditerable: break;
for evals in tagargs[1:]:
x,y = evals.split('=')
if self[x] != y: return False
if not founditerable and len(nodes) > 1:
next = nodes[1].split('@')[0]
if next in self.plugins:
return self.plugins[next].match(nodes[1:])
else:
return False
return True
def find(self, xpath): # for backwards compatiblity, expose elementtree interface
return self.xml.find(xpath)
def findall(self, xpath):
return self.xml.findall(xpath)
def __delitem__(self, attrib):
if attrib.lower() in self.interfaces:
if hasattr(self, "del%s" % attrib.title()):
getattr(self, "del%s" % attrib.title())()
else:
if attrib in self.sub_interfaces:
return self._delSub(attrib)
else:
self._delAttr(attrib)
elif attrib in self.plugin_attrib_map:
if attrib in self.plugins:
del self.plugins[attrib]
return self
def __eq__(self, other):
if not isinstance(other, ElementBase):
return False
values = self.getStanzaValues()
for key in other:
if key not in values or values[key] != other[key]:
return False
return True
def _setAttr(self, name, value):
if value is None or value == '':
self.__delitem__(name)
else:
self.xml.attrib[name] = value
def _delAttr(self, name):
if name in self.xml.attrib:
del self.xml.attrib[name]
def _getAttr(self, name, default=''):
return self.xml.attrib.get(name, default)
def _getSubText(self, 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:
return ''
else:
return stanza.text
def _setSubText(self, name, attrib={}, text=None):
if '}' not in name:
name = "{%s}%s" % (self.namespace, name)
if text is None or text == '':
return self.__delitem__(name)
stanza = self.xml.find(name)
if stanza is None:
stanza = ET.Element(name)
self.xml.append(stanza)
stanza.text = text
return stanza
def _delSub(self, name):
if '}' not in name:
name = "{%s}%s" % (self.namespace, name)
for child in self.xml.getchildren():
if child.tag == name:
self.xml.remove(child)
def appendxml(self, xml):
self.xml.append(xml)
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
# if self.parent is not None:
# self.parent.xml.remove(self.xml)
class StanzaBase(ElementBase): class StanzaBase(ElementBase):
name = 'stanza' name = 'stanza'
namespace = 'jabber:client' namespace = 'jabber:client'
interfaces = set(('type', 'to', 'from', 'id', 'payload')) interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple() sub_interfaces = tuple()
def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None): def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None):
self.stream = stream self.stream = stream
if stream is not None: if stream is not None:
self.namespace = stream.default_ns self.namespace = stream.default_ns
ElementBase.__init__(self, xml) ElementBase.__init__(self, xml)
if stype is not None: if stype is not None:
self['type'] = stype self['type'] = stype
if sto is not None: if sto is not None:
self['to'] = sto self['to'] = sto
if sfrom is not None: if sfrom is not None:
self['from'] = sfrom self['from'] = sfrom
self.tag = "{%s}%s" % (self.namespace, self.name) self.tag = "{%s}%s" % (self.namespace, self.name)
def setType(self, value):
if value in self.types:
self.xml.attrib['type'] = value
return self
def getPayload(self): def setType(self, value):
return self.xml.getchildren() if value in self.types:
self.xml.attrib['type'] = value
def setPayload(self, value): return self
self.xml.append(value)
return self
def delPayload(self):
self.clear()
return self
def clear(self):
for child in self.xml.getchildren():
self.xml.remove(child)
for plugin in list(self.plugins.keys()):
del self.plugins[plugin]
return self
def reply(self):
self['from'], self['to'] = self['to'], self['from']
self.clear()
return self
def error(self):
self['type'] = 'error'
return self
def getTo(self):
return JID(self._getAttr('to'))
def setTo(self, value):
return self._setAttr('to', str(value))
def getFrom(self):
return JID(self._getAttr('from'))
def setFrom(self, value):
return self._setAttr('from', str(value))
def unhandled(self):
pass
def exception(self, e):
logging.error(traceback.format_tb(e))
def send(self):
self.stream.sendRaw(self.__str__())
def getPayload(self):
return self.xml.getchildren()
def setPayload(self, value):
self.xml.append(value)
return self
def delPayload(self):
self.clear()
return self
def clear(self):
for child in self.xml.getchildren():
self.xml.remove(child)
for plugin in list(self.plugins.keys()):
del self.plugins[plugin]
return 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']
else:
self['to'] = self['from']
del self['from']
self.clear()
return self
def error(self):
self['type'] = 'error'
return self
def getTo(self):
return JID(self._getAttr('to'))
def setTo(self, value):
return self._setAttr('to', str(value))
def getFrom(self):
return JID(self._getAttr('from'))
def setFrom(self, value):
return self._setAttr('from', str(value))
def unhandled(self):
pass
def exception(self, e):
logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name))
def send(self):
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;' else:
elif c == '<': from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape
text[cc] = '&lt;'
elif c == '>': __all__ = ['tostring', 'xml_escape']
text[cc] = '&gt;'
elif c == "'":
text[cc] = '&apos;'
else:
text[cc] = '&quot;'
cc += 1
return ''.join(text)

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
@ -36,7 +38,7 @@ if sys.version_info < (3, 0):
#monkey patch broken filesocket object #monkey patch broken filesocket object
from . import filesocket from . import filesocket
#socket._fileobject = filesocket.filesocket #socket._fileobject = filesocket.filesocket
class RestartStream(Exception): class RestartStream(Exception):
pass pass
@ -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>"
@ -81,7 +84,7 @@ class XMLStream(object):
self.namespace_map = {} self.namespace_map = {}
self.run = True self.run = True
def setSocket(self, socket): def setSocket(self, socket):
"Set the socket" "Set the socket"
self.socket = socket self.socket = socket
@ -89,10 +92,10 @@ class XMLStream(object):
self.filesocket = socket.makefile('rb', 0) # ElementTree.iterparse requires a file. 0 buffer files have to be binary self.filesocket = socket.makefile('rb', 0) # ElementTree.iterparse requires a file. 0 buffer files have to be binary
self.state.set('connected', True) self.state.set('connected', True)
def setFileSocket(self, filesocket): def setFileSocket(self, filesocket):
self.filesocket = filesocket self.filesocket = filesocket
def connect(self, host='', port=0, use_ssl=False, use_tls=True): def connect(self, host='', port=0, use_ssl=False, use_tls=True):
"Link to connectTCP" "Link to connectTCP"
return self.connectTCP(host, port, use_ssl, use_tls) return self.connectTCP(host, port, use_ssl, use_tls)
@ -124,7 +127,7 @@ class XMLStream(object):
except socket.error as serr: except socket.error as serr:
logging.error("Could not connect. Socket Error #%s: %s" % (serr.errno, serr.strerror)) logging.error("Could not connect. Socket Error #%s: %s" % (serr.errno, serr.strerror))
time.sleep(1) time.sleep(1)
def connectUnix(self, filepath): def connectUnix(self, filepath):
"Connect to Unix file and create socket" "Connect to Unix file and create socket"
@ -145,7 +148,7 @@ class XMLStream(object):
logging.warning("Tried to enable TLS, but ssl module not found.") logging.warning("Tried to enable TLS, but ssl module not found.")
return False return False
raise RestartStream() raise RestartStream()
def process(self, threaded=True): def process(self, threaded=True):
self.scheduler.process(threaded=True) self.scheduler.process(threaded=True)
for t in range(0, HANDLER_THREADS): for t in range(0, HANDLER_THREADS):
@ -159,10 +162,10 @@ class XMLStream(object):
self.__thread['process'].start() self.__thread['process'].start()
else: else:
self._process() self._process()
def schedule(self, name, seconds, callback, args=None, kwargs=None, repeat=False): def schedule(self, name, seconds, callback, args=None, kwargs=None, repeat=False):
self.scheduler.add(name, seconds, callback, args, kwargs, repeat, qpointer=self.eventqueue) self.scheduler.add(name, seconds, callback, args, kwargs, repeat, qpointer=self.eventqueue)
def _process(self): def _process(self):
"Start processing the socket." "Start processing the socket."
firstrun = True firstrun = True
@ -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()
@ -211,7 +214,7 @@ class XMLStream(object):
#self.__thread['readXML'].start() #self.__thread['readXML'].start()
#self.__thread['spawnEvents'] = threading.Thread(name='spawnEvents', target=self.__spawnEvents) #self.__thread['spawnEvents'] = threading.Thread(name='spawnEvents', target=self.__spawnEvents)
#self.__thread['spawnEvents'].start() #self.__thread['spawnEvents'].start()
def __readXML(self): def __readXML(self):
"Parses the incoming stream, adding to xmlin queue as it goes" "Parses the incoming stream, adding to xmlin queue as it goes"
#build cElementTree object from expat was we go #build cElementTree object from expat was we go
@ -244,7 +247,7 @@ class XMLStream(object):
if event == b'start': if event == b'start':
edepth += 1 edepth += 1
logging.debug("Ending readXML loop") logging.debug("Ending readXML loop")
def _sendThread(self): def _sendThread(self):
while self.run: while self.run:
data = self.sendqueue.get(True) data = self.sendqueue.get(True)
@ -257,14 +260,13 @@ 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):
self.sendqueue.put(data) self.sendqueue.put(data)
return True return True
def disconnect(self, reconnect=False): def disconnect(self, reconnect=False):
self.state.set('reconnect', reconnect) self.state.set('reconnect', reconnect)
if self.state['disconnecting']: if self.state['disconnecting']:
@ -290,41 +292,40 @@ class XMLStream(object):
if self.state['processing']: if self.state['processing']:
#raise CloseStream #raise CloseStream
pass pass
def reconnect(self): def reconnect(self):
self.state.set('tls',False) self.state.set('tls',False)
self.state.set('ssl',False) self.state.set('ssl',False)
time.sleep(1) time.sleep(1)
self.connect() self.connect()
def incoming_filter(self, xmlobj): def incoming_filter(self, xmlobj):
return xmlobj return xmlobj
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:
stanza.unhandled() stanza.unhandled()
#loop through handlers and test match #loop through handlers and test match
#spawn threads as necessary, call handlers, sending Stanza #spawn threads as necessary, call handlers, sending Stanza
def _eventRunner(self): def _eventRunner(self):
logging.debug("Loading event runner") logging.debug("Loading event runner")
while self.run: while self.run:
@ -344,22 +345,22 @@ 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
def registerHandler(self, handler, before=None, after=None): def registerHandler(self, handler, before=None, after=None):
"Add handler with matcher class and parameters." "Add handler with matcher class and parameters."
self.__handlers.append(handler) self.__handlers.append(handler)
def removeHandler(self, name): def removeHandler(self, name):
"Removes the handler." "Removes the handler."
idx = 0 idx = 0
@ -368,81 +369,27 @@ class XMLStream(object):
self.__handlers.pop(idx) self.__handlers.pop(idx)
return return
idx += 1 idx += 1
def registerStanza(self, stanza_class): def registerStanza(self, stanza_class):
"Adds stanza. If root stanzas build stanzas sent in events while non-root stanzas build substanza objects." "Adds stanza. If root stanzas build stanzas sent in events while non-root stanzas build substanza objects."
self.__root_stanza.append(stanza_class) self.__root_stanza.append(stanza_class)
def registerStanzaExtension(self, stanza_class, stanza_extension): def registerStanzaExtension(self, stanza_class, stanza_extension):
if stanza_class not in stanza_extensions: if stanza_class not in stanza_extensions:
stanza_extensions[stanza_class] = [stanza_extension] stanza_extensions[stanza_class] = [stanza_extension]
else: else:
stanza_extensions[stanza_class].append(stanza_extension) stanza_extensions[stanza_class].append(stanza_extension)
def removeStanza(self, stanza_class, root=False): def removeStanza(self, stanza_class, root=False):
"Removes the stanza's registration." "Removes the stanza's registration."
if root: if root:
del self.__root_stanza[stanza_class] del self.__root_stanza[stanza_class]
else: else:
del self.__stanza[stanza_class] del self.__stanza[stanza_class]
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,155 +1,176 @@
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]])
info.delIdentity('automation', 'commands') info.delIdentity('automation', 'commands')
self.failUnless(info.getIdentities() == []) self.failUnless(info.getIdentities() == [])
info.setIdentities(ids) info.setIdentities(ids)
self.failUnless(info.getIdentities() == ids) self.failUnless(info.getIdentities() == ids)
info.delIdentity('automation', 'commands') info.delIdentity('automation', 'commands')
self.failUnless(info.getIdentities() == [ids[1]]) self.failUnless(info.getIdentities() == [ids[1]])
info.delIdentities() info.delIdentities()
self.failUnless(info.getIdentities() == []) self.failUnless(info.getIdentities() == [])
def testAddRemoveFeatures(self): def testAddRemoveFeatures(self):
"""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]])
info.delFeature('foo') info.delFeature('foo')
self.failUnless(info.getFeatures() == []) self.failUnless(info.getFeatures() == [])
info.setFeatures(features) info.setFeatures(features)
self.failUnless(info.getFeatures() == features) self.failUnless(info.getFeatures() == features)
info.delFeature('bar') info.delFeature('bar')
self.failUnless(info.getFeatures() == ['foo', 'baz']) self.failUnless(info.getFeatures() == ['foo', 'baz'])
info.delFeatures() info.delFeatures()
self.failUnless(info.getFeatures() == []) self.failUnless(info.getFeatures() == [])
def testAddRemoveItems(self): def testAddRemoveItems(self):
"""Test adding and removing items to disco#items stanza""" """Test adding and removing items to disco#items stanza"""
items = [('user@localhost', None, None), items = [('user@localhost', None, None),
('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]))
self.failUnless(info.getItems() == [items[0]], info.getItems()) self.failUnless(info.getItems() == [items[0]], info.getItems())
info.delItem('user@localhost') info.delItem('user@localhost')
self.failUnless(info.getItems() == []) self.failUnless(info.getItems() == [])
info.setItems(items) info.setItems(items)
self.failUnless(info.getItems() == items) self.failUnless(info.getItems() == items)
info.delItem('user@localhost', 'foo') info.delItem('user@localhost', 'foo')
self.failUnless(info.getItems() == [items[0], items[2]]) self.failUnless(info.getItems() == [items[0], items[2]])
info.delItems() info.delItems()
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,44 +1,57 @@
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):
def setUp(self): class TestMessageStanzas(SleekTest):
import sleekxmpp.stanza.message as m
from sleekxmpp.basexmpp import stanzaPlugin
from sleekxmpp.stanza.htmlim import HTMLIM
stanzaPlugin(m.Message, HTMLIM)
self.m = m
def testGroupchatReplyRegression(self):
"Regression groupchat reply should be to barejid"
msg = self.m.Message()
msg['to'] = 'me@myserver.tld'
msg['from'] = 'room@someservice.someserver.tld/somenick'
msg['type'] = 'groupchat'
msg['body'] = "this is a message"
msg.reply()
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
def testAttribProperty(self): def setUp(self):
"Test attrib property returning self" registerStanzaPlugin(Message, HTMLIM)
msg = self.m.Message()
msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
self.failUnless(str(msg['to']) == 'usr@server.tld')
def testHTMLPlugin(self):
"Test message/html/html 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.m.Message()
msg['to'] = "fritzy@netflint.net/sleekxmpp"
msg['body'] = "this is the plaintext message"
msg['type'] = 'chat'
p = ET.Element('{http://www.w3.org/1999/xhtml}p')
p.text = "This is the htmlim message"
msg['html']['html'] = p
msg2 = self.m.Message()
values = msg.getValues()
msg2.setValues(values)
self.failUnless(msgtxt == str(msg) == str(msg2))
suite = unittest.TestLoader().loadTestsFromTestCase(testmessagestanzas) def testGroupchatReplyRegression(self):
"Regression groupchat reply should be to barejid"
msg = self.Message()
msg['to'] = 'me@myserver.tld'
msg['from'] = 'room@someservice.someserver.tld/somenick'
msg['type'] = 'groupchat'
msg['body'] = "this is a message"
msg.reply()
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
def testAttribProperty(self):
"Test attrib property returning self"
msg = self.Message()
msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
self.failUnless(str(msg['to']) == 'usr@server.tld')
def testHTMLPlugin(self):
"Test message/html/body stanza"
msg = self.Message()
msg['to'] = "fritzy@netflint.net/sleekxmpp"
msg['body'] = "this is the plaintext message"
msg['type'] = 'chat'
p = ET.Element('{http://www.w3.org/1999/xhtml}p')
p.text = "This is the htmlim message"
msg['html']['body'] = p
self.checkMessage(msg, """
<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>""")
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):
"Regression check presence['type'] = 'dnd' show value working"
p = self.p.Presence()
p['type'] = 'dnd'
self.failUnless(str(p) == "<presence><show>dnd</show></presence>")
def testPresenceUnsolicitedOffline(self):
"Unsolicted offline presence does not spawn changed_status or update roster"
p = self.p.Presence()
p['type'] = 'unavailable'
p['from'] = 'bill@chadmore.com/gmail15af'
import sleekxmpp
c = sleekxmpp.ClientXMPP('crap@wherever', 'password')
happened = []
def handlechangedpresence(event):
happened.append(True)
c.add_event_handler("changed_status", handlechangedpresence)
c._handlePresence(p)
self.failUnless(happened == [], "changed_status event triggered for superfulous unavailable presence")
self.failUnless(c.roster == {}, "Roster updated for superfulous unavailable presence")
suite = unittest.TestLoader().loadTestsFromTestCase(testpresencestanzas) def testPresenceShowRegression(self):
"""Regression check presence['type'] = 'dnd' show value working"""
p = self.Presence()
p['type'] = 'dnd'
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):
"""
Unsolicted offline presence does not spawn changed_status
or update the roster.
"""
p = self.Presence()
p['type'] = 'unavailable'
p['from'] = 'bill@chadmore.com/gmail15af'
c = sleekxmpp.ClientXMPP('crap@wherever', 'password')
happened = []
def handlechangedpresence(event):
happened.append(True)
c.add_event_handler("changed_status", handlechangedpresence)
c._handlePresence(p)
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)

View file

@ -1,315 +1,511 @@
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>
def testSubscriptions(self): </iq>""")
"Testing iq/pubsub/subscriptions/subscription stanzas"
iq = self.ps.Iq()
sub1 = self.ps.Subscription()
sub1['node'] = 'testnode'
sub1['jid'] = 'steve@myserver.tld/someresource'
sub2 = self.ps.Subscription()
sub2['node'] = 'testnode2'
sub2['jid'] = 'boogers@bork.top/bill'
sub2['subscription'] = 'subscribed'
iq['pubsub']['subscriptions'].append(sub1)
iq['pubsub']['subscriptions'].append(sub2)
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscriptions><subscription node="testnode" jid="steve@myserver.tld/someresource" /><subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" /></subscriptions></pubsub></iq>"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
def testOptionalSettings(self):
"Testing iq/pubsub/subscription/subscribe-options stanzas"
iq = self.ps.Iq()
iq['pubsub']['subscription']['suboptions']['required'] = True
iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas'
iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp"
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>"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
def testItems(self):
"Testing iq/pubsub/items stanzas"
iq = self.ps.Iq()
iq['pubsub']['items']
payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""")
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""")
item = self.ps.Item()
item['id'] = 'asdf'
item['payload'] = payload
item2 = self.ps.Item()
item2['id'] = 'asdf2'
item2['payload'] = payload2
iq['pubsub']['items'].append(item)
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>"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
def testCreate(self):
"Testing iq/pubsub/create&configure stanzas"
from sleekxmpp.plugins import xep_0004
iq = self.ps.Iq()
iq['pubsub']['create']['node'] = 'mynode'
form = xep_0004.Form()
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
iq['pubsub']['configure']['config'] = form
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>"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
def testState(self):
"Testing iq/psstate stanzas"
from sleekxmpp.plugins import xep_0004
iq = self.ps.Iq()
iq['psstate']['node']= 'mynode'
iq['psstate']['item']= 'myitem'
pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
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>"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
def testDefault(self):
"Testing iq/pubsub_owner/default stanzas"
from sleekxmpp.plugins import xep_0004
iq = self.ps.Iq()
iq['pubsub_owner']['default']
iq['pubsub_owner']['default']['node'] = 'mynode'
iq['pubsub_owner']['default']['type'] = 'leaf'
form = xep_0004.Form()
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
iq['pubsub_owner']['default']['config'] = form
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>"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
def testSubscribe(self):
"Testing iq/pubsub/subscribe stanzas"
from sleekxmpp.plugins import xep_0004
iq = self.ps.Iq()
iq['pubsub']['subscribe']['options']
iq['pubsub']['subscribe']['node'] = 'cheese'
iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp'
iq['pubsub']['subscribe']['options']['node'] = 'cheese'
iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp'
form = xep_0004.Form()
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
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>"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
def testPublish(self):
"Testing iq/pubsub/publish stanzas"
iq = self.ps.Iq()
iq['pubsub']['publish']['node'] = 'thingers'
payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""")
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""")
item = self.ps.Item()
item['id'] = 'asdf'
item['payload'] = payload
item2 = self.ps.Item()
item2['id'] = 'asdf2'
item2['payload'] = payload2
iq['pubsub']['publish'].append(item)
iq['pubsub']['publish'].append(item2)
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><publish node="thingers"><item id="asdf"><thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1"><child1 /><child2 foo="bar" normandy="cheese" /></thinger></item><item id="asdf2"><thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12"><child12 /><child22 foo="bar2" normandy="cheese2" /></thinger2></item></publish></pubsub></iq>"""
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
iq3 = self.ps.Iq()
values = iq2.getValues()
iq3.setValues(values)
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
def testDelete(self): def testSubscriptions(self):
"Testing iq/pubsub_owner/delete stanzas" "Testing iq/pubsub/subscriptions/subscription stanzas"
iq = self.ps.Iq() iq = self.Iq()
iq['pubsub_owner']['delete']['node'] = 'thingers' sub1 = pubsub.Subscription()
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><delete node="thingers" /></pubsub></iq>""" sub1['node'] = 'testnode'
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring)) sub1['jid'] = 'steve@myserver.tld/someresource'
iq3 = self.ps.Iq() sub2 = pubsub.Subscription()
iq3.setValues(iq2.getValues()) sub2['node'] = 'testnode2'
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3)) sub2['jid'] = 'boogers@bork.top/bill'
sub2['subscription'] = 'subscribed'
def testCreateConfigGet(self): iq['pubsub']['subscriptions'].append(sub1)
"""Testing getting config from full create""" iq['pubsub']['subscriptions'].append(sub2)
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>""" self.checkIq(iq, """
iq = self.ps.Iq(None, self.ps.ET.fromstring(xml)) <iq id="0">
config = iq['pubsub']['configure']['config'] <pubsub xmlns="http://jabber.org/protocol/pubsub">
self.failUnless(config.getValues() != {}) <subscriptions>
<subscription node="testnode" jid="steve@myserver.tld/someresource" />
<subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" />
</subscriptions>
</pubsub>
</iq>""")
def testItemEvent(self): def testOptionalSettings(self):
"""Testing message/pubsub_event/items/item""" "Testing iq/pubsub/subscription/subscribe-options stanzas"
msg = self.ps.Message() iq = self.Iq()
item = self.ps.EventItem() iq['pubsub']['subscription']['suboptions']['required'] = True
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas'
item['payload'] = pl iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp"
item['id'] = 'abc123' iq['pubsub']['subscription']['subscription'] = 'unconfigured'
msg['pubsub_event']['items'].append(item) self.checkIq(iq, """
msg['pubsub_event']['items']['node'] = 'cheese' <iq id="0">
msg['type'] = 'normal' <pubsub xmlns="http://jabber.org/protocol/pubsub">
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>""" <subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured">
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <subscribe-options>
msg3 = self.ps.Message() <required />
msg3.setValues(msg2.getValues()) </subscribe-options>
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) </subscription>
</pubsub>
</iq>""")
def testItemsEvent(self): def testItems(self):
"""Testing multiple message/pubsub_event/items/item""" "Testing iq/pubsub/items stanzas"
msg = self.ps.Message() iq = self.Iq()
item = self.ps.EventItem() iq['pubsub']['items']
item2 = self.ps.EventItem() payload = ET.fromstring("""
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) <thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'>
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'}) <child1 />
item2['payload'] = pl2 <child2 normandy='cheese' foo='bar' />
item['payload'] = pl </thinger>""")
item['id'] = 'abc123' payload2 = ET.fromstring("""
item2['id'] = '123abc' <thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'>
msg['pubsub_event']['items'].append(item) <child12 />
msg['pubsub_event']['items'].append(item2) <child22 normandy='cheese2' foo='bar2' />
msg['pubsub_event']['items']['node'] = 'cheese' </thinger2>""")
msg['type'] = 'normal' item = pubsub.Item()
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>""" item['id'] = 'asdf'
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) item['payload'] = payload
msg3 = self.ps.Message() item2 = pubsub.Item()
msg3.setValues(msg2.getValues()) item2['id'] = 'asdf2'
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) item2['payload'] = payload2
iq['pubsub']['items'].append(item)
iq['pubsub']['items'].append(item2)
self.checkIq(iq, """
<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>""")
def testItemsEvent(self): def testCreate(self):
"""Testing message/pubsub_event/items/item & retract mix""" "Testing iq/pubsub/create&configure stanzas"
msg = self.ps.Message() iq = self.Iq()
item = self.ps.EventItem() iq['pubsub']['create']['node'] = 'mynode'
item2 = self.ps.EventItem() iq['pubsub']['configure']['form'].addField('pubsub#title',
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'}) ftype='text-single',
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'}) value='This thing is awesome')
item2['payload'] = pl2 self.checkIq(iq, """
retract = self.ps.EventRetract() <iq id="0">
retract['id'] = 'aabbcc' <pubsub xmlns="http://jabber.org/protocol/pubsub">
item['payload'] = pl <create node="mynode" />
item['id'] = 'abc123' <configure>
item2['id'] = '123abc' <x xmlns="jabber:x:data" type="form">
msg['pubsub_event']['items'].append(item) <field var="pubsub#title" type="text-single">
msg['pubsub_event']['items'].append(retract) <value>This thing is awesome</value>
msg['pubsub_event']['items'].append(item2) </field>
msg['pubsub_event']['items']['node'] = 'cheese' </x>
msg['type'] = 'normal' </configure>
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>""" </pubsub>
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) </iq>""")
msg3 = self.ps.Message()
msg3.setValues(msg2.getValues())
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
def testCollectionAssociate(self):
"""Testing message/pubsub_event/collection/associate"""
msg = self.ps.Message()
msg['pubsub_event']['collection']['associate']['node'] = 'cheese'
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
msg['type'] = 'headline'
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><associate node="cheese" /></collection></event></message>"""
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
msg3 = self.ps.Message()
msg3.setValues(msg2.getValues())
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
def testCollectionDisassociate(self): def testState(self):
"""Testing message/pubsub_event/collection/disassociate""" "Testing iq/psstate stanzas"
msg = self.ps.Message() iq = self.Iq()
msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese' iq['psstate']['node']= 'mynode'
msg['pubsub_event']['collection']['node'] = 'cheeseburger' iq['psstate']['item']= 'myitem'
msg['type'] = 'headline' pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><disassociate node="cheese" /></collection></event></message>""" iq['psstate']['payload'] = pl
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) self.checkIq(iq, """
msg3 = self.ps.Message() <iq id="0">
msg3.setValues(msg2.getValues()) <state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem">
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) <claimed xmlns="http://andyet.net/protocol/pubsubqueue" />
</state>
</iq>""")
def testEventConfiguration(self): def testDefault(self):
"""Testing message/pubsub_event/configuration/config""" "Testing iq/pubsub_owner/default stanzas"
msg = self.ps.Message() iq = self.Iq()
from sleekxmpp.plugins import xep_0004 iq['pubsub_owner']['default']
form = xep_0004.Form() iq['pubsub_owner']['default']['node'] = 'mynode'
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome') iq['pubsub_owner']['default']['type'] = 'leaf'
msg['pubsub_event']['configuration']['node'] = 'cheese' iq['pubsub_owner']['default']['form'].addField('pubsub#title',
msg['pubsub_event']['configuration']['config'] = form ftype='text-single',
msg['type'] = 'headline' value='This thing is awesome')
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.checkIq(iq, """
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring)) <iq id="0">
msg3 = self.ps.Message() <pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
msg3.setValues(msg2.getValues()) <default node="mynode" type="leaf">
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3)) <x xmlns="jabber:x:data" type="form">
<field var="pubsub#title" type="text-single">
def testEventPurge(self): <value>This thing is awesome</value>
"""Testing message/pubsub_event/purge""" </field>
msg = self.ps.Message() </x>
msg['pubsub_event']['purge']['node'] = 'pickles' </default>
msg['type'] = 'headline' </pubsub>
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><purge node="pickles" /></event></message>""" </iq>""", use_values=False)
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
msg3 = self.ps.Message()
msg3.setValues(msg2.getValues())
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
def testEventSubscription(self):
"""Testing message/pubsub_event/subscription"""
msg = self.ps.Message()
msg['pubsub_event']['subscription']['node'] = 'pickles'
msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test'
msg['pubsub_event']['subscription']['subid'] = 'aabb1122'
msg['pubsub_event']['subscription']['subscription'] = 'subscribed'
msg['pubsub_event']['subscription']['expiry'] = 'presence'
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>"""
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
msg3 = self.ps.Message()
msg3.setValues(msg2.getValues())
self.failUnless(xmlcompare.comparemany([xmlstring, str(msg), str(msg2), str(msg3)]))
suite = unittest.TestLoader().loadTestsFromTestCase(testpubsubstanzas) def testSubscribe(self):
"testing iq/pubsub/subscribe stanzas"
iq = self.Iq()
iq['pubsub']['subscribe']['options']
iq['pubsub']['subscribe']['node'] = 'cheese'
iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp'
iq['pubsub']['subscribe']['options']['node'] = 'cheese'
iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp'
form = xep_0004.Form()
form.addField('pubsub#title', ftype='text-single', value='this thing is awesome')
iq['pubsub']['subscribe']['options']['options'] = form
self.checkIq(iq, """
<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>""", use_values=False)
def testPublish(self):
"Testing iq/pubsub/publish stanzas"
iq = self.Iq()
iq['pubsub']['publish']['node'] = 'thingers'
payload = ET.fromstring("""
<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'>
<child1 />
<child2 normandy='cheese' foo='bar' />
</thinger>""")
payload2 = ET.fromstring("""
<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'>
<child12 />
<child22 normandy='cheese2' foo='bar2' />
</thinger2>""")
item = pubsub.Item()
item['id'] = 'asdf'
item['payload'] = payload
item2 = pubsub.Item()
item2['id'] = 'asdf2'
item2['payload'] = payload2
iq['pubsub']['publish'].append(item)
iq['pubsub']['publish'].append(item2)
self.checkIq(iq, """
<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>""")
def testDelete(self):
"Testing iq/pubsub_owner/delete stanzas"
iq = self.Iq()
iq['pubsub_owner']['delete']['node'] = 'thingers'
self.checkIq(iq, """
<iq id="0">
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
<delete node="thingers" />
</pubsub>
</iq>""")
def testCreateConfigGet(self):
"""Testing getting config from full create"""
iq = self.Iq()
iq['to'] = 'pubsub.asdf'
iq['from'] = 'fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7'
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):
"""Testing message/pubsub_event/items/item"""
msg = self.Message()
item = pubsub.EventItem()
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
item['payload'] = pl
item['id'] = 'abc123'
msg['pubsub_event']['items'].append(item)
msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal'
self.checkMessage(msg, """
<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>""")
def testItemsEvent(self):
"""Testing multiple message/pubsub_event/items/item"""
msg = self.Message()
item = pubsub.EventItem()
item2 = pubsub.EventItem()
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'})
item2['payload'] = pl2
item['payload'] = pl
item['id'] = 'abc123'
item2['id'] = '123abc'
msg['pubsub_event']['items'].append(item)
msg['pubsub_event']['items'].append(item2)
msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal'
self.checkMessage(msg, """
<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>""")
def testItemsEvent(self):
"""Testing message/pubsub_event/items/item & retract mix"""
msg = self.Message()
item = pubsub.EventItem()
item2 = pubsub.EventItem()
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'})
item2['payload'] = pl2
retract = pubsub.EventRetract()
retract['id'] = 'aabbcc'
item['payload'] = pl
item['id'] = 'abc123'
item2['id'] = '123abc'
msg['pubsub_event']['items'].append(item)
msg['pubsub_event']['items'].append(retract)
msg['pubsub_event']['items'].append(item2)
msg['pubsub_event']['items']['node'] = 'cheese'
msg['type'] = 'normal'
self.checkMessage(msg, """
<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>""")
def testCollectionAssociate(self):
"""Testing message/pubsub_event/collection/associate"""
msg = self.Message()
msg['pubsub_event']['collection']['associate']['node'] = 'cheese'
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
msg['type'] = 'headline'
self.checkMessage(msg, """
<message type="headline">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<collection node="cheeseburger">
<associate node="cheese" />
</collection>
</event>
</message>""")
def testCollectionDisassociate(self):
"""Testing message/pubsub_event/collection/disassociate"""
msg = self.Message()
msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese'
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
msg['type'] = 'headline'
self.checkMessage(msg, """
<message type="headline">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<collection node="cheeseburger">
<disassociate node="cheese" />
</collection>
</event>
</message>""")
def testEventConfiguration(self):
"""Testing message/pubsub_event/configuration/config"""
msg = self.Message()
msg['pubsub_event']['configuration']['node'] = 'cheese'
msg['pubsub_event']['configuration']['form'].addField('pubsub#title',
ftype='text-single',
value='This thing is awesome')
msg['type'] = 'headline'
self.checkMessage(msg, """
<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>""")
def testEventPurge(self):
"""Testing message/pubsub_event/purge"""
msg = self.Message()
msg['pubsub_event']['purge']['node'] = 'pickles'
msg['type'] = 'headline'
self.checkMessage(msg, """
<message type="headline">
<event xmlns="http://jabber.org/protocol/pubsub#event">
<purge node="pickles" />
</event>
</message>""")
def testEventSubscription(self):
"""Testing message/pubsub_event/subscription"""
msg = self.Message()
msg['pubsub_event']['subscription']['node'] = 'pickles'
msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test'
msg['pubsub_event']['subscription']['subid'] = 'aabb1122'
msg['pubsub_event']['subscription']['subscription'] = 'subscribed'
msg['pubsub_event']['subscription']['expiry'] = 'presence'
msg['type'] = 'headline'
self.checkMessage(msg, """
<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>""")
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