Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7945b3e738 | ||
![]() |
d4c1ff5309 | ||
![]() |
22868c3924 | ||
![]() |
2de1be188c | ||
![]() |
9faecec2db | ||
![]() |
5d7111fe3b | ||
![]() |
0c86f8288d | ||
![]() |
5a6a65fd9f | ||
![]() |
43c4d23896 | ||
![]() |
9f9e8db814 | ||
![]() |
b8efcc7cf0 | ||
![]() |
2f29d18e53 | ||
![]() |
888e286a09 | ||
![]() |
1a93a187f0 | ||
![]() |
a8d5da5091 | ||
![]() |
e2720fac9e | ||
![]() |
4374729f20 | ||
![]() |
87999333cb | ||
![]() |
335dc2927b | ||
![]() |
ccbef6b696 | ||
![]() |
3e384d3cfe | ||
![]() |
e33949c397 | ||
![]() |
eccac859ad | ||
![]() |
7dd586f2fd | ||
![]() |
3607c5b792 | ||
![]() |
e37adace62 | ||
![]() |
d10f591bf4 | ||
![]() |
262da78ca7 | ||
![]() |
0b83edf439 | ||
![]() |
cf7fcf496e | ||
![]() |
1765271f84 | ||
![]() |
0ec79f8dc3 | ||
![]() |
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
|
44
README.rst
44
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
|
||||
|
||||
@@ -44,6 +45,9 @@ The latest source code for SleekXMPP may be found on `Github
|
||||
``develop`` branch.
|
||||
|
||||
**Stable Releases**
|
||||
- `1.0 RC3 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC3>`_
|
||||
- `1.0 RC2 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC2>`_
|
||||
- `1.0 RC1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-RC1>`_
|
||||
- `1.0 Beta6.1 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta6.1>`_
|
||||
- `1.0 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_
|
||||
- `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_
|
||||
@@ -54,6 +58,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 +81,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 +96,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
|
||||
@@ -101,13 +112,21 @@ SleekXMPP projects::
|
||||
def __init__(self, jid, password):
|
||||
ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.add_event_handler("session_start", self.start)
|
||||
self.add_event_handler("session_start", self.session_start)
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
self.register_plugin('xep_0030') # Service Discovery
|
||||
self.register_plugin('xep_0199') # XMPP Ping
|
||||
|
||||
# If you are working with an OpenFire server, you will
|
||||
# need to use a different SSL version:
|
||||
# import ssl
|
||||
# self.ssl_version = ssl.PROTOCOL_SSLv3
|
||||
|
||||
def session_start(self, event):
|
||||
self.send_presence()
|
||||
|
||||
# Most get_* methods from plugins use Iq stanzas, which
|
||||
# Most get_*/set_* methods from plugins use Iq stanzas, which
|
||||
# can generate IqError and IqTimeout exceptions
|
||||
try:
|
||||
self.get_roster()
|
||||
@@ -132,17 +151,8 @@ SleekXMPP projects::
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||
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
|
||||
|
||||
if xmpp.connect():
|
||||
xmpp.process(block=True)
|
||||
else:
|
||||
print("Unable to connect.")
|
||||
xmpp.connect():
|
||||
xmpp.process(block=True)
|
||||
|
||||
|
||||
Credits
|
||||
|
@@ -4,8 +4,6 @@ clientxmpp
|
||||
|
||||
.. module:: sleekxmpp.clientxmpp
|
||||
|
||||
.. autodata:: SRV_SUPPORT
|
||||
|
||||
.. autoclass:: ClientXMPP
|
||||
|
||||
.. automethod:: connect
|
||||
|
@@ -1,10 +0,0 @@
|
||||
<config xmlns="sleekxmpp:config">
|
||||
<jid>component.localhost</jid>
|
||||
<secret>ssshh</secret>
|
||||
<server>localhost</server>
|
||||
<port>8888</port>
|
||||
|
||||
<query xmlns="jabber:iq:roster">
|
||||
<item jid="user@example.com" subscription="both" />
|
||||
</query>
|
||||
</config>
|
@@ -1,192 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
from optparse import OptionParser
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
||||
from sleekxmpp.stanza.roster import Roster
|
||||
from sleekxmpp.xmlstream import ElementBase
|
||||
from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin
|
||||
|
||||
# Python versions before 3.0 do not use UTF-8 encoding
|
||||
# by default. To ensure that Unicode is handled properly
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
class Config(ElementBase):
|
||||
|
||||
"""
|
||||
In order to make loading and manipulating an XML config
|
||||
file easier, we will create a custom stanza object for
|
||||
our config XML file contents. See the documentation
|
||||
on stanza objects for more information on how to create
|
||||
and use stanza objects and stanza plugins.
|
||||
|
||||
We will reuse the IQ roster query stanza to store roster
|
||||
information since it already exists.
|
||||
|
||||
Example config XML:
|
||||
<config xmlns="sleekxmpp:config">
|
||||
<jid>component.localhost</jid>
|
||||
<secret>ssshh</secret>
|
||||
<server>localhost</server>
|
||||
<port>8888</port>
|
||||
|
||||
<query xmlns="jabber:iq:roster">
|
||||
<item jid="user@example.com" subscription="both" />
|
||||
</query>
|
||||
</config>
|
||||
"""
|
||||
|
||||
name = "config"
|
||||
namespace = "sleekxmpp:config"
|
||||
interfaces = set(('jid', 'secret', 'server', 'port'))
|
||||
sub_interfaces = interfaces
|
||||
|
||||
|
||||
registerStanzaPlugin(Config, Roster)
|
||||
|
||||
|
||||
class ConfigComponent(ComponentXMPP):
|
||||
|
||||
"""
|
||||
A simple SleekXMPP component that uses an external XML
|
||||
file to store its configuration data. To make testing
|
||||
that the component works, it will also echo messages sent
|
||||
to it.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
"""
|
||||
Create a ConfigComponent.
|
||||
|
||||
Arguments:
|
||||
config -- The XML contents of the config file.
|
||||
config_file -- The XML config file object itself.
|
||||
"""
|
||||
ComponentXMPP.__init__(self, config['jid'],
|
||||
config['secret'],
|
||||
config['server'],
|
||||
config['port'])
|
||||
|
||||
# Store the roster information.
|
||||
self.roster = config['roster']['items']
|
||||
|
||||
# The session_start event will be triggered when
|
||||
# the component establishes its connection with the
|
||||
# server and the XML streams are ready for use. We
|
||||
# want to listen for this event so that we we can
|
||||
# broadcast any needed initial presence stanzas.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
# The message event is triggered whenever a message
|
||||
# stanza is received. Be aware that that includes
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
The typical action for the session_start event in a component
|
||||
is to broadcast presence stanzas to all subscribers to the
|
||||
component. Note that the component does not have a roster
|
||||
provided by the XMPP server. In this case, we have possibly
|
||||
saved a roster in the component's configuration file.
|
||||
|
||||
Since the component may use any number of JIDs, you should
|
||||
also include the JID that is sending the presence.
|
||||
|
||||
Arguments:
|
||||
event -- An empty dictionary. The session_start
|
||||
event does not provide any additional
|
||||
data.
|
||||
"""
|
||||
for jid in self.roster:
|
||||
if self.roster[jid]['subscription'] != 'none':
|
||||
self.sendPresence(pfrom=self.jid, pto=jid)
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
Process incoming message stanzas. Be aware that this also
|
||||
includes MUC messages and error messages. It is usually
|
||||
a good idea to check the messages's type before processing
|
||||
or sending replies.
|
||||
|
||||
Since a component may send messages from any number of JIDs,
|
||||
it is best to always include a from JID.
|
||||
|
||||
Arguments:
|
||||
msg -- The received message stanza. See the documentation
|
||||
for stanza objects and the Message stanza to see
|
||||
how it may be used.
|
||||
"""
|
||||
# The reply method will use the messages 'to' JID as the
|
||||
# outgoing reply's 'from' JID.
|
||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
optp = OptionParser()
|
||||
|
||||
# Output verbosity options.
|
||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||
action='store_const', dest='loglevel',
|
||||
const=logging.ERROR, default=logging.INFO)
|
||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||
action='store_const', dest='loglevel',
|
||||
const=logging.DEBUG, default=logging.INFO)
|
||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||
action='store_const', dest='loglevel',
|
||||
const=5, default=logging.INFO)
|
||||
|
||||
# Component name and secret options.
|
||||
optp.add_option("-c", "--config", help="path to config file",
|
||||
dest="config", default="config.xml")
|
||||
|
||||
opts, args = optp.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=opts.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
# Load configuration data.
|
||||
config_file = open(opts.config, 'r+')
|
||||
config_data = "\n".join([line for line in config_file])
|
||||
config = Config(xml=ET.fromstring(config_data))
|
||||
config_file.close()
|
||||
|
||||
# Setup the ConfigComponent and register plugins. Note that while plugins
|
||||
# may have interdependencies, the order in which you register them does
|
||||
# not matter.
|
||||
xmpp = ConfigComponent(config)
|
||||
xmpp.registerPlugin('xep_0030') # Service Discovery
|
||||
xmpp.registerPlugin('xep_0004') # Data Forms
|
||||
xmpp.registerPlugin('xep_0060') # PubSub
|
||||
xmpp.registerPlugin('xep_0199') # XMPP Ping
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
if xmpp.connect():
|
||||
xmpp.process(threaded=False)
|
||||
print("Done")
|
||||
else:
|
||||
print("Unable to connect.")
|
122
examples/echo_component.py
Executable file
122
examples/echo_component.py
Executable file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
from optparse import OptionParser
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
||||
|
||||
# Python versions before 3.0 do not use UTF-8 encoding
|
||||
# by default. To ensure that Unicode is handled properly
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
class EchoComponent(ComponentXMPP):
|
||||
|
||||
"""
|
||||
A simple SleekXMPP component that echoes messages.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, secret, server, port):
|
||||
ComponentXMPP.__init__(self, jid, secret, server, port)
|
||||
|
||||
# You don't need a session_start handler, but that is
|
||||
# where you would broadcast initial presence.
|
||||
|
||||
# The message event is triggered whenever a message
|
||||
# stanza is received. Be aware that that includes
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
Process incoming message stanzas. Be aware that this also
|
||||
includes MUC messages and error messages. It is usually
|
||||
a good idea to check the messages's type before processing
|
||||
or sending replies.
|
||||
|
||||
Since a component may send messages from any number of JIDs,
|
||||
it is best to always include a from JID.
|
||||
|
||||
Arguments:
|
||||
msg -- The received message stanza. See the documentation
|
||||
for stanza objects and the Message stanza to see
|
||||
how it may be used.
|
||||
"""
|
||||
# The reply method will use the messages 'to' JID as the
|
||||
# outgoing reply's 'from' JID.
|
||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
optp = OptionParser()
|
||||
|
||||
# Output verbosity options.
|
||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||
action='store_const', dest='loglevel',
|
||||
const=logging.ERROR, default=logging.INFO)
|
||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||
action='store_const', dest='loglevel',
|
||||
const=logging.DEBUG, default=logging.INFO)
|
||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||
action='store_const', dest='loglevel',
|
||||
const=5, default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
optp.add_option("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
optp.add_option("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
optp.add_option("-s", "--server", dest="server",
|
||||
help="server to connect to")
|
||||
optp.add_option("-P", "--port", dest="port",
|
||||
help="port to connect to")
|
||||
|
||||
opts, args = optp.parse_args()
|
||||
|
||||
if opts.jid is None:
|
||||
opts.jid = raw_input("Component JID: ")
|
||||
if opts.password is None:
|
||||
opts.password = getpass.getpass("Password: ")
|
||||
if opts.server is None:
|
||||
opts.server = raw_input("Server: ")
|
||||
if opts.port is None:
|
||||
opts.port = int(raw_input("Port: "))
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=opts.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
# Setup the EchoComponent and register plugins. Note that while plugins
|
||||
# may have interdependencies, the order in which you register them does
|
||||
# not matter.
|
||||
xmpp = EchoComponent(opts.jid, opts.password, opts.server, opts.port)
|
||||
xmpp.registerPlugin('xep_0030') # Service Discovery
|
||||
xmpp.registerPlugin('xep_0004') # Data Forms
|
||||
xmpp.registerPlugin('xep_0060') # PubSub
|
||||
xmpp.registerPlugin('xep_0199') # XMPP Ping
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
if xmpp.connect():
|
||||
xmpp.process(threaded=False)
|
||||
print("Done")
|
||||
else:
|
||||
print("Unable to connect.")
|
32
setup.py
Normal file → Executable file
32
setup.py
Normal file → Executable file
@@ -4,16 +4,18 @@
|
||||
# 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
|
||||
try:
|
||||
from setuptools import setup, Command
|
||||
except ImportError:
|
||||
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,18 +29,18 @@ 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',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python 2.6',
|
||||
'Programming Language :: Python 2.7',
|
||||
'Programming Language :: Python 3.1',
|
||||
'Programming Language :: Python 3.2',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.1',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
]
|
||||
|
||||
@@ -93,5 +95,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__
|
||||
|
@@ -106,9 +106,6 @@ class BaseXMPP(XMLStream):
|
||||
self.client_roster = self.roster[self.boundjid.bare]
|
||||
|
||||
self.is_component = False
|
||||
self.auto_authorize = True
|
||||
self.auto_subscribe = True
|
||||
|
||||
self.sentpresence = False
|
||||
|
||||
self.stanza = sleekxmpp.stanza
|
||||
@@ -640,6 +637,46 @@ class BaseXMPP(XMLStream):
|
||||
log.warning("server property deprecated. Use boundjid.host")
|
||||
self.boundjid.server = value
|
||||
|
||||
@property
|
||||
def auto_authorize(self):
|
||||
"""
|
||||
Auto accept or deny subscription requests.
|
||||
|
||||
If True, auto accept subscription requests.
|
||||
If False, auto deny subscription requests.
|
||||
If None, don't automatically respond.
|
||||
"""
|
||||
return self.roster.auto_authorize
|
||||
|
||||
@auto_authorize.setter
|
||||
def auto_authorize(self, value):
|
||||
"""
|
||||
Auto accept or deny subscription requests.
|
||||
|
||||
If True, auto accept subscription requests.
|
||||
If False, auto deny subscription requests.
|
||||
If None, don't automatically respond.
|
||||
"""
|
||||
self.roster.auto_authorize = value
|
||||
|
||||
@property
|
||||
def auto_subscribe(self):
|
||||
"""
|
||||
Auto send requests for mutual subscriptions.
|
||||
|
||||
If True, auto send mutual subscription requests.
|
||||
"""
|
||||
return self.roster.auto_subscribe
|
||||
|
||||
@auto_subscribe.setter
|
||||
def auto_subscribe(self, value):
|
||||
"""
|
||||
Auto send requests for mutual subscriptions.
|
||||
|
||||
If True, auto send mutual subscription requests.
|
||||
"""
|
||||
self.roster.auto_subscribe = value
|
||||
|
||||
def set_jid(self, jid):
|
||||
"""Rip a JID apart and claim it as our own."""
|
||||
log.debug("setting jid to %s" % jid)
|
||||
@@ -741,8 +778,6 @@ class BaseXMPP(XMLStream):
|
||||
not presence['type'] in presence.showtypes:
|
||||
return
|
||||
|
||||
self.event("changed_status", presence)
|
||||
|
||||
def exception(self, exception):
|
||||
"""
|
||||
Process any uncaught exceptions, notably IqError and
|
||||
|
@@ -59,7 +59,7 @@ class ClientXMPP(BaseXMPP):
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, ssl=False, plugin_config={},
|
||||
plugin_whitelist=[], escape_quotes=True):
|
||||
plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
|
||||
"""
|
||||
Create a new SleekXMPP client.
|
||||
|
||||
@@ -114,11 +114,13 @@ class ClientXMPP(BaseXMPP):
|
||||
|
||||
# Setup default stream features
|
||||
self.register_plugin('feature_starttls')
|
||||
self.register_plugin('feature_mechanisms')
|
||||
self.register_plugin('feature_bind')
|
||||
self.register_plugin('feature_session')
|
||||
self.register_plugin('feature_mechanisms',
|
||||
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
|
||||
|
||||
def connect(self, address=tuple(), reattempt=True, use_tls=True):
|
||||
def connect(self, address=tuple(), reattempt=True,
|
||||
use_tls=True, use_ssl=False):
|
||||
"""
|
||||
Connect to the XMPP server.
|
||||
|
||||
@@ -132,13 +134,16 @@ class ClientXMPP(BaseXMPP):
|
||||
error occurs. Defaults to True.
|
||||
use_tls -- Indicates if TLS should be used for the
|
||||
connection. Defaults to True.
|
||||
use_ssl -- Indicates if the older SSL connection method
|
||||
should be used. Defaults to False.
|
||||
"""
|
||||
self.session_started_event.clear()
|
||||
if not address:
|
||||
address = (self.boundjid.host, 5222)
|
||||
|
||||
return XMLStream.connect(self, address[0], address[1],
|
||||
use_tls=use_tls, reattempt=reattempt)
|
||||
use_tls=use_tls, use_ssl=use_ssl,
|
||||
reattempt=reattempt)
|
||||
|
||||
def get_dns_records(self, domain, port=None):
|
||||
"""
|
||||
@@ -210,7 +215,7 @@ class ClientXMPP(BaseXMPP):
|
||||
Will be executed when the roster is received.
|
||||
Implies block=False.
|
||||
"""
|
||||
return self.client_roster.updtae(jid, name, subscription, groups,
|
||||
return self.client_roster.update(jid, name, subscription, groups,
|
||||
block, timeout, callback)
|
||||
|
||||
def del_roster_item(self, jid):
|
||||
@@ -254,13 +259,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.
|
||||
|
@@ -63,7 +63,7 @@ def _py2xml(*args):
|
||||
double.text = str(x)
|
||||
val.append(double)
|
||||
elif type(x) is rpcbase64:
|
||||
b64 = ET.Element("Base64")
|
||||
b64 = ET.Element("base64")
|
||||
b64.text = x.encoded()
|
||||
val.append(b64)
|
||||
elif type(x) is rpctime:
|
||||
@@ -110,7 +110,10 @@ def _xml2py(value):
|
||||
return value.find('{%s}string' % namespace).text
|
||||
if value.find('{%s}double' % namespace) is not None:
|
||||
return float(value.find('{%s}double' % namespace).text)
|
||||
if value.find('{%s}base64') is not None:
|
||||
return rpcbase64(value.find('base64' % namespace).text)
|
||||
if value.find('{%s}Base64') is not None:
|
||||
# Older versions of XEP-0009 used Base64
|
||||
return rpcbase64(value.find('Base64' % namespace).text)
|
||||
if value.find('{%s}dateTime.iso8601') is not None:
|
||||
return rpctime(value.find('{%s}dateTime.iso8601'))
|
||||
|
@@ -699,10 +699,10 @@ class Remote(object):
|
||||
with Remote._lock:
|
||||
del cls._sessions[client.boundjid.bare]
|
||||
result = RemoteSession(client, _session_close_callback)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call, threaded=True)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response, threaded=True)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault, threaded=True)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error, threaded=True)
|
||||
if callback is None:
|
||||
start_event_handler = result._notify
|
||||
else:
|
||||
|
@@ -14,6 +14,7 @@ from .. stanza.presence import Presence
|
||||
from .. xmlstream.handler.callback import Callback
|
||||
from .. xmlstream.matcher.xpath import MatchXPath
|
||||
from .. xmlstream.matcher.xmlmask import MatchXMLMask
|
||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -222,10 +223,10 @@ class xep_0045(base.base_plugin):
|
||||
return False
|
||||
return True
|
||||
|
||||
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None):
|
||||
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
|
||||
""" Join the specified room, requesting 'maxhistory' lines of history.
|
||||
"""
|
||||
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow)
|
||||
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom)
|
||||
x = ET.Element('{http://jabber.org/protocol/muc}x')
|
||||
if password:
|
||||
passelement = ET.Element('password')
|
||||
@@ -271,7 +272,7 @@ class xep_0045(base.base_plugin):
|
||||
return False
|
||||
return True
|
||||
|
||||
def setAffiliation(self, room, jid=None, nick=None, affiliation='member'):
|
||||
def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
|
||||
""" Change room affiliation."""
|
||||
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
||||
raise TypeError
|
||||
@@ -283,6 +284,7 @@ class xep_0045(base.base_plugin):
|
||||
query.append(item)
|
||||
iq = self.xmpp.makeIqSet(query)
|
||||
iq['to'] = room
|
||||
iq['from'] = ifrom
|
||||
# For now, swallow errors to preserve existing API
|
||||
try:
|
||||
result = iq.send()
|
||||
@@ -306,13 +308,13 @@ class xep_0045(base.base_plugin):
|
||||
msg.append(x)
|
||||
self.xmpp.send(msg)
|
||||
|
||||
def leaveMUC(self, room, nick, msg=''):
|
||||
def leaveMUC(self, room, nick, msg='', pfrom=None):
|
||||
""" Leave the specified room.
|
||||
"""
|
||||
if msg:
|
||||
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg)
|
||||
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg, pfrom=pfrom)
|
||||
else:
|
||||
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick))
|
||||
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom)
|
||||
del self.rooms[room]
|
||||
|
||||
def getRoomConfig(self, room, ifrom=''):
|
||||
@@ -331,12 +333,13 @@ class xep_0045(base.base_plugin):
|
||||
raise ValueError
|
||||
return self.xmpp.plugin['xep_0004'].buildForm(form)
|
||||
|
||||
def cancelConfig(self, room):
|
||||
def cancelConfig(self, room, ifrom=None):
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
||||
x = ET.Element('{jabber:x:data}x', type='cancel')
|
||||
query.append(x)
|
||||
iq = self.xmpp.makeIqSet(query)
|
||||
iq['to'] = room
|
||||
iq['from'] = ifrom
|
||||
iq.send()
|
||||
|
||||
def setRoomConfig(self, room, config, ifrom=''):
|
||||
|
@@ -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,426 @@ 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=True,
|
||||
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 a new item to a node, or edit an existing item.
|
||||
|
||||
For services that support it, you can use the publish command
|
||||
as an event signal by not including an ID or payload.
|
||||
|
||||
When including a payload and you do not provide an ID then
|
||||
the service will generally create an ID for you.
|
||||
|
||||
Publish options may be specified, and how those options
|
||||
are processed is left to the service, such as treating
|
||||
the options as preconditions that the node's settings
|
||||
must match.
|
||||
|
||||
Arguments:
|
||||
jid -- The JID of the pubsub service.
|
||||
node -- The node to publish the item to.
|
||||
id -- Optionally specify the ID of the item.
|
||||
payload -- The item content to publish.
|
||||
options -- A form of publish 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']['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)
|
||||
|
@@ -224,7 +224,7 @@ class RosterItem(object):
|
||||
if self['to']:
|
||||
p = self.xmpp.Presence()
|
||||
p['to'] = self.jid
|
||||
p['type'] = ['unsubscribe']
|
||||
p['type'] = 'unsubscribe'
|
||||
if self.xmpp.is_component:
|
||||
p['from'] = self.owner
|
||||
p.send()
|
||||
@@ -345,7 +345,11 @@ class RosterItem(object):
|
||||
self.xmpp.event('got_online', presence)
|
||||
if resource not in self.resources:
|
||||
self.resources[resource] = {}
|
||||
old_status = self.resources[resource].get('status', '')
|
||||
old_show = self.resources[resource].get('show', None)
|
||||
self.resources[resource].update(data)
|
||||
if old_show != presence['show'] or old_status != presence['status']:
|
||||
self.xmpp.event('changed_status', presence)
|
||||
|
||||
def handle_unavailable(self, presence):
|
||||
resource = presence['from'].resource
|
||||
@@ -353,6 +357,7 @@ class RosterItem(object):
|
||||
return
|
||||
if resource in self.resources:
|
||||
del self.resources[resource]
|
||||
self.xmpp.event('changed_status', presence)
|
||||
if not self.resources:
|
||||
self.xmpp.event('got_offline', presence)
|
||||
|
||||
|
@@ -48,8 +48,8 @@ class Roster(object):
|
||||
"""
|
||||
self.xmpp = xmpp
|
||||
self.db = db
|
||||
self.auto_authorize = True
|
||||
self.auto_subscribe = True
|
||||
self._auto_authorize = True
|
||||
self._auto_subscribe = True
|
||||
self._rosters = {}
|
||||
|
||||
if self.db:
|
||||
@@ -138,3 +138,47 @@ class Roster(object):
|
||||
ppriority=ppriority,
|
||||
pnick=pnick,
|
||||
pto=pto)
|
||||
|
||||
@property
|
||||
def auto_authorize(self):
|
||||
"""
|
||||
Auto accept or deny subscription requests.
|
||||
|
||||
If True, auto accept subscription requests.
|
||||
If False, auto deny subscription requests.
|
||||
If None, don't automatically respond.
|
||||
"""
|
||||
return self._auto_authorize
|
||||
|
||||
@auto_authorize.setter
|
||||
def auto_authorize(self, value):
|
||||
"""
|
||||
Auto accept or deny subscription requests.
|
||||
|
||||
If True, auto accept subscription requests.
|
||||
If False, auto deny subscription requests.
|
||||
If None, don't automatically respond.
|
||||
"""
|
||||
self._auto_authorize = value
|
||||
for node in self._rosters:
|
||||
self._rosters[node].auto_authorize = value
|
||||
|
||||
@property
|
||||
def auto_subscribe(self):
|
||||
"""
|
||||
Auto send requests for mutual subscriptions.
|
||||
|
||||
If True, auto send mutual subscription requests.
|
||||
"""
|
||||
return self._auto_subscribe
|
||||
|
||||
@auto_subscribe.setter
|
||||
def auto_subscribe(self, value):
|
||||
"""
|
||||
Auto send requests for mutual subscriptions.
|
||||
|
||||
If True, auto send mutual subscription requests.
|
||||
"""
|
||||
self._auto_subscribe = value
|
||||
for node in self._rosters:
|
||||
self._rosters[node].auto_subscribe = value
|
||||
|
@@ -209,11 +209,11 @@ class RosterNode(object):
|
||||
Implies block=False.
|
||||
"""
|
||||
self[jid]['name'] = name
|
||||
self[jid]['groups'] = group
|
||||
self[jid]['groups'] = groups
|
||||
self[jid].save()
|
||||
|
||||
if not self.xmpp.is_component:
|
||||
iq = self.Iq()
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['roster']['items'] = {jid: {'name': name,
|
||||
'subscription': subscription,
|
||||
|
@@ -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)
|
||||
@@ -296,7 +293,8 @@ class SleekTest(unittest.TestCase):
|
||||
def stream_start(self, mode='client', skip=True, header=None,
|
||||
socket='mock', jid='tester@localhost',
|
||||
password='test', server='localhost',
|
||||
port=5222, plugins=None):
|
||||
port=5222, sasl_mech=None,
|
||||
plugins=None, plugin_config={}):
|
||||
"""
|
||||
Initialize an XMPP client or component using a dummy XML stream.
|
||||
|
||||
@@ -320,10 +318,13 @@ class SleekTest(unittest.TestCase):
|
||||
are loaded.
|
||||
"""
|
||||
if mode == 'client':
|
||||
self.xmpp = ClientXMPP(jid, password)
|
||||
self.xmpp = ClientXMPP(jid, password,
|
||||
sasl_mech=sasl_mech,
|
||||
plugin_config=plugin_config)
|
||||
elif mode == 'component':
|
||||
self.xmpp = ComponentXMPP(jid, password,
|
||||
server, port)
|
||||
server, port,
|
||||
plugin_config=plugin_config)
|
||||
else:
|
||||
raise ValueError("Unknown XMPP connection mode.")
|
||||
|
||||
@@ -336,7 +337,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.
|
||||
@@ -351,7 +351,10 @@ class SleekTest(unittest.TestCase):
|
||||
skip_queue.put('started')
|
||||
|
||||
self.xmpp.add_event_handler('session_start', wait_for_session)
|
||||
self.xmpp.connect()
|
||||
if server is not None:
|
||||
self.xmpp.connect((server, port))
|
||||
else:
|
||||
self.xmpp.connect()
|
||||
else:
|
||||
raise ValueError("Unknown socket type.")
|
||||
|
||||
|
@@ -32,7 +32,7 @@ class SCRAM_HMAC(Mechanism):
|
||||
name = name[:-5]
|
||||
self._cb = True
|
||||
|
||||
self.hash = hash(self.name[6:])
|
||||
self.hash = hash(name[6:])
|
||||
if self.hash is None:
|
||||
raise SASLCancelled(self.sasl, self)
|
||||
if not self.sasl.tls_active():
|
||||
|
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.0rc3'
|
||||
__version_info__ = (1, 0, 0, 'rc3', 0)
|
@@ -6,6 +6,8 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import weakref
|
||||
|
||||
|
||||
class BaseHandler(object):
|
||||
|
||||
@@ -43,7 +45,10 @@ class BaseHandler(object):
|
||||
stream -- The XMLStream instance the handler should monitor.
|
||||
"""
|
||||
self.name = name
|
||||
self.stream = stream
|
||||
if stream is not None:
|
||||
self.stream = weakref.ref(stream)
|
||||
else:
|
||||
self.stream = None
|
||||
self._destroy = False
|
||||
self._payload = None
|
||||
self._matcher = matcher
|
||||
|
@@ -85,14 +85,14 @@ class Waiter(BaseHandler):
|
||||
value sleekxmpp.xmlstream.RESPONSE_TIMEOUT.
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self.stream.response_timeout
|
||||
timeout = self.stream().response_timeout
|
||||
|
||||
try:
|
||||
stanza = self._payload.get(True, timeout)
|
||||
except queue.Empty:
|
||||
stanza = False
|
||||
log.warning("Timed out waiting for %s" % self.name)
|
||||
self.stream.removeHandler(self.name)
|
||||
self.stream().remove_handler(self.name)
|
||||
return stanza
|
||||
|
||||
def check_delete(self):
|
||||
|
@@ -135,3 +135,9 @@ class JID(object):
|
||||
"""
|
||||
other = JID(other)
|
||||
return self.full == other.full
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
Two JIDs are considered unequal if they are not equal.
|
||||
"""
|
||||
return not self == other
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -19,6 +19,7 @@ import threading
|
||||
import time
|
||||
import types
|
||||
import random
|
||||
import weakref
|
||||
try:
|
||||
import queue
|
||||
except ImportError:
|
||||
@@ -47,6 +48,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 +183,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 +213,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 +239,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 +333,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 +349,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 +459,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 +515,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:
|
||||
@@ -503,9 +533,14 @@ class XMLStream(object):
|
||||
log.debug("reconnecting...")
|
||||
self.state.transition('connected', 'disconnected', wait=2.0,
|
||||
func=self._disconnect, args=(True,))
|
||||
|
||||
log.debug("connecting...")
|
||||
return self.state.transition('disconnected', 'connected',
|
||||
wait=2.0, func=self._connect)
|
||||
connected = self.state.transition('disconnected', 'connected',
|
||||
wait=2.0, func=self._connect)
|
||||
while not connected:
|
||||
connected = self.state.transition('disconnected', 'connected',
|
||||
wait=2.0, func=self._connect)
|
||||
return connected
|
||||
|
||||
def set_socket(self, socket, ignore=False):
|
||||
"""
|
||||
@@ -532,6 +567,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 +626,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
|
||||
@@ -641,7 +725,7 @@ class XMLStream(object):
|
||||
"""
|
||||
if handler.stream is None:
|
||||
self.__handlers.append(handler)
|
||||
handler.stream = self
|
||||
handler.stream = weakref.ref(self)
|
||||
|
||||
def remove_handler(self, name):
|
||||
"""
|
||||
@@ -669,18 +753,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):
|
||||
@@ -756,8 +846,9 @@ class XMLStream(object):
|
||||
def filter_pointers(handler):
|
||||
return handler[0] != pointer
|
||||
|
||||
self.__event_handlers[name] = filter(filter_pointers,
|
||||
self.__event_handlers[name])
|
||||
self.__event_handlers[name] = list(filter(
|
||||
filter_pointers,
|
||||
self.__event_handlers[name]))
|
||||
|
||||
def event_handled(self, name):
|
||||
"""
|
||||
@@ -905,7 +996,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 +1072,50 @@ 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)
|
||||
if not self.__read_xml():
|
||||
# If the server terminated the stream, end processing
|
||||
break
|
||||
except SyntaxError as e:
|
||||
log.error("Error reading from XML stream.")
|
||||
self.exception(e)
|
||||
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:
|
||||
|
||||
if not self.stop.is_set() and self.auto_reconnect:
|
||||
self.reconnect()
|
||||
else:
|
||||
self.event('killed', direct=True)
|
||||
self.disconnect()
|
||||
self.event_queue.put(('quit', None, None))
|
||||
self.scheduler.run = False
|
||||
break
|
||||
|
||||
def __read_xml(self):
|
||||
"""
|
||||
@@ -1019,39 +1124,35 @@ class XMLStream(object):
|
||||
"""
|
||||
depth = 0
|
||||
root = None
|
||||
try:
|
||||
for (event, xml) in ET.iterparse(self.filesocket,
|
||||
(b'end', b'start')):
|
||||
if event == b'start':
|
||||
if depth == 0:
|
||||
# We have received the start of the root element.
|
||||
root = xml
|
||||
# Perform any stream initialization actions, such
|
||||
# as handshakes.
|
||||
self.stream_end_event.clear()
|
||||
self.start_stream_handler(root)
|
||||
depth += 1
|
||||
if event == b'end':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
# The stream's root element has closed,
|
||||
# terminating the stream.
|
||||
log.debug("End of stream recieved")
|
||||
self.stream_end_event.set()
|
||||
return False
|
||||
elif depth == 1:
|
||||
# We only raise events for stanzas that are direct
|
||||
# children of the root element.
|
||||
try:
|
||||
self.__spawn_event(xml)
|
||||
except RestartStream:
|
||||
return True
|
||||
if root:
|
||||
# Keep the root element empty of children to
|
||||
# save on memory use.
|
||||
root.clear()
|
||||
except SyntaxError:
|
||||
log.error("Error reading from XML stream.")
|
||||
for event, xml in ET.iterparse(self.filesocket, (b'end', b'start')):
|
||||
if event == b'start':
|
||||
if depth == 0:
|
||||
# We have received the start of the root element.
|
||||
root = xml
|
||||
# Perform any stream initialization actions, such
|
||||
# as handshakes.
|
||||
self.stream_end_event.clear()
|
||||
self.start_stream_handler(root)
|
||||
depth += 1
|
||||
if event == b'end':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
# The stream's root element has closed,
|
||||
# terminating the stream.
|
||||
log.debug("End of stream recieved")
|
||||
self.stream_end_event.set()
|
||||
return False
|
||||
elif depth == 1:
|
||||
# We only raise events for stanzas that are direct
|
||||
# children of the root element.
|
||||
try:
|
||||
self.__spawn_event(xml)
|
||||
except RestartStream:
|
||||
return True
|
||||
if root is not None:
|
||||
# Keep the root element empty of children to
|
||||
# save on memory use.
|
||||
root.clear()
|
||||
log.debug("Ending read XML loop")
|
||||
|
||||
def _build_stanza(self, xml, default_ns=None):
|
||||
@@ -1150,7 +1251,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 +1270,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 +1314,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 +1326,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()))
|
||||
|
@@ -48,6 +48,29 @@ class TestEvents(SleekTest):
|
||||
msg = "Event was not triggered the correct number of times: %s"
|
||||
self.failUnless(happened == [True], msg % happened)
|
||||
|
||||
def testAddDelAddEvent(self):
|
||||
"""Test adding, then removing, then adding an event handler."""
|
||||
happened = []
|
||||
|
||||
def handletestevent(event):
|
||||
happened.append(True)
|
||||
|
||||
self.xmpp.add_event_handler("test_event", handletestevent)
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
self.xmpp.del_event_handler("test_event", handletestevent)
|
||||
# Should not trigger because it was deleted
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
self.xmpp.add_event_handler("test_event", handletestevent)
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
# Give the event queue time to process.
|
||||
time.sleep(0.1)
|
||||
|
||||
msg = "Event was not triggered the correct number of times: %s"
|
||||
self.failUnless(happened == [True, True], msg % happened)
|
||||
|
||||
def testDisposableEvent(self):
|
||||
"""Test disposable handler working, then not being triggered again."""
|
||||
happened = []
|
||||
|
@@ -124,5 +124,18 @@ class TestJIDClass(SleekTest):
|
||||
'component.someserver',
|
||||
'component.someserver')
|
||||
|
||||
def testJIDEquality(self):
|
||||
"""Test that JIDs with the same content are equal."""
|
||||
jid1 = JID('user@domain/resource')
|
||||
jid2 = JID('user@domain/resource')
|
||||
self.assertTrue(jid1 == jid2, "Same JIDs are not considered equal")
|
||||
self.assertFalse(jid1 != jid2, "Same JIDs are considered not equal")
|
||||
|
||||
def testJIDInequality(self):
|
||||
jid1 = JID('user@domain/resource')
|
||||
jid2 = JID('otheruser@domain/resource')
|
||||
self.assertFalse(jid1 == jid2, "Same JIDs are not considered equal")
|
||||
self.assertTrue(jid1 != jid2, "Same JIDs are considered not equal")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)
|
||||
|
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)
|
||||
|
@@ -251,5 +251,130 @@ class TestStreamPresence(SleekTest):
|
||||
self.assertEqual(events, ptypes,
|
||||
"Not all events raised: %s" % events)
|
||||
|
||||
def test_changed_status(self):
|
||||
"""Test that the changed_status event is handled properly."""
|
||||
events = []
|
||||
self.stream_start()
|
||||
|
||||
def changed_status(presence):
|
||||
events.append(presence['type'])
|
||||
|
||||
self.xmpp.add_event_handler('changed_status', changed_status)
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost" />
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost" />
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>away</show>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>away</show>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>dnd</show>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>dnd</show>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>chat</show>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>chat</show>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>xa</show>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>xa</show>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com"
|
||||
to="tester@localhost"
|
||||
type="unavailable" />
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com"
|
||||
to="tester@localhost"
|
||||
type="unavailable" />
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost" />
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost" />
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost" />
|
||||
""")
|
||||
|
||||
# Changed status text, so fire new event
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<status>Testing!</status>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
# No change in show/status values, no event
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<status>Testing!</status>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>dnd</show>
|
||||
<status>Testing!</status>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
self.recv("""
|
||||
<presence from="user@example.com" to="tester@localhost">
|
||||
<show>dnd</show>
|
||||
<status>Testing!</status>
|
||||
</presence>
|
||||
""")
|
||||
|
||||
time.sleep(0.3)
|
||||
|
||||
self.assertEqual(events, ['available', 'away', 'dnd', 'chat',
|
||||
'xa', 'unavailable', 'available',
|
||||
'available', 'dnd'],
|
||||
"Changed status events incorrect: %s" % events)
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamPresence)
|
||||
|
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