Begin experiment with a centralized API callback registry.

The API registry generalizes the node handler system from the xep_0030
plugin so that other plugins can use it.
This commit is contained in:
Lance Stout 2012-03-30 23:02:48 -07:00
parent 51e5aee830
commit 488f7ed886
6 changed files with 260 additions and 97 deletions

191
sleekxmpp/api.py Normal file
View File

@ -0,0 +1,191 @@
from sleekxmpp.xmlstream import JID
class APIWrapper(object):
def __init__(self, api, name):
self.api = api
self.name = name
def __getattr__(self, attr):
"""Curry API management commands with the API name."""
if attr == 'name':
return self.name
elif attr == 'settings':
return self.api.settings[self.name]
elif attr == 'register':
def curried_handler(handler, op, jid=None, node=None):
register = getattr(self.api, attr)
return register(handler, self.name, op, jid, node)
return curried_handler
elif attr == 'register_default':
def curried_handler(handler, op, jid=None, node=None):
return getattr(self.api, attr)(handler, self.name, op)
return curried_handler
elif attr in ('run', 'restore_default', 'unregister'):
def curried_handler(*args, **kwargs):
return getattr(self.api, attr)(self.name, *args, **kwargs)
return curried_handler
return None
def __getitem__(self, attr):
def curried_handler(jid=None, node=None, ifrom=None, args=None):
return self.api.run(self.name, attr, jid, node, ifrom, args)
return curried_handler
class APIRegistry(object):
def __init__(self, xmpp):
self._handlers = {}
self._handler_defaults = {}
self.xmpp = xmpp
self.settings = {}
def _setup(self, ctype, op):
"""Initialize the API callback dictionaries.
:param string ctype: The name of the API to initialize.
:param string op: The API operation to initialize.
"""
if ctype not in self.settings:
self.settings[ctype] = {}
if ctype not in self._handler_defaults:
self._handler_defaults[ctype] = {}
if ctype not in self._handlers:
self._handlers[ctype] = {}
if op not in self._handlers[ctype]:
self._handlers[ctype][op] = {'global': None,
'jid': {},
'node': {}}
def wrap(self, ctype):
"""Return a wrapper object that targets a specific API."""
return APIWrapper(self, ctype)
def purge(self, ctype):
"""Remove all information for a given API."""
del self.settings[ctype]
del self._handler_defaults[ctype]
del self._handlers[ctype]
def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None):
"""Execute an API callback, based on specificity.
The API callback that is executed is chosen based on the combination
of the provided JID and node:
JID | node | Handler
==============================
Given | Given | Node handler
Given | None | JID handler
None | None | Global handler
A node handler is responsible for servicing a single node at a single
JID, while a JID handler may respond for any node at a given JID, and
the global handler will answer to any JID+node combination.
Handlers should check that the JID ``ifrom`` is authorized to perform
the desired action.
:param string ctype: The name of the API to use.
:param string op: The API operation to perform.
:param JID jid: Optionally provide specific JID.
:param string node: Optionally provide specific node.
:param JID ifrom: Optionally provide the requesting JID.
:param tuple args: Optional positional arguments to the handler.
"""
self._setup(ctype, op)
if jid in (None, ''):
jid = self.xmpp.boundjid
if jid and not isinstance(jid, JID):
jid = JID(jid)
if node is None:
node = ''
if self.xmpp.is_component:
if self.settings[ctype].get('component_bare', False):
jid = jid.bare
else:
jid = jid.full
else:
if self.settings[ctype].get('client_bare', True):
jid = jid.bare
else:
jid = jid.full
handler = self._handlers[ctype][op]['node'].get((jid, node), None)
if handler is None:
handler = self._handlers[ctype][op]['jid'].get(jid, None)
if handler is None:
handler = self._handlers[ctype][op].get('global', None)
if handler:
try:
return handler(jid, node, ifrom, args)
except TypeError:
# To preserve backward compatibility, drop the ifrom
# parameter for existing handlers that don't understand it.
return handler(jid, node, args)
def register(self, handler, ctype, op, jid=None, node=None, default=False):
"""Register an API callback, with JID+node specificity.
The API callback can later be executed based on the
specificity of the provided JID+node combination.
See :meth:`~ApiRegistry.run` for more details.
:param string ctype: The name of the API to use.
:param string op: The API operation to perform.
:param JID jid: Optionally provide specific JID.
:param string node: Optionally provide specific node.
"""
self._setup(ctype, op)
if jid is None and node is None:
if handler is None:
handler = self._handler_defaults[op]
self._handlers[ctype][op]['global'] = handler
elif jid is not None and node is None:
self._handlers[ctype][op]['jid'][jid] = handler
else:
self._handlers[ctype][op]['node'][(jid, node)] = handler
def register_default(self, handler, ctype, op):
"""Register a default, global handler for an operation.
:param func handler: The default, global handler for the operation.
:param string ctype: The name of the API to modify.
:param string op: The API operation to use.
"""
self._setup(ctype, op)
self._handler_defaults[ctype][op] = handler
def unregister(self, ctype, op, jid=None, node=None):
"""Remove an API callback.
The API callback chosen for removal is based on the
specificity of the provided JID+node combination.
See :meth:`~ApiRegistry.run` for more details.
:param string ctype: The name of the API to use.
:param string op: The API operation to perform.
:param JID jid: Optionally provide specific JID.
:param string node: Optionally provide specific node.
"""
self._setup(ctype, op)
self.register(None, ctype, op, jid, node)
def restore_default(self, ctype, op, jid=None, node=None):
"""Reset an API callback to use a default handler.
:param string ctype: The name of the API to use.
:param string op: The API operation to perform.
:param JID jid: Optionally provide specific JID.
:param string node: Optionally provide specific node.
"""
self.unregister(ctype, op, jid, node)
self.register(self._handler_defaults[ctype][op], ctype, op, jid, node)

