Compare commits

...

75 Commits

Author SHA1 Message Date
Lance Stout
acd9c32a9f Bump version to 1.1.9 2012-07-20 00:17:53 -07:00
Lance Stout
b8581b0278 Of course Peter goes and changes the XEP title the day after I implement it. 2012-07-19 23:59:35 -07:00
Lance Stout
917faecdcb Fix issue of roster data being split across multiple rosters.
Resolved by always normalizing JIDs to bare form, regardless of if they
are JID objects or strings.

Also simplified related code to prefer use of JID objects instead of
strings so they don't need to be parsed multiple times.
2012-07-19 23:54:18 -07:00
Lance Stout
f6edaa56a6 Add plugin for XEP-0191: Simple Communications Blocking 2012-07-16 20:10:14 -07:00
Lance Stout
51fee28bf4 Add a warning log if dnspython is not found for SRV lookup.
Closes issue #183
2012-07-16 19:38:50 -07:00
Lance Stout
e8a3e92ceb Update plugins to use session_bind handler for disco, and use plugin_end 2012-07-10 01:37:44 -07:00
Lance Stout
5df3839b7a Add method to remove a filter. 2012-07-10 01:37:23 -07:00
Lance Stout
8dcb441f44 Add default plugin session_bind handler.
All plugins may now simply define a session_bind method where disco
features and other actions which require the bound JID may be done.
2012-07-10 01:36:21 -07:00
Lance Stout
a347cf625a Add session_bind_event threading event. 2012-07-10 01:35:57 -07:00
Lance Stout
46f49c7a12 Add method to unregister stream features. 2012-07-10 01:35:25 -07:00
Lance Stout
99701c947e Prevent None from being added to the schedule from a timing issue. 2012-07-09 22:59:26 -07:00
Lance Stout
1baae1b81e Fix issues of disco info leaking between entities with the same bare JIDs.
To ensure that disco info, or any settings which depend on the bound
JID, are correct, only set such information on or after the
session_bound event has fired.
2012-07-09 22:22:05 -07:00
Lance Stout
7d20f0e9a6 Fix missing import in xep_0256 plugin. 2012-07-09 22:21:40 -07:00
Lance Stout
fbad22a1cd Merge pull request #181 from whooo/upstream
Fix for the RSM iterator
2012-07-09 09:25:09 -07:00
Erik Larsson
5af2f62c04 Make sure that the last RSM stanza is returned from the iterator 2012-07-08 23:27:13 +02:00
Jay Farrimond
4a4a03858e dereference iq stanza only once for roster processing 2012-07-06 14:03:41 -07:00
Lance Stout
6819b57353 Handle converting None to byte data (b''). 2012-07-06 11:05:47 -07:00
Jay Farrimond
88b5e60807 only log cert errors if not handled by user 2012-07-05 13:38:26 -07:00
Lance Stout
a26a8bd79c Bump version to 1.1.8 2012-06-30 17:40:11 -07:00
Lance Stout
9307a6915f Add notes to echo_client.py example on working with Facebook and MSN. 2012-06-23 22:30:24 -07:00
Lance Stout
85ef2d8d0b Add support for reconnecting based on see-other-host stream errors. 2012-06-22 23:13:16 -07:00
Lance Stout
c2c7cc032b Fix plugin registration for single file plugins. 2012-06-22 21:58:50 -07:00
Lance Stout
e4911e9391 Add meta plugin for XEP-0302 for the 2012 compliance suite.
There are still a few remaining items in the RFCs to add support for,
but the current plugin support matches the advanced client profile.
2012-06-22 21:52:39 -07:00
Lance Stout
b11e1ee92d Add meta plugin for XEP-0270, 2010 compliance suite.
Registering this plugin will load the plugins required for advanced
client compliance status.
2012-06-22 21:26:25 -07:00
Lance Stout
5027d00c10 Change packaging for XEP-0256 to just a single file. 2012-06-22 21:26:01 -07:00
Lance Stout
69ddeceb49 Add support for XEP-0256: Last Activity in Presence 2012-06-22 21:13:30 -07:00
Lance Stout
82698672bb Add 'thread' and 'parent_thread' interfaces to message stanzas.
These values are perisisted across replies.
2012-06-22 20:05:34 -07:00
Lance Stout
9cec284947 Mark presence status as language aware. 2012-06-22 20:05:17 -07:00
Lance Stout
dc501d1902 Mark message body and subject as language aware interfaces. 2012-06-22 19:08:51 -07:00
Lance Stout
100e504b7f Resolve xml:lang issue with duplicated elements depending on ordering. 2012-06-22 18:19:17 -07:00
Lance Stout
8a745c5e81 Bump version to 1.1.7 2012-06-20 23:45:14 -07:00
Lance Stout
bf0a157c5d Add support for XEP-0221: Data Forms Media Element 2012-06-20 23:38:30 -07:00
Lance Stout
f49818be06 Add support for XEP-0186: Invisible Command 2012-06-20 23:37:39 -07:00
Lance Stout
1ad171dfe5 Fix issue with setting subelements values with default langs. 2012-06-20 23:19:52 -07:00
Lance Stout
2a78570d65 Fix setting IPv6 default configuration option. 2012-06-20 22:21:34 -07:00
Lance Stout
7a112f2523 Bump version to 1.1.6 2012-06-20 21:08:43 -07:00
Lance Stout
e86444e5fb Make the use of IPv6 configurable.
Set self.use_ipv6 = False before connecting.

Fixes issue #175
2012-06-20 19:39:24 -07:00
Lance Stout
36c11ad9de Ordering fixes for Python3.3 2012-06-19 18:19:44 -07:00
Lance Stout
019a4b20ae Fix assigning values to error stanzas.
The new data interfaces were deleting the actual error conditions if
they were set afterward with falsy data.
2012-06-19 16:21:34 -07:00
Lance Stout
433ee08687 Allow message and presence stanzas to be embedded as substanzas. 2012-06-19 16:20:54 -07:00
Lance Stout
7858d969d8 Remove usage of deprecated getchildren() method. 2012-06-19 09:47:31 -07:00
Lance Stout
8119551049 Don't compare against booleans using ==. 2012-06-19 01:38:36 -07:00
Lance Stout
061489f03a Limit except clause to just ImportErrors when loading plugins. 2012-06-19 01:38:12 -07:00
Lance Stout
d92aa05b5c PEP8 formatting updates. 2012-06-19 01:29:48 -07:00
Lance Stout
f7a74d960e Simplify send_presence_subscription() 2012-06-19 00:06:31 -07:00
Lance Stout
95a0e51b41 Add example for dealing with GTalk custom domain certificates. 2012-06-19 00:02:36 -07:00
Lance Stout
110e45e187 Add examples for using IBB. 2012-06-19 00:02:17 -07:00
Lance Stout
534aaf2b2a Properly handle certs with no extensions. 2012-06-19 00:01:02 -07:00
Lance Stout
4cc20fdd05 Use plugin_multi_attrib values to make vcards nicer. 2012-06-18 23:19:38 -07:00
Lance Stout
f3fae192a8 Fix plugin_multi_attrib value for avatar pointers. 2012-06-18 23:05:02 -07:00
Paulo Freitas
7d59a8a0ad Fixed typo in _handle_get_vcard() 2012-06-18 22:54:30 -07:00
Lance Stout
8da387a38a Add support for error conditions that include data. 2012-06-18 22:19:04 -07:00
Lance Stout
ff6fc44215 Simplify tracking last sent presence using outgoing filters. 2012-06-18 22:15:21 -07:00
Lance Stout
62391a895a Update plugin list, fix syntax error. 2012-06-18 22:08:38 -07:00
Lance Stout
9bcdd7d18f Add initial support for XEPS 222 and 223. 2012-06-18 22:08:38 -07:00
Lance Stout
5c4f7bfe8b Initial support for XEP-0258 2012-06-18 22:07:39 -07:00
Lance Stout
0b7f134021 Add initial XEP-0084 support.
It does not auto-retrieve and store avatars yet, but everything is there
to do so.
2012-06-18 22:07:17 -07:00
Lance Stout
378a42889f Simplify and update XEP-0033 to latest plugin format. 2012-06-18 22:03:03 -07:00
Lance Stout
f824950552 Enable using xml:lang with normal interfaces.
Using the special language value '*' will return a dictionary of all
such elements keyed by language.

    >>> msg = Message()
    >>> msg['body'] = 'Hi!'
    >>> msg['body|sv'] = 'Hej!'
    >>> print(msg)
    '<message xmlns="jabber:client">
      <body>Hi!</body>
      <body xml:lang="sv">Hej!</body>
    </message>'
    >>> print(msg['body|*'])
    OrderedDict(
        ('', 'Hi!'),
        ('sv', 'Hej!'))

Remaining items:

- Stanza path matching does not support language specifiers for normal
  interfaces, only for plugins.
2012-06-18 22:00:33 -07:00
Lance Stout
3d2d11f169 Update stream features stanza to work with new plugin keys. 2012-06-18 22:00:33 -07:00
Lance Stout
181aea737d Add initial support for xml:lang for streams and stanza plugins.
Remaining items are suitable default actions for language supporting
interfaces.
2012-06-18 22:00:33 -07:00
Lance Stout
ee702f4071 Bump version to 1.1.5 2012-06-15 15:36:01 -07:00
Lance Stout
a08c2161a7 Ensure that ssl_invalid_cert returns PEM formatted certifcate data. 2012-06-15 15:29:53 -07:00
Lance Stout
0e36a01354 Bump version to 1.1.4 2012-06-13 09:17:08 -07:00
Lance Stout
c39ad7dfbb Prevent duplicate certificate expiration timers. 2012-06-13 09:13:33 -07:00
Lance Stout
b92ae706e9 Fix loading cached disco identity data. 2012-06-13 09:13:13 -07:00
Lance Stout
6997261c6b Bump version for 1.1.3 2012-06-09 11:32:03 -07:00
Lance Stout
6cfb5cb14c Add extra check for the cert in the expiration handler. 2012-06-09 11:01:45 -07:00
Lance Stout
8567d6034f Use False for use_tls for components.
A log message is shown for those who try to set it to True.

Fixes issue #171
2012-06-09 11:01:35 -07:00
Lance Stout
e06368f8cd Default use_tls to False for components.
Issue #171
2012-06-09 11:01:21 -07:00
Lance Stout
4b37a4706f Fix SSL handshake handling when not using legacy SSL.
Fixes issue #172
2012-06-09 11:01:11 -07:00
Lance Stout
7b1564947d Ensure that all SSL cert error handling is overridable using event handlers.
Relevant events:

    ssl_invalid_cert
    ssl_invalid_chain
    ssl_expired_cert
2012-06-09 11:00:55 -07:00
Lance Stout
f5652a667b Add 'presence' event, raised for all incoming presence stanzas. 2012-06-06 16:10:25 -07:00
Lance Stout
3b2c865a58 Bump version to 1.1.2 2012-06-06 12:26:15 -07:00
Lance Stout
db0e683d01 Don't request registration forms unless the register event is handled.
Some servers end the stream if registration can not be completed
in-band, which means always requesting the form can prevent regular
login.
2012-06-06 12:23:40 -07:00
117 changed files with 3385 additions and 819 deletions

View File

@@ -45,7 +45,7 @@ The latest source code for SleekXMPP may be found on `Github
``develop`` branch.
**Latest Release**
- `1.1.1 <http://github.com/fritzy/SleekXMPP/zipball/1.1.1>`_
- `1.1.9 <http://github.com/fritzy/SleekXMPP/zipball/1.1.9>`_
**Develop Releases**
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
@@ -74,6 +74,7 @@ help with SleekXMPP.
**Chat**
`sleek@conference.jabber.org <xmpp:sleek@conference.jabber.org?join>`_
Documentation and Testing
-------------------------
Documentation can be found both inline in the code, and as a Sphinx project in ``/docs``.

View File

@@ -122,6 +122,19 @@ if __name__ == '__main__':
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping
# If you are connecting to Facebook and wish to use the
# X-FACEBOOK-PLATFORM authentication mechanism, you will need
# your API key and an access token. Then you'll set:
# xmpp.credentials['api_key'] = 'THE_API_KEY'
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
# If you are connecting to MSN, then you will need an
# access token, and it does not matter what JID you
# specify other than that the domain is 'messenger.live.com',
# so '_@messenger.live.com' will work. You can specify
# the access token as so:
# xmpp.credentials['access_token'] = 'THE_ACCESS_TOKEN'
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3

165
examples/gtalk_custom_domain.py Executable file
View File

@@ -0,0 +1,165 @@
#!/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 getpass
from optparse import OptionParser
import sleekxmpp
import ssl
from sleekxmpp.xmlstream import cert
# 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 GTalkBot(sleekxmpp.ClientXMPP):
"""
A demonstration of using SleekXMPP with accounts from a Google Apps
account with a custom domain, because it requires custom certificate
validation.
"""
def __init__(self, jid, password):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
# The session_start event will be triggered when
# the bot 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 initialize
# our roster.
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)
# Using a Google Apps custom domain, the certificate
# does not contain the custom domain, just the GTalk
# server name. So we will need to process invalid
# certifcates ourselves and check that it really
# is from Google.
self.add_event_handler("ssl_invalid_cert", self.invalid_cert)
def invalid_cert(self, pem_cert):
der_cert = ssl.PEM_cert_to_DER_cert(pem_cert)
try:
cert.verify('talk.google.com', der_cert)
logging.debug("CERT: Found GTalk certificate")
except cert.CertificateError as err:
log.error(err.message)
self.disconnect(send_close=False)
def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an initial
presence stanza.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.send_presence()
self.get_roster()
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.
Arguments:
msg -- The received message stanza. See the documentation
for stanza objects and the Message stanza to see
how it may be used.
"""
if msg['type'] in ('chat', 'normal'):
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")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
# Setup the GTalkBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = GTalkBot(opts.jid, opts.password)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
# If you do not have the dnspython library installed, you will need
# to manually specify the name of the server if it does not match
# the one in the JID. For example, to use Google Talk you would
# need to use:
#
# if xmpp.connect(('talk.google.com', 5222)):
# ...
xmpp.process(block=True)
print("Done")
else:
print("Unable to connect.")

View File

@@ -0,0 +1,149 @@
#!/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 getpass
from optparse import OptionParser
import sleekxmpp
# 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 IBBReceiver(sleekxmpp.ClientXMPP):
"""
A basic example of creating and using an in-band bytestream.
"""
def __init__(self, jid, password):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.register_plugin('xep_0030') # Service Discovery
self.register_plugin('xep_0047', {
'accept_stream': self.accept_stream
}) # In-band Bytestreams
# The session_start event will be triggered when
# the bot 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 initialize
# our roster.
self.add_event_handler("session_start", self.start)
self.add_event_handler("ibb_stream_start", self.stream_opened)
self.add_event_handler("ibb_stream_data", self.stream_data)
def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an initial
presence stanza.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.send_presence()
self.get_roster()
def accept_stream(self, iq):
"""
Check that it is ok to accept a stream request.
Controlling stream acceptance can be done via either:
- setting 'auto_accept' to False in the plugin
configuration. The default is True.
- setting 'accept_stream' to a function which accepts
an Iq stanza as its argument, like this one.
The accept_stream function will be used if it exists, and the
auto_accept value will be used otherwise.
"""
return True
def stream_opened(self, stream):
# NOTE: IBB streams are bi-directional, so the original sender is
# now the opened stream's receiver.
print('Stream opened: %s from ' % (stream.sid, stream.receiver))
# You could run a loop reading from the stream using stream.recv(),
# or use the ibb_stream_data event.
def stream_data(self, event):
print(event['data'])
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")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
xmpp = IBBReceiver(opts.jid, opts.password)
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
# If you do not have the dnspython library installed, you will need
# to manually specify the name of the server if it does not match
# the one in the JID. For example, to use Google Talk you would
# need to use:
#
# if xmpp.connect(('talk.google.com', 5222)):
# ...
xmpp.process(block=True)
print("Done")
else:
print("Unable to connect.")

View File

@@ -0,0 +1,145 @@
#!/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 getpass
from optparse import OptionParser
import sleekxmpp
# 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 IBBSender(sleekxmpp.ClientXMPP):
"""
A basic example of creating and using an in-band bytestream.
"""
def __init__(self, jid, password, receiver, filename):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.receiver = receiver
self.filename = filename
# The session_start event will be triggered when
# the bot 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 initialize
# our roster.
self.add_event_handler("session_start", self.start)
def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an initial
presence stanza.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.send_presence()
self.get_roster()
# For the purpose of demonstration, we'll set a very small block
# size. The default block size is 4096. We'll also use a window
# allowing sending multiple blocks at a time; in this case, three
# block transfers may be in progress at any time.
stream = self['xep_0047'].open_stream(self.receiver)
with open(self.filename) as f:
data = f.read()
stream.sendall(data)
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("-r", "--receiver", dest="receiver",
help="JID to use")
optp.add_option("-f", "--file", dest="filename",
help="JID to use")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
if opts.receiver is None:
opts.receiver = raw_input("Receiver: ")
if opts.filename is None:
opts.filename = raw_input("File path: ")
# Setup the EchoBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = IBBSender(opts.jid, opts.password, opts.receiver, opts.filename)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0047') # In-band Bytestreams
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
# If you do not have the dnspython library installed, you will need
# to manually specify the name of the server if it does not match
# the one in the JID. For example, to use Google Talk you would
# need to use:
#
# if xmpp.connect(('talk.google.com', 5222)):
# ...
xmpp.process(block=True)
print("Done")
else:
print("Unable to connect.")

View File

@@ -61,6 +61,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0027',
'sleekxmpp/plugins/xep_0030',
'sleekxmpp/plugins/xep_0030/stanza',
'sleekxmpp/plugins/xep_0033',
'sleekxmpp/plugins/xep_0047',
'sleekxmpp/plugins/xep_0050',
'sleekxmpp/plugins/xep_0054',
@@ -71,6 +72,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0077',
'sleekxmpp/plugins/xep_0078',
'sleekxmpp/plugins/xep_0080',
'sleekxmpp/plugins/xep_0084',
'sleekxmpp/plugins/xep_0085',
'sleekxmpp/plugins/xep_0086',
'sleekxmpp/plugins/xep_0092',
@@ -82,13 +84,17 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0153',
'sleekxmpp/plugins/xep_0172',
'sleekxmpp/plugins/xep_0184',
'sleekxmpp/plugins/xep_0186',
'sleekxmpp/plugins/xep_0191',
'sleekxmpp/plugins/xep_0198',
'sleekxmpp/plugins/xep_0199',
'sleekxmpp/plugins/xep_0202',
'sleekxmpp/plugins/xep_0203',
'sleekxmpp/plugins/xep_0221',
'sleekxmpp/plugins/xep_0224',
'sleekxmpp/plugins/xep_0231',
'sleekxmpp/plugins/xep_0249',
'sleekxmpp/plugins/xep_0258',
'sleekxmpp/features',
'sleekxmpp/features/feature_mechanisms',
'sleekxmpp/features/feature_mechanisms/stanza',

View File

@@ -16,24 +16,24 @@ class APIWrapper(object):
elif attr == 'settings':
return self.api.settings[self.name]
elif attr == 'register':
def curried_handler(handler, op, jid=None, node=None, default=False):
def partial(handler, op, jid=None, node=None, default=False):
register = getattr(self.api, attr)
return register(handler, self.name, op, jid, node, default)
return curried_handler
return partial
elif attr == 'register_default':
def curried_handler(handler, op, jid=None, node=None):
def partial(handler, op, jid=None, node=None):
return getattr(self.api, attr)(handler, self.name, op)
return curried_handler
return partial
elif attr in ('run', 'restore_default', 'unregister'):
def curried_handler(*args, **kwargs):
def partial(*args, **kwargs):
return getattr(self.api, attr)(self.name, *args, **kwargs)
return curried_handler
return partial
return None
def __getitem__(self, attr):
def curried_handler(jid=None, node=None, ifrom=None, args=None):
def partial(jid=None, node=None, ifrom=None, args=None):
return self.api.run(self.name, attr, jid, node, ifrom, args)
return curried_handler
return partial
class APIRegistry(object):
@@ -42,7 +42,7 @@ class APIRegistry(object):
self._handlers = {}
self._handler_defaults = {}
self.xmpp = xmpp
self.settings = {}
self.settings = {}
def _setup(self, ctype, op):
"""Initialize the API callback dictionaries.
@@ -99,7 +99,7 @@ class APIRegistry(object):
"""
self._setup(ctype, op)
if jid in (None, ''):
if not jid:
jid = self.xmpp.boundjid
if jid and not isinstance(jid, JID):
jid = JID(jid)
@@ -113,7 +113,7 @@ class APIRegistry(object):
else:
jid = jid.full
else:
if self.settings[ctype].get('client_bare', True):
if self.settings[ctype].get('client_bare', False):
jid = jid.bare
else:
jid = jid.full
@@ -138,8 +138,8 @@ class APIRegistry(object):
"""Register an API callback, with JID+node specificity.
The API callback can later be executed based on the
specificity of the provided JID+node combination.
specificity of the provided JID+node combination.
See :meth:`~ApiRegistry.run` for more details.
:param string ctype: The name of the API to use.

