mirror of
https://github.com/correl/SleekXMPP.git
synced 2024-11-30 11:09:56 +00:00
Merge branch 'develop' into stream_features
This commit is contained in:
commit
306bdd8021
9 changed files with 336 additions and 72 deletions
1
setup.py
1
setup.py
|
@ -52,6 +52,7 @@ packages = [ 'sleekxmpp',
|
|||
'sleekxmpp/plugins/xep_0059',
|
||||
'sleekxmpp/plugins/xep_0085',
|
||||
'sleekxmpp/plugins/xep_0092',
|
||||
'sleekxmpp/plugins/xep_0128',
|
||||
'sleekxmpp/plugins/xep_0199',
|
||||
]
|
||||
|
||||
|
|
|
@ -60,10 +60,13 @@ class xep_0030(base_plugin):
|
|||
disco_items_query -- Received a disco#items Iq query request.
|
||||
|
||||
Attributes:
|
||||
stanza -- A reference to the module containing the stanza classes
|
||||
provided by this plugin.
|
||||
static -- Object containing the default set of static node handlers.
|
||||
xmpp -- The main SleekXMPP object.
|
||||
stanza -- A reference to the module containing the
|
||||
stanza classes provided by this plugin.
|
||||
static -- Object containing the default set of
|
||||
static node handlers.
|
||||
default_handlers -- A dictionary mapping operations to the default
|
||||
global handler (by default, the static handlers).
|
||||
xmpp -- The main SleekXMPP object.
|
||||
|
||||
Methods:
|
||||
set_node_handler -- Assign a handler to a JID/node combination.
|
||||
|
@ -110,11 +113,10 @@ class xep_0030(base_plugin):
|
|||
'add_identity', 'del_identity', 'add_feature',
|
||||
'del_feature', 'add_item', 'del_item',
|
||||
'del_identities', 'del_features']
|
||||
self.default_handlers = {}
|
||||
self._handlers = {}
|
||||
for op in self._disco_ops:
|
||||
self._handlers[op] = {'global': getattr(self.static, op),
|
||||
'jid': {},
|
||||
'node': {}}
|
||||
self._add_disco_op(op, getattr(self.static, op))
|
||||
|
||||
def post_init(self):
|
||||
"""Handle cross-plugin dependencies."""
|
||||
|
@ -123,6 +125,12 @@ class xep_0030(base_plugin):
|
|||
register_stanza_plugin(DiscoItems,
|
||||
self.xmpp['xep_0059'].stanza.Set)
|
||||
|
||||
def _add_disco_op(self, op, default_handler):
|
||||
self.default_handlers[op] = default_handler
|
||||
self._handlers[op] = {'global': default_handler,
|
||||
'jid': {},
|
||||
'node': {}}
|
||||
|
||||
def set_node_handler(self, htype, jid=None, node=None, handler=None):
|
||||
"""
|
||||
Add a node handler for the given hierarchy level and
|
||||
|
@ -205,26 +213,29 @@ class xep_0030(base_plugin):
|
|||
"""
|
||||
self.set_node_handler(htype, jid, node, None)
|
||||
|
||||
def make_static(self, jid=None, node=None, handlers=None):
|
||||
def restore_defaults(self, jid=None, node=None, handlers=None):
|
||||
"""
|
||||
Change all of a node's handlers to the default static
|
||||
Change all or some of a node's handlers to the default
|
||||
handlers. Useful for manually overriding the contents
|
||||
of a node that would otherwise be handled by a JID level
|
||||
or global level dynamic handler.
|
||||
|
||||
The default is to use the built-in static handlers, but that
|
||||
may be changed by modifying self.default_handlers.
|
||||
|
||||
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
|
||||
default version. If provided, only these
|
||||
handlers will be changed. Otherwise, all
|
||||
handlers will use the static version.
|
||||
handlers will use the default 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))
|
||||
self.set_node_handler(op, jid, node, self.default_handlers[op])
|
||||
|
||||
def get_info(self, jid=None, node=None, local=False, **kwargs):
|
||||
"""
|
||||
|
@ -609,3 +620,4 @@ class xep_0030(base_plugin):
|
|||
# Retain some backwards compatibility
|
||||
xep_0030.getInfo = xep_0030.get_info
|
||||
xep_0030.getItems = xep_0030.get_items
|
||||
xep_0030.make_static = xep_0030.restore_defaults
|
||||
|
|
|
@ -120,7 +120,8 @@ class StaticDisco(object):
|
|||
"""
|
||||
Replace the stored items data for a JID/node combination.
|
||||
|
||||
The data parameter is not used.
|
||||
The data parameter may provided:
|
||||
items -- A set of items in tuple format.
|
||||
"""
|
||||
items = data.get('items', set())
|
||||
self.add_node(jid, node)
|
||||
|
|
|
@ -1,51 +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
|
||||
from . xep_0030 import DiscoInfo, DiscoItems
|
||||
from . xep_0004 import Form
|
||||
|
||||
|
||||
class xep_0128(base.base_plugin):
|
||||
"""
|
||||
XEP-0128 Service Discovery Extensions
|
||||
"""
|
||||
|
||||
def plugin_init(self):
|
||||
self.xep = '0128'
|
||||
self.description = 'Service Discovery Extensions'
|
||||
|
||||
registerStanzaPlugin(DiscoInfo, Form)
|
||||
registerStanzaPlugin(DiscoItems, Form)
|
||||
|
||||
def extend_info(self, node, data=None):
|
||||
if data is None:
|
||||
data = {}
|
||||
node = self.xmpp['xep_0030'].nodes.get(node, None)
|
||||
if node is None:
|
||||
self.xmpp['xep_0030'].add_node(node)
|
||||
|
||||
info = node.info
|
||||
info['form']['type'] = 'result'
|
||||
info['form'].setFields(data, default=None)
|
||||
|
||||
def extend_items(self, node, data=None):
|
||||
if data is None:
|
||||
data = {}
|
||||
node = self.xmpp['xep_0030'].nodes.get(node, None)
|
||||
if node is None:
|
||||
self.xmpp['xep_0030'].add_node(node)
|
||||
|
||||
items = node.items
|
||||
items['form']['type'] = 'result'
|
||||
items['form'].setFields(data, default=None)
|
10
sleekxmpp/plugins/xep_0128/__init__.py
Normal file
10
sleekxmpp/plugins/xep_0128/__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_0128.static import StaticExtendedDisco
|
||||
from sleekxmpp.plugins.xep_0128.extended_disco import xep_0128
|
101
sleekxmpp/plugins/xep_0128/extended_disco.py
Normal file
101
sleekxmpp/plugins/xep_0128/extended_disco.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
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.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins.base import base_plugin
|
||||
from sleekxmpp.plugins.xep_0004 import Form
|
||||
from sleekxmpp.plugins.xep_0030 import DiscoInfo
|
||||
from sleekxmpp.plugins.xep_0128 import StaticExtendedDisco
|
||||
|
||||
|
||||
class xep_0128(base_plugin):
|
||||
|
||||
"""
|
||||
XEP-0128: Service Discovery Extensions
|
||||
|
||||
Allow the use of data forms to add additional identity
|
||||
information to disco#info results.
|
||||
|
||||
Also see <http://www.xmpp.org/extensions/xep-0128.html>.
|
||||
|
||||
Attributes:
|
||||
disco -- A reference to the XEP-0030 plugin.
|
||||
static -- Object containing the default set of static
|
||||
node handlers.
|
||||
xmpp -- The main SleekXMPP object.
|
||||
|
||||
Methods:
|
||||
set_extended_info -- Set extensions to a disco#info result.
|
||||
add_extended_info -- Add an extension to a disco#info result.
|
||||
del_extended_info -- Remove all extensions from a disco#info result.
|
||||
"""
|
||||
|
||||
def plugin_init(self):
|
||||
"""Start the XEP-0128 plugin."""
|
||||
self.xep = '0128'
|
||||
self.description = 'Service Discovery Extensions'
|
||||
|
||||
self._disco_ops = ['set_extended_info',
|
||||
'add_extended_info',
|
||||
'del_extended_info']
|
||||
|
||||
register_stanza_plugin(DiscoInfo, Form, iterable=True)
|
||||
|
||||
def post_init(self):
|
||||
"""Handle cross-plugin dependencies."""
|
||||
base_plugin.post_init(self)
|
||||
self.disco = self.xmpp['xep_0030']
|
||||
self.static = StaticExtendedDisco(self.disco.static)
|
||||
|
||||
self.disco.set_extended_info = self.set_extended_info
|
||||
self.disco.add_extended_info = self.add_extended_info
|
||||
self.disco.del_extended_info = self.del_extended_info
|
||||
|
||||
for op in self._disco_ops:
|
||||
self.disco._add_disco_op(op, getattr(self.static, op))
|
||||
|
||||
def set_extended_info(self, jid=None, node=None, **kwargs):
|
||||
"""
|
||||
Set additional, extended identity information to a node.
|
||||
|
||||
Replaces any existing extended information.
|
||||
|
||||
Arguments:
|
||||
jid -- The JID to modify.
|
||||
node -- The node to modify.
|
||||
data -- Either a form, or a list of forms to use
|
||||
as extended information, replacing any
|
||||
existing extensions.
|
||||
"""
|
||||
self.disco._run_node_handler('set_extended_info', jid, node, kwargs)
|
||||
|
||||
def add_extended_info(self, jid=None, node=None, **kwargs):
|
||||
"""
|
||||
Add additional, extended identity information to a node.
|
||||
|
||||
Arguments:
|
||||
jid -- The JID to modify.
|
||||
node -- The node to modify.
|
||||
data -- Either a form, or a list of forms to add
|
||||
as extended information.
|
||||
"""
|
||||
self.disco._run_node_handler('add_extended_info', jid, node, kwargs)
|
||||
|
||||
def del_extended_info(self, jid=None, node=None, **kwargs):
|
||||
"""
|
||||
Remove all extended identity information to a node.
|
||||
|
||||
Arguments:
|
||||
jid -- The JID to modify.
|
||||
node -- The node to modify.
|
||||
"""
|
||||
self.disco._run_node_handler('del_extended_info', jid, node, kwargs)
|
72
sleekxmpp/plugins/xep_0128/static.py
Normal file
72
sleekxmpp/plugins/xep_0128/static.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
"""
|
||||
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.plugins.xep_0030 import StaticDisco
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StaticExtendedDisco(object):
|
||||
|
||||
"""
|
||||
Extend the default StaticDisco implementation to provide
|
||||
support for extended identity information.
|
||||
"""
|
||||
|
||||
def __init__(self, static):
|
||||
"""
|
||||
Augment the default XEP-0030 static handler object.
|
||||
|
||||
Arguments:
|
||||
static -- The default static XEP-0030 handler object.
|
||||
"""
|
||||
self.static = static
|
||||
|
||||
def set_extended_info(self, jid, node, data):
|
||||
"""
|
||||
Replace the extended identity data for a JID/node combination.
|
||||
|
||||
The data parameter may provide:
|
||||
data -- Either a single data form, or a list of data forms.
|
||||
"""
|
||||
self.del_extended_info(jid, node, data)
|
||||
self.add_extended_info(jid, node, data)
|
||||
|
||||
def add_extended_info(self, jid, node, data):
|
||||
"""
|
||||
Add additional extended identity data for a JID/node combination.
|
||||
|
||||
The data parameter may provide:
|
||||
data -- Either a single data form, or a list of data forms.
|
||||
"""
|
||||
self.static.add_node(jid, node)
|
||||
|
||||
forms = data.get('data', [])
|
||||
if not isinstance(forms, list):
|
||||
forms = [forms]
|
||||
|
||||
for form in forms:
|
||||
self.static.nodes[(jid, node)]['info'].append(form)
|
||||
|
||||
def del_extended_info(self, jid, node, data):
|
||||
"""
|
||||
Replace the extended identity data for a JID/node combination.
|
||||
|
||||
The data parameter is not used.
|
||||
"""
|
||||
if (jid, node) not in self.static.nodes:
|
||||
return
|
||||
|
||||
info = self.static.nodes[(jid, node)]['info']
|
||||
|
||||
for form in info['substanza']:
|
||||
info.xml.remove(form.xml)
|
|
@ -40,6 +40,7 @@ def register_stanza_plugin(stanza, plugin, iterable=False):
|
|||
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
|
||||
stanza.plugin_tag_map[tag] = plugin
|
||||
if iterable:
|
||||
stanza.plugin_iterables = stanza.plugin_iterables.copy()
|
||||
stanza.plugin_iterables.add(plugin)
|
||||
|
||||
|
||||
|
@ -206,7 +207,7 @@ class ElementBase(object):
|
|||
plugin_attrib_map = {}
|
||||
plugin_iterables = set()
|
||||
plugin_tag_map = {}
|
||||
subitem = None
|
||||
subitem = set()
|
||||
is_extension = False
|
||||
xml_ns = 'http://www.w3.org/XML/1998/namespace'
|
||||
|
||||
|
@ -231,6 +232,10 @@ class ElementBase(object):
|
|||
ElementBase.values = property(ElementBase._get_stanza_values,
|
||||
ElementBase._set_stanza_values)
|
||||
|
||||
if self.subitem is not None:
|
||||
for sub in self.subitem:
|
||||
self.plugin_iterables.add(sub)
|
||||
|
||||
if self.setup(xml):
|
||||
# If we generated our own XML, then everything is ready.
|
||||
return
|
||||
|
@ -240,9 +245,6 @@ class ElementBase(object):
|
|||
if child.tag in self.plugin_tag_map:
|
||||
plugin = self.plugin_tag_map[child.tag]
|
||||
self.plugins[plugin.plugin_attrib] = plugin(child, self)
|
||||
if self.subitem is not None:
|
||||
for sub in self.subitem:
|
||||
self.plugin_iterables.add(sub)
|
||||
for sub in self.plugin_iterables:
|
||||
if child.tag == "{%s}%s" % (sub.namespace, sub.name):
|
||||
self.iterables.append(sub(child, self))
|
||||
|
@ -333,11 +335,20 @@ class ElementBase(object):
|
|||
Plugin interfaces may accept a nested dictionary that
|
||||
will be used recursively.
|
||||
"""
|
||||
iterable_interfaces = [p.plugin_attrib for \
|
||||
p in self.plugin_iterables]
|
||||
|
||||
for interface, value in values.items():
|
||||
if interface == 'substanzas':
|
||||
# Remove existing substanzas
|
||||
for stanza in self.iterables:
|
||||
self.xml.remove(stanza.xml)
|
||||
self.iterables = []
|
||||
|
||||
# Add new substanzas
|
||||
for subdict in value:
|
||||
if '__childtag__' in subdict:
|
||||
for subclass in self.subitem:
|
||||
for subclass in self.plugin_iterables:
|
||||
child_tag = "{%s}%s" % (subclass.namespace,
|
||||
subclass.name)
|
||||
if subdict['__childtag__'] == child_tag:
|
||||
|
@ -348,9 +359,10 @@ class ElementBase(object):
|
|||
elif interface in self.interfaces:
|
||||
self[interface] = value
|
||||
elif interface in self.plugin_attrib_map:
|
||||
if interface not in self.plugins:
|
||||
self.init_plugin(interface)
|
||||
self.plugins[interface].values = value
|
||||
if interface not in iterable_interfaces:
|
||||
if interface not in self.plugins:
|
||||
self.init_plugin(interface)
|
||||
self.plugins[interface].values = value
|
||||
return self
|
||||
|
||||
def __getitem__(self, attrib):
|
||||
|
|
106
tests/test_stream_xep_0128.py
Normal file
106
tests/test_stream_xep_0128.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
import sys
|
||||
import time
|
||||
import threading
|
||||
|
||||
from sleekxmpp.test import *
|
||||
from sleekxmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class TestStreamExtendedDisco(SleekTest):
|
||||
|
||||
"""
|
||||
Test using the XEP-0128 plugin.
|
||||
"""
|
||||
|
||||
def tearDown(self):
|
||||
sys.excepthook = sys.__excepthook__
|
||||
self.stream_close()
|
||||
|
||||
def testUsingExtendedInfo(self):
|
||||
self.stream_start(mode='client',
|
||||
jid='tester@localhost',
|
||||
plugins=['xep_0030',
|
||||
'xep_0004',
|
||||
'xep_0128'])
|
||||
|
||||
form = self.xmpp['xep_0004'].makeForm(ftype='result')
|
||||
form.addField(var='FORM_TYPE', ftype='hidden', value='testing')
|
||||
|
||||
info_ns = 'http://jabber.org/protocol/disco#info'
|
||||
self.xmpp['xep_0030'].add_identity(node='test',
|
||||
category='client',
|
||||
itype='bot')
|
||||
self.xmpp['xep_0030'].add_feature(node='test', feature=info_ns)
|
||||
self.xmpp['xep_0128'].set_extended_info(node='test', data=form)
|
||||
|
||||
self.recv("""
|
||||
<iq type="get" id="test" to="tester@localhost">
|
||||
<query xmlns="http://jabber.org/protocol/disco#info"
|
||||
node="test" />
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.send("""
|
||||
<iq type="result" id="test">
|
||||
<query xmlns="http://jabber.org/protocol/disco#info"
|
||||
node="test">
|
||||
<identity category="client" type="bot" />
|
||||
<feature var="http://jabber.org/protocol/disco#info" />
|
||||
<x xmlns="jabber:x:data" type="result">
|
||||
<field var="FORM_TYPE" type="hidden">
|
||||
<value>testing</value>
|
||||
</field>
|
||||
</x>
|
||||
</query>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testUsingMultipleExtendedInfo(self):
|
||||
self.stream_start(mode='client',
|
||||
jid='tester@localhost',
|
||||
plugins=['xep_0030',
|
||||
'xep_0004',
|
||||
'xep_0128'])
|
||||
|
||||
form1 = self.xmpp['xep_0004'].makeForm(ftype='result')
|
||||
form1.addField(var='FORM_TYPE', ftype='hidden', value='testing')
|
||||
|
||||
form2 = self.xmpp['xep_0004'].makeForm(ftype='result')
|
||||
form2.addField(var='FORM_TYPE', ftype='hidden', value='testing_2')
|
||||
|
||||
info_ns = 'http://jabber.org/protocol/disco#info'
|
||||
self.xmpp['xep_0030'].add_identity(node='test',
|
||||
category='client',
|
||||
itype='bot')
|
||||
self.xmpp['xep_0030'].add_feature(node='test', feature=info_ns)
|
||||
self.xmpp['xep_0128'].set_extended_info(node='test', data=[form1, form2])
|
||||
|
||||
self.recv("""
|
||||
<iq type="get" id="test" to="tester@localhost">
|
||||
<query xmlns="http://jabber.org/protocol/disco#info"
|
||||
node="test" />
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.send("""
|
||||
<iq type="result" id="test">
|
||||
<query xmlns="http://jabber.org/protocol/disco#info"
|
||||
node="test">
|
||||
<identity category="client" type="bot" />
|
||||
<feature var="http://jabber.org/protocol/disco#info" />
|
||||
<x xmlns="jabber:x:data" type="result">
|
||||
<field var="FORM_TYPE" type="hidden">
|
||||
<value>testing</value>
|
||||
</field>
|
||||
</x>
|
||||
<x xmlns="jabber:x:data" type="result">
|
||||
<field var="FORM_TYPE" type="hidden">
|
||||
<value>testing_2</value>
|
||||
</field>
|
||||
</x>
|
||||
</query>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamExtendedDisco)
|
Loading…
Reference in a new issue