Compare commits

...

537 Commits

Author SHA1 Message Date
Lance Stout
753cb3580e Bump to 1.3.0 2014-06-08 20:01:07 -07:00
Lance Stout
60b050b82a Make ssl args work in Python <=2.6.4 2014-06-08 19:59:40 -07:00
Lance Stout
ad91a8cd5e Bring back use of dnspython for A/AAAA resolution.
This is behind a use_dnspython flag, however, so it can be disabled as
desired.
2014-06-08 19:51:57 -07:00
Lance Stout
02f79fc94b Only request auto-receipts for messages with bodies 2014-06-07 20:20:42 -07:00
Lance Stout
230a73fad2 Fix own_host in ping plugin 2014-06-07 20:06:17 -07:00
Lance Stout
d94dd486fe Merge pull request #294 from mofrank/develop
Fixes log.debug message in _connect_proxy
2014-05-16 08:43:39 -07:00
Lance Stout
6ecc39b816 Merge pull request #292 from 4gra/develop
Fix support for jabberd2 with GSSAPI
2014-05-16 08:43:26 -07:00
mofrank
9c240df9db Fixes log.debug message in _connect_proxy 2014-05-16 08:49:01 -05:00
Graham
a918bf3a95 Support jabberd2 SASL with really empty response
Despite http://xmpp.org/rfcs/rfc3920.html#rfc.section.6.2, jabberd version 2.2.14 cannot accept the typical "<response xmlns="urn:ietf:params:xml:ns:xmpp-sasl">=</response>".  Instead it must be truly empty, so we force an empty response for this stanza only.
2014-05-14 22:32:51 +01:00
Graham
9434ae267f support 'success' phase correctly
When the GSSAPI mechanism's process() function is invoked for the third time (on success) it must not attempt further processing.  Instead it should clean the context and return an empty response.
2014-05-14 22:25:09 +01:00
Graham
94187d215a don't use the kerberos.GSSError.message attribute
Replaced the reference to kerberos.GSSError.message in any raised exception, because:

 DeprecationWarning: BaseException.message has been deprecated as of Python 2.6

and its natural repr is probably the most desirable output.
2014-05-14 17:47:34 +01:00
Lance Stout
ef2f5d2978 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2014-04-20 18:10:22 -07:00
Lance Stout
62671e0f56 Fix using SCRAM with ejabberd 2014-04-20 18:09:20 -07:00
Lance Stout
93869f77a0 Merge pull request #285 from lovesnow/develop
Fix Don't process vCard avatars for MUC occupants caused TypeError
2014-04-20 18:06:04 -07:00
Lance Stout
8282d135cc Bump version 2014-04-20 18:05:27 -07:00
Lance Stout
9acc78c81d Merge pull request #288 from tpltnt/develop
doc typo fixed
2014-04-20 17:56:56 -07:00
tpltnt
3642469630 doc typo fixed 2014-04-19 19:12:09 +02:00
lovesnow
34cd20339c Fix Don't process vCard avatars for MUC occupants caused TypeError 2014-02-21 10:31:04 +08:00
Lance Stout
7548f44047 Bump version 2014-02-14 13:53:25 -08:00
Lance Stout
7cf55ef695 Allow IQ processing based on only id value before the session is bound.
See issue #278
2014-02-14 13:50:21 -08:00
Lance Stout
543250da13 Bump version 2014-02-09 14:39:50 -08:00
Lance Stout
69e55d7316 Merge pull request #280 from allan-simon/develop
fixed setRole function,
2014-02-09 14:39:08 -08:00
Lance Stout
158411e918 Include stanza dirs 2014-02-09 14:36:36 -08:00
Lance Stout
3f873002c4 Bump minor version 2014-02-09 14:33:36 -08:00
Lance Stout
818f4e5973 Fix setup.py to include xep_0323 and xep_0325 2014-02-09 14:33:02 -08:00
Allan Simon
c8d6e512d2 fixed setRole function, the check where made against 'affiliation' values, now we do that against actual role values 2014-02-07 12:11:28 +08:00
Lance Stout
a2423b8499 Get the IoT plugins to pass tests on Py3 2014-02-06 09:54:45 -08:00
Lance Stout
49acdac776 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2014-02-06 09:20:24 -08:00
Lance Stout
7e1587faa2 Merge pull request #277 from allan-simon/develop
close #276, now we  trigger 'groupchat_message_error'
2014-02-06 09:19:24 -08:00
Lance Stout
84a6ed8e80 Merge pull request #272 from tfriem/develop
Fix X-FACEBOOK-PLATFORM authentication in Python3.
2014-02-06 09:18:54 -08:00
Lance Stout
654420e351 Add Py3.3 to list of supported versions 2014-02-06 09:14:22 -08:00
Lance Stout
651915f31c Add test for wrong sender in IQ 2014-02-06 09:13:28 -08:00
Lance Stout
d9db1b84fe Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2014-02-03 19:19:15 -06:00
Lance Stout
bd03f071c6 Fix verifying 'from' for IQ results.
Closes issue #278
2014-02-03 19:15:08 -06:00
Allan Simon
eb6ac68d5c close #276, now we trigger 'groupchat_message_error' and muc::ROOM::message_error when we receive a message type=error from the server 2014-01-31 18:54:59 +08:00
Lance Stout
848e6ebd83 Merge pull request #275 from waechtjn/develop
XEP-0065 Implementation Broken
2014-01-26 16:53:50 -08:00
waechtjn
f76524fc9f Fixed XEP-0065 SOCKS5 socket closing
SCOKS5 SID were removed multiple times from the _sessions dictionary
2014-01-26 16:53:53 +01:00
waechtjn
b95532b68b Update xep_0065/proxy.py
Removed reference to undefined variable "conn"
2014-01-26 16:48:31 +01:00
Lance Stout
d002d4c06f Merge pull request #274 from anton-ryzhov/fixes
Examples and thread counting
2014-01-21 12:05:02 -08:00
Anton Ryzhov
7c03cc622c Thread counting fix 2014-01-21 19:28:17 +04:00
Anton Ryzhov
cebfd84416 Examples fixes 2014-01-21 16:19:27 +04:00
Lance Stout
12995e280e Merge pull request #270 from optiflows/fix_hosts
Don't use dnspython for A and AAAA (but keep it for SRV).
2014-01-15 09:51:43 -08:00
Lance Stout
4ae6d44efc Allow setting custom cipher suites in Py2.7+ 2014-01-13 10:14:10 -08:00
Tobias Friemel
01e1878900 Fix X-FACEBOOK-PLATFORM authentication in Python3. 2013-12-23 16:19:49 +01:00
Lance Stout
df9ad82336 Undo event name fix, due to breakage in Py2.x 2013-12-22 01:10:19 -08:00
Lance Stout
c183fd5e35 Merge pull request #271 from louiz/develop
Use strings for ElementTree.iterparse events names
2013-12-19 11:23:45 -08:00
Florent Le Coz
820d07f309 Use strings for ElementTree.iterparse events names
Because if cElementTree is not available on the system,
ElementTree is used instead, and that version doesn't accept
bytes, resulting in an exception. See
http://bugs.python.org/issue9257#msg152864
2013-12-19 11:47:31 +01:00
Guilhem Lettron
f4e3c04bbf Don't use dnspython for A and AAAA (but keep it for SRV).
dnspython don't perform a full resolv.
For example it don't manage /etc/hosts on linux.
2013-12-09 15:44:35 +01:00
Lance Stout
540d6e9dbb Merge pull request #267 from juanrmn/develop
Added a MUC method 'setRole'. Change role property of a nick in a room, ...
2013-11-26 21:18:34 -08:00
juanrmn
79a3a2befd Added a MUC method 'setRole'. Change role property of a nick in a room, useful for moderator bots. 2013-11-06 11:20:50 +01:00
Lance Stout
08a0fd5420 Merge pull request #265 from anton-ryzhov/delay_plugins
Check delay field existence
2013-10-23 11:09:05 -07:00
Anton Ryzhov
92d6bc6875 Check delay field existence
Import missing class
2013-10-23 13:33:52 +04:00
Lance Stout
fb5d20c4f8 Ensure PEP updates default to item if of 'current' 2013-10-09 11:28:09 -07:00
Lance Stout
65e3122f52 Update XEP-0319 plugin to track namespace change. 2013-09-27 00:37:02 -07:00
Lance Stout
be874e3c70 Fix deepcopying JIDs 2013-09-24 16:32:30 -07:00
Lance Stout
beae845281 Fix MAM start query 2013-09-24 16:07:50 -07:00
Lance Stout
6f64dac262 Add log message noting that SCRAM-SHA-1-PLUS requires Py3.3+ 2013-09-21 19:10:12 -07:00
Lance Stout
cd2d25cf87 Chmod +x examples, and add shebang lines 2013-09-20 11:50:51 -07:00
Lance Stout
b8b2f37e7b Make the ssl version log usable 2013-09-17 16:37:52 -07:00
Lance Stout
00152358de Normalize handling html body content
Closes issue #261
2013-09-13 10:01:33 -07:00
Lance Stout
a2784be4d6 Add MAM archived tags 2013-09-12 10:52:15 -07:00
Lance Stout
ad7a57103d ElementTree._escape_cdata isn't reliable across Python versions.
It also does not work as desired.

Revert "Merge pull request #254 from barreverte/develop"

This reverts commit 23750357e2, reversing
changes made to 07284f380f.
2013-09-12 10:39:10 -07:00
Lance Stout
19b24b276d Update MAM to use latest carbons. 2013-09-12 10:32:19 -07:00
Lance Stout
23750357e2 Merge pull request #254 from barreverte/develop
tostring.escape : optimization
2013-09-12 10:21:56 -07:00
Lance Stout
07284f380f Merge pull request #207 from spartanbits/pull_request_gevent_check
Pull request gevent check
2013-09-12 10:15:53 -07:00
Lance Stout
e60401278f Merge pull request #255 from anton-ryzhov/logging
Add null handler to logging engine
2013-09-05 16:27:43 -07:00
Lance Stout
24c474a9ec Merge branch 'xep_0323_325' of git://github.com/joachimlindborg/SleekXMPP into joachimlindborg-xep_0323_325
Conflicts:
	sleekxmpp/plugins/__init__.py
2013-09-05 16:26:18 -07:00
Joachim Lindborg
8fd3781ef5 added disco imformation, fixed some bugs in device 2013-09-04 14:57:27 +02:00
Joachim Lindborg
c85f2494a8 first functional IoT_Test 2013-09-03 22:51:11 +02:00
Lance Stout
6c2fa7a382 Fix pubsub owner subscriptions stanza 2013-08-30 08:51:46 -07:00
Joachim Lindborg
45689fd879 First implementation of the xep_0323 and xep_325 used in IoT systems. Tests are added for stanza and streams 2013-08-30 02:29:52 +02:00
Lance Stout
45a2cfb01b Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2013-08-22 14:57:39 -07:00
Lance Stout
c4bb6c900c Don't reset _expected_server_name when connecting. 2013-08-22 14:54:53 -07:00
Lance Stout
f7c042fc77 Merge pull request #257 from di/patch-1
Typo in _validate_domain error message
2013-08-16 14:52:49 -07:00
Dustin Ingram
b20dc9fe2b Typo in _validate_domain error message
s/illegar/illegal/g
2013-08-16 17:47:26 -04:00
Anton Ryzhov
a030e05993 Add null handler to logging engine 2013-08-10 21:34:18 +04:00
Lance Stout
648b03f811 Make send_message(mhtml='..') work as expected without loading plugin
71.
2013-08-07 08:48:15 -07:00
Lance Stout
e57e321d33 Try re-ordering initial imports to avoid import bugs 2013-08-06 15:54:02 -07:00
Jean-Philippe Caruana
b6e53c7b1b escape: use xml.etree.ElementTree._escape_attrib to avoid duplication 2013-07-31 11:02:10 +02:00
Jean-Philippe Caruana
1c3bfd949b escape: imports at the top 2013-07-31 11:02:06 +02:00
Lance Stout
6401c9aaaa Add back ET and ElementBase references 2013-07-30 11:46:04 -07:00
Jean-Philippe Caruana
c02adbb8e1 tostring.escape : optimization
use of xml.etree.ElementTree._escape_attrib and xml.etree.ElementTree._escape_cdata
2013-07-30 18:51:23 +02:00
Jean-Philippe Caruana
88e64dbfae Merge remote-tracking branch 'upstream/develop' into develop 2013-07-30 18:02:03 +02:00
Lance Stout
afd48b9e08 Merge pull request #253 from kxepal/patch-1
Don't resolve AAAA records if there is no dnspython nor IPv6 support
2013-07-29 09:54:40 -07:00
Jean-Philippe Caruana
db0ab9a0b3 .gitignore: idea 2013-07-29 12:22:10 +02:00
Alexander Shorin
556e4bd74d Don't resolve AAAA records if there is no dnspython nor IPv6 support
If system doesn't has IPv6 support or dnspython package, socket.getaddrinfo 
with AF_INET6 flag return weird IP info for requested host, making SleekXMPP 
crush with more weird error.
2013-07-29 14:21:46 +04:00
Lance Stout
d439c4f215 Merge pull request #252 from jpcaruana/develop
refactor : optimize imports + replace mutable argument (a list) in StateMachine constructor
2013-07-29 02:24:33 -07:00
Jean-Philippe Caruana
a9f2e1482c fix: replace mutable argument (a list) in StateMachine constructor 2013-07-26 17:48:33 +02:00
Jean-Philippe Caruana
2c26fb0d76 optimize imports 2013-07-26 17:48:33 +02:00
Jean-Philippe Caruana
18dde97c8c refactor: no import * in tests 2013-07-26 13:02:26 +02:00
Lance Stout
85bc6f5301 Merge pull request #251 from jpcaruana/jid_performance 2013-07-25 11:23:27 -07:00
Jean-Philippe Caruana
8f364b9a95 performance in jid : replace __getattr__ et __setattr__ by @property and @xxx.setter
this implementatian is much more verbose but faster, especilally if you are dealing a lot with JIDs
on my box, ./testall.py now takes 45s. It takes 53s in the old implementation (about 15% faster)
2013-07-25 16:36:18 +02:00
Lance Stout
ee6c5632ac Merge pull request #248 from jakebasile/develop
Caught OSError when querying AAAA records.
2013-07-18 12:59:16 -07:00
Jake Basile
cc81a0e8da DRYed up the OSError/socket.gaierror handler. 2013-07-18 13:07:25 -05:00
Jake Basile
262652992d Caught OSError when querying AAAA records. 2013-07-18 08:25:28 -05:00
Lance Stout
eb63825dfd Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2013-07-05 14:05:39 -07:00
Lance Stout
c49017c6f1 Update 319 plugin to update XEP-0012/256 last activity too. 2013-07-05 14:04:48 -07:00
Lance Stout
7d08bd3142 Merge pull request #247 from anton-ryzhov/block_queues
Blocking queue get
2013-07-01 00:12:25 -07:00
Anton Ryzhov
f12c241dca Blocking queue get 2013-07-01 10:30:43 +04:00
Lance Stout
cedc9dd175 Adjust get_roster to always return, even with invalid JIDs
Issue #245
2013-06-29 22:33:00 -07:00
Lance Stout
669e708b70 Fix import error 2013-06-23 18:24:35 -07:00
Lance Stout
e76a483931 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2013-06-22 14:35:20 -07:00
Lance Stout
c0437d2de8 Add roster migration example 2013-06-22 14:35:00 -07:00
Lance Stout
37a8043202 Merge pull request #244 from anton-ryzhov/speedup
Speedup main threads loops
2013-06-20 09:21:38 -07:00
Lance Stout
f4c69d4045 Merge pull request #243 from anton-ryzhov/skip_eintr
Skip EINTR errors on raw sockets
2013-06-20 09:19:53 -07:00
Anton Ryzhov
a3606d9e4d Fixed scheduler wait loop
Do fastloop wait until task run time
2013-06-20 18:30:07 +04:00
Anton Ryzhov
805f1c0e39 Use timeout constants instead of magic numbers in scheduler and event loop
Set default wait timeout as max() of previous values
2013-06-20 18:30:07 +04:00
Anton Ryzhov
7430a8ca40 Some optimizations in scheduler 2013-06-20 18:30:07 +04:00
Anton Ryzhov
1776e2edcc Skip EINTR errors on raw sockets 2013-06-20 18:29:53 +04:00
Lance Stout
baf9aaf26c Add test for nodeprep idempotency after explicitly using Unicode 3.2 2013-06-19 08:21:54 -07:00
Lance Stout
4864b07e13 Explicitly use Unicode 3.2 for StringPrep profiles.
See http://labs.spotify.com/2013/06/18/creative-usernames/
2013-06-19 00:28:11 -07:00
Lance Stout
13c919773e Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2013-06-07 19:00:42 -04:00
Lance Stout
ed3a4fb8d4 Add support for XEP-0319, idle presence 2013-06-07 19:00:22 -04:00
Lance Stout
df3e826d0a Merge pull request #242 from Florob/xep-0084-id
Properly set itemID for XEP-0084 metadata
2013-06-07 10:09:30 -07:00
Florian Zeitz
a9e7d489b8 Properly set itemID for XEP-0084 metadata 2013-06-07 13:29:28 +02:00
Lance Stout
da6b549f8b Merge pull request #239 from SecurityForUs/add_95_96_to_setup
Add xep_0095 and xep_0096 to setup so they are installed into the egg
2013-05-27 13:40:57 -07:00
Eric Hansen
76e07a9089 Add xep_0095 and xep_0096 to setup so they are installed into the egg 2013-05-27 06:14:21 -04:00
Lance Stout
4a590d1497 Simplify stream method unregistration process 2013-05-26 14:53:28 -07:00
Lance Stout
82e1508d6f Make stream initiation methods unregisterable. 2013-05-26 14:50:01 -07:00
Lance Stout
400f08db9d Fix crash conditions in IBB 2013-05-22 14:27:14 -07:00
Lance Stout
e48b650caa Fix encrypting with GPG 2013-05-22 11:59:17 -07:00
Lance Stout
d9f595283a Merge pull request #234 from SecurityForUs/str_no_stripped
.stripped() would result in error
2013-05-22 10:19:52 -07:00
Eric Hansen
85fd14f47f .stripped() would result in error 2013-05-22 09:16:49 -04:00
Joachim Lindborg
b7adaafb3e First test stanza 2013-05-17 12:18:00 +02:00
Lance Stout
d0bba87cdd Only remap component namespaces for top level stream elements. 2013-05-14 14:28:16 -07:00
Lance Stout
2cc75d4bbd Update copyright years, and license for Suelta 2013-05-13 14:09:28 -07:00
Lance Stout
24bd591faa Update copyright for sasl modules. 2013-05-13 13:33:04 -07:00
Lance Stout
2e9ccd0623 Merge branch 'si_file_transfer' into develop 2013-05-11 12:48:47 -07:00
Lance Stout
7b49c82210 Add support for XEP-0152: Reachability Addresses 2013-05-11 12:22:56 -07:00
Lance Stout
d3284f1604 Merge pull request #232 from kstaniek/develop
Fix in tzoffset and _get_fixed_offset_tz
2013-05-09 12:19:35 -07:00
Klaudiusz Staniek
3279697128 Fix in tzoffset and _get_fixed_offset_tz
The tzoffset object is constructed with offset in minutes not in
seconds.
2013-05-09 20:59:00 +02:00
Lance Stout
60cfab995f Try preventing strptime thread safety problems.
Fixes #231
2013-04-27 03:56:20 -07:00
Lance Stout
8ec18bdb2c Carry scheduled kwargs all the way 2013-04-23 11:09:04 -07:00
Lance Stout
3c3cd65235 Update gitignore 2013-04-01 22:43:26 -07:00
Lance Stout
7ac75de19d Make XMLMasks match properly for components. 2013-04-01 20:57:16 -07:00
Lance Stout
fae39e1ab4 Fix some errors in the IBB plugin. 2013-03-29 13:16:18 -07:00
Lance Stout
3732139fc3 Save progress on SI file transfer 2013-03-29 13:16:18 -07:00
Lance Stout
0a2737dc77 Merge pull request #228 from anton-ryzhov/events
Some events refactoring
2013-03-28 12:20:38 -07:00
Anton Ryzhov
481971928c failed_auth data returned
Manual updated
2013-03-28 22:41:00 +04:00
Anton Ryzhov
020197718f Event index documentation updated 2013-03-28 22:09:33 +04:00
Anton Ryzhov
a0c77c04a5 XMLStream proxy_error event duplicated with connection_failed
SASL `no_auth` event duplicated with `failed_auth`
2013-03-28 22:09:33 +04:00
Anton Ryzhov
620ee9719f Changed failed_auth event according to manual 2013-03-28 22:09:33 +04:00
Anton Ryzhov
c0d02d9935 Remove roster_received event 2013-03-28 22:09:33 +04:00
Anton Ryzhov
01356d23e5 Log events triggering 2013-03-28 20:44:37 +04:00
Lance Stout
8b73c2bcff Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2013-03-11 16:42:15 -07:00
Lance Stout
5a771dbe2f Prevent race condition in pubsub test. 2013-03-11 16:41:44 -07:00
Lance Stout
9ba5b644cf Add XEP-0196 for User Gaming, from mathieui 2013-03-11 16:26:26 -07:00
Lance Stout
f76f0c3787 Merge pull request #226 from imo/develop
Correct argument order by using keyword for keepalive.
2013-03-06 22:15:26 -08:00
Patrick Horn
01abd6a705 Correct argument order by using keyword for keepalive. 2013-03-06 21:35:07 -08:00
Lance Stout
44e2b5d945 Bump version in prep for 1.2.0 2013-02-28 11:53:24 -08:00
Lance Stout
82bbe5d1a6 Merge branch 'develop' 2013-02-25 09:53:35 -08:00
Lance Stout
a1d71d31e8 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2013-02-22 10:08:35 -08:00
Lance Stout
766e0b685d Clear out iterable data when resetting Disco items. 2013-02-22 10:07:19 -08:00
Lance Stout
58f5e4702b Merge pull request #224 from anton-ryzhov/no_deprecated_methods_usage
Don't use internally deprecated methods
2013-02-21 13:55:34 -08:00
Anton Ryzhov
d9906756cf Don't use internally deprecated methods 2013-02-22 01:48:03 +04:00
Lance Stout
9a45ebd98b Merge branch 'develop' 2013-02-19 01:01:09 -08:00
Lance Stout
7f9ff9d0e7 Use requested_jid instead of boundjid during binding. 2013-02-18 11:56:04 -08:00
Lance Stout
8c763fcf43 Merge pull request #223 from anton-ryzhov/resource_generation
Fixed resource generation via uuid
2013-02-18 11:54:20 -08:00
Anton Ryzhov
6dd4456b11 Fixed resource generation via uuid 2013-02-18 23:41:13 +04:00
Lance Stout
c30c47d291 Add XEP-0020 support. 2013-02-15 10:36:31 -08:00
Lance Stout
d8c9662302 Resolve most Python3.3 related issues.
Tests now run successfully. Occasionally get single error related to
duplicated payload data in pubsub items when copying stanza values.
2013-02-14 01:24:09 -08:00
Lance Stout
ec5e819b16 Merge branch 'develop' 2013-02-12 09:38:57 -08:00
Lance Stout
55e50ad979 Add XEP-0079 Advanced Message Processing plugin. 2013-02-12 01:30:44 -08:00
Lance Stout
99ecb166d3 More caps cleanup 2013-02-11 20:01:53 -08:00
Lance Stout
cdeae7e72f Make legacy caps log more useful, until we support legacy caps. 2013-02-11 19:53:57 -08:00
Lance Stout
fbf79755d7 Track which verstrings are being checked, so we don't request duplicates. 2013-02-11 19:49:38 -08:00
Lance Stout
78bd21b7cf Support using messages for IBB data transfer. 2013-02-11 18:08:34 -08:00
Lance Stout
88c7c29954 Ensure gmail last result time and tid are always updated. 2013-02-10 11:55:49 -08:00
Lance Stout
d4dde89ea6 Remove old_* plugins. 2013-02-10 11:47:58 -08:00
Lance Stout
774bf35fab Tidy up a bit 2013-02-10 11:47:47 -08:00
Lance Stout
1a2db7fb11 Merge branch 'xep_0077' 2013-02-10 11:30:37 -08:00
Lance Stout
da3223ac92 Merge branch 'develop' 2013-02-10 11:29:57 -08:00
Lance Stout
b0fed5a48d Merge pull request #221 from Roger/fix_gmail_notify_check
fixed gmail new mail notification check
2013-02-10 11:27:23 -08:00
roger
43132dab85 fix not blocking gmail notification check 2013-02-09 12:31:13 -03:00
roger
badd327360 fixed gmail new mail notification check
without the tid gmail ignores the query
2013-02-09 11:34:46 -03:00
Lance Stout
9a6bfc6614 Enable force_registration in the register account example. 2013-02-08 09:44:39 -08:00
Lance Stout
79914fb56b Add option to XEP-0077 plugin to force registration attempts. 2013-02-08 09:36:51 -08:00
Lance Stout
75a792eb6f Fix HTML-IM lang support. 2013-02-08 09:09:07 -08:00
Lance Stout
23f112602c Get tests to pass again.
Re-add old gmail_notify plugin for now.
2013-01-26 15:15:01 -08:00
Lance Stout
639a3aa832 Add Google plugins to setup.py 2013-01-26 14:40:23 -08:00
Lance Stout
79a8c5ceae Add proper XEP-0071 plugin. 2013-01-26 14:33:52 -08:00
Lance Stout
97a2f4449d Handle lang='*' in disco info 2013-01-26 14:31:08 -08:00
Lance Stout
7f42d15175 Fix ping event issue. 2013-01-26 14:12:44 -08:00
Lance Stout
ef9c8e910c Tighten up session checks in XEP-0050 plugin. 2013-01-25 09:52:29 -08:00
Lance Stout
a1b33da9ca Refactor Google GTalk extensions into a single meta plugin. 2013-01-24 23:05:05 -08:00
Lance Stout
1741059cf6 Add Google JID Domain Discovery plugin 2013-01-24 02:46:16 -08:00
Lance Stout
1f137735e1 Put StringPrep exception handler with the right try block. 2013-01-24 02:45:28 -08:00
Lance Stout
a186972f09 Ensure XMPPError.text is a string. 2013-01-24 02:45:14 -08:00
Lance Stout
751628401e Fixes for vCard avatar hash calculations and MUC considerations. 2013-01-24 02:44:27 -08:00
Lance Stout
403b1802ec Update tostring to inject xmlns definitions when needed. 2013-01-24 02:43:46 -08:00
Lance Stout
9165cbf7f6 Cleanup and expand XEP-0065 plugin. 2013-01-23 02:18:27 -08:00
Lance Stout
bad405bea9 Merge branch 'master' into develop 2013-01-21 02:34:22 -08:00
Lance Stout
4f9a95b011 Add plugin for Google's nosave feature. 2013-01-21 01:39:08 -08:00
Lance Stout
903e641457 Fix issues in Google settings plugin. 2013-01-21 01:38:42 -08:00
Lance Stout
f34b9399cc Simplify Gmail notifications. 2013-01-21 01:38:02 -08:00
Lance Stout
7d0d96f940 Add Google Settings plugin. 2013-01-20 23:59:28 -08:00
Lance Stout
27196a21ae Modernize the Gmail plugin. 2013-01-20 23:01:54 -08:00
Lance Stout
ea0381fa09 Remove old versions of some plugins. 2013-01-20 21:35:06 -08:00
Lance Stout
3423589ba1 Updated XEP-0199 to take and return standardized values.
Handles Iq errors appropriately when the recipient can't be found.
2013-01-20 20:14:16 -08:00
Lance Stout
1f9286d39e Add BoB data to message and presence stanzas. 2013-01-20 18:44:17 -08:00
Lance Stout
93b8e66b5d Remove unused portions of XMLMask 2013-01-20 16:24:50 -08:00
Lance Stout
a1716de683 Update tests for XEP-0092 2013-01-20 16:08:01 -08:00
Lance Stout
ccf7916257 Allow for simplified XPath namespaces 2013-01-20 15:43:02 -08:00
Lance Stout
d86adfa1b1 Updated XEP-0092 to take callbacks and return the version result stanza. 2013-01-20 13:54:01 -08:00
Lance Stout
648f3f978a Merge branch 'master' into develop 2013-01-16 23:37:05 -08:00
Lance Stout
5e4b8bd67c Ensure that initial vCard requests are sent. 2013-01-16 23:36:22 -08:00
Lance Stout
64ef690432 Merge branch 'master' into develop 2013-01-16 23:06:41 -08:00
Lance Stout
41991b5982 Fix logging for vcard lookup failure. 2013-01-16 23:06:13 -08:00
Lance Stout
01da222d67 Merge branch 'master' into develop 2013-01-16 22:33:05 -08:00
Lance Stout
518eee05c2 Set vCard avatar hash on startup. 2013-01-16 22:32:40 -08:00
Lance Stout
1dbfa29a1e Merge branch 'master' into develop 2013-01-16 16:26:35 -08:00
Lance Stout
6bac4741f6 Fix setting autojoin in bookmarks. 2013-01-16 16:26:19 -08:00
Lance Stout
a0266dac6f Merge branch 'master' into develop 2013-01-11 17:19:29 -08:00
Lance Stout
ce977a7809 Don't reset exponential backoff delay until a stream has been confirmed. 2013-01-11 17:18:58 -08:00
Lance Stout
8644a83ed9 Merge branch 'master' into develop 2013-01-09 22:14:00 -08:00
Lance Stout
7b45245b1d Fix sending BOB data in Python3 2013-01-09 22:13:44 -08:00
Lance Stout
f04f4e4a1a Merge branch 'master' into develop 2013-01-08 21:14:34 -08:00
Lance Stout
b07f1b3bd3 Give X-FACEBOOK-PLATFORM precedence over DIGEST-MD5 2013-01-08 21:14:05 -08:00
Lance Stout
0e7486d7b4 Merge branch 'master' into develop 2013-01-04 03:00:43 -08:00
Lance Stout
6c0afb87b9 Add XEP-0048 support 2013-01-04 00:32:14 -08:00
Lance Stout
e5750b368e Fix setting publish options for pubsub storage. 2013-01-04 00:25:46 -08:00
Lance Stout
ef76f923ad Merge branch 'master' into develop 2013-01-02 17:05:35 -08:00
Oskari Timperi
2c04ae084c util/sasl/mechanisms.py: SASLMutualAuthFailed not defined
SASLMutualAuthFailed was not imported from sleekxmpp.util.sasl.client
2013-01-02 17:05:23 -08:00
Lance Stout
91dc58d967 Fix startup issues with components using caps and vcards. 2013-01-02 17:04:27 -08:00
Lance Stout
0e2abe74d5 Merge pull request #215 from oswjk/patch-1
util/sasl/mechanisms.py: SASLMutualAuthFailed not defined
2013-01-02 12:57:02 -08:00
Oskari Timperi
fea444925e util/sasl/mechanisms.py: SASLMutualAuthFailed not defined
SASLMutualAuthFailed was not imported from sleekxmpp.util.sasl.client
2013-01-02 16:53:16 +02:00
Lance Stout
0998429b07 Merge branch 'master' into develop 2012-12-29 15:17:34 -08:00
Lance Stout
597eb1779c Fix other instance of inet_pton usage. 2012-12-29 15:17:15 -08:00
Lance Stout
9ae3a7dbff Merge branch 'master' into develop 2012-12-21 13:44:08 -08:00
Lance Stout
3519e845a3 Apparently twisted fills in inet_pton on Windows and uses different exceptions. 2012-12-21 13:43:38 -08:00
Lance Stout
29c049612a Merge branch 'master' into develop 2012-12-18 10:33:33 -08:00
Lance Stout
ed48185732 Fix unicode conversion in Python3 2012-12-18 10:33:14 -08:00
Lance Stout
f431bbfca2 Merge branch 'master' into develop 2012-12-14 09:37:45 -08:00
Lance Stout
8b29900be4 Fix some Python3 compatibility issues. 2012-12-14 09:37:29 -08:00
Lance Stout
6f8a4f8354 Merge branch 'master' into develop 2012-12-03 12:42:53 -08:00
Lance Stout
def34f0e42 Fix requesting channel binding from sockets that don't support it. 2012-12-03 12:42:30 -08:00
Lance Stout
e25a49f804 Merge branch 'master' into develop 2012-11-27 19:57:48 -05:00
Lance Stout
b820351f64 Fix DIGEST-MD5 support for picky servers 2012-11-27 19:54:46 -05:00
Lance Stout
0eb009496e Use the username credential instead of jid to enable ANONYMOUS auth. 2012-11-27 19:53:43 -05:00
Lance Stout
2c2498b658 Allow for more credential values to be user specified instead of auto-filled. 2012-11-27 19:53:04 -05:00
Pedro Vicente
a1d988fed5 Merge branch 'upgrading_sleekxmpp_1_1_11' into develop_sleek 2012-11-12 13:55:32 +01:00
Pedro Vicente
b0c50b7a59 Added gevent as parameter to testall checking test suite with gevent
enabled/disabled
2012-11-12 13:48:20 +01:00
Pedro Vicente
1a2b404076 Checked if gevent thread is patched to do the right import 2012-11-12 13:33:09 +01:00
Lance Stout
2d066c34fd Merge branch 'master' into develop 2012-11-09 09:58:51 -08:00
Lance Stout
7a1ed64985 Don't clobber SASL config when specifying sasl_mech in ClientXMPP constructor. 2012-11-09 09:57:20 -08:00
Lance Stout
1b449585f7 Merge pull request #205 from SeyZ/develop
Xep 0065 improvements
2012-11-04 16:01:22 -08:00
Sandro Munda
032d41dbb8 Adapted the xep_0065 plugin to be compatible with all kind of others XMPP
client.
Sent a 'socks_connected' xmpp event when the streamer is connected.
2012-11-04 11:44:33 +01:00
Sandro Munda
3a7569e3ea Avoided to log a debug message error when the socket is normally closed. 2012-11-01 11:38:55 +01:00
Sandro Munda
d444930494 Improved the gitignore files (vim temp file, .pyo file and .baboon directory).
Automatically pack & unpack data through the socket.

