From 291b118aca29b32679f1b2e55d0de98918fe4455 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 18 Nov 2010 11:22:11 -0500 Subject: XEP-0030 bug fixes. --- sleekxmpp/plugins/xep_0030.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'sleekxmpp/plugins/xep_0030.py') diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index e3589077..c8050809 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -253,7 +253,8 @@ class xep_0030(base.base_plugin): A default handler for disco#info requests. If another handler is registered, this one will defer and not run. """ - if not forwarded and self.xmpp.event_handled('disco_info_request'): + if not forwarded and \ + self.xmpp.event_handled('disco_info_request') > 1: return node_name = iq['disco_info']['node'] @@ -281,7 +282,8 @@ class xep_0030(base.base_plugin): If this handler is called by your own custom handler with forwarded set to True, then it will run as normal. """ - if not forwarded and self.xmpp.event_handled('disco_items_request'): + if not forwarded and \ + self.xmpp.event_handled('disco_items_request') > 1: return node_name = iq['disco_items']['node'] -- cgit v1.2.3 From ab25301953138343d3d295aaa8872de9c5bc2cf9 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 18 Nov 2010 15:50:45 -0500 Subject: Adding stream tests for XEP-0030. Fixed some errors when responding to disco requests. --- sleekxmpp/plugins/xep_0030.py | 51 ++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 17 deletions(-) (limited to 'sleekxmpp/plugins/xep_0030.py') diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index c8050809..59c60e66 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -71,11 +71,12 @@ class DiscoInfo(ElementBase): for idXML in idsXML: self.xml.remove(idXML) - def addIdentity(self, category, id_type, name=''): - idXML = ET.Element('{%s}identity' % self.namespace, - {'category': category, - 'type': id_type, - 'name': name}) + def addIdentity(self, category, itype, name=''): + idXML = ET.Element('{%s}identity' % self.namespace) + idXML.attrib['category'] = category + idXML.attrib['type'] = itype + if name: + idXML.attrib['name'] = name self.xml.append(idXML) def delIdentity(self, category, id_type, name=''): @@ -213,8 +214,11 @@ class xep_0030(base.base_plugin): self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) + + self.nodes = {} - self.nodes = {'main': DiscoNode('main')} + self.add_node('') + self.add_feature('http://jabber.org/protocol/disco#info', node='') def add_node(self, node): if node not in self.nodes: @@ -258,14 +262,30 @@ class xep_0030(base.base_plugin): return node_name = iq['disco_info']['node'] - if not node_name: - node_name = 'main' - log.debug("Using default handler for disco#info on node '%s'." % node_name) if node_name in self.nodes: node = self.nodes[node_name] - iq.reply().setPayload(node.info.xml).send() + iq.reply() + iq['disco_info']['node'] = node_name + + identities = node.info['identities'] + if identities: + iq['disco_info']['identities'] = identities + else: + if self.xmpp.is_component: + iq['disco_info'].addIdentity( + category='component', + itype='generic') + else: + iq['disco_info'].addIdentity( + category='client', + itype='bot') + log.info("No identity found for node '%'," + \ + "using default, generic identity") + + iq['disco_info']['features'] = node.info['features'] + iq.send() else: log.debug("Node %s requested, but does not exist." % node_name) iq.reply().error().setPayload(iq['disco_info'].xml) @@ -287,10 +307,7 @@ class xep_0030(base.base_plugin): return node_name = iq['disco_items']['node'] - if not node_name: - node_name = 'main' - - log.debug("Using default handler for disco#items on node '%s'." % node_name) + log.debug("Using default handler for disco#items on node: '%s'." % node_name) if node_name in self.nodes: node = self.nodes[node_name] @@ -321,17 +338,17 @@ class xep_0030(base.base_plugin): iq['disco_items']['node'] = node return iq.send() - def add_feature(self, feature, node='main'): + def add_feature(self, feature, node=''): self.add_node(node) self.nodes[node].addFeature(feature) - def add_identity(self, category='', itype='', name='', node='main'): + def add_identity(self, category='', itype='', name='', node=''): self.add_node(node) self.nodes[node].addIdentity(category=category, id_type=itype, name=name) - def add_item(self, jid=None, name='', node='main', subnode=''): + def add_item(self, jid=None, name='', node='', subnode=''): self.add_node(node) self.add_node(subnode) if jid is None: -- cgit v1.2.3 From 8ead33fc3bdb75312c3112db5001cf9544566efb Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 18 Nov 2010 16:23:18 -0500 Subject: Fixed typo --- sleekxmpp/plugins/xep_0030.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sleekxmpp/plugins/xep_0030.py') diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 59c60e66..3253bb68 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -345,7 +345,7 @@ class xep_0030(base.base_plugin): def add_identity(self, category='', itype='', name='', node=''): self.add_node(node) self.nodes[node].addIdentity(category=category, - id_type=itype, + itype=itype, name=name) def add_item(self, jid=None, name='', node='', subnode=''): -- cgit v1.2.3 From f4451fe6b72f7cfb9680ead7a608d5ca1bc7e753 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 9 Dec 2010 18:57:27 -0500 Subject: 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. --- sleekxmpp/plugins/xep_0030.py | 356 ------------------------------------------ 1 file changed, 356 deletions(-) delete mode 100644 sleekxmpp/plugins/xep_0030.py (limited to 'sleekxmpp/plugins/xep_0030.py') diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py deleted file mode 100644 index 3253bb68..00000000 --- a/sleekxmpp/plugins/xep_0030.py +++ /dev/null @@ -1,356 +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 . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.iq import Iq - - -log = logging.getLogger(__name__) - - -class DiscoInfo(ElementBase): - namespace = 'http://jabber.org/protocol/disco#info' - name = 'query' - plugin_attrib = 'disco_info' - interfaces = set(('node', 'features', 'identities')) - - def getFeatures(self): - features = [] - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for feature in featuresXML: - features.append(feature.attrib['var']) - return features - - def setFeatures(self, features): - self.delFeatures() - for name in features: - self.addFeature(name) - - def delFeatures(self): - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for feature in featuresXML: - self.xml.remove(feature) - - def addFeature(self, feature): - featureXML = ET.Element('{%s}feature' % self.namespace, - {'var': feature}) - self.xml.append(featureXML) - - def delFeature(self, feature): - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for featureXML in featuresXML: - if featureXML.attrib['var'] == feature: - self.xml.remove(featureXML) - - def getIdentities(self): - ids = [] - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - idData = (idXML.attrib['category'], - idXML.attrib['type'], - idXML.attrib.get('name', '')) - ids.append(idData) - return ids - - def setIdentities(self, ids): - self.delIdentities() - for idData in ids: - self.addIdentity(*idData) - - def delIdentities(self): - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - self.xml.remove(idXML) - - def addIdentity(self, category, itype, name=''): - idXML = ET.Element('{%s}identity' % self.namespace) - idXML.attrib['category'] = category - idXML.attrib['type'] = itype - if name: - idXML.attrib['name'] = name - self.xml.append(idXML) - - def delIdentity(self, category, id_type, name=''): - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - idData = (idXML.attrib['category'], - idXML.attrib['type']) - delId = (category, id_type) - if idData == delId: - self.xml.remove(idXML) - - -class DiscoItems(ElementBase): - namespace = 'http://jabber.org/protocol/disco#items' - name = 'query' - plugin_attrib = 'disco_items' - interfaces = set(('node', 'items')) - - def getItems(self): - items = [] - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for item in itemsXML: - itemData = (item.attrib['jid'], - item.attrib.get('node'), - item.attrib.get('name')) - items.append(itemData) - return items - - def setItems(self, items): - self.delItems() - for item in items: - self.addItem(*item) - - def delItems(self): - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for item in itemsXML: - self.xml.remove(item) - - def addItem(self, jid, node='', name=''): - itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid}) - if name: - itemXML.attrib['name'] = name - if node: - itemXML.attrib['node'] = node - self.xml.append(itemXML) - - def delItem(self, jid, node=''): - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for itemXML in itemsXML: - itemData = (itemXML.attrib['jid'], - itemXML.attrib.get('node', '')) - itemDel = (jid, node) - if itemData == itemDel: - self.xml.remove(itemXML) - - -class DiscoNode(object): - """ - Collection object for grouping info and item information - into nodes. - """ - def __init__(self, name): - self.name = name - self.info = DiscoInfo() - self.items = DiscoItems() - - self.info['node'] = name - self.items['node'] = name - - # This is a bit like poor man's inheritance, but - # to simplify adding information to the node we - # map node functions to either the info or items - # stanza objects. - # - # We don't want to make DiscoNode inherit from - # DiscoInfo and DiscoItems because DiscoNode is - # not an actual stanza, and doing so would create - # confusion and potential bugs. - - self._map(self.items, 'items', ['get', 'set', 'del']) - self._map(self.items, 'item', ['add', 'del']) - self._map(self.info, 'identities', ['get', 'set', 'del']) - self._map(self.info, 'identity', ['add', 'del']) - self._map(self.info, 'features', ['get', 'set', 'del']) - self._map(self.info, 'feature', ['add', 'del']) - - def isEmpty(self): - """ - Test if the node contains any information. Useful for - determining if a node can be deleted. - """ - ids = self.getIdentities() - features = self.getFeatures() - items = self.getItems() - - if not ids and not features and not items: - return True - return False - - def _map(self, obj, interface, access): - """ - Map functions of the form obj.accessInterface - to self.accessInterface for each given access type. - """ - interface = interface.title() - for access_type in access: - method = access_type + interface - if hasattr(obj, method): - setattr(self, method, getattr(obj, method)) - - -class xep_0030(base.base_plugin): - """ - XEP-0030 Service Discovery - """ - - def plugin_init(self): - self.xep = '0030' - self.description = 'Service Discovery' - - self.xmpp.registerHandler( - Callback('Disco Items', - MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, - DiscoItems.namespace)), - self.handle_item_query)) - - self.xmpp.registerHandler( - Callback('Disco Info', - MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, - DiscoInfo.namespace)), - self.handle_info_query)) - - registerStanzaPlugin(Iq, DiscoInfo) - registerStanzaPlugin(Iq, DiscoItems) - - self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) - self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) - - self.nodes = {} - - self.add_node('') - self.add_feature('http://jabber.org/protocol/disco#info', node='') - - def add_node(self, node): - if node not in self.nodes: - self.nodes[node] = DiscoNode(node) - - def del_node(self, node): - if node in self.nodes: - del self.nodes[node] - - def rename_node(self, node, new_name): - if new_name not in self.nodes and node in self.nodes: - self.nodes[new_name] = self.nodes[node] - self.nodes[new_name].name = new_name - self.nodes[new_name].info['node'] = new_name - self.nodes[new_name].items['node'] = new_name - self.del_node(node) - - def handle_item_query(self, iq): - if iq['type'] == 'get': - log.debug("Items requested by %s" % iq['from']) - self.xmpp.event('disco_items_request', iq) - elif iq['type'] == 'result': - log.debug("Items result from %s" % iq['from']) - self.xmpp.event('disco_items', iq) - - def handle_info_query(self, iq): - if iq['type'] == 'get': - log.debug("Info requested by %s" % iq['from']) - self.xmpp.event('disco_info_request', iq) - elif iq['type'] == 'result': - log.debug("Info result from %s" % iq['from']) - self.xmpp.event('disco_info', iq) - - def handle_disco_info(self, iq, forwarded=False): - """ - A default handler for disco#info requests. If another - handler is registered, this one will defer and not run. - """ - if not forwarded and \ - self.xmpp.event_handled('disco_info_request') > 1: - return - - node_name = iq['disco_info']['node'] - log.debug("Using default handler for disco#info on node '%s'." % node_name) - - if node_name in self.nodes: - node = self.nodes[node_name] - iq.reply() - iq['disco_info']['node'] = node_name - - identities = node.info['identities'] - if identities: - iq['disco_info']['identities'] = identities - else: - if self.xmpp.is_component: - iq['disco_info'].addIdentity( - category='component', - itype='generic') - else: - iq['disco_info'].addIdentity( - category='client', - itype='bot') - log.info("No identity found for node '%'," + \ - "using default, generic identity") - - iq['disco_info']['features'] = node.info['features'] - iq.send() - else: - log.debug("Node %s requested, but does not exist." % node_name) - iq.reply().error().setPayload(iq['disco_info'].xml) - iq['error']['code'] = '404' - iq['error']['type'] = 'cancel' - iq['error']['condition'] = 'item-not-found' - iq.send() - - def handle_disco_items(self, iq, forwarded=False): - """ - A default handler for disco#items requests. If another - handler is registered, this one will defer and not run. - - If this handler is called by your own custom handler with - forwarded set to True, then it will run as normal. - """ - if not forwarded and \ - self.xmpp.event_handled('disco_items_request') > 1: - return - - node_name = iq['disco_items']['node'] - log.debug("Using default handler for disco#items on node: '%s'." % node_name) - - if node_name in self.nodes: - node = self.nodes[node_name] - iq.reply().setPayload(node.items.xml).send() - else: - log.debug("Node %s requested, but does not exist." % node_name) - iq.reply().error().setPayload(iq['disco_items'].xml) - iq['error']['code'] = '404' - iq['error']['type'] = 'cancel' - iq['error']['condition'] = 'item-not-found' - iq.send() - - # Older interface methods for backwards compatibility - - def getInfo(self, jid, node='', dfrom=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = jid - iq['from'] = dfrom - iq['disco_info']['node'] = node - return iq.send() - - def getItems(self, jid, node='', dfrom=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = jid - iq['from'] = dfrom - iq['disco_items']['node'] = node - return iq.send() - - def add_feature(self, feature, node=''): - self.add_node(node) - self.nodes[node].addFeature(feature) - - def add_identity(self, category='', itype='', name='', node=''): - self.add_node(node) - self.nodes[node].addIdentity(category=category, - itype=itype, - name=name) - - def add_item(self, jid=None, name='', node='', subnode=''): - self.add_node(node) - self.add_node(subnode) - if jid is None: - jid = self.xmpp.fulljid - self.nodes[node].addItem(jid=jid, name=name, node=subnode) -- cgit v1.2.3