View File

@ -19,6 +19,7 @@ import logging
import sleekxmpp import sleekxmpp
from sleekxmpp import plugins, features, roster from sleekxmpp import plugins, features, roster
from sleekxmpp.api import APIRegistry
from sleekxmpp.exceptions import IqError, IqTimeout from sleekxmpp.exceptions import IqError, IqTimeout
from sleekxmpp.stanza import Message, Presence, Iq, StreamError from sleekxmpp.stanza import Message, Presence, Iq, StreamError
@ -97,6 +98,22 @@ class BaseXMPP(XMLStream):
#: ``'to'`` and ``'from'`` JIDs of stanzas. #: ``'to'`` and ``'from'`` JIDs of stanzas.
self.is_component = False self.is_component = False
#: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is
#: marked with:
#:
#: - An API name, e.g. xep_0030
#: - The name of an action, e.g. get_info
#: - The JID that will be affected
#: - The node that will be affected
#:
#: API handlers with no JID or node will act as global handlers,
#: while those with a JID and no node will service all nodes
#: for a JID, and handlers with both a JID and node will be
#: used only for that specific combination. The handler that
#: provides the most specificity will be used.
self.api = APIRegistry(self)
#: Flag indicating that the initial presence broadcast has #: Flag indicating that the initial presence broadcast has
#: been sent. Until this happens, some servers may not #: been sent. Until this happens, some servers may not
#: behave as expected when sending stanzas. #: behave as expected when sending stanzas.

View File

@ -269,6 +269,7 @@ class BasePlugin(object):
def __init__(self, xmpp, config=None): def __init__(self, xmpp, config=None):
self.xmpp = xmpp self.xmpp = xmpp
self.api = self.xmpp.api.wrap(self.name)
#: A plugin's behaviour may be configurable, in which case those #: A plugin's behaviour may be configurable, in which case those
#: configuration settings will be provided as a dictionary. #: configuration settings will be provided as a dictionary.

View File

