Compare commits
32 Commits
sleek-1.0-
...
1.0-RC3
| 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 |
30
README.rst
30
README.rst
@@ -45,6 +45,9 @@ The latest source code for SleekXMPP may be found on `Github
|
|||||||
``develop`` branch.
|
``develop`` branch.
|
||||||
|
|
||||||
**Stable Releases**
|
**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 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 Beta5 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta5>`_
|
||||||
- `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_
|
- `1.0 Beta4 <http://github.com/fritzy/SleekXMPP/zipball/1.0-Beta4>`_
|
||||||
@@ -109,13 +112,21 @@ SleekXMPP projects::
|
|||||||
def __init__(self, jid, password):
|
def __init__(self, jid, password):
|
||||||
ClientXMPP.__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)
|
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()
|
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
|
# can generate IqError and IqTimeout exceptions
|
||||||
try:
|
try:
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
@@ -140,19 +151,8 @@ SleekXMPP projects::
|
|||||||
format='%(levelname)-8s %(message)s')
|
format='%(levelname)-8s %(message)s')
|
||||||
|
|
||||||
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
xmpp = EchoBot('somejid@example.com', 'use_getpass')
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
xmpp.connect():
|
||||||
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)
|
xmpp.process(block=True)
|
||||||
else:
|
|
||||||
print("Unable to connect.")
|
|
||||||
|
|
||||||
|
|
||||||
Credits
|
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.
|
# file, which you should have received as part of this distribution.
|
||||||
|
|
||||||
import sys
|
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 ez_setup import use_setuptools
|
||||||
|
|
||||||
from testall import TestCommand
|
from testall import TestCommand
|
||||||
@@ -34,10 +37,10 @@ with open('README.rst') as readme:
|
|||||||
CLASSIFIERS = [ 'Intended Audience :: Developers',
|
CLASSIFIERS = [ 'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python 2.6',
|
'Programming Language :: Python :: 2.6',
|
||||||
'Programming Language :: Python 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
'Programming Language :: Python 3.1',
|
'Programming Language :: Python :: 3.1',
|
||||||
'Programming Language :: Python 3.2',
|
'Programming Language :: Python :: 3.2',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -106,9 +106,6 @@ class BaseXMPP(XMLStream):
|
|||||||
self.client_roster = self.roster[self.boundjid.bare]
|
self.client_roster = self.roster[self.boundjid.bare]
|
||||||
|
|
||||||
self.is_component = False
|
self.is_component = False
|
||||||
self.auto_authorize = True
|
|
||||||
self.auto_subscribe = True
|
|
||||||
|
|
||||||
self.sentpresence = False
|
self.sentpresence = False
|
||||||
|
|
||||||
self.stanza = sleekxmpp.stanza
|
self.stanza = sleekxmpp.stanza
|
||||||
@@ -640,6 +637,46 @@ class BaseXMPP(XMLStream):
|
|||||||
log.warning("server property deprecated. Use boundjid.host")
|
log.warning("server property deprecated. Use boundjid.host")
|
||||||
self.boundjid.server = value
|
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):
|
def set_jid(self, jid):
|
||||||
"""Rip a JID apart and claim it as our own."""
|
"""Rip a JID apart and claim it as our own."""
|
||||||
log.debug("setting jid to %s" % jid)
|
log.debug("setting jid to %s" % jid)
|
||||||
@@ -741,8 +778,6 @@ class BaseXMPP(XMLStream):
|
|||||||
not presence['type'] in presence.showtypes:
|
not presence['type'] in presence.showtypes:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.event("changed_status", presence)
|
|
||||||
|
|
||||||
def exception(self, exception):
|
def exception(self, exception):
|
||||||
"""
|
"""
|
||||||
Process any uncaught exceptions, notably IqError and
|
Process any uncaught exceptions, notably IqError and
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, jid, password, ssl=False, plugin_config={},
|
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.
|
Create a new SleekXMPP client.
|
||||||
|
|
||||||
@@ -114,11 +114,13 @@ class ClientXMPP(BaseXMPP):
|
|||||||
|
|
||||||
# Setup default stream features
|
# Setup default stream features
|
||||||
self.register_plugin('feature_starttls')
|
self.register_plugin('feature_starttls')
|
||||||
self.register_plugin('feature_mechanisms')
|
|
||||||
self.register_plugin('feature_bind')
|
self.register_plugin('feature_bind')
|
||||||
self.register_plugin('feature_session')
|
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.
|
Connect to the XMPP server.
|
||||||
|
|
||||||
@@ -132,13 +134,16 @@ class ClientXMPP(BaseXMPP):
|
|||||||
error occurs. Defaults to True.
|
error occurs. Defaults to True.
|
||||||
use_tls -- Indicates if TLS should be used for the
|
use_tls -- Indicates if TLS should be used for the
|
||||||
connection. Defaults to True.
|
connection. Defaults to True.
|
||||||
|
use_ssl -- Indicates if the older SSL connection method
|
||||||
|
should be used. Defaults to False.
|
||||||
"""
|
"""
|
||||||
self.session_started_event.clear()
|
self.session_started_event.clear()
|
||||||
if not address:
|
if not address:
|
||||||
address = (self.boundjid.host, 5222)
|
address = (self.boundjid.host, 5222)
|
||||||
|
|
||||||
return XMLStream.connect(self, address[0], address[1],
|
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):
|
def get_dns_records(self, domain, port=None):
|
||||||
"""
|
"""
|
||||||
@@ -210,7 +215,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
Will be executed when the roster is received.
|
Will be executed when the roster is received.
|
||||||
Implies block=False.
|
Implies block=False.
|
||||||
"""
|
"""
|
||||||
return self.client_roster.updtae(jid, name, subscription, groups,
|
return self.client_roster.update(jid, name, subscription, groups,
|
||||||
block, timeout, callback)
|
block, timeout, callback)
|
||||||
|
|
||||||
def del_roster_item(self, jid):
|
def del_roster_item(self, jid):
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ def _py2xml(*args):
|
|||||||
double.text = str(x)
|
double.text = str(x)
|
||||||
val.append(double)
|
val.append(double)
|
||||||
elif type(x) is rpcbase64:
|
elif type(x) is rpcbase64:
|
||||||
b64 = ET.Element("Base64")
|
b64 = ET.Element("base64")
|
||||||
b64.text = x.encoded()
|
b64.text = x.encoded()
|
||||||
val.append(b64)
|
val.append(b64)
|
||||||
elif type(x) is rpctime:
|
elif type(x) is rpctime:
|
||||||
@@ -110,7 +110,10 @@ def _xml2py(value):
|
|||||||
return value.find('{%s}string' % namespace).text
|
return value.find('{%s}string' % namespace).text
|
||||||
if value.find('{%s}double' % namespace) is not None:
|
if value.find('{%s}double' % namespace) is not None:
|
||||||
return float(value.find('{%s}double' % namespace).text)
|
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:
|
if value.find('{%s}Base64') is not None:
|
||||||
|
# Older versions of XEP-0009 used Base64
|
||||||
return rpcbase64(value.find('Base64' % namespace).text)
|
return rpcbase64(value.find('Base64' % namespace).text)
|
||||||
if value.find('{%s}dateTime.iso8601') is not None:
|
if value.find('{%s}dateTime.iso8601') is not None:
|
||||||
return rpctime(value.find('{%s}dateTime.iso8601'))
|
return rpctime(value.find('{%s}dateTime.iso8601'))
|
||||||
|
|||||||
@@ -699,10 +699,10 @@ class Remote(object):
|
|||||||
with Remote._lock:
|
with Remote._lock:
|
||||||
del cls._sessions[client.boundjid.bare]
|
del cls._sessions[client.boundjid.bare]
|
||||||
result = RemoteSession(client, _session_close_callback)
|
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_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)
|
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)
|
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)
|
client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error, threaded=True)
|
||||||
if callback is None:
|
if callback is None:
|
||||||
start_event_handler = result._notify
|
start_event_handler = result._notify
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from .. stanza.presence import Presence
|
|||||||
from .. xmlstream.handler.callback import Callback
|
from .. xmlstream.handler.callback import Callback
|
||||||
from .. xmlstream.matcher.xpath import MatchXPath
|
from .. xmlstream.matcher.xpath import MatchXPath
|
||||||
from .. xmlstream.matcher.xmlmask import MatchXMLMask
|
from .. xmlstream.matcher.xmlmask import MatchXMLMask
|
||||||
|
from sleekxmpp.exceptions import IqError, IqTimeout
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
@@ -222,10 +223,10 @@ class xep_0045(base.base_plugin):
|
|||||||
return False
|
return False
|
||||||
return True
|
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.
|
""" 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')
|
x = ET.Element('{http://jabber.org/protocol/muc}x')
|
||||||
if password:
|
if password:
|
||||||
passelement = ET.Element('password')
|
passelement = ET.Element('password')
|
||||||
@@ -271,7 +272,7 @@ class xep_0045(base.base_plugin):
|
|||||||
return False
|
return False
|
||||||
return True
|
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."""
|
""" Change room affiliation."""
|
||||||
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
|
||||||
raise TypeError
|
raise TypeError
|
||||||
@@ -283,6 +284,7 @@ class xep_0045(base.base_plugin):
|
|||||||
query.append(item)
|
query.append(item)
|
||||||
iq = self.xmpp.makeIqSet(query)
|
iq = self.xmpp.makeIqSet(query)
|
||||||
iq['to'] = room
|
iq['to'] = room
|
||||||
|
iq['from'] = ifrom
|
||||||
# For now, swallow errors to preserve existing API
|
# For now, swallow errors to preserve existing API
|
||||||
try:
|
try:
|
||||||
result = iq.send()
|
result = iq.send()
|
||||||
@@ -306,13 +308,13 @@ class xep_0045(base.base_plugin):
|
|||||||
msg.append(x)
|
msg.append(x)
|
||||||
self.xmpp.send(msg)
|
self.xmpp.send(msg)
|
||||||
|
|
||||||
def leaveMUC(self, room, nick, msg=''):
|
def leaveMUC(self, room, nick, msg='', pfrom=None):
|
||||||
""" Leave the specified room.
|
""" Leave the specified room.
|
||||||
"""
|
"""
|
||||||
if msg:
|
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:
|
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]
|
del self.rooms[room]
|
||||||
|
|
||||||
def getRoomConfig(self, room, ifrom=''):
|
def getRoomConfig(self, room, ifrom=''):
|
||||||
@@ -331,12 +333,13 @@ class xep_0045(base.base_plugin):
|
|||||||
raise ValueError
|
raise ValueError
|
||||||
return self.xmpp.plugin['xep_0004'].buildForm(form)
|
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')
|
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
|
||||||
x = ET.Element('{jabber:x:data}x', type='cancel')
|
x = ET.Element('{jabber:x:data}x', type='cancel')
|
||||||
query.append(x)
|
query.append(x)
|
||||||
iq = self.xmpp.makeIqSet(query)
|
iq = self.xmpp.makeIqSet(query)
|
||||||
iq['to'] = room
|
iq['to'] = room
|
||||||
|
iq['from'] = ifrom
|
||||||
iq.send()
|
iq.send()
|
||||||
|
|
||||||
def setRoomConfig(self, room, config, ifrom=''):
|
def setRoomConfig(self, room, config, ifrom=''):
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ class xep_0060(base_plugin):
|
|||||||
iq['pubsub']['options'].append(options)
|
iq['pubsub']['options'].append(options)
|
||||||
return iq.send(block=block, callback=callback, timeout=timeout)
|
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):
|
callback=None, timeout=None):
|
||||||
"""
|
"""
|
||||||
Retrieve the configuration for a node, or the pubsub service's
|
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,
|
def publish(self, jid, node, id=None, payload=None, options=None,
|
||||||
ifrom=None, block=True, callback=None, timeout=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
|
For services that support it, you can use the publish command
|
||||||
parameters, or you may batch publish by using the items parameter
|
as an event signal by not including an ID or payload.
|
||||||
which accepts a list of id/payload tuples.
|
|
||||||
|
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 = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
|
||||||
iq['pubsub']['publish']['node'] = node
|
iq['pubsub']['publish']['node'] = node
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class RosterItem(object):
|
|||||||
if self['to']:
|
if self['to']:
|
||||||
p = self.xmpp.Presence()
|
p = self.xmpp.Presence()
|
||||||
p['to'] = self.jid
|
p['to'] = self.jid
|
||||||
p['type'] = ['unsubscribe']
|
p['type'] = 'unsubscribe'
|
||||||
if self.xmpp.is_component:
|
if self.xmpp.is_component:
|
||||||
p['from'] = self.owner
|
p['from'] = self.owner
|
||||||
p.send()
|
p.send()
|
||||||
@@ -345,7 +345,11 @@ class RosterItem(object):
|
|||||||
self.xmpp.event('got_online', presence)
|
self.xmpp.event('got_online', presence)
|
||||||
if resource not in self.resources:
|
if resource not in self.resources:
|
||||||
self.resources[resource] = {}
|
self.resources[resource] = {}
|
||||||
|
old_status = self.resources[resource].get('status', '')
|
||||||
|
old_show = self.resources[resource].get('show', None)
|
||||||
self.resources[resource].update(data)
|
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):
|
def handle_unavailable(self, presence):
|
||||||
resource = presence['from'].resource
|
resource = presence['from'].resource
|
||||||
@@ -353,6 +357,7 @@ class RosterItem(object):
|
|||||||
return
|
return
|
||||||
if resource in self.resources:
|
if resource in self.resources:
|
||||||
del self.resources[resource]
|
del self.resources[resource]
|
||||||
|
self.xmpp.event('changed_status', presence)
|
||||||
if not self.resources:
|
if not self.resources:
|
||||||
self.xmpp.event('got_offline', presence)
|
self.xmpp.event('got_offline', presence)
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ class Roster(object):
|
|||||||
"""
|
"""
|
||||||
self.xmpp = xmpp
|
self.xmpp = xmpp
|
||||||
self.db = db
|
self.db = db
|
||||||
self.auto_authorize = True
|
self._auto_authorize = True
|
||||||
self.auto_subscribe = True
|
self._auto_subscribe = True
|
||||||
self._rosters = {}
|
self._rosters = {}
|
||||||
|
|
||||||
if self.db:
|
if self.db:
|
||||||
@@ -138,3 +138,47 @@ class Roster(object):
|
|||||||
ppriority=ppriority,
|
ppriority=ppriority,
|
||||||
pnick=pnick,
|
pnick=pnick,
|
||||||
pto=pto)
|
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.
|
Implies block=False.
|
||||||
"""
|
"""
|
||||||
self[jid]['name'] = name
|
self[jid]['name'] = name
|
||||||
self[jid]['groups'] = group
|
self[jid]['groups'] = groups
|
||||||
self[jid].save()
|
self[jid].save()
|
||||||
|
|
||||||
if not self.xmpp.is_component:
|
if not self.xmpp.is_component:
|
||||||
iq = self.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['roster']['items'] = {jid: {'name': name,
|
iq['roster']['items'] = {jid: {'name': name,
|
||||||
'subscription': subscription,
|
'subscription': subscription,
|
||||||
|
|||||||
@@ -293,7 +293,8 @@ class SleekTest(unittest.TestCase):
|
|||||||
def stream_start(self, mode='client', skip=True, header=None,
|
def stream_start(self, mode='client', skip=True, header=None,
|
||||||
socket='mock', jid='tester@localhost',
|
socket='mock', jid='tester@localhost',
|
||||||
password='test', server='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.
|
Initialize an XMPP client or component using a dummy XML stream.
|
||||||
|
|
||||||
@@ -317,10 +318,13 @@ class SleekTest(unittest.TestCase):
|
|||||||
are loaded.
|
are loaded.
|
||||||
"""
|
"""
|
||||||
if mode == 'client':
|
if mode == 'client':
|
||||||
self.xmpp = ClientXMPP(jid, password)
|
self.xmpp = ClientXMPP(jid, password,
|
||||||
|
sasl_mech=sasl_mech,
|
||||||
|
plugin_config=plugin_config)
|
||||||
elif mode == 'component':
|
elif mode == 'component':
|
||||||
self.xmpp = ComponentXMPP(jid, password,
|
self.xmpp = ComponentXMPP(jid, password,
|
||||||
server, port)
|
server, port,
|
||||||
|
plugin_config=plugin_config)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown XMPP connection mode.")
|
raise ValueError("Unknown XMPP connection mode.")
|
||||||
|
|
||||||
@@ -347,6 +351,9 @@ class SleekTest(unittest.TestCase):
|
|||||||
skip_queue.put('started')
|
skip_queue.put('started')
|
||||||
|
|
||||||
self.xmpp.add_event_handler('session_start', wait_for_session)
|
self.xmpp.add_event_handler('session_start', wait_for_session)
|
||||||
|
if server is not None:
|
||||||
|
self.xmpp.connect((server, port))
|
||||||
|
else:
|
||||||
self.xmpp.connect()
|
self.xmpp.connect()
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown socket type.")
|
raise ValueError("Unknown socket type.")
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class SCRAM_HMAC(Mechanism):
|
|||||||
name = name[:-5]
|
name = name[:-5]
|
||||||
self._cb = True
|
self._cb = True
|
||||||
|
|
||||||
self.hash = hash(self.name[6:])
|
self.hash = hash(name[6:])
|
||||||
if self.hash is None:
|
if self.hash is None:
|
||||||
raise SASLCancelled(self.sasl, self)
|
raise SASLCancelled(self.sasl, self)
|
||||||
if not self.sasl.tls_active():
|
if not self.sasl.tls_active():
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
# We don't want to have to import the entire library
|
# We don't want to have to import the entire library
|
||||||
# just to get the version info for setup.py
|
# just to get the version info for setup.py
|
||||||
|
|
||||||
__version__ = '1.0rc2'
|
__version__ = '1.0rc3'
|
||||||
__version_info__ = (1, 0, 0, 'rc2', 0)
|
__version_info__ = (1, 0, 0, 'rc3', 0)
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import weakref
|
||||||
|
|
||||||
|
|
||||||
class BaseHandler(object):
|
class BaseHandler(object):
|
||||||
|
|
||||||
@@ -43,7 +45,10 @@ class BaseHandler(object):
|
|||||||
stream -- The XMLStream instance the handler should monitor.
|
stream -- The XMLStream instance the handler should monitor.
|
||||||
"""
|
"""
|
||||||
self.name = name
|
self.name = name
|
||||||
self.stream = stream
|
if stream is not None:
|
||||||
|
self.stream = weakref.ref(stream)
|
||||||
|
else:
|
||||||
|
self.stream = None
|
||||||
self._destroy = False
|
self._destroy = False
|
||||||
self._payload = None
|
self._payload = None
|
||||||
self._matcher = matcher
|
self._matcher = matcher
|
||||||
|
|||||||
@@ -85,14 +85,14 @@ class Waiter(BaseHandler):
|
|||||||
value sleekxmpp.xmlstream.RESPONSE_TIMEOUT.
|
value sleekxmpp.xmlstream.RESPONSE_TIMEOUT.
|
||||||
"""
|
"""
|
||||||
if timeout is None:
|
if timeout is None:
|
||||||
timeout = self.stream.response_timeout
|
timeout = self.stream().response_timeout
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stanza = self._payload.get(True, timeout)
|
stanza = self._payload.get(True, timeout)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
stanza = False
|
stanza = False
|
||||||
log.warning("Timed out waiting for %s" % self.name)
|
log.warning("Timed out waiting for %s" % self.name)
|
||||||
self.stream.removeHandler(self.name)
|
self.stream().remove_handler(self.name)
|
||||||
return stanza
|
return stanza
|
||||||
|
|
||||||
def check_delete(self):
|
def check_delete(self):
|
||||||
|
|||||||
@@ -135,3 +135,9 @@ class JID(object):
|
|||||||
"""
|
"""
|
||||||
other = JID(other)
|
other = JID(other)
|
||||||
return self.full == other.full
|
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 time
|
||||||
import types
|
import types
|
||||||
import random
|
import random
|
||||||
|
import weakref
|
||||||
try:
|
try:
|
||||||
import queue
|
import queue
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -532,9 +533,14 @@ class XMLStream(object):
|
|||||||
log.debug("reconnecting...")
|
log.debug("reconnecting...")
|
||||||
self.state.transition('connected', 'disconnected', wait=2.0,
|
self.state.transition('connected', 'disconnected', wait=2.0,
|
||||||
func=self._disconnect, args=(True,))
|
func=self._disconnect, args=(True,))
|
||||||
|
|
||||||
log.debug("connecting...")
|
log.debug("connecting...")
|
||||||
return self.state.transition('disconnected', 'connected',
|
connected = self.state.transition('disconnected', 'connected',
|
||||||
wait=2.0, func=self._connect)
|
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):
|
def set_socket(self, socket, ignore=False):
|
||||||
"""
|
"""
|
||||||
@@ -719,7 +725,7 @@ class XMLStream(object):
|
|||||||
"""
|
"""
|
||||||
if handler.stream is None:
|
if handler.stream is None:
|
||||||
self.__handlers.append(handler)
|
self.__handlers.append(handler)
|
||||||
handler.stream = self
|
handler.stream = weakref.ref(self)
|
||||||
|
|
||||||
def remove_handler(self, name):
|
def remove_handler(self, name):
|
||||||
"""
|
"""
|
||||||
@@ -840,8 +846,9 @@ class XMLStream(object):
|
|||||||
def filter_pointers(handler):
|
def filter_pointers(handler):
|
||||||
return handler[0] != pointer
|
return handler[0] != pointer
|
||||||
|
|
||||||
self.__event_handlers[name] = filter(filter_pointers,
|
self.__event_handlers[name] = list(filter(
|
||||||
self.__event_handlers[name])
|
filter_pointers,
|
||||||
|
self.__event_handlers[name]))
|
||||||
|
|
||||||
def event_handled(self, name):
|
def event_handled(self, name):
|
||||||
"""
|
"""
|
||||||
@@ -1084,7 +1091,12 @@ class XMLStream(object):
|
|||||||
# new connections.
|
# new connections.
|
||||||
if not self.session_started_event.is_set():
|
if not self.session_started_event.is_set():
|
||||||
self.send_raw(self.stream_header, now=True)
|
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:
|
except KeyboardInterrupt:
|
||||||
log.debug("Keyboard Escape Detected in _process")
|
log.debug("Keyboard Escape Detected in _process")
|
||||||
self.stop.set()
|
self.stop.set()
|
||||||
@@ -1099,11 +1111,8 @@ class XMLStream(object):
|
|||||||
if not self.stop.is_set():
|
if not self.stop.is_set():
|
||||||
log.exception('Connection error.')
|
log.exception('Connection error.')
|
||||||
|
|
||||||
if not self.stop.is_set():
|
if not self.stop.is_set() and self.auto_reconnect:
|
||||||
if self.auto_reconnect:
|
|
||||||
self.reconnect()
|
self.reconnect()
|
||||||
else:
|
|
||||||
continue
|
|
||||||
else:
|
else:
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
break
|
break
|
||||||
@@ -1115,9 +1124,7 @@ class XMLStream(object):
|
|||||||
"""
|
"""
|
||||||
depth = 0
|
depth = 0
|
||||||
root = None
|
root = None
|
||||||
try:
|
for event, xml in ET.iterparse(self.filesocket, (b'end', b'start')):
|
||||||
for (event, xml) in ET.iterparse(self.filesocket,
|
|
||||||
(b'end', b'start')):
|
|
||||||
if event == b'start':
|
if event == b'start':
|
||||||
if depth == 0:
|
if depth == 0:
|
||||||
# We have received the start of the root element.
|
# We have received the start of the root element.
|
||||||
@@ -1142,12 +1149,10 @@ class XMLStream(object):
|
|||||||
self.__spawn_event(xml)
|
self.__spawn_event(xml)
|
||||||
except RestartStream:
|
except RestartStream:
|
||||||
return True
|
return True
|
||||||
if root:
|
if root is not None:
|
||||||
# Keep the root element empty of children to
|
# Keep the root element empty of children to
|
||||||
# save on memory use.
|
# save on memory use.
|
||||||
root.clear()
|
root.clear()
|
||||||
except SyntaxError:
|
|
||||||
log.error("Error reading from XML stream.")
|
|
||||||
log.debug("Ending read XML loop")
|
log.debug("Ending read XML loop")
|
||||||
|
|
||||||
def _build_stanza(self, xml, default_ns=None):
|
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"
|
msg = "Event was not triggered the correct number of times: %s"
|
||||||
self.failUnless(happened == [True], msg % happened)
|
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):
|
def testDisposableEvent(self):
|
||||||
"""Test disposable handler working, then not being triggered again."""
|
"""Test disposable handler working, then not being triggered again."""
|
||||||
happened = []
|
happened = []
|
||||||
|
|||||||
@@ -124,5 +124,18 @@ class TestJIDClass(SleekTest):
|
|||||||
'component.someserver',
|
'component.someserver',
|
||||||
'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)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)
|
||||||
|
|||||||
@@ -251,5 +251,130 @@ class TestStreamPresence(SleekTest):
|
|||||||
self.assertEqual(events, ptypes,
|
self.assertEqual(events, ptypes,
|
||||||
"Not all events raised: %s" % events)
|
"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)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamPresence)
|
||||||
|
|||||||
Reference in New Issue
Block a user