View File

@@ -16,6 +16,7 @@ from __future__ import with_statement, unicode_literals
import sys
import logging
import threading
import sleekxmpp
from sleekxmpp import plugins, features, roster
@@ -31,6 +32,7 @@ from sleekxmpp.xmlstream import XMLStream, JID
from sleekxmpp.xmlstream import ET, register_stanza_plugin
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.stanzabase import XML_NS
from sleekxmpp.features import *
from sleekxmpp.plugins import PluginManager, register_plugin, load_plugin
@@ -66,10 +68,13 @@ class BaseXMPP(XMLStream):
#: An identifier for the stream as given by the server.
self.stream_id = None
#: The JabberID (JID) used by this connection.
#: The JabberID (JID) used by this connection.
self.boundjid = JID(jid)
self._expected_server_name = self.boundjid.host
self.session_bind_event = threading.Event()
#: A dictionary mapping plugin names to plugins.
self.plugin = PluginManager(self)
@@ -86,13 +91,13 @@ class BaseXMPP(XMLStream):
#: owner JIDs, as in the case for components. For clients
#: which only have a single JID, see :attr:`client_roster`.
self.roster = roster.Roster(self)
self.roster.add(self.boundjid.bare)
self.roster.add(self.boundjid)
#: The single roster for the bound JID. This is the
#: equivalent of::
#:
#: self.roster[self.boundjid.bare]
self.client_roster = self.roster[self.boundjid.bare]
self.client_roster = self.roster[self.boundjid]
#: The distinction between clients and components can be
#: important, primarily for choosing how to handle the
@@ -102,7 +107,7 @@ class BaseXMPP(XMLStream):
#: The API registry is a way to process callbacks based on
#: JID+node combinations. Each callback in the registry is
#: marked with:
#:
#:
#: - An API name, e.g. xep_0030
#: - The name of an action, e.g. get_info
#: - The JID that will be affected
@@ -133,6 +138,7 @@ class BaseXMPP(XMLStream):
Callback('Presence',
MatchXPath("{%s}presence" % self.default_ns),
self._handle_presence))
self.register_handler(
Callback('Stream Error',
MatchXPath("{%s}error" % self.stream_ns),
@@ -180,6 +186,8 @@ class BaseXMPP(XMLStream):
:param xml: The incoming stream's root element.
"""
self.stream_id = xml.get('id', '')
self.stream_version = xml.get('version', '')
self.peer_default_lang = xml.get('{%s}lang' % XML_NS, None)
def process(self, *args, **kwargs):
"""Initialize plugins and begin processing the XML stream.
@@ -199,7 +207,7 @@ class BaseXMPP(XMLStream):
Defaults to ``True``. This does **not** mean that no
threads are used at all if ``threaded=False``.
Regardless of these threading options, these threads will
Regardless of these threading options, these threads will
always exist:
- The event queue processor
@@ -272,7 +280,9 @@ class BaseXMPP(XMLStream):
def Message(self, *args, **kwargs):
"""Create a Message stanza associated with this stream."""
return Message(self, *args, **kwargs)
msg = Message(self, *args, **kwargs)
msg['lang'] = self.default_lang
return msg
def Iq(self, *args, **kwargs):
"""Create an Iq stanza associated with this stream."""
@@ -280,18 +290,20 @@ class BaseXMPP(XMLStream):
def Presence(self, *args, **kwargs):
"""Create a Presence stanza associated with this stream."""
return Presence(self, *args, **kwargs)
pres = Presence(self, *args, **kwargs)
pres['lang'] = self.default_lang
return pres
def make_iq(self, id=0, ifrom=None, ito=None, itype=None, iquery=None):
"""Create a new Iq stanza with a given Id and from JID.
:param id: An ideally unique ID value for this stanza thread.
Defaults to 0.
:param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
:param ifrom: The from :class:`~sleekxmpp.xmlstream.jid.JID`
to use for this stanza.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
for this stanza.
:param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
:param itype: The :class:`~sleekxmpp.stanza.iq.Iq`'s type,
one of: ``'get'``, ``'set'``, ``'result'``,
or ``'error'``.
:param iquery: Optional namespace for adding a query element.
@@ -329,7 +341,7 @@ class BaseXMPP(XMLStream):
def make_iq_result(self, id=None, ito=None, ifrom=None, iq=None):
"""
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type
``'result'`` with the given ID value.
:param id: An ideally unique ID value. May use :meth:`new_id()`.
@@ -359,10 +371,10 @@ class BaseXMPP(XMLStream):
Optionally, a substanza may be given to use as the
stanza's payload.
:param sub: Either an
:param sub: Either an
:class:`~sleekxmpp.xmlstream.stanzabase.ElementBase`
stanza object or an
:class:`~xml.etree.ElementTree.Element` XML object
:class:`~xml.etree.ElementTree.Element` XML object
to use as the :class:`~sleekxmpp.stanza.iq.Iq`'s payload.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
for this stanza.
@@ -389,9 +401,9 @@ class BaseXMPP(XMLStream):
Create an :class:`~sleekxmpp.stanza.iq.Iq` stanza of type ``'error'``.
:param id: An ideally unique ID value. May use :meth:`new_id()`.
:param type: The type of the error, such as ``'cancel'`` or
:param type: The type of the error, such as ``'cancel'`` or
``'modify'``. Defaults to ``'cancel'``.
:param condition: The error condition. Defaults to
:param condition: The error condition. Defaults to
``'feature-not-implemented'``.
:param text: A message describing the cause of the error.
:param ito: The destination :class:`~sleekxmpp.xmlstream.jid.JID`
@@ -415,7 +427,7 @@ class BaseXMPP(XMLStream):
def make_iq_query(self, iq=None, xmlns='', ito=None, ifrom=None):
"""
Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
Create or modify an :class:`~sleekxmpp.stanza.iq.Iq` stanza
to use the given query namespace.
:param iq: Optionally use an existing stanza instead
@@ -448,7 +460,7 @@ class BaseXMPP(XMLStream):
def make_message(self, mto, mbody=None, msubject=None, mtype=None,
mhtml=None, mfrom=None, mnick=None):
"""
Create and initialize a new
Create and initialize a new
:class:`~sleekxmpp.stanza.message.Message` stanza.
:param mto: The recipient of the message.
@@ -474,7 +486,7 @@ class BaseXMPP(XMLStream):
def make_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, ptype=None, pfrom=None, pnick=None):
"""
Create and initialize a new
Create and initialize a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza.
:param pshow: The presence's show value.
@@ -498,7 +510,7 @@ class BaseXMPP(XMLStream):
def send_message(self, mto, mbody, msubject=None, mtype=None,
mhtml=None, mfrom=None, mnick=None):
"""
Create, initialize, and send a new
Create, initialize, and send a new
:class:`~sleekxmpp.stanza.message.Message` stanza.
:param mto: The recipient of the message.
@@ -518,7 +530,7 @@ class BaseXMPP(XMLStream):
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, pfrom=None, ptype=None, pnick=None):
"""
Create, initialize, and send a new
Create, initialize, and send a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza.
:param pshow: The presence's show value.
@@ -529,23 +541,13 @@ class BaseXMPP(XMLStream):
:param pfrom: The sender of the presence.
:param pnick: Optional nickname of the presence's sender.
"""
# Python2.6 chokes on Unicode strings for dict keys.
args = {str('pto'): pto,
str('ptype'): ptype,
str('pshow'): pshow,
str('pstatus'): pstatus,
str('ppriority'): ppriority,
str('pnick'): pnick}
if self.is_component:
self.roster[pfrom].send_presence(**args)
else:
self.client_roster.send_presence(**args)
self.make_presence(pshow, pstatus, ppriority, pto,
ptype, pfrom, pnick).send()
def send_presence_subscription(self, pto, pfrom=None,
ptype='subscribe', pnick=None):
"""
Create, initialize, and send a new
Create, initialize, and send a new
:class:`~sleekxmpp.stanza.presence.Presence` stanza of
type ``'subscribe'``.
@@ -554,14 +556,10 @@ class BaseXMPP(XMLStream):
:param ptype: The type of presence, such as ``'subscribe'``.
:param pnick: Optional nickname of the presence's sender.
"""
presence = self.makePresence(ptype=ptype,
pfrom=pfrom,
pto=self.getjidbare(pto))
if pnick:
nick = ET.Element('{http://jabber.org/protocol/nick}nick')
nick.text = pnick
presence.append(nick)
presence.send()
self.make_presence(ptype=ptype,
pfrom=pfrom,
pto=JID(pto).bare,
pnick=pnick).send()
@property
def jid(self):
@@ -661,27 +659,45 @@ class BaseXMPP(XMLStream):
def _handle_disconnected(self, event):
"""When disconnected, reset the roster"""
self.roster.reset()
self.session_bind_event.clear()
def _handle_stream_error(self, error):
self.event('stream_error', error)
if error['condition'] == 'see-other-host':
other_host = error['see_other_host']
host = other_host
port = 5222
if '[' in other_host and ']' in other_host:
host = other_host.split(']')[0][1:]
elif ':' in other_host:
host = other_host.split(':')[0]
port_sec = other_host.split(']')[-1]
if ':' in port_sec:
port = int(port_sec.split(':')[1])
self.address = (host, port)
self.default_domain = host
self.dns_records = None
self.reconnect_delay = None
self.reconnect()
def _handle_message(self, msg):
"""Process incoming message stanzas."""
if not self.is_component and not msg['to'].bare:
msg['to'] = self.boundjid
self.event('message', msg)
def _handle_available(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_available(presence)
def _handle_available(self, pres):
self.roster[pres['to']][pres['from']].handle_available(pres)
def _handle_unavailable(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_unavailable(presence)
def _handle_unavailable(self, pres):
self.roster[pres['to']][pres['from']].handle_unavailable(pres)
def _handle_new_subscription(self, stanza):
def _handle_new_subscription(self, pres):
"""Attempt to automatically handle subscription requests.
Subscriptions will be approved if the request is from
@@ -693,8 +709,8 @@ class BaseXMPP(XMLStream):
If a subscription is accepted, a request for a mutual
subscription will be sent if :attr:`auto_subscribe` is ``True``.
"""
roster = self.roster[stanza['to'].bare]
item = self.roster[stanza['to'].bare][stanza['from'].bare]
roster = self.roster[pres['to']]
item = self.roster[pres['to']][pres['from']]
if item['whitelisted']:
item.authorize()
elif roster.auto_authorize:
@@ -704,30 +720,20 @@ class BaseXMPP(XMLStream):
elif roster.auto_authorize == False:
item.unauthorize()
def _handle_removed_subscription(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].unauthorize()
def _handle_removed_subscription(self, pres):
self.roster[pres['to']][pres['from']].handle_unauthorize(pres)
def _handle_subscribe(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_subscribe(presence)
def _handle_subscribe(self, pres):
self.roster[pres['to']][pres['from']].handle_subscribe(pres)
def _handle_subscribed(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_subscribed(presence)
def _handle_subscribed(self, pres):
self.roster[pres['to']][pres['from']].handle_subscribed(pres)
def _handle_unsubscribe(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_unsubscribe(presence)
def _handle_unsubscribe(self, pres):
self.roster[pres['to']][pres['from']].handle_unsubscribe(pres)
def _handle_unsubscribed(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_unsubscribed(presence)
def _handle_unsubscribed(self, pres):
self.roster[pres['to']][pres['from']].handle_unsubscribed(pres)
def _handle_presence(self, presence):
"""Process incoming presence stanzas.
@@ -737,7 +743,8 @@ class BaseXMPP(XMLStream):
if not self.is_component and not presence['to'].bare:
presence['to'] = self.boundjid
self.event("presence_%s" % presence['type'], presence)
self.event('presence', presence)
self.event('presence_%s' % presence['type'], presence)
# Check for changes in subscription state.
if presence['type'] in ('subscribe', 'subscribed',
@@ -749,7 +756,7 @@ class BaseXMPP(XMLStream):
return
def exception(self, exception):
"""Process any uncaught exceptions, notably
"""Process any uncaught exceptions, notably
:class:`~sleekxmpp.exceptions.IqError` and
:class:`~sleekxmpp.exceptions.IqTimeout` exceptions.

View File

@@ -54,14 +54,14 @@ class ClientXMPP(BaseXMPP):
:param password: The password for the XMPP user account.
:param ssl: **Deprecated.**
:param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
:param escape_quotes: **Deprecated.**
"""
def __init__(self, jid, password, ssl=False, plugin_config={},
plugin_whitelist=[], escape_quotes=True, sasl_mech=None):
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
escape_quotes=True, sasl_mech=None, lang='en'):
BaseXMPP.__init__(self, jid, 'jabber:client')
self.set_jid(jid)
@@ -69,15 +69,18 @@ class ClientXMPP(BaseXMPP):
self.plugin_config = plugin_config
self.plugin_whitelist = plugin_whitelist
self.default_port = 5222
self.default_lang = lang
self.credentials = {}
self.password = password
self.stream_header = "<stream:stream to='%s' %s %s version='1.0'>" % (
self.stream_header = "<stream:stream to='%s' %s %s %s %s>" % (
self.boundjid.host,
"xmlns:stream='%s'" % self.stream_ns,
"xmlns='%s'" % self.default_ns)
"xmlns='%s'" % self.default_ns,
"xml:lang='%s'" % self.default_lang,
"version='1.0'")
self.stream_footer = "</stream:stream>"
self.features = set()
@@ -170,6 +173,12 @@ class ClientXMPP(BaseXMPP):
self._stream_feature_order.append((order, name))
self._stream_feature_order.sort()
def unregister_feature(self, name, order):
if name in self._stream_feature_handlers:
del self._stream_feature_handlers[name]
self._stream_feature_order.remove((order, name))
self._stream_feature_order.sort()
def update_roster(self, jid, name=None, subscription=None, groups=[],
block=True, timeout=None, callback=None):
"""Add or change a roster item.
@@ -186,7 +195,7 @@ class ClientXMPP(BaseXMPP):
occurs. Defaults to ``True``.
:param timeout: The length of time (in seconds) to wait
for a response before continuing if blocking
is used. Defaults to
is used. Defaults to
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
:param callback: Optional reference to a stream handler function.
Will be executed when the roster is received.
@@ -197,7 +206,7 @@ class ClientXMPP(BaseXMPP):
def del_roster_item(self, jid):
"""Remove an item from the roster.
This is done by setting its subscription status to ``'remove'``.
:param jid: The JID of the item to remove.
@@ -212,7 +221,7 @@ class ClientXMPP(BaseXMPP):
Defaults to ``True``.
:param timeout: The length of time (in seconds) to wait for a response
before continuing if blocking is used.
Defaults to
Defaults to
:attr:`~sleekxmpp.xmlstream.xmlstream.XMLStream.response_timeout`.
:param callback: Optional reference to a stream handler function. Will
be executed when the roster is received.
@@ -230,7 +239,7 @@ class ClientXMPP(BaseXMPP):
response = iq.send(block, timeout, callback)
self.event('roster_received', response)
if block:
if block:
self._handle_roster(response)
return response
@@ -267,8 +276,9 @@ class ClientXMPP(BaseXMPP):
roster = self.client_roster
if iq['roster']['ver']:
roster.version = iq['roster']['ver']
for jid in iq['roster']['items']:
item = iq['roster']['items'][jid]
items = iq['roster']['items']
for jid in items:
item = items[jid]
roster[jid]['name'] = item['name']
roster[jid]['groups'] = item['groups']
roster[jid]['from'] = item['subscription'] in ['from', 'both']
@@ -276,7 +286,7 @@ class ClientXMPP(BaseXMPP):
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
roster[jid].save(remove=(item['subscription'] == 'remove'))
self.event("roster_update", iq)
if iq['type'] == 'set':
resp = self.Iq(stype='result',

View File

@@ -40,8 +40,8 @@ class ComponentXMPP(BaseXMPP):
:param host: The server accepting the component.
:param port: The port used to connect to the server.
:param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
:param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
should be used instead of the standard
@@ -78,8 +78,8 @@ class ComponentXMPP(BaseXMPP):
self.add_event_handler('presence_probe',
self._handle_probe)
def connect(self, host=None, port=None, use_ssl=False,
use_tls=True, reattempt=True):
def connect(self, host=None, port=None, use_ssl=False,
use_tls=False, reattempt=True):
"""Connect to the server.
Setting ``reattempt`` to ``True`` will cause connection attempts to
@@ -104,10 +104,13 @@ class ComponentXMPP(BaseXMPP):
self.server_name = self.boundjid.host
if use_tls:
log.info("XEP-0114 components can not use TLS")
log.debug("Connecting to %s:%s", host, port)
return XMLStream.connect(self, host=host, port=port,
use_ssl=use_ssl,
use_tls=use_tls,
use_tls=False,
reattempt=reattempt)
def incoming_filter(self, xml):
@@ -153,10 +156,10 @@ class ComponentXMPP(BaseXMPP):
:param xml: The reply handshake stanza.
"""
self.session_bind_event.set()
self.session_started_event.set()
self.event("session_bind", self.xmpp.boundjid, direct=True)
self.event("session_start")
def _handle_probe(self, presence):
pto = presence['to'].bare
pfrom = presence['from'].bare
self.roster[pto][pfrom].handle_probe(presence)
def _handle_probe(self, pres):
self.roster[pres['to']][pres['from']].handle_probe(pres)

View File

@@ -69,10 +69,11 @@ class IqTimeout(XMPPError):
condition='remote-server-timeout',
etype='cancel')
#: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
#: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
#: did not arrive before the timeout expired.
self.iq = iq
class IqError(XMPPError):
"""

View File

@@ -7,9 +7,9 @@
"""
__all__ = [
'feature_starttls',
'feature_mechanisms',
'feature_bind',
'feature_starttls',
'feature_mechanisms',
'feature_bind',
'feature_session',
'feature_rosterver'
]

View File

@@ -51,6 +51,7 @@ class FeatureBind(BasePlugin):
self.xmpp.set_jid(response['bind']['jid'])
self.xmpp.bound = True
self.xmpp.event('session_bind', self.xmpp.boundjid, direct=True)
self.xmpp.session_bind_event.set()
self.xmpp.features.add('bind')

View File

@@ -23,7 +23,7 @@ class Auth(StanzaBase):
interfaces = set(('mechanism', 'value'))
plugin_attrib = name
#: Some SASL mechs require sending values as is,
#: Some SASL mechs require sending values as is,
#: without converting base64.
plain_mechs = set(['X-MESSENGER-OAUTH2'])

View File

@@ -47,7 +47,7 @@ class Failure(StanzaBase):
def get_condition(self):
"""Return the condition element's name."""
for child in self.xml.getchildren():
for child in self.xml:
if "{%s}" % self.namespace in child.tag:
cond = child.tag.split('}', 1)[-1]
if cond in self.conditions:
@@ -68,7 +68,7 @@ class Failure(StanzaBase):
def del_condition(self):
"""Remove the condition element."""
for child in self.xml.getchildren():
for child in self.xml:
if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:

View File

@@ -32,6 +32,7 @@ __all__ = [
# 'xep_0078', # Non-SASL auth. Don't automatically load
'xep_0080', # User Location
'xep_0082', # XMPP Date and Time Profiles
'xep_0084', # User Avatar
'xep_0085', # Chat State Notifications
'xep_0086', # Legacy Error Codes
'xep_0092', # Software Version
@@ -44,11 +45,20 @@ __all__ = [
'xep_0163', # Personal Eventing Protocol
'xep_0172', # User Nickname
'xep_0184', # Message Receipts
'xep_0186', # Invisible Command
'xep_0191', # Blocking Command
'xep_0198', # Stream Management
'xep_0199', # Ping
'xep_0202', # Entity Time
'xep_0203', # Delayed Delivery
'xep_0221', # Data Forms Media Element
'xep_0222', # Persistent Storage of Public Data via Pubsub
'xep_0223', # Persistent Storage of Private Data via Pubsub
'xep_0224', # Attention
'xep_0231', # Bits of Binary
'xep_0249', # Direct MUC Invitations
'xep_0256', # Last Activity in Presence
'xep_0258', # Security Labels in XMPP
'xep_0270', # XMPP Compliance Suites 2010
'xep_0302', # XMPP Compliance Suites 2012
]

View File

@@ -31,10 +31,10 @@ log = logging.getLogger(__name__)
PLUGIN_REGISTRY = {}
#: In order to do cascading plugin disabling, reverse dependencies
#: must be tracked.
#: must be tracked.
PLUGIN_DEPENDENTS = {}
#: Only allow one thread to manipulate the plugin registry at a time.
#: Only allow one thread to manipulate the plugin registry at a time.
REGISTRY_LOCK = threading.RLock()
@@ -75,7 +75,7 @@ def load_plugin(name, module=None):
plugins are in packages matching their name,
even though the plugin class name does not
have to match.
:param str module: The name of the base module to search
:param str module: The name of the base module to search
for the plugin.
"""
try:
@@ -84,7 +84,7 @@ def load_plugin(name, module=None):
module = 'sleekxmpp.plugins.%s' % name
__import__(module)
mod = sys.modules[module]
except:
except ImportError:
module = 'sleekxmpp.features.%s' % name
__import__(module)
mod = sys.modules[module]
@@ -103,11 +103,11 @@ def load_plugin(name, module=None):
# we can work around dependency issues.
plugin.old_style = True
register_plugin(plugin, name)
except:
except ImportError:
log.exception("Unable to load plugin: %s", name)
class PluginManager(object):
class PluginManager(object):
def __init__(self, xmpp, config=None):
#: We will track all enabled plugins in a set so that we
#: can enable plugins in batches and pull in dependencies
@@ -167,8 +167,7 @@ class PluginManager(object):
self._plugins[name] = plugin
for dep in plugin.dependencies:
self.enable(dep, enabled=enabled)
plugin.plugin_init()
log.debug("Loaded Plugin: %s", plugin.description)
plugin._init()
if top_level:
for name in enabled:
@@ -181,7 +180,7 @@ class PluginManager(object):
def enable_all(self, names=None, config=None):
"""Enable all registered plugins.
:param list names: A list of plugin names to enable. If
none are provided, all registered plugins
will be enabled.
@@ -229,7 +228,7 @@ class PluginManager(object):
raise PluginNotFound(name)
for dep in PLUGIN_DEPENDENTS[name]:
self.disable(dep, _disabled)
plugin.plugin_end()
plugin._end()
if name in self._enabled:
self._enabled.remove(name)
del self._plugins[name]
@@ -282,6 +281,28 @@ class BasePlugin(object):
#: configuration settings will be provided as a dictionary.
self.config = config if config is not None else {}
def _init(self):
"""Initialize plugin state, such as registering event handlers.
Also sets up required event handlers.
"""
if self.xmpp is not None:
self.xmpp.add_event_handler('session_bind', self.session_bind)
if self.xmpp.session_bind_event.is_set():
self.session_bind(self.xmpp.boundjid.full)
self.plugin_init()
log.debug('Loaded Plugin: %s', self.description)
def _end(self):
"""Cleanup plugin state, and prepare for plugin removal.
Also removes required event handlers.
"""
if self.xmpp is not None:
self.xmpp.del_event_handler('session_bind', self.session_bind)
self.plugin_end()
log.debug('Disabled Plugin: %s' % self.description)
def plugin_init(self):
"""Initialize plugin state, such as registering event handlers."""
pass
@@ -290,9 +311,13 @@ class BasePlugin(object):
"""Cleanup plugin state, and prepare for plugin removal."""
pass
def session_bind(self, jid):
"""Initialize plugin state based on the bound JID."""
pass
def post_init(self):
"""Initialize any cross-plugin state.
Only needed if the plugin has circular dependencies.
"""
pass

View File

@@ -27,7 +27,7 @@ class XEP_0004(BasePlugin):
stanza = stanza
def plugin_init(self):
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('Data Form',
StanzaPath('message/form'),
self.handle_form))
@@ -36,6 +36,11 @@ class XEP_0004(BasePlugin):
register_stanza_plugin(Form, FormField, iterable=True)
register_stanza_plugin(Message, Form)
def plugin_end(self):
self.xmpp.remove_handler('Data Form')
self.xmpp['xep_0030'].del_feature(feature='jabber:x:data')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('jabber:x:data')
def make_form(self, ftype='form', title='', instructions=''):

View File

@@ -37,13 +37,11 @@ class XEP_0012(BasePlugin):
self._last_activities = {}
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('Last Activity',
StanzaPath('iq@type=get/last_activity'),
self._handle_get_last_activity))
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:last')
self.api.register(self._default_get_last_activity,
'get_last_activity',
default=True)
@@ -54,6 +52,13 @@ class XEP_0012(BasePlugin):
'del_last_activity',
default=True)
def plugin_end(self):
self.xmpp.remove_handler('Last Activity')
self.xmpp['xep_0030'].del_feature(feature='jabber:iq:last')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('jabber:iq:last')
def begin_idle(self, jid=None, status=None):
self.set_last_activity(jid, 0, status)

View File

@@ -79,9 +79,17 @@ class XEP_0027(BasePlugin):
StanzaPath('message/encrypted'),
self._handle_encrypted_message))
def plugin_end(self):
self.xmpp.remove_handler('Encrypted Message')
self.xmpp.remove_handler('Signed Presence')
self.xmpp.del_filter('out', self._sign_presence)
self.xmpp.del_event_handler('unverified_signed_presence',
self._handle_unverified_signed_presence)
def _sign_presence(self, stanza):
if isinstance(stanza, Presence):
if stanza['type'] == 'available' or stanza['type'] in Presence.showtypes:
if stanza['type'] == 'available' or \
stanza['type'] in Presence.showtypes:
stanza['signed'] = stanza['status']
return stanza

View File

@@ -51,6 +51,3 @@ class Encrypted(ElementBase):
if self.xml.text:
return xmpp['xep_0027'].decrypt(self.xml.text, parent['to'])
return None

View File

@@ -339,8 +339,8 @@ class XEP_0030(BasePlugin):
if local:
log.debug("Looking up local disco#info data " + \
"for %s, node %s.", jid, node)
info = self.api['get_info'](jid, node,
kwargs.get('ifrom', None),
info = self.api['get_info'](jid, node,
kwargs.get('ifrom', None),
kwargs)
info = self._fix_default_info(info)
return self._wrap(kwargs.get('ifrom', None), jid, info)
@@ -348,8 +348,8 @@ class XEP_0030(BasePlugin):
if cached:
log.debug("Looking up cached disco#info data " + \
"for %s, node %s.", jid, node)
info = self.api['get_cached_info'](jid, node,
kwargs.get('ifrom', None),
info = self.api['get_cached_info'](jid, node,
kwargs.get('ifrom', None),
kwargs)
if info is not None:
return self._wrap(kwargs.get('ifrom', None), jid, info)
@@ -405,8 +405,8 @@ class XEP_0030(BasePlugin):
Otherwise the parameter is ignored.
"""
if local or jid is None:
items = self.api['get_items'](jid, node,
kwargs.get('ifrom', None),
items = self.api['get_items'](jid, node,
kwargs.get('ifrom', None),
kwargs)
return self._wrap(kwargs.get('ifrom', None), jid, items)
@@ -622,11 +622,7 @@ class XEP_0030(BasePlugin):
if iq['type'] == 'get':
log.debug("Received disco info query from " + \
"<%s> to <%s>.", iq['from'], iq['to'])
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
info = self.api['get_info'](jid,
info = self.api['get_info'](iq['to'],
iq['disco_info']['node'],
iq['from'],
iq)
@@ -649,7 +645,7 @@ class XEP_0030(BasePlugin):
ito = iq['to'].full
else:
ito = None
self.api['cache_info'](iq['from'].full,
self.api['cache_info'](iq['from'],
iq['disco_info']['node'],
ito,
iq)
@@ -667,13 +663,9 @@ class XEP_0030(BasePlugin):
if iq['type'] == 'get':
log.debug("Received disco items query from " + \
"<%s> to <%s>.", iq['from'], iq['to'])
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
items = self.api['get_items'](jid,
items = self.api['get_items'](iq['to'],
iq['disco_items']['node'],
iq['from'].full,
iq['from'],
iq)
if isinstance(items, Iq):
items.send()

View File

@@ -182,11 +182,6 @@ class StaticDisco(object):
data = {'local': data.get('local', False),
'cached': data.get('cached', True)}
if node in (None, ''):
info = self.caps.get_caps(jid)
if info and identity in info['identities']:
return True
try:
info = self.disco.get_info(jid=jid, node=node,
ifrom=ifrom, **data)
@@ -242,7 +237,7 @@ class StaticDisco(object):
with self.lock:
if not self.node_exists(jid, node):
if not node:
return DiscoInfo()
return DiscoItems()
else:
raise XMPPError(condition='item-not-found')
else:
@@ -429,9 +424,6 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if isinstance(jid, JID):
jid = jid.full
if not self.node_exists(jid, node, ifrom):
return None
else:

View File

@@ -1,167 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Message
from sleekxmpp.xmlstream.handler.callback import Callback
from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET, JID
from sleekxmpp.plugins import BasePlugin, register_plugin
class Addresses(ElementBase):
namespace = 'http://jabber.org/protocol/address'
name = 'addresses'
plugin_attrib = 'addresses'
interfaces = set(('addresses', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def addAddress(self, atype='to', jid='', node='', uri='', desc='', delivered=False):
address = Address(parent=self)
address['type'] = atype
address['jid'] = jid
address['node'] = node
address['uri'] = uri
address['desc'] = desc
address['delivered'] = delivered
return address
def getAddresses(self, atype=None):
addresses = []
for addrXML in self.xml.findall('{%s}address' % Address.namespace):
# ElementTree 1.2.6 does not support [@attr='value'] in findall
if atype is None or addrXML.attrib.get('type') == atype:
addresses.append(Address(xml=addrXML, parent=None))
return addresses
def setAddresses(self, addresses, set_type=None):
self.delAddresses(set_type)
for addr in addresses:
addr = dict(addr)
# Remap 'type' to 'atype' to match the add method
if set_type is not None:
addr['type'] = set_type
curr_type = addr.get('type', None)
if curr_type is not None:
del addr['type']
addr['atype'] = curr_type
self.addAddress(**addr)
def delAddresses(self, atype=None):
if atype is None:
return
for addrXML in self.xml.findall('{%s}address' % Address.namespace):
# ElementTree 1.2.6 does not support [@attr='value'] in findall
if addrXML.attrib.get('type') == atype:
self.xml.remove(addrXML)
# --------------------------------------------------------------
def delBcc(self):
self.delAddresses('bcc')
def delCc(self):
self.delAddresses('cc')
def delNoreply(self):
self.delAddresses('noreply')
def delReplyroom(self):
self.delAddresses('replyroom')
def delReplyto(self):
self.delAddresses('replyto')
def delTo(self):
self.delAddresses('to')
# --------------------------------------------------------------
def getBcc(self):
return self.getAddresses('bcc')
def getCc(self):
return self.getAddresses('cc')
def getNoreply(self):
return self.getAddresses('noreply')
def getReplyroom(self):
return self.getAddresses('replyroom')
def getReplyto(self):
return self.getAddresses('replyto')
def getTo(self):
return self.getAddresses('to')
# --------------------------------------------------------------
def setBcc(self, addresses):
self.setAddresses(addresses, 'bcc')
def setCc(self, addresses):
self.setAddresses(addresses, 'cc')
def setNoreply(self, addresses):
self.setAddresses(addresses, 'noreply')
def setReplyroom(self, addresses):
self.setAddresses(addresses, 'replyroom')
def setReplyto(self, addresses):
self.setAddresses(addresses, 'replyto')
def setTo(self, addresses):
self.setAddresses(addresses, 'to')
class Address(ElementBase):
namespace = 'http://jabber.org/protocol/address'
name = 'address'
plugin_attrib = 'address'
interfaces = set(('delivered', 'desc', 'jid', 'node', 'type', 'uri'))
address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def getDelivered(self):
return self.xml.attrib.get('delivered', False)
def setDelivered(self, delivered):
if delivered:
self.xml.attrib['delivered'] = "true"
else:
del self['delivered']
def setUri(self, uri):
if uri:
del self['jid']
del self['node']
self.xml.attrib['uri'] = uri
elif 'uri' in self.xml.attrib:
del self.xml.attrib['uri']
class XEP_0033(BasePlugin):
"""
XEP-0033: Extended Stanza Addressing
"""
name = 'xep_0033'
description = 'XEP-0033: Extended Stanza Addressing'
dependencies = set(['xep_0033'])
def plugin_init(self):
self.xep = '0033'
register_stanza_plugin(Message, Addresses)
self.xmpp.plugin['xep_0030'].add_feature(Addresses.namespace)
xep_0033 = XEP_0033
register_plugin(XEP_0033)

View File

@@ -0,0 +1,20 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0033 import stanza
from sleekxmpp.plugins.xep_0033.stanza import Addresses, Address
from sleekxmpp.plugins.xep_0033.addresses import XEP_0033
register_plugin(XEP_0033)
# Retain some backwards compatibility
xep_0033 = XEP_0033
Addresses.addAddress = Addresses.add_address

View File

@@ -0,0 +1,37 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Message, Presence
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0033 import stanza, Addresses
class XEP_0033(BasePlugin):
"""
XEP-0033: Extended Stanza Addressing
"""
name = 'xep_0033'
description = 'XEP-0033: Extended Stanza Addressing'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, Addresses)
register_stanza_plugin(Presence, Addresses)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Addresses.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Addresses.namespace)

View File

@@ -0,0 +1,131 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import JID, ElementBase, ET, register_stanza_plugin
class Addresses(ElementBase):
name = 'addresses'
namespace = 'http://jabber.org/protocol/address'
plugin_attrib = 'addresses'
interfaces = set()
def add_address(self, atype='to', jid='', node='', uri='',
desc='', delivered=False):
addr = Address(parent=self)
addr['type'] = atype
addr['jid'] = jid
addr['node'] = node
addr['uri'] = uri
addr['desc'] = desc
addr['delivered'] = delivered
return addr
# Additional methods for manipulating sets of addresses
# based on type are generated below.
class Address(ElementBase):
name = 'address'
namespace = 'http://jabber.org/protocol/address'
plugin_attrib = 'address'
interfaces = set(['type', 'jid', 'node', 'uri', 'desc', 'delivered'])
address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
def get_jid(self):
return JID(self._get_attr('jid'))
def set_jid(self, value):
self._set_attr('jid', str(value))
def get_delivered(self):
value = self._get_attr('delivered', False)
return value and value.lower() in ('true', '1')
def set_delivered(self, delivered):
if delivered:
self._set_attr('delivered', 'true')
else:
del self['delivered']
def set_uri(self, uri):
if uri:
del self['jid']
del self['node']
self._set_attr('uri', uri)
else:
self._del_attr('uri')
# =====================================================================
# Auto-generate address type filters for the Addresses class.
def _addr_filter(atype):
def _type_filter(addr):
if isinstance(addr, Address):
if atype == 'all' or addr['type'] == atype:
return True
return False
return _type_filter
def _build_methods(atype):
def get_multi(self):
return list(filter(_addr_filter(atype), self))
def set_multi(self, value):
del self[atype]
for addr in value:
# Support assigning dictionary versions of addresses
# instead of full Address objects.
if not isinstance(addr, Address):
if atype != 'all':
addr['type'] = atype
elif 'atype' in addr and 'type' not in addr:
addr['type'] = addr['atype']
addrObj = Address()
addrObj.values = addr
addr = addrObj
self.append(addr)
def del_multi(self):
res = list(filter(_addr_filter(atype), self))
for addr in res:
self.iterables.remove(addr)
self.xml.remove(addr.xml)
return get_multi, set_multi, del_multi
for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'):
get_multi, set_multi, del_multi = _build_methods(atype)
Addresses.interfaces.add(atype)
setattr(Addresses, "get_%s" % atype, get_multi)
setattr(Addresses, "set_%s" % atype, set_multi)
setattr(Addresses, "del_%s" % atype, del_multi)
# To retain backwards compatibility:
setattr(Addresses, "get%s" % atype.title(), get_multi)
setattr(Addresses, "set%s" % atype.title(), set_multi)
setattr(Addresses, "del%s" % atype.title(), del_multi)
if atype == 'all':
Addresses.interfaces.add('addresses')
setattr(Addresses, "getAddresses", get_multi)
setattr(Addresses, "setAddresses", set_multi)
setattr(Addresses, "delAddresses", del_multi)
register_stanza_plugin(Addresses, Address, iterable=True)

View File

@@ -51,6 +51,13 @@ class XEP_0047(BasePlugin):
StanzaPath('iq@type=set/ibb_data'),
self._handle_data))
def plugin_end(self):
self.xmpp.remove_handler('IBB Open')
self.xmpp.remove_handler('IBB Close')
self.xmpp.remove_handler('IBB Data')
self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb')
def _accept_stream(self, iq):

View File

@@ -110,6 +110,20 @@ class XEP_0050(BasePlugin):
self._handle_command_complete,
threaded=self.threaded)
def plugin_end(self):
self.xmpp.del_event_handler('command_execute',
self._handle_command_start)
self.xmpp.del_event_handler('command_next',
self._handle_command_next)
self.xmpp.del_event_handler('command_cancel',
self._handle_command_cancel)
self.xmpp.del_event_handler('command_complete',
self._handle_command_complete)
self.xmpp.remove_handler('Ad-Hoc Execute')
self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Command.namespace)
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())

View File

@@ -110,14 +110,14 @@ class Command(ElementBase):
"""
Return the set of allowable next actions.
"""
actions = []
actions = set()
actions_xml = self.find('{%s}actions' % self.namespace)
if actions_xml is not None:
for action in self.next_actions:
action_xml = actions_xml.find('{%s}%s' % (self.namespace,
action))
if action_xml is not None:
actions.append(action)
actions.add(action)
return actions
def del_actions(self):

View File

@@ -72,6 +72,7 @@ class Nickname(ElementBase):
name = 'NICKNAME'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'nicknames'
interfaces = set([name])
is_extension = True
@@ -94,6 +95,7 @@ class Email(ElementBase):
name = 'EMAIL'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'emails'
interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'])
sub_interfaces = set(['USERID'])
bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400'])
@@ -103,8 +105,9 @@ class Address(ElementBase):
name = 'ADR'
namespace = 'vcard-temp'
plugin_attrib = name
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
plugin_multi_attrib = 'addresses'
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY'])
sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY'])
@@ -115,12 +118,13 @@ class Telephone(ElementBase):
name = 'TEL'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'telephone_numbers'
interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG',
'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS',
'PREF', 'NUMBER'])
sub_interfaces = set(['NUMBER'])
bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
'ISDN', 'PCS', 'PREF'])
def setup(self, xml=None):
@@ -138,9 +142,10 @@ class Label(ElementBase):
name = 'LABEL'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'labels'
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT',
'PREF', 'lines'])
bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
'INT', 'PREF'])
def add_line(self, value):
@@ -171,6 +176,7 @@ class Geo(ElementBase):
name = 'GEO'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'geolocations'
interfaces = set(['LAT', 'LON'])
sub_interfaces = interfaces
@@ -179,6 +185,7 @@ class Org(ElementBase):
name = 'ORG'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'organizations'
interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits'])
sub_interfaces = set(['ORGNAME', 'ORGUNIT'])
@@ -210,6 +217,7 @@ class Photo(ElementBase):
name = 'PHOTO'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'photos'
interfaces = set(['TYPE', 'EXTVAL'])
sub_interfaces = interfaces
@@ -218,14 +226,16 @@ class Logo(ElementBase):
name = 'LOGO'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'logos'
interfaces = set(['TYPE', 'EXTVAL'])
sub_interfaces = interfaces
class Sound(ElementBase):
name = 'LOGO'
name = 'SOUND'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'sounds'
interfaces = set(['PHONETC', 'EXTVAL'])
sub_interfaces = interfaces
@@ -264,6 +274,7 @@ class Classification(ElementBase):
name = 'CLASS'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'classifications'
interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'])
bool_interfaces = interfaces
@@ -272,6 +283,7 @@ class Categories(ElementBase):
name = 'CATEGORIES'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'categories'
interfaces = set([name])
is_extension = True
@@ -301,6 +313,7 @@ class Birthday(ElementBase):
name = 'BDAY'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'birthdays'
interfaces = set([name])
is_extension = True
@@ -319,6 +332,7 @@ class Rev(ElementBase):
name = 'REV'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'revision_dates'
interfaces = set([name])
is_extension = True
@@ -337,6 +351,7 @@ class Title(ElementBase):
name = 'TITLE'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'titles'
interfaces = set([name])
is_extension = True
@@ -351,6 +366,7 @@ class Role(ElementBase):
name = 'ROLE'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'roles'
interfaces = set([name])
is_extension = True
@@ -365,6 +381,7 @@ class Note(ElementBase):
name = 'NOTE'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'notes'
interfaces = set([name])
is_extension = True
@@ -379,6 +396,7 @@ class Desc(ElementBase):
name = 'DESC'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'descriptions'
interfaces = set([name])
is_extension = True
@@ -393,6 +411,7 @@ class URL(ElementBase):
name = 'URL'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'urls'
interfaces = set([name])
is_extension = True
@@ -407,6 +426,7 @@ class UID(ElementBase):
name = 'UID'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'uids'
interfaces = set([name])
is_extension = True
@@ -421,6 +441,7 @@ class ProdID(ElementBase):
name = 'PRODID'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'product_ids'
interfaces = set([name])
is_extension = True
@@ -435,6 +456,7 @@ class Mailer(ElementBase):
name = 'MAILER'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'mailers'
interfaces = set([name])
is_extension = True
@@ -449,6 +471,7 @@ class SortString(ElementBase):
name = 'SORT-STRING'
namespace = 'vcard-temp'
plugin_attrib = 'SORT_STRING'
plugin_multi_attrib = 'sort_strings'
interfaces = set([name])
is_extension = True
@@ -463,6 +486,7 @@ class Agent(ElementBase):
name = 'AGENT'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'agents'
interfaces = set(['EXTVAL'])
sub_interfaces = interfaces
@@ -471,6 +495,7 @@ class JabberID(ElementBase):
name = 'JABBERID'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'jids'
interfaces = set([name])
is_extension = True
@@ -485,11 +510,12 @@ class TimeZone(ElementBase):
name = 'TZ'
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'timezones'
interfaces = set([name])
is_extension = True
def set_tz(self, value):
time = xep_0082.time(offset=value)
time = xep_0082.time(offset=value)
if time[-1] == 'Z':
self.xml.text = 'Z'
else:

View File

@@ -37,7 +37,6 @@ class XEP_0054(BasePlugin):
"""
register_stanza_plugin(Iq, VCardTemp)
self.xmpp['xep_0030'].add_feature('vcard-temp')
self.api.register(self._set_vcard, 'set_vcard', default=True)
self.api.register(self._get_vcard, 'get_vcard', default=True)
@@ -50,10 +49,17 @@ class XEP_0054(BasePlugin):
StanzaPath('iq/vcard_temp'),
self._handle_get_vcard))
def plugin_end(self):
self.xmpp.remove_handler('VCardTemp')
self.xmpp['xep_0030'].del_feature(feature='vcard-temp')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('vcard-temp')
def make_vcard(self):
return VCardTemp()
def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,
def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,
block=True, callback=None, timeout=None):
if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain:
local = True
@@ -84,12 +90,12 @@ class XEP_0054(BasePlugin):
iq.enable('vcard_temp')
vcard = iq.send(block=block, callback=callback, timeout=timeout)
if block:
self.api['set_vcard'](vcard['from'], args=vcard['vcard_temp'])
return vcard
def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
def publish_vcard(self, vcard=None, jid=None, block=True, ifrom=None,
callback=None, timeout=None):
if self.xmpp.is_component:
self.api['set_vcard'](jid, None, ifrom, vcard)
@@ -107,7 +113,7 @@ class XEP_0054(BasePlugin):
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
return
elif iq['type'] == 'get':
vcard = self.api['get_vard'](iq['from'].bare)
vcard = self.api['get_vcard'](iq['from'].bare)
if isinstance(vcard, Iq):
vcard.send()
else:

