Merge branch 'master' into develop
Conflicts: sleekxmpp/basexmpp.py
This commit is contained in:
commit
5820d49cd4
3
setup.py
3
setup.py
@ -61,6 +61,7 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0027',
|
'sleekxmpp/plugins/xep_0027',
|
||||||
'sleekxmpp/plugins/xep_0030',
|
'sleekxmpp/plugins/xep_0030',
|
||||||
'sleekxmpp/plugins/xep_0030/stanza',
|
'sleekxmpp/plugins/xep_0030/stanza',
|
||||||
|
'sleekxmpp/plugins/xep_0033',
|
||||||
'sleekxmpp/plugins/xep_0047',
|
'sleekxmpp/plugins/xep_0047',
|
||||||
'sleekxmpp/plugins/xep_0050',
|
'sleekxmpp/plugins/xep_0050',
|
||||||
'sleekxmpp/plugins/xep_0054',
|
'sleekxmpp/plugins/xep_0054',
|
||||||
@ -72,6 +73,7 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0077',
|
'sleekxmpp/plugins/xep_0077',
|
||||||
'sleekxmpp/plugins/xep_0078',
|
'sleekxmpp/plugins/xep_0078',
|
||||||
'sleekxmpp/plugins/xep_0080',
|
'sleekxmpp/plugins/xep_0080',
|
||||||
|
'sleekxmpp/plugins/xep_0084',
|
||||||
'sleekxmpp/plugins/xep_0085',
|
'sleekxmpp/plugins/xep_0085',
|
||||||
'sleekxmpp/plugins/xep_0086',
|
'sleekxmpp/plugins/xep_0086',
|
||||||
'sleekxmpp/plugins/xep_0092',
|
'sleekxmpp/plugins/xep_0092',
|
||||||
@ -90,6 +92,7 @@ packages = [ 'sleekxmpp',
|
|||||||
'sleekxmpp/plugins/xep_0224',
|
'sleekxmpp/plugins/xep_0224',
|
||||||
'sleekxmpp/plugins/xep_0231',
|
'sleekxmpp/plugins/xep_0231',
|
||||||
'sleekxmpp/plugins/xep_0249',
|
'sleekxmpp/plugins/xep_0249',
|
||||||
|
'sleekxmpp/plugins/xep_0258',
|
||||||
'sleekxmpp/features',
|
'sleekxmpp/features',
|
||||||
'sleekxmpp/features/feature_mechanisms',
|
'sleekxmpp/features/feature_mechanisms',
|
||||||
'sleekxmpp/features/feature_mechanisms/stanza',
|
'sleekxmpp/features/feature_mechanisms/stanza',
|
||||||
|
@ -16,24 +16,24 @@ class APIWrapper(object):
|
|||||||
elif attr == 'settings':
|
elif attr == 'settings':
|
||||||
return self.api.settings[self.name]
|
return self.api.settings[self.name]
|
||||||
elif attr == 'register':
|
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)
|
register = getattr(self.api, attr)
|
||||||
return register(handler, self.name, op, jid, node, default)
|
return register(handler, self.name, op, jid, node, default)
|
||||||
return curried_handler
|
return partial
|
||||||
elif attr == 'register_default':
|
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 getattr(self.api, attr)(handler, self.name, op)
|
||||||
return curried_handler
|
return partial
|
||||||
elif attr in ('run', 'restore_default', 'unregister'):
|
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 getattr(self.api, attr)(self.name, *args, **kwargs)
|
||||||
return curried_handler
|
return partial
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __getitem__(self, attr):
|
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 self.api.run(self.name, attr, jid, node, ifrom, args)
|
||||||
return curried_handler
|
return partial
|
||||||
|
|
||||||
|
|
||||||
class APIRegistry(object):
|
class APIRegistry(object):
|
||||||
@ -42,7 +42,7 @@ class APIRegistry(object):
|
|||||||
self._handlers = {}
|
self._handlers = {}
|
||||||
self._handler_defaults = {}
|
self._handler_defaults = {}
|
||||||
self.xmpp = xmpp
|
self.xmpp = xmpp
|
||||||
self.settings = {}
|
self.settings = {}
|
||||||
|
|
||||||
def _setup(self, ctype, op):
|
def _setup(self, ctype, op):
|
||||||
"""Initialize the API callback dictionaries.
|
"""Initialize the API callback dictionaries.
|
||||||
@ -138,8 +138,8 @@ class APIRegistry(object):
|
|||||||
"""Register an API callback, with JID+node specificity.
|
"""Register an API callback, with JID+node specificity.
|
||||||
|
|
||||||
The API callback can later be executed based on the
|
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.
|
See :meth:`~ApiRegistry.run` for more details.
|
||||||
|
|
||||||
:param string ctype: The name of the API to use.
|
:param string ctype: The name of the API to use.
|
||||||
|
@ -31,6 +31,7 @@ from sleekxmpp.xmlstream import XMLStream, JID
|
|||||||
from sleekxmpp.xmlstream import ET, register_stanza_plugin
|
from sleekxmpp.xmlstream import ET, register_stanza_plugin
|
||||||
from sleekxmpp.xmlstream.matcher import MatchXPath
|
from sleekxmpp.xmlstream.matcher import MatchXPath
|
||||||
from sleekxmpp.xmlstream.handler import Callback
|
from sleekxmpp.xmlstream.handler import Callback
|
||||||
|
from sleekxmpp.xmlstream.stanzabase import XML_NS
|
||||||
|
|
||||||
from sleekxmpp.features import *
|
from sleekxmpp.features import *
|
||||||
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
|
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.
|
#: An identifier for the stream as given by the server.
|
||||||
self.stream_id = None
|
self.stream_id = None
|
||||||
|
|
||||||
#: The JabberID (JID) used by this connection.
|
#: The JabberID (JID) used by this connection.
|
||||||
self.boundjid = JID(jid)
|
self.boundjid = JID(jid)
|
||||||
self._expected_server_name = self.boundjid.host
|
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
|
#: The API registry is a way to process callbacks based on
|
||||||
#: JID+node combinations. Each callback in the registry is
|
#: JID+node combinations. Each callback in the registry is
|
||||||
#: marked with:
|
#: marked with:
|
||||||
#:
|
#:
|
||||||
#: - An API name, e.g. xep_0030
|
#: - An API name, e.g. xep_0030
|
||||||
#: - The name of an action, e.g. get_info
|
#: - The name of an action, e.g. get_info
|
||||||
#: - The JID that will be affected
|
#: - The JID that will be affected
|
||||||
@ -180,6 +181,8 @@ class BaseXMPP(XMLStream):
|
|||||||
:param xml: The incoming stream's root element.
|
:param xml: The incoming stream's root element.
|
||||||
"""
|
"""
|
||||||
self.stream_id = xml.get('id', '')
|
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):
|
def process(self, *args, **kwargs):
|
||||||
"""Initialize plugins and begin processing the XML stream.
|
"""Initialize plugins and begin processing the XML stream.
|
||||||
@ -199,7 +202,7 @@ class BaseXMPP(XMLStream):
|
|||||||
Defaults to ``True``. This does **not** mean that no
|
Defaults to ``True``. This does **not** mean that no
|
||||||
threads are used at all if ``threaded=False``.
|
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:
|
always exist:
|
||||||
|
|
||||||
- The event queue processor
|
- The event queue processor
|
||||||
@ -272,7 +275,9 @@ class BaseXMPP(XMLStream):
|
|||||||
|
|
||||||
def Message(self, *args, **kwargs):
|
def Message(self, *args, **kwargs):
|
||||||
"""Create a Message stanza associated with this stream."""
|
"""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):
|
def Iq(self, *args, **kwargs):
|
||||||
"""Create an Iq stanza associated with this stream."""
|
"""Create an Iq stanza associated with this stream."""
|
||||||
@ -280,18 +285,20 @@ class BaseXMPP(XMLStream):
|
|||||||
|
|
||||||
def Presence(self, *args, **kwargs):
|
def Presence(self, *args, **kwargs):
|
||||||
"""Create a Presence stanza associated with this stream."""
|
"""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):
|
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.
|
"""Create a new Iq stanza with a given Id and from JID.
|
||||||
|
|
||||||
:param id: An ideally unique ID value for this stanza thread.
|
:param id: An ideally unique ID value for this stanza thread.
|
||||||
Defaults to 0.
|
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.
|
to use for this stanza.
|
||||||
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
|
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
|
||||||
for this stanza.
|
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'``,
|
one of: ``'get'``, ``'set'``, ``'result'``,
|
||||||
or ``'error'``.
|
or ``'error'``.
|
||||||
:param iquery: Optional namespace for adding a query element.
|
: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):
|
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.
|
``'result'`` with the given ID value.
|
||||||
|
|
||||||
:param id: An ideally unique ID value. May use :meth:`new_id()`.
|
: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
|
Optionally, a substanza may be given to use as the
|
||||||
stanza's payload.
|
stanza's payload.
|
||||||
|
|
||||||
:param sub: Either an
|
:param sub: Either an
|
||||||
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
|
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
|
||||||
stanza object or an
|
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.
|
to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
|
||||||
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
|
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
|
||||||
for this stanza.
|
for this stanza.
|
||||||
@ -389,9 +396,9 @@ class BaseXMPP(XMLStream):
|
|||||||
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
|
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
|
||||||
|
|
||||||
:param id: An ideally unique ID value. May use :meth:`new_id()`.
|
: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'``.
|
``'modify'``. Defaults to ``'cancel'``.
|
||||||
:param condition: The error condition. Defaults to
|
:param condition: The error condition. Defaults to
|
||||||
``'feature-not-implemented'``.
|
``'feature-not-implemented'``.
|
||||||
:param text: A message describing the cause of the error.
|
:param text: A message describing the cause of the error.
|
||||||
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
|
: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):
|
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.
|
to use the given query namespace.
|
||||||
|
|
||||||
:param iq: Optionally use an existing stanza instead
|
: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,
|
def make_message(self, mto, mbody=None, msubject=None, mtype=None,
|
||||||
mhtml=None, mfrom=None, mnick=None):
|
mhtml=None, mfrom=None, mnick=None):
|
||||||
"""
|
"""
|
||||||
Create and initialize a new
|
Create and initialize a new
|
||||||
:class:`~sleekxmpp.stanza.message.Message` stanza.
|
:class:`~sleekxmpp.stanza.message.Message` stanza.
|
||||||
|
|
||||||
:param mto: The recipient of the message.
|
:param mto: The recipient of the message.
|
||||||
@ -474,7 +481,7 @@ class BaseXMPP(XMLStream):
|
|||||||
def make_presence(self, pshow=None, pstatus=None, ppriority=None,
|
def make_presence(self, pshow=None, pstatus=None, ppriority=None,
|
||||||
pto=None, ptype=None, pfrom=None, pnick=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.
|
:class:`~sleekxmpp.stanza.presence.Presence` stanza.
|
||||||
|
|
||||||
:param pshow: The presence's show value.
|
:param pshow: The presence's show value.
|
||||||
@ -498,7 +505,7 @@ class BaseXMPP(XMLStream):
|
|||||||
def send_message(self, mto, mbody, msubject=None, mtype=None,
|
def send_message(self, mto, mbody, msubject=None, mtype=None,
|
||||||
mhtml=None, mfrom=None, mnick=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.
|
:class:`~sleekxmpp.stanza.message.Message` stanza.
|
||||||
|
|
||||||
:param mto: The recipient of the message.
|
:param mto: The recipient of the message.
|
||||||
@ -518,7 +525,7 @@ class BaseXMPP(XMLStream):
|
|||||||
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
|
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
|
||||||
pto=None, pfrom=None, ptype=None, pnick=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.
|
:class:`~sleekxmpp.stanza.presence.Presence` stanza.
|
||||||
|
|
||||||
:param pshow: The presence's show value.
|
:param pshow: The presence's show value.
|
||||||
@ -529,26 +536,13 @@ class BaseXMPP(XMLStream):
|
|||||||
:param pfrom: The sender of the presence.
|
:param pfrom: The sender of the presence.
|
||||||
:param pnick: Optional nickname of the presence's sender.
|
:param pnick: Optional nickname of the presence's sender.
|
||||||
"""
|
"""
|
||||||
# Python2.6 chokes on Unicode strings for dict keys.
|
self.make_presence(pshow, pstatus, ppriority, pto,
|
||||||
args = {str('ptype'): ptype,
|
ptype, pfrom, pnick).send()
|
||||||
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)
|
|
||||||
|
|
||||||
def send_presence_subscription(self, pto, pfrom=None,
|
def send_presence_subscription(self, pto, pfrom=None,
|
||||||
ptype='subscribe', pnick=None):
|
ptype='subscribe', pnick=None):
|
||||||
"""
|
"""
|
||||||
Create, initialize, and send a new
|
Create, initialize, and send a new
|
||||||
:class:`~sleekxmpp.stanza.presence.Presence` stanza of
|
:class:`~sleekxmpp.stanza.presence.Presence` stanza of
|
||||||
type ``'subscribe'``.
|
type ``'subscribe'``.
|
||||||
|
|
||||||
@ -557,10 +551,10 @@ class BaseXMPP(XMLStream):
|
|||||||
:param ptype: The type of presence, such as ``'subscribe'``.
|
:param ptype: The type of presence, such as ``'subscribe'``.
|
||||||
:param pnick: Optional nickname of the presence's sender.
|
:param pnick: Optional nickname of the presence's sender.
|
||||||
"""
|
"""
|
||||||
self.send_presence(pto=pto,
|
self.make_presence(ptype=ptype,
|
||||||
pfrom=pfrom,
|
pfrom=pfrom,
|
||||||
ptype=ptype,
|
pto=JID(pto).bare,
|
||||||
pnick=pnick)
|
pnick=pnick).send()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def jid(self):
|
def jid(self):
|
||||||
@ -749,7 +743,7 @@ class BaseXMPP(XMLStream):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def exception(self, exception):
|
def exception(self, exception):
|
||||||
"""Process any uncaught exceptions, notably
|
"""Process any uncaught exceptions, notably
|
||||||
:class:`~sleekxmpp.exceptions.IqError` and
|
:class:`~sleekxmpp.exceptions.IqError` and
|
||||||
:class:`~sleekxmpp.exceptions.IqTimeout` exceptions.
|
:class:`~sleekxmpp.exceptions.IqTimeout` exceptions.
|
||||||
|
|
||||||
|
@ -54,14 +54,14 @@ class ClientXMPP(BaseXMPP):
|
|||||||
:param password: The password for the XMPP user account.
|
:param password: The password for the XMPP user account.
|
||||||
:param ssl: **Deprecated.**
|
:param ssl: **Deprecated.**
|
||||||
:param plugin_config: A dictionary of plugin configurations.
|
:param plugin_config: A dictionary of plugin configurations.
|
||||||
:param plugin_whitelist: A list of approved plugins that
|
:param plugin_whitelist: A list of approved plugins that
|
||||||
will be loaded when calling
|
will be loaded when calling
|
||||||
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
|
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
|
||||||
:param escape_quotes: **Deprecated.**
|
:param escape_quotes: **Deprecated.**
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, jid, password, ssl=False, plugin_config={},
|
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
|
||||||
plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
|
escape_quotes=True, sasl_mech=None, lang='en'):
|
||||||
BaseXMPP.__init__(self, jid, 'jabber:client')
|
BaseXMPP.__init__(self, jid, 'jabber:client')
|
||||||
|
|
||||||
self.set_jid(jid)
|
self.set_jid(jid)
|
||||||
@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP):
|
|||||||
self.plugin_config = plugin_config
|
self.plugin_config = plugin_config
|
||||||
self.plugin_whitelist = plugin_whitelist
|
self.plugin_whitelist = plugin_whitelist
|
||||||
self.default_port = 5222
|
self.default_port = 5222
|
||||||
|
self.default_lang = lang
|
||||||
|
|
||||||
self.credentials = {}
|
self.credentials = {}
|
||||||
|
|
||||||
self.password = password
|
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,
|
self.boundjid.host,
|
||||||
"xmlns:stream='%s'" % self.stream_ns,
|
"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.stream_footer = "</stream:stream>"
|
||||||
|
|
||||||
self.features = set()
|
self.features = set()
|
||||||
@ -186,7 +189,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
occurs. Defaults to ``True``.
|
occurs. Defaults to ``True``.
|
||||||
:param timeout: The length of time (in seconds) to wait
|
:param timeout: The length of time (in seconds) to wait
|
||||||
for a response before continuing if blocking
|
for a response before continuing if blocking
|
||||||
is used. Defaults to
|
is used. Defaults to
|
||||||
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
|
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
|
||||||
:param callback: Optional reference to a stream handler function.
|
:param callback: Optional reference to a stream handler function.
|
||||||
Will be executed when the roster is received.
|
Will be executed when the roster is received.
|
||||||
@ -197,7 +200,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
|
|
||||||
def del_roster_item(self, jid):
|
def del_roster_item(self, jid):
|
||||||
"""Remove an item from the roster.
|
"""Remove an item from the roster.
|
||||||
|
|
||||||
This is done by setting its subscription status to ``'remove'``.
|
This is done by setting its subscription status to ``'remove'``.
|
||||||
|
|
||||||
:param jid: The JID of the item to remove.
|
:param jid: The JID of the item to remove.
|
||||||
@ -212,7 +215,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
Defaults to ``True``.
|
Defaults to ``True``.
|
||||||
:param timeout: The length of time (in seconds) to wait for a response
|
:param timeout: The length of time (in seconds) to wait for a response
|
||||||
before continuing if blocking is used.
|
before continuing if blocking is used.
|
||||||
Defaults to
|
Defaults to
|
||||||
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
|
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
|
||||||
:param callback: Optional reference to a stream handler function. Will
|
:param callback: Optional reference to a stream handler function. Will
|
||||||
be executed when the roster is received.
|
be executed when the roster is received.
|
||||||
@ -230,7 +233,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
response = iq.send(block, timeout, callback)
|
response = iq.send(block, timeout, callback)
|
||||||
self.event('roster_received', response)
|
self.event('roster_received', response)
|
||||||
|
|
||||||
if block:
|
if block:
|
||||||
self._handle_roster(response)
|
self._handle_roster(response)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@ -276,7 +279,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
|
||||||
|
|
||||||
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
roster[jid].save(remove=(item['subscription'] == 'remove'))
|
||||||
|
|
||||||
self.event("roster_update", iq)
|
self.event("roster_update", iq)
|
||||||
if iq['type'] == 'set':
|
if iq['type'] == 'set':
|
||||||
resp = self.Iq(stype='result',
|
resp = self.Iq(stype='result',
|
||||||
|
@ -40,8 +40,8 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
:param host: The server accepting the component.
|
:param host: The server accepting the component.
|
||||||
:param port: The port used to connect to the server.
|
:param port: The port used to connect to the server.
|
||||||
:param plugin_config: A dictionary of plugin configurations.
|
:param plugin_config: A dictionary of plugin configurations.
|
||||||
:param plugin_whitelist: A list of approved plugins that
|
:param plugin_whitelist: A list of approved plugins that
|
||||||
will be loaded when calling
|
will be loaded when calling
|
||||||
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
|
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
|
||||||
:param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
|
:param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
|
||||||
should be used instead of the standard
|
should be used instead of the standard
|
||||||
@ -78,7 +78,7 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
self.add_event_handler('presence_probe',
|
self.add_event_handler('presence_probe',
|
||||||
self._handle_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):
|
use_tls=False, reattempt=True):
|
||||||
"""Connect to the server.
|
"""Connect to the server.
|
||||||
|
|
||||||
|
@ -69,10 +69,11 @@ class IqTimeout(XMPPError):
|
|||||||
condition='remote-server-timeout',
|
condition='remote-server-timeout',
|
||||||
etype='cancel')
|
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.
|
#: did not arrive before the timeout expired.
|
||||||
self.iq = iq
|
self.iq = iq
|
||||||
|
|
||||||
|
|
||||||
class IqError(XMPPError):
|
class IqError(XMPPError):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'feature_starttls',
|
'feature_starttls',
|
||||||
'feature_mechanisms',
|
'feature_mechanisms',
|
||||||
'feature_bind',
|
'feature_bind',
|
||||||
'feature_session',
|
'feature_session',
|
||||||
'feature_rosterver'
|
'feature_rosterver'
|
||||||
]
|
]
|
||||||
|
@ -23,7 +23,7 @@ class Auth(StanzaBase):
|
|||||||
interfaces = set(('mechanism', 'value'))
|
interfaces = set(('mechanism', 'value'))
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
|
||||||
#: Some SASL mechs require sending values as is,
|
#: Some SASL mechs require sending values as is,
|
||||||
#: without converting base64.
|
#: without converting base64.
|
||||||
plain_mechs = set(['X-MESSENGER-OAUTH2'])
|
plain_mechs = set(['X-MESSENGER-OAUTH2'])
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class Failure(StanzaBase):
|
|||||||
|
|
||||||
def get_condition(self):
|
def get_condition(self):
|
||||||
"""Return the condition element's name."""
|
"""Return the condition element's name."""
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
if "{%s}" % self.namespace in child.tag:
|
if "{%s}" % self.namespace in child.tag:
|
||||||
cond = child.tag.split('}', 1)[-1]
|
cond = child.tag.split('}', 1)[-1]
|
||||||
if cond in self.conditions:
|
if cond in self.conditions:
|
||||||
@ -68,7 +68,7 @@ class Failure(StanzaBase):
|
|||||||
|
|
||||||
def del_condition(self):
|
def del_condition(self):
|
||||||
"""Remove the condition element."""
|
"""Remove the condition element."""
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
if "{%s}" % self.condition_ns in child.tag:
|
if "{%s}" % self.condition_ns in child.tag:
|
||||||
tag = child.tag.split('}', 1)[-1]
|
tag = child.tag.split('}', 1)[-1]
|
||||||
if tag in self.conditions:
|
if tag in self.conditions:
|
||||||
|
@ -33,6 +33,7 @@ __all__ = [
|
|||||||
# 'xep_0078', # Non-SASL auth. Don't automatically load
|
# 'xep_0078', # Non-SASL auth. Don't automatically load
|
||||||
'xep_0080', # User Location
|
'xep_0080', # User Location
|
||||||
'xep_0082', # XMPP Date and Time Profiles
|
'xep_0082', # XMPP Date and Time Profiles
|
||||||
|
'xep_0084', # User Avatar
|
||||||
'xep_0085', # Chat State Notifications
|
'xep_0085', # Chat State Notifications
|
||||||
'xep_0086', # Legacy Error Codes
|
'xep_0086', # Legacy Error Codes
|
||||||
'xep_0092', # Software Version
|
'xep_0092', # Software Version
|
||||||
@ -49,7 +50,10 @@ __all__ = [
|
|||||||
'xep_0199', # Ping
|
'xep_0199', # Ping
|
||||||
'xep_0202', # Entity Time
|
'xep_0202', # Entity Time
|
||||||
'xep_0203', # Delayed Delivery
|
'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_0224', # Attention
|
||||||
'xep_0231', # Bits of Binary
|
'xep_0231', # Bits of Binary
|
||||||
'xep_0249', # Direct MUC Invitations
|
'xep_0249', # Direct MUC Invitations
|
||||||
|
'xep_0258', # Security Labels in XMPP
|
||||||
]
|
]
|
||||||
|
@ -31,10 +31,10 @@ log = logging.getLogger(__name__)
|
|||||||
PLUGIN_REGISTRY = {}
|
PLUGIN_REGISTRY = {}
|
||||||
|
|
||||||
#: In order to do cascading plugin disabling, reverse dependencies
|
#: In order to do cascading plugin disabling, reverse dependencies
|
||||||
#: must be tracked.
|
#: must be tracked.
|
||||||
PLUGIN_DEPENDENTS = {}
|
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()
|
REGISTRY_LOCK = threading.RLock()
|
||||||
|
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ def load_plugin(name, module=None):
|
|||||||
plugins are in packages matching their name,
|
plugins are in packages matching their name,
|
||||||
even though the plugin class name does not
|
even though the plugin class name does not
|
||||||
have to match.
|
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.
|
for the plugin.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@ -84,7 +84,7 @@ def load_plugin(name, module=None):
|
|||||||
module = 'sleekxmpp.plugins.%s' % name
|
module = 'sleekxmpp.plugins.%s' % name
|
||||||
__import__(module)
|
__import__(module)
|
||||||
mod = sys.modules[module]
|
mod = sys.modules[module]
|
||||||
except:
|
except ImportError:
|
||||||
module = 'sleekxmpp.features.%s' % name
|
module = 'sleekxmpp.features.%s' % name
|
||||||
__import__(module)
|
__import__(module)
|
||||||
mod = sys.modules[module]
|
mod = sys.modules[module]
|
||||||
@ -103,11 +103,11 @@ def load_plugin(name, module=None):
|
|||||||
# we can work around dependency issues.
|
# we can work around dependency issues.
|
||||||
plugin.old_style = True
|
plugin.old_style = True
|
||||||
register_plugin(plugin, name)
|
register_plugin(plugin, name)
|
||||||
except:
|
except ImportError:
|
||||||
log.exception("Unable to load plugin: %s", name)
|
log.exception("Unable to load plugin: %s", name)
|
||||||
|
|
||||||
|
|
||||||
class PluginManager(object):
|
class PluginManager(object):
|
||||||
def __init__(self, xmpp, config=None):
|
def __init__(self, xmpp, config=None):
|
||||||
#: We will track all enabled plugins in a set so that we
|
#: We will track all enabled plugins in a set so that we
|
||||||
#: can enable plugins in batches and pull in dependencies
|
#: can enable plugins in batches and pull in dependencies
|
||||||
@ -181,7 +181,7 @@ class PluginManager(object):
|
|||||||
|
|
||||||
def enable_all(self, names=None, config=None):
|
def enable_all(self, names=None, config=None):
|
||||||
"""Enable all registered plugins.
|
"""Enable all registered plugins.
|
||||||
|
|
||||||
:param list names: A list of plugin names to enable. If
|
:param list names: A list of plugin names to enable. If
|
||||||
none are provided, all registered plugins
|
none are provided, all registered plugins
|
||||||
will be enabled.
|
will be enabled.
|
||||||
@ -292,7 +292,7 @@ class BasePlugin(object):
|
|||||||
|
|
||||||
def post_init(self):
|
def post_init(self):
|
||||||
"""Initialize any cross-plugin state.
|
"""Initialize any cross-plugin state.
|
||||||
|
|
||||||
Only needed if the plugin has circular dependencies.
|
Only needed if the plugin has circular dependencies.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
@ -81,7 +81,8 @@ class XEP_0027(BasePlugin):
|
|||||||
|
|
||||||
def _sign_presence(self, stanza):
|
def _sign_presence(self, stanza):
|
||||||
if isinstance(stanza, Presence):
|
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']
|
stanza['signed'] = stanza['status']
|
||||||
return stanza
|
return stanza
|
||||||
|
|
||||||
|
@ -51,6 +51,3 @@ class Encrypted(ElementBase):
|
|||||||
if self.xml.text:
|
if self.xml.text:
|
||||||
return xmpp['xep_0027'].decrypt(self.xml.text, parent['to'])
|
return xmpp['xep_0027'].decrypt(self.xml.text, parent['to'])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -339,8 +339,8 @@ class XEP_0030(BasePlugin):
|
|||||||
if local:
|
if local:
|
||||||
log.debug("Looking up local disco#info data " + \
|
log.debug("Looking up local disco#info data " + \
|
||||||
"for %s, node %s.", jid, node)
|
"for %s, node %s.", jid, node)
|
||||||
info = self.api['get_info'](jid, node,
|
info = self.api['get_info'](jid, node,
|
||||||
kwargs.get('ifrom', None),
|
kwargs.get('ifrom', None),
|
||||||
kwargs)
|
kwargs)
|
||||||
info = self._fix_default_info(info)
|
info = self._fix_default_info(info)
|
||||||
return self._wrap(kwargs.get('ifrom', None), jid, info)
|
return self._wrap(kwargs.get('ifrom', None), jid, info)
|
||||||
@ -348,8 +348,8 @@ class XEP_0030(BasePlugin):
|
|||||||
if cached:
|
if cached:
|
||||||
log.debug("Looking up cached disco#info data " + \
|
log.debug("Looking up cached disco#info data " + \
|
||||||
"for %s, node %s.", jid, node)
|
"for %s, node %s.", jid, node)
|
||||||
info = self.api['get_cached_info'](jid, node,
|
info = self.api['get_cached_info'](jid, node,
|
||||||
kwargs.get('ifrom', None),
|
kwargs.get('ifrom', None),
|
||||||
kwargs)
|
kwargs)
|
||||||
if info is not None:
|
if info is not None:
|
||||||
return self._wrap(kwargs.get('ifrom', None), jid, info)
|
return self._wrap(kwargs.get('ifrom', None), jid, info)
|
||||||
@ -405,8 +405,8 @@ class XEP_0030(BasePlugin):
|
|||||||
Otherwise the parameter is ignored.
|
Otherwise the parameter is ignored.
|
||||||
"""
|
"""
|
||||||
if local or jid is None:
|
if local or jid is None:
|
||||||
items = self.api['get_items'](jid, node,
|
items = self.api['get_items'](jid, node,
|
||||||
kwargs.get('ifrom', None),
|
kwargs.get('ifrom', None),
|
||||||
kwargs)
|
kwargs)
|
||||||
return self._wrap(kwargs.get('ifrom', None), jid, items)
|
return self._wrap(kwargs.get('ifrom', None), jid, items)
|
||||||
|
|
||||||
|
@ -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)
|
|
20
sleekxmpp/plugins/xep_0033/__init__.py
Normal file
20
sleekxmpp/plugins/xep_0033/__init__.py
Normal 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
|
32
sleekxmpp/plugins/xep_0033/addresses.py
Normal file
32
sleekxmpp/plugins/xep_0033/addresses.py
Normal 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)
|
131
sleekxmpp/plugins/xep_0033/stanza.py
Normal file
131
sleekxmpp/plugins/xep_0033/stanza.py
Normal 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)
|
@ -110,14 +110,14 @@ class Command(ElementBase):
|
|||||||
"""
|
"""
|
||||||
Return the set of allowable next actions.
|
Return the set of allowable next actions.
|
||||||
"""
|
"""
|
||||||
actions = []
|
actions = set()
|
||||||
actions_xml = self.find('{%s}actions' % self.namespace)
|
actions_xml = self.find('{%s}actions' % self.namespace)
|
||||||
if actions_xml is not None:
|
if actions_xml is not None:
|
||||||
for action in self.next_actions:
|
for action in self.next_actions:
|
||||||
action_xml = actions_xml.find('{%s}%s' % (self.namespace,
|
action_xml = actions_xml.find('{%s}%s' % (self.namespace,
|
||||||
action))
|
action))
|
||||||
if action_xml is not None:
|
if action_xml is not None:
|
||||||
actions.append(action)
|
actions.add(action)
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
def del_actions(self):
|
def del_actions(self):
|
||||||
|
@ -72,6 +72,7 @@ class Nickname(ElementBase):
|
|||||||
name = 'NICKNAME'
|
name = 'NICKNAME'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'nicknames'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -94,6 +95,7 @@ class Email(ElementBase):
|
|||||||
name = 'EMAIL'
|
name = 'EMAIL'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'emails'
|
||||||
interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'])
|
interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'])
|
||||||
sub_interfaces = set(['USERID'])
|
sub_interfaces = set(['USERID'])
|
||||||
bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400'])
|
bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400'])
|
||||||
@ -103,8 +105,9 @@ class Address(ElementBase):
|
|||||||
name = 'ADR'
|
name = 'ADR'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
|
plugin_multi_attrib = 'addresses'
|
||||||
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
|
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
|
||||||
|
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
|
||||||
'REGION', 'PCODE', 'CTRY'])
|
'REGION', 'PCODE', 'CTRY'])
|
||||||
sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY',
|
sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY',
|
||||||
'REGION', 'PCODE', 'CTRY'])
|
'REGION', 'PCODE', 'CTRY'])
|
||||||
@ -115,12 +118,13 @@ class Telephone(ElementBase):
|
|||||||
name = 'TEL'
|
name = 'TEL'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'telephone_numbers'
|
||||||
interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG',
|
interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG',
|
||||||
'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS',
|
'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS',
|
||||||
'PREF', 'NUMBER'])
|
'PREF', 'NUMBER'])
|
||||||
sub_interfaces = set(['NUMBER'])
|
sub_interfaces = set(['NUMBER'])
|
||||||
bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
|
bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
|
||||||
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
|
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
|
||||||
'ISDN', 'PCS', 'PREF'])
|
'ISDN', 'PCS', 'PREF'])
|
||||||
|
|
||||||
def setup(self, xml=None):
|
def setup(self, xml=None):
|
||||||
@ -138,9 +142,10 @@ class Label(ElementBase):
|
|||||||
name = 'LABEL'
|
name = 'LABEL'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'labels'
|
||||||
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT',
|
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT',
|
||||||
'PREF', 'lines'])
|
'PREF', 'lines'])
|
||||||
bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
|
bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
|
||||||
'INT', 'PREF'])
|
'INT', 'PREF'])
|
||||||
|
|
||||||
def add_line(self, value):
|
def add_line(self, value):
|
||||||
@ -171,6 +176,7 @@ class Geo(ElementBase):
|
|||||||
name = 'GEO'
|
name = 'GEO'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'geolocations'
|
||||||
interfaces = set(['LAT', 'LON'])
|
interfaces = set(['LAT', 'LON'])
|
||||||
sub_interfaces = interfaces
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
@ -179,6 +185,7 @@ class Org(ElementBase):
|
|||||||
name = 'ORG'
|
name = 'ORG'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'organizations'
|
||||||
interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits'])
|
interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits'])
|
||||||
sub_interfaces = set(['ORGNAME', 'ORGUNIT'])
|
sub_interfaces = set(['ORGNAME', 'ORGUNIT'])
|
||||||
|
|
||||||
@ -210,6 +217,7 @@ class Photo(ElementBase):
|
|||||||
name = 'PHOTO'
|
name = 'PHOTO'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'photos'
|
||||||
interfaces = set(['TYPE', 'EXTVAL'])
|
interfaces = set(['TYPE', 'EXTVAL'])
|
||||||
sub_interfaces = interfaces
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
@ -218,14 +226,16 @@ class Logo(ElementBase):
|
|||||||
name = 'LOGO'
|
name = 'LOGO'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'logos'
|
||||||
interfaces = set(['TYPE', 'EXTVAL'])
|
interfaces = set(['TYPE', 'EXTVAL'])
|
||||||
sub_interfaces = interfaces
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
|
|
||||||
class Sound(ElementBase):
|
class Sound(ElementBase):
|
||||||
name = 'LOGO'
|
name = 'SOUND'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'sounds'
|
||||||
interfaces = set(['PHONETC', 'EXTVAL'])
|
interfaces = set(['PHONETC', 'EXTVAL'])
|
||||||
sub_interfaces = interfaces
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
@ -264,6 +274,7 @@ class Classification(ElementBase):
|
|||||||
name = 'CLASS'
|
name = 'CLASS'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'classifications'
|
||||||
interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'])
|
interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'])
|
||||||
bool_interfaces = interfaces
|
bool_interfaces = interfaces
|
||||||
|
|
||||||
@ -272,6 +283,7 @@ class Categories(ElementBase):
|
|||||||
name = 'CATEGORIES'
|
name = 'CATEGORIES'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'categories'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -301,6 +313,7 @@ class Birthday(ElementBase):
|
|||||||
name = 'BDAY'
|
name = 'BDAY'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'birthdays'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -319,6 +332,7 @@ class Rev(ElementBase):
|
|||||||
name = 'REV'
|
name = 'REV'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'revision_dates'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -337,6 +351,7 @@ class Title(ElementBase):
|
|||||||
name = 'TITLE'
|
name = 'TITLE'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'titles'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -351,6 +366,7 @@ class Role(ElementBase):
|
|||||||
name = 'ROLE'
|
name = 'ROLE'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'roles'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -365,6 +381,7 @@ class Note(ElementBase):
|
|||||||
name = 'NOTE'
|
name = 'NOTE'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'notes'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -379,6 +396,7 @@ class Desc(ElementBase):
|
|||||||
name = 'DESC'
|
name = 'DESC'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'descriptions'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -393,6 +411,7 @@ class URL(ElementBase):
|
|||||||
name = 'URL'
|
name = 'URL'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'urls'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -407,6 +426,7 @@ class UID(ElementBase):
|
|||||||
name = 'UID'
|
name = 'UID'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'uids'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -421,6 +441,7 @@ class ProdID(ElementBase):
|
|||||||
name = 'PRODID'
|
name = 'PRODID'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'product_ids'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -435,6 +456,7 @@ class Mailer(ElementBase):
|
|||||||
name = 'MAILER'
|
name = 'MAILER'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'mailers'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -449,6 +471,7 @@ class SortString(ElementBase):
|
|||||||
name = 'SORT-STRING'
|
name = 'SORT-STRING'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = 'SORT_STRING'
|
plugin_attrib = 'SORT_STRING'
|
||||||
|
plugin_multi_attrib = 'sort_strings'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -463,6 +486,7 @@ class Agent(ElementBase):
|
|||||||
name = 'AGENT'
|
name = 'AGENT'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'agents'
|
||||||
interfaces = set(['EXTVAL'])
|
interfaces = set(['EXTVAL'])
|
||||||
sub_interfaces = interfaces
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
@ -471,6 +495,7 @@ class JabberID(ElementBase):
|
|||||||
name = 'JABBERID'
|
name = 'JABBERID'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'jids'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
@ -485,11 +510,12 @@ class TimeZone(ElementBase):
|
|||||||
name = 'TZ'
|
name = 'TZ'
|
||||||
namespace = 'vcard-temp'
|
namespace = 'vcard-temp'
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
|
plugin_multi_attrib = 'timezones'
|
||||||
interfaces = set([name])
|
interfaces = set([name])
|
||||||
is_extension = True
|
is_extension = True
|
||||||
|
|
||||||
def set_tz(self, value):
|
def set_tz(self, value):
|
||||||
time = xep_0082.time(offset=value)
|
time = xep_0082.time(offset=value)
|
||||||
if time[-1] == 'Z':
|
if time[-1] == 'Z':
|
||||||
self.xml.text = 'Z'
|
self.xml.text = 'Z'
|
||||||
else:
|
else:
|
||||||
|
@ -53,7 +53,7 @@ class XEP_0054(BasePlugin):
|
|||||||
def make_vcard(self):
|
def make_vcard(self):
|
||||||
return VCardTemp()
|
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):
|
block=True, callback=None, timeout=None):
|
||||||
if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain:
|
if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain:
|
||||||
local = True
|
local = True
|
||||||
@ -84,12 +84,12 @@ class XEP_0054(BasePlugin):
|
|||||||
iq.enable('vcard_temp')
|
iq.enable('vcard_temp')
|
||||||
|
|
||||||
vcard = iq.send(block=block, callback=callback, timeout=timeout)
|
vcard = iq.send(block=block, callback=callback, timeout=timeout)
|
||||||
|
|
||||||
if block:
|
if block:
|
||||||
self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp'])
|
self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp'])
|
||||||
return vcard
|
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):
|
callback=None, timeout=None):
|
||||||
if self.xmpp.is_component:
|
if self.xmpp.is_component:
|
||||||
self.api['set_vcard'](jid, None, ifrom, vcard)
|
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'])
|
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
|
||||||
return
|
return
|
||||||
elif iq['type'] == 'get':
|
elif iq['type'] == 'get':
|
||||||
vcard = self.api['get_vard'](iq['from'].bare)
|
vcard = self.api['get_vcard'](iq['from'].bare)
|
||||||
if isinstance(vcard, Iq):
|
if isinstance(vcard, Iq):
|
||||||
vcard.send()
|
vcard.send()
|
||||||
else:
|
else:
|
||||||
|
@ -74,7 +74,7 @@ class Set(ElementBase):
|
|||||||
if fi is not None:
|
if fi is not None:
|
||||||
if val:
|
if val:
|
||||||
fi.attrib['index'] = val
|
fi.attrib['index'] = val
|
||||||
else:
|
elif 'index' in fi.attrib:
|
||||||
del fi.attrib['index']
|
del fi.attrib['index']
|
||||||
elif val:
|
elif val:
|
||||||
fi = ET.Element("{%s}first" % (self.namespace))
|
fi = ET.Element("{%s}first" % (self.namespace))
|
||||||
@ -93,7 +93,7 @@ class Set(ElementBase):
|
|||||||
|
|
||||||
def set_before(self, val):
|
def set_before(self, val):
|
||||||
b = self.xml.find("{%s}before" % (self.namespace))
|
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)
|
self._set_sub_text('{%s}before' % self.namespace, '', True)
|
||||||
else:
|
else:
|
||||||
self._set_sub_text('{%s}before' % self.namespace, val)
|
self._set_sub_text('{%s}before' % self.namespace, val)
|
||||||
|
@ -77,12 +77,12 @@ class Item(ElementBase):
|
|||||||
self.append(value)
|
self.append(value)
|
||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self):
|
||||||
childs = self.xml.getchildren()
|
childs = list(self.xml)
|
||||||
if len(childs) > 0:
|
if len(childs) > 0:
|
||||||
return childs[0]
|
return childs[0]
|
||||||
|
|
||||||
def del_payload(self):
|
def del_payload(self):
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
self.xml.remove(child)
|
self.xml.remove(child)
|
||||||
|
|
||||||
|
|
||||||
@ -254,12 +254,12 @@ class PubsubState(ElementBase):
|
|||||||
self.xml.append(value)
|
self.xml.append(value)
|
||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self):
|
||||||
childs = self.xml.getchildren()
|
childs = list(self.xml)
|
||||||
if len(childs) > 0:
|
if len(childs) > 0:
|
||||||
return childs[0]
|
return childs[0]
|
||||||
|
|
||||||
def del_payload(self):
|
def del_payload(self):
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
self.xml.remove(child)
|
self.xml.remove(child)
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class PubsubErrorCondition(ElementBase):
|
|||||||
|
|
||||||
def get_condition(self):
|
def get_condition(self):
|
||||||
"""Return the condition element's name."""
|
"""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:
|
if "{%s}" % self.condition_ns in child.tag:
|
||||||
cond = child.tag.split('}', 1)[-1]
|
cond = child.tag.split('}', 1)[-1]
|
||||||
if cond in self.conditions:
|
if cond in self.conditions:
|
||||||
@ -55,7 +55,7 @@ class PubsubErrorCondition(ElementBase):
|
|||||||
|
|
||||||
def del_condition(self):
|
def del_condition(self):
|
||||||
"""Remove the condition element."""
|
"""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:
|
if "{%s}" % self.condition_ns in child.tag:
|
||||||
tag = child.tag.split('}', 1)[-1]
|
tag = child.tag.split('}', 1)[-1]
|
||||||
if tag in self.conditions:
|
if tag in self.conditions:
|
||||||
|
@ -31,12 +31,12 @@ class EventItem(ElementBase):
|
|||||||
self.xml.append(value)
|
self.xml.append(value)
|
||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self):
|
||||||
childs = self.xml.getchildren()
|
childs = list(self.xml)
|
||||||
if len(childs) > 0:
|
if len(childs) > 0:
|
||||||
return childs[0]
|
return childs[0]
|
||||||
|
|
||||||
def del_payload(self):
|
def del_payload(self):
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
self.xml.remove(child)
|
self.xml.remove(child)
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,5 +39,3 @@ class AuthFeature(ElementBase):
|
|||||||
interfaces = set()
|
interfaces = set()
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
|
|
||||||
|
|
||||||
|
@ -40,33 +40,33 @@ class XEP_0080(BasePlugin):
|
|||||||
accuracy -- Horizontal GPS error in meters.
|
accuracy -- Horizontal GPS error in meters.
|
||||||
alt -- Altitude in meters above or below sea level.
|
alt -- Altitude in meters above or below sea level.
|
||||||
area -- A named area such as a campus or neighborhood.
|
area -- A named area such as a campus or neighborhood.
|
||||||
bearing -- GPS bearing (direction in which the entity is
|
bearing -- GPS bearing (direction in which the entity is
|
||||||
heading to reach its next waypoint), measured in
|
heading to reach its next waypoint), measured in
|
||||||
decimal degrees relative to true north.
|
decimal degrees relative to true north.
|
||||||
building -- A specific building on a street or in an area.
|
building -- A specific building on a street or in an area.
|
||||||
country -- The nation where the user is located.
|
country -- The nation where the user is located.
|
||||||
countrycode -- The ISO 3166 two-letter country code.
|
countrycode -- The ISO 3166 two-letter country code.
|
||||||
datum -- GPS datum.
|
datum -- GPS datum.
|
||||||
description -- A natural-language name for or description of
|
description -- A natural-language name for or description of
|
||||||
the location.
|
the location.
|
||||||
error -- Horizontal GPS error in arc minutes. Obsoleted by
|
error -- Horizontal GPS error in arc minutes. Obsoleted by
|
||||||
the accuracy parameter.
|
the accuracy parameter.
|
||||||
floor -- A particular floor in a building.
|
floor -- A particular floor in a building.
|
||||||
lat -- Latitude in decimal degrees North.
|
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.
|
as a town or city.
|
||||||
lon -- Longitude in decimal degrees East.
|
lon -- Longitude in decimal degrees East.
|
||||||
postalcode -- A code used for postal delivery.
|
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.
|
as a state or province.
|
||||||
room -- A particular room in a building.
|
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.
|
in meters per second.
|
||||||
street -- A thoroughfare within the locality, or a crossing
|
street -- A thoroughfare within the locality, or a crossing
|
||||||
of two thoroughfares.
|
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.
|
information about the location.
|
||||||
timestamp -- UTC timestamp specifying the moment when the
|
timestamp -- UTC timestamp specifying the moment when the
|
||||||
reading was taken.
|
reading was taken.
|
||||||
uri -- A URI or URL pointing to information about
|
uri -- A URI or URL pointing to information about
|
||||||
the location.
|
the location.
|
||||||
@ -115,7 +115,7 @@ class XEP_0080(BasePlugin):
|
|||||||
be executed when a reply stanza is received.
|
be executed when a reply stanza is received.
|
||||||
"""
|
"""
|
||||||
geoloc = Geoloc()
|
geoloc = Geoloc()
|
||||||
return self.xmpp['xep_0163'].publish(geoloc,
|
return self.xmpp['xep_0163'].publish(geoloc,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
block=block,
|
||||||
callback=callback,
|
callback=callback,
|
||||||
|
@ -31,33 +31,33 @@ class Geoloc(ElementBase):
|
|||||||
accuracy -- Horizontal GPS error in meters.
|
accuracy -- Horizontal GPS error in meters.
|
||||||
alt -- Altitude in meters above or below sea level.
|
alt -- Altitude in meters above or below sea level.
|
||||||
area -- A named area such as a campus or neighborhood.
|
area -- A named area such as a campus or neighborhood.
|
||||||
bearing -- GPS bearing (direction in which the entity is
|
bearing -- GPS bearing (direction in which the entity is
|
||||||
heading to reach its next waypoint), measured in
|
heading to reach its next waypoint), measured in
|
||||||
decimal degrees relative to true north.
|
decimal degrees relative to true north.
|
||||||
building -- A specific building on a street or in an area.
|
building -- A specific building on a street or in an area.
|
||||||
country -- The nation where the user is located.
|
country -- The nation where the user is located.
|
||||||
countrycode -- The ISO 3166 two-letter country code.
|
countrycode -- The ISO 3166 two-letter country code.
|
||||||
datum -- GPS datum.
|
datum -- GPS datum.
|
||||||
description -- A natural-language name for or description of
|
description -- A natural-language name for or description of
|
||||||
the location.
|
the location.
|
||||||
error -- Horizontal GPS error in arc minutes. Obsoleted by
|
error -- Horizontal GPS error in arc minutes. Obsoleted by
|
||||||
the accuracy parameter.
|
the accuracy parameter.
|
||||||
floor -- A particular floor in a building.
|
floor -- A particular floor in a building.
|
||||||
lat -- Latitude in decimal degrees North.
|
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.
|
as a town or city.
|
||||||
lon -- Longitude in decimal degrees East.
|
lon -- Longitude in decimal degrees East.
|
||||||
postalcode -- A code used for postal delivery.
|
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.
|
as a state or province.
|
||||||
room -- A particular room in a building.
|
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.
|
in meters per second.
|
||||||
street -- A thoroughfare within the locality, or a crossing
|
street -- A thoroughfare within the locality, or a crossing
|
||||||
of two thoroughfares.
|
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.
|
information about the location.
|
||||||
timestamp -- UTC timestamp specifying the moment when the
|
timestamp -- UTC timestamp specifying the moment when the
|
||||||
reading was taken.
|
reading was taken.
|
||||||
uri -- A URI or URL pointing to information about
|
uri -- A URI or URL pointing to information about
|
||||||
the location.
|
the location.
|
||||||
@ -65,10 +65,10 @@ class Geoloc(ElementBase):
|
|||||||
|
|
||||||
namespace = 'http://jabber.org/protocol/geoloc'
|
namespace = 'http://jabber.org/protocol/geoloc'
|
||||||
name = 'geoloc'
|
name = 'geoloc'
|
||||||
interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',
|
interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',
|
||||||
'country', 'countrycode', 'datum', 'dscription',
|
'country', 'countrycode', 'datum', 'dscription',
|
||||||
'error', 'floor', 'lat', 'locality', 'lon',
|
'error', 'floor', 'lat', 'locality', 'lon',
|
||||||
'postalcode', 'region', 'room', 'speed', 'street',
|
'postalcode', 'region', 'room', 'speed', 'street',
|
||||||
'text', 'timestamp', 'uri'))
|
'text', 'timestamp', 'uri'))
|
||||||
sub_interfaces = interfaces
|
sub_interfaces = interfaces
|
||||||
plugin_attrib = name
|
plugin_attrib = name
|
||||||
@ -88,7 +88,7 @@ class Geoloc(ElementBase):
|
|||||||
"""
|
"""
|
||||||
self._set_sub_text('accuracy', text=str(accuracy))
|
self._set_sub_text('accuracy', text=str(accuracy))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_accuracy(self):
|
def get_accuracy(self):
|
||||||
"""
|
"""
|
||||||
Return the value of the <accuracy> element as an integer.
|
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))
|
self._set_sub_text('alt', text=str(alt))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_alt(self):
|
def get_alt(self):
|
||||||
"""
|
"""
|
||||||
Return the value of the <alt> element as an integer.
|
Return the value of the <alt> element as an integer.
|
||||||
@ -130,8 +130,8 @@ class Geoloc(ElementBase):
|
|||||||
Set the value of the <bearing> element.
|
Set the value of the <bearing> element.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
bearing -- GPS bearing (direction in which the entity is heading
|
bearing -- GPS bearing (direction in which the entity is heading
|
||||||
to reach its next waypoint), measured in decimal
|
to reach its next waypoint), measured in decimal
|
||||||
degrees relative to true north
|
degrees relative to true north
|
||||||
"""
|
"""
|
||||||
self._set_sub_text('bearing', text=str(bearing))
|
self._set_sub_text('bearing', text=str(bearing))
|
||||||
@ -155,7 +155,7 @@ class Geoloc(ElementBase):
|
|||||||
Set the value of the <error> element.
|
Set the value of the <error> element.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
error -- Horizontal GPS error in arc minutes; this
|
error -- Horizontal GPS error in arc minutes; this
|
||||||
element is deprecated in favor of <accuracy/>
|
element is deprecated in favor of <accuracy/>
|
||||||
"""
|
"""
|
||||||
self._set_sub_text('error', text=str(error))
|
self._set_sub_text('error', text=str(error))
|
||||||
@ -183,7 +183,7 @@ class Geoloc(ElementBase):
|
|||||||
"""
|
"""
|
||||||
self._set_sub_text('lat', text=str(lat))
|
self._set_sub_text('lat', text=str(lat))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_lat(self):
|
def get_lat(self):
|
||||||
"""
|
"""
|
||||||
Return the value of the <lat> element as a float.
|
Return the value of the <lat> element as a float.
|
||||||
@ -196,7 +196,7 @@ class Geoloc(ElementBase):
|
|||||||
return float(p)
|
return float(p)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_lon(self, lon):
|
def set_lon(self, lon):
|
||||||
"""
|
"""
|
||||||
Set the value of the <lon> element.
|
Set the value of the <lon> element.
|
||||||
@ -225,12 +225,12 @@ class Geoloc(ElementBase):
|
|||||||
Set the value of the <speed> element.
|
Set the value of the <speed> element.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
speed -- The speed at which the entity is moving,
|
speed -- The speed at which the entity is moving,
|
||||||
in meters per second
|
in meters per second
|
||||||
"""
|
"""
|
||||||
self._set_sub_text('speed', text=str(speed))
|
self._set_sub_text('speed', text=str(speed))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def get_speed(self):
|
def get_speed(self):
|
||||||
"""
|
"""
|
||||||
Return the value of the <speed> element as a float.
|
Return the value of the <speed> element as a float.
|
||||||
|
@ -42,6 +42,7 @@ def format_date(time_obj):
|
|||||||
time_obj = time_obj.date()
|
time_obj = time_obj.date()
|
||||||
return time_obj.isoformat()
|
return time_obj.isoformat()
|
||||||
|
|
||||||
|
|
||||||
def format_time(time_obj):
|
def format_time(time_obj):
|
||||||
"""
|
"""
|
||||||
Return a formatted string version of a time object.
|
Return a formatted string version of a time object.
|
||||||
@ -60,6 +61,7 @@ def format_time(time_obj):
|
|||||||
return '%sZ' % timestamp
|
return '%sZ' % timestamp
|
||||||
return timestamp
|
return timestamp
|
||||||
|
|
||||||
|
|
||||||
def format_datetime(time_obj):
|
def format_datetime(time_obj):
|
||||||
"""
|
"""
|
||||||
Return a formatted string version of a datetime object.
|
Return a formatted string version of a datetime object.
|
||||||
@ -76,6 +78,7 @@ def format_datetime(time_obj):
|
|||||||
return '%sZ' % timestamp
|
return '%sZ' % timestamp
|
||||||
return timestamp
|
return timestamp
|
||||||
|
|
||||||
|
|
||||||
def date(year=None, month=None, day=None, obj=False):
|
def date(year=None, month=None, day=None, obj=False):
|
||||||
"""
|
"""
|
||||||
Create a date only timestamp for the given instant.
|
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
|
day = today.day
|
||||||
value = dt.date(year, month, day)
|
value = dt.date(year, month, day)
|
||||||
if obj:
|
if obj:
|
||||||
return value
|
return value
|
||||||
return format_date(value)
|
return format_date(value)
|
||||||
|
|
||||||
|
|
||||||
def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
|
def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
|
||||||
"""
|
"""
|
||||||
Create a time only timestamp for the given instant.
|
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 value
|
||||||
return format_time(value)
|
return format_time(value)
|
||||||
|
|
||||||
|
|
||||||
def datetime(year=None, month=None, day=None, hour=None,
|
def datetime(year=None, month=None, day=None, hour=None,
|
||||||
min=None, sec=None, micro=None, offset=None,
|
min=None, sec=None, micro=None, offset=None,
|
||||||
separators=True, obj=False):
|
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,
|
value = dt.datetime(year, month, day, hour,
|
||||||
min, sec, micro, offset)
|
min, sec, micro, offset)
|
||||||
if obj:
|
if obj:
|
||||||
return value
|
return value
|
||||||
return format_datetime(value)
|
return format_datetime(value)
|
||||||
|
|
||||||
|
|
||||||
|
16
sleekxmpp/plugins/xep_0084/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0084/__init__.py
Normal 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)
|
101
sleekxmpp/plugins/xep_0084/avatar.py
Normal file
101
sleekxmpp/plugins/xep_0084/avatar.py
Normal 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)
|
78
sleekxmpp/plugins/xep_0084/stanza.py
Normal file
78
sleekxmpp/plugins/xep_0084/stanza.py
Normal 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)
|
@ -47,28 +47,28 @@ class LegacyError(ElementBase):
|
|||||||
interfaces = set(('condition',))
|
interfaces = set(('condition',))
|
||||||
overrides = ['set_condition']
|
overrides = ['set_condition']
|
||||||
|
|
||||||
error_map = {'bad-request': ('modify','400'),
|
error_map = {'bad-request': ('modify', '400'),
|
||||||
'conflict': ('cancel','409'),
|
'conflict': ('cancel', '409'),
|
||||||
'feature-not-implemented': ('cancel','501'),
|
'feature-not-implemented': ('cancel', '501'),
|
||||||
'forbidden': ('auth','403'),
|
'forbidden': ('auth', '403'),
|
||||||
'gone': ('modify','302'),
|
'gone': ('modify', '302'),
|
||||||
'internal-server-error': ('wait','500'),
|
'internal-server-error': ('wait', '500'),
|
||||||
'item-not-found': ('cancel','404'),
|
'item-not-found': ('cancel', '404'),
|
||||||
'jid-malformed': ('modify','400'),
|
'jid-malformed': ('modify', '400'),
|
||||||
'not-acceptable': ('modify','406'),
|
'not-acceptable': ('modify', '406'),
|
||||||
'not-allowed': ('cancel','405'),
|
'not-allowed': ('cancel', '405'),
|
||||||
'not-authorized': ('auth','401'),
|
'not-authorized': ('auth', '401'),
|
||||||
'payment-required': ('auth','402'),
|
'payment-required': ('auth', '402'),
|
||||||
'recipient-unavailable': ('wait','404'),
|
'recipient-unavailable': ('wait', '404'),
|
||||||
'redirect': ('modify','302'),
|
'redirect': ('modify', '302'),
|
||||||
'registration-required': ('auth','407'),
|
'registration-required': ('auth', '407'),
|
||||||
'remote-server-not-found': ('cancel','404'),
|
'remote-server-not-found': ('cancel', '404'),
|
||||||
'remote-server-timeout': ('wait','504'),
|
'remote-server-timeout': ('wait', '504'),
|
||||||
'resource-constraint': ('wait','500'),
|
'resource-constraint': ('wait', '500'),
|
||||||
'service-unavailable': ('cancel','503'),
|
'service-unavailable': ('cancel', '503'),
|
||||||
'subscription-required': ('auth','407'),
|
'subscription-required': ('auth', '407'),
|
||||||
'undefined-condition': (None,'500'),
|
'undefined-condition': (None, '500'),
|
||||||
'unexpected-request': ('wait','400')}
|
'unexpected-request': ('wait', '400')}
|
||||||
|
|
||||||
def setup(self, xml):
|
def setup(self, xml):
|
||||||
"""Don't create XML for the plugin."""
|
"""Don't create XML for the plugin."""
|
||||||
|
@ -79,5 +79,7 @@ class XEP_0092(BasePlugin):
|
|||||||
result = iq.send()
|
result = iq.send()
|
||||||
|
|
||||||
if result and result['type'] != 'error':
|
if result and result['type'] != 'error':
|
||||||
return result['software_version'].values
|
values = result['software_version'].values
|
||||||
|
del values['lang']
|
||||||
|
return values
|
||||||
return False
|
return False
|
||||||
|
@ -34,7 +34,7 @@ class XEP_0107(BasePlugin):
|
|||||||
register_stanza_plugin(Message, UserMood)
|
register_stanza_plugin(Message, UserMood)
|
||||||
self.xmpp['xep_0163'].register_pep('user_mood', 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):
|
ifrom=None, block=True, callback=None, timeout=None):
|
||||||
"""
|
"""
|
||||||
Publish the user's current mood.
|
Publish the user's current mood.
|
||||||
@ -79,7 +79,7 @@ class XEP_0107(BasePlugin):
|
|||||||
be executed when a reply stanza is received.
|
be executed when a reply stanza is received.
|
||||||
"""
|
"""
|
||||||
mood = UserMood()
|
mood = UserMood()
|
||||||
return self.xmpp['xep_0163'].publish(mood,
|
return self.xmpp['xep_0163'].publish(mood,
|
||||||
node=UserMood.namespace,
|
node=UserMood.namespace,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
block=block,
|
||||||
|
@ -21,7 +21,7 @@ class UserActivity(ElementBase):
|
|||||||
'talking', 'traveling', 'undefined', 'working'])
|
'talking', 'traveling', 'undefined', 'working'])
|
||||||
specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries',
|
specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries',
|
||||||
'cleaning', 'coding', 'commuting', 'cooking', 'cycling',
|
'cleaning', 'coding', 'commuting', 'cooking', 'cycling',
|
||||||
'dancing', 'day_off', 'doing_maintenance',
|
'dancing', 'day_off', 'doing_maintenance',
|
||||||
'doing_the_dishes', 'doing_the_laundry', 'driving',
|
'doing_the_dishes', 'doing_the_laundry', 'driving',
|
||||||
'fishing', 'gaming', 'gardening', 'getting_a_haircut',
|
'fishing', 'gaming', 'gardening', 'getting_a_haircut',
|
||||||
'going_out', 'hanging_out', 'having_a_beer',
|
'going_out', 'hanging_out', 'having_a_beer',
|
||||||
@ -31,11 +31,11 @@ class UserActivity(ElementBase):
|
|||||||
'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train',
|
'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train',
|
||||||
'on_a_trip', 'on_the_phone', 'on_vacation',
|
'on_a_trip', 'on_the_phone', 'on_vacation',
|
||||||
'on_video_phone', 'other', 'partying', 'playing_sports',
|
'on_video_phone', 'other', 'partying', 'playing_sports',
|
||||||
'praying', 'reading', 'rehearsing', 'running',
|
'praying', 'reading', 'rehearsing', 'running',
|
||||||
'running_an_errand', 'scheduled_holiday', 'shaving',
|
'running_an_errand', 'scheduled_holiday', 'shaving',
|
||||||
'shopping', 'skiing', 'sleeping', 'smoking',
|
'shopping', 'skiing', 'sleeping', 'smoking',
|
||||||
'socializing', 'studying', 'sunbathing', 'swimming',
|
'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',
|
'walking', 'walking_the_dog', 'watching_a_movie',
|
||||||
'watching_tv', 'working_out', 'writing'])
|
'watching_tv', 'working_out', 'writing'])
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ class UserActivity(ElementBase):
|
|||||||
if isinstance(value, tuple) or isinstance(value, list):
|
if isinstance(value, tuple) or isinstance(value, list):
|
||||||
general = value[0]
|
general = value[0]
|
||||||
specific = value[1]
|
specific = value[1]
|
||||||
|
|
||||||
if general in self.general:
|
if general in self.general:
|
||||||
gen_xml = ET.Element('{%s}%s' % (self.namespace, general))
|
gen_xml = ET.Element('{%s}%s' % (self.namespace, general))
|
||||||
if specific:
|
if specific:
|
||||||
|
@ -29,7 +29,7 @@ class XEP_0108(BasePlugin):
|
|||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.xmpp['xep_0163'].register_pep('user_activity', UserActivity)
|
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):
|
ifrom=None, block=True, callback=None, timeout=None):
|
||||||
"""
|
"""
|
||||||
Publish the user's current activity.
|
Publish the user's current activity.
|
||||||
@ -76,7 +76,7 @@ class XEP_0108(BasePlugin):
|
|||||||
be executed when a reply stanza is received.
|
be executed when a reply stanza is received.
|
||||||
"""
|
"""
|
||||||
activity = UserActivity()
|
activity = UserActivity()
|
||||||
return self.xmpp['xep_0163'].publish(activity,
|
return self.xmpp['xep_0163'].publish(activity,
|
||||||
node=UserActivity.namespace,
|
node=UserActivity.namespace,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
block=block,
|
||||||
|
@ -35,7 +35,7 @@ class XEP_0115(BasePlugin):
|
|||||||
stanza = stanza
|
stanza = stanza
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.hashes = {'sha-1': hashlib.sha1,
|
self.hashes = {'sha-1': hashlib.sha1,
|
||||||
'sha1': hashlib.sha1,
|
'sha1': hashlib.sha1,
|
||||||
'md5': hashlib.md5}
|
'md5': hashlib.md5}
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ class XEP_0115(BasePlugin):
|
|||||||
existing_verstring = self.get_verstring(pres['from'].full)
|
existing_verstring = self.get_verstring(pres['from'].full)
|
||||||
if str(existing_verstring) == str(pres['caps']['ver']):
|
if str(existing_verstring) == str(pres['caps']['ver']):
|
||||||
return
|
return
|
||||||
|
|
||||||
if pres['caps']['hash'] not in self.hashes:
|
if pres['caps']['hash'] not in self.hashes:
|
||||||
try:
|
try:
|
||||||
log.debug("Unknown caps hash: %s", pres['caps']['hash'])
|
log.debug("Unknown caps hash: %s", pres['caps']['hash'])
|
||||||
@ -132,7 +132,7 @@ class XEP_0115(BasePlugin):
|
|||||||
return
|
return
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
return
|
return
|
||||||
|
|
||||||
log.debug("New caps verification string: %s", pres['caps']['ver'])
|
log.debug("New caps verification string: %s", pres['caps']['ver'])
|
||||||
try:
|
try:
|
||||||
node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver'])
|
node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver'])
|
||||||
@ -140,7 +140,7 @@ class XEP_0115(BasePlugin):
|
|||||||
|
|
||||||
if isinstance(caps, Iq):
|
if isinstance(caps, Iq):
|
||||||
caps = caps['disco_info']
|
caps = caps['disco_info']
|
||||||
|
|
||||||
if self._validate_caps(caps, pres['caps']['hash'],
|
if self._validate_caps(caps, pres['caps']['hash'],
|
||||||
pres['caps']['ver']):
|
pres['caps']['ver']):
|
||||||
self.assign_verstring(pres['from'], pres['caps']['ver'])
|
self.assign_verstring(pres['from'], pres['caps']['ver'])
|
||||||
@ -173,7 +173,8 @@ class XEP_0115(BasePlugin):
|
|||||||
form_types.append(f_type)
|
form_types.append(f_type)
|
||||||
deduped_form_types.add(f_type)
|
deduped_form_types.add(f_type)
|
||||||
if len(form_types) != len(deduped_form_types):
|
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
|
return False
|
||||||
|
|
||||||
if len(f_type) > 1:
|
if len(f_type) > 1:
|
||||||
@ -183,7 +184,8 @@ class XEP_0115(BasePlugin):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
|
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)
|
caps.xml.remove(stanza.xml)
|
||||||
else:
|
else:
|
||||||
log.debug("No FORM_TYPE found, ignoring form for caps")
|
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))
|
identities = sorted(('/'.join(i) for i in identities))
|
||||||
features = sorted(info['features'])
|
features = sorted(info['features'])
|
||||||
|
|
||||||
S += '<'.join(identities) + '<'
|
S += '<'.join(identities) + '<'
|
||||||
S += '<'.join(features) + '<'
|
S += '<'.join(features) + '<'
|
||||||
|
|
||||||
@ -254,7 +256,7 @@ class XEP_0115(BasePlugin):
|
|||||||
info = info['disco_info']
|
info = info['disco_info']
|
||||||
ver = self.generate_verstring(info, self.hash)
|
ver = self.generate_verstring(info, self.hash)
|
||||||
self.xmpp['xep_0030'].set_info(
|
self.xmpp['xep_0030'].set_info(
|
||||||
jid=jid,
|
jid=jid,
|
||||||
node='%s#%s' % (self.caps_node, ver),
|
node='%s#%s' % (self.caps_node, ver),
|
||||||
info=info)
|
info=info)
|
||||||
self.cache_caps(ver, info)
|
self.cache_caps(ver, info)
|
||||||
|
@ -69,7 +69,7 @@ class StaticCaps(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = self.disco.get_info(jid=jid, node=node,
|
info = self.disco.get_info(jid=jid, node=node,
|
||||||
ifrom=ifrom, **data)
|
ifrom=ifrom, **data)
|
||||||
info = self.disco._wrap(ifrom, jid, info, True)
|
info = self.disco._wrap(ifrom, jid, info, True)
|
||||||
return feature in info['disco_info']['features']
|
return feature in info['disco_info']['features']
|
||||||
@ -99,7 +99,7 @@ class StaticCaps(object):
|
|||||||
be skipped, even if a result has already been
|
be skipped, even if a result has already been
|
||||||
cached. Defaults to false.
|
cached. Defaults to false.
|
||||||
"""
|
"""
|
||||||
identity = (data.get('category', None),
|
identity = (data.get('category', None),
|
||||||
data.get('itype', None),
|
data.get('itype', None),
|
||||||
data.get('lang', None))
|
data.get('lang', None))
|
||||||
|
|
||||||
@ -114,7 +114,7 @@ class StaticCaps(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = self.disco.get_info(jid=jid, node=node,
|
info = self.disco.get_info(jid=jid, node=node,
|
||||||
ifrom=ifrom, **data)
|
ifrom=ifrom, **data)
|
||||||
info = self.disco._wrap(ifrom, jid, info, True)
|
info = self.disco._wrap(ifrom, jid, info, True)
|
||||||
return identity in map(trunc, info['disco_info']['identities'])
|
return identity in map(trunc, info['disco_info']['identities'])
|
||||||
|
@ -14,7 +14,7 @@ class UserTune(ElementBase):
|
|||||||
name = 'tune'
|
name = 'tune'
|
||||||
namespace = 'http://jabber.org/protocol/tune'
|
namespace = 'http://jabber.org/protocol/tune'
|
||||||
plugin_attrib = 'tune'
|
plugin_attrib = 'tune'
|
||||||
interfaces = set(['artist', 'length', 'rating', 'source',
|
interfaces = set(['artist', 'length', 'rating', 'source',
|
||||||
'title', 'track', 'uri'])
|
'title', 'track', 'uri'])
|
||||||
sub_interfaces = interfaces
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ class XEP_0118(BasePlugin):
|
|||||||
self.xmpp['xep_0163'].register_pep('user_tune', UserTune)
|
self.xmpp['xep_0163'].register_pep('user_tune', UserTune)
|
||||||
|
|
||||||
def publish_tune(self, artist=None, length=None, rating=None, source=None,
|
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):
|
ifrom=None, block=True, callback=None, timeout=None):
|
||||||
"""
|
"""
|
||||||
Publish the user's current tune.
|
Publish the user's current tune.
|
||||||
@ -61,7 +61,7 @@ class XEP_0118(BasePlugin):
|
|||||||
tune['title'] = title
|
tune['title'] = title
|
||||||
tune['track'] = track
|
tune['track'] = track
|
||||||
tune['uri'] = uri
|
tune['uri'] = uri
|
||||||
return self.xmpp['xep_0163'].publish(tune,
|
return self.xmpp['xep_0163'].publish(tune,
|
||||||
node=UserTune.namespace,
|
node=UserTune.namespace,
|
||||||
options=options,
|
options=options,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
@ -84,7 +84,7 @@ class XEP_0118(BasePlugin):
|
|||||||
be executed when a reply stanza is received.
|
be executed when a reply stanza is received.
|
||||||
"""
|
"""
|
||||||
tune = UserTune()
|
tune = UserTune()
|
||||||
return self.xmpp['xep_0163'].publish(tune,
|
return self.xmpp['xep_0163'].publish(tune,
|
||||||
node=UserTune.namespace,
|
node=UserTune.namespace,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
block=block,
|
||||||
|
@ -45,7 +45,7 @@ class XEP_0153(BasePlugin):
|
|||||||
self.api.register(self._set_hash, 'set_hash', default=True)
|
self.api.register(self._set_hash, 'set_hash', default=True)
|
||||||
self.api.register(self._get_hash, 'get_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):
|
timeout=None, callback=None):
|
||||||
vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)
|
vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)
|
||||||
vcard = vcard['vcard_temp']
|
vcard = vcard['vcard_temp']
|
||||||
@ -69,7 +69,7 @@ class XEP_0153(BasePlugin):
|
|||||||
own_jid = (jid.bare == self.xmpp.boundjid.bare)
|
own_jid = (jid.bare == self.xmpp.boundjid.bare)
|
||||||
if self.xmpp.is_component:
|
if self.xmpp.is_component:
|
||||||
own_jid = (jid.domain == self.xmpp.boundjid.domain)
|
own_jid = (jid.domain == self.xmpp.boundjid.domain)
|
||||||
|
|
||||||
if jid is not None:
|
if jid is not None:
|
||||||
jid = jid.bare
|
jid = jid.bare
|
||||||
self.api['set_hash'](jid, args=None)
|
self.api['set_hash'](jid, args=None)
|
||||||
@ -77,7 +77,7 @@ class XEP_0153(BasePlugin):
|
|||||||
self.xmpp.roster[jid].send_last_presence()
|
self.xmpp.roster[jid].send_last_presence()
|
||||||
|
|
||||||
iq = self.xmpp['xep_0054'].get_vcard(
|
iq = self.xmpp['xep_0054'].get_vcard(
|
||||||
jid=jid,
|
jid=jid,
|
||||||
ifrom=self.xmpp.boundjid)
|
ifrom=self.xmpp.boundjid)
|
||||||
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
||||||
if not data:
|
if not data:
|
||||||
|
@ -56,7 +56,7 @@ class XEP_0163(BasePlugin):
|
|||||||
jid -- Optionally specify the JID.
|
jid -- Optionally specify the JID.
|
||||||
"""
|
"""
|
||||||
if not isinstance(namespace, set) and not isinstance(namespace, list):
|
if not isinstance(namespace, set) and not isinstance(namespace, list):
|
||||||
namespace = [namespace]
|
namespace = [namespace]
|
||||||
|
|
||||||
for ns in namespace:
|
for ns in namespace:
|
||||||
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
|
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
|
||||||
@ -75,7 +75,7 @@ class XEP_0163(BasePlugin):
|
|||||||
jid -- Optionally specify the JID.
|
jid -- Optionally specify the JID.
|
||||||
"""
|
"""
|
||||||
if not isinstance(namespace, set) and not isinstance(namespace, list):
|
if not isinstance(namespace, set) and not isinstance(namespace, list):
|
||||||
namespace = [namespace]
|
namespace = [namespace]
|
||||||
|
|
||||||
for ns in namespace:
|
for ns in namespace:
|
||||||
self.xmpp['xep_0030'].del_feature(jid=jid,
|
self.xmpp['xep_0030'].del_feature(jid=jid,
|
||||||
@ -109,6 +109,7 @@ class XEP_0163(BasePlugin):
|
|||||||
node = stanza.namespace
|
node = stanza.namespace
|
||||||
|
|
||||||
return self.xmpp['xep_0060'].publish(ifrom, node,
|
return self.xmpp['xep_0060'].publish(ifrom, node,
|
||||||
|
id=id,
|
||||||
payload=stanza.xml,
|
payload=stanza.xml,
|
||||||
options=options,
|
options=options,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
|
@ -78,7 +78,7 @@ class XEP_0172(BasePlugin):
|
|||||||
be executed when a reply stanza is received.
|
be executed when a reply stanza is received.
|
||||||
"""
|
"""
|
||||||
nick = UserNick()
|
nick = UserNick()
|
||||||
return self.xmpp['xep_0163'].publish(nick,
|
return self.xmpp['xep_0163'].publish(nick,
|
||||||
node=UserNick.namespace,
|
node=UserNick.namespace,
|
||||||
ifrom=ifrom,
|
ifrom=ifrom,
|
||||||
block=block,
|
block=block,
|
||||||
|
@ -100,13 +100,13 @@ class XEP_0184(BasePlugin):
|
|||||||
|
|
||||||
if not isinstance(stanza, Message):
|
if not isinstance(stanza, Message):
|
||||||
return stanza
|
return stanza
|
||||||
|
|
||||||
if stanza['request_receipt']:
|
if stanza['request_receipt']:
|
||||||
return stanza
|
return stanza
|
||||||
|
|
||||||
if not stanza['type'] in self.ack_types:
|
if not stanza['type'] in self.ack_types:
|
||||||
return stanza
|
return stanza
|
||||||
|
|
||||||
if stanza['receipt']:
|
if stanza['receipt']:
|
||||||
return stanza
|
return stanza
|
||||||
|
|
||||||
|
@ -82,7 +82,6 @@ class Resumed(StanzaBase):
|
|||||||
self._set_attr('h', str(val))
|
self._set_attr('h', str(val))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Failed(StanzaBase, Error):
|
class Failed(StanzaBase, Error):
|
||||||
name = 'failed'
|
name = 'failed'
|
||||||
namespace = 'urn:xmpp:sm:3'
|
namespace = 'urn:xmpp:sm:3'
|
||||||
@ -106,7 +105,7 @@ class StreamManagement(ElementBase):
|
|||||||
self.del_required()
|
self.del_required()
|
||||||
if val:
|
if val:
|
||||||
self._set_sub_text('required', '', keep=True)
|
self._set_sub_text('required', '', keep=True)
|
||||||
|
|
||||||
def del_required(self):
|
def del_required(self):
|
||||||
self._del_sub('required')
|
self._del_sub('required')
|
||||||
|
|
||||||
@ -117,7 +116,7 @@ class StreamManagement(ElementBase):
|
|||||||
self.del_optional()
|
self.del_optional()
|
||||||
if val:
|
if val:
|
||||||
self._set_sub_text('optional', '', keep=True)
|
self._set_sub_text('optional', '', keep=True)
|
||||||
|
|
||||||
def del_optional(self):
|
def del_optional(self):
|
||||||
self._del_sub('optional')
|
self._del_sub('optional')
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ from sleekxmpp.plugins.xep_0198 import stanza
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
MAX_SEQ = 2**32
|
MAX_SEQ = 2 ** 32
|
||||||
|
|
||||||
|
|
||||||
class XEP_0198(BasePlugin):
|
class XEP_0198(BasePlugin):
|
||||||
@ -69,7 +69,7 @@ class XEP_0198(BasePlugin):
|
|||||||
|
|
||||||
self.enabled = threading.Event()
|
self.enabled = threading.Event()
|
||||||
self.unacked_queue = collections.deque()
|
self.unacked_queue = collections.deque()
|
||||||
|
|
||||||
self.seq_lock = threading.Lock()
|
self.seq_lock = threading.Lock()
|
||||||
self.handled_lock = threading.Lock()
|
self.handled_lock = threading.Lock()
|
||||||
self.ack_lock = threading.Lock()
|
self.ack_lock = threading.Lock()
|
||||||
@ -197,7 +197,7 @@ class XEP_0198(BasePlugin):
|
|||||||
|
|
||||||
def _handle_enabled(self, stanza):
|
def _handle_enabled(self, stanza):
|
||||||
"""Save the SM-ID, if provided.
|
"""Save the SM-ID, if provided.
|
||||||
|
|
||||||
Raises an :term:`sm_enabled` event.
|
Raises an :term:`sm_enabled` event.
|
||||||
"""
|
"""
|
||||||
self.xmpp.features.add('stream_management')
|
self.xmpp.features.add('stream_management')
|
||||||
@ -231,7 +231,7 @@ class XEP_0198(BasePlugin):
|
|||||||
|
|
||||||
def _handle_ack(self, ack):
|
def _handle_ack(self, ack):
|
||||||
"""Process a server ack by freeing acked stanzas from the queue.
|
"""Process a server ack by freeing acked stanzas from the queue.
|
||||||
|
|
||||||
Raises a :term:`stanza_acked` event for each acked stanza.
|
Raises a :term:`stanza_acked` event for each acked stanza.
|
||||||
"""
|
"""
|
||||||
if ack['h'] == self.last_ack:
|
if ack['h'] == self.last_ack:
|
||||||
@ -243,10 +243,10 @@ class XEP_0198(BasePlugin):
|
|||||||
log.debug("Ack: %s, Last Ack: %s, " + \
|
log.debug("Ack: %s, Last Ack: %s, " + \
|
||||||
"Unacked: %s, Num Acked: %s, " + \
|
"Unacked: %s, Num Acked: %s, " + \
|
||||||
"Remaining: %s",
|
"Remaining: %s",
|
||||||
ack['h'],
|
ack['h'],
|
||||||
self.last_ack,
|
self.last_ack,
|
||||||
num_unacked,
|
num_unacked,
|
||||||
num_acked,
|
num_acked,
|
||||||
num_unacked - num_acked)
|
num_unacked - num_acked)
|
||||||
for x in range(num_acked):
|
for x in range(num_acked):
|
||||||
seq, stanza = self.unacked_queue.popleft()
|
seq, stanza = self.unacked_queue.popleft()
|
||||||
|
@ -40,8 +40,12 @@ class XEP_0202(BasePlugin):
|
|||||||
# custom function can be supplied which accepts
|
# custom function can be supplied which accepts
|
||||||
# the JID of the entity to query for the time.
|
# the JID of the entity to query for the time.
|
||||||
self.local_time = self.config.get('local_time', None)
|
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:
|
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(
|
self.xmpp.registerHandler(
|
||||||
Callback('Entity Time',
|
Callback('Entity Time',
|
||||||
|
@ -13,9 +13,7 @@ from sleekxmpp.plugins.xep_0203.stanza import Delay
|
|||||||
from sleekxmpp.plugins.xep_0203.delay import XEP_0203
|
from sleekxmpp.plugins.xep_0203.delay import XEP_0203
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
register_plugin(XEP_0203)
|
register_plugin(XEP_0203)
|
||||||
|
|
||||||
|
|
||||||
# Retain some backwards compatibility
|
# Retain some backwards compatibility
|
||||||
xep_0203 = XEP_0203
|
xep_0203 = XEP_0203
|
||||||
|
126
sleekxmpp/plugins/xep_0222.py
Normal file
126
sleekxmpp/plugins/xep_0222.py
Normal 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)
|
126
sleekxmpp/plugins/xep_0223.py
Normal file
126
sleekxmpp/plugins/xep_0223.py
Normal 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)
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2012 Nathanael C. Fritz,
|
Copyright (C) 2012 Nathanael C. Fritz,
|
||||||
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2012 Nathanael C. Fritz,
|
Copyright (C) 2012 Nathanael C. Fritz,
|
||||||
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
This file is part of SleekXMPP.
|
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._set_bob, 'set_bob', default=True)
|
||||||
self.api.register(self._del_bob, 'del_bob', default=True)
|
self.api.register(self._del_bob, 'del_bob', default=True)
|
||||||
|
|
||||||
|
|
||||||
def set_bob(self, data, mtype, cid=None, max_age=None):
|
def set_bob(self, data, mtype, cid=None, max_age=None):
|
||||||
if cid is None:
|
if cid is None:
|
||||||
cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest()
|
cid = 'sha1+%s@bob.xmpp.org' % hashlib.sha1(data).hexdigest()
|
||||||
@ -73,7 +72,7 @@ class XEP_0231(BasePlugin):
|
|||||||
|
|
||||||
return cid
|
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):
|
block=True, timeout=None, callback=None):
|
||||||
if cached:
|
if cached:
|
||||||
data = self.api['get_bob'](None, None, ifrom, args=cid)
|
data = self.api['get_bob'](None, None, ifrom, args=cid)
|
||||||
@ -112,7 +111,7 @@ class XEP_0231(BasePlugin):
|
|||||||
iq.send()
|
iq.send()
|
||||||
|
|
||||||
def _handle_bob(self, stanza):
|
def _handle_bob(self, stanza):
|
||||||
self.api['set_bob'](stanza['from'], None,
|
self.api['set_bob'](stanza['from'], None,
|
||||||
stanza['to'], args=stanza['bob'])
|
stanza['to'], args=stanza['bob'])
|
||||||
self.xmpp.event('bob', stanza)
|
self.xmpp.event('bob', stanza)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
SleekXMPP: The Sleek XMPP Library
|
SleekXMPP: The Sleek XMPP Library
|
||||||
Copyright (C) 2012 Nathanael C. Fritz,
|
Copyright (C) 2012 Nathanael C. Fritz,
|
||||||
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
This file is part of SleekXMPP.
|
This file is part of SleekXMPP.
|
||||||
|
|
||||||
|
18
sleekxmpp/plugins/xep_0258/__init__.py
Normal file
18
sleekxmpp/plugins/xep_0258/__init__.py
Normal 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)
|
40
sleekxmpp/plugins/xep_0258/security_labels.py
Normal file
40
sleekxmpp/plugins/xep_0258/security_labels.py
Normal 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)
|
142
sleekxmpp/plugins/xep_0258/stanza.py
Normal file
142
sleekxmpp/plugins/xep_0258/stanza.py
Normal 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)
|
@ -307,34 +307,29 @@ class RosterItem(object):
|
|||||||
p['from'] = self.owner
|
p['from'] = self.owner
|
||||||
p.send()
|
p.send()
|
||||||
|
|
||||||
def send_presence(self, ptype=None, pshow=None, pstatus=None,
|
def send_presence(self, **kwargs):
|
||||||
ppriority=None, pnick=None):
|
|
||||||
"""
|
"""
|
||||||
Create, initialize, and send a Presence stanza.
|
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:
|
Arguments:
|
||||||
pshow -- The presence's show value.
|
pshow -- The presence's show value.
|
||||||
pstatus -- The presence's status message.
|
pstatus -- The presence's status message.
|
||||||
ppriority -- This connections' priority.
|
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'.
|
ptype -- The type of presence, such as 'subscribe'.
|
||||||
pnick -- Optional nickname of the presence's sender.
|
pnick -- Optional nickname of the presence's sender.
|
||||||
"""
|
"""
|
||||||
p = self.xmpp.make_presence(pshow=pshow,
|
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
|
||||||
pstatus=pstatus,
|
kwargs['pfrom'] = self.owner
|
||||||
ppriority=ppriority,
|
if not kwargs.get('pto', ''):
|
||||||
ptype=ptype,
|
kwargs['pto'] = self.jid
|
||||||
pnick=pnick,
|
self.xmpp.send_presence(**kwargs)
|
||||||
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
|
|
||||||
|
|
||||||
def send_last_presence(self):
|
def send_last_presence(self):
|
||||||
if self.last_status is None:
|
if self.last_status is None:
|
||||||
|
@ -6,9 +6,11 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.stanza import Presence
|
||||||
from sleekxmpp.xmlstream import JID
|
from sleekxmpp.xmlstream import JID
|
||||||
from sleekxmpp.roster import RosterNode
|
from sleekxmpp.roster import RosterNode
|
||||||
|
|
||||||
|
|
||||||
class Roster(object):
|
class Roster(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -55,6 +57,33 @@ class Roster(object):
|
|||||||
for node in self.db.entries(None, {}):
|
for node in self.db.entries(None, {}):
|
||||||
self.add(node)
|
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):
|
def __getitem__(self, key):
|
||||||
"""
|
"""
|
||||||
Return the roster node for a JID.
|
Return the roster node for a JID.
|
||||||
@ -121,29 +150,27 @@ class Roster(object):
|
|||||||
for node in self:
|
for node in self:
|
||||||
self[node].reset()
|
self[node].reset()
|
||||||
|
|
||||||
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
|
def send_presence(self, **kwargs):
|
||||||
pto=None, pfrom=None, ptype=None, pnick=None):
|
|
||||||
"""
|
"""
|
||||||
Create, initialize, and send a Presence stanza.
|
Create, initialize, and send a Presence stanza.
|
||||||
|
|
||||||
Forwards the send request to the appropriate roster to
|
If no recipient is specified, send the presence immediately.
|
||||||
perform the actual sending.
|
Otherwise, forward the send request to the recipient's roster
|
||||||
|
entry for processing.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
pshow -- The presence's show value.
|
pshow -- The presence's show value.
|
||||||
pstatus -- The presence's status message.
|
pstatus -- The presence's status message.
|
||||||
ppriority -- This connections' priority.
|
ppriority -- This connections' priority.
|
||||||
pto -- The recipient of a directed presence.
|
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'.
|
ptype -- The type of presence, such as 'subscribe'.
|
||||||
pfrom -- The sender of the presence.
|
|
||||||
pnick -- Optional nickname of the presence's sender.
|
pnick -- Optional nickname of the presence's sender.
|
||||||
"""
|
"""
|
||||||
self[pfrom].send_presence(ptype=ptype,
|
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
|
||||||
pshow=pshow,
|
kwargs['pfrom'] = self.jid
|
||||||
pstatus=pstatus,
|
self.xmpp.send_presence(**kwargs)
|
||||||
ppriority=ppriority,
|
|
||||||
pnick=pnick,
|
|
||||||
pto=pto)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auto_authorize(self):
|
def auto_authorize(self):
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
|
||||||
from sleekxmpp.xmlstream import JID
|
from sleekxmpp.xmlstream import JID
|
||||||
from sleekxmpp.roster import RosterItem
|
from sleekxmpp.roster import RosterItem
|
||||||
|
|
||||||
@ -59,13 +61,14 @@ class RosterNode(object):
|
|||||||
self.last_status = None
|
self.last_status = None
|
||||||
self._version = ''
|
self._version = ''
|
||||||
self._jids = {}
|
self._jids = {}
|
||||||
|
self._last_status_lock = threading.Lock()
|
||||||
|
|
||||||
if self.db:
|
if self.db:
|
||||||
if hasattr(self.db, 'version'):
|
if hasattr(self.db, 'version'):
|
||||||
self._version = self.db.version(self.jid)
|
self._version = self.db.version(self.jid)
|
||||||
for jid in self.db.entries(self.jid):
|
for jid in self.db.entries(self.jid):
|
||||||
self.add(jid)
|
self.add(jid)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self):
|
def version(self):
|
||||||
"""Retrieve the roster's version ID."""
|
"""Retrieve the roster's version ID."""
|
||||||
@ -146,7 +149,7 @@ class RosterNode(object):
|
|||||||
self.db = db
|
self.db = db
|
||||||
existing_entries = set(self._jids)
|
existing_entries = set(self._jids)
|
||||||
new_entries = set(self.db.entries(self.jid, {}))
|
new_entries = set(self.db.entries(self.jid, {}))
|
||||||
|
|
||||||
for jid in existing_entries:
|
for jid in existing_entries:
|
||||||
self._jids[jid].set_backend(db, save)
|
self._jids[jid].set_backend(db, save)
|
||||||
for jid in new_entries - existing_entries:
|
for jid in new_entries - existing_entries:
|
||||||
@ -291,8 +294,7 @@ class RosterNode(object):
|
|||||||
for jid in self:
|
for jid in self:
|
||||||
self[jid].reset()
|
self[jid].reset()
|
||||||
|
|
||||||
def send_presence(self, ptype=None, pshow=None, pstatus=None,
|
def send_presence(self, **kwargs):
|
||||||
ppriority=None, pnick=None, pto=None):
|
|
||||||
"""
|
"""
|
||||||
Create, initialize, and send a Presence stanza.
|
Create, initialize, and send a Presence stanza.
|
||||||
|
|
||||||
@ -305,27 +307,14 @@ class RosterNode(object):
|
|||||||
pstatus -- The presence's status message.
|
pstatus -- The presence's status message.
|
||||||
ppriority -- This connections' priority.
|
ppriority -- This connections' priority.
|
||||||
pto -- The recipient of a directed presence.
|
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'.
|
ptype -- The type of presence, such as 'subscribe'.
|
||||||
|
pnick -- Optional nickname of the presence's sender.
|
||||||
"""
|
"""
|
||||||
if pto:
|
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
|
||||||
self[pto].send_presence(ptype, pshow, pstatus,
|
kwargs['pfrom'] = self.jid
|
||||||
ppriority, pnick)
|
self.xmpp.send_presence(**kwargs)
|
||||||
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
|
|
||||||
|
|
||||||
def send_last_presence(self):
|
def send_last_presence(self):
|
||||||
if self.last_status is None:
|
if self.last_status is None:
|
||||||
|
@ -51,7 +51,8 @@ class Error(ElementBase):
|
|||||||
namespace = 'jabber:client'
|
namespace = 'jabber:client'
|
||||||
name = 'error'
|
name = 'error'
|
||||||
plugin_attrib = 'error'
|
plugin_attrib = 'error'
|
||||||
interfaces = set(('code', 'condition', 'text', 'type'))
|
interfaces = set(('code', 'condition', 'text', 'type',
|
||||||
|
'gone', 'redirect'))
|
||||||
sub_interfaces = set(('text',))
|
sub_interfaces = set(('text',))
|
||||||
plugin_attrib_map = {}
|
plugin_attrib_map = {}
|
||||||
plugin_tag_map = {}
|
plugin_tag_map = {}
|
||||||
@ -88,7 +89,7 @@ class Error(ElementBase):
|
|||||||
|
|
||||||
def get_condition(self):
|
def get_condition(self):
|
||||||
"""Return the condition element's name."""
|
"""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:
|
if "{%s}" % self.condition_ns in child.tag:
|
||||||
cond = child.tag.split('}', 1)[-1]
|
cond = child.tag.split('}', 1)[-1]
|
||||||
if cond in self.conditions:
|
if cond in self.conditions:
|
||||||
@ -109,7 +110,7 @@ class Error(ElementBase):
|
|||||||
|
|
||||||
def del_condition(self):
|
def del_condition(self):
|
||||||
"""Remove the condition element."""
|
"""Remove the condition element."""
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
if "{%s}" % self.condition_ns in child.tag:
|
if "{%s}" % self.condition_ns in child.tag:
|
||||||
tag = child.tag.split('}', 1)[-1]
|
tag = child.tag.split('}', 1)[-1]
|
||||||
if tag in self.conditions:
|
if tag in self.conditions:
|
||||||
@ -135,6 +136,33 @@ class Error(ElementBase):
|
|||||||
self._del_sub('{%s}text' % self.condition_ns)
|
self._del_sub('{%s}text' % self.condition_ns)
|
||||||
return self
|
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.
|
# To comply with PEP8, method names now use underscores.
|
||||||
# Deprecated method names are re-mapped for backwards compatibility.
|
# Deprecated method names are re-mapped for backwards compatibility.
|
||||||
|
@ -122,7 +122,7 @@ class Iq(RootStanza):
|
|||||||
|
|
||||||
def get_query(self):
|
def get_query(self):
|
||||||
"""Return the namespace of the <query> element."""
|
"""Return the namespace of the <query> element."""
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
if child.tag.endswith('query'):
|
if child.tag.endswith('query'):
|
||||||
ns = child.tag.split('}')[0]
|
ns = child.tag.split('}')[0]
|
||||||
if '{' in ns:
|
if '{' in ns:
|
||||||
@ -132,7 +132,7 @@ class Iq(RootStanza):
|
|||||||
|
|
||||||
def del_query(self):
|
def del_query(self):
|
||||||
"""Remove the <query> element."""
|
"""Remove the <query> element."""
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
if child.tag.endswith('query'):
|
if child.tag.endswith('query'):
|
||||||
self.xml.remove(child)
|
self.xml.remove(child)
|
||||||
return self
|
return self
|
||||||
|
@ -78,7 +78,8 @@ class RootStanza(StanzaBase):
|
|||||||
self['error']['type'] = 'cancel'
|
self['error']['type'] = 'cancel'
|
||||||
self.send()
|
self.send()
|
||||||
# log the error
|
# 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
|
# Finally raise the exception to a global exception handler
|
||||||
self.stream.exception(e)
|
self.stream.exception(e)
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class Roster(ElementBase):
|
|||||||
roster versioning.
|
roster versioning.
|
||||||
"""
|
"""
|
||||||
return self.xml.attrib.get('ver', None)
|
return self.xml.attrib.get('ver', None)
|
||||||
|
|
||||||
def set_ver(self, ver):
|
def set_ver(self, ver):
|
||||||
"""
|
"""
|
||||||
Ensure handling an empty ver attribute propery.
|
Ensure handling an empty ver attribute propery.
|
||||||
@ -101,7 +101,8 @@ class Roster(ElementBase):
|
|||||||
items[item['jid']] = item.values
|
items[item['jid']] = item.values
|
||||||
# Remove extra JID reference to keep everything
|
# Remove extra JID reference to keep everything
|
||||||
# backward compatible
|
# backward compatible
|
||||||
del items[item['jid']]['jid']
|
del items[item['jid']]['jid']
|
||||||
|
del items[item['jid']]['lang']
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def del_items(self):
|
def del_items(self):
|
||||||
|
@ -54,7 +54,7 @@ class StreamError(Error, StanzaBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
namespace = 'http://etherx.jabber.org/streams'
|
namespace = 'http://etherx.jabber.org/streams'
|
||||||
interfaces = set(('condition', 'text'))
|
interfaces = set(('condition', 'text', 'see_other_host'))
|
||||||
conditions = set((
|
conditions = set((
|
||||||
'bad-format', 'bad-namespace-prefix', 'conflict',
|
'bad-format', 'bad-namespace-prefix', 'conflict',
|
||||||
'connection-timeout', 'host-gone', 'host-unknown',
|
'connection-timeout', 'host-gone', 'host-unknown',
|
||||||
@ -66,3 +66,18 @@ class StreamError(Error, StanzaBase):
|
|||||||
'unsupported-feature', 'unsupported-stanza-type',
|
'unsupported-feature', 'unsupported-stanza-type',
|
||||||
'unsupported-version'))
|
'unsupported-version'))
|
||||||
condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'
|
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)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from sleekxmpp.thirdparty import OrderedDict
|
||||||
from sleekxmpp.xmlstream import StanzaBase
|
from sleekxmpp.xmlstream import StanzaBase
|
||||||
|
|
||||||
|
|
||||||
@ -28,7 +29,10 @@ class StreamFeatures(StanzaBase):
|
|||||||
def get_features(self):
|
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):
|
def set_features(self, value):
|
||||||
"""
|
"""
|
||||||
|
@ -76,7 +76,7 @@ class SleekTest(unittest.TestCase):
|
|||||||
known_prefixes[prefix],
|
known_prefixes[prefix],
|
||||||
xml_string)
|
xml_string)
|
||||||
xml = self.parse_xml(xml_string)
|
xml = self.parse_xml(xml_string)
|
||||||
xml = xml.getchildren()[0]
|
xml = list(xml)[0]
|
||||||
return xml
|
return xml
|
||||||
else:
|
else:
|
||||||
self.fail("XML data was mal-formed:\n%s" % xml_string)
|
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
|
# Remove unique ID prefix to make it easier to test
|
||||||
self.xmpp._id_prefix = ''
|
self.xmpp._id_prefix = ''
|
||||||
self.xmpp._disconnect_wait_for_threads = False
|
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
|
# We will use this to wait for the session_start event
|
||||||
# for live connections.
|
# for live connections.
|
||||||
@ -386,6 +388,7 @@ class SleekTest(unittest.TestCase):
|
|||||||
sid='',
|
sid='',
|
||||||
stream_ns="http://etherx.jabber.org/streams",
|
stream_ns="http://etherx.jabber.org/streams",
|
||||||
default_ns="jabber:client",
|
default_ns="jabber:client",
|
||||||
|
default_lang="en",
|
||||||
version="1.0",
|
version="1.0",
|
||||||
xml_header=True):
|
xml_header=True):
|
||||||
"""
|
"""
|
||||||
@ -413,6 +416,8 @@ class SleekTest(unittest.TestCase):
|
|||||||
parts.append('from="%s"' % sfrom)
|
parts.append('from="%s"' % sfrom)
|
||||||
if sid:
|
if sid:
|
||||||
parts.append('id="%s"' % sid)
|
parts.append('id="%s"' % sid)
|
||||||
|
if default_lang:
|
||||||
|
parts.append('xml:lang="%s"' % default_lang)
|
||||||
parts.append('version="%s"' % version)
|
parts.append('version="%s"' % version)
|
||||||
parts.append('xmlns:stream="%s"' % stream_ns)
|
parts.append('xmlns:stream="%s"' % stream_ns)
|
||||||
parts.append('xmlns="%s"' % default_ns)
|
parts.append('xmlns="%s"' % default_ns)
|
||||||
@ -512,9 +517,9 @@ class SleekTest(unittest.TestCase):
|
|||||||
if '{%s}lang' % xml_ns in recv_xml.attrib:
|
if '{%s}lang' % xml_ns in recv_xml.attrib:
|
||||||
del recv_xml.attrib['{%s}lang' % xml_ns]
|
del recv_xml.attrib['{%s}lang' % xml_ns]
|
||||||
|
|
||||||
if recv_xml.getchildren:
|
if list(recv_xml):
|
||||||
# We received more than just the header
|
# 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))
|
self.xmpp.socket.recv_data(tostring(xml))
|
||||||
|
|
||||||
attrib = recv_xml.attrib
|
attrib = recv_xml.attrib
|
||||||
@ -564,6 +569,7 @@ class SleekTest(unittest.TestCase):
|
|||||||
sid='',
|
sid='',
|
||||||
stream_ns="http://etherx.jabber.org/streams",
|
stream_ns="http://etherx.jabber.org/streams",
|
||||||
default_ns="jabber:client",
|
default_ns="jabber:client",
|
||||||
|
default_lang="en",
|
||||||
version="1.0",
|
version="1.0",
|
||||||
xml_header=False,
|
xml_header=False,
|
||||||
timeout=1):
|
timeout=1):
|
||||||
@ -585,6 +591,7 @@ class SleekTest(unittest.TestCase):
|
|||||||
header = self.make_header(sto, sfrom, sid,
|
header = self.make_header(sto, sfrom, sid,
|
||||||
stream_ns=stream_ns,
|
stream_ns=stream_ns,
|
||||||
default_ns=default_ns,
|
default_ns=default_ns,
|
||||||
|
default_lang=default_lang,
|
||||||
version=version,
|
version=version,
|
||||||
xml_header=xml_header)
|
xml_header=xml_header)
|
||||||
sent_header = self.xmpp.socket.next_sent(timeout)
|
sent_header = self.xmpp.socket.next_sent(timeout)
|
||||||
@ -691,7 +698,7 @@ class SleekTest(unittest.TestCase):
|
|||||||
if xml.tag.startswith('{'):
|
if xml.tag.startswith('{'):
|
||||||
return
|
return
|
||||||
xml.tag = '{%s}%s' % (ns, xml.tag)
|
xml.tag = '{%s}%s' % (ns, xml.tag)
|
||||||
for child in xml.getchildren():
|
for child in xml:
|
||||||
self.fix_namespaces(child, ns)
|
self.fix_namespaces(child, ns)
|
||||||
|
|
||||||
def compare(self, xml, *other):
|
def compare(self, xml, *other):
|
||||||
@ -734,7 +741,7 @@ class SleekTest(unittest.TestCase):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Step 4: Check children count
|
# Step 4: Check children count
|
||||||
if len(xml.getchildren()) != len(other.getchildren()):
|
if len(list(xml)) != len(list(other)):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Step 5: Recursively check children
|
# Step 5: Recursively check children
|
||||||
|
@ -7,11 +7,12 @@ try:
|
|||||||
from pyasn1.type.univ import Any, ObjectIdentifier, OctetString
|
from pyasn1.type.univ import Any, ObjectIdentifier, OctetString
|
||||||
from pyasn1.type.char import BMPString, IA5String, UTF8String
|
from pyasn1.type.char import BMPString, IA5String, UTF8String
|
||||||
from pyasn1.type.useful import GeneralizedTime
|
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_ce_subjectAltName as SUBJECT_ALT_NAME
|
||||||
from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME
|
from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME
|
||||||
|
|
||||||
|
|
||||||
XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
|
XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
|
||||||
SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')
|
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_wild = expected[expected.index('.'):]
|
||||||
expected_srv = '_xmpp-client.%s' % expected
|
expected_srv = '_xmpp-client.%s' % expected
|
||||||
|
|
||||||
for name in cert_names['XMPPAddr']:
|
for name in cert_names['XMPPAddr']:
|
||||||
if name == expected:
|
if name == expected:
|
||||||
return True
|
return True
|
||||||
for name in cert_names['SRV']:
|
for name in cert_names['SRV']:
|
||||||
|
@ -49,7 +49,7 @@ class BaseHandler(object):
|
|||||||
def match(self, xml):
|
def match(self, xml):
|
||||||
"""Compare a stanza or XML object with the handler's matcher.
|
"""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
|
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase` object
|
||||||
"""
|
"""
|
||||||
return self._matcher.match(xml)
|
return self._matcher.match(xml)
|
||||||
@ -73,7 +73,7 @@ class BaseHandler(object):
|
|||||||
self._payload = payload
|
self._payload = payload
|
||||||
|
|
||||||
def check_delete(self):
|
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.
|
of stream handlers.
|
||||||
"""
|
"""
|
||||||
return self._destroy
|
return self._destroy
|
||||||
|
@ -33,7 +33,7 @@ class Callback(BaseHandler):
|
|||||||
:param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
|
:param matcher: A :class:`~sleekxmpp.xmlstream.matcher.base.MatcherBase`
|
||||||
derived object for matching stanza objects.
|
derived object for matching stanza objects.
|
||||||
:param pointer: The function to execute during callback.
|
:param pointer: The function to execute during callback.
|
||||||
:param bool thread: **DEPRECATED.** Remains only for
|
:param bool thread: **DEPRECATED.** Remains only for
|
||||||
backwards compatibility.
|
backwards compatibility.
|
||||||
:param bool once: Indicates if the handler should be used only
|
:param bool once: Indicates if the handler should be used only
|
||||||
once. Defaults to False.
|
once. Defaults to False.
|
||||||
|
@ -34,9 +34,9 @@ class MatchXMLMask(MatcherBase):
|
|||||||
|
|
||||||
<message xmlns="jabber:client"><body /></message>
|
<message xmlns="jabber:client"><body /></message>
|
||||||
|
|
||||||
Use of XMLMask is discouraged, and
|
Use of XMLMask is discouraged, and
|
||||||
:class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or
|
:class:`~sleekxmpp.xmlstream.matcher.xpath.MatchXPath` or
|
||||||
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
|
:class:`~sleekxmpp.xmlstream.matcher.stanzapath.StanzaPath`
|
||||||
should be used instead.
|
should be used instead.
|
||||||
|
|
||||||
The use of namespaces in the mask comparison is controlled by
|
The use of namespaces in the mask comparison is controlled by
|
||||||
@ -151,8 +151,8 @@ class MatchXMLMask(MatcherBase):
|
|||||||
"""
|
"""
|
||||||
tag = tag.split('}')[-1]
|
tag = tag.split('}')[-1]
|
||||||
try:
|
try:
|
||||||
children = [c.tag.split('}')[-1] for c in xml.getchildren()]
|
children = [c.tag.split('}')[-1] for c in xml]
|
||||||
index = children.index(tag)
|
index = children.index(tag)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return None
|
return None
|
||||||
return xml.getchildren()[index]
|
return list(xml)[index]
|
||||||
|
@ -77,10 +77,10 @@ class MatchXPath(MatcherBase):
|
|||||||
# Skip empty tag name artifacts from the cleanup phase.
|
# Skip empty tag name artifacts from the cleanup phase.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
children = [c.tag.split('}')[-1] for c in xml.getchildren()]
|
children = [c.tag.split('}')[-1] for c in xml]
|
||||||
try:
|
try:
|
||||||
index = children.index(tag)
|
index = children.index(tag)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return False
|
return False
|
||||||
xml = xml.getchildren()[index]
|
xml = list(xml)[index]
|
||||||
return True
|
return True
|
||||||
|
@ -57,7 +57,7 @@ class Task(object):
|
|||||||
|
|
||||||
#: The keyword arguments to pass to :attr:`callback`.
|
#: The keyword arguments to pass to :attr:`callback`.
|
||||||
self.kwargs = kwargs or {}
|
self.kwargs = kwargs or {}
|
||||||
|
|
||||||
#: Indicates if the task should repeat after executing,
|
#: Indicates if the task should repeat after executing,
|
||||||
#: using the same :attr:`seconds` delay.
|
#: using the same :attr:`seconds` delay.
|
||||||
self.repeat = repeat
|
self.repeat = repeat
|
||||||
@ -103,7 +103,7 @@ class Scheduler(object):
|
|||||||
def __init__(self, parentstop=None):
|
def __init__(self, parentstop=None):
|
||||||
#: A queue for storing tasks
|
#: A queue for storing tasks
|
||||||
self.addq = queue.Queue()
|
self.addq = queue.Queue()
|
||||||
|
|
||||||
#: A list of tasks in order of execution time.
|
#: A list of tasks in order of execution time.
|
||||||
self.schedule = []
|
self.schedule = []
|
||||||
|
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
:license: MIT, see LICENSE for more details
|
:license: MIT, see LICENSE for more details
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import with_statement, unicode_literals
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import weakref
|
import weakref
|
||||||
@ -29,6 +31,9 @@ log = logging.getLogger(__name__)
|
|||||||
XML_TYPE = type(ET.Element('xml'))
|
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):
|
def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
|
||||||
"""
|
"""
|
||||||
Associate a stanza object as a plugin for another stanza.
|
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
|
substanzas for the parent, using ``parent['substanzas']``. If the
|
||||||
attribute ``plugin_multi_attrib`` was defined for the plugin, then
|
attribute ``plugin_multi_attrib`` was defined for the plugin, then
|
||||||
the substanza set can be filtered to only instances of the plugin
|
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::
|
``plugin_multi_attrib = 'foos'`` then::
|
||||||
|
|
||||||
parent['foos']
|
parent['foos']
|
||||||
@ -94,6 +99,14 @@ def multifactory(stanza, plugin_attrib):
|
|||||||
"""
|
"""
|
||||||
Returns a ElementBase class for handling reoccuring child stanzas
|
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):
|
class Multi(ElementBase):
|
||||||
"""
|
"""
|
||||||
Template class for multifactory
|
Template class for multifactory
|
||||||
@ -101,28 +114,45 @@ def multifactory(stanza, plugin_attrib):
|
|||||||
def setup(self, xml=None):
|
def setup(self, xml=None):
|
||||||
self.xml = ET.Element('')
|
self.xml = ET.Element('')
|
||||||
|
|
||||||
def get_multi(self):
|
def get_multi(self, lang=None):
|
||||||
parent = self.parent()
|
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)
|
return list(res)
|
||||||
|
|
||||||
def set_multi(self, val):
|
def set_multi(self, val, lang=None):
|
||||||
parent = self.parent()
|
parent = self.parent()
|
||||||
del parent[self.plugin_attrib]
|
del_multi = getattr(self, 'del_%s' % plugin_attrib)
|
||||||
|
del_multi(lang)
|
||||||
for sub in val:
|
for sub in val:
|
||||||
parent.append(sub)
|
parent.append(sub)
|
||||||
|
|
||||||
def del_multi(self):
|
def del_multi(self, lang=None):
|
||||||
parent = self.parent()
|
parent = self.parent()
|
||||||
res = filter(lambda sub: isinstance(sub, self._multistanza), parent)
|
if not lang or lang == '*':
|
||||||
for stanza in list(res):
|
res = filter(plugin_filter(self), parent)
|
||||||
parent.iterables.remove(stanza)
|
else:
|
||||||
parent.xml.remove(stanza.xml)
|
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.is_extension = True
|
||||||
Multi.plugin_attrib = plugin_attrib
|
Multi.plugin_attrib = plugin_attrib
|
||||||
Multi._multistanza = stanza
|
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, "get_%s" % plugin_attrib, get_multi)
|
||||||
setattr(Multi, "set_%s" % plugin_attrib, set_multi)
|
setattr(Multi, "set_%s" % plugin_attrib, set_multi)
|
||||||
setattr(Multi, "del_%s" % plugin_attrib, del_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
|
directly from the parent stanza, as shown below, but retrieving
|
||||||
information will require all interfaces to be used, as so::
|
information will require all interfaces to be used, as so::
|
||||||
|
|
||||||
>>> message['custom'] = 'bar' # Same as using message['custom']['custom']
|
>>> # Same as using message['custom']['custom']
|
||||||
>>> message['custom']['custom'] # Must use all interfaces
|
>>> message['custom'] = 'bar'
|
||||||
|
>>> # Must use all interfaces
|
||||||
|
>>> message['custom']['custom']
|
||||||
'bar'
|
'bar'
|
||||||
|
|
||||||
If the plugin sets :attr:`is_extension` to ``True``, then both setting
|
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 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.
|
contain this substanza.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
#: The XML tag name of the element, not including any namespace
|
#: The XML tag name of the element, not including any namespace
|
||||||
#: prefixes. For example, an :class:`ElementBase` object for ``<message />``
|
#: prefixes. For example, an :class:`ElementBase` object for
|
||||||
#: would use ``name = 'message'``.
|
#: ``<message />`` would use ``name = 'message'``.
|
||||||
name = 'stanza'
|
name = 'stanza'
|
||||||
|
|
||||||
#: The XML namespace for the element. Given ``<foo xmlns="bar" />``,
|
#: 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
|
#: subelements of the underlying XML object. Using this set, the text
|
||||||
#: of these subelements may be set, retrieved, or removed without
|
#: of these subelements may be set, retrieved, or removed without
|
||||||
#: needing to define custom methods.
|
#: needing to define custom methods.
|
||||||
sub_interfaces = tuple()
|
sub_interfaces = set()
|
||||||
|
|
||||||
#: A subset of :attr:`interfaces` which maps the presence of
|
#: A subset of :attr:`interfaces` which maps the presence of
|
||||||
#: subelements to boolean values. Using this set allows for quickly
|
#: subelements to boolean values. Using this set allows for quickly
|
||||||
#: checking for the existence of empty subelements like ``<required />``.
|
#: checking for the existence of empty subelements like ``<required />``.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 1.1
|
#: .. 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
|
#: In some cases you may wish to override the behaviour of one of the
|
||||||
#: parent stanza's interfaces. The ``overrides`` list specifies the
|
#: parent stanza's interfaces. The ``overrides`` list specifies the
|
||||||
@ -363,7 +398,7 @@ class ElementBase(object):
|
|||||||
subitem = set()
|
subitem = set()
|
||||||
|
|
||||||
#: The default XML namespace: ``http://www.w3.org/XML/1998/namespace``.
|
#: 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):
|
def __init__(self, xml=None, parent=None):
|
||||||
self._index = 0
|
self._index = 0
|
||||||
@ -375,6 +410,7 @@ class ElementBase(object):
|
|||||||
#: An ordered dictionary of plugin stanzas, mapped by their
|
#: An ordered dictionary of plugin stanzas, mapped by their
|
||||||
#: :attr:`plugin_attrib` value.
|
#: :attr:`plugin_attrib` value.
|
||||||
self.plugins = OrderedDict()
|
self.plugins = OrderedDict()
|
||||||
|
self.loaded_plugins = set()
|
||||||
|
|
||||||
#: A list of child stanzas whose class is included in
|
#: A list of child stanzas whose class is included in
|
||||||
#: :attr:`plugin_iterables`.
|
#: :attr:`plugin_iterables`.
|
||||||
@ -385,6 +421,12 @@ class ElementBase(object):
|
|||||||
#: ``'{namespace}elementname'``.
|
#: ``'{namespace}elementname'``.
|
||||||
self.tag = self.tag_name()
|
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.
|
#: A :class:`weakref.weakref` to the parent stanza, if there is one.
|
||||||
#: If not, then :attr:`parent` is ``None``.
|
#: If not, then :attr:`parent` is ``None``.
|
||||||
self.parent = None
|
self.parent = None
|
||||||
@ -403,13 +445,12 @@ class ElementBase(object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Initialize values using provided XML
|
# Initialize values using provided XML
|
||||||
for child in self.xml.getchildren():
|
for child in self.xml:
|
||||||
if child.tag in self.plugin_tag_map:
|
if child.tag in self.plugin_tag_map:
|
||||||
plugin_class = self.plugin_tag_map[child.tag]
|
plugin_class = self.plugin_tag_map[child.tag]
|
||||||
plugin = plugin_class(child, self)
|
self.init_plugin(plugin_class.plugin_attrib,
|
||||||
self.plugins[plugin.plugin_attrib] = plugin
|
existing_xml=child,
|
||||||
if plugin_class in self.plugin_iterables:
|
reuse=False)
|
||||||
self.iterables.append(plugin)
|
|
||||||
|
|
||||||
def setup(self, xml=None):
|
def setup(self, xml=None):
|
||||||
"""Initialize the stanza's XML contents.
|
"""Initialize the stanza's XML contents.
|
||||||
@ -443,7 +484,7 @@ class ElementBase(object):
|
|||||||
# We did not generate XML
|
# We did not generate XML
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def enable(self, attrib):
|
def enable(self, attrib, lang=None):
|
||||||
"""Enable and initialize a stanza plugin.
|
"""Enable and initialize a stanza plugin.
|
||||||
|
|
||||||
Alias for :meth:`init_plugin`.
|
Alias for :meth:`init_plugin`.
|
||||||
@ -451,24 +492,67 @@ class ElementBase(object):
|
|||||||
:param string attrib: The :attr:`plugin_attrib` value of the
|
:param string attrib: The :attr:`plugin_attrib` value of the
|
||||||
plugin to enable.
|
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.
|
"""Enable and initialize a stanza plugin.
|
||||||
|
|
||||||
:param string attrib: The :attr:`plugin_attrib` value of the
|
:param string attrib: The :attr:`plugin_attrib` value of the
|
||||||
plugin to enable.
|
plugin to enable.
|
||||||
"""
|
"""
|
||||||
if attrib not in self.plugins:
|
if lang is None:
|
||||||
plugin_class = self.plugin_attrib_map[attrib]
|
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())
|
existing_xml = self.xml.find(plugin_class.tag_name())
|
||||||
plugin = plugin_class(parent=self, xml=existing_xml)
|
|
||||||
self.plugins[attrib] = plugin
|
if existing_xml is not None:
|
||||||
if plugin_class in self.plugin_iterables:
|
if existing_xml.attrib.get('{%s}lang' % XML_NS, '') != lang:
|
||||||
self.iterables.append(plugin)
|
existing_xml = None
|
||||||
if plugin_class.plugin_multi_attrib:
|
|
||||||
self.init_plugin(plugin_class.plugin_multi_attrib)
|
plugin = plugin_class(parent=self, xml=existing_xml)
|
||||||
return self
|
|
||||||
|
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):
|
def _get_stanza_values(self):
|
||||||
"""Return A JSON/dictionary version of the XML content
|
"""Return A JSON/dictionary version of the XML content
|
||||||
@ -492,8 +576,14 @@ class ElementBase(object):
|
|||||||
values = {}
|
values = {}
|
||||||
for interface in self.interfaces:
|
for interface in self.interfaces:
|
||||||
values[interface] = self[interface]
|
values[interface] = self[interface]
|
||||||
|
if interface in self.lang_interfaces:
|
||||||
|
values['%s|*' % interface] = self['%s|*' % interface]
|
||||||
for plugin, stanza in self.plugins.items():
|
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:
|
if self.iterables:
|
||||||
iterables = []
|
iterables = []
|
||||||
for stanza in self.iterables:
|
for stanza in self.iterables:
|
||||||
@ -517,6 +607,11 @@ class ElementBase(object):
|
|||||||
p in self.plugin_iterables]
|
p in self.plugin_iterables]
|
||||||
|
|
||||||
for interface, value in values.items():
|
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':
|
if interface == 'substanzas':
|
||||||
# Remove existing substanzas
|
# Remove existing substanzas
|
||||||
for stanza in self.iterables:
|
for stanza in self.iterables:
|
||||||
@ -535,12 +630,12 @@ class ElementBase(object):
|
|||||||
self.iterables.append(sub)
|
self.iterables.append(sub)
|
||||||
break
|
break
|
||||||
elif interface in self.interfaces:
|
elif interface in self.interfaces:
|
||||||
self[interface] = value
|
self[full_interface] = value
|
||||||
elif interface in self.plugin_attrib_map:
|
elif interface in self.plugin_attrib_map:
|
||||||
if interface not in iterable_interfaces:
|
if interface not in iterable_interfaces:
|
||||||
if interface not in self.plugins:
|
plugin = self._get_plugin(interface, lang)
|
||||||
self.init_plugin(interface)
|
if plugin:
|
||||||
self.plugins[interface].values = value
|
plugin.values = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __getitem__(self, attrib):
|
def __getitem__(self, attrib):
|
||||||
@ -572,6 +667,15 @@ class ElementBase(object):
|
|||||||
|
|
||||||
:param string attrib: The name of the requested stanza interface.
|
: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':
|
if attrib == 'substanzas':
|
||||||
return self.iterables
|
return self.iterables
|
||||||
elif attrib in self.interfaces:
|
elif attrib in self.interfaces:
|
||||||
@ -579,32 +683,31 @@ class ElementBase(object):
|
|||||||
get_method2 = "get%s" % attrib.title()
|
get_method2 = "get%s" % attrib.title()
|
||||||
|
|
||||||
if self.plugin_overrides:
|
if self.plugin_overrides:
|
||||||
plugin = self.plugin_overrides.get(get_method, None)
|
name = self.plugin_overrides.get(get_method, None)
|
||||||
if plugin:
|
if name:
|
||||||
if plugin not in self.plugins:
|
plugin = self._get_plugin(name, lang)
|
||||||
self.init_plugin(plugin)
|
if plugin:
|
||||||
handler = getattr(self.plugins[plugin], get_method, None)
|
handler = getattr(plugin, get_method, None)
|
||||||
if handler:
|
if handler:
|
||||||
return handler()
|
return handler(**kwargs)
|
||||||
|
|
||||||
if hasattr(self, get_method):
|
if hasattr(self, get_method):
|
||||||
return getattr(self, get_method)()
|
return getattr(self, get_method)(**kwargs)
|
||||||
elif hasattr(self, get_method2):
|
elif hasattr(self, get_method2):
|
||||||
return getattr(self, get_method2)()
|
return getattr(self, get_method2)(**kwargs)
|
||||||
else:
|
else:
|
||||||
if attrib in self.sub_interfaces:
|
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:
|
elif attrib in self.bool_interfaces:
|
||||||
elem = self.xml.find('{%s}%s' % (self.namespace, attrib))
|
elem = self.xml.find('{%s}%s' % (self.namespace, attrib))
|
||||||
return elem is not None
|
return elem is not None
|
||||||
else:
|
else:
|
||||||
return self._get_attr(attrib)
|
return self._get_attr(attrib)
|
||||||
elif attrib in self.plugin_attrib_map:
|
elif attrib in self.plugin_attrib_map:
|
||||||
if attrib not in self.plugins:
|
plugin = self._get_plugin(attrib, lang)
|
||||||
self.init_plugin(attrib)
|
if plugin and plugin.is_extension:
|
||||||
if self.plugins[attrib].is_extension:
|
return plugin[full_attrib]
|
||||||
return self.plugins[attrib][attrib]
|
return plugin
|
||||||
return self.plugins[attrib]
|
|
||||||
else:
|
else:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@ -640,41 +743,58 @@ class ElementBase(object):
|
|||||||
:param string attrib: The name of the stanza interface to modify.
|
:param string attrib: The name of the stanza interface to modify.
|
||||||
:param value: The new value of the stanza interface.
|
: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 attrib in self.interfaces:
|
||||||
if value is not None:
|
if value is not None:
|
||||||
set_method = "set_%s" % attrib.lower()
|
set_method = "set_%s" % attrib.lower()
|
||||||
set_method2 = "set%s" % attrib.title()
|
set_method2 = "set%s" % attrib.title()
|
||||||
|
|
||||||
if self.plugin_overrides:
|
if self.plugin_overrides:
|
||||||
plugin = self.plugin_overrides.get(set_method, None)
|
name = self.plugin_overrides.get(set_method, None)
|
||||||
if plugin:
|
if name:
|
||||||
if plugin not in self.plugins:
|
plugin = self._get_plugin(name, lang)
|
||||||
self.init_plugin(plugin)
|
if plugin:
|
||||||
handler = getattr(self.plugins[plugin],
|
handler = getattr(plugin, set_method, None)
|
||||||
set_method, None)
|
if handler:
|
||||||
if handler:
|
return handler(value, **kwargs)
|
||||||
return handler(value)
|
|
||||||
|
|
||||||
if hasattr(self, set_method):
|
if hasattr(self, set_method):
|
||||||
getattr(self, set_method)(value,)
|
getattr(self, set_method)(value, **kwargs)
|
||||||
elif hasattr(self, set_method2):
|
elif hasattr(self, set_method2):
|
||||||
getattr(self, set_method2)(value,)
|
getattr(self, set_method2)(value, **kwargs)
|
||||||
else:
|
else:
|
||||||
if attrib in self.sub_interfaces:
|
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:
|
elif attrib in self.bool_interfaces:
|
||||||
if value:
|
if value:
|
||||||
return self._set_sub_text(attrib, '', keep=True)
|
return self._set_sub_text(attrib, '',
|
||||||
|
keep=True,
|
||||||
|
lang=lang)
|
||||||
else:
|
else:
|
||||||
return self._set_sub_text(attrib, '', keep=False)
|
return self._set_sub_text(attrib, '',
|
||||||
|
keep=False,
|
||||||
|
lang=lang)
|
||||||
else:
|
else:
|
||||||
self._set_attr(attrib, value)
|
self._set_attr(attrib, value)
|
||||||
else:
|
else:
|
||||||
self.__delitem__(attrib)
|
self.__delitem__(attrib)
|
||||||
elif attrib in self.plugin_attrib_map:
|
elif attrib in self.plugin_attrib_map:
|
||||||
if attrib not in self.plugins:
|
plugin = self._get_plugin(attrib, lang)
|
||||||
self.init_plugin(attrib)
|
if plugin:
|
||||||
self.plugins[attrib][attrib] = value
|
plugin[full_attrib] = value
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __delitem__(self, attrib):
|
def __delitem__(self, attrib):
|
||||||
@ -709,40 +829,53 @@ class ElementBase(object):
|
|||||||
|
|
||||||
:param attrib: The name of the affected stanza interface.
|
: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:
|
if attrib in self.interfaces:
|
||||||
del_method = "del_%s" % attrib.lower()
|
del_method = "del_%s" % attrib.lower()
|
||||||
del_method2 = "del%s" % attrib.title()
|
del_method2 = "del%s" % attrib.title()
|
||||||
|
|
||||||
if self.plugin_overrides:
|
if self.plugin_overrides:
|
||||||
plugin = self.plugin_overrides.get(del_method, None)
|
name = self.plugin_overrides.get(del_method, None)
|
||||||
if plugin:
|
if name:
|
||||||
if plugin not in self.plugins:
|
plugin = self._get_plugin(attrib, lang)
|
||||||
self.init_plugin(plugin)
|
if plugin:
|
||||||
handler = getattr(self.plugins[plugin], del_method, None)
|
handler = getattr(plugin, del_method, None)
|
||||||
if handler:
|
if handler:
|
||||||
return handler()
|
return handler(**kwargs)
|
||||||
|
|
||||||
if hasattr(self, del_method):
|
if hasattr(self, del_method):
|
||||||
getattr(self, del_method)()
|
getattr(self, del_method)(**kwargs)
|
||||||
elif hasattr(self, del_method2):
|
elif hasattr(self, del_method2):
|
||||||
getattr(self, del_method2)()
|
getattr(self, del_method2)(**kwargs)
|
||||||
else:
|
else:
|
||||||
if attrib in self.sub_interfaces:
|
if attrib in self.sub_interfaces:
|
||||||
return self._del_sub(attrib)
|
return self._del_sub(attrib, lang=lang)
|
||||||
elif attrib in self.bool_interfaces:
|
elif attrib in self.bool_interfaces:
|
||||||
return self._del_sub(attrib)
|
return self._del_sub(attrib, lang=lang)
|
||||||
else:
|
else:
|
||||||
self._del_attr(attrib)
|
self._del_attr(attrib)
|
||||||
elif attrib in self.plugin_attrib_map:
|
elif attrib in self.plugin_attrib_map:
|
||||||
if attrib in self.plugins:
|
plugin = self._get_plugin(attrib, lang)
|
||||||
xml = self.plugins[attrib].xml
|
if not plugin:
|
||||||
if self.plugins[attrib].is_extension:
|
return self
|
||||||
del self.plugins[attrib][attrib]
|
if plugin.is_extension:
|
||||||
del self.plugins[attrib]
|
del plugin[full_attrib]
|
||||||
try:
|
del self.plugins[(attrib, None)]
|
||||||
self.xml.remove(xml)
|
else:
|
||||||
except:
|
del self.plugins[(attrib, lang)]
|
||||||
pass
|
self.loaded_plugins.remove(attrib)
|
||||||
|
try:
|
||||||
|
self.xml.remove(plugin.xml)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _set_attr(self, name, value):
|
def _set_attr(self, name, value):
|
||||||
@ -781,7 +914,7 @@ class ElementBase(object):
|
|||||||
"""
|
"""
|
||||||
return self.xml.attrib.get(name, default)
|
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.
|
"""Return the text contents of a sub element.
|
||||||
|
|
||||||
In case the element does not exist, or it has no textual content,
|
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.
|
not exists. An empty string is returned otherwise.
|
||||||
"""
|
"""
|
||||||
name = self._fix_ns(name)
|
name = self._fix_ns(name)
|
||||||
stanza = self.xml.find(name)
|
if lang == '*':
|
||||||
if stanza is None or stanza.text is None:
|
return self._get_all_sub_text(name, default, None)
|
||||||
return default
|
|
||||||
else:
|
|
||||||
return stanza.text
|
|
||||||
|
|
||||||
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.
|
"""Set the text contents of a sub element.
|
||||||
|
|
||||||
In case the element does not exist, a element will be created,
|
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)
|
path = self._fix_ns(name, split=True)
|
||||||
element = self.xml.find(name)
|
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:
|
if not text and not keep:
|
||||||
return self._del_sub(name)
|
return self._del_sub(name, lang=lang)
|
||||||
|
|
||||||
if element is None:
|
if element is None:
|
||||||
# We need to add the element. If the provided name was
|
# We need to add the element. If the provided name was
|
||||||
@ -833,14 +996,31 @@ class ElementBase(object):
|
|||||||
element = self.xml.find("/".join(walked))
|
element = self.xml.find("/".join(walked))
|
||||||
if element is None:
|
if element is None:
|
||||||
element = ET.Element(ename)
|
element = ET.Element(ename)
|
||||||
|
if lang:
|
||||||
|
element.attrib['{%s}lang' % XML_NS] = lang
|
||||||
last_xml.append(element)
|
last_xml.append(element)
|
||||||
|
parent = last_xml
|
||||||
last_xml = element
|
last_xml = element
|
||||||
element = last_xml
|
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
|
element.text = text
|
||||||
return element
|
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.
|
"""Remove sub elements that match the given name or XPath.
|
||||||
|
|
||||||
If the element is in a path, then any parent elements that become
|
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)
|
path = self._fix_ns(name, split=True)
|
||||||
original_target = path[-1]
|
original_target = path[-1]
|
||||||
|
|
||||||
|
default_lang = self.get_lang()
|
||||||
|
if not lang:
|
||||||
|
lang = default_lang
|
||||||
|
|
||||||
for level, _ in enumerate(path):
|
for level, _ in enumerate(path):
|
||||||
# Generate the paths to the target elements and their parent.
|
# Generate the paths to the target elements and their parent.
|
||||||
element_path = "/".join(path[:len(path) - level])
|
element_path = "/".join(path[:len(path) - level])
|
||||||
@ -866,11 +1050,13 @@ class ElementBase(object):
|
|||||||
if parent is None:
|
if parent is None:
|
||||||
parent = self.xml
|
parent = self.xml
|
||||||
for element in elements:
|
for element in elements:
|
||||||
if element.tag == original_target or \
|
if element.tag == original_target or not list(element):
|
||||||
not element.getchildren():
|
|
||||||
# Only delete the originally requested elements, and
|
# Only delete the originally requested elements, and
|
||||||
# any parent elements that have become empty.
|
# 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 not all:
|
||||||
# If we don't want to delete elements up the tree, stop
|
# If we don't want to delete elements up the tree, stop
|
||||||
# after deleting the first level of elements.
|
# after deleting the first level of elements.
|
||||||
@ -903,7 +1089,7 @@ class ElementBase(object):
|
|||||||
attributes = components[1:]
|
attributes = components[1:]
|
||||||
|
|
||||||
if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
|
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.
|
# The requested tag is not in this stanza, so no match.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -932,10 +1118,12 @@ class ElementBase(object):
|
|||||||
if not matched_substanzas and len(xpath) > 1:
|
if not matched_substanzas and len(xpath) > 1:
|
||||||
# Convert {namespace}tag@attribs to just tag
|
# Convert {namespace}tag@attribs to just tag
|
||||||
next_tag = xpath[1].split('@')[0].split('}')[-1]
|
next_tag = xpath[1].split('@')[0].split('}')[-1]
|
||||||
if next_tag in self.plugins:
|
langs = [name[1] for name in self.plugins if name[0] == next_tag]
|
||||||
return self.plugins[next_tag].match(xpath[1:])
|
for lang in langs:
|
||||||
else:
|
plugin = self._get_plugin(next_tag, lang)
|
||||||
return False
|
if plugin and plugin.match(xpath[1:]):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
# Everything matched.
|
# Everything matched.
|
||||||
return True
|
return True
|
||||||
@ -995,7 +1183,7 @@ class ElementBase(object):
|
|||||||
"""
|
"""
|
||||||
out = []
|
out = []
|
||||||
out += [x for x in self.interfaces]
|
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:
|
if self.iterables:
|
||||||
out.append('substanzas')
|
out.append('substanzas')
|
||||||
return out
|
return out
|
||||||
@ -1075,6 +1263,23 @@ class ElementBase(object):
|
|||||||
"""
|
"""
|
||||||
return "{%s}%s" % (cls.namespace, cls.name)
|
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
|
@property
|
||||||
def attrib(self):
|
def attrib(self):
|
||||||
"""Return the stanza object itself.
|
"""Return the stanza object itself.
|
||||||
@ -1090,8 +1295,8 @@ class ElementBase(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def _fix_ns(self, xpath, split=False, propagate_ns=True):
|
def _fix_ns(self, xpath, split=False, propagate_ns=True):
|
||||||
return fix_ns(xpath, split=split,
|
return fix_ns(xpath, split=split,
|
||||||
propagate_ns=propagate_ns,
|
propagate_ns=propagate_ns,
|
||||||
default_ns=self.namespace)
|
default_ns=self.namespace)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
@ -1219,6 +1424,8 @@ class StanzaBase(ElementBase):
|
|||||||
:param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID`
|
:param sfrom: Optional string or :class:`sleekxmpp.xmlstream.JID`
|
||||||
object of the sender's JID.
|
object of the sender's JID.
|
||||||
:param string sid: Optional ID value for the stanza.
|
: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
|
#: The default XMPP client namespace
|
||||||
@ -1233,11 +1440,11 @@ class StanzaBase(ElementBase):
|
|||||||
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
|
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
|
||||||
|
|
||||||
def __init__(self, stream=None, xml=None, stype=None,
|
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
|
self.stream = stream
|
||||||
if stream is not None:
|
if stream is not None:
|
||||||
self.namespace = stream.default_ns
|
self.namespace = stream.default_ns
|
||||||
ElementBase.__init__(self, xml)
|
ElementBase.__init__(self, xml, parent)
|
||||||
if stype is not None:
|
if stype is not None:
|
||||||
self['type'] = stype
|
self['type'] = stype
|
||||||
if sto is not None:
|
if sto is not None:
|
||||||
@ -1285,7 +1492,7 @@ class StanzaBase(ElementBase):
|
|||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self):
|
||||||
"""Return a list of XML objects contained in the stanza."""
|
"""Return a list of XML objects contained in the stanza."""
|
||||||
return self.xml.getchildren()
|
return list(self.xml)
|
||||||
|
|
||||||
def set_payload(self, value):
|
def set_payload(self, value):
|
||||||
"""Add XML content to the stanza.
|
"""Add XML content to the stanza.
|
||||||
|
@ -13,14 +13,19 @@
|
|||||||
:license: MIT, see LICENSE for more details
|
:license: MIT, see LICENSE for more details
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
|
||||||
|
XML_NS = 'http://www.w3.org/XML/1998/namespace'
|
||||||
|
|
||||||
|
|
||||||
def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
|
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.
|
"""Serialize an XML object to a Unicode string.
|
||||||
|
|
||||||
If namespaces are provided using ``xmlns`` or ``stanza_ns``, then
|
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,
|
output.append(' %s:%s="%s"' % (mapped_ns,
|
||||||
attrib,
|
attrib,
|
||||||
value))
|
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 len(xml) or xml.text:
|
||||||
# If there are additional child elements to serialize.
|
# If there are additional child elements to serialize.
|
||||||
@ -95,7 +107,7 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None,
|
|||||||
if xml.text:
|
if xml.text:
|
||||||
output.append(xml_escape(xml.text))
|
output.append(xml_escape(xml.text))
|
||||||
if len(xml):
|
if len(xml):
|
||||||
for child in xml.getchildren():
|
for child in xml:
|
||||||
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
|
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
|
||||||
output.append("</%s>" % tag_name)
|
output.append("</%s>" % tag_name)
|
||||||
elif xml.text:
|
elif xml.text:
|
||||||
|
@ -55,7 +55,7 @@ RESPONSE_TIMEOUT = 30
|
|||||||
WAIT_TIMEOUT = 0.1
|
WAIT_TIMEOUT = 0.1
|
||||||
|
|
||||||
#: The number of threads to use to handle XML stream events. This is not the
|
#: 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
|
#: :data:`HANDLER_THREADS` must be at least 1. For Python implementations
|
||||||
#: with a GIL, this should be left at 1, but for implemetnations without
|
#: with a GIL, this should be left at 1, but for implemetnations without
|
||||||
#: a GIL increasing this value can provide better performance.
|
#: a GIL increasing this value can provide better performance.
|
||||||
@ -124,7 +124,7 @@ class XMLStream(object):
|
|||||||
self.ssl_support = SSL_SUPPORT
|
self.ssl_support = SSL_SUPPORT
|
||||||
|
|
||||||
#: Most XMPP servers support TLSv1, but OpenFire in particular
|
#: 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``::
|
#: :attr:`ssl_version` to use ``SSLv23``::
|
||||||
#:
|
#:
|
||||||
#: import ssl
|
#: import ssl
|
||||||
@ -134,30 +134,30 @@ class XMLStream(object):
|
|||||||
#: Path to a file containing certificates for verifying the
|
#: Path to a file containing certificates for verifying the
|
||||||
#: server SSL certificate. A non-``None`` value will trigger
|
#: server SSL certificate. A non-``None`` value will trigger
|
||||||
#: certificate checking.
|
#: certificate checking.
|
||||||
#:
|
#:
|
||||||
#: .. note::
|
#: .. note::
|
||||||
#:
|
#:
|
||||||
#: On Mac OS X, certificates in the system keyring will
|
#: On Mac OS X, certificates in the system keyring will
|
||||||
#: be consulted, even if they are not in the provided file.
|
#: be consulted, even if they are not in the provided file.
|
||||||
self.ca_certs = None
|
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.
|
#: and also the time between checks for the process stop signal.
|
||||||
self.wait_timeout = WAIT_TIMEOUT
|
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.
|
#: for response stanzas.
|
||||||
self.response_timeout = RESPONSE_TIMEOUT
|
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
|
#: This value doubles (with some jitter) with each failed
|
||||||
#: connection attempt up to :attr:`reconnect_max_delay` seconds.
|
#: connection attempt up to :attr:`reconnect_max_delay` seconds.
|
||||||
self.reconnect_delay = None
|
self.reconnect_delay = None
|
||||||
|
|
||||||
#: Maximum time to delay between connection attempts is one hour.
|
#: Maximum time to delay between connection attempts is one hour.
|
||||||
self.reconnect_max_delay = RECONNECT_MAX_DELAY
|
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
|
#: quitting and raising a 'connect_failed' event. Setting to
|
||||||
#: ``None`` allows infinite reattempts, while setting it to ``0``
|
#: ``None`` allows infinite reattempts, while setting it to ``0``
|
||||||
#: will disable reconnection attempts. Defaults to ``None``.
|
#: will disable reconnection attempts. Defaults to ``None``.
|
||||||
@ -178,16 +178,16 @@ class XMLStream(object):
|
|||||||
|
|
||||||
#: The default port to return when querying DNS records.
|
#: The default port to return when querying DNS records.
|
||||||
self.default_port = int(port)
|
self.default_port = int(port)
|
||||||
|
|
||||||
#: The domain to try when querying DNS records.
|
#: The domain to try when querying DNS records.
|
||||||
self.default_domain = ''
|
self.default_domain = ''
|
||||||
|
|
||||||
#: The expected name of the server, for validation.
|
#: The expected name of the server, for validation.
|
||||||
self._expected_server_name = ''
|
self._expected_server_name = ''
|
||||||
|
|
||||||
#: The desired, or actual, address of the connected server.
|
#: The desired, or actual, address of the connected server.
|
||||||
self.address = (host, int(port))
|
self.address = (host, int(port))
|
||||||
|
|
||||||
#: A file-like wrapper for the socket for use with the
|
#: A file-like wrapper for the socket for use with the
|
||||||
#: :mod:`~xml.etree.ElementTree` module.
|
#: :mod:`~xml.etree.ElementTree` module.
|
||||||
self.filesocket = None
|
self.filesocket = None
|
||||||
@ -223,6 +223,9 @@ class XMLStream(object):
|
|||||||
#: stream wrapper itself.
|
#: stream wrapper itself.
|
||||||
self.default_ns = ''
|
self.default_ns = ''
|
||||||
|
|
||||||
|
self.default_lang = None
|
||||||
|
self.peer_default_lang = None
|
||||||
|
|
||||||
#: The namespace of the enveloping stream element.
|
#: The namespace of the enveloping stream element.
|
||||||
self.stream_ns = ''
|
self.stream_ns = ''
|
||||||
|
|
||||||
@ -255,7 +258,7 @@ class XMLStream(object):
|
|||||||
#: and data is sent immediately over the wire.
|
#: and data is sent immediately over the wire.
|
||||||
self.session_started_event = threading.Event()
|
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.
|
#: after connecting before reconnecting and trying again.
|
||||||
self.session_timeout = 45
|
self.session_timeout = 45
|
||||||
|
|
||||||
@ -414,12 +417,12 @@ class XMLStream(object):
|
|||||||
if use_tls is not None:
|
if use_tls is not None:
|
||||||
self.use_tls = use_tls
|
self.use_tls = use_tls
|
||||||
|
|
||||||
|
|
||||||
# Repeatedly attempt to connect until a successful connection
|
# Repeatedly attempt to connect until a successful connection
|
||||||
# is established.
|
# is established.
|
||||||
attempts = self.reconnect_max_attempts
|
attempts = self.reconnect_max_attempts
|
||||||
connected = self.state.transition('disconnected', 'connected',
|
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():
|
while reattempt and not connected and not self.stop.is_set():
|
||||||
connected = self.state.transition('disconnected', 'connected',
|
connected = self.state.transition('disconnected', 'connected',
|
||||||
func=self._connect)
|
func=self._connect)
|
||||||
@ -434,7 +437,7 @@ class XMLStream(object):
|
|||||||
def _connect(self, reattempt=True):
|
def _connect(self, reattempt=True):
|
||||||
self.scheduler.remove('Session timeout check')
|
self.scheduler.remove('Session timeout check')
|
||||||
self.stop.clear()
|
self.stop.clear()
|
||||||
|
|
||||||
if self.reconnect_delay is None or not reattempt:
|
if self.reconnect_delay is None or not reattempt:
|
||||||
delay = 1.0
|
delay = 1.0
|
||||||
else:
|
else:
|
||||||
@ -480,7 +483,7 @@ class XMLStream(object):
|
|||||||
if self.use_proxy:
|
if self.use_proxy:
|
||||||
connected = self._connect_proxy()
|
connected = self._connect_proxy()
|
||||||
if not connected:
|
if not connected:
|
||||||
if reattempt:
|
if reattempt:
|
||||||
self.reconnect_delay = delay
|
self.reconnect_delay = delay
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -517,7 +520,8 @@ class XMLStream(object):
|
|||||||
except (Socket.error, ssl.SSLError):
|
except (Socket.error, ssl.SSLError):
|
||||||
log.error('CERT: Invalid certificate trust chain.')
|
log.error('CERT: Invalid certificate trust chain.')
|
||||||
if not self.event_handled('ssl_invalid_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:
|
else:
|
||||||
self.event('ssl_invalid_chain', direct=True)
|
self.event('ssl_invalid_chain', direct=True)
|
||||||
return False
|
return False
|
||||||
@ -525,7 +529,7 @@ class XMLStream(object):
|
|||||||
self._der_cert = self.socket.getpeercert(binary_form=True)
|
self._der_cert = self.socket.getpeercert(binary_form=True)
|
||||||
pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert)
|
pem_cert = ssl.DER_cert_to_PEM_cert(self._der_cert)
|
||||||
log.debug('CERT: %s', pem_cert)
|
log.debug('CERT: %s', pem_cert)
|
||||||
|
|
||||||
self.event('ssl_cert', pem_cert, direct=True)
|
self.event('ssl_cert', pem_cert, direct=True)
|
||||||
try:
|
try:
|
||||||
cert.verify(self._expected_server_name, self._der_cert)
|
cert.verify(self._expected_server_name, self._der_cert)
|
||||||
@ -534,7 +538,9 @@ class XMLStream(object):
|
|||||||
if not self.event_handled('ssl_invalid_cert'):
|
if not self.event_handled('ssl_invalid_cert'):
|
||||||
self.disconnect(send_close=False)
|
self.disconnect(send_close=False)
|
||||||
else:
|
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)
|
self.set_socket(self.socket, ignore=True)
|
||||||
#this event is where you should set your application state
|
#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
|
If the disconnect should take place after all items
|
||||||
in the send queue have been sent, use ``wait=True``.
|
in the send queue have been sent, use ``wait=True``.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
If you are constantly adding items to the queue
|
If you are constantly adding items to the queue
|
||||||
@ -648,7 +654,7 @@ class XMLStream(object):
|
|||||||
"""
|
"""
|
||||||
self.state.transition('connected', 'disconnected',
|
self.state.transition('connected', 'disconnected',
|
||||||
wait=2.0,
|
wait=2.0,
|
||||||
func=self._disconnect,
|
func=self._disconnect,
|
||||||
args=(reconnect, wait, send_close))
|
args=(reconnect, wait, send_close))
|
||||||
|
|
||||||
def _disconnect(self, reconnect=False, wait=None, send_close=True):
|
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."""
|
"""Reset the stream's state and reconnect to the server."""
|
||||||
log.debug("reconnecting...")
|
log.debug("reconnecting...")
|
||||||
if self.state.ensure('connected'):
|
if self.state.ensure('connected'):
|
||||||
self.state.transition('connected', 'disconnected',
|
self.state.transition('connected', 'disconnected',
|
||||||
wait=2.0,
|
wait=2.0,
|
||||||
func=self._disconnect,
|
func=self._disconnect,
|
||||||
args=(True, wait, send_close))
|
args=(True, wait, send_close))
|
||||||
|
|
||||||
attempts = self.reconnect_max_attempts
|
attempts = self.reconnect_max_attempts
|
||||||
|
|
||||||
log.debug("connecting...")
|
log.debug("connecting...")
|
||||||
connected = self.state.transition('disconnected', 'connected',
|
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():
|
while reattempt and not connected and not self.stop.is_set():
|
||||||
connected = self.state.transition('disconnected', 'connected',
|
connected = self.state.transition('disconnected', 'connected',
|
||||||
wait=2.0, func=self._connect)
|
wait=2.0, func=self._connect)
|
||||||
@ -759,8 +767,8 @@ class XMLStream(object):
|
|||||||
"""
|
"""
|
||||||
Configure and set options for a :class:`~dns.resolver.Resolver`
|
Configure and set options for a :class:`~dns.resolver.Resolver`
|
||||||
instance, and other DNS related tasks. For example, you
|
instance, and other DNS related tasks. For example, you
|
||||||
can also check :meth:`~socket.socket.getaddrinfo` to see
|
can also check :meth:`~socket.socket.getaddrinfo` to see
|
||||||
if you need to call out to ``libresolv.so.2`` to
|
if you need to call out to ``libresolv.so.2`` to
|
||||||
run ``res_init()``.
|
run ``res_init()``.
|
||||||
|
|
||||||
Meant to be overridden.
|
Meant to be overridden.
|
||||||
@ -814,7 +822,7 @@ class XMLStream(object):
|
|||||||
log.debug('CERT: %s', pem_cert)
|
log.debug('CERT: %s', pem_cert)
|
||||||
self.event('ssl_cert', pem_cert, direct=True)
|
self.event('ssl_cert', pem_cert, direct=True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cert.verify(self._expected_server_name, self._der_cert)
|
cert.verify(self._expected_server_name, self._der_cert)
|
||||||
except cert.CertificateError as err:
|
except cert.CertificateError as err:
|
||||||
log.error(err.message)
|
log.error(err.message)
|
||||||
@ -874,8 +882,8 @@ class XMLStream(object):
|
|||||||
self.schedule('Whitespace Keepalive',
|
self.schedule('Whitespace Keepalive',
|
||||||
self.whitespace_keepalive_interval,
|
self.whitespace_keepalive_interval,
|
||||||
self.send_raw,
|
self.send_raw,
|
||||||
args = (' ',),
|
args=(' ',),
|
||||||
kwargs = {'now': True},
|
kwargs={'now': True},
|
||||||
repeat=True)
|
repeat=True)
|
||||||
|
|
||||||
def _remove_schedules(self, event):
|
def _remove_schedules(self, event):
|
||||||
@ -884,7 +892,7 @@ class XMLStream(object):
|
|||||||
self.scheduler.remove('Certificate Expiration')
|
self.scheduler.remove('Certificate Expiration')
|
||||||
|
|
||||||
def start_stream_handler(self, xml):
|
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.
|
once the stream header has been sent.
|
||||||
|
|
||||||
Meant to be overridden.
|
Meant to be overridden.
|
||||||
@ -892,8 +900,8 @@ class XMLStream(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def register_stanza(self, stanza_class):
|
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
|
A root stanza is one that appears as a direct child of the stream's
|
||||||
root element.
|
root element.
|
||||||
|
|
||||||
@ -910,8 +918,8 @@ class XMLStream(object):
|
|||||||
self.__root_stanza.append(stanza_class)
|
self.__root_stanza.append(stanza_class)
|
||||||
|
|
||||||
def remove_stanza(self, 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
|
A root stanza is one that appears as a direct child of the stream's
|
||||||
root element.
|
root element.
|
||||||
|
|
||||||
@ -976,8 +984,9 @@ class XMLStream(object):
|
|||||||
"""Add a stream event handler that will be executed when a matching
|
"""Add a stream event handler that will be executed when a matching
|
||||||
stanza is received.
|
stanza is received.
|
||||||
|
|
||||||
:param handler: The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler`
|
:param handler:
|
||||||
derived object to execute.
|
The :class:`~sleekxmpp.xmlstream.handler.base.BaseHandler`
|
||||||
|
derived object to execute.
|
||||||
"""
|
"""
|
||||||
if handler.stream is None:
|
if handler.stream is None:
|
||||||
self.__handlers.append(handler)
|
self.__handlers.append(handler)
|
||||||
@ -1004,11 +1013,12 @@ class XMLStream(object):
|
|||||||
"""
|
"""
|
||||||
if port is None:
|
if port is None:
|
||||||
port = self.default_port
|
port = self.default_port
|
||||||
|
|
||||||
resolver = default_resolver()
|
resolver = default_resolver()
|
||||||
self.configure_dns(resolver, domain=domain, port=port)
|
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):
|
def pick_dns_answer(self, domain, port=None):
|
||||||
"""Pick a server and port from DNS answers.
|
"""Pick a server and port from DNS answers.
|
||||||
@ -1026,7 +1036,7 @@ class XMLStream(object):
|
|||||||
return self.dns_answers.next()
|
return self.dns_answers.next()
|
||||||
else:
|
else:
|
||||||
return next(self.dns_answers)
|
return next(self.dns_answers)
|
||||||
|
|
||||||
def add_event_handler(self, name, pointer,
|
def add_event_handler(self, name, pointer,
|
||||||
threaded=False, disposable=False):
|
threaded=False, disposable=False):
|
||||||
"""Add a custom event handler that will be executed whenever
|
"""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.
|
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.
|
stanza to send on the stream.
|
||||||
:param mask: **DEPRECATED**
|
:param mask: **DEPRECATED**
|
||||||
An XML string snippet matching the structure
|
An XML string snippet matching the structure
|
||||||
of the expected response. Execution will block
|
of the expected response. Execution will block
|
||||||
in this thread until the response is received
|
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
|
"""Send an XML object on the stream, and optionally wait
|
||||||
for a response.
|
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.
|
to send on the stream.
|
||||||
:param mask: **DEPRECATED**
|
:param mask: **DEPRECATED**
|
||||||
An XML string snippet matching the structure
|
An XML string snippet matching the structure
|
||||||
of the expected response. Execution will block
|
of the expected response. Execution will block
|
||||||
in this thread until the response is received
|
in this thread until the response is received
|
||||||
@ -1237,14 +1247,15 @@ class XMLStream(object):
|
|||||||
count += 1
|
count += 1
|
||||||
except ssl.SSLError as serr:
|
except ssl.SSLError as serr:
|
||||||
if tries >= self.ssl_retry_max:
|
if tries >= self.ssl_retry_max:
|
||||||
log.debug('SSL error - max retries reached')
|
log.debug('SSL error: max retries reached')
|
||||||
self.exception(serr)
|
self.exception(serr)
|
||||||
log.warning("Failed to send %s", data)
|
log.warning("Failed to send %s", data)
|
||||||
if reconnect is None:
|
if reconnect is None:
|
||||||
reconnect = self.auto_reconnect
|
reconnect = self.auto_reconnect
|
||||||
if not self.stop.is_set():
|
if not self.stop.is_set():
|
||||||
self.disconnect(reconnect, send_close=False)
|
self.disconnect(reconnect,
|
||||||
log.warning('SSL write error - reattempting')
|
send_close=False)
|
||||||
|
log.warning('SSL write error: retrying')
|
||||||
if not self.stop.is_set():
|
if not self.stop.is_set():
|
||||||
time.sleep(self.ssl_retry_delay)
|
time.sleep(self.ssl_retry_delay)
|
||||||
tries += 1
|
tries += 1
|
||||||
@ -1299,7 +1310,7 @@ class XMLStream(object):
|
|||||||
def _wait_for_threads(self):
|
def _wait_for_threads(self):
|
||||||
with self.__thread_cond:
|
with self.__thread_cond:
|
||||||
if self.__thread_count != 0:
|
if self.__thread_count != 0:
|
||||||
log.debug("Waiting for %s threads to exit." %
|
log.debug("Waiting for %s threads to exit." %
|
||||||
self.__thread_count)
|
self.__thread_count)
|
||||||
name = threading.current_thread().name
|
name = threading.current_thread().name
|
||||||
if name in self.__thread:
|
if name in self.__thread:
|
||||||
@ -1331,7 +1342,7 @@ class XMLStream(object):
|
|||||||
Defaults to ``True``. This does **not** mean that no
|
Defaults to ``True``. This does **not** mean that no
|
||||||
threads are used at all if ``threaded=False``.
|
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:
|
always exist:
|
||||||
|
|
||||||
- The event queue processor
|
- The event queue processor
|
||||||
@ -1421,7 +1432,7 @@ class XMLStream(object):
|
|||||||
|
|
||||||
def __read_xml(self):
|
def __read_xml(self):
|
||||||
"""Parse the incoming XML stream
|
"""Parse the incoming XML stream
|
||||||
|
|
||||||
Stream events are raised for each received stanza.
|
Stream events are raised for each received stanza.
|
||||||
"""
|
"""
|
||||||
depth = 0
|
depth = 0
|
||||||
@ -1431,6 +1442,10 @@ class XMLStream(object):
|
|||||||
if depth == 0:
|
if depth == 0:
|
||||||
# We have received the start of the root element.
|
# We have received the start of the root element.
|
||||||
root = xml
|
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
|
# Perform any stream initialization actions, such
|
||||||
# as handshakes.
|
# as handshakes.
|
||||||
self.stream_end_event.clear()
|
self.stream_end_event.clear()
|
||||||
@ -1461,10 +1476,10 @@ class XMLStream(object):
|
|||||||
"""Create a stanza object from a given XML object.
|
"""Create a stanza object from a given XML object.
|
||||||
|
|
||||||
If a specialized stanza type is not found for the XML, then
|
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.
|
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.
|
to convert into a stanza object.
|
||||||
:param default_ns: Optional default namespace to use instead of the
|
:param default_ns: Optional default namespace to use instead of the
|
||||||
stream's current default namespace.
|
stream's current default namespace.
|
||||||
@ -1478,6 +1493,8 @@ class XMLStream(object):
|
|||||||
stanza_type = stanza_class
|
stanza_type = stanza_class
|
||||||
break
|
break
|
||||||
stanza = stanza_type(self, xml)
|
stanza = stanza_type(self, xml)
|
||||||
|
if stanza['lang'] is None and self.peer_default_lang:
|
||||||
|
stanza['lang'] = self.peer_default_lang
|
||||||
return stanza
|
return stanza
|
||||||
|
|
||||||
def __spawn_event(self, xml):
|
def __spawn_event(self, xml):
|
||||||
@ -1647,12 +1664,13 @@ class XMLStream(object):
|
|||||||
count += 1
|
count += 1
|
||||||
except ssl.SSLError as serr:
|
except ssl.SSLError as serr:
|
||||||
if tries >= self.ssl_retry_max:
|
if tries >= self.ssl_retry_max:
|
||||||
log.debug('SSL error - max retries reached')
|
log.debug('SSL error: max retries reached')
|
||||||
self.exception(serr)
|
self.exception(serr)
|
||||||
log.warning("Failed to send %s", data)
|
log.warning("Failed to send %s", data)
|
||||||
if not self.stop.is_set():
|
if not self.stop.is_set():
|
||||||
self.disconnect(self.auto_reconnect, send_close=False)
|
self.disconnect(self.auto_reconnect,
|
||||||
log.warning('SSL write error - reattempting')
|
send_close=False)
|
||||||
|
log.warning('SSL write error: retrying')
|
||||||
if not self.stop.is_set():
|
if not self.stop.is_set():
|
||||||
time.sleep(self.ssl_retry_delay)
|
time.sleep(self.ssl_retry_delay)
|
||||||
tries += 1
|
tries += 1
|
||||||
|
@ -64,14 +64,18 @@ class TestElementBase(SleekTest):
|
|||||||
stanza.append(substanza)
|
stanza.append(substanza)
|
||||||
|
|
||||||
values = stanza.getStanzaValues()
|
values = stanza.getStanzaValues()
|
||||||
expected = {'bar': 'a',
|
expected = {'lang': '',
|
||||||
|
'bar': 'a',
|
||||||
'baz': '',
|
'baz': '',
|
||||||
'foo2': {'bar': '',
|
'foo2': {'lang': '',
|
||||||
|
'bar': '',
|
||||||
'baz': 'b'},
|
'baz': 'b'},
|
||||||
'substanzas': [{'__childtag__': '{foo}foo2',
|
'substanzas': [{'__childtag__': '{foo}foo2',
|
||||||
|
'lang': '',
|
||||||
'bar': '',
|
'bar': '',
|
||||||
'baz': 'b'},
|
'baz': 'b'},
|
||||||
{'__childtag__': '{foo}subfoo',
|
{'__childtag__': '{foo}subfoo',
|
||||||
|
'lang': '',
|
||||||
'bar': 'c',
|
'bar': 'c',
|
||||||
'baz': ''}]}
|
'baz': ''}]}
|
||||||
self.failUnless(values == expected,
|
self.failUnless(values == expected,
|
||||||
@ -555,12 +559,12 @@ class TestElementBase(SleekTest):
|
|||||||
|
|
||||||
stanza = TestStanza()
|
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.")
|
"Returned set of interface keys does not match expected.")
|
||||||
|
|
||||||
stanza.enable('qux')
|
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.")
|
"Incorrect set of interface and plugin keys.")
|
||||||
|
|
||||||
def testGet(self):
|
def testGet(self):
|
||||||
|
@ -49,7 +49,7 @@ class TestAdHocCommandStanzas(SleekTest):
|
|||||||
iq['command']['actions'] = ['prev', 'next']
|
iq['command']['actions'] = ['prev', 'next']
|
||||||
|
|
||||||
results = iq['command']['actions']
|
results = iq['command']['actions']
|
||||||
expected = ['prev', 'next']
|
expected = set(['prev', 'next'])
|
||||||
self.assertEqual(results, expected,
|
self.assertEqual(results, expected,
|
||||||
"Incorrect next actions: %s" % results)
|
"Incorrect next actions: %s" % results)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user