@ -118,16 +118,13 @@ class XEP_0030(BasePlugin):
'del_item', 'del_identities', 'del_features', 'cache_info', 'del_item', 'del_identities', 'del_features', 'cache_info',
'get_cached_info', 'supports', 'has_identity'] 'get_cached_info', 'supports', 'has_identity']
self.default_handlers = {}
self._handlers = {}
for op in self._disco_ops: for op in self._disco_ops:
self._add_disco_op(op, getattr(self.static, op)) self.api.register(getattr(self.static, op), op)
self.api.register_default(getattr(self.static, op), op)
def _add_disco_op(self, op, default_handler): def _add_disco_op(self, op, default_handler):
self.default_handlers[op] = default_handler self.api.register(default_handler, op)
self._handlers[op] = {'global': default_handler, self.api.register_default(default_handler, op)
'jid': {},
'node': {}}
def set_node_handler(self, htype, jid=None, node=None, handler=None): def set_node_handler(self, htype, jid=None, node=None, handler=None):
""" """
@ -173,20 +170,7 @@ class XEP_0030(BasePlugin):
assumed. assumed.
handler -- The handler function to use. handler -- The handler function to use.
""" """
if htype not in self._disco_ops: self.api.register(handler, htype, jid, node)
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:
if self.xmpp.is_component:
jid = self.xmpp.boundjid.full
else:
jid = self.xmpp.boundjid.bare
self._handlers[htype]['node'][(jid, node)] = handler
else:
self._handlers[htype]['node'][(jid, node)] = handler
def del_node_handler(self, htype, jid, node): def del_node_handler(self, htype, jid, node):
""" """
@ -209,7 +193,7 @@ class XEP_0030(BasePlugin):
jid -- The JID from which to remove the handler. jid -- The JID from which to remove the handler.
node -- The node from which to remove the handler. node -- The node from which to remove the handler.
""" """
self.set_node_handler(htype, jid, node, None) self.api.unregister(htype, jid, node)
def restore_defaults(self, jid=None, node=None, handlers=None): def restore_defaults(self, jid=None, node=None, handlers=None):
""" """
@ -232,8 +216,7 @@ class XEP_0030(BasePlugin):
if handlers is None: if handlers is None:
handlers = self._disco_ops handlers = self._disco_ops
for op in handlers: for op in handlers:
self.del_node_handler(op, jid, node) self.api.restore_default(op, jid, node)
self.set_node_handler(op, jid, node, self.default_handlers[op])
def supports(self, jid=None, node=None, feature=None, local=False, def supports(self, jid=None, node=None, feature=None, local=False,
cached=True, ifrom=None): cached=True, ifrom=None):
@ -266,7 +249,7 @@ class XEP_0030(BasePlugin):
data = {'feature': feature, data = {'feature': feature,
'local': local, 'local': local,
'cached': cached} 'cached': cached}
return self._run_node_handler('supports', jid, node, ifrom, data) return self.api['supports'](jid, node, ifrom, data)
def has_identity(self, jid=None, node=None, category=None, itype=None, def has_identity(self, jid=None, node=None, category=None, itype=None,
lang=None, local=False, cached=True, ifrom=None): lang=None, local=False, cached=True, ifrom=None):
@ -303,7 +286,7 @@ class XEP_0030(BasePlugin):
'lang': lang, 'lang': lang,
'local': local, 'local': local,
'cached': cached} 'cached': cached}
return self._run_node_handler('has_identity', jid, node, ifrom, data) return self.api['has_identity'](jid, node, ifrom, data)
def get_info(self, jid=None, node=None, local=False, def get_info(self, jid=None, node=None, local=False,
cached=None, **kwargs): cached=None, **kwargs):
@ -355,16 +338,18 @@ class XEP_0030(BasePlugin):
if local or jid in (None, ''): if local or jid in (None, ''):
log.debug("Looking up local disco#info data " + \ log.debug("Looking up local disco#info data " + \
"for %s, node %s.", jid, node) "for %s, node %s.", jid, node)
info = self._run_node_handler('get_info', info = self.api['get_info'](jid, node,
jid, node, kwargs.get('ifrom', None), kwargs) kwargs.get('ifrom', None),
kwargs)
info = self._fix_default_info(info) info = self._fix_default_info(info)
return self._wrap(kwargs.get('ifrom', None), jid, info) return self._wrap(kwargs.get('ifrom', None), jid, info)
if cached: if cached:
log.debug("Looking up cached disco#info data " + \ log.debug("Looking up cached disco#info data " + \
"for %s, node %s.", jid, node) "for %s, node %s.", jid, node)
info = self._run_node_handler('get_cached_info', info = self.api['get_cached_info'](jid, node,
jid, node, kwargs.get('ifrom', None), kwargs) kwargs.get('ifrom', None),
kwargs)
if info is not None: if info is not None:
return self._wrap(kwargs.get('ifrom', None), jid, info) return self._wrap(kwargs.get('ifrom', None), jid, info)
@ -385,7 +370,7 @@ class XEP_0030(BasePlugin):
""" """
if isinstance(info, Iq): if isinstance(info, Iq):
info = info['disco_info'] info = info['disco_info']
self._run_node_handler('set_info', jid, node, None, info) self.api['set_info'](jid, node, None, info)
def get_items(self, jid=None, node=None, local=False, **kwargs): def get_items(self, jid=None, node=None, local=False, **kwargs):
""" """
@ -419,8 +404,9 @@ class XEP_0030(BasePlugin):
Otherwise the parameter is ignored. Otherwise the parameter is ignored.
""" """
if local or jid is None: if local or jid is None:
items = self._run_node_handler('get_items', items = self.api['get_items'](jid, node,
jid, node, kwargs.get('ifrom', None), kwargs) kwargs.get('ifrom', None),
kwargs)
return self._wrap(kwargs.get('ifrom', None), jid, items) return self._wrap(kwargs.get('ifrom', None), jid, items)
iq = self.xmpp.Iq() iq = self.xmpp.Iq()
@ -448,7 +434,7 @@ class XEP_0030(BasePlugin):
node -- Optional node to modify. node -- Optional node to modify.
items -- A series of items in tuple format. items -- A series of items in tuple format.
""" """
self._run_node_handler('set_items', jid, node, None, kwargs) self.api['set_items'](jid, node, None, kwargs)
def del_items(self, jid=None, node=None, **kwargs): def del_items(self, jid=None, node=None, **kwargs):
""" """
@ -458,7 +444,7 @@ class XEP_0030(BasePlugin):
jid -- The JID to modify. jid -- The JID to modify.
node -- Optional node to modify. node -- Optional node to modify.
""" """
self._run_node_handler('del_items', jid, node, None, kwargs) self.api['del_items'](jid, node, None, kwargs)
def add_item(self, jid='', name='', node=None, subnode='', ijid=None): def add_item(self, jid='', name='', node=None, subnode='', ijid=None):
""" """
@ -479,7 +465,7 @@ class XEP_0030(BasePlugin):
kwargs = {'ijid': jid, kwargs = {'ijid': jid,
'name': name, 'name': name,
'inode': subnode} 'inode': subnode}
self._run_node_handler('add_item', ijid, node, None, kwargs) self.api['add_item'](ijid, node, None, kwargs)
def del_item(self, jid=None, node=None, **kwargs): def del_item(self, jid=None, node=None, **kwargs):
""" """
@ -491,7 +477,7 @@ class XEP_0030(BasePlugin):
ijid -- The item's JID. ijid -- The item's JID.
inode -- The item's node. inode -- The item's node.
""" """
self._run_node_handler('del_item', jid, node, None, kwargs) self.api['del_item'](jid, node, None, kwargs)
def add_identity(self, category='', itype='', name='', def add_identity(self, category='', itype='', name='',
node=None, jid=None, lang=None): node=None, jid=None, lang=None):
@ -518,7 +504,7 @@ class XEP_0030(BasePlugin):
'itype': itype, 'itype': itype,
'name': name, 'name': name,
'lang': lang} 'lang': lang}
self._run_node_handler('add_identity', jid, node, None, kwargs) self.api['add_identity'](jid, node, None, kwargs)
def add_feature(self, feature, node=None, jid=None): def add_feature(self, feature, node=None, jid=None):
""" """
@ -530,7 +516,7 @@ class XEP_0030(BasePlugin):
jid -- The JID to modify. jid -- The JID to modify.
""" """
kwargs = {'feature': feature} kwargs = {'feature': feature}
self._run_node_handler('add_feature', jid, node, None, kwargs) self.api['add_feature'](jid, node, None, kwargs)
def del_identity(self, jid=None, node=None, **kwargs): def del_identity(self, jid=None, node=None, **kwargs):
""" """
@ -544,7 +530,7 @@ class XEP_0030(BasePlugin):
name -- Optional, human readable name for the identity. name -- Optional, human readable name for the identity.
lang -- Optional, the identity's xml:lang value. lang -- Optional, the identity's xml:lang value.
""" """
self._run_node_handler('del_identity', jid, node, None, kwargs) self.api['del_identity'](jid, node, None, kwargs)
def del_feature(self, jid=None, node=None, **kwargs): def del_feature(self, jid=None, node=None, **kwargs):
""" """
@ -555,7 +541,7 @@ class XEP_0030(BasePlugin):
node -- The node to modify. node -- The node to modify.
feature -- The feature's namespace. feature -- The feature's namespace.
""" """
self._run_node_handler('del_feature', jid, node, None, kwargs) self.api['del_feature'](jid, node, None, kwargs)
def set_identities(self, jid=None, node=None, **kwargs): def set_identities(self, jid=None, node=None, **kwargs):
""" """
@ -570,7 +556,7 @@ class XEP_0030(BasePlugin):
identities -- A set of identities in tuple form. identities -- A set of identities in tuple form.
lang -- Optional, xml:lang value. lang -- Optional, xml:lang value.
""" """
self._run_node_handler('set_identities', jid, node, None, kwargs) self.api['set_identities'](jid, node, None, kwargs)
def del_identities(self, jid=None, node=None, **kwargs): def del_identities(self, jid=None, node=None, **kwargs):
""" """
@ -585,7 +571,7 @@ class XEP_0030(BasePlugin):
lang -- Optional. If given, only remove identities lang -- Optional. If given, only remove identities
using this xml:lang value. using this xml:lang value.
""" """
self._run_node_handler('del_identities', jid, node, None, kwargs) self.api['del_identities'](jid, node, None, kwargs)
def set_features(self, jid=None, node=None, **kwargs): def set_features(self, jid=None, node=None, **kwargs):
""" """
@ -597,7 +583,7 @@ class XEP_0030(BasePlugin):
node -- The node to modify. node -- The node to modify.
features -- The new set of supported features. features -- The new set of supported features.
""" """
self._run_node_handler('set_features', jid, node, None, kwargs) self.api['set_features'](jid, node, None, kwargs)
def del_features(self, jid=None, node=None, **kwargs): def del_features(self, jid=None, node=None, **kwargs):
""" """
@ -607,7 +593,7 @@ class XEP_0030(BasePlugin):
jid -- The JID to modify. jid -- The JID to modify.
node -- The node to modify. node -- The node to modify.
""" """
self._run_node_handler('del_features', jid, node, None, kwargs) self.api['del_features'](jid, node, None, kwargs)
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}): def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}):
""" """
@ -620,39 +606,7 @@ class XEP_0030(BasePlugin):
node -- The node requested. node -- The node requested.
data -- Optional, custom data to pass to the handler. data -- Optional, custom data to pass to the handler.
""" """
if isinstance(jid, JID): return self.api[htype](jid, node, ifrom, data)
jid = jid.full
if jid in (None, ''):
if self.xmpp.is_component:
jid = self.xmpp.boundjid.full
else:
jid = self.xmpp.boundjid.bare
if node is None:
node = ''
try:
args = (jid, node, ifrom, data)
if self._handlers[htype]['node'].get((jid, node), False):
return self._handlers[htype]['node'][(jid, node)](*args)
elif self._handlers[htype]['jid'].get(jid, False):
return self._handlers[htype]['jid'][jid](*args)
elif self._handlers[htype]['global']:
return self._handlers[htype]['global'](*args)
else:
return None
except TypeError:
# To preserve backward compatibility, drop the ifrom parameter
# for existing handlers that don't understand it.
args = (jid, node, data)
if self._handlers[htype]['node'].get((jid, node), False):
return self._handlers[htype]['node'][(jid, node)](*args)
elif self._handlers[htype]['jid'].get(jid, False):
return self._handlers[htype]['jid'][jid](*args)
elif self._handlers[htype]['global']:
return self._handlers[htype]['global'](*args)
else:
return None
def _handle_disco_info(self, iq): def _handle_disco_info(self, iq):
""" """
@ -671,11 +625,10 @@ class XEP_0030(BasePlugin):
jid = iq['to'].full jid = iq['to'].full
else: else:
jid = iq['to'].bare jid = iq['to'].bare
info = self._run_node_handler('get_info', info = self.api['get_info'](jid,
jid, iq['disco_info']['node'],
iq['disco_info']['node'], iq['from'],
iq['from'], iq)
iq)
if isinstance(info, Iq): if isinstance(info, Iq):
info.send() info.send()
else: else:
@ -694,8 +647,7 @@ class XEP_0030(BasePlugin):
ito = iq['to'].full ito = iq['to'].full
else: else:
ito = None ito = None
self._run_node_handler('cache_info', self.api['cache_info'](iq['from'].full,
iq['from'].full,
iq['disco_info']['node'], iq['disco_info']['node'],
ito, ito,
iq) iq)
@ -717,8 +669,7 @@ class XEP_0030(BasePlugin):
jid = iq['to'].full jid = iq['to'].full
else: else:
jid = iq['to'].bare jid = iq['to'].bare
items = self._run_node_handler('get_items', items = self.api['get_items'](jid,
jid,
iq['disco_items']['node'], iq['disco_items']['node'],
iq['from'].full, iq['from'].full,
iq) iq)