View File

@@ -47,6 +47,7 @@ class ResultIterator():
self.start = start
self.interface = interface
self.reverse = reverse
self._stop = False
def __iter__(self):
return self
@@ -62,6 +63,8 @@ class ResultIterator():
results will be the items before the current page
of items.
"""
if self._stop:
raise StopIteration
self.query[self.interface]['rsm']['before'] = self.reverse
self.query['id'] = self.query.stream.new_id()
self.query[self.interface]['rsm']['max'] = str(self.amount)
@@ -84,7 +87,7 @@ class ResultIterator():
first = int(r[self.interface]['rsm']['first_index'])
num_items = len(r[self.interface]['substanzas'])
if first + num_items == count:
raise StopIteration
self._stop = True
if self.reverse:
self.start = r[self.interface]['rsm']['first']
@@ -111,10 +114,15 @@ class XEP_0059(BasePlugin):
"""
Start the XEP-0059 plugin.
"""
self.xmpp['xep_0030'].add_feature(Set.namespace)
register_stanza_plugin(self.xmpp['xep_0030'].stanza.DiscoItems,
self.stanza.Set)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Set.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Set.namespace)
def iterate(self, stanza, interface):
"""
Create a new result set iterator for a given stanza query.

View File

@@ -74,7 +74,7 @@ class Set(ElementBase):
if fi is not None:
if val:
fi.attrib['index'] = val
else:
elif 'index' in fi.attrib:
del fi.attrib['index']
elif val:
fi = ET.Element("{%s}first" % (self.namespace))
@@ -93,7 +93,7 @@ class Set(ElementBase):
def set_before(self, val):
b = self.xml.find("{%s}before" % (self.namespace))
if b is None and val == True:
if b is None and val is True:
self._set_sub_text('{%s}before' % self.namespace, '', True)
else:
self._set_sub_text('{%s}before' % self.namespace, val)