Added some comments to the pack method.

Handled possible error during the unpacking of data.
2012-11-01 11:17:05 +01:00
Lance Stout
6045a6bfb3 Bump version to 1.1.11 2012-11-01 11:17:05 +01:00
Lance Stout
f3f543b31e Merge branch 'master' into develop 2012-10-31 13:55:49 -07:00
Lance Stout
0fea4262ea Bump version to 1.1.11 2012-10-31 13:55:30 -07:00
Lance Stout
4b7ec4a32a Merge branch 'master' into develop 2012-10-31 13:42:32 -07:00
Lance Stout
2229ad8d8e Relax timing issues in Iq timeout callback test. 2012-10-31 13:41:55 -07:00
Joe Hildebrand
61aff9f49a update JID_CACHE logic again. 2012-10-31 13:27:31 -07:00
Joe Hildebrand
67235c4214 Allow IQ timeouts to be asynchronous, by passing a timeout_callback parameter to send(). An example modification of disco is included. If this approach is approved, I'll go through and update the other plugins. 2012-10-31 13:27:06 -07:00
Lance Stout
a00eee1bbe Merge branch 'master' into develop 2012-10-31 00:17:26 -07:00
Lance Stout
12e8bb6ddc Turns out not all data is UTF-8, so don't try to decode it.
Fixes issue #204
2012-10-31 00:16:58 -07:00
Lance Stout
06a690a259 Merge branch 'master' into develop 2012-10-24 13:07:19 -07:00
Paul Molodowitch
52feabbe76 added setdefaultencoding method so reload(sys) not needed
reload(sys) could cause problem in user code - ie, sys.stdout, excepthook, and displayhook would be reset, etc
2012-10-24 13:06:36 -07:00
Lance Stout
14c9e9a9cc Merge branch 'master' into develop 2012-10-24 13:00:01 -07:00
Lance Stout
a22ca228cc Lock the bound JID in the JID cache. 2012-10-24 12:56:54 -07:00
Lance Stout
d0666a5eb6 Update JID cache to do extra memoization and locking.
Passing cache_lock=True to JID() will insert the JID into the cache
and prevent it from being dropped from the cache.
2012-10-24 12:47:25 -07:00
Lance Stout
931d49560a Merge branch 'master' into develop 2012-10-24 01:23:08 -07:00
Lance Stout
2a4e435228 Enable gevent support.
Closes issues #166 and #167

Thanks to @pvicent, @chason, and @gabriel-samfira
2012-10-24 01:20:23 -07:00
Lance Stout
3655827ef2 Merge branch 'master' into develop 2012-10-22 20:10:07 -07:00
Lance Stout
c5046b9c91 Fix JID cache (wrong in-progress version comitted earlier) 2012-10-22 20:09:35 -07:00
Lance Stout
4598031dd2 Respond to probes when the subscription is 'from', not 'to'. 2012-10-22 19:22:27 -07:00
Lance Stout
12e0e1a16b Merge branch 'master' into develop 2012-10-22 13:58:12 -07:00
Lance Stout
5e9266ba90 Optimize generating JIDs with some caching. 2012-10-22 13:57:49 -07:00
Lance Stout
0d448b8221 Merge branch 'master' into develop 2012-10-19 00:15:21 -07:00
Lance Stout
e6c95f0a2a Add support for XEP-0257: Client Certificate Management for SASL EXTERNAL 2012-10-19 00:06:45 -07:00
Lance Stout
63b58edda1 Allow passing form instructions as a list of strings. 2012-10-19 00:06:45 -07:00
Lance Stout
af9632519c Always cache published vcard 2012-10-18 12:26:50 -07:00
Lance Stout
d367fb938d Recognize plugin stanzas when they're appended. 2012-10-18 12:26:17 -07:00
Lance Stout
77f2a339e1 Merge branch 'master' into develop 2012-10-15 22:27:30 -07:00
Lance Stout
4190027a78 Prevent xmlns="" in stream output.
This was causing problems for HTML-IM because the HTML is parsed
without a namespaced context.

While xmlns="" technically can be valid, it's usually wrong, so this will work
for now until the HTML-IM parsing is fixed.
2012-10-15 22:22:07 -07:00
Lance Stout
ef48a8c4d9 Simplify xep-0084 avatar metadata publishing. 2012-10-15 22:20:38 -07:00
Lance Stout
829b225053 Fix vcard-temp stanzas to include organization data. 2012-10-15 22:20:13 -07:00
Lance Stout
747a6e94e6 Auto-subscribe to whitelisted JIDs if auto_subscribe is true 2012-10-15 22:19:47 -07:00
Lance Stout
cebc798e72 Merge branch 'stream_features' 2012-10-15 15:00:23 -07:00
Lance Stout
7c485c6a8b Merge branch 'master' into develop 2012-10-14 17:35:37 -07:00
Lance Stout
e2e8c4b5dc Remove unneeded ssl_support checks. 2012-10-10 11:42:24 -07:00
Lance Stout
675c0112ac Correct handling deleting plugins when xml:lang is active. 2012-10-10 11:07:25 -07:00
Lance Stout
4dd2c15775 Update carbons plugin to use latest spec. 2012-10-10 10:48:30 -07:00
Lance Stout
9f6decdbc1 Fix XEP-0078 error handling 2012-10-05 09:49:04 -07:00
Lance Stout
fc07e23ff8 Merge branch 'master' into develop 2012-10-05 08:58:22 -07:00
Lance Stout
4ea328b9f2 Fix empty namespaces in XEP-0045 plugin. 2012-10-05 08:58:04 -07:00
Lance Stout
84a2fc382b Merge branch 'master' into develop 2012-10-02 09:51:24 -07:00
Lance Stout
098714b3c4 Unclobber connected event handler names.
Fixes issue #199
2012-10-02 09:25:30 -07:00
Lance Stout
cf2c94d974 Add stream_negotiated event.
Fires after all stream features have been processed.
2012-10-01 16:28:31 -07:00
Lance Stout
657102e938 Update legacy auth to be used outside of stream features.
Also, add detection of legacy XMPP version.
2012-10-01 16:27:55 -07:00
Lance Stout
44e7585bf8 Merge branch 'master' into develop 2012-09-30 17:15:13 -07:00
Lance Stout
94488fa2ea Expand warning for missing ASN1 parser to include pyasn1_modules 2012-09-30 17:14:45 -07:00
Lance Stout
a2c60a4911 Merge branch 'master' into develop 2012-09-28 11:02:57 -07:00
Lance Stout
ee9c4abd08 Add support for XEP-0091: Legacy Delayed Delivery 2012-09-26 01:47:45 -07:00
Lance Stout
b5b1c932c7 Add support for XEP-0013: Flexible Offline Message Retrieval 2012-09-26 01:47:05 -07:00
Lance Stout
b8f04983e1 Allow disco queries to got to server when no JID is specified and marked not local. 2012-09-26 01:42:51 -07:00
Lance Stout
90807dd973 Fix RSM tests 2012-09-25 23:22:49 -07:00
Lance Stout
ef974114ea Add support for XEP-0313: Message Archive Management
NOTE: XEP-0313 is still very experimental, and there will likely be
      API changes in the future.
2012-09-25 20:20:43 -07:00
Lance Stout
f6e1fecdf8 Add Collector stanza handler class.
This style of handler is necessary for capturing result sets from
queries that use multiple messages to send the results instead of
in a single result stanza. Notably, XEP-0313 (MAM).
2012-09-25 20:20:22 -07:00
Lance Stout
94e8b2becf Update RSM iterator to specify where to look to count result sizes. 2012-09-25 20:17:37 -07:00
Lance Stout
a6ca6701a0 Add XEP-0308 Last Message Correction support 2012-09-25 12:35:53 -07:00
Lance Stout
c4edb9724b Fix copyright year 2012-09-25 12:27:44 -07:00
Lance Stout
b5c669bdff Add options to auto add ID values to message and presence stanzas. 2012-09-25 12:26:56 -07:00
Lance Stout
e449dce65c Fix handling forwarded stanzas to do proper lookups and deletions. 2012-09-25 12:25:45 -07:00
Lance Stout
73ce9a5ecc Merge branch 'master' into develop 2012-09-25 02:45:48 -07:00
Lance Stout
671f680bb3 Add support for XEP-0280 Message Carbons 2012-09-25 02:34:51 -07:00
Lance Stout
dfff19ffbf Add XEP-0297: Stanza Forwarding support 2012-09-24 22:59:19 -07:00
Lance Stout
a4abdf9fa6 Fix deleting non-existent stanza plugins. 2012-09-24 21:00:23 -07:00
Lance Stout
6c57bb0553 Simplify stringifying XML 2012-09-24 20:59:51 -07:00
Lance Stout
d385b9e708 Merge branch 'master' into develop 2012-09-18 10:37:04 -07:00
Lance Stout
c2ae1ee891 Remove race condition when aborting while connecting/reconnecting. 2012-09-18 10:35:53 -07:00
Lance Stout
67147570e9 Merge branch 'master' into develop 2012-09-13 11:00:58 -07:00
Lance Stout
fb3e6b7e35 Don't break checking certs for localhost. 2012-09-13 11:00:29 -07:00
Lance Stout
cf28d4586d Add support for XEP-0049: Private XML Storage 2012-09-11 20:39:32 -07:00
Lance Stout
f65eb5eeea Add support for Google's X-OAUTH2 SASL mechanism 2012-09-11 20:29:22 -07:00
Lance Stout
26fa9bd87e Don't perform caps lookup if the disco info is already known. 2012-09-11 20:28:28 -07:00
Lance Stout
0016d9a638 Add support for XEP-0279: Server IP Check 2012-09-04 20:39:43 -07:00
Lance Stout
a88b9737ff Add support for XEP-0235: OAuth over XMPP 2012-09-04 19:42:49 -07:00
Lance Stout
df9ac58d05 Merge branch 'master' into develop 2012-09-01 13:57:24 -07:00
Lance Stout
357406d801 Map <group /> elements with no content to '' instead of None. 2012-09-01 13:56:48 -07:00
Lance Stout
19a78f63f4 Merge branch 'master' into develop 2012-08-24 11:51:03 -07:00
Lance Stout
c7ec6a72cd Add catch-all chatstate event. 2012-08-24 11:47:21 -07:00
Florian Fieber
e68b07dbce Fix get_blocked() in XEP-0191 2012-08-24 11:44:56 -07:00
Lance Stout
e20610ab80 Merge pull request #197 from FlorianFieber/develop
Fix get_blocked() in XEP-0191
2012-08-23 11:04:30 -07:00
Lance Stout
1ca0c46333 Special plugin loading case for xep_0115 no longer needed. 2012-08-23 00:23:32 -07:00
Florian Fieber
e510875f64 Fix certificate expiration scheduler
timedelta.seconds does not store the total seconds of a time span.
Internally, seconds is the next smaller unit to days, hence
timedelta.seconds will never exceed (or reach) the number of seconds
in a day (60*60*24=86400)
2012-08-23 00:22:22 -07:00
Florian Fieber
f52a10b061 Fix get_blocked() in XEP-0191 2012-08-23 03:57:05 +02:00
Lance Stout
7d382a2bfd Merge pull request #195 from FlorianFieber/develop
Fix certificate expiration scheduler
2012-08-19 10:11:17 -07:00
Florian Fieber
09bec1c4fe Fix certificate expiration scheduler
timedelta.seconds does not store the total seconds of a time span.
Internally, seconds is the next smaller unit to days, hence
timedelta.seconds will never exceed (or reach) the number of seconds
in a day (60*60*24=86400)
2012-08-19 16:40:22 +02:00
Lance Stout
ff28b0a005 Merge branch 'master' into develop 2012-08-17 10:18:11 -07:00
Lance Stout
8a03bd72ae Ensure that auth is done based on the original, requested JID and not on the bound JID. 2012-08-17 10:17:35 -07:00
Lance Stout
a249f8736a Merge branch 'master' into develop 2012-08-14 11:06:54 -07:00
Lance Stout
f0e1fc5aad Fix using PLAIN over older SSL method. 2012-08-14 11:06:36 -07:00
Lance Stout
f09adf0014 Merge branch 'master' into develop 2012-08-14 09:55:05 -07:00
Lance Stout
c6ac64ed2d Help prevent race condition dealing with auto_reconnect 2012-08-14 09:54:38 -07:00
Lance Stout
04dc68f5f6 Merge branch 'master' into develop 2012-08-13 11:12:41 -07:00
Lance Stout
92be051450 Handle Iq errors/timeouts in XEP-0153 hash reset. 2012-08-13 11:09:35 -07:00
Lance Stout
5c25208fb5 Merge branch 'master' into develop 2012-08-12 22:36:23 -07:00
Lance Stout
779c258e27 Fix ISO date parsing fallback.
Closes issue #194
2012-08-12 22:35:42 -07:00
Lance Stout
962dfad216 Merge branch 'master' into develop 2012-08-10 14:15:55 -07:00
Lance Stout
f7a710e55b Add abort() method to kill the session and stop all processing without properly closing the stream. 2012-08-10 14:12:05 -07:00
Lance Stout
814a50e36f Fix handling state machine lock when quick exiting. 2012-08-10 14:11:44 -07:00
Lance Stout
230465b946 Fix unicode conversion utility. 2012-08-10 12:41:29 -07:00
Lance Stout
d11a67702e Exit transition immediately if already in the desired state. 2012-08-10 12:41:02 -07:00
Lance Stout
4e12e228cb Fix tracking service name for DIGEST-MD5 2012-08-10 12:40:28 -07:00
Lance Stout
4a94aeba49 Save a user's chosen, persistent nickname in the MUC roster data as 'alt_nick'
The use of <nick /> elements in MUCs is now discouraged in XEP-0172, however.
2012-08-07 19:33:17 -07:00
Lance Stout
14aa831169 Merge branch 'master' into develop 2012-08-07 16:45:20 -07:00
Lance Stout
295d23ccf3 Fix disco browser example to handle errors. 2012-08-07 16:44:52 -07:00
Lance Stout
75d904ed01 Merge branch 'master' into develop 2012-08-07 01:40:29 -07:00
Lance Stout
aebcf6ff82 Re-add connection delay after exhausting DNS records. 2012-08-07 01:38:15 -07:00
Lance Stout
8c2ece3bca Ensure self._der_cert exists even if no certs are used. 2012-08-04 21:37:46 -07:00
Lance Stout
80a90a6221 Prevent auto_reconnect interference when disconnecting. 2012-08-04 21:10:45 -07:00
Lance Stout
f81d5e4bd6 Merge branch 'master' into develop 2012-08-02 13:47:37 -07:00
Lance Stout
2324c90232 Ensure default authzids are handled. 2012-08-02 13:47:06 -07:00
Lance Stout
2f65fdbc76 Merge branch 'master' into develop 2012-08-01 23:03:56 -07:00
Lance Stout
59ff08174f Fix SASL exceptions in Py3 2012-08-01 17:43:38 -07:00
Lance Stout
2f4149c7d0 Merge branch 'master' into develop 2012-08-01 11:11:54 -07:00
Lance Stout
b84e359770 Use the proper mappings for nodeprep. 2012-08-01 11:11:40 -07:00
Lance Stout
fb4275648c Merge branch 'master' into develop 2012-08-01 09:05:47 -07:00
Lance Stout
475ccfa8dc Use correct method for getting channel binding. 2012-08-01 09:04:58 -07:00
Lance Stout
267c24c8ef Fix encoding issue in Python3. 2012-08-01 09:04:41 -07:00
Lance Stout
06a9d9fc30 Merge branch 'master' into develop
Conflicts:
	sleekxmpp/thirdparty/__init__.py
