""" 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 sleekxmpp from sleekxmpp import Iq from sleekxmpp.exceptions import XMPPError from sleekxmpp.plugins.base import base_plugin from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID from sleekxmpp.plugins.xep_0030 import DiscoInfo, DiscoItems, StaticDisco log = logging.getLogger(__name__) class xep_0030(base_plugin): """ XEP-0030: Service Discovery Stream Handlers: Disco Info -- Disco Items -- Events: disco_info -- disco_items -- disco_info_query -- disco_items_query -- Methods: set_node_handler -- del_node_handler -- add_identity -- del_identity -- add_feature -- del_feature -- add_item -- del_item -- get_info -- get_items -- """ def plugin_init(self): self.xep = '0030' self.description = 'Service Discovery' self.stanza = sleekxmpp.plugins.xep_0030.stanza 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._disco_ops = ['get_info', 'set_identities', 'set_features', 'del_info', 'get_items', 'set_items', 'del_items', 'add_identity', 'del_identity', 'add_feature', 'del_feature', 'add_item', 'del_item'] self.handlers = {} for op in self._disco_ops: self.handlers[op] = {'global': getattr(self.static, op), 'jid': {}, 'node': {}} def set_node_handler(self, htype, jid=None, node=None, handler=None): """ Arguments: htype jid node handler """ if htype not in self._disco_ops: return if jid is None and node is None: self.handlers[htype]['global'] = handler elif node is None: self.handlers[htype]['jid'][jid] = handler elif jid is None: jid = self.xmpp.boundjid.full self.handlers[htype]['node'][(jid, node)] = handler else: self.handlers[htype]['node'][(jid, node)] = handler def del_node_handler(self, htype, jid, node): """ Arguments: htype jid node """ self.set_node_handler(htype, jid, node, None) def make_static(self, jid=None, node=None, handlers=None): """ Change all of a node's handlers to the default static handlers. Useful for manually overriding the contents of a node that would otherwise be handled by a JID level or global level dynamic handler. 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 static version. If provided, only these handlers will be changed. Otherwise, all handlers will use the static version. """ if handlers is None: handlers = self._disco_ops for op in handlers: self.del_node_handler(op, jid, node) self.set_node_handler(op, jid, node, getattr(self.static, op)) def get_info(self, jid=None, node=None, local=False, **kwargs): """ Arguments: jid -- node -- local -- dfrom -- block -- timeout -- callback -- """ if local or jid is 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) iq = self.xmpp.Iq() iq['from'] = 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', None), callback=kwargs.get('callback', None)) def get_items(self, jid=None, node=None, local=False, **kwargs): """ Arguments: jid -- node -- local -- dfrom -- block -- timeout -- callback -- """ if local or jid is None: return self._run_node_handler('get_items', jid, node, kwargs) iq = self.xmpp.Iq() iq['from'] = kwargs.get('dfrom', '') iq['to'] = jid iq['type'] = 'get' iq['disco_items']['node'] = node if node else '' return iq.send(timeout=kwargs.get('timeout', None), block=kwargs.get('block', None), callback=kwargs.get('callback', None)) def set_info(self, jid=None, node=None, **kwargs): self._run_node_handler('set_info', jid, node, kwargs) def del_info(self, jid=None, node=None, **kwargs): self._run_node_handler('del_info', jid, node, kwargs) def set_items(self, jid=None, node=None, **kwargs): self._run_node_handler('set_items', jid, node, kwargs) def del_items(self, jid=None, node=None, **kwargs): self._run_node_handler('del_items', jid, node, kwargs) def add_identity(self, jid=None, node=None, **kwargs): self._run_node_handler('add_identity', jid, node, kwargs) def add_feature(self, jid=None, node=None, **kwargs): self._run_node_handler('add_feature', jid, node, kwargs) def del_identity(self, jid=None, node=None, **kwargs): self._run_node_handler('del_identity', jid, node, kwargs) def del_feature(self, jid=None, node=None, **kwargs): self._run_node_handler('del_feature', jid, node, kwargs) def add_item(self, jid=None, node=None, **kwargs): self._run_node_handler('add_item', jid, node, kwargs) def del_item(self, jid=None, node=None, **kwargs): self._run_node_handler('del_item', jid, node, kwargs) def _run_node_handler(self, htype, jid, node, data=None): """ 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. dat -- Optional, custom data to pass to the handler. """ if jid is None: jid = self.xmpp.boundjid.full 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 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._run_node_handler('get_info', iq['to'].full, iq['disco_info']['node'], iq) 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'])) 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._run_node_handler('get_items', iq['to'].full, iq['disco_items']['node']) 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. """ 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 info