Compare commits
57 Commits
sleek-1.0-
...
1.0-RC2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f72c05ebf | ||
|
|
20cacc84ba | ||
|
|
24a14a0284 | ||
|
|
982c2d9b83 | ||
|
|
efa4a9b330 | ||
|
|
39ec1cff19 | ||
|
|
24c5f8d374 | ||
|
|
d6b0158ddb | ||
|
|
7e5e9542e9 | ||
|
|
d7fc2aaa9c | ||
|
|
8471a485d1 | ||
|
|
462b375c8f | ||
|
|
afbd506cfc | ||
|
|
ec01e45ed1 | ||
|
|
993829b23f | ||
|
|
002257b820 | ||
|
|
0af35c2224 | ||
|
|
76bc0a2ba6 | ||
|
|
d2dc4824ee | ||
|
|
3f9ca0366b | ||
|
|
b68785e19e | ||
|
|
a1bbb719e1 | ||
|
|
46f23f7348 | ||
|
|
09252baa71 | ||
|
|
3623a7a16a | ||
|
|
cc504ab07c | ||
|
|
2500a0649b | ||
|
|
5ec4e4a026 | ||
|
|
c3df4dd052 | ||
|
|
730c3fada0 | ||
|
|
628978fc8c | ||
|
|
7fb9d68714 | ||
|
|
e0a1c477d0 | ||
|
|
b70565720f | ||
|
|
33ac0c9dd6 | ||
|
|
4699bdff60 | ||
|
|
354641a3ce | ||
|
|
58a43e40c7 | ||
|
|
6b7fde10d3 | ||
|
|
13fdab0139 | ||
|
|
2ce617b2ce | ||
|
|
63e0496c30 | ||
|
|
850e3bb99b | ||
|
|
2d90deb96a | ||
|
|
3fb3f63e51 | ||
|
|
d12949ff1c | ||
|
|
e3e985220e | ||
|
|
802dd8393d | ||
|
|
fe6bc31c60 | ||
|
|
2162d6042e | ||
|
|
b8a4ffece9 | ||
|
|
d929e0deb2 | ||
|
|
4c08c9c524 | ||
|
|
63b8444abe | ||
|
|
82546d776d | ||
|
|
84f9505a8d | ||
|
|
ede59ab40e |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,3 +4,5 @@ dist/
|
||||
MANIFEST
|
||||
docs/_build/
|
||||
*.swp
|
||||
.tox/
|
||||
.coverage
|
||||
|
||||
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@@ -0,0 +1,6 @@
|
||||
include README.rst
|
||||
include LICENSE
|
||||
include testall.py
|
||||
recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png
|
||||
recursive-include examples *.py
|
||||
recursive-include tests *.py
|
||||
20
README.rst
20
README.rst
@@ -34,7 +34,8 @@ SleekXMPP's design goals and philosphy are:
|
||||
|
||||
Get the Code
|
||||
------------
|
||||
.. code-block:: sh
|
||||
|
||||
Get the latest stable version from PyPI::
|
||||
|
||||
pip install sleekxmpp
|
||||
|
||||
@@ -54,6 +55,15 @@ The latest source code for SleekXMPP may be found on `Github
|
||||
**Develop Releases**
|
||||
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
|
||||
|
||||
Installing DNSPython
|
||||
---------------------
|
||||
If you are using Python3 and wish to use dnspython, you will have to checkout and
|
||||
install the ``python3`` branch::
|
||||
|
||||
git clone http://github.com/rthalley/dnspython
|
||||
cd dnspython
|
||||
git checkout python3
|
||||
python3 setup.py install
|
||||
|
||||
Discussion
|
||||
----------
|
||||
@@ -68,7 +78,6 @@ help with SleekXMPP.
|
||||
|
||||
Documentation and Testing
|
||||
-------------------------
|
||||
|
||||
Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``.
|
||||
To generate the Sphinx documentation, follow the commands below. The HTML output will
|
||||
be in ``docs/_build/html``::
|
||||
@@ -84,7 +93,6 @@ To run the test suite for SleekXMPP::
|
||||
|
||||
The SleekXMPP Boilerplate
|
||||
-------------------------
|
||||
|
||||
Projects using SleekXMPP tend to follow a basic pattern for setting up client/component
|
||||
connections and configuration. Here is the gist of the boilerplate needed for a SleekXMPP
|
||||
based project. See the documetation or examples directory for more detailed archetypes for
|
||||
@@ -136,8 +144,10 @@ SleekXMPP projects::
|
||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
||||
|
||||
# If you are working with an OpenFire server, you will need
|
||||
# to useuterborg Larsson version:
|
||||
# xmppissl_version = ssl.PROTOCOL_SSLv3
|
||||
# to use a different SSL version:
|
||||
#
|
||||
# import ssl
|
||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||
|
||||
if xmpp.connect():
|
||||
xmpp.process(block=True)
|
||||
|
||||
@@ -4,8 +4,6 @@ clientxmpp
|
||||
|
||||
.. module:: sleekxmpp.clientxmpp
|
||||
|
||||
.. autodata:: SRV_SUPPORT
|
||||
|
||||
.. autoclass:: ClientXMPP
|
||||
|
||||
.. automethod:: connect
|
||||
|
||||
21
setup.py
Normal file → Executable file
21
setup.py
Normal file → Executable file
@@ -4,16 +4,15 @@
|
||||
# Copyright (C) 2007-2011 Nathanael C. Fritz
|
||||
# All Rights Reserved
|
||||
#
|
||||
# This software is licensed as described in the README file,
|
||||
# which you should have received as part of this distribution.
|
||||
#
|
||||
# This software is licensed as described in the README.rst and LICENSE
|
||||
# file, which you should have received as part of this distribution.
|
||||
|
||||
# from ez_setup import use_setuptools
|
||||
from distutils.core import setup
|
||||
import sys
|
||||
from distutils.core import setup, Command
|
||||
# from ez_setup import use_setuptools
|
||||
|
||||
import sleekxmpp
|
||||
|
||||
from testall import TestCommand
|
||||
from sleekxmpp.version import __version__
|
||||
# if 'cygwin' in sys.platform.lower():
|
||||
# min_version = '0.6c6'
|
||||
# else:
|
||||
@@ -27,10 +26,10 @@ import sleekxmpp
|
||||
#
|
||||
# from setuptools import setup, find_packages, Extension, Feature
|
||||
|
||||
VERSION = sleekxmpp.__version__
|
||||
VERSION = __version__
|
||||
DESCRIPTION = 'SleekXMPP is an elegant Python library for XMPP (aka Jabber, Google Talk, etc).'
|
||||
with open('README.rst') as readme:
|
||||
LONG_DESCRIPTION = '\n'.join(readme)
|
||||
LONG_DESCRIPTION = ''.join(readme)
|
||||
|
||||
CLASSIFIERS = [ 'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
@@ -93,5 +92,7 @@ setup(
|
||||
license = 'MIT',
|
||||
platforms = [ 'any' ],
|
||||
packages = packages,
|
||||
requires = [ 'tlslite', 'pythondns' ],
|
||||
requires = [ 'dnspython' ],
|
||||
classifiers = CLASSIFIERS,
|
||||
cmdclass = {'test': TestCommand}
|
||||
)
|
||||
|
||||
@@ -15,5 +15,4 @@ from sleekxmpp.xmlstream import XMLStream, RestartStream
|
||||
from sleekxmpp.xmlstream.matcher import *
|
||||
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
|
||||
|
||||
__version__ = '1.0rc1'
|
||||
__version_info__ = (1, 0, 0, 'rc1', 0)
|
||||
from sleekxmpp.version import __version__, __version_info__
|
||||
|
||||
@@ -254,13 +254,6 @@ class ClientXMPP(BaseXMPP):
|
||||
self.bindfail = False
|
||||
self.features = set()
|
||||
|
||||
def session_timeout():
|
||||
if not self.session_started_event.isSet():
|
||||
log.debug("Session start has taken more than 15 seconds")
|
||||
self.disconnect(reconnect=self.auto_reconnect)
|
||||
|
||||
self.schedule("session timeout checker", 15, session_timeout)
|
||||
|
||||
def _handle_stream_features(self, features):
|
||||
"""
|
||||
Process the received stream features.
|
||||
|
||||
@@ -431,8 +431,7 @@ class xep_0050(base_plugin):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['to'] = jid
|
||||
if ifrom:
|
||||
iq['from'] = ifrom
|
||||
iq['from'] = ifrom
|
||||
iq['command']['node'] = node
|
||||
iq['command']['action'] = action
|
||||
if sessionid is not None:
|
||||
@@ -482,9 +481,8 @@ class xep_0050(base_plugin):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['to'] = jid
|
||||
if ifrom:
|
||||
iq['from'] = ifrom
|
||||
session['from'] = ifrom
|
||||
iq['from'] = ifrom
|
||||
session['from'] = ifrom
|
||||
iq['command']['node'] = node
|
||||
iq['command']['action'] = 'execute'
|
||||
sessionid = 'client:pending_' + iq['id']
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
from __future__ import with_statement
|
||||
from sleekxmpp.plugins import base
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import logging
|
||||
#from xml.etree import cElementTree as ET
|
||||
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
|
||||
|
||||
from sleekxmpp.xmlstream import JID
|
||||
from sleekxmpp.plugins.base import base_plugin
|
||||
from sleekxmpp.plugins.xep_0060 import stanza
|
||||
from sleekxmpp.plugins.xep_0004 import Form
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class xep_0060(base.base_plugin):
|
||||
class xep_0060(base_plugin):
|
||||
|
||||
"""
|
||||
XEP-0060 Publish Subscribe
|
||||
"""
|
||||
@@ -18,111 +25,404 @@ class xep_0060(base.base_plugin):
|
||||
def plugin_init(self):
|
||||
self.xep = '0060'
|
||||
self.description = 'Publish-Subscribe'
|
||||
self.stanza = stanza
|
||||
|
||||
def create_node(self, jid, node, config=None, ntype=None):
|
||||
iq = IQ(sto=jid, stype='set', sfrom=self.xmpp.jid)
|
||||
def create_node(self, jid, node, config=None, ntype=None, ifrom=None,
|
||||
block=True, callback=None, timeout=None):
|
||||
"""
|
||||
Create and configure a new pubsub node.
|
||||
|
||||
A server MAY use a different name for the node than the one provided,
|
||||
so be sure to check the result stanza for a server assigned name.
|
||||
|
||||
If no configuration form is provided, the node will be created using
|
||||
the server's default configuration. To get the default configuration
|
||||
use get_node_config().
|
||||
|
||||
Arguments:
|
||||
jid -- The JID of the pubsub service.
|
||||
node -- Optional name of the node to create. If no name is
|
||||
provided, the server MAY generate a node ID for you.
|
||||
The server can also assign a different name than the
|
||||
one you provide; check the result stanza to see if
|
||||
the server assigned a name.
|
||||
config -- Optional XEP-0004 data form of configuration settings.
|
||||
ntype -- The type of node to create. Servers typically default
|
||||
to using 'leaf' if no type is provided.
|
||||
ifrom -- Specify the sender's JID.
|
||||
block -- Specify if the send call will block until a response
|
||||
is received, or a timeout occurs. Defaults to True.
|
||||
timeout -- The length of time (in seconds) to wait for a response
|
||||
before exiting the send call if blocking is used.
|
||||
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||
callback -- Optional reference to a stream handler function. Will
|
||||
be executed when a reply stanza is received.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub']['create']['node'] = node
|
||||
if ntype is None:
|
||||
ntype = 'leaf'
|
||||
|
||||
if config is not None:
|
||||
if 'FORM_TYPE' in submitform.field:
|
||||
config.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
|
||||
form_type = 'http://jabber.org/protocol/pubsub#node_config'
|
||||
if 'FORM_TYPE' in config['fields']:
|
||||
config.field['FORM_TYPE']['value'] = form_type
|
||||
else:
|
||||
config.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
|
||||
if 'pubsub#node_type' in submitform.field:
|
||||
config.field['pubsub#node_type'].setValue(ntype)
|
||||
else:
|
||||
config.addField('pubsub#node_type', value=ntype)
|
||||
iq['pubsub']['configure']['form'] = config
|
||||
return iq.send()
|
||||
config.add_field(var='FORM_TYPE',
|
||||
ftype='hidden',
|
||||
value=form_type)
|
||||
if ntype:
|
||||
if 'pubsub#node_type' in config['fields']:
|
||||
config.field['pubsub#node_type']['value'] = ntype
|
||||
else:
|
||||
config.add_field(var='pubsub#node_type', value=ntype)
|
||||
iq['pubsub']['configure'].append(config)
|
||||
|
||||
def subscribe(self, jid, node, bare=True, subscribee=None):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def subscribe(self, jid, node, bare=True, subscribee=None, options=None,
|
||||
ifrom=None, block=True, callback=None, timeout=None):
|
||||
"""
|
||||
Subscribe to updates from a pubsub node.
|
||||
|
||||
The rules for determining the JID that is subscribing to the node are:
|
||||
1. If subscribee is given, use that as provided.
|
||||
2. If ifrom was given, use the bare or full version based on bare.
|
||||
3. Otherwise, use self.xmpp.boundjid based on bare.
|
||||
|
||||
Arguments:
|
||||
jid -- The pubsub service JID.
|
||||
node -- The node to subscribe to.
|
||||
bare -- Indicates if the subscribee is a bare or full JID.
|
||||
Defaults to True for a bare JID.
|
||||
subscribee -- The JID that is subscribing to the node.
|
||||
options --
|
||||
ifrom -- Specify the sender's JID.
|
||||
block -- Specify if the send call will block until a response
|
||||
is received, or a timeout occurs. Defaults to True.
|
||||
timeout -- The length of time (in seconds) to wait for a response
|
||||
before exiting the send call if blocking is used.
|
||||
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||
callback -- Optional reference to a stream handler function. Will
|
||||
be executed when a reply stanza is received.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub']['subscribe']['node'] = node
|
||||
if subscribee is None:
|
||||
if bare:
|
||||
iq['pubsub']['subscribe']['jid'] = self.xmpp.jid.bare
|
||||
else:
|
||||
iq['pubsub']['subscribe']['jid'] = self.xmpp.jid.full
|
||||
else:
|
||||
iq['pubsub']['subscribe']['jid'] = subscribee
|
||||
return iq.send()
|
||||
|
||||
def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
|
||||
if subscribee is None:
|
||||
if ifrom:
|
||||
if bare:
|
||||
subscribee = JID(ifrom).bare
|
||||
else:
|
||||
subscribee = ifrom
|
||||
else:
|
||||
if bare:
|
||||
subscribee = self.xmpp.boundjid.bare
|
||||
else:
|
||||
subscribee = self.xmpp.boundjid
|
||||
|
||||
iq['pubsub']['subscribe']['jid'] = subscribee
|
||||
if options is not None:
|
||||
iq['pubsub']['options'].append(options)
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def unsubscribe(self, jid, node, subid=None, bare=True, subscribee=None,
|
||||
ifrom=None, block=True, callback=None, timeout=None):
|
||||
"""
|
||||
Unubscribe from updates from a pubsub node.
|
||||
|
||||
The rules for determining the JID that is unsubscribing
|
||||
from the node are:
|
||||
1. If subscribee is given, use that as provided.
|
||||
2. If ifrom was given, use the bare or full version based on bare.
|
||||
3. Otherwise, use self.xmpp.boundjid based on bare.
|
||||
|
||||
Arguments:
|
||||
jid -- The pubsub service JID.
|
||||
node -- The node to subscribe to.
|
||||
subid -- The specific subscription, if multiple subscriptions
|
||||
exist for this JID/node combination.
|
||||
bare -- Indicates if the subscribee is a bare or full JID.
|
||||
Defaults to True for a bare JID.
|
||||
subscribee -- The JID that is subscribing to the node.
|
||||
ifrom -- Specify the sender's JID.
|
||||
block -- Specify if the send call will block until a response
|
||||
is received, or a timeout occurs. Defaults to True.
|
||||
timeout -- The length of time (in seconds) to wait for a response
|
||||
before exiting the send call if blocking is used.
|
||||
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||
callback -- Optional reference to a stream handler function. Will
|
||||
be executed when a reply stanza is received.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub']['unsubscribe']['node'] = node
|
||||
if subscribee is None:
|
||||
if bare:
|
||||
iq['pubsub']['unsubscribe']['jid'] = self.xmpp.jid.bare
|
||||
else:
|
||||
iq['pubsub']['unsubscribe']['jid'] = self.xmpp.jid.full
|
||||
else:
|
||||
iq['pubsub']['unsubscribe']['jid'] = subscribee
|
||||
if subid is not None:
|
||||
iq['pubsub']['unsubscribe']['subid'] = subid
|
||||
return iq.send()
|
||||
|
||||
def get_node_config(self, jid, node=None): # if no node, then grab default
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get')
|
||||
if subscribee is None:
|
||||
if ifrom:
|
||||
if bare:
|
||||
subscribee = JID(ifrom).bare
|
||||
else:
|
||||
subscribee = ifrom
|
||||
else:
|
||||
if bare:
|
||||
subscribee = self.xmpp.boundjid.bare
|
||||
else:
|
||||
subscribee = self.xmpp.boundjid
|
||||
|
||||
iq['pubsub']['unsubscribe']['jid'] = subscribee
|
||||
iq['pubsub']['unsubscribe']['subid'] = subid
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_subscriptions(self, jid, node=None, ifrom=None, block=True,
|
||||
callback=None, timeout=None):
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
iq['pubsub']['subscriptions']['node'] = node
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_affiliations(self, jid, node=None, ifrom=None, block=True,
|
||||
callback=None, timeout=None):
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
iq['pubsub']['affiliations']['node'] = node
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_subscription_options(self, jid, node=None, user_jid=None, ifrom=None,
|
||||
block=True, callback=None, timeout=None):
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
if user_jid is None:
|
||||
iq['pubsub']['default']['node'] = node
|
||||
else:
|
||||
iq['pubsub']['options']['node'] = node
|
||||
iq['pubsub']['options']['jid'] = user_jid
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def set_subscription_options(self, jid, node, user_jid, options,
|
||||
ifrom=None, block=True, callback=None,
|
||||
timeout=None):
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
iq['pubsub']['options']['node'] = node
|
||||
iq['pubsub']['options']['jid'] = user_jid
|
||||
iq['pubsub']['options'].append(options)
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_node_config(self, jid, node=None, ifrom=None, block=None,
|
||||
callback=None, timeout=None):
|
||||
"""
|
||||
Retrieve the configuration for a node, or the pubsub service's
|
||||
default configuration for new nodes.
|
||||
|
||||
Arguments:
|
||||
jid -- The JID of the pubsub service.
|
||||
node -- The node to retrieve the configuration for. If None,
|
||||
the default configuration for new nodes will be
|
||||
requested. Defaults to None.
|
||||
ifrom -- Specify the sender's JID.
|
||||
block -- Specify if the send call will block until a response
|
||||
is received, or a timeout occurs. Defaults to True.
|
||||
timeout -- The length of time (in seconds) to wait for a response
|
||||
before exiting the send call if blocking is used.
|
||||
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||
callback -- Optional reference to a stream handler function. Will
|
||||
be executed when a reply stanza is received.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
if node is None:
|
||||
iq['pubsub_owner']['default']
|
||||
else:
|
||||
iq['pubsub_owner']['configure']['node'] = node
|
||||
return iq.send()
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_node_subscriptions(self, jid, node):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get')
|
||||
def get_node_subscriptions(self, jid, node, ifrom=None, block=True,
|
||||
callback=None, timeout=None):
|
||||
"""
|
||||
Retrieve the subscriptions associated with a given node.
|
||||
|
||||
Arguments:
|
||||
jid -- The JID of the pubsub service.
|
||||
node -- The node to retrieve subscriptions from.
|
||||
ifrom -- Specify the sender's JID.
|
||||
block -- Specify if the send call will block until a response
|
||||
is received, or a timeout occurs. Defaults to True.
|
||||
timeout -- The length of time (in seconds) to wait for a response
|
||||
before exiting the send call if blocking is used.
|
||||
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||
callback -- Optional reference to a stream handler function. Will
|
||||
be executed when a reply stanza is received.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
iq['pubsub_owner']['subscriptions']['node'] = node
|
||||
return iq.send()
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_node_affiliations(self, jid, node):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get')
|
||||
def get_node_affiliations(self, jid, node, ifrom=None, block=True,
|
||||
callback=None, timeout=None):
|
||||
"""
|
||||
Retrieve the affiliations associated with a given node.
|
||||
|
||||
Arguments:
|
||||
jid -- The JID of the pubsub service.
|
||||
node -- The node to retrieve affiliations from.
|
||||
ifrom -- Specify the sender's JID.
|
||||
block -- Specify if the send call will block until a response
|
||||
is received, or a timeout occurs. Defaults to True.
|
||||
timeout -- The length of time (in seconds) to wait for a response
|
||||
before exiting the send call if blocking is used.
|
||||
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||
callback -- Optional reference to a stream handler function. Will
|
||||
be executed when a reply stanza is received.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
iq['pubsub_owner']['affiliations']['node'] = node
|
||||
return iq.send()
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def delete_node(self, jid, node):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='get')
|
||||
def delete_node(self, jid, node, ifrom=None, block=True,
|
||||
callback=None, timeout=None):
|
||||
"""
|
||||
Delete a a pubsub node.
|
||||
|
||||
Arguments:
|
||||
jid -- The JID of the pubsub service.
|
||||
node -- The node to delete.
|
||||
ifrom -- Specify the sender's JID.
|
||||
block -- Specify if the send call will block until a response
|
||||
is received, or a timeout occurs. Defaults to True.
|
||||
timeout -- The length of time (in seconds) to wait for a response
|
||||
before exiting the send call if blocking is used.
|
||||
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
|
||||
callback -- Optional reference to a stream handler function. Will
|
||||
be executed when a reply stanza is received.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub_owner']['delete']['node'] = node
|
||||
return iq.send()
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def set_node_config(self, jid, node, config):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
|
||||
def set_node_config(self, jid, node, config, ifrom=None, block=True,
|
||||
callback=None, timeout=None):
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub_owner']['configure']['node'] = node
|
||||
iq['pubsub_owner']['configure']['config'] = config
|
||||
return iq.send()
|
||||
iq['pubsub_owner']['configure']['form'].values = config.values
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def publish(self, jid, node, items=[]):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
|
||||
def publish(self, jid, node, id=None, payload=None, options=None,
|
||||
ifrom=None, block=True, callback=None, timeout=None):
|
||||
"""
|
||||
Add or edit items in a node.
|
||||
|
||||
You may publish an individual item using the item_id and payload
|
||||
parameters, or you may batch publish by using the items parameter
|
||||
which accepts a list of id/payload tuples.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub']['publish']['node'] = node
|
||||
for id, payload in items:
|
||||
item = stanza.pubsub.Item()
|
||||
if id is not None:
|
||||
item['id'] = id
|
||||
item['payload'] = payload
|
||||
iq['pubsub']['publish'].append(item)
|
||||
return iq.send()
|
||||
if id is not None:
|
||||
iq['pubsub']['publish']['item']['id'] = id
|
||||
if payload is not None:
|
||||
iq['pubsub']['publish']['item']['payload'] = payload
|
||||
iq['pubsub']['publish_options'] = options
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def retract(self, jid, node, id, notify=None, ifrom=None, block=True,
|
||||
callback=None, timeout=None):
|
||||
"""
|
||||
Delete a single item from a node.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
|
||||
def retract(self, jid, node, item):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
|
||||
iq['pubsub']['retract']['node'] = node
|
||||
item = stanza.pubsub.Item()
|
||||
item['id'] = item
|
||||
iq['pubsub']['retract'].append(item)
|
||||
return iq.send()
|
||||
iq['pubsub']['retract']['notify'] = notify
|
||||
iq['pubsub']['retract']['item']['id'] = id
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_nodes(self, jid):
|
||||
return self.xmpp.plugin['xep_0030'].get_items(jid)
|
||||
def purge(self, jid, node, ifrom=None, block=True, callback=None,
|
||||
timeout=None):
|
||||
"""
|
||||
Remove all items from a node.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub_owner']['purge']['node'] = node
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def getItems(self, jid, node):
|
||||
return self.xmpp.plugin['xep_0030'].get_items(jid, node)
|
||||
def get_nodes(self, *args, **kwargs):
|
||||
"""
|
||||
Discover the nodes provided by a Pubsub service, using disco.
|
||||
"""
|
||||
return self.xmpp.plugin['xep_0030'].get_items(*args, **kwargs)
|
||||
|
||||
def modify_affiliation(self, jid, node, affiliation, user_jid=None):
|
||||
iq = IQ(sto=jid, sfrom=self.xmpp.jid, stype='set')
|
||||
iq['pubsub_owner']['affiliations']
|
||||
aff = stanza.pubsub.Affiliation()
|
||||
aff['node'] = node
|
||||
if user_jid is not None:
|
||||
aff['jid'] = user_jid
|
||||
aff['affiliation'] = affiliation
|
||||
iq['pubsub_owner']['affiliations'].append(aff)
|
||||
return iq.send()
|
||||
def get_item(self, jid, node, item_id, ifrom=None, block=True,
|
||||
callback=None, timeout=None):
|
||||
"""
|
||||
Retrieve the content of an individual item.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
item = self.stanza.Item()
|
||||
item['id'] = item_id
|
||||
iq['pubsub']['items']['node'] = node
|
||||
iq['pubsub']['items'].append(item)
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_items(self, jid, node, item_ids=None, max_items=None,
|
||||
iterator=False, ifrom=None, block=False,
|
||||
callback=None, timeout=None):
|
||||
"""
|
||||
Request the contents of a node's items.
|
||||
|
||||
The desired items can be specified, or a query for the last
|
||||
few published items can be used.
|
||||
|
||||
Pubsub services may use result set management for nodes with
|
||||
many items, so an iterator can be returned if needed.
|
||||
"""
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='get')
|
||||
iq['pubsub']['items']['node'] = node
|
||||
iq['pubsub']['items']['max_items'] = max_items
|
||||
|
||||
if item_ids is not None:
|
||||
for item_id in item_ids:
|
||||
item = self.stanza.Item()
|
||||
item['id'] = item_id
|
||||
iq['pubsub']['items'].append(item)
|
||||
|
||||
if iterator:
|
||||
return self.xmpp['xep_0059'].iterate(iq, 'pubsub')
|
||||
else:
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_item_ids(self, jid, node, ifrom=None, block=True,
|
||||
callback=None, timeout=None, iterator=False):
|
||||
"""
|
||||
Retrieve the ItemIDs hosted by a given node, using disco.
|
||||
"""
|
||||
return self.xmpp.plugin['xep_0030'].get_items(jid, node,
|
||||
ifrom=ifrom,
|
||||
block=block,
|
||||
callback=callback,
|
||||
timeout=timeout,
|
||||
iterator=iterator)
|
||||
|
||||
def modify_affiliations(self, jid, node, affiliations=None, ifrom=None,
|
||||
block=True, callback=None, timeout=None):
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub_owner']['affiliations']['node'] = node
|
||||
|
||||
if affiliations is None:
|
||||
affiliations = []
|
||||
|
||||
for jid, affiliation in affiliations:
|
||||
aff = self.stanza.OwnerAffiliation()
|
||||
aff['jid'] = jid
|
||||
aff['affiliation'] = affiliation
|
||||
iq['pubsub_owner']['affiliations'].append(aff)
|
||||
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def modify_subscriptions(self, jid, node, subscriptions=None, ifrom=None,
|
||||
block=True, callback=None, timeout=None):
|
||||
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||
iq['pubsub_owner']['subscriptions']['node'] = node
|
||||
|
||||
if subscriptions is None:
|
||||
subscriptions = []
|
||||
|
||||
for jid, subscription in subscriptions:
|
||||
sub = self.stanza.OwnerSubscription()
|
||||
sub['jid'] = jid
|
||||
sub['subscription'] = subscription
|
||||
iq['pubsub_owner']['subscriptions'].append(sub)
|
||||
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Pubsub, Affiliation, Affiliations, Subscription, Subscriptions, SubscribeOptions, Item, Items, Create, Publish, Retract, Unsubscribe, Subscribe, Configure, Options, PubsubState, PubsubStateEvent
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_owner import PubsubOwner, DefaultConfig, OwnerAffiliations, OwnerAffiliation, OwnerConfigure, OwnerDefault, OwnerDelete, OwnerPurge, OwnerRedirect, OwnerSubscriptions, OwnerSubscription
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_event import Event, EventItem, EventRetract, EventItems, EventCollection, EventAssociate, EventDisassociate, EventConfiguration, EventPurge, EventSubscription
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import *
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_owner import *
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_event import *
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub_errors import *
|
||||
|
||||
@@ -1,24 +1,29 @@
|
||||
from xml.etree import cElementTree as ET
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp.xmlstream import ET
|
||||
|
||||
|
||||
class OptionalSetting(object):
|
||||
interfaces = set(('required',))
|
||||
|
||||
def setRequired(self, value):
|
||||
value = bool(value)
|
||||
if value and not self['required']:
|
||||
self.xml.append(ET.Element("{%s}required" % self.namespace))
|
||||
elif not value and self['required']:
|
||||
self.delRequired()
|
||||
|
||||
def getRequired(self):
|
||||
required = self.xml.find("{%s}required" % self.namespace)
|
||||
if required is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def delRequired(self):
|
||||
required = self.xml.find("{%s}required" % self.namespace)
|
||||
if required is not None:
|
||||
self.xml.remove(required)
|
||||
interfaces = set(('required',))
|
||||
|
||||
def set_required(self, value):
|
||||
if value in (True, 'true', 'True', '1'):
|
||||
self.xml.append(ET.Element("{%s}required" % self.namespace))
|
||||
elif self['required']:
|
||||
self.del_required()
|
||||
|
||||
def get_required(self):
|
||||
required = self.xml.find("{%s}required" % self.namespace)
|
||||
return required is not None
|
||||
|
||||
def del_required(self):
|
||||
required = self.xml.find("{%s}required" % self.namespace)
|
||||
if required is not None:
|
||||
self.xml.remove(required)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
||||
from sleekxmpp.stanza.iq import Iq
|
||||
from sleekxmpp.stanza.message import Message
|
||||
from sleekxmpp.basexmpp import basexmpp
|
||||
from sleekxmpp.xmlstream.xmlstream import XMLStream
|
||||
import logging
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp import Iq, Message
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
|
||||
from sleekxmpp.plugins import xep_0004
|
||||
from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
|
||||
|
||||
@@ -11,12 +15,15 @@ from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
|
||||
class Pubsub(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'pubsub'
|
||||
plugin_attrib = 'pubsub'
|
||||
plugin_attrib = name
|
||||
interfaces = set(tuple())
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
registerStanzaPlugin(Iq, Pubsub)
|
||||
|
||||
class Affiliations(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'affiliations'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
|
||||
class Affiliation(ElementBase):
|
||||
@@ -24,25 +31,12 @@ class Affiliation(ElementBase):
|
||||
name = 'affiliation'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'affiliation', 'jid'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setJid(self, value):
|
||||
self._setAttr('jid', str(value))
|
||||
|
||||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
class Affiliations(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'affiliations'
|
||||
plugin_attrib = 'affiliations'
|
||||
interfaces = set(tuple())
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
subitem = (Affiliation,)
|
||||
|
||||
registerStanzaPlugin(Pubsub, Affiliations)
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
|
||||
class Subscription(ElementBase):
|
||||
@@ -50,228 +44,257 @@ class Subscription(ElementBase):
|
||||
name = 'subscription'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('jid', 'node', 'subscription', 'subid'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setjid(self, value):
|
||||
self._setattr('jid', str(value))
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def getjid(self):
|
||||
return jid(self._getattr('jid'))
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
registerStanzaPlugin(Pubsub, Subscription)
|
||||
|
||||
class Subscriptions(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'subscriptions'
|
||||
plugin_attrib = 'subscriptions'
|
||||
interfaces = set(tuple())
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
subitem = (Subscription,)
|
||||
|
||||
registerStanzaPlugin(Pubsub, Subscriptions)
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
|
||||
class SubscribeOptions(ElementBase, OptionalSetting):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'subscribe-options'
|
||||
plugin_attrib = 'suboptions'
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
interfaces = set(('required',))
|
||||
|
||||
registerStanzaPlugin(Subscription, SubscribeOptions)
|
||||
|
||||
class Item(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'item'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('id', 'payload'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setPayload(self, value):
|
||||
self.xml.append(value)
|
||||
def set_payload(self, value):
|
||||
del self['payload']
|
||||
self.append(value)
|
||||
|
||||
def getPayload(self):
|
||||
def get_payload(self):
|
||||
childs = self.xml.getchildren()
|
||||
if len(childs) > 0:
|
||||
return childs[0]
|
||||
|
||||
def delPayload(self):
|
||||
def del_payload(self):
|
||||
for child in self.xml.getchildren():
|
||||
self.xml.remove(child)
|
||||
|
||||
|
||||
class Items(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'items'
|
||||
plugin_attrib = 'items'
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
subitem = (Item,)
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'max_items'))
|
||||
|
||||
def set_max_items(self, value):
|
||||
self._set_attr('max_items', str(value))
|
||||
|
||||
registerStanzaPlugin(Pubsub, Items)
|
||||
|
||||
class Create(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'create'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
registerStanzaPlugin(Pubsub, Create)
|
||||
|
||||
#class Default(ElementBase):
|
||||
# namespace = 'http://jabber.org/protocol/pubsub'
|
||||
# name = 'default'
|
||||
# plugin_attrib = name
|
||||
# interfaces = set(('node', 'type'))
|
||||
# plugin_attrib_map = {}
|
||||
# plugin_tag_map = {}
|
||||
#
|
||||
# def getType(self):
|
||||
# t = self._getAttr('type')
|
||||
# if not t: t == 'leaf'
|
||||
# return t
|
||||
#
|
||||
#registerStanzaPlugin(Pubsub, Default)
|
||||
class Default(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'default'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'type'))
|
||||
|
||||
class Publish(Items):
|
||||
def get_type(self):
|
||||
t = self._get_attr('type')
|
||||
if not t:
|
||||
return 'leaf'
|
||||
return t
|
||||
|
||||
|
||||
class Publish(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'publish'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
subitem = (Item,)
|
||||
|
||||
registerStanzaPlugin(Pubsub, Publish)
|
||||
|
||||
class Retract(Items):
|
||||
class Retract(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'retract'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'notify'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
registerStanzaPlugin(Pubsub, Retract)
|
||||
def get_notify(self):
|
||||
notify = self._get_attr('notify')
|
||||
if notify in ('0', 'false'):
|
||||
return False
|
||||
elif notify in ('1', 'true'):
|
||||
return True
|
||||
return None
|
||||
|
||||
def set_notify(self, value):
|
||||
del self['notify']
|
||||
if value is None:
|
||||
return
|
||||
elif value in (True, '1', 'true', 'True'):
|
||||
self._set_attr('notify', 'true')
|
||||
else:
|
||||
self._set_attr('notify', 'false')
|
||||
|
||||
|
||||
class Unsubscribe(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'unsubscribe'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'jid', 'subid'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setJid(self, value):
|
||||
self._setAttr('jid', str(value))
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
registerStanzaPlugin(Pubsub, Unsubscribe)
|
||||
|
||||
class Subscribe(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'subscribe'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'jid'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setJid(self, value):
|
||||
self._setAttr('jid', str(value))
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
registerStanzaPlugin(Pubsub, Subscribe)
|
||||
|
||||
class Configure(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'configure'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'type'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def getType(self):
|
||||
t = self._getAttr('type')
|
||||
if not t: t == 'leaf'
|
||||
t = self._get_attr('type')
|
||||
if not t:
|
||||
t == 'leaf'
|
||||
return t
|
||||
|
||||
registerStanzaPlugin(Pubsub, Configure)
|
||||
registerStanzaPlugin(Configure, xep_0004.Form)
|
||||
|
||||
class Options(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'options'
|
||||
plugin_attrib = 'options'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('jid', 'node', 'options'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
ElementBase.__init__(self, *args, **kwargs)
|
||||
|
||||
def getOptions(self):
|
||||
def get_options(self):
|
||||
config = self.xml.find('{jabber:x:data}x')
|
||||
form = xep_0004.Form()
|
||||
if config is not None:
|
||||
form.fromXML(config)
|
||||
form = xep_0004.Form(xml=config)
|
||||
return form
|
||||
|
||||
def setOptions(self, value):
|
||||
def set_options(self, value):
|
||||
self.xml.append(value.getXML())
|
||||
return self
|
||||
|
||||
def delOptions(self):
|
||||
def del_options(self):
|
||||
config = self.xml.find('{jabber:x:data}x')
|
||||
self.xml.remove(config)
|
||||
|
||||
def setJid(self, value):
|
||||
self._setAttr('jid', str(value))
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
|
||||
class PublishOptions(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub'
|
||||
name = 'publish-options'
|
||||
plugin_attrib = 'publish_options'
|
||||
interfaces = set(('publish_options',))
|
||||
is_extension = True
|
||||
|
||||
def get_publish_options(self):
|
||||
config = self.xml.find('{jabber:x:data}x')
|
||||
if config is None:
|
||||
return None
|
||||
form = xep_0004.Form(xml=config)
|
||||
return form
|
||||
|
||||
def set_publish_options(self, value):
|
||||
if value is None:
|
||||
self.del_publish_options()
|
||||
else:
|
||||
self.xml.append(value.getXML())
|
||||
return self
|
||||
|
||||
def del_publish_options(self):
|
||||
config = self.xml.find('{jabber:x:data}x')
|
||||
if config is not None:
|
||||
self.xml.remove(config)
|
||||
self.parent().xml.remove(self.xml)
|
||||
|
||||
registerStanzaPlugin(Pubsub, Options)
|
||||
registerStanzaPlugin(Subscribe, Options)
|
||||
|
||||
class PubsubState(ElementBase):
|
||||
"""This is an experimental pubsub extension."""
|
||||
namespace = 'http://jabber.org/protocol/psstate'
|
||||
name = 'state'
|
||||
plugin_attrib = 'psstate'
|
||||
interfaces = set(('node', 'item', 'payload'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setPayload(self, value):
|
||||
def set_payload(self, value):
|
||||
self.xml.append(value)
|
||||
|
||||
def getPayload(self):
|
||||
def get_payload(self):
|
||||
childs = self.xml.getchildren()
|
||||
if len(childs) > 0:
|
||||
return childs[0]
|
||||
|
||||
def delPayload(self):
|
||||
def del_payload(self):
|
||||
for child in self.xml.getchildren():
|
||||
self.xml.remove(child)
|
||||
|
||||
registerStanzaPlugin(Iq, PubsubState)
|
||||
|
||||
class PubsubStateEvent(ElementBase):
|
||||
"""This is an experimental pubsub extension."""
|
||||
namespace = 'http://jabber.org/protocol/psstate#event'
|
||||
name = 'event'
|
||||
plugin_attrib = 'psstate_event'
|
||||
intefaces = set(tuple())
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
registerStanzaPlugin(Message, PubsubStateEvent)
|
||||
registerStanzaPlugin(PubsubStateEvent, PubsubState)
|
||||
|
||||
register_stanza_plugin(Iq, PubsubState)
|
||||
register_stanza_plugin(Message, PubsubStateEvent)
|
||||
register_stanza_plugin(PubsubStateEvent, PubsubState)
|
||||
|
||||
|
||||
register_stanza_plugin(Iq, Pubsub)
|
||||
register_stanza_plugin(Pubsub, Affiliations)
|
||||
register_stanza_plugin(Pubsub, Configure)
|
||||
register_stanza_plugin(Pubsub, Create)
|
||||
register_stanza_plugin(Pubsub, Default)
|
||||
register_stanza_plugin(Pubsub, Items)
|
||||
register_stanza_plugin(Pubsub, Options)
|
||||
register_stanza_plugin(Pubsub, Publish)
|
||||
register_stanza_plugin(Pubsub, PublishOptions)
|
||||
register_stanza_plugin(Pubsub, Retract)
|
||||
register_stanza_plugin(Pubsub, Subscribe)
|
||||
register_stanza_plugin(Pubsub, Subscription)
|
||||
register_stanza_plugin(Pubsub, Subscriptions)
|
||||
register_stanza_plugin(Pubsub, Unsubscribe)
|
||||
register_stanza_plugin(Affiliations, Affiliation, iterable=True)
|
||||
register_stanza_plugin(Configure, xep_0004.Form)
|
||||
register_stanza_plugin(Items, Item, iterable=True)
|
||||
register_stanza_plugin(Publish, Item, iterable=True)
|
||||
register_stanza_plugin(Retract, Item)
|
||||
register_stanza_plugin(Subscribe, Options)
|
||||
register_stanza_plugin(Subscription, SubscribeOptions)
|
||||
register_stanza_plugin(Subscriptions, Subscription, iterable=True)
|
||||
|
||||
86
sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py
Normal file
86
sleekxmpp/plugins/xep_0060/stanza/pubsub_errors.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp.stanza import Error
|
||||
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
|
||||
|
||||
|
||||
class PubsubErrorCondition(ElementBase):
|
||||
|
||||
plugin_attrib = 'pubsub'
|
||||
interfaces = set(('condition', 'unsupported'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
conditions = set(('closed-node', 'configuration-required', 'invalid-jid',
|
||||
'invalid-options', 'invalid-payload', 'invalid-subid',
|
||||
'item-forbidden', 'item-required', 'jid-required',
|
||||
'max-items-exceeded', 'max-nodes-exceeded',
|
||||
'nodeid-required', 'not-in-roster-group',
|
||||
'not-subscribed', 'payload-too-big',
|
||||
'payload-required' 'pending-subscription',
|
||||
'presence-subscription-required', 'subid-required',
|
||||
'too-many-subscriptions', 'unsupported'))
|
||||
condition_ns = 'http://jabber.org/protocol/pubsub#errors'
|
||||
|
||||
def setup(self, xml):
|
||||
"""Don't create XML for the plugin."""
|
||||
self.xml = ET.Element('')
|
||||
|
||||
def get_condition(self):
|
||||
"""Return the condition element's name."""
|
||||
for child in self.parent().xml.getchildren():
|
||||
if "{%s}" % self.condition_ns in child.tag:
|
||||
cond = child.tag.split('}', 1)[-1]
|
||||
if cond in self.conditions:
|
||||
return cond
|
||||
return ''
|
||||
|
||||
def set_condition(self, value):
|
||||
"""
|
||||
Set the tag name of the condition element.
|
||||
|
||||
Arguments:
|
||||
value -- The tag name of the condition element.
|
||||
"""
|
||||
if value in self.conditions:
|
||||
del self['condition']
|
||||
cond = ET.Element("{%s}%s" % (self.condition_ns, value))
|
||||
self.parent().xml.append(cond)
|
||||
return self
|
||||
|
||||
def del_condition(self):
|
||||
"""Remove the condition element."""
|
||||
for child in self.parent().xml.getchildren():
|
||||
if "{%s}" % self.condition_ns in child.tag:
|
||||
tag = child.tag.split('}', 1)[-1]
|
||||
if tag in self.conditions:
|
||||
self.parent().xml.remove(child)
|
||||
return self
|
||||
|
||||
def get_unsupported(self):
|
||||
"""Return the name of an unsupported feature"""
|
||||
xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns)
|
||||
if xml is not None:
|
||||
return xml.attrib.get('feature', '')
|
||||
return ''
|
||||
|
||||
def set_unsupported(self, value):
|
||||
"""Mark a feature as unsupported"""
|
||||
self.del_unsupported()
|
||||
xml = ET.Element('{%s}unsupported' % self.condition_ns)
|
||||
xml.attrib['feature'] = value
|
||||
self.parent().xml.append(xml)
|
||||
|
||||
def del_unsupported(self):
|
||||
"""Delete an unsupported feature condition."""
|
||||
xml = self.parent().xml.find('{%s}unsupported' % self.condition_ns)
|
||||
if xml is not None:
|
||||
self.parent().xml.remove(xml)
|
||||
|
||||
|
||||
register_stanza_plugin(Error, PubsubErrorCondition)
|
||||
@@ -1,124 +1,112 @@
|
||||
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
||||
from sleekxmpp.stanza.iq import Iq
|
||||
from sleekxmpp.stanza.message import Message
|
||||
from sleekxmpp.basexmpp import basexmpp
|
||||
from sleekxmpp.xmlstream.xmlstream import XMLStream
|
||||
import logging
|
||||
from sleekxmpp.plugins import xep_0004
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp import Message
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
|
||||
from sleekxmpp.plugins.xep_0004 import Form
|
||||
|
||||
|
||||
class Event(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'event'
|
||||
plugin_attrib = 'pubsub_event'
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'event'
|
||||
plugin_attrib = 'pubsub_event'
|
||||
interfaces = set(('node',))
|
||||
|
||||
registerStanzaPlugin(Message, Event)
|
||||
|
||||
class EventItem(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'item'
|
||||
plugin_attrib = 'item'
|
||||
interfaces = set(('id', 'payload'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'item'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('id', 'payload'))
|
||||
|
||||
def setPayload(self, value):
|
||||
self.xml.append(value)
|
||||
|
||||
def getPayload(self):
|
||||
childs = self.xml.getchildren()
|
||||
if len(childs) > 0:
|
||||
return childs[0]
|
||||
|
||||
def delPayload(self):
|
||||
for child in self.xml.getchildren():
|
||||
self.xml.remove(child)
|
||||
def set_payload(self, value):
|
||||
self.xml.append(value)
|
||||
|
||||
def get_payload(self):
|
||||
childs = self.xml.getchildren()
|
||||
if len(childs) > 0:
|
||||
return childs[0]
|
||||
|
||||
def del_payload(self):
|
||||
for child in self.xml.getchildren():
|
||||
self.xml.remove(child)
|
||||
|
||||
|
||||
class EventRetract(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'retract'
|
||||
plugin_attrib = 'retract'
|
||||
interfaces = set(('id',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'retract'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('id',))
|
||||
|
||||
|
||||
class EventItems(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'items'
|
||||
plugin_attrib = 'items'
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
subitem = (EventItem, EventRetract)
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'items'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
registerStanzaPlugin(Event, EventItems)
|
||||
|
||||
class EventCollection(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'collection'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'collection'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
registerStanzaPlugin(Event, EventCollection)
|
||||
|
||||
class EventAssociate(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'associate'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'associate'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
registerStanzaPlugin(EventCollection, EventAssociate)
|
||||
|
||||
class EventDisassociate(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'disassociate'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'disassociate'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
registerStanzaPlugin(EventCollection, EventDisassociate)
|
||||
|
||||
class EventConfiguration(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'configuration'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'config'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
registerStanzaPlugin(Event, EventConfiguration)
|
||||
registerStanzaPlugin(EventConfiguration, xep_0004.Form)
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'configuration'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'config'))
|
||||
|
||||
|
||||
class EventPurge(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'purge'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'purge'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
registerStanzaPlugin(Event, EventPurge)
|
||||
|
||||
class EventSubscription(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'subscription'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node','expiry', 'jid', 'subid', 'subscription'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setJid(self, value):
|
||||
self._setAttr('jid', str(value))
|
||||
|
||||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
namespace = 'http://jabber.org/protocol/pubsub#event'
|
||||
name = 'subscription'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'expiry', 'jid', 'subid', 'subscription'))
|
||||
|
||||
registerStanzaPlugin(Event, EventSubscription)
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
|
||||
register_stanza_plugin(Message, Event)
|
||||
register_stanza_plugin(Event, EventCollection)
|
||||
register_stanza_plugin(Event, EventConfiguration)
|
||||
register_stanza_plugin(Event, EventItems)
|
||||
register_stanza_plugin(Event, EventPurge)
|
||||
register_stanza_plugin(Event, EventSubscription)
|
||||
register_stanza_plugin(EventCollection, EventAssociate)
|
||||
register_stanza_plugin(EventCollection, EventDisassociate)
|
||||
register_stanza_plugin(EventConfiguration, Form)
|
||||
register_stanza_plugin(EventItems, EventItem, iterable=True)
|
||||
register_stanza_plugin(EventItems, EventRetract, iterable=True)
|
||||
|
||||
@@ -1,155 +1,131 @@
|
||||
from sleekxmpp.xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID
|
||||
from sleekxmpp.stanza.iq import Iq
|
||||
from sleekxmpp.stanza.message import Message
|
||||
from sleekxmpp.basexmpp import basexmpp
|
||||
from sleekxmpp.xmlstream.xmlstream import XMLStream
|
||||
import logging
|
||||
from sleekxmpp.plugins import xep_0004
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp import Iq
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
|
||||
from sleekxmpp.plugins.xep_0004 import Form
|
||||
from sleekxmpp.plugins.xep_0060.stanza.base import OptionalSetting
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation, Configure, Subscriptions
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Affiliations, Affiliation
|
||||
from sleekxmpp.plugins.xep_0060.stanza.pubsub import Configure, Subscriptions
|
||||
|
||||
|
||||
class PubsubOwner(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
name = 'pubsub'
|
||||
plugin_attrib = 'pubsub_owner'
|
||||
interfaces = set(tuple())
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
registerStanzaPlugin(Iq, PubsubOwner)
|
||||
|
||||
class DefaultConfig(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
name = 'default'
|
||||
plugin_attrib = 'default'
|
||||
interfaces = set(('node', 'type', 'config'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'config'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
ElementBase.__init__(self, *args, **kwargs)
|
||||
|
||||
def getType(self):
|
||||
t = self._getAttr('type')
|
||||
if not t: t = 'leaf'
|
||||
return t
|
||||
|
||||
def getConfig(self):
|
||||
def get_config(self):
|
||||
return self['form']
|
||||
|
||||
def setConfig(self, value):
|
||||
self['form'].setStanzaValues(value.getStanzaValues())
|
||||
def set_config(self, value):
|
||||
self['form'].values = value.values
|
||||
return self
|
||||
|
||||
registerStanzaPlugin(PubsubOwner, DefaultConfig)
|
||||
registerStanzaPlugin(DefaultConfig, xep_0004.Form)
|
||||
|
||||
class OwnerAffiliations(Affiliations):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
interfaces = set(('node'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
interfaces = set(('node',))
|
||||
|
||||
def append(self, affiliation):
|
||||
if not isinstance(affiliation, OwnerAffiliation):
|
||||
raise TypeError
|
||||
self.xml.append(affiliation.xml)
|
||||
return self.affiliations.append(affiliation)
|
||||
|
||||
registerStanzaPlugin(PubsubOwner, OwnerAffiliations)
|
||||
|
||||
class OwnerAffiliation(Affiliation):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
interfaces = set(('affiliation', 'jid'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
|
||||
class OwnerConfigure(Configure):
|
||||
name = 'configure'
|
||||
plugin_attrib = 'configure'
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
interfaces = set(('node', 'config'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def getConfig(self):
|
||||
return self['form']
|
||||
name = 'configure'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
def setConfig(self, value):
|
||||
self['form'].setStanzaValues(value.getStanzaValues())
|
||||
return self
|
||||
|
||||
registerStanzaPlugin(PubsubOwner, OwnerConfigure)
|
||||
|
||||
class OwnerDefault(OwnerConfigure):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
interfaces = set(('node', 'config'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
interfaces = set(('node',))
|
||||
|
||||
|
||||
registerStanzaPlugin(PubsubOwner, OwnerDefault)
|
||||
registerStanzaPlugin(OwnerDefault, xep_0004.Form)
|
||||
|
||||
class OwnerDelete(ElementBase, OptionalSetting):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
name = 'delete'
|
||||
plugin_attrib = 'delete'
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node',))
|
||||
|
||||
registerStanzaPlugin(PubsubOwner, OwnerDelete)
|
||||
|
||||
class OwnerPurge(ElementBase, OptionalSetting):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
name = 'purge'
|
||||
plugin_attrib = name
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
interfaces = set(('node',))
|
||||
|
||||
registerStanzaPlugin(PubsubOwner, OwnerPurge)
|
||||
|
||||
class OwnerRedirect(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
name = 'redirect'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('node', 'jid'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setJid(self, value):
|
||||
self._setAttr('jid', str(value))
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def getJid(self):
|
||||
return JID(self._getAttr('jid'))
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
registerStanzaPlugin(OwnerDelete, OwnerRedirect)
|
||||
|
||||
class OwnerSubscriptions(Subscriptions):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
interfaces = set(('node',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def append(self, subscription):
|
||||
if not isinstance(subscription, OwnerSubscription):
|
||||
raise TypeError
|
||||
self.xml.append(subscription.xml)
|
||||
return self.subscriptions.append(subscription)
|
||||
|
||||
registerStanzaPlugin(PubsubOwner, OwnerSubscriptions)
|
||||
|
||||
class OwnerSubscription(ElementBase):
|
||||
namespace = 'http://jabber.org/protocol/pubsub#owner'
|
||||
name = 'subscription'
|
||||
plugin_attrib = name
|
||||
interfaces = set(('jid', 'subscription'))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
|
||||
def setJid(self, value):
|
||||
self._setAttr('jid', str(value))
|
||||
def set_jid(self, value):
|
||||
self._set_attr('jid', str(value))
|
||||
|
||||
def getJid(self):
|
||||
return JID(self._getAttr('from'))
|
||||
def get_jid(self):
|
||||
return JID(self._get_attr('jid'))
|
||||
|
||||
|
||||
register_stanza_plugin(Iq, PubsubOwner)
|
||||
register_stanza_plugin(PubsubOwner, DefaultConfig)
|
||||
register_stanza_plugin(PubsubOwner, OwnerAffiliations)
|
||||
register_stanza_plugin(PubsubOwner, OwnerConfigure)
|
||||
register_stanza_plugin(PubsubOwner, OwnerDefault)
|
||||
register_stanza_plugin(PubsubOwner, OwnerDelete)
|
||||
register_stanza_plugin(PubsubOwner, OwnerPurge)
|
||||
register_stanza_plugin(PubsubOwner, OwnerSubscriptions)
|
||||
register_stanza_plugin(DefaultConfig, Form)
|
||||
register_stanza_plugin(OwnerAffiliations, OwnerAffiliation, iterable=True)
|
||||
register_stanza_plugin(OwnerConfigure, Form)
|
||||
register_stanza_plugin(OwnerDefault, Form)
|
||||
register_stanza_plugin(OwnerDelete, OwnerRedirect)
|
||||
register_stanza_plugin(OwnerSubscriptions, OwnerSubscription, iterable=True)
|
||||
|
||||
@@ -108,8 +108,7 @@ class xep_0066(base_plugin):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['to'] = to
|
||||
if ifrom:
|
||||
iq['from'] = ifrom
|
||||
iq['from'] = ifrom
|
||||
iq['oob_transfer']['url'] = url
|
||||
iq['oob_transfer']['desc'] = desc
|
||||
return iq.send(**iqargs)
|
||||
|
||||
@@ -76,8 +76,7 @@ class xep_0092(base_plugin):
|
||||
"""
|
||||
iq = self.xmpp.Iq()
|
||||
iq['to'] = jid
|
||||
if ifrom:
|
||||
iq['from'] = ifrom
|
||||
iq['from'] = ifrom
|
||||
iq['type'] = 'get'
|
||||
iq['query'] = Version.namespace
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@ class xep_0199(base_plugin):
|
||||
self.xmpp.add_event_handler('session_start',
|
||||
self._handle_keepalive,
|
||||
threaded=True)
|
||||
self.xmpp.add_event_handler('session_end',
|
||||
self._handle_session_end)
|
||||
|
||||
def post_init(self):
|
||||
"""Handle cross-plugin dependencies."""
|
||||
@@ -106,6 +108,9 @@ class xep_0199(base_plugin):
|
||||
scheduled_ping,
|
||||
repeat=True)
|
||||
|
||||
def _handle_session_end(self, event):
|
||||
self.xmpp.scheduler.remove('Ping Keep Alive')
|
||||
|
||||
def _handle_ping(self, iq):
|
||||
"""
|
||||
Automatically reply to ping requests.
|
||||
@@ -143,8 +148,7 @@ class xep_0199(base_plugin):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['to'] = jid
|
||||
if ifrom:
|
||||
iq['from'] = ifrom
|
||||
iq['from'] = ifrom
|
||||
iq.enable('ping')
|
||||
|
||||
start_time = time.clock()
|
||||
|
||||
@@ -85,8 +85,7 @@ class xep_0202(base_plugin):
|
||||
"""
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['to'] = 'to'
|
||||
if ifrom:
|
||||
iq['from'] = 'ifrom'
|
||||
iq['to'] = to
|
||||
iq['from'] = ifrom
|
||||
iq.enable('entity_time')
|
||||
return iq.send(**iqargs)
|
||||
|
||||
@@ -53,6 +53,8 @@ class Error(ElementBase):
|
||||
plugin_attrib = 'error'
|
||||
interfaces = set(('code', 'condition', 'text', 'type'))
|
||||
sub_interfaces = set(('text',))
|
||||
plugin_attrib_map = {}
|
||||
plugin_tag_map = {}
|
||||
conditions = set(('bad-request', 'conflict', 'feature-not-implemented',
|
||||
'forbidden', 'gone', 'internal-server-error',
|
||||
'item-not-found', 'jid-malformed', 'not-acceptable',
|
||||
|
||||
@@ -138,7 +138,7 @@ class TestLiveSocket(object):
|
||||
"""
|
||||
with self.send_queue_lock:
|
||||
self.send_queue.put(data)
|
||||
self.socket.send(data)
|
||||
return self.socket.send(data)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# File Socket
|
||||
|
||||
@@ -121,6 +121,7 @@ class TestSocket(object):
|
||||
if self.disconnected:
|
||||
raise socket.error
|
||||
self.send_queue.put(data)
|
||||
return len(data)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# File Socket
|
||||
|
||||
@@ -58,9 +58,6 @@ class SleekTest(unittest.TestCase):
|
||||
unittest.TestCase.__init__(self, *args, **kwargs)
|
||||
self.xmpp = None
|
||||
|
||||
def runTest(self):
|
||||
pass
|
||||
|
||||
def parse_xml(self, xml_string):
|
||||
try:
|
||||
xml = ET.fromstring(xml_string)
|
||||
@@ -336,7 +333,6 @@ class SleekTest(unittest.TestCase):
|
||||
|
||||
# Simulate connecting for mock sockets.
|
||||
self.xmpp.auto_reconnect = False
|
||||
self.xmpp.is_client = True
|
||||
self.xmpp.state._set_state('connected')
|
||||
|
||||
# Must have the stream header ready for xmpp.process() to work.
|
||||
|
||||
13
sleekxmpp/version.py
Normal file
13
sleekxmpp/version.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
# We don't want to have to import the entire library
|
||||
# just to get the version info for setup.py
|
||||
|
||||
__version__ = '1.0rc2'
|
||||
__version_info__ = (1, 0, 0, 'rc2', 0)
|
||||
@@ -73,7 +73,8 @@ class Task(object):
|
||||
otherwise, execute the callback immediately.
|
||||
"""
|
||||
if self.qpointer is not None:
|
||||
self.qpointer.put(('schedule', self.callback, self.args))
|
||||
self.qpointer.put(('schedule', self.callback,
|
||||
self.args, self.name))
|
||||
else:
|
||||
self.callback(*self.args, **self.kwargs)
|
||||
self.reset()
|
||||
@@ -95,31 +96,32 @@ class Scheduler(object):
|
||||
http://docs.python.org/library/sched.html#module-sched
|
||||
|
||||
Attributes:
|
||||
addq -- A queue storing added tasks.
|
||||
schedule -- A list of tasks in order of execution times.
|
||||
thread -- If threaded, the thread processing the schedule.
|
||||
run -- Indicates if the scheduler is running.
|
||||
parentqueue -- A parent event queue in control of this scheduler.
|
||||
|
||||
addq -- A queue storing added tasks.
|
||||
schedule -- A list of tasks in order of execution times.
|
||||
thread -- If threaded, the thread processing the schedule.
|
||||
run -- Indicates if the scheduler is running.
|
||||
stop -- Threading event indicating if the main process
|
||||
has been stopped.
|
||||
Methods:
|
||||
add -- Add a new task to the schedule.
|
||||
process -- Process and schedule tasks.
|
||||
quit -- Stop the scheduler.
|
||||
"""
|
||||
|
||||
def __init__(self, parentqueue=None, parentstop=None):
|
||||
def __init__(self, parentstop=None):
|
||||
"""
|
||||
Create a new scheduler.
|
||||
|
||||
Arguments:
|
||||
parentqueue -- A separate event queue controlling this scheduler.
|
||||
parentstop -- A threading event indicating if the main process has
|
||||
been stopped.
|
||||
"""
|
||||
self.addq = queue.Queue()
|
||||
self.schedule = []
|
||||
self.thread = None
|
||||
self.run = False
|
||||
self.parentqueue = parentqueue
|
||||
self.parentstop = parentstop
|
||||
self.stop = parentstop
|
||||
self.schedule_lock = threading.RLock()
|
||||
|
||||
def process(self, threaded=True):
|
||||
"""
|
||||
@@ -141,8 +143,7 @@ class Scheduler(object):
|
||||
"""Process scheduled tasks."""
|
||||
self.run = True
|
||||
try:
|
||||
while self.run and (self.parentstop is None or \
|
||||
not self.parentstop.isSet()):
|
||||
while self.run and not self.stop.isSet():
|
||||
wait = 1
|
||||
updated = False
|
||||
if self.schedule:
|
||||
@@ -156,6 +157,7 @@ class Scheduler(object):
|
||||
newtask = self.addq.get(True, wait)
|
||||
except queue.Empty:
|
||||
cleanup = []
|
||||
self.schedule_lock.acquire()
|
||||
for task in self.schedule:
|
||||
if time.time() >= task.next:
|
||||
updated = True
|
||||
@@ -167,23 +169,18 @@ class Scheduler(object):
|
||||
x = self.schedule.pop(self.schedule.index(task))
|
||||
else:
|
||||
updated = True
|
||||
self.schedule_lock.acquire()
|
||||
self.schedule.append(newtask)
|
||||
finally:
|
||||
if updated:
|
||||
self.schedule = sorted(self.schedule,
|
||||
key=lambda task: task.next)
|
||||
self.schedule_lock.release()
|
||||
except KeyboardInterrupt:
|
||||
self.run = False
|
||||
if self.parentstop is not None:
|
||||
log.debug("stopping parent")
|
||||
self.parentstop.set()
|
||||
except SystemExit:
|
||||
self.run = False
|
||||
if self.parentstop is not None:
|
||||
self.parentstop.set()
|
||||
log.debug("Quitting Scheduler thread")
|
||||
if self.parentqueue is not None:
|
||||
self.parentqueue.put(('quit', None, None))
|
||||
|
||||
def add(self, name, seconds, callback, args=None,
|
||||
kwargs=None, repeat=False, qpointer=None):
|
||||
@@ -201,8 +198,39 @@ class Scheduler(object):
|
||||
qpointer -- A pointer to an event queue for queuing callback
|
||||
execution instead of executing immediately.
|
||||
"""
|
||||
self.addq.put(Task(name, seconds, callback, args,
|
||||
kwargs, repeat, qpointer))
|
||||
try:
|
||||
self.schedule_lock.acquire()
|
||||
for task in self.schedule:
|
||||
if task.name == name:
|
||||
raise ValueError("Key %s already exists" % name)
|
||||
|
||||
self.addq.put(Task(name, seconds, callback, args,
|
||||
kwargs, repeat, qpointer))
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
self.schedule_lock.release()
|
||||
|
||||
def remove(self, name):
|
||||
"""
|
||||
Remove a scheduled task ahead of schedule, and without
|
||||
executing it.
|
||||
|
||||
Arguments:
|
||||
name -- The name of the task to remove.
|
||||
"""
|
||||
try:
|
||||
self.schedule_lock.acquire()
|
||||
the_task = None
|
||||
for task in self.schedule:
|
||||
if task.name == name:
|
||||
the_task = task
|
||||
if the_task is not None:
|
||||
self.schedule.remove(the_task)
|
||||
except:
|
||||
raise
|
||||
finally:
|
||||
self.schedule_lock.release()
|
||||
|
||||
def quit(self):
|
||||
"""Shutdown the scheduler."""
|
||||
|
||||
@@ -39,15 +39,23 @@ def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
|
||||
the parent stanza.
|
||||
"""
|
||||
tag = "{%s}%s" % (plugin.namespace, plugin.name)
|
||||
|
||||
# Prevent weird memory reference gotchas by ensuring
|
||||
# that the parent stanza class has its own set of
|
||||
# plugin info maps and is not using the mappings from
|
||||
# an ancestor class (like ElementBase).
|
||||
plugin_info = ('plugin_attrib_map', 'plugin_tag_map',
|
||||
'plugin_iterables', 'plugin_overrides')
|
||||
for attr in plugin_info:
|
||||
info = getattr(stanza, attr)
|
||||
setattr(stanza, attr, info.copy())
|
||||
|
||||
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
|
||||
stanza.plugin_tag_map[tag] = plugin
|
||||
|
||||
if iterable:
|
||||
# Prevent weird memory reference gotchas.
|
||||
stanza.plugin_iterables = stanza.plugin_iterables.copy()
|
||||
stanza.plugin_iterables.add(plugin)
|
||||
if overrides:
|
||||
# Prevent weird memory reference gotchas.
|
||||
stanza.plugin_overrides = stanza.plugin_overrides.copy()
|
||||
for interface in plugin.overrides:
|
||||
stanza.plugin_overrides[interface] = plugin.plugin_attrib
|
||||
|
||||
|
||||
@@ -47,6 +47,10 @@ else:
|
||||
# The time in seconds to wait before timing out waiting for response stanzas.
|
||||
RESPONSE_TIMEOUT = 30
|
||||
|
||||
# The time in seconds to wait for events from the event queue, and also the
|
||||
# time between checks for the process stop signal.
|
||||
WAIT_TIMEOUT = 1
|
||||
|
||||
# The number of threads to use to handle XML stream events. This is not the
|
||||
# same as the number of custom event handling threads. HANDLER_THREADS must
|
||||
# be at least 1.
|
||||
@@ -178,6 +182,7 @@ class XMLStream(object):
|
||||
self.ssl_version = ssl.PROTOCOL_TLSv1
|
||||
self.ca_certs = None
|
||||
|
||||
self.wait_timeout = WAIT_TIMEOUT
|
||||
self.response_timeout = RESPONSE_TIMEOUT
|
||||
self.reconnect_delay = None
|
||||
self.reconnect_max_delay = RECONNECT_MAX_DELAY
|
||||
@@ -207,15 +212,19 @@ class XMLStream(object):
|
||||
self.stream_header = "<stream>"
|
||||
self.stream_footer = "</stream>"
|
||||
|
||||
self.whitespace_keepalive = True
|
||||
self.whitespace_keepalive_interval = 300
|
||||
|
||||
self.stop = threading.Event()
|
||||
self.stream_end_event = threading.Event()
|
||||
self.stream_end_event.set()
|
||||
self.session_started_event = threading.Event()
|
||||
self.session_timeout = 45
|
||||
|
||||
self.event_queue = queue.Queue()
|
||||
self.send_queue = queue.Queue()
|
||||
self.__failed_send_stanza = None
|
||||
self.scheduler = Scheduler(self.event_queue, self.stop)
|
||||
self.scheduler = Scheduler(self.stop)
|
||||
|
||||
self.namespace_map = {StanzaBase.xml_ns: 'xml'}
|
||||
|
||||
@@ -229,9 +238,12 @@ class XMLStream(object):
|
||||
self._id_lock = threading.Lock()
|
||||
|
||||
self.auto_reconnect = True
|
||||
self.is_client = False
|
||||
self.dns_answers = []
|
||||
|
||||
self.add_event_handler('connected', self._handle_connected)
|
||||
self.add_event_handler('session_start', self._start_keepalive)
|
||||
self.add_event_handler('session_end', self._end_keepalive)
|
||||
|
||||
def use_signals(self, signals=None):
|
||||
"""
|
||||
Register signal handlers for SIGHUP and SIGTERM, if possible,
|
||||
@@ -320,7 +332,6 @@ class XMLStream(object):
|
||||
except Socket.error:
|
||||
self.default_domain = self.address[0]
|
||||
|
||||
self.is_client = True
|
||||
# Respect previous SSL and TLS usage directives.
|
||||
if use_ssl is not None:
|
||||
self.use_ssl = use_ssl
|
||||
@@ -337,12 +348,13 @@ class XMLStream(object):
|
||||
return connected
|
||||
|
||||
def _connect(self):
|
||||
self.scheduler.remove('Session timeout check')
|
||||
self.stop.clear()
|
||||
if self.default_domain:
|
||||
self.address = self.pick_dns_answer(self.default_domain,
|
||||
self.address[1])
|
||||
self.socket = self.socket_class(Socket.AF_INET, Socket.SOCK_STREAM)
|
||||
self.socket.settimeout(None)
|
||||
self.configure_socket()
|
||||
|
||||
if self.reconnect_delay is None:
|
||||
delay = 1.0
|
||||
@@ -446,6 +458,23 @@ class XMLStream(object):
|
||||
serr.errno, serr.strerror))
|
||||
return False
|
||||
|
||||
def _handle_connected(self, event=None):
|
||||
"""
|
||||
Add check to ensure that a session is established within
|
||||
a reasonable amount of time.
|
||||
"""
|
||||
|
||||
def _handle_session_timeout():
|
||||
if not self.session_started_event.isSet():
|
||||
log.debug("Session start has taken more " + \
|
||||
"than %d seconds" % self.session_timeout)
|
||||
self.disconnect(reconnect=self.auto_reconnect)
|
||||
|
||||
self.schedule("Session timeout check",
|
||||
self.session_timeout,
|
||||
_handle_session_timeout)
|
||||
|
||||
|
||||
def disconnect(self, reconnect=False, wait=False):
|
||||
"""
|
||||
Terminate processing and close the XML streams.
|
||||
@@ -485,9 +514,9 @@ class XMLStream(object):
|
||||
if not self.auto_reconnect:
|
||||
self.stop.set()
|
||||
try:
|
||||
self.socket.shutdown(Socket.SHUT_RDWR)
|
||||
self.socket.close()
|
||||
self.filesocket.close()
|
||||
self.socket.shutdown(Socket.SHUT_RDWR)
|
||||
except Socket.error as serr:
|
||||
self.event('socket_error', serr)
|
||||
finally:
|
||||
@@ -532,6 +561,31 @@ class XMLStream(object):
|
||||
if not ignore:
|
||||
self.state._set_state('connected')
|
||||
|
||||
def configure_socket(self):
|
||||
"""
|
||||
Set timeout and other options for self.socket.
|
||||
|
||||
Meant to be overridden.
|
||||
"""
|
||||
self.socket.settimeout(None)
|
||||
|
||||
def configure_dns(self, resolver, domain=None, port=None):
|
||||
"""
|
||||
Configure and set options for a dns.resolver.Resolver
|
||||
instance, and other DNS related tasks. For example, you
|
||||
can also check Socket.getaddrinfo to see if you need to
|
||||
call out to libresolv.so.2 to run res_init().
|
||||
|
||||
Meant to be overridden.
|
||||
|
||||
Arguments:
|
||||
resolver -- A dns.resolver.Resolver instance, or None
|
||||
if dnspython is not installed.
|
||||
domain -- The initial domain under consideration.
|
||||
port -- The initial port under consideration.
|
||||
"""
|
||||
pass
|
||||
|
||||
def start_tls(self):
|
||||
"""
|
||||
Perform handshakes for TLS.
|
||||
@@ -566,6 +620,30 @@ class XMLStream(object):
|
||||
log.warning("Tried to enable TLS, but ssl module not found.")
|
||||
return False
|
||||
|
||||
def _start_keepalive(self, event):
|
||||
"""
|
||||
Begin sending whitespace periodically to keep the connection alive.
|
||||
|
||||
May be disabled by setting:
|
||||
self.whitespace_keepalive = False
|
||||
|
||||
The keepalive interval can be set using:
|
||||
self.whitespace_keepalive_interval = 300
|
||||
"""
|
||||
|
||||
def send_keepalive():
|
||||
if self.send_queue.empty():
|
||||
self.send_raw(' ')
|
||||
|
||||
self.schedule('Whitespace Keepalive',
|
||||
self.whitespace_keepalive_interval,
|
||||
send_keepalive,
|
||||
repeat=True)
|
||||
|
||||
def _end_keepalive(self, event):
|
||||
"""Stop sending whitespace keepalives"""
|
||||
self.scheduler.remove('Whitespace Keepalive')
|
||||
|
||||
def start_stream_handler(self, xml):
|
||||
"""
|
||||
Perform any initialization actions, such as handshakes, once the
|
||||
@@ -669,18 +747,24 @@ class XMLStream(object):
|
||||
if port is None:
|
||||
port = self.default_port
|
||||
if DNSPYTHON:
|
||||
resolver = dns.resolver.get_default_resolver()
|
||||
self.configure_dns(resolver, domain=domain, port=port)
|
||||
|
||||
try:
|
||||
answers = dns.resolver.query(domain, dns.rdatatype.A)
|
||||
answers = resolver.query(domain, dns.rdatatype.A)
|
||||
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
||||
log.warning("No A records for %s" % domain)
|
||||
return [((domain, port), 0, 0)]
|
||||
except dns.exception.Timeout:
|
||||
log.warning("DNS resolution timed out " + \
|
||||
"for A record of %s" % domain)
|
||||
answers = [((answer.address, port), 0, 0) for answer in answers]
|
||||
return answers
|
||||
return [((domain, port), 0, 0)]
|
||||
else:
|
||||
return [((ans.address, port), 0, 0) for ans in answers]
|
||||
else:
|
||||
log.warning("dnspython is not installed -- " + \
|
||||
"relying on OS A record resolution")
|
||||
self.configure_dns(None, domain=domain, port=port)
|
||||
return [((domain, port), 0, 0)]
|
||||
|
||||
def pick_dns_answer(self, domain, port=None):
|
||||
@@ -905,7 +989,15 @@ class XMLStream(object):
|
||||
if now:
|
||||
log.debug("SEND (IMMED): %s" % data)
|
||||
try:
|
||||
self.socket.send(data.encode('utf-8'))
|
||||
data = data.encode('utf-8')
|
||||
total = len(data)
|
||||
sent = 0
|
||||
count = 0
|
||||
while sent < total and not self.stop.is_set():
|
||||
sent += self.socket.send(data[sent:])
|
||||
count += 1
|
||||
if count > 1:
|
||||
log.debug('SENT: %d chunks' % count)
|
||||
except Socket.error as serr:
|
||||
self.event('socket_error', serr)
|
||||
log.warning("Failed to send %s" % data)
|
||||
@@ -973,44 +1065,48 @@ class XMLStream(object):
|
||||
Processing will continue after any recoverable errors
|
||||
if reconnections are allowed.
|
||||
"""
|
||||
firstrun = True
|
||||
|
||||
# The body of this loop will only execute once per connection.
|
||||
# Additional passes will be made only if an error occurs and
|
||||
# reconnecting is permitted.
|
||||
while firstrun or (self.auto_reconnect and not self.stop.isSet()):
|
||||
firstrun = False
|
||||
while True:
|
||||
try:
|
||||
if self.is_client:
|
||||
self.send_raw(self.stream_header, now=True)
|
||||
# The call to self.__read_xml will block and prevent
|
||||
# the body of the loop from running until a disconnect
|
||||
# occurs. After any reconnection, the stream header will
|
||||
# be resent and processing will resume.
|
||||
while not self.stop.isSet() and self.__read_xml():
|
||||
while not self.stop.is_set():
|
||||
# Only process the stream while connected to the server
|
||||
if not self.state.ensure('connected', wait=0.1,
|
||||
block_on_transition=True):
|
||||
continue
|
||||
# Ensure the stream header is sent for any
|
||||
# new connections.
|
||||
if self.is_client:
|
||||
if not self.session_started_event.is_set():
|
||||
self.send_raw(self.stream_header, now=True)
|
||||
self.__read_xml()
|
||||
except KeyboardInterrupt:
|
||||
log.debug("Keyboard Escape Detected in _process")
|
||||
self.stop.set()
|
||||
except SystemExit:
|
||||
log.debug("SystemExit in _process")
|
||||
self.stop.set()
|
||||
self.scheduler.quit()
|
||||
except Socket.error as serr:
|
||||
self.event('socket_error', serr)
|
||||
log.exception('Socket Error')
|
||||
except:
|
||||
if not self.stop.isSet():
|
||||
if not self.stop.is_set():
|
||||
log.exception('Connection error.')
|
||||
if not self.stop.isSet() and self.auto_reconnect:
|
||||
self.reconnect()
|
||||
|
||||
if not self.stop.is_set():
|
||||
if self.auto_reconnect:
|
||||
self.reconnect()
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
self.event('killed', direct=True)
|
||||
self.disconnect()
|
||||
self.event_queue.put(('quit', None, None))
|
||||
self.scheduler.run = False
|
||||
break
|
||||
|
||||
def __read_xml(self):
|
||||
"""
|
||||
@@ -1150,7 +1246,8 @@ class XMLStream(object):
|
||||
try:
|
||||
while not self.stop.isSet():
|
||||
try:
|
||||
event = self.event_queue.get(True, timeout=5)
|
||||
wait = self.wait_timeout
|
||||
event = self.event_queue.get(True, timeout=wait)
|
||||
except queue.Empty:
|
||||
event = None
|
||||
if event is None:
|
||||
@@ -1168,8 +1265,9 @@ class XMLStream(object):
|
||||
log.exception(error_msg % handler.name)
|
||||
orig.exception(e)
|
||||
elif etype == 'schedule':
|
||||
name = args[1]
|
||||
try:
|
||||
log.debug('Scheduled event: %s' % args)
|
||||
log.debug('Scheduled event: %s: %s' % (name, args[0]))
|
||||
handler(*args[0])
|
||||
except Exception as e:
|
||||
log.exception('Error processing scheduled task')
|
||||
@@ -1211,7 +1309,7 @@ class XMLStream(object):
|
||||
Extract stanzas from the send queue and send them on the stream.
|
||||
"""
|
||||
try:
|
||||
while not self.stop.isSet():
|
||||
while not self.stop.is_set():
|
||||
self.session_started_event.wait()
|
||||
if self.__failed_send_stanza is not None:
|
||||
data = self.__failed_send_stanza
|
||||
@@ -1223,22 +1321,26 @@ class XMLStream(object):
|
||||
continue
|
||||
log.debug("SEND: %s" % data)
|
||||
try:
|
||||
self.socket.send(data.encode('utf-8'))
|
||||
enc_data = data.encode('utf-8')
|
||||
total = len(enc_data)
|
||||
sent = 0
|
||||
count = 0
|
||||
while sent < total and not self.stop.is_set():
|
||||
sent += self.socket.send(enc_data[sent:])
|
||||
count += 1
|
||||
if count > 1:
|
||||
log.debug('SENT: %d chunks' % count)
|
||||
self.send_queue.task_done()
|
||||
except Socket.error as serr:
|
||||
self.event('socket_error', serr)
|
||||
log.warning("Failed to send %s" % data)
|
||||
self.__failed_send_stanza = data
|
||||
self.disconnect(self.auto_reconnect)
|
||||
except KeyboardInterrupt:
|
||||
log.debug("Keyboard Escape Detected in _send_thread")
|
||||
self.event('killed', direct=True)
|
||||
self.disconnect()
|
||||
return
|
||||
except SystemExit:
|
||||
self.disconnect()
|
||||
self.event_queue.put(('quit', None, None))
|
||||
return
|
||||
except Exception as ex:
|
||||
log.exception('Unexpected error in send thread: %s' % ex)
|
||||
self.exception(ex)
|
||||
if not self.stop.is_set():
|
||||
self.disconnect(self.auto_reconnect)
|
||||
|
||||
def exception(self, exception):
|
||||
"""
|
||||
|
||||
117
testall.py
Normal file → Executable file
117
testall.py
Normal file → Executable file
@@ -1,70 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
import unittest
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import unittest
|
||||
import distutils.core
|
||||
|
||||
class testoverall(unittest.TestCase):
|
||||
from glob import glob
|
||||
from os.path import splitext, basename, join as pjoin
|
||||
|
||||
def testModules(self):
|
||||
"""Testing all modules by compiling them"""
|
||||
import compileall
|
||||
import re
|
||||
if sys.version_info < (3,0):
|
||||
self.failUnless(compileall.compile_dir('.' + os.sep + 'sleekxmpp', rx=re.compile('/[.]svn'), quiet=True))
|
||||
else:
|
||||
self.failUnless(compileall.compile_dir('.' + os.sep + 'sleekxmpp', rx=re.compile('/[.]svn|.*26.*'), quiet=True))
|
||||
|
||||
def testTabNanny(self):
|
||||
"""Invoking the tabnanny"""
|
||||
import tabnanny
|
||||
self.failIf(tabnanny.check("." + os.sep + 'sleekxmpp'))
|
||||
#raise "Help!"
|
||||
def run_tests():
|
||||
"""
|
||||
Find and run all tests in the tests/ directory.
|
||||
|
||||
Excludes live tests (tests/live_*).
|
||||
"""
|
||||
testfiles = ['tests.test_overall']
|
||||
exclude = ['__init__.py', 'test_overall.py']
|
||||
for t in glob(pjoin('tests', '*.py')):
|
||||
if True not in [t.endswith(ex) for ex in exclude]:
|
||||
if basename(t).startswith('test_'):
|
||||
testfiles.append('tests.%s' % splitext(basename(t))[0])
|
||||
|
||||
suites = []
|
||||
for file in testfiles:
|
||||
__import__(file)
|
||||
suites.append(sys.modules[file].suite)
|
||||
|
||||
tests = unittest.TestSuite(suites)
|
||||
runner = unittest.TextTestRunner(verbosity=2)
|
||||
|
||||
# Disable logging output
|
||||
logging.basicConfig(level=100)
|
||||
logging.disable(100)
|
||||
|
||||
result = runner.run(tests)
|
||||
return result
|
||||
|
||||
|
||||
# Add a 'test' command for setup.py
|
||||
|
||||
class TestCommand(distutils.core.Command):
|
||||
|
||||
user_options = [ ]
|
||||
|
||||
def initialize_options(self):
|
||||
self._dir = os.getcwd()
|
||||
|
||||
def finalize_options(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
run_tests()
|
||||
|
||||
def disabled_testMethodLength(self):
|
||||
"""Testing for excessive method lengths"""
|
||||
import re
|
||||
dirs = os.walk(sys.path[0] + os.sep + 'sleekxmpp')
|
||||
offenders = []
|
||||
for d in dirs:
|
||||
if not '.svn' in d[0]:
|
||||
for filename in d[2]:
|
||||
if filename.endswith('.py') and d[0].find("template%stemplates" % os.sep) == -1:
|
||||
with open("%s%s%s" % (d[0],os.sep,filename), "r") as fp:
|
||||
cur = None
|
||||
methodline = lineno = methodlen = methodindent = 0
|
||||
for line in fp:
|
||||
indentlevel = re.compile("^[\t ]*").search(line).end()
|
||||
line = line.expandtabs()
|
||||
lineno += 1
|
||||
if line.strip().startswith("def ") or line.strip().startswith("except") or (line.strip() and methodindent > indentlevel) or (line.strip() and methodindent == indentlevel): #new method found or old one ended
|
||||
if cur: #existing method needs final evaluation
|
||||
if methodlen > 50 and not cur.strip().startswith("def setupUi"):
|
||||
offenders.append("Method '%s' on line %s of %s/%s is longer than 50 lines (%s)" % (cur.strip(),methodline,d[0][len(rootp):],filename,methodlen))
|
||||
methodlen = 0
|
||||
cur = line
|
||||
methodindent = indentlevel
|
||||
methodline = lineno
|
||||
if line and cur and not line.strip().startswith("#") and not (cur.strip().startswith("try:") and methodindent == 0): #if we weren't all whitespace and weren't a comment
|
||||
methodlen += 1
|
||||
self.failIf(offenders,"\n".join(offenders))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=100)
|
||||
logging.disable(100)
|
||||
#this doesn't need to be very clean
|
||||
alltests = [unittest.TestLoader().loadTestsFromTestCase(testoverall)]
|
||||
rootp = sys.path[0] + os.sep + 'tests'
|
||||
dirs = os.walk(rootp)
|
||||
for d in dirs:
|
||||
if not '.svn' in d[0]:
|
||||
for filename in d[2]:
|
||||
if filename.startswith('test_') and filename.endswith('.py'):
|
||||
modname = ('tests' + "." + filename)[:-3].replace(os.sep,'.')
|
||||
__import__(modname)
|
||||
#sys.modules[modname].config = moduleconfig
|
||||
alltests.append(sys.modules[modname].suite)
|
||||
alltests_suite = unittest.TestSuite(alltests)
|
||||
result = unittest.TextTestRunner(verbosity=2).run(alltests_suite)
|
||||
print("""<tests xmlns='http://andyet.net/protocol/tests' ran='%s' errors='%s' fails='%s' success='%s' />""" % (result.testsRun, len(result.errors), len(result.failures), result.wasSuccessful()))
|
||||
result = run_tests()
|
||||
print("<tests %s ran='%s' errors='%s' fails='%s' success='%s' />" % (
|
||||
"xmlns='http//andyet.net/protocol/tests'",
|
||||
result.testsRun, len(result.errors),
|
||||
len(result.failures), result.wasSuccessful()))
|
||||
|
||||
29
tests/test_overall.py
Normal file
29
tests/test_overall.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import unittest
|
||||
import tabnanny
|
||||
import compileall
|
||||
|
||||
class TestOverall(unittest.TestCase):
|
||||
|
||||
"""
|
||||
Test overall package health by compiling and checking
|
||||
code style.
|
||||
"""
|
||||
|
||||
def testModules(self):
|
||||
"""Testing all modules by compiling them"""
|
||||
src = '.%ssleekxmpp' % os.sep
|
||||
if sys.version_info < (3, 0):
|
||||
rx = re.compile('/[.]svn')
|
||||
else:
|
||||
rx = re.compile('/[.]svn|.*26.*')
|
||||
self.failUnless(compileall.compile_dir(src, rx=rx, quiet=True))
|
||||
|
||||
def testTabNanny(self):
|
||||
"""Testing that indentation is consistent"""
|
||||
self.failIf(tabnanny.check('..%ssleekxmpp' % os.sep))
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestOverall)
|
||||
@@ -148,14 +148,13 @@ class TestPubsubStanzas(SleekTest):
|
||||
iq = self.Iq()
|
||||
iq['pubsub_owner']['default']
|
||||
iq['pubsub_owner']['default']['node'] = 'mynode'
|
||||
iq['pubsub_owner']['default']['type'] = 'leaf'
|
||||
iq['pubsub_owner']['default']['form'].addField('pubsub#title',
|
||||
ftype='text-single',
|
||||
value='This thing is awesome')
|
||||
self.check(iq, """
|
||||
<iq id="0">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<default node="mynode" type="leaf">
|
||||
<default node="mynode">
|
||||
<x xmlns="jabber:x:data" type="form">
|
||||
<field var="pubsub#title" type="text-single">
|
||||
<value>This thing is awesome</value>
|
||||
@@ -213,6 +212,9 @@ class TestPubsubStanzas(SleekTest):
|
||||
item2['payload'] = payload2
|
||||
iq['pubsub']['publish'].append(item)
|
||||
iq['pubsub']['publish'].append(item2)
|
||||
form = xep_0004.Form()
|
||||
form.addField('pubsub#description', ftype='text-single', value='this thing is awesome')
|
||||
iq['pubsub']['publish_options'] = form
|
||||
|
||||
self.check(iq, """
|
||||
<iq id="0">
|
||||
@@ -231,6 +233,13 @@ class TestPubsubStanzas(SleekTest):
|
||||
</thinger2>
|
||||
</item>
|
||||
</publish>
|
||||
<publish-options>
|
||||
<x xmlns="jabber:x:data" type="submit">
|
||||
<field var="pubsub#description">
|
||||
<value>this thing is awesome</value>
|
||||
</field>
|
||||
</x>
|
||||
</publish-options>
|
||||
</pubsub>
|
||||
</iq>""")
|
||||
|
||||
@@ -508,4 +517,59 @@ class TestPubsubStanzas(SleekTest):
|
||||
</event>
|
||||
</message>""")
|
||||
|
||||
def testPubsubError(self):
|
||||
"""Test getting a pubsub specific condition from an error stanza"""
|
||||
iq = self.Iq()
|
||||
iq['error']['type'] = 'cancel'
|
||||
iq['error']['code'] = '501'
|
||||
iq['error']['condition'] = 'feature-not-implemented'
|
||||
iq['error']['pubsub']['condition'] = 'subid-required'
|
||||
self.check(iq, """
|
||||
<iq type="error">
|
||||
<error type="cancel" code="501">
|
||||
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||
<subid-required xmlns="http://jabber.org/protocol/pubsub#errors" />
|
||||
</error>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
del iq['error']['pubsub']['condition']
|
||||
self.check(iq, """
|
||||
<iq type="error">
|
||||
<error type="cancel" code="501">
|
||||
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||
</error>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
def testPubsubUnsupportedError(self):
|
||||
"""Test getting the feature from an unsupported error"""
|
||||
iq = self.Iq()
|
||||
iq['error']['type'] = 'cancel'
|
||||
iq['error']['code'] = '501'
|
||||
iq['error']['condition'] = 'feature-not-implemented'
|
||||
iq['error']['pubsub']['condition'] = 'unsupported'
|
||||
iq['error']['pubsub']['unsupported'] = 'instant-node'
|
||||
self.check(iq, """
|
||||
<iq type="error">
|
||||
<error type="cancel" code="501">
|
||||
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||
<unsupported xmlns="http://jabber.org/protocol/pubsub#errors" feature="instant-node" />
|
||||
</error>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
self.assertEqual(iq['error']['pubsub']['condition'], 'unsupported')
|
||||
self.assertEqual(iq['error']['pubsub']['unsupported'], 'instant-node')
|
||||
|
||||
del iq['error']['pubsub']['unsupported']
|
||||
self.check(iq, """
|
||||
<iq type="error">
|
||||
<error type="cancel" code="501">
|
||||
<feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||
</error>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestPubsubStanzas)
|
||||
|
||||
794
tests/test_stream_xep_0060.py
Normal file
794
tests/test_stream_xep_0060.py
Normal file
@@ -0,0 +1,794 @@
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
|
||||
from sleekxmpp.test import *
|
||||
from sleekxmpp.stanza.atom import AtomEntry
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
|
||||
|
||||
class TestStreamPubsub(SleekTest):
|
||||
|
||||
"""
|
||||
Test using the XEP-0030 plugin.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.stream_start()
|
||||
|
||||
def tearDown(self):
|
||||
self.stream_close()
|
||||
|
||||
def testCreateInstantNode(self):
|
||||
"""Test creating an instant node"""
|
||||
t = threading.Thread(name='create_node',
|
||||
target=self.xmpp['xep_0060'].create_node,
|
||||
args=('pubsub.example.com', None))
|
||||
t.start()
|
||||
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<create />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<iq type="result" id="1"
|
||||
to="tester@localhost" from="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<create node="25e3d37dabbab9541f7523321421edc5bfeb2dae" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
t.join()
|
||||
|
||||
def testCreateNodeNoConfig(self):
|
||||
"""Test creating a node without a config"""
|
||||
self.xmpp['xep_0060'].create_node(
|
||||
'pubsub.example.com',
|
||||
'princely_musings',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<create node="princely_musings" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testCreateNodeConfig(self):
|
||||
"""Test creating a node with a config"""
|
||||
form = self.xmpp['xep_0004'].stanza.Form()
|
||||
form['type'] = 'submit'
|
||||
form.add_field(var='pubsub#access_model', value='whitelist')
|
||||
|
||||
self.xmpp['xep_0060'].create_node(
|
||||
'pubsub.example.com',
|
||||
'princely_musings',
|
||||
config=form, block=False)
|
||||
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<create node="princely_musings" />
|
||||
<configure>
|
||||
<x xmlns="jabber:x:data" type="submit">
|
||||
<field var="pubsub#access_model">
|
||||
<value>whitelist</value>
|
||||
</field>
|
||||
<field var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
</x>
|
||||
</configure>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testDeleteNode(self):
|
||||
"""Test deleting a node"""
|
||||
self.xmpp['xep_0060'].delete_node(
|
||||
'pubsub.example.com',
|
||||
'some_node',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" to="pubsub.example.com" id="1">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<delete node="some_node" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testSubscribeCase1(self):
|
||||
"""
|
||||
Test subscribing to a node: Case 1:
|
||||
No subscribee, default 'from' JID, bare JID
|
||||
"""
|
||||
self.xmpp['xep_0060'].subscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscribe node="somenode" jid="tester@localhost" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testSubscribeCase2(self):
|
||||
"""
|
||||
Test subscribing to a node: Case 2:
|
||||
No subscribee, given 'from' JID, bare JID
|
||||
"""
|
||||
self.xmpp['xep_0060'].subscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
ifrom='foo@comp.example.com/bar',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1"
|
||||
to="pubsub.example.com" from="foo@comp.example.com/bar">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscribe node="somenode" jid="foo@comp.example.com" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testSubscribeCase3(self):
|
||||
"""
|
||||
Test subscribing to a node: Case 3:
|
||||
No subscribee, given 'from' JID, full JID
|
||||
"""
|
||||
self.xmpp['xep_0060'].subscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
ifrom='foo@comp.example.com/bar',
|
||||
bare=False,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1"
|
||||
to="pubsub.example.com" from="foo@comp.example.com/bar">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscribe node="somenode" jid="foo@comp.example.com/bar" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testSubscribeCase4(self):
|
||||
"""
|
||||
Test subscribing to a node: Case 4:
|
||||
No subscribee, no 'from' JID, full JID
|
||||
"""
|
||||
self.stream_close()
|
||||
self.stream_start(jid='tester@localhost/full')
|
||||
|
||||
self.xmpp['xep_0060'].subscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
bare=False,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1"
|
||||
to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscribe node="somenode" jid="tester@localhost/full" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testSubscribeCase5(self):
|
||||
"""
|
||||
Test subscribing to a node: Case 5:
|
||||
Subscribee given
|
||||
"""
|
||||
self.xmpp['xep_0060'].subscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
subscribee='user@example.com/foo',
|
||||
ifrom='foo@comp.example.com/bar',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1"
|
||||
to="pubsub.example.com" from="foo@comp.example.com/bar">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscribe node="somenode" jid="user@example.com/foo" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testSubscribeWithOptions(self):
|
||||
"""Test subscribing to a node, with options."""
|
||||
opts = self.xmpp['xep_0004'].make_form()
|
||||
opts.add_field(
|
||||
var='FORM_TYPE',
|
||||
value='http://jabber.org/protocol/pubsub#subscribe_options',
|
||||
ftype='hidden')
|
||||
opts.add_field(
|
||||
var='pubsub#digest',
|
||||
value=False,
|
||||
ftype='boolean')
|
||||
opts['type'] = 'submit'
|
||||
|
||||
self.xmpp['xep_0060'].subscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
options=opts,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscribe node="somenode" jid="tester@localhost" />
|
||||
<options>
|
||||
<x xmlns="jabber:x:data" type="submit">
|
||||
<field var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#subscribe_options</value>
|
||||
</field>
|
||||
<field var="pubsub#digest">
|
||||
<value>0</value>
|
||||
</field>
|
||||
</x>
|
||||
</options>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testUnsubscribeCase1(self):
|
||||
"""
|
||||
Test unsubscribing from a node: Case 1:
|
||||
No subscribee, default 'from' JID, bare JID
|
||||
"""
|
||||
self.xmpp['xep_0060'].unsubscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<unsubscribe node="somenode" jid="tester@localhost" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testUnsubscribeCase2(self):
|
||||
"""
|
||||
Test unsubscribing from a node: Case 2:
|
||||
No subscribee, given 'from' JID, bare JID
|
||||
"""
|
||||
self.xmpp['xep_0060'].unsubscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
ifrom='foo@comp.example.com/bar',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1"
|
||||
to="pubsub.example.com" from="foo@comp.example.com/bar">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<unsubscribe node="somenode" jid="foo@comp.example.com" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testUnsubscribeCase3(self):
|
||||
"""
|
||||
Test unsubscribing from a node: Case 3:
|
||||
No subscribee, given 'from' JID, full JID
|
||||
"""
|
||||
self.xmpp['xep_0060'].unsubscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
ifrom='foo@comp.example.com/bar',
|
||||
bare=False,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1"
|
||||
to="pubsub.example.com" from="foo@comp.example.com/bar">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<unsubscribe node="somenode" jid="foo@comp.example.com/bar" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testUnsubscribeCase4(self):
|
||||
"""
|
||||
Test unsubscribing from a node: Case 4:
|
||||
No subscribee, no 'from' JID, full JID
|
||||
"""
|
||||
self.stream_close()
|
||||
self.stream_start(jid='tester@localhost/full')
|
||||
|
||||
self.xmpp['xep_0060'].unsubscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
bare=False,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1"
|
||||
to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<unsubscribe node="somenode" jid="tester@localhost/full" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testUnsubscribeCase5(self):
|
||||
"""
|
||||
Test unsubscribing from a node: Case 5:
|
||||
Subscribee given
|
||||
"""
|
||||
self.xmpp['xep_0060'].unsubscribe(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
subscribee='user@example.com/foo',
|
||||
ifrom='foo@comp.example.com/bar',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1"
|
||||
to="pubsub.example.com" from="foo@comp.example.com/bar">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<unsubscribe node="somenode" jid="user@example.com/foo" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetDefaultNodeConfig(self):
|
||||
"""Test retrieving the default node config for a pubsub service."""
|
||||
self.xmpp['xep_0060'].get_node_config(
|
||||
'pubsub.example.com',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<default />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
def testGetNodeConfig(self):
|
||||
"""Test getting the config for a given node."""
|
||||
self.xmpp['xep_0060'].get_node_config(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<configure node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
def testSetNodeConfig(self):
|
||||
"""Test setting the configuration for a node."""
|
||||
form = self.xmpp['xep_0004'].make_form()
|
||||
form.add_field(var='FORM_TYPE', ftype='hidden',
|
||||
value='http://jabber.org/protocol/pubsub#node_config')
|
||||
form.add_field(var='pubsub#title', ftype='text-single',
|
||||
value='This is awesome!')
|
||||
form['type'] = 'submit'
|
||||
|
||||
self.xmpp['xep_0060'].set_node_config(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
form,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<configure node="somenode">
|
||||
<x xmlns="jabber:x:data" type="submit">
|
||||
<field var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||
</field>
|
||||
<field var="pubsub#title">
|
||||
<value>This is awesome!</value>
|
||||
</field>
|
||||
</x>
|
||||
</configure>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testPublishNoItems(self):
|
||||
"""Test publishing no items (in order to generate events)"""
|
||||
self.xmpp['xep_0060'].publish(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<publish node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testPublishSingle(self):
|
||||
"""Test publishing a single item."""
|
||||
payload = AtomEntry()
|
||||
payload['title'] = 'Test'
|
||||
|
||||
register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, AtomEntry)
|
||||
|
||||
self.xmpp['xep_0060'].publish(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
id='id42',
|
||||
payload=payload,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<publish node="somenode">
|
||||
<item id="id42">
|
||||
<entry xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Test</title>
|
||||
</entry>
|
||||
</item>
|
||||
</publish>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testPublishSingleOptions(self):
|
||||
"""Test publishing a single item, with options."""
|
||||
payload = AtomEntry()
|
||||
payload['title'] = 'Test'
|
||||
|
||||
register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, AtomEntry)
|
||||
|
||||
options = self.xmpp['xep_0004'].make_form()
|
||||
options.add_field(var='FORM_TYPE', ftype='hidden',
|
||||
value='http://jabber.org/protocol/pubsub#publish-options')
|
||||
options.add_field(var='pubsub#access_model', ftype='text-single',
|
||||
value='presence')
|
||||
options['type'] = 'submit'
|
||||
|
||||
self.xmpp['xep_0060'].publish(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
id='ID42',
|
||||
payload=payload,
|
||||
options=options,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<publish node="somenode">
|
||||
<item id="ID42">
|
||||
<entry xmlns="http://www.w3.org/2005/Atom">
|
||||
<title>Test</title>
|
||||
</entry>
|
||||
</item>
|
||||
</publish>
|
||||
<publish-options>
|
||||
<x xmlns="jabber:x:data" type="submit">
|
||||
<field var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#publish-options</value>
|
||||
</field>
|
||||
<field var="pubsub#access_model">
|
||||
<value>presence</value>
|
||||
</field>
|
||||
</x>
|
||||
</publish-options>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
def testRetract(self):
|
||||
"""Test deleting an item."""
|
||||
self.xmpp['xep_0060'].retract(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
'ID1',
|
||||
notify=True,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<retract node="somenode" notify="true">
|
||||
<item id="ID1" />
|
||||
</retract>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testRetract(self):
|
||||
"""Test deleting an item."""
|
||||
self.xmpp['xep_0060'].retract(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
'ID1',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<retract node="somenode">
|
||||
<item id="ID1" />
|
||||
</retract>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testPurge(self):
|
||||
"""Test removing all items from a node."""
|
||||
self.xmpp['xep_0060'].purge(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<purge node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetItem(self):
|
||||
"""Test retrieving a single item."""
|
||||
self.xmpp['xep_0060'].get_item(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
'id42',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<items node="somenode">
|
||||
<item id="id42" />
|
||||
</items>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetLatestItems(self):
|
||||
"""Test retrieving the most recent N items."""
|
||||
self.xmpp['xep_0060'].get_items(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
max_items=3,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<items node="somenode" max_items="3" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetAllItems(self):
|
||||
"""Test retrieving all items."""
|
||||
self.xmpp['xep_0060'].get_items(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<items node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetSpecificItems(self):
|
||||
"""Test retrieving a specific set of items."""
|
||||
self.xmpp['xep_0060'].get_items(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
item_ids=['A', 'B', 'C'],
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<items node="somenode">
|
||||
<item id="A" />
|
||||
<item id="B" />
|
||||
<item id="C" />
|
||||
</items>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetSubscriptionGlobalDefaultOptions(self):
|
||||
"""Test getting the subscription options for a node/JID."""
|
||||
self.xmpp['xep_0060'].get_subscription_options(
|
||||
'pubsub.example.com',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<default />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
def testGetSubscriptionNodeDefaultOptions(self):
|
||||
"""Test getting the subscription options for a node/JID."""
|
||||
self.xmpp['xep_0060'].get_subscription_options(
|
||||
'pubsub.example.com',
|
||||
node='somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<default node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
def testGetSubscriptionOptions(self):
|
||||
"""Test getting the subscription options for a node/JID."""
|
||||
self.xmpp['xep_0060'].get_subscription_options(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
'tester@localhost',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<options node="somenode" jid="tester@localhost" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""", use_values=False)
|
||||
|
||||
def testSetSubscriptionOptions(self):
|
||||
"""Test setting the subscription options for a node/JID."""
|
||||
opts = self.xmpp['xep_0004'].make_form()
|
||||
opts.add_field(
|
||||
var='FORM_TYPE',
|
||||
value='http://jabber.org/protocol/pubsub#subscribe_options',
|
||||
ftype='hidden')
|
||||
opts.add_field(
|
||||
var='pubsub#digest',
|
||||
value=False,
|
||||
ftype='boolean')
|
||||
opts['type'] = 'submit'
|
||||
|
||||
self.xmpp['xep_0060'].set_subscription_options(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
'tester@localhost',
|
||||
opts,
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<options node="somenode" jid="tester@localhost">
|
||||
<x xmlns="jabber:x:data" type="submit">
|
||||
<field var="FORM_TYPE">
|
||||
<value>http://jabber.org/protocol/pubsub#subscribe_options</value>
|
||||
</field>
|
||||
<field var="pubsub#digest">
|
||||
<value>0</value>
|
||||
</field>
|
||||
</x>
|
||||
</options>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetNodeSubscriptions(self):
|
||||
"""Test retrieving all subscriptions for a node."""
|
||||
self.xmpp['xep_0060'].get_node_subscriptions(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<subscriptions node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetSubscriptions(self):
|
||||
"""Test retrieving a users's subscriptions."""
|
||||
self.xmpp['xep_0060'].get_subscriptions(
|
||||
'pubsub.example.com',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscriptions />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetSubscriptionsForNode(self):
|
||||
"""Test retrieving a users's subscriptions for a given node."""
|
||||
self.xmpp['xep_0060'].get_subscriptions(
|
||||
'pubsub.example.com',
|
||||
node='somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<subscriptions node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetAffiliations(self):
|
||||
"""Test retrieving a users's affiliations."""
|
||||
self.xmpp['xep_0060'].get_affiliations(
|
||||
'pubsub.example.com',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<affiliations />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetAffiliatinssForNode(self):
|
||||
"""Test retrieving a users's affiliations for a given node."""
|
||||
self.xmpp['xep_0060'].get_affiliations(
|
||||
'pubsub.example.com',
|
||||
node='somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||
<affiliations node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testGetNodeAffiliations(self):
|
||||
"""Test getting the affiliations for a node."""
|
||||
self.xmpp['xep_0060'].get_node_affiliations(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="get" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<affiliations node="somenode" />
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testModifySubscriptions(self):
|
||||
"""Test owner modifying node subscriptions."""
|
||||
self.xmpp['xep_0060'].modify_subscriptions(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
subscriptions=[('user@example.com', 'subscribed'),
|
||||
('foo@example.net', 'none')],
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<subscriptions node="somenode">
|
||||
<subscription jid="user@example.com" subscription="subscribed" />
|
||||
<subscription jid="foo@example.net" subscription="none" />
|
||||
</subscriptions>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
def testModifyAffiliations(self):
|
||||
"""Test owner modifying node affiliations."""
|
||||
self.xmpp['xep_0060'].modify_affiliations(
|
||||
'pubsub.example.com',
|
||||
'somenode',
|
||||
affiliations=[('user@example.com', 'publisher'),
|
||||
('foo@example.net', 'none')],
|
||||
block=False)
|
||||
self.send("""
|
||||
<iq type="set" id="1" to="pubsub.example.com">
|
||||
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||
<affiliations node="somenode">
|
||||
<affiliation jid="user@example.com" affiliation="publisher" />
|
||||
<affiliation jid="foo@example.net" affiliation="none" />
|
||||
</affiliations>
|
||||
</pubsub>
|
||||
</iq>
|
||||
""")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamPubsub)
|
||||
Reference in New Issue
Block a user