View File

@@ -53,6 +53,13 @@ class XEP_0060(BasePlugin):
StanzaPath('message/pubsub_event/subscription'),
self._handle_event_subscription))
def plugin_end(self):
self.xmpp.remove_handler('Pubsub Event: Items')
self.xmpp.remove_handler('Pubsub Event: Purge')
self.xmpp.remove_handler('Pubsub Event: Delete')
self.xmpp.remove_handler('Pubsub Event: Configuration')
self.xmpp.remove_handler('Pubsub Event: Subscription')
def _handle_event_items(self, msg):
"""Raise events for publish and retraction notifications."""
node = msg['pubsub_event']['items']['node']

View File

@@ -77,12 +77,12 @@ class Item(ElementBase):
self.append(value)
def get_payload(self):
childs = self.xml.getchildren()
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
def del_payload(self):
for child in self.xml.getchildren():
for child in self.xml:
self.xml.remove(child)
@@ -254,12 +254,12 @@ class PubsubState(ElementBase):
self.xml.append(value)
def get_payload(self):
childs = self.xml.getchildren()
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
def del_payload(self):
for child in self.xml.getchildren():
for child in self.xml:
self.xml.remove(child)

View File

@@ -33,7 +33,7 @@ class PubsubErrorCondition(ElementBase):
def get_condition(self):
"""Return the condition element's name."""
for child in self.parent().xml.getchildren():
for child in self.parent().xml:
if "{%s}" % self.condition_ns in child.tag:
cond = child.tag.split('}', 1)[-1]
if cond in self.conditions:
@@ -55,7 +55,7 @@ class PubsubErrorCondition(ElementBase):
def del_condition(self):
"""Remove the condition element."""
for child in self.parent().xml.getchildren():
for child in self.parent().xml:
if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:

