Compare commits

...

332 Commits

Author SHA1 Message Date
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
06a690a259 Merge branch 'master' into develop 2012-10-24 13:07:19 -07:00
Lance Stout
14c9e9a9cc Merge branch 'master' into develop 2012-10-24 13:00:01 -07:00
Lance Stout
931d49560a Merge branch 'master' into develop 2012-10-24 01:23:08 -07:00
Lance Stout
3655827ef2 Merge branch 'master' into develop 2012-10-22 20:10:07 -07:00
Lance Stout
12e0e1a16b Merge branch 'master' into develop 2012-10-22 13:58:12 -07:00
Lance Stout
0d448b8221 Merge branch 'master' into develop 2012-10-19 00:15:21 -07:00
Lance Stout
77f2a339e1 Merge branch 'master' into develop 2012-10-15 22:27:30 -07:00
Lance Stout
7c485c6a8b Merge branch 'master' into develop 2012-10-14 17:35:37 -07:00
Lance Stout
fc07e23ff8 Merge branch 'master' into develop 2012-10-05 08:58:22 -07:00
Lance Stout
84a2fc382b Merge branch 'master' into develop 2012-10-02 09:51:24 -07:00
Lance Stout
44e7585bf8 Merge branch 'master' into develop 2012-09-30 17:15:13 -07:00
Lance Stout
a2c60a4911 Merge branch 'master' into develop 2012-09-28 11:02:57 -07:00
Lance Stout
73ce9a5ecc Merge branch 'master' into develop 2012-09-25 02:45:48 -07:00
Lance Stout
d385b9e708 Merge branch 'master' into develop 2012-09-18 10:37:04 -07:00
Lance Stout
67147570e9 Merge branch 'master' into develop 2012-09-13 11:00:58 -07:00
Lance Stout
df9ac58d05 Merge branch 'master' into develop 2012-09-01 13:57:24 -07:00
Lance Stout
19a78f63f4 Merge branch 'master' into develop 2012-08-24 11:51:03 -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
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
a249f8736a Merge branch 'master' into develop 2012-08-14 11:06:54 -07:00
Lance Stout
f09adf0014 Merge branch 'master' into develop 2012-08-14 09:55:05 -07:00
Lance Stout
04dc68f5f6 Merge branch 'master' into develop 2012-08-13 11:12:41 -07:00
Lance Stout
5c25208fb5 Merge branch 'master' into develop 2012-08-12 22:36:23 -07:00
Lance Stout
962dfad216 Merge branch 'master' into develop 2012-08-10 14:15:55 -07:00
Lance Stout
14aa831169 Merge branch 'master' into develop 2012-08-07 16:45:20 -07:00
Lance Stout
75d904ed01 Merge branch 'master' into develop 2012-08-07 01:40:29 -07:00
Lance Stout
f81d5e4bd6 Merge branch 'master' into develop 2012-08-02 13:47:37 -07:00
Lance Stout
2f65fdbc76 Merge branch 'master' into develop 2012-08-01 23:03:56 -07:00
Lance Stout
2f4149c7d0 Merge branch 'master' into develop 2012-08-01 11:11:54 -07:00
Lance Stout
fb4275648c Merge branch 'master' into develop 2012-08-01 09:05:47 -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
44ce01a70b Merge branch 'master' into develop 2012-07-30 09:08:58 -07:00
Lance Stout
c2189b4ecd Merge branch 'master' into develop 2012-07-27 10:45:52 -07:00
Lance Stout
c9b2cf6043 Merge branch 'master' into develop 2012-07-26 12:24:34 -07:00
Lance Stout
16ec0f151a Merge branch 'master' into develop 2012-07-25 01:47:26 -07:00
Lance Stout
c42f1ad4c7 Merge branch 'master' into develop 2012-07-24 20:01:18 -07:00
Lance Stout
a3ec1af205 Merge branch 'master' into develop 2012-07-23 01:52:55 -07:00
Lance Stout
2e580304f9 Merge branch 'master' into develop 2012-07-22 14:02:26 -07:00
Lance Stout
5492e9028d Merge branch 'master' into develop 2012-07-20 18:15:54 -07:00
Lance Stout
060c9ab679 Merge branch 'master' into develop 2012-07-20 00:25:32 -07:00
Lance Stout
78f0325398 Merge branch 'master' into develop 2012-07-16 20:13:35 -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
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
5d6019a962 Merge branch 'master' into develop 2012-06-22 23:17:15 -07:00
Lance Stout
eb5df1aa37 Merge branch 'master' into develop 2012-06-20 23:46:13 -07:00
Lance Stout
546066d677 Merge branch 'master' into develop 2012-06-20 21:13:06 -07:00
Lance Stout
3234596974 Merge branch 'master' into develop 2012-06-20 19:45:11 -07:00
Lance Stout
5820d49cd4 Merge branch 'master' into develop
Conflicts:
	sleekxmpp/basexmpp.py
2012-06-19 21:50: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
f89df6e70c Merge branch 'master' into develop 2012-06-13 09:27:47 -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
0b51afe87a Add extra check for the cert in the expiration handler. 2012-06-09 11:05:18 -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
198 changed files with 9923 additions and 2190 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,7 +45,7 @@ The latest source code for SleekXMPP may be found on `Github
``develop`` branch.
**Latest Release**
- `1.1.10 <http://github.com/fritzy/SleekXMPP/zipball/1.1.10>`_
- `1.2.5 <http://github.com/fritzy/SleekXMPP/zipball/1.2.5>`_
**Develop Releases**
- `Latest Develop Version <http://github.com/fritzy/SleekXMPP/zipball/develop>`_

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

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

@@ -51,7 +51,7 @@ 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))

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

View File

@@ -38,7 +38,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
self.register_plugin('xep_0030') # Service Discovery
self.register_plugin('xep_0047', {
'accept_stream': self.accept_stream
'auto_accept': True
}) # In-band Bytestreams
# The session_start event will be triggered when
@@ -48,7 +48,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
self.add_event_handler("ibb_stream_start", self.stream_opened)
self.add_event_handler("ibb_stream_start", self.stream_opened, threaded=True)
self.add_event_handler("ibb_stream_data", self.stream_data)
def start(self, event):
@@ -69,7 +69,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
def accept_stream(self, iq):
"""
Check that it is ok to accept a stream request.
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
@@ -83,9 +83,7 @@ class IBBReceiver(sleekxmpp.ClientXMPP):
return True
def stream_opened(self, stream):
# NOTE: IBB streams are bi-directional, so the original sender is
# now the opened stream's receiver.
print('Stream opened: %s from %s' % (stream.sid, stream.receiver))
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.

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

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

7
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
@@ -20,7 +23,7 @@ else:
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']

5
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
@@ -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:' % (

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

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

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

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()

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

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

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',
]
@@ -62,18 +63,22 @@ packages = [ 'sleekxmpp',
'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',
@@ -82,17 +87,21 @@ packages = [ 'sleekxmpp',
'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',
@@ -109,6 +118,16 @@ packages = [ 'sleekxmpp',
'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',

View File

@@ -6,14 +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

@@ -18,8 +18,7 @@ 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
@@ -34,8 +33,7 @@ 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__)
@@ -148,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',
@@ -201,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.

View File

@@ -96,6 +96,7 @@ class ClientXMPP(BaseXMPP):
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)
@@ -106,7 +107,7 @@ 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')
@@ -114,8 +115,10 @@ class ClientXMPP(BaseXMPP):
self.register_plugin('feature_session')
self.register_plugin('feature_rosterver')
self.register_plugin('feature_preapproval')
self.register_plugin('feature_mechanisms',
pconfig={'use_mech': sasl_mech} if sasl_mech else None)
self.register_plugin('feature_mechanisms')
if sasl_mech:
self['feature_mechanisms'].use_mech = sasl_mech
@property
def password(self):
@@ -133,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
@@ -152,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)
@@ -242,14 +243,22 @@ 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)
self.event('roster_update', response)
return response
def _reset_connection_state(self, event=None):
@@ -300,7 +309,6 @@ class ClientXMPP(BaseXMPP):
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

@@ -123,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):
@@ -158,8 +152,8 @@ class ComponentXMPP(BaseXMPP):
"""
self.session_bind_event.set()
self.session_started_event.set()
self.event("session_bind", self.boundjid, direct=True)
self.event("session_start")
self.event('session_bind', self.boundjid, direct=True)
self.event('session_start')
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:

View File