2012-07-31 21:33:19 -07:00
Lance Stout
1383ca19b5 Fix disco in XEP-0050 plugin.
Closes issue #191
2012-07-31 09:20:57 -07:00
Lance Stout
4c3ff2abab Add XEP-0242 plugin for 2010 Client Compliance 2012-07-30 22:07:49 -07:00
Lance Stout
7c6ef18e4f Add initial support for XEP-0016 Privacy Lists 2012-07-30 22:07:24 -07:00
Lance Stout
f8856467d5 Fix setup.py after moving SASL stuff. 2012-07-30 22:06:55 -07:00
Lance Stout
3bd84b8d27 Ignore roster updates with unrecognized subscription values. 2012-07-30 19:44:13 -07:00
Lance Stout
bc8b5774ac Fix logging of SASL errors. 2012-07-30 19:43:49 -07:00
Lance Stout
8009b0485e Add stream feature for server support of subscription pre-approvals. 2012-07-30 19:30:01 -07:00
Lance Stout
8742a56b3e Actually commit file of byte and hash utilities. 2012-07-30 19:29:33 -07:00
Lance Stout
a792bcdafe Ensure that sasl mechs that don't require security options work. 2012-07-30 19:15:10 -07:00
Lance Stout
167d1ce97b Add fields for setting client cert and key for SASL EXTERNAL. 2012-07-30 19:15:10 -07:00
Lance Stout
695cd95657 Update and integrate Suelta. 2012-07-30 19:15:10 -07:00
Lance Stout
44ce01a70b Merge branch 'master' into develop 2012-07-30 09:08:58 -07:00
Lance Stout
e4b4c67637 Bump version to 1.1.10 2012-07-30 09:04:15 -07:00
Lance Stout
422e77ae40 Don't wait to retry connection if out of DNS records. 2012-07-29 17:26:04 -07:00
Lance Stout
5ae6c8f8fa Add support for XEP-0131: Standard Headers and Internet Metadata 2012-07-28 01:06:21 -07:00
Lance Stout
54656b331a Restrict caps updates to available presences (not subscriptions, etc). 2012-07-27 15:51:35 -07:00
Lance Stout
9047b627a4 Only broadcast vCard hashes for available presences (not subscriptions, etc). 2012-07-27 15:48:15 -07:00
Lance Stout
6645a3be40 Compile JID pattern regex. 2012-07-27 11:24:01 -07:00
Lance Stout
c2189b4ecd Merge branch 'master' into develop 2012-07-27 10:45:52 -07:00
Jonas Wielicki
e3fab66dfb Allow tasks to remove themselves during execution
The scheduler class is now capable with dealing with tasks which remove
themselves from the scheduler during execution.

Additionally, some optimizations were applied by use of iterators and
some functions better suited for the purpose.

Please peer-review, all tests pass.
2012-07-27 10:45:23 -07:00
Lance Stout
5867f08bf1 Improve docs and fix typo in stringprep profiles. 2012-07-26 23:35:23 -07:00
Lance Stout
a06fa2de67 Enhance plugin config with attribute accessors.
This makes updating the config after plugin initialization much easier.
2012-07-26 23:04:16 -07:00
Lance Stout
c9b2cf6043 Merge branch 'master' into develop 2012-07-26 12:24:34 -07:00
Lance Stout
35396d2977 Don't include a 'from' JID when requesting vCards as a client. 2012-07-26 11:55:54 -07:00
Lance Stout
3bff743d9f Fix logging statement for MUC invitations. 2012-07-26 11:53:07 -07:00
Lance Stout
5a878f829b Fix error with session binding in components. 2012-07-26 11:50:59 -07:00
Lance Stout
16ec0f151a Merge branch 'master' into develop 2012-07-25 01:47:26 -07:00
Lance Stout
26dc6e90ea Add example for setting an avatar. 2012-07-25 01:37:03 -07:00
Lance Stout
94c749fd5a Fix avatar hash advertising. 2012-07-25 01:36:31 -07:00
Lance Stout
7b80ed0807 Substitute a blank JID for the boundjid in API calls. 2012-07-25 01:33:44 -07:00
Lance Stout
98b7e8b10a Fix initializing plugins in stanzas with a language set. 2012-07-25 01:33:17 -07:00
Lance Stout
c42f1ad4c7 Merge branch 'master' into develop 2012-07-24 20:01:18 -07:00
Lance Stout
9d8de7fc15 Fix publish vcard avatars, and PEP avatar metadata. 2012-07-24 19:43:39 -07:00
Lance Stout
70883086b7 Modify update_roster() to only change the information provided.
Before: Not specifying the groups, name, etc would remove them from the
        roster entry.

After: Any parameters not specified are populated with the current
       roster entry's values.
2012-07-24 16:48:24 -07:00
Lance Stout
9a08dfc7d4 Add support for using CDATA for escaping.
CDATA escaping is disabled by default, but may be enabled by setting:

    self.use_cdata = True

Closes issue #114
2012-07-24 03:25:55 -07:00
Lance Stout
3e43b36a9d Standardize importing of queue class.
This will make it easier to enable gevent support.
2012-07-24 02:39:54 -07:00
Lance Stout
352ee2f2fd Fix JID validation bugs, add lots of tests. 2012-07-24 01:43:20 -07:00
Lance Stout
78aa5c3dfa Add more validation for 0 length JID components. 2012-07-24 01:43:20 -07:00
Lance Stout
613323b5fb Finish docstrings for jid.py 2012-07-24 01:43:20 -07:00
Lance Stout
6c4b01db8a Add plugin for advertising XEP-0106 support. 2012-07-24 01:43:20 -07:00
Lance Stout
d06897a635 Add backwards compatibility shim for the old jid.py location. 2012-07-24 01:43:20 -07:00
Lance Stout
1600bb0aaf Cleanup and docs. 2012-07-24 01:43:20 -07:00
Lance Stout
b5c9c98a8b Add JID escaping support. 2012-07-24 01:43:20 -07:00
Lance Stout
e4e18a416f Add validation for JIDs. 2012-07-24 01:43:20 -07:00
Lance Stout
01cc0e6def Add 'by' attribute for error stanzas. 2012-07-23 21:48:19 -07:00
Lance Stout
a3ec1af205 Merge branch 'master' into develop 2012-07-23 01:52:55 -07:00
ekini
d571d691a7 old clients still support xep-184/1.0 version
Now psi (and probably miranda) correctly receive delivery receipts.
2012-07-23 01:52:45 -07:00
Lance Stout
2e580304f9 Merge branch 'master' into develop 2012-07-22 14:02:26 -07:00
Lance Stout
fb221a8dc0 Add XEP-0133 support, which just makes the appropriate XEP-0050 calls. 2012-07-22 13:58:23 -07:00
Lance Stout
459e1ed345 Handle Windows newlines in XEP-0027.
Closes issue #184
2012-07-22 12:15:46 -07:00
Lance Stout
6680c244f5 Fix deprecation warning for setting self.resource 2012-07-20 22:04:36 -07:00
Lance Stout
06423964ec Fix description of XEP-0222 plugin. 2012-07-20 22:03:17 -07:00
Lance Stout
5492e9028d Merge branch 'master' into develop 2012-07-20 18:15:54 -07:00
Lance Stout
474390fa00 Add example for retrieving avatars. 2012-07-20 18:10:14 -07:00
Lance Stout
81d3723084 Add event for vCard avatar update. 2012-07-20 18:07:27 -07:00
Lance Stout
32e798967e Fix see-other-host handling if no host is actually given. Also, limit number of consecutive redirection attempts. 2012-07-20 15:28:18 -07:00
Lance Stout
060c9ab679 Merge branch 'master' into develop 2012-07-20 00:25:32 -07:00
Lance Stout
acd9c32a9f Bump version to 1.1.9 2012-07-20 00:17:53 -07:00
Lance Stout
b8581b0278 Of course Peter goes and changes the XEP title the day after I implement it. 2012-07-19 23:59:35 -07:00
Lance Stout
917faecdcb Fix issue of roster data being split across multiple rosters.
Resolved by always normalizing JIDs to bare form, regardless of if they
are JID objects or strings.

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

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

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

Remaining items:

- Stanza path matching does not support language specifiers for normal
  interfaces, only for plugins.
2012-06-18 22:00:33 -07:00
Lance Stout
3d2d11f169 Update stream features stanza to work with new plugin keys. 2012-06-18 22:00:33 -07:00
Lance Stout
181aea737d Add initial support for xml:lang for streams and stanza plugins.
Remaining items are suitable default actions for language supporting
interfaces.
2012-06-18 22:00:33 -07:00
Lance Stout
1ab66e5767 Add example for dealing with GTalk custom domain certificates. 2012-06-15 16:03:38 -07:00
Lance Stout
aab2682f9a Add examples for using IBB. 2012-06-15 16:03:22 -07:00
Lance Stout
55d332bcc8 Merge branch 'master' into develop 2012-06-15 15:36:30 -07:00
Lance Stout
ee702f4071 Bump version to 1.1.5 2012-06-15 15:36:01 -07:00
Lance Stout
a08c2161a7 Ensure that ssl_invalid_cert returns PEM formatted certifcate data. 2012-06-15 15:29:53 -07:00
Lance Stout
f89df6e70c Merge branch 'master' into develop 2012-06-13 09:27:47 -07:00
Lance Stout
0e36a01354 Bump version to 1.1.4 2012-06-13 09:17:08 -07:00
Lance Stout
c39ad7dfbb Prevent duplicate certificate expiration timers. 2012-06-13 09:13:33 -07:00
Lance Stout
b92ae706e9 Fix loading cached disco identity data. 2012-06-13 09:13:13 -07:00
Lance Stout
250d28e870 Properly handle certs with no extensions. 2012-06-11 08:28:02 -07:00
Lance Stout
19f65c8510 Simplify send_presence_subscription.
It is technically obsolete now, but remains because it set a default
subscription type of 'subscribe'.
2012-06-10 14:42:54 -07:00
Lance Stout
f70b49882f Fix XEP-0065 imports and naming for Python3. 2012-06-10 14:15:58 -07:00
Lance Stout
a7b092a305 Fix Python3 exception handling.
Fixes issue #173
2012-06-09 15:04:27 -07:00
Lance Stout
daa73a3f3c Merge branch 'master' into develop 2012-06-09 11:43:06 -07:00
Lance Stout
6997261c6b Bump version for 1.1.3 2012-06-09 11:32:03 -07:00
Lance Stout
0b51afe87a Add extra check for the cert in the expiration handler. 2012-06-09 11:05:18 -07:00
Lance Stout
6cfb5cb14c Add extra check for the cert in the expiration handler. 2012-06-09 11:01:45 -07:00
Lance Stout
8567d6034f Use False for use_tls for components.
A log message is shown for those who try to set it to True.

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

    ssl_invalid_cert
    ssl_invalid_chain
    ssl_expired_cert
2012-06-09 11:00:55 -07:00
Lance Stout
2b298766c9 Use False for use_tls for components.
A log message is shown for those who try to set it to True.

Fixes issue #171
2012-06-09 10:48:16 -07:00
Lance Stout
10664d723b Default use_tls to False for components.
Issue #171
2012-06-09 10:43:57 -07:00
Lance Stout
c012208a8f Merge pull request #170 from SeyZ/develop
Added the xep_0065 plugin in the setup.py
2012-06-09 10:37:49 -07:00
Lance Stout
0953896d2d Fix SSL handshake handling when not using legacy SSL.
Fixes issue #172
2012-06-09 10:32:25 -07:00
Sandro Munda
cf9e89d0ae Added the xep_0065 plugin in the setup.py 2012-06-09 18:45:58 +02:00
Lance Stout
48dd01b0bb Ensure that all SSL cert error handling is overridable using event handlers.
Relevant events:

    ssl_invalid_cert
    ssl_invalid_chain
    ssl_expired_cert
2012-06-08 09:31:44 -07:00
Lance Stout
7247efe055 Merge pull request #169 from SeyZ/develop
xep_0065 plugin (Socks5 Bytestreams)
2012-06-07 10:40:22 -07:00
Sandro Munda
8def3758e4 Added the get_socket(sid) method to the xep_0065 plugin to retrieve
the socket of the Proxy thread.
2012-06-07 19:36:25 +02:00
Sandro Munda
1851ab6f5f Added the SID in the socks_recv xmpp event in the xep_0065 plugin. 2012-06-07 19:24:23 +02:00
Sandro Munda
289b052338 Renamed Query to Socks5 in the xep_0065.
Renamed the 'q' plugin_attrib of the Socks5 stanza to 'socks'.
2012-06-07 19:14:37 +02:00
Sandro Munda
26147f5ae0 Added a top level field to the xep_0065 class:
name = 'xep_0065'
2012-06-07 19:08:20 +02:00
Sandro Munda
ae01f1071a Fixed the callback names of the xep_0065:
In-Band bytestreams -> Socks5 bytestreams
2012-06-07 19:04:24 +02:00
Sandro Munda
dcdf5dcd09 Added the Socksipy module in the thirdparty of SleekXMPP.
Updated the LICENSE file with the license of the Socksipy
module (New-BSD).
2012-06-07 19:02:09 +02:00
Sandro Munda
c59a6d0f51 Sent a socks_closed when the socket is closed in the xep_0065 plugin. 2012-06-07 18:38:57 +02:00
Sandro Munda
2cd936318d Improved the close of the proxy thread (and the socket) in the xep_0065 plugin. 2012-06-07 18:38:57 +02:00
Sandro Munda
2f38857681 Changed the description of the xep_0065 plugin 2012-06-07 18:38:56 +02:00
Sandro Munda
39505ae1ff The xep_0065 plugin supports now multiple stream (multiple connected
sockets).

To send data over a stream, we need to pass the SID in order to
retrieve the good proxy thread (and so, the good socket).
2012-06-07 18:38:56 +02:00
Sandro Munda
44ee0633f2 Renamed the _handle_on_recv to the on_recv method.
Renamed requester_thread and target_thread to proxy. The send method is now simpler.
2012-06-07 18:38:56 +02:00
Sandro Munda
b52d2768b0 Added some comments to the get_network_address method 2012-06-07 18:38:56 +02:00
Sandro Munda
cf24b870b1 Registered stanza plugin in the stanza module 2012-06-07 18:38:56 +02:00
Sandro Munda
69cffce7dc Used the namespace in all stanzas 2012-06-07 18:38:56 +02:00
Sandro Munda
a14979375b Added a partial support of the XEP 0065 - Socks5 Bytestreams 2012-06-07 18:38:56 +02:00
Sandro Munda
40ef4a16b1 Updated the .gitignore to add .ropeproject/ folder 2012-06-07 18:38:56 +02:00
Lance Stout
f5652a667b Add 'presence' event, raised for all incoming presence stanzas. 2012-06-06 16:10:25 -07:00
345 changed files with 18186 additions and 4772 deletions

7
.gitignore vendored
View File

@@ -1,4 +1,4 @@
*.pyc
*.py[co]
build/
dist/
MANIFEST
@@ -7,3 +7,8 @@ docs/_build/
.tox/
.coverage
sleekxmpp.egg-info/
.ropeproject/
4913
*~
.baboon/
.DS_STORE

31
LICENSE
View File

@@ -69,8 +69,8 @@ modification, are permitted provided that the following conditions are met:
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Red Innovation nor the names of its contributors
may be used to endorse or promote products derived from this software
* Neither the name of Red Innovation nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY RED INNOVATION ``AS IS'' AND ANY
@@ -119,7 +119,7 @@ SUELTA A PURE-PYTHON SASL CLIENT LIBRARY
This software is subject to "The MIT License"
Copyright 2007-2010 David Alan Cridland
Copyright 2004-2013 David Alan Cridland
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -167,3 +167,28 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
socksipy: A Python SOCKS client module.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Copyright 2006 Dan-Haim. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of Dan Haim nor the names of his contributors may be used
to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.

View File

@@ -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.3.0 <http://github.com/fritzy/SleekXMPP/zipball/1.3.0>`_
**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``.

View File

@@ -222,7 +222,7 @@ handler function to process registration requests.
self.description = "In-Band Registration"
self.xep = "0077"
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('In-Band Registration',
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
self.__handleRegistration))
@@ -601,7 +601,7 @@ with some additional registration fields implemented.
self.form_instructions = ""
self.backend = UserStore()
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('In-Band Registration',
MatchXPath('{%s}iq/{jabber:iq:register}query' % self.xmpp.default_ns),
self.__handleRegistration))

View File

@@ -6,14 +6,20 @@ Event Index
connected
- **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.clientxmpp.ClientXMPP`
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
Signal that a connection has been made with the XMPP server, but a session
has not yet been established.
connection_failed
- **Data:** ``{}`` or ``Failure Stanza`` if available
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
Signal that a connection can not be established after number of attempts.
changed_status
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem`
Triggered when a presence stanza is received from a JID with a show type
different than the last presence stanza from the same JID.
@@ -65,8 +71,8 @@ Event Index
disconnected
- **Data:** ``{}``
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
Signal that the connection with the XMPP server has been lost.
entity_time
@@ -93,16 +99,16 @@ Event Index
got_online
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem`
If a presence stanza is received from a JID which was previously marked as
offline, and the presence has a show type of '``chat``', '``dnd``', '``away``',
or '``xa``', then this event is triggered as well.
got_offline
- **Data:** :py:class:`~sleekxmpp.Presence`
- **Source:** :py:class:`~sleekxmpp.BaseXMPP`
- **Source:** :py:class:`~sleekxmpp.roster.item.RosterItem`
Signal that an unavailable presence stanza has been received from a JID.
groupchat_invite
@@ -110,7 +116,7 @@ Event Index
- **Source:**
groupchat_direct_invite
- **Data:** :py:class:`~sleekxmpp.Message`
- **Data:** :py:class:`~sleekxmpp.Message`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0249.direct`
groupchat_message
@@ -147,18 +153,18 @@ Event Index
sure to check the message type in order to handle error messages.
message_form
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
Currently the same as :term:`message_xform`.
message_xform
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
Triggered whenever a data form is received inside a message.
mucc::[room]::got_offline
muc::[room]::got_offline
- **Data:**
- **Source:**
@@ -187,8 +193,8 @@ Event Index
A presence stanza with a type of '``error``' is received.
presence_form
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
- **Data:** :py:class:`~sleekxmpp.plugins.xep_0004.Form`
- **Source:** :py:class:`~sleekxmpp.plugins.xep_0004.xep_0004`
This event is present in the XEP-0004 plugin code, but is currently not used.
@@ -229,22 +235,20 @@ Event Index
A presence stanza with a type of '``unsubscribed``' is received.
roster_update
- **Data:** :py:class:`~sleekxmpp.stanza.Roster`
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
- **Data:** :py:class:`~sleekxmpp.stanza.Roster`
- **Source:** :py:class:`~sleekxmpp.ClientXMPP`
An IQ result containing roster entries is received.
sent_presence
- **Data:** ``{}``
- **Source:** :py:class:`BaseXMPP <sleekxmpp.BaseXMPP>`
- **Source:** :py:class:`~sleekxmpp.roster.multi.Roster`
Signal that an initial presence stanza has been written to the XML stream.
session_end
- **Data:** ``{}``
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
Signal that a connection to the XMPP server has been lost and the current
stream session has ended. Currently equivalent to :term:`disconnected`, but
@@ -256,14 +260,14 @@ Event Index
session_start
- **Data:** ``{}``
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
- **Source:** :py:class:`ClientXMPP <sleekxmpp.ClientXMPP>`,
:py:class:`ComponentXMPP <sleekxmpp.ComponentXMPP>`
:py:class:`XEP-0078 <sleekxmpp.plugins.xep_0078>`
Signal that a connection to the XMPP server has been made and a session has been established.
socket_error
- **Data:** ``Socket`` exception object
- **Data:** ``Socket`` exception object
- **Source:** :py:class:`~sleekxmpp.xmlstream.XMLstream`
stream_error

View File

@@ -69,8 +69,8 @@ use ASCII. We can get Python to use UTF-8 as the default encoding by including:
.. code-block:: python
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
.. warning::

203
examples/IoT_TestDevice.py Executable file
View File

