Compare commits
110 Commits
sleek-1.1.
...
sleek-1.1.
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e4b4c67637 | ||
![]() |
422e77ae40 | ||
![]() |
5ae6c8f8fa | ||
![]() |
54656b331a | ||
![]() |
9047b627a4 | ||
![]() |
6645a3be40 | ||
![]() |
e3fab66dfb | ||
![]() |
5867f08bf1 | ||
![]() |
a06fa2de67 | ||
![]() |
35396d2977 | ||
![]() |
3bff743d9f | ||
![]() |
5a878f829b | ||
![]() |
26dc6e90ea | ||
![]() |
94c749fd5a | ||
![]() |
7b80ed0807 | ||
![]() |
98b7e8b10a | ||
![]() |
9d8de7fc15 | ||
![]() |
70883086b7 | ||
![]() |
9a08dfc7d4 | ||
![]() |
3e43b36a9d | ||
![]() |
352ee2f2fd | ||
![]() |
78aa5c3dfa | ||
![]() |
613323b5fb | ||
![]() |
6c4b01db8a | ||
![]() |
d06897a635 | ||
![]() |
1600bb0aaf | ||
![]() |
b5c9c98a8b | ||
![]() |
e4e18a416f | ||
![]() |
01cc0e6def | ||
![]() |
d571d691a7 | ||
![]() |
fb221a8dc0 | ||
![]() |
459e1ed345 | ||
![]() |
6680c244f5 | ||
![]() |
06423964ec | ||
![]() |
474390fa00 | ||
![]() |
81d3723084 | ||
![]() |
32e798967e | ||
![]() |
acd9c32a9f | ||
![]() |
b8581b0278 | ||
![]() |
917faecdcb | ||
![]() |
f6edaa56a6 | ||
![]() |
51fee28bf4 | ||
![]() |
e8a3e92ceb | ||
![]() |
5df3839b7a | ||
![]() |
8dcb441f44 | ||
![]() |
a347cf625a | ||
![]() |
46f49c7a12 | ||
![]() |
99701c947e | ||
![]() |
1baae1b81e | ||
![]() |
7d20f0e9a6 | ||
![]() |
fbad22a1cd | ||
![]() |
5af2f62c04 | ||
![]() |
4a4a03858e | ||
![]() |
6819b57353 | ||
![]() |
88b5e60807 | ||
![]() |
a26a8bd79c | ||
![]() |
9307a6915f | ||
![]() |
85ef2d8d0b | ||
![]() |
c2c7cc032b | ||
![]() |
e4911e9391 | ||
![]() |
b11e1ee92d | ||
![]() |
5027d00c10 | ||
![]() |
69ddeceb49 | ||
![]() |
82698672bb | ||
![]() |
9cec284947 | ||
![]() |
dc501d1902 | ||
![]() |
100e504b7f | ||
![]() |
8a745c5e81 | ||
![]() |
bf0a157c5d | ||
![]() |
f49818be06 | ||
![]() |
1ad171dfe5 | ||
![]() |
2a78570d65 | ||
![]() |
7a112f2523 | ||
![]() |
e86444e5fb | ||
![]() |
36c11ad9de | ||
![]() |
019a4b20ae | ||
![]() |
433ee08687 | ||
![]() |
7858d969d8 | ||
![]() |
8119551049 | ||
![]() |
061489f03a | ||
![]() |
d92aa05b5c | ||
![]() |
f7a74d960e | ||
![]() |
95a0e51b41 | ||
![]() |
110e45e187 | ||
![]() |
534aaf2b2a | ||
![]() |
4cc20fdd05 | ||
![]() |
f3fae192a8 | ||
![]() |
7d59a8a0ad | ||
![]() |
8da387a38a | ||
![]() |
ff6fc44215 | ||
![]() |
62391a895a | ||
![]() |
9bcdd7d18f | ||
![]() |
5c4f7bfe8b | ||
![]() |
0b7f134021 | ||
![]() |
378a42889f | ||
![]() |
f824950552 | ||
![]() |
3d2d11f169 | ||
![]() |
181aea737d | ||
![]() |
ee702f4071 | ||
![]() |
a08c2161a7 | ||
![]() |
0e36a01354 | ||
![]() |
c39ad7dfbb | ||
![]() |
b92ae706e9 | ||
![]() |
6997261c6b | ||
![]() |
6cfb5cb14c | ||
![]() |
8567d6034f | ||
![]() |
e06368f8cd | ||
![]() |
4b37a4706f | ||
![]() |
7b1564947d | ||
![]() |
f5652a667b |
@@ -45,13 +45,11 @@ The latest source code for SleekXMPP may be found on `Github
|
||||
``develop`` branch.
|
||||
|
||||
**Latest Release**
|
||||
- `1.1.2 <http://github.com/fritzy/SleekXMPP/zipball/1.1.2>`_
|
||||
- `1.1.10 <http://github.com/fritzy/SleekXMPP/zipball/1.1.10>`_
|
||||
|
||||
**Develop Releases**
|
||||
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_
|
||||
|
||||
**Older Stable Releases**
|
||||
- `1.0 <http://github.com/fritzy/SleekXMPP/zipball/1.0>`_
|
||||
|
||||
Installing DNSPython
|
||||
---------------------
|
||||
@@ -74,6 +72,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``.
|
||||
|
178
examples/admin_commands.py
Executable file
178
examples/admin_commands.py
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/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 AdminCommands(sleekxmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A simple SleekXMPP bot that uses admin commands to
|
||||
add a new user to a server.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, command):
|
||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.command = command
|
||||
|
||||
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()
|
||||
|
||||
def command_success(iq, session):
|
||||
print('Command completed')
|
||||
if iq['command']['form']:
|
||||
for var, field in iq['command']['form']['fields'].items():
|
||||
print('%s: %s' % (var, field['value']))
|
||||
if iq['command']['notes']:
|
||||
print('Command Notes:')
|
||||
for note in iq['command']['notes']:
|
||||
print('%s: %s' % note)
|
||||
self.disconnect()
|
||||
|
||||
def command_error(iq, session):
|
||||
print('Error completing command')
|
||||
print('%s: %s' % (iq['error']['condition'],
|
||||
iq['error']['text']))
|
||||
self['xep_0050'].terminate_command(session)
|
||||
self.disconnect()
|
||||
|
||||
def process_form(iq, session):
|
||||
form = iq['command']['form']
|
||||
answers = {}
|
||||
for var, field in form['fields'].items():
|
||||
if var != 'FORM_TYPE':
|
||||
if field['type'] == 'boolean':
|
||||
answers[var] = raw_input('%s (y/n): ' % field['label'])
|
||||
if answers[var].lower() in ('1', 'true', 'y', 'yes'):
|
||||
answers[var] = '1'
|
||||
else:
|
||||
answers[var] = '0'
|
||||
else:
|
||||
answers[var] = raw_input('%s: ' % field['label'])
|
||||
else:
|
||||
answers['FORM_TYPE'] = field['value']
|
||||
form['type'] = 'submit'
|
||||
form['values'] = answers
|
||||
|
||||
session['next'] = command_success
|
||||
session['payload'] = form
|
||||
|
||||
self['xep_0050'].complete_command(session)
|
||||
|
||||
session = {'next': process_form,
|
||||
'error': command_error}
|
||||
|
||||
command = self.command.replace('-', '_')
|
||||
handler = getattr(self['xep_0133'], command, None)
|
||||
|
||||
if handler:
|
||||
handler(session={
|
||||
'next': process_form,
|
||||
'error': command_error
|
||||
})
|
||||
else:
|
||||
print('Invalid command name: %s' % self.command)
|
||||
self.disconnect()
|
||||
|
||||
|
||||
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("-c", "--command", dest="command",
|
||||
help="admin command 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.command is None:
|
||||
opts.command = raw_input("Admin command: ")
|
||||
|
||||
# Setup the CommandBot and register plugins. Note that while plugins may
|
||||
# have interdependencies, the order in which you register them does
|
||||
# not matter.
|
||||
xmpp = AdminCommands(opts.jid, opts.password, opts.command)
|
||||
xmpp.register_plugin('xep_0133') # Service Administration
|
||||
|
||||
# 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.")
|
184
examples/download_avatars.py
Normal file
184
examples/download_avatars.py
Normal file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import getpass
|
||||
import threading
|
||||
from optparse import OptionParser
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
FILE_TYPES = {
|
||||
'image/png': 'png',
|
||||
'image/gif': 'gif',
|
||||
'image/jpeg': 'jpg'
|
||||
}
|
||||
|
||||
|
||||
class AvatarDownloader(sleekxmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic script for downloading the avatars for a user's contacts.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password):
|
||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||
self.add_event_handler("session_start", self.start, threaded=True)
|
||||
self.add_event_handler("changed_status", self.wait_for_presences)
|
||||
|
||||
self.add_event_handler('vcard_avatar_update', self.on_vcard_avatar)
|
||||
self.add_event_handler('avatar_metadata_publish', self.on_avatar)
|
||||
|
||||
self.received = set()
|
||||
self.presences_received = threading.Event()
|
||||
|
||||
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()
|
||||
|
||||
print('Waiting for presence updates...\n')
|
||||
self.presences_received.wait(15)
|
||||
self.disconnect(wait=True)
|
||||
|
||||
def on_vcard_avatar(self, pres):
|
||||
print("Received vCard avatar update from %s" % pres['from'].bare)
|
||||
try:
|
||||
result = self['xep_0054'].get_vcard(pres['from'], cached=True)
|
||||
except XMPPError:
|
||||
print("Error retrieving avatar for %s" % pres['from'])
|
||||
return
|
||||
avatar = result['vcard_temp']['PHOTO']
|
||||
|
||||
filetype = FILE_TYPES.get(avatar['TYPE'], 'png')
|
||||
filename = 'vcard_avatar_%s_%s.%s' % (
|
||||
pres['from'].bare,
|
||||
pres['vcard_temp_update']['photo'],
|
||||
filetype)
|
||||
with open(filename, 'w+') as img:
|
||||
img.write(avatar['BINVAL'])
|
||||
|
||||
def on_avatar(self, msg):
|
||||
print("Received avatar update from %s" % msg['from'])
|
||||
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
||||
for info in metadata['items']:
|
||||
if not info['url']:
|
||||
try:
|
||||
result = self['xep_0084'].retrieve_avatar(msg['from'], info['id'])
|
||||
except XMPPError:
|
||||
print("Error retrieving avatar for %s" % msg['from'])
|
||||
return
|
||||
|
||||
avatar = result['pubsub']['items']['item']['avatar_data']
|
||||
|
||||
filetype = FILE_TYPES.get(metadata['type'], 'png')
|
||||
filename = 'avatar_%s_%s.%s' % (msg['from'].bare, info['id'], filetype)
|
||||
with open(filename, 'w+') as img:
|
||||
img.write(avatar['value'])
|
||||
else:
|
||||
# We could retrieve the avatar via HTTP, etc here instead.
|
||||
pass
|
||||
|
||||
def wait_for_presences(self, pres):
|
||||
"""
|
||||
Wait to receive updates from all roster contacts.
|
||||
"""
|
||||
self.received.add(pres['from'].bare)
|
||||
if len(self.received) >= len(self.client_roster.keys()):
|
||||
self.presences_received.set()
|
||||
else:
|
||||
self.presences_received.clear()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
optp = OptionParser()
|
||||
optp.add_option('-q','--quiet', help='set logging to ERROR',
|
||||
action='store_const',
|
||||
dest='loglevel',
|
||||
const=logging.ERROR,
|
||||
default=logging.ERROR)
|
||||
optp.add_option('-d','--debug', help='set logging to DEBUG',
|
||||
action='store_const',
|
||||
dest='loglevel',
|
||||
const=logging.DEBUG,
|
||||
default=logging.ERROR)
|
||||
optp.add_option('-v','--verbose', help='set logging to COMM',
|
||||
action='store_const',
|
||||
dest='loglevel',
|
||||
const=5,
|
||||
default=logging.ERROR)
|
||||
|
||||
# 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 = AvatarDownloader(opts.jid, opts.password)
|
||||
xmpp.register_plugin('xep_0054')
|
||||
xmpp.register_plugin('xep_0153')
|
||||
xmpp.register_plugin('xep_0084')
|
||||
|
||||
# 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)
|
||||
else:
|
||||
print("Unable to connect.")
|
@@ -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
165
examples/gtalk_custom_domain.py
Executable 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.")
|
149
examples/ibb_transfer/ibb_receiver.py
Executable file
149
examples/ibb_transfer/ibb_receiver.py
Executable 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.")
|
145
examples/ibb_transfer/ibb_sender.py
Executable file
145
examples/ibb_transfer/ibb_sender.py
Executable 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.")
|
174
examples/set_avatar.py
Normal file
174
examples/set_avatar.py
Normal file
@@ -0,0 +1,174 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2012 Nathanael C. Fritz
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import imghdr
|
||||
import logging
|
||||
import getpass
|
||||
import threading
|
||||
from optparse import OptionParser
|
||||
|
||||
import sleekxmpp
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
|
||||
|
||||
# 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 AvatarSetter(sleekxmpp.ClientXMPP):
|
||||
|
||||
"""
|
||||
A basic script for downloading the avatars for a user's contacts.
|
||||
"""
|
||||
|
||||
def __init__(self, jid, password, filepath):
|
||||
sleekxmpp.ClientXMPP.__init__(self, jid, password)
|
||||
|
||||
self.add_event_handler("session_start", self.start, threaded=True)
|
||||
|
||||
self.filepath = filepath
|
||||
|
||||
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()
|
||||
|
||||
avatar_file = None
|
||||
try:
|
||||
avatar_file = open(os.path.expanduser(self.filepath))
|
||||
except IOError:
|
||||
print('Could not find file: %s' % self.filepath)
|
||||
return self.disconnect()
|
||||
|
||||
avatar = avatar_file.read()
|
||||
|
||||
avatar_type = 'image/%s' % imghdr.what('', avatar)
|
||||
avatar_id = self['xep_0084'].generate_id(avatar)
|
||||
avatar_bytes = len(avatar)
|
||||
|
||||
avatar_file.close()
|
||||
|
||||
used_xep84 = False
|
||||
try:
|
||||
print('Publish XEP-0084 avatar data')
|
||||
self['xep_0084'].publish_avatar(avatar)
|
||||
used_xep84 = True
|
||||
except XMPPError:
|
||||
print('Could not publish XEP-0084 avatar')
|
||||
|
||||
try:
|
||||
print('Update vCard with avatar')
|
||||
self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||
except XMPPError:
|
||||
print('Could not set vCard avatar')
|
||||
|
||||
if used_xep84:
|
||||
try:
|
||||
print('Advertise XEP-0084 avatar metadata')
|
||||
self['xep_0084'].publish_avatar_metadata([
|
||||
{'id': avatar_id,
|
||||
'type': avatar_type,
|
||||
'bytes': avatar_bytes}
|
||||
# We could advertise multiple avatars to provide
|
||||
# options in image type, source (HTTP vs pubsub),
|
||||
# size, etc.
|
||||
# {'id': ....}
|
||||
])
|
||||
except XMPPError:
|
||||
print('Could not publish XEP-0084 metadata')
|
||||
|
||||
print('Wait for presence updates to propagate...')
|
||||
self.schedule('end', 5, self.disconnect, kwargs={'wait': True})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Setup the command line arguments.
|
||||
optp = OptionParser()
|
||||
optp.add_option('-q','--quiet', help='set logging to ERROR',
|
||||
action='store_const',
|
||||
dest='loglevel',
|
||||
const=logging.ERROR,
|
||||
default=logging.ERROR)
|
||||
optp.add_option('-d','--debug', help='set logging to DEBUG',
|
||||
action='store_const',
|
||||
dest='loglevel',
|
||||
const=logging.DEBUG,
|
||||
default=logging.ERROR)
|
||||
optp.add_option('-v','--verbose', help='set logging to COMM',
|
||||
action='store_const',
|
||||
dest='loglevel',
|
||||
const=5,
|
||||
default=logging.ERROR)
|
||||
|
||||
# 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("-f", "--file", dest="filepath",
|
||||
help="path to the avatar file")
|
||||
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.filepath is None:
|
||||
opts.filepath = raw_input("Avatar file location: ")
|
||||
|
||||
xmpp = AvatarSetter(opts.jid, opts.password, opts.filepath)
|
||||
xmpp.register_plugin('xep_0054')
|
||||
xmpp.register_plugin('xep_0153')
|
||||
xmpp.register_plugin('xep_0084')
|
||||
|
||||
# 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)
|
||||
else:
|
||||
print("Unable to connect.")
|
8
setup.py
8
setup.py
@@ -49,6 +49,7 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/stanza',
|
||||
'sleekxmpp/test',
|
||||
'sleekxmpp/roster',
|
||||
'sleekxmpp/util',
|
||||
'sleekxmpp/xmlstream',
|
||||
'sleekxmpp/xmlstream/matcher',
|
||||
'sleekxmpp/xmlstream/handler',
|
||||
@@ -61,6 +62,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 +73,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',
|
||||
@@ -79,16 +82,21 @@ packages = [ 'sleekxmpp',
|
||||
'sleekxmpp/plugins/xep_0115',
|
||||
'sleekxmpp/plugins/xep_0118',
|
||||
'sleekxmpp/plugins/xep_0128',
|
||||
'sleekxmpp/plugins/xep_0131',
|
||||
'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',
|
||||
|
@@ -10,6 +10,7 @@ from sleekxmpp.basexmpp import BaseXMPP
|
||||
from sleekxmpp.clientxmpp import ClientXMPP
|
||||
from sleekxmpp.componentxmpp import ComponentXMPP
|
||||
from sleekxmpp.stanza import Message, Presence, Iq
|
||||
from sleekxmpp.jid import JID, InvalidJID
|
||||
from sleekxmpp.xmlstream.handler import *
|
||||
from sleekxmpp.xmlstream import XMLStream, RestartStream
|
||||
from sleekxmpp.xmlstream.matcher import *
|
||||
|
@@ -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,10 +99,12 @@ 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):
|
||||
elif jid and not isinstance(jid, JID):
|
||||
jid = JID(jid)
|
||||
elif jid == JID(''):
|
||||
jid = self.xmpp.boundjid
|
||||
|
||||
if node is None:
|
||||
node = ''
|
||||
@@ -113,7 +115,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 +140,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.
|
||||
|
@@ -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,9 +68,17 @@ 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._redirect_attempts = 0
|
||||
|
||||
#: The maximum number of consecutive see-other-host
|
||||
#: redirections that will be followed before quitting.
|
||||
self.max_redirects = 5
|
||||
|
||||
self.session_bind_event = threading.Event()
|
||||
|
||||
#: A dictionary mapping plugin names to plugins.
|
||||
self.plugin = PluginManager(self)
|
||||
@@ -86,13 +96,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 +112,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,11 +143,14 @@ 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),
|
||||
self._handle_stream_error))
|
||||
|
||||
self.add_event_handler('session_start',
|
||||
self._handle_session_start)
|
||||
self.add_event_handler('disconnected',
|
||||
self._handle_disconnected)
|
||||
self.add_event_handler('presence_available',
|
||||
@@ -180,6 +193,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 +214,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 +287,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 +297,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 +348,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 +378,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 +408,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 +434,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 +467,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 +493,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 +517,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 +537,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 +548,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 +563,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):
|
||||
@@ -593,7 +598,7 @@ class BaseXMPP(XMLStream):
|
||||
|
||||
@resource.setter
|
||||
def resource(self, value):
|
||||
log.warning("fulljid property deprecated. Use boundjid.full")
|
||||
log.warning("fulljid property deprecated. Use boundjid.resource")
|
||||
self.boundjid.resource = value
|
||||
|
||||
@property
|
||||
@@ -658,30 +663,61 @@ class BaseXMPP(XMLStream):
|
||||
def getjidbare(self, fulljid):
|
||||
return fulljid.split('/', 1)[0]
|
||||
|
||||
def _handle_session_start(self, event):
|
||||
"""Reset redirection attempt count."""
|
||||
self._redirect_attempts = 0
|
||||
|
||||
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']
|
||||
if not other_host:
|
||||
log.warning("No other host specified.")
|
||||
return
|
||||
|
||||
if self._redirect_attempts > self.max_redirects:
|
||||
log.error("Exceeded maximum number of redirection attempts.")
|
||||
return
|
||||
|
||||
self._redirect_attempts += 1
|
||||
|
||||
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 +729,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 +740,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 +763,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 +776,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.
|
||||
|
||||
|
@@ -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,8 +173,13 @@ class ClientXMPP(BaseXMPP):
|
||||
self._stream_feature_order.append((order, name))
|
||||
self._stream_feature_order.sort()
|
||||
|
||||
def update_roster(self, jid, name=None, subscription=None, groups=[],
|
||||
block=True, timeout=None, callback=None):
|
||||
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, **kwargs):
|
||||
"""Add or change a roster item.
|
||||
|
||||
:param jid: The JID of the entry to modify.
|
||||
@@ -186,18 +194,28 @@ 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.
|
||||
Implies ``block=False``.
|
||||
"""
|
||||
current = self.client_roster[jid]
|
||||
|
||||
name = kwargs.get('name', current['name'])
|
||||
subscription = kwargs.get('subscription', current['subscription'])
|
||||
groups = kwargs.get('groups', current['groups'])
|
||||
|
||||
block = kwargs.get('block', True)
|
||||
timeout = kwargs.get('timeout', None)
|
||||
callback = kwargs.get('callback', None)
|
||||
|
||||
return self.client_roster.update(jid, name, subscription, groups,
|
||||
block, timeout, callback)
|
||||
|
||||
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 +230,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 +248,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 +285,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 +295,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',
|
||||
|
@@ -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.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)
|
||||
|
@@ -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):
|
||||
|
||||
"""
|
||||
|
@@ -7,9 +7,9 @@
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
'feature_starttls',
|
||||
'feature_mechanisms',
|
||||
'feature_bind',
|
||||
'feature_starttls',
|
||||
'feature_mechanisms',
|
||||
'feature_bind',
|
||||
'feature_session',
|
||||
'feature_rosterver'
|
||||
]
|
||||
|
@@ -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')
|
||||
|
||||
|
@@ -29,10 +29,13 @@ class FeatureMechanisms(BasePlugin):
|
||||
description = 'RFC 6120: Stream Feature: SASL'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'use_mech': None,
|
||||
'sasl_callback': None,
|
||||
'order': 100
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
self.use_mech = self.config.get('use_mech', None)
|
||||
|
||||
if not self.use_mech and not self.xmpp.boundjid.user:
|
||||
self.use_mech = 'ANONYMOUS'
|
||||
|
||||
@@ -53,15 +56,14 @@ class FeatureMechanisms(BasePlugin):
|
||||
values[value] = creds[value]
|
||||
mech.fulfill(values)
|
||||
|
||||
sasl_callback = self.config.get('sasl_callback', None)
|
||||
if sasl_callback is None:
|
||||
sasl_callback = basic_callback
|
||||
if self.sasl_callback is None:
|
||||
self.sasl_callback = basic_callback
|
||||
|
||||
self.mech = None
|
||||
self.sasl = suelta.SASL(self.xmpp.boundjid.domain, 'xmpp',
|
||||
username=self.xmpp.boundjid.user,
|
||||
sec_query=suelta.sec_query_allow,
|
||||
request_values=sasl_callback,
|
||||
request_values=self.sasl_callback,
|
||||
tls_active=tls_active,
|
||||
mech=self.use_mech)
|
||||
|
||||
@@ -95,7 +97,7 @@ class FeatureMechanisms(BasePlugin):
|
||||
self.xmpp.register_feature('mechanisms',
|
||||
self._handle_sasl_auth,
|
||||
restart=True,
|
||||
order=self.config.get('order', 100))
|
||||
order=self.order)
|
||||
|
||||
def _handle_sasl_auth(self, features):
|
||||
"""
|
||||
|
@@ -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'])
|
||||
|
||||
|
@@ -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:
|
||||
|
543
sleekxmpp/jid.py
Normal file
543
sleekxmpp/jid.py
Normal file
@@ -0,0 +1,543 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
sleekxmpp.jid
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module allows for working with Jabber IDs (JIDs).
|
||||
|
||||
Part of SleekXMPP: The Sleek XMPP Library
|
||||
|
||||
:copyright: (c) 2011 Nathanael C. Fritz
|
||||
:license: MIT, see LICENSE for more details
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import socket
|
||||
import stringprep
|
||||
import encodings.idna
|
||||
|
||||
from sleekxmpp.util import stringprep_profiles
|
||||
|
||||
#: These characters are not allowed to appear in a JID.
|
||||
ILLEGAL_CHARS = '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r' + \
|
||||
'\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19' + \
|
||||
'\x1a\x1b\x1c\x1d\x1e\x1f' + \
|
||||
' !"#$%&\'()*+,./:;<=>?@[\\]^_`{|}~\x7f'
|
||||
|
||||
#: The basic regex pattern that a JID must match in order to determine
|
||||
#: the local, domain, and resource parts. This regex does NOT do any
|
||||
#: validation, which requires application of nodeprep, resourceprep, etc.
|
||||
JID_PATTERN = re.compile(
|
||||
"^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$"
|
||||
)
|
||||
|
||||
#: The set of escape sequences for the characters not allowed by nodeprep.
|
||||
JID_ESCAPE_SEQUENCES = set(['\\20', '\\22', '\\26', '\\27', '\\2f',
|
||||
'\\3a', '\\3c', '\\3e', '\\40', '\\5c'])
|
||||
|
||||
#: A mapping of unallowed characters to their escape sequences. An escape
|
||||
#: sequence for '\' is also included since it must also be escaped in
|
||||
#: certain situations.
|
||||
JID_ESCAPE_TRANSFORMATIONS = {' ': '\\20',
|
||||
'"': '\\22',
|
||||
'&': '\\26',
|
||||
"'": '\\27',
|
||||
'/': '\\2f',
|
||||
':': '\\3a',
|
||||
'<': '\\3c',
|
||||
'>': '\\3e',
|
||||
'@': '\\40',
|
||||
'\\': '\\5c'}
|
||||
|
||||
#: The reverse mapping of escape sequences to their original forms.
|
||||
JID_UNESCAPE_TRANSFORMATIONS = {'\\20': ' ',
|
||||
'\\22': '"',
|
||||
'\\26': '&',
|
||||
'\\27': "'",
|
||||
'\\2f': '/',
|
||||
'\\3a': ':',
|
||||
'\\3c': '<',
|
||||
'\\3e': '>',
|
||||
'\\40': '@',
|
||||
'\\5c': '\\'}
|
||||
|
||||
|
||||
# pylint: disable=c0103
|
||||
#: The nodeprep profile of stringprep used to validate the local,
|
||||
#: or username, portion of a JID.
|
||||
nodeprep = stringprep_profiles.create(
|
||||
nfkc=True,
|
||||
bidi=True,
|
||||
mappings=[
|
||||
stringprep_profiles.b1_mapping,
|
||||
stringprep_profiles.c12_mapping],
|
||||
prohibited=[
|
||||
stringprep.in_table_c11,
|
||||
stringprep.in_table_c12,
|
||||
stringprep.in_table_c21,
|
||||
stringprep.in_table_c22,
|
||||
stringprep.in_table_c3,
|
||||
stringprep.in_table_c4,
|
||||
stringprep.in_table_c5,
|
||||
stringprep.in_table_c6,
|
||||
stringprep.in_table_c7,
|
||||
stringprep.in_table_c8,
|
||||
stringprep.in_table_c9,
|
||||
lambda c: c in ' \'"&/:<>@'],
|
||||
unassigned=[stringprep.in_table_a1])
|
||||
|
||||
# pylint: disable=c0103
|
||||
#: The resourceprep profile of stringprep, which is used to validate
|
||||
#: the resource portion of a JID.
|
||||
resourceprep = stringprep_profiles.create(
|
||||
nfkc=True,
|
||||
bidi=True,
|
||||
mappings=[stringprep_profiles.b1_mapping],
|
||||
prohibited=[
|
||||
stringprep.in_table_c12,
|
||||
stringprep.in_table_c21,
|
||||
stringprep.in_table_c22,
|
||||
stringprep.in_table_c3,
|
||||
stringprep.in_table_c4,
|
||||
stringprep.in_table_c5,
|
||||
stringprep.in_table_c6,
|
||||
stringprep.in_table_c7,
|
||||
stringprep.in_table_c8,
|
||||
stringprep.in_table_c9],
|
||||
unassigned=[stringprep.in_table_a1])
|
||||
|
||||
|
||||
def _parse_jid(data):
|
||||
"""
|
||||
Parse string data into the node, domain, and resource
|
||||
components of a JID, if possible.
|
||||
|
||||
:param string data: A string that is potentially a JID.
|
||||
|
||||
:raises InvalidJID:
|
||||
|
||||
:returns: tuple of the validated local, domain, and resource strings
|
||||
"""
|
||||
match = JID_PATTERN.match(data)
|
||||
if not match:
|
||||
raise InvalidJID('JID could not be parsed')
|
||||
|
||||
(node, domain, resource) = match.groups()
|
||||
|
||||
node = _validate_node(node)
|
||||
domain = _validate_domain(domain)
|
||||
resource = _validate_resource(resource)
|
||||
|
||||
return node, domain, resource
|
||||
|
||||
|
||||
def _validate_node(node):
|
||||
"""Validate the local, or username, portion of a JID.
|
||||
|
||||
:raises InvalidJID:
|
||||
|
||||
:returns: The local portion of a JID, as validated by nodeprep.
|
||||
"""
|
||||
try:
|
||||
if node is not None:
|
||||
node = nodeprep(node)
|
||||
|
||||
if not node:
|
||||
raise InvalidJID('Localpart must not be 0 bytes')
|
||||
if len(node) > 1023:
|
||||
raise InvalidJID('Localpart must be less than 1024 bytes')
|
||||
return node
|
||||
except stringprep_profiles.StringPrepError:
|
||||
raise InvalidJID('Invalid local part')
|
||||
|
||||
|
||||
def _validate_domain(domain):
|
||||
"""Validate the domain portion of a JID.
|
||||
|
||||
IP literal addresses are left as-is, if valid. Domain names
|
||||
are stripped of any trailing label separators (`.`), and are
|
||||
checked with the nameprep profile of stringprep. If the given
|
||||
domain is actually a punyencoded version of a domain name, it
|
||||
is converted back into its original Unicode form. Domains must
|
||||
also not start or end with a dash (`-`).
|
||||
|
||||
:raises InvalidJID:
|
||||
|
||||
:returns: The validated domain name
|
||||
"""
|
||||
ip_addr = False
|
||||
|
||||
# First, check if this is an IPv4 address
|
||||
try:
|
||||
socket.inet_aton(domain)
|
||||
ip_addr = True
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
# Check if this is an IPv6 address
|
||||
if not ip_addr and hasattr(socket, 'inet_pton'):
|
||||
try:
|
||||
socket.inet_pton(socket.AF_INET6, domain.strip('[]'))
|
||||
domain = '[%s]' % domain.strip('[]')
|
||||
ip_addr = True
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
if not ip_addr:
|
||||
# This is a domain name, which must be checked further
|
||||
|
||||
if domain and domain[-1] == '.':
|
||||
domain = domain[:-1]
|
||||
|
||||
domain_parts = []
|
||||
for label in domain.split('.'):
|
||||
try:
|
||||
label = encodings.idna.nameprep(label)
|
||||
encodings.idna.ToASCII(label)
|
||||
pass_nameprep = True
|
||||
except UnicodeError:
|
||||
pass_nameprep = False
|
||||
|
||||
if not pass_nameprep:
|
||||
raise InvalidJID('Could not encode domain as ASCII')
|
||||
|
||||
if label.startswith('xn--'):
|
||||
label = encodings.idna.ToUnicode(label)
|
||||
|
||||
for char in label:
|
||||
if char in ILLEGAL_CHARS:
|
||||
raise InvalidJID('Domain contains illegar characters')
|
||||
|
||||
if '-' in (label[0], label[-1]):
|
||||
raise InvalidJID('Domain started or ended with -')
|
||||
|
||||
domain_parts.append(label)
|
||||
domain = '.'.join(domain_parts)
|
||||
|
||||
if not domain:
|
||||
raise InvalidJID('Domain must not be 0 bytes')
|
||||
if len(domain) > 1023:
|
||||
raise InvalidJID('Domain must be less than 1024 bytes')
|
||||
|
||||
return domain
|
||||
|
||||
|
||||
def _validate_resource(resource):
|
||||
"""Validate the resource portion of a JID.
|
||||
|
||||
:raises InvalidJID:
|
||||
|
||||
:returns: The local portion of a JID, as validated by resourceprep.
|
||||
"""
|
||||
try:
|
||||
if resource is not None:
|
||||
resource = resourceprep(resource)
|
||||
|
||||
if not resource:
|
||||
raise InvalidJID('Resource must not be 0 bytes')
|
||||
if len(resource) > 1023:
|
||||
raise InvalidJID('Resource must be less than 1024 bytes')
|
||||
return resource
|
||||
except stringprep_profiles.StringPrepError:
|
||||
raise InvalidJID('Invalid resource')
|
||||
|
||||
|
||||
def _escape_node(node):
|
||||
"""Escape the local portion of a JID."""
|
||||
result = []
|
||||
|
||||
for i, char in enumerate(node):
|
||||
if char == '\\':
|
||||
if ''.join((node[i:i+3])) in JID_ESCAPE_SEQUENCES:
|
||||
result.append('\\5c')
|
||||
continue
|
||||
result.append(char)
|
||||
|
||||
for i, char in enumerate(result):
|
||||
if char != '\\':
|
||||
result[i] = JID_ESCAPE_TRANSFORMATIONS.get(char, char)
|
||||
|
||||
escaped = ''.join(result)
|
||||
|
||||
if escaped.startswith('\\20') or escaped.endswith('\\20'):
|
||||
raise InvalidJID('Escaped local part starts or ends with "\\20"')
|
||||
|
||||
_validate_node(escaped)
|
||||
|
||||
return escaped
|
||||
|
||||
|
||||
def _unescape_node(node):
|
||||
"""Unescape a local portion of a JID.
|
||||
|
||||
.. note::
|
||||
The unescaped local portion is meant ONLY for presentation,
|
||||
and should not be used for other purposes.
|
||||
"""
|
||||
unescaped = []
|
||||
seq = ''
|
||||
for i, char in enumerate(node):
|
||||
if char == '\\':
|
||||
seq = node[i:i+3]
|
||||
if seq not in JID_ESCAPE_SEQUENCES:
|
||||
seq = ''
|
||||
if seq:
|
||||
if len(seq) == 3:
|
||||
unescaped.append(JID_UNESCAPE_TRANSFORMATIONS.get(seq, char))
|
||||
|
||||
# Pop character off the escape sequence, and ignore it
|
||||
seq = seq[1:]
|
||||
else:
|
||||
unescaped.append(char)
|
||||
unescaped = ''.join(unescaped)
|
||||
|
||||
return unescaped
|
||||
|
||||
|
||||
def _format_jid(local=None, domain=None, resource=None):
|
||||
"""Format the given JID components into a full or bare JID.
|
||||
|
||||
:param string local: Optional. The local portion of the JID.
|
||||
:param string domain: Required. The domain name portion of the JID.
|
||||
:param strin resource: Optional. The resource portion of the JID.
|
||||
|
||||
:return: A full or bare JID string.
|
||||
"""
|
||||
result = []
|
||||
if local:
|
||||
result.append(local)
|
||||
result.append('@')
|
||||
if domain:
|
||||
result.append(domain)
|
||||
if resource:
|
||||
result.append('/')
|
||||
result.append(resource)
|
||||
return ''.join(result)
|
||||
|
||||
|
||||
class InvalidJID(ValueError):
|
||||
"""
|
||||
Raised when attempting to create a JID that does not pass validation.
|
||||
|
||||
It can also be raised if modifying an existing JID in such a way as
|
||||
to make it invalid, such trying to remove the domain from an existing
|
||||
full JID while the local and resource portions still exist.
|
||||
"""
|
||||
|
||||
# pylint: disable=R0903
|
||||
class UnescapedJID(object):
|
||||
|
||||
"""
|
||||
.. versionadded:: 1.1.10
|
||||
"""
|
||||
|
||||
def __init__(self, local, domain, resource):
|
||||
self._jid = (local, domain, resource)
|
||||
|
||||
# pylint: disable=R0911
|
||||
def __getattr__(self, name):
|
||||
"""Retrieve the given JID component.
|
||||
|
||||
:param name: one of: user, server, domain, resource,
|
||||
full, or bare.
|
||||
"""
|
||||
if name == 'resource':
|
||||
return self._jid[2] or ''
|
||||
elif name in ('user', 'username', 'local', 'node'):
|
||||
return self._jid[0] or ''
|
||||
elif name in ('server', 'domain', 'host'):
|
||||
return self._jid[1] or ''
|
||||
elif name in ('full', 'jid'):
|
||||
return _format_jid(*self._jid)
|
||||
elif name == 'bare':
|
||||
return _format_jid(self._jid[0], self._jid[1])
|
||||
elif name == '_jid':
|
||||
return getattr(super(JID, self), '_jid')
|
||||
else:
|
||||
return None
|
||||
|
||||
def __str__(self):
|
||||
"""Use the full JID as the string value."""
|
||||
return _format_jid(*self._jid)
|
||||
|
||||
def __repr__(self):
|
||||
"""Use the full JID as the representation."""
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class JID(object):
|
||||
|
||||
"""
|
||||
A representation of a Jabber ID, or JID.
|
||||
|
||||
Each JID may have three components: a user, a domain, and an optional
|
||||
resource. For example: user@domain/resource
|
||||
|
||||
When a resource is not used, the JID is called a bare JID.
|
||||
The JID is a full JID otherwise.
|
||||
|
||||
**JID Properties:**
|
||||
:jid: Alias for ``full``.
|
||||
:full: The string value of the full JID.
|
||||
:bare: The string value of the bare JID.
|
||||
:user: The username portion of the JID.
|
||||
:username: Alias for ``user``.
|
||||
:local: Alias for ``user``.
|
||||
:node: Alias for ``user``.
|
||||
:domain: The domain name portion of the JID.
|
||||
:server: Alias for ``domain``.
|
||||
:host: Alias for ``domain``.
|
||||
:resource: The resource portion of the JID.
|
||||
|
||||
:param string jid:
|
||||
A string of the form ``'[user@]domain[/resource]'``.
|
||||
:param string local:
|
||||
Optional. Specify the local, or username, portion
|
||||
of the JID. If provided, it will override the local
|
||||
value provided by the `jid` parameter. The given
|
||||
local value will also be escaped if necessary.
|
||||
:param string domain:
|
||||
Optional. Specify the domain of the JID. If
|
||||
provided, it will override the domain given by
|
||||
the `jid` parameter.
|
||||
:param string resource:
|
||||
Optional. Specify the resource value of the JID.
|
||||
If provided, it will override the domain given
|
||||
by the `jid` parameter.
|
||||
|
||||
:raises InvalidJID:
|
||||
"""
|
||||
|
||||
# pylint: disable=W0212
|
||||
def __init__(self, jid=None, **kwargs):
|
||||
self._jid = (None, None, None)
|
||||
|
||||
if jid is None or jid == '':
|
||||
jid = (None, None, None)
|
||||
elif not isinstance(jid, JID):
|
||||
jid = _parse_jid(jid)
|
||||
else:
|
||||
jid = jid._jid
|
||||
|
||||
local, domain, resource = jid
|
||||
|
||||
local = kwargs.get('local', local)
|
||||
domain = kwargs.get('domain', domain)
|
||||
resource = kwargs.get('resource', resource)
|
||||
|
||||
if 'local' in kwargs:
|
||||
local = _escape_node(local)
|
||||
if 'domain' in kwargs:
|
||||
domain = _validate_domain(domain)
|
||||
if 'resource' in kwargs:
|
||||
resource = _validate_resource(resource)
|
||||
|
||||
self._jid = (local, domain, resource)
|
||||
|
||||
def unescape(self):
|
||||
"""Return an unescaped JID object.
|
||||
|
||||
Using an unescaped JID is preferred for displaying JIDs
|
||||
to humans, and they should NOT be used for any other
|
||||
purposes than for presentation.
|
||||
|
||||
:return: :class:`UnescapedJID`
|
||||
|
||||
.. versionadded:: 1.1.10
|
||||
"""
|
||||
return UnescapedJID(_unescape_node(self._jid[0]),
|
||||
self._jid[1],
|
||||
self._jid[2])
|
||||
|
||||
def regenerate(self):
|
||||
"""No-op
|
||||
|
||||
.. deprecated:: 1.1.10
|
||||
"""
|
||||
pass
|
||||
|
||||
def reset(self, data):
|
||||
"""Start fresh from a new JID string.
|
||||
|
||||
:param string data: A string of the form ``'[user@]domain[/resource]'``.
|
||||
|
||||
.. deprecated:: 1.1.10
|
||||
"""
|
||||
self._jid = JID(data)._jid
|
||||
|
||||
# pylint: disable=R0911
|
||||
def __getattr__(self, name):
|
||||
"""Retrieve the given JID component.
|
||||
|
||||
:param name: one of: user, server, domain, resource,
|
||||
full, or bare.
|
||||
"""
|
||||
if name == 'resource':
|
||||
return self._jid[2] or ''
|
||||
elif name in ('user', 'username', 'local', 'node'):
|
||||
return self._jid[0] or ''
|
||||
elif name in ('server', 'domain', 'host'):
|
||||
return self._jid[1] or ''
|
||||
elif name in ('full', 'jid'):
|
||||
return _format_jid(*self._jid)
|
||||
elif name == 'bare':
|
||||
return _format_jid(self._jid[0], self._jid[1])
|
||||
elif name == '_jid':
|
||||
return getattr(super(JID, self), '_jid')
|
||||
else:
|
||||
return None
|
||||
|
||||
# pylint: disable=W0212
|
||||
def __setattr__(self, name, value):
|
||||
"""Update the given JID component.
|
||||
|
||||
:param name: one of: ``user``, ``username``, ``local``,
|
||||
``node``, ``server``, ``domain``, ``host``,
|
||||
``resource``, ``full``, ``jid``, or ``bare``.
|
||||
:param value: The new string value of the JID component.
|
||||
"""
|
||||
if name == 'resource':
|
||||
self._jid = JID(self, resource=value)._jid
|
||||
elif name in ('user', 'username', 'local', 'node'):
|
||||
self._jid = JID(self, local=value)._jid
|
||||
elif name in ('server', 'domain', 'host'):
|
||||
self._jid = JID(self, domain=value)._jid
|
||||
elif name in ('full', 'jid'):
|
||||
self._jid = JID(value)._jid
|
||||
elif name == 'bare':
|
||||
parsed = JID(value)._jid
|
||||
self._jid = (parsed[0], parsed[1], self._jid[2])
|
||||
elif name == '_jid':
|
||||
super(JID, self).__setattr__('_jid', value)
|
||||
|
||||
def __str__(self):
|
||||
"""Use the full JID as the string value."""
|
||||
return _format_jid(*self._jid)
|
||||
|
||||
def __repr__(self):
|
||||
"""Use the full JID as the representation."""
|
||||
return self.__str__()
|
||||
|
||||
# pylint: disable=W0212
|
||||
def __eq__(self, other):
|
||||
"""Two JIDs are equal if they have the same full JID value."""
|
||||
if isinstance(other, UnescapedJID):
|
||||
return False
|
||||
|
||||
other = JID(other)
|
||||
return self._jid == other._jid
|
||||
|
||||
# pylint: disable=W0212
|
||||
def __ne__(self, other):
|
||||
"""Two JIDs are considered unequal if they are not equal."""
|
||||
return not self == other
|
||||
|
||||
def __hash__(self):
|
||||
"""Hash a JID based on the string version of its full JID."""
|
||||
return hash(self.__str__())
|
||||
|
||||
def __copy__(self):
|
||||
"""Generate a duplicate JID."""
|
||||
return JID(self)
|
@@ -32,23 +32,36 @@ __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
|
||||
'xep_0106', # JID Escaping
|
||||
'xep_0107', # User Mood
|
||||
'xep_0108', # User Activity
|
||||
'xep_0115', # Entity Capabilities
|
||||
'xep_0118', # User Tune
|
||||
'xep_0128', # Extended Service Discovery
|
||||
'xep_0131', # Standard Headers and Internet Metadata
|
||||
'xep_0133', # Service Administration
|
||||
'xep_0153', # vCard-Based Avatars
|
||||
'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
|
||||
]
|
||||
|
@@ -14,6 +14,7 @@
|
||||
"""
|
||||
|
||||
import sys
|
||||
import copy
|
||||
import logging
|
||||
import threading
|
||||
|
||||
@@ -31,10 +32,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 +76,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 +85,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 +104,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 +168,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 +181,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 +229,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]
|
||||
@@ -273,6 +273,14 @@ class BasePlugin(object):
|
||||
#: be initialized as needed if this plugin is enabled.
|
||||
dependencies = set()
|
||||
|
||||
#: The basic, standard configuration for the plugin, which may
|
||||
#: be overridden when initializing the plugin. The configuration
|
||||
#: fields included here may be accessed directly as attributes of
|
||||
#: the plugin. For example, including the configuration field 'foo'
|
||||
#: would mean accessing `plugin.foo` returns the current value of
|
||||
#: `plugin.config['foo']`.
|
||||
default_config = {}
|
||||
|
||||
def __init__(self, xmpp, config=None):
|
||||
self.xmpp = xmpp
|
||||
if self.xmpp:
|
||||
@@ -280,7 +288,54 @@ class BasePlugin(object):
|
||||
|
||||
#: A plugin's behaviour may be configurable, in which case those
|
||||
#: configuration settings will be provided as a dictionary.
|
||||
self.config = config if config is not None else {}
|
||||
self.config = copy.copy(self.default_config)
|
||||
if config:
|
||||
self.config.update(config)
|
||||
|
||||
def __getattr__(self, key):
|
||||
"""Provide direct access to configuration fields.
|
||||
|
||||
If the standard configuration includes the option `'foo'`, then
|
||||
accessing `self.foo` should be the same as `self.config['foo']`.
|
||||
"""
|
||||
if key in self.default_config:
|
||||
return self.config.get(key, None)
|
||||
else:
|
||||
return object.__getattribute__(self, key)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
"""Provide direct assignment to configuration fields.
|
||||
|
||||
If the standard configuration includes the option `'foo'`, then
|
||||
assigning to `self.foo` should be the same as assigning to
|
||||
`self.config['foo']`.
|
||||
"""
|
||||
if key in self.default_config:
|
||||
self.config[key] = value
|
||||
else:
|
||||
super(BasePlugin, self).__setattr__(key, value)
|
||||
|
||||
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."""
|
||||
@@ -290,9 +345,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
|
||||
|
@@ -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=''):
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -24,7 +24,7 @@ def _extract_data(data, kind):
|
||||
if not begin_headers and 'BEGIN PGP %s' % kind in line:
|
||||
begin_headers = True
|
||||
continue
|
||||
if begin_headers and line == '':
|
||||
if begin_headers and line.stripped() == '':
|
||||
begin_data = True
|
||||
continue
|
||||
if 'END PGP %s' % kind in line:
|
||||
@@ -40,14 +40,15 @@ class XEP_0027(BasePlugin):
|
||||
description = 'XEP-0027: Current Jabber OpenPGP Usage'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'gpg_binary': 'gpg',
|
||||
'gpg_home': '',
|
||||
'use_agent': True,
|
||||
'keyring': None,
|
||||
'key_server': 'pgp.mit.edu'
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
self.gpg_binary = self.config.get('gpg_binary', 'gpg')
|
||||
self.gpg_home = self.config.get('gpg_home', '')
|
||||
self.use_agent = self.config.get('use_agent', True)
|
||||
self.keyring = self.config.get('keyring', None)
|
||||
self.key_server = self.config.get('key_server', 'pgp.mit.edu')
|
||||
|
||||
self.gpg = GPG(gnupghome=self.gpg_home,
|
||||
gpgbinary=self.gpg_binary,
|
||||
use_agent=self.use_agent,
|
||||
@@ -79,9 +80,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
|
||||
|
||||
|
@@ -51,6 +51,3 @@ class Encrypted(ElementBase):
|
||||
if self.xml.text:
|
||||
return xmpp['xep_0027'].decrypt(self.xml.text, parent['to'])
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
@@ -88,6 +88,10 @@ class XEP_0030(BasePlugin):
|
||||
description = 'XEP-0030: Service Discovery'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'use_cache': True,
|
||||
'wrap_results': False
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
"""
|
||||
@@ -108,9 +112,6 @@ class XEP_0030(BasePlugin):
|
||||
|
||||
self.static = StaticDisco(self.xmpp, self)
|
||||
|
||||
self.use_cache = self.config.get('use_cache', True)
|
||||
self.wrap_results = self.config.get('wrap_results', False)
|
||||
|
||||
self._disco_ops = [
|
||||
'get_info', 'set_info', 'set_identities', 'set_features',
|
||||
'get_items', 'set_items', 'del_items', 'add_identity',
|
||||
@@ -339,8 +340,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 +349,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 +406,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 +623,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 +646,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 +664,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()
|
||||
|
@@ -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:
|
||||
|
@@ -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)
|
20
sleekxmpp/plugins/xep_0033/__init__.py
Normal file
20
sleekxmpp/plugins/xep_0033/__init__.py
Normal 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
|
37
sleekxmpp/plugins/xep_0033/addresses.py
Normal file
37
sleekxmpp/plugins/xep_0033/addresses.py
Normal 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)
|
||||
|
131
sleekxmpp/plugins/xep_0033/stanza.py
Normal file
131
sleekxmpp/plugins/xep_0033/stanza.py
Normal 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)
|
@@ -137,7 +137,7 @@ class XEP_0045(BasePlugin):
|
||||
def handle_groupchat_invite(self, inv):
|
||||
""" Handle an invite into a muc.
|
||||
"""
|
||||
logging.debug("MUC invite to %s from %s: %s", inv['from'], inv["from"], inv)
|
||||
logging.debug("MUC invite to %s from %s: %s", inv['to'], inv["from"], inv)
|
||||
if inv['from'] not in self.rooms.keys():
|
||||
self.xmpp.event("groupchat_invite", inv)
|
||||
|
||||
|
@@ -20,18 +20,19 @@ class XEP_0047(BasePlugin):
|
||||
description = 'XEP-0047: In-band Bytestreams'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'max_block_size': 8192,
|
||||
'window_size': 1,
|
||||
'auto_accept': True,
|
||||
'accept_stream': None
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
self.streams = {}
|
||||
self.pending_streams = {3: 5}
|
||||
self.pending_streams = {}
|
||||
self.pending_close_streams = {}
|
||||
self._stream_lock = threading.Lock()
|
||||
|
||||
self.max_block_size = self.config.get('max_block_size', 8192)
|
||||
self.window_size = self.config.get('window_size', 1)
|
||||
self.auto_accept = self.config.get('auto_accept', True)
|
||||
self.accept_stream = self.config.get('accept_stream', None)
|
||||
|
||||
register_stanza_plugin(Iq, Open)
|
||||
register_stanza_plugin(Iq, Close)
|
||||
register_stanza_plugin(Iq, Data)
|
||||
@@ -51,6 +52,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):
|
||||
|
@@ -1,11 +1,8 @@
|
||||
import socket
|
||||
import threading
|
||||
import logging
|
||||
try:
|
||||
import queue
|
||||
except ImportError:
|
||||
import Queue as queue
|
||||
|
||||
from sleekxmpp.util import Queue
|
||||
from sleekxmpp.exceptions import XMPPError
|
||||
|
||||
|
||||
@@ -33,7 +30,7 @@ class IBBytestream(object):
|
||||
self.stream_in_closed = threading.Event()
|
||||
self.stream_out_closed = threading.Event()
|
||||
|
||||
self.recv_queue = queue.Queue()
|
||||
self.recv_queue = Queue()
|
||||
|
||||
self.send_window = threading.BoundedSemaphore(value=self.window_size)
|
||||
self.window_ids = set()
|
||||
|
@@ -82,12 +82,18 @@ class XEP_0050(BasePlugin):
|
||||
description = 'XEP-0050: Ad-Hoc Commands'
|
||||
dependencies = set(['xep_0030', 'xep_0004'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'threaded': True,
|
||||
'session_db': None
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
"""Start the XEP-0050 plugin."""
|
||||
self.threaded = self.config.get('threaded', True)
|
||||
self.sessions = self.session_db
|
||||
if self.sessions is None:
|
||||
self.sessions = {}
|
||||
|
||||
self.commands = {}
|
||||
self.sessions = self.config.get('session_db', {})
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback("Ad-Hoc Execute",
|
||||
@@ -110,6 +116,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())
|
||||
|
||||
|
@@ -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):
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
@@ -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.
|
||||
|
@@ -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)
|
||||
|
@@ -26,7 +26,7 @@ class XEP_0060(BasePlugin):
|
||||
|
||||
name = 'xep_0060'
|
||||
description = 'XEP-0060: Publish-Subscribe'
|
||||
dependencies = set(['xep_0030', 'xep_0004'])
|
||||
dependencies = set(['xep_0030', 'xep_0004', 'xep_0082', 'xep_0131'])
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
@@ -53,6 +53,15 @@ class XEP_0060(BasePlugin):
|
||||
StanzaPath('message/pubsub_event/subscription'),
|
||||
self._handle_event_subscription))
|
||||
|
||||
self.xmpp['xep_0131'].supported_headers.add('SubID')
|
||||
|
||||
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']
|
||||
|
@@ -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)
|
||||
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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)
|
||||
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -27,24 +27,28 @@ class XEP_0077(BasePlugin):
|
||||
description = 'XEP-0077: In-Band Registration'
|
||||
dependencies = set(['xep_0004', 'xep_0066'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'create_account': True,
|
||||
'order': 50
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
self.create_account = self.config.get('create_account', True)
|
||||
|
||||
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,
|
||||
order=self.config.get('order', 50))
|
||||
order=self.order)
|
||||
|
||||
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.order)
|
||||
|
||||
def _handle_register_feature(self, features):
|
||||
if 'mechanisms' in self.xmpp.features:
|
||||
# We have already logged in with an account
|
||||
|
@@ -34,16 +34,22 @@ class XEP_0078(BasePlugin):
|
||||
description = 'XEP-0078: Non-SASL Authentication'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'order': 15
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
self.xmpp.register_feature('auth',
|
||||
self._handle_auth,
|
||||
restart=False,
|
||||
order=self.config.get('order', 15))
|
||||
order=self.order)
|
||||
|
||||
register_stanza_plugin(Iq, stanza.IqAuth)
|
||||
register_stanza_plugin(StreamFeatures, stanza.AuthFeature)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.unregister_feature('auth', self.order)
|
||||
|
||||
def _handle_auth(self, features):
|
||||
# If we can or have already authenticated with SASL, do nothing.
|
||||
if 'mechanisms' in features['features']:
|
||||
|
@@ -39,5 +39,3 @@ class AuthFeature(ElementBase):
|
||||
interfaces = set()
|
||||
plugin_tag_map = {}
|
||||
plugin_attrib_map = {}
|
||||
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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.
|
||||
|
@@ -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)
|
||||
|
||||
|
||||
|
16
sleekxmpp/plugins/xep_0084/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0084/__init__.py
Normal 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)
|
108
sleekxmpp/plugins/xep_0084/avatar.py
Normal file
108
sleekxmpp/plugins/xep_0084/avatar.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
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 generate_id(self, data):
|
||||
return hashlib.sha1(data).hexdigest()
|
||||
|
||||
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,
|
||||
id=self.generate_id(data),
|
||||
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', ''))
|
||||
|
||||
if pointers is not None:
|
||||
for pointer in pointers:
|
||||
metadata.add_pointer(pointer)
|
||||
|
||||
return self.xmpp['xep_0163'].publish(metadata,
|
||||
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)
|
78
sleekxmpp/plugins/xep_0084/stanza.py
Normal file
78
sleekxmpp/plugins/xep_0084/stanza.py
Normal 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': '%s' % 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)
|
@@ -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):
|
||||
|
@@ -37,7 +37,10 @@ class XEP_0086(BasePlugin):
|
||||
description = 'XEP-0086: Error Condition Mappings'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'override': True
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Error, LegacyError,
|
||||
overrides=self.config.get('override', True))
|
||||
overrides=self.override)
|
||||
|
@@ -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."""
|
||||
|
@@ -30,16 +30,18 @@ class XEP_0092(BasePlugin):
|
||||
description = 'XEP-0092: Software Version'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'software_name': 'SleekXMPP',
|
||||
'version': sleekxmpp.__version__,
|
||||
'os': ''
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
"""
|
||||
Start the XEP-0092 plugin.
|
||||
"""
|
||||
self.name = self.config.get('name', 'SleekXMPP')
|
||||
self.version = self.config.get('version', sleekxmpp.__version__)
|
||||
self.os = self.config.get('os', '')
|
||||
|
||||
self.getVersion = self.get_version
|
||||
if 'name' in self.config:
|
||||
self.software_name = self.config['name']
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Software Version',
|
||||
@@ -48,6 +50,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):
|
||||
@@ -58,7 +65,7 @@ class XEP_0092(BasePlugin):
|
||||
iq -- The Iq stanza containing the software version query.
|
||||
"""
|
||||
iq.reply()
|
||||
iq['software_version']['name'] = self.name
|
||||
iq['software_version']['name'] = self.software_name
|
||||
iq['software_version']['version'] = self.version
|
||||
iq['software_version']['os'] = self.os
|
||||
iq.send()
|
||||
@@ -79,5 +86,10 @@ 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
|
||||
|
||||
|
||||
XEP_0092.getVersion = XEP_0092.get_version
|
||||
|
26
sleekxmpp/plugins/xep_0106.py
Normal file
26
sleekxmpp/plugins/xep_0106.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
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_0106(BasePlugin):
|
||||
|
||||
name = 'xep_0106'
|
||||
description = 'XEP-0106: JID Escaping'
|
||||
dependencies = set(['xep_0030'])
|
||||
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0030'].add_feature(feature='jid\\20escaping')
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp['xep_0030'].del_feature(feature='jid\\20escaping')
|
||||
|
||||
|
||||
register_plugin(XEP_0106)
|
@@ -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,
|
||||
|
@@ -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:
|
||||
|
@@ -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,
|
||||
|
@@ -33,16 +33,17 @@ class XEP_0115(BasePlugin):
|
||||
description = 'XEP-0115: Entity Capabilities'
|
||||
dependencies = set(['xep_0030', 'xep_0128', 'xep_0004'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'hash': 'sha-1',
|
||||
'caps_node': None,
|
||||
'broadcast': True
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
self.hashes = {'sha-1': hashlib.sha1,
|
||||
self.hashes = {'sha-1': hashlib.sha1,
|
||||
'sha1': hashlib.sha1,
|
||||
'md5': hashlib.md5}
|
||||
|
||||
self.hash = self.config.get('hash', 'sha-1')
|
||||
self.caps_node = self.config.get('caps_node', None)
|
||||
self.broadcast = self.config.get('broadcast', True)
|
||||
|
||||
if self.caps_node is None:
|
||||
ver = sleekxmpp.__version__
|
||||
self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver
|
||||
@@ -73,16 +74,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,13 +90,31 @@ 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'])
|
||||
if ver:
|
||||
stanza['caps']['node'] = self.caps_node
|
||||
stanza['caps']['hash'] = self.hash
|
||||
stanza['caps']['ver'] = ver
|
||||
if not isinstance(stanza, Presence) or not self.broadcast:
|
||||
return stanza
|
||||
|
||||
if stanza['type'] not in ('available', 'chat', 'away', 'dnd', 'xa'):
|
||||
return stanza
|
||||
|
||||
ver = self.get_verstring(stanza['from'])
|
||||
if ver:
|
||||
stanza['caps']['node'] = self.caps_node
|
||||
stanza['caps']['hash'] = self.hash
|
||||
stanza['caps']['ver'] = ver
|
||||
return stanza
|
||||
|
||||
def _handle_caps(self, presence):
|
||||
@@ -124,7 +142,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 +150,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 +158,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 +191,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 +202,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 +232,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 +274,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)
|
||||
|
@@ -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'])
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
|
||||
|
16
sleekxmpp/plugins/xep_0131/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0131/__init__.py
Normal 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_0131 import stanza
|
||||
from sleekxmpp.plugins.xep_0131.stanza import Headers
|
||||
from sleekxmpp.plugins.xep_0131.headers import XEP_0131
|
||||
|
||||
|
||||
register_plugin(XEP_0131)
|
41
sleekxmpp/plugins/xep_0131/headers.py
Normal file
41
sleekxmpp/plugins/xep_0131/headers.py
Normal file
@@ -0,0 +1,41 @@
|
||||
"""
|
||||
SleekXMPP: The Sleek XMPP Library
|
||||
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of SleekXMPP.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from sleekxmpp import Message, Presence
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.plugins import BasePlugin
|
||||
from sleekxmpp.plugins.xep_0131 import stanza
|
||||
from sleekxmpp.plugins.xep_0131.stanza import Headers
|
||||
|
||||
|
||||
class XEP_0131(BasePlugin):
|
||||
|
||||
name = 'xep_0131'
|
||||
description = 'XEP-0131: Stanza Headers and Internet Metadata'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'supported_headers': set()
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Message, Headers)
|
||||
register_stanza_plugin(Presence, Headers)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp['xep_0030'].del_feature(feature=Headers.namespace)
|
||||
for header in self.supported_headers:
|
||||
self.xmpp['xep_0030'].del_feature(
|
||||
feature='%s#%s' % (Headers.namespace, header))
|
||||
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0030'].add_feature(Headers.namespace)
|
||||
for header in self.supported_headers:
|
||||
self.xmpp['xep_0030'].add_feature('%s#%s' % (
|
||||
Headers.namespace,
|
||||
header))
|
51
sleekxmpp/plugins/xep_0131/stanza.py
Normal file
51
sleekxmpp/plugins/xep_0131/stanza.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
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.thirdparty import OrderedDict
|
||||
from sleekxmpp.xmlstream import ET, ElementBase
|
||||
|
||||
|
||||
class Headers(ElementBase):
|
||||
name = 'headers'
|
||||
namespace = 'http://jabber.org/protocol/shim'
|
||||
plugin_attrib = 'headers'
|
||||
interfaces = set(['headers'])
|
||||
is_extension = True
|
||||
|
||||
def get_headers(self):
|
||||
result = OrderedDict()
|
||||
headers = self.xml.findall('{%s}header' % self.namespace)
|
||||
for header in headers:
|
||||
name = header.attrib.get('name', '')
|
||||
value = header.text
|
||||
if name in result:
|
||||
if not isinstance(result[name], set):
|
||||
result[name] = [result[name]]
|
||||
else:
|
||||
result[name] = []
|
||||
result[name].add(value)
|
||||
else:
|
||||
result[name] = value
|
||||
return result
|
||||
|
||||
def set_headers(self, values):
|
||||
self.del_headers()
|
||||
for name in values:
|
||||
vals = values[name]
|
||||
if not isinstance(vals, (list, set)):
|
||||
vals = [values[name]]
|
||||
for value in vals:
|
||||
header = ET.Element('{%s}header' % self.namespace)
|
||||
header.attrib['name'] = name
|
||||
header.text = value
|
||||
self.xml.append(header)
|
||||
|
||||
def del_headers(self):
|
||||
headers = self.xml.findall('{%s}header' % self.namespace)
|
||||
for header in headers:
|
||||
self.xml.remove(header)
|
54
sleekxmpp/plugins/xep_0133.py
Normal file
54
sleekxmpp/plugins/xep_0133.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
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_0133(BasePlugin):
|
||||
|
||||
name = 'xep_0133'
|
||||
description = 'XEP-0133: Service Administration'
|
||||
dependencies = set(['xep_0030', 'xep_0004', 'xep_0050'])
|
||||
commands = set(['add-user', 'delete-user', 'disable-user',
|
||||
'reenable-user', 'end-user-session', 'get-user-password',
|
||||
'change-user-password', 'get-user-roster',
|
||||
'get-user-lastlogin', 'user-stats', 'edit-blacklist',
|
||||
'edit-whitelist', 'get-registered-users-num',
|
||||
'get-disabled-users-num', 'get-online-users-num',
|
||||
'get-active-users-num', 'get-idle-users-num',
|
||||
'get-registered-users-list', 'get-disabled-users-list',
|
||||
'get-online-users-list', 'get-online-users',
|
||||
'get-active-users', 'get-idle-userslist', 'announce',
|
||||
'set-motd', 'edit-motd', 'delete-motd', 'set-welcome',
|
||||
'delete-welcome', 'edit-admin', 'restart', 'shutdown'])
|
||||
|
||||
def get_commands(self, jid=None, **kwargs):
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.server
|
||||
return self.xmpp['xep_0050'].get_commands(jid, **kwargs)
|
||||
|
||||
|
||||
def create_command(name):
|
||||
def admin_command(self, jid=None, session=None, ifrom=None, block=False):
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.server
|
||||
self.xmpp['xep_0050'].start_command(
|
||||
jid=jid,
|
||||
node='http://jabber.org/protocol/admin#%s' % name,
|
||||
session=session,
|
||||
ifrom=ifrom,
|
||||
block=block)
|
||||
return admin_command
|
||||
|
||||
|
||||
for cmd in XEP_0133.commands:
|
||||
setattr(XEP_0133, cmd.replace('-', '_'), create_command(cmd))
|
||||
|
||||
|
||||
register_plugin(XEP_0133)
|
@@ -8,7 +8,9 @@
|
||||
|
||||
import hashlib
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from sleekxmpp import JID
|
||||
from sleekxmpp.stanza import Presence
|
||||
from sleekxmpp.xmlstream import register_stanza_plugin
|
||||
from sleekxmpp.xmlstream.matcher import StanzaPath
|
||||
@@ -30,11 +32,14 @@ class XEP_0153(BasePlugin):
|
||||
def plugin_init(self):
|
||||
self._hashes = {}
|
||||
|
||||
self._allow_advertising = threading.Event()
|
||||
|
||||
register_stanza_plugin(Presence, VCardTempUpdate)
|
||||
|
||||
self.xmpp.add_filter('out', self._update_presence)
|
||||
|
||||
self.xmpp.add_event_handler('session_start', self._start)
|
||||
self.xmpp.add_event_handler('session_end', self._end)
|
||||
|
||||
self.xmpp.add_event_handler('presence_available', self._recv_presence)
|
||||
self.xmpp.add_event_handler('presence_dnd', self._recv_presence)
|
||||
@@ -44,59 +49,82 @@ class XEP_0153(BasePlugin):
|
||||
|
||||
self.api.register(self._set_hash, 'set_hash', default=True)
|
||||
self.api.register(self._get_hash, 'get_hash', default=True)
|
||||
self.api.register(self._reset_hash, 'reset_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('session_end', self._end)
|
||||
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):
|
||||
if jid is None:
|
||||
jid = self.xmpp.boundjid.bare
|
||||
|
||||
vcard = self.xmpp['xep_0054'].get_vcard(jid, cached=True)
|
||||
vcard = vcard['vcard_temp']
|
||||
vcard['PHOTO']['TYPE'] = mtype
|
||||
vcard['PHOTO']['BINVAL'] = avatar
|
||||
|
||||
self.xmpp['xep_0054'].publish_vcard(jid=jid, vcard=vcard)
|
||||
self._reset_hash(jid)
|
||||
|
||||
self.api['reset_hash'](jid)
|
||||
self.xmpp.roster[jid].send_last_presence()
|
||||
|
||||
def _start(self, event):
|
||||
self.xmpp['xep_0054'].get_vcard()
|
||||
vcard = self.xmpp['xep_0054'].get_vcard()
|
||||
self._allow_advertising.set()
|
||||
|
||||
def _end(self, event):
|
||||
self._allow_advertising.clear()
|
||||
|
||||
def _update_presence(self, stanza):
|
||||
if not isinstance(stanza, Presence):
|
||||
return stanza
|
||||
|
||||
if stanza['type'] not in ('available', 'dnd', 'chat', 'away', 'xa'):
|
||||
return stanza
|
||||
|
||||
current_hash = self.api['get_hash'](stanza['from'])
|
||||
stanza['vcard_temp_update']['photo'] = current_hash
|
||||
return stanza
|
||||
|
||||
def _reset_hash(self, jid=None):
|
||||
def _reset_hash(self, jid, node, ifrom, args):
|
||||
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)
|
||||
if own_jid:
|
||||
self.xmpp.roster[jid].send_last_presence()
|
||||
|
||||
iq = self.xmpp['xep_0054'].get_vcard(
|
||||
jid=jid,
|
||||
ifrom=self.xmpp.boundjid)
|
||||
iq = self.xmpp['xep_0054'].get_vcard(jid=jid.bare, ifrom=ifrom)
|
||||
|
||||
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
||||
if not data:
|
||||
new_hash = ''
|
||||
else:
|
||||
new_hash = hashlib.sha1(data).hexdigest()
|
||||
|
||||
self.api['set_hash'](jid, args=new_hash)
|
||||
if own_jid:
|
||||
self.xmpp.roster[jid].send_last_presence()
|
||||
|
||||
def _recv_presence(self, pres):
|
||||
if not pres.match('presence/vcard_temp_update'):
|
||||
self.api['set_hash'](pres['from'], args=None)
|
||||
return
|
||||
|
||||
data = pres['vcard_temp_update']['photo']
|
||||
if data is None:
|
||||
return
|
||||
elif data == '' or data != self.api['get_hash'](pres['to']):
|
||||
self._reset_hash(pres['from'])
|
||||
ifrom = pres['to'] if self.xmpp.is_component else None
|
||||
self.api['reset_hash'](pres['from'], ifrom=ifrom)
|
||||
self.xmpp.event('vcard_avatar_update', pres)
|
||||
|
||||
# =================================================================
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
@@ -26,13 +26,14 @@ class XEP_0184(BasePlugin):
|
||||
description = 'XEP-0184: Message Delivery Receipts'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'auto_ack': True,
|
||||
'auto_request': False
|
||||
}
|
||||
|
||||
ack_types = ('normal', 'chat', 'headline')
|
||||
|
||||
def plugin_init(self):
|
||||
self.auto_ack = self.config.get('auto_ack', True)
|
||||
self.auto_request = self.config.get('auto_request', False)
|
||||
|
||||
register_stanza_plugin(Message, Request)
|
||||
register_stanza_plugin(Message, Received)
|
||||
|
||||
@@ -48,6 +49,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):
|
||||
@@ -61,7 +69,7 @@ class XEP_0184(BasePlugin):
|
||||
ack['to'] = msg['from']
|
||||
ack['from'] = msg['to']
|
||||
ack['receipt'] = msg['id']
|
||||
ack['id'] = self.xmpp.new_id()
|
||||
ack['id'] = msg['id']
|
||||
ack.send()
|
||||
|
||||
def _handle_receipt_received(self, msg):
|
||||
@@ -100,13 +108,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
|
||||
|
||||
|
16
sleekxmpp/plugins/xep_0186/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0186/__init__.py
Normal 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)
|
44
sleekxmpp/plugins/xep_0186/invisible_command.py
Normal file
44
sleekxmpp/plugins/xep_0186/invisible_command.py
Normal 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)
|
23
sleekxmpp/plugins/xep_0186/stanza.py
Normal file
23
sleekxmpp/plugins/xep_0186/stanza.py
Normal 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()
|
15
sleekxmpp/plugins/xep_0191/__init__.py
Normal file
15
sleekxmpp/plugins/xep_0191/__init__.py
Normal 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)
|
83
sleekxmpp/plugins/xep_0191/blocking.py
Normal file
83
sleekxmpp/plugins/xep_0191/blocking.py
Normal 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)
|
50
sleekxmpp/plugins/xep_0191/stanza.py
Normal file
50
sleekxmpp/plugins/xep_0191/stanza.py
Normal 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'
|
@@ -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')
|
||||
|
||||
|
@@ -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):
|
||||
@@ -34,6 +34,32 @@ class XEP_0198(BasePlugin):
|
||||
description = 'XEP-0198: Stream Management'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
#: The last ack number received from the server.
|
||||
'last_ack': 0,
|
||||
|
||||
#: The number of stanzas to wait between sending ack requests to
|
||||
#: the server. Setting this to ``1`` will send an ack request after
|
||||
#: every sent stanza. Defaults to ``5``.
|
||||
'window': 5,
|
||||
|
||||
#: The stream management ID for the stream. Knowing this value is
|
||||
#: required in order to do stream resumption.
|
||||
'sm_id': None,
|
||||
|
||||
#: A counter of handled incoming stanzas, mod 2^32.
|
||||
'handled': 0,
|
||||
|
||||
#: A counter of unacked outgoing stanzas, mod 2^32.
|
||||
'seq': 0,
|
||||
|
||||
#: Control whether or not the ability to resume the stream will be
|
||||
#: requested when enabling stream management. Defaults to ``True``.
|
||||
'allow_resume': True,
|
||||
|
||||
'order': 10100,
|
||||
'resume_order': 9000
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
"""Start the XEP-0198 plugin."""
|
||||
@@ -43,33 +69,12 @@ class XEP_0198(BasePlugin):
|
||||
if self.xmpp.is_component:
|
||||
return
|
||||
|
||||
#: The stream management ID for the stream. Knowing this value is
|
||||
#: required in order to do stream resumption.
|
||||
self.sm_id = self.config.get('sm_id', None)
|
||||
|
||||
#: A counter of handled incoming stanzas, mod 2^32.
|
||||
self.handled = self.config.get('handled', 0)
|
||||
|
||||
#: A counter of unacked outgoing stanzas, mod 2^32.
|
||||
self.seq = self.config.get('seq', 0)
|
||||
|
||||
#: The last ack number received from the server.
|
||||
self.last_ack = self.config.get('last_ack', 0)
|
||||
|
||||
#: The number of stanzas to wait between sending ack requests to
|
||||
#: the server. Setting this to ``1`` will send an ack request after
|
||||
#: every sent stanza. Defaults to ``5``.
|
||||
self.window = self.config.get('window', 5)
|
||||
self.window_counter = self.window
|
||||
self.window_counter_lock = threading.Lock()
|
||||
|
||||
#: Control whether or not the ability to resume the stream will be
|
||||
#: requested when enabling stream management. Defaults to ``True``.
|
||||
self.allow_resume = self.config.get('allow_resume', True)
|
||||
|
||||
self.enabled = threading.Event()
|
||||
self.unacked_queue = collections.deque()
|
||||
|
||||
|
||||
self.seq_lock = threading.Lock()
|
||||
self.handled_lock = threading.Lock()
|
||||
self.ack_lock = threading.Lock()
|
||||
@@ -92,11 +97,11 @@ class XEP_0198(BasePlugin):
|
||||
self.xmpp.register_feature('sm',
|
||||
self._handle_sm_feature,
|
||||
restart=True,
|
||||
order=self.config.get('order', 10100))
|
||||
order=self.order)
|
||||
self.xmpp.register_feature('sm',
|
||||
self._handle_sm_feature,
|
||||
restart=True,
|
||||
order=self.config.get('resume_order', 9000))
|
||||
order=self.resume_order)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Stream Management Enabled',
|
||||
@@ -133,6 +138,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.order)
|
||||
self.xmpp.unregister_feature('sm', self.resume_order)
|
||||
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 +223,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 +257,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 +269,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()
|
||||
|
@@ -51,15 +51,16 @@ class XEP_0199(BasePlugin):
|
||||
description = 'XEP-0199: XMPP Ping'
|
||||
dependencies = set(['xep_0030'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
'keepalive': False,
|
||||
'frequency': 300,
|
||||
'timeout': 30
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
"""
|
||||
Start the XEP-0199 plugin.
|
||||
"""
|
||||
self.keepalive = self.config.get('keepalive', False)
|
||||
self.frequency = float(self.config.get('frequency', 300))
|
||||
self.timeout = self.config.get('timeout', 30)
|
||||
|
||||
register_stanza_plugin(Iq, Ping)
|
||||
|
||||
self.xmpp.register_handler(
|
||||
@@ -74,6 +75,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):
|
||||
|
@@ -30,18 +30,23 @@ class XEP_0202(BasePlugin):
|
||||
description = 'XEP-0202: Entity Time'
|
||||
dependencies = set(['xep_0030', 'xep_0082'])
|
||||
stanza = stanza
|
||||
default_config = {
|
||||
#: As a default, respond to time requests with the
|
||||
#: local time returned by XEP-0082. However, a
|
||||
#: custom function can be supplied which accepts
|
||||
#: the JID of the entity to query for the time.
|
||||
'local_time': None,
|
||||
'tz_offset': 0
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
"""Start the XEP-0203 plugin."""
|
||||
self.tz_offset = self.config.get('tz_offset', 0)
|
||||
|
||||
# As a default, respond to time requests with the
|
||||
# local time returned by XEP-0082. However, a
|
||||
# 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)
|
||||
if not self.local_time:
|
||||
self.local_time = lambda x: xep_0082.datetime(offset=self.tz_offset)
|
||||
def default_local_time(jid):
|
||||
return xep_0082.datetime(offset=self.tz_offset)
|
||||
|
||||
self.local_time = default_local_time
|
||||
|
||||
self.xmpp.registerHandler(
|
||||
Callback('Entity Time',
|
||||
@@ -49,6 +54,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):
|
||||
|
@@ -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
|
||||
|
16
sleekxmpp/plugins/xep_0221/__init__.py
Normal file
16
sleekxmpp/plugins/xep_0221/__init__.py
Normal 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)
|
27
sleekxmpp/plugins/xep_0221/media.py
Normal file
27
sleekxmpp/plugins/xep_0221/media.py
Normal 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)
|
42
sleekxmpp/plugins/xep_0221/stanza.py
Normal file
42
sleekxmpp/plugins/xep_0221/stanza.py
Normal 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)
|
126
sleekxmpp/plugins/xep_0222.py
Normal file
126
sleekxmpp/plugins/xep_0222.py
Normal 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 Public 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)
|
126
sleekxmpp/plugins/xep_0223.py
Normal file
126
sleekxmpp/plugins/xep_0223.py
Normal 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)
|
@@ -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=''):
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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.
|
||||
|
||||
|
@@ -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):
|
||||
|
73
sleekxmpp/plugins/xep_0256.py
Normal file
73
sleekxmpp/plugins/xep_0256.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
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
|
||||
default_config = {
|
||||
'auto_last_activity': False
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
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)
|
18
sleekxmpp/plugins/xep_0258/__init__.py
Normal file
18
sleekxmpp/plugins/xep_0258/__init__.py
Normal 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)
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user