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/__init__.py | 12 + sleekxmpp/plugins/xep_0030/disco.py | 314 ++++++++++++++++++++++++++ sleekxmpp/plugins/xep_0030/stanza/__init__.py | 10 + sleekxmpp/plugins/xep_0030/stanza/disco.py | 0 sleekxmpp/plugins/xep_0030/stanza/info.py | 262 +++++++++++++++++++++ sleekxmpp/plugins/xep_0030/stanza/items.py | 138 +++++++++++ sleekxmpp/plugins/xep_0030/static.py | 127 +++++++++++ 7 files changed, 863 insertions(+) create mode 100644 sleekxmpp/plugins/xep_0030/__init__.py create mode 100644 sleekxmpp/plugins/xep_0030/disco.py create mode 100644 sleekxmpp/plugins/xep_0030/stanza/__init__.py create mode 100644 sleekxmpp/plugins/xep_0030/stanza/disco.py create mode 100644 sleekxmpp/plugins/xep_0030/stanza/info.py create mode 100644 sleekxmpp/plugins/xep_0030/stanza/items.py create mode 100644 sleekxmpp/plugins/xep_0030/static.py (limited to 'sleekxmpp/plugins/xep_0030') diff --git a/sleekxmpp/plugins/xep_0030/__init__.py b/sleekxmpp/plugins/xep_0030/__init__.py new file mode 100644 index 00000000..2e183852 --- /dev/null +++ b/sleekxmpp/plugins/xep_0030/__init__.py @@ -0,0 +1,12 @@ +""" + 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 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 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 + diff --git a/sleekxmpp/plugins/xep_0030/stanza/__init__.py b/sleekxmpp/plugins/xep_0030/stanza/__init__.py new file mode 100644 index 00000000..0d97cf3d --- /dev/null +++ b/sleekxmpp/plugins/xep_0030/stanza/__init__.py @@ -0,0 +1,10 @@ +""" + 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/disco.py b/sleekxmpp/plugins/xep_0030/stanza/disco.py new file mode 100644 index 00000000..e69de29b diff --git a/sleekxmpp/plugins/xep_0030/stanza/info.py b/sleekxmpp/plugins/xep_0030/stanza/info.py new file mode 100644 index 00000000..6764acbb --- /dev/null +++ b/sleekxmpp/plugins/xep_0030/stanza/info.py @@ -0,0 +1,262 @@ +""" + 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 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 + 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: + + + + + + + + + + + + + + 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): + """ + 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. + """ + identities = set() + 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: + identities.add(( + id_xml.attrib['category'], + id_xml.attrib['type'], + id_xml.attrib.get('{%s}lang' % self.xml_ns, None), + id_xml.attrib.get('name', None))) + 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): + """Return the set of all supported features.""" + features = set() + for feature_xml in self.findall('{%s}feature' % self.namespace): + features.add(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 new file mode 100644 index 00000000..319e666f --- /dev/null +++ b/sleekxmpp/plugins/xep_0030/stanza/items.py @@ -0,0 +1,138 @@ +""" + 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 DiscoItems(ElementBase): + + """ + + + Example disco#items stanzas: + + + + + + + + + + + + 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_xml = ET.Element('{%s}item' % self.namespace) + item_xml.attrib['jid'] = jid + if name: + item_xml.attrib['name'] = name + if node: + item_xml.attrib['node'] = node + self.xml.append(item_xml) + 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_xml in self.findall('{%s}item' % self.namespace): + item = (item_xml.attrib['jid'], + item_xml.attrib.get('node'), + item_xml.attrib.get('name')) + items.add(item) + 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() + for item_xml in self.findall('{%s}item' % self.namespace): + self.xml.remove(item_xml) diff --git a/sleekxmpp/plugins/xep_0030/static.py b/sleekxmpp/plugins/xep_0030/static.py new file mode 100644 index 00000000..f3693228 --- /dev/null +++ b/sleekxmpp/plugins/xep_0030/static.py @@ -0,0 +1,127 @@ +""" + 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 + + +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. + """ + + def __init__(self, xmpp): + """ + Arguments: + xmpp -- The main SleekXMPP object. + """ + self.nodes = {} + self.xmpp = xmpp + + def add_node(self, jid=None, node=None): + if jid is None: + jid = self.xmpp.boundjid.full + if node is None: + node = '' + if (jid, node) not in self.nodes: + self.nodes[(jid, node)] = {'info': DiscoInfo(), + 'items': DiscoItems()} + self.nodes[(jid, node)]['info']['node'] = node + self.nodes[(jid, node)]['items']['node'] = node + + def get_info(self, jid, node, data=None): + if (jid, node) not in self.nodes: + if not node: + return DiscoInfo() + else: + raise XMPPError(condition='item-not-found') + else: + return self.nodes[(jid, node)]['info'] + + def del_info(self, jid, node, data=None): + if (jid, node) in self.nodes: + self.nodes[(jid, node)]['info'] = DiscoInfo() + + def get_items(self, jid, node, data=None): + if (jid, node) not in self.nodes: + if not node: + return DiscoInfo() + else: + raise XMPPError(condition='item-not-found') + else: + return self.nodes[(jid, node)]['items'] + + def set_items(self, jid, node, data=None): + pass + + def del_items(self, jid, node, data=None): + if (jid, node) in self.nodes: + self.nodes[(jid, node)]['items'] = DiscoItems() + + def add_identity(self, jid, node, data={}): + self.add_node(jid, node) + self.nodes[(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, data=None): + pass + + def del_identity(self, jid, node, data=None): + if (jid, node) not in self.nodes: + return + self.nodes[(jid, node)]['info'].del_identity( + data.get('category', ''), + data.get('itype', ''), + data.get('name', None), + data.get('lang', None)) + + + def add_feature(self, jid, node, data=None): + self.add_node(jid, node) + self.nodes[(jid, node)]['info'].add_feature(data.get('feature', '')) + + def set_features(self, jid, node, data=None): + pass + + def del_feature(self, jid, node, data=None): + if (jid, node) not in self.nodes: + return + self.nodes[(jid, node)]['info'].del_feature(data.get('feature', '')) + + def add_item(self, jid, node, data=None): + self.add_node(jid, node) + self.nodes[(jid, node)]['items'].add_item( + data.get('ijid', ''), + node=data.get('inode', None), + name=data.get('name', None)) + + def del_item(self, jid, node, data=None): + if (jid, node) in self.nodes: + self.nodes[(jid, node)]['items'].del_item(**data) + -- cgit v1.2.3