diff options
Diffstat (limited to 'sleekxmpp/plugins/xep_0030')
-rw-r--r-- | sleekxmpp/plugins/xep_0030/__init__.py | 23 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/disco.py | 740 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/stanza/__init__.py | 10 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/stanza/info.py | 276 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/stanza/items.py | 152 | ||||
-rw-r--r-- | sleekxmpp/plugins/xep_0030/static.py | 430 |
6 files changed, 0 insertions, 1631 deletions
diff --git a/sleekxmpp/plugins/xep_0030/__init__.py b/sleekxmpp/plugins/xep_0030/__init__.py deleted file mode 100644 index 0d1de65b..00000000 --- a/sleekxmpp/plugins/xep_0030/__init__.py +++ /dev/null @@ -1,23 +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. -""" - -from sleekxmpp.plugins.base import register_plugin - -from sleekxmpp.plugins.xep_0030 import stanza -from sleekxmpp.plugins.xep_0030.stanza import DiscoInfo, DiscoItems -from sleekxmpp.plugins.xep_0030.static import StaticDisco -from sleekxmpp.plugins.xep_0030.disco import XEP_0030 - - -register_plugin(XEP_0030) - -# Retain some backwards compatibility -xep_0030 = XEP_0030 -XEP_0030.getInfo = XEP_0030.get_info -XEP_0030.getItems = XEP_0030.get_items -XEP_0030.make_static = XEP_0030.restore_defaults diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py deleted file mode 100644 index 8a397923..00000000 --- a/sleekxmpp/plugins/xep_0030/disco.py +++ /dev/null @@ -1,740 +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 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_0030 import stanza, DiscoInfo, DiscoItems -from sleekxmpp.plugins.xep_0030 import StaticDisco - - -log = logging.getLogger(__name__) - - -class XEP_0030(BasePlugin): - - """ - XEP-0030: Service Discovery - - Service discovery in XMPP allows entities to discover information about - other agents in the network, such as the feature sets supported by a - client, or signposts to other, related entities. - - Also see <http://www.xmpp.org/extensions/xep-0030.html>. - - The XEP-0030 plugin works using a hierarchy of dynamic - node handlers, ranging from global handlers to specific - JID+node handlers. The default set of handlers operate - in a static manner, storing disco information in memory. - However, custom handlers may use any available backend - storage mechanism desired, such as SQLite or Redis. - - Node handler hierarchy: - JID | Node | Level - --------------------- - None | None | Global - Given | None | All nodes for the JID - None | Given | Node on self.xmpp.boundjid - Given | Given | A single node - - Stream Handlers: - Disco Info -- Any Iq stanze that includes a query with the - namespace http://jabber.org/protocol/disco#info. - Disco Items -- Any Iq stanze that includes a query with the - namespace http://jabber.org/protocol/disco#items. - - Events: - disco_info -- Received a disco#info Iq query result. - disco_items -- Received a disco#items Iq query result. - disco_info_query -- Received a disco#info Iq query request. - disco_items_query -- Received a disco#items Iq query request. - - Attributes: - stanza -- A reference to the module containing the - stanza classes provided by this plugin. - static -- Object containing the default set of - static node handlers. - default_handlers -- A dictionary mapping operations to the default - global handler (by default, the static handlers). - xmpp -- The main SleekXMPP object. - - Methods: - set_node_handler -- Assign a handler to a JID/node combination. - del_node_handler -- Remove a handler from a JID/node combination. - get_info -- Retrieve disco#info data, locally or remote. - get_items -- Retrieve disco#items data, locally or remote. - set_identities -- - set_features -- - set_items -- - del_items -- - del_identity -- - del_feature -- - del_item -- - add_identity -- - add_feature -- - add_item -- - """ - - name = 'xep_0030' - description = 'XEP-0030: Service Discovery' - dependencies = set() - stanza = stanza - default_config = { - 'use_cache': True, - 'wrap_results': False - } - - def plugin_init(self): - """ - Start the XEP-0030 plugin. - """ - self.xmpp.register_handler( - Callback('Disco Info', - StanzaPath('iq/disco_info'), - self._handle_disco_info)) - - self.xmpp.register_handler( - Callback('Disco Items', - StanzaPath('iq/disco_items'), - self._handle_disco_items)) - - register_stanza_plugin(Iq, DiscoInfo) - register_stanza_plugin(Iq, DiscoItems) - - self.static = StaticDisco(self.xmpp, self) - - 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'] - - for op in self._disco_ops: - self.api.register(getattr(self.static, op), op, default=True) - - def _add_disco_op(self, op, default_handler): - self.api.register(default_handler, op) - self.api.register_default(default_handler, op) - - def set_node_handler(self, htype, jid=None, node=None, handler=None): - """ - Add a node handler for the given hierarchy level and - handler type. - - Node handlers are ordered in a hierarchy where the - most specific handler is executed. Thus, a fallback, - global handler can be used for the majority of cases - with a few node specific handler that override the - global behavior. - - Node handler hierarchy: - JID | Node | Level - --------------------- - None | None | Global - Given | None | All nodes for the JID - None | Given | Node on self.xmpp.boundjid - Given | Given | A single node - - Handler types: - get_info - get_items - set_identities - set_features - set_items - del_items - del_identities - del_identity - del_feature - del_features - del_item - add_identity - add_feature - add_item - - Arguments: - htype -- The operation provided by the handler. - jid -- The JID the handler applies to. May be narrowed - further if a node is given. - node -- The particular node the handler is for. If no JID - is given, then the self.xmpp.boundjid.full is - assumed. - handler -- The handler function to use. - """ - self.api.register(handler, htype, jid, node) - - def del_node_handler(self, htype, jid, node): - """ - Remove a handler type for a JID and node combination. - - The next handler in the hierarchy will be used if one - exists. If removing the global handler, make sure that - other handlers exist to process existing nodes. - - Node handler hierarchy: - JID | Node | Level - --------------------- - None | None | Global - Given | None | All nodes for the JID - None | Given | Node on self.xmpp.boundjid - Given | Given | A single node - - Arguments: - htype -- The type of handler to remove. - jid -- The JID from which to remove the handler. - node -- The node from which to remove the handler. - """ - self.api.unregister(htype, jid, node) - - def restore_defaults(self, jid=None, node=None, handlers=None): - """ - Change all or some of a node's handlers to the default - handlers. Useful for manually overriding the contents - of a node that would otherwise be handled by a JID level - or global level dynamic handler. - - The default is to use the built-in static handlers, but that - may be changed by modifying self.default_handlers. - - Arguments: - jid -- The JID owning the node to modify. - node -- The node to change to using static handlers. - handlers -- Optional list of handlers to change to the - default version. If provided, only these - handlers will be changed. Otherwise, all - handlers will use the default version. - """ - if handlers is None: - handlers = self._disco_ops - for op in handlers: - self.api.restore_default(op, jid, node) - - 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.api['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.api['has_identity'](jid, node, ifrom, data) - - def get_info(self, jid=None, node=None, local=None, - cached=None, **kwargs): - """ - Retrieve the disco#info results from a given JID/node combination. - - Info may be retrieved from both local resources and remote agents; - the local parameter indicates if the information should be gathered - by executing the local node handlers, or if a disco#info stanza - must be generated and sent. - - If requesting items from a local JID/node, then only a DiscoInfo - stanza will be returned. Otherwise, an Iq stanza will be returned. - - Arguments: - jid -- Request info from this JID. - node -- The particular node to query. - 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. - block -- If true, block and wait for the stanzas' reply. - timeout -- The time in seconds to block while waiting for - a reply. If None, then wait indefinitely. The - timeout value is only used when block=True. - callback -- Optional callback to execute when a reply is - received instead of blocking and waiting for - the reply. - timeout_callback -- Optional callback to execute when no result - has been received in timeout seconds. - """ - if local 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 - jid = jid.full - elif jid in (None, ''): - local = True - - if local: - log.debug("Looking up local disco#info data " + \ - "for %s, node %s.", jid, node) - info = self.api['get_info'](jid, node, - kwargs.get('ifrom', None), - 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.api['get_cached_info'](jid, node, - kwargs.get('ifrom', None), - kwargs) - if info is not None: - return self._wrap(kwargs.get('ifrom', None), jid, info) - - iq = self.xmpp.Iq() - # Check dfrom parameter for backwards compatibility - iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', '')) - iq['to'] = jid - iq['type'] = 'get' - iq['disco_info']['node'] = node if node else '' - return iq.send(timeout=kwargs.get('timeout', None), - block=kwargs.get('block', True), - callback=kwargs.get('callback', None), - timeout_callback=kwargs.get('timeout_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.api['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. - - Items may be retrieved from both local resources and remote agents; - the local parameter indicates if the items should be gathered by - executing the local node handlers, or if a disco#items stanza must - be generated and sent. - - If requesting items from a local JID/node, then only a DiscoItems - stanza will be returned. Otherwise, an Iq stanza will be returned. - - Arguments: - jid -- Request info from this JID. - node -- The particular node to query. - 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 items. - 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 - a reply. If None, then wait indefinitely. - callback -- Optional callback to execute when a reply is - received instead of blocking and waiting for - the reply. - iterator -- If True, return a result set iterator using - the XEP-0059 plugin, if the plugin is loaded. - Otherwise the parameter is ignored. - timeout_callback -- Optional callback to execute when no result - has been received in timeout seconds. - """ - if local or local is None and jid is None: - items = self.api['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 - iq['from'] = kwargs.get('ifrom', kwargs.get('dfrom', '')) - iq['to'] = jid - iq['type'] = 'get' - iq['disco_items']['node'] = node if node else '' - if kwargs.get('iterator', False) and self.xmpp['xep_0059']: - return self.xmpp['xep_0059'].iterate(iq, 'disco_items') - else: - return iq.send(timeout=kwargs.get('timeout', None), - block=kwargs.get('block', True), - callback=kwargs.get('callback', None), - timeout_callback=kwargs.get('timeout_callback', None)) - - def set_items(self, jid=None, node=None, **kwargs): - """ - Set or replace all items for the specified JID/node combination. - - The given items must be in a list or set where each item is a - tuple of the form: (jid, node, name). - - Arguments: - jid -- The JID to modify. - node -- Optional node to modify. - items -- A series of items in tuple format. - """ - self.api['set_items'](jid, node, None, kwargs) - - def del_items(self, jid=None, node=None, **kwargs): - """ - Remove all items from the given JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- Optional node to modify. - """ - self.api['del_items'](jid, node, None, kwargs) - - def add_item(self, jid='', name='', node=None, subnode='', ijid=None): - """ - Add a new item element to the given JID/node combination. - - Each item is required to have a JID, but may also specify - a node value to reference non-addressable entities. - - Arguments: - jid -- The JID for the item. - name -- Optional name for the item. - node -- The node to modify. - subnode -- Optional node for the item. - ijid -- The JID to modify. - """ - if not jid: - jid = self.xmpp.boundjid.full - kwargs = {'ijid': jid, - 'name': name, - 'inode': subnode} - self.api['add_item'](ijid, node, None, kwargs) - - def del_item(self, jid=None, node=None, **kwargs): - """ - Remove a single item from the given JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - ijid -- The item's JID. - inode -- The item's node. - """ - self.api['del_item'](jid, node, None, kwargs) - - def add_identity(self, category='', itype='', name='', - node=None, jid=None, lang=None): - """ - Add a new identity to the given JID/node combination. - - Each identity must be unique in terms of all four identity - components: category, type, name, and language. - - Multiple, identical category/type pairs are allowed only - if the xml:lang values are different. Likewise, multiple - category/type/xml:lang pairs are allowed so long as the - names are different. A category and type is always required. - - Arguments: - category -- The identity's category. - itype -- The identity's type. - name -- Optional name for the identity. - lang -- Optional two-letter language code. - node -- The node to modify. - jid -- The JID to modify. - """ - kwargs = {'category': category, - 'itype': itype, - 'name': name, - 'lang': lang} - self.api['add_identity'](jid, node, None, kwargs) - - def add_feature(self, feature, node=None, jid=None): - """ - Add a feature to a JID/node combination. - - Arguments: - feature -- The namespace of the supported feature. - node -- The node to modify. - jid -- The JID to modify. - """ - kwargs = {'feature': feature} - self.api['add_feature'](jid, node, None, kwargs) - - def del_identity(self, jid=None, node=None, **kwargs): - """ - Remove an identity from the given JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - category -- The identity's category. - itype -- The identity's type value. - name -- Optional, human readable name for the identity. - lang -- Optional, the identity's xml:lang value. - """ - self.api['del_identity'](jid, node, None, kwargs) - - def del_feature(self, jid=None, node=None, **kwargs): - """ - Remove a feature from a given JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - feature -- The feature's namespace. - """ - self.api['del_feature'](jid, node, None, kwargs) - - def set_identities(self, jid=None, node=None, **kwargs): - """ - Add or replace all identities for the given JID/node combination. - - The identities must be in a set where each identity is a tuple - of the form: (category, type, lang, name) - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - identities -- A set of identities in tuple form. - lang -- Optional, xml:lang value. - """ - self.api['set_identities'](jid, node, None, kwargs) - - def del_identities(self, jid=None, node=None, **kwargs): - """ - Remove all identities for a JID/node combination. - - If a language is specified, only identities using that - language will be removed. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - lang -- Optional. If given, only remove identities - using this xml:lang value. - """ - self.api['del_identities'](jid, node, None, kwargs) - - def set_features(self, jid=None, node=None, **kwargs): - """ - Add or replace the set of supported features - for a JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - features -- The new set of supported features. - """ - self.api['set_features'](jid, node, None, kwargs) - - def del_features(self, jid=None, node=None, **kwargs): - """ - Remove all features from a JID/node combination. - - Arguments: - jid -- The JID to modify. - node -- The node to modify. - """ - self.api['del_features'](jid, node, None, kwargs) - - def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}): - """ - Execute the most specific node handler for the given - JID/node combination. - - Arguments: - htype -- The handler type to execute. - jid -- The JID requested. - node -- The node requested. - data -- Optional, custom data to pass to the handler. - """ - return self.api[htype](jid, node, ifrom, data) - - def _handle_disco_info(self, iq): - """ - Process an incoming disco#info stanza. If it is a get - request, find and return the appropriate identities - and features. If it is an info result, fire the - disco_info event. - - Arguments: - iq -- The incoming disco#items stanza. - """ - if iq['type'] == 'get': - log.debug("Received disco info query from " + \ - "<%s> to <%s>.", iq['from'], iq['to']) - info = self.api['get_info'](iq['to'], - iq['disco_info']['node'], - iq['from'], - iq) - if isinstance(info, Iq): - info['id'] = iq['id'] - info.send() - else: - iq.reply() - if info: - info = self._fix_default_info(info) - 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']) - 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.api['cache_info'](iq['from'], - iq['disco_info']['node'], - ito, - iq) - self.xmpp.event('disco_info', iq) - - def _handle_disco_items(self, iq): - """ - Process an incoming disco#items stanza. If it is a get - request, find and return the appropriate items. If it - is an items result, fire the disco_items event. - - Arguments: - iq -- The incoming disco#items stanza. - """ - if iq['type'] == 'get': - log.debug("Received disco items query from " + \ - "<%s> to <%s>.", iq['from'], iq['to']) - items = self.api['get_items'](iq['to'], - iq['disco_items']['node'], - iq['from'], - iq) - if isinstance(items, Iq): - items.send() - else: - iq.reply() - if items: - iq.set_payload(items.xml) - iq.send() - elif iq['type'] == 'result': - log.debug("Received disco items result from " + \ - "%s to %s.", iq['from'], iq['to']) - self.xmpp.event('disco_items', iq) - - def _fix_default_info(self, info): - """ - Disco#info results for a JID are required to include at least - one identity and feature. As a default, if no other identity is - provided, SleekXMPP will use either the generic component or the - bot client identity. A the standard disco#info feature will also be - added if no features are provided. - - Arguments: - info -- The disco#info quest (not the full Iq stanza) to modify. - """ - result = info - if isinstance(info, Iq): - info = info['disco_info'] - if not info['node']: - if not info['identities']: - if self.xmpp.is_component: - 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. " + \ - "Using default client identity.") - info.add_identity('client', 'bot') - if not info['features']: - log.debug("No features found for this entity. " + \ - "Using default disco#info feature.") - info.add_feature(info.namespace) - 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 diff --git a/sleekxmpp/plugins/xep_0030/stanza/__init__.py b/sleekxmpp/plugins/xep_0030/stanza/__init__.py deleted file mode 100644 index 0d97cf3d..00000000 --- a/sleekxmpp/plugins/xep_0030/stanza/__init__.py +++ /dev/null @@ -1,10 +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. -""" - -from sleekxmpp.plugins.xep_0030.stanza.info import DiscoInfo -from sleekxmpp.plugins.xep_0030.stanza.items import DiscoItems diff --git a/sleekxmpp/plugins/xep_0030/stanza/info.py b/sleekxmpp/plugins/xep_0030/stanza/info.py deleted file mode 100644 index 25d1d07f..00000000 --- a/sleekxmpp/plugins/xep_0030/stanza/info.py +++ /dev/null @@ -1,276 +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. -""" - -from sleekxmpp.xmlstream import ElementBase, ET - - -class DiscoInfo(ElementBase): - - """ - XMPP allows for users and agents to find the identities and features - supported by other entities in the XMPP network through service discovery, - or "disco". In particular, the "disco#info" query type for <iq> stanzas is - used to request the list of identities and features offered by a JID. - - An identity is a combination of a category and type, such as the 'client' - category with a type of 'pc' to indicate the agent is a human operated - client with a GUI, or a category of 'gateway' with a type of 'aim' to - identify the agent as a gateway for the legacy AIM protocol. See - <http://xmpp.org/registrar/disco-categories.html> for a full list of - accepted category and type combinations. - - Features are simply a set of the namespaces that identify the supported - features. For example, a client that supports service discovery will - include the feature 'http://jabber.org/protocol/disco#info'. - - Since clients and components may operate in several roles at once, identity - and feature information may be grouped into "nodes". If one were to write - all of the identities and features used by a client, then node names would - be like section headings. - - Example disco#info stanzas: - <iq type="get"> - <query xmlns="http://jabber.org/protocol/disco#info" /> - </iq> - - <iq type="result"> - <query xmlns="http://jabber.org/protocol/disco#info"> - <identity category="client" type="bot" name="SleekXMPP Bot" /> - <feature var="http://jabber.org/protocol/disco#info" /> - <feature var="jabber:x:data" /> - <feature var="urn:xmpp:ping" /> - </query> - </iq> - - Stanza Interface: - node -- The name of the node to either - query or return info from. - identities -- A set of 4-tuples, where each tuple contains - the category, type, xml:lang, and name - of an identity. - features -- A set of namespaces for features. - - Methods: - add_identity -- Add a new, single identity. - del_identity -- Remove a single identity. - get_identities -- Return all identities in tuple form. - set_identities -- Use multiple identities, each given in tuple form. - del_identities -- Remove all identities. - add_feature -- Add a single feature. - del_feature -- Remove a single feature. - get_features -- Return a list of all features. - set_features -- Use a given list of features. - del_features -- Remove all features. - """ - - name = 'query' - namespace = 'http://jabber.org/protocol/disco#info' - plugin_attrib = 'disco_info' - interfaces = set(('node', 'features', 'identities')) - lang_interfaces = set(('identities',)) - - # Cache identities and features - _identities = set() - _features = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches identity and feature information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - - self._identities = set([id[0:3] for id in self['identities']]) - self._features = self['features'] - - def add_identity(self, category, itype, name=None, lang=None): - """ - Add a new identity element. Each identity must be unique - in terms of all four identity components. - - Multiple, identical category/type pairs are allowed only - if the xml:lang values are different. Likewise, multiple - category/type/xml:lang pairs are allowed so long as the names - are different. In any case, a category and type are required. - - Arguments: - category -- The general category to which the agent belongs. - itype -- A more specific designation with the category. - name -- Optional human readable name for this identity. - lang -- Optional standard xml:lang value. - """ - identity = (category, itype, lang) - if identity not in self._identities: - self._identities.add(identity) - id_xml = ET.Element('{%s}identity' % self.namespace) - id_xml.attrib['category'] = category - id_xml.attrib['type'] = itype - if lang: - id_xml.attrib['{%s}lang' % self.xml_ns] = lang - if name: - id_xml.attrib['name'] = name - self.xml.append(id_xml) - return True - return False - - def del_identity(self, category, itype, name=None, lang=None): - """ - Remove a given identity. - - Arguments: - category -- The general category to which the agent belonged. - itype -- A more specific designation with the category. - name -- Optional human readable name for this identity. - lang -- Optional, standard xml:lang value. - """ - identity = (category, itype, lang) - if identity in self._identities: - self._identities.remove(identity) - for id_xml in self.findall('{%s}identity' % self.namespace): - id = (id_xml.attrib['category'], - id_xml.attrib['type'], - id_xml.attrib.get('{%s}lang' % self.xml_ns, None)) - if id == identity: - self.xml.remove(id_xml) - return True - return False - - def get_identities(self, lang=None, dedupe=True): - """ - Return a set of all identities in tuple form as so: - (category, type, lang, name) - - If a language was specified, only return identities using - that language. - - Arguments: - lang -- Optional, standard xml:lang value. - dedupe -- If True, de-duplicate identities, otherwise - return a list of all identities. - """ - if dedupe: - identities = set() - else: - identities = [] - for id_xml in self.findall('{%s}identity' % self.namespace): - xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None) - if lang is None or xml_lang == lang: - id = (id_xml.attrib['category'], - id_xml.attrib['type'], - id_xml.attrib.get('{%s}lang' % self.xml_ns, None), - id_xml.attrib.get('name', None)) - if dedupe: - identities.add(id) - else: - identities.append(id) - return identities - - def set_identities(self, identities, lang=None): - """ - Add or replace all identities. The identities must be a in set - where each identity is a tuple of the form: - (category, type, lang, name) - - If a language is specifified, any identities using that language - will be removed to be replaced with the given identities. - - NOTE: An identity's language will not be changed regardless of - the value of lang. - - Arguments: - identities -- A set of identities in tuple form. - lang -- Optional, standard xml:lang value. - """ - self.del_identities(lang) - for identity in identities: - category, itype, lang, name = identity - self.add_identity(category, itype, name, lang) - - def del_identities(self, lang=None): - """ - Remove all identities. If a language was specified, only - remove identities using that language. - - Arguments: - lang -- Optional, standard xml:lang value. - """ - for id_xml in self.findall('{%s}identity' % self.namespace): - if lang is None: - self.xml.remove(id_xml) - elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang: - self._identities.remove(( - id_xml.attrib['category'], - id_xml.attrib['type'], - id_xml.attrib.get('{%s}lang' % self.xml_ns, None))) - self.xml.remove(id_xml) - - def add_feature(self, feature): - """ - Add a single, new feature. - - Arguments: - feature -- The namespace of the supported feature. - """ - if feature not in self._features: - self._features.add(feature) - feature_xml = ET.Element('{%s}feature' % self.namespace) - feature_xml.attrib['var'] = feature - self.xml.append(feature_xml) - return True - return False - - def del_feature(self, feature): - """ - Remove a single feature. - - Arguments: - feature -- The namespace of the removed feature. - """ - if feature in self._features: - self._features.remove(feature) - for feature_xml in self.findall('{%s}feature' % self.namespace): - if feature_xml.attrib['var'] == feature: - self.xml.remove(feature_xml) - return True - return False - - def get_features(self, dedupe=True): - """Return the set of all supported features.""" - if dedupe: - features = set() - else: - features = [] - for feature_xml in self.findall('{%s}feature' % self.namespace): - if dedupe: - features.add(feature_xml.attrib['var']) - else: - features.append(feature_xml.attrib['var']) - return features - - def set_features(self, features): - """ - Add or replace the set of supported features. - - Arguments: - features -- The new set of supported features. - """ - self.del_features() - for feature in features: - self.add_feature(feature) - - def del_features(self): - """Remove all features.""" - self._features = set() - for feature_xml in self.findall('{%s}feature' % self.namespace): - self.xml.remove(feature_xml) diff --git a/sleekxmpp/plugins/xep_0030/stanza/items.py b/sleekxmpp/plugins/xep_0030/stanza/items.py deleted file mode 100644 index 10458614..00000000 --- a/sleekxmpp/plugins/xep_0030/stanza/items.py +++ /dev/null @@ -1,152 +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. -""" - -from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin - - -class DiscoItems(ElementBase): - - """ - Example disco#items stanzas: - <iq type="get"> - <query xmlns="http://jabber.org/protocol/disco#items" /> - </iq> - - <iq type="result"> - <query xmlns="http://jabber.org/protocol/disco#items"> - <item jid="chat.example.com" - node="xmppdev" - name="XMPP Dev" /> - <item jid="chat.example.com" - node="sleekdev" - name="SleekXMPP Dev" /> - </query> - </iq> - - Stanza Interface: - node -- The name of the node to either - query or return info from. - items -- A list of 3-tuples, where each tuple contains - the JID, node, and name of an item. - - Methods: - add_item -- Add a single new item. - del_item -- Remove a single item. - get_items -- Return all items. - set_items -- Set or replace all items. - del_items -- Remove all items. - """ - - name = 'query' - namespace = 'http://jabber.org/protocol/disco#items' - plugin_attrib = 'disco_items' - interfaces = set(('node', 'items')) - - # Cache items - _items = set() - - def setup(self, xml=None): - """ - Populate the stanza object using an optional XML object. - - Overrides ElementBase.setup - - Caches item information. - - Arguments: - xml -- Use an existing XML object for the stanza's values. - """ - ElementBase.setup(self, xml) - self._items = set([item[0:2] for item in self['items']]) - - def add_item(self, jid, node=None, name=None): - """ - Add a new item element. Each item is required to have a - JID, but may also specify a node value to reference - non-addressable entitities. - - Arguments: - jid -- The JID for the item. - node -- Optional additional information to reference - non-addressable items. - name -- Optional human readable name for the item. - """ - if (jid, node) not in self._items: - self._items.add((jid, node)) - item = DiscoItem(parent=self) - item['jid'] = jid - item['node'] = node - item['name'] = name - self.iterables.append(item) - return True - return False - - def del_item(self, jid, node=None): - """ - Remove a single item. - - Arguments: - jid -- JID of the item to remove. - node -- Optional extra identifying information. - """ - if (jid, node) in self._items: - for item_xml in self.findall('{%s}item' % self.namespace): - item = (item_xml.attrib['jid'], - item_xml.attrib.get('node', None)) - if item == (jid, node): - self.xml.remove(item_xml) - return True - return False - - def get_items(self): - """Return all items.""" - items = set() - for item in self['substanzas']: - if isinstance(item, DiscoItem): - items.add((item['jid'], item['node'], item['name'])) - return items - - def set_items(self, items): - """ - Set or replace all items. The given items must be in a - list or set where each item is a tuple of the form: - (jid, node, name) - - Arguments: - items -- A series of items in tuple format. - """ - self.del_items() - for item in items: - jid, node, name = item - self.add_item(jid, node, name) - - def del_items(self): - """Remove all items.""" - self._items = set() - items = [i for i in self.iterables if isinstance(i, DiscoItem)] - for item in items: - self.xml.remove(item.xml) - self.iterables.remove(item) - - -class DiscoItem(ElementBase): - name = 'item' - namespace = 'http://jabber.org/protocol/disco#items' - plugin_attrib = name - interfaces = set(('jid', 'node', 'name')) - - def get_node(self): - """Return the item's node name or ``None``.""" - return self._get_attr('node', None) - - def get_name(self): - """Return the item's human readable name, or ``None``.""" - return self._get_attr('name', None) - - -register_stanza_plugin(DiscoItems, DiscoItem, iterable=True) diff --git a/sleekxmpp/plugins/xep_0030/static.py b/sleekxmpp/plugins/xep_0030/static.py deleted file mode 100644 index dd5317d1..00000000 --- a/sleekxmpp/plugins/xep_0030/static.py +++ /dev/null @@ -1,430 +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 -import threading - -from sleekxmpp import Iq -from sleekxmpp.exceptions import XMPPError, IqError, IqTimeout -from sleekxmpp.xmlstream import JID -from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems - - -log = logging.getLogger(__name__) - - -class StaticDisco(object): - - """ - While components will likely require fully dynamic handling - of service discovery information, most clients and simple bots - only need to manage a few disco nodes that will remain mostly - static. - - StaticDisco provides a set of node handlers that will store - static sets of disco info and items in memory. - - Attributes: - nodes -- A dictionary mapping (JID, node) tuples to a dict - containing a disco#info and a disco#items stanza. - xmpp -- The main SleekXMPP object. - """ - - def __init__(self, xmpp, disco): - """ - Create a static disco interface. Sets of disco#info and - disco#items are maintained for every given JID and node - combination. These stanzas are used to store disco - information in memory without any additional processing. - - Arguments: - xmpp -- The main SleekXMPP object. - """ - self.nodes = {} - self.xmpp = xmpp - self.disco = disco - self.lock = threading.RLock() - - def add_node(self, jid=None, node=None, ifrom=None): - """ - Create a new set of stanzas for the provided - JID and node combination. - - Arguments: - jid -- The JID that will own the new stanzas. - node -- The node that will own the new stanzas. - """ - with self.lock: - if jid is None: - jid = self.xmpp.boundjid.full - if node is None: - node = '' - if ifrom is None: - ifrom = '' - if isinstance(ifrom, JID): - ifrom = ifrom.full - if (jid, node, ifrom) not in self.nodes: - self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(), - 'items': DiscoItems()} - self.nodes[(jid, node, ifrom)]['info']['node'] = node - self.nodes[(jid, node, ifrom)]['items']['node'] = node - - def get_node(self, jid=None, node=None, ifrom=None): - with self.lock: - if jid is None: - jid = self.xmpp.boundjid.full - if node is None: - node = '' - if ifrom is None: - ifrom = '' - if isinstance(ifrom, JID): - ifrom = ifrom.full - if (jid, node, ifrom) not in self.nodes: - self.add_node(jid, node, ifrom) - return self.nodes[(jid, node, ifrom)] - - def node_exists(self, jid=None, node=None, ifrom=None): - with self.lock: - if jid is None: - jid = self.xmpp.boundjid.full - if node is None: - node = '' - if ifrom is None: - ifrom = '' - if isinstance(ifrom, JID): - ifrom = ifrom.full - if (jid, node, ifrom) not in self.nodes: - return False - return True - - # ================================================================= - # Node Handlers - # - # Each handler accepts four arguments: jid, node, ifrom, and data. - # The jid and node parameters together determine the set of info - # and items stanzas that will be retrieved or added. Additionally, - # the ifrom value allows for cached results when results vary based - # on the requester's JID. The data parameter is a dictionary with - # additional parameters that will be passed to other calls. - # - # This implementation does not allow different responses based on - # the requester's JID, except for cached results. To do that, - # register a custom node handler. - - def supports(self, jid, node, ifrom, data): - """ - Check if a JID supports a given feature. - - The data parameter may provide: - feature -- The feature to check for support. - 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. - """ - feature = data.get('feature', None) - - data = {'local': data.get('local', False), - 'cached': data.get('cached', True)} - - if not feature: - return False - - try: - info = self.disco.get_info(jid=jid, node=node, - ifrom=ifrom, **data) - info = self.disco._wrap(ifrom, jid, info, True) - features = info['disco_info']['features'] - return feature in features - except IqError: - return False - except IqTimeout: - return None - - def has_identity(self, jid, node, ifrom, data): - """ - Check if a JID has a given identity. - - The data parameter may provide: - 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. - """ - identity = (data.get('category', None), - data.get('itype', None), - data.get('lang', None)) - - data = {'local': data.get('local', False), - 'cached': data.get('cached', True)} - - try: - info = self.disco.get_info(jid=jid, node=node, - ifrom=ifrom, **data) - info = self.disco._wrap(ifrom, jid, info, True) - trunc = lambda i: (i[0], i[1], i[2]) - return identity in map(trunc, info['disco_info']['identities']) - except IqError: - return False - except IqTimeout: - return None - - def get_info(self, jid, node, ifrom, data): - """ - Return the stored info data for the requested JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if not self.node_exists(jid, node): - if not node: - return DiscoInfo() - else: - raise XMPPError(condition='item-not-found') - else: - return self.get_node(jid, node)['info'] - - def set_info(self, jid, node, ifrom, data): - """ - Set the entire info stanza for a JID/node at once. - - The data parameter is a disco#info substanza. - """ - with self.lock: - self.add_node(jid, node) - self.get_node(jid, node)['info'] = data - - def del_info(self, jid, node, ifrom, data): - """ - Reset the info stanza for a given JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['info'] = DiscoInfo() - - def get_items(self, jid, node, ifrom, data): - """ - Return the stored items data for the requested JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if not self.node_exists(jid, node): - if not node: - return DiscoItems() - else: - raise XMPPError(condition='item-not-found') - else: - return self.get_node(jid, node)['items'] - - def set_items(self, jid, node, ifrom, data): - """ - Replace the stored items data for a JID/node combination. - - The data parameter may provide: - items -- A set of items in tuple format. - """ - with self.lock: - items = data.get('items', set()) - self.add_node(jid, node) - self.get_node(jid, node)['items']['items'] = items - - def del_items(self, jid, node, ifrom, data): - """ - Reset the items stanza for a given JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['items'] = DiscoItems() - - def add_identity(self, jid, node, ifrom, data): - """ - Add a new identity to te JID/node combination. - - The data parameter may provide: - category -- The general category to which the agent belongs. - itype -- A more specific designation with the category. - name -- Optional human readable name for this identity. - lang -- Optional standard xml:lang value. - """ - with self.lock: - self.add_node(jid, node) - self.get_node(jid, node)['info'].add_identity( - data.get('category', ''), - data.get('itype', ''), - data.get('name', None), - data.get('lang', None)) - - def set_identities(self, jid, node, ifrom, data): - """ - Add or replace all identities for a JID/node combination. - - The data parameter should include: - identities -- A list of identities in tuple form: - (category, type, name, lang) - """ - with self.lock: - identities = data.get('identities', set()) - self.add_node(jid, node) - self.get_node(jid, node)['info']['identities'] = identities - - def del_identity(self, jid, node, ifrom, data): - """ - Remove an identity from a JID/node combination. - - The data parameter may provide: - category -- The general category to which the agent belonged. - itype -- A more specific designation with the category. - name -- Optional human readable name for this identity. - lang -- Optional, standard xml:lang value. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['info'].del_identity( - data.get('category', ''), - data.get('itype', ''), - data.get('name', None), - data.get('lang', None)) - - def del_identities(self, jid, node, ifrom, data): - """ - Remove all identities from a JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if self.node_exists(jid, node): - del self.get_node(jid, node)['info']['identities'] - - def add_feature(self, jid, node, ifrom, data): - """ - Add a feature to a JID/node combination. - - The data parameter should include: - feature -- The namespace of the supported feature. - """ - with self.lock: - self.add_node(jid, node) - self.get_node(jid, node)['info'].add_feature( - data.get('feature', '')) - - def set_features(self, jid, node, ifrom, data): - """ - Add or replace all features for a JID/node combination. - - The data parameter should include: - features -- The new set of supported features. - """ - with self.lock: - features = data.get('features', set()) - self.add_node(jid, node) - self.get_node(jid, node)['info']['features'] = features - - def del_feature(self, jid, node, ifrom, data): - """ - Remove a feature from a JID/node combination. - - The data parameter should include: - feature -- The namespace of the removed feature. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['info'].del_feature( - data.get('feature', '')) - - def del_features(self, jid, node, ifrom, data): - """ - Remove all features from a JID/node combination. - - The data parameter is not used. - """ - with self.lock: - if not self.node_exists(jid, node): - return - del self.get_node(jid, node)['info']['features'] - - def add_item(self, jid, node, ifrom, data): - """ - Add an item to a JID/node combination. - - The data parameter may include: - ijid -- The JID for the item. - inode -- Optional additional information to reference - non-addressable items. - name -- Optional human readable name for the item. - """ - with self.lock: - self.add_node(jid, node) - self.get_node(jid, node)['items'].add_item( - data.get('ijid', ''), - node=data.get('inode', ''), - name=data.get('name', '')) - - def del_item(self, jid, node, ifrom, data): - """ - Remove an item from a JID/node combination. - - The data parameter may include: - ijid -- JID of the item to remove. - inode -- Optional extra identifying information. - """ - with self.lock: - if self.node_exists(jid, node): - self.get_node(jid, node)['items'].del_item( - data.get('ijid', ''), - node=data.get('inode', None)) - - def cache_info(self, jid, node, ifrom, data): - """ - Cache disco information for an external JID. - - The data parameter is the Iq result stanza - containing the disco info to cache, or - the disco#info substanza itself. - """ - with self.lock: - if isinstance(data, Iq): - data = data['disco_info'] - - self.add_node(jid, node, ifrom) - self.get_node(jid, node, ifrom)['info'] = data - - def get_cached_info(self, jid, node, ifrom, data): - """ - Retrieve cached disco info data. - - The data parameter is not used. - """ - with self.lock: - if not self.node_exists(jid, node, ifrom): - return None - else: - return self.get_node(jid, node, ifrom)['info'] |