View File

@@ -31,12 +31,12 @@ class EventItem(ElementBase):
self.xml.append(value)
def get_payload(self):
childs = self.xml.getchildren()
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
def del_payload(self):
for child in self.xml.getchildren():
for child in self.xml:
self.xml.remove(child)

View File

@@ -62,6 +62,12 @@ class XEP_0066(BasePlugin):
StanzaPath('iq@type=set/oob_transfer'),
self._handle_transfer))
def plugin_end(self):
self.xmpp.remove_handler('OOB Transfer')
self.xmpp['xep_0030'].del_feature(feature=stanza.OOBTransfer.namespace)
self.xmpp['xep_0030'].del_feature(feature=stanza.OOB.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(stanza.OOBTransfer.namespace)
self.xmpp['xep_0030'].add_feature(stanza.OOB.namespace)

View File

@@ -34,9 +34,7 @@ class XEP_0077(BasePlugin):
register_stanza_plugin(StreamFeatures, RegisterFeature)
register_stanza_plugin(Iq, Register)
if self.xmpp.is_component:
pass
else:
if not self.xmpp.is_component:
self.xmpp.register_feature('register',
self._handle_register_feature,
restart=False,
@@ -45,12 +43,16 @@ class XEP_0077(BasePlugin):
register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form)
register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB)
def plugin_end(self):
if not self.xmpp.is_component:
self.xmpp.unregister_feature('register', self.config.get('order', 50))
def _handle_register_feature(self, features):
if 'mechanisms' in self.xmpp.features:
# We have already logged in with an account
return False
if self.create_account:
if self.create_account and self.xmpp.event_handled('register'):
form = self.get_registration()
self.xmpp.event('register', form, direct=True)
return True

View File

@@ -44,6 +44,9 @@ class XEP_0078(BasePlugin):
register_stanza_plugin(Iq, stanza.IqAuth)
register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
def plugin_end(self):
self.xmpp.unregister_feature('auth', self.config.get('order', 15))
def _handle_auth(self, features):
# If we can or have already authenticated with SASL, do nothing.
if 'mechanisms' in features['features']:

View File

@@ -39,5 +39,3 @@ class AuthFeature(ElementBase):
interfaces = set()
plugin_tag_map = {}
plugin_attrib_map = {}

View File

