summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins/xep_0030/disco.py
diff options
context:
space:
mode:
Diffstat (limited to 'sleekxmpp/plugins/xep_0030/disco.py')
-rw-r--r--sleekxmpp/plugins/xep_0030/disco.py246
1 files changed, 206 insertions, 40 deletions
diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py
index 53086d4e..10f9ef4e 100644
--- a/sleekxmpp/plugins/xep_0030/disco.py
+++ b/sleekxmpp/plugins/xep_0030/disco.py
@@ -10,7 +10,7 @@ import logging
import sleekxmpp
from sleekxmpp import Iq
-from sleekxmpp.exceptions import XMPPError
+from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
@@ -108,11 +108,16 @@ class xep_0030(base_plugin):
self.static = StaticDisco(self.xmpp)
- self._disco_ops = ['get_info', 'set_identities', 'set_features',
- 'get_items', 'set_items', 'del_items',
- 'add_identity', 'del_identity', 'add_feature',
- 'del_feature', 'add_item', 'del_item',
- 'del_identities', 'del_features']
+ self.use_cache = self.config.get('use_cache', True)
+ self.wrap_results = self.config.get('wrap_results', False)
+
+ self._disco_ops = [
+ 'get_info', 'set_info', 'set_identities', 'set_features',
+ 'get_items', 'set_items', 'del_items', 'add_identity',
+ 'del_identity', 'add_feature', 'del_feature', 'add_item',
+ 'del_item', 'del_identities', 'del_features', 'cache_info',
+ 'get_cached_info', 'supports', 'has_identity']
+
self.default_handlers = {}
self._handlers = {}
for op in self._disco_ops:
@@ -237,7 +242,78 @@ class xep_0030(base_plugin):
self.del_node_handler(op, jid, node)
self.set_node_handler(op, jid, node, self.default_handlers[op])
- def get_info(self, jid=None, node=None, local=False, **kwargs):
+ def supports(self, jid=None, node=None, feature=None, local=False,
+ cached=True, ifrom=None):
+ """
+ Check if a JID supports a given feature.
+
+ Return values:
+ True -- The feature is supported
+ False -- The feature is not listed as supported
+ None -- Nothing could be found due to a timeout
+
+ Arguments:
+ jid -- Request info from this JID.
+ node -- The particular node to query.
+ feature -- The name of the feature to check.
+ local -- If true, then the query is for a JID/node
+ combination handled by this Sleek instance and
+ no stanzas need to be sent.
+ Otherwise, a disco stanza must be sent to the
+ remove JID to retrieve the info.
+ cached -- If true, then look for the disco info data from
+ the local cache system. If no results are found,
+ send the query as usual. The self.use_cache
+ setting must be set to true for this option to
+ be useful. If set to false, then the cache will
+ be skipped, even if a result has already been
+ cached. Defaults to false.
+ ifrom -- Specifiy the sender's JID.
+ """
+ data = {'feature': feature,
+ 'local': local,
+ 'cached': cached}
+ return self._run_node_handler('supports', jid, node, ifrom, data)
+
+ def has_identity(self, jid=None, node=None, category=None, itype=None,
+ lang=None, local=False, cached=True, ifrom=None):
+ """
+ Check if a JID provides a given identity.
+
+ Return values:
+ True -- The identity is provided
+ False -- The identity is not listed
+ None -- Nothing could be found due to a timeout
+
+ Arguments:
+ jid -- Request info from this JID.
+ node -- The particular node to query.
+ category -- The category of the identity to check.
+ itype -- The type of the identity to check.
+ lang -- The language of the identity to check.
+ local -- If true, then the query is for a JID/node
+ combination handled by this Sleek instance and
+ no stanzas need to be sent.
+ Otherwise, a disco stanza must be sent to the
+ remove JID to retrieve the info.
+ cached -- If true, then look for the disco info data from
+ the local cache system. If no results are found,
+ send the query as usual. The self.use_cache
+ setting must be set to true for this option to
+ be useful. If set to false, then the cache will
+ be skipped, even if a result has already been
+ cached. Defaults to false.
+ ifrom -- Specifiy the sender's JID.
+ """
+ data = {'category': category,
+ 'itype': itype,
+ 'lang': lang,
+ 'local': local,
+ 'cached': cached}
+ return self._run_node_handler('has_identity', jid, node, ifrom, data)
+
+ def get_info(self, jid=None, node=None, local=False,
+ cached=None, **kwargs):
"""
Retrieve the disco#info results from a given JID/node combination.
@@ -257,6 +333,13 @@ class xep_0030(base_plugin):
no stanzas need to be sent.
Otherwise, a disco stanza must be sent to the
remove JID to retrieve the info.
+ cached -- If true, then look for the disco info data from
+ the local cache system. If no results are found,
+ send the query as usual. The self.use_cache
+ setting must be set to true for this option to
+ be useful. If set to false, then the cache will
+ be skipped, even if a result has already been
+ cached. Defaults to false.
ifrom -- Specifiy the sender's JID.
block -- If true, block and wait for the stanzas' reply.
timeout -- The time in seconds to block while waiting for
@@ -266,12 +349,31 @@ class xep_0030(base_plugin):
received instead of blocking and waiting for
the reply.
"""
- if local or jid is None:
+ if jid is not None and not isinstance(jid, JID):
+ jid = JID(jid)
+ if self.xmpp.is_component:
+ if jid.domain == self.xmpp.boundjid.domain:
+ local = True
+ else:
+ if str(jid) == str(self.xmpp.boundjid):
+ local = True
+
+ if local or jid in (None, ''):
log.debug("Looking up local disco#info data " + \
"for %s, node %s.", jid, node)
- info = self._run_node_handler('get_info', jid, node, kwargs)
- return self._fix_default_info(info)
+ info = self._run_node_handler('get_info',
+ jid, node, kwargs.get('ifrom', None), kwargs)
+ info = self._fix_default_info(info)
+ return self._wrap(kwargs.get('ifrom', None), jid, info)
+ if cached:
+ log.debug("Looking up cached disco#info data " + \
+ "for %s, node %s.", jid, node)
+ info = self._run_node_handler('get_cached_info',
+ jid, node, kwargs.get('ifrom', None), kwargs)
+ if info is not None:
+ return self._wrap(kwargs.get('ifrom', None), jid, info)
+
iq = self.xmpp.Iq()
# Check dfrom parameter for backwards compatibility
iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', ''))
@@ -282,6 +384,15 @@ class xep_0030(base_plugin):
block=kwargs.get('block', True),
callback=kwargs.get('callback', None))
+ def set_info(self, jid=None, node=None, info=None):
+ """
+ Set the disco#info data for a JID/node based on an existing
+ disco#info stanza.
+ """
+ if isinstance(info, Iq):
+ info = info['disco_info']
+ self._run_node_handler('set_info', jid, node, None, info)
+
def get_items(self, jid=None, node=None, local=False, **kwargs):
"""
Retrieve the disco#items results from a given JID/node combination.
@@ -314,7 +425,9 @@ class xep_0030(base_plugin):
Otherwise the parameter is ignored.
"""
if local or jid is None:
- return self._run_node_handler('get_items', jid, node, kwargs)
+ items = self._run_node_handler('get_items',
+ jid, node, kwargs.get('ifrom', None), kwargs)
+ return self._wrap(kwargs.get('ifrom', None), jid, items)
iq = self.xmpp.Iq()
# Check dfrom parameter for backwards compatibility
@@ -341,7 +454,7 @@ class xep_0030(base_plugin):
node -- Optional node to modify.
items -- A series of items in tuple format.
"""
- self._run_node_handler('set_items', jid, node, kwargs)
+ self._run_node_handler('set_items', jid, node, None, kwargs)
def del_items(self, jid=None, node=None, **kwargs):
"""
@@ -351,7 +464,7 @@ class xep_0030(base_plugin):
jid -- The JID to modify.
node -- Optional node to modify.
"""
- self._run_node_handler('del_items', jid, node, kwargs)
+ self._run_node_handler('del_items', jid, node, None, kwargs)
def add_item(self, jid='', name='', node=None, subnode='', ijid=None):
"""
@@ -372,7 +485,7 @@ class xep_0030(base_plugin):
kwargs = {'ijid': jid,
'name': name,
'inode': subnode}
- self._run_node_handler('add_item', ijid, node, kwargs)
+ self._run_node_handler('add_item', ijid, node, None, kwargs)
def del_item(self, jid=None, node=None, **kwargs):
"""
@@ -384,7 +497,7 @@ class xep_0030(base_plugin):
ijid -- The item's JID.
inode -- The item's node.
"""
- self._run_node_handler('del_item', jid, node, kwargs)
+ self._run_node_handler('del_item', jid, node, None, kwargs)
def add_identity(self, category='', itype='', name='',
node=None, jid=None, lang=None):
@@ -411,7 +524,7 @@ class xep_0030(base_plugin):
'itype': itype,
'name': name,
'lang': lang}
- self._run_node_handler('add_identity', jid, node, kwargs)
+ self._run_node_handler('add_identity', jid, node, None, kwargs)
def add_feature(self, feature, node=None, jid=None):
"""
@@ -423,7 +536,7 @@ class xep_0030(base_plugin):
jid -- The JID to modify.
"""
kwargs = {'feature': feature}
- self._run_node_handler('add_feature', jid, node, kwargs)
+ self._run_node_handler('add_feature', jid, node, None, kwargs)
def del_identity(self, jid=None, node=None, **kwargs):
"""
@@ -437,7 +550,7 @@ class xep_0030(base_plugin):
name -- Optional, human readable name for the identity.
lang -- Optional, the identity's xml:lang value.
"""
- self._run_node_handler('del_identity', jid, node, kwargs)
+ self._run_node_handler('del_identity', jid, node, None, kwargs)
def del_feature(self, jid=None, node=None, **kwargs):
"""
@@ -448,7 +561,7 @@ class xep_0030(base_plugin):
node -- The node to modify.
feature -- The feature's namespace.
"""
- self._run_node_handler('del_feature', jid, node, kwargs)
+ self._run_node_handler('del_feature', jid, node, None, kwargs)
def set_identities(self, jid=None, node=None, **kwargs):
"""
@@ -463,7 +576,7 @@ class xep_0030(base_plugin):
identities -- A set of identities in tuple form.
lang -- Optional, xml:lang value.
"""
- self._run_node_handler('set_identities', jid, node, kwargs)
+ self._run_node_handler('set_identities', jid, node, None, kwargs)
def del_identities(self, jid=None, node=None, **kwargs):
"""
@@ -478,7 +591,7 @@ class xep_0030(base_plugin):
lang -- Optional. If given, only remove identities
using this xml:lang value.
"""
- self._run_node_handler('del_identities', jid, node, kwargs)
+ self._run_node_handler('del_identities', jid, node, None, kwargs)
def set_features(self, jid=None, node=None, **kwargs):
"""
@@ -490,7 +603,7 @@ class xep_0030(base_plugin):
node -- The node to modify.
features -- The new set of supported features.
"""
- self._run_node_handler('set_features', jid, node, kwargs)
+ self._run_node_handler('set_features', jid, node, None, kwargs)
def del_features(self, jid=None, node=None, **kwargs):
"""
@@ -500,9 +613,9 @@ class xep_0030(base_plugin):
jid -- The JID to modify.
node -- The node to modify.
"""
- self._run_node_handler('del_features', jid, node, kwargs)
+ self._run_node_handler('del_features', jid, node, None, kwargs)
- def _run_node_handler(self, htype, jid, node, data={}):
+ def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}):
"""
Execute the most specific node handler for the given
JID/node combination.
@@ -513,7 +626,7 @@ class xep_0030(base_plugin):
node -- The node requested.
data -- Optional, custom data to pass to the handler.
"""
- if jid is None:
+ if jid in (None, ''):
if self.xmpp.is_component:
jid = self.xmpp.boundjid.full
else:
@@ -521,14 +634,28 @@ class xep_0030(base_plugin):
if node is None:
node = ''
- if self._handlers[htype]['node'].get((jid, node), False):
- return self._handlers[htype]['node'][(jid, node)](jid, node, data)
- elif self._handlers[htype]['jid'].get(jid, False):
- return self._handlers[htype]['jid'][jid](jid, node, data)
- elif self._handlers[htype]['global']:
- return self._handlers[htype]['global'](jid, node, data)
- else:
- return None
+ try:
+ args = (jid, node, ifrom, data)
+ if self._handlers[htype]['node'].get((jid, node), False):
+ return self._handlers[htype]['node'][(jid, node)](*args)
+ elif self._handlers[htype]['jid'].get(jid, False):
+ return self._handlers[htype]['jid'][jid](*args)
+ elif self._handlers[htype]['global']:
+ return self._handlers[htype]['global'](*args)
+ else:
+ return None
+ except TypeError:
+ # To preserve backward compatibility, drop the ifrom parameter
+ # for existing handlers that don't understand it.
+ args = (jid, node, data)
+ if self._handlers[htype]['node'].get((jid, node), False):
+ return self._handlers[htype]['node'][(jid, node)](*args)
+ elif self._handlers[htype]['jid'].get(jid, False):
+ return self._handlers[htype]['jid'][jid](*args)
+ elif self._handlers[htype]['global']:
+ return self._handlers[htype]['global'](*args)
+ else:
+ return None
def _handle_disco_info(self, iq):
"""
@@ -550,6 +677,7 @@ class xep_0030(base_plugin):
info = self._run_node_handler('get_info',
jid,
iq['disco_info']['node'],
+ iq['from'],
iq)
if isinstance(info, Iq):
info.send()
@@ -560,8 +688,20 @@ class xep_0030(base_plugin):
iq.set_payload(info.xml)
iq.send()
elif iq['type'] == 'result':
- log.debug("Received disco info result from" + \
- "%s to %s.", iq['from'], iq['to'])
+ log.debug("Received disco info result from " + \
+ "<%s> to <%s>.", iq['from'], iq['to'])
+ if self.use_cache:
+ log.debug("Caching disco info result from " \
+ "<%s> to <%s>.", iq['from'], iq['to'])
+ if self.xmpp.is_component:
+ ito = iq['to'].full
+ else:
+ ito = None
+ self._run_node_handler('cache_info',
+ iq['from'].full,
+ iq['disco_info']['node'],
+ ito,
+ iq)
self.xmpp.event('disco_info', iq)
def _handle_disco_items(self, iq):
@@ -583,6 +723,7 @@ class xep_0030(base_plugin):
items = self._run_node_handler('get_items',
jid,
iq['disco_items']['node'],
+ iq['from'].full,
iq)
if isinstance(items, Iq):
items.send()
@@ -592,7 +733,7 @@ class xep_0030(base_plugin):
iq.set_payload(items.xml)
iq.send()
elif iq['type'] == 'result':
- log.debug("Received disco items result from" + \
+ log.debug("Received disco items result from " + \
"%s to %s.", iq['from'], iq['to'])
self.xmpp.event('disco_items', iq)
@@ -607,21 +748,46 @@ class xep_0030(base_plugin):
Arguments:
info -- The disco#info quest (not the full Iq stanza) to modify.
"""
+ result = info
+ if isinstance(info, Iq):
+ info = iq['disco_info']
if not info['node']:
if not info['identities']:
if self.xmpp.is_component:
- log.debug("No identity found for this entity." + \
+ log.debug("No identity found for this entity. " + \
"Using default component identity.")
info.add_identity('component', 'generic')
else:
- log.debug("No identity found for this entity." + \
+ log.debug("No identity found for this entity. " + \
"Using default client identity.")
info.add_identity('client', 'bot')
if not info['features']:
- log.debug("No features found for this entity." + \
+ log.debug("No features found for this entity. " + \
"Using default disco#info feature.")
info.add_feature(info.namespace)
- return info
+ return result
+
+ def _wrap(self, ito, ifrom, payload, force=False):
+ """
+ Ensure that results are wrapped in an Iq stanza
+ if self.wrap_results has been set to True.
+
+ Arguments:
+ ito -- The JID to use as the 'to' value
+ ifrom -- The JID to use as the 'from' value
+ payload -- The disco data to wrap
+ force -- Force wrapping, regardless of self.wrap_results
+ """
+ if (force or self.wrap_results) and not isinstance(payload, Iq):
+ iq = self.xmpp.Iq()
+ # Since we're simulating a result, we have to treat
+ # the 'from' and 'to' values opposite the normal way.
+ iq['to'] = self.xmpp.boundjid if ito is None else ito
+ iq['from'] = self.xmpp.boundjid if ifrom is None else ifrom
+ iq['type'] = 'result'
+ iq.append(payload)
+ return iq
+ return payload
# Retain some backwards compatibility