Merge branch 'master' into develop

Conflicts:
	sleekxmpp/basexmpp.py
This commit is contained in:
Lance Stout 2012-06-19 21:50:33 -07:00
commit 5820d49cd4
78 changed files with 1652 additions and 643 deletions

View File

@ -61,6 +61,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0027',
'sleekxmpp/plugins/xep_0030',
'sleekxmpp/plugins/xep_0030/stanza',
'sleekxmpp/plugins/xep_0033',
'sleekxmpp/plugins/xep_0047',
'sleekxmpp/plugins/xep_0050',
'sleekxmpp/plugins/xep_0054',
@ -72,6 +73,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0077',
'sleekxmpp/plugins/xep_0078',
'sleekxmpp/plugins/xep_0080',
'sleekxmpp/plugins/xep_0084',
'sleekxmpp/plugins/xep_0085',
'sleekxmpp/plugins/xep_0086',
'sleekxmpp/plugins/xep_0092',
@ -90,6 +92,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0224',
'sleekxmpp/plugins/xep_0231',
'sleekxmpp/plugins/xep_0249',
'sleekxmpp/plugins/xep_0258',
'sleekxmpp/features',
'sleekxmpp/features/feature_mechanisms',
'sleekxmpp/features/feature_mechanisms/stanza',

View File

@ -16,24 +16,24 @@ class APIWrapper(object):
elif attr == 'settings':
return self.api.settings[self.name]
elif attr == 'register':
def curried_handler(handler, op, jid=None, node=None, default=False):
def partial(handler, op, jid=None, node=None, default=False):
register = getattr(self.api, attr)
return register(handler, self.name, op, jid, node, default)
return curried_handler
return partial
elif attr == 'register_default':
def curried_handler(handler, op, jid=None, node=None):
def partial(handler, op, jid=None, node=None):
return getattr(self.api, attr)(handler, self.name, op)
return curried_handler
return partial
elif attr in ('run', 'restore_default', 'unregister'):
def curried_handler(*args, **kwargs):
def partial(*args, **kwargs):
return getattr(self.api, attr)(self.name, *args, **kwargs)
return curried_handler
return partial
return None
def __getitem__(self, attr):
def curried_handler(jid=None, node=None, ifrom=None, args=None):
def partial(jid=None, node=None, ifrom=None, args=None):
return self.api.run(self.name, attr, jid, node, ifrom, args)
return curried_handler
return partial
class APIRegistry(object):
@ -42,7 +42,7 @@ class APIRegistry(object):
self._handlers = {}
self._handler_defaults = {}
self.xmpp = xmpp
self.settings = {}
self.settings = {}
def _setup(self, ctype, op):
"""Initialize the API callback dictionaries.
@ -138,8 +138,8 @@ class APIRegistry(object):
"""Register an API callback, with JID+node specificity.
The API callback can later be executed based on the
specificity of the provided JID+node combination.
specificity of the provided JID+node combination.
See :meth:`~ApiRegistry.run` for more details.
:param string ctype: The name of the API to use.

View File

@ -31,6 +31,7 @@ from sleekxmpp.xmlstream import XMLStream, JID
from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.stanzabase import XML_NS
from sleekxmpp.features import *
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
@ -66,7 +67,7 @@ class BaseXMPP(XMLStream):
#: An identifier for the stream as given by the server.
self.stream_id = None
#: The JabberID (JID) used by this connection.
#: The JabberID (JID) used by this connection.
self.boundjid = JID(jid)
self._expected_server_name = self.boundjid.host
@ -102,7 +103,7 @@ class BaseXMPP(XMLStream):
#: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is
#: marked with:
#:
#:
#: - An API name, e.g. xep_0030
#: - The name of an action, e.g. get_info
#: - The JID that will be affected
@ -180,6 +181,8 @@ class BaseXMPP(XMLStream):
:param xml: The incoming stream's root element.
"""
self.stream_id = xml.get('id', '')
self.stream_version = xml.get('version', '')
self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
def process(self, *args, **kwargs):
"""Initialize plugins and begin processing the XML stream.
@ -199,7 +202,7 @@ class BaseXMPP(XMLStream):
Defaults to ``True``. This does **not** mean that no
threads are used at all if ``threaded=False``.
Regardless of these threading options, these threads will
Regardless of these threading options, these threads will
always exist:
- The event queue processor
@ -272,7 +275,9 @@ class BaseXMPP(XMLStream):
def Message(self, *args, **kwargs):
"""Create a Message stanza associated with this stream."""
return Message(self, *args, **kwargs)
msg = Message(self, *args, **kwargs)
msg['lang'] = self.default_lang
return msg
def Iq(self, *args, **kwargs):
"""Create an Iq stanza associated with this stream."""
@ -280,18 +285,20 @@ class BaseXMPP(XMLStream):
def Presence(self, *args, **kwargs):
"""Create a Presence stanza associated with this stream."""
return Presence(self, *args, **kwargs)
pres = Presence(self, *args, **kwargs)
pres['lang'] = self.default_lang
return pres
def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
"""Create a new Iq stanza with a given Id and from JID.
:param id: An ideally unique ID value for this stanza thread.
Defaults to 0.
:param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
:param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
to use for this stanza.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
for this stanza.
:param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
:param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
one of: ``'get'``, ``'set'``, ``'result'``,
or ``'error'``.
:param iquery: Optional namespace for adding a query element.
@ -329,7 +336,7 @@ class BaseXMPP(XMLStream):
def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
"""
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
``'result'`` with the given ID value.
:param id: An ideally unique ID value. May use :meth:`new_id()`.
@ -359,10 +366,10 @@ class BaseXMPP(XMLStream):
Optionally, a substanza may be given to use as the
stanza's payload.
:param sub: Either an
:param sub: Either an
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
stanza object or an
:class:`~xml.etree.ElementTree.Element` XML object
:class:`~xml.etree.ElementTree.Element` XML object
to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
for this stanza.
@ -389,9 +396,9 @@ class BaseXMPP(XMLStream):
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
:param id: An ideally unique ID value. May use :meth:`new_id()`.
:param type: The type of the error, such as ``'cancel'`` or
:param type: The type of the error, such as ``'cancel'`` or
``'modify'``. Defaults to ``'cancel'``.
:param condition: The error condition. Defaults to
:param condition: The error condition. Defaults to
``'feature-not-implemented'``.
:param text: A message describing the cause of the error.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
@ -415,7 +422,7 @@ class BaseXMPP(XMLStream):
def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
"""
Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
to use the given query namespace.
:param iq: Optionally use an existing stanza instead
@ -448,7 +455,7 @@ class BaseXMPP(XMLStream):
def make_message(self, mto, mbody=None, msubject=None, mtype=None,
mhtml=None, mfrom=None, mnick=None):
"""
Create and initialize a new
Create and initialize a new
:class:`~sleekxmpp.stanza.message.Message` stanza.
:param mto: The recipient of the message.
@ -474,7 +481,7 @@ class BaseXMPP(XMLStream):
def make_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, ptype=None, pfrom=None, pnick=None):
"""
Create and initialize a new
Create and initialize a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza.
:param pshow: The presence's show value.
@ -498,7 +505,7 @@ class BaseXMPP(XMLStream):
def send_message(self, mto, mbody, msubject=None, mtype=None,
mhtml=None, mfrom=None, mnick=None):
"""
Create, initialize, and send a new
Create, initialize, and send a new
:class:`~sleekxmpp.stanza.message.Message` stanza.
:param mto: The recipient of the message.
@ -518,7 +525,7 @@ class BaseXMPP(XMLStream):
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, pfrom=None, ptype=None, pnick=None):
"""
Create, initialize, and send a new
Create, initialize, and send a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza.
:param pshow: The presence's show value.
@ -529,26 +536,13 @@ class BaseXMPP(XMLStream):
:param pfrom: The sender of the presence.
:param pnick: Optional nickname of the presence's sender.
"""
# Python2.6 chokes on Unicode strings for dict keys.
args = {str('ptype'): ptype,
str('pshow'): pshow,
str('pstatus'): pstatus,
str('ppriority'): ppriority,
str('pnick'): pnick}
if ptype in ('probe', 'subscribe', 'subscribed', \
'unsubscribe', 'unsubscribed'):
args[str('pto')] = pto.bare
if self.is_component:
self.roster[pfrom].send_presence(**args)
else:
self.client_roster.send_presence(**args)
self.make_presence(pshow, pstatus, ppriority, pto,
ptype, pfrom, pnick).send()
def send_presence_subscription(self, pto, pfrom=None,
ptype='subscribe', pnick=None):
"""
Create, initialize, and send a new
Create, initialize, and send a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza of
type ``'subscribe'``.
@ -557,10 +551,10 @@ class BaseXMPP(XMLStream):
:param ptype: The type of presence, such as ``'subscribe'``.
:param pnick: Optional nickname of the presence's sender.
"""
self.send_presence(pto=pto,
self.make_presence(ptype=ptype,
pfrom=pfrom,
ptype=ptype,
pnick=pnick)
pto=JID(pto).bare,
pnick=pnick).send()
@property
def jid(self):
@ -749,7 +743,7 @@ class BaseXMPP(XMLStream):
return
def exception(self, exception):
"""Process any uncaught exceptions, notably
"""Process any uncaught exceptions, notably
:class:`~sleekxmpp.exceptions.IqError` and
:class:`~sleekxmpp.exceptions.IqTimeout` exceptions.

View File

