Compare commits

...

141 Commits

Author SHA1 Message Date
mathieui
570e653ac2 Release slixmpp 1.4.1 2018-10-28 14:15:51 +01:00
mathieui
282a481059 Merge branch 'setup_dependency_fix' into 'master'
added aiohttp dependency

See merge request poezio/slixmpp!6
2018-10-28 14:08:26 +01:00
Florian Klien
f386db380b docs: auto-set copyright year to current year 2018-10-28 10:48:23 +01:00
Florian Klien
7b87d98fff auto set version of Slixmpp in Docs
getting version of slixmpp from source tree for documentation
2018-10-28 10:48:23 +01:00
Florian Klien
8779d40602 typo 2018-10-27 23:38:09 +02:00
Emmanuel Gil Peyrot
f0b21c42d5 examples: Add the possibility to use another HTTP File Upload domain. 2018-10-27 23:25:59 +02:00
Emmanuel Gil Peyrot
e241d4e3c7 XEP-0030: Don’t call the timeout_callback on each domain which doesn’t reply to disco#info. 2018-10-27 23:21:27 +02:00
Emmanuel Gil Peyrot
bd22a41a78 XEP-0363: Also check for disco#info’s feature instead of just the identity. 2018-10-27 23:14:39 +02:00
Emmanuel Gil Peyrot
a29a29227a XEP-0363: Add a domain argument to discover an upload service on a specific domain. 2018-10-27 22:51:04 +02:00
Florian Klien
d4d542b741 fixing uncaught async exceptions due to missing await
fixes uncaught exceptions in the event loop.
passing timeout and timeout_callback through.
2018-10-27 22:51:04 +02:00
Florian Klien
dc4936a6d3 added aoihttp dependency
slixmpp uses aiohttp in the XEP_0363 plugin.
2018-10-22 12:41:20 +02:00
Florian Klien
897610d819 fix: failUnlessEqual -> assertEqual 2018-10-15 14:59:23 +02:00
Florian Klien
d33366badd fixing deprecation warnings for pytest 2018-10-15 14:59:23 +02:00
mathieui
809c500002 Add the loop parameters at places where it has been forgotten 2018-10-09 12:34:56 +02:00
Emmanuel Gil Peyrot
dda4e18b81 examples: Remove unused asyncio imports. 2018-10-09 10:57:19 +02:00
Emmanuel Gil Peyrot
8c09d932c8 stanzabase: Remove python2 legacy. 2018-10-03 14:56:07 +02:00
louiz’
ad0dc33df9 Trigger poezio’s build if this ours succeeded 2018-08-22 23:19:47 +02:00
Emmanuel Gil Peyrot
7c3b3827b4 jid: Make property aliases proper aliases. 2018-08-20 00:23:21 +01:00
Emmanuel Gil Peyrot
9f6fa65139 examples, tests: Replace all @asyncio.coroutines with proper async functions. 2018-08-19 17:47:26 +01:00
mathieui
35fa33e3c2 Release slixmpp 1.4.0 2018-08-12 21:11:21 +02:00
mathieui
86a2f280d2 Document that slixmpp is now 3.5+ only 2018-08-08 23:42:22 +02:00
mathieui
490f15b8fc Fix compatibility with python 3.5 and 3.6
which do not have loop.start_tls and require the old ssl implementation.
2018-08-08 23:35:33 +02:00
Emmanuel Gil Peyrot
62661ee04f xep_0092: Return <service-unavailable/> instead. Fixes #3415. 2018-08-08 16:52:40 +02:00
Emmanuel Gil Peyrot
37d1f2a6b0 xep_0092: Send a <forbidden/> error if we don’t want to send our version. (thanks lovetox!) 2018-08-08 16:49:16 +02:00
mathieui
20107ad516 features/starttls: handle the case where the socket is an sslobject
Thanks pep.
2018-08-07 23:30:41 +02:00
mathieui
7738a01311 Fix TLS with python 3.7
Use the "new" sslproto API instead of the deprecated TLS API.
Also remove the unused "socket" parameter in XMLStream.__init__.
2018-08-07 23:20:38 +02:00
mathieui
a9abed6151 xep-0054: XMPP clients should not reply to vcard "get" requests 2018-08-07 21:30:13 +02:00
Emmanuel Gil Peyrot
0f690d4005 tests: Fix the XEP-0323 stream test, broken since 59d4420739.
Thanks debacle!
2018-08-02 21:27:19 +02:00
Emmanuel Gil Peyrot
59d4420739 XEP-0323: Display the requested time in addition to the current time on error. 2018-07-29 10:37:28 +02:00
Emmanuel Gil Peyrot
a88f317bbf XEP-0009: Fix invalid function name under Python 3.7. 2018-07-24 18:21:03 +02:00
Link Mauve
2fc2a88970 Merge branch 'factor_find_identities' into 'master'
Factor find identities

See merge request poezio/slixmpp!2
2018-07-22 12:49:37 +02:00
Emmanuel Gil Peyrot
c55e9279ac Fix missing async def in function. 2018-07-02 14:33:21 +02:00
Emmanuel Gil Peyrot
3502480384 Switch from @asyncio.coroutine to async def everywhere. 2018-07-01 18:46:33 +02:00
Maxime “pep” Buquet
caae713dd6 xep_0030: rename find_identities; return all domain infos and let caller filter itself
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:36:26 +01:00
Maxime “pep” Buquet
df0198abfe xep_0030: Add callback parameter to find_identities
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:32:00 +01:00
Maxime “pep” Buquet
c20f4bf5fa xep_0030: Add cached parameter to find_identities, defaults to True
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Maxime “pep” Buquet
9740e93aeb xep_0030: Pass kwargs down in find_identities
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Maxime “pep” Buquet
e7872aaa29 xep_0030: Use self directly as we're already in disco
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Maxime “pep” Buquet
037706552c Factor out fetching of identities in xep_0363 to xep_0030
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Maxime “pep” Buquet
b881c6729b xep_0363: Remove unused parameters to find_upload_services
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2018-07-01 01:29:31 +01:00
Emmanuel Gil Peyrot
66909aafb3 XEP-0153: Prevent a panic when the BINVAL is invalid. 2018-06-23 14:34:24 +02:00
louiz’
cdfb5d56fc apt install gpg before running the ci tests 2018-06-10 21:40:56 +02:00
Emmanuel Gil Peyrot
d146ce9fb6 examples: Display only the form, and not the entire stanza. 2018-05-13 21:26:47 +02:00
Emmanuel Gil Peyrot
cb59d60034 examples: Display the actual cause for a failed command. 2018-05-13 21:21:06 +02:00
Emmanuel Gil Peyrot
1d9fe3553e examples: Use the existing get_node_config function. 2018-05-13 21:13:22 +02:00
Emmanuel Gil Peyrot
fe66c022ad Revert "XEP-0060: Add support for node configuration."
This reverts commit dd7f67d10d.
2018-05-13 21:12:46 +02:00
Emmanuel Gil Peyrot
92ea131721 examples: Add support for node configuration to pubsub_client. 2018-05-13 20:57:48 +02:00
Emmanuel Gil Peyrot
dd7f67d10d XEP-0060: Add support for node configuration. 2018-05-13 20:47:21 +02:00
Emmanuel Gil Peyrot
c1562b76b2 slixmpp is Python 3.4.4+, remove check for channel binding. 2018-03-31 02:22:53 +02:00
Emmanuel Gil Peyrot
32839f5252 util.cache: Let the user select the bare JID or not. 2018-03-31 00:44:53 +02:00
Emmanuel Gil Peyrot
80b7cf6ff8 util.cache: Support None for encode and decode. 2018-03-31 00:44:23 +02:00
Emmanuel Gil Peyrot
128cc2eeb4 XEP-0115: Use the new cache system. 2018-03-31 00:25:28 +02:00
Emmanuel Gil Peyrot
037912ee89 util.cache: New module handling both in-memory and on-file system caching. 2018-03-31 00:24:21 +02:00
Emmanuel Gil Peyrot
769bc6d3bf session: Also fire the session_start event. 2018-03-14 19:39:01 +01:00
Emmanuel Gil Peyrot
084d6cb5d9 session: Don’t bind if it is optional.
See https://tools.ietf.org/html/draft-cridland-xmpp-session-01
2018-03-14 18:54:17 +01:00
Emmanuel Gil Peyrot
5184713356 Rearm an iq callback if it was addressed to ourself. 2018-03-14 17:37:55 +01:00
Emmanuel Gil Peyrot
2f1225bad3 Carry the node attribute to the disco#info result.
Fixes #3323.
2018-03-14 16:25:21 +01:00
Emmanuel Gil Peyrot
841f5a5a5b xep_0363: Only send the basename() of the filename to the server. 2018-03-11 19:40:15 +01:00
Emmanuel Gil Peyrot
0c6de5e972 xep_0363: Simplify Content-Type guessing. 2018-03-11 19:39:51 +01:00
Emmanuel Gil Peyrot
81dc61c55c xep_0363: Fix max_file_size variable name. 2018-03-11 19:39:30 +01:00
Emmanuel Gil Peyrot
bd63b1ce70 Simplify usage of HTTP File Upload plugin.
This makes it usable only on Python 3.5, as documented.
2018-03-08 14:29:07 +01:00
Emmanuel Gil Peyrot
29faf114a7 Add max-file-size support to HTTP File Upload example. 2018-03-08 12:11:26 +01:00
Emmanuel Gil Peyrot
94ea8151d4 Add an HTTP File Upload example. 2018-03-08 03:39:23 +01:00
Emmanuel Gil Peyrot
66500ef5fb Add an HTTP File Upload plugin. 2018-03-08 03:38:59 +01:00
mathieui
979396bb1e asyncio.async has been scheduled for removal for a long time now
move to asyncio.ensure_future
2018-02-11 19:25:38 +01:00
mathieui
e177726387 Fix usage of the 0004 plugin interface
form['fields'] is an ordered list of fields while most plugins expect a
dict there. Fixes, among other things, a caps bug.
2018-02-11 16:42:59 +01:00
Emmanuel Gil Peyrot
20e88fda50 Fix typos, thanks codespell! 2018-01-10 02:18:07 +01:00
Emmanuel Gil Peyrot
f252be9b6d XEP-0115: Fix typo. 2018-01-05 18:03:42 +01:00
Emmanuel Gil Peyrot
ee98159586 Test all known python versions in travais 2017-12-27 15:49:52 +01:00
Emmanuel Gil Peyrot
c6443af29a stringprep: Make pure-Python punycode() return bytes.
Fixes #3366.
2017-12-27 15:48:42 +01:00
mathieui
d73f56a7af Release slixmpp 1.3.0 2017-11-28 20:16:08 +01:00
Emmanuel Gil Peyrot
7c7f4308c5 Add a Markup plugin. 2017-11-23 12:18:01 +00:00
mathieui
eab8c265f4 Record the current connection attempt in a future and allow cancellation
It does not make sense to have competing connection attempts, as the
XMLStream class is not designed for this. On slow and unpredictable
networks, it means we could have two c2s connections opened, leading to
mayhem.
2017-11-23 00:00:37 +01:00
Emmanuel Gil Peyrot
80b9cd43b1 MAM example: Also display the timestamp. 2017-10-24 10:54:53 +01:00
Emmanuel Gil Peyrot
af1f9e08ad Clean up the MAM example a bit. 2017-10-24 10:47:42 +01:00
Emmanuel Gil Peyrot
e3fd0af9c8 xep_0054: Fix parsing BINVAL element. 2017-10-08 15:42:48 +01:00
mathieui
27e23672c1 Update the MAM plugin for asyncio & new namespace
And add an example
2017-09-24 17:43:06 +02:00
mathieui
b38e229359 Update RSM for asyncio
- Use an async iterator
- Add a "recv_interface" parameter in order to differenciate the stanza
   we send from the stanza we receive (required for MAM)