@@ -12,7 +12,7 @@ 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__)
@@ -41,12 +41,12 @@ 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.boundjid = JID(response['bind']['jid'], cache_lock=True)
@@ -56,10 +56,10 @@ class FeatureBind(BasePlugin):
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,7 +6,6 @@
See the file LICENSE for copying permission.
"""
import sys
import ssl
import logging
@@ -44,15 +43,16 @@ class FeatureMechanisms(BasePlugin):
}
def plugin_init(self):
if not self.use_mech and not self.xmpp.requested_jid.user:
self.use_mech = 'ANONYMOUS'
if self.sasl_callback is None:
self.sasl_callback = self._default_credentials
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'
self.mech = None
self.mech_list = set()
self.attempted_mechs = set()
@@ -92,27 +92,26 @@ class FeatureMechanisms(BasePlugin):
values = required_values.union(optional_values)
for value in values:
if value == 'username':
result[value] = self.xmpp.requested_jid.user
elif value == 'password':
result[value] = creds['password']
elif value == 'authzid':
result[value] = creds.get('authzid', '')
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 sys.version_info >= (3, 3):
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] = self.xmpp.requested_jid.domain
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
elif value == 'realm':
result[value] = self.xmpp.requested_jid.domain
result[value] = creds.get('realm', self.xmpp.requested_jid.domain)
elif value == 'service-name':
result[value] = self.xmpp._service_name
result[value] = creds.get('service-name', self.xmpp._service_name)
elif value == 'service':
result[value] = 'xmpp'
result[value] = creds.get('service', 'xmpp')
elif value in creds:
result[value] = creds[value]
return result
@@ -174,8 +173,12 @@ class FeatureMechanisms(BasePlugin):
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
@@ -192,9 +195,6 @@ class FeatureMechanisms(BasePlugin):
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
except StringPrepError:
log.exception("A credential value did not pass SASLprep.")
self.xmpp.disconnect()
else:
resp.send(now=True)

View File

@@ -40,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,7 +8,7 @@
import logging
from sleekxmpp.stanza import Iq, StreamFeatures
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

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

@@ -19,6 +19,8 @@ import stringprep
import threading
import encodings.idna
from copy import deepcopy
from sleekxmpp.util import stringprep_profiles
from sleekxmpp.thirdparty import OrderedDict
@@ -69,6 +71,20 @@ 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,
@@ -188,7 +204,7 @@ def _validate_domain(domain):
socket.inet_pton(socket.AF_INET6, domain.strip('[]'))
domain = '[%s]' % domain.strip('[]')
ip_addr = True
except socket.error:
except (socket.error, ValueError):
pass
if not ip_addr:
@@ -214,7 +230,7 @@ def _validate_domain(domain):
for char in label:
if char in ILLEGAL_CHARS:
raise InvalidJID('Domain contains illegar characters')
raise InvalidJID('Domain contains illegal characters')
if '-' in (label[0], label[-1]):
raise InvalidJID('Domain started or ended with -')
@@ -418,19 +434,29 @@ class JID(object):
# pylint: disable=W0212
def __init__(self, jid=None, **kwargs):
jid_data = (jid, kwargs.get('local', None),
kwargs.get('domain', None),
kwargs.get('resource', None))
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)
if jid_data in JID_CACHE:
parsed_jid, locked = JID_CACHE[jid_data]
self._jid = parsed_jid
else:
if jid is None:
jid = ''
# 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):
@@ -440,27 +466,16 @@ class JID(object):
local, domain, resource = parsed_jid
local = kwargs.get('local', local)
domain = kwargs.get('domain', domain)
resource = kwargs.get('resource', resource)
if 'local' in kwargs:
local = _escape_node(local)
local = _escape_node(in_local)
if 'domain' in kwargs:
domain = _validate_domain(domain)
domain = _validate_domain(in_domain)
if 'resource' in kwargs:
resource = _validate_resource(resource)
resource = _validate_resource(in_resource)
self._jid = (local, domain, resource)
JID_CACHE[jid_data] = (self._jid, locked)
if len(JID_CACHE) > JID_CACHE_MAX_SIZE:
with JID_CACHE_LOCK:
key, item = JID_CACHE.popitem(False)
if item[1]:
# Need to reinsert locked JIDs
JID_CACHE[key] = item
if key:
_cache(key, self._jid, locked)
def unescape(self):
"""Return an unescaped JID object.
@@ -493,50 +508,100 @@ class JID(object):
"""
self._jid = JID(data)._jid
# pylint: disable=R0911
def __getattr__(self, name):
"""Retrieve the given JID component.
@property
def resource(self):
return self._jid[2] or ''
: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
@property
def user(self):
return self._jid[0] or ''
# pylint: disable=W0212
def __setattr__(self, name, value):
"""Update the given JID component.
@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])
:param name: one of: ``user``, ``username``, ``local``,
``node``, ``server``, ``domain``, ``host``,
``resource``, ``full``, ``jid``, or ``bare``.
:param value: The new string value of the JID component.
"""
if name == '_jid':
super(JID, self).__setattr__('_jid', value)
elif name == 'resource':
self._jid = JID(self, resource=value)._jid
elif name in ('user', 'username', 'local', 'node'):
self._jid = JID(self, local=value)._jid
elif name in ('server', 'domain', 'host'):
self._jid = JID(self, domain=value)._jid
elif name in ('full', 'jid'):
self._jid = JID(value)._jid
elif name == 'bare':
parsed = JID(value)._jid
self._jid = (parsed[0], parsed[1], self._jid[2])
def __str__(self):
"""Use the full JID as the string value."""
@@ -567,3 +632,7 @@ class JID(object):
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,28 +11,30 @@ 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
@@ -48,12 +50,14 @@ __all__ = [
'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
@@ -76,4 +80,7 @@ __all__ = [
'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

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

@@ -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):
@@ -221,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

@@ -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.stripped() == '':
if begin_headers and line.strip() == '':
begin_data = True
continue
if 'END PGP %s' % kind in line:

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:

View File

@@ -324,6 +324,8 @@ 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 local is None:
if jid is not None and not isinstance(jid, JID):
@@ -364,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):
"""
@@ -405,6 +408,8 @@ 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 local is None and jid is None:
items = self.api['get_items'](jid, node,
@@ -423,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):
"""

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

