summaryrefslogtreecommitdiff
path: root/sleekxmpp/plugins/xep_0030/disco.py
diff options
context:
space:
mode:
authorLance Stout <lancestout@gmail.com>2010-12-09 18:57:27 -0500
committerLance Stout <lancestout@gmail.com>2010-12-09 18:57:27 -0500
commitf4451fe6b72f7cfb9680ead7a608d5ca1bc7e753 (patch)
tree573a5444ac2e8a79c691c2a8dcafc65404f73ba5 /sleekxmpp/plugins/xep_0030/disco.py
parent8d4e77aba60457d44285f1bec0b5131eee7ff247 (diff)
downloadslixmpp-f4451fe6b72f7cfb9680ead7a608d5ca1bc7e753.tar.gz
slixmpp-f4451fe6b72f7cfb9680ead7a608d5ca1bc7e753.tar.bz2
slixmpp-f4451fe6b72f7cfb9680ead7a608d5ca1bc7e753.tar.xz
slixmpp-f4451fe6b72f7cfb9680ead7a608d5ca1bc7e753.zip
First pass at a new XEP-0030 plugin.
Now with dynamic node handling goodness. Some things are not quite working yet, in particular: set_items set_info set_identities set_features And still need more unit tests to round things out.
Diffstat (limited to 'sleekxmpp/plugins/xep_0030/disco.py')
-rw-r--r--sleekxmpp/plugins/xep_0030/disco.py314
1 files changed, 314 insertions, 0 deletions
diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py
new file mode 100644
index 00000000..c323ba7c
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0030/disco.py
@@ -0,0 +1,314 @@
+"""
+ 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
+