Compare commits

...

212 Commits

Author SHA1 Message Date
Emmanuel Gil Peyrot
f4683546d9 Manual cleanup of the remaining set([…]) and set((…)). 2016-10-22 13:35:54 +01:00
Emmanuel Gil Peyrot
dcacc7d7d5 sed -i 's/set(\[\(.*\)\])$/{\1}/g' **/*.py 2016-10-22 13:21:44 +01:00
Emmanuel Gil Peyrot
c4285961df sed -i 's/set((\(.*\)))$/{\1}/g' **/*.py 2016-10-22 13:21:42 +01:00
Emmanuel Gil Peyrot
1038f656eb sed -i 's/set((\(.*\),))$/{\1}/g' **/*.py 2016-10-22 13:21:41 +01:00
Emmanuel Gil Peyrot
3c7236fe73 setup.py: Check for libidn before trying to use Cython. 2016-10-05 20:28:11 +01:00
mathieui
36824379c3 slixmpp 1.2.1
Fix a few bugs along with the testsuite, and remove the asyncio loop
monkeypatch hack.
2016-10-05 20:32:32 +02:00
mathieui
a0a37c19ff Remove monkeypatching hack on the event loop
This allowed us to schedule events in-order later in the event loop, but
was detrimental to using other event loops and debugging.
2016-10-05 20:19:07 +02:00
mathieui
1b5fe57a5e Fix XEP-0060 tests 2016-10-04 21:21:55 +02:00
mathieui
5da31db0c7 Fix stanza accessors case in tests
They were using deprecated (and-removed) style.
2016-10-04 21:15:01 +02:00
mathieui
f8cea760b6 Fix the gmail_notify plugin 2016-10-04 21:10:10 +02:00
mathieui
5ef01ecdd1 Fix XEP-0033
Re-add relevant stanza methods, broken in 7cd1cf32ae
2016-10-04 19:47:11 +02:00
mathieui
62aafe0ee7 Attrib property has been removed 2016-10-04 19:43:45 +02:00
mathieui
cf3f36ac52 Set unset part of a JID to empty string instead of None
it breaks assumptions on the type of the value
2016-10-04 19:42:05 +02:00
mathieui
b88d2ecd77 Add more checks in the XEP-0060 stanza building
Try to not append slixmpp stanzas to ElementTree objects.
2016-10-04 19:31:49 +02:00
mathieui
e691850a2b Fix XEP-0128
Broken since 125336aeee due to unforeseen consequences of a variable
removal.
2016-10-04 19:26:03 +02:00
mathieui
d4bff8dee6 Fix XEP-0009
Broken since 3a9b45e4f because of an overzealous cleanup.
2016-10-04 19:23:21 +02:00
mathieui
187c350805 Update for slixmpp 1.2 2016-10-02 17:36:14 +02:00
mathieui
96d1c26f90 Add a fallback if the lang we want is not available
Previously, trying to get a text node with a lang which is different
from the one we specified would return nothing, which means e.g. a
message would be ignored because its body is of lang 'fr' when we setup
slixmpp to prefer 'en'. We want to return something when there is an
available, valid content in a different language.
2016-10-02 17:12:47 +02:00
mathieui
46a90749f8 Fix uses of super() in the codebase
Fix #3165, we don’t need to use the long form to get the superobject in
our supported python versions.
2016-09-30 21:25:36 +02:00
mathieui
0c63a4bbda Fix #3226 (unicity of scheduled event names)
Thanks tchiroux for raising the issue and providing the fix as well.
2016-09-30 20:59:31 +02:00
mathieui
e4696e0471 Merge branch 'doc_fixes' of https://github.com/SamWhited/slixmpp 2016-09-30 20:53:36 +02:00
Sam Whited
8217dc5239 Minor documentation fixes 2016-09-30 13:49:04 -05:00
mathieui
2586abc0d3 Fix xep-0050 stanza
broken in 3a9b45e4f2
2016-09-20 20:51:21 +02:00
Emmanuel Gil Peyrot
28f84ab3d9 ElementBase: Remove support for TitleCase methods.
This gains about 1/8th of the time spent in __getitem__.
2016-09-21 01:31:53 +09:00
Emmanuel Gil Peyrot
813b45aded XEP-0045: Remove support for old-style {get,set,del}TitleCase methods. 2016-09-21 01:28:24 +09:00
Emmanuel Gil Peyrot
3a9b45e4f2 ElementBase: Remove deprecated find() and findall() methods. 2016-09-20 16:45:29 +09:00
Emmanuel Gil Peyrot
b8e091233e XEP-0004: Remove deprecated getXML() and fromXML() methods. 2016-09-20 16:34:48 +09:00
Emmanuel Gil Peyrot
0edeefd977 BaseXMPP: Stop automatically enabling UserNick, and remove deprecated alias module. 2016-09-20 16:23:02 +09:00
Emmanuel Gil Peyrot
6ba53cf1ff ElementBase: Remove attrib interface. 2016-09-20 16:23:02 +09:00
Emmanuel Gil Peyrot
d7758eb7f4 ElementBase: Remove subitem interface. 2016-09-20 16:23:02 +09:00
Emmanuel Gil Peyrot
125336aeee Remove locking from static disco. 2016-09-20 16:23:02 +09:00
Emmanuel Gil Peyrot
7cd1cf32ae Various XEPs: Remove deprecated aliases. 2016-09-20 16:23:02 +09:00
Emmanuel Gil Peyrot
d099e353a4 Implement XEP-0333: Chat Markers. 2016-08-26 22:42:24 +01:00
Emmanuel Gil Peyrot
1e4a301c6e Replace _format_jid with a JID method updating both bare and full at the same time. 2016-08-26 22:25:58 +01:00
mathieui
f53b12d227 Fix the MUC address in contributing.rst 2016-08-23 23:10:17 +02:00
Dan Sully
e2562dcccf Make session_bind_event awaitable 2016-08-23 23:05:22 +02:00
louiz’
7b69ae3738 Add a contributing file 2016-08-24 00:33:07 +02:00
Emmanuel Gil Peyrot
ab6df235d7 Pre-compute JID bare and full forms, and store that in each JID.
This wins about 4s over a 54s real-world benchmark.
2016-08-22 23:43:16 +01:00
mathieui
52cd8f4b22 Don’t trigger presence events on MUC presence
Specifically, previously, each MUC would be added as a roster item, and
then each join presence would be counted as a resource of that item,
triggering 1 to 5 events and more backend logic in slixmpp.

As a result, joining big rooms is tremendously slow, (JID() calls,
event() calls, __getitem__ calls for nothing), and takes RAM (a quick
tracemalloc tells me around 1 MiB for 3500 participants, i.e. 2 big IRC
rooms). Those resources may not necessarily be cleaned properly, leading
to memory leaks on long-term usage.

This is a micro-optimization that adds an attribute to roster items so
that MUC room events can be ignored safely while not affecting common
roster usage.
2016-08-22 01:29:07 +02:00
Emmanuel Gil Peyrot
e28318c271 Micro-optimise _format_jid. 2016-08-21 20:26:51 +01:00
mathieui
39ee833c29 Improve XEP-0070 and examples 2016-08-19 23:48:37 +02:00
Emmanuel Gil Peyrot
9019e2bc71 Initial work on XEP_0070, plugin and examples 2016-08-19 23:48:29 +02:00
louiz’
9208bf5bf1 Merge remote-tracking branch 'zejn/master' 2016-08-19 11:18:27 +02:00
Emmanuel Gil Peyrot
f0f1698e46 ElementBase: micro-optimise __getitem__, hands down the most often called function
This makes it go down from 8.767s to 7.960s in a random benchmark.

Remove unnecessary assignations, don’t create an OrderedDict from a
dict to then convert it to a dict again, only obtain the get_method2
name if get_method wasn’t present.

get_method2 (the title-case one) takes about 1/8th of the total time
spent in this function, we should eliminate it as soon as possible.
2016-08-17 00:46:56 +01:00
Gasper Zejn
eccd7f1c98 Provide domain name to loop.create_connection if using SSL. 2016-08-12 15:32:42 +02:00
Emmanuel Gil Peyrot
2587d82af8 Make util.XOR about ten times faster by calling bytes only once. 2016-07-30 00:14:54 +01:00
mathieui
7ea121b115 Don’t swallow presence exceptions abritrarily 2016-06-28 20:58:47 +02:00
mathieui
bb81fbbdfc Implement XEP-0256 (last activity in presence)
mostly useless, but allows to use LastActivity stanzas inside Presence
stanzas as well.
2016-06-05 02:04:52 +02:00
mathieui
1a00a08b7d Make XEP-0186 return futures as well
Improving the api if the developer wants to wait on them.
2016-06-05 00:19:24 +02:00
mathieui
90ea2a3411 Implement XEP-0352 (client state indication) 2016-06-04 22:59:23 +02:00
mathieui
8fc6814b6d Update XEP-0198 for asyncio 2016-06-04 20:51:59 +02:00
mathieui
ffced0ed9a Add a xep-0334 plugin 2016-06-04 19:34:12 +02:00
mathieui
e7248d9af9 Fix the Waiter handler for asyncio 2016-05-28 20:53:41 +02:00
mathieui
6b1a04f59d Fix xep-0199
The keepalive ping was not working, and and ping() was tracebacking due
to a wrong parameter.
2016-05-28 15:13:33 +02:00
mathieui
4905407092 Fix the ordering of stream features
since iq.send is non-blocking, some features handlers could end up
being executed before others were set, leading to issues. Adding yield
from where it’s necessary fixes that.
2016-05-28 14:46:39 +02:00
louiz’
bd6ec10939 Add some credits 2016-03-15 09:35:36 +01:00
Sam Whited
e15e6735f1 The XEP-0198 plugin exists now; fix the docs 2016-03-14 23:59:01 +01:00
mathieui
67afd6a462 Fix #3166 (broken link) 2016-02-03 22:43:47 +01:00
mathieui
2e2b97c53b Merge branch 'xep_0012_fix' of https://github.com/misuzu/slixmpp 2016-01-21 23:22:28 +01:00
Tsukasa Hiiragi
a35df7fe1f Fixed NameError in start_uptime 2016-01-21 14:59:03 +02:00
Krzysztof Kotlenga
fbc8562779 Remove dead code
See 5c769632e8.
2015-12-15 19:44:29 +01:00
mathieui
b549db959a Update version to 1.1 2015-10-02 19:35:29 +02:00
mathieui
d5188ac68a Mention the build of cython modules in the README 2015-10-02 19:22:26 +02:00
mathieui
ada9444bf8 Merge branch 'sleek-merge' 2015-10-02 19:07:45 +02:00
mathieui
acc52fd935 Merge branch 'develop' of https://github.com/fritzy/SleekXMPP into sleek-merge
Conflicts:
	README.rst
	examples/IoT_TestDevice.py
	examples/disco_browser.py
	setup.py
	sleekxmpp/jid.py
	sleekxmpp/plugins/google/auth/stanza.py
	sleekxmpp/plugins/google/gmail/notifications.py
	sleekxmpp/plugins/google/nosave/stanza.py
	sleekxmpp/plugins/google/settings/settings.py
	sleekxmpp/thirdparty/__init__.py
	sleekxmpp/thirdparty/socks.py
	sleekxmpp/thirdparty/statemachine.py
	sleekxmpp/util/__init__.py
	sleekxmpp/xmlstream/xmlstream.py
	slixmpp/basexmpp.py
	slixmpp/plugins/xep_0004/stanza/form.py
	slixmpp/plugins/xep_0009/rpc.py
	slixmpp/plugins/xep_0050/adhoc.py
	slixmpp/plugins/xep_0065/proxy.py
	slixmpp/plugins/xep_0084/stanza.py
	slixmpp/plugins/xep_0202/time.py
	slixmpp/plugins/xep_0323/sensordata.py
	slixmpp/plugins/xep_0325/control.py
	slixmpp/plugins/xep_0325/stanza/control.py
	slixmpp/roster/single.py
	slixmpp/stanza/atom.py
	slixmpp/stanza/rootstanza.py
	slixmpp/test/slixtest.py
	slixmpp/util/sasl/mechanisms.py
	slixmpp/version.py
	slixmpp/xmlstream/stanzabase.py
	tests/test_stanza_xep_0323.py
	tests/test_stanza_xep_0325.py
	tests/test_stream_xep_0323.py
	tests/test_stream_xep_0325.py
2015-10-02 19:00:19 +02:00
mathieui
1100ff1feb Reset the DNS answers after a connection is made succesfully 2015-09-25 19:34:04 +02:00
mathieui
c17fc3a869 Fix IPv6 resolving with aiodns 1.0 2015-09-24 19:38:53 +02:00
mathieui
4dba697075 Fix support for python 3.4 <= 3.4.2
asyncio module is provisional, which means it gets updated everytime
2015-09-23 23:23:02 +02:00
mathieui
e42d651d7e Fix connecting to a custom host/port 2015-09-19 15:27:12 +02:00
Mike Taylor
4305eddb4f Merge pull request #397 from rerobins/xep_0050_updates
Xep 0050 updates
2015-09-18 16:18:41 -04:00
Robert Robinson
c2dc44cfd1 Merge branch 'develop' into xep_0050_updates
# Conflicts:
#	tests/test_stream_xep_0050.py
2015-09-18 13:35:28 -06:00
Robert Robinson
5fc14de32e Merge pull request #3 from fritzy/develop
Merge to fritzy_master
2015-09-18 13:30:30 -06:00
Mike Taylor
d245558fd5 Merge pull request #396 from rerobins/add_xep_0122
XEP_0122: Add support for form validation
2015-09-18 15:15:27 -04:00
Mike Taylor
9d45370e8a Merge pull request #393 from aalba6675/fix/time
Only send time if Iq type is get.
2015-09-18 15:15:01 -04:00
Mike Taylor
cc1cc61d36 Merge pull request #392 from aalba6675/fix/tel_number
Do not overwrite telephone numbers
2015-09-18 15:14:35 -04:00
Mike Taylor
c6740a4908 Merge pull request #389 from alexdraga/develop
Add get users by affiliation.
2015-09-18 15:13:54 -04:00
Mike Taylor
55114bcffe Merge pull request #384 from elya5/patch-1
Fix UnboundlocalError in disco_browser.py example
2015-09-18 15:13:30 -04:00
Mike Taylor
4fa5dedc47 Merge pull request #386 from jdowner/develop-iot
iot: only add the 'done' field when all devices are done
2015-09-18 15:13:07 -04:00
Mike Taylor
5525ef2285 Merge pull request #395 from rerobins/refactor_forms
XEP_0004: Data Forms use register_stanza_plugin
2015-09-18 15:11:47 -04:00
Robert Robinson
a7ac969215 register_Stanza_plugin shouldn't be iterable
Should not use iterable for registering the stanza plugins.
2015-09-17 16:21:54 -06:00
Robert Robinson
329cb5a9f8 Add 0122 to plugin/__init__.py __all__ 2015-09-17 16:21:13 -06:00
Robert Robinson
d9b47b33f5 Update __init__.py
changed xep_0121 to xep_0122
2015-09-15 10:20:37 -06:00
Robert Robinson
3582ac9941 Merge branch 'add_xep_0122' of https://github.com/rerobins/SleekXMPP into add_xep_0122 2015-09-15 10:12:50 -06:00
Robert Robinson
2a127a57a7 Add test case Reported->Data Form Validation
Add a test case that will verify that reported fields can contain data form validation data.
2015-09-15 10:09:06 -06:00
Robert Robinson
7059400020 Merge branch 'refactor_forms' into add_xep_0122 2015-09-15 10:07:34 -06:00
Robert Robinson
0b14ef82d4 Add test case for reported and items
Previous stanza test cases didn't have test cases for reported and item field types in forms.   This fixes that issue.

Modified stanzabase to use an ordered dict so that can guarentee the that 'items' in a form are added after reported.  Also updated the set of interfaces that are stored in Form to be a ordered set.  Used the order set implementation from:  https://code.activestate.com/recipes/576694/

The OrderedSet implementation is licensed under the MIT license and is developed by the same developer of the ordereddict.
2015-09-15 10:05:53 -06:00
Robert Robinson
83953af53d Missing xep_122 dir in setup.py 2015-09-14 20:28:55 -06:00
Robert Robinson
110cf25c6d Add plugin support 2015-09-14 17:06:07 -06:00
Robert Robinson
f2bf6072ec Add plugin
(cherry picked from commit 2296d56)
2015-09-14 17:04:43 -06:00
Robert Robinson
5f9abe2e0e Working through test case issues.
(cherry picked from commit 6b58cef)
2015-09-14 17:04:16 -06:00
Robert Robinson
ea65b672e7 Initial cut at getting the stanzas to work.
(cherry picked from commit 8c7df49)
2015-09-14 17:04:08 -06:00
Robert Robinson
93c705fb31 Fix xep_0050 changes after form refactor. 2015-09-14 17:00:53 -06:00
Robert Robinson
0724f623bb Force forms and fields to use plugin resolution
Instead of using the interface/subinterface code that was currently being implemented for the plugin.
(cherry picked from commit 1467ec7)
2015-09-14 16:46:36 -06:00
mathieui
82e549c0e9 (Temporary) fix for python 3.5
This will work until the old ssl implementation is finally deprecated.
Hopefully, new features to painlessy implement starttls will be around
by then.
2015-09-14 23:14:53 +02:00
mathieui
1aa15792b4 Bump the requirements to aiodns 1.0
(and use install_requires instead of requires in the setup.py)
2015-09-14 23:14:06 +02:00
Robert Robinson
ffb2b6bc04 Update test_stream_xep_0050.py
Fix Unit Test for adhoc 50 stream.
2015-09-12 20:08:21 -06:00
Emmanuel Gil Peyrot
27f98bf22c xep_0231: Fix a traceback on result serialization. 2015-09-05 18:35:59 +01:00
mathieui
3978078710 vcard-temp: add some checks against wrong input 2015-09-04 01:59:40 +02:00
mathieui
00a0698720 Add timeout_callback to a bunch of plugins as a parameter 2015-09-04 01:05:56 +02:00
Robert Robinson
4a24f58be2 XEP0050: Add support for payload in completed response
When sending the command to complete the task, the adhoc plugin does not provide the ability to send a payload from the _handle_command_complete method.
2015-09-03 10:15:41 -06:00
Mike Taylor
da14ce16ec Merge pull request #394 from sangeeths/misc_updates
adding 'id' to self['xep_0332'].send_request()
2015-08-27 13:00:34 -04:00
Sangeeth Saravanaraj
18e5abb9dd adding 'id' to self['xep_0332'].send_request() 2015-08-27 13:24:01 +05:30
Richard Chan
1a75b76916 Only send time if Iq type is get. 2015-08-25 18:21:58 +08:00
Richard Chan
53b56899a0 Do not overwrite telephone numbers; otherwise all TEL/NUMBER received
from a server will be blank.
2015-08-25 18:11:54 +08:00
mathieui
804b23d390 Merge branch 'socks5' of http://git.linkmauve.fr/slixmpp 2015-08-23 17:14:53 +02:00
Emmanuel Gil Peyrot
04eaf52b1d xep_0065: Remove an unused variable. 2015-08-23 16:06:01 +01:00
Emmanuel Gil Peyrot
dc7fef1064 xep_0065: Remove the last useless threading locks. 2015-08-23 16:06:01 +01:00
Emmanuel Gil Peyrot
488c433555 Add SOCKS5 Bytestream examples. 2015-08-23 16:06:01 +01:00
Emmanuel Gil Peyrot
9c5dd024b1 Fix the xep_0065 plugin, by rewriting its socks5 implementation. 2015-08-23 16:06:01 +01:00
Florent Le Coz
6e61adf3db Fix the order in which <identity/> and <feature/> tags are sent on disco#info
The identities should all be at the start, and features at the end, so we
just prepend the identity on add_identity, and append features on
add_feature
2015-08-22 18:48:29 +02:00
Emmanuel Gil Peyrot
041bd63864 Add a function to convert a domain name to punycode. 2015-08-20 20:04:58 +01:00
Aleksandr Draga
a366482551 Add get users by affiliation. 2015-08-10 15:34:27 +03:00
Emmanuel Gil Peyrot
a721084f6e Fix the pubsub_client example. 2015-08-08 17:34:06 +02:00
Emmanuel Gil Peyrot
1b4187fa56 Add a format() method to XMPPError which returns a readable string. 2015-08-08 17:34:06 +02:00
Emmanuel Gil Peyrot
cf7a60705e Fix docstring of unsubscribe method in the PubSub plugin. 2015-08-08 17:34:06 +02:00
Emmanuel Gil Peyrot
349b05b9b7 Stop disco_browser and pubsub_client examples once they are finished. 2015-08-08 17:34:06 +02:00
Emmanuel Gil Peyrot
9fbacf377a Strip strings after pygments, so we don’t include an needless newline. 2015-08-08 17:34:06 +02:00
mathieui
2da9e35cbc Add missing files to the MANIFEST 2015-08-08 17:34:06 +02:00
Mike Taylor
abcec1e2d3 Merge pull request #388 from sangeeths/misc_updates
Retaining 'id' in the response and error stanzas
2015-08-01 14:04:22 -04:00
Sangeeth Saravanaraj
eeab646bfa Retaining 'id' in the response and error stanzas 2015-08-01 17:47:03 +05:30
Mike Taylor
2c69144189 Merge pull request #387 from mcella/378
Fixes #378: must acquire JID_CACHE_LOCK before adding to JID_CACHE
2015-07-31 11:21:01 -04:00
Michele Cella
f54ebec654 Fixes #378: must acquire JID_CACHE_LOCK before adding to JID_CACHE 2015-07-31 11:55:50 +02:00
Joshua Downer
2042e1a4d5 iot: only add the 'done' field when all devices are done 2015-07-20 17:34:09 -04:00
Robert Robinson
be14f0cc52 XEP_0050: Form not iterable in command
Cannot pass in a form into the initial command and have it show up in the payload of the session.  Line 344 makes this possible when following the standard workflow.
2015-07-15 20:52:06 -06:00
elya5
edd9199be8 Fix UnboundlocalError in disco_browser.py example
If self.get is in self.info_types and self.items_types, only self['xep_0030'].get_info is executed and not self['xep_0030'].get_items. So the condition in line 129 is successful but items is not assigned.
2015-07-09 17:15:36 +02:00
Mike Taylor
bb094cc649 Merge pull request #365 from jdowner/staging
Fixed imports
2015-07-05 15:46:04 -04:00
Mike Taylor
dbaa6ed952 Merge pull request #366 from jdowner/develop-iot-cleanup
Minor cleanup of IoT plugin
2015-07-05 15:45:47 -04:00
Mike Taylor
8c94d894ab Merge pull request #369 from stevenroose/patch-2
Change to roster migration e
2015-07-05 15:45:19 -04:00
Mike Taylor
ffc7eac4dc Merge pull request #370 from jdowner/develop-jid
Removed duplicate property
2015-07-05 15:44:58 -04:00
Mike Taylor
555fd6d926 Merge pull request #380 from anirudh-chhangani/XEP-0096-add-hash-param
add hash metadata for file transfer
2015-07-05 15:44:03 -04:00
Mike Taylor
c024ac8f0b Merge pull request #382 from sangeeths/initialize_certificate
Initialize certfile, keyfile and ca_certs in XMLStream. Added **kwargs to ClientXMPP, BaseXMPP and XMLStream.
2015-07-03 15:07:35 -04:00
Sangeeth Saravanaraj
f00177c0cf Added **kwargs to ClientXMPP, BaseXMPP and XMLStream so that certfile, keyfile and ca_certs can be initialized. 2015-07-03 10:47:06 +05:30
Anirudh
224d7ae133 add hash param to file metadata 2015-06-18 00:21:19 +05:30
Sangeeth Saravanaraj
9b25a7cf77 Fixed typo. 2015-06-05 12:25:41 +05:30
Joshua Downer
7a908ac07b Removed duplicate property 2015-05-28 09:35:50 -04:00
Steven Roose
92901637ec Change to roster migration example
I did have the chance to test the script yet, but it seems like that line should be outside the for loop.
2015-05-25 01:01:08 +02:00
Joshua Downer
3590b663ed xep-0323: removed deadcode 2015-05-14 06:27:59 -04:00
Joshua Downer
a33bde9cc3 xep-0323: spelling 2015-05-14 06:27:39 -04:00
Joshua Downer
ac50fdccfc xep-0323: unused import 2015-05-14 06:26:54 -04:00
Joshua Downer
a0c6bf15e9 Fixed imports
Removed unused modules/packages and added getpass, which was missing.
2015-05-13 17:24:06 -04:00
Mike Taylor
a8ac115310 Merge pull request #363 from sangeeths/xep_0332
XEP_332: Prefixed request and response with "http"
2015-05-01 12:50:06 -04:00
Sangeeth Saravanaraj
1345b7c1d0 Misc updates for send_error() 2015-05-01 15:34:53 +05:30
Sangeeth Saravanaraj
d60a652259 data need not be prefixed with http.. 2015-05-01 14:32:36 +05:30
Sangeeth Saravanaraj
61a7cecb31 Prefixed request, response and data with http. Avoided (plugin_attrib) name collision with other plugins. 2015-04-29 14:44:25 +05:30
Mike Taylor
192b7e0349 Merge pull request #345 from sangeeths/xep_0332
XEP-0332: HTTP over XMPP transport
2015-04-28 22:44:27 -04:00
Sangeeth Saravanaraj
80b60fc048 Merge remote-tracking branch 'origin/develop' into xep_0332 2015-04-28 16:53:40 +05:30
Mike Taylor
842157a6cc Merge pull request #187 from ekini/xep_0138
added xep-0138 support (compression)
2015-04-11 20:58:45 -04:00
Mike Taylor
a63cc01482 Merge pull request #316 from rakoo/develop
Extend AtomEntry capabilities
2015-04-11 20:53:44 -04:00
bear (Mike Taylor)
1bbb6f3ff9 Merge branch 'hildjj-develop' into develop 2015-04-11 20:43:56 -04:00
bear (Mike Taylor)
93894247a4 Merge branch 'develop' of https://github.com/hildjj/SleekXMPP into hildjj-develop 2015-04-11 20:42:33 -04:00
Mike Taylor
16bb5e2537 bump to version v1.4 2015-04-11 20:38:11 -04:00
Mike Taylor
d19a6e05b2 remove python v3.1 - v3.3 from tox.ini 2015-04-11 20:37:05 -04:00
Mike Taylor
86e85f9835 Merge pull request #313 from mayflower/develop
Proposing #310 again in fixed version
2015-04-11 20:12:19 -04:00
Mike Taylor
cc145d20b0 Merge pull request #297 from keith-gray-powereng/develop
Fixed a unicode error in xep_0065 on Python 3
2015-04-11 19:49:43 -04:00
Mike Taylor
881d9040c4 Merge pull request #329 from FlySnake/send_queue_overflow
In queues added option to remove first element on addind new if queue is full
2015-04-11 19:46:26 -04:00
Mike Taylor
1e77ea0944 Merge pull request #328 from FlySnake/develop
On initial connect use delay if connection failed
2015-04-11 19:20:39 -04:00
Mike Taylor
140f0885b2 Merge pull request #331 from mathieui/develop
Fix the element name for retrieving certs in XEP-0257
2015-04-11 19:15:26 -04:00
Mike Taylor
83f71a6610 Merge pull request #348 from gribouille-dev/tor_fixes
Makes XEP-0009 compatible with Python 2 & 3.
2015-04-11 18:32:42 -04:00
Mike Taylor
271343a32d Merge pull request #349 from mulog1990/ssl-version-fix
ssl-version not passed to wrap_socket, fixed
2015-04-11 18:26:05 -04:00
Mike Taylor
48857b0030 Merge pull request #354 from erigones/develop
Fixed bug #353 Python3 XEP-0084 error
2015-04-11 18:12:40 -04:00
Mike Taylor
1fe7f5f4e6 Create .travis.yml 2015-04-11 17:45:23 -04:00
Richard Kellner
81b7b2c190 Fixed bug #353 Python3 XEP-0084 error 2015-03-25 14:04:46 +01:00
mulog1990
460de7d301 ssl-version not passed to wrap_socket, fixed 2015-03-10 18:13:53 +08:00
Cédric Souchon
69022c6db7 Makes XEP-0009 compatible with Python 3 while maintaining compatibility with Python 2.6 and up. 2015-03-09 12:33:18 +01:00
Sangeeth Saravanaraj
9044807121 Added help for running example.. 2015-02-05 18:11:41 +05:30
Sangeeth Saravanaraj
24264d3a07 Updated Example.. 2015-02-05 18:10:10 +05:30
Sangeeth Saravanaraj
8bc70264ef misc updates.. 2015-02-05 17:35:04 +05:30
Sangeeth Saravanaraj
c16b862200 Raise http_request and http_response events. 2015-02-03 12:33:25 +05:30
Sangeeth Saravanaraj
a96f608469 Composing request and response. 2015-01-29 08:33:40 +05:30
Sangeeth Saravanaraj
e1f25604ec Added callbacks, registered stanzas, added features, etc. 2015-01-28 14:52:15 +05:30
Sangeeth Saravanaraj
0fe057b5c3 Boilerplate for Stanzas - request and response 2015-01-27 15:13:57 +05:30
Sangeeth Saravanaraj
be76dda21d Added xep_0332 to setup 2015-01-23 10:29:21 +05:30
Sangeeth Saravanaraj
ecd124dd06 Boilerplate for xep_0332 2015-01-22 16:40:03 +05:30
Sangeeth Saravanaraj
4a8951c4ee added xep_0332 to plugins 2015-01-22 16:39:27 +05:30
Sangeeth Saravanaraj
8afba7de85 renamed example for convenience. 2015-01-22 16:38:16 +05:30
Sangeeth Saravanaraj
1ce42d3a2f Boilerplate example. 2015-01-22 11:30:38 +05:30
Sangeeth Saravanaraj
2f4d811db4 Fixed a typo in docs/guide_xep_0030.rst 2015-01-22 11:13:03 +05:30
Sangeeth Saravanaraj
61127f521d Added PyCharm's .idea folder to .gitignore 2015-01-22 11:09:47 +05:30
mathieui
063e73c0d2 Fix the element name for retrieving certs in XEP-0257
And s/258/257/ in the XEP description
2014-12-11 18:32:50 +01:00
Oleg Antonyan
d261318e1a In queues added option to remove first element on addind new if queue is
full
2014-11-27 07:11:06 +02:00
Oleg Antonyan
d33cc00fe9 On initial connect use delay if connection failed 2014-11-23 16:46:01 +02:00
Lance Stout
27582f6fd2 Merge pull request #326 from s-m-b/patch-1
Typo fix of parameter name 'data' it is now 'iq'
2014-11-10 09:07:32 -08:00
s-m-b
e328ff4833 Typo fix of parameter name 'data' it is now 'iq'
Code was broken during refactoring
2014-11-09 04:36:38 +03:00
Lance Stout
403462fdb8 Merge branch 'develop' of github.com:fritzy/SleekXMPP into develop 2014-09-09 08:50:24 -07:00
Lance Stout
f22d8e67b4 Preserve ID for error responses
Fixes #319
2014-09-09 08:49:37 -07:00
Matthieu Rakotojaona
35f33f1614 Extend AtomEntry capabilities 2014-08-30 17:23:27 +02:00
Lance Stout
c9f8ddff65 Merge pull request #315 from louiz/develop
Fix saslprep on the username
2014-08-24 16:19:15 -07:00
Florent Le Coz
f5ae98aaf1 Fix saslprep on the username
Two issues fixed here:

- ints are not comparable with bytes, so char was never == to b',', which
  renders the whole function pointless
- The bytes were converted back to “characters” by using chr(), which
  doesn’t make sense if the username contains characters that fit on more
  than one bytes. This would trigger an “invalid username” error from the
  server when using a non-ascii JID.
2014-08-25 01:08:13 +02:00
Robin Gloster
073e85381a fix args, kwargs which were broken with #310. this is essentially the same but working 2014-08-23 14:25:35 +02:00
Robin Gloster
afc939708f cleanup semicolons, whitespace and mutable default arguments 2014-08-23 12:47:29 +02:00
Lance Stout
aabec8b993 Fix some more Unicode in **kwargs issues in Py2.6 2014-08-21 10:05:42 -07:00
Lance Stout
e5e2fbb16b Merge pull request #311 from Mayflower/develop
Revert "cleanup semicolons, whitespace and mutable default arguments"
2014-08-18 13:34:15 -07:00
Robin Gloster
3dd379cdf1 Revert "cleanup semicolons, whitespace and mutable default arguments"
This reverts commit 7265682a4d.
2014-08-18 15:15:14 +02:00
Lance Stout
a20582aba4 Merge pull request #309 from Mayflower/whitespace_keepalive
only schedule whitespace keepalive if enabled
2014-08-17 17:21:25 -07:00
Lance Stout
09cdbf1b76 Merge pull request #308 from Mayflower/develop
Serialize JID to allow json serializing
2014-08-17 17:20:45 -07:00
Lance Stout
ca306e7cec Merge pull request #310 from Mayflower/cleanup
Cleanup
2014-08-17 17:20:26 -07:00
Robin Gloster
1bf34f7fe6 fix mutable default arguments 💥 2014-08-18 00:55:10 +02:00
Robin Gloster
4144d60017 cleanup semicolons, whitespace and mutable default arguments 2014-08-18 00:55:10 +02:00
Robin Gloster
7265682a4d cleanup semicolons, whitespace and mutable default arguments 2014-08-18 00:52:24 +02:00
Robin Gloster
08c62a6bf1 fix mutable default arguments 💥 2014-08-18 00:18:10 +02:00
Robin Gloster
d61f1cd035 only schedule whitespace keepalive if enabled 2014-08-17 23:38:07 +02:00
Robin Gloster
1063feb33b only schedule whitespace keepalive if enabled 2014-08-17 23:37:19 +02:00
Robin Gloster
79f3c1ac8f serialize JID to allow json serializing 2014-08-17 23:13:56 +02:00
Lance Stout
a5c03b763a Merge pull request #305 from trinque/develop
Added wait param to XEP_0009 RemoteSession.close
2014-08-11 14:08:56 -07:00
Michael Trinque
3670d82f1c Added wait param to XEP_0009 RemoteSession.close
This parameter is False by default to preserve existing behavior.
2014-08-10 16:02:10 -07:00
Keith Gray
e94a73553d New version of the socks library socksipy from https://code.googlle.com/p/socksipy-branch/ 2014-06-15 19:01:19 -05:00
Keith Gray
577fd71472 Fixed a unicode error in xep_0065 on Python 3 2014-06-15 18:40:58 -05:00
Joe Hildebrand
ef1c4368d0 Merge branch 'master' of git://github.com/fritzy/SleekXMPP into develop
# By Joe Hildebrand (2) and Lance Stout (1)
# Via Lance Stout
* 'master' of git://github.com/fritzy/SleekXMPP:
  Relax timing issues in Iq timeout callback test.
  update JID_CACHE logic again.
  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.

Conflicts:
	tests/test_stream_handlers.py
2012-10-31 14:44:51 -06:00
Joe Hildebrand
48def71d0c Merge branch 'master' of git://github.com/fritzy/SleekXMPP into develop
# By Lance Stout
# Via Lance Stout
* 'master' of git://github.com/fritzy/SleekXMPP:
  Turns out not all data is UTF-8, so don't try to decode it.
2012-10-31 12:49:33 -06:00
Joe Hildebrand
c8c20fff71 update JID_CACHE logic again. 2012-10-29 14:15:07 -06:00
Joe Hildebrand
75a18b5ffe 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-29 10:03:32 -06:00
ekini
ea3d39b50e added xep-0138 support (compression) 2012-07-23 15:39:07 +07:00
290 changed files with 4383 additions and 2300 deletions

1
.gitignore vendored
View File

@@ -12,3 +12,4 @@ slixmpp.egg-info/
*~
.baboon/
.DS_STORE
.idea/

10
.travis.yml Normal file
View File

@@ -0,0 +1,10 @@
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "3.4"
install:
- "pip install ."
script: testall.py

14
CONTRIBUTING.rst Normal file
View File

@@ -0,0 +1,14 @@
Contributing to the Slixmpp project
===================================
To contribute, the preferred way is to commit your changes on some
publicly-available git repository (on a fork `on github
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
notify the developers with either:
- a ticket `on the bug tracker <https://dev.poez.io/new>`_
- a pull request on github
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
Even though Slixmpps github repository is just a read-only mirror, we can
still be notified of the pull requests and fetch your mirror manually to
integrate your changes.

View File

@@ -1,6 +1,7 @@
include README.rst
include LICENSE
include testall.py
include run_tests.py
include slixmpp/stringprep.pyx
recursive-include docs Makefile *.bat *.py *.rst *.css *.ttf *.png
recursive-include examples *.py
recursive-include tests *.py

View File

@@ -8,6 +8,14 @@ Slixmpp's goals is to only rewrite the core of the library (the low level
socket handling, the timers, the events dispatching) in order to remove all
threads.
Building
--------
Slixmpp can make use of cython to improve performance on critical modules.
To do that, **cython3** is necessary along with **libidn** headers.
Otherwise, no compilation is needed. Building is done by running setup.py::
python3 setup.py build_ext --inplace
Documentation and Testing
-------------------------
@@ -94,8 +102,17 @@ Slixmpp projects::
Slixmpp Credits
---------------
**Maintainer of the slixmpp fork:** Florent Le Coz
`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_,
**Maintainers:**
- Florent Le Coz (`louiz@louiz.org <xmpp:louiz@louiz.org?message>`_),
- Mathieu Pasquet (`mathieui@mathieui.net <xmpp:mathieui@mathieui.net?message>`_),
**Contributors:**
- Emmanuel Gil Peyrot (`Link mauve <xmpp:linkmauve@linkmauve.fr?message>`_)
- Sam Whited (`Sam Whited <mailto:sam@samwhited.com>`_)
- Dan Sully (`Dan Sully <mailto:daniel@electricalrain.com>`_)
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
Credits (SleekXMPP)
-------------------

View File

@@ -48,9 +48,9 @@ copyright = u'2011, Nathan Fritz, Lance Stout'
# built documents.
#
# The short X.Y version.
version = '1.0'
version = '1.1'
# The full version, including alpha/beta/rc tags.
release = '1.0'
release = '1.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View File

@@ -163,7 +163,7 @@ behaviour:
namespace = 'jabber:iq:register'
name = 'query'
plugin_attrib = 'register'
interfaces = set(('username', 'password', 'registered', 'remove'))
interfaces = {'username', 'password', 'registered', 'remove'}
sub_interfaces = interfaces
def getRegistered(self):
@@ -535,10 +535,10 @@ with some additional registration fields implemented.
namespace = 'jabber:iq:register'
name = 'query'
plugin_attrib = 'register'
interfaces = set(('username', 'password', 'email', 'nick', 'name',
'first', 'last', 'address', 'city', 'state', 'zip',
'phone', 'url', 'date', 'misc', 'text', 'key',
'registered', 'remove', 'instructions'))
interfaces = {'username', 'password', 'email', 'nick', 'name',
'first', 'last', 'address', 'city', 'state', 'zip',
'phone', 'url', 'date', 'misc', 'text', 'key',
'registered', 'remove', 'instructions'}
sub_interfaces = interfaces
def getRegistered(self):

View File

@@ -259,8 +259,8 @@ Event Index
Signal that a connection to the XMPP server has been lost and the current
stream session has ended. Currently equivalent to :term:`disconnected`, but
future implementation of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
will distinguish the two events.
implementations of `XEP-0198: Stream Management <http://xmpp.org/extensions/xep-0198.html>`_
distinguish between the two events.
Plugins that maintain session-based state should clear themselves when
this event is fired.

View File

@@ -70,7 +70,7 @@ as well.
class EchoBot(slixmpp.ClientXMPP):
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
super().__init__(jid, password)
Handling Session Start
~~~~~~~~~~~~~~~~~~~~~~
@@ -83,7 +83,7 @@ started. To do that, we will register an event handler for the :term:`session_st
.. code-block:: python
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
super().__init__(jid, password)
self.add_event_handler('session_start', self.start)
@@ -153,7 +153,7 @@ whenever a messsage is received.
.. code-block:: python
def __init__(self, jid, password):
super(EchoBot, self).__init__(jid, password)
super().__init__(jid, password)
self.add_event_handler('session_start', self.start)
self.add_event_handler('message', self.message)
@@ -329,7 +329,7 @@ The Final Product
-----------------
Here then is what the final result should look like after working through the guide above. The code
can also be found in the Slixmpp `examples directory <http://github.com/fritzy/Slixmpp/tree/master/examples>`_.
can also be found in the Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/examples>`_.
.. compound::

View File

@@ -63,13 +63,13 @@ has been established:
def start(self, event):
self.get_roster()
self.send_presence()
self.plugin['xep_0045'].joinMUC(self.room,
self.nick,
wait=True)
self.plugin['xep_0045'].join_muc(self.room,
self.nick,
wait=True)
Note that as in :ref:`echobot`, we need to include send an initial presence and request
the roster. Next, we want to join the group chat, so we call the
``joinMUC`` method of the MUC plugin.
``join_muc`` method of the MUC plugin.
.. note::

View File

@@ -24,7 +24,7 @@ for the JID that will receive our message, and the string content of the message
class SendMsgBot(slixmpp.ClientXMPP):
def __init__(self, jid, password, recipient, msg):
super(SendMsgBot, self).__init__(jid, password)
super().__init__(jid, password)
self.recipient = recipient
self.msg = msg

View File

@@ -61,7 +61,7 @@ operation using these stanzas without doing any complex operations such as
checking an ACL, etc.
You may find it necessary at some point to revert a particular node or JID to
using the default, static handlers. To do so, use the method ``make_static()``.
using the default, static handlers. To do so, use the method ``restore_defaults()``.
You may also elect to only convert a given set of actions instead.
Creating a Node Handler
@@ -161,8 +161,8 @@ item itself, and the JID and node that will own the item.
In this case, the owning JID and node are provided with the
parameters ``ijid`` and ``node``.
Peforming Disco Queries
-----------------------
Performing Disco Queries
------------------------
The methods ``get_info()`` and ``get_items()`` are used to query remote JIDs
and their nodes for disco information. Since these methods are wrappers for
sending Iq stanzas, they also accept all of the parameters of the ``Iq.send()``
@@ -172,11 +172,10 @@ the `XEP-0059 <http://xmpp.org/extensions/xep-0059.html>`_ plug-in.
.. code-block:: python
info = self['xep_0030'].get_info(jid='foo@example.com',
node='bar',
ifrom='baz@mycomponent.example.com',
block=True,
timeout=30)
info = yield from self['xep_0030'].get_info(jid='foo@example.com',
node='bar',
ifrom='baz@mycomponent.example.com',
timeout=30)
items = self['xep_0030'].get_info(jid='foo@example.com',
node='bar',

View File

@@ -160,9 +160,9 @@ if __name__ == '__main__':
myDevice = TheDevice(args.nodeid);
# myDevice._add_field(name="Relay", typename="numeric", unit="Bool");
myDevice._add_field(name="Temperature", typename="numeric", unit="C");
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"});
myDevice._add_field_momentary_data("Temperature", "23.4", flags={"automaticReadout": "true"})
xmpp['xep_0323'].register_node(nodeId=args.nodeid, device=myDevice, commTimeout=10);
xmpp.beClientOrServer(server=True)

View File

@@ -68,7 +68,7 @@ class CommandBot(slixmpp.ClientXMPP):
session. Additional, custom data may be saved
here to persist across handler callbacks.
"""
form = self['xep_0004'].makeForm('form', 'Greeting')
form = self['xep_0004'].make_form('form', 'Greeting')
form['instructions'] = 'Send a custom greeting to a JID'
form.addField(var='greeting',
ftype='text-single',

View File

@@ -94,7 +94,7 @@ class CommandUserBot(slixmpp.ClientXMPP):
# label="Your greeting" />
# </x>
form = self['xep_0004'].makeForm(ftype='submit')
form = self['xep_0004'].make_form(ftype='submit')
form.addField(var='greeting',
value=session['greeting'])

100
examples/confirm_answer.py Executable file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2015 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
log = logging.getLogger(__name__)
class AnswerConfirm(slixmpp.ClientXMPP):
"""
A basic client demonstrating how to confirm or deny an HTTP request.
"""
def __init__(self, jid, password, trusted):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.add_event_handler("http_confirm", self.confirm)
self.add_event_handler("session_start", self.start)
def start(self, *args):
self.make_presence().send()
def prompt(self, stanza):
confirm = stanza['confirm']
print('Received confirm request %s from %s to access %s using '
'method %s' % (
confirm['id'], stanza['from'], confirm['url'],
confirm['method'])
)
result = input("Do you accept (y/N)? ")
return 'y' == result.lower()
def confirm(self, stanza):
if self.prompt(stanza):
reply = stanza.reply()
else:
reply = stanza.reply()
reply.enable('error')
reply['error']['type'] = 'auth'
reply['error']['code'] = '401'
reply['error']['condition'] = 'not-authorized'
reply.append(stanza['confirm'])
reply.send()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
parser.add_argument("-q","--quiet", help="set logging to ERROR",
action="store_const",
dest="loglevel",
const=logging.ERROR,
default=logging.INFO)
parser.add_argument("-d","--debug", help="set logging to DEBUG",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
# Other options.
parser.add_argument("-t", "--trusted", nargs='*',
help="List of trusted JIDs")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
xmpp = AnswerConfirm(args.jid, args.password, args.trusted)
xmpp.register_plugin('xep_0070')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process()

125
examples/confirm_ask.py Executable file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2015 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import sys
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError, IqError
from slixmpp import asyncio
log = logging.getLogger(__name__)
class AskConfirm(slixmpp.ClientXMPP):
"""
A basic client asking an entity if they confirm the access to an HTTP URL.
"""
def __init__(self, jid, password, recipient, id, url, method):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.recipient = recipient
self.id = id
self.url = url
self.method = method
# Will be used to set the proper exit code.
self.confirmed = asyncio.Future()
self.add_event_handler("session_start", self.start)
self.add_event_handler("message", self.start)
self.add_event_handler("http_confirm_message", self.confirm)
def confirm(self, message):
print(message)
if message['confirm']['id'] == self.id:
if message['type'] == 'error':
self.confirmed.set_result(False)
else:
self.confirmed.set_result(True)
@asyncio.coroutine
def start(self, event):
log.info('Sending confirm request %s to %s who wants to access %s using '
'method %s...' % (self.id, self.recipient, self.url, self.method))
try:
confirmed = yield from self['xep_0070'].ask_confirm(self.recipient,
id=self.id,
url=self.url,
method=self.method,
message='Plz say yes or no for {method} {url} ({id}).')
if isinstance(confirmed, slixmpp.Message):
confirmed = yield from self.confirmed
else:
confirmed = True
except IqError:
confirmed = False
if confirmed:
print('Confirmed')
else:
print('Denied')
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
parser.add_argument("-q","--quiet", help="set logging to ERROR",
action="store_const",
dest="loglevel",
const=logging.ERROR,
default=logging.INFO)
parser.add_argument("-d","--debug", help="set logging to DEBUG",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
# Other options.
parser.add_argument("-r", "--recipient", required=True,
help="Recipient JID")
parser.add_argument("-i", "--id", required=True,
help="id TODO")
parser.add_argument("-u", "--url", required=True,
help="URL the user tried to access")
parser.add_argument("-m", "--method", required=True,
help="HTTP method used")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
xmpp = AskConfirm(args.jid, args.password, args.recipient, args.id,
args.url, args.method)
xmpp.register_plugin('xep_0070')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)
sys.exit(0 if xmpp.confirmed else 1)

View File

@@ -41,7 +41,7 @@ class Action(ElementBase):
#: del action['status']
#:
#: to set, get, or remove its values.
interfaces = set(('method', 'param', 'status'))
interfaces = {'method', 'param', 'status'}
#: By default, values in the `interfaces` set are mapped to
#: attribute values. This can be changed such that an interface

View File

@@ -76,10 +76,6 @@ class Disco(slixmpp.ClientXMPP):
try:
if self.get in self.info_types:
# By using block=True, the result stanza will be
# returned. Execution will block until the reply is
# received. Non-blocking options would be to listen
# for the disco_info event, or passing a handler
# function using the callback parameter.
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node)
@@ -162,4 +158,4 @@ if __name__ == '__main__':
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process()
xmpp.process(forever=False)

View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Implementation of HTTP over XMPP transport
http://xmpp.org/extensions/xep-0332.html
Copyright (C) 2015 Riptide IO, sangeeth@riptideio.com
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp import ClientXMPP
from optparse import OptionParser
import logging
import getpass
class HTTPOverXMPPClient(ClientXMPP):
def __init__(self, jid, password):
ClientXMPP.__init__(self, jid, password)
self.register_plugin('xep_0332') # HTTP over XMPP Transport
self.add_event_handler(
'session_start', self.session_start, threaded=True
)
self.add_event_handler('http_request', self.http_request_received)
self.add_event_handler('http_response', self.http_response_received)
def http_request_received(self, iq):
pass
def http_response_received(self, iq):
print('HTTP Response Received : %s' % iq)
print('From : %s' % iq['from'])
print('To : %s' % iq['to'])
print('Type : %s' % iq['type'])
print('Headers : %s' % iq['resp']['headers'])
print('Code : %s' % iq['resp']['code'])
print('Message : %s' % iq['resp']['message'])
print('Data : %s' % iq['resp']['data'])
def session_start(self, event):
# TODO: Fill in the blanks
self['xep_0332'].send_request(
to='?', method='?', resource='?', headers={}
)
self.disconnect()
if __name__ == '__main__':
#
# NOTE: To run this example, fill up the blanks in session_start() and
# use the following command.
#
# ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v]
#
parser = OptionParser()
# Output verbosity options.
parser.add_option(
'-v', '--verbose', help='set logging to DEBUG', action='store_const',
dest='loglevel', const=logging.DEBUG, default=logging.ERROR
)
# JID and password options.
parser.add_option('-J', '--jid', dest='jid', help='JID')
parser.add_option('-P', '--password', dest='password', help='Password')
# XMPP server ip and port options.
parser.add_option(
'-i', '--ipaddr', dest='ipaddr',
help='IP Address of the XMPP server', default=None
)
parser.add_option(
'-p', '--port', dest='port',
help='Port of the XMPP server', default=None
)
opts, args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
format='%(levelname)-8s %(message)s')
if opts.jid is None:
opts.jid = input('Username: ')
if opts.password is None:
opts.password = getpass.getpass('Password: ')
xmpp = HTTPOverXMPPClient(opts.jid, opts.password)
xmpp.connect()
xmpp.process()

View File

@@ -100,7 +100,7 @@ def on_session2(event):
new_xmpp.update_roster(jid,
name = item['name'],
groups = item['groups'])
new_xmpp.disconnect()
new_xmpp.disconnect()
new_xmpp.add_event_handler('session_start', on_session2)
new_xmpp.connect()

View File

@@ -67,11 +67,11 @@ class MUCBot(slixmpp.ClientXMPP):
"""
self.get_roster()
self.send_presence()
self.plugin['xep_0045'].joinMUC(self.room,
self.nick,
# If a room password is needed, use:
# password=the_room_password,
wait=True)
self.plugin['xep_0045'].join_muc(self.room,
self.nick,
# If a room password is needed, use:
# password=the_room_password,
wait=True)
def muc_message(self, msg):
"""

View File

@@ -5,21 +5,17 @@ import logging
from getpass import getpass
from argparse import ArgumentParser
import asyncio
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream import ET, tostring
from slixmpp.xmlstream.asyncio import asyncio
def make_callback():
future = asyncio.Future()
def callback(result):
future.set_result(result)
return future, callback
class PubsubClient(slixmpp.ClientXMPP):
def __init__(self, jid, password, server,
node=None, action='list', data=''):
super(PubsubClient, self).__init__(jid, password)
node=None, action='nodes', data=''):
super().__init__(jid, password)
self.register_plugin('xep_0030')
self.register_plugin('xep_0059')
@@ -36,87 +32,83 @@ class PubsubClient(slixmpp.ClientXMPP):
self.add_event_handler('session_start', self.start)
@asyncio.coroutine
def start(self, event):
self.get_roster()
self.send_presence()
try:
getattr(self, self.action)()
yield from getattr(self, self.action)()
except:
logging.error('Could not execute: %s' % self.action)
logging.error('Could not execute: %s', self.action)
self.disconnect()
def nodes(self):
future, callback = make_callback()
try:
self['xep_0060'].get_nodes(self.pubsub_server, self.node, callback=callback)
result = yield from future
result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
for item in result['disco_items']['items']:
print(' - %s' % str(item))
except:
logging.error('Could not retrieve node list.')
logging.info(' - %s', str(item))
except XMPPError as error:
logging.error('Could not retrieve node list: %s', error.format())
def create(self):
try:
self['xep_0060'].create_node(self.pubsub_server, self.node)
except:
logging.error('Could not create node: %s' % self.node)
yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
logging.info('Created node %s', self.node)
except XMPPError as error:
logging.error('Could not create node %s: %s', self.node, error.format())
def delete(self):
try:
self['xep_0060'].delete_node(self.pubsub_server, self.node)
print('Deleted node: %s' % self.node)
except:
logging.error('Could not delete node: %s' % self.node)
yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
logging.info('Deleted node %s', self.node)
except XMPPError as error:
logging.error('Could not delete node %s: %s', self.node, error.format())
def publish(self):
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
future, callback = make_callback()
try:
self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload, callback=callback)
result = yield from future
id = result['pubsub']['publish']['item']['id']
print('Published at item id: %s' % id)
except:
logging.error('Could not publish to: %s' % self.node)
result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
except XMPPError as error:
logging.error('Could not publish to %s: %s', self.node, error.format())
def get(self):
future, callback = make_callback()
try:
self['xep_0060'].get_item(self.pubsub_server, self.node, self.data, callback=callback)
result = yield from future
result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
for item in result['pubsub']['items']['substanzas']:
print('Retrieved item %s: %s' % (item['id'], tostring(item['payload'])))
except:
logging.error('Could not retrieve item %s from node %s' % (self.data, self.node))
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
except XMPPError as error:
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
def retract(self):
try:
self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
print('Retracted item %s from node %s' % (self.data, self.node))
except:
logging.error('Could not retract item %s from node %s' % (self.data, self.node))
yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
logging.info('Retracted item %s from node %s', self.data, self.node)
except XMPPError as error:
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
def purge(self):
try:
self['xep_0060'].purge(self.pubsub_server, self.node)
print('Purged all items from node %s' % self.node)
except:
logging.error('Could not purge items from node %s' % self.node)
yield from self['xep_0060'].purge(self.pubsub_server, self.node)
logging.info('Purged all items from node %s', self.node)
except XMPPError as error:
logging.error('Could not purge items from node %s: %s', self.node, error.format())
def subscribe(self):
try:
self['xep_0060'].subscribe(self.pubsub_server, self.node)
print('Subscribed %s to node %s' % (self.boundjid.bare, self.node))
except:
logging.error('Could not subscribe %s to node %s' % (self.boundjid.bare, self.node))
iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
subscription = iq['pubsub']['subscription']
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
except XMPPError as error:
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
def unsubscribe(self):
try:
self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
print('Unsubscribed %s from node %s' % (self.boundjid.bare, self.node))
except:
logging.error('Could not unsubscribe %s from node %s' % (self.boundjid.bare, self.node))
yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
except XMPPError as error:
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
@@ -133,12 +125,12 @@ if __name__ == '__main__':
action="store_const",
dest="loglevel",
const=logging.ERROR,
default=logging.ERROR)
default=logging.INFO)
parser.add_argument("-d","--debug", help="set logging to DEBUG",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.ERROR)
default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
@@ -147,7 +139,7 @@ if __name__ == '__main__':
help="password to use")
parser.add_argument("server")
parser.add_argument("action", choice=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
parser.add_argument("node", nargs='?')
parser.add_argument("data", nargs='?')
@@ -171,4 +163,4 @@ if __name__ == '__main__':
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process()
xmpp.process(forever=False)

View File

@@ -14,7 +14,7 @@ from slixmpp.xmlstream.handler import Callback
class PubsubEvents(slixmpp.ClientXMPP):
def __init__(self, jid, password):
super(PubsubEvents, self).__init__(jid, password)
super().__init__(jid, password)
self.register_plugin('xep_0030')
self.register_plugin('xep_0059')

View File

@@ -59,7 +59,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
self.get_roster(callback=callback)
yield from future
except IqError as err:
print('Error: %' % err.iq['error']['condition'])
print('Error: %s' % err.iq['error']['condition'])
except IqTimeout:
print('Error: Request timed out')
self.send_presence()

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2015 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
class S5BReceiver(slixmpp.ClientXMPP):
"""
A basic example of creating and using a SOCKS5 bytestream.
"""
def __init__(self, jid, password, filename):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.file = open(filename, 'wb')
self.add_event_handler("socks5_connected", self.stream_opened)
self.add_event_handler("socks5_data", self.stream_data)
self.add_event_handler("socks5_closed", self.stream_closed)
def stream_opened(self, sid):
logging.info('Stream opened. %s', sid)
def stream_data(self, data):
self.file.write(data)
def stream_closed(self, exception):
logging.info('Stream closed. %s', exception)
self.file.close()
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
# Output verbosity options.
parser.add_argument("-q", "--quiet", help="set logging to ERROR",
action="store_const", dest="loglevel",
const=logging.ERROR, default=logging.INFO)
parser.add_argument("-d", "--debug", help="set logging to DEBUG",
action="store_const", dest="loglevel",
const=logging.DEBUG, default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
parser.add_argument("-o", "--out", dest="filename",
help="file to save to")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
if args.filename is None:
args.filename = input("File path: ")
# Setup the S5BReceiver and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = S5BReceiver(args.jid, args.password, args.filename)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0065', {
'auto_accept': True
}) # SOCKS5 Bytestreams
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)

View File

@@ -0,0 +1,124 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2015 Emmanuel Gil Peyrot
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import IqError, IqTimeout
class S5BSender(slixmpp.ClientXMPP):
"""
A basic example of creating and using a SOCKS5 bytestream.
"""
def __init__(self, jid, password, receiver, filename):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.receiver = receiver
self.file = open(filename, 'rb')
# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an initial
presence stanza.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
try:
# Open the S5B stream in which to write to.
proxy = yield from self['xep_0065'].handshake(self.receiver)
# Send the entire file.
while True:
data = self.file.read(1048576)
if not data:
break
yield from proxy.write(data)
# And finally close the stream.
proxy.transport.write_eof()
except (IqError, IqTimeout):
print('File transfer errored')
else:
print('File transfer finished')
finally:
self.file.close()
self.disconnect()
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser()
# Output verbosity options.
parser.add_argument("-q", "--quiet", help="set logging to ERROR",
action="store_const", dest="loglevel",
const=logging.ERROR, default=logging.INFO)
parser.add_argument("-d", "--debug", help="set logging to DEBUG",
action="store_const", dest="loglevel",
const=logging.DEBUG, default=logging.INFO)
# JID and password options.
parser.add_argument("-j", "--jid", dest="jid",
help="JID to use")
parser.add_argument("-p", "--password", dest="password",
help="password to use")
parser.add_argument("-r", "--receiver", dest="receiver",
help="JID of the receiver")
parser.add_argument("-f", "--file", dest="filename",
help="file to send")
parser.add_argument("-m", "--use-messages", action="store_true",
help="use messages instead of iqs for file transfer")
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=args.loglevel,
format='%(levelname)-8s %(message)s')
if args.jid is None:
args.jid = input("Username: ")
if args.password is None:
args.password = getpass("Password: ")
if args.receiver is None:
args.receiver = input("Receiver: ")
if args.filename is None:
args.filename = input("File path: ")
# Setup the S5BSender and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = S5BSender(args.jid, args.password, args.receiver, args.filename)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0065') # SOCKS5 Bytestreams
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)

View File

@@ -22,7 +22,7 @@ from slixmpp import ClientXMPP
class LocationBot(ClientXMPP):
def __init__(self, jid, password):
super(LocationBot, self).__init__(jid, password)
super().__init__(jid, password)
self.add_event_handler('session_start', self.start)
self.add_event_handler('user_location_publish',

View File

@@ -17,7 +17,7 @@ from slixmpp import ClientXMPP
class TuneBot(ClientXMPP):
def __init__(self, jid, password):
super(TuneBot, self).__init__(jid, password)
super().__init__(jid, password)
# Check for the current song every 5 seconds.
self.schedule('Check Current Tune', 5, self._update_tune, repeat=True)

View File

@@ -7,20 +7,15 @@
# This software is licensed as described in the README.rst and LICENSE
# file, which you should have received as part of this distribution.
import os
from pathlib import Path
from subprocess import call, DEVNULL
from tempfile import TemporaryFile
try:
from setuptools import setup
except ImportError:
from distutils.core import setup
try:
from Cython.Build import cythonize
except ImportError:
print('Cython not found, falling back to the slow stringprep module.')
ext_modules = None
else:
ext_modules = cythonize('slixmpp/stringprep.pyx')
from run_tests import TestCommand
from slixmpp.version import __version__
@@ -40,6 +35,27 @@ CLASSIFIERS = [
packages = [str(mod.parent) for mod in Path('slixmpp').rglob('__init__.py')]
def check_include(header):
command = [os.environ.get('CC', 'cc'), '-E', '-']
with TemporaryFile('w+') as c_file:
c_file.write('#include <%s>' % header)
c_file.seek(0)
try:
return call(command, stdin=c_file, stdout=DEVNULL, stderr=DEVNULL) == 0
except FileNotFoundError:
return False
ext_modules = None
if check_include('stringprep.h'):
try:
from Cython.Build import cythonize
except ImportError:
print('Cython not found, falling back to the slow stringprep module.')
else:
ext_modules = cythonize('slixmpp/stringprep.pyx')
else:
print('libidn-dev not found, falling back to the slow stringprep module.')
setup(
name="slixmpp",
version=VERSION,
@@ -52,7 +68,7 @@ setup(
platforms=['any'],
packages=packages,
ext_modules=ext_modules,
requires=['aiodns', 'pyasn1', 'pyasn1_modules'],
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
classifiers=CLASSIFIERS,
cmdclass={'test': TestCommand}
)

View File

@@ -6,6 +6,9 @@
See the file LICENSE for copying permission.
"""
import asyncio
if hasattr(asyncio, 'sslproto'): # no ssl proto: very old asyncio = no need for this
asyncio.sslproto._is_sslproto_available=lambda: False
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

View File

@@ -12,8 +12,8 @@
:license: MIT, see LICENSE for more details
"""
import asyncio
import logging
import threading
from slixmpp import plugins, roster, stanza
from slixmpp.api import APIRegistry
@@ -21,8 +21,6 @@ from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.stanza import Message, Presence, Iq, StreamError
from slixmpp.stanza.roster import Roster
from slixmpp.stanza.nick import Nick
from slixmpp.stanza.htmlim import HTMLIM
from slixmpp.xmlstream import XMLStream, JID
from slixmpp.xmlstream import ET, register_stanza_plugin
@@ -46,8 +44,8 @@ class BaseXMPP(XMLStream):
is used during initialization.
"""
def __init__(self, jid='', default_ns='jabber:client'):
XMLStream.__init__(self)
def __init__(self, jid='', default_ns='jabber:client', **kwargs):
XMLStream.__init__(self, **kwargs)
self.default_ns = default_ns
self.stream_ns = 'http://etherx.jabber.org/streams'
@@ -71,7 +69,7 @@ class BaseXMPP(XMLStream):
#: redirections that will be followed before quitting.
self.max_redirects = 5
self.session_bind_event = threading.Event()
self.session_bind_event = asyncio.Event()
#: A dictionary mapping plugin names to plugins.
self.plugin = PluginManager(self)
@@ -195,7 +193,6 @@ class BaseXMPP(XMLStream):
# Initialize a few default stanza plugins.
register_stanza_plugin(Iq, Roster)
register_stanza_plugin(Message, Nick)
def start_stream_handler(self, xml):
"""Save the stream ID once the streams have been established.
@@ -221,7 +218,7 @@ class BaseXMPP(XMLStream):
self.plugin[name].post_init()
self.plugin[name].post_inited = True
def register_plugin(self, plugin, pconfig={}, module=None):
def register_plugin(self, plugin, pconfig=None, module=None):
"""Register and configure a plugin for use in this stream.
:param plugin: The name of the plugin class. Plugin names must
@@ -688,7 +685,6 @@ class BaseXMPP(XMLStream):
self.address = (host, port)
self.default_domain = host
self.dns_records = None
self.reconnect_delay = None
self.reconnect()
def _handle_message(self, msg):
@@ -754,6 +750,9 @@ class BaseXMPP(XMLStream):
Update the roster with presence information.
"""
if self.roster[presence['from']].ignore_updates:
return
if not self.is_component and not presence['to'].bare:
presence['to'] = self.boundjid

View File

@@ -12,6 +12,7 @@
:license: MIT, see LICENSE for more details
"""
import asyncio
import logging
from slixmpp.stanza import StreamFeatures
@@ -19,7 +20,7 @@ from slixmpp.basexmpp import BaseXMPP
from slixmpp.exceptions import XMPPError
from slixmpp.xmlstream import XMLStream
from slixmpp.xmlstream.matcher import StanzaPath, MatchXPath
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.handler import Callback, CoroutineCallback
# Flag indicating if DNS SRV records are available for use.
try:
@@ -50,7 +51,6 @@ class ClientXMPP(BaseXMPP):
:param jid: The JID of the XMPP user account.
:param password: The password for the XMPP user account.
:param ssl: **Deprecated.**
:param plugin_config: A dictionary of plugin configurations.
:param plugin_whitelist: A list of approved plugins that
will be loaded when calling
@@ -58,9 +58,15 @@ class ClientXMPP(BaseXMPP):
:param escape_quotes: **Deprecated.**
"""
def __init__(self, jid, password, plugin_config={}, plugin_whitelist=[],
escape_quotes=True, sasl_mech=None, lang='en'):
BaseXMPP.__init__(self, jid, 'jabber:client')
def __init__(self, jid, password, plugin_config=None,
plugin_whitelist=None, escape_quotes=True, sasl_mech=None,
lang='en', **kwargs):
if not plugin_whitelist:
plugin_whitelist = []
if not plugin_config:
plugin_config = {}
BaseXMPP.__init__(self, jid, 'jabber:client', **kwargs)
self.escape_quotes = escape_quotes
self.plugin_config = plugin_config
@@ -99,9 +105,9 @@ class ClientXMPP(BaseXMPP):
self.register_stanza(StreamFeatures)
self.register_handler(
Callback('Stream Features',
MatchXPath('{%s}features' % self.stream_ns),
self._handle_stream_features))
CoroutineCallback('Stream Features',
MatchXPath('{%s}features' % self.stream_ns),
self._handle_stream_features))
self.register_handler(
Callback('Roster Update',
StanzaPath('iq@type=set/roster'),
@@ -135,8 +141,11 @@ class ClientXMPP(BaseXMPP):
will be used.
:param address: A tuple containing the server's host and port.
:param use_tls: Indicates if TLS should be used for the
connection. Defaults to ``True``.
:param force_starttls: Indicates that negotiation should be aborted
if the server does not advertise support for
STARTTLS. Defaults to ``True``.
:param disable_starttls: Disables TLS for the connection.
Defaults to ``False``.
:param use_ssl: Indicates if the older SSL connection method
should be used. Defaults to ``False``.
"""
@@ -244,6 +253,7 @@ class ClientXMPP(BaseXMPP):
self.bindfail = False
self.features = set()
@asyncio.coroutine
def _handle_stream_features(self, features):
"""Process the received stream features.
@@ -252,7 +262,11 @@ class ClientXMPP(BaseXMPP):
for order, name in self._stream_feature_order:
if name in features['features']:
handler, restart = self._stream_feature_handlers[name]
if handler(features) and restart:
if asyncio.iscoroutinefunction(handler):
result = yield from handler(features)
else:
result = handler(features)
if result and restart:
# Don't continue if the feature requires
# restarting the XML stream.
return True

View File

@@ -46,8 +46,13 @@ class ComponentXMPP(BaseXMPP):
Defaults to ``False``.
"""
def __init__(self, jid, secret, host=None, port=None,
plugin_config={}, plugin_whitelist=[], use_jc_ns=False):
def __init__(self, jid, secret, host=None, port=None, plugin_config=None, plugin_whitelist=None, use_jc_ns=False):
if not plugin_whitelist:
plugin_whitelist = []
if not plugin_config:
plugin_config = {}
if use_jc_ns:
default_ns = 'jabber:client'
else:

View File

@@ -56,6 +56,18 @@ class XMPPError(Exception):
self.extension_ns = extension_ns
self.extension_args = extension_args
def format(self):
"""
Format the error in a simple user-readable string.
"""
text = [self.etype, self.condition]
if self.text:
text.append(self.text)
if self.extension:
text.append(self.extension)
# TODO: handle self.extension_args
return ': '.join(text)
class IqTimeout(XMPPError):
@@ -65,7 +77,7 @@ class IqTimeout(XMPPError):
"""
def __init__(self, iq):
super(IqTimeout, self).__init__(
super().__init__(
condition='remote-server-timeout',
etype='cancel')
@@ -82,7 +94,7 @@ class IqError(XMPPError):
"""
def __init__(self, iq):
super(IqError, self).__init__(
super().__init__(
condition=iq['error']['condition'],
text=iq['error']['text'],
etype=iq['error']['type'])

View File

@@ -13,7 +13,3 @@ from slixmpp.features.feature_bind.stanza import Bind
register_plugin(FeatureBind)
# Retain some backwards compatibility
feature_bind = FeatureBind

View File

@@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from slixmpp.jid import JID
@@ -34,6 +35,7 @@ class FeatureBind(BasePlugin):
register_stanza_plugin(Iq, stanza.Bind)
register_stanza_plugin(StreamFeatures, stanza.Bind)
@asyncio.coroutine
def _handle_bind_resource(self, features):
"""
Handle requesting a specific resource.
@@ -49,7 +51,7 @@ class FeatureBind(BasePlugin):
if self.xmpp.requested_jid.resource:
iq['bind']['resource'] = self.xmpp.requested_jid.resource
iq.send(callback=self._on_bind_response)
yield from iq.send(callback=self._on_bind_response)
def _on_bind_response(self, response):
self.xmpp.boundjid = JID(response['bind']['jid'])

View File

@@ -16,6 +16,6 @@ class Bind(ElementBase):
name = 'bind'
namespace = 'urn:ietf:params:xml:ns:xmpp-bind'
interfaces = set(('resource', 'jid'))
interfaces = {'resource', 'jid'}
sub_interfaces = interfaces
plugin_attrib = 'bind'

View File

@@ -16,7 +16,3 @@ from slixmpp.features.feature_mechanisms.stanza import Failure
register_plugin(FeatureMechanisms)
# Retain some backwards compatibility
feature_mechanisms = FeatureMechanisms

View File

@@ -49,7 +49,7 @@ class FeatureMechanisms(BasePlugin):
if self.security_callback is None:
self.security_callback = self._default_security
creds = self.sasl_callback(set(['username']), set())
creds = self.sasl_callback({'username'}, set())
if not self.use_mech and not creds['username']:
self.use_mech = 'ANONYMOUS'
@@ -190,14 +190,14 @@ class FeatureMechanisms(BasePlugin):
except sasl.SASLCancelled:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
except sasl.SASLFailed:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
except sasl.SASLMutualAuthFailed:
log.error("Mutual authentication failed! " + \
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
except sasl.SASLFailed:
self.attempted_mechs.add(self.mech.name)
self._send_auth()
else:
resp.send()
@@ -210,13 +210,13 @@ class FeatureMechanisms(BasePlugin):
resp['value'] = self.mech.process(stanza['value'])
except sasl.SASLCancelled:
self.stanza.Abort(self.xmpp).send()
except sasl.SASLFailed:
self.stanza.Abort(self.xmpp).send()
except sasl.SASLMutualAuthFailed:
log.error("Mutual authentication failed! " + \
"A security breach is possible.")
self.attempted_mechs.add(self.mech.name)
self.xmpp.disconnect()
except sasl.SASLFailed:
self.stanza.Abort(self.xmpp).send()
else:
if resp.get_value() == '':
resp.del_value()

View File

@@ -19,12 +19,12 @@ class Auth(StanzaBase):
name = 'auth'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('mechanism', 'value'))
interfaces = {'mechanism', 'value'}
plugin_attrib = name
#: Some SASL mechs require sending values as is,
#: without converting base64.
plain_mechs = set(['X-MESSENGER-OAUTH2'])
plain_mechs = {'X-MESSENGER-OAUTH2'}
def setup(self, xml):
StanzaBase.setup(self, xml)

View File

@@ -19,7 +19,7 @@ class Challenge(StanzaBase):
name = 'challenge'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('value',))
interfaces = {'value'}
plugin_attrib = name
def setup(self, xml):

View File

@@ -16,13 +16,14 @@ class Failure(StanzaBase):
name = 'failure'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('condition', 'text'))
interfaces = {'condition', 'text'}
plugin_attrib = name
sub_interfaces = set(('text',))
conditions = set(('aborted', 'account-disabled', 'credentials-expired',
'encryption-required', 'incorrect-encoding', 'invalid-authzid',
'invalid-mechanism', 'malformed-request', 'mechansism-too-weak',
'not-authorized', 'temporary-auth-failure'))
sub_interfaces = {'text'}
conditions = {'aborted', 'account-disabled', 'credentials-expired',
'encryption-required', 'incorrect-encoding',
'invalid-authzid', 'invalid-mechanism', 'malformed-request',
'mechansism-too-weak', 'not-authorized',
'temporary-auth-failure'}
def setup(self, xml=None):
"""

View File

@@ -16,7 +16,7 @@ class Mechanisms(ElementBase):
name = 'mechanisms'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('mechanisms', 'required'))
interfaces = {'mechanisms', 'required'}
plugin_attrib = name
is_extension = True
@@ -29,7 +29,7 @@ class Mechanisms(ElementBase):
"""
"""
results = []
mechs = self.findall('{%s}mechanism' % self.namespace)
mechs = self.xml.findall('{%s}mechanism' % self.namespace)
if mechs:
for mech in mechs:
results.append(mech.text)
@@ -47,7 +47,7 @@ class Mechanisms(ElementBase):
def del_mechanisms(self):
"""
"""
mechs = self.findall('{%s}mechanism' % self.namespace)
mechs = self.xml.findall('{%s}mechanism' % self.namespace)
if mechs:
for mech in mechs:
self.xml.remove(mech)

View File

@@ -19,7 +19,7 @@ class Response(StanzaBase):
name = 'response'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(('value',))
interfaces = {'value'}
plugin_attrib = name
def setup(self, xml):

View File

@@ -18,7 +18,7 @@ class Success(StanzaBase):
name = 'success'
namespace = 'urn:ietf:params:xml:ns:xmpp-sasl'
interfaces = set(['value'])
interfaces = {'value'}
plugin_attrib = name
def setup(self, xml):

View File

@@ -13,7 +13,3 @@ from slixmpp.features.feature_rosterver.stanza import RosterVer
register_plugin(FeatureRosterVer)
# Retain some backwards compatibility
feature_rosterver = FeatureRosterVer

View File

@@ -13,7 +13,3 @@ from slixmpp.features.feature_session.stanza import Session
register_plugin(FeatureSession)
# Retain some backwards compatibility
feature_session = FeatureSession

View File

@@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from slixmpp.stanza import Iq, StreamFeatures
@@ -34,6 +35,7 @@ class FeatureSession(BasePlugin):
register_stanza_plugin(Iq, stanza.Session)
register_stanza_plugin(StreamFeatures, stanza.Session)
@asyncio.coroutine
def _handle_start_session(self, features):
"""
Handle the start of the session.
@@ -44,7 +46,7 @@ class FeatureSession(BasePlugin):
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('session')
iq.send(callback=self._on_start_session_response)
yield from iq.send(callback=self._on_start_session_response)
def _on_start_session_response(self, response):
self.xmpp.features.add('session')

View File

@@ -13,7 +13,3 @@ from slixmpp.features.feature_starttls.stanza import *
register_plugin(FeatureSTARTTLS)
# Retain some backwards compatibility
feature_starttls = FeatureSTARTTLS

View File

@@ -16,7 +16,7 @@ class STARTTLS(ElementBase):
name = 'starttls'
namespace = 'urn:ietf:params:xml:ns:xmpp-tls'
interfaces = set(('required',))
interfaces = {'required'}
plugin_attrib = name
def get_required(self):

View File

@@ -79,7 +79,7 @@ def _validate_node(node):
:returns: The local portion of a JID, as validated by nodeprep.
"""
if node is None:
return None
return ''
try:
node = nodeprep(node)
@@ -160,7 +160,7 @@ def _validate_resource(resource):
:returns: The local portion of a JID, as validated by resourceprep.
"""
if resource is None:
return None
return ''
try:
resource = resourceprep(resource)
@@ -208,16 +208,15 @@ def _format_jid(local=None, domain=None, resource=None):
:return: A full or bare JID string.
"""
result = []
if domain is None:
return ''
if local is not None:
result.append(local)
result.append('@')
if domain is not None:
result.append(domain)
result = local + '@' + domain
else:
result = domain
if resource is not None:
result.append('/')
result.append(resource)
return ''.join(result)
result += '/' + resource
return result
class InvalidJID(ValueError):
@@ -300,19 +299,23 @@ class JID:
:raises InvalidJID:
"""
__slots__ = ('_node', '_domain', '_resource')
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
def __init__(self, jid=None):
if not jid:
self._node = None
self._domain = None
self._resource = None
self._node = ''
self._domain = ''
self._resource = ''
self._bare = ''
self._full = ''
return
elif not isinstance(jid, JID):
self._node, self._domain, self._resource = _parse_jid(jid)
else:
self._node = jid._node
self._domain = jid._domain
self._resource = jid._resource
self._update_bare_full()
def unescape(self):
"""Return an unescaped JID object.
@@ -329,77 +332,94 @@ class JID:
self._domain,
self._resource)
def _update_bare_full(self):
"""Format the given JID into a bare and a full JID.
"""
self._bare = (self._node + '@' + self._domain
if self._node
else self._domain)
self._full = (self._bare + '/' + self._resource
if self._resource
else self._bare)
@property
def node(self):
return self._node or ''
return self._node
@property
def user(self):
return self._node or ''
return self._node
@property
def local(self):
return self._node or ''
return self._node
@property
def username(self):
return self._node or ''
return self._node
@property
def domain(self):
return self._domain or ''
return self._domain
@property
def server(self):
return self._domain or ''
return self._domain
@property
def host(self):
return self._domain or ''
return self._domain
@property
def resource(self):
return self._resource or ''
return self._resource
@property
def bare(self):
return _format_jid(self._node, self._domain)
return self._bare
@property
def full(self):
return _format_jid(self._node, self._domain, self._resource)
return self._full
@property
def jid(self):
return _format_jid(self._node, self._domain, self._resource)
return self._full
@node.setter
def node(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@user.setter
def user(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@local.setter
def local(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@username.setter
def username(self, value):
self._node = _validate_node(value)
self._update_bare_full()
@domain.setter
def domain(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@server.setter
def server(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@host.setter
def host(self, value):
self._domain = _validate_domain(value)
self._update_bare_full()
@bare.setter
def bare(self, value):
@@ -407,26 +427,30 @@ class JID:
assert not resource
self._node = node
self._domain = domain
self._update_bare_full()
@resource.setter
def resource(self, value):
self._resource = _validate_resource(value)
self._update_bare_full()
@full.setter
def full(self, value):
self._node, self._domain, self._resource = _parse_jid(value)
self._update_bare_full()
@jid.setter
def jid(self, value):
self._node, self._domain, self._resource = _parse_jid(value)
self._update_bare_full()
def __str__(self):
"""Use the full JID as the string value."""
return _format_jid(self._node, self._domain, self._resource)
return self._full
def __repr__(self):
"""Use the full JID as the representation."""
return _format_jid(self._node, self._domain, self._resource)
return self._full
# pylint: disable=W0212
def __eq__(self, other):
@@ -446,4 +470,4 @@ class JID:
def __hash__(self):
"""Hash a JID based on the string version of its full JID."""
return hash(_format_jid(self._node, self._domain, self._resource))
return hash(self._full)

View File

@@ -47,6 +47,7 @@ __all__ = [
'xep_0108', # User Activity
'xep_0115', # Entity Capabilities
'xep_0118', # User Tune
'xep_0122', # Data Forms Validation
'xep_0128', # Extended Service Discovery
'xep_0131', # Standard Headers and Internet Metadata
'xep_0133', # Service Administration
@@ -83,4 +84,5 @@ __all__ = [
'xep_0319', # Last User Interaction in Presence
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
'xep_0332', # HTTP Over XMPP Transport
]

View File

@@ -308,7 +308,7 @@ class BasePlugin(object):
if key in self.default_config:
self.config[key] = value
else:
super(BasePlugin, self).__setattr__(key, value)
super().__setattr__(key, value)
def _init(self):
"""Initialize plugin state, such as registering event handlers.

View File

@@ -21,15 +21,15 @@ class GmailQuery(ElementBase):
namespace = 'google:mail:notify'
name = 'query'
plugin_attrib = 'gmail'
interfaces = set(('newer-than-time', 'newer-than-tid', 'q', 'search'))
interfaces = {'newer-than-time', 'newer-than-tid', 'q', 'search'}
def getSearch(self):
def get_search(self):
return self['q']
def setSearch(self, search):
def set_search(self, search):
self['q'] = search
def delSearch(self):
def del_search(self):
del self['q']
@@ -37,20 +37,20 @@ class MailBox(ElementBase):
namespace = 'google:mail:notify'
name = 'mailbox'
plugin_attrib = 'mailbox'
interfaces = set(('result-time', 'total-matched', 'total-estimate',
'url', 'threads', 'matched', 'estimate'))
interfaces = {'result-time', 'total-matched', 'total-estimate',
'url', 'threads', 'matched', 'estimate'}
def getThreads(self):
def get_threads(self):
threads = []
for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace,
MailThread.name)):
threads.append(MailThread(xml=threadXML, parent=None))
return threads
def getMatched(self):
def get_matched(self):
return self['total-matched']
def getEstimate(self):
def get_estimate(self):
return self['total-estimate'] == '1'
@@ -58,11 +58,11 @@ class MailThread(ElementBase):
namespace = 'google:mail:notify'
name = 'mail-thread-info'
plugin_attrib = 'thread'
interfaces = set(('tid', 'participation', 'messages', 'date',
'senders', 'url', 'labels', 'subject', 'snippet'))
sub_interfaces = set(('labels', 'subject', 'snippet'))
interfaces = {'tid', 'participation', 'messages', 'date',
'senders', 'url', 'labels', 'subject', 'snippet'}
sub_interfaces = {'labels', 'subject', 'snippet'}
def getSenders(self):
def get_senders(self):
senders = []
sendersXML = self.xml.find('{%s}senders' % self.namespace)
if sendersXML is not None:
@@ -75,12 +75,12 @@ class MailSender(ElementBase):
namespace = 'google:mail:notify'
name = 'sender'
plugin_attrib = 'sender'
interfaces = set(('address', 'name', 'originator', 'unread'))
interfaces = {'address', 'name', 'originator', 'unread'}
def getOriginator(self):
def get_originator(self):
return self.xml.attrib.get('originator', '0') == '1'
def getUnread(self):
def get_unread(self):
return self.xml.attrib.get('unread', '0') == '1'

View File

@@ -0,0 +1,47 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase, ET
class GoogleAuth(ElementBase):
name = 'auth'
namespace = 'http://www.google.com/talk/protocol/auth'
plugin_attrib = 'google'
interfaces = {'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('')
def get_client_uses_full_bind_result(self):
return self.parent()._get_attr(self.discovery_attr) == 'true'
def set_client_uses_full_bind_result(self, 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,90 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
import logging
from slixmpp.stanza import Iq
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin
from slixmpp.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, timeout=None, callback=None):
last_time = self._last_result_time
last_tid = self._last_result_tid
callback = lambda iq: self._update_last_results(iq, callback)
return self.search(newer_time=last_time,
newer_tid=last_tid,
timeout=timeout,
callback=callback)
def _update_last_results(self, iq, callback=None):
self._last_result_time = iq['gmail_messages']['result_time']
threads = iq['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,
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(timeout=timeout, callback=callback)

View File

@@ -0,0 +1,59 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.jid import JID
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
class NoSave(ElementBase):
name = 'x'
namespace = 'google:nosave'
plugin_attrib = 'google_nosave'
interfaces = {'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 = {'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, value):
self._set_attr('source', str(value))
register_stanza_plugin(NoSaveQuery, Item)

View File

@@ -0,0 +1,63 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2013 Nathanael C. Fritz, Lance J.T. Stout
This file is part of slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.stanza import Iq
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import StanzaPath
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin
from slixmpp.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, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq.enable('google_settings')
return iq.send(timeout=timeout, callback=callback)
def update(self, settings, 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(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

@@ -14,9 +14,3 @@ from slixmpp.plugins.xep_0004.dataforms import XEP_0004
register_plugin(XEP_0004)
# Retain some backwards compatibility
xep_0004 = XEP_0004
xep_0004.makeForm = xep_0004.make_form
xep_0004.buildForm = xep_0004.build_form

View File

@@ -23,7 +23,7 @@ class XEP_0004(BasePlugin):
name = 'xep_0004'
description = 'XEP-0004: Data Forms'
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):

View File

@@ -13,21 +13,22 @@ class FormField(ElementBase):
namespace = 'jabber:x:data'
name = 'field'
plugin_attrib = 'field'
interfaces = set(('answer', 'desc', 'required', 'value',
'options', 'label', 'type', 'var'))
sub_interfaces = set(('desc',))
plugin_multi_attrib = 'fields'
interfaces = {'answer', 'desc', 'required', 'value',
'label', 'type', 'var'}
sub_interfaces = {'desc'}
plugin_tag_map = {}
plugin_attrib_map = {}
field_types = set(('boolean', 'fixed', 'hidden', 'jid-multi',
'jid-single', 'list-multi', 'list-single',
'text-multi', 'text-private', 'text-single'))
field_types = {'boolean', 'fixed', 'hidden', 'jid-multi',
'jid-single', 'list-multi', 'list-single',
'text-multi', 'text-private', 'text-single'}
true_values = set((True, '1', 'true'))
option_types = set(('list-multi', 'list-single'))
multi_line_types = set(('hidden', 'text-multi'))
multi_value_types = set(('hidden', 'jid-multi',
'list-multi', 'text-multi'))
true_values = {True, '1', 'true'}
option_types = {'list-multi', 'list-single'}
multi_line_types = {'hidden', 'text-multi'}
multi_value_types = {'hidden', 'jid-multi',
'list-multi', 'text-multi'}
def setup(self, xml=None):
if ElementBase.setup(self, xml):
@@ -163,8 +164,9 @@ class FieldOption(ElementBase):
namespace = 'jabber:x:data'
name = 'option'
plugin_attrib = 'option'
interfaces = set(('label', 'value'))
sub_interfaces = set(('value',))
interfaces = {'label', 'value'}
sub_interfaces = {'value'}
plugin_multi_attrib = 'options'
FormField.addOption = FormField.add_option

View File

@@ -10,6 +10,7 @@ import copy
import logging
from collections import OrderedDict
from slixmpp.thirdparty import OrderedSet
from slixmpp.xmlstream import ElementBase, ET
from slixmpp.plugins.xep_0004.stanza import FormField
@@ -22,10 +23,9 @@ class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
interfaces = set(('fields', 'instructions', 'items',
'reported', 'title', 'type', 'values'))
sub_interfaces = set(('title',))
form_types = set(('cancel', 'form', 'result', 'submit'))
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
sub_interfaces = {'title'}
form_types = {'cancel', 'form', 'result', 'submit'}
def __init__(self, *args, **kwargs):
title = None
@@ -43,12 +43,12 @@ class Form(ElementBase):
@property
def field(self):
return self['fields']
return self.get_fields()
def set_type(self, ftype):
self._set_attr('type', ftype)
if ftype == 'submit':
fields = self['fields']
fields = self.get_fields()
for var in fields:
field = fields[var]
del field['type']
@@ -74,24 +74,13 @@ class Form(ElementBase):
field['desc'] = desc
field['required'] = required
if options is not None:
field['options'] = options
for option in options:
field.add_option(**option)
else:
del field['type']
self.append(field)
return field
def getXML(self, type='submit'):
self['type'] = type
log.warning("Form.getXML() is deprecated API compatibility " + \
"with plugins/old_0004.py")
return self.xml
def fromXML(self, xml):
log.warning("Form.fromXML() is deprecated API compatibility " + \
"with plugins/old_0004.py")
n = Form(xml=xml)
return n
def add_item(self, values):
itemXML = ET.Element('{%s}item' % self.namespace)
self.xml.append(itemXML)
@@ -151,7 +140,6 @@ class Form(ElementBase):
return fields
def get_instructions(self):
instructions = ''
instsXML = self.xml.findall('{%s}instructions' % self.namespace)
return "\n".join([instXML.text for instXML in instsXML])
@@ -170,7 +158,7 @@ class Form(ElementBase):
def get_reported(self):
fields = OrderedDict()
xml = self.xml.findall('{%s}reported/{%s}field' % (self.namespace,
FormField.namespace))
FormField.namespace))
for field in xml:
field = FormField(xml=field)
fields[field['var']] = field
@@ -178,7 +166,7 @@ class Form(ElementBase):
def get_values(self):
values = OrderedDict()
fields = self['fields']
fields = self.get_fields()
for var in fields:
values[var] = fields[var]['value']
return values
@@ -195,7 +183,14 @@ class Form(ElementBase):
fields = fields.items()
for var, field in fields:
field['var'] = var
self.add_field(**field)
self.add_field(
var=field.get('var'),
label=field.get('label'),
desc=field.get('desc'),
required=field.get('required'),
value=field.get('value'),
options=field.get('options'),
type=field.get('type'))
def set_instructions(self, instructions):
del self['instructions']
@@ -213,17 +208,33 @@ class Form(ElementBase):
self.add_item(item)
def set_reported(self, reported):
"""
This either needs a dictionary of dictionaries or a dictionary of form fields.
:param reported:
:return:
"""
for var in reported:
field = reported[var]
field['var'] = var
self.add_reported(var, **field)
if isinstance(field, dict):
self.add_reported(**field)
else:
reported = self.xml.find('{%s}reported' % self.namespace)
if reported is None:
reported = ET.Element('{%s}reported' % self.namespace)
self.xml.append(reported)
fieldXML = ET.Element('{%s}field' % FormField.namespace)
reported.append(fieldXML)
new_field = FormField(xml=fieldXML)
new_field.values = field.values
def set_values(self, values):
fields = self['fields']
fields = self.get_fields()
for field in values:
if field not in fields:
if field not in self.get_fields():
fields[field] = self.add_field(var=field)
fields[field]['value'] = values[field]
self.get_fields()[field]['value'] = values[field]
def merge(self, other):
new = copy.copy(self)

View File

@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse
register_plugin(XEP_0009)
# Retain some backwards compatibility
xep_0009 = XEP_0009

View File

@@ -25,7 +25,7 @@ def fault2xml(fault):
def xml2fault(params):
vals = []
for value in params.findall('{%s}value' % _namespace):
for value in params.xml.findall('{%s}value' % _namespace):
vals.append(_xml2py(value))
fault = dict()
fault['code'] = vals[0]['faultCode']
@@ -98,33 +98,34 @@ def xml2py(params):
def _xml2py(value):
namespace = 'jabber:iq:rpc'
if value.find('{%s}nil' % namespace) is not None:
find_value = value.find
if find_value('{%s}nil' % namespace) is not None:
return None
if value.find('{%s}i4' % namespace) is not None:
return int(value.find('{%s}i4' % namespace).text)
if value.find('{%s}int' % namespace) is not None:
return int(value.find('{%s}int' % namespace).text)
if value.find('{%s}boolean' % namespace) is not None:
return bool(int(value.find('{%s}boolean' % namespace).text))
if value.find('{%s}string' % namespace) is not None:
return value.find('{%s}string' % namespace).text
if value.find('{%s}double' % namespace) is not None:
return float(value.find('{%s}double' % namespace).text)
if value.find('{%s}base64' % namespace) is not None:
return rpcbase64(value.find('{%s}base64' % namespace).text.encode())
if value.find('{%s}Base64' % namespace) is not None:
if find_value('{%s}i4' % namespace) is not None:
return int(find_value('{%s}i4' % namespace).text)
if find_value('{%s}int' % namespace) is not None:
return int(find_value('{%s}int' % namespace).text)
if find_value('{%s}boolean' % namespace) is not None:
return bool(int(find_value('{%s}boolean' % namespace).text))
if find_value('{%s}string' % namespace) is not None:
return find_value('{%s}string' % namespace).text
if find_value('{%s}double' % namespace) is not None:
return float(find_value('{%s}double' % namespace).text)
if find_value('{%s}base64' % namespace) is not None:
return rpcbase64(find_value('{%s}base64' % namespace).text.encode())
if find_value('{%s}Base64' % namespace) is not None:
# Older versions of XEP-0009 used Base64
return rpcbase64(value.find('{%s}Base64' % namespace).text.encode())
if value.find('{%s}dateTime.iso8601' % namespace) is not None:
return rpctime(value.find('{%s}dateTime.iso8601' % namespace).text)
if value.find('{%s}struct' % namespace) is not None:
return rpcbase64(find_value('{%s}Base64' % namespace).text.encode())
if find_value('{%s}dateTime.iso8601' % namespace) is not None:
return rpctime(find_value('{%s}dateTime.iso8601' % namespace).text)
if find_value('{%s}struct' % namespace) is not None:
struct = {}
for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace):
for member in find_value('{%s}struct' % namespace).findall('{%s}member' % namespace):
struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace))
return struct
if value.find('{%s}array' % namespace) is not None:
if find_value('{%s}array' % namespace) is not None:
array = []
for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace):
for val in find_value('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace):
array.append(_xml2py(val))
return array
raise ValueError()

View File

@@ -6,7 +6,7 @@
See the file LICENSE for copying permission.
"""
from binding import py2xml, xml2py, xml2fault, fault2xml
from slixmpp.plugins.xep_0009.binding import py2xml, xml2py, xml2fault, fault2xml
from threading import RLock
import abc
import inspect
@@ -18,6 +18,38 @@ import traceback
log = logging.getLogger(__name__)
def _isstr(obj):
return isinstance(obj, str)
# Class decorator to declare a metaclass to a class in a way compatible with Python 2 and 3.
# This decorator is copied from 'six' (https://bitbucket.org/gutworth/six):
#
# Copyright (c) 2010-2015 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
def _add_metaclass(metaclass):
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
def _intercept(method, name, public):
def _resolver(instance, *args, **kwargs):
log.debug("Locally calling %s.%s with arguments %s.", instance.FQN(), method.__name__, args)
@@ -68,7 +100,7 @@ def remote(function_argument, public = True):
if hasattr(function_argument, '__call__'):
return _intercept(function_argument, None, public)
else:
if not isinstance(function_argument, basestring):
if not _isstr(function_argument):
if not isinstance(function_argument, bool):
raise Exception('Expected an RPC method name or visibility modifier!')
else:
@@ -131,7 +163,7 @@ class ACL:
@classmethod
def _next_token(cls, expression, index):
new_index = expression.find('*', index)
new_index = expression.xml.find('*', index)
if new_index == 0:
return ''
else:
@@ -150,7 +182,7 @@ class ACL:
#! print "[TOKEN] '%s'" % token
size = len(token)
if size > 0:
token_index = value.find(token, position)
token_index = value.xml.find(token, position)
if token_index == -1:
return False
else:
@@ -222,12 +254,11 @@ class TimeoutException(Exception):
pass
@_add_metaclass(abc.ABCMeta)
class Callback(object):
'''
A base class for callback handlers.
'''
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def set_value(self, value):
@@ -291,7 +322,7 @@ class Future(Callback):
self._event.set()
@_add_metaclass(abc.ABCMeta)
class Endpoint(object):
'''
The Endpoint class is an abstract base class for all objects
@@ -303,8 +334,6 @@ class Endpoint(object):
which specifies which object an RPC call refers to. It is the
first part in a RPC method name '<fqn>.<method>'.
'''
__metaclass__ = abc.ABCMeta
def __init__(self, session, target_jid):
'''
@@ -491,7 +520,7 @@ class RemoteSession(object):
def _find_key(self, dict, value):
"""return the key of dictionary dic given the value"""
search = [k for k, v in dict.iteritems() if v == value]
search = [k for k, v in dict.items() if v == value]
if len(search) == 0:
return None
else:
@@ -547,7 +576,7 @@ class RemoteSession(object):
result = handler_cls(*args, **kwargs)
Endpoint.__init__(result, self, self._client.boundjid.full)
method_dict = result.get_methods()
for method_name, method in method_dict.iteritems():
for method_name, method in method_dict.items():
#!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)
self._register_call(result.FQN(), method, method_name)
self._register_acl(result.FQN(), acl)
@@ -569,11 +598,11 @@ class RemoteSession(object):
self._register_callback(pid, callback)
iq.send()
def close(self):
def close(self, wait=False):
'''
Closes this session.
'''
self._client.disconnect(False)
self._client.disconnect(wait=wait)
self._session_close_callback()
def _on_jabber_rpc_method_call(self, iq):
@@ -697,7 +726,8 @@ class Remote(object):
if(client.boundjid.bare in cls._sessions):
raise RemoteException("There already is a session associated with these credentials!")
else:
cls._sessions[client.boundjid.bare] = client;
cls._sessions[client.boundjid.bare] = client
def _session_close_callback():
with Remote._lock:
del cls._sessions[client.boundjid.bare]

View File

@@ -24,7 +24,7 @@ class XEP_0009(BasePlugin):
name = 'xep_0009'
description = 'XEP-0009: Jabber-RPC'
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):
@@ -220,3 +220,4 @@ class XEP_0009(BasePlugin):
def _extract_method(self, stanza):
xml = ET.fromstring("%s" % stanza)
return xml.find("./methodCall/methodName").text

View File

@@ -14,8 +14,8 @@ class RPCQuery(ElementBase):
name = 'query'
namespace = 'jabber:iq:rpc'
plugin_attrib = 'rpc_query'
interfaces = set(())
subinterfaces = set(())
interfaces = {}
subinterfaces = {}
plugin_attrib_map = {}
plugin_tag_map = {}
@@ -24,8 +24,8 @@ class MethodCall(ElementBase):
name = 'methodCall'
namespace = 'jabber:iq:rpc'
plugin_attrib = 'method_call'
interfaces = set(('method_name', 'params'))
subinterfaces = set(())
interfaces = {'method_name', 'params'}
subinterfaces = {}
plugin_attrib_map = {}
plugin_tag_map = {}
@@ -46,8 +46,8 @@ class MethodResponse(ElementBase):
name = 'methodResponse'
namespace = 'jabber:iq:rpc'
plugin_attrib = 'method_response'
interfaces = set(('params', 'fault'))
subinterfaces = set(())
interfaces = {'params', 'fault'}
subinterfaces = {}
plugin_attrib_map = {}
plugin_tag_map = {}

View File

@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0012.last_activity import XEP_0012
register_plugin(XEP_0012)
# Retain some backwards compatibility
xep_0004 = XEP_0012

View File

@@ -29,7 +29,7 @@ class XEP_0012(BasePlugin):
name = 'xep_0012'
description = 'XEP-0012: Last Activity'
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):
@@ -66,7 +66,7 @@ class XEP_0012(BasePlugin):
self.del_last_activity(jid)
def start_uptime(self, status=None):
self.set_last_activity(jid, 0, status)
self.set_last_activity(None, 0, status)
def set_last_activity(self, jid=None, seconds=None, status=None):
self.api['set_last_activity'](jid, args={

View File

@@ -14,7 +14,7 @@ class LastActivity(ElementBase):
name = 'query'
namespace = 'jabber:iq:last'
plugin_attrib = 'last_activity'
interfaces = set(('seconds', 'status'))
interfaces = {'seconds', 'status'}
def get_seconds(self):
return int(self._get_attr('seconds'))

View File

@@ -29,7 +29,7 @@ class XEP_0013(BasePlugin):
name = 'xep_0013'
description = 'XEP-0013: Flexible Offline Message Retrieval'
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):

View File

@@ -14,7 +14,7 @@ class Offline(ElementBase):
name = 'offline'
namespace = 'http://jabber.org/protocol/offline'
plugin_attrib = 'offline'
interfaces = set(['fetch', 'purge', 'results'])
interfaces = {'fetch', 'purge', 'results'}
bool_interfaces = interfaces
def setup(self, xml=None):
@@ -39,9 +39,9 @@ class Item(ElementBase):
name = 'item'
namespace = 'http://jabber.org/protocol/offline'
plugin_attrib = 'item'
interfaces = set(['action', 'node', 'jid'])
interfaces = {'action', 'node', 'jid'}
actions = set(['view', 'remove'])
actions = {'view', 'remove'}
def get_jid(self):
return JID(self._get_attr('jid'))

View File

@@ -17,7 +17,7 @@ class XEP_0016(BasePlugin):
name = 'xep_0016'
description = 'XEP-0016: Privacy Lists'
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):

View File

@@ -18,14 +18,14 @@ class Active(ElementBase):
name = 'active'
namespace = 'jabber:iq:privacy'
plugin_attrib = name
interfaces = set(['name'])
interfaces = {'name'}
class Default(ElementBase):
name = 'default'
namespace = 'jabber:iq:privacy'
plugin_attrib = name
interfaces = set(['name'])
interfaces = {'name'}
class List(ElementBase):
@@ -33,7 +33,7 @@ class List(ElementBase):
namespace = 'jabber:iq:privacy'
plugin_attrib = name
plugin_multi_attrib = 'lists'
interfaces = set(['name'])
interfaces = {'name'}
def add_item(self, value, action, order, itype=None, iq=False,
message=False, presence_in=False, presence_out=False):
@@ -55,9 +55,9 @@ class Item(ElementBase):
namespace = 'jabber:iq:privacy'
plugin_attrib = name
plugin_multi_attrib = 'items'
interfaces = set(['type', 'value', 'action', 'order', 'iq',
'message', 'presence_in', 'presence_out'])
bool_interfaces = set(['message', 'iq', 'presence_in', 'presence_out'])
interfaces = {'type', 'value', 'action', 'order', 'iq',
'message', 'presence_in', 'presence_out'}
bool_interfaces = {'message', 'iq', 'presence_in', 'presence_out'}
type_values = ('', 'jid', 'group', 'subscription')
action_values = ('allow', 'deny')

View File

@@ -24,7 +24,7 @@ class XEP_0020(BasePlugin):
name = 'xep_0020'
description = 'XEP-0020: Feature Negotiation'
dependencies = set(['xep_0004', 'xep_0030'])
dependencies = {'xep_0004', 'xep_0030'}
stanza = stanza
def plugin_init(self):

View File

@@ -13,7 +13,7 @@ class Signed(ElementBase):
name = 'x'
namespace = 'jabber:x:signed'
plugin_attrib = 'signed'
interfaces = set(['signed'])
interfaces = {'signed'}
is_extension = True
def set_signed(self, value):
@@ -33,7 +33,7 @@ class Encrypted(ElementBase):
name = 'x'
namespace = 'jabber:x:encrypted'
plugin_attrib = 'encrypted'
interfaces = set(['encrypted'])
interfaces = {'encrypted'}
is_extension = True
def set_encrypted(self, value):

View File

@@ -15,8 +15,3 @@ from slixmpp.plugins.xep_0030.disco import XEP_0030
register_plugin(XEP_0030)
# Retain some backwards compatibility
xep_0030 = XEP_0030
XEP_0030.getInfo = XEP_0030.get_info
XEP_0030.make_static = XEP_0030.restore_defaults

View File

@@ -609,7 +609,7 @@ class XEP_0030(BasePlugin):
"""
self.api['del_features'](jid, node, None, kwargs)
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data={}):
def _run_node_handler(self, htype, jid, node=None, ifrom=None, data=None):
"""
Execute the most specific node handler for the given
JID/node combination.
@@ -620,6 +620,9 @@ class XEP_0030(BasePlugin):
node -- The node requested.
data -- Optional, custom data to pass to the handler.
"""
if not data:
data = {}
return self.api[htype](jid, node, ifrom, data)
def _handle_disco_info(self, iq):

View File

@@ -71,8 +71,8 @@ class DiscoInfo(ElementBase):
name = 'query'
namespace = 'http://jabber.org/protocol/disco#info'
plugin_attrib = 'disco_info'
interfaces = set(('node', 'features', 'identities'))
lang_interfaces = set(('identities',))
interfaces = {'node', 'features', 'identities'}
lang_interfaces = {'identities'}
# Cache identities and features
_identities = set()
@@ -91,7 +91,7 @@ class DiscoInfo(ElementBase):
"""
ElementBase.setup(self, xml)
self._identities = set([id[0:3] for id in self['identities']])
self._identities = {id[0:3] for id in self['identities']}
self._features = self['features']
def add_identity(self, category, itype, name=None, lang=None):
@@ -120,7 +120,7 @@ class DiscoInfo(ElementBase):
id_xml.attrib['{%s}lang' % self.xml_ns] = lang
if name:
id_xml.attrib['name'] = name
self.xml.append(id_xml)
self.xml.insert(0, id_xml)
return True
return False
@@ -137,7 +137,7 @@ class DiscoInfo(ElementBase):
identity = (category, itype, lang)
if identity in self._identities:
self._identities.remove(identity)
for id_xml in self.findall('{%s}identity' % self.namespace):
for id_xml in self.xml.findall('{%s}identity' % self.namespace):
id = (id_xml.attrib['category'],
id_xml.attrib['type'],
id_xml.attrib.get('{%s}lang' % self.xml_ns, None))
@@ -163,7 +163,7 @@ class DiscoInfo(ElementBase):
identities = set()
else:
identities = []
for id_xml in self.findall('{%s}identity' % self.namespace):
for id_xml in self.xml.findall('{%s}identity' % self.namespace):
xml_lang = id_xml.attrib.get('{%s}lang' % self.xml_ns, None)
if lang is None or xml_lang == lang:
id = (id_xml.attrib['category'],
@@ -205,7 +205,7 @@ class DiscoInfo(ElementBase):
Arguments:
lang -- Optional, standard xml:lang value.
"""
for id_xml in self.findall('{%s}identity' % self.namespace):
for id_xml in self.xml.findall('{%s}identity' % self.namespace):
if lang is None:
self.xml.remove(id_xml)
elif id_xml.attrib.get('{%s}lang' % self.xml_ns, None) == lang:
@@ -239,7 +239,7 @@ class DiscoInfo(ElementBase):
"""
if feature in self._features:
self._features.remove(feature)
for feature_xml in self.findall('{%s}feature' % self.namespace):
for feature_xml in self.xml.findall('{%s}feature' % self.namespace):
if feature_xml.attrib['var'] == feature:
self.xml.remove(feature_xml)
return True
@@ -251,7 +251,7 @@ class DiscoInfo(ElementBase):
features = set()
else:
features = []
for feature_xml in self.findall('{%s}feature' % self.namespace):
for feature_xml in self.xml.findall('{%s}feature' % self.namespace):
if dedupe:
features.add(feature_xml.attrib['var'])
else:
@@ -272,5 +272,5 @@ class DiscoInfo(ElementBase):
def del_features(self):
"""Remove all features."""
self._features = set()
for feature_xml in self.findall('{%s}feature' % self.namespace):
for feature_xml in self.xml.findall('{%s}feature' % self.namespace):
self.xml.remove(feature_xml)

View File

@@ -45,7 +45,7 @@ class DiscoItems(ElementBase):
name = 'query'
namespace = 'http://jabber.org/protocol/disco#items'
plugin_attrib = 'disco_items'
interfaces = set(('node', 'items'))
interfaces = {'node', 'items'}
# Cache items
_items = set()
@@ -62,7 +62,7 @@ class DiscoItems(ElementBase):
xml -- Use an existing XML object for the stanza's values.
"""
ElementBase.setup(self, xml)
self._items = set([item[0:2] for item in self['items']])
self._items = {item[0:2] for item in self['items']}
def add_item(self, jid, node=None, name=None):
"""
@@ -95,7 +95,7 @@ class DiscoItems(ElementBase):
node -- Optional extra identifying information.
"""
if (jid, node) in self._items:
for item_xml in self.findall('{%s}item' % self.namespace):
for item_xml in self.xml.findall('{%s}item' % self.namespace):
item = (item_xml.attrib['jid'],
item_xml.attrib.get('node', None))
if item == (jid, node):
@@ -138,7 +138,7 @@ class DiscoItem(ElementBase):
name = 'item'
namespace = 'http://jabber.org/protocol/disco#items'
plugin_attrib = name
interfaces = set(('jid', 'node', 'name'))
interfaces = {'jid', 'node', 'name'}
def get_node(self):
"""Return the item's node name or ``None``."""

View File

@@ -7,7 +7,6 @@
"""
import logging
import threading
from slixmpp import Iq
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
@@ -48,7 +47,6 @@ class StaticDisco(object):
self.nodes = {}
self.xmpp = xmpp
self.disco = disco
self.lock = threading.RLock()
def add_node(self, jid=None, node=None, ifrom=None):
"""
@@ -59,48 +57,45 @@ class StaticDisco(object):
jid -- The JID that will own the new stanzas.
node -- The node that will own the new stanzas.
"""
with self.lock:
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if ifrom is None:
ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(),
'items': DiscoItems()}
self.nodes[(jid, node, ifrom)]['info']['node'] = node
self.nodes[(jid, node, ifrom)]['items']['node'] = node
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if ifrom is None:
ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
self.nodes[(jid, node, ifrom)] = {'info': DiscoInfo(),
'items': DiscoItems()}
self.nodes[(jid, node, ifrom)]['info']['node'] = node
self.nodes[(jid, node, ifrom)]['items']['node'] = node
def get_node(self, jid=None, node=None, ifrom=None):
with self.lock:
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if ifrom is None:
ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
self.add_node(jid, node, ifrom)
return self.nodes[(jid, node, ifrom)]
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if ifrom is None:
ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
self.add_node(jid, node, ifrom)
return self.nodes[(jid, node, ifrom)]
def node_exists(self, jid=None, node=None, ifrom=None):
with self.lock:
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if ifrom is None:
ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
return False
return True
if jid is None:
jid = self.xmpp.boundjid.full
if node is None:
node = ''
if ifrom is None:
ifrom = ''
if isinstance(ifrom, JID):
ifrom = ifrom.full
if (jid, node, ifrom) not in self.nodes:
return False
return True
# =================================================================
# Node Handlers
@@ -199,14 +194,13 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if not self.node_exists(jid, node):
if not node:
return DiscoInfo()
else:
raise XMPPError(condition='item-not-found')
if not self.node_exists(jid, node):
if not node:
return DiscoInfo()
else:
return self.get_node(jid, node)['info']
raise XMPPError(condition='item-not-found')
else:
return self.get_node(jid, node)['info']
def set_info(self, jid, node, ifrom, data):
"""
@@ -214,9 +208,8 @@ class StaticDisco(object):
The data parameter is a disco#info substanza.
"""
with self.lock:
self.add_node(jid, node)
self.get_node(jid, node)['info'] = data
self.add_node(jid, node)
self.get_node(jid, node)['info'] = data
def del_info(self, jid, node, ifrom, data):
"""
@@ -224,9 +217,8 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if self.node_exists(jid, node):
self.get_node(jid, node)['info'] = DiscoInfo()
if self.node_exists(jid, node):
self.get_node(jid, node)['info'] = DiscoInfo()
def get_items(self, jid, node, ifrom, data):
"""
@@ -234,14 +226,13 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if not self.node_exists(jid, node):
if not node:
return DiscoItems()
else:
raise XMPPError(condition='item-not-found')
if not self.node_exists(jid, node):
if not node:
return DiscoItems()
else:
return self.get_node(jid, node)['items']
raise XMPPError(condition='item-not-found')
else:
return self.get_node(jid, node)['items']
def set_items(self, jid, node, ifrom, data):
"""
@@ -250,10 +241,9 @@ class StaticDisco(object):
The data parameter may provide:
items -- A set of items in tuple format.
"""
with self.lock:
items = data.get('items', set())
self.add_node(jid, node)
self.get_node(jid, node)['items']['items'] = items
items = data.get('items', set())
self.add_node(jid, node)
self.get_node(jid, node)['items']['items'] = items
def del_items(self, jid, node, ifrom, data):
"""
@@ -261,9 +251,8 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if self.node_exists(jid, node):
self.get_node(jid, node)['items'] = DiscoItems()
if self.node_exists(jid, node):
self.get_node(jid, node)['items'] = DiscoItems()
def add_identity(self, jid, node, ifrom, data):
"""
@@ -275,13 +264,12 @@ class StaticDisco(object):
name -- Optional human readable name for this identity.
lang -- Optional standard xml:lang value.
"""
with self.lock:
self.add_node(jid, node)
self.get_node(jid, node)['info'].add_identity(
data.get('category', ''),
data.get('itype', ''),
data.get('name', None),
data.get('lang', None))
self.add_node(jid, node)
self.get_node(jid, node)['info'].add_identity(
data.get('category', ''),
data.get('itype', ''),
data.get('name', None),
data.get('lang', None))
def set_identities(self, jid, node, ifrom, data):
"""
@@ -291,10 +279,9 @@ class StaticDisco(object):
identities -- A list of identities in tuple form:
(category, type, name, lang)
"""
with self.lock:
identities = data.get('identities', set())
self.add_node(jid, node)
self.get_node(jid, node)['info']['identities'] = identities
identities = data.get('identities', set())
self.add_node(jid, node)
self.get_node(jid, node)['info']['identities'] = identities
def del_identity(self, jid, node, ifrom, data):
"""
@@ -306,13 +293,12 @@ class StaticDisco(object):
name -- Optional human readable name for this identity.
lang -- Optional, standard xml:lang value.
"""
with self.lock:
if self.node_exists(jid, node):
self.get_node(jid, node)['info'].del_identity(
data.get('category', ''),
data.get('itype', ''),
data.get('name', None),
data.get('lang', None))
if self.node_exists(jid, node):
self.get_node(jid, node)['info'].del_identity(
data.get('category', ''),
data.get('itype', ''),
data.get('name', None),
data.get('lang', None))
def del_identities(self, jid, node, ifrom, data):
"""
@@ -320,9 +306,8 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if self.node_exists(jid, node):
del self.get_node(jid, node)['info']['identities']
if self.node_exists(jid, node):
del self.get_node(jid, node)['info']['identities']
def add_feature(self, jid, node, ifrom, data):
"""
@@ -331,10 +316,9 @@ class StaticDisco(object):
The data parameter should include:
feature -- The namespace of the supported feature.
"""
with self.lock:
self.add_node(jid, node)
self.get_node(jid, node)['info'].add_feature(
data.get('feature', ''))
self.add_node(jid, node)
self.get_node(jid, node)['info'].add_feature(
data.get('feature', ''))
def set_features(self, jid, node, ifrom, data):
"""
@@ -343,10 +327,9 @@ class StaticDisco(object):
The data parameter should include:
features -- The new set of supported features.
"""
with self.lock:
features = data.get('features', set())
self.add_node(jid, node)
self.get_node(jid, node)['info']['features'] = features
features = data.get('features', set())
self.add_node(jid, node)
self.get_node(jid, node)['info']['features'] = features
def del_feature(self, jid, node, ifrom, data):
"""
@@ -355,10 +338,9 @@ class StaticDisco(object):
The data parameter should include:
feature -- The namespace of the removed feature.
"""
with self.lock:
if self.node_exists(jid, node):
self.get_node(jid, node)['info'].del_feature(
data.get('feature', ''))
if self.node_exists(jid, node):
self.get_node(jid, node)['info'].del_feature(
data.get('feature', ''))
def del_features(self, jid, node, ifrom, data):
"""
@@ -366,10 +348,9 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if not self.node_exists(jid, node):
return
del self.get_node(jid, node)['info']['features']
if not self.node_exists(jid, node):
return
del self.get_node(jid, node)['info']['features']
def add_item(self, jid, node, ifrom, data):
"""
@@ -381,12 +362,11 @@ class StaticDisco(object):
non-addressable items.
name -- Optional human readable name for the item.
"""
with self.lock:
self.add_node(jid, node)
self.get_node(jid, node)['items'].add_item(
data.get('ijid', ''),
node=data.get('inode', ''),
name=data.get('name', ''))
self.add_node(jid, node)
self.get_node(jid, node)['items'].add_item(
data.get('ijid', ''),
node=data.get('inode', ''),
name=data.get('name', ''))
def del_item(self, jid, node, ifrom, data):
"""
@@ -396,11 +376,10 @@ class StaticDisco(object):
ijid -- JID of the item to remove.
inode -- Optional extra identifying information.
"""
with self.lock:
if self.node_exists(jid, node):
self.get_node(jid, node)['items'].del_item(
data.get('ijid', ''),
node=data.get('inode', None))
if self.node_exists(jid, node):
self.get_node(jid, node)['items'].del_item(
data.get('ijid', ''),
node=data.get('inode', None))
def cache_info(self, jid, node, ifrom, data):
"""
@@ -410,12 +389,11 @@ class StaticDisco(object):
containing the disco info to cache, or
the disco#info substanza itself.
"""
with self.lock:
if isinstance(data, Iq):
data = data['disco_info']
if isinstance(data, Iq):
data = data['disco_info']
self.add_node(jid, node, ifrom)
self.get_node(jid, node, ifrom)['info'] = data
self.add_node(jid, node, ifrom)
self.get_node(jid, node, ifrom)['info'] = data
def get_cached_info(self, jid, node, ifrom, data):
"""
@@ -423,8 +401,7 @@ class StaticDisco(object):
The data parameter is not used.
"""
with self.lock:
if not self.node_exists(jid, node, ifrom):
return None
else:
return self.get_node(jid, node, ifrom)['info']
if not self.node_exists(jid, node, ifrom):
return None
else:
return self.get_node(jid, node, ifrom)['info']

View File

@@ -14,7 +14,3 @@ from slixmpp.plugins.xep_0033.addresses import XEP_0033
register_plugin(XEP_0033)
# Retain some backwards compatibility
xep_0033 = XEP_0033
Addresses.addAddress = Addresses.add_address

View File

@@ -22,7 +22,7 @@ class XEP_0033(BasePlugin):
name = 'xep_0033'
description = 'XEP-0033: Extended Stanza Addressing'
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):

View File

@@ -37,9 +37,9 @@ class Address(ElementBase):
name = 'address'
namespace = 'http://jabber.org/protocol/address'
plugin_attrib = 'address'
interfaces = set(['type', 'jid', 'node', 'uri', 'desc', 'delivered'])
interfaces = {'type', 'jid', 'node', 'uri', 'desc', 'delivered'}
address_types = set(('bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'))
address_types = {'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'}
def get_jid(self):
return JID(self._get_attr('jid'))
@@ -117,15 +117,12 @@ for atype in ('all', 'bcc', 'cc', 'noreply', 'replyroom', 'replyto', 'to'):
setattr(Addresses, "set_%s" % atype, set_multi)
setattr(Addresses, "del_%s" % atype, del_multi)
# To retain backwards compatibility:
setattr(Addresses, "get%s" % atype.title(), get_multi)
setattr(Addresses, "set%s" % atype.title(), set_multi)
setattr(Addresses, "del%s" % atype.title(), del_multi)
if atype == 'all':
Addresses.interfaces.add('addresses')
setattr(Addresses, "getAddresses", get_multi)
setattr(Addresses, "setAddresses", set_multi)
setattr(Addresses, "delAddresses", del_multi)
setattr(Addresses, "get_addresses", get_multi)
setattr(Addresses, "set_addresses", set_multi)
setattr(Addresses, "del_addresses", del_multi)
register_stanza_plugin(Addresses, Address, iterable=True)

View File

@@ -25,86 +25,86 @@ class MUCPresence(ElementBase):
name = 'x'
namespace = 'http://jabber.org/protocol/muc#user'
plugin_attrib = 'muc'
interfaces = set(('affiliation', 'role', 'jid', 'nick', 'room'))
affiliations = set(('', ))
roles = set(('', ))
interfaces = {'affiliation', 'role', 'jid', 'nick', 'room'}
affiliations = {'', }
roles = {'', }
def getXMLItem(self):
def get_xml_item(self):
item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
if item is None:
item = ET.Element('{http://jabber.org/protocol/muc#user}item')
self.xml.append(item)
return item
def getAffiliation(self):
def get_affiliation(self):
#TODO if no affilation, set it to the default and return default
item = self.getXMLItem()
item = self.get_xml_item()
return item.get('affiliation', '')
def setAffiliation(self, value):
item = self.getXMLItem()
def set_affiliation(self, value):
item = self.get_xml_item()
#TODO check for valid affiliation
item.attrib['affiliation'] = value
return self
def delAffiliation(self):
item = self.getXMLItem()
def del_affiliation(self):
item = self.get_xml_item()
#TODO set default affiliation
if 'affiliation' in item.attrib: del item.attrib['affiliation']
return self
def getJid(self):
item = self.getXMLItem()
def get_jid(self):
item = self.get_xml_item()
return JID(item.get('jid', ''))
def setJid(self, value):
item = self.getXMLItem()
def set_jid(self, value):
item = self.get_xml_item()
if not isinstance(value, str):
value = str(value)
item.attrib['jid'] = value
return self
def delJid(self):
item = self.getXMLItem()
def del_jid(self):
item = self.get_xml_item()
if 'jid' in item.attrib: del item.attrib['jid']
return self
def getRole(self):
item = self.getXMLItem()
def get_role(self):
item = self.get_xml_item()
#TODO get default role, set default role if none
return item.get('role', '')
def setRole(self, value):
item = self.getXMLItem()
def set_role(self, value):
item = self.get_xml_item()
#TODO check for valid role
item.attrib['role'] = value
return self
def delRole(self):
item = self.getXMLItem()
def del_role(self):
item = self.get_xml_item()
#TODO set default role
if 'role' in item.attrib: del item.attrib['role']
return self
def getNick(self):
def get_nick(self):
return self.parent()['from'].resource
def getRoom(self):
def get_room(self):
return self.parent()['from'].bare
def setNick(self, value):
def set_nick(self, value):
log.warning("Cannot set nick through mucpresence plugin.")
return self
def setRoom(self, value):
def set_room(self, value):
log.warning("Cannot set room through mucpresence plugin.")
return self
def delNick(self):
def del_nick(self):
log.warning("Cannot delete nick through mucpresence plugin.")
return self
def delRoom(self):
def del_room(self):
log.warning("Cannot delete room through mucpresence plugin.")
return self
@@ -117,11 +117,11 @@ class XEP_0045(BasePlugin):
name = 'xep_0045'
description = 'XEP-0045: Multi-User Chat'
dependencies = set(['xep_0030', 'xep_0004'])
dependencies = {'xep_0030', 'xep_0004'}
def plugin_init(self):
self.rooms = {}
self.ourNicks = {}
self.our_nicks = {}
self.xep = '0045'
# load MUC support in presence stanzas
register_stanza_plugin(Presence, MUCPresence)
@@ -160,6 +160,7 @@ class XEP_0045(BasePlugin):
got_online = False
if pr['muc']['room'] not in self.rooms.keys():
return
self.xmpp.roster[pr['from']].ignore_updates = True
entry = pr['muc'].get_stanza_values()
entry['show'] = pr['show']
entry['status'] = pr['status']
@@ -200,28 +201,28 @@ class XEP_0045(BasePlugin):
"""
self.xmpp.event('groupchat_subject', msg)
def jidInRoom(self, room, jid):
def jid_in_room(self, room, jid):
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return True
return False
def getNick(self, room, jid):
def get_nick(self, room, jid):
for nick in self.rooms[room]:
entry = self.rooms[room][nick]
if entry is not None and entry['jid'].full == jid:
return nick
def configureRoom(self, room, form=None, ifrom=None):
def configure_room(self, room, form=None, ifrom=None):
if form is None:
form = self.getRoomConfig(room, ifrom=ifrom)
form = self.get_room_config(room, ifrom=ifrom)
iq = self.xmpp.make_iq_set()
iq['to'] = room
if ifrom is not None:
iq['from'] = ifrom
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
form = form.getXML('submit')
form['type'] = 'submit'
query.append(form)
iq.append(query)
# For now, swallow errors to preserve existing API
@@ -233,7 +234,7 @@ class XEP_0045(BasePlugin):
return False
return True
def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
def join_muc(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None, pfrom=None):
""" Join the specified room, requesting 'maxhistory' lines of history.
"""
stanza = self.xmpp.make_presence(pto="%s/%s" % (room, nick), pstatus=pstatus, pshow=pshow, pfrom=pfrom)
@@ -257,7 +258,7 @@ class XEP_0045(BasePlugin):
expect = ET.Element("{%s}presence" % self.xmpp.default_ns, {'from':"%s/%s" % (room, nick)})
self.xmpp.send(stanza, expect)
self.rooms[room] = {}
self.ourNicks[room] = nick
self.our_nicks[room] = nick
def destroy(self, room, reason='', altroom = '', ifrom=None):
iq = self.xmpp.make_iq_set()
@@ -282,7 +283,7 @@ class XEP_0045(BasePlugin):
return False
return True
def setAffiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
def set_affiliation(self, room, jid=None, nick=None, affiliation='member', ifrom=None):
""" Change room affiliation."""
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
raise TypeError
@@ -304,7 +305,7 @@ class XEP_0045(BasePlugin):
return False
return True
def setRole(self, room, nick, role):
def set_role(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
@@ -336,7 +337,7 @@ class XEP_0045(BasePlugin):
msg.append(x)
self.xmpp.send(msg)
def leaveMUC(self, room, nick, msg='', pfrom=None):
def leave_muc(self, room, nick, msg='', pfrom=None):
""" Leave the specified room.
"""
if msg:
@@ -345,7 +346,7 @@ class XEP_0045(BasePlugin):
self.xmpp.send_presence(pshow='unavailable', pto="%s/%s" % (room, nick), pfrom=pfrom)
del self.rooms[room]
def getRoomConfig(self, room, ifrom=''):
def get_room_config(self, room, ifrom=''):
iq = self.xmpp.make_iq_get('http://jabber.org/protocol/muc#owner')
iq['to'] = room
iq['from'] = ifrom
@@ -359,9 +360,9 @@ class XEP_0045(BasePlugin):
form = result.xml.find('{http://jabber.org/protocol/muc#owner}query/{jabber:x:data}x')
if form is None:
raise ValueError
return self.xmpp.plugin['xep_0004'].buildForm(form)
return self.xmpp.plugin['xep_0004'].build_form(form)
def cancelConfig(self, room, ifrom=None):
def cancel_config(self, room, ifrom=None):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = ET.Element('{jabber:x:data}x', type='cancel')
query.append(x)
@@ -370,39 +371,48 @@ class XEP_0045(BasePlugin):
iq['from'] = ifrom
iq.send()
def setRoomConfig(self, room, config, ifrom=''):
def set_room_config(self, room, config, ifrom=''):
query = ET.Element('{http://jabber.org/protocol/muc#owner}query')
x = config.getXML('submit')
query.append(x)
config['type'] = 'submit'
query.append(config)
iq = self.xmpp.make_iq_set(query)
iq['to'] = room
iq['from'] = ifrom
iq.send()
def getJoinedRooms(self):
def get_joined_rooms(self):
return self.rooms.keys()
def getOurJidInRoom(self, roomJid):
def get_our_jid_in_room(self, room_jid):
""" Return the jid we're using in a room.
"""
return "%s/%s" % (roomJid, self.ourNicks[roomJid])
return "%s/%s" % (room_jid, self.our_nicks[room_jid])
def getJidProperty(self, room, nick, jidProperty):
def get_jid_property(self, room, nick, jid_property):
""" Get the property of a nick in a room, such as its 'jid' or 'affiliation'
If not found, return None.
"""
if room in self.rooms and nick in self.rooms[room] and jidProperty in self.rooms[room][nick]:
return self.rooms[room][nick][jidProperty]
if room in self.rooms and nick in self.rooms[room] and jid_property in self.rooms[room][nick]:
return self.rooms[room][nick][jid_property]
else:
return None
def getRoster(self, room):
def get_roster(self, room):
""" Get the list of nicks in a room.
"""
if room not in self.rooms.keys():
return None
return self.rooms[room].keys()
def get_users_by_affiliation(cls, room, affiliation='member', ifrom=None):
if affiliation not in ('outcast', 'member', 'admin', 'owner', 'none'):
raise TypeError
query = ET.Element('{http://jabber.org/protocol/muc#admin}query')
item = ET.Element('{http://jabber.org/protocol/muc#admin}item', {'affiliation': affiliation})
query.append(item)
iq = cls.xmpp.Iq(sto=room, sfrom=ifrom, stype='get')
iq.append(query)
return iq.send()
xep_0045 = XEP_0045
register_plugin(XEP_0045)

View File

@@ -15,7 +15,3 @@ from slixmpp.plugins.xep_0047.ibb import XEP_0047
register_plugin(XEP_0047)
# Retain some backwards compatibility
xep_0047 = XEP_0047

View File

@@ -18,7 +18,7 @@ class XEP_0047(BasePlugin):
name = 'xep_0047'
description = 'XEP-0047: In-band Bytestreams'
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
stanza = stanza
default_config = {
'block_size': 4096,

View File

@@ -21,7 +21,7 @@ class Open(ElementBase):
name = 'open'
namespace = 'http://jabber.org/protocol/ibb'
plugin_attrib = 'ibb_open'
interfaces = set(('block_size', 'sid', 'stanza'))
interfaces = {'block_size', 'sid', 'stanza'}
def get_block_size(self):
return int(self._get_attr('block-size', '0'))
@@ -37,8 +37,8 @@ class Data(ElementBase):
name = 'data'
namespace = 'http://jabber.org/protocol/ibb'
plugin_attrib = 'ibb_data'
interfaces = set(('seq', 'sid', 'data'))
sub_interfaces = set(['data'])
interfaces = {'seq', 'sid', 'data'}
sub_interfaces = {'data'}
def get_seq(self):
return int(self._get_attr('seq', '0'))
@@ -67,4 +67,4 @@ class Close(ElementBase):
name = 'close'
namespace = 'http://jabber.org/protocol/ibb'
plugin_attrib = 'ibb_close'
interfaces = set(['sid'])
interfaces = {'sid'}

View File

@@ -24,7 +24,7 @@ class XEP_0048(BasePlugin):
name = 'xep_0048'
description = 'XEP-0048: Bookmarks'
dependencies = set(['xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223'])
dependencies = {'xep_0045', 'xep_0049', 'xep_0060', 'xep_0163', 'xep_0223'}
stanza = stanza
default_config = {
'auto_join': False,
@@ -59,7 +59,7 @@ class XEP_0048(BasePlugin):
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'],
self.xmpp['xep_0045'].join_muc(conf['jid'], conf['nick'],
password=conf['password'])
def set_bookmarks(self, bookmarks, method=None, **iqargs):

View File

@@ -40,8 +40,8 @@ class Conference(ElementBase):
namespace = 'storage:bookmarks'
plugin_attrib = 'conference'
plugin_multi_attrib = 'conferences'
interfaces = set(['nick', 'password', 'autojoin', 'jid', 'name'])
sub_interfaces = set(['nick', 'password'])
interfaces = {'nick', 'password', 'autojoin', 'jid', 'name'}
sub_interfaces = {'nick', 'password'}
def get_autojoin(self):
value = self._get_attr('autojoin')
@@ -58,7 +58,7 @@ class URL(ElementBase):
namespace = 'storage:bookmarks'
plugin_attrib = 'url'
plugin_multi_attrib = 'urls'
interfaces = set(['url', 'name'])
interfaces = {'url', 'name'}
register_stanza_plugin(Bookmarks, Conference, iterable=True)

View File

@@ -23,7 +23,7 @@ class XEP_0049(BasePlugin):
name = 'xep_0049'
description = 'XEP-0049: Private XML Storage'
dependencies = set([])
dependencies = {}
stanza = stanza
def plugin_init(self):

View File

@@ -13,7 +13,3 @@ from slixmpp.plugins.xep_0050.adhoc import XEP_0050
register_plugin(XEP_0050)
# Retain some backwards compatibility
xep_0050 = XEP_0050

View File

@@ -74,7 +74,7 @@ class XEP_0050(BasePlugin):
name = 'xep_0050'
description = 'XEP-0050: Ad-Hoc Commands'
dependencies = set(['xep_0030', 'xep_0004'])
dependencies = {'xep_0030', 'xep_0004'}
stanza = stanza
default_config = {
'session_db': None
@@ -94,7 +94,7 @@ class XEP_0050(BasePlugin):
self._handle_command))
register_stanza_plugin(Iq, Command)
register_stanza_plugin(Command, Form)
register_stanza_plugin(Command, Form, iterable=True)
self.xmpp.add_event_handler('command_execute',
self._handle_command_start)
@@ -225,8 +225,8 @@ class XEP_0050(BasePlugin):
if len(payload) == 1:
payload = payload[0]
interfaces = set([item.plugin_attrib for item in payload])
payload_classes = set([item.__class__ for item in payload])
interfaces = {item.plugin_attrib for item in payload}
payload_classes = {item.__class__ for item in payload}
initial_session = {'id': sessionid,
'from': iq['from'],
@@ -322,8 +322,8 @@ class XEP_0050(BasePlugin):
interfaces = session.get('interfaces', set())
payload_classes = session.get('payload_classes', set())
interfaces.update(set([item.plugin_attrib for item in payload]))
payload_classes.update(set([item.__class__ for item in payload]))
interfaces.update({item.plugin_attrib for item in payload})
payload_classes.update({item.__class__ for item in payload})
session['interfaces'] = interfaces
session['payload_classes'] = payload_classes
@@ -415,12 +415,26 @@ class XEP_0050(BasePlugin):
del self.sessions[sessionid]
payload = session['payload']
if payload is None:
payload = []
if not isinstance(payload, list):
payload = [payload]
for item in payload:
register_stanza_plugin(Command, item.__class__, iterable=True)
iq = iq.reply()
iq['command']['node'] = node
iq['command']['sessionid'] = sessionid
iq['command']['actions'] = []
iq['command']['status'] = 'completed'
iq['command']['notes'] = session['notes']
for item in payload:
iq['command'].append(item)
iq.send()
else:
raise XMPPError('item-not-found')

View File

@@ -72,11 +72,11 @@ class Command(ElementBase):
name = 'command'
namespace = 'http://jabber.org/protocol/commands'
plugin_attrib = 'command'
interfaces = set(('action', 'sessionid', 'node',
'status', 'actions', 'notes'))
actions = set(('cancel', 'complete', 'execute', 'next', 'prev'))
statuses = set(('canceled', 'completed', 'executing'))
next_actions = set(('prev', 'next', 'complete'))
interfaces = {'action', 'sessionid', 'node',
'status', 'actions', 'notes'}
actions = {'cancel', 'complete', 'execute', 'next', 'prev'}
statuses = {'canceled', 'completed', 'executing'}
next_actions = {'prev', 'next', 'complete'}
def get_action(self):
"""
@@ -100,7 +100,7 @@ class Command(ElementBase):
self.del_actions()
if values:
self._set_sub_text('{%s}actions' % self.namespace, '', True)
actions = self.find('{%s}actions' % self.namespace)
actions = self.xml.find('{%s}actions' % self.namespace)
for val in values:
if val in self.next_actions:
action = ET.Element('{%s}%s' % (self.namespace, val))
@@ -111,7 +111,7 @@ class Command(ElementBase):
Return the set of allowable next actions.
"""
actions = set()
actions_xml = self.find('{%s}actions' % self.namespace)
actions_xml = self.xml.find('{%s}actions' % self.namespace)
if actions_xml is not None:
for action in self.next_actions:
action_xml = actions_xml.find('{%s}%s' % (self.namespace,
@@ -136,7 +136,7 @@ class Command(ElementBase):
('error', 'The command ran, but had errors')]
"""
notes = []
notes_xml = self.findall('{%s}note' % self.namespace)
notes_xml = self.xml.findall('{%s}note' % self.namespace)
for note in notes_xml:
notes.append((note.attrib.get('type', 'info'),
note.text))
@@ -167,7 +167,7 @@ class Command(ElementBase):
"""
Remove all notes associated with the command result.
"""
notes_xml = self.findall('{%s}note' % self.namespace)
notes_xml = self.xml.findall('{%s}note' % self.namespace)
for note in notes_xml:
self.xml.remove(note)

View File

@@ -10,15 +10,15 @@ class VCardTemp(ElementBase):
name = 'vCard'
namespace = 'vcard-temp'
plugin_attrib = 'vcard_temp'
interfaces = set(['FN', 'VERSION'])
sub_interfaces = set(['FN', 'VERSION'])
interfaces = {'FN', 'VERSION'}
sub_interfaces = {'FN', 'VERSION'}
class Name(ElementBase):
name = 'N'
namespace = 'vcard-temp'
plugin_attrib = name
interfaces = set(['FAMILY', 'GIVEN', 'MIDDLE', 'PREFIX', 'SUFFIX'])
interfaces = {'FAMILY', 'GIVEN', 'MIDDLE', 'PREFIX', 'SUFFIX'}
sub_interfaces = interfaces
def _set_component(self, name, value):
@@ -72,7 +72,7 @@ class Nickname(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'nicknames'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_nickname(self, value):
@@ -95,9 +95,9 @@ class Email(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'emails'
interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'])
sub_interfaces = set(['USERID'])
bool_interfaces = set(['HOME', 'WORK', 'INTERNET', 'PREF', 'X400'])
interfaces = {'HOME', 'WORK', 'INTERNET', 'PREF', 'X400', 'USERID'}
sub_interfaces = {'USERID'}
bool_interfaces = {'HOME', 'WORK', 'INTERNET', 'PREF', 'X400'}
class Address(ElementBase):
@@ -105,12 +105,12 @@ class Address(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'addresses'
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY'])
sub_interfaces = set(['POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY'])
bool_interfaces = set(['HOME', 'WORK', 'DOM', 'INTL', 'PREF'])
interfaces = {'HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INTL',
'PREF', 'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY'}
sub_interfaces = {'POBOX', 'EXTADD', 'STREET', 'LOCALITY',
'REGION', 'PCODE', 'CTRY'}
bool_interfaces = {'HOME', 'WORK', 'DOM', 'INTL', 'PREF'}
class Telephone(ElementBase):
@@ -118,17 +118,18 @@ class Telephone(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'telephone_numbers'
interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG',
'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS',
'PREF', 'NUMBER'])
sub_interfaces = set(['NUMBER'])
bool_interfaces = set(['HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
'ISDN', 'PCS', 'PREF'])
interfaces = {'HOME', 'WORK', 'VOICE', 'FAX', 'PAGER', 'MSG',
'CELL', 'VIDEO', 'BBS', 'MODEM', 'ISDN', 'PCS',
'PREF', 'NUMBER'}
sub_interfaces = {'NUMBER'}
bool_interfaces = {'HOME', 'WORK', 'VOICE', 'FAX', 'PAGER',
'MSG', 'CELL', 'VIDEO', 'BBS', 'MODEM',
'ISDN', 'PCS', 'PREF'}
def setup(self, xml=None):
super(Telephone, self).setup(xml=xml)
self._set_sub_text('NUMBER', '', keep=True)
super().setup(xml=xml)
## this blanks out numbers received from server
##self._set_sub_text('NUMBER', '', keep=True)
def set_number(self, value):
self._set_sub_text('NUMBER', value, keep=True)
@@ -142,10 +143,10 @@ class Label(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'labels'
interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT',
'PREF', 'lines'])
bool_interfaces = set(['HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
'INT', 'PREF'])
interfaces = {'HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM', 'INT',
'PREF', 'lines'}
bool_interfaces = {'HOME', 'WORK', 'POSTAL', 'PARCEL', 'DOM',
'INT', 'PREF'}
def add_line(self, value):
line = ET.Element('{%s}LINE' % self.namespace)
@@ -176,7 +177,7 @@ class Geo(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'geolocations'
interfaces = set(['LAT', 'LON'])
interfaces = {'LAT', 'LON'}
sub_interfaces = interfaces
@@ -185,8 +186,8 @@ class Org(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'organizations'
interfaces = set(['ORGNAME', 'ORGUNIT', 'orgunits'])
sub_interfaces = set(['ORGNAME', 'ORGUNIT'])
interfaces = {'ORGNAME', 'ORGUNIT', 'orgunits'}
sub_interfaces = {'ORGNAME', 'ORGUNIT'}
def add_orgunit(self, value):
orgunit = ET.Element('{%s}ORGUNIT' % self.namespace)
@@ -217,7 +218,7 @@ class Photo(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'photos'
interfaces = set(['TYPE', 'EXTVAL'])
interfaces = {'TYPE', 'EXTVAL'}
sub_interfaces = interfaces
@@ -226,7 +227,7 @@ class Logo(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'logos'
interfaces = set(['TYPE', 'EXTVAL'])
interfaces = {'TYPE', 'EXTVAL'}
sub_interfaces = interfaces
@@ -235,7 +236,7 @@ class Sound(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'sounds'
interfaces = set(['PHONETC', 'EXTVAL'])
interfaces = {'PHONETC', 'EXTVAL'}
sub_interfaces = interfaces
@@ -243,7 +244,7 @@ class BinVal(ElementBase):
name = 'BINVAL'
namespace = 'vcard-temp'
plugin_attrib = name
interfaces = set(['BINVAL'])
interfaces = {'BINVAL'}
is_extension = True
def setup(self, xml=None):
@@ -274,7 +275,7 @@ class Classification(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'classifications'
interfaces = set(['PUBLIC', 'PRIVATE', 'CONFIDENTIAL'])
interfaces = {'PUBLIC', 'PRIVATE', 'CONFIDENTIAL'}
bool_interfaces = interfaces
@@ -283,7 +284,7 @@ class Categories(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'categories'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_categories(self, values):
@@ -313,7 +314,7 @@ class Birthday(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'birthdays'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_bday(self, value):
@@ -324,7 +325,10 @@ class Birthday(ElementBase):
def get_bday(self):
if not self.xml.text:
return None
return xep_0082.parse(self.xml.text)
try:
return xep_0082.parse(self.xml.text)
except ValueError:
return self.xml.text
class Rev(ElementBase):
@@ -332,7 +336,7 @@ class Rev(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'revision_dates'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_rev(self, value):
@@ -343,7 +347,10 @@ class Rev(ElementBase):
def get_rev(self):
if not self.xml.text:
return None
return xep_0082.parse(self.xml.text)
try:
return xep_0082.parse(self.xml.text)
except ValueError:
return self.xml.text
class Title(ElementBase):
@@ -351,7 +358,7 @@ class Title(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'titles'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_title(self, value):
@@ -366,7 +373,7 @@ class Role(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'roles'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_role(self, value):
@@ -381,7 +388,7 @@ class Note(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'notes'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_note(self, value):
@@ -396,7 +403,7 @@ class Desc(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'descriptions'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_desc(self, value):
@@ -411,7 +418,7 @@ class URL(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'urls'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_url(self, value):
@@ -426,7 +433,7 @@ class UID(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'uids'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_uid(self, value):
@@ -441,7 +448,7 @@ class ProdID(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'product_ids'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_prodid(self, value):
@@ -456,7 +463,7 @@ class Mailer(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'mailers'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_mailer(self, value):
@@ -471,7 +478,7 @@ class SortString(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = 'SORT_STRING'
plugin_multi_attrib = 'sort_strings'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_sort_string(self, value):
@@ -486,7 +493,7 @@ class Agent(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'agents'
interfaces = set(['EXTVAL'])
interfaces = {'EXTVAL'}
sub_interfaces = interfaces
@@ -495,7 +502,7 @@ class JabberID(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'jids'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_jabberid(self, value):
@@ -510,7 +517,7 @@ class TimeZone(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'timezones'
interfaces = set([name])
interfaces = {name}
is_extension = True
def set_tz(self, value):
@@ -523,8 +530,11 @@ class TimeZone(ElementBase):
def get_tz(self):
if not self.xml.text:
return xep_0082.tzutc()
time = xep_0082.parse('00:00:00%s' % self.xml.text)
return time.tzinfo
try:
time = xep_0082.parse('00:00:00%s' % self.xml.text)
return time.tzinfo
except ValueError:
return self.xml.text
register_stanza_plugin(VCardTemp, Name)

View File

@@ -29,7 +29,7 @@ class XEP_0054(BasePlugin):
name = 'xep_0054'
description = 'XEP-0054: vcard-temp'
dependencies = set(['xep_0030', 'xep_0082'])
dependencies = {'xep_0030', 'xep_0082'}
stanza = stanza
def plugin_init(self):
@@ -62,7 +62,7 @@ class XEP_0054(BasePlugin):
@future_wrapper
def get_vcard(self, jid=None, ifrom=None, local=None, cached=False,
callback=None, timeout=None):
callback=None, timeout=None, timeout_callback=None):
if local is None:
if jid is not None and not isinstance(jid, JID):
jid = JID(jid)
@@ -101,11 +101,12 @@ class XEP_0054(BasePlugin):
iq['type'] = 'get'
iq.enable('vcard_temp')
return iq.send(callback=callback, timeout=timeout)
return iq.send(callback=callback, timeout=timeout,
timeout_callback=timeout_callback)
@future_wrapper
def publish_vcard(self, vcard=None, jid=None, ifrom=None,
callback=None, timeout=None):
callback=None, timeout=None, timeout_callback=None):
self.api['set_vcard'](jid, None, ifrom, vcard)
if self.xmpp.is_component:
return
@@ -115,7 +116,8 @@ class XEP_0054(BasePlugin):
iq['from'] = ifrom
iq['type'] = 'set'
iq.append(vcard)
return iq.send(callback=callback, timeout=timeout)
return iq.send(callback=callback, timeout=timeout,
timeout_callback=timeout_callback)
def _handle_get_vcard(self, iq):
if iq['type'] == 'result':

View File

@@ -13,6 +13,3 @@ from slixmpp.plugins.xep_0059.rsm import ResultIterator, XEP_0059
register_plugin(XEP_0059)
# Retain some backwards compatibility
xep_0059 = XEP_0059

View File

@@ -111,7 +111,7 @@ class XEP_0059(BasePlugin):
name = 'xep_0059'
description = 'XEP-0059: Result Set Management'
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
stanza = stanza
def plugin_init(self):

View File

@@ -64,13 +64,13 @@ class Set(ElementBase):
namespace = 'http://jabber.org/protocol/rsm'
name = 'set'
plugin_attrib = 'rsm'
sub_interfaces = set(('first', 'after', 'before', 'count',
'index', 'last', 'max'))
interfaces = set(('first_index', 'first', 'after', 'before',
'count', 'index', 'last', 'max'))
sub_interfaces = {'first', 'after', 'before', 'count',
'index', 'last', 'max'}
interfaces = {'first_index', 'first', 'after', 'before',
'count', 'index', 'last', 'max'}
def set_first_index(self, val):
fi = self.find("{%s}first" % (self.namespace))
fi = self.xml.find("{%s}first" % (self.namespace))
if fi is not None:
if val:
fi.attrib['index'] = val
@@ -82,7 +82,7 @@ class Set(ElementBase):
self.xml.append(fi)
def get_first_index(self):
fi = self.find("{%s}first" % (self.namespace))
fi = self.xml.find("{%s}first" % (self.namespace))
if fi is not None:
return fi.attrib.get('index', '')

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