- Add a pre_cb to run before sending the query stanza
- Add a post_cb to run after receiving the result stanza
2017-07-21 15:01:13 +02:00
Emmanuel Gil Peyrot
9a563f1425 XEP-0030: Optimise add_node usage a bit. 2017-07-17 22:46:48 +01:00
Emmanuel Gil Peyrot
8b6f5953a7 XEP-0319: Use the correct timezone.
This fixes a specification violation, XEP-0082 says that a date MUST
have a timezone, but we were sending the *local* time without any
timezone information.
2017-07-17 22:20:30 +01:00
Emmanuel Gil Peyrot
2d2a80c73d xmlstream: Remove pygments dumping.
It’s slow and makes the debug logs difficult to parse.
2017-07-17 21:17:02 +01:00
Mathias Ertl
4dfdd5d8e3 always define ssl_context 2017-05-24 13:18:22 +02:00
Mathias Ertl
1994ed3025 pass SSL context to TLS connections 2017-05-24 11:31:13 +02:00
Mathias Ertl
aaa45846d3 add function to explicitly get the ssl context 2017-05-24 11:31:13 +02:00
louiz’
d7ffcb54eb Merge remote-tracking branch 'samwhited/sslsocket_workaround' 2017-05-16 17:24:46 +02:00
Tom Wambold
c33749e57a Fixes port being set to 0 when connecting via hostname.
This seems to be the same issue as:

  https://dev.louiz.org/issues/3164

Using their suggested fix, if the DNS lookup doesn't return a port, use
the one passed in instead.
2017-05-08 15:58:28 -04:00
Emmanuel Gil Peyrot
e4107d8b4d sasl: Merge two bytes instead of concatenating them at runtime. 2017-04-28 21:26:03 +01:00
mathieui
da5cb72d3a Add XMPP classifier to setup.py 2017-04-10 02:24:14 +02:00
Emmanuel Gil Peyrot
c372bd5168 xmlstream: Warn when the parser is None when data is received. 2017-02-16 11:27:36 +00:00
mathieui
cabf623131 Fix the http over xmpp example 2017-02-14 01:04:38 +01:00
mathieui
ffc240d5b6 Fix the gtalk example 2017-02-14 01:04:27 +01:00
mathieui
cc4522d9cd Fix custom stanza examples 2017-02-14 01:00:41 +01:00
mathieui
5bf69dca76 Return a Future on clientxmpp.get_roster() 2017-02-14 00:46:36 +01:00
Emmanuel Gil Peyrot
59dad12820 XEP-0300: Workaround for Python 3.5 or below. 2017-02-11 23:30:43 +00:00
Emmanuel Gil Peyrot
007c836296 XEP-0300: Add rudimentary tests. 2017-02-11 04:02:44 +00:00
Emmanuel Gil Peyrot
3721bf9f6b Implement XEP-0300 (Use of Cryptographic Hash Functions in XMPP)
This is used to provide hash agility support and let other XEPs select
which hash function they support.
2017-02-11 04:02:20 +00:00
Cédric 'dek' Laudrel
802949eba8 fix small typo in README 2017-02-10 00:08:40 +01:00
mathieui
24f35e433f slixmpp 1.2.4 release 2017-01-30 23:02:45 +01:00
mathieui
22664ee7b8 Fix carbons 2017-01-28 00:02:27 +01:00
Clint Olson
6476cfcde5 Remove unused import caught by Codacy. 2017-01-23 23:58:53 -08:00
Clint Olson
5bb347e884 Fix partially-merged Google plugin from acc52fd935. 2017-01-23 23:51:59 -08:00
Emmanuel Gil Peyrot
eb1251b919 Fix a typo in the title of the MUC documentation. 2017-01-08 17:38:11 +00:00
Emmanuel Gil Peyrot
820144c40c Add missing asyncio.coroutine decorators. 2016-12-30 13:41:15 +01:00
Emmanuel Gil Peyrot
6034df0a78 Check for XML parsing errors and disconnect in that case. 2016-12-29 18:59:09 +01:00
Emmanuel Gil Peyrot
df4012e66d XMLStream: Break a long line to make it more readable. 2016-12-29 18:41:09 +01:00
Emmanuel Gil Peyrot
c372f3071a Examples: Use argparse for http_over_xmpp. 2016-12-29 18:34:37 +01:00
Emmanuel Gil Peyrot
829c8b27b6 Test more things before trying to build our stringprep module. 2016-12-25 13:28:51 +01:00
mathieui
fb3ac78bf9 slixmpp 1.2.3 2016-12-07 21:47:54 +01:00
mathieui
ffd9436e5c Fix roster push origin detection and tests 2016-12-07 19:06:25 +01:00
louiz’
bbb1344d79 Add very basic gitlab-ci.yml file 2016-12-05 00:13:13 +01:00
Emmanuel Gil Peyrot
457785b286 XEP-0380: Add a helper to test for the presence of an EME tag. 2016-11-26 16:41:48 +00:00
Emmanuel Gil Peyrot
4847f834bd Add a plugin for XEP-0380: Explicit Message Encryption. 2016-11-26 16:29:19 +00:00
mathieui
53191ff1cf slixmpp 1.2.2
Fix CVE-2015-8688, and a few bugfixes.
2016-11-21 21:46:02 +01:00
mathieui
ffdb6ffd69 Check origin of roster pushes
slixmpp is vulnerable to roster push attacks as described by Daniel
Gultsch at https://gultsch.de/gajim_roster_push_and_message_interception.html.

(CVE-2015-8688)
2016-11-21 21:42:51 +01:00
Emmanuel Gil Peyrot
7560db856b stringprep_profiles: Emit a correct StringPrepError on query + unassigned. 2016-10-27 06:44:38 +01:00
Emmanuel Gil Peyrot
63d245ac48 SASL: Fix traceback on non-hashing mechanism using channel binding. 2016-10-27 06:26:39 +01:00
Emmanuel Gil Peyrot
7ddd37be29 XEP-0323: Fix wrong import. 2016-10-27 06:23:08 +01:00
Emmanuel Gil Peyrot
a4d3a4a25e XEP-0313: Add missing setter argument. 2016-10-27 06:22:50 +01:00
mathieui
58bd07628b Add missing parameters in XEP-0222 and XEP-0223 2016-10-27 00:21:01 +02:00
mathieui
3569038493 XEP-0009: fix a traceback on recipient unavailable
(probably a past typo)
2016-10-27 00:18:43 +02:00
mathieui
20c4ff823a Add missing JID import in XEP-0079 and 0258 2016-10-27 00:17:29 +02:00
mathieui
8a7448a5a1 Add missing imports in XEP-0333 2016-10-27 00:15:25 +02:00
mathieui
d23d8f901e Fix a traceback on XEP-0221 del uri['value']
(typo)
2016-10-27 00:11:27 +02:00
Emmanuel Gil Peyrot
391f12eeab Transform an if into an elif in cert parsing. 2016-10-23 14:15:02 +01:00
Emmanuel Gil Peyrot
d008988843 Manual cleanup of the remaining set([…]) and set((…)). 2016-10-22 13:37:46 +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
Sam Whited
8b06aa1146 Fix fetching the SSL socket for Python 3.4 and 3.5 2016-10-06 13:00:17 -05: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
265 changed files with 3217 additions and 1336 deletions

21
.gitlab-ci.yml Normal file
View File

@@ -0,0 +1,21 @@
stages:
- test
- trigger
test:
stage: test
tags:
- docker
image: ubuntu:latest
script:
- apt update
- apt install -y python3 cython3 gpg
- ./run_tests.py
trigger_poezio:
stage: trigger
tags:
- docker
image: appropriate/curl:latest
script:
- curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline

View File

@@ -1,10 +1,9 @@
language: python
python:
- "2.6"
- "2.7"
- "3.2"
- "3.3"
- "3.4"
- "3.5"
- "3.6"
- "3.7-dev"
install:
- "pip install ."
script: testall.py

View File

@@ -1,5 +1,5 @@
Pre-requisites:
- Python 3.4
- Python 3.5+
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
Install:

View File

@@ -1,7 +1,7 @@
Slixmpp
#########
Slixmpp is an MIT licensed XMPP library for Python 3.4+. It is a fork of
Slixmpp is an MIT licensed XMPP library for Python 3.5+. It is a fork of
SleekXMPP.
Slixmpp's goals is to only rewrite the core of the library (the low level
@@ -36,7 +36,7 @@ The Slixmpp Boilerplate
-------------------------
Projects using Slixmpp tend to follow a basic pattern for setting up client/component
connections and configuration. Here is the gist of the boilerplate needed for a Slixmpp
based project. See the documetation or examples directory for more detailed archetypes for
based project. See the documentation or examples directory for more detailed archetypes for
Slixmpp projects::
import logging

View File

