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.
This commit is contained in:
10
sleekxmpp/plugins/xep_0030/stanza/__init__.py
Normal file
10
sleekxmpp/plugins/xep_0030/stanza/__init__.py
Normal file
@@ -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
|
0
sleekxmpp/plugins/xep_0030/stanza/disco.py
Normal file
0
sleekxmpp/plugins/xep_0030/stanza/disco.py
Normal file
262
sleekxmpp/plugins/xep_0030/stanza/info.py
Normal file
262
sleekxmpp/plugins/xep_0030/stanza/info.py
Normal file
@@ -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 <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)
|
138
sleekxmpp/plugins/xep_0030/stanza/items.py
Normal file
138
sleekxmpp/plugins/xep_0030/stanza/items.py
Normal file
@@ -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:
|
||||
<iq type="get">
|
||||
<query xmlns="http://jabber.org/protocol/disco#items" />
|
||||
</iq>
|
||||
|
||||
<iq type="result">
|
||||
<query xmlns="http://jabber.org/protocol/disco#items">
|
||||
<item jid="chat.example.com"
|
||||
node="xmppdev"
|
||||
name="XMPP Dev" />
|
||||
<item jid="chat.example.com"
|
||||
node="sleekdev"
|
||||
name="SleekXMPP Dev" />
|
||||
</query>
|
||||
</iq>
|
||||
|
||||
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)
|
Reference in New Issue
Block a user