Compare commits

...

89 Commits

Author SHA1 Message Date
Lance Stout
7945b3e738 Remove the config_component example in favor of echo_component.
The roster portion of the example is too outdated.
2011-11-18 16:26:02 -08:00
Lance Stout
d4c1ff5309 Also fire changed_status when the status text changes for a resource. 2011-11-18 13:57:41 -08:00
Lance Stout
22868c3924 Fix changed_status event
Once again only fires when a resource's presence show value changes.
2011-11-18 13:39:02 -08:00
Lance Stout
2de1be188c Add echo component example. 2011-11-17 12:25:56 -08:00
Lance Stout
9faecec2db Simplify boilerplate example. 2011-11-14 12:00:21 -08:00
Lance Stout
5d7111fe3b Update list of stable releases. 2011-11-14 11:46:07 -08:00
Lance Stout
0c86f8288d No need to continue processing loop if an error ocurred and auto_reconnect=False. 2011-11-14 11:21:05 -08:00
Lance Stout
5a6a65fd9f Fix typo 2011-11-14 11:20:53 -08:00
Lance Stout
43c4d23896 Explicitly test for inequality in JIDs.
Fixes issue #113
2011-11-14 09:15:43 -08:00
Lance Stout
9f9e8db814 Add use_ssl parameter to ClientXMPP.connect 2011-11-11 01:52:18 -08:00
Lance Stout
b8efcc7cf0 Don't just call self.disconnect in self.reconnect.
It messes up the auto_reconnect value and causes the XML processing
loop to spin wildly with errors on a stream disconnect.
2011-11-08 19:23:53 -08:00
Lance Stout
2f29d18e53 Use setuptools if available. 2011-11-08 07:01:16 -08:00
Lance Stout
888e286a09 Continue trying to reconnect, even if the attempt fails.
The transition from disconnected to connected states must be done in a
loop in case the transition fails, not just once and hope it worked.
2011-11-07 01:13:34 -08:00
Lance Stout
1a93a187f0 Fix a crash when removing a contact.
Original author: louiz
2011-11-06 08:33:03 -08:00
Lance Stout
a8d5da5091 Restore original behaviour for auto_authorize and auto_subscribe.
The change to using the new roster broke the original auto_* values
and used per-roster versions. The original auto_* values will now set
the behaviour globally. Use the per-roster values to override for a
specific JID.
2011-11-06 08:25:29 -08:00
Lance Stout
e2720fac9e FIX SCRAM-SHA-1-PLUS
The mechanism name was being correctly de-plussed, but then we used the
original, -PLUS, name to extract the hash, finding SHA-1-PLUS and therefore
finding no match.

Test-Information:

Tested with Sleek against an Isode M-Link with SCRAM-SHA-1-PLUS available.

Author: dwd
2011-10-27 15:16:54 -04:00
Lance Stout
4374729f20 Update the docs for XEP-0060 publish method. 2011-10-11 20:37:50 -04:00
Lance Stout
87999333cb Fix MUC methods to optionally specify the sending JID.
Should fix issue #107
2011-10-10 11:31:03 -04:00
Lance Stout
335dc2927b Break reference cycle to fix potential memory leaks for callback handlers. 2011-10-08 17:31:30 -04:00
Lance Stout
ccbef6b696 Fix typos in the roster update method. 2011-10-07 18:13:50 -04:00
Lance Stout
3e384d3cfe XEP-0009 will likely be updated to use <base64 /> instead of <Base64 />
Both are supported when reading, but <base64 /> will be used for output.
2011-10-05 12:09:50 -04:00
Lance Stout
e33949c397 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2011-10-04 10:37:42 -04:00
Lance Stout
eccac859ad Fix missing import statement.
Fixes issue #105
2011-10-04 10:36:52 -04:00
Lance Stout
7dd586f2fd Merge pull request #104 from correl/develop
Make RPC events threaded
2011-10-03 13:19:16 -07:00
Correl Roush
3607c5b792 Make RPC events threaded
Allows, for example, an RPC service to make remote RPC calls with its
own connection without blocking its own thread waiting for the result.
2011-10-03 14:32:48 -04:00
Lance Stout
e37adace62 Allow SASL mechanism to be set when creating a ClientXMPP instance.
Instead of using:

    ClientXMPP(jid, password, plugin_config={
        'feature_mechanisms': {'use_mech': 'SOME-MECH'}})