@@ -0,0 +1,203 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Implementation of xeps for Internet of Things
http://wiki.xmpp.org/web/Tech_pages/IoT_systems
Copyright (C) 2013 Sustainable Innovation, Joachim.lindborg@sust.se
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import os
import sys
# This can be used when you are in a test environment and need to make paths right
sys.path=['/Users/jocke/Dropbox/06_dev/SleekXMPP']+sys.path
import logging
import unittest
import distutils.core
import datetime
from glob import glob
from os.path import splitext, basename, join as pjoin
from optparse import OptionParser
from urllib import urlopen
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):
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
from sleekxmpp.plugins.xep_0323.device import Device
#from sleekxmpp.exceptions import IqError, IqTimeout
class IoT_TestDevice(sleekxmpp.ClientXMPP):
"""
A simple IoT device that can act as server or client
"""
def __init__(self, jid, password):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.add_event_handler("session_start", self.session_start)
self.add_event_handler("message", self.message)
self.device=None
self.releaseMe=False
self.beServer=True
self.clientJID=None
def datacallback(self,from_jid,result,nodeId=None,timestamp=None,fields=None,error_msg=None):
"""
This method will be called when you ask another IoT device for data with the xep_0323
se script below for the registration of the callback
"""
logging.debug("we got data %s from %s",str(result),from_jid)
def beClientOrServer(self,server=True,clientJID=None ):
if server:
self.beServer=True
self.clientJID=None
else:
self.beServer=False
self.clientJID=clientJID
def testForRelease(self):
# todo thread safe
return self.releaseMe
def doReleaseMe(self):
# todo thread safe
self.releaseMe=True
def addDevice(self, device):
self.device=device
def session_start(self, event):
self.send_presence()
self.get_roster()
# tell your preffered friend that you are alive
self.send_message(mto='jocke@jabber.sust.se', mbody=self.boundjid.bare +' is now online use xep_323 stanza to talk to me')
if not(self.beServer):
session=self['xep_0323'].request_data(self.boundjid.full,self.clientJID,self.datacallback)
def message(self, msg):
if msg['type'] in ('chat', 'normal'):
logging.debug("got normal chat message" + str(msg))
ip=urlopen('http://icanhazip.com').read()
msg.reply("Hi I am " + self.boundjid.full + " and I am on IP " + ip).send()
else:
logging.debug("got unknown message type %s", str(msg['type']))
class TheDevice(Device):
"""
This is the actual device object that you will use to get information from your real hardware
You will be called in the refresh method when someone is requesting information from you
"""
def __init__(self,nodeId):
Device.__init__(self,nodeId)
self.counter=0
def refresh(self,fields):
"""
the implementation of the refresh method
"""
self._set_momentary_timestamp(self._get_timestamp())
self.counter+=self.counter
self._add_field_momentary_data(self, "Temperature", self.counter)
if __name__ == '__main__':
# Setup the command line arguments.
#
# This script can act both as
# "server" an IoT device that can provide sensorinformation
# python IoT_TestDevice.py -j "serverjid@yourdomain.com" -p "password" -n "TestIoT" --debug
#
# "client" an IoT device or other party that would like to get data from another device
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)
optp.add_option('-t', '--pingto', help='set jid to ping',
action='store', type='string', dest='pingjid',
default=None)
# 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")
# IoT test
optp.add_option("-c", "--sensorjid", dest="sensorjid",
help="Another device to call for data on", default=None)
optp.add_option("-n", "--nodeid", dest="nodeid",
help="I am a device get ready to be called", default=None)
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 = IoT_TestDevice(opts.jid,opts.password)
xmpp.register_plugin('xep_0030')
#xmpp['xep_0030'].add_feature(feature='urn:xmpp:iot:sensordata',
# node=None,
# jid=None)
xmpp.register_plugin('xep_0323')
xmpp.register_plugin('xep_0325')
if opts.nodeid:
# xmpp['xep_0030'].add_feature(feature='urn:xmpp:sn',
# node=opts.nodeid,
# jid=xmpp.boundjid.full)
myDevice = TheDevice(opts.nodeid);
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
myDevice._set_momentary_timestamp("2013-03-07T16:24:30")
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"});
xmpp['xep_0323'].register_node(nodeId=opts.nodeid, device=myDevice, commTimeout=10);
xmpp.beClientOrServer(server=True)
while not(xmpp.testForRelease()):
xmpp.connect()
xmpp.process(block=True)
logging.debug("lost connection")
if opts.sensorjid:
logging.debug("will try to call another device for data")
xmpp.beClientOrServer(server=False,clientJID=opts.sensorjid)
xmpp.connect()
xmpp.process(block=True)
logging.debug("ready ending")
else:
print "noopp didn't happen"

View File

@@ -21,8 +21,8 @@ import sleekxmpp
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

View File

@@ -21,8 +21,8 @@ import sleekxmpp
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

178
examples/admin_commands.py Executable file
View 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):
from sleekxmpp.util.misc_ops import setdefaultencoding
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.")

View File

@@ -28,8 +28,8 @@ from stanza import Action
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
@@ -51,13 +51,13 @@ class ActionBot(sleekxmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
self.registerHandler(
self.register_handler(
Callback('Some custom iq',
StanzaPath('iq@type=set/action'),
self._handle_action))
self.add_event_handler('custom_action',
self._handle_action_event,
self.add_event_handler('custom_action',
self._handle_action_event,
threaded=True)
register_stanza_plugin(Iq, Action)

View File

@@ -26,8 +26,8 @@ from stanza import Action
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

View File

@@ -15,6 +15,7 @@ import getpass
from optparse import OptionParser
import sleekxmpp
from sleekxmpp.exceptions import IqError, IqTimeout
# Python versions before 3.0 do not use UTF-8 encoding
@@ -22,8 +23,8 @@ import sleekxmpp
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
@@ -83,50 +84,54 @@ class Disco(sleekxmpp.ClientXMPP):
self.get_roster()
self.send_presence()
if self.get in self.info_types:
# By using block=True, the result stanza will be
# returned. Execution will block until the reply is
# received. Non-blocking options would be to listen
# for the disco_info event, or passing a handler
# function using the callback parameter.
info = self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node,
block=True)
if self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.
items = self['xep_0030'].get_items(jid=self.target_jid,
node=self.target_node,
block=True)
try:
if self.get in self.info_types:
# By using block=True, the result stanza will be
# returned. Execution will block until the reply is
# received. Non-blocking options would be to listen
# for the disco_info event, or passing a handler
# function using the callback parameter.
info = self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node,
block=True)
elif self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.
items = self['xep_0030'].get_items(jid=self.target_jid,
node=self.target_node,
block=True)
else:
logging.error("Invalid disco request type.")
return
except IqError as e:
logging.error("Entity returned an error: %s" % e.iq['error']['condition'])
except IqTimeout:
logging.error("No response received.")
else:
logging.error("Invalid disco request type.")
self.disconnect()
return
header = 'XMPP Service Discovery: %s' % self.target_jid
print(header)
print('-' * len(header))
if self.target_node != '':
print('Node: %s' % self.target_node)
header = 'XMPP Service Discovery: %s' % self.target_jid
print(header)
print('-' * len(header))
if self.target_node != '':
print('Node: %s' % self.target_node)
print('-' * len(header))
if self.get in self.identity_types:
print('Identities:')
for identity in info['disco_info']['identities']:
print(' - %s' % str(identity))
if self.get in self.identity_types:
print('Identities:')
for identity in info['disco_info']['identities']:
print(' - %s' % str(identity))
if self.get in self.feature_types:
print('Features:')
for feature in info['disco_info']['features']:
print(' - %s' % feature)
if self.get in self.feature_types:
print('Features:')
for feature in info['disco_info']['features']:
print(' - %s' % feature)
if self.get in self.items_types:
print('Items:')
for item in items['disco_items']['items']:
print(' - %s' % str(item))
self.disconnect()
if self.get in self.items_types:
print('Items:')
for item in items['disco_items']['items']:
print(' - %s' % str(item))
finally:
self.disconnect()
if __name__ == '__main__':

184
examples/download_avatars.py Executable file
View 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):
from sleekxmpp.util.misc_ops import setdefaultencoding
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.")

View File

@@ -21,8 +21,8 @@ import sleekxmpp
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
@@ -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

View File

@@ -22,8 +22,8 @@ from sleekxmpp.componentxmpp import ComponentXMPP
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

165
examples/gtalk_custom_domain.py Executable file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import sys
import logging
import getpass
from optparse import OptionParser
import sleekxmpp
import ssl
from sleekxmpp.xmlstream import cert
# Python versions before 3.0 do not use UTF-8 encoding
# by default. To ensure that Unicode is handled properly
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
class GTalkBot(sleekxmpp.ClientXMPP):
"""
A demonstration of using SleekXMPP with accounts from a Google Apps
account with a custom domain, because it requires custom certificate
validation.
"""
def __init__(self, jid, password):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use. We want to
# listen for this event so that we we can initialize
# our roster.
self.add_event_handler("session_start", self.start)
# The message event is triggered whenever a message
# stanza is received. Be aware that that includes
# MUC messages and error messages.
self.add_event_handler("message", self.message)
# Using a Google Apps custom domain, the certificate
# does not contain the custom domain, just the GTalk
# server name. So we will need to process invalid
# certifcates ourselves and check that it really
# is from Google.
self.add_event_handler("ssl_invalid_cert", self.invalid_cert)
def invalid_cert(self, pem_cert):
der_cert = ssl.PEM_cert_to_DER_cert(pem_cert)
try:
cert.verify('talk.google.com', der_cert)
logging.debug("CERT: Found GTalk certificate")
except cert.CertificateError as err:
log.error(err.message)
self.disconnect(send_close=False)
def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an initial
presence stanza.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.send_presence()
self.get_roster()
def message(self, msg):
"""
Process incoming message stanzas. Be aware that this also
includes MUC messages and error messages. It is usually
a good idea to check the messages's type before processing
or sending replies.
Arguments:
msg -- The received message stanza. See the documentation
for stanza objects and the Message stanza to see
how it may be used.
"""
if msg['type'] in ('chat', 'normal'):
msg.reply("Thanks for sending\n%(body)s" % msg).send()
if __name__ == '__main__':
# Setup the command line arguments.
optp = OptionParser()
# Output verbosity options.
optp.add_option('-q', '--quiet', help='set logging to ERROR',
action='store_const', dest='loglevel',
const=logging.ERROR, default=logging.INFO)
optp.add_option('-d', '--debug', help='set logging to DEBUG',
action='store_const', dest='loglevel',
const=logging.DEBUG, default=logging.INFO)
optp.add_option('-v', '--verbose', help='set logging to COMM',
action='store_const', dest='loglevel',
const=5, default=logging.INFO)
# JID and password options.
optp.add_option("-j", "--jid", dest="jid",
help="JID to use")
optp.add_option("-p", "--password", dest="password",
help="password to use")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
# Setup the GTalkBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = GTalkBot(opts.jid, opts.password)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0004') # Data Forms
xmpp.register_plugin('xep_0060') # PubSub
xmpp.register_plugin('xep_0199') # XMPP Ping
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
# If you do not have the dnspython library installed, you will need
# to manually specify the name of the server if it does not match
# the one in the JID. For example, to use Google Talk you would
# need to use:
#
# if xmpp.connect(('talk.google.com', 5222)):
# ...
xmpp.process(block=True)
print("Done")
else:
print("Unable to connect.")

View File

@@ -0,0 +1,147 @@
#!/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):
from sleekxmpp.util.misc_ops import setdefaultencoding
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', {
'auto_accept': True
}) # 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, threaded=True)
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):
print('Stream opened: %s from %s' % (stream.sid, stream.peer_jid))
# You could run a loop reading from the stream using stream.recv(),
# or use the ibb_stream_data event.
def stream_data(self, event):
print(event['data'])
if __name__ == '__main__':
# Setup the command line arguments.
optp = OptionParser()
# Output verbosity options.
optp.add_option('-q', '--quiet', help='set logging to ERROR',
action='store_const', dest='loglevel',
const=logging.ERROR, default=logging.INFO)
optp.add_option('-d', '--debug', help='set logging to DEBUG',
action='store_const', dest='loglevel',
const=logging.DEBUG, default=logging.INFO)
optp.add_option('-v', '--verbose', help='set logging to COMM',
action='store_const', dest='loglevel',
const=5, default=logging.INFO)
# JID and password options.
optp.add_option("-j", "--jid", dest="jid",
help="JID to use")
optp.add_option("-p", "--password", dest="password",
help="password to use")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.jid is None:
opts.jid = raw_input("Username: ")
if opts.password is None:
opts.password = getpass.getpass("Password: ")
xmpp = IBBReceiver(opts.jid, opts.password)
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
# Connect to the XMPP server and start processing XMPP stanzas.
if xmpp.connect():
# If you do not have the dnspython library installed, you will need
# to manually specify the name of the server if it does not match
# the one in the JID. For example, to use Google Talk you would
# need to use:
#
# if xmpp.connect(('talk.google.com', 5222)):
# ...
xmpp.process(block=True)
print("Done")
else:
print("Unable to connect.")

View File

@@ -0,0 +1,145 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import sys
import logging
import getpass
from optparse import OptionParser
import sleekxmpp
# Python versions before 3.0 do not use UTF-8 encoding
# by default. To ensure that Unicode is handled properly
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
from sleekxmpp.util.misc_ops import setdefaultencoding
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.")

120
examples/migrate_roster.py Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
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):
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
# 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("--oldjid", dest="old_jid",
help="JID of the old account")
optp.add_option("--oldpassword", dest="old_password",
help="password of the old account")
optp.add_option("--newjid", dest="new_jid",
help="JID of the old account")
optp.add_option("--newpassword", dest="new_password",
help="password of the old account")
opts, args = optp.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.old_jid is None:
opts.old_jid = raw_input("Old JID: ")
if opts.old_password is None:
opts.old_password = getpass.getpass("Old Password: ")
if opts.new_jid is None:
opts.new_jid = raw_input("New JID: ")
if opts.new_password is None:
opts.new_password = getpass.getpass("New Password: ")
old_xmpp = sleekxmpp.ClientXMPP(opts.old_jid, opts.old_password)
# 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
# If you want to verify the SSL certificates offered by a server:
# xmpp.ca_certs = "path/to/ca/cert"
roster = []
def on_session(event):
roster.append(old_xmpp.get_roster())
old_xmpp.disconnect()
old_xmpp.add_event_handler('session_start', on_session)
if old_xmpp.connect():
old_xmpp.process(block=True)
if not roster:
print('No roster to migrate')
sys.exit()
new_xmpp = sleekxmpp.ClientXMPP(opts.new_jid, opts.new_password)
def on_session2(event):
new_xmpp.get_roster()
new_xmpp.send_presence()
logging.info(roster[0])
data = roster[0]['roster']['items']
logging.info(data)
for jid, item in data.items():
if item['subscription'] != 'none':
new_xmpp.send_presence(ptype='subscribe', pto=jid)
new_xmpp.update_roster(jid,
name = item['name'],
groups = item['groups'])
new_xmpp.disconnect()
new_xmpp.add_event_handler('session_start', on_session2)
if new_xmpp.connect():
new_xmpp.process(block=True)

View File

@@ -21,8 +21,8 @@ import sleekxmpp
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

View File

@@ -21,8 +21,8 @@ import sleekxmpp
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
@@ -37,7 +37,7 @@ class PingTest(sleekxmpp.ClientXMPP):
def __init__(self, jid, password, pingjid):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
if pingjid is None:
pingjid = self.jid
pingjid = self.boundjid.bare
self.pingjid = pingjid
# The session_start event will be triggered when
@@ -62,16 +62,18 @@ class PingTest(sleekxmpp.ClientXMPP):
"""
self.send_presence()
self.get_roster()
result = self['xep_0199'].send_ping(self.pingjid,
timeout=10,
errorfalse=True)
logging.info("Pinging...")
if result is False:
logging.info("Couldn't ping.")
self.disconnect()
sys.exit(1)
else:
logging.info("Success! RTT: %s", str(result))
try:
rtt = self['xep_0199'].ping(self.pingjid,
timeout=10)
logging.info("Success! RTT: %s", rtt)
except IqError as e:
logging.info("Error pinging %s: %s",
self.pingjid,
e.iq['error']['condition'])
except IqTimeout:
logging.info("No response from %s", self.pingjid)
finally:
self.disconnect()

View File

@@ -21,8 +21,8 @@ import sleekxmpp
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

11
examples/pubsub_client.py Normal file → Executable file
View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import logging
import getpass
@@ -12,15 +15,15 @@ from sleekxmpp.xmlstream import ET, tostring
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
class PubsubClient(sleekxmpp.ClientXMPP):
def __init__(self, jid, password, server,
def __init__(self, jid, password, server,
node=None, action='list', data=''):
super(PubsubClient, self).__init__(jid, password)
@@ -28,7 +31,7 @@ class PubsubClient(sleekxmpp.ClientXMPP):
self.register_plugin('xep_0059')
self.register_plugin('xep_0060')
self.actions = ['nodes', 'create', 'delete',
self.actions = ['nodes', 'create', 'delete',
'publish', 'get', 'retract',
'purge', 'subscribe', 'unsubscribe']

9
examples/pubsub_events.py Normal file → Executable file
View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import logging
import getpass
@@ -14,8 +17,8 @@ from sleekxmpp.xmlstream.handler import Callback
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
@@ -77,7 +80,7 @@ class PubsubEvents(sleekxmpp.ClientXMPP):
"""Handle receiving a node deletion event."""
print('Deleted node %s' % (
msg['pubsub_event']['delete']['node']))
def _config(self, msg):
"""Handle receiving a node configuration event."""
print('Configured node %s:' % (

12
examples/register_account.py Normal file → Executable file
View File

@@ -22,8 +22,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input
@@ -51,7 +51,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
# The register event provides an Iq result stanza with
# a registration form from the server. This may include
# the basic registration fields, a data form, an
# the basic registration fields, a data form, an
# out-of-band URL, or any combination. For more advanced
# cases, you will need to examine the fields provided
# and respond accordingly. SleekXMPP provides plugins
@@ -104,7 +104,7 @@ class RegisterBot(sleekxmpp.ClientXMPP):
resp.send(now=True)
logging.info("Account created for %s!" % self.boundjid)
except IqError as e:
logging.error("Could not register account: %s" %
logging.error("Could not register account: %s" %
e.iq['error']['text'])
self.disconnect()
except IqTimeout:
@@ -153,6 +153,10 @@ if __name__ == '__main__':
xmpp.register_plugin('xep_0066') # Out-of-band Data
xmpp.register_plugin('xep_0077') # In-band Registration
# Some servers don't advertise support for inband registration, even
# though they allow it. If this applies to your server, use:
xmpp['xep_0077'].force_registration = True
# If you are working with an OpenFire server, you may need
# to adjust the SSL version used:
# xmpp.ssl_version = ssl.PROTOCOL_SSLv3

4
examples/roster_browser.py Normal file → Executable file
View File

@@ -24,8 +24,8 @@ from sleekxmpp.exceptions import IqError, IqTimeout
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

29
examples/rpc_async.py Normal file → Executable file
View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens
@@ -11,34 +14,34 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
import time
class Boomerang(Endpoint):
def FQN(self):
return 'boomerang'
@remote
def throw(self):
print "Duck!"
def main():
session = Remote.new_session('kangaroo@xmpp.org/rpc', '*****')
session.new_handler(ANY_ALL, Boomerang)
session.new_handler(ANY_ALL, Boomerang)
boomerang = session.new_proxy('kangaroo@xmpp.org/rpc', Boomerang)
callback = Future()
boomerang.async(callback).throw()
time.sleep(10)
session.close()
if __name__ == '__main__':
main()

29
examples/rpc_client_side.py Normal file → Executable file
View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens
@@ -12,18 +15,18 @@ import threading
import time
class Thermostat(Endpoint):
def FQN(self):
return 'thermostat'
def __init__(self, initial_temperature):
self._temperature = initial_temperature
self._event = threading.Event()
self._event = threading.Event()
@remote
def set_temperature(self, temperature):
return NotImplemented
@remote
def get_temperature(self):
return NotImplemented
@@ -31,23 +34,23 @@ class Thermostat(Endpoint):
@remote(False)
def release(self):
return NotImplemented
def main():
session = Remote.new_session('operator@xmpp.org/rpc', '*****')
thermostat = session.new_proxy('thermostat@xmpp.org/rpc', Thermostat)
print("Current temperature is %s" % thermostat.get_temperature())
thermostat.set_temperature(20)
time.sleep(10)
session.close()
if __name__ == '__main__':
main()

31
examples/rpc_server_side.py Normal file → Executable file
View File

@@ -1,3 +1,6 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Dann Martens
@@ -11,42 +14,42 @@ from sleekxmpp.plugins.xep_0009.remote import Endpoint, remote, Remote, \
import threading
class Thermostat(Endpoint):
def FQN(self):
return 'thermostat'
def __init__(self, initial_temperature):
self._temperature = initial_temperature
self._event = threading.Event()
self._event = threading.Event()
@remote
def set_temperature(self, temperature):
print("Setting temperature to %s" % temperature)
self._temperature = temperature
@remote
def get_temperature(self):
return self._temperature
@remote(False)
def release(self):
self._event.set()
self._event.set()
def wait_for_release(self):
self._event.wait()
self._event.wait()
def main():
session = Remote.new_session('sleek@xmpp.org/rpc', '*****')
thermostat = session.new_handler(ANY_ALL, Thermostat, 18)
thermostat.wait_for_release()
session.close()
if __name__ == '__main__':
main()

View File

@@ -21,8 +21,8 @@ import sleekxmpp
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

174
examples/set_avatar.py Executable file
View 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):
from sleekxmpp.util.misc_ops import setdefaultencoding
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.")

4
examples/thirdparty_auth.py Normal file → Executable file
View File

@@ -29,8 +29,8 @@ from sleekxmpp.xmlstream import JID
# throughout SleekXMPP, we will set the default encoding
# ourselves to UTF-8.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
else:
raw_input = input

0
examples/user_location.py Normal file → Executable file
View File

0
examples/user_tune.py Normal file → Executable file
View File

View File

@@ -42,6 +42,7 @@ CLASSIFIERS = [ 'Intended Audience :: Developers',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.1',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Topic :: Software Development :: Libraries :: Python Modules',
]
@@ -49,6 +50,8 @@ packages = [ 'sleekxmpp',
'sleekxmpp/stanza',
'sleekxmpp/test',
'sleekxmpp/roster',
'sleekxmpp/util',
'sleekxmpp/util/sasl',
'sleekxmpp/xmlstream',
'sleekxmpp/xmlstream/matcher',
'sleekxmpp/xmlstream/handler',
@@ -58,37 +61,73 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0009',
'sleekxmpp/plugins/xep_0009/stanza',
'sleekxmpp/plugins/xep_0012',
'sleekxmpp/plugins/xep_0013',
'sleekxmpp/plugins/xep_0016',
'sleekxmpp/plugins/xep_0020',
'sleekxmpp/plugins/xep_0027',
'sleekxmpp/plugins/xep_0030',
'sleekxmpp/plugins/xep_0030/stanza',
'sleekxmpp/plugins/xep_0033',
'sleekxmpp/plugins/xep_0047',
'sleekxmpp/plugins/xep_0048',
'sleekxmpp/plugins/xep_0049',
'sleekxmpp/plugins/xep_0050',
'sleekxmpp/plugins/xep_0054',
'sleekxmpp/plugins/xep_0059',
'sleekxmpp/plugins/xep_0060',
'sleekxmpp/plugins/xep_0060/stanza',
'sleekxmpp/plugins/xep_0065',
'sleekxmpp/plugins/xep_0066',
'sleekxmpp/plugins/xep_0071',
'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_0091',
'sleekxmpp/plugins/xep_0092',
'sleekxmpp/plugins/xep_0095',
'sleekxmpp/plugins/xep_0096',
'sleekxmpp/plugins/xep_0107',
'sleekxmpp/plugins/xep_0108',
'sleekxmpp/plugins/xep_0115',
'sleekxmpp/plugins/xep_0118',
'sleekxmpp/plugins/xep_0128',
'sleekxmpp/plugins/xep_0131',
'sleekxmpp/plugins/xep_0152',
'sleekxmpp/plugins/xep_0153',
'sleekxmpp/plugins/xep_0172',
'sleekxmpp/plugins/xep_0184',
'sleekxmpp/plugins/xep_0186',
'sleekxmpp/plugins/xep_0191',
'sleekxmpp/plugins/xep_0196',
'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_0235',
'sleekxmpp/plugins/xep_0249',
'sleekxmpp/plugins/xep_0257',
'sleekxmpp/plugins/xep_0258',
'sleekxmpp/plugins/xep_0279',
'sleekxmpp/plugins/xep_0280',
'sleekxmpp/plugins/xep_0297',
'sleekxmpp/plugins/xep_0308',
'sleekxmpp/plugins/xep_0313',
'sleekxmpp/plugins/xep_0319',
'sleekxmpp/plugins/xep_0323',
'sleekxmpp/plugins/xep_0323/stanza',
'sleekxmpp/plugins/xep_0325',
'sleekxmpp/plugins/xep_0325/stanza',
'sleekxmpp/plugins/google',
'sleekxmpp/plugins/google/gmail',
'sleekxmpp/plugins/google/auth',
'sleekxmpp/plugins/google/settings',
'sleekxmpp/plugins/google/nosave',
'sleekxmpp/features',
'sleekxmpp/features/feature_mechanisms',
'sleekxmpp/features/feature_mechanisms/stanza',
@@ -96,9 +135,8 @@ packages = [ 'sleekxmpp',
'sleekxmpp/features/feature_bind',
'sleekxmpp/features/feature_session',
'sleekxmpp/features/feature_rosterver',
'sleekxmpp/features/feature_preapproval',
'sleekxmpp/thirdparty',
'sleekxmpp/thirdparty/suelta',
'sleekxmpp/thirdparty/suelta/mechanisms',
]
setup(

View File

@@ -6,13 +6,25 @@
See the file LICENSE for copying permission.
"""
from sleekxmpp.basexmpp import BaseXMPP
from sleekxmpp.clientxmpp import ClientXMPP
from sleekxmpp.componentxmpp import ComponentXMPP
import logging
if hasattr(logging, 'NullHandler'):
NullHandler = logging.NullHandler
else:
class NullHandler(logging.Handler):
def handle(self, record):
pass
logging.getLogger(__name__).addHandler(NullHandler())
del NullHandler
from sleekxmpp.stanza import Message, Presence, Iq
from sleekxmpp.jid import JID, InvalidJID
from sleekxmpp.xmlstream.stanzabase import ET, ElementBase, register_stanza_plugin
from sleekxmpp.xmlstream.handler import *
from sleekxmpp.xmlstream import XMLStream, RestartStream
from sleekxmpp.xmlstream.matcher import *
from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
from sleekxmpp.basexmpp import BaseXMPP
from sleekxmpp.clientxmpp import ClientXMPP
from sleekxmpp.componentxmpp import ComponentXMPP
from sleekxmpp.version import __version__, __version_info__