@@ -28,8 +28,11 @@ class XEP_0080(BasePlugin):
dependencies = set(['xep_0163'])
stanza = stanza
def plugin_init(self):
"""Start the XEP-0080 plugin."""
def plugin_end(self):
self.xmpp['xep_0163'].remove_interest(Geoloc.namespace)
self.xmpp['xep_0030'].del_feature(feature=Geoloc.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_location', Geoloc)
def publish_location(self, **kwargs):
@@ -40,33 +43,33 @@ class XEP_0080(BasePlugin):
accuracy -- Horizontal GPS error in meters.
alt -- Altitude in meters above or below sea level.
area -- A named area such as a campus or neighborhood.
bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in
bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in
decimal degrees relative to true north.
building -- A specific building on a street or in an area.
country -- The nation where the user is located.
countrycode -- The ISO 3166 two-letter country code.
datum -- GPS datum.
description -- A natural-language name for or description of
description -- A natural-language name for or description of
the location.
error -- Horizontal GPS error in arc minutes. Obsoleted by
the accuracy parameter.
floor -- A particular floor in a building.
lat -- Latitude in decimal degrees North.
locality -- A locality within the administrative region, such
locality -- A locality within the administrative region, such
as a town or city.
lon -- Longitude in decimal degrees East.
postalcode -- A code used for postal delivery.
region -- An administrative region of the nation, such
region -- An administrative region of the nation, such
as a state or province.
room -- A particular room in a building.
speed -- The speed at which the entity is moving,
speed -- The speed at which the entity is moving,
in meters per second.
street -- A thoroughfare within the locality, or a crossing
of two thoroughfares.
text -- A catch-all element that captures any other
text -- A catch-all element that captures any other
information about the location.
timestamp -- UTC timestamp specifying the moment when the
timestamp -- UTC timestamp specifying the moment when the
reading was taken.
uri -- A URI or URL pointing to information about
the location.
@@ -115,7 +118,7 @@ class XEP_0080(BasePlugin):
be executed when a reply stanza is received.
"""
geoloc = Geoloc()
return self.xmpp['xep_0163'].publish(geoloc,
return self.xmpp['xep_0163'].publish(geoloc,
ifrom=ifrom,
block=block,
callback=callback,

View File

@@ -31,33 +31,33 @@ class Geoloc(ElementBase):
accuracy -- Horizontal GPS error in meters.
alt -- Altitude in meters above or below sea level.
area -- A named area such as a campus or neighborhood.
bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in
bearing -- GPS bearing (direction in which the entity is
heading to reach its next waypoint), measured in
decimal degrees relative to true north.
building -- A specific building on a street or in an area.
country -- The nation where the user is located.
countrycode -- The ISO 3166 two-letter country code.
datum -- GPS datum.
description -- A natural-language name for or description of
description -- A natural-language name for or description of
the location.
error -- Horizontal GPS error in arc minutes. Obsoleted by
the accuracy parameter.
floor -- A particular floor in a building.
lat -- Latitude in decimal degrees North.
locality -- A locality within the administrative region, such
locality -- A locality within the administrative region, such
as a town or city.
lon -- Longitude in decimal degrees East.
postalcode -- A code used for postal delivery.
region -- An administrative region of the nation, such
region -- An administrative region of the nation, such
as a state or province.
room -- A particular room in a building.
speed -- The speed at which the entity is moving,
speed -- The speed at which the entity is moving,
in meters per second.
street -- A thoroughfare within the locality, or a crossing
of two thoroughfares.
text -- A catch-all element that captures any other
text -- A catch-all element that captures any other
information about the location.
timestamp -- UTC timestamp specifying the moment when the
timestamp -- UTC timestamp specifying the moment when the
reading was taken.
uri -- A URI or URL pointing to information about
the location.
@@ -65,10 +65,10 @@ class Geoloc(ElementBase):
namespace = 'http://jabber.org/protocol/geoloc'
name = 'geoloc'
interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',
'country', 'countrycode', 'datum', 'dscription',
'error', 'floor', 'lat', 'locality', 'lon',
'postalcode', 'region', 'room', 'speed', 'street',
interfaces = set(('accuracy', 'alt', 'area', 'bearing', 'building',
'country', 'countrycode', 'datum', 'dscription',
'error', 'floor', 'lat', 'locality', 'lon',
'postalcode', 'region', 'room', 'speed', 'street',
'text', 'timestamp', 'uri'))
sub_interfaces = interfaces
plugin_attrib = name
@@ -88,7 +88,7 @@ class Geoloc(ElementBase):
"""
self._set_sub_text('accuracy', text=str(accuracy))
return self
def get_accuracy(self):
"""
Return the value of the <accuracy> element as an integer.
@@ -111,7 +111,7 @@ class Geoloc(ElementBase):
"""
self._set_sub_text('alt', text=str(alt))
return self
def get_alt(self):
"""
Return the value of the <alt> element as an integer.
@@ -130,8 +130,8 @@ class Geoloc(ElementBase):
Set the value of the <bearing> element.
Arguments:
bearing -- GPS bearing (direction in which the entity is heading
to reach its next waypoint), measured in decimal
bearing -- GPS bearing (direction in which the entity is heading
to reach its next waypoint), measured in decimal
degrees relative to true north
"""
self._set_sub_text('bearing', text=str(bearing))
@@ -155,7 +155,7 @@ class Geoloc(ElementBase):
Set the value of the <error> element.
Arguments:
error -- Horizontal GPS error in arc minutes; this
error -- Horizontal GPS error in arc minutes; this
element is deprecated in favor of <accuracy/>
"""
self._set_sub_text('error', text=str(error))
@@ -183,7 +183,7 @@ class Geoloc(ElementBase):
"""
self._set_sub_text('lat', text=str(lat))
return self
def get_lat(self):
"""
Return the value of the <lat> element as a float.
@@ -196,7 +196,7 @@ class Geoloc(ElementBase):
return float(p)
except ValueError:
return None
def set_lon(self, lon):
"""
Set the value of the <lon> element.
@@ -225,12 +225,12 @@ class Geoloc(ElementBase):
Set the value of the <speed> element.
Arguments:
speed -- The speed at which the entity is moving,
speed -- The speed at which the entity is moving,
in meters per second
"""
self._set_sub_text('speed', text=str(speed))
return self
def get_speed(self):
"""
Return the value of the <speed> element as a float.

View File

@@ -42,6 +42,7 @@ def format_date(time_obj):
time_obj = time_obj.date()
return time_obj.isoformat()
def format_time(time_obj):
"""
Return a formatted string version of a time object.
@@ -60,6 +61,7 @@ def format_time(time_obj):
return '%sZ' % timestamp
return timestamp
def format_datetime(time_obj):
"""
Return a formatted string version of a datetime object.
@@ -76,6 +78,7 @@ def format_datetime(time_obj):
return '%sZ' % timestamp
return timestamp
def date(year=None, month=None, day=None, obj=False):
"""
Create a date only timestamp for the given instant.
@@ -98,9 +101,10 @@ def date(year=None, month=None, day=None, obj=False):
day = today.day
value = dt.date(year, month, day)
if obj:
return value
return value
return format_date(value)
def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
"""
Create a time only timestamp for the given instant.
@@ -136,6 +140,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
return value
return format_time(value)
def datetime(year=None, month=None, day=None, hour=None,
min=None, sec=None, micro=None, offset=None,
separators=True, obj=False):
@@ -181,7 +186,7 @@ def datetime(year=None, month=None, day=None, hour=None,
value = dt.datetime(year, month, day, hour,
min, sec, micro, offset)
if obj:
return value
return value
return format_datetime(value)

View File

@@ -0,0 +1,16 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0084 import stanza
from sleekxmpp.plugins.xep_0084.stanza import Data, MetaData
from sleekxmpp.plugins.xep_0084.avatar import XEP_0084
register_plugin(XEP_0084)

View File

@@ -0,0 +1,106 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import hashlib
import logging
from sleekxmpp import Iq
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.xep_0084 import stanza, Data, MetaData
log = logging.getLogger(__name__)
class XEP_0084(BasePlugin):
name = 'xep_0084'
description = 'XEP-0084: User Avatar'
dependencies = set(['xep_0163', 'xep_0060'])
stanza = stanza
def plugin_init(self):
pubsub_stanza = self.xmpp['xep_0060'].stanza
register_stanza_plugin(pubsub_stanza.Item, Data)
register_stanza_plugin(pubsub_stanza.EventItem, Data)
self.xmpp['xep_0060'].map_node_event(Data.namespace, 'avatar_data')
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=MetaData.namespace)
self.xmpp['xep_0163'].remove_interest(MetaData.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('avatar_metadata', MetaData)
def retrieve_avatar(self, jid, id, url=None, ifrom=None, block=True,
callback=None, timeout=None):
return self.xmpp['xep_0060'].get_item(jid, Data.namespace, id,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def publish_avatar(self, data, ifrom=None, block=True, callback=None,
timeout=None):
payload = Data()
payload['value'] = data
return self.xmpp['xep_0163'].publish(payload,
node=Data.namespace,
id=hashlib.sha1(data).hexdigest(),
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def publish_avatar_metadata(self, items=None, pointers=None,
ifrom=None, block=True,
callback=None, timeout=None):
metadata = MetaData()
if items is None:
items = []
for info in items:
metadata.add_info(info['id'], info['type'], info['bytes'],
height=info.get('height', ''),
width=info.get('width', ''),
url=info.get('url', ''))
for pointer in pointers:
metadata.add_pointer(pointer)
return self.xmpp['xep_0163'].publish(payload,
node=Data.namespace,
id=hashlib.sha1(data).hexdigest(),
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
"""
Clear existing avatar metadata information to stop notifications.
Arguments:
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.
"""
metadata = MetaData()
return self.xmpp['xep_0163'].publish(metadata,
node=MetaData.namespace,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)

View File

@@ -0,0 +1,78 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from base64 import b64encode, b64decode
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
class Data(ElementBase):
name = 'data'
namespace = 'urn:xmpp:avatar:data'
plugin_attrib = 'avatar_data'
interfaces = set(['value'])
def get_value(self):
if self.xml.text:
return b64decode(bytes(self.xml.text))
return ''
def set_value(self, value):
if value:
self.xml.text = b64encode(bytes(value))
else:
self.xml.text = ''
def del_value(self):
self.xml.text = ''
class MetaData(ElementBase):
name = 'metadata'
namespace = 'urn:xmpp:avatar:metadata'
plugin_attrib = 'avatar_metadata'
interfaces = set()
def add_info(self, id, itype, ibytes, height=None, width=None, url=None):
info = Info()
info.values = {'id': id,
'type': itype,
'bytes': ibytes,
'height': height,
'width': width,
'url': url}
self.append(info)
def add_pointer(self, xml):
if not isinstance(xml, Pointer):
pointer = Pointer()
pointer.append(xml)
self.append(pointer)
else:
self.append(xml)
class Info(ElementBase):
name = 'info'
namespace = 'urn:xmpp:avatar:metadata'
plugin_attrib = 'info'
plugin_multi_attrib = 'items'
interfaces = set(['bytes', 'height', 'id', 'type', 'url', 'width'])
class Pointer(ElementBase):
name = 'pointer'
namespace = 'urn:xmpp:avatar:metadata'
plugin_attrib = 'pointer'
plugin_multi_attrib = 'pointers'
interfaces = set()
register_stanza_plugin(MetaData, Info, iterable=True)
register_stanza_plugin(MetaData, Pointer, iterable=True)

View File

@@ -43,6 +43,10 @@ class XEP_0085(BasePlugin):
register_stanza_plugin(Message, stanza.Inactive)
register_stanza_plugin(Message, stanza.Paused)
def plugin_end(self):
self.xmpp.remove_handler('Chat State')
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace)
def _handle_chat_state(self, msg):

View File

@@ -47,28 +47,28 @@ class LegacyError(ElementBase):
interfaces = set(('condition',))
overrides = ['set_condition']
error_map = {'bad-request': ('modify','400'),
'conflict': ('cancel','409'),
'feature-not-implemented': ('cancel','501'),
'forbidden': ('auth','403'),
'gone': ('modify','302'),
'internal-server-error': ('wait','500'),
'item-not-found': ('cancel','404'),
'jid-malformed': ('modify','400'),
'not-acceptable': ('modify','406'),
'not-allowed': ('cancel','405'),
'not-authorized': ('auth','401'),
'payment-required': ('auth','402'),
'recipient-unavailable': ('wait','404'),
'redirect': ('modify','302'),
'registration-required': ('auth','407'),
'remote-server-not-found': ('cancel','404'),
'remote-server-timeout': ('wait','504'),
'resource-constraint': ('wait','500'),
'service-unavailable': ('cancel','503'),
'subscription-required': ('auth','407'),
'undefined-condition': (None,'500'),
'unexpected-request': ('wait','400')}
error_map = {'bad-request': ('modify', '400'),
'conflict': ('cancel', '409'),
'feature-not-implemented': ('cancel', '501'),
'forbidden': ('auth', '403'),
'gone': ('modify', '302'),
'internal-server-error': ('wait', '500'),
'item-not-found': ('cancel', '404'),
'jid-malformed': ('modify', '400'),
'not-acceptable': ('modify', '406'),
'not-allowed': ('cancel', '405'),
'not-authorized': ('auth', '401'),
'payment-required': ('auth', '402'),
'recipient-unavailable': ('wait', '404'),
'redirect': ('modify', '302'),
'registration-required': ('auth', '407'),
'remote-server-not-found': ('cancel', '404'),
'remote-server-timeout': ('wait', '504'),
'resource-constraint': ('wait', '500'),
'service-unavailable': ('cancel', '503'),
'subscription-required': ('auth', '407'),
'undefined-condition': (None, '500'),
'unexpected-request': ('wait', '400')}
def setup(self, xml):
"""Don't create XML for the plugin."""

View File

@@ -48,6 +48,11 @@ class XEP_0092(BasePlugin):
register_stanza_plugin(Iq, Version)
def plugin_end(self):
self.xmpp.remove_handler('Software Version')
self.xmpp['xep_0030'].del_feature(feature='jabber:iq:version')
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:version')
def _handle_version(self, iq):
@@ -79,5 +84,7 @@ class XEP_0092(BasePlugin):
result = iq.send()
if result and result['type'] != 'error':
return result['software_version'].values
values = result['software_version'].values
del values['lang']
return values
return False

View File

@@ -32,9 +32,15 @@ class XEP_0107(BasePlugin):
def plugin_init(self):
register_stanza_plugin(Message, UserMood)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=UserMood.namespace)
self.xmpp['xep_0163'].remove_interest(UserMood.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_mood', UserMood)
def publish_mood(self, value=None, text=None, options=None,
def publish_mood(self, value=None, text=None, options=None,
ifrom=None, block=True, callback=None, timeout=None):
"""
Publish the user's current mood.
@@ -79,7 +85,7 @@ class XEP_0107(BasePlugin):
be executed when a reply stanza is received.
"""
mood = UserMood()
return self.xmpp['xep_0163'].publish(mood,
return self.xmpp['xep_0163'].publish(mood,
node=UserMood.namespace,
ifrom=ifrom,
block=block,

View File

@@ -21,7 +21,7 @@ class UserActivity(ElementBase):
'talking', 'traveling', 'undefined', 'working'])
specific = set(['at_the_spa', 'brushing_teeth', 'buying_groceries',
'cleaning', 'coding', 'commuting', 'cooking', 'cycling',
'dancing', 'day_off', 'doing_maintenance',
'dancing', 'day_off', 'doing_maintenance',
'doing_the_dishes', 'doing_the_laundry', 'driving',
'fishing', 'gaming', 'gardening', 'getting_a_haircut',
'going_out', 'hanging_out', 'having_a_beer',
@@ -31,11 +31,11 @@ class UserActivity(ElementBase):
'jogging', 'on_a_bus', 'on_a_plane', 'on_a_train',
'on_a_trip', 'on_the_phone', 'on_vacation',
'on_video_phone', 'other', 'partying', 'playing_sports',
'praying', 'reading', 'rehearsing', 'running',
'praying', 'reading', 'rehearsing', 'running',
'running_an_errand', 'scheduled_holiday', 'shaving',
'shopping', 'skiing', 'sleeping', 'smoking',
'socializing', 'studying', 'sunbathing', 'swimming',
'taking_a_bath', 'taking_a_shower', 'thinking',
'taking_a_bath', 'taking_a_shower', 'thinking',
'walking', 'walking_the_dog', 'watching_a_movie',
'watching_tv', 'working_out', 'writing'])
@@ -46,7 +46,7 @@ class UserActivity(ElementBase):
if isinstance(value, tuple) or isinstance(value, list):
general = value[0]
specific = value[1]
if general in self.general:
gen_xml = ET.Element('{%s}%s' % (self.namespace, general))
if specific:

View File

@@ -26,10 +26,14 @@ class XEP_0108(BasePlugin):
dependencies = set(['xep_0163'])
stanza = stanza
def plugin_init(self):
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=UserActivity.namespace)
self.xmpp['xep_0163'].remove_interest(UserActivity.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_activity', UserActivity)
def publish_activity(self, general, specific=None, text=None, options=None,
def publish_activity(self, general, specific=None, text=None, options=None,
ifrom=None, block=True, callback=None, timeout=None):
"""
Publish the user's current activity.
@@ -76,7 +80,7 @@ class XEP_0108(BasePlugin):
be executed when a reply stanza is received.
"""
activity = UserActivity()
return self.xmpp['xep_0163'].publish(activity,
return self.xmpp['xep_0163'].publish(activity,
node=UserActivity.namespace,
ifrom=ifrom,
block=block,

View File

@@ -35,7 +35,7 @@ class XEP_0115(BasePlugin):
stanza = stanza
def plugin_init(self):
self.hashes = {'sha-1': hashlib.sha1,
self.hashes = {'sha-1': hashlib.sha1,
'sha1': hashlib.sha1,
'md5': hashlib.md5}
@@ -73,16 +73,15 @@ class XEP_0115(BasePlugin):
restart=False,
order=10010)
self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace)
disco = self.xmpp['xep_0030']
self.static = StaticCaps(self.xmpp, disco.static)
self.api.settings['client_bare'] = False
self.api.settings['component_bare'] = False
for op in self._disco_ops:
self.api.register(getattr(self.static, op), op, default=True)
for op in ('supports', 'has_identity'):
self.xmpp['xep_0030'].api.register(getattr(self.static, op), op)
self._run_node_handler = disco._run_node_handler
disco.cache_caps = self.cache_caps
@@ -90,6 +89,19 @@ class XEP_0115(BasePlugin):
disco.assign_verstring = self.assign_verstring
disco.get_verstring = self.get_verstring
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
self.xmpp.del_filter('out', self._filter_add_caps)
self.xmpp.del_event_handler('entity_caps', self._process_caps)
self.xmpp.remove_handler('Entity Capabilities')
if not self.xmpp.is_component:
self.xmpp.unregister_feature('caps', 10010)
for op in ('supports', 'has_identity'):
self.xmpp['xep_0030'].restore_defaults(op)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(stanza.Capabilities.namespace)
def _filter_add_caps(self, stanza):
if isinstance(stanza, Presence) and self.broadcast:
ver = self.get_verstring(stanza['from'])
@@ -124,7 +136,7 @@ class XEP_0115(BasePlugin):
existing_verstring = self.get_verstring(pres['from'].full)
if str(existing_verstring) == str(pres['caps']['ver']):
return
if pres['caps']['hash'] not in self.hashes:
try:
log.debug("Unknown caps hash: %s", pres['caps']['hash'])
@@ -132,7 +144,7 @@ class XEP_0115(BasePlugin):
return
except XMPPError:
return
log.debug("New caps verification string: %s", pres['caps']['ver'])
try:
node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver'])
@@ -140,7 +152,7 @@ class XEP_0115(BasePlugin):
if isinstance(caps, Iq):
caps = caps['disco_info']
if self._validate_caps(caps, pres['caps']['hash'],
pres['caps']['ver']):
self.assign_verstring(pres['from'], pres['caps']['ver'])
@@ -173,7 +185,8 @@ class XEP_0115(BasePlugin):
form_types.append(f_type)
deduped_form_types.add(f_type)
if len(form_types) != len(deduped_form_types):
log.debug("Duplicated FORM_TYPE values, invalid for caps")
log.debug("Duplicated FORM_TYPE values, " + \
"invalid for caps")
return False
if len(f_type) > 1:
@@ -183,7 +196,8 @@ class XEP_0115(BasePlugin):
return False
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
log.debug("Field FORM_TYPE type not 'hidden', ignoring form for caps")
log.debug("Field FORM_TYPE type not 'hidden', " + \
"ignoring form for caps")
caps.xml.remove(stanza.xml)
else:
log.debug("No FORM_TYPE found, ignoring form for caps")
@@ -212,7 +226,7 @@ class XEP_0115(BasePlugin):
identities = sorted(('/'.join(i) for i in identities))
features = sorted(info['features'])
S += '<'.join(identities) + '<'
S += '<'.join(features) + '<'
@@ -254,7 +268,7 @@ class XEP_0115(BasePlugin):
info = info['disco_info']
ver = self.generate_verstring(info, self.hash)
self.xmpp['xep_0030'].set_info(
jid=jid,
jid=jid,
node='%s#%s' % (self.caps_node, ver),
info=info)
self.cache_caps(ver, info)

View File

@@ -69,7 +69,7 @@ class StaticCaps(object):
return True
try:
info = self.disco.get_info(jid=jid, node=node,
info = self.disco.get_info(jid=jid, node=node,
ifrom=ifrom, **data)
info = self.disco._wrap(ifrom, jid, info, True)
return feature in info['disco_info']['features']
@@ -99,7 +99,7 @@ class StaticCaps(object):
be skipped, even if a result has already been
cached. Defaults to false.
"""
identity = (data.get('category', None),
identity = (data.get('category', None),
data.get('itype', None),
data.get('lang', None))
@@ -114,7 +114,7 @@ class StaticCaps(object):
return True
try:
info = self.disco.get_info(jid=jid, node=node,
info = self.disco.get_info(jid=jid, node=node,
ifrom=ifrom, **data)
info = self.disco._wrap(ifrom, jid, info, True)
return identity in map(trunc, info['disco_info']['identities'])

View File

@@ -14,7 +14,7 @@ class UserTune(ElementBase):
name = 'tune'
namespace = 'http://jabber.org/protocol/tune'
plugin_attrib = 'tune'
interfaces = set(['artist', 'length', 'rating', 'source',
interfaces = set(['artist', 'length', 'rating', 'source',
'title', 'track', 'uri'])
sub_interfaces = interfaces

View File

@@ -26,11 +26,15 @@ class XEP_0118(BasePlugin):
dependencies = set(['xep_0163'])
stanza = stanza
def plugin_init(self):
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=UserTune.namespace)
self.xmpp['xep_0163'].remove_interest(UserTune.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_tune', UserTune)
def publish_tune(self, artist=None, length=None, rating=None, source=None,
title=None, track=None, uri=None, options=None,
title=None, track=None, uri=None, options=None,
ifrom=None, block=True, callback=None, timeout=None):
"""
Publish the user's current tune.
@@ -61,7 +65,7 @@ class XEP_0118(BasePlugin):
tune['title'] = title
tune['track'] = track
tune['uri'] = uri
return self.xmpp['xep_0163'].publish(tune,
return self.xmpp['xep_0163'].publish(tune,
node=UserTune.namespace,
options=options,
ifrom=ifrom,
@@ -84,7 +88,7 @@ class XEP_0118(BasePlugin):
be executed when a reply stanza is received.
"""
tune = UserTune()
return self.xmpp['xep_0163'].publish(tune,
return self.xmpp['xep_0163'].publish(tune,
node=UserTune.namespace,
ifrom=ifrom,
block=block,

View File

@@ -51,8 +51,6 @@ class XEP_0128(BasePlugin):
register_stanza_plugin(DiscoInfo, Form, iterable=True)
def post_init(self):
"""Handle cross-plugin dependencies."""
self.disco = self.xmpp['xep_0030']
self.static = StaticExtendedDisco(self.disco.static)

View File

@@ -45,7 +45,16 @@ class XEP_0153(BasePlugin):
self.api.register(self._set_hash, 'set_hash', default=True)
self.api.register(self._get_hash, 'get_hash', default=True)
def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,
def plugin_end(self):
self.xmpp.del_filter('out', self._update_presence)
self.xmpp.del_event_handler('session_start', self._start)
self.xmpp.del_event_handler('presence_available', self._recv_presence)
self.xmpp.del_event_handler('presence_dnd', self._recv_presence)
self.xmpp.del_event_handler('presence_xa', self._recv_presence)
self.xmpp.del_event_handler('presence_chat', self._recv_presence)
self.xmpp.del_event_handler('presence_away', self._recv_presence)
def set_avatar(self, jid=None, avatar=None, mtype=None, block=True,
timeout=None, callback=None):
vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)
vcard = vcard['vcard_temp']
@@ -69,7 +78,7 @@ class XEP_0153(BasePlugin):
own_jid = (jid.bare == self.xmpp.boundjid.bare)
if self.xmpp.is_component:
own_jid = (jid.domain == self.xmpp.boundjid.domain)
if jid is not None:
jid = jid.bare
self.api['set_hash'](jid, args=None)
@@ -77,7 +86,7 @@ class XEP_0153(BasePlugin):
self.xmpp.roster[jid].send_last_presence()
iq = self.xmpp['xep_0054'].get_vcard(
jid=jid,
jid=jid,
ifrom=self.xmpp.boundjid)
data = iq['vcard_temp']['PHOTO']['BINVAL']
if not data:

View File

@@ -56,7 +56,7 @@ class XEP_0163(BasePlugin):
jid -- Optionally specify the JID.
"""
if not isinstance(namespace, set) and not isinstance(namespace, list):
namespace = [namespace]
namespace = [namespace]
for ns in namespace:
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
@@ -74,8 +74,8 @@ class XEP_0163(BasePlugin):
be a list of such namespaces.
jid -- Optionally specify the JID.
"""
if not isinstance(namespace, set) and not isinstance(namespace, list):
namespace = [namespace]
if not isinstance(namespace, (set, list)):
namespace = [namespace]
for ns in namespace:
self.xmpp['xep_0030'].del_feature(jid=jid,
@@ -109,6 +109,7 @@ class XEP_0163(BasePlugin):
node = stanza.namespace
return self.xmpp['xep_0060'].publish(ifrom, node,
id=id,
payload=stanza.xml,
options=options,
ifrom=ifrom,

View File

@@ -34,6 +34,12 @@ class XEP_0172(BasePlugin):
def plugin_init(self):
register_stanza_plugin(Message, UserNick)
register_stanza_plugin(Presence, UserNick)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=UserNick.namespace)
self.xmpp['xep_0163'].remove_interest(UserNick.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('user_nick', UserNick)
def publish_nick(self, nick=None, options=None, ifrom=None, block=True,
@@ -78,7 +84,7 @@ class XEP_0172(BasePlugin):
be executed when a reply stanza is received.
"""
nick = UserNick()
return self.xmpp['xep_0163'].publish(nick,
return self.xmpp['xep_0163'].publish(nick,
node=UserNick.namespace,
ifrom=ifrom,
block=block,

View File

@@ -48,6 +48,13 @@ class XEP_0184(BasePlugin):
StanzaPath('message/request_receipt'),
self._handle_receipt_request))
def plugin_end(self):
self.xmpp['xep_0030'].del_feature('urn:xmpp:receipts')
self.xmpp.del_filter('out', self._filter_add_receipt_request)
self.xmpp.remove_handler('Message Receipt')
self.xmpp.remove_handler('Message Receipt Request')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('urn:xmpp:receipts')
def ack(self, msg):
@@ -100,13 +107,13 @@ class XEP_0184(BasePlugin):
if not isinstance(stanza, Message):
return stanza
if stanza['request_receipt']:
return stanza
if not stanza['type'] in self.ack_types:
return stanza
if stanza['receipt']:
return stanza

View File

@@ -0,0 +1,16 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0186 import stanza
from sleekxmpp.plugins.xep_0186.stanza import Invisible, Visible
from sleekxmpp.plugins.xep_0186.invisible_command import XEP_0186
register_plugin(XEP_0186)

View File

@@ -0,0 +1,44 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0186 import stanza, Visible, Invisible
log = logging.getLogger(__name__)
class XEP_0186(BasePlugin):
name = 'xep_0186'
description = 'XEP-0186: Invisible Command'
dependencies = set(['xep_0030'])
def plugin_init(self):
register_stanza_plugin(Iq, Visible)
register_stanza_plugin(Iq, Invisible)
def set_invisible(self, ifrom=None, block=True, callback=None,
timeout=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
iq.enable('invisible')
iq.send(block=block, callback=callback, timeout=timeout)
def set_visible(self, ifrom=None, block=True, callback=None,
timeout=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
iq.enable('visible')
iq.send(block=block, callback=callback, timeout=timeout)

View File

@@ -0,0 +1,23 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase, ET
class Invisible(ElementBase):
name = 'invisible'
namespace = 'urn:xmpp:invisible:0'
plugin_attrib = 'invisible'
interfaces = set()
class Visible(ElementBase):
name = 'visible'
namespace = 'urn:xmpp:visible:0'
plugin_attrib = 'visible'
interfaces = set()

View File

@@ -0,0 +1,15 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0191.stanza import Block, Unblock, BlockList
from sleekxmpp.plugins.xep_0191.blocking import XEP_0191
register_plugin(XEP_0191)

View File

@@ -0,0 +1,83 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.xep_0191 import stanza, Block, Unblock, BlockList
log = logging.getLogger(__name__)
class XEP_0191(BasePlugin):
name = 'xep_0191'
description = 'XEP-0191: Blocking Command'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Iq, BlockList)
register_stanza_plugin(Iq, Block)
register_stanza_plugin(Iq, Unblock)
self.xmpp.register_handler(
Callback('Blocked Contact',
StanzaPath('iq@type=set/block'),
self._handle_blocked))
self.xmpp.register_handler(
Callback('Unblocked Contact',
StanzaPath('iq@type=set/unblock'),
self._handle_unblocked))
def plugin_end(self):
self.xmpp.remove_handler('Blocked Contact')
self.xmpp.remove_handler('Unblocked Contact')
def get_blocked(self, ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['from'] = 'ifrom'
iq.enable('blocklist')
return iq.send(block=block, timeout=timeout, callback=callback)
def block(self, jids, ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
if not isinstance(jids, (set, list)):
jids = [jids]
iq['block']['items'] = jids
return iq.send(block=block, timeout=timeout, callback=callback)
def unblock(self, jids=None, ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
if jids is None:
jids = []
if not isinstance(jids, (set, list)):
jids = [jids]
iq['unblock']['items'] = jids
return iq.send(block=block, timeout=timeout, callback=callback)
def _handle_blocked(self, iq):
self.xmpp.event('blocked', iq)
def _handle_unblocked(self, iq):
self.xmpp.event('unblocked', iq)

View File

@@ -0,0 +1,50 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ET, ElementBase, JID
class BlockList(ElementBase):
name = 'blocklist'
namespace = 'urn:xmpp:blocking'
plugin_attrib = 'blocklist'
interfaces = set(['items'])
def get_items(self):
result = set()
items = self.xml.findall('{%s}item' % self.namespace)
if items is not None:
for item in items:
jid = JID(item.attrib.get('jid', ''))
if jid:
result.add(jid)
return result
def set_items(self, values):
self.del_items()
for jid in values:
if jid:
item = ET.Element('{%s}item' % self.namespace)
item.attrib['jid'] = JID(jid).full
self.xml.append(item)
def del_items(self):
items = self.xml.findall('{%s}item' % self.namespace)
if items is not None:
for item in items:
self.xml.remove(item)
class Block(BlockList):
name = 'block'
plugin_attrib = 'block'
class Unblock(BlockList):
name = 'unblock'
plugin_attrib = 'unblock'

View File

@@ -82,7 +82,6 @@ class Resumed(StanzaBase):
self._set_attr('h', str(val))
class Failed(StanzaBase, Error):
name = 'failed'
namespace = 'urn:xmpp:sm:3'
@@ -106,7 +105,7 @@ class StreamManagement(ElementBase):
self.del_required()
if val:
self._set_sub_text('required', '', keep=True)
def del_required(self):
self._del_sub('required')
@@ -117,7 +116,7 @@ class StreamManagement(ElementBase):
self.del_optional()
if val:
self._set_sub_text('optional', '', keep=True)
def del_optional(self):
self._del_sub('optional')

View File

@@ -21,7 +21,7 @@ from sleekxmpp.plugins.xep_0198 import stanza
log = logging.getLogger(__name__)
MAX_SEQ = 2**32
MAX_SEQ = 2 ** 32
class XEP_0198(BasePlugin):
@@ -69,7 +69,7 @@ class XEP_0198(BasePlugin):
self.enabled = threading.Event()
self.unacked_queue = collections.deque()
self.seq_lock = threading.Lock()
self.handled_lock = threading.Lock()
self.ack_lock = threading.Lock()
@@ -133,6 +133,27 @@ class XEP_0198(BasePlugin):
self.xmpp.add_event_handler('session_end', self.session_end)
def plugin_end(self):
if self.xmpp.is_component:
return
self.xmpp.unregister_feature('sm', self.config.get('order', 10100))
self.xmpp.unregister_feature('sm', self.config.get('resume_order', 9000))
self.xmpp.del_event_handler('session_end', self.session_end)
self.xmpp.del_filter('in', self._handle_incoming)
self.xmpp.del_filter('out_sync', self._handle_outgoing)
self.xmpp.remove_handler('Stream Management Enabled')
self.xmpp.remove_handler('Stream Management Resumed')
self.xmpp.remove_handler('Stream Management Failed')
self.xmpp.remove_handler('Stream Management Ack')
self.xmpp.remove_handler('Stream Management Request Ack')
self.xmpp.remove_stanza(stanza.Enable)
self.xmpp.remove_stanza(stanza.Enabled)
self.xmpp.remove_stanza(stanza.Resume)
self.xmpp.remove_stanza(stanza.Resumed)
self.xmpp.remove_stanza(stanza.Ack)
self.xmpp.remove_stanza(stanza.RequestAck)
def session_end(self, event):
"""Reset stream management state."""
self.enabled.clear()
@@ -197,7 +218,7 @@ class XEP_0198(BasePlugin):
def _handle_enabled(self, stanza):
"""Save the SM-ID, if provided.
Raises an :term:`sm_enabled` event.
"""
self.xmpp.features.add('stream_management')
@@ -231,7 +252,7 @@ class XEP_0198(BasePlugin):
def _handle_ack(self, ack):
"""Process a server ack by freeing acked stanzas from the queue.
Raises a :term:`stanza_acked` event for each acked stanza.
"""
if ack['h'] == self.last_ack:
@@ -243,10 +264,10 @@ class XEP_0198(BasePlugin):
log.debug("Ack: %s, Last Ack: %s, " + \
"Unacked: %s, Num Acked: %s, " + \
"Remaining: %s",
ack['h'],
self.last_ack,
ack['h'],
self.last_ack,
num_unacked,
num_acked,
num_acked,
num_unacked - num_acked)
for x in range(num_acked):
seq, stanza = self.unacked_queue.popleft()

View File

@@ -74,6 +74,16 @@ class XEP_0199(BasePlugin):
self.xmpp.add_event_handler('session_end',
self._handle_session_end)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Ping.namespace)
self.xmpp.remove_handler('Ping')
if self.keepalive:
self.xmpp.del_event_handler('session_start',
self._handle_keepalive)
self.xmpp.del_event_handler('session_end',
self._handle_session_end)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Ping.namespace)
def _handle_keepalive(self, event):

View File

@@ -40,8 +40,12 @@ class XEP_0202(BasePlugin):
# custom function can be supplied which accepts
# the JID of the entity to query for the time.
self.local_time = self.config.get('local_time', None)
def default_local_time(jid):
return xep_0082.datetime(offset=self.tz_offset)
if not self.local_time:
self.local_time = lambda x: xep_0082.datetime(offset=self.tz_offset)
self.local_time = default_local_time
self.xmpp.registerHandler(
Callback('Entity Time',
@@ -49,6 +53,11 @@ class XEP_0202(BasePlugin):
self._handle_time_request))
register_stanza_plugin(Iq, stanza.EntityTime)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:time')
self.xmpp.remove_handler('Entity Time')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('urn:xmpp:time')
def _handle_time_request(self, iq):

View File

@@ -13,9 +13,7 @@ from sleekxmpp.plugins.xep_0203.stanza import Delay
from sleekxmpp.plugins.xep_0203.delay import XEP_0203
register_plugin(XEP_0203)
# Retain some backwards compatibility
xep_0203 = XEP_0203

View File

@@ -0,0 +1,16 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0221 import stanza
from sleekxmpp.plugins.xep_0221.stanza import Media, URI
from sleekxmpp.plugins.xep_0221.media import XEP_0221
register_plugin(XEP_0221)

View File

@@ -0,0 +1,27 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0221 import stanza, Media, URI
from sleekxmpp.plugins.xep_0004 import FormField
log = logging.getLogger(__name__)
class XEP_0221(BasePlugin):
name = 'xep_0221'
description = 'XEP-0221: Data Forms Media Element'
dependencies = set(['xep_0004'])
def plugin_init(self):
register_stanza_plugin(FormField, Media)

View File

@@ -0,0 +1,42 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class Media(ElementBase):
name = 'media'
namespace = 'urn:xmpp:media-element'
plugin_attrib = 'media'
interfaces = set(['height', 'width', 'alt'])
def add_uri(self, value, itype):
uri = URI()
uri['value'] = value
uri['type'] = itype
self.append(uri)
class URI(ElementBase):
name = 'uri'
namespace = 'urn:xmpp:media-element'
plugin_attrib = 'uri'
plugin_multi_attrib = 'uris'
interfaces = set(['type', 'value'])
def get_value(self):
return self.xml.text
def set_value(self, value):
self.xml.text = value
def del_value(self):
sel.xml.text = ''
register_stanza_plugin(Media, URI, iterable=True)

View File

@@ -0,0 +1,126 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin, register_plugin
log = logging.getLogger(__name__)
class XEP_0222(BasePlugin):
"""
XEP-0222: Persistent Storage of Public Data via PubSub
"""
name = 'xep_0222'
description = 'XEP-0222: Persistent Storage of Private Data via PubSub'
dependencies = set(['xep_0163', 'xep_0060', 'xep_0004'])
profile = {'pubsub#persist_items': True,
'pubsub#send_last_published_item': 'never'}
def configure(self, node):
"""
Update a node's configuration to match the public storage profile.
"""
config = self.xmpp['xep_0004'].Form()
config['type'] = 'submit'
for field, value in self.profile.items():
config.add_field(var=field, value=value)
return self.xmpp['xep_0060'].set_node_config(None, node, config,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def store(self, stanza, node=None, id=None, ifrom=None, options=None,
block=True, callback=None, timeout=None):
"""
Store public data via PEP.
This is just a (very) thin wrapper around the XEP-0060 publish()
method to set the defaults expected by PEP.
Arguments:
stanza -- The private content to store.
node -- The node to publish the content to. If not specified,
the stanza's namespace will be used.
id -- Optionally specify the ID of the item.
options -- Publish options to use, which will be modified to
fit the persistent storage option profile.
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.
"""
if not options:
options = self.xmpp['xep_0004'].stanza.Form()
options['type'] = 'submit'
options.add_field(
var='FORM_TYPE',
ftype='hidden',
value='http://jabber.org/protocol/pubsub#publish-options')
for field, value in self.profile.items():
if field not in options.fields:
options.add_field(var=field)
options.fields[field]['value'] = value
return self.xmpp['xep_0163'].publish(stanza, node,
options=options,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def retrieve(self, node, id=None, item_ids=None, ifrom=None,
block=True, callback=None, timeout=None):
"""
Retrieve public data via PEP.
This is just a (very) thin wrapper around the XEP-0060 publish()
method to set the defaults expected by PEP.
Arguments:
node -- The node to retrieve content from.
id -- Optionally specify the ID of the item.
item_ids -- Specify a group of IDs. If id is also specified, it
will be included in item_ids.
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.
"""
if item_ids is None:
item_ids = []
if id is not None:
item_ids.append(id)
return self.xmpp['xep_0060'].get_items(None, node,
item_ids=item_ids,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
register_plugin(XEP_0222)

View File

@@ -0,0 +1,126 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin, register_plugin
log = logging.getLogger(__name__)
class XEP_0223(BasePlugin):
"""
XEP-0223: Persistent Storage of Private Data via PubSub
"""
name = 'xep_0223'
description = 'XEP-0223: Persistent Storage of Private Data via PubSub'
dependencies = set(['xep_0163', 'xep_0060', 'xep_0004'])
profile = {'pubsub#persist_items': True,
'pubsub#send_last_published_item': 'never'}
def configure(self, node):
"""
Update a node's configuration to match the public storage profile.
"""
config = self.xmpp['xep_0004'].Form()
config['type'] = 'submit'
for field, value in self.profile.items():
config.add_field(var=field, value=value)
return self.xmpp['xep_0060'].set_node_config(None, node, config,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def store(self, stanza, node=None, id=None, ifrom=None, options=None,
block=True, callback=None, timeout=None):
"""
Store private data via PEP.
This is just a (very) thin wrapper around the XEP-0060 publish()
method to set the defaults expected by PEP.
Arguments:
stanza -- The private content to store.
node -- The node to publish the content to. If not specified,
the stanza's namespace will be used.
id -- Optionally specify the ID of the item.
options -- Publish options to use, which will be modified to
fit the persistent storage option profile.
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.
"""
if not options:
options = self.xmpp['xep_0004'].stanza.Form()
options['type'] = 'submit'
options.add_field(
var='FORM_TYPE',
ftype='hidden',
value='http://jabber.org/protocol/pubsub#publish-options')
for field, value in self.profile.items():
if field not in options.fields:
options.add_field(var=field)
options.fields[field]['value'] = value
return self.xmpp['xep_0163'].publish(stanza, node,
options=options,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def retrieve(self, node, id=None, item_ids=None, ifrom=None,
block=True, callback=None, timeout=None):
"""
Retrieve private data via PEP.
This is just a (very) thin wrapper around the XEP-0060 publish()
method to set the defaults expected by PEP.
Arguments:
node -- The node to retrieve content from.
id -- Optionally specify the ID of the item.
item_ids -- Specify a group of IDs. If id is also specified, it
will be included in item_ids.
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.
"""
if item_ids is None:
item_ids = []
if id is not None:
item_ids.append(id)
return self.xmpp['xep_0060'].get_items(None, node,
item_ids=item_ids,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
register_plugin(XEP_0223)

View File

@@ -39,6 +39,11 @@ class XEP_0224(BasePlugin):
StanzaPath('message/attention'),
self._handle_attention))
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.Attention.namespace)
self.xmpp.remove_handler('Attention')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(stanza.Attention.namespace)
def request_attention(self, to, mfrom=None, mbody=''):

View File

@@ -1,6 +1,6 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz,
Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP.

View File

@@ -1,6 +1,6 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz,
Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP.
@@ -35,8 +35,6 @@ class XEP_0231(BasePlugin):
def plugin_init(self):
self._cids = {}
self.xmpp['xep_0030'].add_feature('urn:xmpp:bob')
register_stanza_plugin(Iq, BitsOfBinary)
self.xmpp.register_handler(
@@ -58,6 +56,14 @@ class XEP_0231(BasePlugin):
self.api.register(self._set_bob, 'set_bob', default=True)
self.api.register(self._del_bob, 'del_bob', default=True)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:bob')
self.xmpp.remove_handler('Bits of Binary - Iq')
self.xmpp.remove_handler('Bits of Binary - Message')
self.xmpp.remove_handler('Bits of Binary - Presence')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('urn:xmpp:bob')
def set_bob(self, data, mtype, cid=None, max_age=None):
if cid is None:
@@ -73,7 +79,7 @@ class XEP_0231(BasePlugin):
return cid
def get_bob(self, jid=None, cid=None, cached=True, ifrom=None,
def get_bob(self, jid=None, cid=None, cached=True, ifrom=None,
block=True, timeout=None, callback=None):
if cached:
data = self.api['get_bob'](None, None, ifrom, args=cid)
@@ -112,7 +118,7 @@ class XEP_0231(BasePlugin):
iq.send()
def _handle_bob(self, stanza):
self.api['set_bob'](stanza['from'], None,
self.api['set_bob'](stanza['from'], None,
stanza['to'], args=stanza['bob'])
self.xmpp.event('bob', stanza)

View File

@@ -1,6 +1,6 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz,
Copyright (C) 2012 Nathanael C. Fritz,
Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
This file is part of SleekXMPP.

View File

@@ -39,6 +39,11 @@ class XEP_0249(BasePlugin):
register_stanza_plugin(Message, Invite)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Invite.namespace)
self.xmpp.remove_handler('Direct MUC Invitations')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Invite.namespace)
def _handle_invite(self, msg):

View File

@@ -0,0 +1,72 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Presence
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.plugins import BasePlugin, register_plugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0012 import stanza, LastActivity
log = logging.getLogger(__name__)
class XEP_0256(BasePlugin):
name = 'xep_0256'
description = 'XEP-0256: Last Activity in Presence'
dependencies = set(['xep_0012'])
stanza = stanza
def plugin_init(self):
self.auto_last_activity = self.config.get('auto_last_activity', False)
register_stanza_plugin(Presence, LastActivity)
self.xmpp.add_filter('out', self._initial_presence_activity)
self.xmpp.add_event_handler('connected', self._reset_presence_activity)
self._initial_presence = set()
def plugin_end(self):
self.xmpp.del_filter('out', self._initial_presence_activity)
self.xmpp.del_event_handler('connected', self._reset_presence_activity)
def _reset_presence_activity(self, e):
self._initial_presence = set()
def _initial_presence_activity(self, stanza):
if isinstance(stanza, Presence):
use_last_activity = False
if self.auto_last_activity and stanza['show'] in ('xa', 'away'):
use_last_activity = True
if stanza['from'] not in self._initial_presence:
self._initial_presence.add(stanza['from'])
use_last_activity = True
if use_last_activity:
plugin = self.xmpp['xep_0012']
try:
result = plugin.api['get_last_activity'](stanza['from'],
None,
stanza['to'])
seconds = result['last_activity']['seconds']
except XMPPError:
seconds = None
if seconds is not None:
stanza['last_activity']['seconds'] = seconds
return stanza
register_plugin(XEP_0256)

View File

@@ -0,0 +1,18 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0258 import stanza
from sleekxmpp.plugins.xep_0258.stanza import SecurityLabel, Label
from sleekxmpp.plugins.xep_0258.stanza import DisplayMarking, EquivalentLabel
from sleekxmpp.plugins.xep_0258.stanza import ESSLabel, Catalog, CatalogItem
from sleekxmpp.plugins.xep_0258.security_labels import XEP_0258
register_plugin(XEP_0258)

View File

@@ -0,0 +1,44 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq, Message
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0258 import stanza, SecurityLabel, Catalog
log = logging.getLogger(__name__)
class XEP_0258(BasePlugin):
name = 'xep_0258'
description = 'XEP-0258: Security Labels in XMPP'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, SecurityLabel)
register_stanza_plugin(Iq, Catalog)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=SecurityLabel.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(SecurityLabel.namespace)
def get_catalog(self, jid, ifrom=None, block=True,
callback=None, timeout=None):
iq = self.xmpp.Iq()
iq['to'] = jid
iq['from'] = ifrom
iq['type'] = 'get'
iq.enable('security_label_catalog')
return iq.send(block=block, callback=callback, timeout=timeout)

View File

@@ -0,0 +1,142 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from base64 import b64encode, b64decode
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class SecurityLabel(ElementBase):
name = 'securitylabel'
namespace = 'urn:xmpp:sec-label:0'
plugin_attrib = 'security_label'
def add_equivalent(self, label):
equiv = EquivalentLabel(parent=self)
equiv.append(label)
return equiv
class Label(ElementBase):
name = 'label'
namespace = 'urn:xmpp:sec-label:0'
plugin_attrib = 'label'
class DisplayMarking(ElementBase):
name = 'displaymarking'
namespace = 'urn:xmpp:sec-label:0'
plugin_attrib = 'display_marking'
interfaces = set(['fgcolor', 'bgcolor', 'value'])
def get_fgcolor(self):
return self._get_attr('fgcolor', 'black')
def get_bgcolor(self):
return self._get_attr('fgcolor', 'white')
def get_value(self):
return self.xml.text
def set_value(self, value):
self.xml.text = value
def del_value(self):
self.xml.text = ''
class EquivalentLabel(ElementBase):
name = 'equivalentlabel'
namespace = 'urn:xmpp:sec-label:0'
plugin_attrib = 'equivalent_label'
plugin_multi_attrib = 'equivalent_labels'
class Catalog(ElementBase):
name = 'catalog'
namespace = 'urn:xmpp:sec-label:catalog:2'
plugin_attrib = 'security_label_catalog'
interfaces = set(['to', 'from', 'name', 'desc', 'id', 'size', 'restrict'])
def get_to(self):
return JID(self._get_attr('to'))
pass
def set_to(self, value):
return self._set_attr('to', str(value))
def get_from(self):
return JID(self._get_attr('from'))
def set_from(self, value):
return self._set_attr('from', str(value))
def get_restrict(self):
value = self._get_attr('restrict', '')
if value and value.lower() in ('true', '1'):
return True
return False
def set_restrict(self, value):
self._del_attr('restrict')
if value:
self._set_attr('restrict', 'true')
elif value is False:
self._set_attr('restrict', 'false')
class CatalogItem(ElementBase):
name = 'catalog'
namespace = 'urn:xmpp:sec-label:catalog:2'
plugin_attrib = 'item'
plugin_multi_attrib = 'items'
interfaces = set(['selector', 'default'])
def get_default(self):
value = self._get_attr('default', '')
if value.lower() in ('true', '1'):
return True
return False
def set_default(self, value):
self._del_attr('default')
if value:
self._set_attr('default', 'true')
elif value is False:
self._set_attr('default', 'false')
class ESSLabel(ElementBase):
name = 'esssecuritylabel'
namespace = 'urn:xmpp:sec-label:ess:0'
plugin_attrib = 'ess'
interfaces = set(['value'])
def get_value(self):
if self.xml.text:
return b64decode(bytes(self.xml.text))
return ''
def set_value(self, value):
self.xml.text = ''
if value:
self.xml.text = b64encode(bytes(value))
def del_value(self):
self.xml.text = ''
register_stanza_plugin(Catalog, CatalogItem, iterable=True)
register_stanza_plugin(CatalogItem, SecurityLabel)
register_stanza_plugin(EquivalentLabel, ESSLabel)
register_stanza_plugin(Label, ESSLabel)
register_stanza_plugin(SecurityLabel, DisplayMarking)
register_stanza_plugin(SecurityLabel, EquivalentLabel, iterable=True)
register_stanza_plugin(SecurityLabel, Label)

View File

@@ -0,0 +1,20 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins import BasePlugin, register_plugin
class XEP_0270(BasePlugin):
name = 'xep_0270'
description = 'XEP-0270: XMPP Compliance Suites 2010'
dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
'xep_0163', 'xep_0045', 'xep_0085'])
register_plugin(XEP_0270)

View File

@@ -0,0 +1,21 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins import BasePlugin, register_plugin
class XEP_0302(BasePlugin):
name = 'xep_0302'
description = 'XEP-0302: XMPP Compliance Suites 2012'
dependencies = set(['xep_0030', 'xep_0115', 'xep_0054',
'xep_0163', 'xep_0045', 'xep_0085',
'xep_0184', 'xep_0198'])
register_plugin(XEP_0302)

View File

@@ -307,34 +307,29 @@ class RosterItem(object):
p['from'] = self.owner
p.send()
def send_presence(self, ptype=None, pshow=None, pstatus=None,
ppriority=None, pnick=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
If no recipient is specified, send the presence immediately.
Otherwise, forward the send request to the recipient's roster
entry for processing.
Arguments:
pshow -- The presence's show value.
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pnick -- Optional nickname of the presence's sender.
"""
p = self.xmpp.make_presence(pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
ptype=ptype,
pnick=pnick,
pto=self.jid)
if self.xmpp.is_component:
p['from'] = self.owner
if p['type'] in p.showtypes or \
p['type'] in ['available', 'unavailable']:
self.last_status = p
p.send()
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.owner
if not kwargs.get('pto', ''):
kwargs['pto'] = self.jid
self.xmpp.send_presence(**kwargs)
def send_last_presence(self):
if self.last_status is None:

View File

@@ -6,9 +6,11 @@
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza import Presence
from sleekxmpp.xmlstream import JID
from sleekxmpp.roster import RosterNode
class Roster(object):
"""
@@ -55,6 +57,33 @@ class Roster(object):
for node in self.db.entries(None, {}):
self.add(node)
self.xmpp.add_filter('out', self._save_last_status)
def _save_last_status(self, stanza):
if isinstance(stanza, Presence):
sfrom = stanza['from'].full
sto = stanza['to'].full
if not sfrom:
sfrom = self.xmpp.boundjid
if stanza['type'] in stanza.showtypes or \
stanza['type'] in ('available', 'unavailable'):
if sto:
self[sfrom][sto].last_status = stanza
else:
self[sfrom].last_status = stanza
with self[sfrom]._last_status_lock:
for jid in self[sfrom]:
self[sfrom][jid].last_status = None
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
return stanza
def __getitem__(self, key):
"""
Return the roster node for a JID.
@@ -65,10 +94,12 @@ class Roster(object):
Arguments:
key -- Return the roster for this JID.
"""
if isinstance(key, JID):
key = key.bare
if key is None:
key = self.xmpp.boundjid.bare
key = self.xmpp.boundjid
if not isinstance(key, JID):
key = JID(key)
key = key.bare
if key not in self._rosters:
self.add(key)
self._rosters[key].auto_authorize = self.auto_authorize
@@ -90,8 +121,10 @@ class Roster(object):
Arguments:
node -- The JID for the new roster node.
"""
if isinstance(node, JID):
node = node.bare
if not isinstance(node, JID):
node = JID(node)
node = node.bare
if node not in self._rosters:
self._rosters[node] = RosterNode(self.xmpp, node, self.db)
@@ -121,29 +154,27 @@ class Roster(object):
for node in self:
self[node].reset()
def send_presence(self, pshow=None, pstatus=None, ppriority=None,
pto=None, pfrom=None, ptype=None, pnick=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
Forwards the send request to the appropriate roster to
perform the actual sending.
If no recipient is specified, send the presence immediately.
Otherwise, forward the send request to the recipient's roster
entry for processing.
Arguments:
pshow -- The presence's show value.
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pfrom -- The sender of the presence.
pnick -- Optional nickname of the presence's sender.
"""
self[pfrom].send_presence(ptype=ptype,
pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
pnick=pnick,
pto=pto)
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.jid
self.xmpp.send_presence(**kwargs)
@property
def auto_authorize(self):

View File

@@ -6,6 +6,8 @@
See the file LICENSE for copying permission.
"""
import threading
from sleekxmpp.xmlstream import JID
from sleekxmpp.roster import RosterItem
@@ -59,13 +61,14 @@ class RosterNode(object):
self.last_status = None
self._version = ''
self._jids = {}
self._last_status_lock = threading.Lock()
if self.db:
if hasattr(self.db, 'version'):
self._version = self.db.version(self.jid)
for jid in self.db.entries(self.jid):
self.add(jid)
@property
def version(self):
"""Retrieve the roster's version ID."""
@@ -86,8 +89,11 @@ class RosterNode(object):
A new item entry will be created if one does not already exist.
"""
if isinstance(key, JID):
key = key.bare
if key is None:
key = JID('')
if not isinstance(key, JID):
key = JID(key)
key = key.bare
if key not in self._jids:
self.add(key, save=True)
return self._jids[key]
@@ -98,8 +104,11 @@ class RosterNode(object):
To remove an item from the server, use the remove() method.
"""
if isinstance(key, JID):
key = key.bare
if key is None:
key = JID('')
if not isinstance(key, JID):
key = JID(key)
key = key.bare
if key in self._jids:
del self._jids[key]
@@ -146,7 +155,7 @@ class RosterNode(object):
self.db = db
existing_entries = set(self._jids)
new_entries = set(self.db.entries(self.jid, {}))
for jid in existing_entries:
self._jids[jid].set_backend(db, save)
for jid in new_entries - existing_entries:
@@ -291,8 +300,7 @@ class RosterNode(object):
for jid in self:
self[jid].reset()
def send_presence(self, ptype=None, pshow=None, pstatus=None,
ppriority=None, pnick=None, pto=None):
def send_presence(self, **kwargs):
"""
Create, initialize, and send a Presence stanza.
@@ -305,27 +313,14 @@ class RosterNode(object):
pstatus -- The presence's status message.
ppriority -- This connections' priority.
pto -- The recipient of a directed presence.
pfrom -- The sender of a directed presence, which should
be the owner JID plus resource.
ptype -- The type of presence, such as 'subscribe'.
pnick -- Optional nickname of the presence's sender.
"""
if pto:
self[pto].send_presence(ptype, pshow, pstatus,
ppriority, pnick)
else:
p = self.xmpp.make_presence(pshow=pshow,
pstatus=pstatus,
ppriority=ppriority,
ptype=ptype,
pnick=pnick)
if self.xmpp.is_component:
p['from'] = self.jid
if p['type'] in p.showtypes or \
p['type'] in ['available', 'unavailable']:
self.last_status = p
p.send()
if not self.xmpp.sentpresence:
self.xmpp.event('sent_presence')
self.xmpp.sentpresence = True
if self.xmpp.is_component and not kwargs.get('pfrom', ''):
kwargs['pfrom'] = self.jid
self.xmpp.send_presence(**kwargs)
def send_last_presence(self):
if self.last_status is None:

View File

@@ -51,7 +51,8 @@ class Error(ElementBase):
namespace = 'jabber:client'
name = 'error'
plugin_attrib = 'error'
interfaces = set(('code', 'condition', 'text', 'type'))
interfaces = set(('code', 'condition', 'text', 'type',
'gone', 'redirect'))
sub_interfaces = set(('text',))
plugin_attrib_map = {}
plugin_tag_map = {}
@@ -88,7 +89,7 @@ class Error(ElementBase):
def get_condition(self):
"""Return the condition element's name."""
for child in self.xml.getchildren():
for child in self.xml:
if "{%s}" % self.condition_ns in child.tag:
cond = child.tag.split('}', 1)[-1]
if cond in self.conditions:
@@ -109,7 +110,7 @@ class Error(ElementBase):
def del_condition(self):
"""Remove the condition element."""
for child in self.xml.getchildren():
for child in self.xml:
if "{%s}" % self.condition_ns in child.tag:
tag = child.tag.split('}', 1)[-1]
if tag in self.conditions:
@@ -135,6 +136,33 @@ class Error(ElementBase):
self._del_sub('{%s}text' % self.condition_ns)
return self
def get_gone(self):
return self._get_sub_text('{%s}gone' % self.condition_ns, '')
def get_redirect(self):
return self._get_sub_text('{%s}redirect' % self.condition_ns, '')
def set_gone(self, value):
if value:
del self['condition']
return self._set_sub_text('{%s}gone' % self.condition_ns, value)
elif self['condition'] == 'gone':
del self['condition']
def set_redirect(self, value):
if value:
del self['condition']
ns = self.condition_ns
return self._set_sub_text('{%s}redirect' % ns, value)
elif self['condition'] == 'redirect':
del self['condition']
def del_gone(self):
self._del_sub('{%s}gone' % self.condition_ns)
def del_redirect(self):
self._del_sub('{%s}redirect' % self.condition_ns)
# To comply with PEP8, method names now use underscores.
# Deprecated method names are re-mapped for backwards compatibility.

View File

@@ -122,7 +122,7 @@ class Iq(RootStanza):
def get_query(self):
"""Return the namespace of the <query> element."""
for child in self.xml.getchildren():
for child in self.xml:
if child.tag.endswith('query'):
ns = child.tag.split('}')[0]
if '{' in ns:
@@ -132,7 +132,7 @@ class Iq(RootStanza):
def del_query(self):
"""Remove the <query> element."""
for child in self.xml.getchildren():
for child in self.xml:
if child.tag.endswith('query'):
self.xml.remove(child)
return self

View File

@@ -7,7 +7,7 @@
"""
from sleekxmpp.stanza.rootstanza import RootStanza
from sleekxmpp.xmlstream import StanzaBase
from sleekxmpp.xmlstream import StanzaBase, ET
class Message(RootStanza):
@@ -54,13 +54,14 @@ class Message(RootStanza):
del_mucnick -- Dummy method to prevent deletion.
"""
namespace = 'jabber:client'
name = 'message'
interfaces = set(('type', 'to', 'from', 'id', 'body', 'subject',
'mucroom', 'mucnick'))
sub_interfaces = set(('body', 'subject'))
namespace = 'jabber:client'
plugin_attrib = name
types = set((None, 'normal', 'chat', 'headline', 'error', 'groupchat'))
interfaces = set(['type', 'to', 'from', 'id', 'body', 'subject',
'thread', 'parent_thread', 'mucroom', 'mucnick'])
sub_interfaces = set(['body', 'subject', 'thread'])
lang_interfaces = sub_interfaces
types = set(['normal', 'chat', 'headline', 'error', 'groupchat'])
def get_type(self):
"""
@@ -72,6 +73,31 @@ class Message(RootStanza):
"""
return self._get_attr('type', 'normal')
def get_parent_thread(self):
"""Return the message thread's parent thread."""
thread = self.xml.find('{%s}thread' % self.namespace)
if thread is not None:
return thread.attrib.get('parent', '')
return ''
def set_parent_thread(self, value):
"""Add or change the message thread's parent thread."""
thread = self.xml.find('{%s}thread' % self.namespace)
if value:
if thread is None:
thread = ET.Element('{%s}thread' % self.namespace)
self.xml.append(thread)
thread.attrib['parent'] = value
else:
if thread is not None and 'parent' in thread.attrib:
del thread.attrib['parent']
def del_parent_thread(self):
"""Delete the message thread's parent reference."""
thread = self.xml.find('{%s}thread' % self.namespace)
if thread is not None and 'parent' in thread.attrib:
del thread.attrib['parent']
def chat(self):
"""Set the message type to 'chat'."""
self['type'] = 'chat'
@@ -96,10 +122,16 @@ class Message(RootStanza):
clear -- Indicates if existing content should be removed
before replying. Defaults to True.
"""
thread = self['thread']
parent = self['parent_thread']
StanzaBase.reply(self, clear)
if self['type'] == 'groupchat':
self['to'] = self['to'].bare
self['thread'] = thread
self['parent_thread'] = parent
del self['id']
if body is not None:

View File

@@ -60,16 +60,17 @@ class Presence(RootStanza):
set_priority -- Set the value of the <priority> element.
"""
namespace = 'jabber:client'
name = 'presence'
interfaces = set(('type', 'to', 'from', 'id', 'show',
'status', 'priority'))
sub_interfaces = set(('show', 'status', 'priority'))
namespace = 'jabber:client'
plugin_attrib = name
interfaces = set(['type', 'to', 'from', 'id', 'show',
'status', 'priority'])
sub_interfaces = set(['show', 'status', 'priority'])
lang_interfaces = set(['status'])
types = set(('available', 'unavailable', 'error', 'probe', 'subscribe',
'subscribed', 'unsubscribe', 'unsubscribed'))
showtypes = set(('dnd', 'chat', 'xa', 'away'))
types = set(['available', 'unavailable', 'error', 'probe', 'subscribe',
'subscribed', 'unsubscribe', 'unsubscribed'])
showtypes = set(['dnd', 'chat', 'xa', 'away'])
def exception(self, e):
"""

View File

@@ -78,7 +78,8 @@ class RootStanza(StanzaBase):
self['error']['type'] = 'cancel'
self.send()
# log the error
log.exception('Error handling {%s}%s stanza' , self.namespace, self.name)
log.exception('Error handling {%s}%s stanza',
self.namespace, self.name)
# Finally raise the exception to a global exception handler
self.stream.exception(e)

View File

@@ -47,7 +47,7 @@ class Roster(ElementBase):
roster versioning.
"""
return self.xml.attrib.get('ver', None)
def set_ver(self, ver):
"""
Ensure handling an empty ver attribute propery.
@@ -101,7 +101,8 @@ class Roster(ElementBase):
items[item['jid']] = item.values
# Remove extra JID reference to keep everything
# backward compatible
del items[item['jid']]['jid']
del items[item['jid']]['jid']
del items[item['jid']]['lang']
return items
def del_items(self):

View File

@@ -54,7 +54,7 @@ class StreamError(Error, StanzaBase):
"""
namespace = 'http://etherx.jabber.org/streams'
interfaces = set(('condition', 'text'))
interfaces = set(('condition', 'text', 'see_other_host'))
conditions = set((
'bad-format', 'bad-namespace-prefix', 'conflict',
'connection-timeout', 'host-gone', 'host-unknown',
@@ -66,3 +66,18 @@ class StreamError(Error, StanzaBase):
'unsupported-feature', 'unsupported-stanza-type',
'unsupported-version'))
condition_ns = 'urn:ietf:params:xml:ns:xmpp-streams'
def get_see_other_host(self):
ns = self.condition_ns
return self._get_sub_text('{%s}see-other-host' % ns, '')
def set_see_other_host(self, value):
if value:
del self['condition']
ns = self.condition_ns
return self._set_sub_text('{%s}see-other-host' % ns, value)
elif self['condition'] == 'see-other-host':
del self['condition']
def del_see_other_host(self):
self._del_sub('{%s}see-other-host' % self.condition_ns)

Some files were not shown because too many files have changed in this diff Show More