f4451fe6b7
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.
262 lines
9.7 KiB
Python
262 lines
9.7 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):
|
|
"""
|
|
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)
|