@@ -125,11 +125,12 @@ class XEP_0045(BasePlugin):
self.xep = '0045'
# load MUC support in presence stanzas
register_stanza_plugin(Presence, MUCPresence)
self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
self.xmpp.registerHandler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change))
self.xmpp.registerHandler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % (
self.xmpp.register_handler(Callback('MUCPresence', MatchXMLMask("<presence xmlns='%s' />" % self.xmpp.default_ns), self.handle_groupchat_presence))
self.xmpp.register_handler(Callback('MUCError', MatchXMLMask("<message xmlns='%s' type='error'><error/></message>" % self.xmpp.default_ns), self.handle_groupchat_error_message))
self.xmpp.register_handler(Callback('MUCMessage', MatchXMLMask("<message xmlns='%s' type='groupchat'><body/></message>" % self.xmpp.default_ns), self.handle_groupchat_message))
self.xmpp.register_handler(Callback('MUCSubject', MatchXMLMask("<message xmlns='%s' type='groupchat'><subject/></message>" % self.xmpp.default_ns), self.handle_groupchat_subject))
self.xmpp.register_handler(Callback('MUCConfig', MatchXMLMask("<message xmlns='%s' type='groupchat'><x xmlns='http://jabber.org/protocol/muc#user'><status/></x></message>" % self.xmpp.default_ns), self.handle_config_change))
self.xmpp.register_handler(Callback('MUCInvite', MatchXPath("{%s}message/{%s}x/{%s}invite" % (
self.xmpp.default_ns,
'http://jabber.org/protocol/muc#user',
'http://jabber.org/protocol/muc#user')), self.handle_groupchat_invite))
@@ -179,6 +180,14 @@ class XEP_0045(BasePlugin):
self.xmpp.event('groupchat_message', msg)
self.xmpp.event("muc::%s::message" % msg['from'].bare, msg)
def handle_groupchat_error_message(self, msg):
""" Handle a message error event in a muc.
"""
self.xmpp.event('groupchat_message_error', msg)
self.xmpp.event("muc::%s::message_error" % msg['from'].bare, msg)
def handle_groupchat_subject(self, msg):
""" Handle a message coming from a muc indicating
a change of subject (or announcing it when joining the room)
@@ -198,30 +207,9 @@ class XEP_0045(BasePlugin):
if entry is not None and entry['jid'].full == jid:
return nick
def getRoomForm(self, room, ifrom=None):
iq = self.xmpp.makeIqGet()
iq['to'] = room
if ifrom is not None:
iq['from'] = ifrom
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
iq.append(query)
# For now, swallow errors to preserve existing API
try:
result = iq.send()
except IqError:
return False
except IqTimeout:
return False
xform = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if xform is None: return False
form = self.xmpp.plugin['old_0004'].buildForm(xform)
return form
def configureRoom(self, room, form=None, ifrom=None):
if form is None:
form = self.getRoomForm(room, ifrom=ifrom)
#form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit')
#form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig')
form = self.getRoomConfig(room, ifrom=ifrom)
iq = self.xmpp.makeIqSet()
iq['to'] = room
if ifrom is not None:
@@ -310,6 +298,24 @@ class XEP_0045(BasePlugin):
return False
return True
def setRole(self, room, nick, role):
""" Change role property of a nick in a room.
Typically, roles are temporary (they last only as long as you are in the
room), whereas affiliations are permanent (they last across groupchat
sessions).
"""
if role not in ('moderator', 'participant', 'visitor', 'none'):
raise TypeError
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
item = ET.Element('item', {'role':role, 'nick':nick})
query.append(item)
iq = self.xmpp.makeIqSet(query)
iq['to'] = room
result = iq.send()
if result is False or result['type'] != 'result':
raise ValueError
return True
def invite(self, room, jid, reason='', mfrom=''):
""" Invite a jid to a room."""
msg = self.xmpp.makeMessage(room)

View File

@@ -21,21 +21,25 @@ class XEP_0047(BasePlugin):
dependencies = set(['xep_0030'])
stanza = stanza
default_config = {
'block_size': 4096,
'max_block_size': 8192,
'window_size': 1,
'auto_accept': True,
'accept_stream': None
'auto_accept': False,
}
def plugin_init(self):
self.streams = {}
self.pending_streams = {}
self.pending_close_streams = {}
self._streams = {}
self._pending_streams = {}
self._pending_lock = threading.Lock()
self._stream_lock = threading.Lock()
self._preauthed_sids_lock = threading.Lock()
self._preauthed_sids = {}
register_stanza_plugin(Iq, Open)
register_stanza_plugin(Iq, Close)
register_stanza_plugin(Iq, Data)
register_stanza_plugin(Message, Data)
self.xmpp.register_handler(Callback(
'IBB Open',
@@ -52,27 +56,71 @@ class XEP_0047(BasePlugin):
StanzaPath('iq@type=set/ibb_data'),
self._handle_data))
self.xmpp.register_handler(Callback(
'IBB Message Data',
StanzaPath('message/ibb_data'),
self._handle_data))
self.api.register(self._authorized, 'authorized', default=True)
self.api.register(self._authorized_sid, 'authorized_sid', default=True)
self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
self.api.register(self._get_stream, 'get_stream', default=True)
self.api.register(self._set_stream, 'set_stream', default=True)
self.api.register(self._del_stream, 'del_stream', default=True)
def plugin_end(self):
self.xmpp.remove_handler('IBB Open')
self.xmpp.remove_handler('IBB Close')
self.xmpp.remove_handler('IBB Data')
self.xmpp.remove_handler('IBB Message Data')
self.xmpp['xep_0030'].del_feature(feature='http://jabber.org/protocol/ibb')
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature('http://jabber.org/protocol/ibb')
def _get_stream(self, jid, sid, peer_jid, data):
return self._streams.get((jid, sid, peer_jid), None)
def _set_stream(self, jid, sid, peer_jid, stream):
self._streams[(jid, sid, peer_jid)] = stream
def _del_stream(self, jid, sid, peer_jid, data):
with self._stream_lock:
if (jid, sid, peer_jid) in self._streams:
del self._streams[(jid, sid, peer_jid)]
def _accept_stream(self, iq):
if self.accept_stream is not None:
return self.accept_stream(iq)
receiver = iq['to']
sender = iq['from']
sid = iq['ibb_open']['sid']
if self.api['authorized_sid'](receiver, sid, sender, iq):
return True
return self.api['authorized'](receiver, sid, sender, iq)
def _authorized(self, jid, sid, ifrom, iq):
if self.auto_accept:
if iq['ibb_open']['block_size'] <= self.max_block_size:
return True
return False
def open_stream(self, jid, block_size=4096, sid=None, window=1,
def _authorized_sid(self, jid, sid, ifrom, iq):
with self._preauthed_sids_lock:
if (jid, sid, ifrom) in self._preauthed_sids:
del self._preauthed_sids[(jid, sid, ifrom)]
return True
return False
def _preauthorize_sid(self, jid, sid, ifrom, data):
with self._preauthed_sids_lock:
self._preauthed_sids[(jid, sid, ifrom)] = True
def open_stream(self, jid, block_size=None, sid=None, window=1, use_messages=False,
ifrom=None, block=True, timeout=None, callback=None):
if sid is None:
sid = str(uuid.uuid4())
if block_size is None:
block_size = self.block_size
iq = self.xmpp.Iq()
iq['type'] = 'set'
@@ -83,12 +131,13 @@ class XEP_0047(BasePlugin):
iq['ibb_open']['stanza'] = 'iq'
stream = IBBytestream(self.xmpp, sid, block_size,
iq['to'], iq['from'], window)
iq['from'], iq['to'], window,
use_messages)
with self._stream_lock:
self.pending_streams[iq['id']] = stream
self._pending_streams[iq['id']] = stream
self.pending_streams[iq['id']] = stream
self._pending_streams[iq['id']] = stream
if block:
resp = iq.send(timeout=timeout)
@@ -108,49 +157,59 @@ class XEP_0047(BasePlugin):
def _handle_opened_stream(self, iq):
if iq['type'] == 'result':
with self._stream_lock:
stream = self.pending_streams.get(iq['id'], None)
if stream is not None:
stream.sender = iq['to']
stream.receiver = iq['from']
stream.stream_started.set()
self.streams[stream.sid] = stream
self.xmpp.event('ibb_stream_start', stream)
stream = self._pending_streams.get(iq['id'], None)
if stream is not None:
log.debug('IBB stream (%s) accepted by %s', stream.sid, iq['from'])
stream.self_jid = iq['to']
stream.peer_jid = iq['from']
stream.stream_started.set()
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
self.xmpp.event('ibb_stream_start', stream)
self.xmpp.event('stream:%s:%s' % (stream.sid, stream.peer_jid), stream)
with self._stream_lock:
if iq['id'] in self.pending_streams:
del self.pending_streams[iq['id']]
if iq['id'] in self._pending_streams:
del self._pending_streams[iq['id']]
def _handle_open_request(self, iq):
sid = iq['ibb_open']['sid']
size = iq['ibb_open']['block_size']
size = iq['ibb_open']['block_size'] or self.block_size
log.debug('Received IBB stream request from %s', iq['from'])
if not sid:
raise XMPPError(etype='modify', condition='bad-request')
if not self._accept_stream(iq):
raise XMPPError('not-acceptable')
raise XMPPError(etype='modify', condition='not-acceptable')
if size > self.max_block_size:
raise XMPPError('resource-constraint')
stream = IBBytestream(self.xmpp, sid, size,
iq['from'], iq['to'],
iq['to'], iq['from'],
self.window_size)
stream.stream_started.set()
self.streams[sid] = stream
self.api['set_stream'](stream.self_jid, stream.sid, stream.peer_jid, stream)
iq.reply()
iq.send()
self.xmpp.event('ibb_stream_start', stream)
self.xmpp.event('stream:%s:%s' % (sid, stream.peer_jid), stream)
def _handle_data(self, iq):
sid = iq['ibb_data']['sid']
stream = self.streams.get(sid, None)
if stream is not None and iq['from'] != stream.sender:
stream._recv_data(iq)
def _handle_data(self, stanza):
sid = stanza['ibb_data']['sid']
stream = self.api['get_stream'](stanza['to'], sid, stanza['from'])
if stream is not None and stanza['from'] == stream.peer_jid:
stream._recv_data(stanza)
else:
raise XMPPError('item-not-found')
def _handle_close(self, iq):
sid = iq['ibb_close']['sid']
stream = self.streams.get(sid, None)
if stream is not None and iq['from'] != stream.sender:
stream = self.api['get_stream'](iq['to'], sid, iq['from'])
if stream is not None and iq['from'] == stream.peer_jid:
stream._closed(iq)
self.api['del_stream'](stream.self_jid, stream.sid, stream.peer_jid)
else:
raise XMPPError('item-not-found')

View File

@@ -2,6 +2,7 @@ import socket
import threading
import logging
from sleekxmpp.stanza import Iq
from sleekxmpp.util import Queue
from sleekxmpp.exceptions import XMPPError
@@ -11,14 +12,17 @@ log = logging.getLogger(__name__)
class IBBytestream(object):
def __init__(self, xmpp, sid, block_size, to, ifrom, window_size=1):
def __init__(self, xmpp, sid, block_size, jid, peer, window_size=1, use_messages=False):
self.xmpp = xmpp
self.sid = sid
self.block_size = block_size
self.window_size = window_size
self.use_messages = use_messages
self.receiver = to
self.sender = ifrom
if jid is None:
jid = xmpp.boundjid
self.self_jid = jid
self.peer_jid = peer
self.send_seq = -1
self.recv_seq = -1
@@ -46,16 +50,27 @@ class IBBytestream(object):
with self._send_seq_lock:
self.send_seq = (self.send_seq + 1) % 65535
seq = self.send_seq
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['to'] = self.receiver
iq['from'] = self.sender
iq['ibb_data']['sid'] = self.sid
iq['ibb_data']['seq'] = seq
iq['ibb_data']['data'] = data
self.window_empty.clear()
self.window_ids.add(iq['id'])
iq.send(block=False, callback=self._recv_ack)
if self.use_messages:
msg = self.xmpp.Message()
msg['to'] = self.peer_jid
msg['from'] = self.self_jid
msg['id'] = self.xmpp.new_id()
msg['ibb_data']['sid'] = self.sid
msg['ibb_data']['seq'] = seq
msg['ibb_data']['data'] = data
msg.send()
self.send_window.release()
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['to'] = self.peer_jid
iq['from'] = self.self_jid
iq['ibb_data']['sid'] = self.sid
iq['ibb_data']['seq'] = seq
iq['ibb_data']['data'] = data
self.window_empty.clear()
self.window_ids.add(iq['id'])
iq.send(block=False, callback=self._recv_ack)
return len(data)
def sendall(self, data):
@@ -71,23 +86,25 @@ class IBBytestream(object):
if iq['type'] == 'error':
self.close()
def _recv_data(self, iq):
def _recv_data(self, stanza):
with self._recv_seq_lock:
new_seq = iq['ibb_data']['seq']
new_seq = stanza['ibb_data']['seq']
if new_seq != (self.recv_seq + 1) % 65535:
self.close()
raise XMPPError('unexpected-request')
self.recv_seq = new_seq
data = iq['ibb_data']['data']
data = stanza['ibb_data']['data']
if len(data) > self.block_size:
self.close()
raise XMPPError('not-acceptable')
self.recv_queue.put(data)
self.xmpp.event('ibb_stream_data', {'stream': self, 'data': data})
iq.reply()
iq.send()
if isinstance(stanza, Iq):
stanza.reply()
stanza.send()
def recv(self, *args, **kwargs):
return self.read(block=True)
@@ -106,8 +123,8 @@ class IBBytestream(object):
def close(self):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['to'] = self.receiver
iq['from'] = self.sender
iq['to'] = self.peer_jid
iq['from'] = self.self_jid
iq['ibb_close']['sid'] = self.sid
self.stream_out_closed.set()
iq.send(block=False,
@@ -117,9 +134,6 @@ class IBBytestream(object):
def _closed(self, iq):
self.stream_in_closed.set()
self.stream_out_closed.set()
while not self.window_empty.is_set():
log.info('waiting for send window to empty')
self.window_empty.wait(timeout=1)
iq.reply()
iq.send()
self.xmpp.event('ibb_stream_end', self)

View File

@@ -0,0 +1,15 @@
"""
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_0048.stanza import Bookmarks, Conference, URL
from sleekxmpp.plugins.xep_0048.bookmarks import XEP_0048
register_plugin(XEP_0048)

View File

@@ -0,0 +1,76 @@
"""
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
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0048 import stanza, Bookmarks, Conference, URL
log = logging.getLogger(__name__)
class XEP_0048(BasePlugin):
name = 'xep_0048'
description = 'XEP-0048: Bookmarks'
dependencies = set(['xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223'])
stanza = stanza
default_config = {
'auto_join': False,
'storage_method': 'xep_0049'
}
def plugin_init(self):
register_stanza_plugin(self.xmpp['xep_0060'].stanza.Item, Bookmarks)
self.xmpp['xep_0049'].register(Bookmarks)
self.xmpp['xep_0163'].register_pep('bookmarks', Bookmarks)
self.xmpp.add_event_handler('session_start', self._autojoin)
def plugin_end(self):
self.xmpp.del_event_handler('session_start', self._autojoin)
def _autojoin(self, __):
if not self.auto_join:
return
try:
result = self.get_bookmarks(method=self.storage_method)
except XMPPError:
return
if self.storage_method == 'xep_0223':
bookmarks = result['pubsub']['items']['item']['bookmarks']
else:
bookmarks = result['private']['bookmarks']
for conf in bookmarks['conferences']:
if conf['autojoin']:
log.debug('Auto joining %s as %s', conf['jid'], conf['nick'])
self.xmpp['xep_0045'].joinMUC(conf['jid'], conf['nick'],
password=conf['password'])
def set_bookmarks(self, bookmarks, method=None, **iqargs):
if not method:
method = self.storage_method
return self.xmpp[method].store(bookmarks, **iqargs)
def get_bookmarks(self, method=None, **iqargs):
if not method:
method = self.storage_method
loc = 'storage:bookmarks' if method == 'xep_0223' else 'bookmarks'
return self.xmpp[method].retrieve(loc, **iqargs)

View File

@@ -0,0 +1,65 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.xmlstream import ET, ElementBase, register_stanza_plugin
class Bookmarks(ElementBase):
name = 'storage'
namespace = 'storage:bookmarks'
plugin_attrib = 'bookmarks'
interfaces = set()
def add_conference(self, jid, nick, name=None, autojoin=None, password=None):
conf = Conference()
conf['jid'] = jid
conf['nick'] = nick
if name is None:
name = jid
conf['name'] = name
conf['autojoin'] = autojoin
conf['password'] = password
self.append(conf)
def add_url(self, url, name=None):
saved_url = URL()
saved_url['url'] = url
if name is None:
name = url
saved_url['name'] = name
self.append(saved_url)
class Conference(ElementBase):
name = 'conference'
namespace = 'storage:bookmarks'
plugin_attrib = 'conference'
plugin_multi_attrib = 'conferences'
interfaces = set(['nick', 'password', 'autojoin', 'jid', 'name'])
sub_interfaces = set(['nick', 'password'])
def get_autojoin(self):
value = self._get_attr('autojoin')
return value in ('1', 'true')
def set_autojoin(self, value):
del self['autojoin']
if value in ('1', 'true', True):
self._set_attr('autojoin', 'true')
class URL(ElementBase):
name = 'url'
namespace = 'storage:bookmarks'
plugin_attrib = 'url'
plugin_multi_attrib = 'urls'
interfaces = set(['url', 'name'])
register_stanza_plugin(Bookmarks, Conference, iterable=True)
register_stanza_plugin(Bookmarks, URL, iterable=True)

View File

@@ -267,20 +267,50 @@ class XEP_0050(BasePlugin):
iq -- The command continuation request.
"""
sessionid = iq['command']['sessionid']
session = self.sessions[sessionid]
session = self.sessions.get(sessionid)
handler = session['next']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
if session:
handler = session['next']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
session = handler(results, session)
session = handler(results, session)
self._process_command_response(iq, session)
self._process_command_response(iq, session)
else:
raise XMPPError('item-not-found')
def _handle_command_prev(self, iq):
"""
Process a request for the prev step in the workflow
for a command with multiple steps.
Arguments:
iq -- The command continuation request.
"""
sessionid = iq['command']['sessionid']
session = self.sessions.get(sessionid)
if session:
handler = session['prev']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
session = handler(results, session)
self._process_command_response(iq, session)
else:
raise XMPPError('item-not-found')
def _process_command_response(self, iq, session):
"""
@@ -348,23 +378,23 @@ class XEP_0050(BasePlugin):
"""
node = iq['command']['node']
sessionid = iq['command']['sessionid']
session = self.sessions[sessionid]
handler = session['cancel']
if handler:
handler(iq, session)
session = self.sessions.get(sessionid)
try:
if session:
handler = session['cancel']
if handler:
handler(iq, session)
del self.sessions[sessionid]
except:
pass
iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['status'] = 'canceled'
iq['command']['notes'] = session['notes']
iq.send()
else:
raise XMPPError('item-not-found')
iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['status'] = 'canceled'
iq['command']['notes'] = session['notes']
iq.send()
def _handle_command_complete(self, iq):
"""
@@ -378,28 +408,32 @@ class XEP_0050(BasePlugin):
"""
node = iq['command']['node']
sessionid = iq['command']['sessionid']
session = self.sessions[sessionid]
handler = session['next']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
session = self.sessions.get(sessionid)
if handler:
handler(results, session)
if session:
handler = session['next']
interfaces = session['interfaces']
results = []
for stanza in iq['command']['substanzas']:
if stanza.plugin_attrib in interfaces:
results.append(stanza)
if len(results) == 1:
results = results[0]
iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['actions'] = []
iq['command']['status'] = 'completed'
iq['command']['notes'] = session['notes']
iq.send()
if handler:
handler(results, session)
del self.sessions[sessionid]
del self.sessions[sessionid]
iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['actions'] = []
iq['command']['status'] = 'completed'
iq['command']['notes'] = session['notes']
iq.send()
else:
raise XMPPError('item-not-found')
# =================================================================
# Client side (command user) API
@@ -537,7 +571,7 @@ class XEP_0050(BasePlugin):
else:
iq.send(block=False, callback=self._handle_command_result)
def continue_command(self, session):
def continue_command(self, session, direction='next'):
"""
Execute the next action of the command.
@@ -551,7 +585,7 @@ class XEP_0050(BasePlugin):
self.send_command(session['jid'],
session['node'],
ifrom=session.get('from', None),
action='next',
action=direction,
payload=session.get('payload', None),
sessionid=session['id'],
flow=True,

View File

@@ -8,7 +8,7 @@
import logging
from sleekxmpp import Iq
from sleekxmpp import JID, Iq
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback
@@ -59,10 +59,20 @@ class XEP_0054(BasePlugin):
def make_vcard(self):
return VCardTemp()
def get_vcard(self, jid=None, ifrom=None, local=False, cached=False,
def get_vcard(self, jid=None, ifrom=None, local=None, cached=False,
block=True, callback=None, timeout=None):
if self.xmpp.is_component and jid.domain == self.xmpp.boundjid.domain:
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:
vcard = self.api['get_vcard'](jid, None, ifrom)

View File

@@ -423,7 +423,7 @@ class XEP_0060(BasePlugin):
callback=None, timeout=None):
iq = self.xmpp.Iq(sto=jid, sfrom=ifrom, stype='set')
iq['pubsub_owner']['configure']['node'] = node
iq['pubsub_owner']['configure']['form'].values = config.values
iq['pubsub_owner']['configure'].append(config)
return iq.send(block=block, callback=callback, timeout=timeout)
def publish(self, jid, node, id=None, payload=None, options=None,

View File

@@ -74,7 +74,12 @@ class Item(ElementBase):
def set_payload(self, value):
del self['payload']
self.append(value)
if isinstance(value, ElementBase):
if value.tag_name() in self.plugin_tag_map:
self.init_plugin(value.plugin_attrib, existing_xml=value.xml)
self.xml.append(value.xml)
else:
self.xml.append(value)
def get_payload(self):
childs = list(self.xml)
@@ -243,39 +248,6 @@ class PublishOptions(ElementBase):
self.parent().xml.remove(self.xml)
class PubsubState(ElementBase):
"""This is an experimental pubsub extension."""
namespace = 'http://jabber.org/protocol/psstate'
name = 'state'
plugin_attrib = 'psstate'
interfaces = set(('node', 'item', 'payload'))
def set_payload(self, value):
self.xml.append(value)
def get_payload(self):
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
def del_payload(self):
for child in self.xml:
self.xml.remove(child)
class PubsubStateEvent(ElementBase):
"""This is an experimental pubsub extension."""
namespace = 'http://jabber.org/protocol/psstate#event'
name = 'event'
plugin_attrib = 'psstate_event'
intefaces = set(tuple())
register_stanza_plugin(Iq, PubsubState)
register_stanza_plugin(Message, PubsubStateEvent)
register_stanza_plugin(PubsubStateEvent, PubsubState)
register_stanza_plugin(Iq, Pubsub)
register_stanza_plugin(Pubsub, Affiliations)
register_stanza_plugin(Pubsub, Configure)

View File

@@ -34,7 +34,8 @@ class DefaultConfig(ElementBase):
return self['form']
def set_config(self, value):
self['form'].values = value.values
del self['from']
self.append(value)
return self
@@ -93,7 +94,9 @@ class OwnerRedirect(ElementBase):
class OwnerSubscriptions(Subscriptions):
name = 'subscriptions'
namespace = 'http://jabber.org/protocol/pubsub#owner'
plugin_attrib = name
interfaces = set(('node',))
def append(self, subscription):

View File

@@ -0,0 +1,7 @@
from sleekxmpp.plugins.base import register_plugin
from sleekxmpp.plugins.xep_0065.stanza import Socks5
from sleekxmpp.plugins.xep_0065.proxy import XEP_0065
register_plugin(XEP_0065)

View File

@@ -0,0 +1,292 @@
import logging
import threading
import socket
from hashlib import sha1
from uuid import uuid4
from sleekxmpp.thirdparty.socks import socksocket, PROXY_TYPE_SOCKS5
from sleekxmpp.stanza import Iq
from sleekxmpp.exceptions import XMPPError
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.xmlstream.matcher import StanzaPath
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0065 import stanza, Socks5
log = logging.getLogger(__name__)
class XEP_0065(base_plugin):
name = 'xep_0065'
description = "Socks5 Bytestreams"
dependencies = set(['xep_0030'])
default_config = {
'auto_accept': False
}
def plugin_init(self):
register_stanza_plugin(Iq, Socks5)
self._proxies = {}
self._sessions = {}
self._sessions_lock = threading.Lock()
self._preauthed_sids_lock = threading.Lock()
self._preauthed_sids = {}
self.xmpp.register_handler(
Callback('Socks5 Bytestreams',
StanzaPath('iq@type=set/socks/streamhost'),
self._handle_streamhost))
self.api.register(self._authorized, 'authorized', default=True)
self.api.register(self._authorized_sid, 'authorized_sid', default=True)
self.api.register(self._preauthorize_sid, 'preauthorize_sid', default=True)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Socks5.namespace)
def plugin_end(self):
self.xmpp.remove_handler('Socks5 Bytestreams')
self.xmpp.remove_handler('Socks5 Streamhost Used')
self.xmpp['xep_0030'].del_feature(feature=Socks5.namespace)
def get_socket(self, sid):
"""Returns the socket associated to the SID."""
return self._sessions.get(sid, None)
def handshake(self, to, ifrom=None, sid=None, timeout=None):
""" Starts the handshake to establish the socks5 bytestreams
connection.
"""
if not self._proxies:
self._proxies = self.discover_proxies()
if sid is None:
sid = uuid4().hex
used = self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
proxy = used['socks']['streamhost_used']['jid']
if proxy not in self._proxies:
log.warning('Received unknown SOCKS5 proxy: %s', proxy)
return
with self._sessions_lock:
self._sessions[sid] = self._connect_proxy(
sid,
self.xmpp.boundjid,
to,
self._proxies[proxy][0],
self._proxies[proxy][1],
peer=to)
# Request that the proxy activate the session with the target.
self.activate(proxy, sid, to, timeout=timeout)
socket = self.get_socket(sid)
self.xmpp.event('stream:%s:%s' % (sid, to), socket)
return socket
def request_stream(self, to, sid=None, ifrom=None, block=True, timeout=None, callback=None):
if sid is None:
sid = uuid4().hex
# Requester initiates S5B negotiation with Target by sending
# IQ-set that includes the JabberID and network address of
# StreamHost as well as the StreamID (SID) of the proposed
# bytestream.
iq = self.xmpp.Iq()
iq['to'] = to
iq['from'] = ifrom
iq['type'] = 'set'
iq['socks']['sid'] = sid
for proxy, (host, port) in self._proxies.items():
iq['socks'].add_streamhost(proxy, host, port)
return iq.send(block=block, timeout=timeout, callback=callback)
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
if jid is None:
if self.xmpp.is_component:
jid = self.xmpp.server
else:
jid = self.xmpp.boundjid.server
discovered = set()
disco_items = self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
for item in disco_items['disco_items']['items']:
try:
disco_info = self.xmpp['xep_0030'].get_info(item[0], timeout=timeout)
except XMPPError:
continue
else:
# Verify that the identity is a bytestream proxy.
identities = disco_info['disco_info']['identities']
for identity in identities:
if identity[0] == 'proxy' and identity[1] == 'bytestreams':
discovered.add(disco_info['from'])
for jid in discovered:
try:
addr = self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
self._proxies[jid] = (addr['socks']['streamhost']['host'],
addr['socks']['streamhost']['port'])
except XMPPError:
continue
return self._proxies
def get_network_address(self, proxy, ifrom=None, block=True, timeout=None, callback=None):
"""Get the network information of a proxy."""
iq = self.xmpp.Iq(sto=proxy, stype='get', sfrom=ifrom)
iq.enable('socks')
return iq.send(block=block, timeout=timeout, callback=callback)
def _handle_streamhost(self, iq):
"""Handle incoming SOCKS5 session request."""
sid = iq['socks']['sid']
if not sid:
raise XMPPError(etype='modify', condition='bad-request')
if not self._accept_stream(iq):
raise XMPPError(etype='modify', condition='not-acceptable')
streamhosts = iq['socks']['streamhosts']
conn = None
used_streamhost = None
sender = iq['from']
for streamhost in streamhosts:
try:
conn = self._connect_proxy(sid,
sender,
self.xmpp.boundjid,
streamhost['host'],
streamhost['port'],
peer=sender)
used_streamhost = streamhost['jid']
break
except socket.error:
continue
else:
raise XMPPError(etype='cancel', condition='item-not-found')
iq.reply()
with self._sessions_lock:
self._sessions[sid] = conn
iq['socks']['sid'] = sid
iq['socks']['streamhost_used']['jid'] = used_streamhost
iq.send()
self.xmpp.event('socks5_stream', conn)
self.xmpp.event('stream:%s:%s' % (sid, conn.peer_jid), conn)
def activate(self, proxy, sid, target, ifrom=None, block=True, timeout=None, callback=None):
"""Activate the socks5 session that has been negotiated."""
iq = self.xmpp.Iq(sto=proxy, stype='set', sfrom=ifrom)
iq['socks']['sid'] = sid
iq['socks']['activate'] = target
iq.send(block=block, timeout=timeout, callback=callback)
def deactivate(self, sid):
"""Closes the proxy socket associated with this SID."""
sock = self._sessions.get(sid)
if sock:
try:
# sock.close() will also delete sid from self._sessions (see _connect_proxy)
sock.close()
except socket.error:
pass
# Though this should not be neccessary remove the closed session anyway
with self._sessions_lock:
if sid in self._sessions:
log.warn(('SOCKS5 session with sid = "%s" was not ' +
'removed from _sessions by sock.close()') % sid)
del self._sessions[sid]
def close(self):
"""Closes all proxy sockets."""
for sid, sock in self._sessions.items():
sock.close()
with self._sessions_lock:
self._sessions = {}
def _connect_proxy(self, sid, requester, target, proxy, proxy_port, peer=None):
""" Establishes a connection between the client and the server-side
Socks5 proxy.
sid : The StreamID. <str>
requester : The JID of the requester. <str>
target : The JID of the target. <str>
proxy_host : The hostname or the IP of the proxy. <str>
proxy_port : The port of the proxy. <str> or <int>
peer : The JID for the other side of the stream, regardless
of target or requester status.
"""
# Because the xep_0065 plugin uses the proxy_port as string,
# the Proxy class accepts the proxy_port argument as a string
# or an integer. Here, we force to use the port as an integer.
proxy_port = int(proxy_port)
sock = socksocket()
sock.setproxy(PROXY_TYPE_SOCKS5, proxy, port=proxy_port)
# The hostname MUST be SHA1(SID + Requester JID + Target JID)
# where the output is hexadecimal-encoded (not binary).
digest = sha1()
digest.update(sid)
digest.update(str(requester))
digest.update(str(target))
dest = digest.hexdigest()
# The port MUST be 0.
sock.connect((dest, 0))
log.info('Socket connected.')
_close = sock.close
def close(*args, **kwargs):
with self._sessions_lock:
if sid in self._sessions:
del self._sessions[sid]
_close()
log.info('Socket closed.')
sock.close = close
sock.peer_jid = peer
sock.self_jid = target if requester == peer else requester
self.xmpp.event('socks_connected', sid)
return sock
def _accept_stream(self, iq):
receiver = iq['to']
sender = iq['from']
sid = iq['socks']['sid']
if self.api['authorized_sid'](receiver, sid, sender, iq):
return True
return self.api['authorized'](receiver, sid, sender, iq)
def _authorized(self, jid, sid, ifrom, iq):
return self.auto_accept
def _authorized_sid(self, jid, sid, ifrom, iq):
with self._preauthed_sids_lock:
log.debug('>>> authed sids: %s', self._preauthed_sids)
log.debug('>>> lookup: %s %s %s', jid, sid, ifrom)
if (jid, sid, ifrom) in self._preauthed_sids:
del self._preauthed_sids[(jid, sid, ifrom)]
return True
return False
def _preauthorize_sid(self, jid, sid, ifrom, data):
log.debug('>>>> %s %s %s %s', jid, sid, ifrom, data)
with self._preauthed_sids_lock:
self._preauthed_sids[(jid, sid, ifrom)] = True

View File

@@ -0,0 +1,47 @@
from sleekxmpp.jid import JID
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class Socks5(ElementBase):
name = 'query'
namespace = 'http://jabber.org/protocol/bytestreams'
plugin_attrib = 'socks'
interfaces = set(['sid', 'activate'])
sub_interfaces = set(['activate'])
def add_streamhost(self, jid, host, port):
sh = StreamHost(parent=self)
sh['jid'] = jid
sh['host'] = host
sh['port'] = port
class StreamHost(ElementBase):
name = 'streamhost'
namespace = 'http://jabber.org/protocol/bytestreams'
plugin_attrib = 'streamhost'
plugin_multi_attrib = 'streamhosts'
interfaces = set(['host', 'jid', 'port'])
def set_jid(self, value):
return self._set_attr('jid', str(value))
def get_jid(self):
return JID(self._get_attr('jid'))
class StreamHostUsed(ElementBase):
name = 'streamhost-used'
namespace = 'http://jabber.org/protocol/bytestreams'
plugin_attrib = 'streamhost_used'
interfaces = set(['jid'])
def set_jid(self, value):
return self._set_attr('jid', str(value))
def get_jid(self):
return JID(self._get_attr('jid'))
register_stanza_plugin(Socks5, StreamHost, iterable=True)
register_stanza_plugin(Socks5, StreamHostUsed)

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_0071.stanza import XHTML_IM
from sleekxmpp.plugins.xep_0071.xhtml_im import XEP_0071
register_plugin(XEP_0071)

View File

@@ -0,0 +1,81 @@
"""
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.stanza import Message
from sleekxmpp.util import unicode
from sleekxmpp.thirdparty import OrderedDict
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin, tostring
XHTML_NS = 'http://www.w3.org/1999/xhtml'
class XHTML_IM(ElementBase):
namespace = 'http://jabber.org/protocol/xhtml-im'
name = 'html'
interfaces = set(['body'])
lang_interfaces = set(['body'])
plugin_attrib = name
def set_body(self, content, lang=None):
if lang is None:
lang = self.get_lang()
self.del_body(lang)
if lang == '*':
for sublang, subcontent in content.items():
self.set_body(subcontent, sublang)
else:
if isinstance(content, type(ET.Element('test'))):
content = unicode(ET.tostring(content))
else:
content = unicode(content)
header = '<body xmlns="%s"' % XHTML_NS
if lang:
header = '%s xml:lang="%s"' % (header, lang)
content = '%s>%s</body>' % (header, content)
xhtml = ET.fromstring(content)
self.xml.append(xhtml)
def get_body(self, lang=None):
"""Return the contents of the HTML body."""
if lang is None:
lang = self.get_lang()
bodies = self.xml.findall('{%s}body' % XHTML_NS)
if lang == '*':
result = OrderedDict()
for body in bodies:
body_lang = body.attrib.get('{%s}lang' % self.xml_ns, '')
body_result = []
body_result.append(body.text if body.text else '')
for child in body:
body_result.append(tostring(child, xmlns=XHTML_NS))
body_result.append(body.tail if body.tail else '')
result[body_lang] = ''.join(body_result)
return result
else:
for body in bodies:
if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
result = []
result.append(body.text if body.text else '')
for child in body:
result.append(tostring(child, xmlns=XHTML_NS))
result.append(body.tail if body.tail else '')
return ''.join(result)
return ''
def del_body(self, lang=None):
if lang is None:
lang = self.get_lang()
bodies = self.xml.findall('{%s}body' % XHTML_NS)
for body in bodies:
if body.attrib.get('{%s}lang' % self.xml_ns, self.get_lang()) == lang:
self.xml.remove(body)
return

View File

@@ -0,0 +1,30 @@
"""
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.stanza import Message
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.xep_0071 import stanza, XHTML_IM
class XEP_0071(BasePlugin):
name = 'xep_0071'
description = 'XEP-0071: XHTML-IM'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, XHTML_IM)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(feature=XHTML_IM.namespace)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=XHTML_IM.namespace)

View File

@@ -7,6 +7,7 @@
"""
import logging
import ssl
from sleekxmpp.stanza import StreamFeatures, Iq
from sleekxmpp.xmlstream import register_stanza_plugin, JID
@@ -29,6 +30,7 @@ class XEP_0077(BasePlugin):
stanza = stanza
default_config = {
'create_account': True,
'force_registration': False,
'order': 50
}
@@ -45,10 +47,29 @@ class XEP_0077(BasePlugin):
register_stanza_plugin(Register, self.xmpp['xep_0004'].stanza.Form)
register_stanza_plugin(Register, self.xmpp['xep_0066'].stanza.OOB)
self.xmpp.add_event_handler('connected', self._force_registration)
def plugin_end(self):
if not self.xmpp.is_component:
self.xmpp.unregister_feature('register', self.order)
def _force_registration(self, event):
if self.force_registration:
self.xmpp.add_filter('in', self._force_stream_feature)
def _force_stream_feature(self, stanza):
if isinstance(stanza, StreamFeatures):
if self.xmpp.use_tls or self.xmpp.use_ssl:
if 'starttls' not in self.xmpp.features:
return stanza
elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
return stanza
if 'mechanisms' not in self.xmpp.features:
log.debug('Forced adding in-band registration stream feature')
stanza.enable('register')
self.xmpp.del_filter('in', self._force_stream_feature)
return stanza
def _handle_register_feature(self, features):
if 'mechanisms' in self.xmpp.features:
# We have already logged in with an account

View File

@@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
import uuid
import logging
import hashlib
import random
@@ -98,7 +99,7 @@ class XEP_0078(BasePlugin):
# A resource is required, so create a random one if necessary
resource = self.xmpp.requested_jid.resource
if not resource:
resource = uuid.uuid4()
resource = str(uuid.uuid4())
iq['auth']['resource'] = resource

View File

@@ -0,0 +1,18 @@
"""
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_0079.stanza import (
AMP, Rule, InvalidRules, UnsupportedConditions,
UnsupportedActions, FailedRules, FailedRule,
AMPFeature)
from sleekxmpp.plugins.xep_0079.amp import XEP_0079
register_plugin(XEP_0079)

View File

@@ -0,0 +1,79 @@
"""
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 permissio
"""
import logging
from sleekxmpp.stanza import Message, Error, StreamFeatures
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.xmlstream.matcher import StanzaPath, MatchMany
from sleekxmpp.xmlstream.handler import Callback
from sleekxmpp.plugins import BasePlugin
from sleekxmpp.plugins.xep_0079 import stanza
log = logging.getLogger(__name__)
class XEP_0079(BasePlugin):
"""
XEP-0079 Advanced Message Processing
"""
name = 'xep_0079'
description = 'XEP-0079: Advanced Message Processing'
dependencies = set(['xep_0030'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.AMP)
register_stanza_plugin(Error, stanza.InvalidRules)
register_stanza_plugin(Error, stanza.UnsupportedConditions)
register_stanza_plugin(Error, stanza.UnsupportedActions)
register_stanza_plugin(Error, stanza.FailedRules)
self.xmpp.register_handler(
Callback('AMP Response',
MatchMany([
StanzaPath('message/error/failed_rules'),
StanzaPath('message/amp')
]),
self._handle_amp_response))
if not self.xmpp.is_component:
self.xmpp.register_feature('amp',
self._handle_amp_feature,
restart=False,
order=9000)
register_stanza_plugin(StreamFeatures, stanza.AMPFeature)
def plugin_end(self):
self.xmpp.remove_handler('AMP Response')
def _handle_amp_response(self, msg):
log.debug('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
if msg['type'] == 'error':
self.xmpp.event('amp_error', msg)
elif msg['amp']['status'] in ('alert', 'notify'):
self.xmpp.event('amp_%s' % msg['amp']['status'], msg)
def _handle_amp_feature(self, features):
log.debug('Advanced Message Processing is available.')
self.xmpp.features.add('amp')
def discover_support(self, jid=None, **iqargs):
if jid is None:
if self.xmpp.is_component:
jid = self.xmpp.server_host
else:
jid = self.xmpp.boundjid.host
return self.xmpp['xep_0030'].get_info(
jid=jid,
node='http://jabber.org/protocol/amp',
**iqargs)

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.
"""
from __future__ import unicode_literals
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
class AMP(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'amp'
plugin_attrib = 'amp'
interfaces = set(['from', 'to', 'status', 'per_hop'])
def get_from(self):
return JID(self._get_attr('from'))
def set_from(self, value):
return self._set_attr('from', str(value))
def get_to(self):
return JID(self._get_attr('from'))
def set_to(self, value):
return self._set_attr('to', str(value))
def get_per_hop(self):
return self._get_attr('per-hop') == 'true'
def set_per_hop(self, value):
if value:
return self._set_attr('per-hop', 'true')
else:
return self._del_attr('per-hop')
def del_per_hop(self):
return self._del_attr('per-hop')
def add_rule(self, action, condition, value):
rule = Rule(parent=self)
rule['action'] = action
rule['condition'] = condition
rule['value'] = value
class Rule(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'rule'
plugin_attrib = name
plugin_multi_attrib = 'rules'
interfaces = set(['action', 'condition', 'value'])
class InvalidRules(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'invalid-rules'
plugin_attrib = 'invalid_rules'
class UnsupportedConditions(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'unsupported-conditions'
plugin_attrib = 'unsupported_conditions'
class UnsupportedActions(ElementBase):
namespace = 'http://jabber.org/protocol/amp'
name = 'unsupported-actions'
plugin_attrib = 'unsupported_actions'
class FailedRule(Rule):
namespace = 'http://jabber.org/protocol/amp#errors'
class FailedRules(ElementBase):
namespace = 'http://jabber.org/protocol/amp#errors'
name = 'failed-rules'
plugin_attrib = 'failed_rules'
class AMPFeature(ElementBase):
namespace = 'http://jabber.org/features/amp'
name = 'amp'
register_stanza_plugin(AMP, Rule, iterable=True)
register_stanza_plugin(InvalidRules, Rule, iterable=True)
register_stanza_plugin(UnsupportedConditions, Rule, iterable=True)
register_stanza_plugin(UnsupportedActions, Rule, iterable=True)
register_stanza_plugin(FailedRules, FailedRule, iterable=True)

View File

@@ -6,7 +6,6 @@
See the file LICENSE for copying permission.
"""
import logging
import datetime as dt
from sleekxmpp.plugins import BasePlugin, register_plugin

View File

@@ -82,6 +82,7 @@ class XEP_0084(BasePlugin):
metadata.add_pointer(pointer)
return self.xmpp['xep_0163'].publish(metadata,
id=info['id'],
ifrom=ifrom,
block=block,
callback=callback,

View File

@@ -21,14 +21,15 @@ class LegacyDelay(ElementBase):
interfaces = set(('from', 'stamp', 'text'))
def get_from(self):
return JID(self._get_attr('from'))
from_ = self._get_attr('from')
return JID(from_) if from_ else None
def set_from(self, value):
self._set_attr('from', str(value))
def get_stamp(self):
timestamp = self._get_attr('stamp')
return xep_0082.parse('%sZ' % timestamp)
return xep_0082.parse('%sZ' % timestamp) if timestamp else None
def set_stamp(self, value):
if isinstance(value, dt.datetime):

View File

@@ -70,7 +70,7 @@ class XEP_0092(BasePlugin):
iq['software_version']['os'] = self.os
iq.send()
def get_version(self, jid, ifrom=None):
def get_version(self, jid, ifrom=None, block=True, timeout=None, callback=None):
"""
Retrieve the software version of a remote agent.
@@ -82,14 +82,4 @@ class XEP_0092(BasePlugin):
iq['from'] = ifrom
iq['type'] = 'get'
iq['query'] = Version.namespace
result = iq.send()
if result and result['type'] != 'error':
values = result['software_version'].values
del values['lang']
return values
return False
XEP_0092.getVersion = XEP_0092.get_version
return iq.send(block=block, timeout=timeout, callback=callback)

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_0095 import stanza
from sleekxmpp.plugins.xep_0095.stanza import SI
from sleekxmpp.plugins.xep_0095.stream_initiation import XEP_0095
register_plugin(XEP_0095)

View File

@@ -0,0 +1,25 @@
"""
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 SI(ElementBase):
name = 'si'
namespace = 'http://jabber.org/protocol/si'
plugin_attrib = 'si'
interfaces = set(['id', 'mime_type', 'profile'])
def get_mime_type(self):
return self._get_attr('mime-type', 'application/octet-stream')
def set_mime_type(self, value):
self._set_attr('mime-type', value)
def del_mime_type(self):
self._del_attr('mime-type')

View File

@@ -0,0 +1,214 @@
"""
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
import threading
from uuid import uuid4
from sleekxmpp import Iq, Message
from sleekxmpp.exceptions import XMPPError
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_0095 import stanza, SI
log = logging.getLogger(__name__)
SOCKS5 = 'http://jabber.org/protocol/bytestreams'
IBB = 'http://jabber.org/protocol/ibb'
class XEP_0095(BasePlugin):
name = 'xep_0095'
description = 'XEP-0095: Stream Initiation'
dependencies = set(['xep_0020', 'xep_0030', 'xep_0047', 'xep_0065'])
stanza = stanza
def plugin_init(self):
self._profiles = {}
self._methods = {}
self._methods_order = []
self._pending_lock = threading.Lock()
self._pending= {}
self.register_method(SOCKS5, 'xep_0065', 100)
self.register_method(IBB, 'xep_0047', 50)
register_stanza_plugin(Iq, SI)
register_stanza_plugin(SI, self.xmpp['xep_0020'].stanza.FeatureNegotiation)
self.xmpp.register_handler(
Callback('SI Request',
StanzaPath('iq@type=set/si'),
self._handle_request))
self.api.register(self._add_pending, 'add_pending', default=True)
self.api.register(self._get_pending, 'get_pending', default=True)
self.api.register(self._del_pending, 'del_pending', default=True)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(SI.namespace)
def plugin_end(self):
self.xmpp.remove_handler('SI Request')
self.xmpp['xep_0030'].del_feature(feature=SI.namespace)
def register_profile(self, profile_name, plugin):
self._profiles[profile_name] = plugin
def unregister_profile(self, profile_name):
try:
del self._profiles[profile_name]
except KeyError:
pass
def register_method(self, method, plugin_name, order=50):
self._methods[method] = (plugin_name, order)
self._methods_order.append((order, method, plugin_name))
self._methods_order.sort()
def unregister_method(self, method):
if method in self._methods:
plugin_name, order = self._methods[method]
del self._methods[method]
self._methods_order.remove((order, method, plugin_name))
self._methods_order.sort()
def _handle_request(self, iq):
profile = iq['si']['profile']
sid = iq['si']['id']
if not sid:
raise XMPPError(etype='modify', condition='bad-request')
if profile not in self._profiles:
raise XMPPError(
etype='modify',
condition='bad-request',
extension='bad-profile',
extension_ns=SI.namespace)
neg = iq['si']['feature_neg']['form']['fields']
options = neg['stream-method']['options'] or []
methods = []
for opt in options:
methods.append(opt['value'])
for method in methods:
if method in self._methods:
supported = True
break
else:
raise XMPPError('bad-request',
extension='no-valid-streams',
extension_ns=SI.namespace)
selected_method = None
log.debug('Available: %s', methods)
for order, method, plugin in self._methods_order:
log.debug('Testing: %s', method)
if method in methods:
selected_method = method
break
receiver = iq['to']
sender = iq['from']
self.api['add_pending'](receiver, sid, sender, {
'response_id': iq['id'],
'method': selected_method,
'profile': profile
})
self.xmpp.event('si_request', iq)
def offer(self, jid, sid=None, mime_type=None, profile=None,
methods=None, payload=None, ifrom=None,
**iqargs):
if sid is None:
sid = uuid4().hex
if methods is None:
methods = list(self._methods.keys())
if not isinstance(methods, (list, tuple, set)):
methods = [methods]
si = self.xmpp.Iq()
si['to'] = jid
si['from'] = ifrom
si['type'] = 'set'
si['si']['id'] = sid
si['si']['mime_type'] = mime_type
si['si']['profile'] = profile
if not isinstance(payload, (list, tuple, set)):
payload = [payload]
for item in payload:
si['si'].append(item)
si['si']['feature_neg']['form'].add_field(
var='stream-method',
ftype='list-single',
options=methods)
return si.send(**iqargs)
def accept(self, jid, sid, payload=None, ifrom=None, stream_handler=None):
stream = self.api['get_pending'](ifrom, sid, jid)
iq = self.xmpp.Iq()
iq['id'] = stream['response_id']
iq['to'] = jid
iq['from'] = ifrom
iq['type'] = 'result'
if payload:
iq['si'].append(payload)
iq['si']['feature_neg']['form']['type'] = 'submit'
iq['si']['feature_neg']['form'].add_field(
var='stream-method',
ftype='list-single',
value=stream['method'])
if ifrom is None:
ifrom = self.xmpp.boundjid
method_plugin = self._methods[stream['method']][0]
self.xmpp[method_plugin].api['preauthorize_sid'](ifrom, sid, jid)
self.api['del_pending'](ifrom, sid, jid)
if stream_handler:
self.xmpp.add_event_handler('stream:%s:%s' % (sid, jid),
stream_handler,
threaded=True,
disposable=True)
return iq.send()
def decline(self, jid, sid, ifrom=None):
stream = self.api['get_pending'](ifrom, sid, jid)
if not stream:
return
iq = self.xmpp.Iq()
iq['id'] = stream['response_id']
iq['to'] = jid
iq['from'] = ifrom
iq['type'] = 'error'
iq['error']['condition'] = 'forbidden'
iq['error']['text'] = 'Offer declined'
self.api['del_pending'](ifrom, sid, jid)
return iq.send()
def _add_pending(self, jid, node, ifrom, data):
with self._pending_lock:
self._pending[(jid, node, ifrom)] = data
def _get_pending(self, jid, node, ifrom, data):
with self._pending_lock:
return self._pending.get((jid, node, ifrom), None)
def _del_pending(self, jid, node, ifrom, data):
with self._pending_lock:
if (jid, node, ifrom) in self._pending:
del self._pending[(jid, node, ifrom)]

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_0096 import stanza
from sleekxmpp.plugins.xep_0096.stanza import File
from sleekxmpp.plugins.xep_0096.file_transfer import XEP_0096
register_plugin(XEP_0096)

View File

@@ -0,0 +1,58 @@
"""
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_0096 import stanza, File
log = logging.getLogger(__name__)
class XEP_0096(BasePlugin):
name = 'xep_0096'
description = 'XEP-0096: SI File Transfer'
dependencies = set(['xep_0095'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(self.xmpp['xep_0095'].stanza.SI, File)
self.xmpp['xep_0095'].register_profile(File.namespace, self)
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(File.namespace)
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=File.namespace)
self.xmpp['xep_0095'].unregister_profile(File.namespace, self)
def request_file_transfer(self, jid, sid=None, name=None, size=None,
desc=None, hash=None, date=None,
allow_ranged=False, mime_type=None,
**iqargs):
data = File()
data['name'] = name
data['size'] = size
data['date'] = date
data['desc'] = desc
if allow_ranged:
data.enable('range')
return self.xmpp['xep_0095'].offer(jid,
sid=sid,
mime_type=mime_type,
profile=File.namespace,
payload=data,
**iqargs)

View File

@@ -0,0 +1,48 @@
"""
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 datetime as dt
from sleekxmpp.xmlstream import ElementBase, register_stanza_plugin
from sleekxmpp.plugins import xep_0082
class File(ElementBase):
name = 'file'
namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
plugin_attrib = 'file'
interfaces = set(['name', 'size', 'date', 'hash', 'desc'])
sub_interfaces = set(['desc'])
def set_size(self, value):
self._set_attr('size', str(value))
def get_date(self):
timestamp = self._get_attr('date')
return xep_0082.parse(timestamp)
def set_date(self, value):
if isinstance(value, dt.datetime):
value = xep_0082.format_datetime(value)
self._set_attr('date', value)
class Range(ElementBase):
name = 'range'
namespace = 'http://jabber.org/protocol/si/profile/file-transfer'
plugin_attrib = 'range'
interfaces = set(['length', 'offset'])
def set_length(self, value):
self._set_attr('length', str(value))
def set_offset(self, value):
self._set_attr('offset', str(value))
register_stanza_plugin(File, Range)

View File

@@ -9,8 +9,9 @@
import logging
import hashlib
import base64
import threading
import sleekxmpp
from sleekxmpp import __version__
from sleekxmpp.stanza import StreamFeatures, Presence, Iq
from sleekxmpp.xmlstream import register_stanza_plugin, JID
from sleekxmpp.xmlstream.handler import Callback
@@ -45,8 +46,7 @@ class XEP_0115(BasePlugin):
'md5': hashlib.md5}
if self.caps_node is None:
ver = sleekxmpp.__version__
self.caps_node = 'http://sleekxmpp.com/ver/%s' % ver
self.caps_node = 'http://sleekxmpp.com/ver/%s' % __version__
register_stanza_plugin(Presence, stanza.Capabilities)
register_stanza_plugin(StreamFeatures, stanza.Capabilities)
@@ -90,6 +90,9 @@ class XEP_0115(BasePlugin):
disco.assign_verstring = self.assign_verstring
disco.get_verstring = self.get_verstring
self._processing_lock = threading.Lock()
self._processing = set()
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.Capabilities.namespace)
self.xmpp.del_filter('out', self._filter_add_caps)
@@ -135,17 +138,22 @@ class XEP_0115(BasePlugin):
def _process_caps(self, pres):
if not pres['caps']['hash']:
log.debug("Received unsupported legacy caps.")
log.debug("Received unsupported legacy caps: %s, %s, %s",
pres['caps']['node'],
pres['caps']['ver'],
pres['caps']['ext'])
self.xmpp.event('entity_caps_legacy', pres)
return
ver = pres['caps']['ver']
existing_verstring = self.get_verstring(pres['from'].full)
if str(existing_verstring) == str(pres['caps']['ver']):
if str(existing_verstring) == str(ver):
return
existing_caps = self.get_caps(verstring=pres['caps']['ver'])
existing_caps = self.get_caps(verstring=ver)
if existing_caps is not None:
self.assign_verstring(pres['from'], pres['caps']['ver'])
self.assign_verstring(pres['from'], ver)
return
if pres['caps']['hash'] not in self.hashes:
@@ -156,9 +164,16 @@ class XEP_0115(BasePlugin):
except XMPPError:
return
log.debug("New caps verification string: %s", pres['caps']['ver'])
# Only lookup the same caps once at a time.
with self._processing_lock:
if ver in self._processing:
log.debug('Already processing verstring %s' % ver)
return
self._processing.add(ver)
log.debug("New caps verification string: %s", ver)
try:
node = '%s#%s' % (pres['caps']['node'], pres['caps']['ver'])
node = '%s#%s' % (pres['caps']['node'], ver)
caps = self.xmpp['xep_0030'].get_info(pres['from'], node)
if isinstance(caps, Iq):
@@ -168,7 +183,10 @@ class XEP_0115(BasePlugin):
pres['caps']['ver']):
self.assign_verstring(pres['from'], pres['caps']['ver'])
except XMPPError:
log.debug("Could not retrieve disco#info results for caps")
log.debug("Could not retrieve disco#info results for caps for %s", node)
with self._processing_lock:
self._processing.remove(ver)
def _validate_caps(self, caps, hash, check_verstring):
# Check Identities
@@ -179,7 +197,6 @@ class XEP_0115(BasePlugin):
return False
# Check Features
full_features = caps.get_features(dedupe=False)
deduped_features = caps.get_features()
if len(full_features) != len(deduped_features):
@@ -190,29 +207,32 @@ class XEP_0115(BasePlugin):
form_types = []
deduped_form_types = set()
for stanza in caps['substanzas']:
if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
if 'FORM_TYPE' in stanza['fields']:
f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
form_types.append(f_type)
deduped_form_types.add(f_type)
if len(form_types) != len(deduped_form_types):
log.debug("Duplicated FORM_TYPE values, " + \
"invalid for caps")
if not isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
log.debug("Non form extension found, ignoring for caps")
caps.xml.remove(stanza.xml)
continue
if 'FORM_TYPE' in stanza['fields']:
f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
form_types.append(f_type)
deduped_form_types.add(f_type)
if len(form_types) != len(deduped_form_types):
log.debug("Duplicated FORM_TYPE values, " + \
"invalid for caps")
return False
if len(f_type) > 1:
deduped_type = set(f_type)
if len(f_type) != len(deduped_type):
log.debug("Extra FORM_TYPE data, invalid for caps")
return False
if len(f_type) > 1:
deduped_type = set(f_type)
if len(f_type) != len(deduped_type):
log.debug("Extra FORM_TYPE data, invalid for caps")
return False
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
log.debug("Field FORM_TYPE type not 'hidden', " + \
"ignoring form for caps")
caps.xml.remove(stanza.xml)
else:
log.debug("No FORM_TYPE found, ignoring form for caps")
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
log.debug("Field FORM_TYPE type not 'hidden', " + \
"ignoring form for caps")
caps.xml.remove(stanza.xml)
else:
log.debug("No FORM_TYPE found, ignoring form for caps")
caps.xml.remove(stanza.xml)
verstring = self.generate_verstring(caps, hash)
if verstring != check_verstring:
@@ -272,7 +292,7 @@ class XEP_0115(BasePlugin):
binary = hash(S.encode('utf8')).digest()
return base64.b64encode(binary).decode('utf-8')
def update_caps(self, jid=None, node=None):
def update_caps(self, jid=None, node=None, preserve=False):
try:
info = self.xmpp['xep_0030'].get_info(jid, node, local=True)
if isinstance(info, Iq):
@@ -286,19 +306,11 @@ class XEP_0115(BasePlugin):
self.assign_verstring(jid, ver)
if self.xmpp.session_started_event.is_set() and self.broadcast:
# Check if we've sent directed presence. If we haven't, we
# can just send a normal presence stanza. If we have, then
# we will send presence to each contact individually so
# that we don't clobber existing statuses.
directed = False
for contact in self.xmpp.roster[jid]:
if self.xmpp.roster[jid][contact].last_status is not None:
directed = True
if not directed:
self.xmpp.roster[jid].send_last_presence()
else:
if self.xmpp.is_component or preserve:
for contact in self.xmpp.roster[jid]:
self.xmpp.roster[jid][contact].send_last_presence()
else:
self.xmpp.roster[jid].send_last_presence()
except XMPPError:
return

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_0152 import stanza
from sleekxmpp.plugins.xep_0152.stanza import Reachability
from sleekxmpp.plugins.xep_0152.reachability import XEP_0152
register_plugin(XEP_0152)

View File

@@ -0,0 +1,93 @@
"""
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.plugins.base import BasePlugin
from sleekxmpp.plugins.xep_0152 import stanza, Reachability
log = logging.getLogger(__name__)
class XEP_0152(BasePlugin):
"""
XEP-0152: Reachability Addresses
"""
name = 'xep_0152'
description = 'XEP-0152: Reachability Addresses'
dependencies = set(['xep_0163'])
stanza = stanza
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=Reachability.namespace)
self.xmpp['xep_0163'].remove_interest(Reachability.namespace)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('reachability', Reachability)
def publish_reachability(self, addresses, options=None,
ifrom=None, block=True, callback=None, timeout=None):
"""
Publish alternative addresses where the user can be reached.
Arguments:
addresses -- A list of dictionaries containing the URI and
optional description for each address.
options -- Optional form of publish options.
ifrom -- Specify the sender's JID.
block -- Specify if the send call will block until a response
is received, or a timeout occurs. Defaults to True.
timeout -- The length of time (in seconds) to wait for a response
before exiting the send call if blocking is used.
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
if not isinstance(addresses, (list, tuple)):
addresses = [addresses]
reach = Reachability()
for address in addresses:
if not hasattr(address, 'items'):
address = {'uri': address}
addr = stanza.Address()
for key, val in address.items():
addr[key] = val
reach.append(addr)
return self.xmpp['xep_0163'].publish(reach,
node=Reachability.namespace,
options=options,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)
def stop(self, ifrom=None, block=True, callback=None, timeout=None):
"""
Clear existing user activity information to stop notifications.
Arguments:
ifrom -- Specify the sender's JID.
block -- Specify if the send call will block until a response
is received, or a timeout occurs. Defaults to True.
timeout -- The length of time (in seconds) to wait for a response
before exiting the send call if blocking is used.
Defaults to sleekxmpp.xmlstream.RESPONSE_TIMEOUT
callback -- Optional reference to a stream handler function. Will
be executed when a reply stanza is received.
"""
reach = Reachability()
return self.xmpp['xep_0163'].publish(reach,
node=Reachability.namespace,
ifrom=ifrom,
block=block,
callback=callback,
timeout=timeout)

View File

@@ -0,0 +1,29 @@
"""
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 Reachability(ElementBase):
name = 'reach'
namespace = 'urn:xmpp:reach:0'
plugin_attrib = 'reach'
interfaces = set()
class Address(ElementBase):
name = 'addr'
namespace = 'urn:xmpp:reach:0'
plugin_attrib = 'address'
plugin_multi_attrib = 'addresses'
interfaces = set(['uri', 'desc'])
lang_interfaces = set(['desc'])
sub_interfaces = set(['desc'])
register_stanza_plugin(Reachability, Address, iterable=True)

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