View File

@@ -16,24 +16,24 @@ class APIWrapper(object):
elif attr == 'settings':
return self.api.settings[self.name]
elif attr == 'register':
def curried_handler(handler, op, jid=None, node=None, default=False):
def partial(handler, op, jid=None, node=None, default=False):
register = getattr(self.api, attr)
return register(handler, self.name, op, jid, node, default)
return curried_handler
return partial
elif attr == 'register_default':
def curried_handler(handler, op, jid=None, node=None):
def partial(handler, op, jid=None, node=None):
return getattr(self.api, attr)(handler, self.name, op)
return curried_handler
return partial
elif attr in ('run', 'restore_default', 'unregister'):
def curried_handler(*args, **kwargs):
def partial(*args, **kwargs):
return getattr(self.api, attr)(self.name, *args, **kwargs)
return curried_handler
return partial
return None
def __getitem__(self, attr):
def curried_handler(jid=None, node=None, ifrom=None, args=None):
def partial(jid=None, node=None, ifrom=None, args=None):
return self.api.run(self.name, attr, jid, node, ifrom, args)
return curried_handler
return partial
class APIRegistry(object):
@@ -42,7 +42,7 @@ class APIRegistry(object):
self._handlers = {}
self._handler_defaults = {}
self.xmpp = xmpp
self.settings = {}
self.settings = {}
def _setup(self, ctype, op):
"""Initialize the API callback dictionaries.
@@ -99,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.

View File

@@ -16,9 +16,9 @@ from __future__ import with_statement, unicode_literals
import sys
import logging
import threading
import sleekxmpp
from sleekxmpp import plugins, features, roster
from sleekxmpp import plugins, roster, stanza
from sleekxmpp.api import APIRegistry
from sleekxmpp.exceptions import IqError, IqTimeout
@@ -31,9 +31,9 @@ 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
from sleekxmpp.plugins import PluginManager, load_plugin
log = logging.getLogger(__name__)
@@ -41,8 +41,8 @@ log = logging.getLogger(__name__)
# In order to make sure that Unicode is handled properly
# in Python 2.x, reset the default encoding.
if sys.version_info < (3, 0):
reload(sys)
sys.setdefaultencoding('utf8')
from sleekxmpp.util.misc_ops import setdefaultencoding
setdefaultencoding('utf8')
class BaseXMPP(XMLStream):
@@ -66,9 +66,22 @@ class BaseXMPP(XMLStream):
#: An identifier for the stream as given by the server.
self.stream_id = None
#: The JabberID (JID) used by this connection.
self.boundjid = JID(jid)
#: The JabberID (JID) requested for this connection.
self.requested_jid = JID(jid, cache_lock=True)
#: The JabberID (JID) used by this connection,
#: as set after session binding. This may even be a
#: different bare JID than what was requested.
self.boundjid = JID(jid, cache_lock=True)
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,23 +99,34 @@ 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
#: ``'to'`` and ``'from'`` JIDs of stanzas.
self.is_component = False
#: Messages may optionally be tagged with ID values. Setting
#: :attr:`use_message_ids` to `True` will assign all outgoing
#: messages an ID. Some plugin features require enabling
#: this option.
self.use_message_ids = False
#: Presence updates may optionally be tagged with ID values.
#: Setting :attr:`use_message_ids` to `True` will assign all
#: outgoing messages an ID.
self.use_presence_ids = False
#: 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
@@ -122,7 +146,7 @@ class BaseXMPP(XMLStream):
#: A reference to :mod:`sleekxmpp.stanza` to make accessing
#: stanza classes easier.
self.stanza = sleekxmpp.stanza
self.stanza = stanza
self.register_handler(
Callback('IM',
@@ -133,11 +157,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',
@@ -172,7 +199,6 @@ class BaseXMPP(XMLStream):
# Initialize a few default stanza plugins.
register_stanza_plugin(Iq, Roster)
register_stanza_plugin(Message, Nick)
register_stanza_plugin(Message, HTMLIM)
def start_stream_handler(self, xml):
"""Save the stream ID once the streams have been established.
@@ -180,6 +206,12 @@ 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)
if not self.is_component and not self.stream_version:
log.warning('Legacy XMPP 0.9 protocol detected.')
self.event('legacy_protocol')
def process(self, *args, **kwargs):
"""Initialize plugins and begin processing the XML stream.
@@ -199,20 +231,13 @@ 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
- The send queue processor
- The scheduler
"""
if 'xep_0115' in self.plugin:
name = 'xep_0115'
if not hasattr(self.plugin[name], 'post_inited'):
if hasattr(self.plugin[name], 'post_init'):
self.plugin[name].post_init()
self.plugin[name].post_inited = True
for name in self.plugin:
if not hasattr(self.plugin[name], 'post_inited'):
if hasattr(self.plugin[name], 'post_init'):
@@ -272,7 +297,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 +307,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 +358,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 +388,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 +418,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 +444,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 +477,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 +503,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 +527,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 +547,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 +558,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 +573,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 +608,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
@@ -647,7 +662,7 @@ class BaseXMPP(XMLStream):
def set_jid(self, jid):
"""Rip a JID apart and claim it as our own."""
log.debug("setting jid to %s", jid)
self.boundjid.full = jid
self.boundjid = JID(jid, cache_lock=True)
def getjidresource(self, fulljid):
if '/' in fulljid:
@@ -658,30 +673,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,10 +739,12 @@ 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()
if roster.auto_subscribe:
item.subscribe()
elif roster.auto_authorize:
item.authorize()
if roster.auto_subscribe:
@@ -704,30 +752,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 +775,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 +788,7 @@ class BaseXMPP(XMLStream):
return
def exception(self, exception):
"""Process any uncaught exceptions, notably
"""Process any uncaught exceptions, notably
:class:`~sleekxmpp.exceptions.IqError` and
:class:`~sleekxmpp.exceptions.IqTimeout` exceptions.

View File

@@ -54,30 +54,32 @@ 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)
self.escape_quotes = escape_quotes
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()
@@ -92,8 +94,9 @@ class ClientXMPP(BaseXMPP):
self.bound = False
self.bindfail = False
self.add_event_handler('connected', self._handle_connected)
self.add_event_handler('connected', self._reset_connection_state)
self.add_event_handler('session_bind', self._handle_session_bind)
self.add_event_handler('roster_update', self._handle_roster)
self.register_stanza(StreamFeatures)
@@ -104,15 +107,18 @@ class ClientXMPP(BaseXMPP):
self.register_handler(
Callback('Roster Update',
StanzaPath('iq@type=set/roster'),
self._handle_roster))
lambda iq: self.event('roster_update', iq)))
# Setup default stream features
self.register_plugin('feature_starttls')
self.register_plugin('feature_bind')
self.register_plugin('feature_session')
self.register_plugin('feature_mechanisms',
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
self.register_plugin('feature_rosterver')
self.register_plugin('feature_preapproval')
self.register_plugin('feature_mechanisms')
if sasl_mech:
self['feature_mechanisms'].use_mech = sasl_mech
@property
def password(self):
@@ -130,7 +136,7 @@ class ClientXMPP(BaseXMPP):
be attempted. If that fails, the server user in the JID
will be used.
:param address -- A tuple containing the server's host and port.
:param address: A tuple containing the server's host and port.
:param reattempt: If ``True``, repeat attempting to connect if an
error occurs. Defaults to ``True``.
:param use_tls: Indicates if TLS should be used for the
@@ -149,8 +155,6 @@ class ClientXMPP(BaseXMPP):
address = (self.boundjid.host, 5222)
self.dns_service = 'xmpp-client'
self._expected_server_name = self.boundjid.host
return XMLStream.connect(self, address[0], address[1],
use_tls=use_tls, use_ssl=use_ssl,
reattempt=reattempt)
@@ -170,8 +174,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 +195,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 +231,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.
@@ -224,17 +243,25 @@ class ClientXMPP(BaseXMPP):
if 'rosterver' in self.features:
iq['roster']['ver'] = self.client_roster.version
if not block and callback is None:
callback = lambda resp: self._handle_roster(resp)
if not block or callback is not None:
block = False
if callback is None:
callback = lambda resp: self.event('roster_update', resp)
else:
orig_cb = callback
def wrapped(resp):
self.event('roster_update', resp)
orig_cb(resp)
callback = wrapped
response = iq.send(block, timeout, callback)
self.event('roster_received', response)
if block:
self._handle_roster(response)
if block:
self.event('roster_update', response)
return response
def _handle_connected(self, event=None):
def _reset_connection_state(self, event=None):
#TODO: Use stream state here
self.authenticated = False
self.sessionstarted = False
@@ -254,6 +281,8 @@ class ClientXMPP(BaseXMPP):
# Don't continue if the feature requires
# restarting the XML stream.
return True
log.debug('Finished processing stream features.')
self.event('stream_negotiated')
def _handle_roster(self, iq):
"""Update the roster after receiving a roster stanza.
@@ -267,17 +296,19 @@ 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]
roster[jid]['name'] = item['name']
roster[jid]['groups'] = item['groups']
roster[jid]['from'] = item['subscription'] in ['from', 'both']
roster[jid]['to'] = item['subscription'] in ['to', 'both']
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
items = iq['roster']['items']
valid_subscriptions = ('to', 'from', 'both', 'none', 'remove')
for jid, item in items.items():
if item['subscription'] in valid_subscriptions:
roster[jid]['name'] = item['name']
roster[jid]['groups'] = item['groups']
roster[jid]['from'] = item['subscription'] in ('from', 'both')
roster[jid]['to'] = item['subscription'] in ('to', 'both')
roster[jid]['pending_out'] = (item['ask'] == 'subscribe')
roster[jid].save(remove=(item['subscription'] == 'remove'))
roster[jid].save(remove=(item['subscription'] == 'remove'))
self.event("roster_update", iq)
if iq['type'] == 'set':
resp = self.Iq(stype='result',
sto=iq['from'],

View File

@@ -40,8 +40,8 @@ class ComponentXMPP(BaseXMPP):
:param host: The server accepting the component.
:param port: The port used to connect to the server.
:param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
:meth:`~sleekxmpp.basexmpp.BaseXMPP.register_plugins()`.
:param use_jc_ns: Indicates if the ``'jabber:client'`` namespace
should be used instead of the standard
@@ -78,8 +78,8 @@ class ComponentXMPP(BaseXMPP):
self.add_event_handler('presence_probe',
self._handle_probe)
def connect(self, host=None, port=None, use_ssl=False,
use_tls=True, reattempt=True):
def connect(self, host=None, port=None, use_ssl=False,
use_tls=False, reattempt=True):
"""Connect to the server.
Setting ``reattempt`` to ``True`` will cause connection attempts to
@@ -104,10 +104,13 @@ class ComponentXMPP(BaseXMPP):
self.server_name = self.boundjid.host
if use_tls:
log.info("XEP-0114 components can not use TLS")
log.debug("Connecting to %s:%s", host, port)
return XMLStream.connect(self, host=host, port=port,
use_ssl=use_ssl,
use_tls=use_tls,
use_tls=False,
reattempt=reattempt)
def incoming_filter(self, xml):
@@ -120,12 +123,6 @@ class ComponentXMPP(BaseXMPP):
"""
if xml.tag.startswith('{jabber:client}'):
xml.tag = xml.tag.replace('jabber:client', self.default_ns)
# The incoming_filter call is only made on top level stanza
# elements. So we manually continue filtering on sub-elements.
for sub in xml:
self.incoming_filter(sub)
return xml
def start_stream_handler(self, xml):
@@ -153,10 +150,10 @@ class ComponentXMPP(BaseXMPP):
:param xml: The reply handshake stanza.
"""
self.session_bind_event.set()
self.session_started_event.set()
self.event("session_start")
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)

View File

@@ -42,7 +42,7 @@ class XMPPError(Exception):
Defaults to ``True``.
"""
def __init__(self, condition='undefined-condition', text=None,
def __init__(self, condition='undefined-condition', text='',
etype='cancel', extension=None, extension_ns=None,
extension_args=None, clear=True):
if extension_args is None:
@@ -69,10 +69,11 @@ class IqTimeout(XMPPError):
condition='remote-server-timeout',
etype='cancel')
#: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
#: The :class:`~sleekxmpp.stanza.iq.Iq` stanza whose response
#: did not arrive before the timeout expired.
self.iq = iq
class IqError(XMPPError):
"""

View File

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

View File

@@ -8,10 +8,11 @@
import logging
from sleekxmpp.jid import JID
from sleekxmpp.stanza import Iq, StreamFeatures
from sleekxmpp.features.feature_bind import stanza
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin, register_plugin
from sleekxmpp.plugins import BasePlugin
log = logging.getLogger(__name__)
@@ -40,24 +41,25 @@ class FeatureBind(BasePlugin):
Arguments:
features -- The stream features stanza.
"""
log.debug("Requesting resource: %s", self.xmpp.boundjid.resource)
log.debug("Requesting resource: %s", self.xmpp.requested_jid.resource)
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('bind')
if self.xmpp.boundjid.resource:
iq['bind']['resource'] = self.xmpp.boundjid.resource
if self.xmpp.requested_jid.resource:
iq['bind']['resource'] = self.xmpp.requested_jid.resource
response = iq.send(now=True)
self.xmpp.set_jid(response['bind']['jid'])
self.xmpp.boundjid = JID(response['bind']['jid'], cache_lock=True)
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')
log.info("Node set to: %s", self.xmpp.boundjid.full)
log.info("JID set to: %s", self.xmpp.boundjid.full)
if 'session' not in features['features']:
log.debug("Established Session")
self.xmpp.sessionstarted = True
self.xmpp.session_started_event.set()
self.xmpp.event("session_start")
self.xmpp.event('session_start')

View File

@@ -6,12 +6,11 @@
See the file LICENSE for copying permission.
"""
import ssl
import logging
from sleekxmpp.thirdparty import suelta
from sleekxmpp.thirdparty.suelta.exceptions import SASLCancelled, SASLError
from sleekxmpp.thirdparty.suelta.exceptions import SASLPrepFailure
from sleekxmpp.util import sasl
from sleekxmpp.util.stringprep_profiles import StringPrepError
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.xmlstream import RestartStream, register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
@@ -29,42 +28,32 @@ class FeatureMechanisms(BasePlugin):
description = 'RFC 6120: Stream Feature: SASL'
dependencies = set()
stanza = stanza
default_config = {
'use_mech': None,
'use_mechs': None,
'min_mech': None,
'sasl_callback': None,
'security_callback': None,
'encrypted_plain': True,
'unencrypted_plain': False,
'unencrypted_digest': False,
'unencrypted_cram': False,
'unencrypted_scram': True,
'order': 100
}
def plugin_init(self):
self.use_mech = self.config.get('use_mech', None)
if self.sasl_callback is None:
self.sasl_callback = self._default_credentials
if not self.use_mech and not self.xmpp.boundjid.user:
if self.security_callback is None:
self.security_callback = self._default_security
creds = self.sasl_callback(set(['username']), set())
if not self.use_mech and not creds['username']:
self.use_mech = 'ANONYMOUS'
def tls_active():
return 'starttls' in self.xmpp.features
def basic_callback(mech, values):
creds = self.xmpp.credentials
for value in values:
if value == 'username':
values['username'] = self.xmpp.boundjid.user
elif value == 'password':
values['password'] = creds['password']
elif value == 'email':
jid = self.xmpp.boundjid.bare
values['email'] = creds.get('email', jid)
elif value in creds:
values[value] = creds[value]
mech.fulfill(values)
sasl_callback = self.config.get('sasl_callback', None)
if sasl_callback is None:
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,
tls_active=tls_active,
mech=self.use_mech)
self.mech_list = set()
self.attempted_mechs = set()
@@ -95,7 +84,51 @@ class FeatureMechanisms(BasePlugin):
self.xmpp.register_feature('mechanisms',
self._handle_sasl_auth,
restart=True,
order=self.config.get('order', 100))
order=self.order)
def _default_credentials(self, required_values, optional_values):
creds = self.xmpp.credentials
result = {}
values = required_values.union(optional_values)
for value in values:
if value == 'username':
result[value] = creds.get('username', self.xmpp.requested_jid.user)
elif value == 'email':
jid = self.xmpp.requested_jid.bare
result[value] = creds.get('email', jid)
elif value == 'channel_binding':
if hasattr(self.xmpp.socket, 'get_channel_binding'):
result[value] = self.xmpp.socket.get_channel_binding()
else:
log.debug("Channel binding not supported.")
log.debug("Use Python 3.3+ for channel binding and " + \
"SCRAM-SHA-1-PLUS support")
result[value] = None
elif value == 'host':
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
elif value == 'realm':
result[value] = creds.get('realm', self.xmpp.requested_jid.domain)
elif value == 'service-name':
result[value] = creds.get('service-name', self.xmpp._service_name)
elif value == 'service':
result[value] = creds.get('service', 'xmpp')
elif value in creds:
result[value] = creds[value]
return result
def _default_security(self, values):
result = {}
for value in values:
if value == 'encrypted':
if 'starttls' in self.xmpp.features:
result[value] = True
elif isinstance(self.xmpp.socket, ssl.SSLSocket):
result[value] = True
else:
result[value] = False
else:
result[value] = self.config.get(value, False)
return result
def _handle_sasl_auth(self, features):
"""
@@ -109,37 +142,62 @@ class FeatureMechanisms(BasePlugin):
# server has incorrectly offered it again.
return False
if not self.use_mech:
self.mech_list = set(features['mechanisms'])
else:
self.mech_list = set([self.use_mech])
enforce_limit = False
limited_mechs = self.use_mechs
if limited_mechs is None:
limited_mechs = set()
elif limited_mechs and not isinstance(limited_mechs, set):
limited_mechs = set(limited_mechs)
enforce_limit = True
if self.use_mech:
limited_mechs.add(self.use_mech)
enforce_limit = True
if enforce_limit:
self.use_mechs = limited_mechs
self.mech_list = set(features['mechanisms'])
return self._send_auth()
def _send_auth(self):
mech_list = self.mech_list - self.attempted_mechs
self.mech = self.sasl.choose_mechanism(mech_list)
if mech_list and self.mech is not None:
resp = stanza.Auth(self.xmpp)
resp['mechanism'] = self.mech.name
try:
resp['value'] = self.mech.process()
except SASLCancelled:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
except SASLError:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
except SASLPrepFailure:
log.exception("A credential value did not pass SASLprep.")
self.xmpp.disconnect()
else:
resp.send(now=True)
else:
try:
self.mech = sasl.choose(mech_list,
self.sasl_callback,
self.security_callback,
limit=self.use_mechs,
min_mech=self.min_mech)
except sasl.SASLNoAppropriateMechanism:
log.error("No appropriate login method.")
self.xmpp.event("no_auth", direct=True)
self.xmpp.event("failed_auth", direct=True)
self.attempted_mechs = set()
return self.xmpp.disconnect()
except StringPrepError:
log.exception("A credential value did not pass SASLprep.")
self.xmpp.disconnect()
resp = stanza.Auth(self.xmpp)
resp['mechanism'] = self.mech.name
try:
resp['value'] = self.mech.process()
except sasl.SASLCancelled:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
except sasl.SASLFailed:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
except sasl.SASLMutualAuthFailed:
log.error("Mutual authentication failed! " + \
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
else:
resp.send(now=True)
return True
def _handle_challenge(self, stanza):
@@ -147,20 +205,35 @@ class FeatureMechanisms(BasePlugin):
resp = self.stanza.Response(self.xmpp)
try:
resp['value'] = self.mech.process(stanza['value'])
except SASLCancelled:
except sasl.SASLCancelled:
self.stanza.Abort(self.xmpp).send()
except SASLError:
except sasl.SASLFailed:
self.stanza.Abort(self.xmpp).send()
except sasl.SASLMutualAuthFailed:
log.error("Mutual authentication failed! " + \
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
else:
if resp.get_value() == '':
resp.del_value()
resp.send(now=True)
def _handle_success(self, stanza):
"""SASL authentication succeeded. Restart the stream."""
self.attempted_mechs = set()
self.xmpp.authenticated = True
self.xmpp.features.add('mechanisms')
self.xmpp.event('auth_success', stanza, direct=True)
raise RestartStream()
try:
final = self.mech.process(stanza['value'])
except sasl.SASLMutualAuthFailed:
log.error("Mutual authentication failed! " + \
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
else:
self.attempted_mechs = set()
self.xmpp.authenticated = True
self.xmpp.features.add('mechanisms')
self.xmpp.event('auth_success', stanza, direct=True)
raise RestartStream()
def _handle_fail(self, stanza):
"""SASL authentication failed. Disconnect and shutdown."""

View File

@@ -8,8 +8,7 @@
import base64
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.util import bytes
from sleekxmpp.xmlstream import StanzaBase
@@ -23,7 +22,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'])
@@ -41,7 +40,7 @@ class Auth(StanzaBase):
if not self['mechanism'] in self.plain_mechs:
if values:
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
else:
elif values == b'':
self.xml.text = '='
else:
self.xml.text = bytes(values).decode('utf-8')

View File

@@ -8,8 +8,7 @@
import base64
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.util import bytes
from sleekxmpp.xmlstream import StanzaBase

View File

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

View File

@@ -8,8 +8,7 @@
import base64
from sleekxmpp.thirdparty.suelta.util import bytes
from sleekxmpp.util import bytes
from sleekxmpp.xmlstream import StanzaBase

View File

@@ -6,8 +6,10 @@
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import StanzaBase
import base64
from sleekxmpp.util import bytes
from sleekxmpp.xmlstream import StanzaBase
class Success(StanzaBase):
@@ -16,9 +18,21 @@ class Success(StanzaBase):
name = 'success'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set()
interfaces = set(['value'])
plugin_attrib = name
def setup(self, xml):
StanzaBase.setup(self, xml)
self.xml.tag = self.tag_name()
def get_value(self):
return base64.b64decode(bytes(self.xml.text))
def set_value(self, values):
if values:
self.xml.text = bytes(base64.b64encode(values)).decode('utf-8')
else:
self.xml.text = '='
def del_value(self):
self.xml.text = ''

View File

@@ -0,0 +1,15 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.features.feature_preapproval.preapproval import FeaturePreApproval
from sleekxmpp.features.feature_preapproval.stanza import PreApproval
register_plugin(FeaturePreApproval)

View File

@@ -0,0 +1,42 @@
"""
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 logging
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.features.feature_preapproval import stanza
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin
log = logging.getLogger(__name__)
class FeaturePreApproval(BasePlugin):
name = 'feature_preapproval'
description = 'RFC 6121: Stream Feature: Subscription Pre-Approval'
dependences = set()
stanza = stanza
def plugin_init(self):
self.xmpp.register_feature('preapproval',
self._handle_preapproval,
restart=False,
order=9001)
register_stanza_plugin(StreamFeatures, stanza.PreApproval)
def _handle_preapproval(self, features):
"""Save notice that the server support subscription pre-approvals.
Arguments:
features -- The stream features stanza.
"""
log.debug("Server supports subscription pre-approvals.")
self.xmpp.features.add('preapproval')