@@ -12,12 +12,17 @@
# serve to show the default.
import sys, os
import datetime
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('..'))
# get version automagically from source tree
from slixmpp.version import __version__ as version
release = ".".join(version.split(".")[0:2])
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
@@ -41,16 +46,18 @@ master_doc = 'index'
# General information about the project.
project = u'Slixmpp'
copyright = u'2011, Nathan Fritz, Lance Stout'
year = datetime.datetime.now().year
copyright = u'{}, Nathan Fritz, Lance Stout'.format(year)
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# auto imported from code!
# The short X.Y version.
version = '1.1'
# version = '1.4'
# The full version, including alpha/beta/rc tags.
release = '1.1'
# release = '1.4.0'
# 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

@@ -3,8 +3,8 @@
Differences from SleekXMPP
==========================
**Python 3.4+ only**
slixmpp will only work on python 3.4 and above.
**Python 3.5+ only**
slixmpp will only work on python 3.5 and above.
**Stanza copies**
The same stanza object is given through all the handlers; a handler that

View File

@@ -1,7 +1,7 @@
.. _mucbot:
=========================
Mulit-User Chat (MUC) Bot
Multi-User Chat (MUC) Bot
=========================
.. note::

View File

@@ -21,7 +21,7 @@ Slixmpp
which goal is to use asyncio instead of threads to handle networking. See
:ref:`differences`.
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.4+,
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.5+,
Slixmpp's design goals and philosphy are:

View File

@@ -15,7 +15,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
log = logging.getLogger(__name__)

View File

@@ -51,18 +51,17 @@ class AskConfirm(slixmpp.ClientXMPP):
else:
self.confirmed.set_result(True)
@asyncio.coroutine
def start(self, event):
async 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,
confirmed = await 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
confirmed = await self.confirmed
else:
confirmed = True
except IqError:

View File

@@ -50,7 +50,7 @@ class ActionBot(slixmpp.ClientXMPP):
register_stanza_plugin(Iq, Action)
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -73,7 +73,7 @@ class ActionBot(slixmpp.ClientXMPP):
"""
self.event('custom_action', iq)
def _handle_action_event(self, iq):
async def _handle_action_event(self, iq):
"""
Respond to the custom action event.
"""
@@ -82,17 +82,20 @@ class ActionBot(slixmpp.ClientXMPP):
if method == 'is_prime' and param == '2':
print("got message: %s" % iq)
iq.reply()
iq['action']['status'] = 'done'
iq.send()
rep = iq.reply()
rep['action']['status'] = 'done'
await rep.send()
elif method == 'bye':
print("got message: %s" % iq)
rep = iq.reply()
rep['action']['status'] = 'done'
await rep.send()
self.disconnect()
else:
print("got message: %s" % iq)
iq.reply()
iq['action']['status'] = 'error'
iq.send()
rep = iq.reply()
rep['action']['status'] = 'error'
await rep.send()
if __name__ == '__main__':
# Setup the command line arguments.

View File

@@ -43,7 +43,7 @@ class ActionUserBot(slixmpp.ClientXMPP):
register_stanza_plugin(Iq, Action)
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -57,11 +57,11 @@ class ActionUserBot(slixmpp.ClientXMPP):
data.
"""
self.send_presence()
self.get_roster()
await self.get_roster()
self.send_custom_iq()
await self.send_custom_iq()
def send_custom_iq(self):
async def send_custom_iq(self):
"""Create and send two custom actions.
If the first action was successful, then send
@@ -74,14 +74,14 @@ class ActionUserBot(slixmpp.ClientXMPP):
iq['action']['param'] = '2'
try:
resp = iq.send()
resp = await iq.send()
if resp['action']['status'] == 'done':
#sending bye
iq2 = self.Iq()
iq2['to'] = self.action_provider
iq2['type'] = 'set'
iq2['action']['method'] = 'bye'
iq2.send(block=False)
await iq2.send()
self.disconnect()
except XMPPError:

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

@@ -15,7 +15,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.xmlstream.asyncio import asyncio
class Disco(slixmpp.ClientXMPP):
@@ -54,8 +53,7 @@ class Disco(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -77,13 +75,13 @@ class Disco(slixmpp.ClientXMPP):
try:
if self.get in self.info_types:
# function using the callback parameter.
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
info = await self['xep_0030'].get_info(jid=self.target_jid,
node=self.target_node)
if self.get in self.items_types:
# The same applies from above. Listen for the
# disco_items event or pass a callback function
# if you need to process a non-blocking request.
items = yield from self['xep_0030'].get_items(jid=self.target_jid,
items = await self['xep_0030'].get_items(jid=self.target_jid,
node=self.target_node)
if self.get not in self.info_types and self.get not in self.items_types:
logging.error("Invalid disco request type.")

View File

@@ -47,8 +47,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.roster_received.set()
self.presences_received.clear()
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -65,16 +64,15 @@ class AvatarDownloader(slixmpp.ClientXMPP):
self.get_roster(callback=self.roster_received_cb)
print('Waiting for presence updates...\n')
yield from self.roster_received.wait()
await self.roster_received.wait()
print('Roster received')
yield from self.presences_received.wait()
await self.presences_received.wait()
self.disconnect()
@asyncio.coroutine
def on_vcard_avatar(self, pres):
async def on_vcard_avatar(self, pres):
print("Received vCard avatar update from %s" % pres['from'].bare)
try:
result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
result = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
timeout=5)
except XMPPError:
print("Error retrieving avatar for %s" % pres['from'])
@@ -89,14 +87,13 @@ class AvatarDownloader(slixmpp.ClientXMPP):
with open(filename, 'wb+') as img:
img.write(avatar['BINVAL'])
@asyncio.coroutine
def on_avatar(self, msg):
async def on_avatar(self, msg):
print("Received avatar update from %s" % msg['from'])
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
for info in metadata['items']:
if not info['url']:
try:
result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
result = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
timeout=5)
except XMPPError:
print("Error retrieving avatar for %s" % msg['from'])

View File

@@ -55,8 +55,8 @@ class GTalkBot(slixmpp.ClientXMPP):
cert.verify('talk.google.com', der_cert)
logging.debug("CERT: Found GTalk certificate")
except cert.CertificateError as err:
log.error(err.message)
self.disconnect(send_close=False)
logging.error(err.message)
self.disconnect()
def start(self, event):
"""

View File