You can use:

    ClientXMPP(jid, password, sasl_mech='SOME-MECH')

If you need to change the mechanism after instantiation, use:

    xmpp['feature_mechanisms'].sasl.mech = 'SCRAM-MD5'
2011-09-28 22:48:30 -04:00
Lance Stout
d10f591bf4 Expand live stream testing capabilities. 2011-09-28 17:26:29 -04:00
Lance Stout
262da78ca7 Fix del_event_handler for Python3 (different semantics for filter()).
Fixes issue #103
2011-09-23 12:03:49 -04:00
Lance Stout
0b83edf439 Fix regression for handling the case where the server terminates the stream.
The processing loop was continuing to call __read_xml after </stream>
was received, which caused SyntaxErrors (can't find starting element).

This should fix issue #102
2011-09-22 01:32:44 -04:00
Nathan Fritz
cf7fcf496e SyntaxError requires a restart 2011-09-19 11:53:09 -07:00
Lance Stout
1765271f84 Make get_node_config block by default. 2011-09-02 11:52:56 -07:00
Lance Stout
0ec79f8dc3 Tweak setup.py, and bump dev version to RC3. 2011-09-01 16:47:30 -07:00
Lance Stout
6f72c05ebf Add whitespace keepalive option.
May be disabled by setting:
    self.whitespace_keepalive = False

The keepalive interval can be adjusted using:
    self.whitespace_keepalive_interval = 300

The default interval is 5min.
2011-09-01 16:24:09 -07:00
Nathan Fritz
20cacc84ba remove ping schedule on disconnect 2011-09-01 15:51:43 -07:00
Lance Stout
24a14a0284 Mark pubsub state stanzas as non-standard. 2011-09-01 15:29:05 -07:00
Lance Stout
982c2d9b83 Add tests for pubsub error stanzas 2011-09-01 15:26:54 -07:00
Lance Stout
efa4a9b330 More stanza cleanup for pubsub. 2011-09-01 14:20:58 -07:00
Lance Stout
39ec1cff19 Some more minor cleanup. 2011-09-01 14:03:11 -07:00
Lance Stout
24c5f8d374 Clean up pubsub#event stanzas. 2011-09-01 14:01:58 -07:00
Lance Stout
d6b0158ddb Clean up pubsub#owner stanzas. 2011-09-01 13:47:55 -07:00
Lance Stout
7e5e9542e9 Add support for notify attribute when retracting an item. 2011-09-01 13:36:11 -07:00
Lance Stout
d7fc2aaa9c Add ability to get global/node default subscription options. 2011-09-01 13:25:35 -07:00
Lance Stout
8471a485d1 Clean up pubsub stanzas. 2011-09-01 13:12:26 -07:00
Lance Stout
462b375c8f Owners can modify subscriptions/affiliations. With tests.
94% coverage for the main pubsub plugin! (91% including stanzas)
2011-09-01 12:09:24 -07:00
Lance Stout
afbd506cfc Users can retrieve their affiliations now, with tests. 2011-09-01 11:30:55 -07:00
Lance Stout
ec01e45ed1 Add ability for a user to get retrieve subscriptions, with tests. 2011-09-01 11:19:25 -07:00
Lance Stout
993829b23f Add tests for pubsub subscription options. 2011-09-01 10:44:14 -07:00
Lance Stout
002257b820 Add tests for retrieving pubsub items. 2011-09-01 09:27:10 -07:00
Lance Stout
0af35c2224 Fix memory reference bugs. 2011-09-01 00:50:45 -07:00
Lance Stout
76bc0a2ba6 XEP-0060 v1.13 dictates publishing/retracting one item at a time. 2011-08-31 23:48:22 -07:00
Lance Stout
d2dc4824ee Simplify pubsub tests.
We don't really care about empty responses, so let's use block=False.
2011-08-31 21:52:17 -07:00
Lance Stout
3f9ca0366b Add test for purging a pubsub node. 2011-08-31 21:09:25 -07:00
Lance Stout
b68785e19e Retract stanzas are behaving oddly when using stanza values. 2011-08-31 16:03:32 -07:00
Lance Stout
a1bbb719e1 Test publishing multiple items, and with options. 2011-08-31 15:04:46 -07:00
Lance Stout
46f23f7348 Test publishng an item with options. 2011-08-31 14:55:37 -07:00
Lance Stout
09252baa71 Test publishing a single item. 2011-08-31 14:31:20 -07:00
Lance Stout
3623a7a16a More pubsub unit tests! 2011-08-31 14:05:29 -07:00
Lance Stout
cc504ab07c Fix pubsub get_items.
- item_ids checked for None
- pubsub node is set
2011-08-31 10:56:43 -07:00
Lance Stout
2500a0649b Fix requesting pubsub node configuration, and add tests.
- <default /> doesn't have a type attribute in the XEP
- <configure /> isn't used anymore for requesting default configuration
2011-08-31 10:43:33 -07:00
Lance Stout
5ec4e4a026 Added pubsub error stanza.
iq['error']['pubsub']['condition']
iq['error']['pubsub']['unsupported']
2011-08-31 00:42:37 -07:00
Lance Stout
c3df4dd052 Create a tox config for automating tests for different Python versions.
To use:
    sudo pip install tox
    tox
2011-08-31 00:00:12 -07:00
Lance Stout
730c3fada0 Add tests for pubsub unsubscribe. 2011-08-30 23:18:13 -07:00
Lance Stout
628978fc8c Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2011-08-30 23:11:11 -07:00
Nathan Fritz
7fb9d68714 fixed form accessors in pubsub stanzas 2011-08-30 23:10:13 -07:00
Lance Stout
e0a1c477d0 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2011-08-30 23:03:51 -07:00
Nathan Fritz
b70565720f fixed test further... but now I have an out of order problem 2011-08-30 23:03:04 -07:00
Lance Stout
33ac0c9dd6 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2011-08-30 22:45:08 -07:00
Nathan Fritz
4699bdff60 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2011-08-30 22:44:34 -07:00
Nathan Fritz
354641a3ce added publish-options element 2011-08-30 22:44:19 -07:00
Lance Stout
58a43e40c7 Get/set pubsub subscription options. 2011-08-30 22:27:21 -07:00
Lance Stout
6b7fde10d3 Test pubsub subscribe. 2011-08-30 22:27:02 -07:00
Lance Stout
13fdab0139 Test and fix XEP-0060 delete_node() 2011-08-30 21:57:11 -07:00
Lance Stout
2ce617b2ce Fix typo 2011-08-30 09:24:46 -07:00
Lance Stout
63e0496c30 Finish up all major actions in the current XEP-0060.
Still need tests and docs.
2011-08-29 23:05:14 -07:00
Lance Stout
850e3bb99b Stub out missing functionality for pubsub 2011-08-29 21:38:41 -07:00
Lance Stout
2d90deb96a The ifrom parameter doesn't need special treatment. 2011-08-26 22:06:32 -07:00
Lance Stout
3fb3f63e51 Add docs + extended Iq send arguments to pubsub methods. 2011-08-26 16:57:37 -07:00
Lance Stout
d12949ff1c Fix typos in XEP-0060, start of docs and tests. 2011-08-26 12:14:57 -07:00
Lance Stout
e3e985220e Simplify the main process loop. 2011-08-25 17:08:20 -07:00
Lance Stout
802dd8393d Make the timeout for event queue checks configurable.
Now defaults xmlstream.WAIT_TIMEOUT, and settable with
self.wait_timeout.

The new default timeout is 1sec instead of 5sec.
2011-08-25 16:45:34 -07:00
Lance Stout
fe6bc31c60 Added XMLStream.configure_dns.
This can be overridden to do custom configuration for the DNS resolver,
or any other DNS related tasks such as calling the system's res_init().
2011-08-25 16:18:26 -07:00
Lance Stout
2162d6042e Session timeout now defaults to 45sec, but can be adjusted.
e.g.

    self.session_timeout = 15

It is also managed by XMLStream instead of ClientXMPP now.
2011-08-25 15:40:13 -07:00
Lance Stout
b8a4ffece9 Handle sending stanzas in chunks if the socket has poor performance. 2011-08-25 15:08:45 -07:00
Lance Stout
d929e0deb2 Shutdown socket before closing. 2011-08-25 13:48:43 -07:00
Lance Stout
4c08c9c524 Update scheduler with locks and ability to remove tasks.
Scheduled tasks must have a unique name.
2011-08-25 13:34:30 -07:00
Lance Stout
63b8444abe Add overridable method self.configure_socket().
Allows for setting app specific socket timeouts and other socket options.
2011-08-25 00:22:26 -07:00
Lance Stout
82546d776d Fix tests in Python3. 2011-08-25 00:21:53 -07:00
Lance Stout
84f9505a8d Fix handling of DNS exceptions. 2011-08-24 22:40:57 -07:00
Lance Stout
ede59ab40e Clean and get setup.py working once and for all.
Fixes:
    README.rst now included
    Double line spacing removed from long_description
    Source package now includes tests, examples, etc using Manifest.in
    README.rst typos fixed
    Added README.rst section on installing dnspython for Python3
    Version bumped to RC2
    Version is now taken from sleekxmpp.version.__version__ without
        having to pull in the entire library
    Added 'test' command for setup.py
    Simplified testall.py
    Docs build cleanly from source package after installation
2011-08-24 22:09:02 -07:00
49 changed files with 2520 additions and 866 deletions

2
.gitignore vendored
View File

@@ -4,3 +4,5 @@ dist/
MANIFEST
docs/_build/
*.swp
.tox/
.coverage

6
MANIFEST.in Normal file
View 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

View File

@@ -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

View File

@@ -4,8 +4,6 @@ clientxmpp
.. module:: sleekxmpp.clientxmpp
.. autodata:: SRV_SUPPORT
.. autoclass:: ClientXMPP
.. automethod:: connect

View File

@@ -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>

View File

@@ -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
View 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
View 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}
)

View File

@@ -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__

View File

@@ -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

View File

@@ -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.

View File

@@ -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'))

View File

@@ -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:

View File

@@ -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=''):

View File

@@ -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']

View File

@@ -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)

View File

@@ -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 *

View File

@@ -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)

View File

@@ -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)

View 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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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,

View File

@@ -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',

View File

@@ -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

View File

@@ -121,6 +121,7 @@ class TestSocket(object):
if self.disconnected:
raise socket.error
self.send_queue.put(data)
return len(data)
# ------------------------------------------------------------------
# File Socket

View File

@@ -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.")

View File

@@ -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
View 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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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."""

View File

@@ -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

View File

@@ -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
View 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()))

View File

@@ -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 = []

View File

@@ -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
View 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)

View File

@@ -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)

View File

@@ -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)

View 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)

5
tox.ini Normal file
View File

@@ -0,0 +1,5 @@
[tox]
envlist = py26,py27,py31,py32
[testenv]
deps = nose
commands = nosetests --where=tests --exclude=live -i sleektest.py