View File

@@ -0,0 +1,17 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ElementBase
class PreApproval(ElementBase):
name = 'sub'
namespace = 'urn:xmpp:features:pre-approval'
interfaces = set()
plugin_attrib = 'preapproval'

View File

@@ -8,7 +8,7 @@
import logging
from sleekxmpp.stanza import Iq, StreamFeatures
from sleekxmpp.stanza import StreamFeatures
from sleekxmpp.features.feature_rosterver import stanza
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import BasePlugin

View File

@@ -51,4 +51,4 @@ class FeatureSession(BasePlugin):
log.debug("Established Session")
self.xmpp.sessionstarted = True
self.xmpp.session_started_event.set()
self.xmpp.event("session_start")
self.xmpp.event('session_start')

View File

@@ -54,13 +54,9 @@ class FeatureSTARTTLS(BasePlugin):
return False
elif not self.xmpp.use_tls:
return False
elif self.xmpp.ssl_support:
else:
self.xmpp.send(features['starttls'], now=True)
return True
else:
log.warning("The module tlslite is required to log in" + \
" to some servers, and has not been found.")
return False
def _handle_starttls_proceed(self, proceed):
"""Restart the XML stream when TLS is accepted."""

638
sleekxmpp/jid.py Normal file
View File

@@ -0,0 +1,638 @@
# -*- 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 threading
import encodings.idna
from copy import deepcopy
from sleekxmpp.util import stringprep_profiles
from sleekxmpp.thirdparty import OrderedDict
#: 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': '\\'}
JID_CACHE = OrderedDict()
JID_CACHE_LOCK = threading.Lock()
JID_CACHE_MAX_SIZE = 1024
def _cache(key, parts, locked):
JID_CACHE[key] = (parts, locked)
if len(JID_CACHE) > JID_CACHE_MAX_SIZE:
with JID_CACHE_LOCK:
while len(JID_CACHE) > JID_CACHE_MAX_SIZE:
found = None
for key, item in JID_CACHE.items():
if not item[1]: # if not locked
found = key
break
if not found: # more than MAX_SIZE locked
# warn?
break
del JID_CACHE[found]
# 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.map_table_b2],
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, ValueError):
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 illegal 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):
locked = kwargs.get('cache_lock', False)
in_local = kwargs.get('local', None)
in_domain = kwargs.get('domain', None)
in_resource = kwargs.get('resource', None)
parts = None
if in_local or in_domain or in_resource:
parts = (in_local, in_domain, in_resource)
# only check cache if there is a jid string, or parts, not if there
# are both
self._jid = None
key = None
if (jid is not None) and (parts is None):
if isinstance(jid, JID):
# it's already good to go, and there are no additions
self._jid = jid._jid
return
key = jid
self._jid, locked = JID_CACHE.get(jid, (None, locked))
elif jid is None and parts is not None:
key = parts
self._jid, locked = JID_CACHE.get(parts, (None, locked))
if not self._jid:
if not jid:
parsed_jid = (None, None, None)
elif not isinstance(jid, JID):
parsed_jid = _parse_jid(jid)
else:
parsed_jid = jid._jid
local, domain, resource = parsed_jid
if 'local' in kwargs:
local = _escape_node(in_local)
if 'domain' in kwargs:
domain = _validate_domain(in_domain)
if 'resource' in kwargs:
resource = _validate_resource(in_resource)
self._jid = (local, domain, resource)
if key:
_cache(key, self._jid, locked)
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
@property
def resource(self):
return self._jid[2] or ''
@property
def user(self):
return self._jid[0] or ''
@property
def local(self):
return self._jid[0] or ''
@property
def node(self):
return self._jid[0] or ''
@property
def username(self):
return self._jid[0] or ''
@property
def bare(self):
return _format_jid(self._jid[0], self._jid[1])
@property
def server(self):
return self._jid[1] or ''
@property
def domain(self):
return self._jid[1] or ''
@property
def host(self):
return self._jid[1] or ''
@property
def full(self):
return _format_jid(*self._jid)
@property
def jid(self):
return _format_jid(*self._jid)
@property
def bare(self):
return _format_jid(self._jid[0], self._jid[1])
@resource.setter
def resource(self, value):
self._jid = JID(self, resource=value)._jid
@user.setter
def user(self, value):
self._jid = JID(self, local=value)._jid
@username.setter
def username(self, value):
self._jid = JID(self, local=value)._jid
@local.setter
def local(self, value):
self._jid = JID(self, local=value)._jid
@node.setter
def node(self, value):
self._jid = JID(self, local=value)._jid
@server.setter
def server(self, value):
self._jid = JID(self, domain=value)._jid
@domain.setter
def domain(self, value):
self._jid = JID(self, domain=value)._jid
@host.setter
def host(self, value):
self._jid = JID(self, domain=value)._jid
@full.setter
def full(self, value):
self._jid = JID(value)._jid
@jid.setter
def jid(self, value):
self._jid = JID(value)._jid
@bare.setter
def bare(self, value):
parsed = JID(value)._jid
self._jid = (parsed[0], parsed[1], self._jid[2])
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)
def __deepcopy__(self, memo):
"""Generate a duplicate JID."""
return JID(deepcopy(str(self), memo))

View File

@@ -11,44 +11,76 @@ from sleekxmpp.plugins.base import register_plugin, load_plugin
__all__ = [
# Non-standard
'gmail_notify', # Gmail searching and notifications
# XEPS
'xep_0004', # Data Forms
'xep_0009', # Jabber-RPC
'xep_0012', # Last Activity
'xep_0013', # Flexible Offline Message Retrieval
'xep_0016', # Privacy Lists
'xep_0020', # Feature Negotiation
'xep_0027', # Current Jabber OpenPGP Usage
'xep_0030', # Service Discovery
'xep_0033', # Extended Stanza Addresses
'xep_0045', # Multi-User Chat (Client)
'xep_0047', # In-Band Bytestreams
'xep_0048', # Bookmarks
'xep_0049', # Private XML Storage
'xep_0050', # Ad-hoc Commands
'xep_0054', # vcard-temp
'xep_0059', # Result Set Management
'xep_0060', # Pubsub (Client)
'xep_0065', # SOCKS5 Bytestreams
'xep_0066', # Out of Band Data
'xep_0071', # XHTML-IM
'xep_0077', # In-Band Registration
# 'xep_0078', # Non-SASL auth. Don't automatically load
'xep_0079', # Advanced Message Processing
'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_0091', # Legacy Delayed Delivery
'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_0152', # Reachability Addresses
'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_0196', # User Gaming
'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_0235', # OAuth Over XMPP
'xep_0242', # XMPP Client Compliance 2009
'xep_0249', # Direct MUC Invitations
'xep_0256', # Last Activity in Presence
'xep_0257', # Client Certificate Management for SASL EXTERNAL
'xep_0258', # Security Labels in XMPP
'xep_0270', # XMPP Compliance Suites 2010
'xep_0279', # Server IP Check
'xep_0280', # Message Carbons
'xep_0297', # Stanza Forwarding
'xep_0302', # XMPP Compliance Suites 2012
'xep_0308', # Last Message Correction
'xep_0313', # Message Archive Management
'xep_0319', # Last User Interaction in Presence
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
]

View File

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

View File

@@ -0,0 +1,47 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 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, BasePlugin
from sleekxmpp.plugins.google.gmail import Gmail
from sleekxmpp.plugins.google.auth import GoogleAuth
from sleekxmpp.plugins.google.settings import GoogleSettings
from sleekxmpp.plugins.google.nosave import GoogleNoSave
class Google(BasePlugin):
"""
Google: Custom GTalk Features
Also see: <https://developers.google.com/talk/jep_extensions/extensions>
"""
name = 'google'
description = 'Google: Custom GTalk Features'
dependencies = set([
'gmail',
'google_settings',
'google_nosave',
'google_auth'
])
def __getitem__(self, attr):
if attr in ('settings', 'nosave', 'auth'):
return self.xmpp['google_%s' % attr]
elif attr == 'gmail':
return self.xmpp['gmail']
else:
raise KeyError(attr)
register_plugin(Gmail)
register_plugin(GoogleAuth)
register_plugin(GoogleSettings)
register_plugin(GoogleNoSave)
register_plugin(Google)

View File

@@ -0,0 +1,10 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.google.auth import stanza
from sleekxmpp.plugins.google.auth.auth import GoogleAuth

View File

@@ -0,0 +1,52 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 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 import BasePlugin
from sleekxmpp.plugins.google.auth import stanza
log = logging.getLogger(__name__)
class GoogleAuth(BasePlugin):
"""
Google: Auth Extensions (JID Domain Discovery, OAuth2)
Also see:
<https://developers.google.com/talk/jep_extensions/jid_domain_change>
<https://developers.google.com/talk/jep_extensions/oauth>
"""
name = 'google_auth'
description = 'Google: Auth Extensions (JID Domain Discovery, OAuth2)'
dependencies = set(['feature_mechanisms'])
stanza = stanza
def plugin_init(self):
self.xmpp.namespace_map['http://www.google.com/talk/protocol/auth'] = 'ga'
register_stanza_plugin(self.xmpp['feature_mechanisms'].stanza.Auth,
stanza.GoogleAuth)
self.xmpp.add_filter('out', self._auth)
def plugin_end(self):
self.xmpp.del_filter('out', self._auth)
def _auth(self, stanza):
if isinstance(stanza, self.xmpp['feature_mechanisms'].stanza.Auth):
stanza.stream = self.xmpp
stanza['google']['client_uses_full_bind_result'] = True
if stanza['mechanism'] == 'X-OAUTH2':
stanza['google']['service'] = 'oauth2'
print(stanza)
return stanza

View File

@@ -0,0 +1,49 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 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 GoogleAuth(ElementBase):
name = 'auth'
namespace = 'http://www.google.com/talk/protocol/auth'
plugin_attrib = 'google'
interfaces = set(['client_uses_full_bind_result', 'service'])
discovery_attr= '{%s}client-uses-full-bind-result' % namespace
service_attr= '{%s}service' % namespace
def setup(self, xml):
"""Don't create XML for the plugin."""
self.xml = ET.Element('')
print('setting up google extension')
def get_client_uses_full_bind_result(self):
return self.parent()._get_attr(self.disovery_attr) == 'true'
def set_client_uses_full_bind_result(self, value):
print('>>>', value)
if value in (True, 'true'):
self.parent()._set_attr(self.discovery_attr, 'true')
else:
self.parent()._del_attr(self.discovery_attr)
def del_client_uses_full_bind_result(self):
self.parent()._del_attr(self.discovery_attr)
def get_service(self):
return self.parent()._get_attr(self.service_attr, '')
def set_service(self, value):
if value:
self.parent()._set_attr(self.service_attr, value)
else:
self.parent()._del_attr(self.service_attr)
def del_service(self):
self.parent()._del_attr(self.service_attr)

View File

@@ -0,0 +1,10 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.google.gmail import stanza
from sleekxmpp.plugins.google.gmail.notifications import Gmail

View File

@@ -0,0 +1,96 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.stanza import Iq
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import MatchXPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.google.gmail import stanza
log = logging.getLogger(__name__)
class Gmail(BasePlugin):
"""
Google: Gmail Notifications
Also see <https://developers.google.com/talk/jep_extensions/gmail>.
"""
name = 'gmail'
description = 'Google: Gmail Notifications'
dependencies = set()
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Iq, stanza.GmailQuery)
register_stanza_plugin(Iq, stanza.MailBox)
register_stanza_plugin(Iq, stanza.NewMail)
self.xmpp.register_handler(
Callback('Gmail New Mail',
MatchXPath('{%s}iq/{%s}%s' % (
self.xmpp.default_ns,
stanza.NewMail.namespace,
stanza.NewMail.name)),
self._handle_new_mail))
self._last_result_time = None
self._last_result_tid = None
def plugin_end(self):
self.xmpp.remove_handler('Gmail New Mail')
def _handle_new_mail(self, iq):
log.info('Gmail: New email!')
iq.reply().send()
self.xmpp.event('gmail_notification')
def check(self, block=True, timeout=None, callback=None):
last_time = self._last_result_time
last_tid = self._last_result_tid
if not block:
callback = lambda iq: self._update_last_results(iq, callback)
resp = self.search(newer_time=last_time,
newer_tid=last_tid,
block=block,
timeout=timeout,
callback=callback)
if block:
self._update_last_results(resp)
return resp
def _update_last_results(self, iq, callback=None):
self._last_result_time = data['gmail_messages']['result_time']
threads = data['gmail_messages']['threads']
if threads:
self._last_result_tid = threads[0]['tid']
if callback:
callback(iq)
def search(self, query=None, newer_time=None, newer_tid=None, block=True,
timeout=None, callback=None):
if not query:
log.info('Gmail: Checking for new email')
else:
log.info('Gmail: Searching for emails matching: "%s"', query)
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['to'] = self.xmpp.boundjid.bare
iq['gmail']['search'] = query
iq['gmail']['newer_than_time'] = newer_time
iq['gmail']['newer_than_tid'] = newer_tid
return iq.send(block=block, timeout=timeout, callback=callback)

View File

@@ -0,0 +1,101 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 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 GmailQuery(ElementBase):
namespace = 'google:mail:notify'
name = 'query'
plugin_attrib = 'gmail'
interfaces = set(['newer_than_time', 'newer_than_tid', 'search'])
def get_search(self):
return self._get_attr('q', '')
def set_search(self, search):
self._set_attr('q', search)
def del_search(self):
self._del_attr('q')
def get_newer_than_time(self):
return self._get_attr('newer-than-time', '')
def set_newer_than_time(self, value):
self._set_attr('newer-than-time', value)
def del_newer_than_time(self):
self._del_attr('newer-than-time')
def get_newer_than_tid(self):
return self._get_attr('newer-than-tid', '')
def set_newer_than_tid(self, value):
self._set_attr('newer-than-tid', value)
def del_newer_than_tid(self):
self._del_attr('newer-than-tid')
class MailBox(ElementBase):
namespace = 'google:mail:notify'
name = 'mailbox'
plugin_attrib = 'gmail_messages'
interfaces = set(['result_time', 'url', 'matched', 'estimate'])
def get_matched(self):
return self._get_attr('total-matched', '')
def get_estimate(self):
return self._get_attr('total-estimate', '') == '1'
def get_result_time(self):
return self._get_attr('result-time', '')
class MailThread(ElementBase):
namespace = 'google:mail:notify'
name = 'mail-thread-info'
plugin_attrib = 'thread'
plugin_multi_attrib = 'threads'
interfaces = set(['tid', 'participation', 'messages', 'date',
'senders', 'url', 'labels', 'subject', 'snippet'])
sub_interfaces = set(['labels', 'subject', 'snippet'])
def get_senders(self):
result = []
senders = self.xml.findall('{%s}senders/{%s}sender' % (
self.namespace, self.namespace))
for sender in senders:
result.append(MailSender(xml=sender))
return result
class MailSender(ElementBase):
namespace = 'google:mail:notify'
name = 'sender'
plugin_attrib = name
interfaces = set(['address', 'name', 'originator', 'unread'])
def get_originator(self):
return self.xml.attrib.get('originator', '0') == '1'
def get_unread(self):
return self.xml.attrib.get('unread', '0') == '1'
class NewMail(ElementBase):
namespace = 'google:mail:notify'
name = 'new-mail'
plugin_attrib = 'gmail_notification'
register_stanza_plugin(MailBox, MailThread, iterable=True)

View File

@@ -0,0 +1,10 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.google.nosave import stanza
from sleekxmpp.plugins.google.nosave.nosave import GoogleNoSave

View File

