277 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			277 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
|     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)
 | 