View File

@ -78,7 +78,9 @@ class XEP_0115(BasePlugin):
self.static = StaticCaps(self.xmpp, disco.static) self.static = StaticCaps(self.xmpp, disco.static)
for op in self._disco_ops: for op in self._disco_ops:
disco._add_disco_op(op, getattr(self.static, op)) self.api.register(getattr(self.static, op), 'xep_0115', op)
self.api.register_default(getattr(self.static, op),
'xep_0115', op)
self._run_node_handler = disco._run_node_handler self._run_node_handler = disco._run_node_handler
@ -279,19 +281,19 @@ class XEP_0115(BasePlugin):
jid = self.xmpp.boundjid.full jid = self.xmpp.boundjid.full
if isinstance(jid, JID): if isinstance(jid, JID):
jid = jid.full jid = jid.full
return self._run_node_handler('get_verstring', jid) return self.api['get_verstring'](jid)
def assign_verstring(self, jid=None, verstring=None): def assign_verstring(self, jid=None, verstring=None):
if jid in (None, ''): if jid in (None, ''):
jid = self.xmpp.boundjid.full jid = self.xmpp.boundjid.full
if isinstance(jid, JID): if isinstance(jid, JID):
jid = jid.full jid = jid.full
return self._run_node_handler('assign_verstring', jid, return self.api['assign_verstring'](jid, args={
data={'verstring': verstring}) 'verstring': verstring})
def cache_caps(self, verstring=None, info=None): def cache_caps(self, verstring=None, info=None):
data = {'verstring': verstring, 'info': info} data = {'verstring': verstring, 'info': info}
return self._run_node_handler('cache_caps', None, None, data=data) return self.api['cache_caps'](args=data)
def get_caps(self, jid=None, verstring=None): def get_caps(self, jid=None, verstring=None):
if verstring is None: if verstring is None:
@ -302,4 +304,4 @@ class XEP_0115(BasePlugin):
if isinstance(jid, JID): if isinstance(jid, JID):
jid = jid.full jid = jid.full
data = {'verstring': verstring} data = {'verstring': verstring}
return self._run_node_handler('get_caps', jid, None, None, data) return self.api['get_caps'](jid, args=data)

