Compare commits
32 Commits
sleek-1.0-
...
sleek-1.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7945b3e738 | ||
|
|
d4c1ff5309 | ||
|
|
22868c3924 | ||
|
|
2de1be188c | ||
|
|
9faecec2db | ||
|
|
5d7111fe3b | ||
|
|
0c86f8288d | ||
|
|
5a6a65fd9f | ||
|
|
43c4d23896 | ||
|
|
9f9e8db814 | ||
|
|
b8efcc7cf0 | ||
|
|
2f29d18e53 | ||
|
|
888e286a09 | ||
|
|
1a93a187f0 | ||
|
|
a8d5da5091 | ||
|
|
e2720fac9e | ||
|
|
4374729f20 | ||
|
|
87999333cb | ||
|
|
335dc2927b | ||
|
|
ccbef6b696 | ||
|
|
3e384d3cfe | ||
|
|
e33949c397 | ||
|
|
eccac859ad | ||
|
|
7dd586f2fd | ||
|
|
3607c5b792 | ||
|
|
e37adace62 | ||
|
|
d10f591bf4 | ||
|
|
262da78ca7 | ||
|
|
0b83edf439 | ||
|
|
cf7fcf496e | ||
|
|
1765271f84 | ||
|
|
0ec79f8dc3 |
32
README.rst
32
README.rst
@@ -45,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>`_
|
||||
@@ -109,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()
|
||||
@@ -140,19 +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 use a different SSL version:
|
||||
#
|
||||
# import ssl
|
||||
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
|
||||
|
||||
if xmpp.connect():
|
||||
xmpp.process(block=True)
|
||||
else:
|
||||
print("Unable to connect.")
|
||||
xmpp.connect():
|
||||
xmpp.process(block=True)
|
||||
|
||||
|
||||
Credits
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<config xmlns="sleekxmpp:config">
|
||||
<jid>component.localhost</jid>
|
||||
<secret>ssshh</secret>
|
||||
<server>localhost</server>
|
||||
<port>8888</port>
|
||||
|
||||
<query xmlns="jabber:iq:roster">
|
||||
<item jid="user@example.com" subscription="both" />
|
||||
</query>
|
||||
</config>
|
||||
@@ -1,192 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
from optparse import OptionParser
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
||||
from sleekxmpp.stanza.roster import Roster
|
||||
from sleekxmpp.xmlstream import ElementBase
|
||||
from sleekxmpp.xmlstream.stanzabase import ET, registerStanzaPlugin
|
||||
|
||||
# Python versions before 3.0 do not use UTF-8 encoding
|
||||
# by default. To ensure that Unicode is handled properly
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
class Config(ElementBase):
|
||||
|
||||
"""
|
||||
In order to make loading and manipulating an XML config
|
||||
file easier, we will create a custom stanza object for
|
||||
our config XML file contents. See the documentation
|
||||
on stanza objects for more information on how to create
|
||||
and use stanza objects and stanza plugins.
|
||||
|
||||
We will reuse the IQ roster query stanza to store roster
|
||||
information since it already exists.
|
||||
|
||||
Example config XML:
|
||||
<config xmlns="sleekxmpp:config">
|
||||
<jid>component.localhost</jid>
|
||||
<secret>ssshh</secret>
|
||||
<server>localhost</server>
|
||||
<port>8888</port>
|
||||
|
||||
<query xmlns="jabber:iq:roster">
|
||||
<item jid="user@example.com" subscription="both" />
|
||||
</query>
|
||||
</config>
|
||||
"""
|
||||
|
||||
name = "config"
|
||||
namespace = "sleekxmpp:config"
|
||||
interfaces = set(('jid', 'secret', 'server', 'port'))
|
||||
sub_interfaces = interfaces
|
||||
|
||||
|
||||
registerStanzaPlugin(Config, Roster)
|
||||
|
||||
|
||||
class ConfigComponent(ComponentXMPP):
|
||||
|
||||
"""
|
||||
A simple SleekXMPP component that uses an external XML
|
||||
file to store its configuration data. To make testing
|
||||
that the component works, it will also echo messages sent
|
||||
to it.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
"""
|
||||
Create a ConfigComponent.
|
||||
|
||||
Arguments:
|
||||
config -- The XML contents of the config file.
|
||||
config_file -- The XML config file object itself.
|
||||
"""
|
||||
ComponentXMPP.__init__(self, config['jid'],
|
||||
config['secret'],
|
||||
config['server'],
|
||||
config['port'])
|
||||
|
||||
# Store the roster information.
|
||||
self.roster = config['roster']['items']
|
||||
|
||||
# The session_start event will be triggered when
|
||||
# the component establishes its connection with the
|
||||
# server and the XML streams are ready for use. We
|
||||
# want to listen for this event so that we we can
|
||||
# broadcast any needed initial presence stanzas.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
# The message event is triggered whenever a message
|
||||
# stanza is received. Be aware that that includes
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
The typical action for the session_start event in a component
|
||||
is to broadcast presence stanzas to all subscribers to the
|
||||
component. Note that the component does not have a roster
|
||||
provided by the XMPP server. In this case, we have possibly
|
||||
saved a roster in the component's configuration file.
|
||||
|
||||
Since the component may use any number of JIDs, you should
|
||||
also include the JID that is sending the presence.
|
||||
|
||||
Arguments:
|
||||
event -- An empty dictionary. The session_start
|
||||
event does not provide any additional
|
||||
data.
|
||||
"""
|
||||
for jid in self.roster:
|
||||
if self.roster[jid]['subscription'] != 'none':
|
||||
self.sendPresence(pfrom=self.jid, pto=jid)
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
Process incoming message stanzas. Be aware that this also
|
||||
includes MUC messages and error messages. It is usually
|
||||
a good idea to check the messages's type before processing
|
||||
or sending replies.
|
||||
|
||||
Since a component may send messages from any number of JIDs,
|
||||
it is best to always include a from JID.
|
||||
|
||||
Arguments:
|
||||
msg -- The received message stanza. See the documentation
|
||||
for stanza objects and the Message stanza to see
|
||||
how it may be used.
|
||||
"""
|
||||
# The reply method will use the messages 'to' JID as the
|
||||
# outgoing reply's 'from' JID.
|
||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
optp = OptionParser()
|
||||
|
||||
# Output verbosity options.
|
||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||
action='store_const', dest='loglevel',
|
||||
const=logging.ERROR, default=logging.INFO)
|
||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||
action='store_const', dest='loglevel',
|
||||
const=logging.DEBUG, default=logging.INFO)
|
||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||
action='store_const', dest='loglevel',
|
||||
const=5, default=logging.INFO)
|
||||
|
||||
# Component name and secret options.
|
||||
optp.add_option("-c", "--config", help="path to config file",
|
||||
dest="config", default="config.xml")
|
||||
|
||||
opts, args = optp.parse_args()
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=opts.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
# Load configuration data.
|
||||
config_file = open(opts.config, 'r+')
|
||||
config_data = "\n".join([line for line in config_file])
|
||||
config = Config(xml=ET.fromstring(config_data))
|
||||
config_file.close()
|
||||
|
||||
# Setup the ConfigComponent and register plugins. Note that while plugins
|
||||
# may have interdependencies, the order in which you register them does
|
||||
# not matter.
|
||||
xmpp = ConfigComponent(config)
|
||||
xmpp.registerPlugin('xep_0030') # Service Discovery
|
||||
xmpp.registerPlugin('xep_0004') # Data Forms
|
||||
xmpp.registerPlugin('xep_0060') # PubSub
|
||||
xmpp.registerPlugin('xep_0199') # XMPP Ping
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
if xmpp.connect():
|
||||
xmpp.process(threaded=False)
|
||||
print("Done")
|
||||
else:
|
||||
print("Unable to connect.")
|
||||
122
examples/echo_component.py
Executable file
122
examples/echo_component.py
Executable file
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2010 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
from optparse import OptionParser
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
||||
|
||||
# Python versions before 3.0 do not use UTF-8 encoding
|
||||
# by default. To ensure that Unicode is handled properly
|
||||
# throughout SleekXMPP, we will set the default encoding
|
||||
# ourselves to UTF-8.
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf8')
|
||||
else:
|
||||
raw_input = input
|
||||
|
||||
|
||||
class EchoComponent(ComponentXMPP):
|
||||
|
||||
"""
|
||||
A simple SleekXMPP component that echoes messages.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, secret, server, port):
|
||||
ComponentXMPP.__init__(self, jid, secret, server, port)
|
||||
|
||||
# You don't need a session_start handler, but that is
|
||||
# where you would broadcast initial presence.
|
||||
|
||||
# The message event is triggered whenever a message
|
||||
# stanza is received. Be aware that that includes
|
||||
# MUC messages and error messages.
|
||||
self.add_event_handler("message", self.message)
|
||||
|
||||
def message(self, msg):
|
||||
"""
|
||||
Process incoming message stanzas. Be aware that this also
|
||||
includes MUC messages and error messages. It is usually
|
||||
a good idea to check the messages's type before processing
|
||||
or sending replies.
|
||||
|
||||
Since a component may send messages from any number of JIDs,
|
||||
it is best to always include a from JID.
|
||||
|
||||
Arguments:
|
||||
msg -- The received message stanza. See the documentation
|
||||
for stanza objects and the Message stanza to see
|
||||
how it may be used.
|
||||
"""
|
||||
# The reply method will use the messages 'to' JID as the
|
||||
# outgoing reply's 'from' JID.
|
||||
msg.reply("Thanks for sending\n%(body)s" % msg).send()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
optp = OptionParser()
|
||||
|
||||
# Output verbosity options.
|
||||
optp.add_option('-q', '--quiet', help='set logging to ERROR',
|
||||
action='store_const', dest='loglevel',
|
||||
const=logging.ERROR, default=logging.INFO)
|
||||
optp.add_option('-d', '--debug', help='set logging to DEBUG',
|
||||
action='store_const', dest='loglevel',
|
||||
const=logging.DEBUG, default=logging.INFO)
|
||||
optp.add_option('-v', '--verbose', help='set logging to COMM',
|
||||
action='store_const', dest='loglevel',
|
||||
const=5, default=logging.INFO)
|
||||
|
||||
# JID and password options.
|
||||
optp.add_option("-j", "--jid", dest="jid",
|
||||
help="JID to use")
|
||||
optp.add_option("-p", "--password", dest="password",
|
||||
help="password to use")
|
||||
optp.add_option("-s", "--server", dest="server",
|
||||
help="server to connect to")
|
||||
optp.add_option("-P", "--port", dest="port",
|
||||
help="port to connect to")
|
||||
|
||||
opts, args = optp.parse_args()
|
||||
|
||||
if opts.jid is None:
|
||||
opts.jid = raw_input("Component JID: ")
|
||||
if opts.password is None:
|
||||
opts.password = getpass.getpass("Password: ")
|
||||
if opts.server is None:
|
||||
opts.server = raw_input("Server: ")
|
||||
if opts.port is None:
|
||||
opts.port = int(raw_input("Port: "))
|
||||
|
||||
# Setup logging.
|
||||
logging.basicConfig(level=opts.loglevel,
|
||||
format='%(levelname)-8s %(message)s')
|
||||
|
||||
# Setup the EchoComponent and register plugins. Note that while plugins
|
||||
# may have interdependencies, the order in which you register them does
|
||||
# not matter.
|
||||
xmpp = EchoComponent(opts.jid, opts.password, opts.server, opts.port)
|
||||
xmpp.registerPlugin('xep_0030') # Service Discovery
|
||||
xmpp.registerPlugin('xep_0004') # Data Forms
|
||||
xmpp.registerPlugin('xep_0060') # PubSub
|
||||
xmpp.registerPlugin('xep_0199') # XMPP Ping
|
||||
|
||||
# Connect to the XMPP server and start processing XMPP stanzas.
|
||||
if xmpp.connect():
|
||||
xmpp.process(threaded=False)
|
||||
print("Done")
|
||||
else:
|
||||
print("Unable to connect.")
|
||||
13
setup.py
13
setup.py
@@ -8,7 +8,10 @@
|
||||
# file, which you should have received as part of this distribution.
|
||||
|
||||
import sys
|
||||
from distutils.core import setup, Command
|
||||
try:
|
||||
from setuptools import setup, Command
|
||||
except ImportError:
|
||||
from distutils.core import setup, Command
|
||||
# from ez_setup import use_setuptools
|
||||
|
||||
from testall import TestCommand
|
||||
@@ -34,10 +37,10 @@ with open('README.rst') as 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',
|
||||
]
|
||||
|
||||
|
||||
@@ -106,9 +106,6 @@ class BaseXMPP(XMLStream):
|
||||
self.client_roster = self.roster[self.boundjid.bare]
|
||||
|
||||
self.is_component = False
|
||||
self.auto_authorize = True
|
||||
self.auto_subscribe = True
|
||||
|
||||
self.sentpresence = False
|
||||
|
||||
self.stanza = sleekxmpp.stanza
|
||||
@@ -640,6 +637,46 @@ class BaseXMPP(XMLStream):
|
||||
log.warning("server property deprecated. Use boundjid.host")
|
||||
self.boundjid.server = value
|
||||
|
||||
@property
|
||||
def auto_authorize(self):
|
||||
"""
|
||||
Auto accept or deny subscription requests.
|
||||
|
||||
If True, auto accept subscription requests.
|
||||
If False, auto deny subscription requests.
|
||||
If None, don't automatically respond.
|
||||
"""
|
||||
return self.roster.auto_authorize
|
||||
|
||||
@auto_authorize.setter
|
||||
def auto_authorize(self, value):
|
||||
"""
|
||||
Auto accept or deny subscription requests.
|
||||
|
||||
If True, auto accept subscription requests.
|
||||
If False, auto deny subscription requests.
|
||||
If None, don't automatically respond.
|
||||
"""
|
||||
self.roster.auto_authorize = value
|
||||
|
||||
@property
|
||||
def auto_subscribe(self):
|
||||
"""
|
||||
Auto send requests for mutual subscriptions.
|
||||
|
||||
If True, auto send mutual subscription requests.
|
||||
"""
|
||||
return self.roster.auto_subscribe
|
||||
|
||||
@auto_subscribe.setter
|
||||
def auto_subscribe(self, value):
|
||||
"""
|
||||
Auto send requests for mutual subscriptions.
|
||||
|
||||
If True, auto send mutual subscription requests.
|
||||
"""
|
||||
self.roster.auto_subscribe = value
|
||||
|
||||
def set_jid(self, jid):
|
||||
"""Rip a JID apart and claim it as our own."""
|
||||
log.debug("setting jid to %s" % jid)
|
||||
@@ -741,8 +778,6 @@ class BaseXMPP(XMLStream):
|
||||
not presence['type'] in presence.showtypes:
|
||||
return
|
||||
|
||||
self.event("changed_status", presence)
|
||||
|
||||
def exception(self, exception):
|
||||
"""
|
||||
Process any uncaught exceptions, notably IqError and
|
||||
|
||||
@@ -59,7 +59,7 @@ class ClientXMPP(BaseXMPP):
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, ssl=False, plugin_config={},
|
||||
plugin_whitelist=[], escape_quotes=True):
|
||||
plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
|
||||
"""
|
||||
Create a new SleekXMPP client.
|
||||
|
||||
@@ -114,11 +114,13 @@ class ClientXMPP(BaseXMPP):
|
||||
|
||||
# Setup default stream features
|
||||
self.register_plugin('feature_starttls')
|
||||
self.register_plugin('feature_mechanisms')
|
||||
self.register_plugin('feature_bind')
|
||||
self.register_plugin('feature_session')
|
||||
self.register_plugin('feature_mechanisms',
|
||||
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
|
||||
|
||||
def connect(self, address=tuple(), reattempt=True, use_tls=True):
|
||||
def connect(self, address=tuple(), reattempt=True,
|
||||
use_tls=True, use_ssl=False):
|
||||
"""
|
||||
Connect to the XMPP server.
|
||||
|
||||
@@ -132,13 +134,16 @@ class ClientXMPP(BaseXMPP):
|
||||
error occurs. Defaults to True.
|
||||
use_tls -- Indicates if TLS should be used for the
|
||||
connection. Defaults to True.
|
||||
use_ssl -- Indicates if the older SSL connection method
|
||||
should be used. Defaults to False.
|
||||
"""
|
||||
self.session_started_event.clear()
|
||||
if not address:
|
||||
address = (self.boundjid.host, 5222)
|
||||
|
||||
return XMLStream.connect(self, address[0], address[1],
|
||||
use_tls=use_tls, reattempt=reattempt)
|
||||
use_tls=use_tls, use_ssl=use_ssl,
|
||||
reattempt=reattempt)
|
||||
|
||||
def get_dns_records(self, domain, port=None):
|
||||
"""
|
||||
@@ -210,7 +215,7 @@ class ClientXMPP(BaseXMPP):
|
||||
Will be executed when the roster is received.
|
||||
Implies block=False.
|
||||
"""
|
||||
return self.client_roster.updtae(jid, name, subscription, groups,
|
||||
return self.client_roster.update(jid, name, subscription, groups,
|
||||
block, timeout, callback)
|
||||
|
||||
def del_roster_item(self, jid):
|
||||
|
||||
@@ -63,7 +63,7 @@ def _py2xml(*args):
|
||||
double.text = str(x)
|
||||
val.append(double)
|
||||
elif type(x) is rpcbase64:
|
||||
b64 = ET.Element("Base64")
|
||||
b64 = ET.Element("base64")
|
||||
b64.text = x.encoded()
|
||||
val.append(b64)
|
||||
elif type(x) is rpctime:
|
||||
@@ -110,7 +110,10 @@ def _xml2py(value):
|
||||
return value.find('{%s}string' % namespace).text
|
||||
if value.find('{%s}double' % namespace) is not None:
|
||||
return float(value.find('{%s}double' % namespace).text)
|
||||
if value.find('{%s}base64') is not None:
|
||||
return rpcbase64(value.find('base64' % namespace).text)
|
||||
if value.find('{%s}Base64') is not None:
|
||||
# Older versions of XEP-0009 used Base64
|
||||
return rpcbase64(value.find('Base64' % namespace).text)
|
||||
if value.find('{%s}dateTime.iso8601') is not None:
|
||||
return rpctime(value.find('{%s}dateTime.iso8601'))
|
||||
|
||||
@@ -699,10 +699,10 @@ class Remote(object):
|
||||
with Remote._lock:
|
||||
del cls._sessions[client.boundjid.bare]
|
||||
result = RemoteSession(client, _session_close_callback)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call, threaded=True)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response, threaded=True)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault, threaded=True)
|
||||
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error, threaded=True)
|
||||
if callback is None:
|
||||
start_event_handler = result._notify
|
||||
else:
|
||||
|
||||
@@ -14,6 +14,7 @@ from .. stanza.presence import Presence
|
||||
from .. xmlstream.handler.callback import Callback
|
||||
from .. xmlstream.matcher.xpath import MatchXPath
|
||||
from .. xmlstream.matcher.xmlmask import MatchXMLMask
|
||||
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -222,10 +223,10 @@ class xep_0045(base.base_plugin):
|
||||
return False
|
||||
return True
|
||||
|
||||
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None):
|
||||
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
|
||||
""" Join the specified room, requesting 'maxhistory' lines of history.
|
||||
"""
|
||||
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow)
|
||||
stanza = self.xmpp.makePresence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom)
|
||||
x = ET.Element('{http://jabber.org/protocol/muc}x')
|
||||
if password:
|
||||
passelement = ET.Element('password')
|
||||
@@ -271,7 +272,7 @@ class xep_0045(base.base_plugin):
|
||||
return False
|
||||
return True
|
||||
|
||||
def setAffiliation(self, room, jid=None, nick=None, affiliation='member'):
|
||||
def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
|
||||
""" Change room affiliation."""
|
||||
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
||||
raise TypeError
|
||||
@@ -283,6 +284,7 @@ class xep_0045(base.base_plugin):
|
||||
query.append(item)
|
||||
iq = self.xmpp.makeIqSet(query)
|
||||
iq['to'] = room
|
||||
iq['from'] = ifrom
|
||||
# For now, swallow errors to preserve existing API
|
||||
try:
|
||||
result = iq.send()
|
||||
@@ -306,13 +308,13 @@ class xep_0045(base.base_plugin):
|
||||
msg.append(x)
|
||||
self.xmpp.send(msg)
|
||||
|
||||
def leaveMUC(self, room, nick, msg=''):
|
||||
def leaveMUC(self, room, nick, msg='', pfrom=None):
|
||||
""" Leave the specified room.
|
||||
"""
|
||||
if msg:
|
||||
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg)
|
||||
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pstatus=msg, pfrom=pfrom)
|
||||
else:
|
||||
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick))
|
||||
self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom)
|
||||
del self.rooms[room]
|
||||
|
||||
def getRoomConfig(self, room, ifrom=''):
|
||||
@@ -331,12 +333,13 @@ class xep_0045(base.base_plugin):
|
||||
raise ValueError
|
||||
return self.xmpp.plugin['xep_0004'].buildForm(form)
|
||||
|
||||
def cancelConfig(self, room):
|
||||
def cancelConfig(self, room, ifrom=None):
|
||||
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
||||
x = ET.Element('{jabber:x:data}x', type='cancel')
|
||||
query.append(x)
|
||||
iq = self.xmpp.makeIqSet(query)
|
||||
iq['to'] = room
|
||||
iq['from'] = ifrom
|
||||
iq.send()
|
||||
|
||||
def setRoomConfig(self, room, config, ifrom=''):
|
||||
|
||||
@@ -202,7 +202,7 @@ class xep_0060(base_plugin):
|
||||
iq['pubsub']['options'].append(options)
|
||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
||||
|
||||
def get_node_config(self, jid, node=None, ifrom=None, block=None,
|
||||
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
|
||||
@@ -302,11 +302,33 @@ class xep_0060(base_plugin):
|
||||
def publish(self, jid, node, id=None, payload=None, options=None,
|
||||
ifrom=None, block=True, callback=None, timeout=None):
|
||||
"""
|
||||
Add or edit items in a node.
|
||||
Add a new item to a node, or edit an existing item.
|
||||
|
||||
You may publish an individual item using the item_id and payload
|
||||
parameters, or you may batch publish by using the items parameter
|
||||
which accepts a list of id/payload tuples.
|
||||
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
|
||||
|
||||
@@ -224,7 +224,7 @@ class RosterItem(object):
|
||||
if self['to']:
|
||||
p = self.xmpp.Presence()
|
||||
p['to'] = self.jid
|
||||
p['type'] = ['unsubscribe']
|
||||
p['type'] = 'unsubscribe'
|
||||
if self.xmpp.is_component:
|
||||
p['from'] = self.owner
|
||||
p.send()
|
||||
@@ -345,7 +345,11 @@ class RosterItem(object):
|
||||
self.xmpp.event('got_online', presence)
|
||||
if resource not in self.resources:
|
||||
self.resources[resource] = {}
|
||||
old_status = self.resources[resource].get('status', '')
|
||||
old_show = self.resources[resource].get('show', None)
|
||||
self.resources[resource].update(data)
|
||||
if old_show != presence['show'] or old_status != presence['status']:
|
||||
self.xmpp.event('changed_status', presence)
|
||||
|
||||
def handle_unavailable(self, presence):
|
||||
resource = presence['from'].resource
|
||||
@@ -353,6 +357,7 @@ class RosterItem(object):
|
||||
return
|
||||
if resource in self.resources:
|
||||
del self.resources[resource]
|
||||
self.xmpp.event('changed_status', presence)
|
||||
if not self.resources:
|
||||
self.xmpp.event('got_offline', presence)
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ class Roster(object):
|
||||
"""
|
||||
self.xmpp = xmpp
|
||||
self.db = db
|
||||
self.auto_authorize = True
|
||||
self.auto_subscribe = True
|
||||
self._auto_authorize = True
|
||||
self._auto_subscribe = True
|
||||
self._rosters = {}
|
||||
|
||||
if self.db:
|
||||
@@ -138,3 +138,47 @@ class Roster(object):
|
||||
ppriority=ppriority,
|
||||
pnick=pnick,
|
||||
pto=pto)
|
||||
|
||||
@property
|
||||
def auto_authorize(self):
|
||||
"""
|
||||
Auto accept or deny subscription requests.
|
||||
|
||||
If True, auto accept subscription requests.
|
||||
If False, auto deny subscription requests.
|
||||
If None, don't automatically respond.
|
||||
"""
|
||||
return self._auto_authorize
|
||||
|
||||
@auto_authorize.setter
|
||||
def auto_authorize(self, value):
|
||||
"""
|
||||
Auto accept or deny subscription requests.
|
||||
|
||||
If True, auto accept subscription requests.
|
||||
If False, auto deny subscription requests.
|
||||
If None, don't automatically respond.
|
||||
"""
|
||||
self._auto_authorize = value
|
||||
for node in self._rosters:
|
||||
self._rosters[node].auto_authorize = value
|
||||
|
||||
@property
|
||||
def auto_subscribe(self):
|
||||
"""
|
||||
Auto send requests for mutual subscriptions.
|
||||
|
||||
If True, auto send mutual subscription requests.
|
||||
"""
|
||||
return self._auto_subscribe
|
||||
|
||||
@auto_subscribe.setter
|
||||
def auto_subscribe(self, value):
|
||||
"""
|
||||
Auto send requests for mutual subscriptions.
|
||||
|
||||
If True, auto send mutual subscription requests.
|
||||
"""
|
||||
self._auto_subscribe = value
|
||||
for node in self._rosters:
|
||||
self._rosters[node].auto_subscribe = value
|
||||
|
||||
@@ -209,11 +209,11 @@ class RosterNode(object):
|
||||
Implies block=False.
|
||||
"""
|
||||
self[jid]['name'] = name
|
||||
self[jid]['groups'] = group
|
||||
self[jid]['groups'] = groups
|
||||
self[jid].save()
|
||||
|
||||
if not self.xmpp.is_component:
|
||||
iq = self.Iq()
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['roster']['items'] = {jid: {'name': name,
|
||||
'subscription': subscription,
|
||||
|
||||
@@ -293,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.
|
||||
|
||||
@@ -317,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.")
|
||||
|
||||
@@ -347,7 +351,10 @@ class SleekTest(unittest.TestCase):
|
||||
skip_queue.put('started')
|
||||
|
||||
self.xmpp.add_event_handler('session_start', wait_for_session)
|
||||
self.xmpp.connect()
|
||||
if server is not None:
|
||||
self.xmpp.connect((server, port))
|
||||
else:
|
||||
self.xmpp.connect()
|
||||
else:
|
||||
raise ValueError("Unknown socket type.")
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class SCRAM_HMAC(Mechanism):
|
||||
name = name[:-5]
|
||||
self._cb = True
|
||||
|
||||
self.hash = hash(self.name[6:])
|
||||
self.hash = hash(name[6:])
|
||||
if self.hash is None:
|
||||
raise SASLCancelled(self.sasl, self)
|
||||
if not self.sasl.tls_active():
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
# We don't want to have to import the entire library
|
||||
# just to get the version info for setup.py
|
||||
|
||||
__version__ = '1.0rc2'
|
||||
__version_info__ = (1, 0, 0, 'rc2', 0)
|
||||
__version__ = '1.0rc3'
|
||||
__version_info__ = (1, 0, 0, 'rc3', 0)
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import weakref
|
||||
|
||||
|
||||
class BaseHandler(object):
|
||||
|
||||
@@ -43,7 +45,10 @@ class BaseHandler(object):
|
||||
stream -- The XMLStream instance the handler should monitor.
|
||||
"""
|
||||
self.name = name
|
||||
self.stream = stream
|
||||
if stream is not None:
|
||||
self.stream = weakref.ref(stream)
|
||||
else:
|
||||
self.stream = None
|
||||
self._destroy = False
|
||||
self._payload = None
|
||||
self._matcher = matcher
|
||||
|
||||
@@ -85,14 +85,14 @@ class Waiter(BaseHandler):
|
||||
value sleekxmpp.xmlstream.RESPONSE_TIMEOUT.
|
||||
"""
|
||||
if timeout is None:
|
||||
timeout = self.stream.response_timeout
|
||||
timeout = self.stream().response_timeout
|
||||
|
||||
try:
|
||||
stanza = self._payload.get(True, timeout)
|
||||
except queue.Empty:
|
||||
stanza = False
|
||||
log.warning("Timed out waiting for %s" % self.name)
|
||||
self.stream.removeHandler(self.name)
|
||||
self.stream().remove_handler(self.name)
|
||||
return stanza
|
||||
|
||||
def check_delete(self):
|
||||
|
||||
@@ -135,3 +135,9 @@ class JID(object):
|
||||
"""
|
||||
other = JID(other)
|
||||
return self.full == other.full
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
Two JIDs are considered unequal if they are not equal.
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
@@ -19,6 +19,7 @@ import threading
|
||||
import time
|
||||
import types
|
||||
import random
|
||||
import weakref
|
||||
try:
|
||||
import queue
|
||||
except ImportError:
|
||||
@@ -532,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):
|
||||
"""
|
||||
@@ -719,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):
|
||||
"""
|
||||
@@ -840,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):
|
||||
"""
|
||||
@@ -1084,7 +1091,12 @@ class XMLStream(object):
|
||||
# new connections.
|
||||
if not self.session_started_event.is_set():
|
||||
self.send_raw(self.stream_header, now=True)
|
||||
self.__read_xml()
|
||||
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()
|
||||
@@ -1099,11 +1111,8 @@ class XMLStream(object):
|
||||
if not self.stop.is_set():
|
||||
log.exception('Connection error.')
|
||||
|
||||
if not self.stop.is_set():
|
||||
if self.auto_reconnect:
|
||||
self.reconnect()
|
||||
else:
|
||||
continue
|
||||
if not self.stop.is_set() and self.auto_reconnect:
|
||||
self.reconnect()
|
||||
else:
|
||||
self.disconnect()
|
||||
break
|
||||
@@ -1115,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):
|
||||
|
||||
@@ -48,6 +48,29 @@ class TestEvents(SleekTest):
|
||||
msg = "Event was not triggered the correct number of times: %s"
|
||||
self.failUnless(happened == [True], msg % happened)
|
||||
|
||||
def testAddDelAddEvent(self):
|
||||
"""Test adding, then removing, then adding an event handler."""
|
||||
happened = []
|
||||
|
||||
def handletestevent(event):
|
||||
happened.append(True)
|
||||
|
||||
self.xmpp.add_event_handler("test_event", handletestevent)
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
self.xmpp.del_event_handler("test_event", handletestevent)
|
||||
# Should not trigger because it was deleted
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
self.xmpp.add_event_handler("test_event", handletestevent)
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
# Give the event queue time to process.
|
||||
time.sleep(0.1)
|
||||
|
||||
msg = "Event was not triggered the correct number of times: %s"
|
||||
self.failUnless(happened == [True, True], msg % happened)
|
||||
|
||||
def testDisposableEvent(self):
|
||||
"""Test disposable handler working, then not being triggered again."""
|
||||
happened = []
|
||||
|
||||
@@ -124,5 +124,18 @@ class TestJIDClass(SleekTest):
|
||||
'component.someserver',
|
||||
'component.someserver')
|
||||
|
||||
def testJIDEquality(self):
|
||||
"""Test that JIDs with the same content are equal."""
|
||||
jid1 = JID('user@domain/resource')
|
||||
jid2 = JID('user@domain/resource')
|
||||
self.assertTrue(jid1 == jid2, "Same JIDs are not considered equal")
|
||||
self.assertFalse(jid1 != jid2, "Same JIDs are considered not equal")
|
||||
|
||||
def testJIDInequality(self):
|
||||
jid1 = JID('user@domain/resource')
|
||||
jid2 = JID('otheruser@domain/resource')
|
||||
self.assertFalse(jid1 == jid2, "Same JIDs are not considered equal")
|
||||
self.assertTrue(jid1 != jid2, "Same JIDs are considered not equal")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user