mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-27 19:19:54 +00:00
fixed todo merge
This commit is contained in:
commit
d150b35464
81 changed files with 5839 additions and 2547 deletions
3
INSTALL
3
INSTALL
|
@ -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]
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -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
|
||||||
|
|
39
MANIFEST
39
MANIFEST
|
@ -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
5
README
|
@ -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.
|
||||||
|
|
64
example.py
64
example.py
|
@ -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')
|
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
||||||
xmpp = Example('user@gmail.com/sleekxmpp', 'password')
|
xmpp = Example(opts.jid, opts.password)
|
||||||
xmpp.registerPlugin('xep_0030')
|
xmpp.registerPlugin('xep_0030')
|
||||||
xmpp.registerPlugin('xep_0004')
|
xmpp.registerPlugin('xep_0004')
|
||||||
xmpp.registerPlugin('xep_0060')
|
xmpp.registerPlugin('xep_0060')
|
||||||
xmpp.registerPlugin('xep_0199')
|
xmpp.registerPlugin('xep_0199')
|
||||||
if xmpp.connect(('talk.google.com', 5222)):
|
|
||||||
xmpp.process(threaded=False)
|
# use this if you don't have pydns, and want to
|
||||||
print("done")
|
# talk to GoogleTalk (e.g.)
|
||||||
else:
|
# if xmpp.connect(('talk.google.com', 5222)):
|
||||||
print("Unable to connect.")
|
if xmpp.connect():
|
||||||
|
xmpp.process(threaded=False)
|
||||||
|
print("done")
|
||||||
|
else:
|
||||||
|
print("Unable to connect.")
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -42,12 +42,8 @@ packages = [ 'sleekxmpp',
|
||||||
'sleekxmpp/stanza',
|
'sleekxmpp/stanza',
|
||||||
'sleekxmpp/xmlstream',
|
'sleekxmpp/xmlstream',
|
||||||
'sleekxmpp/xmlstream/matcher',
|
'sleekxmpp/xmlstream/matcher',
|
||||||
'sleekxmpp/xmlstream/handler' ]
|
'sleekxmpp/xmlstream/handler',
|
||||||
|
'sleekxmpp/xmlstream/tostring']
|
||||||
if sys.version_info < (3, 0):
|
|
||||||
packages.append('sleekxmpp/xmlstream/tostring26')
|
|
||||||
else:
|
|
||||||
packages.append('sleekxmpp/xmlstream/tostring')
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name = "sleekxmpp",
|
name = "sleekxmpp",
|
||||||
|
|
|
@ -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.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
|
||||||
#self.register_plugins()
|
#self.register_plugins()
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if key in self.plugin:
|
if key in self.plugin:
|
||||||
return self.plugin[key]
|
return self.plugin[key]
|
||||||
else:
|
else:
|
||||||
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get(self, key, default):
|
def get(self, key, default):
|
||||||
return self.plugin.get(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):
|
def delRosterItem(self, jid):
|
||||||
"""Request the roster be sent."""
|
iq = self.Iq()
|
||||||
iq = self.Iq().setValues({'type': 'get'}).enable('roster').send()
|
iq['type'] = 'set'
|
||||||
self._handleRoster(iq, request=True)
|
iq['roster']['items'] = {jid: {'subscription': 'remove'}}
|
||||||
|
return iq.send()['type'] == 'result'
|
||||||
|
|
||||||
def _handleStreamFeatures(self, features):
|
def getRoster(self):
|
||||||
self.features = []
|
"""Request the roster be sent."""
|
||||||
for sub in features.xml:
|
iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send()
|
||||||
self.features.append(sub.tag)
|
self._handleRoster(iq, request=True)
|
||||||
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):
|
def _handleStreamFeatures(self, features):
|
||||||
if not self.authenticated and self.ssl_support:
|
self.features = []
|
||||||
self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, instream=True)
|
for sub in features.xml:
|
||||||
self.sendXML(xml)
|
self.features.append(sub.tag)
|
||||||
return True
|
for subelement in features.xml:
|
||||||
else:
|
for feature in self.registered_features:
|
||||||
logging.warning("The module tlslite is required in to some servers, and has not been found.")
|
if feature[0].match(subelement):
|
||||||
return False
|
#if self.maskcmp(subelement, feature[0], True):
|
||||||
|
if feature[1](subelement) and feature[2]: #if breaker, don't continue
|
||||||
|
return True
|
||||||
|
|
||||||
def handler_tls_start(self, xml):
|
def handler_starttls(self, xml):
|
||||||
logging.debug("Starting TLS")
|
if not self.authenticated and self.ssl_support:
|
||||||
if self.startTLS():
|
self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, name='TLS Proceed', instream=True)
|
||||||
raise RestartStream()
|
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_sasl_auth(self, xml):
|
def handler_tls_start(self, xml):
|
||||||
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
|
logging.debug("Starting TLS")
|
||||||
return False
|
if self.startTLS():
|
||||||
logging.debug("Starting SASL Auth")
|
raise RestartStream()
|
||||||
self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=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')
|
|
||||||
if len(sasl_mechs):
|
|
||||||
for sasl_mech in sasl_mechs:
|
|
||||||
self.features.append("sasl:%s" % sasl_mech.text)
|
|
||||||
if 'sasl:PLAIN' in self.features:
|
|
||||||
if sys.version_info < (3,0):
|
|
||||||
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'))
|
|
||||||
else:
|
|
||||||
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'))
|
|
||||||
else:
|
|
||||||
logging.error("No appropriate login method.")
|
|
||||||
self.disconnect()
|
|
||||||
#if 'sasl:DIGEST-MD5' in self.features:
|
|
||||||
# self._auth_digestmd5()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def handler_auth_success(self, xml):
|
def handler_sasl_auth(self, xml):
|
||||||
self.authenticated = True
|
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
|
||||||
self.features = []
|
return False
|
||||||
raise RestartStream()
|
logging.debug("Starting SASL Auth")
|
||||||
|
self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, name='SASL Sucess', instream=True)
|
||||||
|
self.add_handler("<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_fail, name='SASL Failure', instream=True)
|
||||||
|
sasl_mechs = xml.findall('{urn:ietf:params:xml:ns:xmpp-sasl}mechanism')
|
||||||
|
if len(sasl_mechs):
|
||||||
|
for sasl_mech in sasl_mechs:
|
||||||
|
self.features.append("sasl:%s" % sasl_mech.text)
|
||||||
|
if 'sasl:PLAIN' in self.features:
|
||||||
|
if sys.version_info < (3,0):
|
||||||
|
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'))
|
||||||
|
else:
|
||||||
|
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'))
|
||||||
|
else:
|
||||||
|
logging.error("No appropriate login method.")
|
||||||
|
self.disconnect()
|
||||||
|
#if 'sasl:DIGEST-MD5' in self.features:
|
||||||
|
# self._auth_digestmd5()
|
||||||
|
return True
|
||||||
|
|
||||||
def handler_auth_fail(self, xml):
|
def handler_auth_success(self, xml):
|
||||||
logging.info("Authentication failed.")
|
self.authenticated = True
|
||||||
self.disconnect()
|
self.features = []
|
||||||
self.event("failed_auth")
|
raise RestartStream()
|
||||||
|
|
||||||
def handler_bind_resource(self, xml):
|
def handler_auth_fail(self, xml):
|
||||||
logging.debug("Requesting resource: %s" % self.resource)
|
logging.info("Authentication failed.")
|
||||||
iq = self.Iq(stype='set')
|
self.disconnect()
|
||||||
res = ET.Element('resource')
|
self.event("failed_auth")
|
||||||
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):
|
def handler_bind_resource(self, xml):
|
||||||
if self.authenticated and self.bound:
|
logging.debug("Requesting resource: %s" % self.resource)
|
||||||
iq = self.makeIqSet(xml)
|
xml.clear()
|
||||||
response = iq.send()
|
iq = self.Iq(stype='set')
|
||||||
logging.debug("Established Session")
|
if self.resource:
|
||||||
self.sessionstarted = True
|
res = ET.Element('resource')
|
||||||
self.event("session_start")
|
res.text = self.resource
|
||||||
else:
|
xml.append(res)
|
||||||
#bind probably hasn't happened yet
|
iq.append(xml)
|
||||||
self.bindfail = True
|
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 _handleRoster(self, iq, request=False):
|
def handler_start_session(self, xml):
|
||||||
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
|
if self.authenticated and self.bound:
|
||||||
for jid in iq['roster']['items']:
|
iq = self.makeIqSet(xml)
|
||||||
if not jid in self.roster:
|
response = iq.send()
|
||||||
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
|
logging.debug("Established Session")
|
||||||
self.roster[jid].update(iq['roster']['items'][jid])
|
self.sessionstarted = True
|
||||||
if iq['type'] == 'set':
|
self.event("session_start")
|
||||||
self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster'))
|
else:
|
||||||
self.event("roster_update", iq)
|
#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)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
See the file license.txt for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
from __future__ import with_statement, unicode_literals
|
from __future__ import with_statement, unicode_literals
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ from . xmlstream.handler.xmlcallback import XMLCallback
|
||||||
from . xmlstream.handler.xmlwaiter import XMLWaiter
|
from . xmlstream.handler.xmlwaiter import XMLWaiter
|
||||||
from . xmlstream.handler.waiter import Waiter
|
from . xmlstream.handler.waiter import Waiter
|
||||||
from . xmlstream.handler.callback import Callback
|
from . xmlstream.handler.callback import Callback
|
||||||
|
from . xmlstream.stanzabase import registerStanzaPlugin
|
||||||
from . import plugins
|
from . import plugins
|
||||||
from . stanza.message import Message
|
from . stanza.message import Message
|
||||||
from . stanza.iq import Iq
|
from . stanza.iq import Iq
|
||||||
|
@ -24,9 +25,11 @@ from . stanza.roster import Roster
|
||||||
from . stanza.nick import Nick
|
from . stanza.nick import Nick
|
||||||
from . stanza.htmlim import HTMLIM
|
from . stanza.htmlim import HTMLIM
|
||||||
from . stanza.error import Error
|
from . stanza.error import Error
|
||||||
|
from sleekxmpp.xmlstream.tostring import tostring
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import threading
|
import threading
|
||||||
|
import copy
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -34,12 +37,6 @@ if sys.version_info < (3,0):
|
||||||
reload(sys)
|
reload(sys)
|
||||||
sys.setdefaultencoding('utf8')
|
sys.setdefaultencoding('utf8')
|
||||||
|
|
||||||
|
|
||||||
def stanzaPlugin(stanza, plugin):
|
|
||||||
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
|
|
||||||
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
|
|
||||||
|
|
||||||
|
|
||||||
class basexmpp(object):
|
class basexmpp(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.id = 0
|
self.id = 0
|
||||||
|
@ -61,13 +58,9 @@ class basexmpp(object):
|
||||||
self.registerStanza(Message)
|
self.registerStanza(Message)
|
||||||
self.registerStanza(Iq)
|
self.registerStanza(Iq)
|
||||||
self.registerStanza(Presence)
|
self.registerStanza(Presence)
|
||||||
self.stanzaPlugin(Iq, Roster)
|
registerStanzaPlugin(Iq, Roster)
|
||||||
self.stanzaPlugin(Message, Nick)
|
registerStanzaPlugin(Message, Nick)
|
||||||
self.stanzaPlugin(Message, HTMLIM)
|
registerStanzaPlugin(Message, HTMLIM)
|
||||||
|
|
||||||
def stanzaPlugin(self, stanza, plugin):
|
|
||||||
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
|
|
||||||
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
|
|
||||||
|
|
||||||
def Message(self, *args, **kwargs):
|
def Message(self, *args, **kwargs):
|
||||||
return Message(self, *args, **kwargs)
|
return Message(self, *args, **kwargs)
|
||||||
|
@ -126,15 +119,17 @@ class basexmpp(object):
|
||||||
self.id += 1
|
self.id += 1
|
||||||
return self.getId()
|
return self.getId()
|
||||||
|
|
||||||
def add_handler(self, mask, pointer, disposable=False, threaded=False, filter=False, instream=False):
|
def add_handler(self, mask, pointer, name=None, disposable=False, threaded=False, filter=False, instream=False):
|
||||||
#logging.warning("Deprecated add_handler used for %s: %s." % (mask, pointer))
|
# threaded is no longer needed, but leaving it for backwards compatibility for now
|
||||||
self.registerHandler(XMLCallback('add_handler_%s' % self.getNewId(), MatchXMLMask(mask), pointer, threaded, disposable, instream))
|
if name is None:
|
||||||
|
name = 'add_handler_%s' % self.getNewId()
|
||||||
|
self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer, threaded, disposable, instream))
|
||||||
|
|
||||||
def getId(self):
|
def getId(self):
|
||||||
return "%x".upper() % self.id
|
return "%x".upper() % self.id
|
||||||
|
|
||||||
def sendXML(self, data, mask=None, timeout=10):
|
def sendXML(self, data, mask=None, timeout=10):
|
||||||
return self.send(self.tostring(data), mask, timeout)
|
return self.send(tostring(data), mask, timeout)
|
||||||
|
|
||||||
def send(self, data, mask=None, timeout=10):
|
def send(self, data, mask=None, timeout=10):
|
||||||
#logging.warning("Deprecated send used for \"%s\"" % (data,))
|
#logging.warning("Deprecated send used for \"%s\"" % (data,))
|
||||||
|
@ -152,26 +147,26 @@ class basexmpp(object):
|
||||||
return waitfor.wait(timeout)
|
return waitfor.wait(timeout)
|
||||||
|
|
||||||
def makeIq(self, id=0, ifrom=None):
|
def makeIq(self, id=0, ifrom=None):
|
||||||
return self.Iq().setValues({'id': id, 'from': ifrom})
|
return self.Iq().setStanzaValues({'id': str(id), 'from': ifrom})
|
||||||
|
|
||||||
def makeIqGet(self, queryxmlns = None):
|
def makeIqGet(self, queryxmlns = None):
|
||||||
iq = self.Iq().setValues({'type': 'get'})
|
iq = self.Iq().setStanzaValues({'type': 'get'})
|
||||||
if queryxmlns:
|
if queryxmlns:
|
||||||
iq.append(ET.Element("{%s}query" % queryxmlns))
|
iq.append(ET.Element("{%s}query" % queryxmlns))
|
||||||
return iq
|
return iq
|
||||||
|
|
||||||
def makeIqResult(self, id):
|
def makeIqResult(self, id):
|
||||||
return self.Iq().setValues({'id': id, 'type': 'result'})
|
return self.Iq().setStanzaValues({'id': id, 'type': 'result'})
|
||||||
|
|
||||||
def makeIqSet(self, sub=None):
|
def makeIqSet(self, sub=None):
|
||||||
iq = self.Iq().setValues({'type': 'set'})
|
iq = self.Iq().setStanzaValues({'type': 'set'})
|
||||||
if sub != None:
|
if sub != None:
|
||||||
iq.append(sub)
|
iq.append(sub)
|
||||||
return iq
|
return iq
|
||||||
|
|
||||||
def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None):
|
def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None):
|
||||||
iq = self.Iq().setValues({'id': id})
|
iq = self.Iq().setStanzaValues({'id': id})
|
||||||
iq['error'].setValues({'type': type, 'condition': condition, 'text': text})
|
iq['error'].setStanzaValues({'type': type, 'condition': condition, 'text': text})
|
||||||
return iq
|
return iq
|
||||||
|
|
||||||
def makeIqQuery(self, iq, xmlns):
|
def makeIqQuery(self, iq, xmlns):
|
||||||
|
@ -205,12 +200,13 @@ class basexmpp(object):
|
||||||
|
|
||||||
def event(self, name, eventdata = {}): # called on an event
|
def event(self, name, eventdata = {}): # called on an event
|
||||||
for handler in self.event_handlers.get(name, []):
|
for handler in self.event_handlers.get(name, []):
|
||||||
|
handlerdata = copy.copy(eventdata)
|
||||||
if handler[1]: #if threaded
|
if handler[1]: #if threaded
|
||||||
#thread.start_new(handler[0], (eventdata,))
|
#thread.start_new(handler[0], (eventdata,))
|
||||||
x = threading.Thread(name="Event_%s" % str(handler[0]), target=handler[0], args=(eventdata,))
|
x = threading.Thread(name="Event_%s" % str(handler[0]), target=handler[0], args=(handlerdata,))
|
||||||
x.start()
|
x.start()
|
||||||
else:
|
else:
|
||||||
handler[0](eventdata)
|
handler[0](handlerdata)
|
||||||
if handler[2]: #disposable
|
if handler[2]: #disposable
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.event_handlers[name].pop(self.event_handlers[name].index(handler))
|
self.event_handlers[name].pop(self.event_handlers[name].index(handler))
|
||||||
|
|
|
@ -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):
|
def __getitem__(self, key):
|
||||||
if key in self.plugin:
|
if key in self.plugin:
|
||||||
return self.plugin[key]
|
return self.plugin[key]
|
||||||
else:
|
else:
|
||||||
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get(self, key, default):
|
def get(self, key, default):
|
||||||
return self.plugin.get(key, default)
|
return self.plugin.get(key, default)
|
||||||
|
|
||||||
def incoming_filter(self, xmlobj):
|
def incoming_filter(self, xmlobj):
|
||||||
if xmlobj.tag.startswith('{jabber:client}'):
|
if xmlobj.tag.startswith('{jabber:client}'):
|
||||||
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
|
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
|
||||||
for sub in xmlobj:
|
for sub in xmlobj:
|
||||||
self.incoming_filter(sub)
|
self.incoming_filter(sub)
|
||||||
return xmlobj
|
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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -1,57 +1,146 @@
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2007 Nathanael C. Fritz
|
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
SleekXMPP is free software; you can redistribute it and/or modify
|
See the file LICENSE for copying permission.
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SleekXMPP is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SleekXMPP; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
"""
|
"""
|
||||||
from __future__ import with_statement
|
|
||||||
from . import base
|
|
||||||
import logging
|
import logging
|
||||||
from xml.etree import cElementTree as ET
|
from . import base
|
||||||
import traceback
|
from .. xmlstream.handler.callback import Callback
|
||||||
import time
|
from .. xmlstream.matcher.xpath import MatchXPath
|
||||||
|
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
||||||
|
from .. stanza.iq import Iq
|
||||||
|
|
||||||
|
|
||||||
|
class GmailQuery(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'query'
|
||||||
|
plugin_attrib = 'gmail'
|
||||||
|
interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
|
||||||
|
|
||||||
|
def getSearch(self):
|
||||||
|
return self['q']
|
||||||
|
|
||||||
|
def setSearch(self, search):
|
||||||
|
self['q'] = search
|
||||||
|
|
||||||
|
def delSearch(self):
|
||||||
|
del self['q']
|
||||||
|
|
||||||
|
|
||||||
|
class MailBox(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'mailbox'
|
||||||
|
plugin_attrib = 'mailbox'
|
||||||
|
interfaces = set(('result-time', 'total-matched', 'total-estimate',
|
||||||
|
'url', 'threads', 'matched', 'estimate'))
|
||||||
|
|
||||||
|
def getThreads(self):
|
||||||
|
threads = []
|
||||||
|
for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
|
||||||
|
MailThread.name)):
|
||||||
|
threads.append(MailThread(xml=threadXML, parent=None))
|
||||||
|
return threads
|
||||||
|
|
||||||
|
def getMatched(self):
|
||||||
|
return self['total-matched']
|
||||||
|
|
||||||
|
def getEstimate(self):
|
||||||
|
return self['total-estimate'] == '1'
|
||||||
|
|
||||||
|
|
||||||
|
class MailThread(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'mail-thread-info'
|
||||||
|
plugin_attrib = 'thread'
|
||||||
|
interfaces = set(('tid', 'participation', 'messages', 'date',
|
||||||
|
'senders', 'url', 'labels', 'subject', 'snippet'))
|
||||||
|
sub_interfaces = set(('labels', 'subject', 'snippet'))
|
||||||
|
|
||||||
|
def getSenders(self):
|
||||||
|
senders = []
|
||||||
|
sendersXML = self.xml.find('{%s}senders' % self.namespace)
|
||||||
|
if sendersXML is not None:
|
||||||
|
for senderXML in sendersXML.findall('{%s}sender' % self.namespace):
|
||||||
|
senders.append(MailSender(xml=senderXML, parent=None))
|
||||||
|
return senders
|
||||||
|
|
||||||
|
|
||||||
|
class MailSender(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'sender'
|
||||||
|
plugin_attrib = 'sender'
|
||||||
|
interfaces = set(('address', 'name', 'originator', 'unread'))
|
||||||
|
|
||||||
|
def getOriginator(self):
|
||||||
|
return self.xml.attrib.get('originator', '0') == '1'
|
||||||
|
|
||||||
|
def getUnread(self):
|
||||||
|
return self.xml.attrib.get('unread', '0') == '1'
|
||||||
|
|
||||||
|
|
||||||
|
class NewMail(ElementBase):
|
||||||
|
namespace = 'google:mail:notify'
|
||||||
|
name = 'new-mail'
|
||||||
|
plugin_attrib = 'new-mail'
|
||||||
|
|
||||||
|
|
||||||
class gmail_notify(base.base_plugin):
|
class gmail_notify(base.base_plugin):
|
||||||
|
"""
|
||||||
|
Google Talk: Gmail Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.description = 'Google Talk Gmail Notification'
|
self.description = 'Google Talk: Gmail Notifications'
|
||||||
self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True)
|
|
||||||
self.emails = []
|
|
||||||
|
|
||||||
def handler_gmailcheck(self, payload):
|
self.xmpp.registerHandler(
|
||||||
#TODO XEP 30 should cache results and have getFeature
|
Callback('Gmail Result',
|
||||||
result = self.xmpp['xep_0030'].getInfo(self.xmpp.server)
|
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
|
||||||
features = []
|
MailBox.namespace,
|
||||||
for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'):
|
MailBox.name)),
|
||||||
features.append(feature.get('var'))
|
self.handle_gmail))
|
||||||
if 'google:mail:notify' in features:
|
|
||||||
logging.debug("Server supports Gmail Notify")
|
|
||||||
self.xmpp.add_handler("<iq type='set' xmlns='%s'><new-mail xmlns='google:mail:notify' /></iq>" % self.xmpp.default_ns, self.handler_notify)
|
|
||||||
self.getEmail()
|
|
||||||
|
|
||||||
def handler_notify(self, xml):
|
self.xmpp.registerHandler(
|
||||||
logging.info("New Gmail recieved!")
|
Callback('Gmail New Mail',
|
||||||
self.xmpp.event('gmail_notify')
|
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
|
||||||
|
NewMail.namespace,
|
||||||
|
NewMail.name)),
|
||||||
|
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()
|
||||||
|
|
417
sleekxmpp/plugins/old_0004.py
Normal file
417
sleekxmpp/plugins/old_0004.py
Normal 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
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from .. xmlstream.stanzabase import ElementBase, ET, JID
|
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
||||||
from .. stanza.iq import Iq
|
from .. stanza.iq import Iq
|
||||||
from .. stanza.message import Message
|
from .. stanza.message import Message
|
||||||
from .. basexmpp import basexmpp
|
from .. basexmpp import basexmpp
|
||||||
|
@ -6,9 +6,6 @@ from .. xmlstream.xmlstream import XMLStream
|
||||||
import logging
|
import logging
|
||||||
from . import xep_0004
|
from . import xep_0004
|
||||||
|
|
||||||
def stanzaPlugin(stanza, plugin):
|
|
||||||
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
|
|
||||||
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
|
|
||||||
|
|
||||||
class PubsubState(ElementBase):
|
class PubsubState(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/psstate'
|
namespace = 'http://jabber.org/protocol/psstate'
|
||||||
|
@ -30,7 +27,7 @@ class PubsubState(ElementBase):
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml.getchildren():
|
||||||
self.xml.remove(child)
|
self.xml.remove(child)
|
||||||
|
|
||||||
stanzaPlugin(Iq, PubsubState)
|
registerStanzaPlugin(Iq, PubsubState)
|
||||||
|
|
||||||
class PubsubStateEvent(ElementBase):
|
class PubsubStateEvent(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/psstate#event'
|
namespace = 'http://jabber.org/protocol/psstate#event'
|
||||||
|
@ -40,8 +37,8 @@ class PubsubStateEvent(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(Message, PubsubStateEvent)
|
registerStanzaPlugin(Message, PubsubStateEvent)
|
||||||
stanzaPlugin(PubsubStateEvent, PubsubState)
|
registerStanzaPlugin(PubsubStateEvent, PubsubState)
|
||||||
|
|
||||||
class Pubsub(ElementBase):
|
class Pubsub(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -51,7 +48,7 @@ class Pubsub(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(Iq, Pubsub)
|
registerStanzaPlugin(Iq, Pubsub)
|
||||||
|
|
||||||
class PubsubOwner(ElementBase):
|
class PubsubOwner(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -61,7 +58,7 @@ class PubsubOwner(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(Iq, PubsubOwner)
|
registerStanzaPlugin(Iq, PubsubOwner)
|
||||||
|
|
||||||
class Affiliation(ElementBase):
|
class Affiliation(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -86,7 +83,7 @@ class Affiliations(ElementBase):
|
||||||
self.xml.append(affiliation.xml)
|
self.xml.append(affiliation.xml)
|
||||||
return self.iterables.append(affiliation)
|
return self.iterables.append(affiliation)
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Affiliations)
|
registerStanzaPlugin(Pubsub, Affiliations)
|
||||||
|
|
||||||
|
|
||||||
class Subscription(ElementBase):
|
class Subscription(ElementBase):
|
||||||
|
@ -103,7 +100,7 @@ class Subscription(ElementBase):
|
||||||
def getjid(self):
|
def getjid(self):
|
||||||
return jid(self._getattr('jid'))
|
return jid(self._getattr('jid'))
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Subscription)
|
registerStanzaPlugin(Pubsub, Subscription)
|
||||||
|
|
||||||
class Subscriptions(ElementBase):
|
class Subscriptions(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -114,7 +111,7 @@ class Subscriptions(ElementBase):
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
subitem = (Subscription,)
|
subitem = (Subscription,)
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Subscriptions)
|
registerStanzaPlugin(Pubsub, Subscriptions)
|
||||||
|
|
||||||
class OptionalSetting(object):
|
class OptionalSetting(object):
|
||||||
interfaces = set(('required',))
|
interfaces = set(('required',))
|
||||||
|
@ -147,7 +144,7 @@ class SubscribeOptions(ElementBase, OptionalSetting):
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
interfaces = set(('required',))
|
interfaces = set(('required',))
|
||||||
|
|
||||||
stanzaPlugin(Subscription, SubscribeOptions)
|
registerStanzaPlugin(Subscription, SubscribeOptions)
|
||||||
|
|
||||||
class Item(ElementBase):
|
class Item(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -178,7 +175,7 @@ class Items(ElementBase):
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
subitem = (Item,)
|
subitem = (Item,)
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Items)
|
registerStanzaPlugin(Pubsub, Items)
|
||||||
|
|
||||||
class Create(ElementBase):
|
class Create(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -188,7 +185,7 @@ class Create(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Create)
|
registerStanzaPlugin(Pubsub, Create)
|
||||||
|
|
||||||
#class Default(ElementBase):
|
#class Default(ElementBase):
|
||||||
# namespace = 'http://jabber.org/protocol/pubsub'
|
# namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -203,7 +200,7 @@ stanzaPlugin(Pubsub, Create)
|
||||||
# if not t: t == 'leaf'
|
# if not t: t == 'leaf'
|
||||||
# return t
|
# return t
|
||||||
#
|
#
|
||||||
#stanzaPlugin(Pubsub, Default)
|
#registerStanzaPlugin(Pubsub, Default)
|
||||||
|
|
||||||
class Publish(Items):
|
class Publish(Items):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -214,7 +211,7 @@ class Publish(Items):
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
subitem = (Item,)
|
subitem = (Item,)
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Publish)
|
registerStanzaPlugin(Pubsub, Publish)
|
||||||
|
|
||||||
class Retract(Items):
|
class Retract(Items):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -224,7 +221,7 @@ class Retract(Items):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Retract)
|
registerStanzaPlugin(Pubsub, Retract)
|
||||||
|
|
||||||
class Unsubscribe(ElementBase):
|
class Unsubscribe(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -254,13 +251,13 @@ class Subscribe(ElementBase):
|
||||||
def getJid(self):
|
def getJid(self):
|
||||||
return JID(self._getAttr('jid'))
|
return JID(self._getAttr('jid'))
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Subscribe)
|
registerStanzaPlugin(Pubsub, Subscribe)
|
||||||
|
|
||||||
class Configure(ElementBase):
|
class Configure(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
name = 'configure'
|
name = 'configure'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
interfaces = set(('node', 'type', 'config'))
|
interfaces = set(('node', 'type'))
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
|
@ -269,22 +266,8 @@ class Configure(ElementBase):
|
||||||
if not t: t == 'leaf'
|
if not t: t == 'leaf'
|
||||||
return t
|
return t
|
||||||
|
|
||||||
def getConfig(self):
|
registerStanzaPlugin(Pubsub, Configure)
|
||||||
config = self.xml.find('{jabber:x:data}x')
|
registerStanzaPlugin(Configure, xep_0004.Form)
|
||||||
form = xep_0004.Form()
|
|
||||||
if config is not None:
|
|
||||||
form.fromXML(config)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def setConfig(self, value):
|
|
||||||
self.xml.append(value.getXML())
|
|
||||||
return self
|
|
||||||
|
|
||||||
def delConfig(self):
|
|
||||||
config = self.xml.find('{jabber:x:data}x')
|
|
||||||
self.xml.remove(config)
|
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Configure)
|
|
||||||
|
|
||||||
class DefaultConfig(ElementBase):
|
class DefaultConfig(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -297,27 +280,13 @@ class DefaultConfig(ElementBase):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
ElementBase.__init__(self, *args, **kwargs)
|
ElementBase.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
def getConfig(self):
|
|
||||||
config = self.xml.find('{jabber:x:data}x')
|
|
||||||
form = xep_0004.Form()
|
|
||||||
if config is not None:
|
|
||||||
form.fromXML(config)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def setConfig(self, value):
|
|
||||||
self.xml.append(value.getXML())
|
|
||||||
return self
|
|
||||||
|
|
||||||
def delConfig(self):
|
|
||||||
config = self.xml.find('{jabber:x:data}x')
|
|
||||||
self.xml.remove(config)
|
|
||||||
|
|
||||||
def getType(self):
|
def getType(self):
|
||||||
t = self._getAttr('type')
|
t = self._getAttr('type')
|
||||||
if not t: t = 'leaf'
|
if not t: t = 'leaf'
|
||||||
return t
|
return t
|
||||||
|
|
||||||
stanzaPlugin(PubsubOwner, DefaultConfig)
|
registerStanzaPlugin(PubsubOwner, DefaultConfig)
|
||||||
|
registerStanzaPlugin(DefaultConfig, xep_0004.Form)
|
||||||
|
|
||||||
class Options(ElementBase):
|
class Options(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub'
|
namespace = 'http://jabber.org/protocol/pubsub'
|
||||||
|
@ -351,8 +320,8 @@ class Options(ElementBase):
|
||||||
def getJid(self):
|
def getJid(self):
|
||||||
return JID(self._getAttr('jid'))
|
return JID(self._getAttr('jid'))
|
||||||
|
|
||||||
stanzaPlugin(Pubsub, Options)
|
registerStanzaPlugin(Pubsub, Options)
|
||||||
stanzaPlugin(Subscribe, Options)
|
registerStanzaPlugin(Subscribe, Options)
|
||||||
|
|
||||||
class OwnerAffiliations(Affiliations):
|
class OwnerAffiliations(Affiliations):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -366,7 +335,7 @@ class OwnerAffiliations(Affiliations):
|
||||||
self.xml.append(affiliation.xml)
|
self.xml.append(affiliation.xml)
|
||||||
return self.affiliations.append(affiliation)
|
return self.affiliations.append(affiliation)
|
||||||
|
|
||||||
stanzaPlugin(PubsubOwner, OwnerAffiliations)
|
registerStanzaPlugin(PubsubOwner, OwnerAffiliations)
|
||||||
|
|
||||||
class OwnerAffiliation(Affiliation):
|
class OwnerAffiliation(Affiliation):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -380,7 +349,7 @@ class OwnerConfigure(Configure):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(PubsubOwner, OwnerConfigure)
|
registerStanzaPlugin(PubsubOwner, OwnerConfigure)
|
||||||
|
|
||||||
class OwnerDefault(OwnerConfigure):
|
class OwnerDefault(OwnerConfigure):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -388,7 +357,7 @@ class OwnerDefault(OwnerConfigure):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(PubsubOwner, OwnerDefault)
|
registerStanzaPlugin(PubsubOwner, OwnerDefault)
|
||||||
|
|
||||||
class OwnerDelete(ElementBase, OptionalSetting):
|
class OwnerDelete(ElementBase, OptionalSetting):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -398,7 +367,7 @@ class OwnerDelete(ElementBase, OptionalSetting):
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
interfaces = set(('node',))
|
interfaces = set(('node',))
|
||||||
|
|
||||||
stanzaPlugin(PubsubOwner, OwnerDelete)
|
registerStanzaPlugin(PubsubOwner, OwnerDelete)
|
||||||
|
|
||||||
class OwnerPurge(ElementBase, OptionalSetting):
|
class OwnerPurge(ElementBase, OptionalSetting):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -407,7 +376,7 @@ class OwnerPurge(ElementBase, OptionalSetting):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(PubsubOwner, OwnerPurge)
|
registerStanzaPlugin(PubsubOwner, OwnerPurge)
|
||||||
|
|
||||||
class OwnerRedirect(ElementBase):
|
class OwnerRedirect(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -423,7 +392,7 @@ class OwnerRedirect(ElementBase):
|
||||||
def getJid(self):
|
def getJid(self):
|
||||||
return JID(self._getAttr('jid'))
|
return JID(self._getAttr('jid'))
|
||||||
|
|
||||||
stanzaPlugin(OwnerDelete, OwnerRedirect)
|
registerStanzaPlugin(OwnerDelete, OwnerRedirect)
|
||||||
|
|
||||||
class OwnerSubscriptions(Subscriptions):
|
class OwnerSubscriptions(Subscriptions):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -437,7 +406,7 @@ class OwnerSubscriptions(Subscriptions):
|
||||||
self.xml.append(subscription.xml)
|
self.xml.append(subscription.xml)
|
||||||
return self.subscriptions.append(subscription)
|
return self.subscriptions.append(subscription)
|
||||||
|
|
||||||
stanzaPlugin(PubsubOwner, OwnerSubscriptions)
|
registerStanzaPlugin(PubsubOwner, OwnerSubscriptions)
|
||||||
|
|
||||||
class OwnerSubscription(ElementBase):
|
class OwnerSubscription(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||||
|
@ -461,7 +430,7 @@ class Event(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(Message, Event)
|
registerStanzaPlugin(Message, Event)
|
||||||
|
|
||||||
class EventItem(ElementBase):
|
class EventItem(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||||
|
@ -501,7 +470,7 @@ class EventItems(ElementBase):
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
subitem = (EventItem, EventRetract)
|
subitem = (EventItem, EventRetract)
|
||||||
|
|
||||||
stanzaPlugin(Event, EventItems)
|
registerStanzaPlugin(Event, EventItems)
|
||||||
|
|
||||||
class EventCollection(ElementBase):
|
class EventCollection(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||||
|
@ -511,7 +480,7 @@ class EventCollection(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(Event, EventCollection)
|
registerStanzaPlugin(Event, EventCollection)
|
||||||
|
|
||||||
class EventAssociate(ElementBase):
|
class EventAssociate(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||||
|
@ -521,7 +490,7 @@ class EventAssociate(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(EventCollection, EventAssociate)
|
registerStanzaPlugin(EventCollection, EventAssociate)
|
||||||
|
|
||||||
class EventDisassociate(ElementBase):
|
class EventDisassociate(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||||
|
@ -531,7 +500,7 @@ class EventDisassociate(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(EventCollection, EventDisassociate)
|
registerStanzaPlugin(EventCollection, EventDisassociate)
|
||||||
|
|
||||||
class EventConfiguration(ElementBase):
|
class EventConfiguration(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||||
|
@ -541,22 +510,8 @@ class EventConfiguration(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
def getConfig(self):
|
registerStanzaPlugin(Event, EventConfiguration)
|
||||||
config = self.xml.find('{jabber:x:data}x')
|
registerStanzaPlugin(EventConfiguration, xep_0004.Form)
|
||||||
form = xep_0004.Form()
|
|
||||||
if config is not None:
|
|
||||||
form.fromXML(config)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def setConfig(self, value):
|
|
||||||
self.xml.append(value.getXML())
|
|
||||||
return self
|
|
||||||
|
|
||||||
def delConfig(self):
|
|
||||||
config = self.xml.find('{jabber:x:data}x')
|
|
||||||
self.xml.remove(config)
|
|
||||||
|
|
||||||
stanzaPlugin(Event, EventConfiguration)
|
|
||||||
|
|
||||||
class EventPurge(ElementBase):
|
class EventPurge(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||||
|
@ -566,7 +521,7 @@ class EventPurge(ElementBase):
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
|
|
||||||
stanzaPlugin(Event, EventPurge)
|
registerStanzaPlugin(Event, EventPurge)
|
||||||
|
|
||||||
class EventSubscription(ElementBase):
|
class EventSubscription(ElementBase):
|
||||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||||
|
@ -582,4 +537,4 @@ class EventSubscription(ElementBase):
|
||||||
def getJid(self):
|
def getJid(self):
|
||||||
return JID(self._getAttr('jid'))
|
return JID(self._getAttr('jid'))
|
||||||
|
|
||||||
stanzaPlugin(Event, EventSubscription)
|
registerStanzaPlugin(Event, EventSubscription)
|
||||||
|
|
|
@ -1,427 +1,347 @@
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2007 Nathanael C. Fritz
|
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
SleekXMPP is free software; you can redistribute it and/or modify
|
See the file LICENSE for copying permission.
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SleekXMPP is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SleekXMPP; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
"""
|
"""
|
||||||
from . import base
|
|
||||||
import logging
|
import logging
|
||||||
from xml.etree import cElementTree as ET
|
|
||||||
import copy
|
import copy
|
||||||
#TODO support item groups and results
|
from . import base
|
||||||
|
from .. xmlstream.handler.callback import Callback
|
||||||
|
from .. xmlstream.matcher.xpath import MatchXPath
|
||||||
|
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
||||||
|
from .. stanza.message import Message
|
||||||
|
|
||||||
|
|
||||||
|
class Form(ElementBase):
|
||||||
|
namespace = 'jabber:x:data'
|
||||||
|
name = 'x'
|
||||||
|
plugin_attrib = 'form'
|
||||||
|
interfaces = set(('fields', 'instructions', 'items', 'reported', 'title', 'type', 'values'))
|
||||||
|
sub_interfaces = set(('title',))
|
||||||
|
form_types = set(('cancel', 'form', 'result', 'submit'))
|
||||||
|
|
||||||
|
def setup(self, xml=None):
|
||||||
|
if ElementBase.setup(self, xml): #if we had to generate xml
|
||||||
|
self['type'] = 'form'
|
||||||
|
|
||||||
|
def addField(self, var='', ftype=None, label='', desc='', required=False, value=None, options=None, **kwargs):
|
||||||
|
kwtype = kwargs.get('type', None)
|
||||||
|
if kwtype is None:
|
||||||
|
kwtype = ftype
|
||||||
|
|
||||||
|
field = FormField(parent=self)
|
||||||
|
field['var'] = var
|
||||||
|
field['type'] = kwtype
|
||||||
|
field['label'] = label
|
||||||
|
field['desc'] = desc
|
||||||
|
field['required'] = required
|
||||||
|
field['value'] = value
|
||||||
|
if options is not None:
|
||||||
|
field['options'] = options
|
||||||
|
return field
|
||||||
|
|
||||||
|
def getXML(self):
|
||||||
|
logging.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py")
|
||||||
|
return self.xml
|
||||||
|
|
||||||
|
def fromXML(self, xml):
|
||||||
|
logging.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py")
|
||||||
|
n = Form(xml=xml)
|
||||||
|
return n
|
||||||
|
|
||||||
|
def addItem(self, values):
|
||||||
|
itemXML = ET.Element('{%s}item' % self.namespace)
|
||||||
|
self.xml.append(itemXML)
|
||||||
|
reported_vars = self['reported'].keys()
|
||||||
|
for var in reported_vars:
|
||||||
|
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
||||||
|
itemXML.append(fieldXML)
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
field['var'] = var
|
||||||
|
field['value'] = values.get(var, None)
|
||||||
|
|
||||||
|
def addReported(self, var, ftype=None, label='', desc='', **kwargs):
|
||||||
|
kwtype = kwargs.get('type', None)
|
||||||
|
if kwtype is None:
|
||||||
|
kwtype = ftype
|
||||||
|
reported = self.xml.find('{%s}reported' % self.namespace)
|
||||||
|
if reported is None:
|
||||||
|
reported = ET.Element('{%s}reported' % self.namespace)
|
||||||
|
self.xml.append(reported)
|
||||||
|
fieldXML = ET.Element('{%s}field' % FormField.namespace)
|
||||||
|
reported.append(fieldXML)
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
field['var'] = var
|
||||||
|
field['type'] = kwtype
|
||||||
|
field['label'] = label
|
||||||
|
field['desc'] = desc
|
||||||
|
return field
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self['type'] = 'cancel'
|
||||||
|
|
||||||
|
def delFields(self):
|
||||||
|
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
|
||||||
|
for fieldXML in fieldsXML:
|
||||||
|
self.xml.remove(fieldXML)
|
||||||
|
|
||||||
|
def delInstructions(self):
|
||||||
|
instsXML = self.xml.findall('{%s}instructions')
|
||||||
|
for instXML in instsXML:
|
||||||
|
self.xml.remove(instXML)
|
||||||
|
|
||||||
|
def delItems(self):
|
||||||
|
itemsXML = self.xml.find('{%s}item' % self.namespace)
|
||||||
|
for itemXML in itemsXML:
|
||||||
|
self.xml.remove(itemXML)
|
||||||
|
|
||||||
|
def delReported(self):
|
||||||
|
reportedXML = self.xml.find('{%s}reported' % self.namespace)
|
||||||
|
if reportedXML is not None:
|
||||||
|
self.xml.remove(reportedXML)
|
||||||
|
|
||||||
|
def getFields(self, use_dict=False):
|
||||||
|
fields = {} if use_dict else []
|
||||||
|
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
|
||||||
|
for fieldXML in fieldsXML:
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
if use_dict:
|
||||||
|
fields[field['var']] = field
|
||||||
|
else:
|
||||||
|
fields.append((field['var'], field))
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def getInstructions(self):
|
||||||
|
instructions = ''
|
||||||
|
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
|
||||||
|
return "\n".join([instXML.text for instXML in instsXML])
|
||||||
|
|
||||||
|
def getItems(self):
|
||||||
|
items = []
|
||||||
|
itemsXML = self.xml.findall('{%s}item' % self.namespace)
|
||||||
|
for itemXML in itemsXML:
|
||||||
|
item = {}
|
||||||
|
fieldsXML = itemXML.findall('{%s}field' % FormField.namespace)
|
||||||
|
for fieldXML in fieldsXML:
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
item[field['var']] = field['value']
|
||||||
|
items.append(item)
|
||||||
|
return items
|
||||||
|
|
||||||
|
def getReported(self):
|
||||||
|
fields = {}
|
||||||
|
fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
|
||||||
|
FormField.namespace))
|
||||||
|
for fieldXML in fieldsXML:
|
||||||
|
field = FormField(xml=fieldXML)
|
||||||
|
fields[field['var']] = field
|
||||||
|
return fields
|
||||||
|
|
||||||
|
def getValues(self):
|
||||||
|
values = {}
|
||||||
|
fields = self.getFields(use_dict=True)
|
||||||
|
for var in fields:
|
||||||
|
values[var] = fields[var]['value']
|
||||||
|
return values
|
||||||
|
|
||||||
|
def reply(self):
|
||||||
|
if self['type'] == 'form':
|
||||||
|
self['type'] = 'submit'
|
||||||
|
elif self['type'] == 'submit':
|
||||||
|
self['type'] = 'result'
|
||||||
|
|
||||||
|
def setFields(self, fields, default=None):
|
||||||
|
del self['fields']
|
||||||
|
for field_data in fields:
|
||||||
|
var = field_data[0]
|
||||||
|
field = field_data[1]
|
||||||
|
field['var'] = var
|
||||||
|
|
||||||
|
self.addField(**field)
|
||||||
|
|
||||||
|
def setInstructions(self, instructions):
|
||||||
|
del self['instructions']
|
||||||
|
if instructions in [None, '']:
|
||||||
|
return
|
||||||
|
instructions = instructions.split('\n')
|
||||||
|
for instruction in instructions:
|
||||||
|
inst = ET.Element('{%s}instructions' % self.namespace)
|
||||||
|
inst.text = instruction
|
||||||
|
self.xml.append(inst)
|
||||||
|
|
||||||
|
def setItems(self, items):
|
||||||
|
for item in items:
|
||||||
|
self.addItem(item)
|
||||||
|
|
||||||
|
def setReported(self, reported, default=None):
|
||||||
|
for var in reported:
|
||||||
|
field = reported[var]
|
||||||
|
field['var'] = var
|
||||||
|
self.addReported(var, **field)
|
||||||
|
|
||||||
|
def setValues(self, values):
|
||||||
|
fields = self.getFields(use_dict=True)
|
||||||
|
for field in values:
|
||||||
|
fields[field]['value'] = values[field]
|
||||||
|
|
||||||
|
|
||||||
|
class FormField(ElementBase):
|
||||||
|
namespace = 'jabber:x:data'
|
||||||
|
name = 'field'
|
||||||
|
plugin_attrib = 'field'
|
||||||
|
interfaces = set(('answer', 'desc', 'required', 'value', 'options', 'label', 'type', 'var'))
|
||||||
|
sub_interfaces = set(('desc',))
|
||||||
|
field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
|
||||||
|
'list-single', 'text-multi', 'text-private', 'text-single'))
|
||||||
|
multi_value_types = set(('hidden', 'jid-multi', 'list-multi', 'text-multi'))
|
||||||
|
multi_line_types = set(('hidden', 'text-multi'))
|
||||||
|
option_types = set(('list-multi', 'list-single'))
|
||||||
|
true_values = set((True, '1', 'true'))
|
||||||
|
|
||||||
|
def addOption(self, label='', value=''):
|
||||||
|
if self['type'] in self.option_types:
|
||||||
|
opt = FieldOption(parent=self)
|
||||||
|
opt['label'] = label
|
||||||
|
opt['value'] = value
|
||||||
|
else:
|
||||||
|
raise ValueError("Cannot add options to a %s field." % self['type'])
|
||||||
|
|
||||||
|
def delOptions(self):
|
||||||
|
optsXML = self.xml.findall('{%s}option' % self.namespace)
|
||||||
|
for optXML in optsXML:
|
||||||
|
self.xml.remove(optXML)
|
||||||
|
|
||||||
|
def delRequired(self):
|
||||||
|
reqXML = self.xml.find('{%s}required' % self.namespace)
|
||||||
|
if reqXML is not None:
|
||||||
|
self.xml.remove(reqXML)
|
||||||
|
|
||||||
|
def delValue(self):
|
||||||
|
valsXML = self.xml.findall('{%s}value' % self.namespace)
|
||||||
|
for valXML in valsXML:
|
||||||
|
self.xml.remove(valXML)
|
||||||
|
|
||||||
|
def getAnswer(self):
|
||||||
|
return self.getValue()
|
||||||
|
|
||||||
|
def getOptions(self):
|
||||||
|
options = []
|
||||||
|
optsXML = self.xml.findall('{%s}option' % self.namespace)
|
||||||
|
for optXML in optsXML:
|
||||||
|
opt = FieldOption(xml=optXML)
|
||||||
|
options.append({'label': opt['label'], 'value':opt['value']})
|
||||||
|
return options
|
||||||
|
|
||||||
|
def getRequired(self):
|
||||||
|
reqXML = self.xml.find('{%s}required' % self.namespace)
|
||||||
|
return reqXML is not None
|
||||||
|
|
||||||
|
def getValue(self):
|
||||||
|
valsXML = self.xml.findall('{%s}value' % self.namespace)
|
||||||
|
if len(valsXML) == 0:
|
||||||
|
return None
|
||||||
|
elif self['type'] == 'boolean':
|
||||||
|
return valsXML[0].text in self.true_values
|
||||||
|
elif self['type'] in self.multi_value_types:
|
||||||
|
values = []
|
||||||
|
for valXML in valsXML:
|
||||||
|
if valXML.text is None:
|
||||||
|
valXML.text = ''
|
||||||
|
values.append(valXML.text)
|
||||||
|
if self['type'] == 'text-multi':
|
||||||
|
values = "\n".join(values)
|
||||||
|
return values
|
||||||
|
else:
|
||||||
|
return valsXML[0].text
|
||||||
|
|
||||||
|
def setAnswer(self, answer):
|
||||||
|
self.setValue(answer)
|
||||||
|
|
||||||
|
def setFalse(self):
|
||||||
|
self.setValue(False)
|
||||||
|
|
||||||
|
def setOptions(self, options):
|
||||||
|
for value in options:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
self.addOption(**value)
|
||||||
|
else:
|
||||||
|
self.addOption(value=value)
|
||||||
|
|
||||||
|
def setRequired(self, required):
|
||||||
|
exists = self.getRequired()
|
||||||
|
if not exists and required:
|
||||||
|
self.xml.append(ET.Element('{%s}required' % self.namespace))
|
||||||
|
elif exists and not required:
|
||||||
|
self.delRequired()
|
||||||
|
|
||||||
|
def setTrue(self):
|
||||||
|
self.setValue(True)
|
||||||
|
|
||||||
|
def setValue(self, value):
|
||||||
|
self.delValue()
|
||||||
|
valXMLName = '{%s}value' % self.namespace
|
||||||
|
|
||||||
|
if self['type'] == 'boolean':
|
||||||
|
if value in self.true_values:
|
||||||
|
valXML = ET.Element(valXMLName)
|
||||||
|
valXML.text = '1'
|
||||||
|
self.xml.append(valXML)
|
||||||
|
else:
|
||||||
|
valXML = ET.Element(valXMLName)
|
||||||
|
valXML.text = '0'
|
||||||
|
self.xml.append(valXML)
|
||||||
|
elif self['type'] in self.multi_value_types or self['type'] in ['', None]:
|
||||||
|
if self['type'] in self.multi_line_types and isinstance(value, str):
|
||||||
|
value = value.split('\n')
|
||||||
|
if not isinstance(value, list):
|
||||||
|
value = [value]
|
||||||
|
for val in value:
|
||||||
|
if self['type'] in ['', None] and val in self.true_values:
|
||||||
|
val = '1'
|
||||||
|
valXML = ET.Element(valXMLName)
|
||||||
|
valXML.text = val
|
||||||
|
self.xml.append(valXML)
|
||||||
|
else:
|
||||||
|
if isinstance(value, list):
|
||||||
|
raise ValueError("Cannot add multiple values to a %s field." % self['type'])
|
||||||
|
valXML = ET.Element(valXMLName)
|
||||||
|
valXML.text = value
|
||||||
|
self.xml.append(valXML)
|
||||||
|
|
||||||
|
|
||||||
|
class FieldOption(ElementBase):
|
||||||
|
namespace = 'jabber:x:data'
|
||||||
|
name = 'option'
|
||||||
|
plugin_attrib = 'option'
|
||||||
|
interfaces = set(('label', 'value'))
|
||||||
|
sub_interfaces = set(('value',))
|
||||||
|
|
||||||
|
|
||||||
class xep_0004(base.base_plugin):
|
class xep_0004(base.base_plugin):
|
||||||
|
"""
|
||||||
|
XEP-0004: Data Forms
|
||||||
|
"""
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.xep = '0004'
|
self.xep = '0004'
|
||||||
self.description = 'Data Forms'
|
self.description = 'Data Forms'
|
||||||
self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform)
|
|
||||||
|
self.xmpp.registerHandler(
|
||||||
|
Callback('Data Form',
|
||||||
|
MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns,
|
||||||
|
Form.namespace)),
|
||||||
|
self.handle_form))
|
||||||
|
|
||||||
|
registerStanzaPlugin(FormField, FieldOption)
|
||||||
|
registerStanzaPlugin(Form, FormField)
|
||||||
|
registerStanzaPlugin(Message, Form)
|
||||||
|
|
||||||
def post_init(self):
|
def post_init(self):
|
||||||
base.base_plugin.post_init(self)
|
base.base_plugin.post_init(self)
|
||||||
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
|
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
|
||||||
|
|
||||||
def handler_message_xform(self, xml):
|
def handle_form(self, message):
|
||||||
object = self.handle_form(xml)
|
self.xmpp.event("message_xform", message)
|
||||||
self.xmpp.event("message_form", object)
|
|
||||||
|
|
||||||
def handler_presence_xform(self, xml):
|
|
||||||
object = self.handle_form(xml)
|
|
||||||
self.xmpp.event("presence_form", object)
|
|
||||||
|
|
||||||
def handle_form(self, xml):
|
|
||||||
xmlform = xml.find('{jabber:x:data}x')
|
|
||||||
object = self.buildForm(xmlform)
|
|
||||||
self.xmpp.event("message_xform", object)
|
|
||||||
return object
|
|
||||||
|
|
||||||
def buildForm(self, xml):
|
|
||||||
form = Form(ftype=xml.attrib['type'])
|
|
||||||
form.fromXML(xml)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def makeForm(self, ftype='form', title='', instructions=''):
|
|
||||||
return Form(self.xmpp, ftype, title, instructions)
|
|
||||||
|
|
||||||
class FieldContainer(object):
|
|
||||||
def __init__(self, stanza = 'form'):
|
|
||||||
self.fields = []
|
|
||||||
self.field = {}
|
|
||||||
self.stanza = stanza
|
|
||||||
|
|
||||||
def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
|
|
||||||
self.field[var] = FormField(var, ftype, label, desc, required, value)
|
|
||||||
self.fields.append(self.field[var])
|
|
||||||
return self.field[var]
|
|
||||||
|
|
||||||
def buildField(self, xml):
|
|
||||||
self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
|
|
||||||
self.fields.append(self.field[xml.get('var', '__unnamed__')])
|
|
||||||
self.field[xml.get('var', '__unnamed__')].buildField(xml)
|
|
||||||
|
|
||||||
def buildContainer(self, xml):
|
|
||||||
self.stanza = xml.tag
|
|
||||||
for field in xml.findall('{jabber:x:data}field'):
|
|
||||||
self.buildField(field)
|
|
||||||
|
|
||||||
def getXML(self, ftype):
|
|
||||||
container = ET.Element(self.stanza)
|
|
||||||
for field in self.fields:
|
|
||||||
container.append(field.getXML(ftype))
|
|
||||||
return container
|
|
||||||
|
|
||||||
class Form(FieldContainer):
|
|
||||||
types = ('form', 'submit', 'cancel', 'result')
|
|
||||||
def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
|
|
||||||
if not ftype in self.types:
|
|
||||||
raise ValueError("Invalid Form Type")
|
|
||||||
FieldContainer.__init__(self)
|
|
||||||
self.xmpp = xmpp
|
|
||||||
self.type = ftype
|
|
||||||
self.title = title
|
|
||||||
self.instructions = instructions
|
|
||||||
self.reported = []
|
|
||||||
self.items = []
|
|
||||||
|
|
||||||
def merge(self, form2):
|
|
||||||
form1 = Form(ftype=self.type)
|
|
||||||
form1.fromXML(self.getXML(self.type))
|
|
||||||
for field in form2.fields:
|
|
||||||
if not field.var in form1.field:
|
|
||||||
form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
|
|
||||||
else:
|
|
||||||
form1.field[field.var].value = field.value
|
|
||||||
for option, label in field.options:
|
|
||||||
if (option, label) not in form1.field[field.var].options:
|
|
||||||
form1.fields[field.var].addOption(option, label)
|
|
||||||
return form1
|
|
||||||
|
|
||||||
def copy(self):
|
|
||||||
newform = Form(ftype=self.type)
|
|
||||||
newform.fromXML(self.getXML(self.type))
|
|
||||||
return newform
|
|
||||||
|
|
||||||
def update(self, form):
|
|
||||||
values = form.getValues()
|
|
||||||
for var in values:
|
|
||||||
if var in self.fields:
|
|
||||||
self.fields[var].setValue(self.fields[var])
|
|
||||||
|
|
||||||
def getValues(self):
|
|
||||||
result = {}
|
|
||||||
for field in self.fields:
|
|
||||||
value = field.value
|
|
||||||
if len(value) == 1:
|
|
||||||
value = value[0]
|
|
||||||
result[field.var] = value
|
|
||||||
return result
|
|
||||||
|
|
||||||
def setValues(self, values={}):
|
|
||||||
for field in values:
|
|
||||||
if field in self.field:
|
|
||||||
if isinstance(values[field], list) or isinstance(values[field], tuple):
|
|
||||||
for value in values[field]:
|
|
||||||
self.field[field].setValue(value)
|
|
||||||
else:
|
|
||||||
self.field[field].setValue(values[field])
|
|
||||||
|
|
||||||
def fromXML(self, xml):
|
|
||||||
self.buildForm(xml)
|
|
||||||
|
|
||||||
def addItem(self):
|
|
||||||
newitem = FieldContainer('item')
|
|
||||||
self.items.append(newitem)
|
|
||||||
return newitem
|
|
||||||
|
|
||||||
def buildItem(self, xml):
|
|
||||||
newitem = self.addItem()
|
|
||||||
newitem.buildContainer(xml)
|
|
||||||
|
|
||||||
def addReported(self):
|
|
||||||
reported = FieldContainer('reported')
|
|
||||||
self.reported.append(reported)
|
|
||||||
return reported
|
|
||||||
|
|
||||||
def buildReported(self, xml):
|
|
||||||
reported = self.addReported()
|
|
||||||
reported.buildContainer(xml)
|
|
||||||
|
|
||||||
def setTitle(self, title):
|
|
||||||
self.title = title
|
|
||||||
|
|
||||||
def setInstructions(self, instructions):
|
|
||||||
self.instructions = instructions
|
|
||||||
|
|
||||||
def setType(self, ftype):
|
|
||||||
self.type = ftype
|
|
||||||
|
|
||||||
def getXMLMessage(self, to):
|
|
||||||
msg = self.xmpp.makeMessage(to)
|
|
||||||
msg.append(self.getXML())
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def buildForm(self, xml):
|
|
||||||
self.type = xml.get('type', 'form')
|
|
||||||
if xml.find('{jabber:x:data}title') is not None:
|
|
||||||
self.setTitle(xml.find('{jabber:x:data}title').text)
|
|
||||||
if xml.find('{jabber:x:data}instructions') is not None:
|
|
||||||
self.setInstructions(xml.find('{jabber:x:data}instructions').text)
|
|
||||||
for field in xml.findall('{jabber:x:data}field'):
|
|
||||||
self.buildField(field)
|
|
||||||
for reported in xml.findall('{jabber:x:data}reported'):
|
|
||||||
self.buildReported(reported)
|
|
||||||
for item in xml.findall('{jabber:x:data}item'):
|
|
||||||
self.buildItem(item)
|
|
||||||
|
|
||||||
#def getXML(self, tostring = False):
|
|
||||||
def getXML(self, ftype=None):
|
|
||||||
if ftype:
|
|
||||||
self.type = ftype
|
|
||||||
form = ET.Element('{jabber:x:data}x')
|
|
||||||
form.attrib['type'] = self.type
|
|
||||||
if self.title and self.type in ('form', 'result'):
|
|
||||||
title = ET.Element('{jabber:x:data}title')
|
|
||||||
title.text = self.title
|
|
||||||
form.append(title)
|
|
||||||
if self.instructions and self.type == 'form':
|
|
||||||
instructions = ET.Element('{jabber:x:data}instructions')
|
|
||||||
instructions.text = self.instructions
|
|
||||||
form.append(instructions)
|
|
||||||
for field in self.fields:
|
|
||||||
form.append(field.getXML(self.type))
|
|
||||||
for reported in self.reported:
|
|
||||||
form.append(reported.getXML('{jabber:x:data}reported'))
|
|
||||||
for item in self.items:
|
|
||||||
form.append(item.getXML(self.type))
|
|
||||||
#if tostring:
|
|
||||||
# form = self.xmpp.tostring(form)
|
|
||||||
return form
|
|
||||||
|
|
||||||
def getXHTML(self):
|
|
||||||
form = ET.Element('{http://www.w3.org/1999/xhtml}form')
|
|
||||||
if self.title:
|
|
||||||
title = ET.Element('h2')
|
|
||||||
title.text = self.title
|
|
||||||
form.append(title)
|
|
||||||
if self.instructions:
|
|
||||||
instructions = ET.Element('p')
|
|
||||||
instructions.text = self.instructions
|
|
||||||
form.append(instructions)
|
|
||||||
for field in self.fields:
|
|
||||||
form.append(field.getXHTML())
|
|
||||||
for field in self.reported:
|
|
||||||
form.append(field.getXHTML())
|
|
||||||
for field in self.items:
|
|
||||||
form.append(field.getXHTML())
|
|
||||||
return form
|
|
||||||
|
|
||||||
|
|
||||||
def makeSubmit(self):
|
|
||||||
self.setType('submit')
|
|
||||||
|
|
||||||
class FormField(object):
|
|
||||||
types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
|
|
||||||
listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
|
|
||||||
lbtypes = ('fixed', 'text-multi')
|
|
||||||
def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
|
|
||||||
if not ftype in self.types:
|
|
||||||
raise ValueError("Invalid Field Type")
|
|
||||||
self.type = ftype
|
|
||||||
self.var = var
|
|
||||||
self.label = label
|
|
||||||
self.desc = desc
|
|
||||||
self.options = []
|
|
||||||
self.required = False
|
|
||||||
self.value = []
|
|
||||||
if self.type in self.listtypes:
|
|
||||||
self.islist = True
|
|
||||||
else:
|
|
||||||
self.islist = False
|
|
||||||
if self.type in self.lbtypes:
|
|
||||||
self.islinebreak = True
|
|
||||||
else:
|
|
||||||
self.islinebreak = False
|
|
||||||
if value:
|
|
||||||
self.setValue(value)
|
|
||||||
|
|
||||||
def addOption(self, value, label):
|
|
||||||
if self.islist:
|
|
||||||
self.options.append((value, label))
|
|
||||||
else:
|
|
||||||
raise ValueError("Cannot add options to non-list type field.")
|
|
||||||
|
|
||||||
def setTrue(self):
|
|
||||||
if self.type == 'boolean':
|
|
||||||
self.value = [True]
|
|
||||||
|
|
||||||
def setFalse(self):
|
|
||||||
if self.type == 'boolean':
|
|
||||||
self.value = [False]
|
|
||||||
|
|
||||||
def require(self):
|
|
||||||
self.required = True
|
|
||||||
|
|
||||||
def setDescription(self, desc):
|
|
||||||
self.desc = desc
|
|
||||||
|
|
||||||
def setValue(self, value):
|
|
||||||
if self.type == 'boolean':
|
|
||||||
if value in ('1', 1, True, 'true', 'True', 'yes'):
|
|
||||||
value = True
|
|
||||||
else:
|
|
||||||
value = False
|
|
||||||
if self.islinebreak and value is not None:
|
|
||||||
self.value += value.split('\n')
|
|
||||||
else:
|
|
||||||
if len(self.value) and (not self.islist or self.type == 'list-single'):
|
|
||||||
self.value = [value]
|
|
||||||
else:
|
|
||||||
self.value.append(value)
|
|
||||||
|
|
||||||
def delValue(self, value):
|
|
||||||
if type(self.value) == type([]):
|
|
||||||
try:
|
|
||||||
idx = self.value.index(value)
|
|
||||||
if idx != -1:
|
|
||||||
self.value.pop(idx)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
self.value = ''
|
|
||||||
|
|
||||||
def setAnswer(self, value):
|
|
||||||
self.setValue(value)
|
|
||||||
|
|
||||||
def buildField(self, xml):
|
|
||||||
self.type = xml.get('type', 'text-single')
|
|
||||||
self.label = xml.get('label', '')
|
|
||||||
for option in xml.findall('{jabber:x:data}option'):
|
|
||||||
self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
|
|
||||||
for value in xml.findall('{jabber:x:data}value'):
|
|
||||||
self.setValue(value.text)
|
|
||||||
if xml.find('{jabber:x:data}required') is not None:
|
|
||||||
self.require()
|
|
||||||
if xml.find('{jabber:x:data}desc') is not None:
|
|
||||||
self.setDescription(xml.find('{jabber:x:data}desc').text)
|
|
||||||
|
|
||||||
def getXML(self, ftype):
|
|
||||||
field = ET.Element('{jabber:x:data}field')
|
|
||||||
if ftype != 'result':
|
|
||||||
field.attrib['type'] = self.type
|
|
||||||
if self.type != 'fixed':
|
|
||||||
if self.var:
|
|
||||||
field.attrib['var'] = self.var
|
|
||||||
if self.label:
|
|
||||||
field.attrib['label'] = self.label
|
|
||||||
if ftype == 'form':
|
|
||||||
for option in self.options:
|
|
||||||
optionxml = ET.Element('{jabber:x:data}option')
|
|
||||||
optionxml.attrib['label'] = option[1]
|
|
||||||
optionval = ET.Element('{jabber:x:data}value')
|
|
||||||
optionval.text = option[0]
|
|
||||||
optionxml.append(optionval)
|
|
||||||
field.append(optionxml)
|
|
||||||
if self.required:
|
|
||||||
required = ET.Element('{jabber:x:data}required')
|
|
||||||
field.append(required)
|
|
||||||
if self.desc:
|
|
||||||
desc = ET.Element('{jabber:x:data}desc')
|
|
||||||
desc.text = self.desc
|
|
||||||
field.append(desc)
|
|
||||||
for value in self.value:
|
|
||||||
valuexml = ET.Element('{jabber:x:data}value')
|
|
||||||
if value is True or value is False:
|
|
||||||
if value:
|
|
||||||
valuexml.text = '1'
|
|
||||||
else:
|
|
||||||
valuexml.text = '0'
|
|
||||||
else:
|
|
||||||
valuexml.text = value
|
|
||||||
field.append(valuexml)
|
|
||||||
return field
|
|
||||||
|
|
||||||
def getXHTML(self):
|
|
||||||
field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
|
|
||||||
if self.label:
|
|
||||||
label = ET.Element('p')
|
|
||||||
label.text = "%s: " % self.label
|
|
||||||
else:
|
|
||||||
label = ET.Element('p')
|
|
||||||
label.text = "%s: " % self.var
|
|
||||||
field.append(label)
|
|
||||||
if self.type == 'boolean':
|
|
||||||
formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
|
|
||||||
if len(self.value) and self.value[0] in (True, 'true', '1'):
|
|
||||||
formf.attrib['checked'] = 'checked'
|
|
||||||
elif self.type == 'fixed':
|
|
||||||
formf = ET.Element('p')
|
|
||||||
try:
|
|
||||||
formf.text = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
field.append(formf)
|
|
||||||
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.text = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
elif self.type == 'hidden':
|
|
||||||
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.text = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
elif self.type in ('jid-multi', 'list-multi'):
|
|
||||||
formf = ET.Element('select', {'name': self.var})
|
|
||||||
for option in self.options:
|
|
||||||
optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
|
|
||||||
optf.text = option[1]
|
|
||||||
if option[1] in self.value:
|
|
||||||
optf.attrib['selected'] = 'selected'
|
|
||||||
formf.append(option)
|
|
||||||
elif self.type in ('jid-single', 'text-single'):
|
|
||||||
formf = ET.Element('input', {'type': 'text', 'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.attrib['value'] = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
elif self.type == 'list-single':
|
|
||||||
formf = ET.Element('select', {'name': self.var})
|
|
||||||
for option in self.options:
|
|
||||||
optf = ET.Element('option', {'value': option[0]})
|
|
||||||
optf.text = option[1]
|
|
||||||
if not optf.text:
|
|
||||||
optf.text = option[0]
|
|
||||||
if option[1] in self.value:
|
|
||||||
optf.attrib['selected'] = 'selected'
|
|
||||||
formf.append(optf)
|
|
||||||
elif self.type == 'text-multi':
|
|
||||||
formf = ET.Element('textarea', {'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.text = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
if not formf.text:
|
|
||||||
formf.text = ' '
|
|
||||||
elif self.type == 'text-private':
|
|
||||||
formf = ET.Element('input', {'type': 'password', 'name': self.var})
|
|
||||||
try:
|
|
||||||
formf.attrib['value'] = ', '.join(self.value)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
label.append(formf)
|
|
||||||
return field
|
|
||||||
|
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
161
sleekxmpp/plugins/xep_0033.py
Normal file
161
sleekxmpp/plugins/xep_0033.py
Normal 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)
|
|
@ -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')
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
101
sleekxmpp/plugins/xep_0085.py
Normal file
101
sleekxmpp/plugins/xep_0085.py
Normal 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)
|
|
@ -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)
|
||||||
|
|
51
sleekxmpp/plugins/xep_0128.py
Normal file
51
sleekxmpp/plugins/xep_0128.py
Normal 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)
|
|
@ -1,22 +1,9 @@
|
||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
XEP-0199 (Ping) support
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
Copyright (C) 2007 Kevin Smith
|
This file is part of SleekXMPP.
|
||||||
This file is part of SleekXMPP.
|
|
||||||
|
|
||||||
SleekXMPP is free software; you can redistribute it and/or modify
|
See the file LICENSE for copying permission.
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
SleekXMPP is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with SleekXMPP; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
"""
|
"""
|
||||||
from xml.etree import cElementTree as ET
|
from xml.etree import cElementTree as ET
|
||||||
from . import base
|
from . import base
|
||||||
|
@ -29,7 +16,7 @@ class xep_0199(base.base_plugin):
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.description = "XMPP Ping"
|
self.description = "XMPP Ping"
|
||||||
self.xep = "0199"
|
self.xep = "0199"
|
||||||
self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='http://www.xmpp.org/extensions/xep-0199.html#ns'/></iq>" % self.xmpp.default_ns, self.handler_ping)
|
self.xmpp.add_handler("<iq type='get' xmlns='%s'><ping xmlns='http://www.xmpp.org/extensions/xep-0199.html#ns'/></iq>" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping')
|
||||||
self.running = False
|
self.running = False
|
||||||
#if self.config.get('keepalive', True):
|
#if self.config.get('keepalive', True):
|
||||||
#self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True)
|
#self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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'
|
|
||||||
conditions = set(('bad-request', 'conflict', 'feature-not-implemented', 'forbidden', 'gone', 'internal-server-error', 'item-not-found', 'jid-malformed', 'not-acceptable', 'not-allowed', 'not-authorized', 'payment-required', 'recipient-unavailable', 'redirect', 'registration-required', 'remote-server-not-found', 'remote-server-timeout', 'resource-constraint', 'service-unavailable', 'subscription-required', 'undefined-condition', 'unexpected-request'))
|
|
||||||
interfaces = set(('code', 'condition', 'text', 'type'))
|
|
||||||
types = set(('cancel', 'continue', 'modify', 'auth', 'wait'))
|
|
||||||
sub_interfaces = set(('text',))
|
|
||||||
condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
|
|
||||||
|
|
||||||
def setup(self, xml=None):
|
"""
|
||||||
if ElementBase.setup(self, xml): #if we had to generate xml
|
XMPP stanzas of type 'error' should include an <error> stanza that
|
||||||
self['type'] = 'cancel'
|
describes the nature of the error and how it should be handled.
|
||||||
self['condition'] = 'feature-not-implemented'
|
|
||||||
if self.parent is not None:
|
|
||||||
self.parent()['type'] = 'error'
|
|
||||||
|
|
||||||
def getCondition(self):
|
Use the 'XEP-0086: Error Condition Mappings' plugin to include error
|
||||||
for child in self.xml.getchildren():
|
codes used in older XMPP versions.
|
||||||
if "{%s}" % self.condition_ns in child.tag:
|
|
||||||
return child.tag.split('}', 1)[-1]
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def setCondition(self, value):
|
Example error stanza:
|
||||||
if value in self.conditions:
|
<error type="cancel" code="404">
|
||||||
for child in self.xml.getchildren():
|
<item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||||
if "{%s}" % self.condition_ns in child.tag:
|
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||||
self.xml.remove(child)
|
The item was not found.
|
||||||
condition = ET.Element("{%s}%s" % (self.condition_ns, value))
|
</text>
|
||||||
self.xml.append(condition)
|
</error>
|
||||||
return self
|
|
||||||
|
|
||||||
def delCondition(self):
|
Stanza Interface:
|
||||||
return self
|
code -- The error code used in older XMPP versions.
|
||||||
|
condition -- The name of the condition element.
|
||||||
|
text -- Human readable description of the error.
|
||||||
|
type -- Error type indicating how the error should be handled.
|
||||||
|
|
||||||
def getText(self):
|
Attributes:
|
||||||
text = ''
|
conditions -- The set of allowable error condition elements.
|
||||||
textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text")
|
condition_ns -- The namespace for the condition element.
|
||||||
if textxml is not None:
|
types -- A set of values indicating how the error
|
||||||
text = textxml.text
|
should be treated.
|
||||||
return text
|
|
||||||
|
|
||||||
def setText(self, value):
|
Methods:
|
||||||
self.delText()
|
setup -- Overrides ElementBase.setup.
|
||||||
textxml = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}text')
|
getCondition -- Retrieve the name of the condition element.
|
||||||
textxml.text = value
|
setCondition -- Add a condition element.
|
||||||
self.xml.append(textxml)
|
delCondition -- Remove the condition element.
|
||||||
return self
|
getText -- Retrieve the contents of the <text> element.
|
||||||
|
setText -- Set the contents of the <text> element.
|
||||||
|
delText -- Remove the <text> element.
|
||||||
|
"""
|
||||||
|
|
||||||
def delText(self):
|
namespace = 'jabber:client'
|
||||||
textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text")
|
name = 'error'
|
||||||
if textxml is not None:
|
plugin_attrib = 'error'
|
||||||
self.xml.remove(textxml)
|
interfaces = set(('code', 'condition', 'text', 'type'))
|
||||||
|
sub_interfaces = set(('text',))
|
||||||
|
conditions = set(('bad-request', 'conflict', 'feature-not-implemented',
|
||||||
|
'forbidden', 'gone', 'internal-server-error',
|
||||||
|
'item-not-found', 'jid-malformed', 'not-acceptable',
|
||||||
|
'not-allowed', 'not-authorized', 'payment-required',
|
||||||
|
'recipient-unavailable', 'redirect',
|
||||||
|
'registration-required', 'remote-server-not-found',
|
||||||
|
'remote-server-timeout', 'resource-constraint',
|
||||||
|
'service-unavailable', 'subscription-required',
|
||||||
|
'undefined-condition', 'unexpected-request'))
|
||||||
|
condition_ns = 'urn:ietf:params:xml:ns:xmpp-stanzas'
|
||||||
|
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
|
||||||
|
|
|
@ -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)
|
|
||||||
self.xml.append(body)
|
|
||||||
else:
|
|
||||||
self.xml.append(html)
|
|
||||||
|
|
||||||
def getHtml(self):
|
Only a subset of XHTML is recommended for use with XHTML-IM.
|
||||||
html = self.xml.find('{http://www.w3.org/1999/xhtml}body')
|
See the full spec at 'http://xmpp.org/extensions/xep-0071.html'
|
||||||
if html is None: return ''
|
for more information.
|
||||||
return html
|
|
||||||
|
|
||||||
def delHtml(self):
|
Example stanza:
|
||||||
if self.parent is not None:
|
<message to="user@example.com">
|
||||||
self.parent().xml.remove(self.xml)
|
<body>Non-html message content.</body>
|
||||||
|
<html xmlns="http://jabber.org/protocol/xhtml-im">
|
||||||
|
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<p><b>HTML!</b></p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</message>
|
||||||
|
|
||||||
|
Stanza Interface:
|
||||||
|
body -- The contents of the HTML body tag.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
getBody -- Return the HTML body contents.
|
||||||
|
setBody -- Set the HTML body contents.
|
||||||
|
delBody -- Remove the HTML body contents.
|
||||||
|
"""
|
||||||
|
|
||||||
|
namespace = 'http://jabber.org/protocol/xhtml-im'
|
||||||
|
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)
|
||||||
|
|
|
@ -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:
|
|
||||||
self['id'] = '0'
|
|
||||||
|
|
||||||
def unhandled(self):
|
Each <iq> stanza must have an 'id' value which associates the
|
||||||
if self['type'] in ('get', 'set'):
|
stanza with the response stanza. XMPP entities must always
|
||||||
self.reply()
|
be given a response <iq> stanza with a type of 'result' after
|
||||||
self['error']['condition'] = 'feature-not-implemented'
|
sending a stanza of type 'get' or 'set'.
|
||||||
self['error']['text'] = 'No handlers registered for this request.'
|
|
||||||
self.send()
|
|
||||||
|
|
||||||
def setPayload(self, value):
|
Most uses cases for <iq> stanzas will involve adding a <query>
|
||||||
self.clear()
|
element whose namespace indicates the type of information
|
||||||
StanzaBase.setPayload(self, value)
|
desired. However, some custom XMPP applications use <iq> stanzas
|
||||||
return self
|
as a carrier stanza for an application-specific protocol instead.
|
||||||
|
|
||||||
def setQuery(self, value):
|
Example <iq> Stanzas:
|
||||||
query = self.xml.find("{%s}query" % value)
|
<iq to="user@example.com" type="get" id="314">
|
||||||
if query is None and value:
|
<query xmlns="http://jabber.org/protocol/disco#items" />
|
||||||
self.clear()
|
</iq>
|
||||||
query = ET.Element("{%s}query" % value)
|
|
||||||
self.xml.append(query)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def getQuery(self):
|
<iq to="user@localhost" type="result" id="17">
|
||||||
for child in self.xml.getchildren():
|
<query xmlns='jabber:iq:roster'>
|
||||||
if child.tag.endswith('query'):
|
<item jid='otheruser@example.net'
|
||||||
ns =child.tag.split('}')[0]
|
name='John Doe'
|
||||||
if '{' in ns:
|
subscription='both'>
|
||||||
ns = ns[1:]
|
<group>Friends</group>
|
||||||
return ns
|
</item>
|
||||||
return ''
|
</query>
|
||||||
|
</iq>
|
||||||
|
|
||||||
def reply(self):
|
Stanza Interface:
|
||||||
self['type'] = 'result'
|
query -- The namespace of the <query> element if one exists.
|
||||||
StanzaBase.reply(self)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def delQuery(self):
|
Attributes:
|
||||||
for child in self.getchildren():
|
types -- May be one of: get, set, result, or error.
|
||||||
if child.tag.endswith('query'):
|
|
||||||
self.xml.remove(child)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def send(self, block=True, timeout=10):
|
Methods:
|
||||||
if block and self['type'] in ('get', 'set'):
|
__init__ -- Overrides StanzaBase.__init__.
|
||||||
waitfor = Waiter('IqWait_%s' % self['id'], MatcherId(self['id']))
|
unhandled -- Send error if there are no handlers.
|
||||||
self.stream.registerHandler(waitfor)
|
setPayload -- Overrides StanzaBase.setPayload.
|
||||||
StanzaBase.send(self)
|
setQuery -- Add or modify a <query> element.
|
||||||
return waitfor.wait(timeout)
|
getQuery -- Return the namespace of the <query> element.
|
||||||
else:
|
delQuery -- Remove the <query> element.
|
||||||
return StanzaBase.send(self)
|
reply -- Overrides StanzaBase.reply
|
||||||
|
send -- Overrides StanzaBase.send
|
||||||
|
"""
|
||||||
|
|
||||||
|
namespace = 'jabber:client'
|
||||||
|
name = 'iq'
|
||||||
|
interfaces = set(('type', 'to', 'from', 'id', 'query'))
|
||||||
|
types = set(('get', 'result', 'set', 'error'))
|
||||||
|
plugin_attrib = name
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
|
@ -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):
|
Chat clients will typically use <message> stanzas that have a type
|
||||||
self['type'] = 'chat'
|
of either "chat" or "groupchat".
|
||||||
return self
|
|
||||||
|
|
||||||
def normal(self):
|
When handling a message event, be sure to check if the message is
|
||||||
self['type'] = 'normal'
|
an error response.
|
||||||
return self
|
|
||||||
|
|
||||||
def reply(self, body=None):
|
Example <message> stanzas:
|
||||||
StanzaBase.reply(self)
|
<message to="user1@example.com" from="user2@example.com">
|
||||||
if self['type'] == 'groupchat':
|
<body>Hi!</body>
|
||||||
self['to'] = self['to'].bare
|
</message>
|
||||||
del self['id']
|
|
||||||
if body is not None:
|
|
||||||
self['body'] = body
|
|
||||||
return self
|
|
||||||
|
|
||||||
def getMucroom(self):
|
<message type="groupchat" to="room@conference.example.com">
|
||||||
if self['type'] == 'groupchat':
|
<body>Hi everyone!</body>
|
||||||
return self['from'].bare
|
</message>
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def setMucroom(self, value):
|
Stanza Interface:
|
||||||
pass
|
body -- The main contents of the message.
|
||||||
|
subject -- An optional description of the message's contents.
|
||||||
|
mucroom -- (Read-only) The name of the MUC room that sent the message.
|
||||||
|
mucnick -- (Read-only) The MUC nickname of message's sender.
|
||||||
|
|
||||||
def delMucroom(self):
|
Attributes:
|
||||||
pass
|
types -- May be one of: normal, chat, headline, groupchat, or error.
|
||||||
|
|
||||||
def getMucnick(self):
|
Methods:
|
||||||
if self['type'] == 'groupchat':
|
chat -- Set the message type to 'chat'.
|
||||||
return self['from'].resource
|
normal -- Set the message type to 'normal'.
|
||||||
else:
|
reply -- Overrides StanzaBase.reply
|
||||||
return ''
|
getType -- Overrides StanzaBase interface
|
||||||
|
getMucroom -- Return the name of the MUC room of the message.
|
||||||
|
setMucroom -- Dummy method to prevent assignment.
|
||||||
|
delMucroom -- Dummy method to prevent deletion.
|
||||||
|
getMucnick -- Return the MUC nickname of the message's sender.
|
||||||
|
setMucnick -- Dummy method to prevent assignment.
|
||||||
|
delMucnick -- Dummy method to prevent deletion.
|
||||||
|
"""
|
||||||
|
|
||||||
def setMucnick(self, value):
|
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 delMucnick(self):
|
def getType(self):
|
||||||
pass
|
"""
|
||||||
|
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
|
||||||
|
|
|
@ -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):
|
The nickname contained in a <nick> should be the global, friendly or
|
||||||
return self.xml.text
|
informal name chosen by the owner of a bare JID. The <nick> element
|
||||||
|
may be included when establishing communications with new entities,
|
||||||
|
such as normal XMPP users or MUC services.
|
||||||
|
|
||||||
def delNick(self):
|
The nickname contained in a <nick> element will not necessarily be
|
||||||
if self.parent is not None:
|
the same as the nickname used in a MUC.
|
||||||
self.parent().xml.remove(self.xml)
|
|
||||||
|
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)
|
||||||
|
|
|
@ -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):
|
<presence type="unavailable" />
|
||||||
out = self._getAttr('type')
|
|
||||||
if not out:
|
|
||||||
show = self.getShowElement()
|
|
||||||
if show is not None:
|
|
||||||
out = show.text
|
|
||||||
if not out or out is None:
|
|
||||||
out = 'available'
|
|
||||||
return out
|
|
||||||
|
|
||||||
def reply(self):
|
<presence to="user@otherhost.com" type="subscribe" />
|
||||||
if self['type'] == 'unsubscribe':
|
|
||||||
self['type'] = 'unsubscribed'
|
Stanza Interface:
|
||||||
elif self['type'] == 'subscribe':
|
priority -- A value used by servers to determine message routing.
|
||||||
self['type'] = 'subscribed'
|
show -- The type of status, such as away or available for chat.
|
||||||
return StanzaBase.reply(self)
|
status -- Custom, human readable status message.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
types -- One of: available, unavailable, error, probe,
|
||||||
|
subscribe, subscribed, unsubscribe,
|
||||||
|
and unsubscribed.
|
||||||
|
showtypes -- One of: away, chat, dnd, and xa.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
reply -- Overrides StanzaBase.reply
|
||||||
|
setShow -- Set the value of the <show> element.
|
||||||
|
getType -- Get the value of the type attribute or <show> element.
|
||||||
|
setType -- Set the value of the type attribute or <show> element.
|
||||||
|
getPriority -- Get the value of the <priority> element.
|
||||||
|
setPriority -- Set the value of the <priority> element.
|
||||||
|
"""
|
||||||
|
|
||||||
|
namespace = 'jabber:client'
|
||||||
|
name = 'presence'
|
||||||
|
interfaces = set(('type', 'to', 'from', 'id', 'show',
|
||||||
|
'status', 'priority'))
|
||||||
|
sub_interfaces = set(('show', 'status', 'priority'))
|
||||||
|
plugin_attrib = name
|
||||||
|
|
||||||
|
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
|
||||||
|
'subscribed', 'unsubscribe', 'unsubscribed'))
|
||||||
|
showtypes = set(('dnd', 'chat', 'xa', 'away'))
|
||||||
|
|
||||||
|
def setShow(self, show):
|
||||||
|
"""
|
||||||
|
Set the value of the <show> element.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
show -- Must be one of: away, chat, dnd, or xa.
|
||||||
|
"""
|
||||||
|
if show in self.showtypes:
|
||||||
|
self._setSubText('show', text=show)
|
||||||
|
return self
|
||||||
|
|
||||||
|
def setType(self, value):
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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']:
|
|
||||||
groupxml = ET.Element('{jabber:iq:roster}group')
|
|
||||||
groupxml.text = group
|
|
||||||
item.append(groupxml)
|
|
||||||
self.xml.append(item)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def getItems(self):
|
Stanza Inteface:
|
||||||
items = {}
|
items -- A dictionary of roster entries contained
|
||||||
itemsxml = self.xml.findall('{jabber:iq:roster}item')
|
in the stanza.
|
||||||
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):
|
Methods:
|
||||||
for child in self.xml.getchildren():
|
getItems -- Return a dictionary of roster entries.
|
||||||
self.xml.remove(child)
|
setItems -- Add <item> elements.
|
||||||
|
delItems -- Remove all <item> elements.
|
||||||
|
"""
|
||||||
|
|
||||||
|
namespace = 'jabber:iq:roster'
|
||||||
|
name = 'query'
|
||||||
|
plugin_attrib = 'roster'
|
||||||
|
interfaces = set(('items',))
|
||||||
|
|
||||||
|
def setItems(self, items):
|
||||||
|
"""
|
||||||
|
Set the roster entries in the <roster> stanza.
|
||||||
|
|
||||||
|
Uses a dictionary using JIDs as keys, where each entry is itself
|
||||||
|
a dictionary that contains:
|
||||||
|
name -- An alias or nickname for the JID.
|
||||||
|
subscription -- The subscription type. Can be one of 'to',
|
||||||
|
'from', 'both', 'none', or 'remove'.
|
||||||
|
groups -- A list of group names to which the JID
|
||||||
|
has been assigned.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
items -- A dictionary of roster entries.
|
||||||
|
"""
|
||||||
|
self.delItems()
|
||||||
|
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)
|
||||||
|
|
|
@ -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']
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
121
sleekxmpp/xmlstream/jid.py
Normal 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
|
|
@ -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
|
|
@ -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):
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
Arguments:
|
||||||
self.idx += 1
|
stanza -- The class of the parent stanza.
|
||||||
if self.idx > len(self.iterables):
|
plugin -- The class of the plugin stanza.
|
||||||
self.idx = 0
|
"""
|
||||||
raise StopIteration
|
tag = "{%s}%s" % (plugin.namespace, plugin.name)
|
||||||
return self.iterables[self.idx - 1]
|
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
|
||||||
|
stanza.plugin_tag_map[tag] = plugin
|
||||||
|
|
||||||
def next(self):
|
|
||||||
return self.__next__()
|
|
||||||
|
|
||||||
def __len__(self):
|
class ElementBase(object):
|
||||||
return len(self.iterables)
|
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 append(self, item):
|
def __init__(self, xml=None, parent=None):
|
||||||
if not isinstance(item, ElementBase):
|
"""
|
||||||
if type(item) == xmltester:
|
Create a new stanza object.
|
||||||
return self.appendxml(item)
|
|
||||||
else:
|
|
||||||
raise TypeError
|
|
||||||
self.xml.append(item.xml)
|
|
||||||
self.iterables.append(item)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def pop(self, idx=0):
|
Arguments:
|
||||||
aff = self.iterables.pop(idx)
|
xml -- Initialize the stanza with optional existing XML.
|
||||||
self.xml.remove(aff.xml)
|
parent -- Optional stanza object that contains this stanza.
|
||||||
return aff
|
"""
|
||||||
|
self.xml = xml
|
||||||
|
self.plugins = {}
|
||||||
|
self.iterables = []
|
||||||
|
self.idx = 0
|
||||||
|
if parent is None:
|
||||||
|
self.parent = None
|
||||||
|
else:
|
||||||
|
self.parent = weakref.ref(parent)
|
||||||
|
|
||||||
def get(self, key, defaultvalue=None):
|
if self.setup(xml):
|
||||||
value = self[key]
|
# If we generated our own XML, then everything is ready.
|
||||||
if value is None or value == '':
|
return
|
||||||
return defaultvalue
|
|
||||||
return value
|
|
||||||
|
|
||||||
def keys(self):
|
# Initialize values using provided XML
|
||||||
out = []
|
for child in self.xml.getchildren():
|
||||||
out += [x for x in self.interfaces]
|
if child.tag in self.plugin_tag_map:
|
||||||
out += [x for x in self.plugins]
|
plugin = self.plugin_tag_map[child.tag]
|
||||||
if self.iterables:
|
self.plugins[plugin.plugin_attrib] = plugin(child, self)
|
||||||
out.append('substanzas')
|
if self.subitem is not None:
|
||||||
return tuple(out)
|
for sub in self.subitem:
|
||||||
|
if child.tag == "{%s}%s" % (sub.namespace, sub.name):
|
||||||
|
self.iterables.append(sub(child, self))
|
||||||
|
break
|
||||||
|
|
||||||
def match(self, matchstring):
|
def setup(self, xml=None):
|
||||||
if isinstance(matchstring, str):
|
"""
|
||||||
nodes = matchstring.split('/')
|
Initialize the stanza's XML contents.
|
||||||
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
|
Will return True if XML was generated according to the stanza's
|
||||||
return self.xml.find(xpath)
|
definition.
|
||||||
|
|
||||||
def findall(self, xpath):
|
Arguments:
|
||||||
return self.xml.findall(xpath)
|
xml -- Optional XML object to use for the stanza's content
|
||||||
|
instead of generating XML.
|
||||||
|
"""
|
||||||
|
if self.xml is None:
|
||||||
|
self.xml = xml
|
||||||
|
|
||||||
def setup(self, xml=None):
|
if self.xml is None:
|
||||||
if self.xml is None:
|
# Generate XML from the stanza definition
|
||||||
self.xml = xml
|
for ename in self.name.split('/'):
|
||||||
if self.xml is None:
|
new = ET.Element("{%s}%s" % (self.namespace, ename))
|
||||||
for ename in self.name.split('/'):
|
if self.xml is None:
|
||||||
new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace})
|
self.xml = new
|
||||||
if self.xml is None:
|
else:
|
||||||
self.xml = new
|
last_xml.append(new)
|
||||||
else:
|
last_xml = new
|
||||||
self.xml.append(new)
|
if self.parent is not None:
|
||||||
if self.parent is not None:
|
self.parent().xml.append(self.xml)
|
||||||
self.parent().xml.append(self.xml)
|
|
||||||
return True #had to generate XML
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def enable(self, attrib):
|
# We had to generate XML
|
||||||
self.initPlugin(attrib)
|
return True
|
||||||
return self
|
else:
|
||||||
|
# We did not generate XML
|
||||||
|
return False
|
||||||
|
|
||||||
def initPlugin(self, attrib):
|
def enable(self, attrib):
|
||||||
if attrib not in self.plugins:
|
"""
|
||||||
self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self)
|
Enable and initialize a stanza plugin.
|
||||||
|
|
||||||
def __getitem__(self, attrib):
|
Alias for initPlugin.
|
||||||
if attrib == 'substanzas':
|
|
||||||
return self.iterables
|
|
||||||
elif attrib in self.interfaces:
|
|
||||||
if hasattr(self, "get%s" % attrib.title()):
|
|
||||||
return getattr(self, "get%s" % attrib.title())()
|
|
||||||
else:
|
|
||||||
if attrib in self.sub_interfaces:
|
|
||||||
return self._getSubText(attrib)
|
|
||||||
else:
|
|
||||||
return self._getAttr(attrib)
|
|
||||||
elif attrib in self.plugin_attrib_map:
|
|
||||||
if attrib not in self.plugins: self.initPlugin(attrib)
|
|
||||||
return self.plugins[attrib]
|
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def __setitem__(self, attrib, value):
|
Arguments:
|
||||||
if attrib in self.interfaces:
|
attrib -- The stanza interface for the plugin.
|
||||||
if value is not None:
|
"""
|
||||||
if hasattr(self, "set%s" % attrib.title()):
|
return self.initPlugin(attrib)
|
||||||
getattr(self, "set%s" % attrib.title())(value,)
|
|
||||||
else:
|
|
||||||
if attrib in self.sub_interfaces:
|
|
||||||
return self._setSubText(attrib, text=value)
|
|
||||||
else:
|
|
||||||
self._setAttr(attrib, value)
|
|
||||||
else:
|
|
||||||
self.__delitem__(attrib)
|
|
||||||
elif attrib in self.plugin_attrib_map:
|
|
||||||
if attrib not in self.plugins: self.initPlugin(attrib)
|
|
||||||
self.initPlugin(attrib)
|
|
||||||
self.plugins[attrib][attrib] = value
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __delitem__(self, attrib):
|
def initPlugin(self, attrib):
|
||||||
if attrib.lower() in self.interfaces:
|
"""
|
||||||
if hasattr(self, "del%s" % attrib.title()):
|
Enable and initialize a stanza plugin.
|
||||||
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):
|
Arguments:
|
||||||
if not isinstance(other, ElementBase):
|
attrib -- The stanza interface for the plugin.
|
||||||
return False
|
"""
|
||||||
values = self.getValues()
|
if attrib not in self.plugins:
|
||||||
for key in other:
|
plugin_class = self.plugin_attrib_map[attrib]
|
||||||
if key not in values or values[key] != other[key]:
|
self.plugins[attrib] = plugin_class(parent=self)
|
||||||
return False
|
return self
|
||||||
return True
|
|
||||||
|
|
||||||
def _setAttr(self, name, value):
|
def getStanzaValues(self):
|
||||||
if value is None or value == '':
|
"""
|
||||||
self.__delitem__(name)
|
Return a dictionary of the stanza's interface values.
|
||||||
else:
|
|
||||||
self.xml.attrib[name] = value
|
|
||||||
|
|
||||||
def _delAttr(self, name):
|
Stanza plugin values are included as nested dictionaries.
|
||||||
if name in self.xml.attrib:
|
"""
|
||||||
del self.xml.attrib[name]
|
values = {}
|
||||||
|
for interface in self.interfaces:
|
||||||
|
values[interface] = self[interface]
|
||||||
|
for plugin, stanza in self.plugins.items():
|
||||||
|
values[plugin] = stanza.getStanzaValues()
|
||||||
|
if self.iterables:
|
||||||
|
iterables = []
|
||||||
|
for stanza in self.iterables:
|
||||||
|
iterables.append(stanza.getStanzaValues())
|
||||||
|
iterables[-1].update({
|
||||||
|
'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)
|
||||||
|
})
|
||||||
|
values['substanzas'] = iterables
|
||||||
|
return values
|
||||||
|
|
||||||
def _getAttr(self, name):
|
def setStanzaValues(self, values):
|
||||||
return self.xml.attrib.get(name, '')
|
"""
|
||||||
|
Set multiple stanza interface values using a dictionary.
|
||||||
|
|
||||||
def _getSubText(self, name):
|
Stanza plugin values may be set using nested dictionaries.
|
||||||
stanza = self.xml.find("{%s}%s" % (self.namespace, name))
|
|
||||||
if stanza is None or stanza.text is None:
|
|
||||||
return ''
|
|
||||||
else:
|
|
||||||
return stanza.text
|
|
||||||
|
|
||||||
def _setSubText(self, name, attrib={}, text=None):
|
Arguments:
|
||||||
if text is None or text == '':
|
values -- A dictionary mapping stanza interface with values.
|
||||||
return self.__delitem__(name)
|
Plugin interfaces may accept a nested dictionary that
|
||||||
stanza = self.xml.find("{%s}%s" % (self.namespace, name))
|
will be used recursively.
|
||||||
if stanza is None:
|
"""
|
||||||
#self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib))
|
for interface, value in values.items():
|
||||||
stanza = ET.Element("{%s}%s" % (self.namespace, name))
|
if interface == 'substanzas':
|
||||||
self.xml.append(stanza)
|
for subdict in value:
|
||||||
stanza.text = text
|
if '__childtag__' in subdict:
|
||||||
return stanza
|
for subclass in self.subitem:
|
||||||
|
child_tag = "{%s}%s" % (subclass.namespace,
|
||||||
|
subclass.name)
|
||||||
|
if subdict['__childtag__'] == child_tag:
|
||||||
|
sub = subclass(parent=self)
|
||||||
|
sub.setStanzaValues(subdict)
|
||||||
|
self.iterables.append(sub)
|
||||||
|
break
|
||||||
|
elif interface in self.interfaces:
|
||||||
|
self[interface] = value
|
||||||
|
elif interface in self.plugin_attrib_map:
|
||||||
|
if interface not in self.plugins:
|
||||||
|
self.initPlugin(interface)
|
||||||
|
self.plugins[interface].setStanzaValues(value)
|
||||||
|
return self
|
||||||
|
|
||||||
def _delSub(self, name):
|
def __getitem__(self, attrib):
|
||||||
for child in self.xml.getchildren():
|
"""
|
||||||
if child.tag == "{%s}%s" % (self.namespace, name):
|
Return the value of a stanza interface using dictionary-like syntax.
|
||||||
self.xml.remove(child)
|
|
||||||
|
|
||||||
def getValues(self):
|
Example:
|
||||||
out = {}
|
>>> msg['body']
|
||||||
for interface in self.interfaces:
|
'Message contents'
|
||||||
out[interface] = self[interface]
|
|
||||||
for pluginkey in self.plugins:
|
|
||||||
out[pluginkey] = self.plugins[pluginkey].getValues()
|
|
||||||
if self.iterables:
|
|
||||||
iterables = []
|
|
||||||
for stanza in self.iterables:
|
|
||||||
iterables.append(stanza.getValues())
|
|
||||||
iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)})
|
|
||||||
out['substanzas'] = iterables
|
|
||||||
return out
|
|
||||||
|
|
||||||
def setValues(self, attrib):
|
Stanza interfaces are typically mapped directly to the underlying XML
|
||||||
for interface in attrib:
|
object, but can be overridden by the presence of a getAttrib method
|
||||||
if interface == 'substanzas':
|
(or getFoo where the interface is named foo, etc).
|
||||||
for subdict in attrib['substanzas']:
|
|
||||||
if '__childtag__' in subdict:
|
|
||||||
for subclass in self.subitem:
|
|
||||||
if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name):
|
|
||||||
sub = subclass(parent=self)
|
|
||||||
sub.setValues(subdict)
|
|
||||||
self.iterables.append(sub)
|
|
||||||
break
|
|
||||||
elif interface in self.interfaces:
|
|
||||||
self[interface] = attrib[interface]
|
|
||||||
elif interface in self.plugin_attrib_map and interface not in self.plugins:
|
|
||||||
self.initPlugin(interface)
|
|
||||||
if interface in self.plugins:
|
|
||||||
self.plugins[interface].setValues(attrib[interface])
|
|
||||||
return self
|
|
||||||
|
|
||||||
def appendxml(self, xml):
|
The search order for interface value retrieval for an interface
|
||||||
self.xml.append(xml)
|
named 'foo' is:
|
||||||
return self
|
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.
|
||||||
|
|
||||||
#def __del__(self): #prevents garbage collection of reference cycle
|
Arguments:
|
||||||
# if self.parent is not None:
|
attrib -- The name of the requested stanza interface.
|
||||||
# self.parent.xml.remove(self.xml)
|
"""
|
||||||
|
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):
|
def setType(self, value):
|
||||||
if value in self.types:
|
if value in self.types:
|
||||||
self.xml.attrib['type'] = value
|
self.xml.attrib['type'] = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def getPayload(self):
|
def getPayload(self):
|
||||||
return self.xml.getchildren()
|
return self.xml.getchildren()
|
||||||
|
|
||||||
def setPayload(self, value):
|
def setPayload(self, value):
|
||||||
self.xml.append(value)
|
self.xml.append(value)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def delPayload(self):
|
def delPayload(self):
|
||||||
self.clear()
|
self.clear()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def clear(self):
|
def clear(self):
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml.getchildren():
|
||||||
self.xml.remove(child)
|
self.xml.remove(child)
|
||||||
for plugin in list(self.plugins.keys()):
|
for plugin in list(self.plugins.keys()):
|
||||||
del self.plugins[plugin]
|
del self.plugins[plugin]
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def reply(self):
|
def reply(self):
|
||||||
self['from'], self['to'] = self['to'], self['from']
|
# if it's a component, use from
|
||||||
self.clear()
|
if self.stream and hasattr(self.stream, "is_component") and self.stream.is_component:
|
||||||
return self
|
self['from'], self['to'] = self['to'], self['from']
|
||||||
|
else:
|
||||||
|
self['to'] = self['from']
|
||||||
|
del self['from']
|
||||||
|
self.clear()
|
||||||
|
return self
|
||||||
|
|
||||||
def error(self):
|
def error(self):
|
||||||
self['type'] = 'error'
|
self['type'] = 'error'
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def getTo(self):
|
def getTo(self):
|
||||||
return JID(self._getAttr('to'))
|
return JID(self._getAttr('to'))
|
||||||
|
|
||||||
def setTo(self, value):
|
def setTo(self, value):
|
||||||
return self._setAttr('to', str(value))
|
return self._setAttr('to', str(value))
|
||||||
|
|
||||||
def getFrom(self):
|
def getFrom(self):
|
||||||
return JID(self._getAttr('from'))
|
return JID(self._getAttr('from'))
|
||||||
|
|
||||||
def setFrom(self, value):
|
def setFrom(self, value):
|
||||||
return self._setAttr('from', str(value))
|
return self._setAttr('from', str(value))
|
||||||
|
|
||||||
def unhandled(self):
|
def unhandled(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def exception(self, e):
|
def exception(self, e):
|
||||||
logging.error(traceback.format_tb(e))
|
logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name))
|
||||||
|
|
||||||
def send(self):
|
def send(self):
|
||||||
self.stream.sendRaw(self.__str__())
|
self.stream.sendRaw(self.__str__())
|
||||||
|
|
||||||
|
def __copy__(self):
|
||||||
|
return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return tostring(self.xml, xmlns='', stanza_ns=self.namespace, stream=self.stream)
|
||||||
|
|
|
@ -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
|
||||||
|
|
139
sleekxmpp/xmlstream/statemanager.py
Normal file
139
sleekxmpp/xmlstream/statemanager.py
Normal 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)))
|
|
@ -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] = '&'
|
else:
|
||||||
elif c == '<':
|
from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape
|
||||||
text[cc] = '<'
|
|
||||||
elif c == '>':
|
__all__ = ['tostring', 'xml_escape']
|
||||||
text[cc] = '>'
|
|
||||||
elif c == "'":
|
|
||||||
text[cc] = '''
|
|
||||||
else:
|
|
||||||
text[cc] = '"'
|
|
||||||
cc += 1
|
|
||||||
return ''.join(text)
|
|
||||||
|
|
95
sleekxmpp/xmlstream/tostring/tostring.py
Normal file
95
sleekxmpp/xmlstream/tostring/tostring.py
Normal 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 = {'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
"'": ''',
|
||||||
|
'"': '"'}
|
||||||
|
for i, c in enumerate(text):
|
||||||
|
text[i] = escapes.get(c, c)
|
||||||
|
return ''.join(text)
|
104
sleekxmpp/xmlstream/tostring/tostring26.py
Normal file
104
sleekxmpp/xmlstream/tostring/tostring26.py
Normal 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'&',
|
||||||
|
u'<': u'<',
|
||||||
|
u'>': u'>',
|
||||||
|
u"'": u''',
|
||||||
|
u'"': u'"'}
|
||||||
|
for i, c in enumerate(text):
|
||||||
|
text[i] = escapes.get(c, c)
|
||||||
|
return u''.join(text)
|
|
@ -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'&'
|
|
||||||
elif c == u'<':
|
|
||||||
text[cc] = u'<'
|
|
||||||
elif c == u'>':
|
|
||||||
text[cc] = u'>'
|
|
||||||
elif c == u"'":
|
|
||||||
text[cc] = u'''
|
|
||||||
else:
|
|
||||||
text[cc] = u'"'
|
|
||||||
cc += 1
|
|
||||||
return ''.join(text)
|
|
|
@ -3,7 +3,7 @@
|
||||||
Copyright (C) 2010 Nathanael C. Fritz
|
Copyright (C) 2010 Nathanael C. Fritz
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
See the file license.txt for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import with_statement, unicode_literals
|
from __future__ import with_statement, unicode_literals
|
||||||
|
@ -19,11 +19,13 @@ import logging
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
|
||||||
import types
|
import types
|
||||||
|
import copy
|
||||||
import xml.sax.saxutils
|
import xml.sax.saxutils
|
||||||
from . import scheduler
|
from . import scheduler
|
||||||
|
from sleekxmpp.xmlstream.tostring import tostring
|
||||||
|
|
||||||
|
RESPONSE_TIMEOUT = 10
|
||||||
HANDLER_THREADS = 1
|
HANDLER_THREADS = 1
|
||||||
|
|
||||||
ssl_support = True
|
ssl_support = True
|
||||||
|
@ -71,6 +73,7 @@ class XMLStream(object):
|
||||||
self.use_ssl = False
|
self.use_ssl = False
|
||||||
self.use_tls = False
|
self.use_tls = False
|
||||||
|
|
||||||
|
self.default_ns = ''
|
||||||
self.stream_header = "<stream>"
|
self.stream_header = "<stream>"
|
||||||
self.stream_footer = "</stream>"
|
self.stream_footer = "</stream>"
|
||||||
|
|
||||||
|
@ -194,14 +197,14 @@ class XMLStream(object):
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.state.set('processing', False)
|
self.state.set('processing', False)
|
||||||
traceback.print_exc()
|
logging.exception('Socket Error')
|
||||||
self.disconnect(reconnect=True)
|
self.disconnect(reconnect=True)
|
||||||
except:
|
except:
|
||||||
if not self.state.reconnect:
|
if not self.state.reconnect:
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
self.state.set('processing', False)
|
self.state.set('processing', False)
|
||||||
traceback.print_exc()
|
logging.exception('Connection error. Reconnecting.')
|
||||||
self.disconnect(reconnect=True)
|
self.disconnect(reconnect=True)
|
||||||
if self.state['reconnect']:
|
if self.state['reconnect']:
|
||||||
self.reconnect()
|
self.reconnect()
|
||||||
|
@ -257,8 +260,7 @@ class XMLStream(object):
|
||||||
logging.warning("Failed to send %s" % data)
|
logging.warning("Failed to send %s" % data)
|
||||||
self.state.set('connected', False)
|
self.state.set('connected', False)
|
||||||
if self.state.reconnect:
|
if self.state.reconnect:
|
||||||
logging.error("Disconnected. Socket Error.")
|
logging.exception("Disconnected. Socket Error.")
|
||||||
traceback.print_exc()
|
|
||||||
self.disconnect(reconnect=True)
|
self.disconnect(reconnect=True)
|
||||||
|
|
||||||
def sendRaw(self, data):
|
def sendRaw(self, data):
|
||||||
|
@ -303,21 +305,20 @@ class XMLStream(object):
|
||||||
def __spawnEvent(self, xmlobj):
|
def __spawnEvent(self, xmlobj):
|
||||||
"watching xmlOut and processes handlers"
|
"watching xmlOut and processes handlers"
|
||||||
#convert XML into Stanza
|
#convert XML into Stanza
|
||||||
logging.debug("RECV: %s" % cElementTree.tostring(xmlobj))
|
logging.debug("RECV: %s" % tostring(xmlobj, xmlns=self.default_ns, stream=self))
|
||||||
xmlobj = self.incoming_filter(xmlobj)
|
xmlobj = self.incoming_filter(xmlobj)
|
||||||
stanza = None
|
stanza_type = StanzaBase
|
||||||
for stanza_class in self.__root_stanza:
|
for stanza_class in self.__root_stanza:
|
||||||
if xmlobj.tag == "{%s}%s" % (self.default_ns, stanza_class.name):
|
if xmlobj.tag == "{%s}%s" % (self.default_ns, stanza_class.name):
|
||||||
#if self.__root_stanza[stanza_class].match(xmlobj):
|
stanza_type = stanza_class
|
||||||
stanza = stanza_class(self, xmlobj)
|
|
||||||
break
|
break
|
||||||
if stanza is None:
|
|
||||||
stanza = StanzaBase(self, xmlobj)
|
|
||||||
unhandled = True
|
unhandled = True
|
||||||
|
stanza = stanza_type(self, xmlobj)
|
||||||
for handler in self.__handlers:
|
for handler in self.__handlers:
|
||||||
if handler.match(stanza):
|
if handler.match(stanza):
|
||||||
handler.prerun(stanza)
|
stanza_copy = stanza_type(self, copy.deepcopy(xmlobj))
|
||||||
self.eventqueue.put(('stanza', handler, stanza))
|
handler.prerun(stanza_copy)
|
||||||
|
self.eventqueue.put(('stanza', handler, stanza_copy))
|
||||||
if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler))
|
if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler))
|
||||||
unhandled = False
|
unhandled = False
|
||||||
if unhandled:
|
if unhandled:
|
||||||
|
@ -344,14 +345,14 @@ class XMLStream(object):
|
||||||
try:
|
try:
|
||||||
handler.run(args[0])
|
handler.run(args[0])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
logging.exception('Error processing event handler: %s' % handler.name)
|
||||||
args[0].exception(e)
|
args[0].exception(e)
|
||||||
elif etype == 'schedule':
|
elif etype == 'schedule':
|
||||||
try:
|
try:
|
||||||
logging.debug(args)
|
logging.debug(args)
|
||||||
handler(*args[0])
|
handler(*args[0])
|
||||||
except:
|
except:
|
||||||
logging.error(traceback.format_exc())
|
logging.exception('Error processing scheduled task')
|
||||||
elif etype == 'quit':
|
elif etype == 'quit':
|
||||||
logging.debug("Quitting eventRunner thread")
|
logging.debug("Quitting eventRunner thread")
|
||||||
return False
|
return False
|
||||||
|
@ -389,60 +390,6 @@ class XMLStream(object):
|
||||||
def removeStanzaExtension(self, stanza_class, stanza_extension):
|
def removeStanzaExtension(self, stanza_class, stanza_extension):
|
||||||
stanza_extension[stanza_class].pop(stanza_extension)
|
stanza_extension[stanza_class].pop(stanza_extension)
|
||||||
|
|
||||||
def tostring(self, xml, xmlns='', stringbuffer=''):
|
|
||||||
newoutput = [stringbuffer]
|
|
||||||
#TODO respect ET mapped namespaces
|
|
||||||
itag = xml.tag.split('}', 1)[-1]
|
|
||||||
if '}' in xml.tag:
|
|
||||||
ixmlns = xml.tag.split('}', 1)[0][1:]
|
|
||||||
else:
|
|
||||||
ixmlns = ''
|
|
||||||
nsbuffer = ''
|
|
||||||
if xmlns != ixmlns and ixmlns != '':
|
|
||||||
if ixmlns in self.namespace_map:
|
|
||||||
if self.namespace_map[ixmlns] != '':
|
|
||||||
itag = "%s:%s" % (self.namespace_map[ixmlns], itag)
|
|
||||||
else:
|
|
||||||
nsbuffer = """ xmlns="%s\"""" % ixmlns
|
|
||||||
newoutput.append("<%s" % itag)
|
|
||||||
newoutput.append(nsbuffer)
|
|
||||||
for attrib in xml.attrib:
|
|
||||||
newoutput.append(""" %s="%s\"""" % (attrib, self.xmlesc(xml.attrib[attrib])))
|
|
||||||
if len(xml) or xml.text or xml.tail:
|
|
||||||
newoutput.append(">")
|
|
||||||
if xml.text:
|
|
||||||
newoutput.append(self.xmlesc(xml.text))
|
|
||||||
if len(xml):
|
|
||||||
for child in xml.getchildren():
|
|
||||||
newoutput.append(self.tostring(child, ixmlns))
|
|
||||||
newoutput.append("</%s>" % (itag, ))
|
|
||||||
if xml.tail:
|
|
||||||
newoutput.append(self.xmlesc(xml.tail))
|
|
||||||
elif xml.text:
|
|
||||||
newoutput.append(">%s</%s>" % (self.xmlesc(xml.text), itag))
|
|
||||||
else:
|
|
||||||
newoutput.append(" />")
|
|
||||||
return ''.join(newoutput)
|
|
||||||
|
|
||||||
def xmlesc(self, text):
|
|
||||||
text = list(text)
|
|
||||||
cc = 0
|
|
||||||
matches = ('&', '<', '"', '>', "'")
|
|
||||||
for c in text:
|
|
||||||
if c in matches:
|
|
||||||
if c == '&':
|
|
||||||
text[cc] = '&'
|
|
||||||
elif c == '<':
|
|
||||||
text[cc] = '<'
|
|
||||||
elif c == '>':
|
|
||||||
text[cc] = '>'
|
|
||||||
elif c == "'":
|
|
||||||
text[cc] = '''
|
|
||||||
elif self.escape_quotes:
|
|
||||||
text[cc] = '"'
|
|
||||||
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
|
||||||
|
|
|
@ -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
519
tests/sleektest.py
Normal 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
111
tests/test_addresses.py
Normal 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
44
tests/test_chatstates.py
Normal 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)
|
|
@ -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
193
tests/test_elementbase.py
Normal 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)
|
56
tests/test_errorstanzas.py
Normal file
56
tests/test_errorstanzas.py
Normal 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)
|
|
@ -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
115
tests/test_forms.py
Normal 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
88
tests/test_gmail.py
Normal 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
90
tests/test_iqstanzas.py
Normal 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
26
tests/test_jid.py
Normal 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)
|
|
@ -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):
|
def setUp(self):
|
||||||
"Regression groupchat reply should be to barejid"
|
registerStanzaPlugin(Message, HTMLIM)
|
||||||
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 testGroupchatReplyRegression(self):
|
||||||
"Test attrib property returning self"
|
"Regression groupchat reply should be to barejid"
|
||||||
msg = self.m.Message()
|
msg = self.Message()
|
||||||
msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
|
msg['to'] = 'me@myserver.tld'
|
||||||
self.failUnless(str(msg['to']) == 'usr@server.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 testHTMLPlugin(self):
|
def testAttribProperty(self):
|
||||||
"Test message/html/html stanza"
|
"Test attrib property returning self"
|
||||||
msgtxt = """<message to="fritzy@netflint.net/sleekxmpp" type="chat"><body>this is the plaintext message</body><html xmlns="http://jabber.org/protocol/xhtml-im"><body xmlns="http://www.w3.org/1999/xhtml"><p>This is the htmlim message</p></body></html></message>"""
|
msg = self.Message()
|
||||||
msg = self.m.Message()
|
msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
|
||||||
msg['to'] = "fritzy@netflint.net/sleekxmpp"
|
self.failUnless(str(msg['to']) == 'usr@server.tld')
|
||||||
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 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)
|
||||||
|
|
|
@ -1,31 +1,67 @@
|
||||||
import unittest
|
import sleekxmpp
|
||||||
|
from . sleektest import *
|
||||||
class testpresencestanzas(unittest.TestCase):
|
from sleekxmpp.stanza.presence import Presence
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
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)
|
class TestPresenceStanzas(SleekTest):
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
|
@ -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>
|
||||||
|
</iq>""")
|
||||||
|
|
||||||
def testSubscriptions(self):
|
def testSubscriptions(self):
|
||||||
"Testing iq/pubsub/subscriptions/subscription stanzas"
|
"Testing iq/pubsub/subscriptions/subscription stanzas"
|
||||||
iq = self.ps.Iq()
|
iq = self.Iq()
|
||||||
sub1 = self.ps.Subscription()
|
sub1 = pubsub.Subscription()
|
||||||
sub1['node'] = 'testnode'
|
sub1['node'] = 'testnode'
|
||||||
sub1['jid'] = 'steve@myserver.tld/someresource'
|
sub1['jid'] = 'steve@myserver.tld/someresource'
|
||||||
sub2 = self.ps.Subscription()
|
sub2 = pubsub.Subscription()
|
||||||
sub2['node'] = 'testnode2'
|
sub2['node'] = 'testnode2'
|
||||||
sub2['jid'] = 'boogers@bork.top/bill'
|
sub2['jid'] = 'boogers@bork.top/bill'
|
||||||
sub2['subscription'] = 'subscribed'
|
sub2['subscription'] = 'subscribed'
|
||||||
iq['pubsub']['subscriptions'].append(sub1)
|
iq['pubsub']['subscriptions'].append(sub1)
|
||||||
iq['pubsub']['subscriptions'].append(sub2)
|
iq['pubsub']['subscriptions'].append(sub2)
|
||||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscriptions><subscription node="testnode" jid="steve@myserver.tld/someresource" /><subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" /></subscriptions></pubsub></iq>"""
|
self.checkIq(iq, """
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
<iq id="0">
|
||||||
iq3 = self.ps.Iq()
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
values = iq2.getValues()
|
<subscriptions>
|
||||||
iq3.setValues(values)
|
<subscription node="testnode" jid="steve@myserver.tld/someresource" />
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
<subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" />
|
||||||
|
</subscriptions>
|
||||||
|
</pubsub>
|
||||||
|
</iq>""")
|
||||||
|
|
||||||
def testOptionalSettings(self):
|
def testOptionalSettings(self):
|
||||||
"Testing iq/pubsub/subscription/subscribe-options stanzas"
|
"Testing iq/pubsub/subscription/subscribe-options stanzas"
|
||||||
iq = self.ps.Iq()
|
iq = self.Iq()
|
||||||
iq['pubsub']['subscription']['suboptions']['required'] = True
|
iq['pubsub']['subscription']['suboptions']['required'] = True
|
||||||
iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas'
|
iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas'
|
||||||
iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp"
|
iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp"
|
||||||
iq['pubsub']['subscription']['subscription'] = 'unconfigured'
|
iq['pubsub']['subscription']['subscription'] = 'unconfigured'
|
||||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured"><subscribe-options><required /></subscribe-options></subscription></pubsub></iq>"""
|
self.checkIq(iq, """
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
<iq id="0">
|
||||||
iq3 = self.ps.Iq()
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
values = iq2.getValues()
|
<subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured">
|
||||||
iq3.setValues(values)
|
<subscribe-options>
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
<required />
|
||||||
|
</subscribe-options>
|
||||||
|
</subscription>
|
||||||
|
</pubsub>
|
||||||
|
</iq>""")
|
||||||
|
|
||||||
def testItems(self):
|
def testItems(self):
|
||||||
"Testing iq/pubsub/items stanzas"
|
"Testing iq/pubsub/items stanzas"
|
||||||
iq = self.ps.Iq()
|
iq = self.Iq()
|
||||||
iq['pubsub']['items']
|
iq['pubsub']['items']
|
||||||
payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""")
|
payload = ET.fromstring("""
|
||||||
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""")
|
<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'>
|
||||||
item = self.ps.Item()
|
<child1 />
|
||||||
item['id'] = 'asdf'
|
<child2 normandy='cheese' foo='bar' />
|
||||||
item['payload'] = payload
|
</thinger>""")
|
||||||
item2 = self.ps.Item()
|
payload2 = ET.fromstring("""
|
||||||
item2['id'] = 'asdf2'
|
<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'>
|
||||||
item2['payload'] = payload2
|
<child12 />
|
||||||
iq['pubsub']['items'].append(item)
|
<child22 normandy='cheese2' foo='bar2' />
|
||||||
iq['pubsub']['items'].append(item2)
|
</thinger2>""")
|
||||||
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>"""
|
item = pubsub.Item()
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
item['id'] = 'asdf'
|
||||||
iq3 = self.ps.Iq()
|
item['payload'] = payload
|
||||||
values = iq2.getValues()
|
item2 = pubsub.Item()
|
||||||
iq3.setValues(values)
|
item2['id'] = 'asdf2'
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
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 testCreate(self):
|
def testCreate(self):
|
||||||
"Testing iq/pubsub/create&configure stanzas"
|
"Testing iq/pubsub/create&configure stanzas"
|
||||||
from sleekxmpp.plugins import xep_0004
|
iq = self.Iq()
|
||||||
iq = self.ps.Iq()
|
iq['pubsub']['create']['node'] = 'mynode'
|
||||||
iq['pubsub']['create']['node'] = 'mynode'
|
iq['pubsub']['configure']['form'].addField('pubsub#title',
|
||||||
form = xep_0004.Form()
|
ftype='text-single',
|
||||||
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
|
value='This thing is awesome')
|
||||||
iq['pubsub']['configure']['config'] = form
|
self.checkIq(iq, """
|
||||||
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>"""
|
<iq id="0">
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
iq3 = self.ps.Iq()
|
<create node="mynode" />
|
||||||
values = iq2.getValues()
|
<configure>
|
||||||
iq3.setValues(values)
|
<x xmlns="jabber:x:data" type="form">
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
<field var="pubsub#title" type="text-single">
|
||||||
|
<value>This thing is awesome</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
</configure>
|
||||||
|
</pubsub>
|
||||||
|
</iq>""")
|
||||||
|
|
||||||
def testState(self):
|
def testState(self):
|
||||||
"Testing iq/psstate stanzas"
|
"Testing iq/psstate stanzas"
|
||||||
from sleekxmpp.plugins import xep_0004
|
iq = self.Iq()
|
||||||
iq = self.ps.Iq()
|
iq['psstate']['node']= 'mynode'
|
||||||
iq['psstate']['node']= 'mynode'
|
iq['psstate']['item']= 'myitem'
|
||||||
iq['psstate']['item']= 'myitem'
|
pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
|
||||||
pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
|
iq['psstate']['payload'] = pl
|
||||||
iq['psstate']['payload'] = pl
|
self.checkIq(iq, """
|
||||||
xmlstring = """<iq id="0"><state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem"><claimed xmlns="http://andyet.net/protocol/pubsubqueue" /></state></iq>"""
|
<iq id="0">
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
<state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem">
|
||||||
iq3 = self.ps.Iq()
|
<claimed xmlns="http://andyet.net/protocol/pubsubqueue" />
|
||||||
values = iq2.getValues()
|
</state>
|
||||||
iq3.setValues(values)
|
</iq>""")
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
|
||||||
|
|
||||||
def testDefault(self):
|
def testDefault(self):
|
||||||
"Testing iq/pubsub_owner/default stanzas"
|
"Testing iq/pubsub_owner/default stanzas"
|
||||||
from sleekxmpp.plugins import xep_0004
|
iq = self.Iq()
|
||||||
iq = self.ps.Iq()
|
iq['pubsub_owner']['default']
|
||||||
iq['pubsub_owner']['default']
|
iq['pubsub_owner']['default']['node'] = 'mynode'
|
||||||
iq['pubsub_owner']['default']['node'] = 'mynode'
|
iq['pubsub_owner']['default']['type'] = 'leaf'
|
||||||
iq['pubsub_owner']['default']['type'] = 'leaf'
|
iq['pubsub_owner']['default']['form'].addField('pubsub#title',
|
||||||
form = xep_0004.Form()
|
ftype='text-single',
|
||||||
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
|
value='This thing is awesome')
|
||||||
iq['pubsub_owner']['default']['config'] = form
|
self.checkIq(iq, """
|
||||||
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>"""
|
<iq id="0">
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||||
iq3 = self.ps.Iq()
|
<default node="mynode" type="leaf">
|
||||||
values = iq2.getValues()
|
<x xmlns="jabber:x:data" type="form">
|
||||||
iq3.setValues(values)
|
<field var="pubsub#title" type="text-single">
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
<value>This thing is awesome</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
</default>
|
||||||
|
</pubsub>
|
||||||
|
</iq>""", use_values=False)
|
||||||
|
|
||||||
def testSubscribe(self):
|
def testSubscribe(self):
|
||||||
"Testing iq/pubsub/subscribe stanzas"
|
"testing iq/pubsub/subscribe stanzas"
|
||||||
from sleekxmpp.plugins import xep_0004
|
iq = self.Iq()
|
||||||
iq = self.ps.Iq()
|
iq['pubsub']['subscribe']['options']
|
||||||
iq['pubsub']['subscribe']['options']
|
iq['pubsub']['subscribe']['node'] = 'cheese'
|
||||||
iq['pubsub']['subscribe']['node'] = 'cheese'
|
iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp'
|
||||||
iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp'
|
iq['pubsub']['subscribe']['options']['node'] = 'cheese'
|
||||||
iq['pubsub']['subscribe']['options']['node'] = 'cheese'
|
iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp'
|
||||||
iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp'
|
form = xep_0004.Form()
|
||||||
form = xep_0004.Form()
|
form.addField('pubsub#title', ftype='text-single', value='this thing is awesome')
|
||||||
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
|
iq['pubsub']['subscribe']['options']['options'] = form
|
||||||
iq['pubsub']['subscribe']['options']['options'] = form
|
self.checkIq(iq, """
|
||||||
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>"""
|
<iq id="0">
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
iq3 = self.ps.Iq()
|
<subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp">
|
||||||
values = iq2.getValues()
|
<options node="cheese" jid="fritzy@netflint.net/sleekxmpp">
|
||||||
iq3.setValues(values)
|
<x xmlns="jabber:x:data" type="form">
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
<field var="pubsub#title" type="text-single">
|
||||||
|
<value>this thing is awesome</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
</options>
|
||||||
|
</subscribe>
|
||||||
|
</pubsub>
|
||||||
|
</iq>""", use_values=False)
|
||||||
|
|
||||||
def testPublish(self):
|
def testPublish(self):
|
||||||
"Testing iq/pubsub/publish stanzas"
|
"Testing iq/pubsub/publish stanzas"
|
||||||
iq = self.ps.Iq()
|
iq = self.Iq()
|
||||||
iq['pubsub']['publish']['node'] = 'thingers'
|
iq['pubsub']['publish']['node'] = 'thingers'
|
||||||
payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""")
|
payload = ET.fromstring("""
|
||||||
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""")
|
<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'>
|
||||||
item = self.ps.Item()
|
<child1 />
|
||||||
item['id'] = 'asdf'
|
<child2 normandy='cheese' foo='bar' />
|
||||||
item['payload'] = payload
|
</thinger>""")
|
||||||
item2 = self.ps.Item()
|
payload2 = ET.fromstring("""
|
||||||
item2['id'] = 'asdf2'
|
<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'>
|
||||||
item2['payload'] = payload2
|
<child12 />
|
||||||
iq['pubsub']['publish'].append(item)
|
<child22 normandy='cheese2' foo='bar2' />
|
||||||
iq['pubsub']['publish'].append(item2)
|
</thinger2>""")
|
||||||
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>"""
|
item = pubsub.Item()
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
item['id'] = 'asdf'
|
||||||
iq3 = self.ps.Iq()
|
item['payload'] = payload
|
||||||
values = iq2.getValues()
|
item2 = pubsub.Item()
|
||||||
iq3.setValues(values)
|
item2['id'] = 'asdf2'
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
item2['payload'] = payload2
|
||||||
|
iq['pubsub']['publish'].append(item)
|
||||||
|
iq['pubsub']['publish'].append(item2)
|
||||||
|
|
||||||
def testDelete(self):
|
self.checkIq(iq, """
|
||||||
"Testing iq/pubsub_owner/delete stanzas"
|
<iq id="0">
|
||||||
iq = self.ps.Iq()
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
iq['pubsub_owner']['delete']['node'] = 'thingers'
|
<publish node="thingers">
|
||||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><delete node="thingers" /></pubsub></iq>"""
|
<item id="asdf">
|
||||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
<thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1">
|
||||||
iq3 = self.ps.Iq()
|
<child1 />
|
||||||
iq3.setValues(iq2.getValues())
|
<child2 foo="bar" normandy="cheese" />
|
||||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
</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 testCreateConfigGet(self):
|
def testDelete(self):
|
||||||
"""Testing getting config from full create"""
|
"Testing iq/pubsub_owner/delete stanzas"
|
||||||
xml = """<iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7"><pubsub xmlns="http://jabber.org/protocol/pubsub"><create node="testnode2" /><configure><x xmlns="jabber:x:data" type="submit"><field var="FORM_TYPE" type="hidden"><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var="pubsub#node_type" type="list-single" label="Select the node type"><value>leaf</value></field><field var="pubsub#title" type="text-single" label="A friendly name for the node" /><field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications"><value>1</value></field><field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications"><value>1</value></field><field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" /><field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" /><field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node"><value>1</value></field><field var="pubsub#notify_sub" type="boolean" label="Notify owners about new subscribers and unsubscribes" /><field var="pubsub#persist_items" type="boolean" label="Persist items in storage" /><field var="pubsub#max_items" type="text-single" label="Max # of items to persist"><value>10</value></field><field var="pubsub#subscribe" type="boolean" label="Whether to allow subscriptions"><value>1</value></field><field var="pubsub#access_model" type="list-single" label="Specify the subscriber model"><value>open</value></field><field var="pubsub#publish_model" type="list-single" label="Specify the publisher model"><value>publishers</value></field><field var="pubsub#send_last_published_item" type="list-single" label="Send last published item"><value>never</value></field><field var="pubsub#presence_based_delivery" type="boolean" label="Deliver notification only to available users" /></x></configure></pubsub></iq>"""
|
iq = self.Iq()
|
||||||
iq = self.ps.Iq(None, self.ps.ET.fromstring(xml))
|
iq['pubsub_owner']['delete']['node'] = 'thingers'
|
||||||
config = iq['pubsub']['configure']['config']
|
self.checkIq(iq, """
|
||||||
self.failUnless(config.getValues() != {})
|
<iq id="0">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||||
|
<delete node="thingers" />
|
||||||
|
</pubsub>
|
||||||
|
</iq>""")
|
||||||
|
|
||||||
def testItemEvent(self):
|
def testCreateConfigGet(self):
|
||||||
"""Testing message/pubsub_event/items/item"""
|
"""Testing getting config from full create"""
|
||||||
msg = self.ps.Message()
|
iq = self.Iq()
|
||||||
item = self.ps.EventItem()
|
iq['to'] = 'pubsub.asdf'
|
||||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
iq['from'] = 'fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7'
|
||||||
item['payload'] = pl
|
iq['type'] = 'set'
|
||||||
item['id'] = 'abc123'
|
iq['id'] = 'E'
|
||||||
msg['pubsub_event']['items'].append(item)
|
|
||||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
|
||||||
msg['type'] = 'normal'
|
|
||||||
xmlstring = """<message type="normal"><event xmlns="http://jabber.org/protocol/pubsub#event"><items node="cheese"><item id="abc123"><test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" /></item></items></event></message>"""
|
|
||||||
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 testItemsEvent(self):
|
pub = iq['pubsub']
|
||||||
"""Testing multiple message/pubsub_event/items/item"""
|
pub['create']['node'] = 'testnode2'
|
||||||
msg = self.ps.Message()
|
pub['configure']['form']['type'] = 'submit'
|
||||||
item = self.ps.EventItem()
|
pub['configure']['form'].setFields([
|
||||||
item2 = self.ps.EventItem()
|
('FORM_TYPE', {'type': 'hidden',
|
||||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
'value': 'http://jabber.org/protocol/pubsub#node_config'}),
|
||||||
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
|
('pubsub#node_type', {'type': 'list-single',
|
||||||
item2['payload'] = pl2
|
'label': 'Select the node type',
|
||||||
item['payload'] = pl
|
'value': 'leaf'}),
|
||||||
item['id'] = 'abc123'
|
('pubsub#title', {'type': 'text-single',
|
||||||
item2['id'] = '123abc'
|
'label': 'A friendly name for the node'}),
|
||||||
msg['pubsub_event']['items'].append(item)
|
('pubsub#deliver_notifications', {'type': 'boolean',
|
||||||
msg['pubsub_event']['items'].append(item2)
|
'label': 'Deliver event notifications',
|
||||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
'value': True}),
|
||||||
msg['type'] = 'normal'
|
('pubsub#deliver_payloads', {'type': 'boolean',
|
||||||
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>"""
|
'label': 'Deliver payloads with event notifications',
|
||||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
'value': True}),
|
||||||
msg3 = self.ps.Message()
|
('pubsub#notify_config', {'type': 'boolean',
|
||||||
msg3.setValues(msg2.getValues())
|
'label': 'Notify subscribers when the node configuration changes'}),
|
||||||
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
|
('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'}),
|
||||||
|
])
|
||||||
|
|
||||||
def testItemsEvent(self):
|
self.checkIq(iq, """
|
||||||
"""Testing message/pubsub_event/items/item & retract mix"""
|
<iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7">
|
||||||
msg = self.ps.Message()
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
item = self.ps.EventItem()
|
<create node="testnode2" />
|
||||||
item2 = self.ps.EventItem()
|
<configure>
|
||||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
<x xmlns="jabber:x:data" type="submit">
|
||||||
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
|
<field var="FORM_TYPE" type="hidden">
|
||||||
item2['payload'] = pl2
|
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||||
retract = self.ps.EventRetract()
|
</field>
|
||||||
retract['id'] = 'aabbcc'
|
<field var="pubsub#node_type" type="list-single" label="Select the node type">
|
||||||
item['payload'] = pl
|
<value>leaf</value>
|
||||||
item['id'] = 'abc123'
|
</field>
|
||||||
item2['id'] = '123abc'
|
<field var="pubsub#title" type="text-single" label="A friendly name for the node" />
|
||||||
msg['pubsub_event']['items'].append(item)
|
<field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications">
|
||||||
msg['pubsub_event']['items'].append(retract)
|
<value>1</value>
|
||||||
msg['pubsub_event']['items'].append(item2)
|
</field>
|
||||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
<field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications">
|
||||||
msg['type'] = 'normal'
|
<value>1</value>
|
||||||
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>"""
|
</field>
|
||||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
<field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" />
|
||||||
msg3 = self.ps.Message()
|
<field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" />
|
||||||
msg3.setValues(msg2.getValues())
|
<field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node">
|
||||||
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
|
<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 testCollectionAssociate(self):
|
def testItemEvent(self):
|
||||||
"""Testing message/pubsub_event/collection/associate"""
|
"""Testing message/pubsub_event/items/item"""
|
||||||
msg = self.ps.Message()
|
msg = self.Message()
|
||||||
msg['pubsub_event']['collection']['associate']['node'] = 'cheese'
|
item = pubsub.EventItem()
|
||||||
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
|
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||||
msg['type'] = 'headline'
|
item['payload'] = pl
|
||||||
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><associate node="cheese" /></collection></event></message>"""
|
item['id'] = 'abc123'
|
||||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
msg['pubsub_event']['items'].append(item)
|
||||||
msg3 = self.ps.Message()
|
msg['pubsub_event']['items']['node'] = 'cheese'
|
||||||
msg3.setValues(msg2.getValues())
|
msg['type'] = 'normal'
|
||||||
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
|
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 testCollectionDisassociate(self):
|
def testItemsEvent(self):
|
||||||
"""Testing message/pubsub_event/collection/disassociate"""
|
"""Testing multiple message/pubsub_event/items/item"""
|
||||||
msg = self.ps.Message()
|
msg = self.Message()
|
||||||
msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese'
|
item = pubsub.EventItem()
|
||||||
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
|
item2 = pubsub.EventItem()
|
||||||
msg['type'] = 'headline'
|
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||||
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><disassociate node="cheese" /></collection></event></message>"""
|
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
|
||||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
item2['payload'] = pl2
|
||||||
msg3 = self.ps.Message()
|
item['payload'] = pl
|
||||||
msg3.setValues(msg2.getValues())
|
item['id'] = 'abc123'
|
||||||
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
|
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 testEventConfiguration(self):
|
def testItemsEvent(self):
|
||||||
"""Testing message/pubsub_event/configuration/config"""
|
"""Testing message/pubsub_event/items/item & retract mix"""
|
||||||
msg = self.ps.Message()
|
msg = self.Message()
|
||||||
from sleekxmpp.plugins import xep_0004
|
item = pubsub.EventItem()
|
||||||
form = xep_0004.Form()
|
item2 = pubsub.EventItem()
|
||||||
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
|
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||||
msg['pubsub_event']['configuration']['node'] = 'cheese'
|
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
|
||||||
msg['pubsub_event']['configuration']['config'] = form
|
item2['payload'] = pl2
|
||||||
msg['type'] = 'headline'
|
retract = pubsub.EventRetract()
|
||||||
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>"""
|
retract['id'] = 'aabbcc'
|
||||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
item['payload'] = pl
|
||||||
msg3 = self.ps.Message()
|
item['id'] = 'abc123'
|
||||||
msg3.setValues(msg2.getValues())
|
item2['id'] = '123abc'
|
||||||
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
|
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 testEventPurge(self):
|
def testCollectionAssociate(self):
|
||||||
"""Testing message/pubsub_event/purge"""
|
"""Testing message/pubsub_event/collection/associate"""
|
||||||
msg = self.ps.Message()
|
msg = self.Message()
|
||||||
msg['pubsub_event']['purge']['node'] = 'pickles'
|
msg['pubsub_event']['collection']['associate']['node'] = 'cheese'
|
||||||
msg['type'] = 'headline'
|
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
|
||||||
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><purge node="pickles" /></event></message>"""
|
msg['type'] = 'headline'
|
||||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
self.checkMessage(msg, """
|
||||||
msg3 = self.ps.Message()
|
<message type="headline">
|
||||||
msg3.setValues(msg2.getValues())
|
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||||
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
|
<collection node="cheeseburger">
|
||||||
|
<associate node="cheese" />
|
||||||
|
</collection>
|
||||||
|
</event>
|
||||||
|
</message>""")
|
||||||
|
|
||||||
def testEventSubscription(self):
|
def testCollectionDisassociate(self):
|
||||||
"""Testing message/pubsub_event/subscription"""
|
"""Testing message/pubsub_event/collection/disassociate"""
|
||||||
msg = self.ps.Message()
|
msg = self.Message()
|
||||||
msg['pubsub_event']['subscription']['node'] = 'pickles'
|
msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese'
|
||||||
msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test'
|
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
|
||||||
msg['pubsub_event']['subscription']['subid'] = 'aabb1122'
|
msg['type'] = 'headline'
|
||||||
msg['pubsub_event']['subscription']['subscription'] = 'subscribed'
|
self.checkMessage(msg, """
|
||||||
msg['pubsub_event']['subscription']['expiry'] = 'presence'
|
<message type="headline">
|
||||||
msg['type'] = 'headline'
|
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||||
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>"""
|
<collection node="cheeseburger">
|
||||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
<disassociate node="cheese" />
|
||||||
msg3 = self.ps.Message()
|
</collection>
|
||||||
msg3.setValues(msg2.getValues())
|
</event>
|
||||||
self.failUnless(xmlcompare.comparemany([xmlstring, str(msg), str(msg2), str(msg3)]))
|
</message>""")
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(testpubsubstanzas)
|
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
84
tests/test_roster.py
Normal 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
34
tests/test_stream.py
Normal 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
104
tests/test_tostring.py
Normal 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 = """<foo bar="baz">'Hi"""
|
||||||
|
desired += """ & welcome!'</foo>"""
|
||||||
|
|
||||||
|
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)
|
|
@ -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
|
|
Loading…
Reference in a new issue