@ -54,14 +54,14 @@ class ClientXMPP(BaseXMPP):
:param password: The password for the XMPP user account.
:param ssl: **Deprecated.**
:param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
:param escape_quotes: **Deprecated.**
"""
def __init__(self, jid, password, ssl=False, plugin_config={},
plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
escape_quotes=True, sasl_mech=None, lang='en'):
BaseXMPP.__init__(self, jid, 'jabber:client')
self.set_jid(jid)
@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP):
self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist
self.default_port = 5222
self.default_lang = lang
self.credentials = {}
self.password = password
self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % (
self.boundjid.host,
"xmlns:stream='%s'" % self.stream_ns,
"xmlns='%s'" % self.default_ns)
"xmlns='%s'" % self.default_ns,
"xml:lang='%s'" % self.default_lang,
"version='1.0'")
self.stream_footer = "</stream:stream>"
self.features = set()
@ -186,7 +189,7 @@ class ClientXMPP(BaseXMPP):
occurs. Defaults to ``True``.
:param timeout: The length of time (in seconds) to wait
for a response before continuing if blocking
is used. Defaults to
is used. Defaults to
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
:param callback: Optional reference to a stream handler function.
Will be executed when the roster is received.
@ -197,7 +200,7 @@ class ClientXMPP(BaseXMPP):
def del_roster_item(self, jid):
"""Remove an item from the roster.
This is done by setting its subscription status to ``'remove'``.
:param jid: The JID of the item to remove.
@ -212,7 +215,7 @@ class ClientXMPP(BaseXMPP):
Defaults to ``True``.
:param timeout: The length of time (in seconds) to wait for a response
before continuing if blocking is used.
Defaults to
Defaults to
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
:param callback: Optional reference to a stream handler function. Will
be executed when the roster is received.
@ -230,7 +233,7 @@ class ClientXMPP(BaseXMPP):
response = iq.send(block, timeout, callback)
self.event('roster_received', response)
if block:
if block:
self._handle_roster(response)
return response
@ -276,7 +279,7 @@ class ClientXMPP(BaseXMPP):
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
roster[jid].save(remove=(item['subscription'] == 'remove'))
self.event("roster_update", iq)
if iq['type'] == 'set':
resp = self.Iq(stype='result',

View File

@ -40,8 +40,8 @@ class ComponentXMPP(BaseXMPP):
:param host: The server accepting the component.
:param port: The port used to connect to the server.
:param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
:param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
should be used instead of the standard
@ -78,7 +78,7 @@ class ComponentXMPP(BaseXMPP):
self.add_event_handler('presence_probe',
self._handle_probe)
def connect(self, host=None, port=None, use_ssl=False,
def connect(self, host=None, port=None, use_ssl=False,
use_tls=False, reattempt=True):
"""Connect to the server.

View File

@ -69,10 +69,11 @@ class IqTimeout(XMPPError):
condition='remote-server-timeout',
etype='cancel')
#: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
#: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
#: did not arrive before the timeout expired.
self.iq = iq
class IqError(XMPPError):
"""

View File

@ -7,9 +7,9 @@
"""
__all__ = [
'feature_starttls',
'feature_mechanisms',
'feature_bind',
'feature_starttls',
'feature_mechanisms',
'feature_bind',
'feature_session',
'feature_rosterver'
]

View File

@ -23,7 +23,7 @@ class Auth(StanzaBase):
interfaces = set(('mechanism', 'value'))
plugin_attrib = name
#: Some SASL mechs require sending values as is,
#: Some SASL mechs require sending values as is,
#: without converting base64.
plain_mechs = set(['X-MESSENGER-OAUTH2'])

View File

@ -47,7 +47,7 @@ class Failure(StanzaBase):
def get_condition(self):
"""Return the condition element's name."""
for child in self.xml.getchildren():
for child in self.xml:
if "{%s}" % self.namespace in child.tag:
cond = child.tag.split('}', 1)[-1]
if cond in self.conditions:
@ -68,7 +68,7 @@ class Failure(StanzaBase):
def del_condition(self):
"""Remove the condition element."""
for child in self.xml.getchildren():
for child in self.xml:
if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:

View File

@ -33,6 +33,7 @@ __all__ = [
# 'xep_0078', # Non-SASL auth. Don't automatically load
'xep_0080', # User Location
'xep_0082', # XMPP Date and Time Profiles
'xep_0084', # User Avatar
'xep_0085', # Chat State Notifications
'xep_0086', # Legacy Error Codes
'xep_0092', # Software Version
@ -49,7 +50,10 @@ __all__ = [
'xep_0199', # Ping
'xep_0202', # Entity Time
'xep_0203', # Delayed Delivery
'xep_0222', # Persistent Storage of Public Data via Pubsub
'xep_0223', # Persistent Storage of Private Data via Pubsub
'xep_0224', # Attention
'xep_0231', # Bits of Binary
'xep_0249', # Direct MUC Invitations
'xep_0258', # Security Labels in XMPP
]

View File

@ -31,10 +31,10 @@ log = logging.getLogger(__name__)
PLUGIN_REGISTRY = {}
#: In order to do cascading plugin disabling, reverse dependencies
#: must be tracked.
#: must be tracked.
PLUGIN_DEPENDENTS = {}
#: Only allow one thread to manipulate the plugin registry at a time.
#: Only allow one thread to manipulate the plugin registry at a time.
REGISTRY_LOCK = threading.RLock()
@ -75,7 +75,7 @@ def load_plugin(name, module=None):
plugins are in packages matching their name,
even though the plugin class name does not
have to match.
:param str module: The name of the base module to search
:param str module: The name of the base module to search
for the plugin.
"""
try:
@ -84,7 +84,7 @@ def load_plugin(name, module=None):
module = 'sleekxmpp.plugins.%s' % name
__import__(module)
mod = sys.modules[module]
except:
except ImportError:
module = 'sleekxmpp.features.%s' % name
__import__(module)
mod = sys.modules[module]
@ -103,11 +103,11 @@ def load_plugin(name, module=None):
# we can work around dependency issues.
plugin.old_style = True
register_plugin(plugin, name)
except:
except ImportError:
log.exception("Unable to load plugin: %s", name)
class PluginManager(object):
class PluginManager(object):
def __init__(self, xmpp, config=None):
#: We will track all enabled plugins in a set so that we
#: can enable plugins in batches and pull in dependencies
@ -181,7 +181,7 @@ class PluginManager(object):
def enable_all(self, names=None, config=None):
"""Enable all registered plugins.
:param list names: A list of plugin names to enable. If
none are provided, all registered plugins
will be enabled.
@ -292,7 +292,7 @@ class BasePlugin(object):
def post_init(self):
"""Initialize any cross-plugin state.
Only needed if the plugin has circular dependencies.
"""
pass

View File

@ -81,7 +81,8 @@ class XEP_0027(BasePlugin):
def _sign_presence(self, stanza):
if isinstance(stanza, Presence):
if stanza['type'] == 'available' or stanza['type'] in Presence.showtypes:
if stanza['type'] == 'available' or \
stanza['type'] in Presence.showtypes:
stanza['signed'] = stanza['status']
return stanza

View File

@ -51,6 +51,3 @@ class Encrypted(ElementBase):
if self.xml.text:
return xmpp['xep_0027'].decrypt(self.xml.text, parent['to'])
return None

View File

@ -339,8 +339,8 @@ class XEP_0030(BasePlugin):
if local:
log.debug("Looking up local disco#info data " + \
"for %s, node %s.", jid, node)
info = self.api['get_info'](jid, node,
kwargs.get('ifrom', None),
info = self.api['get_info'](jid, node,
kwargs.get('ifrom', None),
kwargs)
info = self._fix_default_info(info)
return self._wrap(kwargs.get('ifrom', None), jid, info)
@ -348,8 +348,8 @@ class XEP_0030(BasePlugin):
if cached:
log.debug("Looking up cached disco#info data " + \
"for %s, node %s.", jid, node)
info = self.api['get_cached_info'](jid, node,
kwargs.get('ifrom', None),
info = self.api['get_cached_info'](jid, node,
kwargs.get('ifrom', None),
kwargs)
if info is not None:
return self._wrap(kwargs.get('ifrom', None), jid, info)
@ -405,8 +405,8 @@ class XEP_0030(BasePlugin):
Otherwise the parameter is ignored.
"""
if local or jid is None:
items = self.api['get_items'](jid, node,
kwargs.get('ifrom', None),
items = self.api['get_items'](jid, node,
kwargs.get('ifrom', None),
kwargs)
return self._wrap(kwargs.get('ifrom', None), jid, items)

View File

@ -1,167 +0,0 @@
"""
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 sleekxmpp import Message
from sleekxmpp.xmlstream.handler.callback import Callback
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
from sleekxmpp.plugins import BasePlugin, register_plugin
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_0033(BasePlugin):
"""
XEP-0033: Extended Stanza Addressing
"""
name = 'xep_0033'
description = 'XEP-0033: Extended Stanza Addressing'
dependencies = set(['xep_0033'])
def plugin_init(self):
self.xep = '0033'
register_stanza_plugin(Message, Addresses)
self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace)
xep_0033 = XEP_0033
register_plugin(XEP_0033)

View File

@ -0,0 +1,20 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0033 import stanza
from sleekxmpp.plugins.xep_0033.stanza import Addresses, Address
from sleekxmpp.plugins.xep_0033.addresses import XEP_0033
register_plugin(XEP_0033)
# Retain some backwards compatibility
xep_0033 = XEP_0033
Addresses.addAddress = Addresses.add_address

View File

@ -0,0 +1,32 @@
"""
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 sleekxmpp import Message, Presence
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0033 import stanza, Addresses
class XEP_0033(BasePlugin):
"""
XEP-0033: Extended Stanza Addressing
"""
name = 'xep_0033'
description = 'XEP-0033: Extended Stanza Addressing'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
self.xmpp['xep_0030'].add_feature(Addresses.namespace)
register_stanza_plugin(Message, Addresses)
register_stanza_plugin(Presence, Addresses)

View File

@ -0,0 +1,131 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import JID, ElementBase, ET, register_stanza_plugin
class Addresses(ElementBase):
name = 'addresses'
namespace = 'http://jabber.org/protocol/address'
plugin_attrib = 'addresses'
interfaces = set()
def add_address(self, atype='to', jid='', node='', uri='',
desc='', delivered=False):
addr = Address(parent=self)
addr['type'] = atype
addr['jid'] = jid
addr['node'] = node
addr['uri'] = uri
addr['desc'] = desc
addr['delivered'] = delivered
return addr
# Additional methods for manipulating sets of addresses
# based on type are generated below.
class Address(ElementBase):
name = 'address'
namespace = 'http://jabber.org/protocol/address'
plugin_attrib = 'address'
interfaces = set(['type', 'jid', 'node', 'uri', 'desc', 'delivered'])
address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def get_jid(self):
return JID(self._get_attr('jid'))
def set_jid(self, value):
self._set_attr('jid', str(value))
def get_delivered(self):
value = self._get_attr('delivered', False)
return value and value.lower() in ('true', '1')
def set_delivered(self, delivered):
if delivered:
self._set_attr('delivered', 'true')
else:
del self['delivered']
def set_uri(self, uri):
if uri:
del self['jid']
del self['node']
self._set_attr('uri', uri)
else:
self._del_attr('uri')
# =====================================================================
# Auto-generate address type filters for the Addresses class.
def _addr_filter(atype):
def _type_filter(addr):
if isinstance(addr, Address):
if atype == 'all' or addr['type'] == atype:
return True
return False
return _type_filter
def _build_methods(atype):
def get_multi(self):
return list(filter(_addr_filter(atype), self))
def set_multi(self, value):
del self[atype]
for addr in value:
# Support assigning dictionary versions of addresses
# instead of full Address objects.
if not isinstance(addr, Address):
if atype != 'all':
addr['type'] = atype
elif 'atype' in addr and 'type' not in addr:
addr['type'] = addr['atype']
addrObj = Address()
addrObj.values = addr
addr = addrObj
self.append(addr)
def del_multi(self):
res = list(filter(_addr_filter(atype), self))
for addr in res:
self.iterables.remove(addr)
self.xml.remove(addr.xml)
return get_multi, set_multi, del_multi
for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'):
get_multi, set_multi, del_multi = _build_methods(atype)
Addresses.interfaces.add(atype)
setattr(Addresses, "get_%s" % atype, get_multi)
setattr(Addresses, "set_%s" % atype, set_multi)
setattr(Addresses, "del_%s" % atype, del_multi)
# To retain backwards compatibility:
setattr(Addresses, "get%s" % atype.title(), get_multi)
setattr(Addresses, "set%s" % atype.title(), set_multi)
setattr(Addresses, "del%s" % atype.title(), del_multi)
if atype == 'all':
Addresses.interfaces.add('addresses')
setattr(Addresses, "getAddresses", get_multi)
setattr(Addresses, "setAddresses", set_multi)
setattr(Addresses, "delAddresses", del_multi)
register_stanza_plugin(Addresses, Address, iterable=True)

View File

@ -110,14 +110,14 @@ class Command(ElementBase):
"""
Return the set of allowable next actions.
"""
actions = []
actions = set()
actions_xml = self.find('{%s}actions' % self.namespace)
if actions_xml is not None:
for action in self.next_actions:
action_xml = actions_xml.find('{%s}%s' % (self.namespace,
action))
if action_xml is not None:
actions.append(action)
actions.add(action)
return actions
def del_actions(self):

View File

@ -72,6 +72,7 @@ class Nickname(ElementBase):
name = 'NICKNAME'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'nicknames'
interfaces = set([name])
is_extension = True
@ -94,6 +95,7 @@ class Email(ElementBase):
name = 'EMAIL'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'emails'
interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'])
sub_interfaces = set(['USERID'])
bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400'])
@ -103,8 +105,9 @@ class Address(ElementBase):
name = 'ADR'
namespace = 'vcard-temp'
plugin_attrib = name
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
plugin_multi_attrib = 'addresses'
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY'])
sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY'])
@ -115,12 +118,13 @@ class Telephone(ElementBase):
name = 'TEL'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'telephone_numbers'
interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG',
'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS',
'PREF', 'NUMBER'])
sub_interfaces = set(['NUMBER'])
bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
'ISDN', 'PCS', 'PREF'])
def setup(self, xml=None):
@ -138,9 +142,10 @@ class Label(ElementBase):
name = 'LABEL'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'labels'
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT',
'PREF', 'lines'])
bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
'INT', 'PREF'])
def add_line(self, value):
@ -171,6 +176,7 @@ class Geo(ElementBase):
name = 'GEO'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'geolocations'
interfaces = set(['LAT', 'LON'])
sub_interfaces = interfaces
@ -179,6 +185,7 @@ class Org(ElementBase):
name = 'ORG'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'organizations'
interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits'])
sub_interfaces = set(['ORGNAME', 'ORGUNIT'])
@ -210,6 +217,7 @@ class Photo(ElementBase):
name = 'PHOTO'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'photos'
interfaces = set(['TYPE', 'EXTVAL'])
sub_interfaces = interfaces
@ -218,14 +226,16 @@ class Logo(ElementBase):
name = 'LOGO'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'logos'
interfaces = set(['TYPE', 'EXTVAL'])
sub_interfaces = interfaces
class Sound(ElementBase):
name = 'LOGO'
name = 'SOUND'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'sounds'
interfaces = set(['PHONETC', 'EXTVAL'])
sub_interfaces = interfaces
@ -264,6 +274,7 @@ class Classification(ElementBase):
name = 'CLASS'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'classifications'
interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'])
bool_interfaces = interfaces
@ -272,6 +283,7 @@ class Categories(ElementBase):
name = 'CATEGORIES'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'categories'
interfaces = set([name])
is_extension = True
@ -301,6 +313,7 @@ class Birthday(ElementBase):
name = 'BDAY'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'birthdays'
interfaces = set([name])
is_extension = True
@ -319,6 +332,7 @@ class Rev(ElementBase):
name = 'REV'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'revision_dates'
interfaces = set([name])
is_extension = True
@ -337,6 +351,7 @@ class Title(ElementBase):
name = 'TITLE'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'titles'
interfaces = set([name])
is_extension = True
@ -351,6 +366,7 @@ class Role(ElementBase):
name = 'ROLE'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'roles'
interfaces = set([name])
is_extension = True
@ -365,6 +381,7 @@ class Note(ElementBase):
name = 'NOTE'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'notes'
interfaces = set([name])
is_extension = True
@ -379,6 +396,7 @@ class Desc(ElementBase):
name = 'DESC'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'descriptions'
interfaces = set([name])
is_extension = True
@ -393,6 +411,7 @@ class URL(ElementBase):
name = 'URL'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'urls'
interfaces = set([name])
is_extension = True
@ -407,6 +426,7 @@ class UID(ElementBase):
name = 'UID'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'uids'
interfaces = set([name])
is_extension = True
@ -421,6 +441,7 @@ class ProdID(ElementBase):
name = 'PRODID'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'product_ids'
interfaces = set([name])
is_extension = True
@ -435,6 +456,7 @@ class Mailer(ElementBase):
name = 'MAILER'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'mailers'
interfaces = set([name])
is_extension = True
@ -449,6 +471,7 @@ class SortString(ElementBase):
name = 'SORT-STRING'
namespace = 'vcard-temp'
plugin_attrib = 'SORT_STRING'
plugin_multi_attrib = 'sort_strings'
interfaces = set([name])
is_extension = True
@ -463,6 +486,7 @@ class Agent(ElementBase):
name = 'AGENT'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'agents'
interfaces = set(['EXTVAL'])
sub_interfaces = interfaces
@ -471,6 +495,7 @@ class JabberID(ElementBase):
name = 'JABBERID'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'jids'
interfaces = set([name])
is_extension = True
@ -485,11 +510,12 @@ class TimeZone(ElementBase):
name = 'TZ'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'timezones'
interfaces = set([name])
is_extension = True
def set_tz(self, value):
time = xep_0082.time(offset=value)
time = xep_0082.time(offset=value)
if time[-1] == 'Z':
self.xml.text = 'Z'
else:

View File

@ -53,7 +53,7 @@ class XEP_0054(BasePlugin):
def make_vcard(self):
return VCardTemp()
def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,
def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,
block=True, callback=None, timeout=None):
if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain:
local = True
@ -84,12 +84,12 @@ class XEP_0054(BasePlugin):
iq.enable('vcard_temp')
vcard = iq.send(block=block, callback=callback, timeout=timeout)
if block:
self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp'])
return vcard
def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
callback=None, timeout=None):
if self.xmpp.is_component:
self.api['set_vcard'](jid, None, ifrom, vcard)
@ -107,7 +107,7 @@ class XEP_0054(BasePlugin):
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
return
elif iq['type'] == 'get':
vcard = self.api['get_vard'](iq['from'].bare)
vcard = self.api['get_vcard'](iq['from'].bare)
if isinstance(vcard, Iq):
vcard.send()
else:

View File

@ -74,7 +74,7 @@ class Set(ElementBase):
if fi is not None:
if val:
fi.attrib['index'] = val
else:
elif 'index' in fi.attrib:
del fi.attrib['index']
elif val:
fi = ET.Element("{%s}first" % (self.namespace))
@ -93,7 +93,7 @@ class Set(ElementBase):
def set_before(self, val):
b = self.xml.find("{%s}before" % (self.namespace))
if b is None and val == True:
if b is None and val is True:
self._set_sub_text('{%s}before' % self.namespace, '', True)
else:
self._set_sub_text('{%s}before' % self.namespace, val)

View File

@ -77,12 +77,12 @@ class Item(ElementBase):
self.append(value)
def get_payload(self):
childs = self.xml.getchildren()
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
def del_payload(self):
for child in self.xml.getchildren():
for child in self.xml:
self.xml.remove(child)
@ -254,12 +254,12 @@ class PubsubState(ElementBase):
self.xml.append(value)
def get_payload(self):
childs = self.xml.getchildren()
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
def del_payload(self):
for child in self.xml.getchildren():
for child in self.xml:
self.xml.remove(child)

View File

@ -33,7 +33,7 @@ class PubsubErrorCondition(ElementBase):
def get_condition(self):
"""Return the condition element's name."""
for child in self.parent().xml.getchildren():
for child in self.parent().xml:
if "{%s}" % self.condition_ns in child.tag:
cond = child.tag.split('}', 1)[-1]
if cond in self.conditions:
@ -55,7 +55,7 @@ class PubsubErrorCondition(ElementBase):
def del_condition(self):
"""Remove the condition element."""
for child in self.parent().xml.getchildren():
for child in self.parent().xml:
if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:

View File

@ -31,12 +31,12 @@ class EventItem(ElementBase):
self.xml.append(value)
def get_payload(self):
childs = self.xml.getchildren()
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
def del_payload(self):
for child in self.xml.getchildren():
for child in self.xml:
self.xml.remove(child)

View File

@ -39,5 +39,3 @@ class AuthFeature(ElementBase):
interfaces = set()
plugin_tag_map = {}
plugin_attrib_map = {}

View File

@ -40,33 +40,33 @@ class XEP_0080(BasePlugin):
accuracy -- Horizontal GPS error in meters.
alt -- Altitude in meters above or below sea level.
area -- A named area such as a campus or neighborhood.
bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in
bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in
decimal degrees relative to true north.
building -- A specific building on a street or in an area.
country -- The nation where the user is located.
countrycode -- The ISO 3166 two-letter country code.
datum -- GPS datum.
description -- A natural-language name for or description of
description -- A natural-language name for or description of
the location.
error -- Horizontal GPS error in arc minutes. Obsoleted by
the accuracy parameter.
floor -- A particular floor in a building.
lat -- Latitude in decimal degrees North.
locality -- A locality within the administrative region, such
locality -- A locality within the administrative region, such
as a town or city.
lon -- Longitude in decimal degrees East.
postalcode -- A code used for postal delivery.
region -- An administrative region of the nation, such
region -- An administrative region of the nation, such
as a state or province.
room -- A particular room in a building.
speed -- The speed at which the entity is moving,
speed -- The speed at which the entity is moving,
in meters per second.
street -- A thoroughfare within the locality, or a crossing
of two thoroughfares.
text -- A catch-all element that captures any other
text -- A catch-all element that captures any other
information about the location.
timestamp -- UTC timestamp specifying the moment when the
timestamp -- UTC timestamp specifying the moment when the
reading was taken.
uri -- A URI or URL pointing to information about
the location.
@ -115,7 +115,7 @@ class XEP_0080(BasePlugin):
be executed when a reply stanza is received.
"""
geoloc = Geoloc()
return self.xmpp['xep_0163'].publish(geoloc,
return self.xmpp['xep_0163'].publish(geoloc,
ifrom=ifrom,
block=block,
callback=callback,

View File

@ -31,33 +31,33 @@ class Geoloc(ElementBase):
accuracy -- Horizontal GPS error in meters.
alt -- Altitude in meters above or below sea level.
area -- A named area such as a campus or neighborhood.
bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in
bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in
decimal degrees relative to true north.
building -- A specific building on a street or in an area.
country -- The nation where the user is located.
countrycode -- The ISO 3166 two-letter country code.
datum -- GPS datum.
description -- A natural-language name for or description of
description -- A natural-language name for or description of
the location.
error -- Horizontal GPS error in arc minutes. Obsoleted by
the accuracy parameter.
floor -- A particular floor in a building.
lat -- Latitude in decimal degrees North.
locality -- A locality within the administrative region, such
locality -- A locality within the administrative region, such
as a town or city.
lon -- Longitude in decimal degrees East.
postalcode -- A code used for postal delivery.
region -- An administrative region of the nation, such
region -- An administrative region of the nation, such
as a state or province.
room -- A particular room in a building.
speed -- The speed at which the entity is moving,
speed -- The speed at which the entity is moving,
in meters per second.
street -- A thoroughfare within the locality, or a crossing
of two thoroughfares.
text -- A catch-all element that captures any other
text -- A catch-all element that captures any other
information about the location.
timestamp -- UTC timestamp specifying the moment when the
timestamp -- UTC timestamp specifying the moment when the
reading was taken.
uri -- A URI or URL pointing to information about
the location.
@ -65,10 +65,10 @@ class Geoloc(ElementBase):
namespace = 'http://jabber.org/protocol/geoloc'
name = 'geoloc'
interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',
'country', 'countrycode', 'datum', 'dscription',
'error', 'floor', 'lat', 'locality', 'lon',
'postalcode', 'region', 'room', 'speed', 'street',
interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',
'country', 'countrycode', 'datum', 'dscription',
'error', 'floor', 'lat', 'locality', 'lon',
'postalcode', 'region', 'room', 'speed', 'street',
'text', 'timestamp', 'uri'))
sub_interfaces = interfaces
plugin_attrib = name
@ -88,7 +88,7 @@ class Geoloc(ElementBase):
"""
self._set_sub_text('accuracy', text=str(accuracy))
return self
def get_accuracy(self):
"""
Return the value of the <accuracy> element as an integer.
@ -111,7 +111,7 @@ class Geoloc(ElementBase):
"""
self._set_sub_text('alt', text=str(alt))
return self
def get_alt(self):
"""
Return the value of the <alt> element as an integer.
@ -130,8 +130,8 @@ class Geoloc(ElementBase):
Set the value of the <bearing> element.
Arguments:
bearing -- GPS bearing (direction in which the entity is heading
to reach its next waypoint), measured in decimal
bearing -- GPS bearing (direction in which the entity is heading
to reach its next waypoint), measured in decimal
degrees relative to true north
"""
self._set_sub_text('bearing', text=str(bearing))
@ -155,7 +155,7 @@ class Geoloc(ElementBase):
Set the value of the <error> element.
Arguments:
error -- Horizontal GPS error in arc minutes; this
error -- Horizontal GPS error in arc minutes; this
element is deprecated in favor of <accuracy/>
"""
self._set_sub_text('error', text=str(error))
@ -183,7 +183,7 @@ class Geoloc(ElementBase):
"""
self._set_sub_text('lat', text=str(lat))
return self
def get_lat(self):
"""
Return the value of the <lat> element as a float.
@ -196,7 +196,7 @@ class Geoloc(ElementBase):
return float(p)
except ValueError:
return None
def set_lon(self, lon):
"""
Set the value of the <lon> element.
@ -225,12 +225,12 @@ class Geoloc(ElementBase):
Set the value of the <speed> element.
Arguments:
speed -- The speed at which the entity is moving,
speed -- The speed at which the entity is moving,
in meters per second
"""
self._set_sub_text('speed', text=str(speed))
return self
def get_speed(self):
"""
Return the value of the <speed> element as a float.

View File

@ -42,6 +42,7 @@ def format_date(time_obj):
time_obj = time_obj.date()
return time_obj.isoformat()
def format_time(time_obj):
"""
Return a formatted string version of a time object.
@ -60,6 +61,7 @@ def format_time(time_obj):
return '%sZ' % timestamp
return timestamp
def format_datetime(time_obj):
"""
Return a formatted string version of a datetime object.
@ -76,6 +78,7 @@ def format_datetime(time_obj):
return '%sZ' % timestamp
return timestamp
def date(year=None, month=None, day=None, obj=False):
"""
Create a date only timestamp for the given instant.
@ -98,9 +101,10 @@ def date(year=None, month=None, day=None, obj=False):
day = today.day
value = dt.date(year, month, day)
if obj:
return value
return value
return format_date(value)
def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
"""
Create a time only timestamp for the given instant.
@ -136,6 +140,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
return value
return format_time(value)
def datetime(year=None, month=None, day=None, hour=None,
min=None, sec=None, micro=None, offset=None,
separators=True, obj=False):
@ -181,7 +186,7 @@ def datetime(year=None, month=None, day=None, hour=None,
value = dt.datetime(year, month, day, hour,
min, sec, micro, offset)
if obj:
return value
return value
return format_datetime(value)

View File

@ -0,0 +1,16 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0084 import stanza
from sleekxmpp.plugins.xep_0084.stanza import Data, MetaData
from sleekxmpp.plugins.xep_0084.avatar import XEP_0084
register_plugin(XEP_0084)

View File

@ -0,0 +1,101 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import hashlib
import logging
from sleekxmpp import Iq
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.xep_0084 import stanza, Data, MetaData
log = logging.getLogger(__name__)
class XEP_0084(BasePlugin):
name = 'xep_0084'
description = 'XEP-0084: User Avatar'
dependencies = set(['xep_0163', 'xep_0060'])
stanza = stanza
def plugin_init(self):
self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData)
pubsub_stanza = self.xmpp['xep_0060'].stanza
register_stanza_plugin(pubsub_stanza.Item, Data)
register_stanza_plugin(pubsub_stanza.EventItem, Data)
self.xmpp['xep_0060'].map_node_event(Data.namespace, 'avatar_data')
def retrieve_avatar(self, jid, id, url=None, ifrom=None, block=True,
callback=None, timeout=None):
return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def publish_avatar(self, data, ifrom=None, block=True, callback=None,
timeout=None):
payload = Data()
payload['value'] = data
return self.xmpp['xep_0163'].publish(payload,
node=Data.namespace,
id=hashlib.sha1(data).hexdigest(),
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def publish_avatar_metadata(self, items=None, pointers=None,
ifrom=None, block=True,
callback=None, timeout=None):
metadata = MetaData()
if items is None:
items = []
for info in items:
metadata.add_info(info['id'], info['type'], info['bytes'],
height=info.get('height', ''),
width=info.get('width', ''),
url=info.get('url', ''))
for pointer in pointers:
metadata.add_pointer(pointer)
return self.xmpp['xep_0163'].publish(payload,
node=Data.namespace,
id=hashlib.sha1(data).hexdigest(),
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
"""
Clear existing avatar metadata information to stop notifications.
Arguments:
ifrom -- Specify the sender's JID.
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
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
metadata = MetaData()
return self.xmpp['xep_0163'].publish(metadata,
node=MetaData.namespace,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)

View File

@ -0,0 +1,78 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from base64 import b64encode, b64decode
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
class Data(ElementBase):
name = 'data'
namespace = 'urn:xmpp:avatar:data'
plugin_attrib = 'avatar_data'
interfaces = set(['value'])
def get_value(self):
if self.xml.text:
return b64decode(bytes(self.xml.text))
return ''
def set_value(self, value):
if value:
self.xml.text = b64encode(bytes(value))
else:
self.xml.text = ''
def del_value(self):
self.xml.text = ''
class MetaData(ElementBase):
name = 'metadata'
namespace = 'urn:xmpp:avatar:metadata'
plugin_attrib = 'avatar_metadata'
interfaces = set()
def add_info(self, id, itype, ibytes, height=None, width=None, url=None):
info = Info()
info.values = {'id': id,
'type': itype,
'bytes': ibytes,
'height': height,
'width': width,
'url': url}
self.append(info)
def add_pointer(self, xml):
if not isinstance(xml, Pointer):
pointer = Pointer()
pointer.append(xml)
self.append(pointer)
else:
self.append(xml)
class Info(ElementBase):
name = 'info'
namespace = 'urn:xmpp:avatar:metadata'
plugin_attrib = 'info'
plugin_multi_attrib = 'items'
interfaces = set(['bytes', 'height', 'id', 'type', 'url', 'width'])
class Pointer(ElementBase):
name = 'pointer'
namespace = 'urn:xmpp:avatar:metadata'
plugin_attrib = 'pointer'
plugin_multi_attrib = 'pointers'
interfaces = set()
register_stanza_plugin(MetaData, Info, iterable=True)
register_stanza_plugin(MetaData, Pointer, iterable=True)

View File

@ -47,28 +47,28 @@ class LegacyError(ElementBase):
interfaces = set(('condition',))
overrides = ['set_condition']
error_map = {'bad-request': ('modify','400'),
'conflict': ('cancel','409'),
'feature-not-implemented': ('cancel','501'),
'forbidden': ('auth','403'),
'gone': ('modify','302'),
'internal-server-error': ('wait','500'),
'item-not-found': ('cancel','404'),
'jid-malformed': ('modify','400'),
'not-acceptable': ('modify','406'),
'not-allowed': ('cancel','405'),
'not-authorized': ('auth','401'),
'payment-required': ('auth','402'),
'recipient-unavailable': ('wait','404'),
'redirect': ('modify','302'),
'registration-required': ('auth','407'),
'remote-server-not-found': ('cancel','404'),
'remote-server-timeout': ('wait','504'),
'resource-constraint': ('wait','500'),
'service-unavailable': ('cancel','503'),
'subscription-required': ('auth','407'),
'undefined-condition': (None,'500'),
'unexpected-request': ('wait','400')}
error_map = {'bad-request': ('modify', '400'),
'conflict': ('cancel', '409'),
'feature-not-implemented': ('cancel', '501'),
'forbidden': ('auth', '403'),
'gone': ('modify', '302'),
'internal-server-error': ('wait', '500'),
'item-not-found': ('cancel', '404'),
'jid-malformed': ('modify', '400'),
'not-acceptable': ('modify', '406'),
'not-allowed': ('cancel', '405'),
'not-authorized': ('auth', '401'),
'payment-required': ('auth', '402'),
'recipient-unavailable': ('wait', '404'),
'redirect': ('modify', '302'),
'registration-required': ('auth', '407'),
'remote-server-not-found': ('cancel', '404'),
'remote-server-timeout': ('wait', '504'),
'resource-constraint': ('wait', '500'),
'service-unavailable': ('cancel', '503'),
'subscription-required': ('auth', '407'),
'undefined-condition': (None, '500'),
'unexpected-request': ('wait', '400')}
def setup(self, xml):
"""Don't create XML for the plugin."""

View File

@ -79,5 +79,7 @@ class XEP_0092(BasePlugin):
result = iq.send()
if result and result['type'] != 'error':
return result['software_version'].values
values = result['software_version'].values
del values['lang']
return values
return False

View File

@ -34,7 +34,7 @@ class XEP_0107(BasePlugin):
register_stanza_plugin(Message, UserMood)
self.xmpp['xep_0163'].register_pep('user_mood', UserMood)
def publish_mood(self, value=None, text=None, options=None,
def publish_mood(self, value=None, text=None, options=None,
ifrom=None, block=True, callback=None, timeout=None):
"""
Publish the user's current mood.
@ -79,7 +79,7 @@ class XEP_0107(BasePlugin):
be executed when a reply stanza is received.
"""
mood = UserMood()
return self.xmpp['xep_0163'].publish(mood,
return self.xmpp['xep_0163'].publish(mood,
node=UserMood.namespace,
ifrom=ifrom,
block=block,

View File

@ -21,7 +21,7 @@ class UserActivity(ElementBase):
'talking', 'traveling', 'undefined', 'working'])
specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries',
'cleaning', 'coding', 'commuting', 'cooking', 'cycling',
'dancing', 'day_off', 'doing_maintenance',
'dancing', 'day_off', 'doing_maintenance',
'doing_the_dishes', 'doing_the_laundry', 'driving',
'fishing', 'gaming', 'gardening', 'getting_a_haircut',
'going_out', 'hanging_out', 'having_a_beer',
@ -31,11 +31,11 @@ class UserActivity(ElementBase):
'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train',
'on_a_trip', 'on_the_phone', 'on_vacation',
'on_video_phone', 'other', 'partying', 'playing_sports',
'praying', 'reading', 'rehearsing', 'running',
'praying', 'reading', 'rehearsing', 'running',
'running_an_errand', 'scheduled_holiday', 'shaving',
'shopping', 'skiing', 'sleeping', 'smoking',
'socializing', 'studying', 'sunbathing', 'swimming',
'taking_a_bath', 'taking_a_shower', 'thinking',
'taking_a_bath', 'taking_a_shower', 'thinking',
'walking', 'walking_the_dog', 'watching_a_movie',
'watching_tv', 'working_out', 'writing'])
@ -46,7 +46,7 @@ class UserActivity(ElementBase):
if isinstance(value, tuple) or isinstance(value, list):
general = value[0]
specific = value[1]
if general in self.general:
gen_xml = ET.Element('{%s}%s' % (self.namespace, general))
if specific:

View File

@ -29,7 +29,7 @@ class XEP_0108(BasePlugin):
def plugin_init(self):
self.xmpp['xep_0163'].register_pep('user_activity', UserActivity)
def publish_activity(self, general, specific=None, text=None, options=None,
def publish_activity(self, general, specific=None, text=None, options=None,
ifrom=None, block=True, callback=None, timeout=None):
"""
Publish the user's current activity.
@ -76,7 +76,7 @@ class XEP_0108(BasePlugin):
be executed when a reply stanza is received.
"""
activity = UserActivity()
return self.xmpp['xep_0163'].publish(activity,
return self.xmpp['xep_0163'].publish(activity,
node=UserActivity.namespace,
ifrom=ifrom,
block=block,

View File

@ -35,7 +35,7 @@ class XEP_0115(BasePlugin):
stanza = stanza
def plugin_init(self):
self.hashes = {'sha-1': hashlib.sha1,
self.hashes = {'sha-1': hashlib.sha1,
'sha1': hashlib.sha1,
'md5': hashlib.md5}
@ -124,7 +124,7 @@ class XEP_0115(BasePlugin):
existing_verstring = self.get_verstring(pres['from'].full)
if str(existing_verstring) == str(pres['caps']['ver']):
return
if pres['caps']['hash'] not in self.hashes:
try:
log.debug("Unknown caps hash: %s", pres['caps']['hash'])
@ -132,7 +132,7 @@ class XEP_0115(BasePlugin):
return
except XMPPError:
return
log.debug("New caps verification string: %s", pres['caps']['ver'])
try:
node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver'])
@ -140,7 +140,7 @@ class XEP_0115(BasePlugin):
if isinstance(caps, Iq):
caps = caps['disco_info']
if self._validate_caps(caps, pres['caps']['hash'],
pres['caps']['ver']):
self.assign_verstring(pres['from'], pres['caps']['ver'])
@ -173,7 +173,8 @@ class XEP_0115(BasePlugin):
form_types.append(f_type)
deduped_form_types.add(f_type)
if len(form_types) != len(deduped_form_types):
log.debug("Duplicated FORM_TYPE values, invalid for caps")
log.debug("Duplicated FORM_TYPE values, " + \
"invalid for caps")
return False
if len(f_type) > 1:
@ -183,7 +184,8 @@ class XEP_0115(BasePlugin):
return False
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
log.debug("Field FORM_TYPE type not 'hidden', ignoring form for caps")
log.debug("Field FORM_TYPE type not 'hidden', " + \
"ignoring form for caps")
caps.xml.remove(stanza.xml)
else:
log.debug("No FORM_TYPE found, ignoring form for caps")
@ -212,7 +214,7 @@ class XEP_0115(BasePlugin):
identities = sorted(('/'.join(i) for i in identities))
features = sorted(info['features'])
S += '<'.join(identities) + '<'
S += '<'.join(features) + '<'
@ -254,7 +256,7 @@ class XEP_0115(BasePlugin):
info = info['disco_info']
ver = self.generate_verstring(info, self.hash)
self.xmpp['xep_0030'].set_info(
jid=jid,
jid=jid,
node='%s#%s' % (self.caps_node, ver),
info=info)
self.cache_caps(ver, info)

View File

@ -69,7 +69,7 @@ class StaticCaps(object):
return True
try:
info = self.disco.get_info(jid=jid, node=node,
info = self.disco.get_info(jid=jid, node=node,
ifrom=ifrom, **data)
info = self.disco._wrap(ifrom, jid, info, True)
return feature in info['disco_info']['features']
@ -99,7 +99,7 @@ class StaticCaps(object):
be skipped, even if a result has already been
cached. Defaults to false.
"""
identity = (data.get('category', None),
identity = (data.get('category', None),
data.get('itype', None),
data.get('lang', None))
@ -114,7 +114,7 @@ class StaticCaps(object):
return True
try:
info = self.disco.get_info(jid=jid, node=node,
info = self.disco.get_info(jid=jid, node=node,
ifrom=ifrom, **data)
info = self.disco._wrap(ifrom, jid, info, True)
return identity in map(trunc, info['disco_info']['identities'])

View File

@ -14,7 +14,7 @@ class UserTune(ElementBase):
name = 'tune'
namespace = 'http://jabber.org/protocol/tune'
plugin_attrib = 'tune'
interfaces = set(['artist', 'length', 'rating', 'source',
interfaces = set(['artist', 'length', 'rating', 'source',
'title', 'track', 'uri'])
sub_interfaces = interfaces

View File

@ -30,7 +30,7 @@ class XEP_0118(BasePlugin):
self.xmpp['xep_0163'].register_pep('user_tune', UserTune)
def publish_tune(self, artist=None, length=None, rating=None, source=None,
title=None, track=None, uri=None, options=None,
title=None, track=None, uri=None, options=None,
ifrom=None, block=True, callback=None, timeout=None):
"""
Publish the user's current tune.
@ -61,7 +61,7 @@ class XEP_0118(BasePlugin):
tune['title'] = title
tune['track'] = track
tune['uri'] = uri
return self.xmpp['xep_0163'].publish(tune,
return self.xmpp['xep_0163'].publish(tune,
node=UserTune.namespace,
options=options,
ifrom=ifrom,
@ -84,7 +84,7 @@ class XEP_0118(BasePlugin):
be executed when a reply stanza is received.
"""
tune = UserTune()
return self.xmpp['xep_0163'].publish(tune,
return self.xmpp['xep_0163'].publish(tune,
node=UserTune.namespace,
ifrom=ifrom,
block=block,

View File

@ -45,7 +45,7 @@ class XEP_0153(BasePlugin):
self.api.register(self._set_hash, 'set_hash', default=True)
self.api.register(self._get_hash, 'get_hash', default=True)
def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,
def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,
timeout=None, callback=None):
vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)
vcard = vcard['vcard_temp']
@ -69,7 +69,7 @@ class XEP_0153(BasePlugin):
own_jid = (jid.bare == self.xmpp.boundjid.bare)
if self.xmpp.is_component:
own_jid = (jid.domain == self.xmpp.boundjid.domain)
if jid is not None:
jid = jid.bare
self.api['set_hash'](jid, args=None)
@ -77,7 +77,7 @@ class XEP_0153(BasePlugin):
self.xmpp.roster[jid].send_last_presence()
iq = self.xmpp['xep_0054'].get_vcard(
jid=jid,
jid=jid,
ifrom=self.xmpp.boundjid)
data = iq['vcard_temp']['PHOTO']['BINVAL']
if not data:

View File

@ -56,7 +56,7 @@ class XEP_0163(BasePlugin):
jid -- Optionally specify the JID.
"""
if not isinstance(namespace, set) and not isinstance(namespace, list):
namespace = [namespace]
namespace = [namespace]
for ns in namespace:
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
@ -75,7 +75,7 @@ class XEP_0163(BasePlugin):
jid -- Optionally specify the JID.
"""
if not isinstance(namespace, set) and not isinstance(namespace, list):
namespace = [namespace]
namespace = [namespace]
for ns in namespace:
self.xmpp['xep_0030'].del_feature(jid=jid,
@ -109,6 +109,7 @@ class XEP_0163(BasePlugin):
node = stanza.namespace
return self.xmpp['xep_0060'].publish(ifrom, node,
id=id,
payload=stanza.xml,
options=options,
ifrom=ifrom,

View File

@ -78,7 +78,7 @@ class XEP_0172(BasePlugin):
be executed when a reply stanza is received.
"""
nick = UserNick()
return self.xmpp['xep_0163'].publish(nick,
return self.xmpp['xep_0163'].publish(nick,
node=UserNick.namespace,
ifrom=ifrom,
block=block,

View File

@ -100,13 +100,13 @@ class XEP_0184(BasePlugin):
if not isinstance(stanza, Message):
return stanza
if stanza['request_receipt']:
return stanza
if not stanza['type'] in self.ack_types:
return stanza
if stanza['receipt']:
return stanza

View File

@ -82,7 +82,6 @@ class Resumed(StanzaBase):
self._set_attr('h', str(val))
class Failed(StanzaBase, Error):
name = 'failed'
namespace = 'urn:xmpp:sm:3'
@ -106,7 +105,7 @@ class StreamManagement(ElementBase):
self.del_required()
if val:
self._set_sub_text('required', '', keep=True)
def del_required(self):
self._del_sub('required')
@ -117,7 +116,7 @@ class StreamManagement(ElementBase):
self.del_optional()
if val:
self._set_sub_text('optional', '', keep=True)
def del_optional(self):
self._del_sub('optional')

View File

@ -21,7 +21,7 @@ from sleekxmpp.plugins.xep_0198 import stanza
log = logging.getLogger(__name__)
MAX_SEQ = 2**32
MAX_SEQ = 2 ** 32
class XEP_0198(BasePlugin):
@ -69,7 +69,7 @@ class XEP_0198(BasePlugin):
self.enabled = threading.Event()
self.unacked_queue = collections.deque()
self.seq_lock = threading.Lock()
self.handled_lock = threading.Lock()
self.ack_lock = threading.Lock()
@ -197,7 +197,7 @@ class XEP_0198(BasePlugin):
def _handle_enabled(self, stanza):
"""Save the SM-ID, if provided.
Raises an :term:`sm_enabled` event.
"""
self.xmpp.features.add('stream_management')
@ -231,7 +231,7 @@ class XEP_0198(BasePlugin):
def _handle_ack(self, ack):
"""Process a server ack by freeing acked stanzas from the queue.
Raises a :term:`stanza_acked` event for each acked stanza.
"""
if ack['h'] == self.last_ack:
@ -243,10 +243,10 @@ class XEP_0198(BasePlugin):
log.debug("Ack: %s, Last Ack: %s, " + \
"Unacked: %s, Num Acked: %s, " + \
"Remaining: %s",
ack['h'],
self.last_ack,
ack['h'],
self.last_ack,
num_unacked,
num_acked,
num_acked,
num_unacked - num_acked)
for x in range(num_acked):
seq, stanza = self.unacked_queue.popleft()

View File

@ -40,8 +40,12 @@ class XEP_0202(BasePlugin):
# custom function can be supplied which accepts
# the JID of the entity to query for the time.
self.local_time = self.config.get('local_time', None)
def default_local_time(jid):
return xep_0082.datetime(offset=self.tz_offset)
if not self.local_time:
self.local_time = lambda x: xep_0082.datetime(offset=self.tz_offset)
self.local_time = default_local_time
self.xmpp.registerHandler(
Callback('Entity Time',

View File

@ -13,9 +13,7 @@ from sleekxmpp.plugins.xep_0203.stanza import Delay
from sleekxmpp.plugins.xep_0203.delay import XEP_0203
register_plugin(XEP_0203)
# Retain some backwards compatibility
xep_0203 = XEP_0203

View File

@ -0,0 +1,126 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin, register_plugin
log = logging.getLogger(__name__)
class XEP_0222(BasePlugin):
"""
XEP-0222: Persistent Storage of Public Data via PubSub
"""
name = 'xep_0222'
description = 'XEP-0222: Persistent Storage of Private Data via PubSub'
dependencies = set(['xep_0163', 'xep_0060', 'xep_0004'])
profile = {'pubsub#persist_items': True,
'pubsub#send_last_published_item': 'never'}
def configure(self, node):
"""
Update a node's configuration to match the public storage profile.
"""
config = self.xmpp['xep_0004'].Form()
config['type'] = 'submit'
for field, value in self.profile.items():
config.add_field(var=field, value=value)
return self.xmpp['xep_0060'].set_node_config(None, node, config,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def store(self, stanza, node=None, id=None, ifrom=None, options=None,
block=True, callback=None, timeout=None):
"""
Store public data via PEP.
This is just a (very) thin wrapper around the XEP-0060 publish()
method to set the defaults expected by PEP.
Arguments:
stanza -- The private content to store.
node -- The node to publish the content to. If not specified,
the stanza's namespace will be used.
id -- Optionally specify the ID of the item.
options -- Publish options to use, which will be modified to
fit the persistent storage option profile.
ifrom -- Specify the sender's JID.
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
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
if not options:
options = self.xmpp['xep_0004'].stanza.Form()
options['type'] = 'submit'
options.add_field(
var='FORM_TYPE',
ftype='hidden',
value='http://jabber.org/protocol/pubsub#publish-options')
for field, value in self.profile.items():
if field not in options.fields:
options.add_field(var=field)
options.fields[field]['value'] = value
return self.xmpp['xep_0163'].publish(stanza, node,
options=options,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def retrieve(self, node, id=None, item_ids=None, ifrom=None,
block=True, callback=None, timeout=None):
"""
Retrieve public data via PEP.
This is just a (very) thin wrapper around the XEP-0060 publish()
method to set the defaults expected by PEP.
Arguments:
node -- The node to retrieve content from.
id -- Optionally specify the ID of the item.
item_ids -- Specify a group of IDs. If id is also specified, it
will be included in item_ids.
ifrom -- Specify the sender's JID.
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
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
if item_ids is None:
item_ids = []
if id is not None:
item_ids.append(id)
return self.xmpp['xep_0060'].get_items(None, node,
item_ids=item_ids,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
register_plugin(XEP_0222)

View File

@ -0,0 +1,126 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin, register_plugin
log = logging.getLogger(__name__)
class XEP_0223(BasePlugin):
"""
XEP-0223: Persistent Storage of Private Data via PubSub
"""
name = 'xep_0223'
description = 'XEP-0223: Persistent Storage of Private Data via PubSub'
dependencies = set(['xep_0163', 'xep_0060', 'xep_0004'])
profile = {'pubsub#persist_items': True,
'pubsub#send_last_published_item': 'never'}
def configure(self, node):
"""
Update a node's configuration to match the public storage profile.
"""
config = self.xmpp['xep_0004'].Form()
config['type'] = 'submit'
for field, value in self.profile.items():
config.add_field(var=field, value=value)
return self.xmpp['xep_0060'].set_node_config(None, node, config,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def store(self, stanza, node=None, id=None, ifrom=None, options=None,
block=True, callback=None, timeout=None):
"""
Store private data via PEP.
This is just a (very) thin wrapper around the XEP-0060 publish()
method to set the defaults expected by PEP.
Arguments:
stanza -- The private content to store.
node -- The node to publish the content to. If not specified,
the stanza's namespace will be used.
id -- Optionally specify the ID of the item.
options -- Publish options to use, which will be modified to
fit the persistent storage option profile.
ifrom -- Specify the sender's JID.
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
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
if not options:
options = self.xmpp['xep_0004'].stanza.Form()
options['type'] = 'submit'
options.add_field(
var='FORM_TYPE',
ftype='hidden',
value='http://jabber.org/protocol/pubsub#publish-options')
for field, value in self.profile.items():
if field not in options.fields:
options.add_field(var=field)
options.fields[field]['value'] = value
return self.xmpp['xep_0163'].publish(stanza, node,
options=options,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def retrieve(self, node, id=None, item_ids=None, ifrom=None,
block=True, callback=None, timeout=None):
"""
Retrieve private data via PEP.
This is just a (very) thin wrapper around the XEP-0060 publish()
method to set the defaults expected by PEP.
Arguments:
node -- The node to retrieve content from.
id -- Optionally specify the ID of the item.
item_ids -- Specify a group of IDs. If id is also specified, it
will be included in item_ids.
ifrom -- Specify the sender's JID.
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
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
if item_ids is None:
item_ids = []
if id is not None:
item_ids.append(id)
return self.xmpp['xep_0060'].get_items(None, node,
item_ids=item_ids,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
register_plugin(XEP_0223)

View File

@ -1,6 +1,6 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz,
Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP.

View File

@ -1,6 +1,6 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz,
Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP.
@ -58,7 +58,6 @@ class XEP_0231(BasePlugin):
self.api.register(self._set_bob, 'set_bob', default=True)
self.api.register(self._del_bob, 'del_bob', default=True)
def set_bob(self, data, mtype, cid=None, max_age=None):
if cid is None:
cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest()
@ -73,7 +72,7 @@ class XEP_0231(BasePlugin):
return cid
def get_bob(self, jid=None, cid=None, cached=True, ifrom=None,
def get_bob(self, jid=None, cid=None, cached=True, ifrom=None,
block=True, timeout=None, callback=None):
if cached:
data = self.api['get_bob'](None, None, ifrom, args=cid)
@ -112,7 +111,7 @@ class XEP_0231(BasePlugin):
iq.send()
def _handle_bob(self, stanza):
self.api['set_bob'](stanza['from'], None,
self.api['set_bob'](stanza['from'], None,
stanza['to'], args=stanza['bob'])
self.xmpp.event('bob', stanza)

View File

@ -1,6 +1,6 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz,
Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP.

View File

@ -0,0 +1,18 @@
"""
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 sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0258 import stanza
from sleekxmpp.plugins.xep_0258.stanza import SecurityLabel, Label
from sleekxmpp.plugins.xep_0258.stanza import DisplayMarking, EquivalentLabel
from sleekxmpp.plugins.xep_0258.stanza import ESSLabel, Catalog, CatalogItem
from sleekxmpp.plugins.xep_0258.security_labels import XEP_0258
register_plugin(XEP_0258)

View File

@ -0,0 +1,40 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq, Message
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0258 import stanza, SecurityLabel, Catalog
log = logging.getLogger(__name__)
class XEP_0258(BasePlugin):
name = 'xep_0258'
description = 'XEP-0258: Security Labels in XMPP'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace)
register_stanza_plugin(Message, SecurityLabel)
register_stanza_plugin(Iq, Catalog)
def get_catalog(self, jid, ifrom=None, block=True,
callback=None, timeout=None):
iq = self.xmpp.Iq()
iq['to'] = jid
iq['from'] = ifrom
iq['type'] = 'get'
iq.enable('security_label_catalog')
return iq.send(block=block, callback=callback, timeout=timeout)

View File

@ -0,0 +1,142 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from base64 import b64encode, b64decode
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class SecurityLabel(ElementBase):
name = 'securitylabel'
namespace = 'urn:xmpp:sec-label:0'
plugin_attrib = 'security_label'
def add_equivalent(self, label):
equiv = EquivalentLabel(parent=self)
equiv.append(label)
return equiv
class Label(ElementBase):
name = 'label'
namespace = 'urn:xmpp:sec-label:0'
plugin_attrib = 'label'
class DisplayMarking(ElementBase):
name = 'displaymarking'
namespace = 'urn:xmpp:sec-label:0'
plugin_attrib = 'display_marking'
interfaces = set(['fgcolor', 'bgcolor', 'value'])
def get_fgcolor(self):
return self._get_attr('fgcolor', 'black')
def get_bgcolor(self):
return self._get_attr('fgcolor', 'white')
def get_value(self):
return self.xml.text
def set_value(self, value):
self.xml.text = value
def del_value(self):
self.xml.text = ''
class EquivalentLabel(ElementBase):
name = 'equivalentlabel'
namespace = 'urn:xmpp:sec-label:0'
plugin_attrib = 'equivalent_label'
plugin_multi_attrib = 'equivalent_labels'
class Catalog(ElementBase):
name = 'catalog'
namespace = 'urn:xmpp:sec-label:catalog:2'
plugin_attrib = 'security_label_catalog'
interfaces = set(['to', 'from', 'name', 'desc', 'id', 'size', 'restrict'])
def get_to(self):
return JID(self._get_attr('to'))
pass
def set_to(self, value):
return self._set_attr('to', str(value))
def get_from(self):
return JID(self._get_attr('from'))
def set_from(self, value):
return self._set_attr('from', str(value))
def get_restrict(self):
value = self._get_attr('restrict', '')
if value and value.lower() in ('true', '1'):
return True
return False
def set_restrict(self, value):
self._del_attr('restrict')
if value:
self._set_attr('restrict', 'true')
elif value is False:
self._set_attr('restrict', 'false')
class CatalogItem(ElementBase):
name = 'catalog'
namespace = 'urn:xmpp:sec-label:catalog:2'
plugin_attrib = 'item'
plugin_multi_attrib = 'items'
interfaces = set(['selector', 'default'])
def get_default(self):
value = self._get_attr('default', '')
if value.lower() in ('true', '1'):
return True
return False
def set_default(self, value):
self._del_attr('default')
if value:
self._set_attr('default', 'true')
elif value is False:
self._set_attr('default', 'false')
class ESSLabel(ElementBase):
name = 'esssecuritylabel'
namespace = 'urn:xmpp:sec-label:ess:0'
plugin_attrib = 'ess'
interfaces = set(['value'])
def get_value(self):
if self.xml.text:
return b64decode(bytes(self.xml.text))
return ''
def set_value(self, value):
self.xml.text = ''
if value:
self.xml.text = b64encode(bytes(value))
def del_value(self):
self.xml.text = ''
register_stanza_plugin(Catalog, CatalogItem, iterable=True)
register_stanza_plugin(CatalogItem, SecurityLabel)
register_stanza_plugin(EquivalentLabel, ESSLabel)
register_stanza_plugin(Label, ESSLabel)
register_stanza_plugin(SecurityLabel, DisplayMarking)
register_stanza_plugin(SecurityLabel, EquivalentLabel, iterable=True)
register_stanza_plugin(SecurityLabel, Label)

View File

@ -307,34 +307,29 @@ class RosterItem(object):
p['from'] = self.owner
p.send()
def send_presence(self, ptype=None, pshow=None, pstatus=None,
ppriority=None, pnick=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
If no recipient is specified, send the presence immediately.
Otherwise, forward the send request to the recipient's roster
entry for processing.
Arguments:
pshow -- The presence's show value.
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pnick -- Optional nickname of the presence's sender.
"""
p = self.xmpp.make_presence(pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
ptype=ptype,
pnick=pnick,
pto=self.jid)
if self.xmpp.is_component:
p['from'] = self.owner
if p['type'] in p.showtypes or \
p['type'] in ['available', 'unavailable']:
self.last_status = p
p.send()
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.owner
if not kwargs.get('pto', ''):
kwargs['pto'] = self.jid
self.xmpp.send_presence(**kwargs)
def send_last_presence(self):
if self.last_status is None:

View File

@ -6,9 +6,11 @@
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza import Presence
from sleekxmpp.xmlstream import JID
from sleekxmpp.roster import RosterNode
class Roster(object):
"""
@ -55,6 +57,33 @@ class Roster(object):
for node in self.db.entries(None, {}):
self.add(node)
self.xmpp.add_filter('out', self._save_last_status)
def _save_last_status(self, stanza):
if isinstance(stanza, Presence):
sfrom = stanza['from'].full
sto = stanza['to'].full
if not sfrom:
sfrom = self.xmpp.boundjid
if stanza['type'] in stanza.showtypes or \
stanza['type'] in ('available', 'unavailable'):
if sto:
self[sfrom][sto].last_status = stanza
else:
self[sfrom].last_status = stanza
with self[sfrom]._last_status_lock:
for jid in self[sfrom]:
self[sfrom][jid].last_status = None
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
return stanza
def __getitem__(self, key):
"""
Return the roster node for a JID.
@ -121,29 +150,27 @@ class Roster(object):
for node in self:
self[node].reset()
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, pfrom=None, ptype=None, pnick=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
Forwards the send request to the appropriate roster to
perform the actual sending.
If no recipient is specified, send the presence immediately.
Otherwise, forward the send request to the recipient's roster
entry for processing.
Arguments:
pshow -- The presence's show value.
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pfrom -- The sender of the presence.
pnick -- Optional nickname of the presence's sender.
"""
self[pfrom].send_presence(ptype=ptype,
pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
pnick=pnick,
pto=pto)
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.jid
self.xmpp.send_presence(**kwargs)
@property
def auto_authorize(self):

View File

@ -6,6 +6,8 @@
See the file LICENSE for copying permission.
"""
import threading
from sleekxmpp.xmlstream import JID
from sleekxmpp.roster import RosterItem
@ -59,13 +61,14 @@ class RosterNode(object):
self.last_status = None
self._version = ''
self._jids = {}
self._last_status_lock = threading.Lock()
if self.db:
if hasattr(self.db, 'version'):
self._version = self.db.version(self.jid)
for jid in self.db.entries(self.jid):
self.add(jid)
@property
def version(self):
"""Retrieve the roster's version ID."""
@ -146,7 +149,7 @@ class RosterNode(object):
self.db = db
existing_entries = set(self._jids)
new_entries = set(self.db.entries(self.jid, {}))
for jid in existing_entries:
self._jids[jid].set_backend(db, save)
for jid in new_entries - existing_entries:
@ -291,8 +294,7 @@ class RosterNode(object):
for jid in self:
self[jid].reset()
def send_presence(self, ptype=None, pshow=None, pstatus=None,
ppriority=None, pnick=None, pto=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
@ -305,27 +307,14 @@ class RosterNode(object):
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pnick -- Optional nickname of the presence's sender.
"""
if pto:
self[pto].send_presence(ptype, pshow, pstatus,
ppriority, pnick)
else:
p = self.xmpp.make_presence(pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
ptype=ptype,
pnick=pnick)
if self.xmpp.is_component:
p['from'] = self.jid
if p['type'] in p.showtypes or \
p['type'] in ['available', 'unavailable']:
self.last_status = p
p.send()
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.jid
self.xmpp.send_presence(**kwargs)
def send_last_presence(self):
if self.last_status is None:

View File

@ -51,7 +51,8 @@ class Error(ElementBase):
namespace = 'jabber:client'
name = 'error'
plugin_attrib = 'error'
interfaces = set(('code', 'condition', 'text', 'type'))
interfaces = set(('code', 'condition', 'text', 'type',
'gone', 'redirect'))
sub_interfaces = set(('text',))
plugin_attrib_map = {}
plugin_tag_map = {}
@ -88,7 +89,7 @@ class Error(ElementBase):
def get_condition(self):
"""Return the condition element's name."""
for child in self.xml.getchildren():
for child in self.xml:
if "{%s}" % self.condition_ns in child.tag:
cond = child.tag.split('}', 1)[-1]
if cond in self.conditions:
@ -109,7 +110,7 @@ class Error(ElementBase):
def del_condition(self):
"""Remove the condition element."""
for child in self.xml.getchildren():
for child in self.xml:
if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:
@ -135,6 +136,33 @@ class Error(ElementBase):
self._del_sub('{%s}text' % self.condition_ns)
return self
def get_gone(self):
return self._get_sub_text('{%s}gone' % self.condition_ns, '')
def get_redirect(self):
return self._get_sub_text('{%s}redirect' % self.condition_ns, '')
def set_gone(self, value):
if value:
del self['condition']
return self._set_sub_text('{%s}gone' % self.condition_ns, value)
elif self['condition'] == 'gone':
del self['condition']
def set_redirect(self, value):
if value:
del self['condition']
ns = self.condition_ns
return self._set_sub_text('{%s}redirect' % ns, value)
elif self['condition'] == 'redirect':
del self['condition']
def del_gone(self):
self._del_sub('{%s}gone' % self.condition_ns)
def del_redirect(self):
self._del_sub('{%s}redirect' % self.condition_ns)
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.

View File

@ -122,7 +122,7 @@ class Iq(RootStanza):
def get_query(self):
"""Return the namespace of the <query> element."""
for child in self.xml.getchildren():
for child in self.xml:
if child.tag.endswith('query'):
ns = child.tag.split('}')[0]
if '{' in ns:
@ -132,7 +132,7 @@ class Iq(RootStanza):
def del_query(self):
"""Remove the <query> element."""
for child in self.xml.getchildren():
for child in self.xml:
if child.tag.endswith('query'):
self.xml.remove(child)
return self

View File

@ -78,7 +78,8 @@ class RootStanza(StanzaBase):
self['error']['type'] = 'cancel'
self.send()
# log the error
log.exception('Error handling {%s}%s stanza' , self.namespace, self.name)
log.exception('Error handling {%s}%s stanza',
self.namespace, self.name)
# Finally raise the exception to a global exception handler
self.stream.exception(e)

View File

@ -47,7 +47,7 @@ class Roster(ElementBase):
roster versioning.
"""
return self.xml.attrib.get('ver', None)
def set_ver(self, ver):
"""
Ensure handling an empty ver attribute propery.
@ -101,7 +101,8 @@ class Roster(ElementBase):
items[item['jid']] = item.values
# Remove extra JID reference to keep everything
# backward compatible
del items[item['jid']]['jid']
del items[item['jid']]['jid']
del items[item['jid']]['lang']
return items
def del_items(self):

View File

@ -54,7 +54,7 @@ class StreamError(Error, StanzaBase):
"""
namespace = 'http://etherx.jabber.org/streams'
interfaces = set(('condition', 'text'))
interfaces = set(('condition', 'text', 'see_other_host'))
conditions = set((
'bad-format', 'bad-namespace-prefix', 'conflict',
'connection-timeout', 'host-gone', 'host-unknown',
@ -66,3 +66,18 @@ class StreamError(Error, StanzaBase):
'unsupported-feature', 'unsupported-stanza-type',
'unsupported-version'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'
def get_see_other_host(self):
ns = self.condition_ns
return self._get_sub_text('{%s}see-other-host' % ns, '')
def set_see_other_host(self, value):
if value:
del self['condition']
ns = self.condition_ns
return self._set_sub_text('{%s}see-other-host' % ns, value)
elif self['condition'] == 'see-other-host':
del self['condition']
def del_see_other_host(self):
self._del_sub('{%s}see-other-host' % self.condition_ns)

View File

@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
from sleekxmpp.thirdparty import OrderedDict
from sleekxmpp.xmlstream import StanzaBase
@ -28,7 +29,10 @@ class StreamFeatures(StanzaBase):
def get_features(self):
"""
"""
return self.plugins
features = OrderedDict()
for (name, lang), plugin in self.plugins.items():
features[name] = plugin
return features
def set_features(self, value):
"""

View File

@ -76,7 +76,7 @@ class SleekTest(unittest.TestCase):
known_prefixes[prefix],
xml_string)
xml = self.parse_xml(xml_string)
xml = xml.getchildren()[0]
xml = list(xml)[0]
return xml
else:
self.fail("XML data was mal-formed:\n%s" % xml_string)
@ -333,6 +333,8 @@ class SleekTest(unittest.TestCase):
# Remove unique ID prefix to make it easier to test
self.xmpp._id_prefix = ''
self.xmpp._disconnect_wait_for_threads = False
self.xmpp.default_lang = None
self.xmpp.peer_default_lang = None
# We will use this to wait for the session_start event
# for live connections.
@ -386,6 +388,7 @@ class SleekTest(unittest.TestCase):
sid='',
stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client",
default_lang="en",
version="1.0",
xml_header=True):
"""
@ -413,6 +416,8 @@ class SleekTest(unittest.TestCase):
parts.append('from="%s"' % sfrom)
if sid:
parts.append('id="%s"' % sid)
if default_lang:
parts.append('xml:lang="%s"' % default_lang)
parts.append('version="%s"' % version)
parts.append('xmlns:stream="%s"' % stream_ns)
parts.append('xmlns="%s"' % default_ns)
@ -512,9 +517,9 @@ class SleekTest(unittest.TestCase):
if '{%s}lang' % xml_ns in recv_xml.attrib:
del recv_xml.attrib['{%s}lang' % xml_ns]
if recv_xml.getchildren:
if list(recv_xml):
# We received more than just the header
for xml in recv_xml.getchildren():
for xml in recv_xml:
self.xmpp.socket.recv_data(tostring(xml))
attrib = recv_xml.attrib
@ -564,6 +569,7 @@ class SleekTest(unittest.TestCase):
sid='',
stream_ns="http://etherx.jabber.org/streams",
default_ns="jabber:client",
default_lang="en",
version="1.0",
xml_header=False,
timeout=1):
@ -585,6 +591,7 @@ class SleekTest(unittest.TestCase):
header = self.make_header(sto, sfrom, sid,
stream_ns=stream_ns,
default_ns=default_ns,
default_lang=default_lang,
version=version,
xml_header=xml_header)
sent_header = self.xmpp.socket.next_sent(timeout)
@ -691,7 +698,7 @@ class SleekTest(unittest.TestCase):
if xml.tag.startswith('{'):
return
xml.tag = '{%s}%s' % (ns, xml.tag)
for child in xml.getchildren():
for child in xml:
self.fix_namespaces(child, ns)
def compare(self, xml, *other):
@ -734,7 +741,7 @@ class SleekTest(unittest.TestCase):
return False
# Step 4: Check children count
if len(xml.getchildren()) != len(other.getchildren()):
if len(list(xml)) != len(list(other)):
return False
# Step 5: Recursively check children

View File

@ -7,11 +7,12 @@ try:
from pyasn1.type.univ import Any, ObjectIdentifier, OctetString
from pyasn1.type.char import BMPString, IA5String, UTF8String
from pyasn1.type.useful import GeneralizedTime
from pyasn1_modules.rfc2459 import Certificate, DirectoryString, SubjectAltName, GeneralNames, GeneralName
from pyasn1_modules.rfc2459 import (Certificate, DirectoryString,
SubjectAltName, GeneralNames,
GeneralName)
from pyasn1_modules.rfc2459 import id_ce_subjectAltName as SUBJECT_ALT_NAME
from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME
XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')
@ -149,7 +150,7 @@ def verify(expected, raw_cert):
expected_wild = expected[expected.index('.'):]
expected_srv = '_xmpp-client.%s' % expected
for name in cert_names['XMPPAddr']:
for name in cert_names['XMPPAddr']:
if name == expected:
return True
for name in cert_names['SRV']:

View File

@ -49,7 +49,7 @@ class BaseHandler(object):
def match(self, xml):
"""Compare a stanza or XML object with the handler's matcher.
:param xml: An XML or
:param xml: An XML or
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object
"""
return self._matcher.match(xml)
@ -73,7 +73,7 @@ class BaseHandler(object):
self._payload = payload
def check_delete(self):
"""Check if the handler should be removed from the list
"""Check if the handler should be removed from the list
of stream handlers.
"""
return self._destroy

View File

@ -33,7 +33,7 @@ class Callback(BaseHandler):
:param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
derived object for matching stanza objects.
:param pointer: The function to execute during callback.
:param bool thread: **DEPRECATED.** Remains only for
:param bool thread: **DEPRECATED.** Remains only for
backwards compatibility.
:param bool once: Indicates if the handler should be used only
once. Defaults to False.

View File

@ -34,9 +34,9 @@ class MatchXMLMask(MatcherBase):
<message xmlns="jabber:client"><body /></message>
Use of XMLMask is discouraged, and
:class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
Use of XMLMask is discouraged, and
:class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
should be used instead.
The use of namespaces in the mask comparison is controlled by
@ -151,8 +151,8 @@ class MatchXMLMask(MatcherBase):
"""
tag = tag.split('}')[-1]
try:
children = [c.tag.split('}')[-1] for c in xml.getchildren()]
children = [c.tag.split('}')[-1] for c in xml]
index = children.index(tag)
except ValueError:
return None
return xml.getchildren()[index]
return list(xml)[index]

View File

@ -77,10 +77,10 @@ class MatchXPath(MatcherBase):
# Skip empty tag name artifacts from the cleanup phase.
continue
children = [c.tag.split('}')[-1] for c in xml.getchildren()]
children = [c.tag.split('}')[-1] for c in xml]
try:
index = children.index(tag)
except ValueError:
return False
xml = xml.getchildren()[index]
xml = list(xml)[index]
return True

View File

@ -57,7 +57,7 @@ class Task(object):
#: The keyword arguments to pass to :attr:`callback`.
self.kwargs = kwargs or {}
#: Indicates if the task should repeat after executing,
#: using the same :attr:`seconds` delay.
self.repeat = repeat
@ -103,7 +103,7 @@ class Scheduler(object):
def __init__(self, parentstop=None):
#: A queue for storing tasks
self.addq = queue.Queue()
#: A list of tasks in order of execution time.
self.schedule = []

View File

@ -12,6 +12,8 @@
:license: MIT, see LICENSE for more details
"""
from __future__ import with_statement, unicode_literals
import copy
import logging
import weakref
@ -29,6 +31,9 @@ log = logging.getLogger(__name__)
XML_TYPE = type(ET.Element('xml'))
XML_NS = 'http://www.w3.org/XML/1998/namespace'
def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
"""
Associate a stanza object as a plugin for another stanza.
@ -40,7 +45,7 @@ def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
substanzas for the parent, using ``parent['substanzas']``. If the
attribute ``plugin_multi_attrib`` was defined for the plugin, then
the substanza set can be filtered to only instances of the plugin
class. For example, given a plugin class ``Foo`` with
class. For example, given a plugin class ``Foo`` with
``plugin_multi_attrib = 'foos'`` then::
parent['foos']
@ -94,6 +99,14 @@ def multifactory(stanza, plugin_attrib):
"""
Returns a ElementBase class for handling reoccuring child stanzas
"""
def plugin_filter(self):
return lambda x: isinstance(x, self._multistanza)
def plugin_lang_filter(self, lang):
return lambda x: isinstance(x, self._multistanza) and \
x['lang'] == lang
class Multi(ElementBase):
"""
Template class for multifactory
@ -101,28 +114,45 @@ def multifactory(stanza, plugin_attrib):
def setup(self, xml=None):
self.xml = ET.Element('')
def get_multi(self):
def get_multi(self, lang=None):
parent = self.parent()
res = filter(lambda sub: isinstance(sub, self._multistanza), parent)
if not lang or lang == '*':
res = filter(plugin_filter(self), parent)
else:
res = filter(plugin_filter(self, lang), parent)
return list(res)
def set_multi(self, val):
def set_multi(self, val, lang=None):
parent = self.parent()
del parent[self.plugin_attrib]
del_multi = getattr(self, 'del_%s' % plugin_attrib)
del_multi(lang)
for sub in val:
parent.append(sub)
def del_multi(self):
def del_multi(self, lang=None):
parent = self.parent()
res = filter(lambda sub: isinstance(sub, self._multistanza), parent)
for stanza in list(res):
parent.iterables.remove(stanza)
parent.xml.remove(stanza.xml)
if not lang or lang == '*':
res = filter(plugin_filter(self), parent)
else:
res = filter(plugin_filter(self, lang), parent)
res = list(res)
if not res:
del parent.plugins[(plugin_attrib, None)]
parent.loaded_plugins.remove(plugin_attrib)
try:
parent.xml.remove(self.xml)
except:
pass
else:
for stanza in list(res):
parent.iterables.remove(stanza)
parent.xml.remove(stanza.xml)
Multi.is_extension = True
Multi.plugin_attrib = plugin_attrib
Multi._multistanza = stanza
Multi.interfaces = (plugin_attrib,)
Multi.interfaces = set([plugin_attrib])
Multi.lang_interfaces = set([plugin_attrib])
setattr(Multi, "get_%s" % plugin_attrib, get_multi)
setattr(Multi, "set_%s" % plugin_attrib, set_multi)
setattr(Multi, "del_%s" % plugin_attrib, del_multi)
@ -231,8 +261,10 @@ class ElementBase(object):
directly from the parent stanza, as shown below, but retrieving
information will require all interfaces to be used, as so::
>>> message['custom'] = 'bar' # Same as using message['custom']['custom']
>>> message['custom']['custom'] # Must use all interfaces
>>> # Same as using message['custom']['custom']
>>> message['custom'] = 'bar'
>>> # Must use all interfaces
>>> message['custom']['custom']
'bar'
If the plugin sets :attr:`is_extension` to ``True``, then both setting
@ -245,13 +277,13 @@ class ElementBase(object):
:param xml: Initialize the stanza object with an existing XML object.
:param parent: Optionally specify a parent stanza object will will
:param parent: Optionally specify a parent stanza object will
contain this substanza.
"""
#: The XML tag name of the element, not including any namespace
#: prefixes. For example, an :class:`ElementBase` object for ``<message />``
#: would use ``name = 'message'``.
#: prefixes. For example, an :class:`ElementBase` object for
#: ``<message />`` would use ``name = 'message'``.
name = 'stanza'
#: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
@ -289,14 +321,17 @@ class ElementBase(object):
#: subelements of the underlying XML object. Using this set, the text
#: of these subelements may be set, retrieved, or removed without
#: needing to define custom methods.
sub_interfaces = tuple()
sub_interfaces = set()
#: A subset of :attr:`interfaces` which maps the presence of
#: subelements to boolean values. Using this set allows for quickly
#: checking for the existence of empty subelements like ``<required />``.
#:
#: .. versionadded:: 1.1
bool_interfaces = tuple()
bool_interfaces = set()
#: .. versionadded:: 1.1.2
lang_interfaces = set()
#: In some cases you may wish to override the behaviour of one of the
#: parent stanza's interfaces. The ``overrides`` list specifies the
@ -363,7 +398,7 @@ class ElementBase(object):
subitem = set()
#: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
xml_ns = 'http://www.w3.org/XML/1998/namespace'
xml_ns = XML_NS
def __init__(self, xml=None, parent=None):
self._index = 0
@ -375,6 +410,7 @@ class ElementBase(object):
#: An ordered dictionary of plugin stanzas, mapped by their
#: :attr:`plugin_attrib` value.
self.plugins = OrderedDict()
self.loaded_plugins = set()
#: A list of child stanzas whose class is included in
#: :attr:`plugin_iterables`.
@ -385,6 +421,12 @@ class ElementBase(object):
#: ``'{namespace}elementname'``.
self.tag = self.tag_name()
if 'lang' not in self.interfaces:
if isinstance(self.interfaces, tuple):
self.interfaces += ('lang',)
else:
self.interfaces.add('lang')
#: A :class:`weakref.weakref` to the parent stanza, if there is one.
#: If not, then :attr:`parent` is ``None``.
self.parent = None
@ -403,13 +445,12 @@ class ElementBase(object):
return
# Initialize values using provided XML
for child in self.xml.getchildren():
for child in self.xml:
if child.tag in self.plugin_tag_map:
plugin_class = self.plugin_tag_map[child.tag]
plugin = plugin_class(child, self)
self.plugins[plugin.plugin_attrib] = plugin
if plugin_class in self.plugin_iterables:
self.iterables.append(plugin)
self.init_plugin(plugin_class.plugin_attrib,
existing_xml=child,
reuse=False)
def setup(self, xml=None):
"""Initialize the stanza's XML contents.
@ -443,7 +484,7 @@ class ElementBase(object):
# We did not generate XML
return False
def enable(self, attrib):
def enable(self, attrib, lang=None):
"""Enable and initialize a stanza plugin.
Alias for :meth:`init_plugin`.
@ -451,24 +492,67 @@ class ElementBase(object):
:param string attrib: The :attr:`plugin_attrib` value of the
plugin to enable.
"""
return self.init_plugin(attrib)
return self.init_plugin(attrib, lang)
def init_plugin(self, attrib):
def _get_plugin(self, name, lang=None):
if lang is None:
lang = self.get_lang()
if name not in self.plugin_attrib_map:
return None
plugin_class = self.plugin_attrib_map[name]
if plugin_class.is_extension:
if (name, None) in self.plugins:
return self.plugins[(name, None)]
else:
return self.init_plugin(name, lang)
else:
if (name, lang) in self.plugins:
return self.plugins[(name, lang)]
else:
return self.init_plugin(name, lang)
def init_plugin(self, attrib, lang=None, existing_xml=None, reuse=True):
"""Enable and initialize a stanza plugin.
:param string attrib: The :attr:`plugin_attrib` value of the
plugin to enable.
"""
if attrib not in self.plugins:
plugin_class = self.plugin_attrib_map[attrib]
if lang is None:
lang = self.get_lang()
plugin_class = self.plugin_attrib_map[attrib]
if plugin_class.is_extension and (attrib, None) in self.plugins:
return self.plugins[(attrib, None)]
if reuse and (attrib, lang) in self.plugins:
return self.plugins[(attrib, lang)]
if existing_xml is None:
existing_xml = self.xml.find(plugin_class.tag_name())
plugin = plugin_class(parent=self, xml=existing_xml)
self.plugins[attrib] = plugin
if plugin_class in self.plugin_iterables:
self.iterables.append(plugin)
if plugin_class.plugin_multi_attrib:
self.init_plugin(plugin_class.plugin_multi_attrib)
return self
if existing_xml is not None:
if existing_xml.attrib.get('{%s}lang' % XML_NS, '') != lang:
existing_xml = None
plugin = plugin_class(parent=self, xml=existing_xml)
if plugin.is_extension:
self.plugins[(attrib, None)] = plugin
else:
plugin['lang'] = lang
self.plugins[(attrib, lang)] = plugin
if plugin_class in self.plugin_iterables:
self.iterables.append(plugin)
if plugin_class.plugin_multi_attrib:
self.init_plugin(plugin_class.plugin_multi_attrib)
self.loaded_plugins.add(attrib)
return plugin
def _get_stanza_values(self):
"""Return A JSON/dictionary version of the XML content
@ -492,8 +576,14 @@ class ElementBase(object):
values = {}
for interface in self.interfaces:
values[interface] = self[interface]
if interface in self.lang_interfaces:
values['%s|*' % interface] = self['%s|*' % interface]
for plugin, stanza in self.plugins.items():
values[plugin] = stanza.values
lang = stanza['lang']
if lang:
values['%s|%s' % (plugin, lang)] = stanza.values
else:
values[plugin[0]] = stanza.values
if self.iterables:
iterables = []
for stanza in self.iterables:
@ -517,6 +607,11 @@ class ElementBase(object):
p in self.plugin_iterables]
for interface, value in values.items():
full_interface = interface
interface_lang = ('%s|' % interface).split('|')
interface = interface_lang[0]
lang = interface_lang[1] or self.get_lang()
if interface == 'substanzas':
# Remove existing substanzas
for stanza in self.iterables:
@ -535,12 +630,12 @@ class ElementBase(object):
self.iterables.append(sub)
break
elif interface in self.interfaces:
self[interface] = value
self[full_interface] = value
elif interface in self.plugin_attrib_map:
if interface not in iterable_interfaces:
if interface not in self.plugins:
self.init_plugin(interface)
self.plugins[interface].values = value
plugin = self._get_plugin(interface, lang)
if plugin:
plugin.values = value
return self
def __getitem__(self, attrib):
@ -572,6 +667,15 @@ class ElementBase(object):
:param string attrib: The name of the requested stanza interface.
"""
full_attrib = attrib
attrib_lang = ('%s|' % attrib).split('|')
attrib = attrib_lang[0]
lang = attrib_lang[1] or ''
kwargs = {}
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
if attrib == 'substanzas':
return self.iterables
elif attrib in self.interfaces:
@ -579,32 +683,31 @@ class ElementBase(object):
get_method2 = "get%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(get_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], get_method, None)
if handler:
return handler()
name = self.plugin_overrides.get(get_method, None)
if name:
plugin = self._get_plugin(name, lang)
if plugin:
handler = getattr(plugin, get_method, None)
if handler:
return handler(**kwargs)
if hasattr(self, get_method):
return getattr(self, get_method)()
return getattr(self, get_method)(**kwargs)
elif hasattr(self, get_method2):
return getattr(self, get_method2)()
return getattr(self, get_method2)(**kwargs)
else:
if attrib in self.sub_interfaces:
return self._get_sub_text(attrib)
return self._get_sub_text(attrib, lang=lang)
elif attrib in self.bool_interfaces:
elem = self.xml.find('{%s}%s' % (self.namespace, attrib))
return elem is not None
else:
return self._get_attr(attrib)
elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins:
self.init_plugin(attrib)
if self.plugins[attrib].is_extension:
return self.plugins[attrib][attrib]
return self.plugins[attrib]
plugin = self._get_plugin(attrib, lang)
if plugin and plugin.is_extension:
return plugin[full_attrib]
return plugin
else:
return ''
@ -640,41 +743,58 @@ class ElementBase(object):
:param string attrib: The name of the stanza interface to modify.
:param value: The new value of the stanza interface.
"""
full_attrib = attrib
attrib_lang = ('%s|' % attrib).split('|')
attrib = attrib_lang[0]
lang = attrib_lang[1] or ''
kwargs = {}
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
if attrib in self.interfaces:
if value is not None:
set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(set_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin],
set_method, None)
if handler:
return handler(value)
name = self.plugin_overrides.get(set_method, None)
if name:
plugin = self._get_plugin(name, lang)
if plugin:
handler = getattr(plugin, set_method, None)
if handler:
return handler(value, **kwargs)
if hasattr(self, set_method):
getattr(self, set_method)(value,)
getattr(self, set_method)(value, **kwargs)
elif hasattr(self, set_method2):
getattr(self, set_method2)(value,)
getattr(self, set_method2)(value, **kwargs)
else:
if attrib in self.sub_interfaces:
return self._set_sub_text(attrib, text=value)
if lang == '*':
return self._set_all_sub_text(attrib,
value,
lang='*')
return self._set_sub_text(attrib, text=value,
lang=lang)
elif attrib in self.bool_interfaces:
if value:
return self._set_sub_text(attrib, '', keep=True)
return self._set_sub_text(attrib, '',
keep=True,
lang=lang)
else:
return self._set_sub_text(attrib, '', keep=False)
return self._set_sub_text(attrib, '',
keep=False,
lang=lang)
else:
self._set_attr(attrib, value)
else:
self.__delitem__(attrib)
elif attrib in self.plugin_attrib_map:
if attrib not in self.plugins:
self.init_plugin(attrib)
self.plugins[attrib][attrib] = value
plugin = self._get_plugin(attrib, lang)
if plugin:
plugin[full_attrib] = value
return self
def __delitem__(self, attrib):
@ -709,40 +829,53 @@ class ElementBase(object):
:param attrib: The name of the affected stanza interface.
"""
full_attrib = attrib
attrib_lang = ('%s|' % attrib).split('|')
attrib = attrib_lang[0]
lang = attrib_lang[1] or ''
kwargs = {}
if lang and attrib in self.lang_interfaces:
kwargs['lang'] = lang
if attrib in self.interfaces:
del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(del_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], del_method, None)
if handler:
return handler()
name = self.plugin_overrides.get(del_method, None)
if name:
plugin = self._get_plugin(attrib, lang)
if plugin:
handler = getattr(plugin, del_method, None)
if handler:
return handler(**kwargs)
if hasattr(self, del_method):
getattr(self, del_method)()
getattr(self, del_method)(**kwargs)
elif hasattr(self, del_method2):
getattr(self, del_method2)()
getattr(self, del_method2)(**kwargs)
else:
if attrib in self.sub_interfaces:
return self._del_sub(attrib)
return self._del_sub(attrib, lang=lang)
elif attrib in self.bool_interfaces:
return self._del_sub(attrib)
return self._del_sub(attrib, lang=lang)
else:
self._del_attr(attrib)
elif attrib in self.plugin_attrib_map:
if attrib in self.plugins:
xml = self.plugins[attrib].xml
if self.plugins[attrib].is_extension:
del self.plugins[attrib][attrib]
del self.plugins[attrib]
try:
self.xml.remove(xml)
except:
pass
plugin = self._get_plugin(attrib, lang)
if not plugin:
return self
if plugin.is_extension:
del plugin[full_attrib]
del self.plugins[(attrib, None)]
else:
del self.plugins[(attrib, lang)]
self.loaded_plugins.remove(attrib)
try:
self.xml.remove(plugin.xml)
except:
pass
return self
def _set_attr(self, name, value):
@ -781,7 +914,7 @@ class ElementBase(object):
"""
return self.xml.attrib.get(name, default)
def _get_sub_text(self, name, default=''):
def _get_sub_text(self, name, default='', lang=None):
"""Return the text contents of a sub element.
In case the element does not exist, or it has no textual content,
@ -793,13 +926,38 @@ class ElementBase(object):
not exists. An empty string is returned otherwise.
"""
name = self._fix_ns(name)
stanza = self.xml.find(name)
if stanza is None or stanza.text is None:
return default
else:
return stanza.text
if lang == '*':
return self._get_all_sub_text(name, default, None)
def _set_sub_text(self, name, text=None, keep=False):
default_lang = self.get_lang()
if not lang:
lang = default_lang
stanzas = self.xml.findall(name)
if not stanzas:
return default
for stanza in stanzas:
if stanza.attrib.get('{%s}lang' % XML_NS, default_lang) == lang:
if stanza.text is None:
return default
return stanza.text
return default
def _get_all_sub_text(self, name, default='', lang=None):
name = self._fix_ns(name)
default_lang = self.get_lang()
results = OrderedDict()
stanzas = self.xml.findall(name)
if stanzas:
for stanza in stanzas:
stanza_lang = stanza.attrib.get('{%s}lang' % XML_NS,
default_lang)
if not lang or lang == '*' or stanza_lang == lang:
results[stanza_lang] = stanza.text
return results
def _set_sub_text(self, name, text=None, keep=False, lang=None):
"""Set the text contents of a sub element.
In case the element does not exist, a element will be created,
@ -817,9 +975,14 @@ class ElementBase(object):
"""
path = self._fix_ns(name, split=True)
element = self.xml.find(name)
parent = self.xml
default_lang = self.get_lang()
if lang is None:
lang = default_lang
if not text and not keep:
return self._del_sub(name)
return self._del_sub(name, lang=lang)
if element is None:
# We need to add the element. If the provided name was
@ -833,14 +996,31 @@ class ElementBase(object):
element = self.xml.find("/".join(walked))
if element is None:
element = ET.Element(ename)
if lang:
element.attrib['{%s}lang' % XML_NS] = lang
last_xml.append(element)
parent = last_xml
last_xml = element
element = last_xml
if element.attrib.get('{%s}lang' % XML_NS, default_lang) != lang:
element = ET.Element(ename)
if lang:
element.attrib['{%s}lang' % XML_NS] = lang
parent.append(element)
element.text = text
return element
def _del_sub(self, name, all=False):
def _set_all_sub_text(self, name, values, keep=False, lang=None):
self._del_sub(name, lang)
for value_lang, value in values.items():
if not lang or lang == '*' or value_lang == lang:
self._set_sub_text(name, text=value,
keep=keep,
lang=value_lang)
def _del_sub(self, name, all=False, lang=None):
"""Remove sub elements that match the given name or XPath.
If the element is in a path, then any parent elements that become
@ -854,6 +1034,10 @@ class ElementBase(object):
path = self._fix_ns(name, split=True)
original_target = path[-1]
default_lang = self.get_lang()
if not lang:
lang = default_lang
for level, _ in enumerate(path):
# Generate the paths to the target elements and their parent.
element_path = "/".join(path[:len(path) - level])
@ -866,11 +1050,13 @@ class ElementBase(object):
if parent is None:
parent = self.xml
for element in elements:
if element.tag == original_target or \
not element.getchildren():
if element.tag == original_target or not list(element):
# Only delete the originally requested elements, and
# any parent elements that have become empty.
parent.remove(element)
elem_lang = element.attrib.get('{%s}lang' % XML_NS,
default_lang)
if lang == '*' or elem_lang == lang:
parent.remove(element)
if not all:
# If we don't want to delete elements up the tree, stop
# after deleting the first level of elements.
@ -903,7 +1089,7 @@ class ElementBase(object):
attributes = components[1:]
if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
tag not in self.plugins and tag not in self.plugin_attrib:
tag not in self.loaded_plugins and tag not in self.plugin_attrib:
# The requested tag is not in this stanza, so no match.
return False
@ -932,10 +1118,12 @@ class ElementBase(object):
if not matched_substanzas and len(xpath) > 1:
# Convert {namespace}tag@attribs to just tag
next_tag = xpath[1].split('@')[0].split('}')[-1]
if next_tag in self.plugins:
return self.plugins[next_tag].match(xpath[1:])
else:
return False
langs = [name[1] for name in self.plugins if name[0] == next_tag]
for lang in langs:
plugin = self._get_plugin(next_tag, lang)
if plugin and plugin.match(xpath[1:]):
return True
return False
# Everything matched.
return True
@ -995,7 +1183,7 @@ class ElementBase(object):
"""
out = []
out += [x for x in self.interfaces]
out += [x for x in self.plugins]
out += [x for x in self.loaded_plugins]
if self.iterables:
out.append('substanzas')
return out
@ -1075,6 +1263,23 @@ class ElementBase(object):
"""
return "{%s}%s" % (cls.namespace, cls.name)
def get_lang(self):
result = self.xml.attrib.get('{%s}lang' % XML_NS, '')
if not result and self.parent and self.parent():
return self.parent()['lang']
return result
def set_lang(self, lang):
self.del_lang()
attr = '{%s}lang' % XML_NS
if lang:
self.xml.attrib[attr] = lang
def del_lang(self):
attr = '{%s}lang' % XML_NS
if attr in self.xml.attrib:
del self.xml.attrib[attr]
@property
def attrib(self):
"""Return the stanza object itself.
@ -1090,8 +1295,8 @@ class ElementBase(object):
return self
def _fix_ns(self, xpath, split=False, propagate_ns=True):
return fix_ns(xpath, split=split,
propagate_ns=propagate_ns,
return fix_ns(xpath, split=split,
propagate_ns=propagate_ns,
default_ns=self.namespace)
def __eq__(self, other):
@ -1219,6 +1424,8 @@ class StanzaBase(ElementBase):
:param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID`
object of the sender's JID.
:param string sid: Optional ID value for the stanza.
:param parent: Optionally specify a parent stanza object will
contain this substanza.
"""
#: The default XMPP client namespace
@ -1233,11 +1440,11 @@ class StanzaBase(ElementBase):
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
def __init__(self, stream=None, xml=None, stype=None,
sto=None, sfrom=None, sid=None):
sto=None, sfrom=None, sid=None, parent=None):
self.stream = stream
if stream is not None:
self.namespace = stream.default_ns
ElementBase.__init__(self, xml)
ElementBase.__init__(self, xml, parent)
if stype is not None:
self['type'] = stype
if sto is not None:
@ -1285,7 +1492,7 @@ class StanzaBase(ElementBase):
def get_payload(self):
"""Return a list of XML objects contained in the stanza."""
return self.xml.getchildren()
return list(self.xml)
def set_payload(self, value):
"""Add XML content to the stanza.

View File

@ -13,14 +13,19 @@
:license: MIT, see LICENSE for more details
"""
from __future__ import unicode_literals
import sys
if sys.version_info < (3, 0):
import types
XML_NS = 'http://www.w3.org/XML/1998/namespace'
def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
outbuffer='', top_level=False):
outbuffer='', top_level=False, open_only=False):
"""Serialize an XML object to a Unicode string.
If namespaces are provided using ``xmlns`` or ``stanza_ns``, then
@ -88,6 +93,13 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
output.append(' %s:%s="%s"' % (mapped_ns,
attrib,
value))
elif attrib_ns == XML_NS:
output.append(' xml:%s="%s"' % (attrib, value))
if open_only:
# Only output the opening tag, regardless of content.
output.append(">")
return ''.join(output)
if len(xml) or xml.text:
# If there are additional child elements to serialize.
@ -95,7 +107,7 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
if xml.text:
output.append(xml_escape(xml.text))
if len(xml):
for child in xml.getchildren():
for child in xml:
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
output.append("</%s>" % tag_name)
elif xml.text:

View File

@ -55,7 +55,7 @@ RESPONSE_TIMEOUT = 30
WAIT_TIMEOUT = 0.1
#: The number of threads to use to handle XML stream events. This is not the
#: same as the number of custom event handling threads.
#: same as the number of custom event handling threads.
#: :data:`HANDLER_THREADS` must be at least 1. For Python implementations
#: with a GIL, this should be left at 1, but for implemetnations without
#: a GIL increasing this value can provide better performance.
@ -124,7 +124,7 @@ class XMLStream(object):
self.ssl_support = SSL_SUPPORT
#: Most XMPP servers support TLSv1, but OpenFire in particular
#: does not work well with it. For OpenFire, set
#: does not work well with it. For OpenFire, set
#: :attr:`ssl_version` to use ``SSLv23``::
#:
#: import ssl
@ -134,30 +134,30 @@ class XMLStream(object):
#: Path to a file containing certificates for verifying the
#: server SSL certificate. A non-``None`` value will trigger
#: certificate checking.
#:
#:
#: .. note::
#:
#: On Mac OS X, certificates in the system keyring will
#: be consulted, even if they are not in the provided file.
self.ca_certs = None
#: The time in seconds to wait for events from the event queue,
#: The time in seconds to wait for events from the event queue,
#: and also the time between checks for the process stop signal.
self.wait_timeout = WAIT_TIMEOUT
#: The time in seconds to wait before timing out waiting
#: The time in seconds to wait before timing out waiting
#: for response stanzas.
self.response_timeout = RESPONSE_TIMEOUT
#: The current amount to time to delay attempting to reconnect.
#: The current amount to time to delay attempting to reconnect.
#: This value doubles (with some jitter) with each failed
#: connection attempt up to :attr:`reconnect_max_delay` seconds.
self.reconnect_delay = None
#: Maximum time to delay between connection attempts is one hour.
self.reconnect_max_delay = RECONNECT_MAX_DELAY
#: Maximum number of attempts to connect to the server before
#: Maximum number of attempts to connect to the server before
#: quitting and raising a 'connect_failed' event. Setting to
#: ``None`` allows infinite reattempts, while setting it to ``0``
#: will disable reconnection attempts. Defaults to ``None``.
@ -178,16 +178,16 @@ class XMLStream(object):
#: The default port to return when querying DNS records.
self.default_port = int(port)
#: The domain to try when querying DNS records.
#: The domain to try when querying DNS records.
self.default_domain = ''
#: The expected name of the server, for validation.
self._expected_server_name = ''
#: The desired, or actual, address of the connected server.
self.address = (host, int(port))
#: A file-like wrapper for the socket for use with the
#: :mod:`~xml.etree.ElementTree` module.
self.filesocket = None
@ -223,6 +223,9 @@ class XMLStream(object):
#: stream wrapper itself.
self.default_ns = ''
self.default_lang = None
self.peer_default_lang = None
#: The namespace of the enveloping stream element.
self.stream_ns = ''
@ -255,7 +258,7 @@ class XMLStream(object):
#: and data is sent immediately over the wire.
self.session_started_event = threading.Event()
#: The default time in seconds to wait for a session to start
#: The default time in seconds to wait for a session to start
#: after connecting before reconnecting and trying again.
self.session_timeout = 45
@ -414,12 +417,12 @@ class XMLStream(object):
if use_tls is not None:
self.use_tls = use_tls
# Repeatedly attempt to connect until a successful connection
# is established.
attempts = self.reconnect_max_attempts
connected = self.state.transition('disconnected', 'connected',
func=self._connect, args=(reattempt,))
func=self._connect,
args=(reattempt,))
while reattempt and not connected and not self.stop.is_set():
connected = self.state.transition('disconnected', 'connected',
func=self._connect)
@ -434,7 +437,7 @@ class XMLStream(object):
def _connect(self, reattempt=True):
self.scheduler.remove('Session timeout check')
self.stop.clear()
if self.reconnect_delay is None or not reattempt:
delay = 1.0
else:
@ -480,7 +483,7 @@ class XMLStream(object):
if self.use_proxy:
connected = self._connect_proxy()
if not connected:
if reattempt:
if reattempt:
self.reconnect_delay = delay
return False
@ -517,7 +520,8 @@ class XMLStream(object):
except (Socket.error, ssl.SSLError):
log.error('CERT: Invalid certificate trust chain.')
if not self.event_handled('ssl_invalid_chain'):
self.disconnect(self.auto_reconnect, send_close=False)
self.disconnect(self.auto_reconnect,
send_close=False)
else:
self.event('ssl_invalid_chain', direct=True)
return False
@ -525,7 +529,7 @@ class XMLStream(object):
self._der_cert = self.socket.getpeercert(binary_form=True)
pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert)
log.debug('CERT: %s', pem_cert)
self.event('ssl_cert', pem_cert, direct=True)
try:
cert.verify(self._expected_server_name, self._der_cert)
@ -534,7 +538,9 @@ class XMLStream(object):
if not self.event_handled('ssl_invalid_cert'):
self.disconnect(send_close=False)
else:
self.event('ssl_invalid_cert', pem_cert, direct=True)
self.event('ssl_invalid_cert',
pem_cert,
direct=True)
self.set_socket(self.socket, ignore=True)
#this event is where you should set your application state
@ -627,7 +633,7 @@ class XMLStream(object):
If the disconnect should take place after all items
in the send queue have been sent, use ``wait=True``.
.. warning::
If you are constantly adding items to the queue
@ -648,7 +654,7 @@ class XMLStream(object):
"""
self.state.transition('connected', 'disconnected',
wait=2.0,
func=self._disconnect,
func=self._disconnect,
args=(reconnect, wait, send_close))
def _disconnect(self, reconnect=False, wait=None, send_close=True):
@ -702,16 +708,18 @@ class XMLStream(object):
"""Reset the stream's state and reconnect to the server."""
log.debug("reconnecting...")
if self.state.ensure('connected'):
self.state.transition('connected', 'disconnected',
self.state.transition('connected', 'disconnected',
wait=2.0,
func=self._disconnect,
func=self._disconnect,
args=(True, wait, send_close))
attempts = self.reconnect_max_attempts
log.debug("connecting...")
connected = self.state.transition('disconnected', 'connected',
wait=2.0, func=self._connect, args=(reattempt,))
wait=2.0,
func=self._connect,
args=(reattempt,))
while reattempt and not connected and not self.stop.is_set():
connected = self.state.transition('disconnected', 'connected',
wait=2.0, func=self._connect)
@ -759,8 +767,8 @@ class XMLStream(object):
"""
Configure and set options for a :class:`~dns.resolver.Resolver`
instance, and other DNS related tasks. For example, you
can also check :meth:`~socket.socket.getaddrinfo` to see
if you need to call out to ``libresolv.so.2`` to
can also check :meth:`~socket.socket.getaddrinfo` to see
if you need to call out to ``libresolv.so.2`` to
run ``res_init()``.
Meant to be overridden.
@ -814,7 +822,7 @@ class XMLStream(object):
log.debug('CERT: %s', pem_cert)
self.event('ssl_cert', pem_cert, direct=True)
try:
try:
cert.verify(self._expected_server_name, self._der_cert)
except cert.CertificateError as err:
log.error(err.message)
@ -874,8 +882,8 @@ class XMLStream(object):
self.schedule('Whitespace Keepalive',
self.whitespace_keepalive_interval,
self.send_raw,
args = (' ',),
kwargs = {'now': True},
args=(' ',),
kwargs={'now': True},
repeat=True)
def _remove_schedules(self, event):
@ -884,7 +892,7 @@ class XMLStream(object):
self.scheduler.remove('Certificate Expiration')
def start_stream_handler(self, xml):
"""Perform any initialization actions, such as handshakes,
"""Perform any initialization actions, such as handshakes,
once the stream header has been sent.
Meant to be overridden.
@ -892,8 +900,8 @@ class XMLStream(object):
pass
def register_stanza(self, stanza_class):
"""Add a stanza object class as a known root stanza.
"""Add a stanza object class as a known root stanza.
A root stanza is one that appears as a direct child of the stream's
root element.
@ -910,8 +918,8 @@ class XMLStream(object):
self.__root_stanza.append(stanza_class)
def remove_stanza(self, stanza_class):
"""Remove a stanza from being a known root stanza.
"""Remove a stanza from being a known root stanza.
A root stanza is one that appears as a direct child of the stream's
root element.
@ -976,8 +984,9 @@ class XMLStream(object):
"""Add a stream event handler that will be executed when a matching
stanza is received.
:param handler: The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler`
derived object to execute.
:param handler:
The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler`
derived object to execute.
"""
if handler.stream is None:
self.__handlers.append(handler)
@ -1004,11 +1013,12 @@ class XMLStream(object):
"""
if port is None:
port = self.default_port
resolver = default_resolver()
self.configure_dns(resolver, domain=domain, port=port)
return resolve(domain, port, service=self.dns_service, resolver=resolver)
return resolve(domain, port, service=self.dns_service,
resolver=resolver)
def pick_dns_answer(self, domain, port=None):
"""Pick a server and port from DNS answers.
@ -1026,7 +1036,7 @@ class XMLStream(object):
return self.dns_answers.next()
else:
return next(self.dns_answers)
def add_event_handler(self, name, pointer,
threaded=False, disposable=False):
"""Add a custom event handler that will be executed whenever
@ -1141,9 +1151,9 @@ class XMLStream(object):
May optionally block until an expected response is received.
:param data: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
:param data: The :class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
stanza to send on the stream.
:param mask: **DEPRECATED**
:param mask: **DEPRECATED**
An XML string snippet matching the structure
of the expected response. Execution will block
in this thread until the response is received
@ -1195,9 +1205,9 @@ class XMLStream(object):
"""Send an XML object on the stream, and optionally wait
for a response.
:param data: The :class:`~xml.etree.ElementTree.Element` XML object
:param data: The :class:`~xml.etree.ElementTree.Element` XML object
to send on the stream.
:param mask: **DEPRECATED**
:param mask: **DEPRECATED**
An XML string snippet matching the structure
of the expected response. Execution will block
in this thread until the response is received
@ -1237,14 +1247,15 @@ class XMLStream(object):
count += 1
except ssl.SSLError as serr:
if tries >= self.ssl_retry_max:
log.debug('SSL error - max retries reached')
log.debug('SSL error: max retries reached')
self.exception(serr)
log.warning("Failed to send %s", data)
if reconnect is None:
reconnect = self.auto_reconnect
if not self.stop.is_set():
self.disconnect(reconnect, send_close=False)
log.warning('SSL write error - reattempting')
self.disconnect(reconnect,
send_close=False)
log.warning('SSL write error: retrying')
if not self.stop.is_set():
time.sleep(self.ssl_retry_delay)
tries += 1
@ -1299,7 +1310,7 @@ class XMLStream(object):
def _wait_for_threads(self):
with self.__thread_cond:
if self.__thread_count != 0:
log.debug("Waiting for %s threads to exit." %
log.debug("Waiting for %s threads to exit." %
self.__thread_count)
name = threading.current_thread().name
if name in self.__thread:
@ -1331,7 +1342,7 @@ class XMLStream(object):
Defaults to ``True``. This does **not** mean that no
threads are used at all if ``threaded=False``.
Regardless of these threading options, these threads will
Regardless of these threading options, these threads will
always exist:
- The event queue processor
@ -1421,7 +1432,7 @@ class XMLStream(object):
def __read_xml(self):
"""Parse the incoming XML stream
Stream events are raised for each received stanza.
"""
depth = 0
@ -1431,6 +1442,10 @@ class XMLStream(object):
if depth == 0:
# We have received the start of the root element.
root = xml
log.debug('RECV: %s', tostring(root, xmlns=self.default_ns,
stream=self,
top_level=True,
open_only=True))
# Perform any stream initialization actions, such
# as handshakes.
self.stream_end_event.clear()
@ -1461,10 +1476,10 @@ class XMLStream(object):
"""Create a stanza object from a given XML object.
If a specialized stanza type is not found for the XML, then
a generic :class:`~sleekxmpp.xmlstream.stanzabase.StanzaBase`
a generic :class:`~sleekxmpp.xmlstream.stanzabase.StanzaBase`
stanza will be returned.
:param xml: The :class:`~xml.etree.ElementTree.Element` XML object
:param xml: The :class:`~xml.etree.ElementTree.Element` XML object
to convert into a stanza object.
:param default_ns: Optional default namespace to use instead of the
stream's current default namespace.
@ -1478,6 +1493,8 @@ class XMLStream(object):
stanza_type = stanza_class
break
stanza = stanza_type(self, xml)
if stanza['lang'] is None and self.peer_default_lang:
stanza['lang'] = self.peer_default_lang
return stanza
def __spawn_event(self, xml):
@ -1647,12 +1664,13 @@ class XMLStream(object):
count += 1
except ssl.SSLError as serr:
if tries >= self.ssl_retry_max:
log.debug('SSL error - max retries reached')
log.debug('SSL error: max retries reached')
self.exception(serr)
log.warning("Failed to send %s", data)
if not self.stop.is_set():
self.disconnect(self.auto_reconnect, send_close=False)
log.warning('SSL write error - reattempting')
self.disconnect(self.auto_reconnect,
send_close=False)
log.warning('SSL write error: retrying')
if not self.stop.is_set():
time.sleep(self.ssl_retry_delay)
tries += 1

View File

@ -64,14 +64,18 @@ class TestElementBase(SleekTest):
stanza.append(substanza)
values = stanza.getStanzaValues()
expected = {'bar': 'a',
expected = {'lang': '',
'bar': 'a',
'baz': '',
'foo2': {'bar': '',
'foo2': {'lang': '',
'bar': '',
'baz': 'b'},
'substanzas': [{'__childtag__': '{foo}foo2',
'lang': '',
'bar': '',
'baz': 'b'},
{'__childtag__': '{foo}subfoo',
'lang': '',
'bar': 'c',
'baz': ''}]}
self.failUnless(values == expected,
@ -555,12 +559,12 @@ class TestElementBase(SleekTest):
stanza = TestStanza()
self.failUnless(set(stanza.keys()) == set(('bar', 'baz')),
self.failUnless(set(stanza.keys()) == set(('lang', 'bar', 'baz')),
"Returned set of interface keys does not match expected.")
stanza.enable('qux')
self.failUnless(set(stanza.keys()) == set(('bar', 'baz', 'qux')),
self.failUnless(set(stanza.keys()) == set(('lang', 'bar', 'baz', 'qux')),
"Incorrect set of interface and plugin keys.")
def testGet(self):

View File

@ -49,7 +49,7 @@ class TestAdHocCommandStanzas(SleekTest):
iq['command']['actions'] = ['prev', 'next']
results = iq['command']['actions']
expected = ['prev', 'next']
expected = set(['prev', 'next'])
self.assertEqual(results, expected,
"Incorrect next actions: %s" % results)