@@ -13,7 +13,7 @@
from slixmpp import ClientXMPP
from optparse import OptionParser
from argparse import ArgumentParser
import logging
import getpass
@@ -23,7 +23,7 @@ class HTTPOverXMPPClient(ClientXMPP):
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
'session_start', self.session_start
)
self.add_event_handler('http_request', self.http_request_received)
self.add_event_handler('http_response', self.http_response_received)
@@ -58,40 +58,40 @@ if __name__ == '__main__':
# ./http_over_xmpp.py -J <jid> -P <pwd> -i <ip> -p <port> [-v]
#
parser = OptionParser()
parser = ArgumentParser()
# Output verbosity options.
parser.add_option(
parser.add_argument(
'-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')
parser.add_argument('-J', '--jid', dest='jid', help='JID')
parser.add_argument('-P', '--password', dest='password', help='Password')
# XMPP server ip and port options.
parser.add_option(
parser.add_argument(
'-i', '--ipaddr', dest='ipaddr',
help='IP Address of the XMPP server', default=None
)
parser.add_option(
parser.add_argument(
'-p', '--port', dest='port',
help='Port of the XMPP server', default=None
)
opts, args = parser.parse_args()
args = parser.parse_args()
# Setup logging.
logging.basicConfig(level=opts.loglevel,
logging.basicConfig(level=args.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: ')
if args.jid is None:
args.jid = input('Username: ')
if args.password is None:
args.password = getpass.getpass('Password: ')
xmpp = HTTPOverXMPPClient(opts.jid, opts.password)
xmpp = HTTPOverXMPPClient(args.jid, args.password)
xmpp.connect()
xmpp.process()

96
examples/http_upload.py Executable file
View File

@@ -0,0 +1,96 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2018 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
log = logging.getLogger(__name__)
class HttpUpload(slixmpp.ClientXMPP):
"""
A basic client asking an entity if they confirm the access to an HTTP URL.
"""
def __init__(self, jid, password, recipient, filename, domain=None):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.recipient = recipient
self.filename = filename
self.domain = domain
self.add_event_handler("session_start", self.start)
async def start(self, event):
log.info('Uploading file %s...', self.filename)
def timeout_callback(arg):
raise TimeoutError("could not send message in time")
url = await self['xep_0363'].upload_file(
self.filename, domain=self.domain, timeout=10, timeout_callback=timeout_callback)
log.info('Upload success!')
log.info('Sending file to %s', self.recipient)
html = '<body xmlns="http://www.w3.org/1999/xhtml"><a href="%s">%s</a></body>' % (url, url)
self.send_message(self.recipient, url, mhtml=html)
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("-f", "--file", required=True,
help="File to send")
parser.add_argument("--domain",
help="Domain to use for HTTP File Upload (leave out for your own servers)")
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 = HttpUpload(args.jid, args.password, args.recipient, args.file, args.domain)
xmpp.register_plugin('xep_0071')
xmpp.register_plugin('xep_0128')
xmpp.register_plugin('xep_0363')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser
@@ -39,8 +38,7 @@ class IBBSender(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -58,13 +56,13 @@ class IBBSender(slixmpp.ClientXMPP):
try:
# Open the IBB stream in which to write to.
stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
stream = await self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
# If you want to send in-memory bytes, use stream.sendall() instead.
yield from stream.sendfile(self.file, timeout=10)
await stream.sendfile(self.file, timeout=10)
# And finally close the stream.
yield from stream.close(timeout=10)
await stream.close(timeout=10)
except (IqError, IqTimeout):
print('File transfer errored')
else:

97
examples/mam.py Executable file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2017 Mathieu Pasquet
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
log = logging.getLogger(__name__)
class MAM(slixmpp.ClientXMPP):
"""
A basic client fetching mam archive messages
"""
def __init__(self, jid, password, remote_jid, start):
slixmpp.ClientXMPP.__init__(self, jid, password)
self.remote_jid = remote_jid
self.start_date = start
self.add_event_handler("session_start", self.start)
async def start(self, *args):
"""
Fetch mam results for the specified JID.
Use RSM to paginate the results.
"""
results = self.plugin['xep_0313'].retrieve(jid=self.remote_jid, iterator=True, rsm={'max': 10}, start=self.start_date)
page = 1
async for rsm in results:
print('Page %d' % page)
for msg in rsm['mam']['results']:
forwarded = msg['mam_result']['forwarded']
timestamp = forwarded['delay']['stamp']
message = forwarded['stanza']
print('[%s] %s: %s' % (timestamp, message['from'], message['body']))
page += 1
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", "--remote-jid", dest="remote_jid",
help="Remote JID")
parser.add_argument("--start", help="Start date", default='2017-09-20T12:00:00Z')
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.remote_jid is None:
args.remote_jid = input("Remote JID: ")
if args.start is None:
args.start = input("Start time: ")
xmpp = MAM(args.jid, args.password, args.remote_jid, args.start)
xmpp.register_plugin('xep_0313')
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process(forever=False)

120
examples/markup.py Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2010 Nathanael C. Fritz
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.plugins.xep_0394 import stanza as markup_stanza
class EchoBot(slixmpp.ClientXMPP):
"""
A simple Slixmpp bot that will echo messages it
receives, along with a short thank you message.
"""
def __init__(self, jid, password):
slixmpp.ClientXMPP.__init__(self, jid, password)
# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use. We want to
# listen for this event so that we we can initialize
# our roster.
self.add_event_handler("session_start", self.start)
# The message event is triggered whenever a message
# stanza is received. Be aware that that includes
# MUC messages and error messages.
self.add_event_handler("message", self.message)
def start(self, event):
"""
Process the session_start event.
Typical actions for the session_start event are
requesting the roster and broadcasting an initial
presence stanza.
Arguments:
event -- An empty dictionary. The session_start
event does not provide any additional
data.
"""
self.send_presence()
self.get_roster()
def message(self, msg):
"""
Process incoming message stanzas. Be aware that this also
includes MUC messages and error messages. It is usually
a good idea to check the messages's type before processing
or sending replies.
Arguments:
msg -- The received message stanza. See the documentation
for stanza objects and the Message stanza to see
how it may be used.
"""
body = msg['body']
new_body = self['xep_0394'].to_plain_text(body, msg['markup'])
xhtml = self['xep_0394'].to_xhtml_im(body, msg['markup'])
print('Plain text:', new_body)
print('XHTML-IM:', xhtml['body'])
message = msg.reply()
message['body'] = new_body
message['html']['body'] = xhtml['body']
self.send(message)
if __name__ == '__main__':
# Setup the command line arguments.
parser = ArgumentParser(description=EchoBot.__doc__)
# 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")
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: ")
# Setup the EchoBot and register plugins. Note that while plugins may
# have interdependencies, the order in which you register them does
# not matter.
xmpp = EchoBot(args.jid, args.password)
xmpp.register_plugin('xep_0030') # Service Discovery
xmpp.register_plugin('xep_0199') # XMPP Ping
xmpp.register_plugin('xep_0394') # Message Markup
# Connect to the XMPP server and start processing XMPP stanzas.
xmpp.connect()
xmpp.process()

View File

@@ -13,7 +13,6 @@ import logging
from getpass import getpass
from argparse import ArgumentParser
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp import asyncio
import slixmpp
@@ -38,8 +37,7 @@ class PingTest(slixmpp.ClientXMPP):
# our roster.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -56,7 +54,7 @@ class PingTest(slixmpp.ClientXMPP):
self.get_roster()
try:
rtt = yield from self['xep_0199'].ping(self.pingjid,
rtt = await self['xep_0199'].ping(self.pingjid,
timeout=10)
logging.info("Success! RTT: %s", rtt)
except IqError as e:

View File

@@ -5,7 +5,6 @@ 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
@@ -21,7 +20,7 @@ class PubsubClient(slixmpp.ClientXMPP):
self.register_plugin('xep_0059')
self.register_plugin('xep_0060')
self.actions = ['nodes', 'create', 'delete',
self.actions = ['nodes', 'create', 'delete', 'get_configure',
'publish', 'get', 'retract',
'purge', 'subscribe', 'unsubscribe']
@@ -32,80 +31,86 @@ class PubsubClient(slixmpp.ClientXMPP):
self.add_event_handler('session_start', self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
self.get_roster()
self.send_presence()
try:
yield from getattr(self, self.action)()
await getattr(self, self.action)()
except:
logging.error('Could not execute: %s', self.action)
logging.exception('Could not execute %s:', self.action)
self.disconnect()
def nodes(self):
async def nodes(self):
try:
result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
result = await self['xep_0060'].get_nodes(self.pubsub_server, self.node)
for item in result['disco_items']['items']:
logging.info(' - %s', str(item))
except XMPPError as error:
logging.error('Could not retrieve node list: %s', error.format())
def create(self):
async def create(self):
try:
yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
await 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):
async def delete(self):
try:
yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
await 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):
async def get_configure(self):
try:
configuration_form = await self['xep_0060'].get_node_config(self.pubsub_server, self.node)
logging.info('Configure form received from node %s: %s', self.node, configuration_form['pubsub_owner']['configure']['form'])
except XMPPError as error:
logging.error('Could not retrieve configure form from node %s: %s', self.node, error.format())
async def publish(self):
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
try:
result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
result = await 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):
async def get(self):
try:
result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
result = await self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
for item in result['pubsub']['items']['substanzas']:
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):
async def retract(self):
try:
yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
await 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):
async def purge(self):
try:
yield from self['xep_0060'].purge(self.pubsub_server, self.node)
await 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):
async def subscribe(self):
try:
iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
iq = await 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):
async def unsubscribe(self):
try:
yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
await 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())
@@ -118,7 +123,7 @@ if __name__ == '__main__':
parser = ArgumentParser()
parser.version = '%%prog 0.1'
parser.usage = "Usage: %%prog [options] <jid> " + \
'nodes|create|delete|purge|subscribe|unsubscribe|publish|retract|get' + \
'nodes|create|delete|get_configure|purge|subscribe|unsubscribe|publish|retract|get' + \
' [<node> <data>]'
parser.add_argument("-q","--quiet", help="set logging to ERROR",
@@ -139,7 +144,7 @@ if __name__ == '__main__':
help="password to use")
parser.add_argument("server")
parser.add_argument("action", choices=["nodes", "create", "delete", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
parser.add_argument("action", choices=["nodes", "create", "delete", "get_configure", "purge", "subscribe", "unsubscribe", "publish", "retract", "get"])
parser.add_argument("node", nargs='?')
parser.add_argument("data", nargs='?')

View File

@@ -66,7 +66,7 @@ class RegisterBot(slixmpp.ClientXMPP):
# We're only concerned about registering, so nothing more to do here.
self.disconnect()
def register(self, iq):
async def register(self, iq):
"""
Fill out and submit a registration form.
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
resp['register']['password'] = self.password
try:
yield from resp.send()
await resp.send()
logging.info("Account created for %s!" % self.boundjid)
except IqError as e:
logging.error("Could not register account: %s" %

View File

@@ -38,8 +38,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
self.received = set()
self.presences_received = asyncio.Event()
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -57,7 +56,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
future.set_result(None)
try:
self.get_roster(callback=callback)
yield from future
await future
except IqError as err:
print('Error: %s' % err.iq['error']['condition'])
except IqTimeout:
@@ -66,7 +65,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
print('Waiting for presence updates...\n')
yield from asyncio.sleep(10)
await asyncio.sleep(10)
print('Roster for %s' % self.boundjid.bare)
groups = self.client_roster.groups()

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser

View File

@@ -9,7 +9,6 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from getpass import getpass
from argparse import ArgumentParser
@@ -36,8 +35,7 @@ class S5BSender(slixmpp.ClientXMPP):
# and the XML streams are ready for use.
self.add_event_handler("session_start", self.start)
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -53,14 +51,14 @@ class S5BSender(slixmpp.ClientXMPP):
try:
# Open the S5B stream in which to write to.
proxy = yield from self['xep_0065'].handshake(self.receiver)
proxy = await 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)
await proxy.write(data)
# And finally close the stream.
proxy.transport.write_eof()

View File

@@ -18,7 +18,6 @@ from argparse import ArgumentParser
import slixmpp
from slixmpp.exceptions import XMPPError
from slixmpp import asyncio
class AvatarSetter(slixmpp.ClientXMPP):
@@ -33,8 +32,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
self.filepath = filepath
@asyncio.coroutine
def start(self, event):
async def start(self, event):
"""
Process the session_start event.
@@ -68,20 +66,20 @@ class AvatarSetter(slixmpp.ClientXMPP):
used_xep84 = False
print('Publish XEP-0084 avatar data')
result = yield from self['xep_0084'].publish_avatar(avatar)
result = await self['xep_0084'].publish_avatar(avatar)
if isinstance(result, XMPPError):
print('Could not publish XEP-0084 avatar')
else:
used_xep84 = True
print('Update vCard with avatar')
result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
if isinstance(result, XMPPError):
print('Could not set vCard avatar')
if used_xep84:
print('Advertise XEP-0084 avatar metadata')
result = yield from self['xep_0084'].publish_avatar_metadata([
result = await self['xep_0084'].publish_avatar_metadata([
{'id': avatar_id,
'type': avatar_type,
'bytes': avatar_bytes}

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, check_output, CalledProcessError
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__
@@ -34,12 +29,49 @@ CLASSIFIERS = [
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: XMPP',
'Topic :: Software Development :: Libraries :: Python Modules',
]
packages = [str(mod.parent) for mod in Path('slixmpp').rglob('__init__.py')]
def check_include(library_name, header):
command = [os.environ.get('PKG_CONFIG', 'pkg-config'), '--cflags', library_name]
try:
cflags = check_output(command).decode('utf-8').split()
except FileNotFoundError:
print('pkg-config not found.')
return False
except CalledProcessError:
# pkg-config already prints the missing libraries on stderr.
return False
command = [os.environ.get('CC', 'cc')] + cflags + ['-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:
print('%s headers not found.' % library_name)
return False
HAS_PYTHON_HEADERS = check_include('python3', 'Python.h')
HAS_STRINGPREP_HEADERS = check_include('libidn', 'stringprep.h')
ext_modules = None
if HAS_PYTHON_HEADERS and HAS_STRINGPREP_HEADERS:
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('Falling back to the slow stringprep module.')
setup(
name="slixmpp",
version=VERSION,
@@ -52,7 +84,7 @@ setup(
platforms=['any'],
packages=packages,
ext_modules=ext_modules,
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'],
classifiers=CLASSIFIERS,
cmdclass={'test': TestCommand}
)

View File

@@ -6,12 +6,13 @@
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())
import asyncio
# Required for python < 3.7 to use the old ssl implementation
# and manage to do starttls as an unintended side effect
asyncio.sslproto._is_sslproto_available = lambda: False
from slixmpp.stanza import Message, Presence, Iq
from slixmpp.jid import JID, InvalidJID

View File

@@ -15,6 +15,7 @@
import asyncio
import logging
from slixmpp.jid import JID
from slixmpp.stanza import StreamFeatures
from slixmpp.basexmpp import BaseXMPP
from slixmpp.exceptions import XMPPError
@@ -108,10 +109,21 @@ class ClientXMPP(BaseXMPP):
CoroutineCallback('Stream Features',
MatchXPath('{%s}features' % self.stream_ns),
self._handle_stream_features))
def roster_push_filter(iq):
from_ = iq['from']
if from_ and from_ != JID('') and from_ != self.boundjid.bare:
reply = iq.reply()
reply['type'] = 'error'
reply['error']['type'] = 'cancel'
reply['error']['code'] = 503
reply['error']['condition'] = 'service-unavailable'
reply.send()
return
self.event('roster_update', iq)
self.register_handler(
Callback('Roster Update',
StanzaPath('iq@type=set/roster'),
lambda iq: self.event('roster_update', iq)))
roster_push_filter))
# Setup default stream features
self.register_plugin('feature_starttls')
@@ -243,7 +255,7 @@ class ClientXMPP(BaseXMPP):
orig_cb(resp)
callback = wrapped
iq.send(callback, timeout, timeout_callback)
return iq.send(callback, timeout, timeout_callback)
def _reset_connection_state(self, event=None):
#TODO: Use stream state here
@@ -253,8 +265,7 @@ class ClientXMPP(BaseXMPP):
self.bindfail = False
self.features = set()
@asyncio.coroutine
def _handle_stream_features(self, features):
async def _handle_stream_features(self, features):
"""Process the received stream features.
:param features: The features stanza.
@@ -263,7 +274,7 @@ class ClientXMPP(BaseXMPP):
if name in features['features']:
handler, restart = self._stream_feature_handlers[name]
if asyncio.iscoroutinefunction(handler):
result = yield from handler(features)
result = await handler(features)
else:
result = handler(features)
if result and restart:

View File

@@ -35,8 +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):
async def _handle_bind_resource(self, features):
"""
Handle requesting a specific resource.
@@ -51,7 +50,7 @@ class FeatureBind(BasePlugin):
if self.xmpp.requested_jid.resource:
iq['bind']['resource'] = self.xmpp.requested_jid.resource
yield from iq.send(callback=self._on_bind_response)
await 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

@@ -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'
@@ -97,13 +97,7 @@ class FeatureMechanisms(BasePlugin):
jid = self.xmpp.requested_jid.bare
result[value] = creds.get('email', jid)
elif value == 'channel_binding':
if hasattr(self.xmpp.socket, 'get_channel_binding'):
result[value] = self.xmpp.socket.get_channel_binding()
else:
log.debug("Channel binding not supported.")
log.debug("Use Python 3.3+ for channel binding and " + \
"SCRAM-SHA-1-PLUS support")
result[value] = None
result[value] = self.xmpp.socket.get_channel_binding()
elif value == 'host':
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
elif value == 'realm':
@@ -122,7 +116,7 @@ class FeatureMechanisms(BasePlugin):
if value == 'encrypted':
if 'starttls' in self.xmpp.features:
result[value] = True
elif isinstance(self.xmpp.socket, ssl.SSLSocket):
elif isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
result[value] = True
else:
result[value] = False

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

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

@@ -35,18 +35,22 @@ class FeatureSession(BasePlugin):
register_stanza_plugin(Iq, stanza.Session)
register_stanza_plugin(StreamFeatures, stanza.Session)
@asyncio.coroutine
def _handle_start_session(self, features):
async def _handle_start_session(self, features):
"""
Handle the start of the session.
Arguments:
feature -- The stream features element.
"""
if features['session']['optional']:
self.xmpp.sessionstarted = True
self.xmpp.event('session_start')
return
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq.enable('session')
yield from iq.send(callback=self._on_start_session_response)
await iq.send(callback=self._on_start_session_response)
def _on_start_session_response(self, response):
self.xmpp.features.add('session')

View File

@@ -6,7 +6,7 @@
See the file LICENSE for copying permission.
"""
from slixmpp.xmlstream import ElementBase
from slixmpp.xmlstream import ElementBase, ET
class Session(ElementBase):
@@ -16,5 +16,19 @@ class Session(ElementBase):
name = 'session'
namespace = 'urn:ietf:params:xml:ns:xmpp-session'
interfaces = set()
interfaces = {'optional'}
plugin_attrib = 'session'
def get_optional(self):
return self.xml.find('{%s}optional' % self.namespace) is not None
def set_optional(self, value):
if value:
optional = ET.Element('{%s}optional' % self.namespace)
self.xml.append(optional)
else:
self.del_optional()
def del_optional(self):
optional = self.xml.find('{%s}optional' % self.namespace)
self.xml.remove(optional)

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

@@ -12,7 +12,7 @@ from slixmpp.stanza import StreamFeatures
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.handler import CoroutineCallback
from slixmpp.features.feature_starttls import stanza
@@ -28,7 +28,7 @@ class FeatureSTARTTLS(BasePlugin):
def plugin_init(self):
self.xmpp.register_handler(
Callback('STARTTLS Proceed',
CoroutineCallback('STARTTLS Proceed',
MatchXPath(stanza.Proceed.tag_name()),
self._handle_starttls_proceed,
instream=True))
@@ -58,8 +58,8 @@ class FeatureSTARTTLS(BasePlugin):
self.xmpp.send(features['starttls'])
return True
def _handle_starttls_proceed(self, proceed):
async def _handle_starttls_proceed(self, proceed):
"""Restart the XML stream when TLS is accepted."""
log.debug("Starting TLS")
if self.xmpp.start_tls():
if await self.xmpp.start_tls():
self.xmpp.features.add('starttls')

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)
@@ -346,30 +346,10 @@ class JID:
def node(self):
return self._node
@property
def user(self):
return self._node
@property
def local(self):
return self._node
@property
def username(self):
return self._node
@property
def domain(self):
return self._domain
@property
def server(self):
return self._domain
@property
def host(self):
return self._domain
@property
def resource(self):
return self._resource
@@ -382,45 +362,16 @@ class JID:
def full(self):
return self._full
@property
def jid(self):
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):
node, domain, resource = _parse_jid(value)
@@ -439,10 +390,14 @@ class JID:
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()
user = node
local = node
username = node
server = domain
host = domain
jid = full
def __str__(self):
"""Use the full JID as the string value."""

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

View File

@@ -0,0 +1,10 @@
"""
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.plugins.google.auth import stanza
from slixmpp.plugins.google.auth.auth import GoogleAuth

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

View File

@@ -13,7 +13,7 @@ class GoogleAuth(ElementBase):
name = 'auth'
namespace = 'http://www.google.com/talk/protocol/auth'
plugin_attrib = 'google'
interfaces = set(['client_uses_full_bind_result', 'service'])
interfaces = {'client_uses_full_bind_result', 'service'}
discovery_attr= '{%s}client-uses-full-bind-result' % namespace
service_attr= '{%s}service' % namespace

View File

@@ -0,0 +1,10 @@
"""
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.plugins.google.gmail import stanza
from slixmpp.plugins.google.gmail.notifications import Gmail

View File

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

View File

@@ -0,0 +1,10 @@
"""
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.plugins.google.nosave import stanza
from slixmpp.plugins.google.nosave.nosave import GoogleNoSave

View File

@@ -0,0 +1,78 @@
"""
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, Message
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.nosave import stanza
class GoogleNoSave(BasePlugin):
"""
Google: Off the Record Chats
NOTE: This is NOT an encryption method.
Also see <https://developers.google.com/talk/jep_extensions/otr>.
"""
name = 'google_nosave'
description = 'Google: Off the Record Chats'
dependencies = set(['google_settings'])
stanza = stanza
def plugin_init(self):
register_stanza_plugin(Message, stanza.NoSave)
register_stanza_plugin(Iq, stanza.NoSaveQuery)
self.xmpp.register_handler(
Callback('Google Nosave',
StanzaPath('iq@type=set/google_nosave'),
self._handle_nosave_change))
def plugin_end(self):
self.xmpp.remove_handler('Google Nosave')
def enable(self, jid=None, timeout=None, callback=None):
if jid is None:
self.xmpp['google_settings'].update({'archiving_enabled': False},
timeout=timeout, callback=callback)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['google_nosave']['item']['jid'] = jid
iq['google_nosave']['item']['value'] = True
return iq.send(timeout=timeout, callback=callback)
def disable(self, jid=None, timeout=None, callback=None):
if jid is None:
self.xmpp['google_settings'].update({'archiving_enabled': True},
timeout=timeout, callback=callback)
else:
iq = self.xmpp.Iq()
iq['type'] = 'set'
iq['google_nosave']['item']['jid'] = jid
iq['google_nosave']['item']['value'] = False
return iq.send(timeout=timeout, callback=callback)
def get(self, timeout=None, callback=None):
iq = self.xmpp.Iq()
iq['type'] = 'get'
iq.enable('google_nosave')
return iq.send(timeout=timeout, callback=callback)
def _handle_nosave_change(self, iq):
reply = self.xmpp.Iq()
reply['type'] = 'result'
reply['id'] = iq['id']
reply['to'] = iq['from']
reply.send()
self.xmpp.event('google_nosave_change', iq)

View File

@@ -14,7 +14,7 @@ class NoSave(ElementBase):
name = 'x'
namespace = 'google:nosave'
plugin_attrib = 'google_nosave'
interfaces = set(['value'])
interfaces = {'value'}
def get_value(self):
return self._get_attr('value', '') == 'enabled'
@@ -35,7 +35,7 @@ class Item(ElementBase):
namespace = 'google:nosave'
plugin_attrib = 'item'
plugin_multi_attrib = 'items'
interfaces = set(['jid', 'source', 'value'])
interfaces = {'jid', 'source', 'value'}
def get_value(self):
return self._get_attr('value', '') == 'enabled'

View File

@@ -0,0 +1,10 @@
"""
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.plugins.google.settings import stanza
from slixmpp.plugins.google.settings.settings import GoogleSettings

View File

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

View File

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

@@ -14,21 +14,21 @@ class FormField(ElementBase):
name = 'field'
plugin_attrib = 'field'
plugin_multi_attrib = 'fields'
interfaces = set(('answer', 'desc', 'required', 'value',
'label', 'type', 'var'))
sub_interfaces = set(('desc',))
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):
@@ -164,8 +164,8 @@ 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'

View File

@@ -23,9 +23,9 @@ class Form(ElementBase):
namespace = 'jabber:x:data'
name = 'x'
plugin_attrib = 'form'
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
sub_interfaces = set(('title',))
form_types = set(('cancel', 'form', 'result', 'submit'))
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', 'values'))
sub_interfaces = {'title'}
form_types = {'cancel', 'form', 'result', 'submit'}
def __init__(self, *args, **kwargs):
title = None

View File

@@ -92,13 +92,13 @@ def _py2xml(*args):
def xml2py(params):
namespace = 'jabber:iq:rpc'
vals = []
for param in params.xml.findall('{%s}param' % namespace):
for param in params.findall('{%s}param' % namespace):
vals.append(_xml2py(param.find('{%s}value' % namespace)))
return vals
def _xml2py(value):
namespace = 'jabber:iq:rpc'
find_value = value.xml.find
find_value = value.find
if find_value('{%s}nil' % namespace) is not None:
return None
if find_value('{%s}i4' % namespace) is not None:

View File

@@ -61,7 +61,7 @@ def _intercept(method, name, public):
except InvocationException:
raise
except Exception as e:
raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e)
raise InvocationException("A problem occurred calling %s.%s!" % (instance.FQN(), method.__name__), e)
_resolver._rpc = public
_resolver._rpc_name = method.__name__ if name is None else name
return _resolver
@@ -405,8 +405,10 @@ class Proxy(Endpoint):
self._callback = callback
def __getattribute__(self, name, *args):
if name in ('__dict__', '_endpoint', 'async', '_callback'):
if name in ('__dict__', '_endpoint', '_callback'):
return object.__getattribute__(self, name)
elif name == 'async':
return lambda callback: Proxy(self._endpoint, callback)
else:
attribute = self._endpoint.__getattribute__(name)
if hasattr(attribute, '__call__'):
@@ -420,9 +422,6 @@ class Proxy(Endpoint):
pass # If the attribute doesn't exist, don't care!
return attribute
def async(self, callback):
return Proxy(self._endpoint, callback)
def get_endpoint(self):
'''
Returns the proxified endpoint.
@@ -696,7 +695,7 @@ class RemoteSession(object):
e = {
'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])),
'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])),
'undefined-condition': RemoteException("An unexpected problem occurred trying to invoke %s at %s!" % (pmethod, iq['from'])),
}[condition]
if e is None:
RemoteException("An unexpected exception occurred at %s!" % iq['from'])

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):
@@ -121,7 +121,7 @@ class XEP_0009(BasePlugin):
def _recipient_unvailable(self, iq):
payload = iq.get_payload()
iq = iq.reply()
error().set_payload(payload)
iq.error().set_payload(payload)
iq['error']['code'] = '404'
iq['error']['type'] = 'wait'
iq['error']['condition'] = 'recipient-unavailable'

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

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

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

@@ -6,6 +6,7 @@
See the file LICENSE for copying permission.
"""
import asyncio
import logging
from slixmpp import Iq
@@ -123,6 +124,8 @@ class XEP_0030(BasePlugin):
for op in self._disco_ops:
self.api.register(getattr(self.static, op), op, default=True)
self.domain_infos = {}
def session_bind(self, jid):
self.add_feature('http://jabber.org/protocol/disco#info')
@@ -295,6 +298,35 @@ class XEP_0030(BasePlugin):
'cached': cached}
return self.api['has_identity'](jid, node, ifrom, data)
async def get_info_from_domain(self, domain=None, timeout=None,
cached=True, callback=None):
if domain is None:
domain = self.xmpp.boundjid.domain
if not cached or domain not in self.domain_infos:
infos = [self.get_info(
domain, timeout=timeout)]
iq_items = await self.get_items(
domain, timeout=timeout)
items = iq_items['disco_items']['items']
infos += [
self.get_info(item[0], timeout=timeout)
for item in items]
info_futures, _ = await asyncio.wait(
infos,
timeout=timeout,
loop=self.xmpp.loop
)
self.domain_infos[domain] = [
future.result() for future in info_futures if not future.exception()]
results = self.domain_infos[domain]
if callback is not None:
callback(results)
return results
@future_wrapper
def get_info(self, jid=None, node=None, local=None,
cached=None, **kwargs):
@@ -646,9 +678,11 @@ class XEP_0030(BasePlugin):
info['id'] = iq['id']
info.send()
else:
node = iq['disco_info']['node']
iq = iq.reply()
if info:
info = self._fix_default_info(info)
info['node'] = node
iq.set_payload(info.xml)
iq.send()
elif iq['type'] == 'result':

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

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):
"""
@@ -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

@@ -66,10 +66,11 @@ class StaticDisco(object):
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
new_node = {'info': DiscoInfo(), 'items': DiscoItems()}
new_node['info']['node'] = node
new_node['items']['node'] = node
self.nodes[(jid, node, ifrom)] = new_node
return self.nodes[(jid, node, ifrom)]
def get_node(self, jid=None, node=None, ifrom=None):
if jid is None:
@@ -208,8 +209,8 @@ class StaticDisco(object):
The data parameter is a disco#info substanza.
"""
self.add_node(jid, node)
self.get_node(jid, node)['info'] = data
new_node = self.add_node(jid, node)
new_node['info'] = data
def del_info(self, jid, node, ifrom, data):
"""
@@ -242,8 +243,8 @@ class StaticDisco(object):
items -- A set of items in tuple format.
"""
items = data.get('items', set())
self.add_node(jid, node)
self.get_node(jid, node)['items']['items'] = items
new_node = self.add_node(jid, node)
new_node['items']['items'] = items
def del_items(self, jid, node, ifrom, data):
"""
@@ -256,7 +257,7 @@ class StaticDisco(object):
def add_identity(self, jid, node, ifrom, data):
"""
Add a new identity to te JID/node combination.
Add a new identity to the JID/node combination.
The data parameter may provide:
category -- The general category to which the agent belongs.
@@ -264,8 +265,8 @@ class StaticDisco(object):
name -- Optional human readable name for this identity.
lang -- Optional standard xml:lang value.
"""
self.add_node(jid, node)
self.get_node(jid, node)['info'].add_identity(
new_node = self.add_node(jid, node)
new_node['info'].add_identity(
data.get('category', ''),
data.get('itype', ''),
data.get('name', None),
@@ -280,8 +281,8 @@ class StaticDisco(object):
(category, type, name, lang)
"""
identities = data.get('identities', set())
self.add_node(jid, node)
self.get_node(jid, node)['info']['identities'] = identities
new_node = self.add_node(jid, node)
new_node['info']['identities'] = identities
def del_identity(self, jid, node, ifrom, data):
"""
@@ -316,8 +317,8 @@ class StaticDisco(object):
The data parameter should include:
feature -- The namespace of the supported feature.
"""
self.add_node(jid, node)
self.get_node(jid, node)['info'].add_feature(
new_node = self.add_node(jid, node)
new_node['info'].add_feature(
data.get('feature', ''))
def set_features(self, jid, node, ifrom, data):
@@ -328,8 +329,8 @@ class StaticDisco(object):
features -- The new set of supported features.
"""
features = data.get('features', set())
self.add_node(jid, node)
self.get_node(jid, node)['info']['features'] = features
new_node = self.add_node(jid, node)
new_node['info']['features'] = features
def del_feature(self, jid, node, ifrom, data):
"""
@@ -362,8 +363,8 @@ class StaticDisco(object):
non-addressable items.
name -- Optional human readable name for the item.
"""
self.add_node(jid, node)
self.get_node(jid, node)['items'].add_item(
new_node = self.add_node(jid, node)
new_node['items'].add_item(
data.get('ijid', ''),
node=data.get('inode', ''),
name=data.get('name', ''))
@@ -392,8 +393,8 @@ class StaticDisco(object):
if isinstance(data, Iq):
data = data['disco_info']
self.add_node(jid, node, ifrom)
self.get_node(jid, node, ifrom)['info'] = data
new_node = self.add_node(jid, node, ifrom)
new_node['info'] = data
def get_cached_info(self, jid, node, ifrom, data):
"""

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,9 +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:
if atype == 'all':
Addresses.interfaces.add('addresses')
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,9 +25,9 @@ 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 get_xml_item(self):
item = self.xml.find('{http://jabber.org/protocol/muc#user}item')
@@ -117,7 +117,7 @@ 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 = {}

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

@@ -31,8 +31,7 @@ class IBBytestream(object):
self.recv_queue = asyncio.Queue()
@asyncio.coroutine
def send(self, data, timeout=None):
async def send(self, data, timeout=None):
if not self.stream_started or self.stream_out_closed:
raise socket.error
if len(data) > self.block_size:
@@ -56,22 +55,20 @@ class IBBytestream(object):
iq['ibb_data']['sid'] = self.sid
iq['ibb_data']['seq'] = seq
iq['ibb_data']['data'] = data
yield from iq.send(timeout=timeout)
await iq.send(timeout=timeout)
return len(data)
@asyncio.coroutine
def sendall(self, data, timeout=None):
async def sendall(self, data, timeout=None):
sent_len = 0
while sent_len < len(data):
sent_len += yield from self.send(data[sent_len:self.block_size], timeout=timeout)
sent_len += await self.send(data[sent_len:self.block_size], timeout=timeout)
@asyncio.coroutine
def sendfile(self, file, timeout=None):
async def sendfile(self, file, timeout=None):
while True:
data = file.read(self.block_size)
if not data:
break
yield from self.send(data, timeout=timeout)
await self.send(data, timeout=timeout)
def _recv_data(self, stanza):
new_seq = stanza['ibb_data']['seq']

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,

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

@@ -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
@@ -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
@@ -611,7 +611,7 @@ class XEP_0050(BasePlugin):
def terminate_command(self, session):
"""
Delete a command's session after a command has completed
or an error has occured.
or an error has occurred.
Arguments:
session -- All stored data relevant to the current

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):
"""

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,13 +118,13 @@ 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().setup(xml=xml)
@@ -143,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)
@@ -177,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
@@ -186,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)
@@ -218,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
@@ -227,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
@@ -236,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
@@ -244,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):
@@ -261,7 +261,7 @@ class BinVal(ElementBase):
def get_binval(self):
parent = self.parent()
xml = parent.find('{%s}BINVAL' % self.namespace)
xml = parent.xml.find('{%s}BINVAL' % self.namespace)
if xml is not None:
return base64.b64decode(bytes(xml.text))
return b''
@@ -275,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
@@ -284,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):
@@ -314,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):
@@ -336,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):
@@ -358,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):
@@ -373,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):
@@ -388,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):
@@ -403,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):
@@ -418,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):
@@ -433,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):
@@ -448,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):
@@ -463,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):
@@ -478,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):
@@ -493,7 +493,7 @@ class Agent(ElementBase):
namespace = 'vcard-temp'
plugin_attrib = name
plugin_multi_attrib = 'agents'
interfaces = set(['EXTVAL'])
interfaces = {'EXTVAL'}
sub_interfaces = interfaces
@@ -502,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):
@@ -517,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):

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):
@@ -123,7 +123,7 @@ class XEP_0054(BasePlugin):
if iq['type'] == 'result':
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
return
elif iq['type'] == 'get':
elif iq['type'] == 'get' and self.xmpp.is_component:
vcard = self.api['get_vcard'](iq['from'].bare)
if isinstance(vcard, Iq):
vcard.send()

View File

@@ -19,23 +19,27 @@ from slixmpp.exceptions import XMPPError
log = logging.getLogger(__name__)
class ResultIterator():
class ResultIterator:
"""
An iterator for Result Set Managment
An iterator for Result Set Management
"""
def __init__(self, query, interface, results='substanzas', amount=10,
start=None, reverse=False):
start=None, reverse=False, recv_interface=None,
pre_cb=None, post_cb=None):
"""
Arguments:
query -- The template query
interface -- The substanza of the query, for example disco_items
interface -- The substanza of the query to send, for example disco_items
recv_interface -- The substanza of the query to receive, for example disco_items
results -- The query stanza's interface which provides a
countable list of query results.
amount -- The max amounts of items to request per iteration
start -- From which item id to start
reverse -- If True, page backwards through the results
pre_cb -- Callback to run before sending the stanza
post_cb -- Callback to run after receiving the reply
Example:
q = Iq()
@@ -49,17 +53,23 @@ class ResultIterator():
self.amount = amount
self.start = start
self.interface = interface
if recv_interface:
self.recv_interface = recv_interface
else:
self.recv_interface = interface
self.pre_cb = pre_cb
self.post_cb = post_cb
self.results = results
self.reverse = reverse
self._stop = False
def __iter__(self):
def __aiter__(self):
return self
def __next__(self):
return self.next()
async def __anext__(self):
return await self.next()
def next(self):
async def next(self):
"""
Return the next page of results from a query.
@@ -68,7 +78,7 @@ class ResultIterator():
of items.
"""
if self._stop:
raise StopIteration
raise StopAsyncIteration
self.query[self.interface]['rsm']['before'] = self.reverse
self.query['id'] = self.query.stream.new_id()
self.query[self.interface]['rsm']['max'] = str(self.amount)
@@ -79,28 +89,32 @@ class ResultIterator():
self.query[self.interface]['rsm']['after'] = self.start
try:
r = self.query.send(block=True)
if self.pre_cb:
self.pre_cb(self.query)
r = await self.query.send()
if not r[self.interface]['rsm']['first'] and \
not r[self.interface]['rsm']['last']:
raise StopIteration
if not r[self.recv_interface]['rsm']['first'] and \
not r[self.recv_interface]['rsm']['last']:
raise StopAsyncIteration
if r[self.interface]['rsm']['count'] and \
r[self.interface]['rsm']['first_index']:
count = int(r[self.interface]['rsm']['count'])
first = int(r[self.interface]['rsm']['first_index'])
num_items = len(r[self.interface][self.results])
if r[self.recv_interface]['rsm']['count'] and \
r[self.recv_interface]['rsm']['first_index']:
count = int(r[self.recv_interface]['rsm']['count'])
first = int(r[self.recv_interface]['rsm']['first_index'])
num_items = len(r[self.recv_interface][self.results])
if first + num_items == count:
self._stop = True
if self.reverse:
self.start = r[self.interface]['rsm']['first']
self.start = r[self.recv_interface]['rsm']['first']
else:
self.start = r[self.interface]['rsm']['last']
self.start = r[self.recv_interface]['rsm']['last']
if self.post_cb:
self.post_cb(r)
return r
except XMPPError:
raise StopIteration
raise StopAsyncIteration
class XEP_0059(BasePlugin):
@@ -111,7 +125,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):
@@ -127,7 +141,8 @@ class XEP_0059(BasePlugin):
def session_bind(self, jid):
self.xmpp['xep_0030'].add_feature(Set.namespace)
def iterate(self, stanza, interface, results='substanzas'):
def iterate(self, stanza, interface, results='substanzas',
recv_interface=None, pre_cb=None, post_cb=None):
"""
Create a new result set iterator for a given stanza query.
@@ -137,9 +152,23 @@ class XEP_0059(BasePlugin):
basic disco#items query.
interface -- The name of the substanza to which the
result set management stanza should be
appended. For example, for disco#items queries
the interface 'disco_items' should be used.
appended in the query stanza. For example,
for disco#items queries the interface
'disco_items' should be used.
recv_interface -- The name of the substanza from which the
result set management stanza should be
read in the result stanza. If unspecified,
it will be set to the same value as the
``interface`` parameter.
pre_cb -- Callback to run before sending each stanza e.g.
setting the MAM queryid and starting a stanza
collector.
post_cb -- Callback to run after receiving each stanza e.g.
stopping a MAM stanza collector in order to
gather results.
results -- The name of the interface containing the
query results (typically just 'substanzas').
"""
return ResultIterator(stanza, interface, results)
return ResultIterator(stanza, interface, results,
recv_interface=recv_interface, pre_cb=pre_cb,
post_cb=post_cb)

View File

@@ -13,7 +13,7 @@ from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
class Set(ElementBase):
"""
XEP-0059 (Result Set Managment) can be used to manage the
XEP-0059 (Result Set Management) can be used to manage the
results of queries. For example, limiting the number of items
per response or starting at certain positions.
@@ -64,10 +64,10 @@ 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.xml.find("{%s}first" % (self.namespace))

View File

@@ -26,7 +26,7 @@ class XEP_0060(BasePlugin):
name = 'xep_0060'
description = 'XEP-0060: Publish-Subscribe'
dependencies = set(['xep_0030', 'xep_0004', 'xep_0082', 'xep_0131'])
dependencies = {'xep_0030', 'xep_0004', 'xep_0082', 'xep_0131'}
stanza = stanza
def plugin_init(self):
@@ -185,14 +185,14 @@ class XEP_0060(BasePlugin):
if config is not None:
form_type = 'http://jabber.org/protocol/pubsub#node_config'
if 'FORM_TYPE' in config['fields']:
if 'FORM_TYPE' in config.get_fields():
config.field['FORM_TYPE']['value'] = form_type
else:
config.add_field(var='FORM_TYPE',
ftype='hidden',
value=form_type)
if ntype:
if 'pubsub#node_type' in config['fields']:
if 'pubsub#node_type' in config.get_fields():
config.field['pubsub#node_type']['value'] = ntype
else:
config.add_field(var='pubsub#node_type', value=ntype)

View File

@@ -11,7 +11,7 @@ from slixmpp.xmlstream import ET
class OptionalSetting(object):
interfaces = set(('required',))
interfaces = {'required'}
def set_required(self, value):
if value in (True, 'true', 'True', '1'):

View File

@@ -23,14 +23,14 @@ class Affiliations(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'affiliations'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class Affiliation(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'affiliation'
plugin_attrib = name
interfaces = set(('node', 'affiliation', 'jid'))
interfaces = {'node', 'affiliation', 'jid'}
def set_jid(self, value):
self._set_attr('jid', str(value))
@@ -43,7 +43,7 @@ class Subscription(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'subscription'
plugin_attrib = name
interfaces = set(('jid', 'node', 'subscription', 'subid'))
interfaces = {'jid', 'node', 'subscription', 'subid'}
def set_jid(self, value):
self._set_attr('jid', str(value))
@@ -56,21 +56,21 @@ class Subscriptions(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'subscriptions'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class SubscribeOptions(ElementBase, OptionalSetting):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'subscribe-options'
plugin_attrib = 'suboptions'
interfaces = set(('required',))
interfaces = {'required'}
class Item(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'item'
plugin_attrib = name
interfaces = set(('id', 'payload'))
interfaces = {'id', 'payload'}
def set_payload(self, value):
del self['payload']
@@ -82,9 +82,9 @@ class Item(ElementBase):
self.xml.append(value)
def get_payload(self):
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
children = list(self.xml)
if len(children) > 0:
return children[0]
def del_payload(self):
for child in self.xml:
@@ -95,7 +95,7 @@ class Items(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'items'
plugin_attrib = name
interfaces = set(('node', 'max_items'))
interfaces = {'node', 'max_items'}
def set_max_items(self, value):
self._set_attr('max_items', str(value))
@@ -105,14 +105,14 @@ class Create(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'create'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class Default(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'default'
plugin_attrib = name
interfaces = set(('node', 'type'))
interfaces = {'node', 'type'}
def get_type(self):
t = self._get_attr('type')
@@ -125,14 +125,14 @@ class Publish(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'publish'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class Retract(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'retract'
plugin_attrib = name
interfaces = set(('node', 'notify'))
interfaces = {'node', 'notify'}
def get_notify(self):
notify = self._get_attr('notify')
@@ -156,7 +156,7 @@ class Unsubscribe(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'unsubscribe'
plugin_attrib = name
interfaces = set(('node', 'jid', 'subid'))
interfaces = {'node', 'jid', 'subid'}
def set_jid(self, value):
self._set_attr('jid', str(value))
@@ -169,7 +169,7 @@ class Subscribe(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'subscribe'
plugin_attrib = name
interfaces = set(('node', 'jid'))
interfaces = {'node', 'jid'}
def set_jid(self, value):
self._set_attr('jid', str(value))
@@ -182,7 +182,7 @@ class Configure(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'configure'
plugin_attrib = name
interfaces = set(('node', 'type'))
interfaces = {'node', 'type'}
def getType(self):
t = self._get_attr('type')
@@ -195,7 +195,7 @@ class Options(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'options'
plugin_attrib = name
interfaces = set(('jid', 'node', 'options'))
interfaces = {'jid', 'node', 'options'}
def __init__(self, *args, **kwargs):
ElementBase.__init__(self, *args, **kwargs)
@@ -206,7 +206,10 @@ class Options(ElementBase):
return form
def set_options(self, value):
self.xml.append(value)
if isinstance(value, ElementBase):
self.xml.append(value.xml)
else:
self.xml.append(value)
return self
def del_options(self):
@@ -224,7 +227,7 @@ class PublishOptions(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub'
name = 'publish-options'
plugin_attrib = 'publish_options'
interfaces = set(('publish_options',))
interfaces = {'publish_options'}
is_extension = True
def get_publish_options(self):
@@ -238,7 +241,10 @@ class PublishOptions(ElementBase):
if value is None:
self.del_publish_options()
else:
self.xml.append(value)
if isinstance(value, ElementBase):
self.xml.append(value.xml)
else:
self.xml.append(value)
return self
def del_publish_options(self):

View File

@@ -13,18 +13,18 @@ from slixmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class PubsubErrorCondition(ElementBase):
plugin_attrib = 'pubsub'
interfaces = set(('condition', 'unsupported'))
interfaces = {'condition', 'unsupported'}
plugin_attrib_map = {}
plugin_tag_map = {}
conditions = set(('closed-node', 'configuration-required', 'invalid-jid',
'invalid-options', 'invalid-payload', 'invalid-subid',
'item-forbidden', 'item-required', 'jid-required',
'max-items-exceeded', 'max-nodes-exceeded',
'nodeid-required', 'not-in-roster-group',
'not-subscribed', 'payload-too-big',
'payload-required', 'pending-subscription',
'presence-subscription-required', 'subid-required',
'too-many-subscriptions', 'unsupported'))
conditions = {'closed-node', 'configuration-required', 'invalid-jid',
'invalid-options', 'invalid-payload', 'invalid-subid',
'item-forbidden', 'item-required', 'jid-required',
'max-items-exceeded', 'max-nodes-exceeded',
'nodeid-required', 'not-in-roster-group',
'not-subscribed', 'payload-too-big',
'payload-required', 'pending-subscription',
'presence-subscription-required', 'subid-required',
'too-many-subscriptions', 'unsupported'}
condition_ns = 'http://jabber.org/protocol/pubsub#errors'
def setup(self, xml):

View File

@@ -25,15 +25,15 @@ class EventItem(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'item'
plugin_attrib = name
interfaces = set(('id', 'payload', 'node', 'publisher'))
interfaces = {'id', 'payload', 'node', 'publisher'}
def set_payload(self, value):
self.xml.append(value)
def get_payload(self):
childs = list(self.xml)
if len(childs) > 0:
return childs[0]
children = list(self.xml)
if len(children) > 0:
return children[0]
def del_payload(self):
for child in self.xml:
@@ -44,56 +44,56 @@ class EventRetract(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'retract'
plugin_attrib = name
interfaces = set(('id',))
interfaces = {'id'}
class EventItems(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'items'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class EventCollection(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'collection'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class EventAssociate(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'associate'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class EventDisassociate(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'disassociate'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class EventConfiguration(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'configuration'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class EventPurge(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'purge'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class EventDelete(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'delete'
plugin_attrib = name
interfaces = set(('node', 'redirect'))
interfaces = {'node', 'redirect'}
def set_redirect(self, uri):
del self['redirect']
@@ -117,7 +117,7 @@ class EventSubscription(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#event'
name = 'subscription'
plugin_attrib = name
interfaces = set(('node', 'expiry', 'jid', 'subid', 'subscription'))
interfaces = {'node', 'expiry', 'jid', 'subid', 'subscription'}
def get_expiry(self):
expiry = self._get_attr('expiry')

View File

@@ -25,7 +25,7 @@ class DefaultConfig(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#owner'
name = 'default'
plugin_attrib = name
interfaces = set(('node', 'config'))
interfaces = {'node', 'config'}
def __init__(self, *args, **kwargs):
ElementBase.__init__(self, *args, **kwargs)
@@ -41,7 +41,7 @@ class DefaultConfig(ElementBase):
class OwnerAffiliations(Affiliations):
namespace = 'http://jabber.org/protocol/pubsub#owner'
interfaces = set(('node',))
interfaces = {'node'}
def append(self, affiliation):
if not isinstance(affiliation, OwnerAffiliation):
@@ -51,40 +51,40 @@ class OwnerAffiliations(Affiliations):
class OwnerAffiliation(Affiliation):
namespace = 'http://jabber.org/protocol/pubsub#owner'
interfaces = set(('affiliation', 'jid'))
interfaces = {'affiliation', 'jid'}
class OwnerConfigure(Configure):
namespace = 'http://jabber.org/protocol/pubsub#owner'
name = 'configure'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class OwnerDefault(OwnerConfigure):
namespace = 'http://jabber.org/protocol/pubsub#owner'
interfaces = set(('node',))
interfaces = {'node'}
class OwnerDelete(ElementBase, OptionalSetting):
namespace = 'http://jabber.org/protocol/pubsub#owner'
name = 'delete'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class OwnerPurge(ElementBase, OptionalSetting):
namespace = 'http://jabber.org/protocol/pubsub#owner'
name = 'purge'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
class OwnerRedirect(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#owner'
name = 'redirect'
plugin_attrib = name
interfaces = set(('node', 'jid'))
interfaces = {'node', 'jid'}
def set_jid(self, value):
self._set_attr('jid', str(value))
@@ -97,7 +97,7 @@ class OwnerSubscriptions(Subscriptions):
name = 'subscriptions'
namespace = 'http://jabber.org/protocol/pubsub#owner'
plugin_attrib = name
interfaces = set(('node',))
interfaces = {'node'}
def append(self, subscription):
if not isinstance(subscription, OwnerSubscription):
@@ -109,7 +109,7 @@ class OwnerSubscription(ElementBase):
namespace = 'http://jabber.org/protocol/pubsub#owner'
name = 'subscription'
plugin_attrib = name
interfaces = set(('jid', 'subscription'))
interfaces = {'jid', 'subscription'}
def set_jid(self, value):
self._set_attr('jid', str(value))

View File

@@ -22,7 +22,7 @@ class XEP_0065(BasePlugin):
name = 'xep_0065'
description = "XEP-0065: SOCKS5 Bytestreams"
dependencies = set(['xep_0030'])
dependencies = {'xep_0030'}
default_config = {
'auto_accept': False
}
@@ -55,17 +55,17 @@ class XEP_0065(BasePlugin):
"""Returns the socket associated to the SID."""
return self._sessions.get(sid, None)
def handshake(self, to, ifrom=None, sid=None, timeout=None):
async def handshake(self, to, ifrom=None, sid=None, timeout=None):
""" Starts the handshake to establish the socks5 bytestreams
connection.
"""
if not self._proxies:
self._proxies = yield from self.discover_proxies()
self._proxies = await self.discover_proxies()
if sid is None:
sid = uuid4().hex
used = yield from self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
used = await self.request_stream(to, sid=sid, ifrom=ifrom, timeout=timeout)
proxy = used['socks']['streamhost_used']['jid']
if proxy not in self._proxies:
@@ -73,16 +73,16 @@ class XEP_0065(BasePlugin):
return
try:
self._sessions[sid] = (yield from self._connect_proxy(
self._sessions[sid] = (await self._connect_proxy(
self._get_dest_sha1(sid, self.xmpp.boundjid, to),
self._proxies[proxy][0],
self._proxies[proxy][1]))[1]
except socket.error:
return None
addr, port = yield from self._sessions[sid].connected
addr, port = await self._sessions[sid].connected
# Request that the proxy activate the session with the target.
yield from self.activate(proxy, sid, to, timeout=timeout)
await self.activate(proxy, sid, to, timeout=timeout)
sock = self.get_socket(sid)
self.xmpp.event('stream:%s:%s' % (sid, to), sock)
return sock
@@ -104,7 +104,7 @@ class XEP_0065(BasePlugin):
iq['socks'].add_streamhost(proxy, host, port)
return iq.send(timeout=timeout, callback=callback)
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
async def discover_proxies(self, jid=None, ifrom=None, timeout=None):
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
if jid is None:
if self.xmpp.is_component:
@@ -114,7 +114,7 @@ class XEP_0065(BasePlugin):
discovered = set()
disco_items = yield from self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
disco_items = await self.xmpp['xep_0030'].get_items(jid, timeout=timeout)
disco_items = {item[0] for item in disco_items['disco_items']['items']}
disco_info_futures = {}
@@ -123,7 +123,7 @@ class XEP_0065(BasePlugin):
for item in disco_items:
try:
disco_info = yield from disco_info_futures[item]
disco_info = await disco_info_futures[item]
except XMPPError:
continue
else:
@@ -135,7 +135,7 @@ class XEP_0065(BasePlugin):
for jid in discovered:
try:
addr = yield from self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
addr = await self.get_network_address(jid, ifrom=ifrom, timeout=timeout)
self._proxies[jid] = (addr['socks']['streamhost']['host'],
addr['socks']['streamhost']['port'])
except XMPPError:
@@ -180,9 +180,8 @@ class XEP_0065(BasePlugin):
streamhost['host'],
streamhost['port']))
@asyncio.coroutine
def gather(futures, iq, streamhosts):
proxies = yield from asyncio.gather(*futures, return_exceptions=True)
async def gather(futures, iq, streamhosts):
proxies = await asyncio.gather(*futures, return_exceptions=True)
for streamhost, proxy in zip(streamhosts, proxies):
if isinstance(proxy, ValueError):
continue
@@ -192,7 +191,7 @@ class XEP_0065(BasePlugin):
proxy = proxy[1]
# TODO: what if the future never happens?
try:
addr, port = yield from proxy.connected
addr, port = await proxy.connected
except socket.error:
log.exception('Socket error while connecting to the proxy.')
continue
@@ -213,7 +212,7 @@ class XEP_0065(BasePlugin):
self.xmpp.event('socks5_stream', conn)
self.xmpp.event('stream:%s:%s' % (sid, requester), conn)
asyncio.async(gather(proxy_futures, iq, streamhosts))
asyncio.ensure_future(gather(proxy_futures, iq, streamhosts))
def activate(self, proxy, sid, target, ifrom=None, timeout=None, callback=None):
"""Activate the socks5 session that has been negotiated."""
@@ -231,7 +230,7 @@ class XEP_0065(BasePlugin):
sock.close()
except socket.error:
pass
# Though this should not be neccessary remove the closed session anyway
# Though this should not be necessary remove the closed session anyway
if sid in self._sessions:
log.warn(('SOCKS5 session with sid = "%s" was not ' +
'removed from _sessions by sock.close()') % sid)

View File

@@ -137,8 +137,8 @@ class Socks5Protocol(asyncio.Protocol):
def resume_writing(self):
self.paused.set_result(None)
def write(self, data):
yield from self.paused
async def write(self, data):
await self.paused
self.transport.write(data)
def _send_methods(self):

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