View File

@ -61,7 +61,8 @@ class XEP_0128(BasePlugin):
self.disco.del_extended_info = self.del_extended_info self.disco.del_extended_info = self.del_extended_info
for op in self._disco_ops: for op in self._disco_ops:
self.disco._add_disco_op(op, getattr(self.static, op)) self.api.register(getattr(self.static, op), op)
self.api.register_default(getattr(self.static, op), op)
def set_extended_info(self, jid=None, node=None, **kwargs): def set_extended_info(self, jid=None, node=None, **kwargs):
""" """
@ -76,7 +77,7 @@ class XEP_0128(BasePlugin):
as extended information, replacing any as extended information, replacing any
existing extensions. existing extensions.
""" """
self.disco._run_node_handler('set_extended_info', jid, node, None, kwargs) self.api['set_extended_info'](jid, node, None, kwargs)
def add_extended_info(self, jid=None, node=None, **kwargs): def add_extended_info(self, jid=None, node=None, **kwargs):
""" """
@ -88,7 +89,7 @@ class XEP_0128(BasePlugin):
data -- Either a form, or a list of forms to add data -- Either a form, or a list of forms to add
as extended information. as extended information.
""" """
self.disco._run_node_handler('add_extended_info', jid, node, None, kwargs) self.api['add_extended_info'](jid, node, None, kwargs)
def del_extended_info(self, jid=None, node=None, **kwargs): def del_extended_info(self, jid=None, node=None, **kwargs):
""" """
@ -98,4 +99,4 @@ class XEP_0128(BasePlugin):
jid -- The JID to modify. jid -- The JID to modify.
node -- The node to modify. node -- The node to modify.
""" """
self.disco._run_node_handler('del_extended_info', jid, node, None, kwargs) self.api['del_extended_info'](jid, node, None, kwargs)