mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-24 03:00:15 +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:
|
||||
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
|
||||
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
|
||||
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:
|
||||
- Low number of dependencies.
|
||||
- Every XEP as a plugin.
|
||||
|
|
70
example.py
70
example.py
|
@ -1,3 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
# coding=utf8
|
||||
|
||||
import sleekxmpp
|
||||
|
@ -8,41 +9,46 @@ import time
|
|||
import sys
|
||||
|
||||
if sys.version_info < (3,0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
|
||||
|
||||
class Example(sleekxmpp.ClientXMPP):
|
||||
|
||||
def __init__(self, jid, password):
|
||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||
self.add_event_handler("session_start", self.start)
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
self.getRoster()
|
||||
self.sendPresence()
|
||||
|
||||
def __init__(self, jid, password):
|
||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||
self.add_event_handler("session_start", self.start)
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
self.getRoster()
|
||||
self.sendPresence()
|
||||
|
||||
def message(self, msg):
|
||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||
def message(self, msg):
|
||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||
|
||||
if __name__ == '__main__':
|
||||
#parse command line arguements
|
||||
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('-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("-c","--config", dest="configfile", default="config.xml", help="set config file to use")
|
||||
opts,args = optp.parse_args()
|
||||
|
||||
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
||||
xmpp = Example('user@gmail.com/sleekxmpp', 'password')
|
||||
xmpp.registerPlugin('xep_0030')
|
||||
xmpp.registerPlugin('xep_0004')
|
||||
xmpp.registerPlugin('xep_0060')
|
||||
xmpp.registerPlugin('xep_0199')
|
||||
if xmpp.connect(('talk.google.com', 5222)):
|
||||
xmpp.process(threaded=False)
|
||||
print("done")
|
||||
else:
|
||||
print("Unable to connect.")
|
||||
#parse command line arguements
|
||||
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('-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("-j","--jid", dest="jid", help="JID to use")
|
||||
optp.add_option("-p","--password", dest="password", help="password to use")
|
||||
opts,args = optp.parse_args()
|
||||
|
||||
logging.basicConfig(level=opts.loglevel, format='%(levelname)-8s %(message)s')
|
||||
xmpp = Example(opts.jid, opts.password)
|
||||
xmpp.registerPlugin('xep_0030')
|
||||
xmpp.registerPlugin('xep_0004')
|
||||
xmpp.registerPlugin('xep_0060')
|
||||
xmpp.registerPlugin('xep_0199')
|
||||
|
||||
# use this if you don't have pydns, and want to
|
||||
# talk to GoogleTalk (e.g.)
|
||||
# if xmpp.connect(('talk.google.com', 5222)):
|
||||
if xmpp.connect():
|
||||
xmpp.process(threaded=False)
|
||||
print("done")
|
||||
else:
|
||||
print("Unable to connect.")
|
||||
|
|
14
setup.py
14
setup.py
|
@ -16,13 +16,13 @@ import sys
|
|||
# min_version = '0.6c6'
|
||||
# else:
|
||||
# min_version = '0.6a9'
|
||||
#
|
||||
#
|
||||
# try:
|
||||
# use_setuptools(min_version=min_version)
|
||||
# except TypeError:
|
||||
# # locally installed ez_setup won't have min_version
|
||||
# use_setuptools()
|
||||
#
|
||||
#
|
||||
# from setuptools import setup, find_packages, Extension, Feature
|
||||
|
||||
VERSION = '0.2.3.1'
|
||||
|
@ -37,17 +37,13 @@ CLASSIFIERS = [ 'Intended Audience :: Developers',
|
|||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
]
|
||||
|
||||
packages = [ 'sleekxmpp',
|
||||
packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins',
|
||||
'sleekxmpp/stanza',
|
||||
'sleekxmpp/xmlstream',
|
||||
'sleekxmpp/xmlstream/matcher',
|
||||
'sleekxmpp/xmlstream/handler' ]
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
packages.append('sleekxmpp/xmlstream/tostring26')
|
||||
else:
|
||||
packages.append('sleekxmpp/xmlstream/tostring')
|
||||
'sleekxmpp/xmlstream/handler',
|
||||
'sleekxmpp/xmlstream/tostring']
|
||||
|
||||
setup(
|
||||
name = "sleekxmpp",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#!/usr/bin/python2.5
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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 . basexmpp import basexmpp
|
||||
|
@ -30,223 +30,232 @@ from . import plugins
|
|||
#from . import stanza
|
||||
srvsupport = True
|
||||
try:
|
||||
import dns.resolver
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
srvsupport = False
|
||||
srvsupport = False
|
||||
|
||||
|
||||
|
||||
#class PresenceStanzaType(object):
|
||||
#
|
||||
# def fromXML(self, xml):
|
||||
# self.ptype = xml.get('type')
|
||||
#
|
||||
# def fromXML(self, xml):
|
||||
# self.ptype = xml.get('type')
|
||||
|
||||
|
||||
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):
|
||||
global srvsupport
|
||||
XMLStream.__init__(self)
|
||||
self.default_ns = 'jabber:client'
|
||||
basexmpp.__init__(self)
|
||||
self.plugin_config = plugin_config
|
||||
self.escape_quotes = escape_quotes
|
||||
self.set_jid(jid)
|
||||
self.plugin_whitelist = plugin_whitelist
|
||||
self.auto_reconnect = True
|
||||
self.srvsupport = srvsupport
|
||||
self.password = password
|
||||
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_footer = "</stream:stream>"
|
||||
#self.map_namespace('http://etherx.jabber.org/streams', 'stream')
|
||||
#self.map_namespace('jabber:client', '')
|
||||
self.features = []
|
||||
#TODO: Use stream state here
|
||||
self.authenticated = False
|
||||
self.sessionstarted = False
|
||||
self.bound = False
|
||||
self.bindfail = False
|
||||
self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True))
|
||||
self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True))
|
||||
#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True))
|
||||
self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True)
|
||||
self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True)
|
||||
self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource)
|
||||
self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session)
|
||||
|
||||
#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
|
||||
#self.register_plugins()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.plugin:
|
||||
return self.plugin[key]
|
||||
else:
|
||||
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
||||
return False
|
||||
|
||||
def get(self, key, default):
|
||||
return self.plugin.get(key, default)
|
||||
def __init__(self, jid, password, ssl=False, plugin_config = {}, plugin_whitelist=[], escape_quotes=True):
|
||||
global srvsupport
|
||||
XMLStream.__init__(self)
|
||||
self.default_ns = 'jabber:client'
|
||||
basexmpp.__init__(self)
|
||||
self.plugin_config = plugin_config
|
||||
self.escape_quotes = escape_quotes
|
||||
self.set_jid(jid)
|
||||
self.plugin_whitelist = plugin_whitelist
|
||||
self.auto_reconnect = True
|
||||
self.srvsupport = srvsupport
|
||||
self.password = password
|
||||
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_footer = "</stream:stream>"
|
||||
#self.map_namespace('http://etherx.jabber.org/streams', 'stream')
|
||||
#self.map_namespace('jabber:client', '')
|
||||
self.features = []
|
||||
#TODO: Use stream state here
|
||||
self.authenticated = False
|
||||
self.sessionstarted = False
|
||||
self.bound = False
|
||||
self.bindfail = False
|
||||
self.is_component = False
|
||||
self.registerHandler(Callback('Stream Features', MatchXPath('{http://etherx.jabber.org/streams}features'), self._handleStreamFeatures, thread=True))
|
||||
self.registerHandler(Callback('Roster Update', MatchXPath('{%s}iq/{jabber:iq:roster}query' % self.default_ns), self._handleRoster, thread=True))
|
||||
#self.registerHandler(Callback('Roster Update', MatchXMLMask("<presence xmlns='%s' type='subscribe' />" % self.default_ns), self._handlePresenceSubscribe, thread=True))
|
||||
self.registerFeature("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_starttls, True)
|
||||
self.registerFeature("<mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_sasl_auth, True)
|
||||
self.registerFeature("<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind' />", self.handler_bind_resource)
|
||||
self.registerFeature("<session xmlns='urn:ietf:params:xml:ns:xmpp-session' />", self.handler_start_session)
|
||||
|
||||
#self.registerStanzaExtension('PresenceStanza', PresenceStanzaType)
|
||||
#self.register_plugins()
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.plugin:
|
||||
return self.plugin[key]
|
||||
else:
|
||||
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
||||
return False
|
||||
|
||||
def get(self, key, default):
|
||||
return self.plugin.get(key, default)
|
||||
|
||||
def connect(self, address=tuple()):
|
||||
"""Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses
|
||||
the JID server."""
|
||||
if not address or len(address) < 2:
|
||||
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.")
|
||||
else:
|
||||
logging.debug("Since no address is supplied, attempting SRV lookup.")
|
||||
try:
|
||||
answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV)
|
||||
except dns.resolver.NXDOMAIN:
|
||||
logging.debug("No appropriate SRV record found. Using JID server name.")
|
||||
else:
|
||||
# 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
|
||||
# suggestions are welcome
|
||||
addresses = {}
|
||||
intmax = 0
|
||||
priorities = []
|
||||
for answer in answers:
|
||||
intmax += answer.priority
|
||||
addresses[intmax] = (answer.target.to_text()[:-1], answer.port)
|
||||
priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort()
|
||||
picked = random.randint(0, intmax)
|
||||
for priority in priorities:
|
||||
if picked <= priority:
|
||||
address = addresses[priority]
|
||||
break
|
||||
if not address:
|
||||
# if all else fails take server from JID.
|
||||
address = (self.server, 5222)
|
||||
result = XMLStream.connect(self, address[0], address[1], use_tls=True)
|
||||
if result:
|
||||
self.event("connected")
|
||||
else:
|
||||
logging.warning("Failed to connect")
|
||||
self.event("disconnected")
|
||||
return result
|
||||
|
||||
# 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
|
||||
def reconnect(self):
|
||||
logging.info("Reconnecting")
|
||||
self.event("disconnected")
|
||||
XMLStream.reconnect(self)
|
||||
|
||||
def disconnect(self, init=True, close=False, reconnect=False):
|
||||
self.event("disconnected")
|
||||
XMLStream.disconnect(self, reconnect)
|
||||
|
||||
def registerFeature(self, mask, pointer, breaker = False):
|
||||
"""Register a stream feature."""
|
||||
self.registered_features.append((MatchXMLMask(mask), pointer, breaker))
|
||||
def connect(self, address=tuple()):
|
||||
"""Connect to the Jabber Server. Attempts SRV lookup, and if it fails, uses
|
||||
the JID server."""
|
||||
if not address or len(address) < 2:
|
||||
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.")
|
||||
else:
|
||||
logging.debug("Since no address is supplied, attempting SRV lookup.")
|
||||
try:
|
||||
answers = dns.resolver.query("_xmpp-client._tcp.%s" % self.server, dns.rdatatype.SRV)
|
||||
except dns.resolver.NXDOMAIN:
|
||||
logging.debug("No appropriate SRV record found. Using JID server name.")
|
||||
else:
|
||||
# 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
|
||||
# suggestions are welcome
|
||||
addresses = {}
|
||||
intmax = 0
|
||||
priorities = []
|
||||
for answer in answers:
|
||||
intmax += answer.priority
|
||||
addresses[intmax] = (answer.target.to_text()[:-1], answer.port)
|
||||
priorities.append(intmax) # sure, I could just do priorities = addresses.keys()\n priorities.sort()
|
||||
picked = random.randint(0, intmax)
|
||||
for priority in priorities:
|
||||
if picked <= priority:
|
||||
address = addresses[priority]
|
||||
break
|
||||
if not address:
|
||||
# if all else fails take server from JID.
|
||||
address = (self.server, 5222)
|
||||
result = XMLStream.connect(self, address[0], address[1], use_tls=True)
|
||||
if result:
|
||||
self.event("connected")
|
||||
else:
|
||||
logging.warning("Failed to connect")
|
||||
self.event("disconnected")
|
||||
return result
|
||||
|
||||
# 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
|
||||
def reconnect(self):
|
||||
logging.info("Reconnecting")
|
||||
self.event("disconnected")
|
||||
XMLStream.reconnect(self)
|
||||
|
||||
def disconnect(self, init=True, close=False, reconnect=False):
|
||||
self.event("disconnected")
|
||||
XMLStream.disconnect(self, reconnect)
|
||||
|
||||
def registerFeature(self, mask, pointer, breaker = False):
|
||||
"""Register a stream feature."""
|
||||
self.registered_features.append((MatchXMLMask(mask), pointer, breaker))
|
||||
|
||||
def updateRoster(self, jid, name=None, subscription=None, groups=[]):
|
||||
"""Add or change a roster item."""
|
||||
iq = self.Iq().setValues({'type': 'set'})
|
||||
iq['roster'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}}
|
||||
#self.send(iq, self.Iq().setValues({'id': iq['id']}))
|
||||
r = iq.send()
|
||||
return r['type'] == 'result'
|
||||
|
||||
def getRoster(self):
|
||||
"""Request the roster be sent."""
|
||||
iq = self.Iq().setValues({'type': 'get'}).enable('roster').send()
|
||||
self._handleRoster(iq, request=True)
|
||||
|
||||
def _handleStreamFeatures(self, features):
|
||||
self.features = []
|
||||
for sub in features.xml:
|
||||
self.features.append(sub.tag)
|
||||
for subelement in features.xml:
|
||||
for feature in self.registered_features:
|
||||
if feature[0].match(subelement):
|
||||
#if self.maskcmp(subelement, feature[0], True):
|
||||
if feature[1](subelement) and feature[2]: #if breaker, don't continue
|
||||
return True
|
||||
|
||||
def handler_starttls(self, xml):
|
||||
if not self.authenticated and self.ssl_support:
|
||||
self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, instream=True)
|
||||
self.sendXML(xml)
|
||||
return True
|
||||
else:
|
||||
logging.warning("The module tlslite is required in to some servers, and has not been found.")
|
||||
return False
|
||||
def updateRoster(self, jid, name=None, subscription=None, groups=[]):
|
||||
"""Add or change a roster item."""
|
||||
iq = self.Iq().setStanzaValues({'type': 'set'})
|
||||
iq['roster']['items'] = {jid: {'name': name, 'subscription': subscription, 'groups': groups}}
|
||||
#self.send(iq, self.Iq().setValues({'id': iq['id']}))
|
||||
r = iq.send()
|
||||
return r['type'] == 'result'
|
||||
|
||||
def handler_tls_start(self, xml):
|
||||
logging.debug("Starting TLS")
|
||||
if self.startTLS():
|
||||
raise RestartStream()
|
||||
|
||||
def handler_sasl_auth(self, xml):
|
||||
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
|
||||
return False
|
||||
logging.debug("Starting SASL Auth")
|
||||
self.add_handler("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl' />", self.handler_auth_success, instream=True)
|
||||
self.add_handler("<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):
|
||||
self.authenticated = True
|
||||
self.features = []
|
||||
raise RestartStream()
|
||||
def delRosterItem(self, jid):
|
||||
iq = self.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['roster']['items'] = {jid: {'subscription': 'remove'}}
|
||||
return iq.send()['type'] == 'result'
|
||||
|
||||
def getRoster(self):
|
||||
"""Request the roster be sent."""
|
||||
iq = self.Iq().setStanzaValues({'type': 'get'}).enable('roster').send()
|
||||
self._handleRoster(iq, request=True)
|
||||
|
||||
def _handleStreamFeatures(self, features):
|
||||
self.features = []
|
||||
for sub in features.xml:
|
||||
self.features.append(sub.tag)
|
||||
for subelement in features.xml:
|
||||
for feature in self.registered_features:
|
||||
if feature[0].match(subelement):
|
||||
#if self.maskcmp(subelement, feature[0], True):
|
||||
if feature[1](subelement) and feature[2]: #if breaker, don't continue
|
||||
return True
|
||||
|
||||
def handler_starttls(self, xml):
|
||||
if not self.authenticated and self.ssl_support:
|
||||
self.add_handler("<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls' />", self.handler_tls_start, name='TLS Proceed', instream=True)
|
||||
self.sendXML(xml)
|
||||
return True
|
||||
else:
|
||||
logging.warning("The module tlslite is required in to some servers, and has not been found.")
|
||||
return False
|
||||
|
||||
def handler_auth_fail(self, xml):
|
||||
logging.info("Authentication failed.")
|
||||
self.disconnect()
|
||||
self.event("failed_auth")
|
||||
|
||||
def handler_bind_resource(self, xml):
|
||||
logging.debug("Requesting resource: %s" % self.resource)
|
||||
iq = self.Iq(stype='set')
|
||||
res = ET.Element('resource')
|
||||
res.text = self.resource
|
||||
xml.append(res)
|
||||
iq.append(xml)
|
||||
response = iq.send()
|
||||
#response = self.send(iq, self.Iq(sid=iq['id']))
|
||||
self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text)
|
||||
self.bound = True
|
||||
logging.info("Node set to: %s" % self.fulljid)
|
||||
if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail:
|
||||
logging.debug("Established Session")
|
||||
self.sessionstarted = True
|
||||
self.event("session_start")
|
||||
|
||||
def handler_start_session(self, xml):
|
||||
if self.authenticated and self.bound:
|
||||
iq = self.makeIqSet(xml)
|
||||
response = iq.send()
|
||||
logging.debug("Established Session")
|
||||
self.sessionstarted = True
|
||||
self.event("session_start")
|
||||
else:
|
||||
#bind probably hasn't happened yet
|
||||
self.bindfail = True
|
||||
|
||||
def _handleRoster(self, iq, request=False):
|
||||
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
|
||||
for jid in iq['roster']['items']:
|
||||
if not jid in self.roster:
|
||||
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
|
||||
self.roster[jid].update(iq['roster']['items'][jid])
|
||||
if iq['type'] == 'set':
|
||||
self.send(self.Iq().setValues({'type': 'result', 'id': iq['id']}).enable('roster'))
|
||||
self.event("roster_update", iq)
|
||||
def handler_tls_start(self, xml):
|
||||
logging.debug("Starting TLS")
|
||||
if self.startTLS():
|
||||
raise RestartStream()
|
||||
|
||||
def handler_sasl_auth(self, xml):
|
||||
if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features:
|
||||
return False
|
||||
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_success(self, xml):
|
||||
self.authenticated = True
|
||||
self.features = []
|
||||
raise RestartStream()
|
||||
|
||||
def handler_auth_fail(self, xml):
|
||||
logging.info("Authentication failed.")
|
||||
self.disconnect()
|
||||
self.event("failed_auth")
|
||||
|
||||
def handler_bind_resource(self, xml):
|
||||
logging.debug("Requesting resource: %s" % self.resource)
|
||||
xml.clear()
|
||||
iq = self.Iq(stype='set')
|
||||
if self.resource:
|
||||
res = ET.Element('resource')
|
||||
res.text = self.resource
|
||||
xml.append(res)
|
||||
iq.append(xml)
|
||||
response = iq.send()
|
||||
#response = self.send(iq, self.Iq(sid=iq['id']))
|
||||
self.set_jid(response.xml.find('{urn:ietf:params:xml:ns:xmpp-bind}bind/{urn:ietf:params:xml:ns:xmpp-bind}jid').text)
|
||||
self.bound = True
|
||||
logging.info("Node set to: %s" % self.fulljid)
|
||||
if "{urn:ietf:params:xml:ns:xmpp-session}session" not in self.features or self.bindfail:
|
||||
logging.debug("Established Session")
|
||||
self.sessionstarted = True
|
||||
self.event("session_start")
|
||||
|
||||
def handler_start_session(self, xml):
|
||||
if self.authenticated and self.bound:
|
||||
iq = self.makeIqSet(xml)
|
||||
response = iq.send()
|
||||
logging.debug("Established Session")
|
||||
self.sessionstarted = True
|
||||
self.event("session_start")
|
||||
else:
|
||||
#bind probably hasn't happened yet
|
||||
self.bindfail = True
|
||||
|
||||
def _handleRoster(self, iq, request=False):
|
||||
if iq['type'] == 'set' or (iq['type'] == 'result' and request):
|
||||
for jid in iq['roster']['items']:
|
||||
if not jid in self.roster:
|
||||
self.roster[jid] = {'groups': [], 'name': '', 'subscription': 'none', 'presence': {}, 'in_roster': True}
|
||||
self.roster[jid].update(iq['roster']['items'][jid])
|
||||
if iq['type'] == 'set':
|
||||
self.send(self.Iq().setStanzaValues({'type': 'result', 'id': iq['id']}).enable('roster'))
|
||||
self.event("roster_update", iq)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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
|
||||
|
||||
|
@ -16,6 +16,7 @@ from . xmlstream.handler.xmlcallback import XMLCallback
|
|||
from . xmlstream.handler.xmlwaiter import XMLWaiter
|
||||
from . xmlstream.handler.waiter import Waiter
|
||||
from . xmlstream.handler.callback import Callback
|
||||
from . xmlstream.stanzabase import registerStanzaPlugin
|
||||
from . import plugins
|
||||
from . stanza.message import Message
|
||||
from . stanza.iq import Iq
|
||||
|
@ -24,9 +25,11 @@ from . stanza.roster import Roster
|
|||
from . stanza.nick import Nick
|
||||
from . stanza.htmlim import HTMLIM
|
||||
from . stanza.error import Error
|
||||
from sleekxmpp.xmlstream.tostring import tostring
|
||||
|
||||
import logging
|
||||
import threading
|
||||
import copy
|
||||
|
||||
import sys
|
||||
|
||||
|
@ -34,12 +37,6 @@ if sys.version_info < (3,0):
|
|||
reload(sys)
|
||||
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):
|
||||
def __init__(self):
|
||||
self.id = 0
|
||||
|
@ -61,14 +58,10 @@ class basexmpp(object):
|
|||
self.registerStanza(Message)
|
||||
self.registerStanza(Iq)
|
||||
self.registerStanza(Presence)
|
||||
self.stanzaPlugin(Iq, Roster)
|
||||
self.stanzaPlugin(Message, Nick)
|
||||
self.stanzaPlugin(Message, HTMLIM)
|
||||
registerStanzaPlugin(Iq, Roster)
|
||||
registerStanzaPlugin(Message, Nick)
|
||||
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):
|
||||
return Message(self, *args, **kwargs)
|
||||
|
||||
|
@ -77,7 +70,7 @@ class basexmpp(object):
|
|||
|
||||
def Presence(self, *args, **kwargs):
|
||||
return Presence(self, *args, **kwargs)
|
||||
|
||||
|
||||
def set_jid(self, jid):
|
||||
"""Rip a JID apart and claim it as our own."""
|
||||
self.fulljid = jid
|
||||
|
@ -85,12 +78,12 @@ class basexmpp(object):
|
|||
self.jid = self.getjidbare(jid)
|
||||
self.username = jid.split('@', 1)[0]
|
||||
self.server = jid.split('@',1)[-1].split('/', 1)[0]
|
||||
|
||||
|
||||
def process(self, *args, **kwargs):
|
||||
for idx in self.plugin:
|
||||
if not self.plugin[idx].post_inited: self.plugin[idx].post_init()
|
||||
return super(basexmpp, self).process(*args, **kwargs)
|
||||
|
||||
|
||||
def registerPlugin(self, plugin, pconfig = {}):
|
||||
"""Register a plugin not in plugins.__init__.__all__ but in the plugins
|
||||
directory."""
|
||||
|
@ -105,7 +98,7 @@ class basexmpp(object):
|
|||
if hasattr(self.plugin[plugin], 'xep'):
|
||||
xep = "(XEP-%s) " % self.plugin[plugin].xep
|
||||
logging.debug("Loaded Plugin %s%s" % (xep, self.plugin[plugin].description))
|
||||
|
||||
|
||||
def register_plugins(self):
|
||||
"""Initiates all plugins in the plugins/__init__.__all__"""
|
||||
if self.plugin_whitelist:
|
||||
|
@ -120,22 +113,24 @@ class basexmpp(object):
|
|||
# run post_init() for cross-plugin interaction
|
||||
for plugin in self.plugin:
|
||||
self.plugin[plugin].post_init()
|
||||
|
||||
|
||||
def getNewId(self):
|
||||
with self.id_lock:
|
||||
self.id += 1
|
||||
return self.getId()
|
||||
|
||||
def add_handler(self, mask, pointer, disposable=False, threaded=False, filter=False, instream=False):
|
||||
#logging.warning("Deprecated add_handler used for %s: %s." % (mask, pointer))
|
||||
self.registerHandler(XMLCallback('add_handler_%s' % self.getNewId(), MatchXMLMask(mask), pointer, threaded, disposable, instream))
|
||||
|
||||
|
||||
def add_handler(self, mask, pointer, name=None, disposable=False, threaded=False, filter=False, instream=False):
|
||||
# threaded is no longer needed, but leaving it for backwards compatibility for now
|
||||
if name is None:
|
||||
name = 'add_handler_%s' % self.getNewId()
|
||||
self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer, threaded, disposable, instream))
|
||||
|
||||
def getId(self):
|
||||
return "%x".upper() % self.id
|
||||
|
||||
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):
|
||||
#logging.warning("Deprecated send used for \"%s\"" % (data,))
|
||||
#if not type(data) == type(''):
|
||||
|
@ -150,41 +145,41 @@ class basexmpp(object):
|
|||
self.sendRaw(data)
|
||||
if mask is not None:
|
||||
return waitfor.wait(timeout)
|
||||
|
||||
|
||||
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):
|
||||
iq = self.Iq().setValues({'type': 'get'})
|
||||
iq = self.Iq().setStanzaValues({'type': 'get'})
|
||||
if queryxmlns:
|
||||
iq.append(ET.Element("{%s}query" % queryxmlns))
|
||||
return iq
|
||||
|
||||
|
||||
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):
|
||||
iq = self.Iq().setValues({'type': 'set'})
|
||||
iq = self.Iq().setStanzaValues({'type': 'set'})
|
||||
if sub != None:
|
||||
iq.append(sub)
|
||||
return iq
|
||||
|
||||
def makeIqError(self, id, type='cancel', condition='feature-not-implemented', text=None):
|
||||
iq = self.Iq().setValues({'id': id})
|
||||
iq['error'].setValues({'type': type, 'condition': condition, 'text': text})
|
||||
iq = self.Iq().setStanzaValues({'id': id})
|
||||
iq['error'].setStanzaValues({'type': type, 'condition': condition, 'text': text})
|
||||
return iq
|
||||
|
||||
def makeIqQuery(self, iq, xmlns):
|
||||
query = ET.Element("{%s}query" % xmlns)
|
||||
iq.append(query)
|
||||
return iq
|
||||
|
||||
|
||||
def makeQueryRoster(self, iq=None):
|
||||
query = ET.Element("{jabber:iq:roster}query")
|
||||
if iq:
|
||||
iq.append(query)
|
||||
return query
|
||||
|
||||
|
||||
def add_event_handler(self, name, pointer, threaded=False, disposable=False):
|
||||
if not name in self.event_handlers:
|
||||
self.event_handlers[name] = []
|
||||
|
@ -194,27 +189,28 @@ class basexmpp(object):
|
|||
"""Remove a handler for an event."""
|
||||
if not name in self.event_handlers:
|
||||
return
|
||||
|
||||
|
||||
# Need to keep handlers that do not use
|
||||
# the given function pointer
|
||||
def filter_pointers(handler):
|
||||
return handler[0] != pointer
|
||||
|
||||
self.event_handlers[name] = filter(filter_pointers,
|
||||
self.event_handlers[name] = filter(filter_pointers,
|
||||
self.event_handlers[name])
|
||||
|
||||
def event(self, name, eventdata = {}): # called on an event
|
||||
for handler in self.event_handlers.get(name, []):
|
||||
handlerdata = copy.copy(eventdata)
|
||||
if handler[1]: #if threaded
|
||||
#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()
|
||||
else:
|
||||
handler[0](eventdata)
|
||||
handler[0](handlerdata)
|
||||
if handler[2]: #disposable
|
||||
with self.lock:
|
||||
self.event_handlers[name].pop(self.event_handlers[name].index(handler))
|
||||
|
||||
|
||||
def makeMessage(self, mto, mbody=None, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None):
|
||||
message = self.Message(sto=mto, stype=mtype, sfrom=mfrom)
|
||||
message['body'] = mbody
|
||||
|
@ -222,7 +218,7 @@ class basexmpp(object):
|
|||
if mnick is not None: message['nick'] = mnick
|
||||
if mhtml is not None: message['html']['html'] = mhtml
|
||||
return message
|
||||
|
||||
|
||||
def makePresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, ptype=None, pfrom=None):
|
||||
presence = self.Presence(stype=ptype, sfrom=pfrom, sto=pto)
|
||||
if pshow is not None: presence['type'] = pshow
|
||||
|
@ -231,10 +227,10 @@ class basexmpp(object):
|
|||
presence['priority'] = ppriority
|
||||
presence['status'] = pstatus
|
||||
return presence
|
||||
|
||||
|
||||
def sendMessage(self, mto, mbody, msubject=None, mtype=None, mhtml=None, mfrom=None, mnick=None):
|
||||
self.send(self.makeMessage(mto,mbody,msubject,mtype,mhtml,mfrom,mnick))
|
||||
|
||||
|
||||
def sendPresence(self, pshow=None, pstatus=None, ppriority=None, pto=None, pfrom=None, ptype=None):
|
||||
self.send(self.makePresence(pshow,pstatus,ppriority,pto, ptype=ptype, pfrom=pfrom))
|
||||
if not self.sentpresence:
|
||||
|
@ -248,19 +244,19 @@ class basexmpp(object):
|
|||
nick.text = pnick
|
||||
presence.append(nick)
|
||||
self.send(presence)
|
||||
|
||||
|
||||
def getjidresource(self, fulljid):
|
||||
if '/' in fulljid:
|
||||
return fulljid.split('/', 1)[-1]
|
||||
else:
|
||||
return ''
|
||||
|
||||
|
||||
def getjidbare(self, fulljid):
|
||||
return fulljid.split('/', 1)[0]
|
||||
|
||||
def _handleMessage(self, msg):
|
||||
self.event('message', msg)
|
||||
|
||||
|
||||
def _handlePresence(self, presence):
|
||||
"""Update roster items based on presence"""
|
||||
self.event("presence_%s" % presence['type'], presence)
|
||||
|
@ -301,7 +297,7 @@ class basexmpp(object):
|
|||
if name:
|
||||
name = "(%s) " % name
|
||||
logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, show,status))
|
||||
|
||||
|
||||
def _handlePresenceSubscribe(self, presence):
|
||||
"""Handling subscriptions automatically."""
|
||||
if self.auto_authorize == True:
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#!/usr/bin/python2.6
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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 . basexmpp import basexmpp
|
||||
|
@ -30,59 +30,60 @@ from . import stanza
|
|||
import hashlib
|
||||
srvsupport = True
|
||||
try:
|
||||
import dns.resolver
|
||||
import dns.resolver
|
||||
except ImportError:
|
||||
srvsupport = False
|
||||
srvsupport = False
|
||||
|
||||
|
||||
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):
|
||||
XMLStream.__init__(self)
|
||||
if use_jc_ns:
|
||||
self.default_ns = 'jabber:client'
|
||||
else:
|
||||
self.default_ns = 'jabber:component:accept'
|
||||
basexmpp.__init__(self)
|
||||
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_footer = "</stream:stream>"
|
||||
self.server_host = host
|
||||
self.server_port = port
|
||||
self.set_jid(jid)
|
||||
self.secret = secret
|
||||
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.plugin:
|
||||
return self.plugin[key]
|
||||
else:
|
||||
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
||||
return False
|
||||
|
||||
def get(self, key, default):
|
||||
return self.plugin.get(key, default)
|
||||
|
||||
def incoming_filter(self, xmlobj):
|
||||
if xmlobj.tag.startswith('{jabber:client}'):
|
||||
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
|
||||
for sub in xmlobj:
|
||||
self.incoming_filter(sub)
|
||||
return xmlobj
|
||||
def __init__(self, jid, secret, host, port, plugin_config = {}, plugin_whitelist=[], use_jc_ns=False):
|
||||
XMLStream.__init__(self)
|
||||
if use_jc_ns:
|
||||
self.default_ns = 'jabber:client'
|
||||
else:
|
||||
self.default_ns = 'jabber:component:accept'
|
||||
basexmpp.__init__(self)
|
||||
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_footer = "</stream:stream>"
|
||||
self.server_host = host
|
||||
self.server_port = port
|
||||
self.set_jid(jid)
|
||||
self.secret = secret
|
||||
self.is_component = True
|
||||
self.registerHandler(Callback('Handshake', MatchXPath('{jabber:component:accept}handshake'), self._handleHandshake))
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.plugin:
|
||||
return self.plugin[key]
|
||||
else:
|
||||
logging.warning("""Plugin "%s" is not loaded.""" % key)
|
||||
return False
|
||||
|
||||
def get(self, key, default):
|
||||
return self.plugin.get(key, default)
|
||||
|
||||
def incoming_filter(self, xmlobj):
|
||||
if xmlobj.tag.startswith('{jabber:client}'):
|
||||
xmlobj.tag = xmlobj.tag.replace('jabber:client', self.default_ns)
|
||||
for sub in xmlobj:
|
||||
self.incoming_filter(sub)
|
||||
return xmlobj
|
||||
|
||||
def start_stream_handler(self, xml):
|
||||
sid = xml.get('id', '')
|
||||
handshake = ET.Element('{jabber:component:accept}handshake')
|
||||
if sys.version_info < (3,0):
|
||||
handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower()
|
||||
else:
|
||||
handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower()
|
||||
self.sendXML(handshake)
|
||||
|
||||
def _handleHandshake(self, xml):
|
||||
self.event("session_start")
|
||||
|
||||
def connect(self):
|
||||
logging.debug("Connecting to %s:%s" % (self.server_host, self.server_port))
|
||||
return xmlstreammod.XMLStream.connect(self, self.server_host, self.server_port)
|
||||
def start_stream_handler(self, xml):
|
||||
sid = xml.get('id', '')
|
||||
handshake = ET.Element('{jabber:component:accept}handshake')
|
||||
if sys.version_info < (3,0):
|
||||
handshake.text = hashlib.sha1("%s%s" % (sid, self.secret)).hexdigest().lower()
|
||||
else:
|
||||
handshake.text = hashlib.sha1(bytes("%s%s" % (sid, self.secret), 'utf-8')).hexdigest().lower()
|
||||
self.sendXML(handshake)
|
||||
|
||||
def _handleHandshake(self, xml):
|
||||
self.event("session_start")
|
||||
|
||||
def connect(self):
|
||||
logging.debug("Connecting to %s:%s" % (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
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file license.txt for copying permission.
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
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
|
||||
self.etype = etype
|
||||
self.extension = extension
|
||||
self.extension_ns = extension_ns
|
||||
self.extension_args = extension_args
|
||||
|
||||
"""
|
||||
A generic exception that may be raised while processing an XMPP stanza
|
||||
to indicate that an error response stanza should be sent.
|
||||
|
||||
The exception method for stanza objects extending RootStanza will create
|
||||
an error stanza and initialize any additional substanzas using the
|
||||
extension information included in the exception.
|
||||
|
||||
Meant for use in SleekXMPP plugins and applications using SleekXMPP.
|
||||
"""
|
||||
|
||||
def __init__(self, condition='undefined-condition', text=None, etype=None,
|
||||
extension=None, extension_ns=None, extension_args=None):
|
||||
"""
|
||||
Create a new XMPPError exception.
|
||||
|
||||
Extension information can be included to add additional XML elements
|
||||
to the generated error stanza.
|
||||
|
||||
Arguments:
|
||||
condition -- The XMPP defined error condition.
|
||||
text -- Human readable text describing the error.
|
||||
etype -- The XMPP error type, such as cancel or modify.
|
||||
extension -- Tag name of the extension's XML content.
|
||||
extension_ns -- XML namespace of the extensions' XML content.
|
||||
extension_args -- Content and attributes for the extension
|
||||
element. Same as the additional arguments to
|
||||
the ET.Element constructor.
|
||||
"""
|
||||
self.condition = condition
|
||||
self.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
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
__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
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
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 base_plugin(object):
|
||||
|
||||
def __init__(self, xmpp, config):
|
||||
|
|
|
@ -1,57 +1,146 @@
|
|||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from __future__ import with_statement
|
||||
from . import base
|
||||
|
||||
import logging
|
||||
from xml.etree import cElementTree as ET
|
||||
import traceback
|
||||
import time
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
|
||||
def plugin_init(self):
|
||||
self.description = 'Google Talk Gmail Notification'
|
||||
self.xmpp.add_event_handler('sent_presence', self.handler_gmailcheck, threaded=True)
|
||||
self.emails = []
|
||||
|
||||
def handler_gmailcheck(self, payload):
|
||||
#TODO XEP 30 should cache results and have getFeature
|
||||
result = self.xmpp['xep_0030'].getInfo(self.xmpp.server)
|
||||
features = []
|
||||
for feature in result.findall('{http://jabber.org/protocol/disco#info}query/{http://jabber.org/protocol/disco#info}feature'):
|
||||
features.append(feature.get('var'))
|
||||
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):
|
||||
logging.info("New Gmail recieved!")
|
||||
self.xmpp.event('gmail_notify')
|
||||
|
||||
def getEmail(self):
|
||||
iq = self.xmpp.makeIqGet()
|
||||
iq.attrib['from'] = self.xmpp.fulljid
|
||||
iq.attrib['to'] = self.xmpp.jid
|
||||
self.xmpp.makeIqQuery(iq, 'google:mail:notify')
|
||||
emails = iq.send()
|
||||
mailbox = emails.find('{google:mail:notify}mailbox')
|
||||
total = int(mailbox.get('total-matched', 0))
|
||||
logging.info("%s New Gmail Messages" % total)
|
||||
"""
|
||||
Google Talk: Gmail Notifications
|
||||
"""
|
||||
|
||||
def plugin_init(self):
|
||||
self.description = 'Google Talk: Gmail Notifications'
|
||||
|
||||
self.xmpp.registerHandler(
|
||||
Callback('Gmail Result',
|
||||
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
|
||||
MailBox.namespace,
|
||||
MailBox.name)),
|
||||
self.handle_gmail))
|
||||
|
||||
self.xmpp.registerHandler(
|
||||
Callback('Gmail New Mail',
|
||||
MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns,
|
||||
NewMail.namespace,
|
||||
NewMail.name)),
|
||||
self.handle_new_mail))
|
||||
|
||||
registerStanzaPlugin(Iq, GmailQuery)
|
||||
registerStanzaPlugin(Iq, MailBox)
|
||||
registerStanzaPlugin(Iq, NewMail)
|
||||
|
||||
self.last_result_time = None
|
||||
|
||||
def handle_gmail(self, iq):
|
||||
mailbox = iq['mailbox']
|
||||
approx = ' approximately' if mailbox['estimated'] else ''
|
||||
logging.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched']))
|
||||
self.last_result_time = mailbox['result-time']
|
||||
self.xmpp.event('gmail_messages', iq)
|
||||
|
||||
def handle_new_mail(self, iq):
|
||||
logging.info("Gmail: New emails received!")
|
||||
self.xmpp.event('gmail_notify')
|
||||
self.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.message import Message
|
||||
from .. basexmpp import basexmpp
|
||||
|
@ -6,9 +6,6 @@ from .. xmlstream.xmlstream import XMLStream
|
|||
import logging
|
||||
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):
|
||||
namespace = 'http://jabber.org/protocol/psstate'
|
||||
|
@ -30,7 +27,7 @@ class PubsubState(ElementBase):
|
|||
for child in self.xml.getchildren():
|
||||
self.xml.remove(child)
|
||||
|
||||
stanzaPlugin(Iq, PubsubState)
|
||||
registerStanzaPlugin(Iq, PubsubState)
|
||||
|
||||
class PubsubStateEvent(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/psstate#event'
|
||||
|
@ -40,8 +37,8 @@ class PubsubStateEvent(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(Message, PubsubStateEvent)
|
||||
stanzaPlugin(PubsubStateEvent, PubsubState)
|
||||
registerStanzaPlugin(Message, PubsubStateEvent)
|
||||
registerStanzaPlugin(PubsubStateEvent, PubsubState)
|
||||
|
||||
class Pubsub(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -51,7 +48,7 @@ class Pubsub(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(Iq, Pubsub)
|
||||
registerStanzaPlugin(Iq, Pubsub)
|
||||
|
||||
class PubsubOwner(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -61,7 +58,7 @@ class PubsubOwner(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(Iq, PubsubOwner)
|
||||
registerStanzaPlugin(Iq, PubsubOwner)
|
||||
|
||||
class Affiliation(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -86,7 +83,7 @@ class Affiliations(ElementBase):
|
|||
self.xml.append(affiliation.xml)
|
||||
return self.iterables.append(affiliation)
|
||||
|
||||
stanzaPlugin(Pubsub, Affiliations)
|
||||
registerStanzaPlugin(Pubsub, Affiliations)
|
||||
|
||||
|
||||
class Subscription(ElementBase):
|
||||
|
@ -103,7 +100,7 @@ class Subscription(ElementBase):
|
|||
def getjid(self):
|
||||
return jid(self._getattr('jid'))
|
||||
|
||||
stanzaPlugin(Pubsub, Subscription)
|
||||
registerStanzaPlugin(Pubsub, Subscription)
|
||||
|
||||
class Subscriptions(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -114,7 +111,7 @@ class Subscriptions(ElementBase):
|
|||
plugin_tag_map = {}
|
||||
subitem = (Subscription,)
|
||||
|
||||
stanzaPlugin(Pubsub, Subscriptions)
|
||||
registerStanzaPlugin(Pubsub, Subscriptions)
|
||||
|
||||
class OptionalSetting(object):
|
||||
interfaces = set(('required',))
|
||||
|
@ -147,7 +144,7 @@ class SubscribeOptions(ElementBase, OptionalSetting):
|
|||
plugin_tag_map = {}
|
||||
interfaces = set(('required',))
|
||||
|
||||
stanzaPlugin(Subscription, SubscribeOptions)
|
||||
registerStanzaPlugin(Subscription, SubscribeOptions)
|
||||
|
||||
class Item(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -178,7 +175,7 @@ class Items(ElementBase):
|
|||
plugin_tag_map = {}
|
||||
subitem = (Item,)
|
||||
|
||||
stanzaPlugin(Pubsub, Items)
|
||||
registerStanzaPlugin(Pubsub, Items)
|
||||
|
||||
class Create(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -188,7 +185,7 @@ class Create(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(Pubsub, Create)
|
||||
registerStanzaPlugin(Pubsub, Create)
|
||||
|
||||
#class Default(ElementBase):
|
||||
# namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -203,7 +200,7 @@ stanzaPlugin(Pubsub, Create)
|
|||
# if not t: t == 'leaf'
|
||||
# return t
|
||||
#
|
||||
#stanzaPlugin(Pubsub, Default)
|
||||
#registerStanzaPlugin(Pubsub, Default)
|
||||
|
||||
class Publish(Items):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -214,7 +211,7 @@ class Publish(Items):
|
|||
plugin_tag_map = {}
|
||||
subitem = (Item,)
|
||||
|
||||
stanzaPlugin(Pubsub, Publish)
|
||||
registerStanzaPlugin(Pubsub, Publish)
|
||||
|
||||
class Retract(Items):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -224,7 +221,7 @@ class Retract(Items):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(Pubsub, Retract)
|
||||
registerStanzaPlugin(Pubsub, Retract)
|
||||
|
||||
class Unsubscribe(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -254,13 +251,13 @@ class Subscribe(ElementBase):
|
|||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
|
||||
stanzaPlugin(Pubsub, Subscribe)
|
||||
registerStanzaPlugin(Pubsub, Subscribe)
|
||||
|
||||
class Configure(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'configure'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'type', 'config'))
|
||||
interfaces = set(('node', 'type'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
|
@ -269,22 +266,8 @@ class Configure(ElementBase):
|
|||
if not t: t == 'leaf'
|
||||
return t
|
||||
|
||||
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)
|
||||
|
||||
stanzaPlugin(Pubsub, Configure)
|
||||
registerStanzaPlugin(Pubsub, Configure)
|
||||
registerStanzaPlugin(Configure, xep_0004.Form)
|
||||
|
||||
class DefaultConfig(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -296,28 +279,14 @@ class DefaultConfig(ElementBase):
|
|||
|
||||
def __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):
|
||||
t = self._getAttr('type')
|
||||
if not t: t = 'leaf'
|
||||
return t
|
||||
|
||||
stanzaPlugin(PubsubOwner, DefaultConfig)
|
||||
registerStanzaPlugin(PubsubOwner, DefaultConfig)
|
||||
registerStanzaPlugin(DefaultConfig, xep_0004.Form)
|
||||
|
||||
class Options(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
|
@ -351,8 +320,8 @@ class Options(ElementBase):
|
|||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
|
||||
stanzaPlugin(Pubsub, Options)
|
||||
stanzaPlugin(Subscribe, Options)
|
||||
registerStanzaPlugin(Pubsub, Options)
|
||||
registerStanzaPlugin(Subscribe, Options)
|
||||
|
||||
class OwnerAffiliations(Affiliations):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -366,7 +335,7 @@ class OwnerAffiliations(Affiliations):
|
|||
self.xml.append(affiliation.xml)
|
||||
return self.affiliations.append(affiliation)
|
||||
|
||||
stanzaPlugin(PubsubOwner, OwnerAffiliations)
|
||||
registerStanzaPlugin(PubsubOwner, OwnerAffiliations)
|
||||
|
||||
class OwnerAffiliation(Affiliation):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -380,7 +349,7 @@ class OwnerConfigure(Configure):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(PubsubOwner, OwnerConfigure)
|
||||
registerStanzaPlugin(PubsubOwner, OwnerConfigure)
|
||||
|
||||
class OwnerDefault(OwnerConfigure):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -388,7 +357,7 @@ class OwnerDefault(OwnerConfigure):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(PubsubOwner, OwnerDefault)
|
||||
registerStanzaPlugin(PubsubOwner, OwnerDefault)
|
||||
|
||||
class OwnerDelete(ElementBase, OptionalSetting):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -398,7 +367,7 @@ class OwnerDelete(ElementBase, OptionalSetting):
|
|||
plugin_tag_map = {}
|
||||
interfaces = set(('node',))
|
||||
|
||||
stanzaPlugin(PubsubOwner, OwnerDelete)
|
||||
registerStanzaPlugin(PubsubOwner, OwnerDelete)
|
||||
|
||||
class OwnerPurge(ElementBase, OptionalSetting):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -407,7 +376,7 @@ class OwnerPurge(ElementBase, OptionalSetting):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(PubsubOwner, OwnerPurge)
|
||||
registerStanzaPlugin(PubsubOwner, OwnerPurge)
|
||||
|
||||
class OwnerRedirect(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -423,7 +392,7 @@ class OwnerRedirect(ElementBase):
|
|||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
|
||||
stanzaPlugin(OwnerDelete, OwnerRedirect)
|
||||
registerStanzaPlugin(OwnerDelete, OwnerRedirect)
|
||||
|
||||
class OwnerSubscriptions(Subscriptions):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -437,7 +406,7 @@ class OwnerSubscriptions(Subscriptions):
|
|||
self.xml.append(subscription.xml)
|
||||
return self.subscriptions.append(subscription)
|
||||
|
||||
stanzaPlugin(PubsubOwner, OwnerSubscriptions)
|
||||
registerStanzaPlugin(PubsubOwner, OwnerSubscriptions)
|
||||
|
||||
class OwnerSubscription(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
|
@ -461,7 +430,7 @@ class Event(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(Message, Event)
|
||||
registerStanzaPlugin(Message, Event)
|
||||
|
||||
class EventItem(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
|
@ -501,7 +470,7 @@ class EventItems(ElementBase):
|
|||
plugin_tag_map = {}
|
||||
subitem = (EventItem, EventRetract)
|
||||
|
||||
stanzaPlugin(Event, EventItems)
|
||||
registerStanzaPlugin(Event, EventItems)
|
||||
|
||||
class EventCollection(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
|
@ -511,7 +480,7 @@ class EventCollection(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(Event, EventCollection)
|
||||
registerStanzaPlugin(Event, EventCollection)
|
||||
|
||||
class EventAssociate(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
|
@ -521,7 +490,7 @@ class EventAssociate(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(EventCollection, EventAssociate)
|
||||
registerStanzaPlugin(EventCollection, EventAssociate)
|
||||
|
||||
class EventDisassociate(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
|
@ -531,7 +500,7 @@ class EventDisassociate(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(EventCollection, EventDisassociate)
|
||||
registerStanzaPlugin(EventCollection, EventDisassociate)
|
||||
|
||||
class EventConfiguration(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
|
@ -541,22 +510,8 @@ class EventConfiguration(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
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)
|
||||
|
||||
stanzaPlugin(Event, EventConfiguration)
|
||||
registerStanzaPlugin(Event, EventConfiguration)
|
||||
registerStanzaPlugin(EventConfiguration, xep_0004.Form)
|
||||
|
||||
class EventPurge(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
|
@ -566,7 +521,7 @@ class EventPurge(ElementBase):
|
|||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
stanzaPlugin(Event, EventPurge)
|
||||
registerStanzaPlugin(Event, EventPurge)
|
||||
|
||||
class EventSubscription(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
|
@ -582,4 +537,4 @@ class EventSubscription(ElementBase):
|
|||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
|
||||
stanzaPlugin(Event, EventSubscription)
|
||||
registerStanzaPlugin(Event, EventSubscription)
|
||||
|
|
|
@ -1,427 +1,347 @@
|
|||
"""
|
||||
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.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from . import base
|
||||
|
||||
import logging
|
||||
from xml.etree import cElementTree as ET
|
||||
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):
|
||||
|
||||
"""
|
||||
XEP-0004: Data Forms
|
||||
"""
|
||||
|
||||
def plugin_init(self):
|
||||
self.xep = '0004'
|
||||
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):
|
||||
base.base_plugin.post_init(self)
|
||||
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
|
||||
|
||||
def handler_message_xform(self, xml):
|
||||
object = self.handle_form(xml)
|
||||
self.xmpp.event("message_form", object)
|
||||
|
||||
def handler_presence_xform(self, xml):
|
||||
object = self.handle_form(xml)
|
||||
self.xmpp.event("presence_form", object)
|
||||
|
||||
def handle_form(self, xml):
|
||||
xmlform = xml.find('{jabber:x:data}x')
|
||||
object = self.buildForm(xmlform)
|
||||
self.xmpp.event("message_xform", object)
|
||||
return object
|
||||
|
||||
def buildForm(self, xml):
|
||||
form = Form(ftype=xml.attrib['type'])
|
||||
form.fromXML(xml)
|
||||
return form
|
||||
|
||||
def makeForm(self, ftype='form', title='', instructions=''):
|
||||
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
|
||||
|
||||
def handle_form(self, message):
|
||||
self.xmpp.event("message_xform", message)
|
||||
|
|
|
@ -178,9 +178,12 @@ class xep_0009(base.base_plugin):
|
|||
def plugin_init(self):
|
||||
self.xep = '0009'
|
||||
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='result'><query xmlns='jabber:iq:rpc' /></iq>", self._callResult)
|
||||
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", self._callError)
|
||||
self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",
|
||||
self._callMethod, name='Jabber RPC Call')
|
||||
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.activeCalls = []
|
||||
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file license.txt for copying permissio
|
||||
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 ElementBase, ET, JID
|
||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
||||
from .. stanza.iq import Iq
|
||||
|
||||
class DiscoInfo(ElementBase):
|
||||
|
@ -138,6 +138,9 @@ class DiscoNode(object):
|
|||
self.info = DiscoInfo()
|
||||
self.items = DiscoItems()
|
||||
|
||||
self.info['node'] = name
|
||||
self.items['node'] = name
|
||||
|
||||
# This is a bit like poor man's inheritance, but
|
||||
# to simplify adding information to the node we
|
||||
# map node functions to either the info or items
|
||||
|
@ -201,8 +204,8 @@ class xep_0030(base.base_plugin):
|
|||
DiscoInfo.namespace)),
|
||||
self.handle_info_query))
|
||||
|
||||
self.xmpp.stanzaPlugin(Iq, DiscoInfo)
|
||||
self.xmpp.stanzaPlugin(Iq, DiscoItems)
|
||||
registerStanzaPlugin(Iq, DiscoInfo)
|
||||
registerStanzaPlugin(Iq, DiscoItems)
|
||||
|
||||
self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items)
|
||||
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
|
||||
|
||||
def getInfo(self, jid, node=''):
|
||||
def getInfo(self, jid, node='', dfrom=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['to'] = jid
|
||||
iq['from'] = self.xmpp.fulljid
|
||||
iq['from'] = dfrom
|
||||
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['type'] = 'get'
|
||||
iq['to'] = jid
|
||||
iq['from'] = self.xmpp.fulljid
|
||||
iq['from'] = dfrom
|
||||
iq['disco_items']['node'] = node
|
||||
iq.send()
|
||||
return iq.send()
|
||||
|
||||
def add_feature(self, feature, node='main'):
|
||||
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
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
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 with_statement
|
||||
from . import base
|
||||
import logging
|
||||
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 .. xmlstream.handler.callback import Callback
|
||||
from .. xmlstream.matcher.xpath import MatchXPath
|
||||
|
@ -125,7 +113,7 @@ class xep_0045(base.base_plugin):
|
|||
self.xep = '0045'
|
||||
self.description = 'Multi User Chat'
|
||||
# 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('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():
|
||||
return
|
||||
entry = pr['muc'].getValues()
|
||||
entry = pr['muc'].getStanzaValues()
|
||||
if pr['type'] == 'unavailable':
|
||||
del self.rooms[entry['room']][entry['nick']]
|
||||
else:
|
||||
|
@ -166,13 +154,13 @@ class xep_0045(base.base_plugin):
|
|||
return False
|
||||
xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
|
||||
if xform is None: return False
|
||||
form = self.xmpp.plugin['xep_0004'].buildForm(xform)
|
||||
form = self.xmpp.plugin['old_0004'].buildForm(xform)
|
||||
return form
|
||||
|
||||
def configureRoom(self, room, form=None, ifrom=None):
|
||||
if form is None:
|
||||
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')
|
||||
iq = self.xmpp.makeIqSet()
|
||||
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')
|
||||
if form is None:
|
||||
raise ValueError
|
||||
return self.xmpp.plugin['xep_0004'].buildForm(form)
|
||||
return self.xmpp.plugin['old_0004'].buildForm(form)
|
||||
|
||||
def cancelConfig(self, room):
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
||||
|
|
|
@ -1,27 +1,14 @@
|
|||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
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 with_statement
|
||||
from . import base
|
||||
import logging
|
||||
from xml.etree import cElementTree as ET
|
||||
import traceback
|
||||
import time
|
||||
|
||||
class xep_0050(base.base_plugin):
|
||||
|
@ -32,11 +19,11 @@ class xep_0050(base.base_plugin):
|
|||
def plugin_init(self):
|
||||
self.xep = '0050'
|
||||
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='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='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='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='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='__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, 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, 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, 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, name='Ad-Hoc Complete')
|
||||
self.commands = {}
|
||||
self.sessions = {}
|
||||
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')
|
||||
sessionid = in_command.get('sessionid', None)
|
||||
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'))
|
||||
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=[]))
|
||||
|
@ -94,7 +81,7 @@ class xep_0050(base.base_plugin):
|
|||
in_command = xml.find('{http://jabber.org/protocol/commands}command')
|
||||
sessionid = in_command.get('sessionid', None)
|
||||
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'))
|
||||
form, npointer, next = pointer(results,sessionid)
|
||||
self.sessions[sessionid]['next'] = npointer
|
||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import with_statement
|
|||
from . import base
|
||||
import logging
|
||||
#from xml.etree import cElementTree as ET
|
||||
from .. xmlstream.stanzabase import ElementBase, ET
|
||||
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
|
||||
from . import stanza_pubsub
|
||||
|
||||
class xep_0060(base.base_plugin):
|
||||
|
|
|
@ -1,21 +1,9 @@
|
|||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
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 with_statement
|
||||
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
|
||||
Copyright (C) 2007 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
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 xml.etree import cElementTree as ET
|
||||
from . import base
|
||||
|
@ -30,7 +18,7 @@ class xep_0092(base.base_plugin):
|
|||
self.xep = "0092"
|
||||
self.name = self.config.get('name', 'SleekXMPP')
|
||||
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):
|
||||
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
|
||||
XEP-0199 (Ping) support
|
||||
Copyright (C) 2007 Kevin Smith
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
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 xml.etree import cElementTree as ET
|
||||
from . import base
|
||||
|
@ -29,7 +16,7 @@ class xep_0199(base.base_plugin):
|
|||
def plugin_init(self):
|
||||
self.description = "XMPP Ping"
|
||||
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
|
||||
#if self.config.get('keepalive', True):
|
||||
#self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True)
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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
|
||||
|
||||
class AtomEntry(ElementBase):
|
||||
|
|
|
@ -3,60 +3,131 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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):
|
||||
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
|
||||
self['type'] = 'cancel'
|
||||
self['condition'] = 'feature-not-implemented'
|
||||
if self.parent is not None:
|
||||
self.parent()['type'] = 'error'
|
||||
|
||||
def getCondition(self):
|
||||
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):
|
||||
if value in self.conditions:
|
||||
for child in self.xml.getchildren():
|
||||
if "{%s}" % self.condition_ns in child.tag:
|
||||
self.xml.remove(child)
|
||||
condition = ET.Element("{%s}%s" % (self.condition_ns, value))
|
||||
self.xml.append(condition)
|
||||
return self
|
||||
|
||||
def delCondition(self):
|
||||
return self
|
||||
|
||||
def getText(self):
|
||||
text = ''
|
||||
textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text")
|
||||
if textxml is not None:
|
||||
text = textxml.text
|
||||
return text
|
||||
|
||||
def setText(self, value):
|
||||
self.delText()
|
||||
textxml = ET.Element('{urn:ietf:params:xml:ns:xmpp-stanzas}text')
|
||||
textxml.text = value
|
||||
self.xml.append(textxml)
|
||||
return self
|
||||
|
||||
def delText(self):
|
||||
textxml = self.xml.find("{urn:ietf:params:xml:ns:xmpp-stanzas}text")
|
||||
if textxml is not None:
|
||||
self.xml.remove(textxml)
|
||||
|
||||
"""
|
||||
XMPP stanzas of type 'error' should include an <error> stanza that
|
||||
describes the nature of the error and how it should be handled.
|
||||
|
||||
Use the 'XEP-0086: Error Condition Mappings' plugin to include error
|
||||
codes used in older XMPP versions.
|
||||
|
||||
Example error stanza:
|
||||
<error type="cancel" code="404">
|
||||
<item-not-found xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||
The item was not found.
|
||||
</text>
|
||||
</error>
|
||||
|
||||
Stanza Interface:
|
||||
code -- The error code used in older XMPP versions.
|
||||
condition -- The name of the condition element.
|
||||
text -- Human readable description of the error.
|
||||
type -- Error type indicating how the error should be handled.
|
||||
|
||||
Attributes:
|
||||
conditions -- The set of allowable error condition elements.
|
||||
condition_ns -- The namespace for the condition element.
|
||||
types -- A set of values indicating how the error
|
||||
should be treated.
|
||||
|
||||
Methods:
|
||||
setup -- Overrides ElementBase.setup.
|
||||
getCondition -- Retrieve the name of the condition element.
|
||||
setCondition -- Add a condition element.
|
||||
delCondition -- Remove the condition element.
|
||||
getText -- Retrieve the contents of the <text> element.
|
||||
setText -- Set the contents of the <text> element.
|
||||
delText -- Remove the <text> element.
|
||||
"""
|
||||
|
||||
namespace = 'jabber:client'
|
||||
name = 'error'
|
||||
plugin_attrib = 'error'
|
||||
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
|
||||
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):
|
||||
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):
|
||||
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 getHtml(self):
|
||||
html = self.xml.find('{http://www.w3.org/1999/xhtml}body')
|
||||
if html is None: return ''
|
||||
return html
|
||||
|
||||
def delHtml(self):
|
||||
if self.parent is not None:
|
||||
self.parent().xml.remove(self.xml)
|
||||
"""
|
||||
XEP-0071: XHTML-IM defines a method for embedding XHTML content
|
||||
within a <message> stanza so that lightweight markup can be used
|
||||
to format the message contents and to create links.
|
||||
|
||||
Only a subset of XHTML is recommended for use with XHTML-IM.
|
||||
See the full spec at 'http://xmpp.org/extensions/xep-0071.html'
|
||||
for more information.
|
||||
|
||||
Example stanza:
|
||||
<message to="user@example.com">
|
||||
<body>Non-html message content.</body>
|
||||
<html xmlns="http://jabber.org/protocol/xhtml-im">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p><b>HTML!</b></p>
|
||||
</body>
|
||||
</html>
|
||||
</message>
|
||||
|
||||
Stanza Interface:
|
||||
body -- The contents of the HTML body tag.
|
||||
|
||||
Methods:
|
||||
getBody -- Return the HTML body contents.
|
||||
setBody -- Set the HTML body contents.
|
||||
delBody -- Remove the HTML body contents.
|
||||
"""
|
||||
|
||||
namespace = 'http://jabber.org/protocol/xhtml-im'
|
||||
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
|
||||
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 . error import Error
|
||||
from .. xmlstream.handler.waiter import Waiter
|
||||
from .. xmlstream.matcher.id import MatcherId
|
||||
from . rootstanza import RootStanza
|
||||
|
||||
from sleekxmpp.stanza import Error
|
||||
from sleekxmpp.stanza.rootstanza import RootStanza
|
||||
from sleekxmpp.xmlstream import RESPONSE_TIMEOUT
|
||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
|
||||
from sleekxmpp.xmlstream.handler import Waiter
|
||||
from sleekxmpp.xmlstream.matcher import MatcherId
|
||||
|
||||
|
||||
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)
|
||||
if self['id'] == '':
|
||||
if self.stream is not None:
|
||||
self['id'] = self.stream.getNewId()
|
||||
else:
|
||||
self['id'] = '0'
|
||||
|
||||
def unhandled(self):
|
||||
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):
|
||||
self.clear()
|
||||
StanzaBase.setPayload(self, value)
|
||||
return self
|
||||
|
||||
def setQuery(self, value):
|
||||
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):
|
||||
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 reply(self):
|
||||
self['type'] = 'result'
|
||||
StanzaBase.reply(self)
|
||||
return self
|
||||
|
||||
def delQuery(self):
|
||||
for child in self.getchildren():
|
||||
if child.tag.endswith('query'):
|
||||
self.xml.remove(child)
|
||||
return self
|
||||
|
||||
def send(self, block=True, timeout=10):
|
||||
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)
|
||||
"""
|
||||
XMPP <iq> stanzas, or info/query stanzas, are XMPP's method of
|
||||
requesting and modifying information, similar to HTTP's GET and
|
||||
POST methods.
|
||||
|
||||
Each <iq> stanza must have an 'id' value which associates the
|
||||
stanza with the response stanza. XMPP entities must always
|
||||
be given a response <iq> stanza with a type of 'result' after
|
||||
sending a stanza of type 'get' or 'set'.
|
||||
|
||||
Most uses cases for <iq> stanzas will involve adding a <query>
|
||||
element whose namespace indicates the type of information
|
||||
desired. However, some custom XMPP applications use <iq> stanzas
|
||||
as a carrier stanza for an application-specific protocol instead.
|
||||
|
||||
Example <iq> Stanzas:
|
||||
<iq to="user@example.com" type="get" id="314">
|
||||
<query xmlns="http://jabber.org/protocol/disco#items" />
|
||||
</iq>
|
||||
|
||||
<iq to="user@localhost" type="result" id="17">
|
||||
<query xmlns='jabber:iq:roster'>
|
||||
<item jid='otheruser@example.net'
|
||||
name='John Doe'
|
||||
subscription='both'>
|
||||
<group>Friends</group>
|
||||
</item>
|
||||
</query>
|
||||
</iq>
|
||||
|
||||
Stanza Interface:
|
||||
query -- The namespace of the <query> element if one exists.
|
||||
|
||||
Attributes:
|
||||
types -- May be one of: get, set, result, or error.
|
||||
|
||||
Methods:
|
||||
__init__ -- Overrides StanzaBase.__init__.
|
||||
unhandled -- Send error if there are no handlers.
|
||||
setPayload -- Overrides StanzaBase.setPayload.
|
||||
setQuery -- Add or modify a <query> element.
|
||||
getQuery -- Return the namespace of the <query> element.
|
||||
delQuery -- Remove the <query> element.
|
||||
reply -- Overrides StanzaBase.reply
|
||||
send -- Overrides StanzaBase.send
|
||||
"""
|
||||
|
||||
namespace = 'jabber:client'
|
||||
name = 'iq'
|
||||
interfaces = set(('type', 'to', 'from', 'id', 'query'))
|
||||
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
|
||||
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 . error import Error
|
||||
from . rootstanza import RootStanza
|
||||
|
||||
from sleekxmpp.stanza import Error
|
||||
from sleekxmpp.stanza.rootstanza import RootStanza
|
||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
|
||||
|
||||
|
||||
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')
|
||||
|
||||
def chat(self):
|
||||
self['type'] = 'chat'
|
||||
return self
|
||||
|
||||
def normal(self):
|
||||
self['type'] = 'normal'
|
||||
return self
|
||||
|
||||
def reply(self, body=None):
|
||||
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):
|
||||
if self['type'] == 'groupchat':
|
||||
return self['from'].bare
|
||||
else:
|
||||
return ''
|
||||
|
||||
def setMucroom(self, value):
|
||||
pass
|
||||
|
||||
def delMucroom(self):
|
||||
pass
|
||||
|
||||
def getMucnick(self):
|
||||
if self['type'] == 'groupchat':
|
||||
return self['from'].resource
|
||||
else:
|
||||
return ''
|
||||
|
||||
def setMucnick(self, value):
|
||||
pass
|
||||
|
||||
def delMucnick(self):
|
||||
pass
|
||||
"""
|
||||
XMPP's <message> stanzas are a "push" mechanism to send information
|
||||
to other XMPP entities without requiring a response.
|
||||
|
||||
Chat clients will typically use <message> stanzas that have a type
|
||||
of either "chat" or "groupchat".
|
||||
|
||||
When handling a message event, be sure to check if the message is
|
||||
an error response.
|
||||
|
||||
Example <message> stanzas:
|
||||
<message to="user1@example.com" from="user2@example.com">
|
||||
<body>Hi!</body>
|
||||
</message>
|
||||
|
||||
<message type="groupchat" to="room@conference.example.com">
|
||||
<body>Hi everyone!</body>
|
||||
</message>
|
||||
|
||||
Stanza Interface:
|
||||
body -- The main contents of the message.
|
||||
subject -- An optional description of the message's contents.
|
||||
mucroom -- (Read-only) The name of the MUC room that sent the message.
|
||||
mucnick -- (Read-only) The MUC nickname of message's sender.
|
||||
|
||||
Attributes:
|
||||
types -- May be one of: normal, chat, headline, groupchat, or error.
|
||||
|
||||
Methods:
|
||||
chat -- Set the message type to 'chat'.
|
||||
normal -- Set the message type to 'normal'.
|
||||
reply -- Overrides StanzaBase.reply
|
||||
getType -- Overrides StanzaBase interface
|
||||
getMucroom -- Return the name of the MUC room of the message.
|
||||
setMucroom -- Dummy method to prevent assignment.
|
||||
delMucroom -- Dummy method to prevent deletion.
|
||||
getMucnick -- Return the MUC nickname of the message's sender.
|
||||
setMucnick -- Dummy method to prevent assignment.
|
||||
delMucnick -- Dummy method to prevent deletion.
|
||||
"""
|
||||
|
||||
namespace = 'jabber:client'
|
||||
name = 'message'
|
||||
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject',
|
||||
'mucroom', 'mucnick'))
|
||||
sub_interfaces = set(('body', 'subject'))
|
||||
plugin_attrib = name
|
||||
types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
|
||||
|
||||
def getType(self):
|
||||
"""
|
||||
Return the message type.
|
||||
|
||||
Overrides default stanza interface behavior.
|
||||
|
||||
Returns 'normal' if no type attribute is present.
|
||||
"""
|
||||
return self._getAttr('type', 'normal')
|
||||
|
||||
def chat(self):
|
||||
"""Set the message type to 'chat'."""
|
||||
self['type'] = 'chat'
|
||||
return self
|
||||
|
||||
def normal(self):
|
||||
"""Set the message type to 'chat'."""
|
||||
self['type'] = 'normal'
|
||||
return self
|
||||
|
||||
def reply(self, body=None):
|
||||
"""
|
||||
Create a message reply.
|
||||
|
||||
Overrides StanzaBase.reply.
|
||||
|
||||
Sets proper 'to' attribute if the message is from a MUC, and
|
||||
adds a message body if one is given.
|
||||
|
||||
Arguments:
|
||||
body -- Optional text content for the message.
|
||||
"""
|
||||
StanzaBase.reply(self)
|
||||
if self['type'] == 'groupchat':
|
||||
self['to'] = self['to'].bare
|
||||
|
||||
del self['id']
|
||||
|
||||
if body is not None:
|
||||
self['body'] = body
|
||||
return self
|
||||
|
||||
def getMucroom(self):
|
||||
"""
|
||||
Return the name of the MUC room where the message originated.
|
||||
|
||||
Read-only stanza interface.
|
||||
"""
|
||||
if self['type'] == 'groupchat':
|
||||
return self['from'].bare
|
||||
else:
|
||||
return ''
|
||||
|
||||
def getMucnick(self):
|
||||
"""
|
||||
Return the nickname of the MUC user that sent the message.
|
||||
|
||||
Read-only stanza interface.
|
||||
"""
|
||||
if self['type'] == 'groupchat':
|
||||
return self['from'].resource
|
||||
else:
|
||||
return ''
|
||||
|
||||
def setMucroom(self, value):
|
||||
"""Dummy method to prevent modification."""
|
||||
pass
|
||||
|
||||
def delMucroom(self):
|
||||
"""Dummy method to prevent deletion."""
|
||||
pass
|
||||
|
||||
def setMucnick(self, value):
|
||||
"""Dummy method to prevent modification."""
|
||||
pass
|
||||
|
||||
def delMucnick(self):
|
||||
"""Dummy method to prevent deletion."""
|
||||
pass
|
||||
|
|
|
@ -3,24 +3,70 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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):
|
||||
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
|
||||
|
||||
def getNick(self):
|
||||
return self.xml.text
|
||||
|
||||
def delNick(self):
|
||||
if self.parent is not None:
|
||||
self.parent().xml.remove(self.xml)
|
||||
"""
|
||||
XEP-0172: User Nickname allows the addition of a <nick> element
|
||||
in several stanza types, including <message> and <presence> stanzas.
|
||||
|
||||
The nickname contained in a <nick> should be the global, friendly or
|
||||
informal name chosen by the owner of a bare JID. The <nick> element
|
||||
may be included when establishing communications with new entities,
|
||||
such as normal XMPP users or MUC services.
|
||||
|
||||
The nickname contained in a <nick> element will not necessarily be
|
||||
the same as the nickname used in a MUC.
|
||||
|
||||
Example stanzas:
|
||||
<message to="user@example.com">
|
||||
<nick xmlns="http://jabber.org/nick/nick">The User</nick>
|
||||
<body>...</body>
|
||||
</message>
|
||||
|
||||
<presence to="otheruser@example.com" type="subscribe">
|
||||
<nick xmlns="http://jabber.org/nick/nick">The User</nick>
|
||||
</presence>
|
||||
|
||||
Stanza Interface:
|
||||
nick -- A global, friendly or informal name chosen by a user.
|
||||
|
||||
Methods:
|
||||
getNick -- Return the nickname in the <nick> element.
|
||||
setNick -- Add a <nick> element with the given nickname.
|
||||
delNick -- Remove the <nick> element.
|
||||
"""
|
||||
|
||||
namespace = 'http://jabber.org/nick/nick'
|
||||
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
|
||||
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 . error import Error
|
||||
from . rootstanza import RootStanza
|
||||
|
||||
from sleekxmpp.stanza import Error
|
||||
from sleekxmpp.stanza.rootstanza import RootStanza
|
||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
|
||||
|
||||
|
||||
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):
|
||||
show = self.getShowElement()
|
||||
if value in self.types:
|
||||
if show is not None:
|
||||
self.xml.remove(show)
|
||||
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
|
||||
Since <presence> stanzas are broadcast when an XMPP entity changes
|
||||
its status, the bulk of the traffic in an XMPP network will be from
|
||||
<presence> stanzas. Therefore, do not include more information than
|
||||
necessary in a status message or within a <presence> stanza in order
|
||||
to help keep the network running smoothly.
|
||||
|
||||
def setPriority(self, value):
|
||||
self._setSubText('priority', text = str(value))
|
||||
|
||||
def getPriority(self):
|
||||
p = self._getSubText('priority')
|
||||
if not p: p = 0
|
||||
return int(p)
|
||||
|
||||
def getType(self):
|
||||
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):
|
||||
if self['type'] == 'unsubscribe':
|
||||
self['type'] = 'unsubscribed'
|
||||
elif self['type'] == 'subscribe':
|
||||
self['type'] = 'subscribed'
|
||||
return StanzaBase.reply(self)
|
||||
Example <presence> stanzas:
|
||||
<presence />
|
||||
|
||||
<presence from="user@example.com">
|
||||
<show>away</show>
|
||||
<status>Getting lunch.</status>
|
||||
<priority>5</priority>
|
||||
</presence>
|
||||
|
||||
<presence type="unavailable" />
|
||||
|
||||
<presence to="user@otherhost.com" type="subscribe" />
|
||||
|
||||
Stanza Interface:
|
||||
priority -- A value used by servers to determine message routing.
|
||||
show -- The type of status, such as away or available for chat.
|
||||
status -- Custom, human readable status message.
|
||||
|
||||
Attributes:
|
||||
types -- One of: available, unavailable, error, probe,
|
||||
subscribe, subscribed, unsubscribe,
|
||||
and unsubscribed.
|
||||
showtypes -- One of: away, chat, dnd, and xa.
|
||||
|
||||
Methods:
|
||||
reply -- Overrides StanzaBase.reply
|
||||
setShow -- Set the value of the <show> element.
|
||||
getType -- Get the value of the type attribute or <show> element.
|
||||
setType -- Set the value of the type attribute or <show> element.
|
||||
getPriority -- Get the value of the <priority> element.
|
||||
setPriority -- Set the value of the <priority> element.
|
||||
"""
|
||||
|
||||
namespace = 'jabber:client'
|
||||
name = 'presence'
|
||||
interfaces = set(('type', 'to', 'from', 'id', 'show',
|
||||
'status', 'priority'))
|
||||
sub_interfaces = set(('show', 'status', 'priority'))
|
||||
plugin_attrib = name
|
||||
|
||||
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
|
||||
'subscribed', 'unsubscribe', 'unsubscribed'))
|
||||
showtypes = set(('dnd', 'chat', 'xa', 'away'))
|
||||
|
||||
def setShow(self, show):
|
||||
"""
|
||||
Set the value of the <show> element.
|
||||
|
||||
Arguments:
|
||||
show -- Must be one of: away, chat, dnd, or xa.
|
||||
"""
|
||||
if show in self.showtypes:
|
||||
self._setSubText('show', text=show)
|
||||
return self
|
||||
|
||||
def setType(self, value):
|
||||
"""
|
||||
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
|
||||
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 . error import Error
|
||||
from .. exceptions import XMPPError
|
||||
|
||||
import logging
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
from sleekxmpp.stanza import Error
|
||||
from sleekxmpp.xmlstream.stanzabase import ET, StanzaBase, registerStanzaPlugin
|
||||
|
||||
|
||||
class RootStanza(StanzaBase):
|
||||
|
||||
def exception(self, e): #called when a handler raises an exception
|
||||
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'].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()
|
||||
"""
|
||||
A top-level XMPP stanza in an XMLStream.
|
||||
|
||||
# all jabber:client root stanzas should have the error plugin
|
||||
RootStanza.plugin_attrib_map['error'] = Error
|
||||
RootStanza.plugin_tag_map["{%s}%s" % (Error.namespace, Error.name)] = Error
|
||||
The RootStanza class provides a more XMPP specific exception
|
||||
handler than provided by the generic StanzaBase class.
|
||||
|
||||
Methods:
|
||||
exception -- Overrides StanzaBase.exception
|
||||
"""
|
||||
|
||||
def exception(self, e):
|
||||
"""
|
||||
Create and send an error reply.
|
||||
|
||||
Typically called when an event handler raises an exception.
|
||||
The error's type and text content are based on the exception
|
||||
object's type and content.
|
||||
|
||||
Overrides StanzaBase.exception.
|
||||
|
||||
Arguments:
|
||||
e -- Exception object
|
||||
"""
|
||||
self.reply()
|
||||
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
|
||||
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):
|
||||
namespace = 'jabber:iq:roster'
|
||||
name = 'query'
|
||||
plugin_attrib = 'roster'
|
||||
interfaces = set(('items',))
|
||||
sub_interfaces = set()
|
||||
|
||||
def setItems(self, items):
|
||||
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]:
|
||||
item.attrib['name'] = items[jid]['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):
|
||||
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):
|
||||
for child in self.xml.getchildren():
|
||||
self.xml.remove(child)
|
||||
"""
|
||||
Example roster stanzas:
|
||||
<iq type="set">
|
||||
<query xmlns="jabber:iq:roster">
|
||||
<item jid="user@example.com" subscription="both" name="User">
|
||||
<group>Friends</group>
|
||||
</item>
|
||||
</query>
|
||||
</iq>
|
||||
|
||||
Stanza Inteface:
|
||||
items -- A dictionary of roster entries contained
|
||||
in the stanza.
|
||||
|
||||
Methods:
|
||||
getItems -- Return a dictionary of roster entries.
|
||||
setItems -- Add <item> elements.
|
||||
delItems -- Remove all <item> elements.
|
||||
"""
|
||||
|
||||
namespace = 'jabber:iq:roster'
|
||||
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.
|
||||
|
||||
SleekXMPP is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
SleekXMPP is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with SleekXMPP; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
@ -34,9 +24,9 @@ class testps(sleekxmpp.ClientXMPP):
|
|||
self.registerPlugin('xep_0030')
|
||||
self.registerPlugin('xep_0060')
|
||||
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_handler("<iq type='error' />", self.handleError)
|
||||
self.add_handler("<iq type='error' />", self.handleError, name='Iq Error')
|
||||
self.events = Queue.Queue()
|
||||
self.default_config = None
|
||||
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
|
||||
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
|
||||
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
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file license.txt for copying permission.
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
class BaseHandler(object):
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file license.txt for copying permission.
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from . import base
|
||||
import logging
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file license.txt for copying permission.
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from . import base
|
||||
try:
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file license.txt for copying permission.
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
import threading
|
||||
from . callback import Callback
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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
|
||||
|
||||
|
|
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
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file license.txt for copying permission.
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
class MatcherBase(object):
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file license.txt for copying permission.
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from . import base
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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 xml.etree import cElementTree
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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 xml.etree import cElementTree
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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 xml.etree import cElementTree
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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 xml.etree import cElementTree
|
||||
|
|
|
@ -3,386 +3,516 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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 traceback
|
||||
import sys
|
||||
import weakref
|
||||
from xml.etree import cElementTree as ET
|
||||
|
||||
if sys.version_info < (3,0):
|
||||
from . import tostring26 as 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
|
||||
from sleekxmpp.xmlstream import JID
|
||||
from sleekxmpp.xmlstream.tostring import tostring
|
||||
|
||||
|
||||
@property
|
||||
def attrib(self): #backwards compatibility
|
||||
return self
|
||||
# Used to check if an argument is an XML object.
|
||||
XML_TYPE = type(ET.Element('xml'))
|
||||
|
||||
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 registerStanzaPlugin(stanza, plugin):
|
||||
"""
|
||||
Associate a stanza object as a plugin for another stanza.
|
||||
|
||||
def __len__(self):
|
||||
return len(self.iterables)
|
||||
|
||||
def append(self, item):
|
||||
if not isinstance(item, ElementBase):
|
||||
if type(item) == xmltester:
|
||||
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)
|
||||
Arguments:
|
||||
stanza -- The class of the parent stanza.
|
||||
plugin -- The class of the plugin stanza.
|
||||
"""
|
||||
tag = "{%s}%s" % (plugin.namespace, plugin.name)
|
||||
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
|
||||
stanza.plugin_tag_map[tag] = plugin
|
||||
|
||||
def findall(self, xpath):
|
||||
return self.xml.findall(xpath)
|
||||
|
||||
def setup(self, xml=None):
|
||||
if self.xml is None:
|
||||
self.xml = xml
|
||||
if self.xml is None:
|
||||
for ename in self.name.split('/'):
|
||||
new = ET.Element("{%(namespace)s}%(name)s" % {'name': self.name, 'namespace': self.namespace})
|
||||
if self.xml is None:
|
||||
self.xml = new
|
||||
else:
|
||||
self.xml.append(new)
|
||||
if self.parent is not None:
|
||||
self.parent().xml.append(self.xml)
|
||||
return True #had to generate XML
|
||||
else:
|
||||
return False
|
||||
|
||||
def enable(self, attrib):
|
||||
self.initPlugin(attrib)
|
||||
return self
|
||||
|
||||
def initPlugin(self, attrib):
|
||||
if attrib not in self.plugins:
|
||||
self.plugins[attrib] = self.plugin_attrib_map[attrib](parent=self)
|
||||
|
||||
def __getitem__(self, attrib):
|
||||
if attrib == 'substanzas':
|
||||
return self.iterables
|
||||
elif attrib in self.interfaces:
|
||||
if hasattr(self, "get%s" % attrib.title()):
|
||||
return getattr(self, "get%s" % attrib.title())()
|
||||
else:
|
||||
if attrib in self.sub_interfaces:
|
||||
return self._getSubText(attrib)
|
||||
else:
|
||||
return self._getAttr(attrib)
|
||||
elif attrib in self.plugin_attrib_map:
|
||||
if attrib not in self.plugins: self.initPlugin(attrib)
|
||||
return self.plugins[attrib]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def __setitem__(self, attrib, value):
|
||||
if attrib in self.interfaces:
|
||||
if value is not None:
|
||||
if hasattr(self, "set%s" % attrib.title()):
|
||||
getattr(self, "set%s" % attrib.title())(value,)
|
||||
else:
|
||||
if attrib in self.sub_interfaces:
|
||||
return self._setSubText(attrib, text=value)
|
||||
else:
|
||||
self._setAttr(attrib, value)
|
||||
else:
|
||||
self.__delitem__(attrib)
|
||||
elif attrib in self.plugin_attrib_map:
|
||||
if attrib not in self.plugins: self.initPlugin(attrib)
|
||||
self.initPlugin(attrib)
|
||||
self.plugins[attrib][attrib] = value
|
||||
return self
|
||||
|
||||
def __delitem__(self, attrib):
|
||||
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.getValues()
|
||||
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):
|
||||
return self.xml.attrib.get(name, '')
|
||||
|
||||
def _getSubText(self, name):
|
||||
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):
|
||||
if text is None or text == '':
|
||||
return self.__delitem__(name)
|
||||
stanza = self.xml.find("{%s}%s" % (self.namespace, name))
|
||||
if stanza is None:
|
||||
#self.xml.append(ET.Element("{%s}%s" % (self.namespace, name), attrib))
|
||||
stanza = ET.Element("{%s}%s" % (self.namespace, name))
|
||||
self.xml.append(stanza)
|
||||
stanza.text = text
|
||||
return stanza
|
||||
|
||||
def _delSub(self, name):
|
||||
for child in self.xml.getchildren():
|
||||
if child.tag == "{%s}%s" % (self.namespace, name):
|
||||
self.xml.remove(child)
|
||||
|
||||
def getValues(self):
|
||||
out = {}
|
||||
for interface in self.interfaces:
|
||||
out[interface] = self[interface]
|
||||
for pluginkey in self.plugins:
|
||||
out[pluginkey] = self.plugins[pluginkey].getValues()
|
||||
if self.iterables:
|
||||
iterables = []
|
||||
for stanza in self.iterables:
|
||||
iterables.append(stanza.getValues())
|
||||
iterables[-1].update({'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)})
|
||||
out['substanzas'] = iterables
|
||||
return out
|
||||
|
||||
def setValues(self, attrib):
|
||||
for interface in attrib:
|
||||
if interface == 'substanzas':
|
||||
for subdict in attrib['substanzas']:
|
||||
if '__childtag__' in subdict:
|
||||
for subclass in self.subitem:
|
||||
if subdict['__childtag__'] == "{%s}%s" % (subclass.namespace, subclass.name):
|
||||
sub = subclass(parent=self)
|
||||
sub.setValues(subdict)
|
||||
self.iterables.append(sub)
|
||||
break
|
||||
elif interface in self.interfaces:
|
||||
self[interface] = attrib[interface]
|
||||
elif interface in self.plugin_attrib_map and interface not in self.plugins:
|
||||
self.initPlugin(interface)
|
||||
if interface in self.plugins:
|
||||
self.plugins[interface].setValues(attrib[interface])
|
||||
return self
|
||||
|
||||
def appendxml(self, xml):
|
||||
self.xml.append(xml)
|
||||
return self
|
||||
|
||||
#def __del__(self): #prevents garbage collection of reference cycle
|
||||
# if self.parent is not None:
|
||||
# self.parent.xml.remove(self.xml)
|
||||
class ElementBase(object):
|
||||
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):
|
||||
"""
|
||||
Create a new stanza object.
|
||||
|
||||
Arguments:
|
||||
xml -- Initialize the stanza with optional existing XML.
|
||||
parent -- Optional stanza object that contains this stanza.
|
||||
"""
|
||||
self.xml = xml
|
||||
self.plugins = {}
|
||||
self.iterables = []
|
||||
self.idx = 0
|
||||
if parent is None:
|
||||
self.parent = None
|
||||
else:
|
||||
self.parent = weakref.ref(parent)
|
||||
|
||||
if self.setup(xml):
|
||||
# If we generated our own XML, then everything is ready.
|
||||
return
|
||||
|
||||
# Initialize values using provided XML
|
||||
for child in self.xml.getchildren():
|
||||
if child.tag in self.plugin_tag_map:
|
||||
plugin = self.plugin_tag_map[child.tag]
|
||||
self.plugins[plugin.plugin_attrib] = plugin(child, 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(child, self))
|
||||
break
|
||||
|
||||
def setup(self, xml=None):
|
||||
"""
|
||||
Initialize the stanza's XML contents.
|
||||
|
||||
Will return True if XML was generated according to the stanza's
|
||||
definition.
|
||||
|
||||
Arguments:
|
||||
xml -- Optional XML object to use for the stanza's content
|
||||
instead of generating XML.
|
||||
"""
|
||||
if self.xml is None:
|
||||
self.xml = xml
|
||||
|
||||
if self.xml is None:
|
||||
# Generate XML from the stanza definition
|
||||
for ename in self.name.split('/'):
|
||||
new = ET.Element("{%s}%s" % (self.namespace, ename))
|
||||
if self.xml is None:
|
||||
self.xml = new
|
||||
else:
|
||||
last_xml.append(new)
|
||||
last_xml = new
|
||||
if self.parent is not None:
|
||||
self.parent().xml.append(self.xml)
|
||||
|
||||
# We had to generate XML
|
||||
return True
|
||||
else:
|
||||
# We did not generate XML
|
||||
return False
|
||||
|
||||
def enable(self, attrib):
|
||||
"""
|
||||
Enable and initialize a stanza plugin.
|
||||
|
||||
Alias for initPlugin.
|
||||
|
||||
Arguments:
|
||||
attrib -- The stanza interface for the plugin.
|
||||
"""
|
||||
return self.initPlugin(attrib)
|
||||
|
||||
def initPlugin(self, attrib):
|
||||
"""
|
||||
Enable and initialize a stanza plugin.
|
||||
|
||||
Arguments:
|
||||
attrib -- The stanza interface for the plugin.
|
||||
"""
|
||||
if attrib not in self.plugins:
|
||||
plugin_class = self.plugin_attrib_map[attrib]
|
||||
self.plugins[attrib] = plugin_class(parent=self)
|
||||
return self
|
||||
|
||||
def getStanzaValues(self):
|
||||
"""
|
||||
Return a dictionary of the stanza's interface values.
|
||||
|
||||
Stanza plugin values are included as nested dictionaries.
|
||||
"""
|
||||
values = {}
|
||||
for interface in self.interfaces:
|
||||
values[interface] = self[interface]
|
||||
for plugin, stanza in self.plugins.items():
|
||||
values[plugin] = stanza.getStanzaValues()
|
||||
if self.iterables:
|
||||
iterables = []
|
||||
for stanza in self.iterables:
|
||||
iterables.append(stanza.getStanzaValues())
|
||||
iterables[-1].update({
|
||||
'__childtag__': "{%s}%s" % (stanza.namespace, stanza.name)
|
||||
})
|
||||
values['substanzas'] = iterables
|
||||
return values
|
||||
|
||||
def setStanzaValues(self, values):
|
||||
"""
|
||||
Set multiple stanza interface values using a dictionary.
|
||||
|
||||
Stanza plugin values may be set using nested dictionaries.
|
||||
|
||||
Arguments:
|
||||
values -- A dictionary mapping stanza interface with values.
|
||||
Plugin interfaces may accept a nested dictionary that
|
||||
will be used recursively.
|
||||
"""
|
||||
for interface, value in values.items():
|
||||
if interface == 'substanzas':
|
||||
for subdict in value:
|
||||
if '__childtag__' in subdict:
|
||||
for subclass in self.subitem:
|
||||
child_tag = "{%s}%s" % (subclass.namespace,
|
||||
subclass.name)
|
||||
if subdict['__childtag__'] == child_tag:
|
||||
sub = subclass(parent=self)
|
||||
sub.setStanzaValues(subdict)
|
||||
self.iterables.append(sub)
|
||||
break
|
||||
elif interface in self.interfaces:
|
||||
self[interface] = value
|
||||
elif interface in self.plugin_attrib_map:
|
||||
if interface not in self.plugins:
|
||||
self.initPlugin(interface)
|
||||
self.plugins[interface].setStanzaValues(value)
|
||||
return self
|
||||
|
||||
def __getitem__(self, attrib):
|
||||
"""
|
||||
Return the value of a stanza interface using dictionary-like syntax.
|
||||
|
||||
Example:
|
||||
>>> msg['body']
|
||||
'Message contents'
|
||||
|
||||
Stanza interfaces are typically mapped directly to the underlying XML
|
||||
object, but can be overridden by the presence of a getAttrib method
|
||||
(or getFoo where the interface is named foo, etc).
|
||||
|
||||
The search order for interface value retrieval for an interface
|
||||
named 'foo' is:
|
||||
1. The list of substanzas.
|
||||
2. The result of calling getFoo.
|
||||
3. The contents of the foo subelement, if foo is a sub interface.
|
||||
4. The value of the foo attribute of the XML object.
|
||||
5. The plugin named 'foo'
|
||||
6. An empty string.
|
||||
|
||||
Arguments:
|
||||
attrib -- The name of the requested stanza interface.
|
||||
"""
|
||||
if attrib == 'substanzas':
|
||||
return self.iterables
|
||||
elif attrib in self.interfaces:
|
||||
get_method = "get%s" % attrib.title()
|
||||
if hasattr(self, get_method):
|
||||
return getattr(self, get_method)()
|
||||
else:
|
||||
if attrib in self.sub_interfaces:
|
||||
return self._getSubText(attrib)
|
||||
else:
|
||||
return self._getAttr(attrib)
|
||||
elif attrib in self.plugin_attrib_map:
|
||||
if attrib not in self.plugins:
|
||||
self.initPlugin(attrib)
|
||||
return self.plugins[attrib]
|
||||
else:
|
||||
return ''
|
||||
|
||||
def __setitem__(self, attrib, value):
|
||||
"""
|
||||
Set the value of a stanza interface using dictionary-like syntax.
|
||||
|
||||
Example:
|
||||
>>> msg['body'] = "Hi!"
|
||||
>>> msg['body']
|
||||
'Hi!'
|
||||
|
||||
Stanza interfaces are typically mapped directly to the underlying XML
|
||||
object, but can be overridden by the presence of a setAttrib method
|
||||
(or setFoo where the interface is named foo, etc).
|
||||
|
||||
The effect of interface value assignment for an interface
|
||||
named 'foo' will be one of:
|
||||
1. Delete the interface's contents if the value is None.
|
||||
2. Call setFoo, if it exists.
|
||||
3. Set the text of a foo element, if foo is in sub_interfaces.
|
||||
4. Set the value of a top level XML attribute name foo.
|
||||
5. Attempt to pass value to a plugin named foo using the plugin's
|
||||
foo interface.
|
||||
6. Do nothing.
|
||||
|
||||
Arguments:
|
||||
attrib -- The name of the stanza interface to modify.
|
||||
value -- The new value of the stanza interface.
|
||||
"""
|
||||
if attrib in self.interfaces:
|
||||
if value is not None:
|
||||
if hasattr(self, "set%s" % attrib.title()):
|
||||
getattr(self, "set%s" % attrib.title())(value,)
|
||||
else:
|
||||
if attrib in self.sub_interfaces:
|
||||
return self._setSubText(attrib, text=value)
|
||||
else:
|
||||
self._setAttr(attrib, value)
|
||||
else:
|
||||
self.__delitem__(attrib)
|
||||
elif attrib in self.plugin_attrib_map:
|
||||
if attrib not in self.plugins:
|
||||
self.initPlugin(attrib)
|
||||
self.plugins[attrib][attrib] = value
|
||||
return self
|
||||
|
||||
@property
|
||||
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):
|
||||
name = 'stanza'
|
||||
namespace = 'jabber:client'
|
||||
interfaces = set(('type', 'to', 'from', 'id', 'payload'))
|
||||
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
|
||||
sub_interfaces = tuple()
|
||||
name = 'stanza'
|
||||
namespace = 'jabber:client'
|
||||
interfaces = set(('type', 'to', 'from', 'id', 'payload'))
|
||||
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
|
||||
sub_interfaces = tuple()
|
||||
|
||||
def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None):
|
||||
self.stream = stream
|
||||
if stream is not None:
|
||||
self.namespace = stream.default_ns
|
||||
ElementBase.__init__(self, xml)
|
||||
if stype is not None:
|
||||
self['type'] = stype
|
||||
if sto is not None:
|
||||
self['to'] = sto
|
||||
if sfrom is not None:
|
||||
self['from'] = sfrom
|
||||
self.tag = "{%s}%s" % (self.namespace, self.name)
|
||||
|
||||
def setType(self, value):
|
||||
if value in self.types:
|
||||
self.xml.attrib['type'] = value
|
||||
return self
|
||||
def __init__(self, stream=None, xml=None, stype=None, sto=None, sfrom=None, sid=None):
|
||||
self.stream = stream
|
||||
if stream is not None:
|
||||
self.namespace = stream.default_ns
|
||||
ElementBase.__init__(self, xml)
|
||||
if stype is not None:
|
||||
self['type'] = stype
|
||||
if sto is not None:
|
||||
self['to'] = sto
|
||||
if sfrom is not None:
|
||||
self['from'] = sfrom
|
||||
self.tag = "{%s}%s" % (self.namespace, self.name)
|
||||
|
||||
def getPayload(self):
|
||||
return self.xml.getchildren()
|
||||
|
||||
def setPayload(self, value):
|
||||
self.xml.append(value)
|
||||
return self
|
||||
|
||||
def delPayload(self):
|
||||
self.clear()
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
for child in self.xml.getchildren():
|
||||
self.xml.remove(child)
|
||||
for plugin in list(self.plugins.keys()):
|
||||
del self.plugins[plugin]
|
||||
return self
|
||||
|
||||
def reply(self):
|
||||
self['from'], self['to'] = self['to'], self['from']
|
||||
self.clear()
|
||||
return self
|
||||
|
||||
def error(self):
|
||||
self['type'] = 'error'
|
||||
return self
|
||||
|
||||
def getTo(self):
|
||||
return JID(self._getAttr('to'))
|
||||
|
||||
def setTo(self, value):
|
||||
return self._setAttr('to', str(value))
|
||||
|
||||
def getFrom(self):
|
||||
return JID(self._getAttr('from'))
|
||||
|
||||
def setFrom(self, value):
|
||||
return self._setAttr('from', str(value))
|
||||
|
||||
def unhandled(self):
|
||||
pass
|
||||
|
||||
def exception(self, e):
|
||||
logging.error(traceback.format_tb(e))
|
||||
|
||||
def send(self):
|
||||
self.stream.sendRaw(self.__str__())
|
||||
def setType(self, value):
|
||||
if value in self.types:
|
||||
self.xml.attrib['type'] = value
|
||||
return self
|
||||
|
||||
def getPayload(self):
|
||||
return self.xml.getchildren()
|
||||
|
||||
def setPayload(self, value):
|
||||
self.xml.append(value)
|
||||
return self
|
||||
|
||||
def delPayload(self):
|
||||
self.clear()
|
||||
return self
|
||||
|
||||
def clear(self):
|
||||
for child in self.xml.getchildren():
|
||||
self.xml.remove(child)
|
||||
for plugin in list(self.plugins.keys()):
|
||||
del self.plugins[plugin]
|
||||
return self
|
||||
|
||||
def reply(self):
|
||||
# if it's a component, use from
|
||||
if self.stream and hasattr(self.stream, "is_component") and self.stream.is_component:
|
||||
self['from'], self['to'] = self['to'], self['from']
|
||||
else:
|
||||
self['to'] = self['from']
|
||||
del self['from']
|
||||
self.clear()
|
||||
return self
|
||||
|
||||
def error(self):
|
||||
self['type'] = 'error'
|
||||
return self
|
||||
|
||||
def getTo(self):
|
||||
return JID(self._getAttr('to'))
|
||||
|
||||
def setTo(self, value):
|
||||
return self._setAttr('to', str(value))
|
||||
|
||||
def getFrom(self):
|
||||
return JID(self._getAttr('from'))
|
||||
|
||||
def setFrom(self, value):
|
||||
return self._setAttr('from', str(value))
|
||||
|
||||
def unhandled(self):
|
||||
pass
|
||||
|
||||
def exception(self, e):
|
||||
logging.exception('Error handling {%s}%s stanza' % (self.namespace, self.name))
|
||||
|
||||
def send(self):
|
||||
self.stream.sendRaw(self.__str__())
|
||||
|
||||
def __copy__(self):
|
||||
return self.__class__(xml=copy.deepcopy(self.xml), stream=self.stream)
|
||||
|
||||
def __str__(self):
|
||||
return tostring(self.xml, xmlns='', stanza_ns=self.namespace, stream=self.stream)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
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
|
||||
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):
|
||||
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)
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
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] = '''
|
||||
else:
|
||||
text[cc] = '"'
|
||||
cc += 1
|
||||
return ''.join(text)
|
||||
import sys
|
||||
|
||||
# Import the correct tostring and xml_escape functions based on the Python
|
||||
# version in order to properly handle Unicode.
|
||||
|
||||
if sys.version_info < (3, 0):
|
||||
from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape
|
||||
else:
|
||||
from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape
|
||||
|
||||
__all__ = ['tostring', 'xml_escape']
|
||||
|
|
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
|
||||
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
|
||||
|
@ -19,11 +19,13 @@ import logging
|
|||
import socket
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
import types
|
||||
import copy
|
||||
import xml.sax.saxutils
|
||||
from . import scheduler
|
||||
from sleekxmpp.xmlstream.tostring import tostring
|
||||
|
||||
RESPONSE_TIMEOUT = 10
|
||||
HANDLER_THREADS = 1
|
||||
|
||||
ssl_support = True
|
||||
|
@ -36,7 +38,7 @@ if sys.version_info < (3, 0):
|
|||
#monkey patch broken filesocket object
|
||||
from . import filesocket
|
||||
#socket._fileobject = filesocket.filesocket
|
||||
|
||||
|
||||
|
||||
class RestartStream(Exception):
|
||||
pass
|
||||
|
@ -71,6 +73,7 @@ class XMLStream(object):
|
|||
self.use_ssl = False
|
||||
self.use_tls = False
|
||||
|
||||
self.default_ns = ''
|
||||
self.stream_header = "<stream>"
|
||||
self.stream_footer = "</stream>"
|
||||
|
||||
|
@ -81,7 +84,7 @@ class XMLStream(object):
|
|||
self.namespace_map = {}
|
||||
|
||||
self.run = True
|
||||
|
||||
|
||||
def setSocket(self, socket):
|
||||
"Set the socket"
|
||||
self.socket = socket
|
||||
|
@ -89,10 +92,10 @@ class XMLStream(object):
|
|||
self.filesocket = socket.makefile('rb', 0) # ElementTree.iterparse requires a file. 0 buffer files have to be binary
|
||||
self.state.set('connected', True)
|
||||
|
||||
|
||||
|
||||
def setFileSocket(self, filesocket):
|
||||
self.filesocket = filesocket
|
||||
|
||||
|
||||
def connect(self, host='', port=0, use_ssl=False, use_tls=True):
|
||||
"Link to connectTCP"
|
||||
return self.connectTCP(host, port, use_ssl, use_tls)
|
||||
|
@ -124,7 +127,7 @@ class XMLStream(object):
|
|||
except socket.error as serr:
|
||||
logging.error("Could not connect. Socket Error #%s: %s" % (serr.errno, serr.strerror))
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def connectUnix(self, filepath):
|
||||
"Connect to Unix file and create socket"
|
||||
|
||||
|
@ -145,7 +148,7 @@ class XMLStream(object):
|
|||
logging.warning("Tried to enable TLS, but ssl module not found.")
|
||||
return False
|
||||
raise RestartStream()
|
||||
|
||||
|
||||
def process(self, threaded=True):
|
||||
self.scheduler.process(threaded=True)
|
||||
for t in range(0, HANDLER_THREADS):
|
||||
|
@ -159,10 +162,10 @@ class XMLStream(object):
|
|||
self.__thread['process'].start()
|
||||
else:
|
||||
self._process()
|
||||
|
||||
|
||||
def schedule(self, name, seconds, callback, args=None, kwargs=None, repeat=False):
|
||||
self.scheduler.add(name, seconds, callback, args, kwargs, repeat, qpointer=self.eventqueue)
|
||||
|
||||
|
||||
def _process(self):
|
||||
"Start processing the socket."
|
||||
firstrun = True
|
||||
|
@ -194,14 +197,14 @@ class XMLStream(object):
|
|||
return
|
||||
else:
|
||||
self.state.set('processing', False)
|
||||
traceback.print_exc()
|
||||
logging.exception('Socket Error')
|
||||
self.disconnect(reconnect=True)
|
||||
except:
|
||||
if not self.state.reconnect:
|
||||
return
|
||||
else:
|
||||
self.state.set('processing', False)
|
||||
traceback.print_exc()
|
||||
logging.exception('Connection error. Reconnecting.')
|
||||
self.disconnect(reconnect=True)
|
||||
if self.state['reconnect']:
|
||||
self.reconnect()
|
||||
|
@ -211,7 +214,7 @@ class XMLStream(object):
|
|||
#self.__thread['readXML'].start()
|
||||
#self.__thread['spawnEvents'] = threading.Thread(name='spawnEvents', target=self.__spawnEvents)
|
||||
#self.__thread['spawnEvents'].start()
|
||||
|
||||
|
||||
def __readXML(self):
|
||||
"Parses the incoming stream, adding to xmlin queue as it goes"
|
||||
#build cElementTree object from expat was we go
|
||||
|
@ -244,7 +247,7 @@ class XMLStream(object):
|
|||
if event == b'start':
|
||||
edepth += 1
|
||||
logging.debug("Ending readXML loop")
|
||||
|
||||
|
||||
def _sendThread(self):
|
||||
while self.run:
|
||||
data = self.sendqueue.get(True)
|
||||
|
@ -257,14 +260,13 @@ class XMLStream(object):
|
|||
logging.warning("Failed to send %s" % data)
|
||||
self.state.set('connected', False)
|
||||
if self.state.reconnect:
|
||||
logging.error("Disconnected. Socket Error.")
|
||||
traceback.print_exc()
|
||||
logging.exception("Disconnected. Socket Error.")
|
||||
self.disconnect(reconnect=True)
|
||||
|
||||
|
||||
def sendRaw(self, data):
|
||||
self.sendqueue.put(data)
|
||||
return True
|
||||
|
||||
|
||||
def disconnect(self, reconnect=False):
|
||||
self.state.set('reconnect', reconnect)
|
||||
if self.state['disconnecting']:
|
||||
|
@ -290,41 +292,40 @@ class XMLStream(object):
|
|||
if self.state['processing']:
|
||||
#raise CloseStream
|
||||
pass
|
||||
|
||||
|
||||
def reconnect(self):
|
||||
self.state.set('tls',False)
|
||||
self.state.set('ssl',False)
|
||||
time.sleep(1)
|
||||
self.connect()
|
||||
|
||||
|
||||
def incoming_filter(self, xmlobj):
|
||||
return xmlobj
|
||||
|
||||
|
||||
def __spawnEvent(self, xmlobj):
|
||||
"watching xmlOut and processes handlers"
|
||||
#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)
|
||||
stanza = None
|
||||
stanza_type = StanzaBase
|
||||
for stanza_class in self.__root_stanza:
|
||||
if xmlobj.tag == "{%s}%s" % (self.default_ns, stanza_class.name):
|
||||
#if self.__root_stanza[stanza_class].match(xmlobj):
|
||||
stanza = stanza_class(self, xmlobj)
|
||||
stanza_type = stanza_class
|
||||
break
|
||||
if stanza is None:
|
||||
stanza = StanzaBase(self, xmlobj)
|
||||
unhandled = True
|
||||
stanza = stanza_type(self, xmlobj)
|
||||
for handler in self.__handlers:
|
||||
if handler.match(stanza):
|
||||
handler.prerun(stanza)
|
||||
self.eventqueue.put(('stanza', handler, stanza))
|
||||
stanza_copy = stanza_type(self, copy.deepcopy(xmlobj))
|
||||
handler.prerun(stanza_copy)
|
||||
self.eventqueue.put(('stanza', handler, stanza_copy))
|
||||
if handler.checkDelete(): self.__handlers.pop(self.__handlers.index(handler))
|
||||
unhandled = False
|
||||
if unhandled:
|
||||
stanza.unhandled()
|
||||
#loop through handlers and test match
|
||||
#spawn threads as necessary, call handlers, sending Stanza
|
||||
|
||||
|
||||
def _eventRunner(self):
|
||||
logging.debug("Loading event runner")
|
||||
while self.run:
|
||||
|
@ -344,22 +345,22 @@ class XMLStream(object):
|
|||
try:
|
||||
handler.run(args[0])
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
logging.exception('Error processing event handler: %s' % handler.name)
|
||||
args[0].exception(e)
|
||||
elif etype == 'schedule':
|
||||
try:
|
||||
logging.debug(args)
|
||||
handler(*args[0])
|
||||
except:
|
||||
logging.error(traceback.format_exc())
|
||||
logging.exception('Error processing scheduled task')
|
||||
elif etype == 'quit':
|
||||
logging.debug("Quitting eventRunner thread")
|
||||
return False
|
||||
|
||||
|
||||
def registerHandler(self, handler, before=None, after=None):
|
||||
"Add handler with matcher class and parameters."
|
||||
self.__handlers.append(handler)
|
||||
|
||||
|
||||
def removeHandler(self, name):
|
||||
"Removes the handler."
|
||||
idx = 0
|
||||
|
@ -368,81 +369,27 @@ class XMLStream(object):
|
|||
self.__handlers.pop(idx)
|
||||
return
|
||||
idx += 1
|
||||
|
||||
|
||||
def registerStanza(self, stanza_class):
|
||||
"Adds stanza. If root stanzas build stanzas sent in events while non-root stanzas build substanza objects."
|
||||
self.__root_stanza.append(stanza_class)
|
||||
|
||||
|
||||
def registerStanzaExtension(self, stanza_class, stanza_extension):
|
||||
if stanza_class not in stanza_extensions:
|
||||
stanza_extensions[stanza_class] = [stanza_extension]
|
||||
else:
|
||||
stanza_extensions[stanza_class].append(stanza_extension)
|
||||
|
||||
|
||||
def removeStanza(self, stanza_class, root=False):
|
||||
"Removes the stanza's registration."
|
||||
if root:
|
||||
del self.__root_stanza[stanza_class]
|
||||
else:
|
||||
del self.__stanza[stanza_class]
|
||||
|
||||
|
||||
def removeStanzaExtension(self, stanza_class, 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):
|
||||
"""Meant to be overridden"""
|
||||
pass
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/python2.6
|
||||
#!/usr/bin/env python
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
@ -21,7 +21,7 @@ class testoverall(unittest.TestCase):
|
|||
self.failIf(tabnanny.check("." + os.sep + 'sleekxmpp'))
|
||||
#raise "Help!"
|
||||
|
||||
def testMethodLength(self):
|
||||
def disabled_testMethodLength(self):
|
||||
"""Testing for excessive method lengths"""
|
||||
import re
|
||||
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 xml.etree import cElementTree as ET
|
||||
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
|
||||
from . import xmlcompare
|
||||
from . sleektest import *
|
||||
import sleekxmpp.plugins.xep_0030 as xep_0030
|
||||
|
||||
import sleekxmpp.plugins.xep_0030 as sd
|
||||
|
||||
def stanzaPlugin(stanza, plugin):
|
||||
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
|
||||
stanza.plugin_tag_map["{%s}%s" % (plugin.namespace, plugin.name)] = plugin
|
||||
|
||||
class testdisco(unittest.TestCase):
|
||||
class TestDisco(SleekTest):
|
||||
|
||||
def setUp(self):
|
||||
self.sd = sd
|
||||
stanzaPlugin(self.sd.Iq, self.sd.DiscoInfo)
|
||||
stanzaPlugin(self.sd.Iq, self.sd.DiscoItems)
|
||||
registerStanzaPlugin(Iq, xep_0030.DiscoInfo)
|
||||
registerStanzaPlugin(Iq, xep_0030.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):
|
||||
"""Testing disco#info query with no node."""
|
||||
iq = self.sd.Iq()
|
||||
iq = self.Iq()
|
||||
iq['id'] = "0"
|
||||
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):
|
||||
"""Testing disco#info query with a node."""
|
||||
iq = self.sd.Iq()
|
||||
iq = self.Iq()
|
||||
iq['id'] = "0"
|
||||
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):
|
||||
"""Testing disco#items query with no node."""
|
||||
iq = self.sd.Iq()
|
||||
iq = self.Iq()
|
||||
iq['id'] = "0"
|
||||
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):
|
||||
"""Testing disco#items query with a node."""
|
||||
iq = self.sd.Iq()
|
||||
iq = self.Iq()
|
||||
iq['id'] = "0"
|
||||
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):
|
||||
"""Testing adding identities to disco#info."""
|
||||
iq = self.sd.Iq()
|
||||
iq = self.Iq()
|
||||
iq['id'] = "0"
|
||||
iq['disco_info']['node'] = 'foo'
|
||||
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)
|
||||
iq['disco_info'].addIdentity('conference', 'text', 'Chatroom')
|
||||
|
||||
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):
|
||||
"""Testing adding features to disco#info."""
|
||||
iq = self.sd.Iq()
|
||||
iq = self.Iq()
|
||||
iq['id'] = "0"
|
||||
iq['disco_info']['node'] = 'foo'
|
||||
iq['disco_info'].addFeature('foo')
|
||||
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)
|
||||
iq['disco_info'].addFeature('foo')
|
||||
iq['disco_info'].addFeature('bar')
|
||||
|
||||
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):
|
||||
"""Testing adding features to disco#info."""
|
||||
iq = self.sd.Iq()
|
||||
iq = self.Iq()
|
||||
iq['id'] = "0"
|
||||
iq['disco_items']['node'] = 'foo'
|
||||
iq['disco_items'].addItem('user@localhost')
|
||||
iq['disco_items'].addItem('user@localhost', 'foo')
|
||||
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)
|
||||
iq['disco_items'].addItem('user@localhost')
|
||||
iq['disco_items'].addItem('user@localhost', 'foo')
|
||||
iq['disco_items'].addItem('user@localhost', 'bar', 'Testing')
|
||||
|
||||
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):
|
||||
"""Test adding and removing identities to disco#info stanza"""
|
||||
ids = [('automation', 'commands', 'AdHoc'),
|
||||
('conference', 'text', 'ChatRoom')]
|
||||
ids = [('automation', 'commands', 'AdHoc'),
|
||||
('conference', 'text', 'ChatRoom')]
|
||||
|
||||
info = self.sd.DiscoInfo()
|
||||
info.addIdentity(*ids[0])
|
||||
self.failUnless(info.getIdentities() == [ids[0]])
|
||||
info = xep_0030.DiscoInfo()
|
||||
info.addIdentity(*ids[0])
|
||||
self.failUnless(info.getIdentities() == [ids[0]])
|
||||
|
||||
info.delIdentity('automation', 'commands')
|
||||
self.failUnless(info.getIdentities() == [])
|
||||
info.delIdentity('automation', 'commands')
|
||||
self.failUnless(info.getIdentities() == [])
|
||||
|
||||
info.setIdentities(ids)
|
||||
self.failUnless(info.getIdentities() == ids)
|
||||
info.setIdentities(ids)
|
||||
self.failUnless(info.getIdentities() == ids)
|
||||
|
||||
info.delIdentity('automation', 'commands')
|
||||
self.failUnless(info.getIdentities() == [ids[1]])
|
||||
info.delIdentity('automation', 'commands')
|
||||
self.failUnless(info.getIdentities() == [ids[1]])
|
||||
|
||||
info.delIdentities()
|
||||
self.failUnless(info.getIdentities() == [])
|
||||
info.delIdentities()
|
||||
self.failUnless(info.getIdentities() == [])
|
||||
|
||||
def testAddRemoveFeatures(self):
|
||||
"""Test adding and removing features to disco#info stanza"""
|
||||
features = ['foo', 'bar', 'baz']
|
||||
features = ['foo', 'bar', 'baz']
|
||||
|
||||
info = self.sd.DiscoInfo()
|
||||
info.addFeature(features[0])
|
||||
self.failUnless(info.getFeatures() == [features[0]])
|
||||
info = xep_0030.DiscoInfo()
|
||||
info.addFeature(features[0])
|
||||
self.failUnless(info.getFeatures() == [features[0]])
|
||||
|
||||
info.delFeature('foo')
|
||||
self.failUnless(info.getFeatures() == [])
|
||||
info.delFeature('foo')
|
||||
self.failUnless(info.getFeatures() == [])
|
||||
|
||||
info.setFeatures(features)
|
||||
self.failUnless(info.getFeatures() == features)
|
||||
info.setFeatures(features)
|
||||
self.failUnless(info.getFeatures() == features)
|
||||
|
||||
info.delFeature('bar')
|
||||
self.failUnless(info.getFeatures() == ['foo', 'baz'])
|
||||
info.delFeature('bar')
|
||||
self.failUnless(info.getFeatures() == ['foo', 'baz'])
|
||||
|
||||
info.delFeatures()
|
||||
self.failUnless(info.getFeatures() == [])
|
||||
info.delFeatures()
|
||||
self.failUnless(info.getFeatures() == [])
|
||||
|
||||
def testAddRemoveItems(self):
|
||||
"""Test adding and removing items to disco#items stanza"""
|
||||
items = [('user@localhost', None, None),
|
||||
('user@localhost', 'foo', None),
|
||||
('user@localhost', 'bar', 'Test')]
|
||||
items = [('user@localhost', None, None),
|
||||
('user@localhost', 'foo', None),
|
||||
('user@localhost', 'bar', 'Test')]
|
||||
|
||||
info = self.sd.DiscoItems()
|
||||
self.failUnless(True, ""+str(items[0]))
|
||||
info = xep_0030.DiscoItems()
|
||||
self.failUnless(True, ""+str(items[0]))
|
||||
|
||||
info.addItem(*(items[0]))
|
||||
self.failUnless(info.getItems() == [items[0]], info.getItems())
|
||||
info.addItem(*(items[0]))
|
||||
self.failUnless(info.getItems() == [items[0]], info.getItems())
|
||||
|
||||
info.delItem('user@localhost')
|
||||
self.failUnless(info.getItems() == [])
|
||||
info.delItem('user@localhost')
|
||||
self.failUnless(info.getItems() == [])
|
||||
|
||||
info.setItems(items)
|
||||
self.failUnless(info.getItems() == items)
|
||||
info.setItems(items)
|
||||
self.failUnless(info.getItems() == items)
|
||||
|
||||
info.delItem('user@localhost', 'foo')
|
||||
self.failUnless(info.getItems() == [items[0], items[2]])
|
||||
info.delItem('user@localhost', 'foo')
|
||||
self.failUnless(info.getItems() == [items[0], items[2]])
|
||||
|
||||
info.delItems()
|
||||
self.failUnless(info.getItems() == [])
|
||||
|
||||
info.delItems()
|
||||
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):
|
||||
import sleekxmpp.stanza.presence as p
|
||||
self.p = p
|
||||
class TestEvents(SleekTest):
|
||||
|
||||
def testEventHappening(self):
|
||||
"Test handler working"
|
||||
import sleekxmpp
|
||||
c = sleekxmpp.ClientXMPP('crap@wherever', 'password')
|
||||
happened = []
|
||||
def handletestevent(event):
|
||||
|
@ -20,7 +17,6 @@ class testevents(unittest.TestCase):
|
|||
|
||||
def testDelEvent(self):
|
||||
"Test handler working, then deleted and not triggered"
|
||||
import sleekxmpp
|
||||
c = sleekxmpp.ClientXMPP('crap@wherever', 'password')
|
||||
happened = []
|
||||
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")
|
||||
|
||||
|
||||
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 xml.etree import cElementTree as ET
|
||||
from . sleektest import *
|
||||
from sleekxmpp.stanza.message import Message
|
||||
from sleekxmpp.stanza.htmlim import HTMLIM
|
||||
|
||||
class testmessagestanzas(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
import sleekxmpp.stanza.message as m
|
||||
from sleekxmpp.basexmpp import stanzaPlugin
|
||||
from sleekxmpp.stanza.htmlim import HTMLIM
|
||||
stanzaPlugin(m.Message, HTMLIM)
|
||||
self.m = m
|
||||
|
||||
def testGroupchatReplyRegression(self):
|
||||
"Regression groupchat reply should be to barejid"
|
||||
msg = self.m.Message()
|
||||
msg['to'] = 'me@myserver.tld'
|
||||
msg['from'] = 'room@someservice.someserver.tld/somenick'
|
||||
msg['type'] = 'groupchat'
|
||||
msg['body'] = "this is a message"
|
||||
msg.reply()
|
||||
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
|
||||
class TestMessageStanzas(SleekTest):
|
||||
|
||||
def testAttribProperty(self):
|
||||
"Test attrib property returning self"
|
||||
msg = self.m.Message()
|
||||
msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
|
||||
self.failUnless(str(msg['to']) == 'usr@server.tld')
|
||||
|
||||
def testHTMLPlugin(self):
|
||||
"Test message/html/html stanza"
|
||||
msgtxt = """<message to="fritzy@netflint.net/sleekxmpp" type="chat"><body>this is the plaintext message</body><html xmlns="http://jabber.org/protocol/xhtml-im"><body xmlns="http://www.w3.org/1999/xhtml"><p>This is the htmlim message</p></body></html></message>"""
|
||||
msg = self.m.Message()
|
||||
msg['to'] = "fritzy@netflint.net/sleekxmpp"
|
||||
msg['body'] = "this is the plaintext message"
|
||||
msg['type'] = 'chat'
|
||||
p = ET.Element('{http://www.w3.org/1999/xhtml}p')
|
||||
p.text = "This is the htmlim message"
|
||||
msg['html']['html'] = p
|
||||
msg2 = self.m.Message()
|
||||
values = msg.getValues()
|
||||
msg2.setValues(values)
|
||||
self.failUnless(msgtxt == str(msg) == str(msg2))
|
||||
def setUp(self):
|
||||
registerStanzaPlugin(Message, HTMLIM)
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(testmessagestanzas)
|
||||
def testGroupchatReplyRegression(self):
|
||||
"Regression groupchat reply should be to barejid"
|
||||
msg = self.Message()
|
||||
msg['to'] = 'me@myserver.tld'
|
||||
msg['from'] = 'room@someservice.someserver.tld/somenick'
|
||||
msg['type'] = 'groupchat'
|
||||
msg['body'] = "this is a message"
|
||||
msg.reply()
|
||||
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
|
||||
|
||||
def testAttribProperty(self):
|
||||
"Test attrib property returning self"
|
||||
msg = self.Message()
|
||||
msg.attrib.attrib.attrib['to'] = 'usr@server.tld'
|
||||
self.failUnless(str(msg['to']) == 'usr@server.tld')
|
||||
|
||||
def testHTMLPlugin(self):
|
||||
"Test message/html/body stanza"
|
||||
msg = self.Message()
|
||||
msg['to'] = "fritzy@netflint.net/sleekxmpp"
|
||||
msg['body'] = "this is the plaintext message"
|
||||
msg['type'] = 'chat'
|
||||
p = ET.Element('{http://www.w3.org/1999/xhtml}p')
|
||||
p.text = "This is the htmlim message"
|
||||
msg['html']['body'] = p
|
||||
self.checkMessage(msg, """
|
||||
<message to="fritzy@netflint.net/sleekxmpp" type="chat">
|
||||
<body>this is the plaintext message</body>
|
||||
<html xmlns="http://jabber.org/protocol/xhtml-im">
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p>This is the htmlim message</p>
|
||||
</body>
|
||||
</html>
|
||||
</message>""")
|
||||
|
||||
def testNickPlugin(self):
|
||||
"Test message/nick/nick stanza."
|
||||
msg = self.Message()
|
||||
msg['nick']['nick'] = 'A nickname!'
|
||||
self.checkMessage(msg, """
|
||||
<message>
|
||||
<nick xmlns="http://jabber.org/nick/nick">A nickname!</nick>
|
||||
</message>
|
||||
""")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestMessageStanzas)
|
||||
|
|
|
@ -1,31 +1,67 @@
|
|||
import unittest
|
||||
import sleekxmpp
|
||||
from . sleektest import *
|
||||
from sleekxmpp.stanza.presence import Presence
|
||||
|
||||
class testpresencestanzas(unittest.TestCase):
|
||||
|
||||
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")
|
||||
|
||||
class TestPresenceStanzas(SleekTest):
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(testpresencestanzas)
|
||||
def testPresenceShowRegression(self):
|
||||
"""Regression check presence['type'] = 'dnd' show value working"""
|
||||
p = self.Presence()
|
||||
p['type'] = 'dnd'
|
||||
self.checkPresence(p, "<presence><show>dnd</show></presence>")
|
||||
|
||||
def testPresenceType(self):
|
||||
"""Test manipulating presence['type']"""
|
||||
p = self.Presence()
|
||||
p['type'] = 'available'
|
||||
self.checkPresence(p, "<presence />")
|
||||
self.failUnless(p['type'] == 'available',
|
||||
"Incorrect presence['type'] for type 'available'")
|
||||
|
||||
for showtype in ['away', 'chat', 'dnd', 'xa']:
|
||||
p['type'] = showtype
|
||||
self.checkPresence(p, """
|
||||
<presence><show>%s</show></presence>
|
||||
""" % showtype)
|
||||
self.failUnless(p['type'] == showtype,
|
||||
"Incorrect presence['type'] for type '%s'" % showtype)
|
||||
|
||||
p['type'] = None
|
||||
self.checkPresence(p, "<presence />")
|
||||
|
||||
def testPresenceUnsolicitedOffline(self):
|
||||
"""
|
||||
Unsolicted offline presence does not spawn changed_status
|
||||
or update the roster.
|
||||
"""
|
||||
p = self.Presence()
|
||||
p['type'] = 'unavailable'
|
||||
p['from'] = 'bill@chadmore.com/gmail15af'
|
||||
|
||||
c = sleekxmpp.ClientXMPP('crap@wherever', 'password')
|
||||
happened = []
|
||||
|
||||
def handlechangedpresence(event):
|
||||
happened.append(True)
|
||||
|
||||
c.add_event_handler("changed_status", handlechangedpresence)
|
||||
c._handlePresence(p)
|
||||
|
||||
self.failUnless(happened == [],
|
||||
"changed_status event triggered for extra unavailable presence")
|
||||
self.failUnless(c.roster == {},
|
||||
"Roster updated for superfulous unavailable presence")
|
||||
|
||||
def testNickPlugin(self):
|
||||
"""Test presence/nick/nick stanza."""
|
||||
p = self.Presence()
|
||||
p['nick']['nick'] = 'A nickname!'
|
||||
self.checkPresence(p, """
|
||||
<presence>
|
||||
<nick xmlns="http://jabber.org/nick/nick">A nickname!</nick>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestPresenceStanzas)
|
||||
|
|
|
@ -1,315 +1,511 @@
|
|||
import unittest
|
||||
from xml.etree import cElementTree as ET
|
||||
from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
|
||||
from . import xmlcompare
|
||||
from . sleektest import *
|
||||
import sleekxmpp.plugins.xep_0004 as xep_0004
|
||||
import sleekxmpp.plugins.stanza_pubsub as pubsub
|
||||
|
||||
class testpubsubstanzas(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
import sleekxmpp.plugins.stanza_pubsub as ps
|
||||
self.ps = ps
|
||||
class TestPubsubStanzas(SleekTest):
|
||||
|
||||
def testAffiliations(self):
|
||||
"Testing iq/pubsub/affiliations/affiliation stanzas"
|
||||
iq = self.ps.Iq()
|
||||
aff1 = self.ps.Affiliation()
|
||||
aff1['node'] = 'testnode'
|
||||
aff1['affiliation'] = 'owner'
|
||||
aff2 = self.ps.Affiliation()
|
||||
aff2['node'] = 'testnode2'
|
||||
aff2['affiliation'] = 'publisher'
|
||||
iq['pubsub']['affiliations'].append(aff1)
|
||||
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>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3), "3 methods for creating stanza don't match")
|
||||
self.failUnless(iq.match('iq@id=0/pubsub/affiliations/affiliation@node=testnode2@affiliation=publisher'), 'Match path failed')
|
||||
|
||||
def testSubscriptions(self):
|
||||
"Testing iq/pubsub/subscriptions/subscription stanzas"
|
||||
iq = self.ps.Iq()
|
||||
sub1 = self.ps.Subscription()
|
||||
sub1['node'] = 'testnode'
|
||||
sub1['jid'] = 'steve@myserver.tld/someresource'
|
||||
sub2 = self.ps.Subscription()
|
||||
sub2['node'] = 'testnode2'
|
||||
sub2['jid'] = 'boogers@bork.top/bill'
|
||||
sub2['subscription'] = 'subscribed'
|
||||
iq['pubsub']['subscriptions'].append(sub1)
|
||||
iq['pubsub']['subscriptions'].append(sub2)
|
||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscriptions><subscription node="testnode" jid="steve@myserver.tld/someresource" /><subscription node="testnode2" jid="boogers@bork.top/bill" subscription="subscribed" /></subscriptions></pubsub></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
|
||||
def testOptionalSettings(self):
|
||||
"Testing iq/pubsub/subscription/subscribe-options stanzas"
|
||||
iq = self.ps.Iq()
|
||||
iq['pubsub']['subscription']['suboptions']['required'] = True
|
||||
iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas'
|
||||
iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp"
|
||||
iq['pubsub']['subscription']['subscription'] = 'unconfigured'
|
||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscription node="testnode alsdkjfas" jid="fritzy@netflint.net/sleekxmpp" subscription="unconfigured"><subscribe-options><required /></subscribe-options></subscription></pubsub></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
|
||||
def testItems(self):
|
||||
"Testing iq/pubsub/items stanzas"
|
||||
iq = self.ps.Iq()
|
||||
iq['pubsub']['items']
|
||||
payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""")
|
||||
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""")
|
||||
item = self.ps.Item()
|
||||
item['id'] = 'asdf'
|
||||
item['payload'] = payload
|
||||
item2 = self.ps.Item()
|
||||
item2['id'] = 'asdf2'
|
||||
item2['payload'] = payload2
|
||||
iq['pubsub']['items'].append(item)
|
||||
iq['pubsub']['items'].append(item2)
|
||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><items><item id="asdf"><thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1"><child1 /><child2 foo="bar" normandy="cheese" /></thinger></item><item id="asdf2"><thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12"><child12 /><child22 foo="bar2" normandy="cheese2" /></thinger2></item></items></pubsub></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
|
||||
def testCreate(self):
|
||||
"Testing iq/pubsub/create&configure stanzas"
|
||||
from sleekxmpp.plugins import xep_0004
|
||||
iq = self.ps.Iq()
|
||||
iq['pubsub']['create']['node'] = 'mynode'
|
||||
form = xep_0004.Form()
|
||||
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
|
||||
iq['pubsub']['configure']['config'] = form
|
||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><create node="mynode" /><configure><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></configure></pubsub></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
|
||||
def testState(self):
|
||||
"Testing iq/psstate stanzas"
|
||||
from sleekxmpp.plugins import xep_0004
|
||||
iq = self.ps.Iq()
|
||||
iq['psstate']['node']= 'mynode'
|
||||
iq['psstate']['item']= 'myitem'
|
||||
pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
|
||||
iq['psstate']['payload'] = pl
|
||||
xmlstring = """<iq id="0"><state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem"><claimed xmlns="http://andyet.net/protocol/pubsubqueue" /></state></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
|
||||
def testDefault(self):
|
||||
"Testing iq/pubsub_owner/default stanzas"
|
||||
from sleekxmpp.plugins import xep_0004
|
||||
iq = self.ps.Iq()
|
||||
iq['pubsub_owner']['default']
|
||||
iq['pubsub_owner']['default']['node'] = 'mynode'
|
||||
iq['pubsub_owner']['default']['type'] = 'leaf'
|
||||
form = xep_0004.Form()
|
||||
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
|
||||
iq['pubsub_owner']['default']['config'] = form
|
||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><default node="mynode" type="leaf"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></default></pubsub></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
|
||||
def testSubscribe(self):
|
||||
"Testing iq/pubsub/subscribe stanzas"
|
||||
from sleekxmpp.plugins import xep_0004
|
||||
iq = self.ps.Iq()
|
||||
iq['pubsub']['subscribe']['options']
|
||||
iq['pubsub']['subscribe']['node'] = 'cheese'
|
||||
iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp'
|
||||
iq['pubsub']['subscribe']['options']['node'] = 'cheese'
|
||||
iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp'
|
||||
form = xep_0004.Form()
|
||||
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
|
||||
iq['pubsub']['subscribe']['options']['options'] = form
|
||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp"><options node="cheese" jid="fritzy@netflint.net/sleekxmpp"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></options></subscribe></pubsub></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
|
||||
def testPublish(self):
|
||||
"Testing iq/pubsub/publish stanzas"
|
||||
iq = self.ps.Iq()
|
||||
iq['pubsub']['publish']['node'] = 'thingers'
|
||||
payload = ET.fromstring("""<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'><child1 /><child2 normandy='cheese' foo='bar' /></thinger>""")
|
||||
payload2 = ET.fromstring("""<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'><child12 /><child22 normandy='cheese2' foo='bar2' /></thinger2>""")
|
||||
item = self.ps.Item()
|
||||
item['id'] = 'asdf'
|
||||
item['payload'] = payload
|
||||
item2 = self.ps.Item()
|
||||
item2['id'] = 'asdf2'
|
||||
item2['payload'] = payload2
|
||||
iq['pubsub']['publish'].append(item)
|
||||
iq['pubsub']['publish'].append(item2)
|
||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub"><publish node="thingers"><item id="asdf"><thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1"><child1 /><child2 foo="bar" normandy="cheese" /></thinger></item><item id="asdf2"><thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12"><child12 /><child22 foo="bar2" normandy="cheese2" /></thinger2></item></publish></pubsub></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
values = iq2.getValues()
|
||||
iq3.setValues(values)
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
def testAffiliations(self):
|
||||
"Testing iq/pubsub/affiliations/affiliation stanzas"
|
||||
iq = self.Iq()
|
||||
aff1 = pubsub.Affiliation()
|
||||
aff1['node'] = 'testnode'
|
||||
aff1['affiliation'] = 'owner'
|
||||
aff2 = pubsub.Affiliation()
|
||||
aff2['node'] = 'testnode2'
|
||||
aff2['affiliation'] = 'publisher'
|
||||
iq['pubsub']['affiliations'].append(aff1)
|
||||
iq['pubsub']['affiliations'].append(aff2)
|
||||
self.checkIq(iq, """
|
||||
<iq id="0">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<affiliations>
|
||||
<affiliation node="testnode" affiliation="owner" />
|
||||
<affiliation node="testnode2" affiliation="publisher" />
|
||||
</affiliations>
|
||||
</pubsub>
|
||||
</iq>""")
|
||||
|
||||
def testDelete(self):
|
||||
"Testing iq/pubsub_owner/delete stanzas"
|
||||
iq = self.ps.Iq()
|
||||
iq['pubsub_owner']['delete']['node'] = 'thingers'
|
||||
xmlstring = """<iq id="0"><pubsub xmlns="http://jabber.org/protocol/pubsub#owner"><delete node="thingers" /></pubsub></iq>"""
|
||||
iq2 = self.ps.Iq(None, self.ps.ET.fromstring(xmlstring))
|
||||
iq3 = self.ps.Iq()
|
||||
iq3.setValues(iq2.getValues())
|
||||
self.failUnless(xmlstring == str(iq) == str(iq2) == str(iq3))
|
||||
|
||||
def testCreateConfigGet(self):
|
||||
"""Testing getting config from full create"""
|
||||
xml = """<iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7"><pubsub xmlns="http://jabber.org/protocol/pubsub"><create node="testnode2" /><configure><x xmlns="jabber:x:data" type="submit"><field var="FORM_TYPE" type="hidden"><value>http://jabber.org/protocol/pubsub#node_config</value></field><field var="pubsub#node_type" type="list-single" label="Select the node type"><value>leaf</value></field><field var="pubsub#title" type="text-single" label="A friendly name for the node" /><field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications"><value>1</value></field><field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications"><value>1</value></field><field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" /><field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" /><field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node"><value>1</value></field><field var="pubsub#notify_sub" type="boolean" label="Notify owners about new subscribers and unsubscribes" /><field var="pubsub#persist_items" type="boolean" label="Persist items in storage" /><field var="pubsub#max_items" type="text-single" label="Max # of items to persist"><value>10</value></field><field var="pubsub#subscribe" type="boolean" label="Whether to allow subscriptions"><value>1</value></field><field var="pubsub#access_model" type="list-single" label="Specify the subscriber model"><value>open</value></field><field var="pubsub#publish_model" type="list-single" label="Specify the publisher model"><value>publishers</value></field><field var="pubsub#send_last_published_item" type="list-single" label="Send last published item"><value>never</value></field><field var="pubsub#presence_based_delivery" type="boolean" label="Deliver notification only to available users" /></x></configure></pubsub></iq>"""
|
||||
iq = self.ps.Iq(None, self.ps.ET.fromstring(xml))
|
||||
config = iq['pubsub']['configure']['config']
|
||||
self.failUnless(config.getValues() != {})
|
||||
def testSubscriptions(self):
|
||||
"Testing iq/pubsub/subscriptions/subscription stanzas"
|
||||
iq = self.Iq()
|
||||
sub1 = pubsub.Subscription()
|
||||
sub1['node'] = 'testnode'
|
||||
sub1['jid'] = 'steve@myserver.tld/someresource'
|
||||
sub2 = pubsub.Subscription()
|
||||
sub2['node'] = 'testnode2'
|
||||
sub2['jid'] = 'boogers@bork.top/bill'
|
||||
sub2['subscription'] = 'subscribed'
|
||||
iq['pubsub']['subscriptions'].append(sub1)
|
||||
iq['pubsub']['subscriptions'].append(sub2)
|
||||
self.checkIq(iq, """
|
||||
<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>""")
|
||||
|
||||
def testItemEvent(self):
|
||||
"""Testing message/pubsub_event/items/item"""
|
||||
msg = self.ps.Message()
|
||||
item = self.ps.EventItem()
|
||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||
item['payload'] = pl
|
||||
item['id'] = 'abc123'
|
||||
msg['pubsub_event']['items'].append(item)
|
||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
||||
msg['type'] = 'normal'
|
||||
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 testOptionalSettings(self):
|
||||
"Testing iq/pubsub/subscription/subscribe-options stanzas"
|
||||
iq = self.Iq()
|
||||
iq['pubsub']['subscription']['suboptions']['required'] = True
|
||||
iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas'
|
||||
iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp"
|
||||
iq['pubsub']['subscription']['subscription'] = 'unconfigured'
|
||||
self.checkIq(iq, """
|
||||
<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>""")
|
||||
|
||||
def testItemsEvent(self):
|
||||
"""Testing multiple message/pubsub_event/items/item"""
|
||||
msg = self.ps.Message()
|
||||
item = self.ps.EventItem()
|
||||
item2 = self.ps.EventItem()
|
||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
|
||||
item2['payload'] = pl2
|
||||
item['payload'] = pl
|
||||
item['id'] = 'abc123'
|
||||
item2['id'] = '123abc'
|
||||
msg['pubsub_event']['items'].append(item)
|
||||
msg['pubsub_event']['items'].append(item2)
|
||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
||||
msg['type'] = 'normal'
|
||||
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>"""
|
||||
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 testItems(self):
|
||||
"Testing iq/pubsub/items stanzas"
|
||||
iq = self.Iq()
|
||||
iq['pubsub']['items']
|
||||
payload = ET.fromstring("""
|
||||
<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'>
|
||||
<child1 />
|
||||
<child2 normandy='cheese' foo='bar' />
|
||||
</thinger>""")
|
||||
payload2 = ET.fromstring("""
|
||||
<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'>
|
||||
<child12 />
|
||||
<child22 normandy='cheese2' foo='bar2' />
|
||||
</thinger2>""")
|
||||
item = pubsub.Item()
|
||||
item['id'] = 'asdf'
|
||||
item['payload'] = payload
|
||||
item2 = pubsub.Item()
|
||||
item2['id'] = 'asdf2'
|
||||
item2['payload'] = payload2
|
||||
iq['pubsub']['items'].append(item)
|
||||
iq['pubsub']['items'].append(item2)
|
||||
self.checkIq(iq, """
|
||||
<iq id="0">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<items>
|
||||
<item id="asdf">
|
||||
<thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1">
|
||||
<child1 />
|
||||
<child2 foo="bar" normandy="cheese" />
|
||||
</thinger>
|
||||
</item>
|
||||
<item id="asdf2">
|
||||
<thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12">
|
||||
<child12 />
|
||||
<child22 foo="bar2" normandy="cheese2" />
|
||||
</thinger2>
|
||||
</item>
|
||||
</items>
|
||||
</pubsub>
|
||||
</iq>""")
|
||||
|
||||
def testItemsEvent(self):
|
||||
"""Testing message/pubsub_event/items/item & retract mix"""
|
||||
msg = self.ps.Message()
|
||||
item = self.ps.EventItem()
|
||||
item2 = self.ps.EventItem()
|
||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
|
||||
item2['payload'] = pl2
|
||||
retract = self.ps.EventRetract()
|
||||
retract['id'] = 'aabbcc'
|
||||
item['payload'] = pl
|
||||
item['id'] = 'abc123'
|
||||
item2['id'] = '123abc'
|
||||
msg['pubsub_event']['items'].append(item)
|
||||
msg['pubsub_event']['items'].append(retract)
|
||||
msg['pubsub_event']['items'].append(item2)
|
||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
||||
msg['type'] = 'normal'
|
||||
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>"""
|
||||
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 testCollectionAssociate(self):
|
||||
"""Testing message/pubsub_event/collection/associate"""
|
||||
msg = self.ps.Message()
|
||||
msg['pubsub_event']['collection']['associate']['node'] = 'cheese'
|
||||
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
|
||||
msg['type'] = 'headline'
|
||||
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><associate node="cheese" /></collection></event></message>"""
|
||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
||||
msg3 = self.ps.Message()
|
||||
msg3.setValues(msg2.getValues())
|
||||
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
|
||||
def testCreate(self):
|
||||
"Testing iq/pubsub/create&configure stanzas"
|
||||
iq = self.Iq()
|
||||
iq['pubsub']['create']['node'] = 'mynode'
|
||||
iq['pubsub']['configure']['form'].addField('pubsub#title',
|
||||
ftype='text-single',
|
||||
value='This thing is awesome')
|
||||
self.checkIq(iq, """
|
||||
<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>""")
|
||||
|
||||
def testCollectionDisassociate(self):
|
||||
"""Testing message/pubsub_event/collection/disassociate"""
|
||||
msg = self.ps.Message()
|
||||
msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese'
|
||||
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
|
||||
msg['type'] = 'headline'
|
||||
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><collection node="cheeseburger"><disassociate node="cheese" /></collection></event></message>"""
|
||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
||||
msg3 = self.ps.Message()
|
||||
msg3.setValues(msg2.getValues())
|
||||
self.failUnless(xmlstring == str(msg) == str(msg2) == str(msg3))
|
||||
def testState(self):
|
||||
"Testing iq/psstate stanzas"
|
||||
iq = self.Iq()
|
||||
iq['psstate']['node']= 'mynode'
|
||||
iq['psstate']['item']= 'myitem'
|
||||
pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed')
|
||||
iq['psstate']['payload'] = pl
|
||||
self.checkIq(iq, """
|
||||
<iq id="0">
|
||||
<state xmlns="http://jabber.org/protocol/psstate" node="mynode" item="myitem">
|
||||
<claimed xmlns="http://andyet.net/protocol/pubsubqueue" />
|
||||
</state>
|
||||
</iq>""")
|
||||
|
||||
def testEventConfiguration(self):
|
||||
"""Testing message/pubsub_event/configuration/config"""
|
||||
msg = self.ps.Message()
|
||||
from sleekxmpp.plugins import xep_0004
|
||||
form = xep_0004.Form()
|
||||
form.addField('pubsub#title', ftype='text-single', value='This thing is awesome')
|
||||
msg['pubsub_event']['configuration']['node'] = 'cheese'
|
||||
msg['pubsub_event']['configuration']['config'] = form
|
||||
msg['type'] = 'headline'
|
||||
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><configuration node="cheese"><x xmlns="jabber:x:data" type="form"><field var="pubsub#title" type="text-single"><value>This thing is awesome</value></field></x></configuration></event></message>"""
|
||||
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 testEventPurge(self):
|
||||
"""Testing message/pubsub_event/purge"""
|
||||
msg = self.ps.Message()
|
||||
msg['pubsub_event']['purge']['node'] = 'pickles'
|
||||
msg['type'] = 'headline'
|
||||
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><purge node="pickles" /></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 testEventSubscription(self):
|
||||
"""Testing message/pubsub_event/subscription"""
|
||||
msg = self.ps.Message()
|
||||
msg['pubsub_event']['subscription']['node'] = 'pickles'
|
||||
msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test'
|
||||
msg['pubsub_event']['subscription']['subid'] = 'aabb1122'
|
||||
msg['pubsub_event']['subscription']['subscription'] = 'subscribed'
|
||||
msg['pubsub_event']['subscription']['expiry'] = 'presence'
|
||||
msg['type'] = 'headline'
|
||||
xmlstring = """<message type="headline"><event xmlns="http://jabber.org/protocol/pubsub#event"><subscription node="pickles" subid="aabb1122" jid="fritzy@netflint.net/test" subscription="subscribed" expiry="presence" /></event></message>"""
|
||||
msg2 = self.ps.Message(None, self.ps.ET.fromstring(xmlstring))
|
||||
msg3 = self.ps.Message()
|
||||
msg3.setValues(msg2.getValues())
|
||||
self.failUnless(xmlcompare.comparemany([xmlstring, str(msg), str(msg2), str(msg3)]))
|
||||
def testDefault(self):
|
||||
"Testing iq/pubsub_owner/default stanzas"
|
||||
iq = self.Iq()
|
||||
iq['pubsub_owner']['default']
|
||||
iq['pubsub_owner']['default']['node'] = 'mynode'
|
||||
iq['pubsub_owner']['default']['type'] = 'leaf'
|
||||
iq['pubsub_owner']['default']['form'].addField('pubsub#title',
|
||||
ftype='text-single',
|
||||
value='This thing is awesome')
|
||||
self.checkIq(iq, """
|
||||
<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>""", use_values=False)
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(testpubsubstanzas)
|
||||
def testSubscribe(self):
|
||||
"testing iq/pubsub/subscribe stanzas"
|
||||
iq = self.Iq()
|
||||
iq['pubsub']['subscribe']['options']
|
||||
iq['pubsub']['subscribe']['node'] = 'cheese'
|
||||
iq['pubsub']['subscribe']['jid'] = 'fritzy@netflint.net/sleekxmpp'
|
||||
iq['pubsub']['subscribe']['options']['node'] = 'cheese'
|
||||
iq['pubsub']['subscribe']['options']['jid'] = 'fritzy@netflint.net/sleekxmpp'
|
||||
form = xep_0004.Form()
|
||||
form.addField('pubsub#title', ftype='text-single', value='this thing is awesome')
|
||||
iq['pubsub']['subscribe']['options']['options'] = form
|
||||
self.checkIq(iq, """
|
||||
<iq id="0">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscribe node="cheese" jid="fritzy@netflint.net/sleekxmpp">
|
||||
<options node="cheese" jid="fritzy@netflint.net/sleekxmpp">
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<field var="pubsub#title" type="text-single">
|
||||
<value>this thing is awesome</value>
|
||||
</field>
|
||||
</x>
|
||||
</options>
|
||||
</subscribe>
|
||||
</pubsub>
|
||||
</iq>""", use_values=False)
|
||||
|
||||
def testPublish(self):
|
||||
"Testing iq/pubsub/publish stanzas"
|
||||
iq = self.Iq()
|
||||
iq['pubsub']['publish']['node'] = 'thingers'
|
||||
payload = ET.fromstring("""
|
||||
<thinger xmlns="http://andyet.net/protocol/thinger" x="1" y='2'>
|
||||
<child1 />
|
||||
<child2 normandy='cheese' foo='bar' />
|
||||
</thinger>""")
|
||||
payload2 = ET.fromstring("""
|
||||
<thinger2 xmlns="http://andyet.net/protocol/thinger2" x="12" y='22'>
|
||||
<child12 />
|
||||
<child22 normandy='cheese2' foo='bar2' />
|
||||
</thinger2>""")
|
||||
item = pubsub.Item()
|
||||
item['id'] = 'asdf'
|
||||
item['payload'] = payload
|
||||
item2 = pubsub.Item()
|
||||
item2['id'] = 'asdf2'
|
||||
item2['payload'] = payload2
|
||||
iq['pubsub']['publish'].append(item)
|
||||
iq['pubsub']['publish'].append(item2)
|
||||
|
||||
self.checkIq(iq, """
|
||||
<iq id="0">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<publish node="thingers">
|
||||
<item id="asdf">
|
||||
<thinger xmlns="http://andyet.net/protocol/thinger" y="2" x="1">
|
||||
<child1 />
|
||||
<child2 foo="bar" normandy="cheese" />
|
||||
</thinger>
|
||||
</item>
|
||||
<item id="asdf2">
|
||||
<thinger2 xmlns="http://andyet.net/protocol/thinger2" y="22" x="12">
|
||||
<child12 />
|
||||
<child22 foo="bar2" normandy="cheese2" />
|
||||
</thinger2>
|
||||
</item>
|
||||
</publish>
|
||||
</pubsub>
|
||||
</iq>""")
|
||||
|
||||
def testDelete(self):
|
||||
"Testing iq/pubsub_owner/delete stanzas"
|
||||
iq = self.Iq()
|
||||
iq['pubsub_owner']['delete']['node'] = 'thingers'
|
||||
self.checkIq(iq, """
|
||||
<iq id="0">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<delete node="thingers" />
|
||||
</pubsub>
|
||||
</iq>""")
|
||||
|
||||
def testCreateConfigGet(self):
|
||||
"""Testing getting config from full create"""
|
||||
iq = self.Iq()
|
||||
iq['to'] = 'pubsub.asdf'
|
||||
iq['from'] = 'fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7'
|
||||
iq['type'] = 'set'
|
||||
iq['id'] = 'E'
|
||||
|
||||
pub = iq['pubsub']
|
||||
pub['create']['node'] = 'testnode2'
|
||||
pub['configure']['form']['type'] = 'submit'
|
||||
pub['configure']['form'].setFields([
|
||||
('FORM_TYPE', {'type': 'hidden',
|
||||
'value': 'http://jabber.org/protocol/pubsub#node_config'}),
|
||||
('pubsub#node_type', {'type': 'list-single',
|
||||
'label': 'Select the node type',
|
||||
'value': 'leaf'}),
|
||||
('pubsub#title', {'type': 'text-single',
|
||||
'label': 'A friendly name for the node'}),
|
||||
('pubsub#deliver_notifications', {'type': 'boolean',
|
||||
'label': 'Deliver event notifications',
|
||||
'value': True}),
|
||||
('pubsub#deliver_payloads', {'type': 'boolean',
|
||||
'label': 'Deliver payloads with event notifications',
|
||||
'value': True}),
|
||||
('pubsub#notify_config', {'type': 'boolean',
|
||||
'label': 'Notify subscribers when the node configuration changes'}),
|
||||
('pubsub#notify_delete', {'type': 'boolean',
|
||||
'label': 'Notify subscribers when the node is deleted'}),
|
||||
('pubsub#notify_retract', {'type': 'boolean',
|
||||
'label': 'Notify subscribers when items are removed from the node',
|
||||
'value': True}),
|
||||
('pubsub#notify_sub', {'type': 'boolean',
|
||||
'label': 'Notify owners about new subscribers and unsubscribes'}),
|
||||
('pubsub#persist_items', {'type': 'boolean',
|
||||
'label': 'Persist items in storage'}),
|
||||
('pubsub#max_items', {'type': 'text-single',
|
||||
'label': 'Max # of items to persist',
|
||||
'value': '10'}),
|
||||
('pubsub#subscribe', {'type': 'boolean',
|
||||
'label': 'Whether to allow subscriptions',
|
||||
'value': True}),
|
||||
('pubsub#access_model', {'type': 'list-single',
|
||||
'label': 'Specify the subscriber model',
|
||||
'value': 'open'}),
|
||||
('pubsub#publish_model', {'type': 'list-single',
|
||||
'label': 'Specify the publisher model',
|
||||
'value': 'publishers'}),
|
||||
('pubsub#send_last_published_item', {'type': 'list-single',
|
||||
'label': 'Send last published item',
|
||||
'value': 'never'}),
|
||||
('pubsub#presence_based_delivery', {'type': 'boolean',
|
||||
'label': 'Deliver notification only to available users'}),
|
||||
])
|
||||
|
||||
self.checkIq(iq, """
|
||||
<iq to="pubsub.asdf" type="set" id="E" from="fritzy@asdf/87292ede-524d-4117-9076-d934ed3db8e7">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<create node="testnode2" />
|
||||
<configure>
|
||||
<x xmlns="jabber:x:data" type="submit">
|
||||
<field var="FORM_TYPE" type="hidden">
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field var="pubsub#node_type" type="list-single" label="Select the node type">
|
||||
<value>leaf</value>
|
||||
</field>
|
||||
<field var="pubsub#title" type="text-single" label="A friendly name for the node" />
|
||||
<field var="pubsub#deliver_notifications" type="boolean" label="Deliver event notifications">
|
||||
<value>1</value>
|
||||
</field>
|
||||
<field var="pubsub#deliver_payloads" type="boolean" label="Deliver payloads with event notifications">
|
||||
<value>1</value>
|
||||
</field>
|
||||
<field var="pubsub#notify_config" type="boolean" label="Notify subscribers when the node configuration changes" />
|
||||
<field var="pubsub#notify_delete" type="boolean" label="Notify subscribers when the node is deleted" />
|
||||
<field var="pubsub#notify_retract" type="boolean" label="Notify subscribers when items are removed from the node">
|
||||
<value>1</value>
|
||||
</field>
|
||||
<field var="pubsub#notify_sub" type="boolean" label="Notify owners about new subscribers and unsubscribes" />
|
||||
<field var="pubsub#persist_items" type="boolean" label="Persist items in storage" />
|
||||
<field var="pubsub#max_items" type="text-single" label="Max # of items to persist">
|
||||
<value>10</value>
|
||||
</field>
|
||||
<field var="pubsub#subscribe" type="boolean" label="Whether to allow subscriptions">
|
||||
<value>1</value>
|
||||
</field>
|
||||
<field var="pubsub#access_model" type="list-single" label="Specify the subscriber model">
|
||||
<value>open</value>
|
||||
</field>
|
||||
<field var="pubsub#publish_model" type="list-single" label="Specify the publisher model">
|
||||
<value>publishers</value>
|
||||
</field>
|
||||
<field var="pubsub#send_last_published_item" type="list-single" label="Send last published item">
|
||||
<value>never</value>
|
||||
</field>
|
||||
<field var="pubsub#presence_based_delivery" type="boolean" label="Deliver notification only to available users" />
|
||||
</x>
|
||||
</configure>
|
||||
</pubsub>
|
||||
</iq>""")
|
||||
|
||||
def testItemEvent(self):
|
||||
"""Testing message/pubsub_event/items/item"""
|
||||
msg = self.Message()
|
||||
item = pubsub.EventItem()
|
||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||
item['payload'] = pl
|
||||
item['id'] = 'abc123'
|
||||
msg['pubsub_event']['items'].append(item)
|
||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
||||
msg['type'] = 'normal'
|
||||
self.checkMessage(msg, """
|
||||
<message type="normal">
|
||||
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||
<items node="cheese">
|
||||
<item id="abc123">
|
||||
<test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" />
|
||||
</item>
|
||||
</items>
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
def testItemsEvent(self):
|
||||
"""Testing multiple message/pubsub_event/items/item"""
|
||||
msg = self.Message()
|
||||
item = pubsub.EventItem()
|
||||
item2 = pubsub.EventItem()
|
||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
|
||||
item2['payload'] = pl2
|
||||
item['payload'] = pl
|
||||
item['id'] = 'abc123'
|
||||
item2['id'] = '123abc'
|
||||
msg['pubsub_event']['items'].append(item)
|
||||
msg['pubsub_event']['items'].append(item2)
|
||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
||||
msg['type'] = 'normal'
|
||||
self.checkMessage(msg, """
|
||||
<message type="normal">
|
||||
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||
<items node="cheese">
|
||||
<item id="abc123">
|
||||
<test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" />
|
||||
</item>
|
||||
<item id="123abc">
|
||||
<test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" />
|
||||
</item>
|
||||
</items>
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
def testItemsEvent(self):
|
||||
"""Testing message/pubsub_event/items/item & retract mix"""
|
||||
msg = self.Message()
|
||||
item = pubsub.EventItem()
|
||||
item2 = pubsub.EventItem()
|
||||
pl = ET.Element('{http://netflint.net/protocol/test}test', {'failed':'3', 'passed':'24'})
|
||||
pl2 = ET.Element('{http://netflint.net/protocol/test-other}test', {'total':'27', 'failed':'3'})
|
||||
item2['payload'] = pl2
|
||||
retract = pubsub.EventRetract()
|
||||
retract['id'] = 'aabbcc'
|
||||
item['payload'] = pl
|
||||
item['id'] = 'abc123'
|
||||
item2['id'] = '123abc'
|
||||
msg['pubsub_event']['items'].append(item)
|
||||
msg['pubsub_event']['items'].append(retract)
|
||||
msg['pubsub_event']['items'].append(item2)
|
||||
msg['pubsub_event']['items']['node'] = 'cheese'
|
||||
msg['type'] = 'normal'
|
||||
self.checkMessage(msg, """
|
||||
<message type="normal">
|
||||
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||
<items node="cheese">
|
||||
<item id="abc123">
|
||||
<test xmlns="http://netflint.net/protocol/test" failed="3" passed="24" />
|
||||
</item><retract id="aabbcc" />
|
||||
<item id="123abc">
|
||||
<test xmlns="http://netflint.net/protocol/test-other" failed="3" total="27" />
|
||||
</item>
|
||||
</items>
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
def testCollectionAssociate(self):
|
||||
"""Testing message/pubsub_event/collection/associate"""
|
||||
msg = self.Message()
|
||||
msg['pubsub_event']['collection']['associate']['node'] = 'cheese'
|
||||
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
|
||||
msg['type'] = 'headline'
|
||||
self.checkMessage(msg, """
|
||||
<message type="headline">
|
||||
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||
<collection node="cheeseburger">
|
||||
<associate node="cheese" />
|
||||
</collection>
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
def testCollectionDisassociate(self):
|
||||
"""Testing message/pubsub_event/collection/disassociate"""
|
||||
msg = self.Message()
|
||||
msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese'
|
||||
msg['pubsub_event']['collection']['node'] = 'cheeseburger'
|
||||
msg['type'] = 'headline'
|
||||
self.checkMessage(msg, """
|
||||
<message type="headline">
|
||||
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||
<collection node="cheeseburger">
|
||||
<disassociate node="cheese" />
|
||||
</collection>
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
def testEventConfiguration(self):
|
||||
"""Testing message/pubsub_event/configuration/config"""
|
||||
msg = self.Message()
|
||||
msg['pubsub_event']['configuration']['node'] = 'cheese'
|
||||
msg['pubsub_event']['configuration']['form'].addField('pubsub#title',
|
||||
ftype='text-single',
|
||||
value='This thing is awesome')
|
||||
msg['type'] = 'headline'
|
||||
self.checkMessage(msg, """
|
||||
<message type="headline">
|
||||
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||
<configuration node="cheese">
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<field var="pubsub#title" type="text-single">
|
||||
<value>This thing is awesome</value>
|
||||
</field>
|
||||
</x>
|
||||
</configuration>
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
def testEventPurge(self):
|
||||
"""Testing message/pubsub_event/purge"""
|
||||
msg = self.Message()
|
||||
msg['pubsub_event']['purge']['node'] = 'pickles'
|
||||
msg['type'] = 'headline'
|
||||
self.checkMessage(msg, """
|
||||
<message type="headline">
|
||||
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||
<purge node="pickles" />
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
def testEventSubscription(self):
|
||||
"""Testing message/pubsub_event/subscription"""
|
||||
msg = self.Message()
|
||||
msg['pubsub_event']['subscription']['node'] = 'pickles'
|
||||
msg['pubsub_event']['subscription']['jid'] = 'fritzy@netflint.net/test'
|
||||
msg['pubsub_event']['subscription']['subid'] = 'aabb1122'
|
||||
msg['pubsub_event']['subscription']['subscription'] = 'subscribed'
|
||||
msg['pubsub_event']['subscription']['expiry'] = 'presence'
|
||||
msg['type'] = 'headline'
|
||||
self.checkMessage(msg, """
|
||||
<message type="headline">
|
||||
<event xmlns="http://jabber.org/protocol/pubsub#event">
|
||||
<subscription node="pickles" subid="aabb1122" jid="fritzy@netflint.net/test" subscription="subscribed" expiry="presence" />
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubStanzas)
|
||||
|
|
84
tests/test_roster.py
Normal file
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