@@ -0,0 +1,83 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.stanza import Iq, Message
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.google.nosave import stanza
log = logging.getLogger(__name__)
class GoogleNoSave(BasePlugin):
"""
Google: Off the Record Chats
NOTE: This is NOT an encryption method.
Also see <https://developers.google.com/talk/jep_extensions/otr>.
"""
name = 'google_nosave'
description = 'Google: Off the Record Chats'
dependencies = set(['google_settings'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.NoSave)
register_stanza_plugin(Iq, stanza.NoSaveQuery)
self.xmpp.register_handler(
Callback('Google Nosave',
StanzaPath('iq@type=set/google_nosave'),
self._handle_nosave_change))
def plugin_end(self):
self.xmpp.remove_handler('Google Nosave')
def enable(self, jid=None, block=True, timeout=None, callback=None):
if jid is None:
self.xmpp['google_settings'].update({'archiving_enabled': False},
block=block, timeout=timeout, callback=callback)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['google_nosave']['item']['jid'] = jid
iq['google_nosave']['item']['value'] = True
return iq.send(block=block, timeout=timeout, callback=callback)
def disable(self, jid=None, block=True, timeout=None, callback=None):
if jid is None:
self.xmpp['google_settings'].update({'archiving_enabled': True},
block=block, timeout=timeout, callback=callback)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['google_nosave']['item']['jid'] = jid
iq['google_nosave']['item']['value'] = False
return iq.send(block=block, timeout=timeout, callback=callback)
def get(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq.enable('google_nosave')
return iq.send(block=block, timeout=timeout, callback=callback)
def _handle_nosave_change(self, iq):
reply = self.xmpp.Iq()
reply['type'] = 'result'
reply['id'] = iq['id']
reply['to'] = iq['from']
reply.send()
self.xmpp.event('google_nosave_change', iq)

View File

@@ -0,0 +1,59 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.jid import JID
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class NoSave(ElementBase):
name = 'x'
namespace = 'google:nosave'
plugin_attrib = 'google_nosave'
interfaces = set(['value'])
def get_value(self):
return self._get_attr('value', '') == 'enabled'
def set_value(self, value):
self._set_attr('value', 'enabled' if value else 'disabled')
class NoSaveQuery(ElementBase):
name = 'query'
namespace = 'google:nosave'
plugin_attrib = 'google_nosave'
interfaces = set()
class Item(ElementBase):
name = 'item'
namespace = 'google:nosave'
plugin_attrib = 'item'
plugin_multi_attrib = 'items'
interfaces = set(['jid', 'source', 'value'])
def get_value(self):
return self._get_attr('value', '') == 'enabled'
def set_value(self, value):
self._set_attr('value', 'enabled' if value else 'disabled')
def get_jid(self):
return JID(self._get_attr('jid', ''))
def set_jid(self, value):
self._set_attr('jid', str(value))
def get_source(self):
return JID(self._get_attr('source', ''))
def set_source(self):
self._set_attr('source', str(value))
register_stanza_plugin(NoSaveQuery, Item)

View File

@@ -0,0 +1,10 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.google.settings import stanza
from sleekxmpp.plugins.google.settings.settings import GoogleSettings

View File

@@ -0,0 +1,65 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp.stanza import Iq
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.google.settings import stanza
class GoogleSettings(BasePlugin):
"""
Google: Gmail Notifications
Also see <https://developers.google.com/talk/jep_extensions/usersettings>.
"""
name = 'google_settings'
description = 'Google: User Settings'
dependencies = set()
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Iq, stanza.UserSettings)
self.xmpp.register_handler(
Callback('Google Settings',
StanzaPath('iq@type=set/google_settings'),
self._handle_settings_change))
def plugin_end(self):
self.xmpp.remove_handler('Google Settings')
def get(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq.enable('google_settings')
return iq.send(block=block, timeout=timeout, callback=callback)
def update(self, settings, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('google_settings')
for setting, value in settings.items():
iq['google_settings'][setting] = value
return iq.send(block=block, timeout=timeout, callback=callback)
def _handle_settings_change(self, iq):
reply = self.xmpp.Iq()
reply['type'] = 'result'
reply['id'] = iq['id']
reply['to'] = iq['from']
reply.send()
self.xmpp.event('google_settings_change', iq)

View File

@@ -0,0 +1,110 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 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, register_stanza_plugin
class UserSettings(ElementBase):
name = 'usersetting'
namespace = 'google:setting'
plugin_attrib = 'google_settings'
interfaces = set(['auto_accept_suggestions',
'mail_notifications',
'archiving_enabled',
'gmail',
'email_verified',
'domain_privacy_notice',
'display_name'])
def _get_setting(self, setting):
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
if xml is not None:
return xml.attrib.get('value', '') == 'true'
return False
def _set_setting(self, setting, value):
self._del_setting(setting)
if value in (True, False):
xml = ET.Element('{%s}%s' % (self.namespace, setting))
xml.attrib['value'] = 'true' if value else 'false'
self.xml.append(xml)
def _del_setting(self, setting):
xml = self.xml.find('{%s}%s' % (self.namespace, setting))
if xml is not None:
self.xml.remove(xml)
def get_display_name(self):
xml = self.xml.find('{%s}%s' % (self.namespace, 'displayname'))
if xml is not None:
return xml.attrib.get('value', '')
return ''
def set_display_name(self, value):
self._del_setting(setting)
if value:
xml = ET.Element('{%s}%s' % (self.namespace, 'displayname'))
xml.attrib['value'] = value
self.xml.append(xml)
def del_display_name(self):
self._del_setting('displayname')
def get_auto_accept_suggestions(self):
return self._get_setting('autoacceptsuggestions')
def get_mail_notifications(self):
return self._get_setting('mailnotifications')
def get_archiving_enabled(self):
return self._get_setting('archivingenabled')
def get_gmail(self):
return self._get_setting('gmail')
def get_email_verified(self):
return self._get_setting('emailverified')
def get_domain_privacy_notice(self):
return self._get_setting('domainprivacynotice')
def set_auto_accept_suggestions(self, value):
self._set_setting('autoacceptsuggestions', value)
def set_mail_notifications(self, value):
self._set_setting('mailnotifications', value)
def set_archiving_enabled(self, value):
self._set_setting('archivingenabled', value)
def set_gmail(self, value):
self._set_setting('gmail', value)
def set_email_verified(self, value):
self._set_setting('emailverified', value)
def set_domain_privacy_notice(self, value):
self._set_setting('domainprivacynotice', value)
def del_auto_accept_suggestions(self):
self._del_setting('autoacceptsuggestions')
def del_mail_notifications(self):
self._del_setting('mailnotifications')
def del_archiving_enabled(self):
self._del_setting('archivingenabled')
def del_gmail(self):
self._del_setting('gmail')
def del_email_verified(self):
self._del_setting('emailverified')
def del_domain_privacy_notice(self):
self._del_setting('domainprivacynotice')

View File

@@ -1,49 +0,0 @@
from . import base
import logging
from xml.etree import cElementTree as ET
log = logging.getLogger(__name__)
class jobs(base.base_plugin):
def plugin_init(self):
self.xep = 'pubsubjob'
self.description = "Job distribution over Pubsub"
def post_init(self):
pass
#TODO add event
def createJobNode(self, host, jid, node, config=None):
pass
def createJob(self, host, node, jobid=None, payload=None):
return self.xmpp.plugin['xep_0060'].setItem(host, node, ((jobid, payload),))
def claimJob(self, host, node, jobid, ifrom=None):
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}claimed'))
def unclaimJob(self, host, node, jobid):
return self._setState(host, node, jobid, ET.Element('{http://andyet.net/protocol/pubsubjob}unclaimed'))
def finishJob(self, host, node, jobid, payload=None):
finished = ET.Element('{http://andyet.net/protocol/pubsubjob}finished')
if payload is not None:
finished.append(payload)
return self._setState(host, node, jobid, finished)
def _setState(self, host, node, jobid, state, ifrom=None):
iq = self.xmpp.Iq()
iq['to'] = host
if ifrom: iq['from'] = ifrom
iq['type'] = 'set'
iq['psstate']['node'] = node
iq['psstate']['item'] = jobid
iq['psstate']['payload'] = state
result = iq.send()
if result is None or type(result) == bool or result['type'] != 'result':
log.error("Unable to change %s:%s to %s", node, jobid, state)
return False
return True

View File

@@ -1,421 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from . import base
import logging
from xml.etree import cElementTree as ET
import copy
import logging
#TODO support item groups and results
log = logging.getLogger(__name__)
class old_0004(base.base_plugin):
def plugin_init(self):
self.xep = '0004'
self.description = '*Deprecated Data Forms'
self.xmpp.add_handler("<message><x xmlns='jabber:x:data' /></message>", self.handler_message_xform, name='Old Message Form')
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data')
log.warning("This implementation of XEP-0004 is deprecated.")
def handler_message_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("message_form", object)
def handler_presence_xform(self, xml):
object = self.handle_form(xml)
self.xmpp.event("presence_form", object)
def handle_form(self, xml):
xmlform = xml.find('{jabber:x:data}x')
object = self.buildForm(xmlform)
self.xmpp.event("message_xform", object)
return object
def buildForm(self, xml):
form = Form(ftype=xml.attrib['type'])
form.fromXML(xml)
return form
def makeForm(self, ftype='form', title='', instructions=''):
return Form(self.xmpp, ftype, title, instructions)
class FieldContainer(object):
def __init__(self, stanza = 'form'):
self.fields = []
self.field = {}
self.stanza = stanza
def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None):
self.field[var] = FormField(var, ftype, label, desc, required, value)
self.fields.append(self.field[var])
return self.field[var]
def buildField(self, xml):
self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single'))
self.fields.append(self.field[xml.get('var', '__unnamed__')])
self.field[xml.get('var', '__unnamed__')].buildField(xml)
def buildContainer(self, xml):
self.stanza = xml.tag
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
def getXML(self, ftype):
container = ET.Element(self.stanza)
for field in self.fields:
container.append(field.getXML(ftype))
return container
class Form(FieldContainer):
types = ('form', 'submit', 'cancel', 'result')
def __init__(self, xmpp=None, ftype='form', title='', instructions=''):
if not ftype in self.types:
raise ValueError("Invalid Form Type")
FieldContainer.__init__(self)
self.xmpp = xmpp
self.type = ftype
self.title = title
self.instructions = instructions
self.reported = []
self.items = []
def merge(self, form2):
form1 = Form(ftype=self.type)
form1.fromXML(self.getXML(self.type))
for field in form2.fields:
if not field.var in form1.field:
form1.addField(field.var, field.type, field.label, field.desc, field.required, field.value)
else:
form1.field[field.var].value = field.value
for option, label in field.options:
if (option, label) not in form1.field[field.var].options:
form1.fields[field.var].addOption(option, label)
return form1
def copy(self):
newform = Form(ftype=self.type)
newform.fromXML(self.getXML(self.type))
return newform
def update(self, form):
values = form.getValues()
for var in values:
if var in self.fields:
self.fields[var].setValue(self.fields[var])
def getValues(self):
result = {}
for field in self.fields:
value = field.value
if len(value) == 1:
value = value[0]
result[field.var] = value
return result
def setValues(self, values={}):
for field in values:
if field in self.field:
if isinstance(values[field], list) or isinstance(values[field], tuple):
for value in values[field]:
self.field[field].setValue(value)
else:
self.field[field].setValue(values[field])
def fromXML(self, xml):
self.buildForm(xml)
def addItem(self):
newitem = FieldContainer('item')
self.items.append(newitem)
return newitem
def buildItem(self, xml):
newitem = self.addItem()
newitem.buildContainer(xml)
def addReported(self):
reported = FieldContainer('reported')
self.reported.append(reported)
return reported
def buildReported(self, xml):
reported = self.addReported()
reported.buildContainer(xml)
def setTitle(self, title):
self.title = title
def setInstructions(self, instructions):
self.instructions = instructions
def setType(self, ftype):
self.type = ftype
def getXMLMessage(self, to):
msg = self.xmpp.makeMessage(to)
msg.append(self.getXML())
return msg
def buildForm(self, xml):
self.type = xml.get('type', 'form')
if xml.find('{jabber:x:data}title') is not None:
self.setTitle(xml.find('{jabber:x:data}title').text)
if xml.find('{jabber:x:data}instructions') is not None:
self.setInstructions(xml.find('{jabber:x:data}instructions').text)
for field in xml.findall('{jabber:x:data}field'):
self.buildField(field)
for reported in xml.findall('{jabber:x:data}reported'):
self.buildReported(reported)
for item in xml.findall('{jabber:x:data}item'):
self.buildItem(item)
#def getXML(self, tostring = False):
def getXML(self, ftype=None):
if ftype:
self.type = ftype
form = ET.Element('{jabber:x:data}x')
form.attrib['type'] = self.type
if self.title and self.type in ('form', 'result'):
title = ET.Element('{jabber:x:data}title')
title.text = self.title
form.append(title)
if self.instructions and self.type == 'form':
instructions = ET.Element('{jabber:x:data}instructions')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXML(self.type))
for reported in self.reported:
form.append(reported.getXML('{jabber:x:data}reported'))
for item in self.items:
form.append(item.getXML(self.type))
#if tostring:
# form = self.xmpp.tostring(form)
return form
def getXHTML(self):
form = ET.Element('{http://www.w3.org/1999/xhtml}form')
if self.title:
title = ET.Element('h2')
title.text = self.title
form.append(title)
if self.instructions:
instructions = ET.Element('p')
instructions.text = self.instructions
form.append(instructions)
for field in self.fields:
form.append(field.getXHTML())
for field in self.reported:
form.append(field.getXHTML())
for field in self.items:
form.append(field.getXHTML())
return form
def makeSubmit(self):
self.setType('submit')
class FormField(object):
types = ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', 'text-single')
listtypes = ('jid-multi', 'jid-single', 'list-multi', 'list-single')
lbtypes = ('fixed', 'text-multi')
def __init__(self, var, ftype='text-single', label='', desc='', required=False, value=None):
if not ftype in self.types:
raise ValueError("Invalid Field Type")
self.type = ftype
self.var = var
self.label = label
self.desc = desc
self.options = []
self.required = False
self.value = []
if self.type in self.listtypes:
self.islist = True
else:
self.islist = False
if self.type in self.lbtypes:
self.islinebreak = True
else:
self.islinebreak = False
if value:
self.setValue(value)
def addOption(self, value, label):
if self.islist:
self.options.append((value, label))
else:
raise ValueError("Cannot add options to non-list type field.")
def setTrue(self):
if self.type == 'boolean':
self.value = [True]
def setFalse(self):
if self.type == 'boolean':
self.value = [False]
def require(self):
self.required = True
def setDescription(self, desc):
self.desc = desc
def setValue(self, value):
if self.type == 'boolean':
if value in ('1', 1, True, 'true', 'True', 'yes'):
value = True
else:
value = False
if self.islinebreak and value is not None:
self.value += value.split('\n')
else:
if len(self.value) and (not self.islist or self.type == 'list-single'):
self.value = [value]
else:
self.value.append(value)
def delValue(self, value):
if type(self.value) == type([]):
try:
idx = self.value.index(value)
if idx != -1:
self.value.pop(idx)
except ValueError:
pass
else:
self.value = ''
def setAnswer(self, value):
self.setValue(value)
def buildField(self, xml):
self.type = xml.get('type', 'text-single')
self.label = xml.get('label', '')
for option in xml.findall('{jabber:x:data}option'):
self.addOption(option.find('{jabber:x:data}value').text, option.get('label', ''))
for value in xml.findall('{jabber:x:data}value'):
self.setValue(value.text)
if xml.find('{jabber:x:data}required') is not None:
self.require()
if xml.find('{jabber:x:data}desc') is not None:
self.setDescription(xml.find('{jabber:x:data}desc').text)
def getXML(self, ftype):
field = ET.Element('{jabber:x:data}field')
if ftype != 'result':
field.attrib['type'] = self.type
if self.type != 'fixed':
if self.var:
field.attrib['var'] = self.var
if self.label:
field.attrib['label'] = self.label
if ftype == 'form':
for option in self.options:
optionxml = ET.Element('{jabber:x:data}option')
optionxml.attrib['label'] = option[1]
optionval = ET.Element('{jabber:x:data}value')
optionval.text = option[0]
optionxml.append(optionval)
field.append(optionxml)
if self.required:
required = ET.Element('{jabber:x:data}required')
field.append(required)
if self.desc:
desc = ET.Element('{jabber:x:data}desc')
desc.text = self.desc
field.append(desc)
for value in self.value:
valuexml = ET.Element('{jabber:x:data}value')
if value is True or value is False:
if value:
valuexml.text = '1'
else:
valuexml.text = '0'
else:
valuexml.text = value
field.append(valuexml)
return field
def getXHTML(self):
field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type})
if self.label:
label = ET.Element('p')
label.text = "%s: " % self.label
else:
label = ET.Element('p')
label.text = "%s: " % self.var
field.append(label)
if self.type == 'boolean':
formf = ET.Element('input', {'type': 'checkbox', 'name': self.var})
if len(self.value) and self.value[0] in (True, 'true', '1'):
formf.attrib['checked'] = 'checked'
elif self.type == 'fixed':
formf = ET.Element('p')
try:
formf.text = ', '.join(self.value)
except:
pass
field.append(formf)
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type == 'hidden':
formf = ET.Element('input', {'type': 'hidden', 'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
elif self.type in ('jid-multi', 'list-multi'):
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0], 'multiple': 'multiple'})
optf.text = option[1]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(option)
elif self.type in ('jid-single', 'text-single'):
formf = ET.Element('input', {'type': 'text', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
elif self.type == 'list-single':
formf = ET.Element('select', {'name': self.var})
for option in self.options:
optf = ET.Element('option', {'value': option[0]})
optf.text = option[1]
if not optf.text:
optf.text = option[0]
if option[1] in self.value:
optf.attrib['selected'] = 'selected'
formf.append(optf)
elif self.type == 'text-multi':
formf = ET.Element('textarea', {'name': self.var})
try:
formf.text = ', '.join(self.value)
except:
pass
if not formf.text:
formf.text = ' '
elif self.type == 'text-private':
formf = ET.Element('input', {'type': 'password', 'name': self.var})
try:
formf.attrib['value'] = ', '.join(self.value)
except:
pass
label.append(formf)
return field

View File

@@ -1,277 +0,0 @@
"""
XEP-0009 XMPP Remote Procedure Calls
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import copy
import time
import base64
def py2xml(*args):
params = ET.Element("params")
for x in args:
param = ET.Element("param")
param.append(_py2xml(x))
params.append(param) #<params><param>...
return params
def _py2xml(*args):
for x in args:
val = ET.Element("value")
if type(x) is int:
i4 = ET.Element("i4")
i4.text = str(x)
val.append(i4)
if type(x) is bool:
boolean = ET.Element("boolean")
boolean.text = str(int(x))
val.append(boolean)
elif type(x) is str:
string = ET.Element("string")
string.text = x
val.append(string)
elif type(x) is float:
double = ET.Element("double")
double.text = str(x)
val.append(double)
elif type(x) is rpcbase64:
b64 = ET.Element("Base64")
b64.text = x.encoded()
val.append(b64)
elif type(x) is rpctime:
iso = ET.Element("dateTime.iso8601")
iso.text = str(x)
val.append(iso)
elif type(x) is list:
array = ET.Element("array")
data = ET.Element("data")
for y in x:
data.append(_py2xml(y))
array.append(data)
val.append(array)
elif type(x) is dict:
struct = ET.Element("struct")
for y in x.keys():
member = ET.Element("member")
name = ET.Element("name")
name.text = y
member.append(name)
member.append(_py2xml(x[y]))
struct.append(member)
val.append(struct)
return val
def xml2py(params):
vals = []
for param in params.findall('param'):
vals.append(_xml2py(param.find('value')))
return vals
def _xml2py(value):
if value.find('i4') is not None:
return int(value.find('i4').text)
if value.find('int') is not None:
return int(value.find('int').text)
if value.find('boolean') is not None:
return bool(value.find('boolean').text)
if value.find('string') is not None:
return value.find('string').text
if value.find('double') is not None:
return float(value.find('double').text)
if value.find('Base64') is not None:
return rpcbase64(value.find('Base64').text)
if value.find('dateTime.iso8601') is not None:
return rpctime(value.find('dateTime.iso8601'))
if value.find('struct') is not None:
struct = {}
for member in value.find('struct').findall('member'):
struct[member.find('name').text] = _xml2py(member.find('value'))
return struct
if value.find('array') is not None:
array = []
for val in value.find('array').find('data').findall('value'):
array.append(_xml2py(val))
return array
raise ValueError()
class rpcbase64(object):
def __init__(self, data):
#base 64 encoded string
self.data = data
def decode(self):
return base64.decodestring(data)
def __str__(self):
return self.decode()
def encoded(self):
return self.data
class rpctime(object):
def __init__(self,data=None):
#assume string data is in iso format YYYYMMDDTHH:MM:SS
if type(data) is str:
self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
elif type(data) is time.struct_time:
self.timestamp = data
elif data is None:
self.timestamp = time.gmtime()
else:
raise ValueError()
def iso8601(self):
#return a iso8601 string
return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
def __str__(self):
return self.iso8601()
class JabberRPCEntry(object):
def __init__(self,call):
self.call = call
self.result = None
self.error = None
self.allow = {} #{'<jid>':['<resource1>',...],...}
self.deny = {}
def check_acl(self, jid, resource):
#Check for deny
if jid in self.deny.keys():
if self.deny[jid] == None or resource in self.deny[jid]:
return False
#Check for allow
if allow == None:
return True
if jid in self.allow.keys():
if self.allow[jid] == None or resource in self.allow[jid]:
return True
return False
def acl_allow(self, jid, resource):
if jid == None:
self.allow = None
elif resource == None:
self.allow[jid] = None
elif jid in self.allow.keys():
self.allow[jid].append(resource)
else:
self.allow[jid] = [resource]
def acl_deny(self, jid, resource):
if jid == None:
self.deny = None
elif resource == None:
self.deny[jid] = None
elif jid in self.deny.keys():
self.deny[jid].append(resource)
else:
self.deny[jid] = [resource]
def call_method(self, args):
ret = self.call(*args)
class xep_0009(base.base_plugin):
def plugin_init(self):
self.xep = '0009'
self.description = 'Jabber-RPC'
self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>",
self._callMethod, name='Jabber RPC Call')
self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>",
self._callResult, name='Jabber RPC Result')
self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>",
self._callError, name='Jabber RPC Error')
self.entries = {}
self.activeCalls = []
def post_init(self):
base.base_plugin.post_init(self)
self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc')
def register_call(self, method, name=None):
#@returns an string that can be used in acl commands.
with self.lock:
if name is None:
self.entries[method.__name__] = JabberRPCEntry(method)
return method.__name__
else:
self.entries[name] = JabberRPCEntry(method)
return name
def acl_allow(self, entry, jid=None, resource=None):
#allow the method entry to be called by the given jid and resource.
#if jid is None it will allow any jid/resource.
#if resource is None it will allow any resource belonging to the jid.
with self.lock:
if self.entries[entry]:
self.entries[entry].acl_allow(jid,resource)
else:
raise ValueError()
def acl_deny(self, entry, jid=None, resource=None):
#Note: by default all requests are denied unless allowed with acl_allow.
#If you deny an entry it will not be allowed regardless of acl_allow
with self.lock:
if self.entries[entry]:
self.entries[entry].acl_deny(jid,resource)
else:
raise ValueError()
def unregister_call(self, entry):
#removes the registered call
with self.lock:
if self.entries[entry]:
del self.entries[entry]
else:
raise ValueError()
def makeMethodCallQuery(self,pmethod,params):
query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
methodCall = ET.Element('methodCall')
methodName = ET.Element('methodName')
methodName.text = pmethod
methodCall.append(methodName)
methodCall.append(params)
query.append(methodCall)
return query
def makeIqMethodCall(self,pto,pmethod,params):
iq = self.xmpp.makeIqSet()
iq.set('to',pto)
iq.append(self.makeMethodCallQuery(pmethod,params))
return iq
def makeIqMethodResponse(self,pto,pid,params):
iq = self.xmpp.makeIqResult(pid)
iq.set('to',pto)
query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
methodResponse = ET.Element('methodResponse')
methodResponse.append(params)
query.append(methodResponse)
return iq
def makeIqMethodError(self,pto,id,pmethod,params,condition):
iq = self.xmpp.makeIqError(id)
iq.set('to',pto)
iq.append(self.makeMethodCallQuery(pmethod,params))
iq.append(self.xmpp['xep_0086'].makeError(condition))
return iq
def call_remote(self, pto, pmethod, *args):
#calls a remote method. Returns the id of the Iq.
pass
def _callMethod(self,xml):
pass
def _callResult(self,xml):
pass
def _callError(self,xml):
pass

View File

@@ -1,133 +0,0 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import time
class old_0050(base.base_plugin):
"""
XEP-0050 Ad-Hoc Commands
"""
def plugin_init(self):
self.xep = '0050'
self.description = 'Ad-Hoc Commands'
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='__None__'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc None')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='execute'/></iq>" % self.xmpp.default_ns, self.handler_command, name='Ad-Hoc Execute')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='next'/></iq>" % self.xmpp.default_ns, self.handler_command_next, name='Ad-Hoc Next', threaded=True)
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='cancel'/></iq>" % self.xmpp.default_ns, self.handler_command_cancel, name='Ad-Hoc Cancel')
self.xmpp.add_handler("<iq type='set' xmlns='%s'><command xmlns='http://jabber.org/protocol/commands' action='complete'/></iq>" % self.xmpp.default_ns, self.handler_command_complete, name='Ad-Hoc Complete')
self.commands = {}
self.sessions = {}
self.sd = self.xmpp.plugin['xep_0030']
def post_init(self):
base.base_plugin.post_init(self)
self.sd.add_feature('http://jabber.org/protocol/commands')
def addCommand(self, node, name, form, pointer=None, multi=False):
self.sd.add_item(None, name, 'http://jabber.org/protocol/commands', node)
self.sd.add_identity('automation', 'command-node', name, node)
self.sd.add_feature('http://jabber.org/protocol/commands', node)
self.sd.add_feature('jabber:x:data', node)
self.commands[node] = (name, form, pointer, multi)
def getNewSession(self):
return str(time.time()) + '-' + self.xmpp.getNewId()
def handler_command(self, xml):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
node = in_command.get('node')
sessionid = self.getNewSession()
name, form, pointer, multi = self.commands[node]
self.sessions[sessionid] = {}
self.sessions[sessionid]['jid'] = xml.get('from')
self.sessions[sessionid]['to'] = xml.get('to')
self.sessions[sessionid]['past'] = [(form, None)]
self.sessions[sessionid]['next'] = pointer
npointer = pointer
if multi:
actions = ['next']
status = 'executing'
else:
if pointer is None:
status = 'completed'
actions = []
else:
status = 'executing'
actions = ['complete']
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
def handler_command_complete(self, xml):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
pointer = self.sessions[sessionid]['next']
results = self.xmpp.plugin['old_0004'].makeForm('result')
results.fromXML(in_command.find('{jabber:x:data}x'))
pointer(results,sessionid)
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=None, id=xml.attrib['id'], sessionid=sessionid, status='completed', actions=[]))
del self.sessions[in_command.get('sessionid')]
def handler_command_next(self, xml):
in_command = xml.find('{http://jabber.org/protocol/commands}command')
sessionid = in_command.get('sessionid', None)
pointer = self.sessions[sessionid]['next']
results = self.xmpp.plugin['old_0004'].makeForm('result')
results.fromXML(in_command.find('{jabber:x:data}x'))
form, npointer, next = pointer(results,sessionid)
self.sessions[sessionid]['next'] = npointer
self.sessions[sessionid]['past'].append((form, pointer))
actions = []
actions.append('prev')
if npointer is None:
status = 'completed'
else:
status = 'executing'
if next:
actions.append('next')
else:
actions.append('complete')
self.xmpp.send(self.makeCommand(xml.attrib['from'], in_command.attrib['node'], form=form, id=xml.attrib['id'], sessionid=sessionid, status=status, actions=actions))
def handler_command_cancel(self, xml):
command = xml.find('{http://jabber.org/protocol/commands}command')
try:
del self.sessions[command.get('sessionid')]
except:
pass
self.xmpp.send(self.makeCommand(xml.attrib['from'], command.attrib['node'], id=xml.attrib['id'], sessionid=command.attrib['sessionid'], status='canceled'))
def makeCommand(self, to, node, id=None, form=None, sessionid=None, status='executing', actions=[]):
if not id:
id = self.xmpp.getNewId()
iq = self.xmpp.makeIqResult(id)
iq.attrib['from'] = self.xmpp.boundjid.full
iq.attrib['to'] = to
command = ET.Element('{http://jabber.org/protocol/commands}command')
command.attrib['node'] = node
command.attrib['status'] = status
xmlactions = ET.Element('actions')
for action in actions:
xmlactions.append(ET.Element(action))
if xmlactions:
command.append(xmlactions)
if not sessionid:
sessionid = self.getNewSession()
else:
iq.attrib['from'] = self.sessions[sessionid]['to']
command.attrib['sessionid'] = sessionid
if form is not None:
if hasattr(form,'getXML'):
form = form.getXML()
command.append(form)
iq.append(command)
return iq

View File

@@ -1,313 +0,0 @@
from __future__ import with_statement
from . import base
import logging
#from xml.etree import cElementTree as ET
from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET
from . import stanza_pubsub
from . xep_0004 import Form
log = logging.getLogger(__name__)
class xep_0060(base.base_plugin):
"""
XEP-0060 Publish Subscribe
"""
def plugin_init(self):
self.xep = '0060'
self.description = 'Publish-Subscribe'
def create_node(self, jid, node, config=None, collection=False, ntype=None):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
create = ET.Element('create')
create.set('node', node)
pubsub.append(create)
configure = ET.Element('configure')
if collection:
ntype = 'collection'
#if config is None:
# submitform = self.xmpp.plugin['xep_0004'].makeForm('submit')
#else:
if config is not None:
submitform = config
if 'FORM_TYPE' in submitform.field:
submitform.field['FORM_TYPE'].setValue('http://jabber.org/protocol/pubsub#node_config')
else:
submitform.addField('FORM_TYPE', 'hidden', value='http://jabber.org/protocol/pubsub#node_config')
if ntype:
if 'pubsub#node_type' in submitform.field:
submitform.field['pubsub#node_type'].setValue(ntype)
else:
submitform.addField('pubsub#node_type', value=ntype)
else:
if 'pubsub#node_type' in submitform.field:
submitform.field['pubsub#node_type'].setValue('leaf')
else:
submitform.addField('pubsub#node_type', value='leaf')
submitform['type'] = 'submit'
configure.append(submitform.xml)
pubsub.append(configure)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is False or result is None or result['type'] == 'error': return False
return True
def subscribe(self, jid, node, bare=True, subscribee=None):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
subscribe = ET.Element('subscribe')
subscribe.attrib['node'] = node
if subscribee is None:
if bare:
subscribe.attrib['jid'] = self.xmpp.boundjid.bare
else:
subscribe.attrib['jid'] = self.xmpp.boundjid.full
else:
subscribe.attrib['jid'] = subscribee
pubsub.append(subscribe)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is False or result is None or result['type'] == 'error': return False
return True
def unsubscribe(self, jid, node, bare=True, subscribee=None):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
unsubscribe = ET.Element('unsubscribe')
unsubscribe.attrib['node'] = node
if subscribee is None:
if bare:
unsubscribe.attrib['jid'] = self.xmpp.boundjid.bare
else:
unsubscribe.attrib['jid'] = self.xmpp.boundjid.full
else:
unsubscribe.attrib['jid'] = subscribee
pubsub.append(unsubscribe)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is False or result is None or result['type'] == 'error': return False
return True
def getNodeConfig(self, jid, node=None): # if no node, then grab default
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
if node is not None:
configure = ET.Element('configure')
configure.attrib['node'] = node
else:
configure = ET.Element('default')
pubsub.append(configure)
#TODO: Add configure support.
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
#self.xmpp.add_handler("<iq id='%s'/>" % id, self.handlerCreateNodeResponse)
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
return False
if node is not None:
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x')
else:
form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x')
if not form or form is None:
log.error("No form found.")
return False
return Form(xml=form)
def getNodeSubscriptions(self, jid, node):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
subscriptions = ET.Element('subscriptions')
subscriptions.attrib['node'] = node
pubsub.append(subscriptions)
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
return False
else:
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription')
if results is None:
return False
subs = {}
for sub in results:
subs[sub.get('jid')] = sub.get('subscription')
return subs
def getNodeAffiliations(self, jid, node):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
affiliations = ET.Element('affiliations')
affiliations.attrib['node'] = node
pubsub.append(affiliations)
iq = self.xmpp.makeIqGet()
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result == False or result['type'] == 'error':
log.warning("got error instead of config")
return False
else:
results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation')
if results is None:
return False
subs = {}
for sub in results:
subs[sub.get('jid')] = sub.get('affiliation')
return subs
def deleteNode(self, jid, node):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
iq = self.xmpp.makeIqSet()
delete = ET.Element('delete')
delete.attrib['node'] = node
pubsub.append(delete)
iq.append(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
result = iq.send()
if result is not None and result is not False and result['type'] != 'error':
return True
else:
return False
def setNodeConfig(self, jid, node, config):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
configure = ET.Element('configure')
configure.attrib['node'] = node
config = config.getXML('submit')
configure.append(config)
pubsub.append(configure)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result['type'] == 'error':
return False
return True
def setItem(self, jid, node, items=[]):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
publish = ET.Element('publish')
publish.attrib['node'] = node
for pub_item in items:
id, payload = pub_item
item = ET.Element('item')
if id is not None:
item.attrib['id'] = id
item.append(payload)
publish.append(item)
pubsub.append(publish)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result is False or result['type'] == 'error': return False
return True
def addItem(self, jid, node, items=[]):
return self.setItem(jid, node, items)
def deleteItem(self, jid, node, item):
pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub')
retract = ET.Element('retract')
retract.attrib['node'] = node
itemn = ET.Element('item')
itemn.attrib['id'] = item
retract.append(itemn)
pubsub.append(retract)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result is False or result['type'] == 'error': return False
return True
def getNodes(self, jid):
response = self.xmpp.plugin['xep_0030'].getItems(jid)
items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
nodes = {}
if items is not None and items is not False:
for item in items:
nodes[item.get('node')] = item.get('name')
return nodes
def getItems(self, jid, node):
response = self.xmpp.plugin['xep_0030'].getItems(jid, node)
items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item')
nodeitems = []
if items is not None and items is not False:
for item in items:
nodeitems.append(item.get('node'))
return nodeitems
def addNodeToCollection(self, jid, child, parent=''):
config = self.getNodeConfig(jid, child)
if not config or config is None:
self.lasterror = "Config Error"
return False
try:
config.field['pubsub#collection'].setValue(parent)
except KeyError:
log.warning("pubsub#collection doesn't exist in config, trying to add it")
config.addField('pubsub#collection', value=parent)
if not self.setNodeConfig(jid, child, config):
return False
return True
def modifyAffiliation(self, ps_jid, node, user_jid, affiliation):
if affiliation not in ('owner', 'publisher', 'member', 'none', 'outcast'):
raise TypeError
pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub')
affs = ET.Element('affiliations')
affs.attrib['node'] = node
aff = ET.Element('affiliation')
aff.attrib['jid'] = user_jid
aff.attrib['affiliation'] = affiliation
affs.append(aff)
pubsub.append(affs)
iq = self.xmpp.makeIqSet(pubsub)
iq.attrib['to'] = ps_jid
iq.attrib['from'] = self.xmpp.boundjid.full
id = iq['id']
result = iq.send()
if result is None or result is False or result['type'] == 'error':
return False
return True
def addNodeToCollection(self, jid, child, parent=''):
config = self.getNodeConfig(jid, child)
if not config or config is None:
self.lasterror = "Config Error"
return False
try:
config.field['pubsub#collection'].setValue(parent)
except KeyError:
log.warning("pubsub#collection doesn't exist in config, trying to add it")
config.addField('pubsub#collection', value=parent)
if not self.setNodeConfig(jid, child, config):
return False
return True
def removeNodeFromCollection(self, jid, child):
self.addNodeToCollection(jid, child, '')

View File

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

View File

@@ -41,10 +41,11 @@ class FormField(ElementBase):
self._type = value
def add_option(self, label='', value=''):
if self._type in self.option_types:
opt = FieldOption(parent=self)
if self._type is None or self._type in self.option_types:
opt = FieldOption()
opt['label'] = label
opt['value'] = value
self.append(opt)
else:
raise ValueError("Cannot add options to " + \
"a %s field." % self['type'])

View File

@@ -65,7 +65,7 @@ class Form(ElementBase):
if kwtype is None:
kwtype = ftype
field = FormField(parent=self)
field = FormField()
field['var'] = var
field['type'] = kwtype
field['value'] = value
@@ -77,6 +77,7 @@ class Form(ElementBase):
field['options'] = options
else:
del field['type']
self.append(field)
return field
def getXML(self, type='submit'):
@@ -144,10 +145,9 @@ class Form(ElementBase):
def get_fields(self, use_dict=False):
fields = OrderedDict()
fieldsXML = self.xml.findall('{%s}field' % FormField.namespace)
for fieldXML in fieldsXML:
field = FormField(xml=fieldXML)
fields[field['var']] = field
for stanza in self['substanzas']:
if isinstance(stanza, FormField):
fields[stanza['var']] = stanza
return fields
def get_instructions(self):
@@ -201,7 +201,8 @@ class Form(ElementBase):
del self['instructions']
if instructions in [None, '']:
return
instructions = instructions.split('\n')
if not isinstance(instructions, list):
instructions = instructions.split('\n')
for instruction in instructions:
inst = ET.Element('{%s}instructions' % self.namespace)
inst.text = instruction
@@ -220,6 +221,8 @@ class Form(ElementBase):
def set_values(self, values):
fields = self['fields']
for field in values:
if field not in fields:
fields[field] = self.add_field(var=field)
fields[field]['value'] = values[field]
def merge(self, other):

View File

@@ -32,15 +32,15 @@ class XEP_0009(BasePlugin):
register_stanza_plugin(RPCQuery, MethodCall)
register_stanza_plugin(RPCQuery, MethodResponse)
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
self._handle_method_call)
)
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
self._handle_method_response)
)
self.xmpp.registerHandler(
self.xmpp.register_handler(
Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
self._handle_error)
)

View File

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

View File

@@ -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 permissio
"""
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0013.stanza import Offline
from sleekxmpp.plugins.xep_0013.offline import XEP_0013
register_plugin(XEP_0013)

View File

@@ -0,0 +1,134 @@
"""
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 permissio
"""
import logging
import sleekxmpp
from sleekxmpp.stanza import Message, Iq
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream.handler import Collector
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0013 import stanza
log = logging.getLogger(__name__)
class XEP_0013(BasePlugin):
"""
XEP-0013 Flexible Offline Message Retrieval
"""
name = 'xep_0013'
description = 'XEP-0013: Flexible Offline Message Retrieval'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Iq, stanza.Offline)
register_stanza_plugin(Message, stanza.Offline)
def get_count(self, **kwargs):
return self.xmpp['xep_0030'].get_info(
node='http://jabber.org/protocol/offline',
local=False,
**kwargs)
def get_headers(self, **kwargs):
return self.xmpp['xep_0030'].get_items(
node='http://jabber.org/protocol/offline',
local=False,
**kwargs)
def view(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
if not isinstance(nodes, (list, set)):
nodes = [nodes]
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['from'] = ifrom
offline = iq['offline']
for node in nodes:
item = stanza.Item()
item['node'] = node
item['action'] = 'view'
offline.append(item)
collector = Collector(
'Offline_Results_%s' % iq['id'],
StanzaPath('message/offline'))
self.xmpp.register_handler(collector)
if not block and callback is not None:
def wrapped_cb(iq):
results = collector.stop()
if iq['type'] == 'result':
iq['offline']['results'] = results
callback(iq)
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
else:
try:
resp = iq.send(block=block, timeout=timeout, callback=callback)
resp['offline']['results'] = collector.stop()
return resp
except XMPPError as e:
collector.stop()
raise e
def remove(self, nodes, ifrom=None, block=True, timeout=None, callback=None):
if not isinstance(nodes, (list, set)):
nodes = [nodes]
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
offline = iq['offline']
for node in nodes:
item = stanza.Item()
item['node'] = node
item['action'] = 'remove'
offline.append(item)
return iq.send(block=block, timeout=timeout, callback=callback)
def fetch(self, ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
iq['offline']['fetch'] = True
collector = Collector(
'Offline_Results_%s' % iq['id'],
StanzaPath('message/offline'))
self.xmpp.register_handler(collector)
if not block and callback is not None:
def wrapped_cb(iq):
results = collector.stop()
if iq['type'] == 'result':
iq['offline']['results'] = results
callback(iq)
return iq.send(block=block, timeout=timeout, callback=wrapped_cb)
else:
try:
resp = iq.send(block=block, timeout=timeout, callback=callback)
resp['offline']['results'] = collector.stop()
return resp
except XMPPError as e:
collector.stop()
raise e
def purge(self, ifrom=None, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['from'] = ifrom
iq['offline']['purge'] = True
return iq.send(block=block, timeout=timeout, callback=callback)

View File

@@ -0,0 +1,53 @@
"""
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 permissio
"""
from sleekxmpp.jid import JID
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class Offline(ElementBase):
name = 'offline'
namespace = 'http://jabber.org/protocol/offline'
plugin_attrib = 'offline'
interfaces = set(['fetch', 'purge', 'results'])
bool_interfaces = interfaces
def setup(self, xml=None):
ElementBase.setup(self, xml)
self._results = []
# The results interface is meant only as an easy
# way to access the set of collected message responses
# from the query.
def get_results(self):
return self._results
def set_results(self, values):
self._results = values
def del_results(self):
self._results = []
class Item(ElementBase):
name = 'item'
namespace = 'http://jabber.org/protocol/offline'
plugin_attrib = 'item'
interfaces = set(['action', 'node', 'jid'])
actions = set(['view', 'remove'])
def get_jid(self):
return JID(self._get_attr('jid'))
def set_jid(self, value):
self._set_attr('jid', str(value))
register_stanza_plugin(Offline, Item, iterable=True)

View File

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

View File

@@ -0,0 +1,110 @@
"""
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 Iq
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0016 import stanza
from sleekxmpp.plugins.xep_0016.stanza import Privacy, Item
class XEP_0016(BasePlugin):
name = 'xep_0016'
description = 'XEP-0016: Privacy Lists'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Iq, Privacy)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Privacy.namespace)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Privacy.namespace)
def get_privacy_lists(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq.enable('privacy')
return iq.send(block=block, timeout=timeout, callback=callback)
def get_list(self, name, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['privacy']['list']['name'] = name
return iq.send(block=block, timeout=timeout, callback=callback)
def get_active(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['privacy'].enable('active')
return iq.send(block=block, timeout=timeout, callback=callback)
def get_default(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq['privacy'].enable('default')
return iq.send(block=block, timeout=timeout, callback=callback)
def activate(self, name, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['privacy']['active']['name'] = name
return iq.send(block=block, timeout=timeout, callback=callback)
def deactivate(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['privacy'].enable('active')
return iq.send(block=block, timeout=timeout, callback=callback)
def make_default(self, name, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['privacy']['default']['name'] = name
return iq.send(block=block, timeout=timeout, callback=callback)
def remove_default(self, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['privacy'].enable('default')
return iq.send(block=block, timeout=timeout, callback=callback)
def edit_list(self, name, rules, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['privacy']['list']['name'] = name
priv_list = iq['privacy']['list']
if not rules:
rules = []
for rule in rules:
if isinstance(rule, Item):
priv_list.append(rule)
continue
priv_list.add_item(
rule['value'],
rule['action'],
rule['order'],
itype=rule.get('type', None),
iq=rule.get('iq', None),
message=rule.get('message', None),
presence_in=rule.get('presence_in',
rule.get('presence-in', None)),
presence_out=rule.get('presence_out',
rule.get('presence-out', None)))
def remove_list(self, name, block=True, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['privacy']['list']['name'] = name
return iq.send(block=block, timeout=timeout, callback=callback)

View File

@@ -0,0 +1,103 @@
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
class Privacy(ElementBase):
name = 'query'
namespace = 'jabber:iq:privacy'
plugin_attrib = 'privacy'
interfaces = set()
def add_list(self, name):
priv_list = List()
priv_list['name'] = name
self.append(priv_list)
return priv_list
class Active(ElementBase):
name = 'active'
namespace = 'jabber:iq:privacy'
plugin_attrib = name
interfaces = set(['name'])
class Default(ElementBase):
name = 'default'
namespace = 'jabber:iq:privacy'
plugin_attrib = name
interfaces = set(['name'])
class List(ElementBase):
name = 'list'
namespace = 'jabber:iq:privacy'
plugin_attrib = name
plugin_multi_attrib = 'lists'
interfaces = set(['name'])
def add_item(self, value, action, order, itype=None, iq=False,
message=False, presence_in=False, presence_out=False):
item = Item()
item.values = {'type': itype,
'value': value,
'action': action,
'order': order,
'message': message,
'iq': iq,
'presence_in': presence_in,
'presence_out': presence_out}
self.append(item)
return item
class Item(ElementBase):
name = 'item'
namespace = 'jabber:iq:privacy'
plugin_attrib = name
plugin_multi_attrib = 'items'
interfaces = set(['type', 'value', 'action', 'order', 'iq',
'message', 'presence_in', 'presence_out'])
bool_interfaces = set(['message', 'iq', 'presence_in', 'presence_out'])
type_values = ('', 'jid', 'group', 'subscription')
action_values = ('allow', 'deny')
def set_type(self, value):
if value and value not in self.type_values:
raise ValueError('Unknown type value: %s' % value)
else:
self._set_attr('type', value)
def set_action(self, value):
if value not in self.action_values:
raise ValueError('Unknown action value: %s' % value)
else:
self._set_attr('action', value)
def set_presence_in(self, value):
keep = True if value else False
self._set_sub_text('presence-in', '', keep=keep)
def get_presence_in(self):
pres = self.xml.find('{%s}presence-in' % self.namespace)
return pres is not None
def del_presence_in(self):
self._del_sub('{%s}presence-in' % self.namespace)
def set_presence_out(self, value):
keep = True if value else False
self._set_sub_text('presence-in', '', keep=keep)
def get_presence_out(self):
pres = self.xml.find('{%s}presence-in' % self.namespace)
return pres is not None
def del_presence_out(self):
self._del_sub('{%s}presence-in' % self.namespace)
register_stanza_plugin(Privacy, Active)
register_stanza_plugin(Privacy, Default)
register_stanza_plugin(Privacy, List, iterable=True)
register_stanza_plugin(List, Item, iterable=True)

View File

@@ -0,0 +1,16 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 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_0020 import stanza
from sleekxmpp.plugins.xep_0020.stanza import FeatureNegotiation
from sleekxmpp.plugins.xep_0020.feature_negotiation import XEP_0020
register_plugin(XEP_0020)

View File

@@ -0,0 +1,36 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
import logging
from sleekxmpp import Iq, Message
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.plugins.xep_0020 import stanza, FeatureNegotiation
from sleekxmpp.plugins.xep_0004 import Form
log = logging.getLogger(__name__)
class XEP_0020(BasePlugin):
name = 'xep_0020'
description = 'XEP-0020: Feature Negotiation'
dependencies = set(['xep_0004', 'xep_0030'])
stanza = stanza
def plugin_init(self):
self.xmpp['xep_0030'].add_feature(FeatureNegotiation.namespace)
register_stanza_plugin(FeatureNegotiation, Form)
register_stanza_plugin(Iq, FeatureNegotiation)
register_stanza_plugin(Message, FeatureNegotiation)

View File

@@ -0,0 +1,17 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2013 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
class FeatureNegotiation(ElementBase):
name = 'feature'
namespace = 'http://jabber.org/protocol/feature-neg'
plugin_attrib = 'feature_neg'
interfaces = set()

View File

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

View File

@@ -39,7 +39,7 @@ class Encrypted(ElementBase):
def set_encrypted(self, value):
parent = self.parent()
xmpp = parent.stream
data = xmpp['xep_0027'].encrypt(value, parent['to'].bare)
data = xmpp['xep_0027'].encrypt(value, parent['to'])
if data:
self.xml.text = data
else:
@@ -51,6 +51,3 @@ class Encrypted(ElementBase):
if self.xml.text:
return xmpp['xep_0027'].decrypt(self.xml.text, parent['to'])
return None

View File

@@ -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',
@@ -287,7 +288,7 @@ class XEP_0030(BasePlugin):
'cached': cached}
return self.api['has_identity'](jid, node, ifrom, data)
def get_info(self, jid=None, node=None, local=False,
def get_info(self, jid=None, node=None, local=None,
cached=None, **kwargs):
"""
Retrieve the disco#info results from a given JID/node combination.
@@ -323,24 +324,27 @@ class XEP_0030(BasePlugin):
callback -- Optional callback to execute when a reply is
received instead of blocking and waiting for
the reply.
timeout_callback -- Optional callback to execute when no result
has been received in timeout seconds.
"""
if jid is not None and not isinstance(jid, JID):
jid = JID(jid)
if self.xmpp.is_component:
if jid.domain == self.xmpp.boundjid.domain:
local = True
else:
if str(jid) == str(self.xmpp.boundjid):
local = True
jid = jid.full
elif jid in (None, ''):
local = True
if local is None:
if jid is not None and not isinstance(jid, JID):
jid = JID(jid)
if self.xmpp.is_component:
if jid.domain == self.xmpp.boundjid.domain:
local = True
else:
if str(jid) == str(self.xmpp.boundjid):
local = True
jid = jid.full
elif jid in (None, ''):
local = True
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 +352,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)
@@ -362,7 +366,8 @@ class XEP_0030(BasePlugin):
iq['disco_info']['node'] = node if node else ''
return iq.send(timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None))
callback=kwargs.get('callback', None),
timeout_callback=kwargs.get('timeout_callback', None))
def set_info(self, jid=None, node=None, info=None):
"""
@@ -403,10 +408,12 @@ class XEP_0030(BasePlugin):
iterator -- If True, return a result set iterator using
the XEP-0059 plugin, if the plugin is loaded.
Otherwise the parameter is ignored.
timeout_callback -- Optional callback to execute when no result
has been received in timeout seconds.
"""
if local or jid is None:
items = self.api['get_items'](jid, node,
kwargs.get('ifrom', None),
if local or local is None and jid is None:
items = self.api['get_items'](jid, node,
kwargs.get('ifrom', None),
kwargs)
return self._wrap(kwargs.get('ifrom', None), jid, items)
@@ -421,7 +428,8 @@ class XEP_0030(BasePlugin):
else:
return iq.send(timeout=kwargs.get('timeout', None),
block=kwargs.get('block', True),
callback=kwargs.get('callback', None))
callback=kwargs.get('callback', None),
timeout_callback=kwargs.get('timeout_callback', None))
def set_items(self, jid=None, node=None, **kwargs):
"""
@@ -622,11 +630,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 +653,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 +671,9 @@ class XEP_0030(BasePlugin):
if iq['type'] == 'get':
log.debug("Received disco items query from " + \
"<%s> to <%s>.", iq['from'], iq['to'])
if self.xmpp.is_component:
jid = iq['to'].full
else:
jid = iq['to'].bare
items = self.api['get_items'](jid,
items = self.api['get_items'](iq['to'],
iq['disco_items']['node'],
iq['from'].full,
iq['from'],
iq)
if isinstance(items, Iq):
items.send()

View File

@@ -128,9 +128,10 @@ class DiscoItems(ElementBase):
def del_items(self):
"""Remove all items."""
self._items = set()
for item in self['substanzas']:
if isinstance(item, DiscoItem):
self.xml.remove(item.xml)
items = [i for i in self.iterables if isinstance(i, DiscoItem)]
for item in items:
self.xml.remove(item.xml)
self.iterables.remove(item)
class DiscoItem(ElementBase):

View File

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

View File

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

View File

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

View File

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

View File

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

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