Compare commits
119 Commits
markup
...
adhoc-exec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
412a9169bd | ||
|
|
72b355de8c | ||
|
|
af246dcfe1 | ||
|
|
9612e518fb | ||
|
|
fde8264191 | ||
|
|
1cdc656208 | ||
|
|
0042108a67 | ||
|
|
704161a285 | ||
|
|
6b1b58a339 | ||
|
|
4f96e5fa75 | ||
|
|
bcb90a653e | ||
|
|
7e435b703d | ||
|
|
2dda6b80d4 | ||
|
|
5629e44710 | ||
|
|
6a06881d8b | ||
|
|
2b666eb1de | ||
|
|
400e7a3903 | ||
|
|
fbab3ad214 | ||
|
|
628b357b06 | ||
|
|
88260cc240 | ||
|
|
e9f2f503b8 | ||
|
|
696a72247b | ||
|
|
05d76e4b1d | ||
|
|
d52d4fbbbe | ||
|
|
e53c0fcb30 | ||
|
|
97d68c5196 | ||
|
|
b42fafabb4 | ||
|
|
3a44ec8f15 | ||
|
|
93f385562f | ||
|
|
9cab02438b | ||
|
|
74ed50e626 | ||
|
|
9d378c611c | ||
|
|
d85d8f4479 | ||
|
|
fb75f7cda9 | ||
|
|
41419a2161 | ||
|
|
7cd73b594e | ||
|
|
15c6b775ff | ||
|
|
4b482477e2 | ||
|
|
f7e4caadfe | ||
|
|
5f25b0b6a0 | ||
|
|
d228bc42ea | ||
|
|
ecdc44a601 | ||
|
|
33370e42f1 | ||
|
|
4699861925 | ||
|
|
2d228bdb56 | ||
|
|
570e653ac2 | ||
|
|
282a481059 | ||
|
|
f386db380b | ||
|
|
7b87d98fff | ||
|
|
8779d40602 | ||
|
|
f0b21c42d5 | ||
|
|
e241d4e3c7 | ||
|
|
bd22a41a78 | ||
|
|
a29a29227a | ||
|
|
d4d542b741 | ||
|
|
dc4936a6d3 | ||
|
|
897610d819 | ||
|
|
d33366badd | ||
|
|
809c500002 | ||
|
|
dda4e18b81 | ||
|
|
8c09d932c8 | ||
|
|
31f5e84671 | ||
|
|
ad0dc33df9 | ||
|
|
7c3b3827b4 | ||
|
|
9f6fa65139 | ||
|
|
35fa33e3c2 | ||
|
|
86a2f280d2 | ||
|
|
490f15b8fc | ||
|
|
62661ee04f | ||
|
|
37d1f2a6b0 | ||
|
|
20107ad516 | ||
|
|
7738a01311 | ||
|
|
a9abed6151 | ||
|
|
0f690d4005 | ||
|
|
59d4420739 | ||
|
|
a88f317bbf | ||
|
|
2fc2a88970 | ||
|
|
c55e9279ac | ||
|
|
3502480384 | ||
|
|
caae713dd6 | ||
|
|
df0198abfe | ||
|
|
c20f4bf5fa | ||
|
|
9740e93aeb | ||
|
|
e7872aaa29 | ||
|
|
037706552c | ||
|
|
b881c6729b | ||
|
|
66909aafb3 | ||
|
|
cdfb5d56fc | ||
|
|
d146ce9fb6 | ||
|
|
cb59d60034 | ||
|
|
1d9fe3553e | ||
|
|
fe66c022ad | ||
|
|
92ea131721 | ||
|
|
dd7f67d10d | ||
|
|
c1562b76b2 | ||
|
|
32839f5252 | ||
|
|
80b7cf6ff8 | ||
|
|
128cc2eeb4 | ||
|
|
037912ee89 | ||
|
|
769bc6d3bf | ||
|
|
084d6cb5d9 | ||
|
|
5184713356 | ||
|
|
2f1225bad3 | ||
|
|
841f5a5a5b | ||
|
|
0c6de5e972 | ||
|
|
81dc61c55c | ||
|
|
bd63b1ce70 | ||
|
|
29faf114a7 | ||
|
|
94ea8151d4 | ||
|
|
66500ef5fb | ||
|
|
979396bb1e | ||
|
|
e177726387 | ||
|
|
20e88fda50 | ||
|
|
f252be9b6d | ||
|
|
ee98159586 | ||
|
|
c6443af29a | ||
|
|
d73f56a7af | ||
|
|
7c7f4308c5 | ||
|
|
eab8c265f4 |
@@ -1,8 +1,21 @@
|
|||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- trigger
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
stage: test
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- docker
|
||||||
image: ubuntu:latest
|
image: ubuntu:latest
|
||||||
script:
|
script:
|
||||||
- apt update
|
- apt update
|
||||||
- apt install -y python3 cython3
|
- apt install -y python3 cython3 gpg
|
||||||
- ./run_tests.py
|
- ./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
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "2.6"
|
|
||||||
- "2.7"
|
|
||||||
- "3.2"
|
|
||||||
- "3.3"
|
|
||||||
- "3.4"
|
- "3.4"
|
||||||
|
- "3.5"
|
||||||
|
- "3.6"
|
||||||
|
- "3.7-dev"
|
||||||
install:
|
install:
|
||||||
- "pip install ."
|
- "pip install ."
|
||||||
script: testall.py
|
script: testall.py
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ To contribute, the preferred way is to commit your changes on some
|
|||||||
publicly-available git repository (on a fork `on github
|
publicly-available git repository (on a fork `on github
|
||||||
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
|
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
|
||||||
notify the developers with either:
|
notify the developers with either:
|
||||||
- a ticket `on the bug tracker <https://dev.poez.io/new>`_
|
- a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
|
||||||
- a pull request on github
|
- a pull request on github
|
||||||
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
|
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
|
||||||
|
|
||||||
|
|||||||
3
INSTALL
3
INSTALL
@@ -1,6 +1,7 @@
|
|||||||
Pre-requisites:
|
Pre-requisites:
|
||||||
- Python 3.4
|
- Python 3.5+
|
||||||
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
|
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
|
||||||
|
- GnuPG, for testing
|
||||||
|
|
||||||
Install:
|
Install:
|
||||||
> python3 setup.py install
|
> python3 setup.py install
|
||||||
|
|||||||
25
LICENSE
25
LICENSE
@@ -167,28 +167,3 @@ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
||||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
socksipy: A Python SOCKS client module.
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Copyright 2006 Dan-Haim. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
1. Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
|
||||||
to endorse or promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
|
||||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
|
||||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
|
||||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
||||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
|
||||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
||||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Slixmpp
|
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.
|
SleekXMPP.
|
||||||
|
|
||||||
Slixmpp's goals is to only rewrite the core of the library (the low level
|
Slixmpp's goals is to only rewrite the core of the library (the low level
|
||||||
@@ -113,6 +113,7 @@ Slixmpp Credits
|
|||||||
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
|
- Gasper Zejn (`Gasper Zejn <mailto:zejn@kiberpipa.org>`_)
|
||||||
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
|
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
|
||||||
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
|
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
|
||||||
|
- Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_)
|
||||||
|
|
||||||
Credits (SleekXMPP)
|
Credits (SleekXMPP)
|
||||||
-------------------
|
-------------------
|
||||||
|
|||||||
13
docs/conf.py
13
docs/conf.py
@@ -12,12 +12,17 @@
|
|||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys, os
|
import sys, os
|
||||||
|
import datetime
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# 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
|
# 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.
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
sys.path.insert(0, os.path.abspath('..'))
|
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 -----------------------------------------------------
|
# -- General configuration -----------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
@@ -41,16 +46,18 @@ master_doc = 'index'
|
|||||||
|
|
||||||
# General information about the project.
|
# General information about the project.
|
||||||
project = u'Slixmpp'
|
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
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
# |version| and |release|, also used in various other places throughout the
|
# |version| and |release|, also used in various other places throughout the
|
||||||
# built documents.
|
# built documents.
|
||||||
#
|
#
|
||||||
|
# auto imported from code!
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
version = '1.1'
|
# version = '1.4'
|
||||||
# The full version, including alpha/beta/rc tags.
|
# 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
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
# for a list of supported languages.
|
# for a list of supported languages.
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
Differences from SleekXMPP
|
Differences from SleekXMPP
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
**Python 3.4+ only**
|
**Python 3.5+ only**
|
||||||
slixmpp will only work on python 3.4 and above.
|
slixmpp will only work on python 3.5 and above.
|
||||||
|
|
||||||
**Stanza copies**
|
**Stanza copies**
|
||||||
The same stanza object is given through all the handlers; a handler that
|
The same stanza object is given through all the handlers; a handler that
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Create and Run a Server Component
|
|||||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
with `Git <http://git.poez.io/slixmpp>`_.
|
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||||
|
|
||||||
Many XMPP applications eventually graduate to requiring to run as a server
|
Many XMPP applications eventually graduate to requiring to run as a server
|
||||||
component in order to meet scalability requirements. To demonstrate how to
|
component in order to meet scalability requirements. To demonstrate how to
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Slixmpp Quickstart - Echo Bot
|
|||||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
with `Git <http://git.poez.io/slixmpp>`_.
|
with `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||||
|
|
||||||
As a basic starting project, we will create an echo bot which will reply to any
|
As a basic starting project, we will create an echo bot which will reply to any
|
||||||
messages sent to it. We will also go through adding some basic command line configuration
|
messages sent to it. We will also go through adding some basic command line configuration
|
||||||
@@ -329,7 +329,7 @@ The Final Product
|
|||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
Here then is what the final result should look like after working through the guide above. The code
|
Here then is what the final result should look like after working through the guide above. The code
|
||||||
can also be found in the Slixmpp `examples directory <http://git.poez.io/slixmpp/tree/examples>`_.
|
can also be found in the Slixmpp `examples directory <https://lab.louiz.org/poezio/slixmpp/tree/master/examples>`_.
|
||||||
|
|
||||||
.. compound::
|
.. compound::
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Multi-User Chat (MUC) Bot
|
|||||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||||
|
|
||||||
If you have not yet installed Slixmpp, do so now by either checking out a version
|
If you have not yet installed Slixmpp, do so now by either checking out a version
|
||||||
from `Git <http://git.poez.io/slixmpp>`_.
|
from `Git <https://lab.louiz.org/poezio/slixmpp>`_.
|
||||||
|
|
||||||
Now that you've got the basic gist of using Slixmpp by following the
|
Now that you've got the basic gist of using Slixmpp by following the
|
||||||
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ Slixmpp
|
|||||||
.. sidebar:: Get the Code
|
.. sidebar:: Get the Code
|
||||||
|
|
||||||
The latest source code for Slixmpp may be found on the `Git repo
|
The latest source code for Slixmpp may be found on the `Git repo
|
||||||
<http://git.poez.io/slixmpp>`_. ::
|
<https://lab.louiz.org/poezio/slixmpp>`_. ::
|
||||||
|
|
||||||
git clone git://git.poez.io/slixmpp
|
git clone https://lab.louiz.org/poezio/slixmpp
|
||||||
|
|
||||||
An XMPP chat room is available for discussing and getting help with slixmpp.
|
An XMPP chat room is available for discussing and getting help with slixmpp.
|
||||||
|
|
||||||
@@ -14,14 +14,14 @@ Slixmpp
|
|||||||
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
|
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
|
||||||
|
|
||||||
**Reporting bugs**
|
**Reporting bugs**
|
||||||
You can report bugs at http://dev.louiz.org/projects/slixmpp/issues.
|
You can report bugs at http://lab.louiz.org/poezio/slixmpp/issues.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
|
slixmpp is a friendly fork of `SleekXMPP <https://github.com/fritzy/SleekXMPP>`_
|
||||||
which goal is to use asyncio instead of threads to handle networking. See
|
which goal is to use asyncio instead of threads to handle networking. See
|
||||||
:ref:`differences`.
|
: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:
|
Slixmpp's design goals and philosphy are:
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
|
|||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
from slixmpp import asyncio
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -51,18 +51,17 @@ class AskConfirm(slixmpp.ClientXMPP):
|
|||||||
else:
|
else:
|
||||||
self.confirmed.set_result(True)
|
self.confirmed.set_result(True)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
log.info('Sending confirm request %s to %s who wants to access %s using '
|
log.info('Sending confirm request %s to %s who wants to access %s using '
|
||||||
'method %s...' % (self.id, self.recipient, self.url, self.method))
|
'method %s...' % (self.id, self.recipient, self.url, self.method))
|
||||||
try:
|
try:
|
||||||
confirmed = yield from self['xep_0070'].ask_confirm(self.recipient,
|
confirmed = await self['xep_0070'].ask_confirm(self.recipient,
|
||||||
id=self.id,
|
id=self.id,
|
||||||
url=self.url,
|
url=self.url,
|
||||||
method=self.method,
|
method=self.method,
|
||||||
message='Plz say yes or no for {method} {url} ({id}).')
|
message='Plz say yes or no for {method} {url} ({id}).')
|
||||||
if isinstance(confirmed, slixmpp.Message):
|
if isinstance(confirmed, slixmpp.Message):
|
||||||
confirmed = yield from self.confirmed
|
confirmed = await self.confirmed
|
||||||
else:
|
else:
|
||||||
confirmed = True
|
confirmed = True
|
||||||
except IqError:
|
except IqError:
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
|
|||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import IqError, IqTimeout
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
from slixmpp.xmlstream.asyncio import asyncio
|
|
||||||
|
|
||||||
|
|
||||||
class Disco(slixmpp.ClientXMPP):
|
class Disco(slixmpp.ClientXMPP):
|
||||||
@@ -54,8 +53,7 @@ class Disco(slixmpp.ClientXMPP):
|
|||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
@@ -77,13 +75,13 @@ class Disco(slixmpp.ClientXMPP):
|
|||||||
try:
|
try:
|
||||||
if self.get in self.info_types:
|
if self.get in self.info_types:
|
||||||
# function using the callback parameter.
|
# 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)
|
node=self.target_node)
|
||||||
if self.get in self.items_types:
|
if self.get in self.items_types:
|
||||||
# The same applies from above. Listen for the
|
# The same applies from above. Listen for the
|
||||||
# disco_items event or pass a callback function
|
# disco_items event or pass a callback function
|
||||||
# if you need to process a non-blocking request.
|
# 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)
|
node=self.target_node)
|
||||||
if self.get not in self.info_types and self.get not in self.items_types:
|
if self.get not in self.info_types and self.get not in self.items_types:
|
||||||
logging.error("Invalid disco request type.")
|
logging.error("Invalid disco request type.")
|
||||||
|
|||||||
@@ -47,8 +47,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
|||||||
self.roster_received.set()
|
self.roster_received.set()
|
||||||
self.presences_received.clear()
|
self.presences_received.clear()
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
@@ -65,16 +64,15 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
|||||||
self.get_roster(callback=self.roster_received_cb)
|
self.get_roster(callback=self.roster_received_cb)
|
||||||
|
|
||||||
print('Waiting for presence updates...\n')
|
print('Waiting for presence updates...\n')
|
||||||
yield from self.roster_received.wait()
|
await self.roster_received.wait()
|
||||||
print('Roster received')
|
print('Roster received')
|
||||||
yield from self.presences_received.wait()
|
await self.presences_received.wait()
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def on_vcard_avatar(self, pres):
|
||||||
def on_vcard_avatar(self, pres):
|
|
||||||
print("Received vCard avatar update from %s" % pres['from'].bare)
|
print("Received vCard avatar update from %s" % pres['from'].bare)
|
||||||
try:
|
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)
|
timeout=5)
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
print("Error retrieving avatar for %s" % pres['from'])
|
print("Error retrieving avatar for %s" % pres['from'])
|
||||||
@@ -89,14 +87,13 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
|||||||
with open(filename, 'wb+') as img:
|
with open(filename, 'wb+') as img:
|
||||||
img.write(avatar['BINVAL'])
|
img.write(avatar['BINVAL'])
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def on_avatar(self, msg):
|
||||||
def on_avatar(self, msg):
|
|
||||||
print("Received avatar update from %s" % msg['from'])
|
print("Received avatar update from %s" % msg['from'])
|
||||||
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
||||||
for info in metadata['items']:
|
for info in metadata['items']:
|
||||||
if not info['url']:
|
if not info['url']:
|
||||||
try:
|
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)
|
timeout=5)
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
print("Error retrieving avatar for %s" % msg['from'])
|
print("Error retrieving avatar for %s" % msg['from'])
|
||||||
|
|||||||
96
examples/http_upload.py
Executable file
96
examples/http_upload.py
Executable 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 server’s)")
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -9,7 +9,6 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
@@ -39,8 +38,7 @@ class IBBSender(slixmpp.ClientXMPP):
|
|||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
@@ -58,13 +56,13 @@ class IBBSender(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Open the IBB stream in which to write to.
|
# 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.
|
# 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.
|
# And finally close the stream.
|
||||||
yield from stream.close(timeout=10)
|
await stream.close(timeout=10)
|
||||||
except (IqError, IqTimeout):
|
except (IqError, IqTimeout):
|
||||||
print('File transfer errored')
|
print('File transfer errored')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
|
|||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
from slixmpp import asyncio
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
120
examples/markup.py
Executable file
120
examples/markup.py
Executable 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()
|
||||||
@@ -13,7 +13,6 @@ import logging
|
|||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from slixmpp.exceptions import IqError, IqTimeout
|
from slixmpp.exceptions import IqError, IqTimeout
|
||||||
from slixmpp import asyncio
|
|
||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
|
|
||||||
@@ -38,8 +37,7 @@ class PingTest(slixmpp.ClientXMPP):
|
|||||||
# our roster.
|
# our roster.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
@@ -56,7 +54,7 @@ class PingTest(slixmpp.ClientXMPP):
|
|||||||
self.get_roster()
|
self.get_roster()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rtt = yield from self['xep_0199'].ping(self.pingjid,
|
rtt = await self['xep_0199'].ping(self.pingjid,
|
||||||
timeout=10)
|
timeout=10)
|
||||||
logging.info("Success! RTT: %s", rtt)
|
logging.info("Success! RTT: %s", rtt)
|
||||||
except IqError as e:
|
except IqError as e:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import logging
|
|||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
from slixmpp.xmlstream import ET, tostring
|
from slixmpp.xmlstream import ET, tostring
|
||||||
@@ -21,7 +20,7 @@ class PubsubClient(slixmpp.ClientXMPP):
|
|||||||
self.register_plugin('xep_0059')
|
self.register_plugin('xep_0059')
|
||||||
self.register_plugin('xep_0060')
|
self.register_plugin('xep_0060')
|
||||||
|
|
||||||
self.actions = ['nodes', 'create', 'delete',
|
self.actions = ['nodes', 'create', 'delete', 'get_configure',
|
||||||
'publish', 'get', 'retract',
|
'publish', 'get', 'retract',
|
||||||
'purge', 'subscribe', 'unsubscribe']
|
'purge', 'subscribe', 'unsubscribe']
|
||||||
|
|
||||||
@@ -32,80 +31,86 @@ class PubsubClient(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
self.add_event_handler('session_start', self.start)
|
self.add_event_handler('session_start', self.start)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
self.get_roster()
|
self.get_roster()
|
||||||
self.send_presence()
|
self.send_presence()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield from getattr(self, self.action)()
|
await getattr(self, self.action)()
|
||||||
except:
|
except:
|
||||||
logging.error('Could not execute: %s', self.action)
|
logging.exception('Could not execute %s:', self.action)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
def nodes(self):
|
async def nodes(self):
|
||||||
try:
|
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']:
|
for item in result['disco_items']['items']:
|
||||||
logging.info(' - %s', str(item))
|
logging.info(' - %s', str(item))
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not retrieve node list: %s', error.format())
|
logging.error('Could not retrieve node list: %s', error.format())
|
||||||
|
|
||||||
def create(self):
|
async def create(self):
|
||||||
try:
|
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)
|
logging.info('Created node %s', self.node)
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not create node %s: %s', self.node, error.format())
|
logging.error('Could not create node %s: %s', self.node, error.format())
|
||||||
|
|
||||||
def delete(self):
|
async def delete(self):
|
||||||
try:
|
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)
|
logging.info('Deleted node %s', self.node)
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not delete node %s: %s', self.node, error.format())
|
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)
|
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
|
||||||
try:
|
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'])
|
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not publish to %s: %s', self.node, error.format())
|
logging.error('Could not publish to %s: %s', self.node, error.format())
|
||||||
|
|
||||||
def get(self):
|
async def get(self):
|
||||||
try:
|
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']:
|
for item in result['pubsub']['items']['substanzas']:
|
||||||
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
|
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
|
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:
|
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)
|
logging.info('Retracted item %s from node %s', self.data, self.node)
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
|
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:
|
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)
|
logging.info('Purged all items from node %s', self.node)
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not purge items from node %s: %s', self.node, error.format())
|
logging.error('Could not purge items from node %s: %s', self.node, error.format())
|
||||||
|
|
||||||
def subscribe(self):
|
async def subscribe(self):
|
||||||
try:
|
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']
|
subscription = iq['pubsub']['subscription']
|
||||||
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
|
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
|
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:
|
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)
|
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
|
||||||
except XMPPError as error:
|
except XMPPError as error:
|
||||||
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
|
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 = ArgumentParser()
|
||||||
parser.version = '%%prog 0.1'
|
parser.version = '%%prog 0.1'
|
||||||
parser.usage = "Usage: %%prog [options] <jid> " + \
|
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>]'
|
' [<node> <data>]'
|
||||||
|
|
||||||
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
parser.add_argument("-q","--quiet", help="set logging to ERROR",
|
||||||
@@ -139,7 +144,7 @@ if __name__ == '__main__':
|
|||||||
help="password to use")
|
help="password to use")
|
||||||
|
|
||||||
parser.add_argument("server")
|
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("node", nargs='?')
|
||||||
parser.add_argument("data", nargs='?')
|
parser.add_argument("data", nargs='?')
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class RegisterBot(slixmpp.ClientXMPP):
|
|||||||
# We're only concerned about registering, so nothing more to do here.
|
# We're only concerned about registering, so nothing more to do here.
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
def register(self, iq):
|
async def register(self, iq):
|
||||||
"""
|
"""
|
||||||
Fill out and submit a registration form.
|
Fill out and submit a registration form.
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
|
|||||||
resp['register']['password'] = self.password
|
resp['register']['password'] = self.password
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yield from resp.send()
|
await resp.send()
|
||||||
logging.info("Account created for %s!" % self.boundjid)
|
logging.info("Account created for %s!" % self.boundjid)
|
||||||
except IqError as e:
|
except IqError as e:
|
||||||
logging.error("Could not register account: %s" %
|
logging.error("Could not register account: %s" %
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
|||||||
self.received = set()
|
self.received = set()
|
||||||
self.presences_received = asyncio.Event()
|
self.presences_received = asyncio.Event()
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
@@ -57,7 +56,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
|||||||
future.set_result(None)
|
future.set_result(None)
|
||||||
try:
|
try:
|
||||||
self.get_roster(callback=callback)
|
self.get_roster(callback=callback)
|
||||||
yield from future
|
await future
|
||||||
except IqError as err:
|
except IqError as err:
|
||||||
print('Error: %s' % err.iq['error']['condition'])
|
print('Error: %s' % err.iq['error']['condition'])
|
||||||
except IqTimeout:
|
except IqTimeout:
|
||||||
@@ -66,7 +65,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
|
|
||||||
print('Waiting for presence updates...\n')
|
print('Waiting for presence updates...\n')
|
||||||
yield from asyncio.sleep(10)
|
await asyncio.sleep(10)
|
||||||
|
|
||||||
print('Roster for %s' % self.boundjid.bare)
|
print('Roster for %s' % self.boundjid.bare)
|
||||||
groups = self.client_roster.groups()
|
groups = self.client_roster.groups()
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import logging
|
import logging
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
@@ -36,8 +35,7 @@ class S5BSender(slixmpp.ClientXMPP):
|
|||||||
# and the XML streams are ready for use.
|
# and the XML streams are ready for use.
|
||||||
self.add_event_handler("session_start", self.start)
|
self.add_event_handler("session_start", self.start)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
@@ -53,14 +51,14 @@ class S5BSender(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Open the S5B stream in which to write to.
|
# 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.
|
# Send the entire file.
|
||||||
while True:
|
while True:
|
||||||
data = self.file.read(1048576)
|
data = self.file.read(1048576)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
yield from proxy.write(data)
|
await proxy.write(data)
|
||||||
|
|
||||||
# And finally close the stream.
|
# And finally close the stream.
|
||||||
proxy.transport.write_eof()
|
proxy.transport.write_eof()
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from argparse import ArgumentParser
|
|||||||
|
|
||||||
import slixmpp
|
import slixmpp
|
||||||
from slixmpp.exceptions import XMPPError
|
from slixmpp.exceptions import XMPPError
|
||||||
from slixmpp import asyncio
|
|
||||||
|
|
||||||
class AvatarSetter(slixmpp.ClientXMPP):
|
class AvatarSetter(slixmpp.ClientXMPP):
|
||||||
|
|
||||||
@@ -33,8 +32,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
|||||||
|
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def start(self, event):
|
||||||
def start(self, event):
|
|
||||||
"""
|
"""
|
||||||
Process the session_start event.
|
Process the session_start event.
|
||||||
|
|
||||||
@@ -68,20 +66,20 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
|||||||
used_xep84 = False
|
used_xep84 = False
|
||||||
|
|
||||||
print('Publish XEP-0084 avatar data')
|
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):
|
if isinstance(result, XMPPError):
|
||||||
print('Could not publish XEP-0084 avatar')
|
print('Could not publish XEP-0084 avatar')
|
||||||
else:
|
else:
|
||||||
used_xep84 = True
|
used_xep84 = True
|
||||||
|
|
||||||
print('Update vCard with avatar')
|
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):
|
if isinstance(result, XMPPError):
|
||||||
print('Could not set vCard avatar')
|
print('Could not set vCard avatar')
|
||||||
|
|
||||||
if used_xep84:
|
if used_xep84:
|
||||||
print('Advertise XEP-0084 avatar metadata')
|
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,
|
{'id': avatar_id,
|
||||||
'type': avatar_type,
|
'type': avatar_type,
|
||||||
'bytes': avatar_bytes}
|
'bytes': avatar_bytes}
|
||||||
|
|||||||
9
setup.py
9
setup.py
@@ -20,8 +20,7 @@ from run_tests import TestCommand
|
|||||||
from slixmpp.version import __version__
|
from slixmpp.version import __version__
|
||||||
|
|
||||||
VERSION = __version__
|
VERSION = __version__
|
||||||
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, '
|
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).')
|
||||||
'Google Talk, etc).')
|
|
||||||
with open('README.rst', encoding='utf8') as readme:
|
with open('README.rst', encoding='utf8') as readme:
|
||||||
LONG_DESCRIPTION = readme.read()
|
LONG_DESCRIPTION = readme.read()
|
||||||
|
|
||||||
@@ -29,9 +28,9 @@ CLASSIFIERS = [
|
|||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 3.4',
|
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.6',
|
||||||
|
'Programming Language :: Python :: 3.7',
|
||||||
'Topic :: Internet :: XMPP',
|
'Topic :: Internet :: XMPP',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
]
|
]
|
||||||
@@ -79,12 +78,12 @@ setup(
|
|||||||
long_description=LONG_DESCRIPTION,
|
long_description=LONG_DESCRIPTION,
|
||||||
author='Florent Le Coz',
|
author='Florent Le Coz',
|
||||||
author_email='louiz@louiz.org',
|
author_email='louiz@louiz.org',
|
||||||
url='https://dev.louiz.org/projects/slixmpp',
|
url='https://lab.louiz.org/poezio/slixmpp',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
platforms=['any'],
|
platforms=['any'],
|
||||||
packages=packages,
|
packages=packages,
|
||||||
ext_modules=ext_modules,
|
ext_modules=ext_modules,
|
||||||
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
|
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'],
|
||||||
classifiers=CLASSIFIERS,
|
classifiers=CLASSIFIERS,
|
||||||
cmdclass={'test': TestCommand}
|
cmdclass={'test': TestCommand}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
See the file LICENSE for copying permission.
|
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
|
import logging
|
||||||
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
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.stanza import Message, Presence, Iq
|
||||||
from slixmpp.jid import JID, InvalidJID
|
from slixmpp.jid import JID, InvalidJID
|
||||||
|
|||||||
@@ -104,12 +104,12 @@ class BaseXMPP(XMLStream):
|
|||||||
#: :attr:`use_message_ids` to `True` will assign all outgoing
|
#: :attr:`use_message_ids` to `True` will assign all outgoing
|
||||||
#: messages an ID. Some plugin features require enabling
|
#: messages an ID. Some plugin features require enabling
|
||||||
#: this option.
|
#: this option.
|
||||||
self.use_message_ids = False
|
self.use_message_ids = True
|
||||||
|
|
||||||
#: Presence updates may optionally be tagged with ID values.
|
#: Presence updates may optionally be tagged with ID values.
|
||||||
#: Setting :attr:`use_message_ids` to `True` will assign all
|
#: Setting :attr:`use_message_ids` to `True` will assign all
|
||||||
#: outgoing messages an ID.
|
#: outgoing messages an ID.
|
||||||
self.use_presence_ids = False
|
self.use_presence_ids = True
|
||||||
|
|
||||||
#: The API registry is a way to process callbacks based on
|
#: The API registry is a way to process callbacks based on
|
||||||
#: JID+node combinations. Each callback in the registry is
|
#: JID+node combinations. Each callback in the registry is
|
||||||
|
|||||||
@@ -265,8 +265,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
self.bindfail = False
|
self.bindfail = False
|
||||||
self.features = set()
|
self.features = set()
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _handle_stream_features(self, features):
|
||||||
def _handle_stream_features(self, features):
|
|
||||||
"""Process the received stream features.
|
"""Process the received stream features.
|
||||||
|
|
||||||
:param features: The features stanza.
|
:param features: The features stanza.
|
||||||
@@ -275,7 +274,7 @@ class ClientXMPP(BaseXMPP):
|
|||||||
if name in features['features']:
|
if name in features['features']:
|
||||||
handler, restart = self._stream_feature_handlers[name]
|
handler, restart = self._stream_feature_handlers[name]
|
||||||
if asyncio.iscoroutinefunction(handler):
|
if asyncio.iscoroutinefunction(handler):
|
||||||
result = yield from handler(features)
|
result = await handler(features)
|
||||||
else:
|
else:
|
||||||
result = handler(features)
|
result = handler(features)
|
||||||
if result and restart:
|
if result and restart:
|
||||||
|
|||||||
@@ -35,8 +35,7 @@ class FeatureBind(BasePlugin):
|
|||||||
register_stanza_plugin(Iq, stanza.Bind)
|
register_stanza_plugin(Iq, stanza.Bind)
|
||||||
register_stanza_plugin(StreamFeatures, stanza.Bind)
|
register_stanza_plugin(StreamFeatures, stanza.Bind)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _handle_bind_resource(self, features):
|
||||||
def _handle_bind_resource(self, features):
|
|
||||||
"""
|
"""
|
||||||
Handle requesting a specific resource.
|
Handle requesting a specific resource.
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ class FeatureBind(BasePlugin):
|
|||||||
if self.xmpp.requested_jid.resource:
|
if self.xmpp.requested_jid.resource:
|
||||||
iq['bind']['resource'] = 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):
|
def _on_bind_response(self, response):
|
||||||
self.xmpp.boundjid = JID(response['bind']['jid'])
|
self.xmpp.boundjid = JID(response['bind']['jid'])
|
||||||
|
|||||||
@@ -97,12 +97,9 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
jid = self.xmpp.requested_jid.bare
|
jid = self.xmpp.requested_jid.bare
|
||||||
result[value] = creds.get('email', jid)
|
result[value] = creds.get('email', jid)
|
||||||
elif value == 'channel_binding':
|
elif value == 'channel_binding':
|
||||||
if hasattr(self.xmpp.socket, 'get_channel_binding'):
|
if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
|
||||||
result[value] = self.xmpp.socket.get_channel_binding()
|
result[value] = self.xmpp.socket.get_channel_binding()
|
||||||
else:
|
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] = None
|
||||||
elif value == 'host':
|
elif value == 'host':
|
||||||
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
|
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
|
||||||
@@ -122,7 +119,7 @@ class FeatureMechanisms(BasePlugin):
|
|||||||
if value == 'encrypted':
|
if value == 'encrypted':
|
||||||
if 'starttls' in self.xmpp.features:
|
if 'starttls' in self.xmpp.features:
|
||||||
result[value] = True
|
result[value] = True
|
||||||
elif isinstance(self.xmpp.socket, ssl.SSLSocket):
|
elif isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
|
||||||
result[value] = True
|
result[value] = True
|
||||||
else:
|
else:
|
||||||
result[value] = False
|
result[value] = False
|
||||||
|
|||||||
@@ -35,18 +35,22 @@ class FeatureSession(BasePlugin):
|
|||||||
register_stanza_plugin(Iq, stanza.Session)
|
register_stanza_plugin(Iq, stanza.Session)
|
||||||
register_stanza_plugin(StreamFeatures, stanza.Session)
|
register_stanza_plugin(StreamFeatures, stanza.Session)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _handle_start_session(self, features):
|
||||||
def _handle_start_session(self, features):
|
|
||||||
"""
|
"""
|
||||||
Handle the start of the session.
|
Handle the start of the session.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
feature -- The stream features element.
|
feature -- The stream features element.
|
||||||
"""
|
"""
|
||||||
|
if features['session']['optional']:
|
||||||
|
self.xmpp.sessionstarted = True
|
||||||
|
self.xmpp.event('session_start')
|
||||||
|
return
|
||||||
|
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq.enable('session')
|
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):
|
def _on_start_session_response(self, response):
|
||||||
self.xmpp.features.add('session')
|
self.xmpp.features.add('session')
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from slixmpp.xmlstream import ElementBase
|
from slixmpp.xmlstream import ElementBase, ET
|
||||||
|
|
||||||
|
|
||||||
class Session(ElementBase):
|
class Session(ElementBase):
|
||||||
@@ -16,5 +16,19 @@ class Session(ElementBase):
|
|||||||
|
|
||||||
name = 'session'
|
name = 'session'
|
||||||
namespace = 'urn:ietf:params:xml:ns:xmpp-session'
|
namespace = 'urn:ietf:params:xml:ns:xmpp-session'
|
||||||
interfaces = set()
|
interfaces = {'optional'}
|
||||||
plugin_attrib = 'session'
|
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)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from slixmpp.stanza import StreamFeatures
|
|||||||
from slixmpp.xmlstream import register_stanza_plugin
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
from slixmpp.plugins import BasePlugin
|
from slixmpp.plugins import BasePlugin
|
||||||
from slixmpp.xmlstream.matcher import MatchXPath
|
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
|
from slixmpp.features.feature_starttls import stanza
|
||||||
|
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ class FeatureSTARTTLS(BasePlugin):
|
|||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
self.xmpp.register_handler(
|
self.xmpp.register_handler(
|
||||||
Callback('STARTTLS Proceed',
|
CoroutineCallback('STARTTLS Proceed',
|
||||||
MatchXPath(stanza.Proceed.tag_name()),
|
MatchXPath(stanza.Proceed.tag_name()),
|
||||||
self._handle_starttls_proceed,
|
self._handle_starttls_proceed,
|
||||||
instream=True))
|
instream=True))
|
||||||
@@ -58,8 +58,8 @@ class FeatureSTARTTLS(BasePlugin):
|
|||||||
self.xmpp.send(features['starttls'])
|
self.xmpp.send(features['starttls'])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_starttls_proceed(self, proceed):
|
async def _handle_starttls_proceed(self, proceed):
|
||||||
"""Restart the XML stream when TLS is accepted."""
|
"""Restart the XML stream when TLS is accepted."""
|
||||||
log.debug("Starting TLS")
|
log.debug("Starting TLS")
|
||||||
if self.xmpp.start_tls():
|
if await self.xmpp.start_tls():
|
||||||
self.xmpp.features.add('starttls')
|
self.xmpp.features.add('starttls')
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import socket
|
|||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
|
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
|
||||||
|
|
||||||
@@ -71,7 +72,7 @@ def _parse_jid(data):
|
|||||||
return node, domain, resource
|
return node, domain, resource
|
||||||
|
|
||||||
|
|
||||||
def _validate_node(node):
|
def _validate_node(node: Optional[str]):
|
||||||
"""Validate the local, or username, portion of a JID.
|
"""Validate the local, or username, portion of a JID.
|
||||||
|
|
||||||
:raises InvalidJID:
|
:raises InvalidJID:
|
||||||
@@ -93,7 +94,7 @@ def _validate_node(node):
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
def _validate_domain(domain):
|
def _validate_domain(domain: str):
|
||||||
"""Validate the domain portion of a JID.
|
"""Validate the domain portion of a JID.
|
||||||
|
|
||||||
IP literal addresses are left as-is, if valid. Domain names
|
IP literal addresses are left as-is, if valid. Domain names
|
||||||
@@ -152,7 +153,7 @@ def _validate_domain(domain):
|
|||||||
return domain
|
return domain
|
||||||
|
|
||||||
|
|
||||||
def _validate_resource(resource):
|
def _validate_resource(resource: Optional[str]):
|
||||||
"""Validate the resource portion of a JID.
|
"""Validate the resource portion of a JID.
|
||||||
|
|
||||||
:raises InvalidJID:
|
:raises InvalidJID:
|
||||||
@@ -174,7 +175,7 @@ def _validate_resource(resource):
|
|||||||
return resource
|
return resource
|
||||||
|
|
||||||
|
|
||||||
def _unescape_node(node):
|
def _unescape_node(node: str):
|
||||||
"""Unescape a local portion of a JID.
|
"""Unescape a local portion of a JID.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -199,7 +200,11 @@ def _unescape_node(node):
|
|||||||
return ''.join(unescaped)
|
return ''.join(unescaped)
|
||||||
|
|
||||||
|
|
||||||
def _format_jid(local=None, domain=None, resource=None):
|
def _format_jid(
|
||||||
|
local: Optional[str] = None,
|
||||||
|
domain: Optional[str] = None,
|
||||||
|
resource: Optional[str] = None,
|
||||||
|
):
|
||||||
"""Format the given JID components into a full or bare JID.
|
"""Format the given JID components into a full or bare JID.
|
||||||
|
|
||||||
:param string local: Optional. The local portion of the JID.
|
:param string local: Optional. The local portion of the JID.
|
||||||
@@ -237,12 +242,17 @@ class UnescapedJID:
|
|||||||
|
|
||||||
__slots__ = ('_node', '_domain', '_resource')
|
__slots__ = ('_node', '_domain', '_resource')
|
||||||
|
|
||||||
def __init__(self, node, domain, resource):
|
def __init__(
|
||||||
|
self,
|
||||||
|
node: Optional[str],
|
||||||
|
domain: Optional[str],
|
||||||
|
resource: Optional[str],
|
||||||
|
):
|
||||||
self._node = node
|
self._node = node
|
||||||
self._domain = domain
|
self._domain = domain
|
||||||
self._resource = resource
|
self._resource = resource
|
||||||
|
|
||||||
def __getattribute__(self, name):
|
def __getattribute__(self, name: str):
|
||||||
"""Retrieve the given JID component.
|
"""Retrieve the given JID component.
|
||||||
|
|
||||||
:param name: one of: user, server, domain, resource,
|
:param name: one of: user, server, domain, resource,
|
||||||
@@ -301,7 +311,7 @@ class JID:
|
|||||||
|
|
||||||
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
|
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
|
||||||
|
|
||||||
def __init__(self, jid=None):
|
def __init__(self, jid: Optional[str] = None):
|
||||||
if not jid:
|
if not jid:
|
||||||
self._node = ''
|
self._node = ''
|
||||||
self._domain = ''
|
self._domain = ''
|
||||||
@@ -346,30 +356,10 @@ class JID:
|
|||||||
def node(self):
|
def node(self):
|
||||||
return self._node
|
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
|
@property
|
||||||
def domain(self):
|
def domain(self):
|
||||||
return self._domain
|
return self._domain
|
||||||
|
|
||||||
@property
|
|
||||||
def server(self):
|
|
||||||
return self._domain
|
|
||||||
|
|
||||||
@property
|
|
||||||
def host(self):
|
|
||||||
return self._domain
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def resource(self):
|
def resource(self):
|
||||||
return self._resource
|
return self._resource
|
||||||
@@ -382,47 +372,18 @@ class JID:
|
|||||||
def full(self):
|
def full(self):
|
||||||
return self._full
|
return self._full
|
||||||
|
|
||||||
@property
|
|
||||||
def jid(self):
|
|
||||||
return self._full
|
|
||||||
|
|
||||||
@node.setter
|
@node.setter
|
||||||
def node(self, value):
|
def node(self, value: str):
|
||||||
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._node = _validate_node(value)
|
||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@domain.setter
|
@domain.setter
|
||||||
def domain(self, value):
|
def domain(self, value: str):
|
||||||
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._domain = _validate_domain(value)
|
||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@bare.setter
|
@bare.setter
|
||||||
def bare(self, value):
|
def bare(self, value: str):
|
||||||
node, domain, resource = _parse_jid(value)
|
node, domain, resource = _parse_jid(value)
|
||||||
assert not resource
|
assert not resource
|
||||||
self._node = node
|
self._node = node
|
||||||
@@ -430,19 +391,23 @@ class JID:
|
|||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@resource.setter
|
@resource.setter
|
||||||
def resource(self, value):
|
def resource(self, value: str):
|
||||||
self._resource = _validate_resource(value)
|
self._resource = _validate_resource(value)
|
||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@full.setter
|
@full.setter
|
||||||
def full(self, value):
|
def full(self, value: str):
|
||||||
self._node, self._domain, self._resource = _parse_jid(value)
|
self._node, self._domain, self._resource = _parse_jid(value)
|
||||||
self._update_bare_full()
|
self._update_bare_full()
|
||||||
|
|
||||||
@jid.setter
|
user = node
|
||||||
def jid(self, value):
|
local = node
|
||||||
self._node, self._domain, self._resource = _parse_jid(value)
|
username = node
|
||||||
self._update_bare_full()
|
|
||||||
|
server = domain
|
||||||
|
host = domain
|
||||||
|
|
||||||
|
jid = full
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Use the full JID as the string value."""
|
"""Use the full JID as the string value."""
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Form(ElementBase):
|
|||||||
namespace = 'jabber:x:data'
|
namespace = 'jabber:x:data'
|
||||||
name = 'x'
|
name = 'x'
|
||||||
plugin_attrib = 'form'
|
plugin_attrib = 'form'
|
||||||
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', ))
|
interfaces = OrderedSet(('instructions', 'reported', 'title', 'type', 'items', 'values'))
|
||||||
sub_interfaces = {'title'}
|
sub_interfaces = {'title'}
|
||||||
form_types = {'cancel', 'form', 'result', 'submit'}
|
form_types = {'cancel', 'form', 'result', 'submit'}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ def _intercept(method, name, public):
|
|||||||
except InvocationException:
|
except InvocationException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
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 = public
|
||||||
_resolver._rpc_name = method.__name__ if name is None else name
|
_resolver._rpc_name = method.__name__ if name is None else name
|
||||||
return _resolver
|
return _resolver
|
||||||
@@ -405,8 +405,10 @@ class Proxy(Endpoint):
|
|||||||
self._callback = callback
|
self._callback = callback
|
||||||
|
|
||||||
def __getattribute__(self, name, *args):
|
def __getattribute__(self, name, *args):
|
||||||
if name in ('__dict__', '_endpoint', 'async', '_callback'):
|
if name in ('__dict__', '_endpoint', '_callback'):
|
||||||
return object.__getattribute__(self, name)
|
return object.__getattribute__(self, name)
|
||||||
|
elif name == 'async':
|
||||||
|
return lambda callback: Proxy(self._endpoint, callback)
|
||||||
else:
|
else:
|
||||||
attribute = self._endpoint.__getattribute__(name)
|
attribute = self._endpoint.__getattribute__(name)
|
||||||
if hasattr(attribute, '__call__'):
|
if hasattr(attribute, '__call__'):
|
||||||
@@ -420,9 +422,6 @@ class Proxy(Endpoint):
|
|||||||
pass # If the attribute doesn't exist, don't care!
|
pass # If the attribute doesn't exist, don't care!
|
||||||
return attribute
|
return attribute
|
||||||
|
|
||||||
def async(self, callback):
|
|
||||||
return Proxy(self._endpoint, callback)
|
|
||||||
|
|
||||||
def get_endpoint(self):
|
def get_endpoint(self):
|
||||||
'''
|
'''
|
||||||
Returns the proxified endpoint.
|
Returns the proxified endpoint.
|
||||||
@@ -696,7 +695,7 @@ class RemoteSession(object):
|
|||||||
e = {
|
e = {
|
||||||
'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
|
'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'])),
|
'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]
|
}[condition]
|
||||||
if e is None:
|
if e is None:
|
||||||
RemoteException("An unexpected exception occurred at %s!" % iq['from'])
|
RemoteException("An unexpected exception occurred at %s!" % iq['from'])
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from slixmpp import Iq
|
from slixmpp import Iq
|
||||||
@@ -123,6 +124,8 @@ class XEP_0030(BasePlugin):
|
|||||||
for op in self._disco_ops:
|
for op in self._disco_ops:
|
||||||
self.api.register(getattr(self.static, op), op, default=True)
|
self.api.register(getattr(self.static, op), op, default=True)
|
||||||
|
|
||||||
|
self.domain_infos = {}
|
||||||
|
|
||||||
def session_bind(self, jid):
|
def session_bind(self, jid):
|
||||||
self.add_feature('http://jabber.org/protocol/disco#info')
|
self.add_feature('http://jabber.org/protocol/disco#info')
|
||||||
|
|
||||||
@@ -295,6 +298,35 @@ class XEP_0030(BasePlugin):
|
|||||||
'cached': cached}
|
'cached': cached}
|
||||||
return self.api['has_identity'](jid, node, ifrom, data)
|
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
|
@future_wrapper
|
||||||
def get_info(self, jid=None, node=None, local=None,
|
def get_info(self, jid=None, node=None, local=None,
|
||||||
cached=None, **kwargs):
|
cached=None, **kwargs):
|
||||||
@@ -316,7 +348,7 @@ class XEP_0030(BasePlugin):
|
|||||||
combination handled by this Slixmpp instance and
|
combination handled by this Slixmpp instance and
|
||||||
no stanzas need to be sent.
|
no stanzas need to be sent.
|
||||||
Otherwise, a disco stanza must be sent to the
|
Otherwise, a disco stanza must be sent to the
|
||||||
remove JID to retrieve the info.
|
remote JID to retrieve the info.
|
||||||
cached -- If true, then look for the disco info data from
|
cached -- If true, then look for the disco info data from
|
||||||
the local cache system. If no results are found,
|
the local cache system. If no results are found,
|
||||||
send the query as usual. The self.use_cache
|
send the query as usual. The self.use_cache
|
||||||
@@ -646,9 +678,11 @@ class XEP_0030(BasePlugin):
|
|||||||
info['id'] = iq['id']
|
info['id'] = iq['id']
|
||||||
info.send()
|
info.send()
|
||||||
else:
|
else:
|
||||||
|
node = iq['disco_info']['node']
|
||||||
iq = iq.reply()
|
iq = iq.reply()
|
||||||
if info:
|
if info:
|
||||||
info = self._fix_default_info(info)
|
info = self._fix_default_info(info)
|
||||||
|
info['node'] = node
|
||||||
iq.set_payload(info.xml)
|
iq.set_payload(info.xml)
|
||||||
iq.send()
|
iq.send()
|
||||||
elif iq['type'] == 'result':
|
elif iq['type'] == 'result':
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ class StaticDisco(object):
|
|||||||
|
|
||||||
def add_identity(self, jid, node, ifrom, data):
|
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:
|
The data parameter may provide:
|
||||||
category -- The general category to which the agent belongs.
|
category -- The general category to which the agent belongs.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from __future__ import with_statement
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from slixmpp import Presence
|
from slixmpp import Presence, Message
|
||||||
from slixmpp.plugins import BasePlugin, register_plugin
|
from slixmpp.plugins import BasePlugin, register_plugin
|
||||||
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
|
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
|
||||||
from slixmpp.xmlstream.handler.callback import Callback
|
from slixmpp.xmlstream.handler.callback import Callback
|
||||||
@@ -181,7 +181,7 @@ class XEP_0045(BasePlugin):
|
|||||||
if got_online:
|
if got_online:
|
||||||
self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
|
self.xmpp.event("muc::%s::got_online" % entry['room'], pr)
|
||||||
|
|
||||||
def handle_groupchat_message(self, msg):
|
def handle_groupchat_message(self, msg: Message) -> None:
|
||||||
""" Handle a message event in a muc.
|
""" Handle a message event in a muc.
|
||||||
"""
|
"""
|
||||||
self.xmpp.event('groupchat_message', msg)
|
self.xmpp.event('groupchat_message', msg)
|
||||||
@@ -195,10 +195,14 @@ class XEP_0045(BasePlugin):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def handle_groupchat_subject(self, msg):
|
def handle_groupchat_subject(self, msg: Message) -> None:
|
||||||
""" Handle a message coming from a muc indicating
|
""" Handle a message coming from a muc indicating
|
||||||
a change of subject (or announcing it when joining the room)
|
a change of subject (or announcing it when joining the room)
|
||||||
"""
|
"""
|
||||||
|
# See poezio#3452. A message containing subject _and_ (body or thread)
|
||||||
|
# is not a subject change.
|
||||||
|
if msg['body'] or msg['thread']:
|
||||||
|
return None
|
||||||
self.xmpp.event('groupchat_subject', msg)
|
self.xmpp.event('groupchat_subject', msg)
|
||||||
|
|
||||||
def jid_in_room(self, room, jid):
|
def jid_in_room(self, room, jid):
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ class IBBytestream(object):
|
|||||||
|
|
||||||
self.recv_queue = asyncio.Queue()
|
self.recv_queue = asyncio.Queue()
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def send(self, data, timeout=None):
|
||||||
def send(self, data, timeout=None):
|
|
||||||
if not self.stream_started or self.stream_out_closed:
|
if not self.stream_started or self.stream_out_closed:
|
||||||
raise socket.error
|
raise socket.error
|
||||||
if len(data) > self.block_size:
|
if len(data) > self.block_size:
|
||||||
@@ -56,22 +55,20 @@ class IBBytestream(object):
|
|||||||
iq['ibb_data']['sid'] = self.sid
|
iq['ibb_data']['sid'] = self.sid
|
||||||
iq['ibb_data']['seq'] = seq
|
iq['ibb_data']['seq'] = seq
|
||||||
iq['ibb_data']['data'] = data
|
iq['ibb_data']['data'] = data
|
||||||
yield from iq.send(timeout=timeout)
|
await iq.send(timeout=timeout)
|
||||||
return len(data)
|
return len(data)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def sendall(self, data, timeout=None):
|
||||||
def sendall(self, data, timeout=None):
|
|
||||||
sent_len = 0
|
sent_len = 0
|
||||||
while sent_len < len(data):
|
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
|
async def sendfile(self, file, timeout=None):
|
||||||
def sendfile(self, file, timeout=None):
|
|
||||||
while True:
|
while True:
|
||||||
data = file.read(self.block_size)
|
data = file.read(self.block_size)
|
||||||
if not data:
|
if not data:
|
||||||
break
|
break
|
||||||
yield from self.send(data, timeout=timeout)
|
await self.send(data, timeout=timeout)
|
||||||
|
|
||||||
def _recv_data(self, stanza):
|
def _recv_data(self, stanza):
|
||||||
new_seq = stanza['ibb_data']['seq']
|
new_seq = stanza['ibb_data']['seq']
|
||||||
|
|||||||
@@ -89,31 +89,17 @@ class XEP_0050(BasePlugin):
|
|||||||
self.commands = {}
|
self.commands = {}
|
||||||
|
|
||||||
self.xmpp.register_handler(
|
self.xmpp.register_handler(
|
||||||
Callback("Ad-Hoc Execute",
|
Callback("Ad-Hoc Execute",
|
||||||
StanzaPath('iq@type=set/command'),
|
StanzaPath('iq@type=set/command'),
|
||||||
self._handle_command))
|
self._handle_command))
|
||||||
|
|
||||||
register_stanza_plugin(Iq, Command)
|
register_stanza_plugin(Iq, Command)
|
||||||
register_stanza_plugin(Command, Form, iterable=True)
|
register_stanza_plugin(Command, Form, iterable=True)
|
||||||
|
|
||||||
self.xmpp.add_event_handler('command_execute',
|
self.xmpp.add_event_handler('command', self._handle_command_all)
|
||||||
self._handle_command_start)
|
|
||||||
self.xmpp.add_event_handler('command_next',
|
|
||||||
self._handle_command_next)
|
|
||||||
self.xmpp.add_event_handler('command_cancel',
|
|
||||||
self._handle_command_cancel)
|
|
||||||
self.xmpp.add_event_handler('command_complete',
|
|
||||||
self._handle_command_complete)
|
|
||||||
|
|
||||||
def plugin_end(self):
|
def plugin_end(self):
|
||||||
self.xmpp.del_event_handler('command_execute',
|
self.xmpp.del_event_handler('command', self._handle_command_all)
|
||||||
self._handle_command_start)
|
|
||||||
self.xmpp.del_event_handler('command_next',
|
|
||||||
self._handle_command_next)
|
|
||||||
self.xmpp.del_event_handler('command_cancel',
|
|
||||||
self._handle_command_cancel)
|
|
||||||
self.xmpp.del_event_handler('command_complete',
|
|
||||||
self._handle_command_complete)
|
|
||||||
self.xmpp.remove_handler('Ad-Hoc Execute')
|
self.xmpp.remove_handler('Ad-Hoc Execute')
|
||||||
self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
|
self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
|
||||||
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
|
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
|
||||||
@@ -201,8 +187,27 @@ class XEP_0050(BasePlugin):
|
|||||||
|
|
||||||
def _handle_command(self, iq):
|
def _handle_command(self, iq):
|
||||||
"""Raise command events based on the command action."""
|
"""Raise command events based on the command action."""
|
||||||
|
self.xmpp.event('command', iq)
|
||||||
self.xmpp.event('command_%s' % iq['command']['action'], iq)
|
self.xmpp.event('command_%s' % iq['command']['action'], iq)
|
||||||
|
|
||||||
|
def _handle_command_all(self, iq: Iq) -> None:
|
||||||
|
action = iq['command']['action']
|
||||||
|
sessionid = iq['command']['sessionid']
|
||||||
|
session = self.sessions.get(sessionid)
|
||||||
|
|
||||||
|
if session is None:
|
||||||
|
return self._handle_command_start(iq)
|
||||||
|
|
||||||
|
if action in ('next', 'execute'):
|
||||||
|
return self._handle_command_next(iq)
|
||||||
|
if action == 'prev':
|
||||||
|
return self._handle_command_prev(iq)
|
||||||
|
if action == 'complete':
|
||||||
|
return self._handle_command_complete(iq)
|
||||||
|
if action == 'cancel':
|
||||||
|
return self._handle_command_cancel(iq)
|
||||||
|
return None
|
||||||
|
|
||||||
def _handle_command_start(self, iq):
|
def _handle_command_start(self, iq):
|
||||||
"""
|
"""
|
||||||
Process an initial request to execute a command.
|
Process an initial request to execute a command.
|
||||||
@@ -468,7 +473,7 @@ class XEP_0050(BasePlugin):
|
|||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
def send_command(self, jid, node, ifrom=None, action='execute',
|
def send_command(self, jid, node, ifrom=None, action='execute',
|
||||||
payload=None, sessionid=None, flow=False, **kwargs):
|
payload=None, sessionid=None, flow=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Create and send a command stanza, without using the provided
|
Create and send a command stanza, without using the provided
|
||||||
workflow management APIs.
|
workflow management APIs.
|
||||||
@@ -611,7 +616,7 @@ class XEP_0050(BasePlugin):
|
|||||||
def terminate_command(self, session):
|
def terminate_command(self, session):
|
||||||
"""
|
"""
|
||||||
Delete a command's session after a command has completed
|
Delete a command's session after a command has completed
|
||||||
or an error has occured.
|
or an error has occurred.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
session -- All stored data relevant to the current
|
session -- All stored data relevant to the current
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class XEP_0054(BasePlugin):
|
|||||||
if iq['type'] == 'result':
|
if iq['type'] == 'result':
|
||||||
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
|
self.api['set_vcard'](jid=iq['from'], args=iq['vcard_temp'])
|
||||||
return
|
return
|
||||||
elif iq['type'] == 'get':
|
elif iq['type'] == 'get' and self.xmpp.is_component:
|
||||||
vcard = self.api['get_vcard'](iq['from'].bare)
|
vcard = self.api['get_vcard'](iq['from'].bare)
|
||||||
if isinstance(vcard, Iq):
|
if isinstance(vcard, Iq):
|
||||||
vcard.send()
|
vcard.send()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ 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,
|
def __init__(self, query, interface, results='substanzas', amount=10,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
|
|||||||
class Set(ElementBase):
|
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
|
results of queries. For example, limiting the number of items
|
||||||
per response or starting at certain positions.
|
per response or starting at certain positions.
|
||||||
|
|
||||||
|
|||||||
@@ -185,14 +185,14 @@ class XEP_0060(BasePlugin):
|
|||||||
|
|
||||||
if config is not None:
|
if config is not None:
|
||||||
form_type = 'http://jabber.org/protocol/pubsub#node_config'
|
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
|
config.field['FORM_TYPE']['value'] = form_type
|
||||||
else:
|
else:
|
||||||
config.add_field(var='FORM_TYPE',
|
config.add_field(var='FORM_TYPE',
|
||||||
ftype='hidden',
|
ftype='hidden',
|
||||||
value=form_type)
|
value=form_type)
|
||||||
if ntype:
|
if ntype:
|
||||||
if 'pubsub#node_type' in config['fields']:
|
if 'pubsub#node_type' in config.get_fields():
|
||||||
config.field['pubsub#node_type']['value'] = ntype
|
config.field['pubsub#node_type']['value'] = ntype
|
||||||
else:
|
else:
|
||||||
config.add_field(var='pubsub#node_type', value=ntype)
|
config.add_field(var='pubsub#node_type', value=ntype)
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ class Item(ElementBase):
|
|||||||
self.xml.append(value)
|
self.xml.append(value)
|
||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self):
|
||||||
childs = list(self.xml)
|
children = list(self.xml)
|
||||||
if len(childs) > 0:
|
if len(children) > 0:
|
||||||
return childs[0]
|
return children[0]
|
||||||
|
|
||||||
def del_payload(self):
|
def del_payload(self):
|
||||||
for child in self.xml:
|
for child in self.xml:
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ class EventItem(ElementBase):
|
|||||||
self.xml.append(value)
|
self.xml.append(value)
|
||||||
|
|
||||||
def get_payload(self):
|
def get_payload(self):
|
||||||
childs = list(self.xml)
|
children = list(self.xml)
|
||||||
if len(childs) > 0:
|
if len(children) > 0:
|
||||||
return childs[0]
|
return children[0]
|
||||||
|
|
||||||
def del_payload(self):
|
def del_payload(self):
|
||||||
for child in self.xml:
|
for child in self.xml:
|
||||||
|
|||||||
@@ -55,18 +55,17 @@ class XEP_0065(BasePlugin):
|
|||||||
"""Returns the socket associated to the SID."""
|
"""Returns the socket associated to the SID."""
|
||||||
return self._sessions.get(sid, None)
|
return self._sessions.get(sid, None)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def handshake(self, to, ifrom=None, sid=None, timeout=None):
|
||||||
def handshake(self, to, ifrom=None, sid=None, timeout=None):
|
|
||||||
""" Starts the handshake to establish the socks5 bytestreams
|
""" Starts the handshake to establish the socks5 bytestreams
|
||||||
connection.
|
connection.
|
||||||
"""
|
"""
|
||||||
if not self._proxies:
|
if not self._proxies:
|
||||||
self._proxies = yield from self.discover_proxies()
|
self._proxies = await self.discover_proxies()
|
||||||
|
|
||||||
if sid is None:
|
if sid is None:
|
||||||
sid = uuid4().hex
|
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']
|
proxy = used['socks']['streamhost_used']['jid']
|
||||||
|
|
||||||
if proxy not in self._proxies:
|
if proxy not in self._proxies:
|
||||||
@@ -74,16 +73,16 @@ class XEP_0065(BasePlugin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
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._get_dest_sha1(sid, self.xmpp.boundjid, to),
|
||||||
self._proxies[proxy][0],
|
self._proxies[proxy][0],
|
||||||
self._proxies[proxy][1]))[1]
|
self._proxies[proxy][1]))[1]
|
||||||
except socket.error:
|
except socket.error:
|
||||||
return None
|
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.
|
# 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)
|
sock = self.get_socket(sid)
|
||||||
self.xmpp.event('stream:%s:%s' % (sid, to), sock)
|
self.xmpp.event('stream:%s:%s' % (sid, to), sock)
|
||||||
return sock
|
return sock
|
||||||
@@ -105,8 +104,7 @@ class XEP_0065(BasePlugin):
|
|||||||
iq['socks'].add_streamhost(proxy, host, port)
|
iq['socks'].add_streamhost(proxy, host, port)
|
||||||
return iq.send(timeout=timeout, callback=callback)
|
return iq.send(timeout=timeout, callback=callback)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def discover_proxies(self, jid=None, ifrom=None, timeout=None):
|
||||||
def discover_proxies(self, jid=None, ifrom=None, timeout=None):
|
|
||||||
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
|
"""Auto-discover the JIDs of SOCKS5 proxies on an XMPP server."""
|
||||||
if jid is None:
|
if jid is None:
|
||||||
if self.xmpp.is_component:
|
if self.xmpp.is_component:
|
||||||
@@ -116,7 +114,7 @@ class XEP_0065(BasePlugin):
|
|||||||
|
|
||||||
discovered = set()
|
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_items = {item[0] for item in disco_items['disco_items']['items']}
|
||||||
|
|
||||||
disco_info_futures = {}
|
disco_info_futures = {}
|
||||||
@@ -125,7 +123,7 @@ class XEP_0065(BasePlugin):
|
|||||||
|
|
||||||
for item in disco_items:
|
for item in disco_items:
|
||||||
try:
|
try:
|
||||||
disco_info = yield from disco_info_futures[item]
|
disco_info = await disco_info_futures[item]
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
@@ -137,7 +135,7 @@ class XEP_0065(BasePlugin):
|
|||||||
|
|
||||||
for jid in discovered:
|
for jid in discovered:
|
||||||
try:
|
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'],
|
self._proxies[jid] = (addr['socks']['streamhost']['host'],
|
||||||
addr['socks']['streamhost']['port'])
|
addr['socks']['streamhost']['port'])
|
||||||
except XMPPError:
|
except XMPPError:
|
||||||
@@ -182,9 +180,8 @@ class XEP_0065(BasePlugin):
|
|||||||
streamhost['host'],
|
streamhost['host'],
|
||||||
streamhost['port']))
|
streamhost['port']))
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def gather(futures, iq, streamhosts):
|
||||||
def gather(futures, iq, streamhosts):
|
proxies = await asyncio.gather(*futures, return_exceptions=True)
|
||||||
proxies = yield from asyncio.gather(*futures, return_exceptions=True)
|
|
||||||
for streamhost, proxy in zip(streamhosts, proxies):
|
for streamhost, proxy in zip(streamhosts, proxies):
|
||||||
if isinstance(proxy, ValueError):
|
if isinstance(proxy, ValueError):
|
||||||
continue
|
continue
|
||||||
@@ -194,7 +191,7 @@ class XEP_0065(BasePlugin):
|
|||||||
proxy = proxy[1]
|
proxy = proxy[1]
|
||||||
# TODO: what if the future never happens?
|
# TODO: what if the future never happens?
|
||||||
try:
|
try:
|
||||||
addr, port = yield from proxy.connected
|
addr, port = await proxy.connected
|
||||||
except socket.error:
|
except socket.error:
|
||||||
log.exception('Socket error while connecting to the proxy.')
|
log.exception('Socket error while connecting to the proxy.')
|
||||||
continue
|
continue
|
||||||
@@ -215,7 +212,7 @@ class XEP_0065(BasePlugin):
|
|||||||
self.xmpp.event('socks5_stream', conn)
|
self.xmpp.event('socks5_stream', conn)
|
||||||
self.xmpp.event('stream:%s:%s' % (sid, requester), 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):
|
def activate(self, proxy, sid, target, ifrom=None, timeout=None, callback=None):
|
||||||
"""Activate the socks5 session that has been negotiated."""
|
"""Activate the socks5 session that has been negotiated."""
|
||||||
@@ -233,7 +230,7 @@ class XEP_0065(BasePlugin):
|
|||||||
sock.close()
|
sock.close()
|
||||||
except socket.error:
|
except socket.error:
|
||||||
pass
|
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:
|
if sid in self._sessions:
|
||||||
log.warn(('SOCKS5 session with sid = "%s" was not ' +
|
log.warn(('SOCKS5 session with sid = "%s" was not ' +
|
||||||
'removed from _sessions by sock.close()') % sid)
|
'removed from _sessions by sock.close()') % sid)
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ class Socks5Protocol(asyncio.Protocol):
|
|||||||
def resume_writing(self):
|
def resume_writing(self):
|
||||||
self.paused.set_result(None)
|
self.paused.set_result(None)
|
||||||
|
|
||||||
def write(self, data):
|
async def write(self, data):
|
||||||
yield from self.paused
|
await self.paused
|
||||||
self.transport.write(data)
|
self.transport.write(data)
|
||||||
|
|
||||||
def _send_methods(self):
|
def _send_methods(self):
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class XEP_0077(BasePlugin):
|
|||||||
|
|
||||||
def _force_stream_feature(self, stanza):
|
def _force_stream_feature(self, stanza):
|
||||||
if isinstance(stanza, StreamFeatures):
|
if isinstance(stanza, StreamFeatures):
|
||||||
if self.xmpp.use_tls or self.xmpp.use_ssl:
|
if not self.xmpp.disable_starttls:
|
||||||
if 'starttls' not in self.xmpp.features:
|
if 'starttls' not in self.xmpp.features:
|
||||||
return stanza
|
return stanza
|
||||||
elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
|
elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ def time(hour=None, min=None, sec=None, micro=None, offset=None, obj=False):
|
|||||||
sec = now.second
|
sec = now.second
|
||||||
if micro is None:
|
if micro is None:
|
||||||
micro = now.microsecond
|
micro = now.microsecond
|
||||||
if offset is None:
|
if offset in (None, 0):
|
||||||
offset = tzutc()
|
offset = tzutc()
|
||||||
elif not isinstance(offset, dt.tzinfo):
|
elif not isinstance(offset, dt.tzinfo):
|
||||||
offset = tzoffset(None, offset)
|
offset = tzoffset(None, offset)
|
||||||
@@ -177,7 +177,7 @@ def datetime(year=None, month=None, day=None, hour=None,
|
|||||||
sec = now.second
|
sec = now.second
|
||||||
if micro is None:
|
if micro is None:
|
||||||
micro = now.microsecond
|
micro = now.microsecond
|
||||||
if offset is None:
|
if offset in (None, 0):
|
||||||
offset = tzutc()
|
offset = tzutc()
|
||||||
elif not isinstance(offset, dt.tzinfo):
|
elif not isinstance(offset, dt.tzinfo):
|
||||||
offset = tzoffset(None, offset)
|
offset = tzoffset(None, offset)
|
||||||
|
|||||||
@@ -65,9 +65,14 @@ class XEP_0092(BasePlugin):
|
|||||||
iq -- The Iq stanza containing the software version query.
|
iq -- The Iq stanza containing the software version query.
|
||||||
"""
|
"""
|
||||||
iq = iq.reply()
|
iq = iq.reply()
|
||||||
iq['software_version']['name'] = self.software_name
|
if self.software_name:
|
||||||
iq['software_version']['version'] = self.version
|
iq['software_version']['name'] = self.software_name
|
||||||
iq['software_version']['os'] = self.os
|
iq['software_version']['version'] = self.version
|
||||||
|
iq['software_version']['os'] = self.os
|
||||||
|
else:
|
||||||
|
iq.error()
|
||||||
|
iq['error']['type'] = 'cancel'
|
||||||
|
iq['error']['condition'] = 'service-unavailable'
|
||||||
iq.send()
|
iq.send()
|
||||||
|
|
||||||
def get_version(self, jid, ifrom=None, timeout=None, callback=None,
|
def get_version(self, jid, ifrom=None, timeout=None, callback=None,
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ class XEP_0095(BasePlugin):
|
|||||||
extension='bad-profile',
|
extension='bad-profile',
|
||||||
extension_ns=SI.namespace)
|
extension_ns=SI.namespace)
|
||||||
|
|
||||||
neg = iq['si']['feature_neg']['form']['fields']
|
neg = iq['si']['feature_neg']['form'].get_fields()
|
||||||
options = neg['stream-method']['options'] or []
|
options = neg['stream-method']['options'] or []
|
||||||
methods = []
|
methods = []
|
||||||
for opt in options:
|
for opt in options:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from slixmpp.stanza import StreamFeatures, Presence, Iq
|
|||||||
from slixmpp.xmlstream import register_stanza_plugin, JID
|
from slixmpp.xmlstream import register_stanza_plugin, JID
|
||||||
from slixmpp.xmlstream.handler import Callback
|
from slixmpp.xmlstream.handler import Callback
|
||||||
from slixmpp.xmlstream.matcher import StanzaPath
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from slixmpp.util import MemoryCache
|
||||||
from slixmpp import asyncio
|
from slixmpp import asyncio
|
||||||
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
|
from slixmpp.exceptions import XMPPError, IqError, IqTimeout
|
||||||
from slixmpp.plugins import BasePlugin
|
from slixmpp.plugins import BasePlugin
|
||||||
@@ -27,7 +28,7 @@ log = logging.getLogger(__name__)
|
|||||||
class XEP_0115(BasePlugin):
|
class XEP_0115(BasePlugin):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
XEP-0115: Entity Capabalities
|
XEP-0115: Entity Capabilities
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = 'xep_0115'
|
name = 'xep_0115'
|
||||||
@@ -37,7 +38,8 @@ class XEP_0115(BasePlugin):
|
|||||||
default_config = {
|
default_config = {
|
||||||
'hash': 'sha-1',
|
'hash': 'sha-1',
|
||||||
'caps_node': None,
|
'caps_node': None,
|
||||||
'broadcast': True
|
'broadcast': True,
|
||||||
|
'cache': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
@@ -48,6 +50,9 @@ class XEP_0115(BasePlugin):
|
|||||||
if self.caps_node is None:
|
if self.caps_node is None:
|
||||||
self.caps_node = 'http://slixmpp.com/ver/%s' % __version__
|
self.caps_node = 'http://slixmpp.com/ver/%s' % __version__
|
||||||
|
|
||||||
|
if self.cache is None:
|
||||||
|
self.cache = MemoryCache()
|
||||||
|
|
||||||
register_stanza_plugin(Presence, stanza.Capabilities)
|
register_stanza_plugin(Presence, stanza.Capabilities)
|
||||||
register_stanza_plugin(StreamFeatures, stanza.Capabilities)
|
register_stanza_plugin(StreamFeatures, stanza.Capabilities)
|
||||||
|
|
||||||
@@ -132,8 +137,7 @@ class XEP_0115(BasePlugin):
|
|||||||
|
|
||||||
self.xmpp.event('entity_caps', p)
|
self.xmpp.event('entity_caps', p)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _process_caps(self, pres):
|
||||||
def _process_caps(self, pres):
|
|
||||||
if not pres['caps']['hash']:
|
if not pres['caps']['hash']:
|
||||||
log.debug("Received unsupported legacy caps: %s, %s, %s",
|
log.debug("Received unsupported legacy caps: %s, %s, %s",
|
||||||
pres['caps']['node'],
|
pres['caps']['node'],
|
||||||
@@ -164,7 +168,7 @@ class XEP_0115(BasePlugin):
|
|||||||
log.debug("New caps verification string: %s", ver)
|
log.debug("New caps verification string: %s", ver)
|
||||||
try:
|
try:
|
||||||
node = '%s#%s' % (pres['caps']['node'], ver)
|
node = '%s#%s' % (pres['caps']['node'], ver)
|
||||||
caps = yield from self.xmpp['xep_0030'].get_info(pres['from'], node,
|
caps = await self.xmpp['xep_0030'].get_info(pres['from'], node,
|
||||||
coroutine=True)
|
coroutine=True)
|
||||||
|
|
||||||
if isinstance(caps, Iq):
|
if isinstance(caps, Iq):
|
||||||
@@ -199,8 +203,8 @@ class XEP_0115(BasePlugin):
|
|||||||
log.debug("Non form extension found, ignoring for caps")
|
log.debug("Non form extension found, ignoring for caps")
|
||||||
caps.xml.remove(stanza.xml)
|
caps.xml.remove(stanza.xml)
|
||||||
continue
|
continue
|
||||||
if 'FORM_TYPE' in stanza['fields']:
|
if 'FORM_TYPE' in stanza.get_fields():
|
||||||
f_type = tuple(stanza['fields']['FORM_TYPE']['value'])
|
f_type = tuple(stanza.get_fields()['FORM_TYPE']['value'])
|
||||||
form_types.append(f_type)
|
form_types.append(f_type)
|
||||||
deduped_form_types.add(f_type)
|
deduped_form_types.add(f_type)
|
||||||
if len(form_types) != len(deduped_form_types):
|
if len(form_types) != len(deduped_form_types):
|
||||||
@@ -214,7 +218,7 @@ class XEP_0115(BasePlugin):
|
|||||||
log.debug("Extra FORM_TYPE data, invalid for caps")
|
log.debug("Extra FORM_TYPE data, invalid for caps")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if stanza['fields']['FORM_TYPE']['type'] != 'hidden':
|
if stanza.get_fields()['FORM_TYPE']['type'] != 'hidden':
|
||||||
log.debug("Field FORM_TYPE type not 'hidden', " + \
|
log.debug("Field FORM_TYPE type not 'hidden', " + \
|
||||||
"ignoring form for caps")
|
"ignoring form for caps")
|
||||||
caps.xml.remove(stanza.xml)
|
caps.xml.remove(stanza.xml)
|
||||||
@@ -253,7 +257,7 @@ class XEP_0115(BasePlugin):
|
|||||||
|
|
||||||
for stanza in info['substanzas']:
|
for stanza in info['substanzas']:
|
||||||
if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
|
if isinstance(stanza, self.xmpp['xep_0004'].stanza.Form):
|
||||||
if 'FORM_TYPE' in stanza['fields']:
|
if 'FORM_TYPE' in stanza.get_fields():
|
||||||
f_type = stanza['values']['FORM_TYPE']
|
f_type = stanza['values']['FORM_TYPE']
|
||||||
if len(f_type):
|
if len(f_type):
|
||||||
f_type = f_type[0]
|
f_type = f_type[0]
|
||||||
@@ -265,11 +269,11 @@ class XEP_0115(BasePlugin):
|
|||||||
for f_type in sorted_forms:
|
for f_type in sorted_forms:
|
||||||
for form in form_types[f_type]:
|
for form in form_types[f_type]:
|
||||||
S += '%s<' % f_type
|
S += '%s<' % f_type
|
||||||
fields = sorted(form['fields'].keys())
|
fields = sorted(form.get_fields().keys())
|
||||||
fields.remove('FORM_TYPE')
|
fields.remove('FORM_TYPE')
|
||||||
for field in fields:
|
for field in fields:
|
||||||
S += '%s<' % field
|
S += '%s<' % field
|
||||||
vals = form['fields'][field].get_value(convert=False)
|
vals = form.get_fields()[field].get_value(convert=False)
|
||||||
if vals is None:
|
if vals is None:
|
||||||
S += '<'
|
S += '<'
|
||||||
else:
|
else:
|
||||||
@@ -280,10 +284,9 @@ class XEP_0115(BasePlugin):
|
|||||||
binary = hash(S.encode('utf8')).digest()
|
binary = hash(S.encode('utf8')).digest()
|
||||||
return base64.b64encode(binary).decode('utf-8')
|
return base64.b64encode(binary).decode('utf-8')
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def update_caps(self, jid=None, node=None, preserve=False):
|
||||||
def update_caps(self, jid=None, node=None, preserve=False):
|
|
||||||
try:
|
try:
|
||||||
info = yield from self.xmpp['xep_0030'].get_info(jid, node, local=True)
|
info = await self.xmpp['xep_0030'].get_info(jid, node, local=True)
|
||||||
if isinstance(info, Iq):
|
if isinstance(info, Iq):
|
||||||
info = info['disco_info']
|
info = info['disco_info']
|
||||||
ver = self.generate_verstring(info, self.hash)
|
ver = self.generate_verstring(info, self.hash)
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ class StaticCaps(object):
|
|||||||
self.disco = self.xmpp['xep_0030']
|
self.disco = self.xmpp['xep_0030']
|
||||||
self.caps = self.xmpp['xep_0115']
|
self.caps = self.xmpp['xep_0115']
|
||||||
self.static = static
|
self.static = static
|
||||||
self.ver_cache = {}
|
|
||||||
self.jid_vers = {}
|
self.jid_vers = {}
|
||||||
|
|
||||||
def supports(self, jid, node, ifrom, data):
|
def supports(self, jid, node, ifrom, data):
|
||||||
@@ -128,7 +127,7 @@ class StaticCaps(object):
|
|||||||
info = data.get('info', None)
|
info = data.get('info', None)
|
||||||
if not verstring or not info:
|
if not verstring or not info:
|
||||||
return
|
return
|
||||||
self.ver_cache[verstring] = info
|
self.caps.cache.store(verstring, info)
|
||||||
|
|
||||||
def assign_verstring(self, jid, node, ifrom, data):
|
def assign_verstring(self, jid, node, ifrom, data):
|
||||||
if isinstance(jid, JID):
|
if isinstance(jid, JID):
|
||||||
@@ -139,4 +138,7 @@ class StaticCaps(object):
|
|||||||
return self.jid_vers.get(jid, None)
|
return self.jid_vers.get(jid, None)
|
||||||
|
|
||||||
def get_caps(self, jid, node, ifrom, data):
|
def get_caps(self, jid, node, ifrom, data):
|
||||||
return self.ver_cache.get(data.get('verstring', None), None)
|
verstring = data.get('verstring', None)
|
||||||
|
if verstring is None:
|
||||||
|
return None
|
||||||
|
return self.caps.cache.retrieve(verstring)
|
||||||
|
|||||||
@@ -98,10 +98,9 @@ class XEP_0153(BasePlugin):
|
|||||||
first_future.add_done_callback(propagate_timeout_exception)
|
first_future.add_done_callback(propagate_timeout_exception)
|
||||||
return future
|
return future
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _start(self, event):
|
||||||
def _start(self, event):
|
|
||||||
try:
|
try:
|
||||||
vcard = yield from self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
|
vcard = await self.xmpp['xep_0054'].get_vcard(self.xmpp.boundjid.bare)
|
||||||
data = vcard['vcard_temp']['PHOTO']['BINVAL']
|
data = vcard['vcard_temp']['PHOTO']['BINVAL']
|
||||||
if not data:
|
if not data:
|
||||||
new_hash = ''
|
new_hash = ''
|
||||||
@@ -138,7 +137,11 @@ class XEP_0153(BasePlugin):
|
|||||||
if iq['type'] == 'error':
|
if iq['type'] == 'error':
|
||||||
log.debug('Could not retrieve vCard for %s', jid)
|
log.debug('Could not retrieve vCard for %s', jid)
|
||||||
return
|
return
|
||||||
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
try:
|
||||||
|
data = iq['vcard_temp']['PHOTO']['BINVAL']
|
||||||
|
except ValueError:
|
||||||
|
log.debug('Invalid BINVAL in vCard’s PHOTO for %s:', jid, exc_info=True)
|
||||||
|
data = None
|
||||||
if not data:
|
if not data:
|
||||||
new_hash = ''
|
new_hash = ''
|
||||||
else:
|
else:
|
||||||
@@ -164,10 +167,7 @@ class XEP_0153(BasePlugin):
|
|||||||
data = pres['vcard_temp_update']['photo']
|
data = pres['vcard_temp_update']['photo']
|
||||||
if data is None:
|
if data is None:
|
||||||
return
|
return
|
||||||
elif data == '' or data != self.api['get_hash'](pres['from']):
|
self.xmpp.event('vcard_avatar_update', pres)
|
||||||
ifrom = pres['to'] if self.xmpp.is_component else None
|
|
||||||
self.api['reset_hash'](pres['from'], ifrom=ifrom)
|
|
||||||
self.xmpp.event('vcard_avatar_update', pres)
|
|
||||||
|
|
||||||
# =================================================================
|
# =================================================================
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,10 @@ class XEP_0163(BasePlugin):
|
|||||||
for ns in namespace:
|
for ns in namespace:
|
||||||
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
|
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
|
||||||
jid=jid)
|
jid=jid)
|
||||||
asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
|
asyncio.ensure_future(
|
||||||
|
self.xmpp['xep_0115'].update_caps(jid),
|
||||||
|
loop=self.xmpp.loop,
|
||||||
|
)
|
||||||
|
|
||||||
def remove_interest(self, namespace, jid=None):
|
def remove_interest(self, namespace, jid=None):
|
||||||
"""
|
"""
|
||||||
@@ -81,7 +84,10 @@ class XEP_0163(BasePlugin):
|
|||||||
for ns in namespace:
|
for ns in namespace:
|
||||||
self.xmpp['xep_0030'].del_feature(jid=jid,
|
self.xmpp['xep_0030'].del_feature(jid=jid,
|
||||||
feature='%s+notify' % namespace)
|
feature='%s+notify' % namespace)
|
||||||
asyncio.async(self.xmpp['xep_0115'].update_caps(jid))
|
asyncio.ensure_future(
|
||||||
|
self.xmpp['xep_0115'].update_caps(jid),
|
||||||
|
loop=self.xmpp.loop,
|
||||||
|
)
|
||||||
|
|
||||||
def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
|
def publish(self, stanza, node=None, id=None, options=None, ifrom=None,
|
||||||
timeout_callback=None, callback=None, timeout=None):
|
timeout_callback=None, callback=None, timeout=None):
|
||||||
|
|||||||
@@ -174,8 +174,7 @@ class XEP_0198(BasePlugin):
|
|||||||
req = stanza.RequestAck(self.xmpp)
|
req = stanza.RequestAck(self.xmpp)
|
||||||
self.xmpp.send_raw(str(req))
|
self.xmpp.send_raw(str(req))
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _handle_sm_feature(self, features):
|
||||||
def _handle_sm_feature(self, features):
|
|
||||||
"""
|
"""
|
||||||
Enable or resume stream management.
|
Enable or resume stream management.
|
||||||
|
|
||||||
@@ -203,7 +202,7 @@ class XEP_0198(BasePlugin):
|
|||||||
MatchXPath(stanza.Enabled.tag_name()),
|
MatchXPath(stanza.Enabled.tag_name()),
|
||||||
MatchXPath(stanza.Failed.tag_name())]))
|
MatchXPath(stanza.Failed.tag_name())]))
|
||||||
self.xmpp.register_handler(waiter)
|
self.xmpp.register_handler(waiter)
|
||||||
result = yield from waiter.wait()
|
result = await waiter.wait()
|
||||||
elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
|
elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
|
||||||
self.enabled = True
|
self.enabled = True
|
||||||
resume = stanza.Resume(self.xmpp)
|
resume = stanza.Resume(self.xmpp)
|
||||||
@@ -219,7 +218,7 @@ class XEP_0198(BasePlugin):
|
|||||||
MatchXPath(stanza.Resumed.tag_name()),
|
MatchXPath(stanza.Resumed.tag_name()),
|
||||||
MatchXPath(stanza.Failed.tag_name())]))
|
MatchXPath(stanza.Failed.tag_name())]))
|
||||||
self.xmpp.register_handler(waiter)
|
self.xmpp.register_handler(waiter)
|
||||||
result = yield from waiter.wait()
|
result = await waiter.wait()
|
||||||
if result is not None and result.name == 'resumed':
|
if result is not None and result.name == 'resumed':
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -95,7 +95,10 @@ class XEP_0199(BasePlugin):
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
|
||||||
self.keepalive = True
|
self.keepalive = True
|
||||||
handler = lambda event=None: asyncio.ensure_future(self._keepalive(event))
|
handler = lambda event=None: asyncio.ensure_future(
|
||||||
|
self._keepalive(event),
|
||||||
|
loop=self.xmpp.loop,
|
||||||
|
)
|
||||||
self.xmpp.schedule('Ping keepalive',
|
self.xmpp.schedule('Ping keepalive',
|
||||||
self.interval,
|
self.interval,
|
||||||
handler,
|
handler,
|
||||||
@@ -104,15 +107,14 @@ class XEP_0199(BasePlugin):
|
|||||||
def disable_keepalive(self, event=None):
|
def disable_keepalive(self, event=None):
|
||||||
self.xmpp.cancel_schedule('Ping keepalive')
|
self.xmpp.cancel_schedule('Ping keepalive')
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _keepalive(self, event=None):
|
||||||
def _keepalive(self, event=None):
|
|
||||||
log.debug("Keepalive ping...")
|
log.debug("Keepalive ping...")
|
||||||
try:
|
try:
|
||||||
rtt = yield from self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
|
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
|
||||||
except IqTimeout:
|
except IqTimeout:
|
||||||
log.debug("Did not recieve ping back in time." + \
|
log.debug("Did not receive ping back in time. " + \
|
||||||
"Requesting Reconnect.")
|
"Requesting Reconnect.")
|
||||||
self.xmpp.reconnect()
|
self.xmpp.reconnect(0.0, "Ping timeout after %ds" % self.timeout)
|
||||||
else:
|
else:
|
||||||
log.debug('Keepalive RTT: %s' % rtt)
|
log.debug('Keepalive RTT: %s' % rtt)
|
||||||
|
|
||||||
@@ -145,8 +147,7 @@ class XEP_0199(BasePlugin):
|
|||||||
return iq.send(timeout=timeout, callback=callback,
|
return iq.send(timeout=timeout, callback=callback,
|
||||||
timeout_callback=timeout_callback)
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def ping(self, jid=None, ifrom=None, timeout=None):
|
||||||
def ping(self, jid=None, ifrom=None, timeout=None):
|
|
||||||
"""Send a ping request and calculate RTT.
|
"""Send a ping request and calculate RTT.
|
||||||
This is a coroutine.
|
This is a coroutine.
|
||||||
|
|
||||||
@@ -174,7 +175,7 @@ class XEP_0199(BasePlugin):
|
|||||||
|
|
||||||
log.debug('Pinging %s' % jid)
|
log.debug('Pinging %s' % jid)
|
||||||
try:
|
try:
|
||||||
yield from self.send_ping(jid, ifrom=ifrom, timeout=timeout)
|
await self.send_ping(jid, ifrom=ifrom, timeout=timeout)
|
||||||
except IqError as e:
|
except IqError as e:
|
||||||
if own_host:
|
if own_host:
|
||||||
rtt = time.time() - start
|
rtt = time.time() - start
|
||||||
|
|||||||
@@ -123,5 +123,5 @@ class EntityTime(ElementBase):
|
|||||||
if not isinstance(value, dt.datetime):
|
if not isinstance(value, dt.datetime):
|
||||||
date = xep_0082.parse(value)
|
date = xep_0082.parse(value)
|
||||||
date = date.astimezone(tzutc())
|
date = date.astimezone(tzutc())
|
||||||
value = xep_0082.format_datetime(date)[:-1]
|
value = xep_0082.format_datetime(date)
|
||||||
self._set_sub_text('utc', value)
|
self._set_sub_text('utc', value)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class XEP_0202(BasePlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def plugin_init(self):
|
def plugin_init(self):
|
||||||
"""Start the XEP-0203 plugin."""
|
"""Start the XEP-0202 plugin."""
|
||||||
|
|
||||||
if not self.local_time:
|
if not self.local_time:
|
||||||
def default_local_time(jid):
|
def default_local_time(jid):
|
||||||
|
|||||||
@@ -73,11 +73,11 @@ class XEP_0222(BasePlugin):
|
|||||||
ftype='hidden',
|
ftype='hidden',
|
||||||
value='http://jabber.org/protocol/pubsub#publish-options')
|
value='http://jabber.org/protocol/pubsub#publish-options')
|
||||||
|
|
||||||
fields = options['fields']
|
fields = options.get_fields()
|
||||||
for field, value in self.profile.items():
|
for field, value in self.profile.items():
|
||||||
if field not in fields:
|
if field not in fields:
|
||||||
options.add_field(var=field)
|
options.add_field(var=field)
|
||||||
options['fields'][field]['value'] = value
|
options.get_fields()[field]['value'] = value
|
||||||
|
|
||||||
return self.xmpp['xep_0163'].publish(stanza, node,
|
return self.xmpp['xep_0163'].publish(stanza, node,
|
||||||
options=options,
|
options=options,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class XEP_0223(BasePlugin):
|
|||||||
dependencies = {'xep_0163', 'xep_0060', 'xep_0004'}
|
dependencies = {'xep_0163', 'xep_0060', 'xep_0004'}
|
||||||
|
|
||||||
profile = {'pubsub#persist_items': True,
|
profile = {'pubsub#persist_items': True,
|
||||||
'pubsub#send_last_published_item': 'never'}
|
'pubsub#access_model': 'whitelist'}
|
||||||
|
|
||||||
def configure(self, node, ifrom=None, callback=None, timeout=None):
|
def configure(self, node, ifrom=None, callback=None, timeout=None):
|
||||||
"""
|
"""
|
||||||
@@ -78,7 +78,7 @@ class XEP_0223(BasePlugin):
|
|||||||
for field, value in self.profile.items():
|
for field, value in self.profile.items():
|
||||||
if field not in fields:
|
if field not in fields:
|
||||||
options.add_field(var=field)
|
options.add_field(var=field)
|
||||||
options['fields'][field]['value'] = value
|
options.get_fields()[field]['value'] = value
|
||||||
|
|
||||||
return self.xmpp['xep_0163'].publish(stanza, node, options=options,
|
return self.xmpp['xep_0163'].publish(stanza, node, options=options,
|
||||||
ifrom=ifrom, callback=callback,
|
ifrom=ifrom, callback=callback,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class XEP_0300(BasePlugin):
|
|||||||
stanza = stanza
|
stanza = stanza
|
||||||
default_config = {
|
default_config = {
|
||||||
'block_size': 1024 * 1024, # One MiB
|
'block_size': 1024 * 1024, # One MiB
|
||||||
'prefered': 'sha-256',
|
'preferded': 'sha-256',
|
||||||
'enable_sha-1': False,
|
'enable_sha-1': False,
|
||||||
'enable_sha-256': True,
|
'enable_sha-256': True,
|
||||||
'enable_sha-512': True,
|
'enable_sha-512': True,
|
||||||
@@ -73,7 +73,7 @@ class XEP_0300(BasePlugin):
|
|||||||
|
|
||||||
def compute_hash(self, filename, function=None):
|
def compute_hash(self, filename, function=None):
|
||||||
if function is None:
|
if function is None:
|
||||||
function = self.prefered
|
function = self.preferred
|
||||||
h = self._hashlib_function[function]()
|
h = self._hashlib_function[function]()
|
||||||
with open(filename, 'rb') as f:
|
with open(filename, 'rb') as f:
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ class XEP_0323(BasePlugin):
|
|||||||
request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600
|
request_delay_sec = dtdiff.seconds + dtdiff.days * 24 * 3600
|
||||||
if request_delay_sec <= 0:
|
if request_delay_sec <= 0:
|
||||||
req_ok = False
|
req_ok = False
|
||||||
error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past. Current time: " + dtnow.isoformat()
|
error_msg = "Invalid datetime in 'when' flag, cannot set a time in the past (%s). Current time: %s" % (dt.isoformat(), dtnow.isoformat())
|
||||||
|
|
||||||
if req_ok:
|
if req_ok:
|
||||||
session = self._new_session()
|
session = self._new_session()
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ class XEP_0325(BasePlugin):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if not session in self.sessions:
|
if not session in self.sessions:
|
||||||
# This can happend if a session was deleted, like in a timeout. Just drop the data.
|
# This can happen if a session was deleted, like in a timeout. Just drop the data.
|
||||||
return
|
return
|
||||||
|
|
||||||
if result == "error":
|
if result == "error":
|
||||||
@@ -457,7 +457,7 @@ class XEP_0325(BasePlugin):
|
|||||||
Arguments:
|
Arguments:
|
||||||
from_jid -- The jid of the requester
|
from_jid -- The jid of the requester
|
||||||
to_jid -- The jid of the device(s)
|
to_jid -- The jid of the device(s)
|
||||||
callback -- The callback function to call when data is availble.
|
callback -- The callback function to call when data is available.
|
||||||
|
|
||||||
The callback function must support the following arguments:
|
The callback function must support the following arguments:
|
||||||
|
|
||||||
|
|||||||
15
slixmpp/plugins/xep_0335/__init__.py
Normal file
15
slixmpp/plugins/xep_0335/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2018 Maxime “pep” Buquet
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from slixmpp.plugins.xep_0335 import stanza
|
||||||
|
from slixmpp.plugins.xep_0335.stanza import JSON_Container
|
||||||
|
from slixmpp.plugins.xep_0335.json_containers import XEP_0335
|
||||||
|
|
||||||
|
register_plugin(XEP_0335)
|
||||||
23
slixmpp/plugins/xep_0335/json_containers.py
Normal file
23
slixmpp/plugins/xep_0335/json_containers.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2018 Maxime “pep” Buquet
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp import Message
|
||||||
|
from slixmpp.plugins import BasePlugin
|
||||||
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
|
from slixmpp.plugins.xep_0335 import JSON_Container
|
||||||
|
from slixmpp.plugins.xep_0335 import stanza
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0335(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0335'
|
||||||
|
description = 'XEP-0335: JSON Containers'
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Message, JSON_Container)
|
||||||
28
slixmpp/plugins/xep_0335/stanza.py
Normal file
28
slixmpp/plugins/xep_0335/stanza.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2018 Maxime “pep” Buquet
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from slixmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
|
||||||
|
class JSON_Container(ElementBase):
|
||||||
|
name = 'json'
|
||||||
|
plugin_attrib = 'json'
|
||||||
|
namespace = 'urn:xmpp:json:0'
|
||||||
|
interfaces = {'value'}
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return json.loads(self.xml.text)
|
||||||
|
|
||||||
|
def set_value(self, value):
|
||||||
|
if not isinstance(value, str):
|
||||||
|
value = json.dumps(value)
|
||||||
|
self.xml.text = value
|
||||||
|
|
||||||
|
def del_value(self):
|
||||||
|
self.xml.text = ''
|
||||||
14
slixmpp/plugins/xep_0363/__init__.py
Normal file
14
slixmpp/plugins/xep_0363/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
"""
|
||||||
|
slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2018 Emmanuel Gil Peyrot
|
||||||
|
This file is part of slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from slixmpp.plugins.xep_0363.stanza import Request, Slot, Put, Get, Header
|
||||||
|
from slixmpp.plugins.xep_0363.http_upload import XEP_0363
|
||||||
|
|
||||||
|
register_plugin(XEP_0363)
|
||||||
159
slixmpp/plugins/xep_0363/http_upload.py
Normal file
159
slixmpp/plugins/xep_0363/http_upload.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
"""
|
||||||
|
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
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
from mimetypes import guess_type
|
||||||
|
|
||||||
|
from slixmpp import Iq, __version__
|
||||||
|
from slixmpp.plugins import BasePlugin
|
||||||
|
from slixmpp.xmlstream import register_stanza_plugin
|
||||||
|
from slixmpp.xmlstream.handler import Callback
|
||||||
|
from slixmpp.xmlstream.matcher import StanzaPath
|
||||||
|
from slixmpp.plugins.xep_0363 import stanza, Request, Slot, Put, Get, Header
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class FileUploadError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class UploadServiceNotFound(FileUploadError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class FileTooBig(FileUploadError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class HTTPError(FileUploadError):
|
||||||
|
def __str__(self):
|
||||||
|
return 'Could not upload file: %d (%s)' % (self.args[0], self.args[1])
|
||||||
|
|
||||||
|
class XEP_0363(BasePlugin):
|
||||||
|
''' This plugin only supports Python 3.5+ '''
|
||||||
|
|
||||||
|
name = 'xep_0363'
|
||||||
|
description = 'XEP-0363: HTTP File Upload'
|
||||||
|
dependencies = {'xep_0030', 'xep_0128'}
|
||||||
|
stanza = stanza
|
||||||
|
default_config = {
|
||||||
|
'upload_service': None,
|
||||||
|
'max_file_size': float('+inf'),
|
||||||
|
'default_content_type': 'application/octet-stream',
|
||||||
|
}
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Iq, Request)
|
||||||
|
register_stanza_plugin(Iq, Slot)
|
||||||
|
register_stanza_plugin(Slot, Put)
|
||||||
|
register_stanza_plugin(Slot, Get)
|
||||||
|
register_stanza_plugin(Put, Header, iterable=True)
|
||||||
|
|
||||||
|
self.xmpp.register_handler(
|
||||||
|
Callback('HTTP Upload Request',
|
||||||
|
StanzaPath('iq@type=get/http_upload_request'),
|
||||||
|
self._handle_request))
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self._http_session.close()
|
||||||
|
self.xmpp.remove_handler('HTTP Upload Request')
|
||||||
|
self.xmpp.remove_handler('HTTP Upload Slot')
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature=Request.namespace)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp.plugin['xep_0030'].add_feature(Request.namespace)
|
||||||
|
|
||||||
|
def _handle_request(self, iq):
|
||||||
|
self.xmpp.event('http_upload_request', iq)
|
||||||
|
|
||||||
|
async def find_upload_service(self, domain=None, timeout=None):
|
||||||
|
results = await self.xmpp['xep_0030'].get_info_from_domain(
|
||||||
|
domain=domain, timeout=timeout)
|
||||||
|
|
||||||
|
candidates = []
|
||||||
|
for info in results:
|
||||||
|
for identity in info['disco_info']['identities']:
|
||||||
|
if identity[0] == 'store' and identity[1] == 'file':
|
||||||
|
candidates.append(info)
|
||||||
|
for info in candidates:
|
||||||
|
for feature in info['disco_info']['features']:
|
||||||
|
if feature == Request.namespace:
|
||||||
|
return info
|
||||||
|
|
||||||
|
def request_slot(self, jid, filename, size, content_type=None, ifrom=None,
|
||||||
|
timeout=None, callback=None, timeout_callback=None):
|
||||||
|
iq = self.xmpp.Iq()
|
||||||
|
iq['to'] = jid
|
||||||
|
iq['from'] = ifrom
|
||||||
|
iq['type'] = 'get'
|
||||||
|
request = iq['http_upload_request']
|
||||||
|
request['filename'] = filename
|
||||||
|
request['size'] = str(size)
|
||||||
|
request['content-type'] = content_type or self.default_content_type
|
||||||
|
return iq.send(timeout=timeout, callback=callback,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
|
||||||
|
async def upload_file(self, filename, size=None, content_type=None, *,
|
||||||
|
input_file=None, ifrom=None, domain=None, timeout=None,
|
||||||
|
callback=None, timeout_callback=None):
|
||||||
|
''' Helper function which does all of the uploading process. '''
|
||||||
|
if self.upload_service is None:
|
||||||
|
info_iq = await self.find_upload_service(
|
||||||
|
domain=domain, timeout=timeout)
|
||||||
|
if info_iq is None:
|
||||||
|
raise UploadServiceNotFound()
|
||||||
|
self.upload_service = info_iq['from']
|
||||||
|
for form in info_iq['disco_info'].iterables:
|
||||||
|
values = form['values']
|
||||||
|
if values['FORM_TYPE'] == ['urn:xmpp:http:upload:0']:
|
||||||
|
try:
|
||||||
|
self.max_file_size = int(values['max-file-size'])
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
log.error('Invalid max size received from HTTP File Upload service')
|
||||||
|
self.max_file_size = float('+inf')
|
||||||
|
break
|
||||||
|
|
||||||
|
if input_file is None:
|
||||||
|
input_file = open(filename, 'rb')
|
||||||
|
|
||||||
|
if size is None:
|
||||||
|
size = input_file.seek(0, 2)
|
||||||
|
input_file.seek(0)
|
||||||
|
|
||||||
|
if size > self.max_file_size:
|
||||||
|
raise FileTooBig()
|
||||||
|
|
||||||
|
if content_type is None:
|
||||||
|
content_type = guess_type(filename)[0]
|
||||||
|
if content_type is None:
|
||||||
|
content_type = self.default_content_type
|
||||||
|
|
||||||
|
basename = os.path.basename(filename)
|
||||||
|
slot_iq = await self.request_slot(self.upload_service, basename, size,
|
||||||
|
content_type, ifrom, timeout,
|
||||||
|
timeout_callback=timeout_callback)
|
||||||
|
slot = slot_iq['http_upload_slot']
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'Content-Length': str(size),
|
||||||
|
'Content-Type': content_type or self.default_content_type,
|
||||||
|
**{header['name']: header['value'] for header in slot['put']['headers']}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Do the actual upload here.
|
||||||
|
async with ClientSession(headers={'User-Agent': 'slixmpp ' + __version__}) as session:
|
||||||
|
response = await session.put(
|
||||||
|
slot['put']['url'],
|
||||||
|
data=input_file,
|
||||||
|
headers=headers,
|
||||||
|
timeout=timeout)
|
||||||
|
if response.status >= 400:
|
||||||
|
raise HTTPError(response.status, await response.text())
|
||||||
|
log.info('Response code: %d (%s)', response.status, await response.text())
|
||||||
|
response.close()
|
||||||
|
return slot['get']['url']
|
||||||
48
slixmpp/plugins/xep_0363/stanza.py
Normal file
48
slixmpp/plugins/xep_0363/stanza.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"""
|
||||||
|
slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2018 Emmanuel Gil Peyrot
|
||||||
|
This file is part of slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp.xmlstream import ElementBase
|
||||||
|
|
||||||
|
class Request(ElementBase):
|
||||||
|
plugin_attrib = 'http_upload_request'
|
||||||
|
name = 'request'
|
||||||
|
namespace = 'urn:xmpp:http:upload:0'
|
||||||
|
interfaces = {'filename', 'size', 'content-type'}
|
||||||
|
|
||||||
|
class Slot(ElementBase):
|
||||||
|
plugin_attrib = 'http_upload_slot'
|
||||||
|
name = 'slot'
|
||||||
|
namespace = 'urn:xmpp:http:upload:0'
|
||||||
|
|
||||||
|
class Put(ElementBase):
|
||||||
|
plugin_attrib = 'put'
|
||||||
|
name = 'put'
|
||||||
|
namespace = 'urn:xmpp:http:upload:0'
|
||||||
|
interfaces = {'url'}
|
||||||
|
|
||||||
|
class Get(ElementBase):
|
||||||
|
plugin_attrib = 'get'
|
||||||
|
name = 'get'
|
||||||
|
namespace = 'urn:xmpp:http:upload:0'
|
||||||
|
interfaces = {'url'}
|
||||||
|
|
||||||
|
class Header(ElementBase):
|
||||||
|
plugin_attrib = 'header'
|
||||||
|
name = 'header'
|
||||||
|
namespace = 'urn:xmpp:http:upload:0'
|
||||||
|
plugin_multi_attrib = 'headers'
|
||||||
|
interfaces = {'name', 'value'}
|
||||||
|
|
||||||
|
def get_value(self):
|
||||||
|
return self.xml.text
|
||||||
|
|
||||||
|
def set_value(self, value):
|
||||||
|
self.xml.text = value
|
||||||
|
|
||||||
|
def del_value(self):
|
||||||
|
self.xml.text = ''
|
||||||
@@ -49,15 +49,17 @@ class XEP_0380(BasePlugin):
|
|||||||
|
|
||||||
register_stanza_plugin(Message, Encryption)
|
register_stanza_plugin(Message, Encryption)
|
||||||
|
|
||||||
def plugin_end(self):
|
|
||||||
self.xmpp.remove_handler('Chat State')
|
|
||||||
|
|
||||||
def session_bind(self, jid):
|
def session_bind(self, jid):
|
||||||
self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace)
|
self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace)
|
||||||
|
|
||||||
def has_eme(self, msg):
|
def has_eme(self, msg):
|
||||||
return msg.xml.find('{%s}encryption' % Encryption.namespace) is not None
|
return msg.xml.find('{%s}encryption' % Encryption.namespace) is not None
|
||||||
|
|
||||||
|
def add_eme(self, msg: Message, namespace: str) -> Message:
|
||||||
|
msg['eme']['name'] = self.mechanisms[namespace]
|
||||||
|
msg['eme']['namespace'] = namespace
|
||||||
|
return msg
|
||||||
|
|
||||||
def replace_body_with_eme(self, msg):
|
def replace_body_with_eme(self, msg):
|
||||||
eme = msg['eme']
|
eme = msg['eme']
|
||||||
namespace = eme['namespace']
|
namespace = eme['namespace']
|
||||||
|
|||||||
15
slixmpp/plugins/xep_0394/__init__.py
Normal file
15
slixmpp/plugins/xep_0394/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from slixmpp.plugins.xep_0394.stanza import Markup, Span, BlockCode, List, Li, BlockQuote
|
||||||
|
from slixmpp.plugins.xep_0394.markup import XEP_0394
|
||||||
|
|
||||||
|
|
||||||
|
register_plugin(XEP_0394)
|
||||||
161
slixmpp/plugins/xep_0394/markup.py
Normal file
161
slixmpp/plugins/xep_0394/markup.py
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from slixmpp.stanza import Message
|
||||||
|
from slixmpp.plugins import BasePlugin
|
||||||
|
from slixmpp.xmlstream import register_stanza_plugin, ET, tostring
|
||||||
|
from slixmpp.plugins.xep_0394 import stanza, Markup, Span, BlockCode, List, Li, BlockQuote
|
||||||
|
from slixmpp.plugins.xep_0071 import XHTML_IM
|
||||||
|
|
||||||
|
|
||||||
|
class Start:
|
||||||
|
def __init__(self, elem):
|
||||||
|
self.elem = elem
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Start(%s)' % self.elem
|
||||||
|
|
||||||
|
|
||||||
|
class End:
|
||||||
|
def __init__(self, elem):
|
||||||
|
self.elem = elem
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'End(%s)' % self.elem
|
||||||
|
|
||||||
|
|
||||||
|
class XEP_0394(BasePlugin):
|
||||||
|
|
||||||
|
name = 'xep_0394'
|
||||||
|
description = 'XEP-0394: Message Markup'
|
||||||
|
dependencies = {'xep_0030', 'xep_0071'}
|
||||||
|
stanza = stanza
|
||||||
|
|
||||||
|
def plugin_init(self):
|
||||||
|
register_stanza_plugin(Message, Markup)
|
||||||
|
|
||||||
|
def session_bind(self, jid):
|
||||||
|
self.xmpp['xep_0030'].add_feature(feature=Markup.namespace)
|
||||||
|
|
||||||
|
def plugin_end(self):
|
||||||
|
self.xmpp['xep_0030'].del_feature(feature=Markup.namespace)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _split_first_level(body, markup_elem):
|
||||||
|
split_points = []
|
||||||
|
elements = {}
|
||||||
|
for markup in markup_elem['substanzas']:
|
||||||
|
start = markup['start']
|
||||||
|
end = markup['end']
|
||||||
|
split_points.append(start)
|
||||||
|
split_points.append(end)
|
||||||
|
elements.setdefault(start, []).append(Start(markup))
|
||||||
|
elements.setdefault(end, []).append(End(markup))
|
||||||
|
if isinstance(markup, List):
|
||||||
|
lis = markup['lis']
|
||||||
|
for i, li in enumerate(lis):
|
||||||
|
start = li['start']
|
||||||
|
split_points.append(start)
|
||||||
|
li_end = lis[i + 1]['start'] if i < len(lis) - 1 else end
|
||||||
|
elements.setdefault(li_end, []).append(End(li))
|
||||||
|
elements.setdefault(start, []).append(Start(li))
|
||||||
|
split_points = set(split_points)
|
||||||
|
new_body = [[]]
|
||||||
|
for i, letter in enumerate(body + '\x00'):
|
||||||
|
if i in split_points:
|
||||||
|
body_elements = []
|
||||||
|
for elem in elements[i]:
|
||||||
|
body_elements.append(elem)
|
||||||
|
new_body.append(body_elements)
|
||||||
|
new_body.append([])
|
||||||
|
new_body[-1].append(letter)
|
||||||
|
new_body[-1] = new_body[-1][:-1]
|
||||||
|
final = []
|
||||||
|
for chunk in new_body:
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
final.append(''.join(chunk) if isinstance(chunk[0], str) else chunk)
|
||||||
|
return final
|
||||||
|
|
||||||
|
def to_plain_text(self, body, markup_elem):
|
||||||
|
chunks = self._split_first_level(body, markup_elem)
|
||||||
|
final = []
|
||||||
|
for chunk in chunks:
|
||||||
|
if isinstance(chunk, str):
|
||||||
|
final.append(chunk)
|
||||||
|
return ''.join(final)
|
||||||
|
|
||||||
|
def to_xhtml_im(self, body, markup_elem):
|
||||||
|
chunks = self._split_first_level(body, markup_elem)
|
||||||
|
final = []
|
||||||
|
stack = []
|
||||||
|
for chunk in chunks:
|
||||||
|
if isinstance(chunk, str):
|
||||||
|
chunk = (chunk.replace("&", '&')
|
||||||
|
.replace('<', '<')
|
||||||
|
.replace('>', '>')
|
||||||
|
.replace('"', '"')
|
||||||
|
.replace("'", ''')
|
||||||
|
.replace('\n', '<br/>'))
|
||||||
|
final.append(chunk)
|
||||||
|
continue
|
||||||
|
num_end = 0
|
||||||
|
for elem in chunk:
|
||||||
|
if isinstance(elem, End):
|
||||||
|
num_end += 1
|
||||||
|
|
||||||
|
for i in range(num_end):
|
||||||
|
stack_top = stack.pop()
|
||||||
|
for elem in chunk:
|
||||||
|
if not isinstance(elem, End):
|
||||||
|
continue
|
||||||
|
elem = elem.elem
|
||||||
|
if elem is stack_top:
|
||||||
|
if isinstance(elem, Span):
|
||||||
|
final.append('</span>')
|
||||||
|
elif isinstance(elem, BlockCode):
|
||||||
|
final.append('</code></pre>')
|
||||||
|
elif isinstance(elem, List):
|
||||||
|
final.append('</ul>')
|
||||||
|
elif isinstance(elem, Li):
|
||||||
|
final.append('</li>')
|
||||||
|
elif isinstance(elem, BlockQuote):
|
||||||
|
final.append('</blockquote>')
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
for elem in chunk:
|
||||||
|
if not isinstance(elem, Start):
|
||||||
|
continue
|
||||||
|
elem = elem.elem
|
||||||
|
stack.append(elem)
|
||||||
|
if isinstance(elem, Span):
|
||||||
|
style = []
|
||||||
|
for type_ in elem['types']:
|
||||||
|
if type_ == 'emphasis':
|
||||||
|
style.append('font-style: italic;')
|
||||||
|
if type_ == 'code':
|
||||||
|
style.append('font-family: monospace;')
|
||||||
|
if type_ == 'deleted':
|
||||||
|
style.append('text-decoration: line-through;')
|
||||||
|
final.append("<span style='%s'>" % ' '.join(style))
|
||||||
|
elif isinstance(elem, BlockCode):
|
||||||
|
final.append('<pre><code>')
|
||||||
|
elif isinstance(elem, List):
|
||||||
|
final.append('<ul>')
|
||||||
|
elif isinstance(elem, Li):
|
||||||
|
final.append('<li>')
|
||||||
|
elif isinstance(elem, BlockQuote):
|
||||||
|
final.append('<blockquote>')
|
||||||
|
p = "<p xmlns='http://www.w3.org/1999/xhtml'>%s</p>" % ''.join(final)
|
||||||
|
p2 = ET.fromstring(p)
|
||||||
|
print('coucou', p, tostring(p2))
|
||||||
|
xhtml_im = XHTML_IM()
|
||||||
|
xhtml_im['body'] = p2
|
||||||
|
return xhtml_im
|
||||||
123
slixmpp/plugins/xep_0394/stanza.py
Normal file
123
slixmpp/plugins/xep_0394/stanza.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
"""
|
||||||
|
Slixmpp: The Slick XMPP Library
|
||||||
|
Copyright (C) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
|
||||||
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
|
See the file LICENSE for copying permission.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from slixmpp.xmlstream import ElementBase, register_stanza_plugin, ET
|
||||||
|
|
||||||
|
|
||||||
|
class Markup(ElementBase):
|
||||||
|
namespace = 'urn:xmpp:markup:0'
|
||||||
|
name = 'markup'
|
||||||
|
plugin_attrib = 'markup'
|
||||||
|
|
||||||
|
|
||||||
|
class _FirstLevel(ElementBase):
|
||||||
|
namespace = 'urn:xmpp:markup:0'
|
||||||
|
interfaces = {'start', 'end'}
|
||||||
|
|
||||||
|
def get_start(self):
|
||||||
|
return int(self._get_attr('start'))
|
||||||
|
|
||||||
|
def set_start(self, value):
|
||||||
|
self._set_attr('start', '%d' % value)
|
||||||
|
|
||||||
|
def get_end(self):
|
||||||
|
return int(self._get_attr('end'))
|
||||||
|
|
||||||
|
def set_end(self, value):
|
||||||
|
self._set_attr('end', '%d' % value)
|
||||||
|
|
||||||
|
class Span(_FirstLevel):
|
||||||
|
name = 'span'
|
||||||
|
plugin_attrib = 'span'
|
||||||
|
plugin_multi_attrib = 'spans'
|
||||||
|
interfaces = {'start', 'end', 'types'}
|
||||||
|
|
||||||
|
def get_types(self):
|
||||||
|
types = []
|
||||||
|
if self.xml.find('{urn:xmpp:markup:0}emphasis') is not None:
|
||||||
|
types.append('emphasis')
|
||||||
|
if self.xml.find('{urn:xmpp:markup:0}code') is not None:
|
||||||
|
types.append('code')
|
||||||
|
if self.xml.find('{urn:xmpp:markup:0}deleted') is not None:
|
||||||
|
types.append('deleted')
|
||||||
|
return types
|
||||||
|
|
||||||
|
def set_types(self, value):
|
||||||
|
del self['types']
|
||||||
|
for type_ in value:
|
||||||
|
if type_ == 'emphasis':
|
||||||
|
self.xml.append(ET.Element('{urn:xmpp:markup:0}emphasis'))
|
||||||
|
elif type_ == 'code':
|
||||||
|
self.xml.append(ET.Element('{urn:xmpp:markup:0}code'))
|
||||||
|
elif type_ == 'deleted':
|
||||||
|
self.xml.append(ET.Element('{urn:xmpp:markup:0}deleted'))
|
||||||
|
|
||||||
|
def det_types(self):
|
||||||
|
for child in self.xml:
|
||||||
|
self.xml.remove(child)
|
||||||
|
|
||||||
|
|
||||||
|
class _SpanType(ElementBase):
|
||||||
|
namespace = 'urn:xmpp:markup:0'
|
||||||
|
|
||||||
|
|
||||||
|
class EmphasisType(_SpanType):
|
||||||
|
name = 'emphasis'
|
||||||
|
plugin_attrib = 'emphasis'
|
||||||
|
|
||||||
|
|
||||||
|
class CodeType(_SpanType):
|
||||||
|
name = 'code'
|
||||||
|
plugin_attrib = 'code'
|
||||||
|
|
||||||
|
|
||||||
|
class DeletedType(_SpanType):
|
||||||
|
name = 'deleted'
|
||||||
|
plugin_attrib = 'deleted'
|
||||||
|
|
||||||
|
|
||||||
|
class BlockCode(_FirstLevel):
|
||||||
|
name = 'bcode'
|
||||||
|
plugin_attrib = 'bcode'
|
||||||
|
plugin_multi_attrib = 'bcodes'
|
||||||
|
|
||||||
|
|
||||||
|
class List(_FirstLevel):
|
||||||
|
name = 'list'
|
||||||
|
plugin_attrib = 'list'
|
||||||
|
plugin_multi_attrib = 'lists'
|
||||||
|
interfaces = {'start', 'end', 'li'}
|
||||||
|
|
||||||
|
|
||||||
|
class Li(ElementBase):
|
||||||
|
namespace = 'urn:xmpp:markup:0'
|
||||||
|
name = 'li'
|
||||||
|
plugin_attrib = 'li'
|
||||||
|
plugin_multi_attrib = 'lis'
|
||||||
|
interfaces = {'start'}
|
||||||
|
|
||||||
|
def get_start(self):
|
||||||
|
return int(self._get_attr('start'))
|
||||||
|
|
||||||
|
def set_start(self, value):
|
||||||
|
self._set_attr('start', '%d' % value)
|
||||||
|
|
||||||
|
|
||||||
|
class BlockQuote(_FirstLevel):
|
||||||
|
name = 'bquote'
|
||||||
|
plugin_attrib = 'bquote'
|
||||||
|
plugin_multi_attrib = 'bquotes'
|
||||||
|
|
||||||
|
register_stanza_plugin(Markup, Span, iterable=True)
|
||||||
|
register_stanza_plugin(Markup, BlockCode, iterable=True)
|
||||||
|
register_stanza_plugin(Markup, List, iterable=True)
|
||||||
|
register_stanza_plugin(Markup, BlockQuote, iterable=True)
|
||||||
|
register_stanza_plugin(Span, EmphasisType)
|
||||||
|
register_stanza_plugin(Span, CodeType)
|
||||||
|
register_stanza_plugin(Span, DeletedType)
|
||||||
|
register_stanza_plugin(List, Li, iterable=True)
|
||||||
@@ -187,11 +187,24 @@ class Iq(RootStanza):
|
|||||||
|
|
||||||
future = asyncio.Future()
|
future = asyncio.Future()
|
||||||
|
|
||||||
|
# Prevents handlers from existing forever.
|
||||||
|
if timeout is None:
|
||||||
|
timeout = 120
|
||||||
|
|
||||||
def callback_success(result):
|
def callback_success(result):
|
||||||
if result['type'] == 'error':
|
type_ = result['type']
|
||||||
|
if type_ == 'result':
|
||||||
|
future.set_result(result)
|
||||||
|
elif type_ == 'error':
|
||||||
future.set_exception(IqError(result))
|
future.set_exception(IqError(result))
|
||||||
else:
|
else:
|
||||||
future.set_result(result)
|
# Most likely an iq addressed to ourself, rearm the callback.
|
||||||
|
handler = constr(handler_name,
|
||||||
|
matcher,
|
||||||
|
callback_success,
|
||||||
|
once=True)
|
||||||
|
self.stream.register_handler(handler)
|
||||||
|
return
|
||||||
|
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
self.stream.cancel_schedule('IqTimeout_%s' % self['id'])
|
self.stream.cancel_schedule('IqTimeout_%s' % self['id'])
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class Roster(ElementBase):
|
|||||||
|
|
||||||
def get_ver(self):
|
def get_ver(self):
|
||||||
"""
|
"""
|
||||||
Ensure handling an empty ver attribute propery.
|
Ensure handling an empty ver attribute property.
|
||||||
|
|
||||||
The ver attribute is special in that the presence of the
|
The ver attribute is special in that the presence of the
|
||||||
attribute with an empty value is important for boostrapping
|
attribute with an empty value is important for boostrapping
|
||||||
@@ -50,7 +50,7 @@ class Roster(ElementBase):
|
|||||||
|
|
||||||
def set_ver(self, ver):
|
def set_ver(self, ver):
|
||||||
"""
|
"""
|
||||||
Ensure handling an empty ver attribute propery.
|
Ensure handling an empty ver attribute property.
|
||||||
|
|
||||||
The ver attribute is special in that the presence of the
|
The ver attribute is special in that the presence of the
|
||||||
attribute with an empty value is important for boostrapping
|
attribute with an empty value is important for boostrapping
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ def punycode(domain):
|
|||||||
if char in ILLEGAL_CHARS:
|
if char in ILLEGAL_CHARS:
|
||||||
raise StringprepError
|
raise StringprepError
|
||||||
|
|
||||||
domain_parts.append(label)
|
domain_parts.append(label.encode('ascii'))
|
||||||
return b'.'.join(domain_parts)
|
return b'.'.join(domain_parts)
|
||||||
|
|
||||||
logging.getLogger(__name__).warning('Using slower stringprep, consider '
|
logging.getLogger(__name__).warning('Using slower stringprep, consider '
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ class SlixTest(unittest.TestCase):
|
|||||||
if Matcher is None:
|
if Matcher is None:
|
||||||
raise ValueError("Unknown matching method.")
|
raise ValueError("Unknown matching method.")
|
||||||
test = Matcher(criteria)
|
test = Matcher(criteria)
|
||||||
self.failUnless(test.match(stanza),
|
self.assertTrue(test.match(stanza),
|
||||||
"Stanza did not match using %s method:\n" % method + \
|
"Stanza did not match using %s method:\n" % method + \
|
||||||
"Criteria:\n%s\n" % str(criteria) + \
|
"Criteria:\n%s\n" % str(criteria) + \
|
||||||
"Stanza:\n%s" % str(stanza))
|
"Stanza:\n%s" % str(stanza))
|
||||||
@@ -280,7 +280,7 @@ class SlixTest(unittest.TestCase):
|
|||||||
debug += "Generated stanza:\n%s\n" % highlight(tostring(stanza2.xml))
|
debug += "Generated stanza:\n%s\n" % highlight(tostring(stanza2.xml))
|
||||||
result = self.compare(xml, stanza.xml, stanza2.xml)
|
result = self.compare(xml, stanza.xml, stanza2.xml)
|
||||||
|
|
||||||
self.failUnless(result, debug)
|
self.assertTrue(result, debug)
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Methods for simulating stanza streams.
|
# Methods for simulating stanza streams.
|
||||||
@@ -361,6 +361,7 @@ class SlixTest(unittest.TestCase):
|
|||||||
# Some plugins require messages to have ID values. Set
|
# Some plugins require messages to have ID values. Set
|
||||||
# this to True in tests related to those plugins.
|
# this to True in tests related to those plugins.
|
||||||
self.xmpp.use_message_ids = False
|
self.xmpp.use_message_ids = False
|
||||||
|
self.xmpp.use_presence_ids = False
|
||||||
|
|
||||||
def make_header(self, sto='',
|
def make_header(self, sto='',
|
||||||
sfrom='',
|
sfrom='',
|
||||||
@@ -487,7 +488,7 @@ class SlixTest(unittest.TestCase):
|
|||||||
recv_xml.clear()
|
recv_xml.clear()
|
||||||
recv_xml.attrib = attrib
|
recv_xml.attrib = attrib
|
||||||
|
|
||||||
self.failUnless(
|
self.assertTrue(
|
||||||
self.compare(xml, recv_xml),
|
self.compare(xml, recv_xml),
|
||||||
"Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % (
|
"Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % (
|
||||||
'%s %s' % (xml.tag, xml.attrib),
|
'%s %s' % (xml.tag, xml.attrib),
|
||||||
@@ -543,7 +544,7 @@ class SlixTest(unittest.TestCase):
|
|||||||
xml = self.parse_xml(header2)
|
xml = self.parse_xml(header2)
|
||||||
sent_xml = self.parse_xml(sent_header2)
|
sent_xml = self.parse_xml(sent_header2)
|
||||||
|
|
||||||
self.failUnless(
|
self.assertTrue(
|
||||||
self.compare(xml, sent_xml),
|
self.compare(xml, sent_xml),
|
||||||
"Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
|
"Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
|
||||||
header, sent_header))
|
header, sent_header))
|
||||||
@@ -557,12 +558,12 @@ class SlixTest(unittest.TestCase):
|
|||||||
if sent_data is None:
|
if sent_data is None:
|
||||||
self.fail("No stanza was sent.")
|
self.fail("No stanza was sent.")
|
||||||
if method == 'exact':
|
if method == 'exact':
|
||||||
self.failUnless(self.compare(xml, sent_xml),
|
self.assertTrue(self.compare(xml, sent_xml),
|
||||||
"Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
|
"Features do not match.\nDesired:\n%s\nReceived:\n%s" % (
|
||||||
highlight(tostring(xml)), highlight(tostring(sent_xml))))
|
highlight(tostring(xml)), highlight(tostring(sent_xml))))
|
||||||
elif method == 'mask':
|
elif method == 'mask':
|
||||||
matcher = MatchXMLMask(xml)
|
matcher = MatchXMLMask(xml)
|
||||||
self.failUnless(matcher.match(sent_xml),
|
self.assertTrue(matcher.match(sent_xml),
|
||||||
"Stanza did not match using %s method:\n" % method + \
|
"Stanza did not match using %s method:\n" % method + \
|
||||||
"Criteria:\n%s\n" % highlight(tostring(xml)) + \
|
"Criteria:\n%s\n" % highlight(tostring(xml)) + \
|
||||||
"Stanza:\n%s" % highlight(tostring(sent_xml)))
|
"Stanza:\n%s" % highlight(tostring(sent_xml)))
|
||||||
|
|||||||
@@ -13,3 +13,5 @@
|
|||||||
from slixmpp.util.misc_ops import bytes, unicode, hashes, hash, \
|
from slixmpp.util.misc_ops import bytes, unicode, hashes, hash, \
|
||||||
num_to_bytes, bytes_to_num, quote, \
|
num_to_bytes, bytes_to_num, quote, \
|
||||||
XOR
|
XOR
|
||||||
|
from slixmpp.util.cache import MemoryCache, MemoryPerJidCache, \
|
||||||
|
FileSystemCache, FileSystemPerJidCache
|
||||||
|
|||||||
143
slixmpp/util/cache.py
Normal file
143
slixmpp/util/cache.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
"""
|
||||||
|
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 os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Cache:
|
||||||
|
def retrieve(self, key):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def store(self, key, value):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def remove(self, key):
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
class PerJidCache:
|
||||||
|
def retrieve_by_jid(self, jid, key):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def store_by_jid(self, jid, key, value):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def remove_by_jid(self, jid, key):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
class MemoryCache(Cache):
|
||||||
|
def __init__(self):
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
def retrieve(self, key):
|
||||||
|
return self.cache.get(key, None)
|
||||||
|
|
||||||
|
def store(self, key, value):
|
||||||
|
self.cache[key] = value
|
||||||
|
return True
|
||||||
|
|
||||||
|
def remove(self, key):
|
||||||
|
if key in self.cache:
|
||||||
|
del self.cache[key]
|
||||||
|
return True
|
||||||
|
|
||||||
|
class MemoryPerJidCache(PerJidCache):
|
||||||
|
def __init__(self):
|
||||||
|
self.cache = {}
|
||||||
|
|
||||||
|
def retrieve_by_jid(self, jid, key):
|
||||||
|
cache = self.cache.get(jid, None)
|
||||||
|
if cache is None:
|
||||||
|
return None
|
||||||
|
return cache.get(key, None)
|
||||||
|
|
||||||
|
def store_by_jid(self, jid, key, value):
|
||||||
|
cache = self.cache.setdefault(jid, {})
|
||||||
|
cache[key] = value
|
||||||
|
return True
|
||||||
|
|
||||||
|
def remove_by_jid(self, jid, key):
|
||||||
|
cache = self.cache.get(jid, None)
|
||||||
|
if cache is not None and key in cache:
|
||||||
|
del cache[key]
|
||||||
|
return True
|
||||||
|
|
||||||
|
class FileSystemStorage:
|
||||||
|
def __init__(self, encode, decode, binary):
|
||||||
|
self.encode = encode if encode is not None else lambda x: x
|
||||||
|
self.decode = decode if decode is not None else lambda x: x
|
||||||
|
self.read = 'rb' if binary else 'r'
|
||||||
|
self.write = 'wb' if binary else 'w'
|
||||||
|
|
||||||
|
def _retrieve(self, directory, key):
|
||||||
|
filename = os.path.join(directory, key.replace('/', '_'))
|
||||||
|
try:
|
||||||
|
with open(filename, self.read) as cache_file:
|
||||||
|
return self.decode(cache_file.read())
|
||||||
|
except FileNotFoundError:
|
||||||
|
log.debug('%s not present in cache', key)
|
||||||
|
except OSError:
|
||||||
|
log.debug('Failed to read %s from cache:', key, exc_info=True)
|
||||||
|
except Exception:
|
||||||
|
log.debug('Failed to decode %s from cache:', key, exc_info=True)
|
||||||
|
log.debug('Removing %s entry', key)
|
||||||
|
self._remove(directory, key)
|
||||||
|
|
||||||
|
def _store(self, directory, key, value):
|
||||||
|
filename = os.path.join(directory, key.replace('/', '_'))
|
||||||
|
try:
|
||||||
|
os.makedirs(directory, exist_ok=True)
|
||||||
|
with open(filename, self.write) as output:
|
||||||
|
output.write(self.encode(value))
|
||||||
|
return True
|
||||||
|
except OSError:
|
||||||
|
log.debug('Failed to store %s to cache:', key, exc_info=True)
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
log.debug('Failed to encode %s to cache:', key, exc_info=True)
|
||||||
|
|
||||||
|
def _remove(self, directory, key):
|
||||||
|
filename = os.path.join(directory, key.replace('/', '_'))
|
||||||
|
try:
|
||||||
|
os.remove(filename)
|
||||||
|
except OSError:
|
||||||
|
log.debug('Failed to remove %s from cache:', key, exc_info=True)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
class FileSystemCache(Cache, FileSystemStorage):
|
||||||
|
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
|
||||||
|
FileSystemStorage.__init__(self, encode, decode, binary)
|
||||||
|
self.base_dir = os.path.join(directory, cache_type)
|
||||||
|
|
||||||
|
def retrieve(self, key):
|
||||||
|
return self._retrieve(self.base_dir, key)
|
||||||
|
|
||||||
|
def store(self, key, value):
|
||||||
|
return self._store(self.base_dir, key, value)
|
||||||
|
|
||||||
|
def remove(self, key):
|
||||||
|
return self._remove(self.base_dir, key)
|
||||||
|
|
||||||
|
class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
|
||||||
|
def __init__(self, directory, cache_type, *, encode=None, decode=None, binary=False):
|
||||||
|
FileSystemStorage.__init__(self, encode, decode, binary)
|
||||||
|
self.base_dir = os.path.join(directory, cache_type)
|
||||||
|
|
||||||
|
def retrieve_by_jid(self, jid, key):
|
||||||
|
directory = os.path.join(self.base_dir, jid)
|
||||||
|
return self._retrieve(directory, key)
|
||||||
|
|
||||||
|
def store_by_jid(self, jid, key, value):
|
||||||
|
directory = os.path.join(self.base_dir, jid)
|
||||||
|
return self._store(directory, key, value)
|
||||||
|
|
||||||
|
def remove_by_jid(self, jid, key):
|
||||||
|
directory = os.path.join(self.base_dir, jid)
|
||||||
|
return self._remove(directory, key)
|
||||||
@@ -516,13 +516,13 @@ else:
|
|||||||
def setup(self, name):
|
def setup(self, name):
|
||||||
authzid = self.credentials['authzid']
|
authzid = self.credentials['authzid']
|
||||||
if not authzid:
|
if not authzid:
|
||||||
authzid = 'xmpp@%s' % self.credentials['service-name']
|
authzid = 'xmpp@' + self.credentials['service-name'].decode()
|
||||||
|
|
||||||
_, self.gss = kerberos.authGSSClientInit(authzid)
|
_, self.gss = kerberos.authGSSClientInit(authzid)
|
||||||
self.step = 0
|
self.step = 0
|
||||||
|
|
||||||
def process(self, challenge=b''):
|
def process(self, challenge=b''):
|
||||||
b64_challenge = b64encode(challenge)
|
b64_challenge = b64encode(challenge).decode('ascii')
|
||||||
try:
|
try:
|
||||||
if self.step == 0:
|
if self.step == 0:
|
||||||
result = kerberos.authGSSClientStep(self.gss, b64_challenge)
|
result = kerberos.authGSSClientStep(self.gss, b64_challenge)
|
||||||
@@ -536,7 +536,7 @@ else:
|
|||||||
|
|
||||||
kerberos.authGSSClientUnwrap(self.gss, b64_challenge)
|
kerberos.authGSSClientUnwrap(self.gss, b64_challenge)
|
||||||
resp = kerberos.authGSSClientResponse(self.gss)
|
resp = kerberos.authGSSClientResponse(self.gss)
|
||||||
kerberos.authGSSClientWrap(self.gss, resp, username)
|
kerberos.authGSSClientWrap(self.gss, resp, username.decode())
|
||||||
|
|
||||||
resp = kerberos.authGSSClientResponse(self.gss)
|
resp = kerberos.authGSSClientResponse(self.gss)
|
||||||
except kerberos.GSSError as e:
|
except kerberos.GSSError as e:
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
# We don't want to have to import the entire library
|
# We don't want to have to import the entire library
|
||||||
# just to get the version info for setup.py
|
# just to get the version info for setup.py
|
||||||
|
|
||||||
__version__ = '1.2.4'
|
__version__ = '1.4.2'
|
||||||
__version_info__ = (1, 2, 4)
|
__version_info__ = (1, 4, 2)
|
||||||
|
|||||||
@@ -45,10 +45,9 @@ class CoroutineCallback(BaseHandler):
|
|||||||
if not asyncio.iscoroutinefunction(pointer):
|
if not asyncio.iscoroutinefunction(pointer):
|
||||||
raise ValueError("Given function is not a coroutine")
|
raise ValueError("Given function is not a coroutine")
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def pointer_wrapper(stanza, *args, **kwargs):
|
||||||
def pointer_wrapper(stanza, *args, **kwargs):
|
|
||||||
try:
|
try:
|
||||||
yield from pointer(stanza, *args, **kwargs)
|
await pointer(stanza, *args, **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
stanza.exception(e)
|
stanza.exception(e)
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ class CoroutineCallback(BaseHandler):
|
|||||||
:meth:`prerun()`. Defaults to ``False``.
|
:meth:`prerun()`. Defaults to ``False``.
|
||||||
"""
|
"""
|
||||||
if not self._instream or instream:
|
if not self._instream or instream:
|
||||||
asyncio.async(self._pointer(payload))
|
asyncio.ensure_future(self._pointer(payload))
|
||||||
if self._once:
|
if self._once:
|
||||||
self._destroy = True
|
self._destroy = True
|
||||||
del self._pointer
|
del self._pointer
|
||||||
|
|||||||
@@ -50,8 +50,7 @@ class Waiter(BaseHandler):
|
|||||||
"""Do not process this handler during the main event loop."""
|
"""Do not process this handler during the main event loop."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def wait(self, timeout=None):
|
||||||
def wait(self, timeout=None):
|
|
||||||
"""Block an event handler while waiting for a stanza to arrive.
|
"""Block an event handler while waiting for a stanza to arrive.
|
||||||
|
|
||||||
Be aware that this will impact performance if called from a
|
Be aware that this will impact performance if called from a
|
||||||
@@ -70,7 +69,7 @@ class Waiter(BaseHandler):
|
|||||||
|
|
||||||
stanza = None
|
stanza = None
|
||||||
try:
|
try:
|
||||||
stanza = yield from self._payload.get()
|
stanza = await self._payload.get()
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
log.warning("Timed out waiting for %s", self.name)
|
log.warning("Timed out waiting for %s", self.name)
|
||||||
self.stream().remove_handler(self.name)
|
self.stream().remove_handler(self.name)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class MatchXMLMask(MatcherBase):
|
|||||||
Defaults to ``"__no_ns__"``.
|
Defaults to ``"__no_ns__"``.
|
||||||
"""
|
"""
|
||||||
if source is None:
|
if source is None:
|
||||||
# If the element was not found. May happend during recursive calls.
|
# If the element was not found. May happen during recursive calls.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Convert the mask to an XML object if it is a string.
|
# Convert the mask to an XML object if it is a string.
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ def default_resolver(loop):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def resolve(host, port=None, service=None, proto='tcp',
|
||||||
def resolve(host, port=None, service=None, proto='tcp',
|
|
||||||
resolver=None, use_ipv6=True, use_aiodns=True, loop=None):
|
resolver=None, use_ipv6=True, use_aiodns=True, loop=None):
|
||||||
"""Peform DNS resolution for a given hostname.
|
"""Peform DNS resolution for a given hostname.
|
||||||
|
|
||||||
@@ -127,7 +126,7 @@ def resolve(host, port=None, service=None, proto='tcp',
|
|||||||
if not service:
|
if not service:
|
||||||
hosts = [(host, port)]
|
hosts = [(host, port)]
|
||||||
else:
|
else:
|
||||||
hosts = yield from get_SRV(host, port, service, proto,
|
hosts = await get_SRV(host, port, service, proto,
|
||||||
resolver=resolver,
|
resolver=resolver,
|
||||||
use_aiodns=use_aiodns)
|
use_aiodns=use_aiodns)
|
||||||
if not hosts:
|
if not hosts:
|
||||||
@@ -141,20 +140,19 @@ def resolve(host, port=None, service=None, proto='tcp',
|
|||||||
results.append((host, '127.0.0.1', port))
|
results.append((host, '127.0.0.1', port))
|
||||||
|
|
||||||
if use_ipv6:
|
if use_ipv6:
|
||||||
aaaa = yield from get_AAAA(host, resolver=resolver,
|
aaaa = await get_AAAA(host, resolver=resolver,
|
||||||
use_aiodns=use_aiodns, loop=loop)
|
use_aiodns=use_aiodns, loop=loop)
|
||||||
for address in aaaa:
|
for address in aaaa:
|
||||||
results.append((host, address, port))
|
results.append((host, address, port))
|
||||||
|
|
||||||
a = yield from get_A(host, resolver=resolver,
|
a = await get_A(host, resolver=resolver,
|
||||||
use_aiodns=use_aiodns, loop=loop)
|
use_aiodns=use_aiodns, loop=loop)
|
||||||
for address in a:
|
for address in a:
|
||||||
results.append((host, address, port))
|
results.append((host, address, port))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def get_A(host, resolver=None, use_aiodns=True, loop=None):
|
||||||
def get_A(host, resolver=None, use_aiodns=True, loop=None):
|
|
||||||
"""Lookup DNS A records for a given host.
|
"""Lookup DNS A records for a given host.
|
||||||
|
|
||||||
If ``resolver`` is not provided, or is ``None``, then resolution will
|
If ``resolver`` is not provided, or is ``None``, then resolution will
|
||||||
@@ -178,7 +176,7 @@ def get_A(host, resolver=None, use_aiodns=True, loop=None):
|
|||||||
# getaddrinfo() method.
|
# getaddrinfo() method.
|
||||||
if resolver is None or not use_aiodns:
|
if resolver is None or not use_aiodns:
|
||||||
try:
|
try:
|
||||||
recs = yield from loop.getaddrinfo(host, None,
|
recs = await loop.getaddrinfo(host, None,
|
||||||
family=socket.AF_INET,
|
family=socket.AF_INET,
|
||||||
type=socket.SOCK_STREAM)
|
type=socket.SOCK_STREAM)
|
||||||
return [rec[4][0] for rec in recs]
|
return [rec[4][0] for rec in recs]
|
||||||
@@ -189,15 +187,14 @@ def get_A(host, resolver=None, use_aiodns=True, loop=None):
|
|||||||
# Using aiodns:
|
# Using aiodns:
|
||||||
future = resolver.query(host, 'A')
|
future = resolver.query(host, 'A')
|
||||||
try:
|
try:
|
||||||
recs = yield from future
|
recs = await future
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug('DNS: Exception while querying for %s A records: %s', host, e)
|
log.debug('DNS: Exception while querying for %s A records: %s', host, e)
|
||||||
recs = []
|
recs = []
|
||||||
return [rec.host for rec in recs]
|
return [rec.host for rec in recs]
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def get_AAAA(host, resolver=None, use_aiodns=True, loop=None):
|
||||||
def get_AAAA(host, resolver=None, use_aiodns=True, loop=None):
|
|
||||||
"""Lookup DNS AAAA records for a given host.
|
"""Lookup DNS AAAA records for a given host.
|
||||||
|
|
||||||
If ``resolver`` is not provided, or is ``None``, then resolution will
|
If ``resolver`` is not provided, or is ``None``, then resolution will
|
||||||
@@ -224,26 +221,25 @@ def get_AAAA(host, resolver=None, use_aiodns=True, loop=None):
|
|||||||
log.debug("DNS: Unable to query %s for AAAA records: IPv6 is not supported", host)
|
log.debug("DNS: Unable to query %s for AAAA records: IPv6 is not supported", host)
|
||||||
return []
|
return []
|
||||||
try:
|
try:
|
||||||
recs = yield from loop.getaddrinfo(host, None,
|
recs = await loop.getaddrinfo(host, None,
|
||||||
family=socket.AF_INET6,
|
family=socket.AF_INET6,
|
||||||
type=socket.SOCK_STREAM)
|
type=socket.SOCK_STREAM)
|
||||||
return [rec[4][0] for rec in recs]
|
return [rec[4][0] for rec in recs]
|
||||||
except (OSError, socket.gaierror):
|
except (OSError, socket.gaierror):
|
||||||
log.debug("DNS: Error retreiving AAAA address " + \
|
log.debug("DNS: Error retrieving AAAA address " + \
|
||||||
"info for %s." % host)
|
"info for %s." % host)
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Using aiodns:
|
# Using aiodns:
|
||||||
future = resolver.query(host, 'AAAA')
|
future = resolver.query(host, 'AAAA')
|
||||||
try:
|
try:
|
||||||
recs = yield from future
|
recs = await future
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug('DNS: Exception while querying for %s AAAA records: %s', host, e)
|
log.debug('DNS: Exception while querying for %s AAAA records: %s', host, e)
|
||||||
recs = []
|
recs = []
|
||||||
return [rec.host for rec in recs]
|
return [rec.host for rec in recs]
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def get_SRV(host, port, service, proto='tcp', resolver=None, use_aiodns=True):
|
||||||
def get_SRV(host, port, service, proto='tcp', resolver=None, use_aiodns=True):
|
|
||||||
"""Perform SRV record resolution for a given host.
|
"""Perform SRV record resolution for a given host.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@@ -277,7 +273,7 @@ def get_SRV(host, port, service, proto='tcp', resolver=None, use_aiodns=True):
|
|||||||
try:
|
try:
|
||||||
future = resolver.query('_%s._%s.%s' % (service, proto, host),
|
future = resolver.query('_%s._%s.%s' % (service, proto, host),
|
||||||
'SRV')
|
'SRV')
|
||||||
recs = yield from future
|
recs = await future
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.debug('DNS: Exception while querying for %s SRV records: %s', host, e)
|
log.debug('DNS: Exception while querying for %s SRV records: %s', host, e)
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -177,8 +177,9 @@ def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
|
|||||||
if '}' in ns_block:
|
if '}' in ns_block:
|
||||||
# Apply the found namespace to following elements
|
# Apply the found namespace to following elements
|
||||||
# that do not have namespaces.
|
# that do not have namespaces.
|
||||||
namespace = ns_block.split('}')[0]
|
ns_block_split = ns_block.split('}')
|
||||||
elements = ns_block.split('}')[1].split('/')
|
namespace = ns_block_split[0]
|
||||||
|
elements = ns_block_split[1].split('/')
|
||||||
else:
|
else:
|
||||||
# Apply the stanza's namespace to the following
|
# Apply the stanza's namespace to the following
|
||||||
# elements since no namespace was provided.
|
# elements since no namespace was provided.
|
||||||
@@ -1291,15 +1292,6 @@ class ElementBase(object):
|
|||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""Stanza objects should be treated as True in boolean contexts.
|
"""Stanza objects should be treated as True in boolean contexts.
|
||||||
|
|
||||||
Python 3.x version.
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
|
||||||
"""Stanza objects should be treated as True in boolean contexts.
|
|
||||||
|
|
||||||
Python 2.x version.
|
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -45,11 +45,12 @@ def tostring(xml=None, xmlns='', stream=None, outbuffer='',
|
|||||||
output = [outbuffer]
|
output = [outbuffer]
|
||||||
|
|
||||||
# Extract the element's tag name.
|
# Extract the element's tag name.
|
||||||
tag_name = xml.tag.split('}', 1)[-1]
|
tag_split = xml.tag.split('}', 1)
|
||||||
|
tag_name = tag_split[-1]
|
||||||
|
|
||||||
# Extract the element's namespace if it is defined.
|
# Extract the element's namespace if it is defined.
|
||||||
if '}' in xml.tag:
|
if '}' in xml.tag:
|
||||||
tag_xmlns = xml.tag.split('}', 1)[0][1:]
|
tag_xmlns = tag_split[0][1:]
|
||||||
else:
|
else:
|
||||||
tag_xmlns = ''
|
tag_xmlns = ''
|
||||||
|
|
||||||
@@ -82,8 +83,9 @@ def tostring(xml=None, xmlns='', stream=None, outbuffer='',
|
|||||||
if '}' not in attrib:
|
if '}' not in attrib:
|
||||||
output.append(' %s="%s"' % (attrib, value))
|
output.append(' %s="%s"' % (attrib, value))
|
||||||
else:
|
else:
|
||||||
attrib_ns = attrib.split('}')[0][1:]
|
attrib_split = attrib.split('}')
|
||||||
attrib = attrib.split('}')[1]
|
attrib_ns = attrib_split[0][1:]
|
||||||
|
attrib = attrib_split[1]
|
||||||
if attrib_ns == XML_NS:
|
if attrib_ns == XML_NS:
|
||||||
output.append(' xml:%s="%s"' % (attrib, value))
|
output.append(' xml:%s="%s"' % (attrib, value))
|
||||||
elif stream and attrib_ns in stream.namespace_map:
|
elif stream and attrib_ns in stream.namespace_map:
|
||||||
@@ -144,10 +146,7 @@ def escape(text, use_cdata=False):
|
|||||||
'"': '"'}
|
'"': '"'}
|
||||||
|
|
||||||
if not use_cdata:
|
if not use_cdata:
|
||||||
text = list(text)
|
return ''.join(escapes.get(c, c) for c in text)
|
||||||
for i, c in enumerate(text):
|
|
||||||
text[i] = escapes.get(c, c)
|
|
||||||
return ''.join(text)
|
|
||||||
else:
|
else:
|
||||||
escape_needed = False
|
escape_needed = False
|
||||||
for c in text:
|
for c in text:
|
||||||
|
|||||||
@@ -64,12 +64,12 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
:param int port: The port to use for the connection. Defaults to 0.
|
:param int port: The port to use for the connection. Defaults to 0.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, socket=None, host='', port=0):
|
def __init__(self, host='', port=0):
|
||||||
# The asyncio.Transport object provided by the connection_made()
|
# The asyncio.Transport object provided by the connection_made()
|
||||||
# callback when we are connected
|
# callback when we are connected
|
||||||
self.transport = None
|
self.transport = None
|
||||||
|
|
||||||
# The socket the is used internally by the transport object
|
# The socket that is used internally by the transport object
|
||||||
self.socket = None
|
self.socket = None
|
||||||
|
|
||||||
self.connect_loop_wait = 0
|
self.connect_loop_wait = 0
|
||||||
@@ -204,6 +204,9 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
#: We use an ID prefix to ensure that all ID values are unique.
|
#: We use an ID prefix to ensure that all ID values are unique.
|
||||||
self._id_prefix = '%s-' % uuid.uuid4()
|
self._id_prefix = '%s-' % uuid.uuid4()
|
||||||
|
|
||||||
|
# Current connection attempt (Future)
|
||||||
|
self._current_connection_attempt = None
|
||||||
|
|
||||||
#: A list of DNS results that have not yet been tried.
|
#: A list of DNS results that have not yet been tried.
|
||||||
self.dns_answers = None
|
self.dns_answers = None
|
||||||
|
|
||||||
@@ -212,6 +215,9 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
#: ``_xmpp-client._tcp`` service.
|
#: ``_xmpp-client._tcp`` service.
|
||||||
self.dns_service = None
|
self.dns_service = None
|
||||||
|
|
||||||
|
#: The reason why we are disconnecting from the server
|
||||||
|
self.disconnect_reason = None
|
||||||
|
|
||||||
#: An asyncio Future being done when the stream is disconnected.
|
#: An asyncio Future being done when the stream is disconnected.
|
||||||
self.disconnected = asyncio.Future()
|
self.disconnected = asyncio.Future()
|
||||||
|
|
||||||
@@ -265,6 +271,8 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
localhost
|
localhost
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.disconnect_reason = None
|
||||||
|
self.cancel_connection_attempt()
|
||||||
if host and port:
|
if host and port:
|
||||||
self.address = (host, int(port))
|
self.address = (host, int(port))
|
||||||
try:
|
try:
|
||||||
@@ -281,13 +289,15 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.disable_starttls = disable_starttls
|
self.disable_starttls = disable_starttls
|
||||||
|
|
||||||
self.event("connecting")
|
self.event("connecting")
|
||||||
asyncio.async(self._connect_routine())
|
self._current_connection_attempt = asyncio.ensure_future(
|
||||||
|
self._connect_routine(),
|
||||||
|
loop=self.loop,
|
||||||
|
)
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def _connect_routine(self):
|
||||||
def _connect_routine(self):
|
|
||||||
self.event_when_connected = "connected"
|
self.event_when_connected = "connected"
|
||||||
|
|
||||||
record = yield from self.pick_dns_answer(self.default_domain)
|
record = await self.pick_dns_answer(self.default_domain)
|
||||||
if record is not None:
|
if record is not None:
|
||||||
host, address, dns_port = record
|
host, address, dns_port = record
|
||||||
port = dns_port if dns_port else self.address[1]
|
port = dns_port if dns_port else self.address[1]
|
||||||
@@ -303,23 +313,29 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
else:
|
else:
|
||||||
ssl_context = None
|
ssl_context = None
|
||||||
|
|
||||||
yield from asyncio.sleep(self.connect_loop_wait)
|
await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
|
||||||
|
if self._current_connection_attempt is None:
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
yield from self.loop.create_connection(lambda: self,
|
await self.loop.create_connection(lambda: self,
|
||||||
self.address[0],
|
self.address[0],
|
||||||
self.address[1],
|
self.address[1],
|
||||||
ssl=ssl_context,
|
ssl=ssl_context,
|
||||||
server_hostname=self.default_domain if self.use_ssl else None)
|
server_hostname=self.default_domain if self.use_ssl else None)
|
||||||
|
self.connect_loop_wait = 0
|
||||||
except Socket.gaierror as e:
|
except Socket.gaierror as e:
|
||||||
self.event('connection_failed',
|
self.event('connection_failed',
|
||||||
'No DNS record available for %s' % self.default_domain)
|
'No DNS record available for %s' % self.default_domain)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.debug('Connection failed: %s', e)
|
log.debug('Connection failed: %s', e)
|
||||||
self.event("connection_failed", e)
|
self.event("connection_failed", e)
|
||||||
|
if self._current_connection_attempt is None:
|
||||||
|
return
|
||||||
self.connect_loop_wait = self.connect_loop_wait * 2 + 1
|
self.connect_loop_wait = self.connect_loop_wait * 2 + 1
|
||||||
asyncio.async(self._connect_routine())
|
self._current_connection_attempt = asyncio.ensure_future(
|
||||||
else:
|
self._connect_routine(),
|
||||||
self.connect_loop_wait = 0
|
loop=self.loop,
|
||||||
|
)
|
||||||
|
|
||||||
def process(self, *, forever=True, timeout=None):
|
def process(self, *, forever=True, timeout=None):
|
||||||
"""Process all the available XMPP events (receiving or sending data on the
|
"""Process all the available XMPP events (receiving or sending data on the
|
||||||
@@ -334,10 +350,10 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
else:
|
else:
|
||||||
self.loop.run_until_complete(self.disconnected)
|
self.loop.run_until_complete(self.disconnected)
|
||||||
else:
|
else:
|
||||||
tasks = [asyncio.sleep(timeout)]
|
tasks = [asyncio.sleep(timeout, loop=self.loop)]
|
||||||
if not forever:
|
if not forever:
|
||||||
tasks.append(self.disconnected)
|
tasks.append(self.disconnected)
|
||||||
self.loop.run_until_complete(asyncio.wait(tasks))
|
self.loop.run_until_complete(asyncio.wait(tasks, loop=self.loop))
|
||||||
|
|
||||||
def init_parser(self):
|
def init_parser(self):
|
||||||
"""init the XML parser. The parser must always be reset for each new
|
"""init the XML parser. The parser must always be reset for each new
|
||||||
@@ -352,7 +368,10 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
"""
|
"""
|
||||||
self.event(self.event_when_connected)
|
self.event(self.event_when_connected)
|
||||||
self.transport = transport
|
self.transport = transport
|
||||||
self.socket = self.transport.get_extra_info("socket")
|
self.socket = self.transport.get_extra_info(
|
||||||
|
"ssl_object",
|
||||||
|
default=self.transport.get_extra_info("socket")
|
||||||
|
)
|
||||||
self.init_parser()
|
self.init_parser()
|
||||||
self.send_raw(self.stream_header)
|
self.send_raw(self.stream_header)
|
||||||
self.dns_answers = None
|
self.dns_answers = None
|
||||||
@@ -387,7 +406,9 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
if self.xml_depth == 0:
|
if self.xml_depth == 0:
|
||||||
# The stream's root element has closed,
|
# The stream's root element has closed,
|
||||||
# terminating the stream.
|
# terminating the stream.
|
||||||
|
self.end_session_on_disconnect = True
|
||||||
log.debug("End of stream received")
|
log.debug("End of stream received")
|
||||||
|
self.disconnect_reason = "End of stream"
|
||||||
self.abort()
|
self.abort()
|
||||||
elif self.xml_depth == 1:
|
elif self.xml_depth == 1:
|
||||||
# A stanza is an XML element that is a direct child of
|
# A stanza is an XML element that is a direct child of
|
||||||
@@ -422,7 +443,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
closure of the TCP connection
|
closure of the TCP connection
|
||||||
"""
|
"""
|
||||||
log.info("connection_lost: %s", (exception,))
|
log.info("connection_lost: %s", (exception,))
|
||||||
self.event("disconnected")
|
self.event("disconnected", self.disconnect_reason or exception and exception.strerror)
|
||||||
if self.end_session_on_disconnect:
|
if self.end_session_on_disconnect:
|
||||||
self.event('session_end')
|
self.event('session_end')
|
||||||
# All these objects are associated with one TCP connection. Since
|
# All these objects are associated with one TCP connection. Since
|
||||||
@@ -431,21 +452,35 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.transport = None
|
self.transport = None
|
||||||
self.socket = None
|
self.socket = None
|
||||||
|
|
||||||
def disconnect(self, wait=2.0):
|
def cancel_connection_attempt(self):
|
||||||
|
"""
|
||||||
|
Immediately cancel the current create_connection() Future.
|
||||||
|
This is useful when a client using slixmpp tries to connect
|
||||||
|
on flaky networks, where sometimes a connection just gets lost
|
||||||
|
and it needs to reconnect while the attempt is still ongoing.
|
||||||
|
"""
|
||||||
|
if self._current_connection_attempt:
|
||||||
|
self._current_connection_attempt.cancel()
|
||||||
|
self._current_connection_attempt = None
|
||||||
|
|
||||||
|
def disconnect(self, wait=2.0, reason=None):
|
||||||
"""Close the XML stream and wait for an acknowldgement from the server for
|
"""Close the XML stream and wait for an acknowldgement from the server for
|
||||||
at most `wait` seconds. After the given number of seconds has
|
at most `wait` seconds. After the given number of seconds has
|
||||||
passed without a response from the serveur, or when the server
|
passed without a response from the serveur, or when the server
|
||||||
successfuly responds with a closure of its own stream, abort() is
|
successfully responds with a closure of its own stream, abort() is
|
||||||
called. If wait is 0.0, this is almost equivalent to calling abort()
|
called. If wait is 0.0, this will call abort() directly without closing
|
||||||
directly.
|
the stream.
|
||||||
|
|
||||||
Does nothing if we are not connected.
|
Does nothing if we are not connected.
|
||||||
|
|
||||||
:param wait: Time to wait for a response from the server.
|
:param wait: Time to wait for a response from the server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.disconnect_reason = reason
|
||||||
|
self.cancel_connection_attempt()
|
||||||
if self.transport:
|
if self.transport:
|
||||||
self.send_raw(self.stream_footer)
|
if wait > 0.0:
|
||||||
|
self.send_raw(self.stream_footer)
|
||||||
self.schedule('Disconnect wait', wait,
|
self.schedule('Disconnect wait', wait,
|
||||||
self.abort, repeat=False)
|
self.abort, repeat=False)
|
||||||
|
|
||||||
@@ -453,6 +488,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
"""
|
"""
|
||||||
Forcibly close the connection
|
Forcibly close the connection
|
||||||
"""
|
"""
|
||||||
|
self.cancel_connection_attempt()
|
||||||
if self.transport:
|
if self.transport:
|
||||||
self.transport.close()
|
self.transport.close()
|
||||||
self.transport.abort()
|
self.transport.abort()
|
||||||
@@ -460,13 +496,13 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.disconnected.set_result(True)
|
self.disconnected.set_result(True)
|
||||||
self.disconnected = asyncio.Future()
|
self.disconnected = asyncio.Future()
|
||||||
|
|
||||||
def reconnect(self, wait=2.0):
|
def reconnect(self, wait=2.0, reason="Reconnecting"):
|
||||||
"""Calls disconnect(), and once we are disconnected (after the timeout, or
|
"""Calls disconnect(), and once we are disconnected (after the timeout, or
|
||||||
when the server acknowledgement is received), call connect()
|
when the server acknowledgement is received), call connect()
|
||||||
"""
|
"""
|
||||||
log.debug("reconnecting...")
|
log.debug("reconnecting...")
|
||||||
self.disconnect(wait)
|
self.disconnect(wait, reason)
|
||||||
self.add_event_handler('disconnected', self.connect, disposable=True)
|
self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True)
|
||||||
|
|
||||||
def configure_socket(self):
|
def configure_socket(self):
|
||||||
"""Set timeout and other options for self.socket.
|
"""Set timeout and other options for self.socket.
|
||||||
@@ -512,37 +548,42 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
|
|
||||||
return self.ssl_context
|
return self.ssl_context
|
||||||
|
|
||||||
def start_tls(self):
|
async def start_tls(self):
|
||||||
"""Perform handshakes for TLS.
|
"""Perform handshakes for TLS.
|
||||||
|
|
||||||
If the handshake is successful, the XML stream will need
|
If the handshake is successful, the XML stream will need
|
||||||
to be restarted.
|
to be restarted.
|
||||||
"""
|
"""
|
||||||
self.event_when_connected = "tls_success"
|
self.event_when_connected = "tls_success"
|
||||||
|
|
||||||
ssl_context = self.get_ssl_context()
|
ssl_context = self.get_ssl_context()
|
||||||
ssl_connect_routine = self.loop.create_connection(lambda: self, ssl=ssl_context,
|
try:
|
||||||
sock=self.socket,
|
if hasattr(self.loop, 'start_tls'):
|
||||||
server_hostname=self.default_domain)
|
transp = await self.loop.start_tls(self.transport,
|
||||||
@asyncio.coroutine
|
self, ssl_context)
|
||||||
def ssl_coro():
|
# Python < 3.7
|
||||||
try:
|
|
||||||
transp, prot = yield from ssl_connect_routine
|
|
||||||
except ssl.SSLError as e:
|
|
||||||
log.debug('SSL: Unable to connect', exc_info=True)
|
|
||||||
log.error('CERT: Invalid certificate trust chain.')
|
|
||||||
if not self.event_handled('ssl_invalid_chain'):
|
|
||||||
self.disconnect()
|
|
||||||
else:
|
|
||||||
self.event('ssl_invalid_chain', e)
|
|
||||||
else:
|
else:
|
||||||
# Workaround for a regression in 3.4 where ssl_object was not set.
|
transp, _ = await self.loop.create_connection(
|
||||||
der_cert = transp.get_extra_info("ssl_object",
|
lambda: self,
|
||||||
default=transp.get_extra_info("socket")).getpeercert(True)
|
ssl=self.ssl_context,
|
||||||
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
|
sock=self.socket,
|
||||||
self.event('ssl_cert', pem_cert)
|
server_hostname=self.default_domain
|
||||||
|
)
|
||||||
asyncio.async(ssl_coro())
|
except ssl.SSLError as e:
|
||||||
|
log.debug('SSL: Unable to connect', exc_info=True)
|
||||||
|
log.error('CERT: Invalid certificate trust chain.')
|
||||||
|
if not self.event_handled('ssl_invalid_chain'):
|
||||||
|
self.disconnect()
|
||||||
|
else:
|
||||||
|
self.event('ssl_invalid_chain', e)
|
||||||
|
return False
|
||||||
|
der_cert = transp.get_extra_info("ssl_object").getpeercert(True)
|
||||||
|
pem_cert = ssl.DER_cert_to_PEM_cert(der_cert)
|
||||||
|
self.event('ssl_cert', pem_cert)
|
||||||
|
# If we use the builtin start_tls, the connection_made() protocol
|
||||||
|
# method is not called automatically
|
||||||
|
if hasattr(self.loop, 'start_tls'):
|
||||||
|
self.connection_made(transp)
|
||||||
|
return True
|
||||||
|
|
||||||
def _start_keepalive(self, event):
|
def _start_keepalive(self, event):
|
||||||
"""Begin sending whitespace periodically to keep the connection alive.
|
"""Begin sending whitespace periodically to keep the connection alive.
|
||||||
@@ -655,8 +696,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
idx += 1
|
idx += 1
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def get_dns_records(self, domain, port=None):
|
||||||
def get_dns_records(self, domain, port=None):
|
|
||||||
"""Get the DNS records for a domain.
|
"""Get the DNS records for a domain.
|
||||||
|
|
||||||
:param domain: The domain in question.
|
:param domain: The domain in question.
|
||||||
@@ -668,7 +708,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
resolver = default_resolver(loop=self.loop)
|
resolver = default_resolver(loop=self.loop)
|
||||||
self.configure_dns(resolver, domain=domain, port=port)
|
self.configure_dns(resolver, domain=domain, port=port)
|
||||||
|
|
||||||
result = yield from resolve(domain, port,
|
result = await resolve(domain, port,
|
||||||
service=self.dns_service,
|
service=self.dns_service,
|
||||||
resolver=resolver,
|
resolver=resolver,
|
||||||
use_ipv6=self.use_ipv6,
|
use_ipv6=self.use_ipv6,
|
||||||
@@ -676,8 +716,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
loop=self.loop)
|
loop=self.loop)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def pick_dns_answer(self, domain, port=None):
|
||||||
def pick_dns_answer(self, domain, port=None):
|
|
||||||
"""Pick a server and port from DNS answers.
|
"""Pick a server and port from DNS answers.
|
||||||
|
|
||||||
Gets DNS answers if none available.
|
Gets DNS answers if none available.
|
||||||
@@ -687,7 +726,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
:param port: If the results don't include a port, use this one.
|
:param port: If the results don't include a port, use this one.
|
||||||
"""
|
"""
|
||||||
if self.dns_answers is None:
|
if self.dns_answers is None:
|
||||||
dns_records = yield from self.get_dns_records(domain, port)
|
dns_records = await self.get_dns_records(domain, port)
|
||||||
self.dns_answers = iter(dns_records)
|
self.dns_answers = iter(dns_records)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -752,16 +791,18 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
# If the callback is a coroutine, schedule it instead of
|
# If the callback is a coroutine, schedule it instead of
|
||||||
# running it directly
|
# running it directly
|
||||||
if asyncio.iscoroutinefunction(handler_callback):
|
if asyncio.iscoroutinefunction(handler_callback):
|
||||||
@asyncio.coroutine
|
async def handler_callback_routine(cb):
|
||||||
def handler_callback_routine(cb):
|
|
||||||
try:
|
try:
|
||||||
yield from cb(data)
|
await cb(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if old_exception:
|
if old_exception:
|
||||||
old_exception(e)
|
old_exception(e)
|
||||||
else:
|
else:
|
||||||
self.exception(e)
|
self.exception(e)
|
||||||
asyncio.async(handler_callback_routine(handler_callback))
|
asyncio.ensure_future(
|
||||||
|
handler_callback_routine(handler_callback),
|
||||||
|
loop=self.loop,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
handler_callback(data)
|
handler_callback(data)
|
||||||
@@ -975,4 +1016,3 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
:param exception: An unhandled exception object.
|
:param exception: An unhandled exception object.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
74
tests/test_cache.py
Normal file
74
tests/test_cache.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import unittest
|
||||||
|
from slixmpp.test import SlixTest
|
||||||
|
from slixmpp.util import (
|
||||||
|
MemoryCache, MemoryPerJidCache,
|
||||||
|
FileSystemCache, FileSystemPerJidCache
|
||||||
|
)
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
class TestCacheClass(SlixTest):
|
||||||
|
|
||||||
|
def testMemoryCache(self):
|
||||||
|
cache = MemoryCache()
|
||||||
|
|
||||||
|
cache.store("test", "test_value")
|
||||||
|
self.assertEqual(cache.retrieve("test"), "test_value")
|
||||||
|
self.assertEqual(cache.retrieve("test2"), None)
|
||||||
|
|
||||||
|
cache.remove("test")
|
||||||
|
self.assertEqual(cache.retrieve("test"), None)
|
||||||
|
|
||||||
|
def testMemoryPerJidcache(self):
|
||||||
|
cache = MemoryPerJidCache()
|
||||||
|
|
||||||
|
cache.store_by_jid("test@example.com", "test", "test_value")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve_by_jid("test@example.com", "test"),
|
||||||
|
"test_value"
|
||||||
|
)
|
||||||
|
|
||||||
|
cache.remove_by_jid("test@example.com", "test")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve_by_jid("test@example.com", "test"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def testFileSystemCache(self):
|
||||||
|
def failing_decode(value):
|
||||||
|
if value == "failme":
|
||||||
|
raise Exception("you failed")
|
||||||
|
return value
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
cache = FileSystemCache(tmpdir, "test", decode=failing_decode)
|
||||||
|
cache.store("test", "test_value")
|
||||||
|
cache.store("test2", "failme")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve("test"),
|
||||||
|
"test_value"
|
||||||
|
)
|
||||||
|
cache.remove("test")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve("test"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve("test2"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
def testFileSystemPerJidCache(self):
|
||||||
|
with TemporaryDirectory() as tmpdir:
|
||||||
|
cache = FileSystemPerJidCache(tmpdir, "test")
|
||||||
|
cache.store_by_jid("test@example.com", "test", "test_value")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve_by_jid("test@example.com", "test"),
|
||||||
|
"test_value"
|
||||||
|
)
|
||||||
|
cache.remove_by_jid("test@example.com", "test")
|
||||||
|
self.assertEqual(
|
||||||
|
cache.retrieve_by_jid("test@example.com", "test"),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestCacheClass)
|
||||||
@@ -23,7 +23,7 @@ class TestEvents(SlixTest):
|
|||||||
self.xmpp.event("test_event")
|
self.xmpp.event("test_event")
|
||||||
|
|
||||||
msg = "Event was not triggered the correct number of times: %s"
|
msg = "Event was not triggered the correct number of times: %s"
|
||||||
self.failUnless(happened == [True, True], msg)
|
self.assertTrue(happened == [True, True], msg)
|
||||||
|
|
||||||
def testDelEvent(self):
|
def testDelEvent(self):
|
||||||
"""Test handler working, then deleted and not triggered"""
|
"""Test handler working, then deleted and not triggered"""
|
||||||
@@ -41,7 +41,7 @@ class TestEvents(SlixTest):
|
|||||||
self.xmpp.event("test_event", {})
|
self.xmpp.event("test_event", {})
|
||||||
|
|
||||||
msg = "Event was not triggered the correct number of times: %s"
|
msg = "Event was not triggered the correct number of times: %s"
|
||||||
self.failUnless(happened == [True], msg % happened)
|
self.assertTrue(happened == [True], msg % happened)
|
||||||
|
|
||||||
def testAddDelAddEvent(self):
|
def testAddDelAddEvent(self):
|
||||||
"""Test adding, then removing, then adding an event handler."""
|
"""Test adding, then removing, then adding an event handler."""
|
||||||
@@ -61,7 +61,7 @@ class TestEvents(SlixTest):
|
|||||||
self.xmpp.event("test_event", {})
|
self.xmpp.event("test_event", {})
|
||||||
|
|
||||||
msg = "Event was not triggered the correct number of times: %s"
|
msg = "Event was not triggered the correct number of times: %s"
|
||||||
self.failUnless(happened == [True, True], msg % happened)
|
self.assertTrue(happened == [True, True], msg % happened)
|
||||||
|
|
||||||
def testDisposableEvent(self):
|
def testDisposableEvent(self):
|
||||||
"""Test disposable handler working, then not being triggered again."""
|
"""Test disposable handler working, then not being triggered again."""
|
||||||
@@ -78,7 +78,7 @@ class TestEvents(SlixTest):
|
|||||||
self.xmpp.event("test_event", {})
|
self.xmpp.event("test_event", {})
|
||||||
|
|
||||||
msg = "Event was not triggered the correct number of times: %s"
|
msg = "Event was not triggered the correct number of times: %s"
|
||||||
self.failUnless(happened == [True], msg % happened)
|
self.assertTrue(happened == [True], msg % happened)
|
||||||
|
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestEvents)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestEvents)
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ class TestOverall(unittest.TestCase):
|
|||||||
"""Testing all modules by compiling them"""
|
"""Testing all modules by compiling them"""
|
||||||
src = '.%sslixmpp' % os.sep
|
src = '.%sslixmpp' % os.sep
|
||||||
rx = re.compile('/[.]svn|.*26.*')
|
rx = re.compile('/[.]svn|.*26.*')
|
||||||
self.failUnless(compileall.compile_dir(src, rx=rx, quiet=True))
|
self.assertTrue(compileall.compile_dir(src, rx=rx, quiet=True))
|
||||||
|
|
||||||
def testTabNanny(self):
|
def testTabNanny(self):
|
||||||
"""Testing that indentation is consistent"""
|
"""Testing that indentation is consistent"""
|
||||||
self.failIf(tabnanny.check('..%sslixmpp' % os.sep))
|
self.assertFalse(tabnanny.check('..%sslixmpp' % os.sep))
|
||||||
|
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestOverall)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestOverall)
|
||||||
|
|||||||
@@ -9,37 +9,37 @@ class TestStanzaBase(SlixTest):
|
|||||||
"""Test the 'to' interface of StanzaBase."""
|
"""Test the 'to' interface of StanzaBase."""
|
||||||
stanza = StanzaBase()
|
stanza = StanzaBase()
|
||||||
stanza['to'] = 'user@example.com'
|
stanza['to'] = 'user@example.com'
|
||||||
self.failUnless(str(stanza['to']) == 'user@example.com',
|
self.assertTrue(str(stanza['to']) == 'user@example.com',
|
||||||
"Setting and retrieving stanza 'to' attribute did not work.")
|
"Setting and retrieving stanza 'to' attribute did not work.")
|
||||||
|
|
||||||
def testFrom(self):
|
def testFrom(self):
|
||||||
"""Test the 'from' interface of StanzaBase."""
|
"""Test the 'from' interface of StanzaBase."""
|
||||||
stanza = StanzaBase()
|
stanza = StanzaBase()
|
||||||
stanza['from'] = 'user@example.com'
|
stanza['from'] = 'user@example.com'
|
||||||
self.failUnless(str(stanza['from']) == 'user@example.com',
|
self.assertTrue(str(stanza['from']) == 'user@example.com',
|
||||||
"Setting and retrieving stanza 'from' attribute did not work.")
|
"Setting and retrieving stanza 'from' attribute did not work.")
|
||||||
|
|
||||||
def testPayload(self):
|
def testPayload(self):
|
||||||
"""Test the 'payload' interface of StanzaBase."""
|
"""Test the 'payload' interface of StanzaBase."""
|
||||||
stanza = StanzaBase()
|
stanza = StanzaBase()
|
||||||
self.failUnless(stanza['payload'] == [],
|
self.assertTrue(stanza['payload'] == [],
|
||||||
"Empty stanza does not have an empty payload.")
|
"Empty stanza does not have an empty payload.")
|
||||||
|
|
||||||
stanza['payload'] = ET.Element("{foo}foo")
|
stanza['payload'] = ET.Element("{foo}foo")
|
||||||
self.failUnless(len(stanza['payload']) == 1,
|
self.assertTrue(len(stanza['payload']) == 1,
|
||||||
"Stanza contents and payload do not match.")
|
"Stanza contents and payload do not match.")
|
||||||
|
|
||||||
stanza['payload'] = ET.Element('{bar}bar')
|
stanza['payload'] = ET.Element('{bar}bar')
|
||||||
self.failUnless(len(stanza['payload']) == 2,
|
self.assertTrue(len(stanza['payload']) == 2,
|
||||||
"Stanza payload was not appended.")
|
"Stanza payload was not appended.")
|
||||||
|
|
||||||
del stanza['payload']
|
del stanza['payload']
|
||||||
self.failUnless(stanza['payload'] == [],
|
self.assertTrue(stanza['payload'] == [],
|
||||||
"Stanza payload not cleared after deletion.")
|
"Stanza payload not cleared after deletion.")
|
||||||
|
|
||||||
stanza['payload'] = [ET.Element('{foo}foo'),
|
stanza['payload'] = [ET.Element('{foo}foo'),
|
||||||
ET.Element('{bar}bar')]
|
ET.Element('{bar}bar')]
|
||||||
self.failUnless(len(stanza['payload']) == 2,
|
self.assertTrue(len(stanza['payload']) == 2,
|
||||||
"Adding multiple elements to stanza's payload did not work.")
|
"Adding multiple elements to stanza's payload did not work.")
|
||||||
|
|
||||||
def testClear(self):
|
def testClear(self):
|
||||||
@@ -49,9 +49,9 @@ class TestStanzaBase(SlixTest):
|
|||||||
stanza['payload'] = ET.Element("{foo}foo")
|
stanza['payload'] = ET.Element("{foo}foo")
|
||||||
stanza.clear()
|
stanza.clear()
|
||||||
|
|
||||||
self.failUnless(stanza['payload'] == [],
|
self.assertTrue(stanza['payload'] == [],
|
||||||
"Stanza payload was not cleared after calling .clear()")
|
"Stanza payload was not cleared after calling .clear()")
|
||||||
self.failUnless(str(stanza['to']) == "user@example.com",
|
self.assertTrue(str(stanza['to']) == "user@example.com",
|
||||||
"Stanza attributes were not preserved after calling .clear()")
|
"Stanza attributes were not preserved after calling .clear()")
|
||||||
|
|
||||||
def testReply(self):
|
def testReply(self):
|
||||||
@@ -63,9 +63,9 @@ class TestStanzaBase(SlixTest):
|
|||||||
|
|
||||||
stanza = stanza.reply()
|
stanza = stanza.reply()
|
||||||
|
|
||||||
self.failUnless(str(stanza['to'] == "sender@example.com"),
|
self.assertTrue(str(stanza['to'] == "sender@example.com"),
|
||||||
"Stanza reply did not change 'to' attribute.")
|
"Stanza reply did not change 'to' attribute.")
|
||||||
self.failUnless(stanza['payload'] == [],
|
self.assertTrue(stanza['payload'] == [],
|
||||||
"Stanza reply did not empty stanza payload.")
|
"Stanza reply did not empty stanza payload.")
|
||||||
|
|
||||||
def testError(self):
|
def testError(self):
|
||||||
@@ -73,7 +73,7 @@ class TestStanzaBase(SlixTest):
|
|||||||
stanza = StanzaBase()
|
stanza = StanzaBase()
|
||||||
stanza['type'] = 'get'
|
stanza['type'] = 'get'
|
||||||
stanza.error()
|
stanza.error()
|
||||||
self.failUnless(stanza['type'] == 'error',
|
self.assertTrue(stanza['type'] == 'error',
|
||||||
"Stanza type is not 'error' after calling error()")
|
"Stanza type is not 'error' after calling error()")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class TestElementBase(SlixTest):
|
|||||||
"{%s}bar" % ns,
|
"{%s}bar" % ns,
|
||||||
"{abc}baz",
|
"{abc}baz",
|
||||||
"{%s}more" % ns])
|
"{%s}more" % ns])
|
||||||
self.failUnless(expected == result,
|
self.assertTrue(expected == result,
|
||||||
"Incorrect namespace fixing result: %s" % str(result))
|
"Incorrect namespace fixing result: %s" % str(result))
|
||||||
|
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ class TestElementBase(SlixTest):
|
|||||||
'lang': '',
|
'lang': '',
|
||||||
'bar': 'c',
|
'bar': 'c',
|
||||||
'baz': ''}]}
|
'baz': ''}]}
|
||||||
self.failUnless(values == expected,
|
self.assertTrue(values == expected,
|
||||||
"Unexpected stanza values:\n%s\n%s" % (str(expected), str(values)))
|
"Unexpected stanza values:\n%s\n%s" % (str(expected), str(values)))
|
||||||
|
|
||||||
|
|
||||||
@@ -170,13 +170,13 @@ class TestElementBase(SlixTest):
|
|||||||
'meh': ''}
|
'meh': ''}
|
||||||
for interface, value in expected.items():
|
for interface, value in expected.items():
|
||||||
result = stanza[interface]
|
result = stanza[interface]
|
||||||
self.failUnless(result == value,
|
self.assertTrue(result == value,
|
||||||
"Incorrect stanza interface access result: %s" % result)
|
"Incorrect stanza interface access result: %s" % result)
|
||||||
|
|
||||||
# Test plugin interfaces
|
# Test plugin interfaces
|
||||||
self.failUnless(isinstance(stanza['foobar'], TestStanzaPlugin),
|
self.assertTrue(isinstance(stanza['foobar'], TestStanzaPlugin),
|
||||||
"Incorrect plugin object result.")
|
"Incorrect plugin object result.")
|
||||||
self.failUnless(stanza['foobar']['fizz'] == 'c',
|
self.assertTrue(stanza['foobar']['fizz'] == 'c',
|
||||||
"Incorrect plugin subvalue result.")
|
"Incorrect plugin subvalue result.")
|
||||||
|
|
||||||
def testSetItem(self):
|
def testSetItem(self):
|
||||||
@@ -269,7 +269,7 @@ class TestElementBase(SlixTest):
|
|||||||
<foo xmlns="foo" />
|
<foo xmlns="foo" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.failUnless(stanza._get_attr('bar') == '',
|
self.assertTrue(stanza._get_attr('bar') == '',
|
||||||
"Incorrect value returned for an unset XML attribute.")
|
"Incorrect value returned for an unset XML attribute.")
|
||||||
|
|
||||||
stanza._set_attr('bar', 'a')
|
stanza._set_attr('bar', 'a')
|
||||||
@@ -279,7 +279,7 @@ class TestElementBase(SlixTest):
|
|||||||
<foo xmlns="foo" bar="a" baz="b" />
|
<foo xmlns="foo" bar="a" baz="b" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.failUnless(stanza._get_attr('bar') == 'a',
|
self.assertTrue(stanza._get_attr('bar') == 'a',
|
||||||
"Retrieved XML attribute value is incorrect.")
|
"Retrieved XML attribute value is incorrect.")
|
||||||
|
|
||||||
stanza._set_attr('bar', None)
|
stanza._set_attr('bar', None)
|
||||||
@@ -289,7 +289,7 @@ class TestElementBase(SlixTest):
|
|||||||
<foo xmlns="foo" />
|
<foo xmlns="foo" />
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.failUnless(stanza._get_attr('bar', 'c') == 'c',
|
self.assertTrue(stanza._get_attr('bar', 'c') == 'c',
|
||||||
"Incorrect default value returned for an unset XML attribute.")
|
"Incorrect default value returned for an unset XML attribute.")
|
||||||
|
|
||||||
def testGetSubText(self):
|
def testGetSubText(self):
|
||||||
@@ -311,7 +311,7 @@ class TestElementBase(SlixTest):
|
|||||||
return self._get_sub_text("wrapper/bar", default="not found")
|
return self._get_sub_text("wrapper/bar", default="not found")
|
||||||
|
|
||||||
stanza = TestStanza()
|
stanza = TestStanza()
|
||||||
self.failUnless(stanza['bar'] == 'not found',
|
self.assertTrue(stanza['bar'] == 'not found',
|
||||||
"Default _get_sub_text value incorrect.")
|
"Default _get_sub_text value incorrect.")
|
||||||
|
|
||||||
stanza['bar'] = 'found'
|
stanza['bar'] = 'found'
|
||||||
@@ -322,7 +322,7 @@ class TestElementBase(SlixTest):
|
|||||||
</wrapper>
|
</wrapper>
|
||||||
</foo>
|
</foo>
|
||||||
""")
|
""")
|
||||||
self.failUnless(stanza['bar'] == 'found',
|
self.assertTrue(stanza['bar'] == 'found',
|
||||||
"_get_sub_text value incorrect: %s." % stanza['bar'])
|
"_get_sub_text value incorrect: %s." % stanza['bar'])
|
||||||
|
|
||||||
def testSubElement(self):
|
def testSubElement(self):
|
||||||
@@ -481,45 +481,45 @@ class TestElementBase(SlixTest):
|
|||||||
register_stanza_plugin(TestStanza, TestStanzaPlugin)
|
register_stanza_plugin(TestStanza, TestStanzaPlugin)
|
||||||
|
|
||||||
stanza = TestStanza()
|
stanza = TestStanza()
|
||||||
self.failUnless(stanza.match("foo"),
|
self.assertTrue(stanza.match("foo"),
|
||||||
"Stanza did not match its own tag name.")
|
"Stanza did not match its own tag name.")
|
||||||
|
|
||||||
self.failUnless(stanza.match("{foo}foo"),
|
self.assertTrue(stanza.match("{foo}foo"),
|
||||||
"Stanza did not match its own namespaced name.")
|
"Stanza did not match its own namespaced name.")
|
||||||
|
|
||||||
stanza['bar'] = 'a'
|
stanza['bar'] = 'a'
|
||||||
self.failUnless(stanza.match("foo@bar=a"),
|
self.assertTrue(stanza.match("foo@bar=a"),
|
||||||
"Stanza did not match its own name with attribute value check.")
|
"Stanza did not match its own name with attribute value check.")
|
||||||
|
|
||||||
stanza['baz'] = 'b'
|
stanza['baz'] = 'b'
|
||||||
self.failUnless(stanza.match("foo@bar=a@baz=b"),
|
self.assertTrue(stanza.match("foo@bar=a@baz=b"),
|
||||||
"Stanza did not match its own name with multiple attributes.")
|
"Stanza did not match its own name with multiple attributes.")
|
||||||
|
|
||||||
stanza['qux'] = 'c'
|
stanza['qux'] = 'c'
|
||||||
self.failUnless(stanza.match("foo/qux"),
|
self.assertTrue(stanza.match("foo/qux"),
|
||||||
"Stanza did not match with subelements.")
|
"Stanza did not match with subelements.")
|
||||||
|
|
||||||
stanza['qux'] = ''
|
stanza['qux'] = ''
|
||||||
self.failUnless(stanza.match("foo/qux") == False,
|
self.assertTrue(stanza.match("foo/qux") == False,
|
||||||
"Stanza matched missing subinterface element.")
|
"Stanza matched missing subinterface element.")
|
||||||
|
|
||||||
self.failUnless(stanza.match("foo/bar") == False,
|
self.assertTrue(stanza.match("foo/bar") == False,
|
||||||
"Stanza matched nonexistent element.")
|
"Stanza matched nonexistent element.")
|
||||||
|
|
||||||
stanza['plugin']['attrib'] = 'c'
|
stanza['plugin']['attrib'] = 'c'
|
||||||
self.failUnless(stanza.match("foo/plugin@attrib=c"),
|
self.assertTrue(stanza.match("foo/plugin@attrib=c"),
|
||||||
"Stanza did not match with plugin and attribute.")
|
"Stanza did not match with plugin and attribute.")
|
||||||
|
|
||||||
self.failUnless(stanza.match("foo/{http://test/slash/bar}plugin"),
|
self.assertTrue(stanza.match("foo/{http://test/slash/bar}plugin"),
|
||||||
"Stanza did not match with namespaced plugin.")
|
"Stanza did not match with namespaced plugin.")
|
||||||
|
|
||||||
substanza = TestSubStanza()
|
substanza = TestSubStanza()
|
||||||
substanza['attrib'] = 'd'
|
substanza['attrib'] = 'd'
|
||||||
stanza.append(substanza)
|
stanza.append(substanza)
|
||||||
self.failUnless(stanza.match("foo/sub@attrib=d"),
|
self.assertTrue(stanza.match("foo/sub@attrib=d"),
|
||||||
"Stanza did not match with substanzas and attribute.")
|
"Stanza did not match with substanzas and attribute.")
|
||||||
|
|
||||||
self.failUnless(stanza.match("foo/{baz}sub"),
|
self.assertTrue(stanza.match("foo/{baz}sub"),
|
||||||
"Stanza did not match with namespaced substanza.")
|
"Stanza did not match with namespaced substanza.")
|
||||||
|
|
||||||
def testComparisons(self):
|
def testComparisons(self):
|
||||||
@@ -533,19 +533,19 @@ class TestElementBase(SlixTest):
|
|||||||
stanza1 = TestStanza()
|
stanza1 = TestStanza()
|
||||||
stanza1['bar'] = 'a'
|
stanza1['bar'] = 'a'
|
||||||
|
|
||||||
self.failUnless(stanza1,
|
self.assertTrue(stanza1,
|
||||||
"Stanza object does not evaluate to True")
|
"Stanza object does not evaluate to True")
|
||||||
|
|
||||||
stanza2 = TestStanza()
|
stanza2 = TestStanza()
|
||||||
stanza2['baz'] = 'b'
|
stanza2['baz'] = 'b'
|
||||||
|
|
||||||
self.failUnless(stanza1 != stanza2,
|
self.assertTrue(stanza1 != stanza2,
|
||||||
"Different stanza objects incorrectly compared equal.")
|
"Different stanza objects incorrectly compared equal.")
|
||||||
|
|
||||||
stanza1['baz'] = 'b'
|
stanza1['baz'] = 'b'
|
||||||
stanza2['bar'] = 'a'
|
stanza2['bar'] = 'a'
|
||||||
|
|
||||||
self.failUnless(stanza1 == stanza2,
|
self.assertTrue(stanza1 == stanza2,
|
||||||
"Equal stanzas incorrectly compared inequal.")
|
"Equal stanzas incorrectly compared inequal.")
|
||||||
|
|
||||||
def testKeys(self):
|
def testKeys(self):
|
||||||
@@ -561,12 +561,12 @@ class TestElementBase(SlixTest):
|
|||||||
|
|
||||||
stanza = TestStanza()
|
stanza = TestStanza()
|
||||||
|
|
||||||
self.failUnless(set(stanza.keys()) == {'lang', 'bar', 'baz'},
|
self.assertTrue(set(stanza.keys()) == {'lang', 'bar', 'baz'},
|
||||||
"Returned set of interface keys does not match expected.")
|
"Returned set of interface keys does not match expected.")
|
||||||
|
|
||||||
stanza.enable('qux')
|
stanza.enable('qux')
|
||||||
|
|
||||||
self.failUnless(set(stanza.keys()) == {'lang', 'bar', 'baz', 'qux'},
|
self.assertTrue(set(stanza.keys()) == {'lang', 'bar', 'baz', 'qux'},
|
||||||
"Incorrect set of interface and plugin keys.")
|
"Incorrect set of interface and plugin keys.")
|
||||||
|
|
||||||
def testGet(self):
|
def testGet(self):
|
||||||
@@ -580,10 +580,10 @@ class TestElementBase(SlixTest):
|
|||||||
stanza = TestStanza()
|
stanza = TestStanza()
|
||||||
stanza['bar'] = 'a'
|
stanza['bar'] = 'a'
|
||||||
|
|
||||||
self.failUnless(stanza.get('bar') == 'a',
|
self.assertTrue(stanza.get('bar') == 'a',
|
||||||
"Incorrect value returned by stanza.get")
|
"Incorrect value returned by stanza.get")
|
||||||
|
|
||||||
self.failUnless(stanza.get('baz', 'b') == 'b',
|
self.assertTrue(stanza.get('baz', 'b') == 'b',
|
||||||
"Incorrect default value returned by stanza.get")
|
"Incorrect default value returned by stanza.get")
|
||||||
|
|
||||||
def testSubStanzas(self):
|
def testSubStanzas(self):
|
||||||
@@ -608,7 +608,7 @@ class TestElementBase(SlixTest):
|
|||||||
substanza2['qux'] = 'b'
|
substanza2['qux'] = 'b'
|
||||||
|
|
||||||
# Test appending substanzas
|
# Test appending substanzas
|
||||||
self.failUnless(len(stanza) == 0,
|
self.assertTrue(len(stanza) == 0,
|
||||||
"Incorrect empty stanza size.")
|
"Incorrect empty stanza size.")
|
||||||
|
|
||||||
stanza.append(substanza1)
|
stanza.append(substanza1)
|
||||||
@@ -617,7 +617,7 @@ class TestElementBase(SlixTest):
|
|||||||
<foobar qux="a" />
|
<foobar qux="a" />
|
||||||
</foo>
|
</foo>
|
||||||
""", use_values=False)
|
""", use_values=False)
|
||||||
self.failUnless(len(stanza) == 1,
|
self.assertTrue(len(stanza) == 1,
|
||||||
"Incorrect stanza size with 1 substanza.")
|
"Incorrect stanza size with 1 substanza.")
|
||||||
|
|
||||||
stanza.append(substanza2)
|
stanza.append(substanza2)
|
||||||
@@ -627,7 +627,7 @@ class TestElementBase(SlixTest):
|
|||||||
<foobar qux="b" />
|
<foobar qux="b" />
|
||||||
</foo>
|
</foo>
|
||||||
""", use_values=False)
|
""", use_values=False)
|
||||||
self.failUnless(len(stanza) == 2,
|
self.assertTrue(len(stanza) == 2,
|
||||||
"Incorrect stanza size with 2 substanzas.")
|
"Incorrect stanza size with 2 substanzas.")
|
||||||
|
|
||||||
# Test popping substanzas
|
# Test popping substanzas
|
||||||
@@ -643,7 +643,7 @@ class TestElementBase(SlixTest):
|
|||||||
results = []
|
results = []
|
||||||
for substanza in stanza:
|
for substanza in stanza:
|
||||||
results.append(substanza['qux'])
|
results.append(substanza['qux'])
|
||||||
self.failUnless(results == ['b', 'a'],
|
self.assertTrue(results == ['b', 'a'],
|
||||||
"Iteration over substanzas failed: %s." % str(results))
|
"Iteration over substanzas failed: %s." % str(results))
|
||||||
|
|
||||||
def testCopy(self):
|
def testCopy(self):
|
||||||
@@ -659,11 +659,11 @@ class TestElementBase(SlixTest):
|
|||||||
|
|
||||||
stanza2 = stanza1.__copy__()
|
stanza2 = stanza1.__copy__()
|
||||||
|
|
||||||
self.failUnless(stanza1 == stanza2,
|
self.assertTrue(stanza1 == stanza2,
|
||||||
"Copied stanzas are not equal to each other.")
|
"Copied stanzas are not equal to each other.")
|
||||||
|
|
||||||
stanza1['baz'] = 'b'
|
stanza1['baz'] = 'b'
|
||||||
self.failUnless(stanza1 != stanza2,
|
self.assertTrue(stanza1 != stanza2,
|
||||||
"Divergent stanza copies incorrectly compared equal.")
|
"Divergent stanza copies incorrectly compared equal.")
|
||||||
|
|
||||||
def testExtension(self):
|
def testExtension(self):
|
||||||
@@ -701,7 +701,7 @@ class TestElementBase(SlixTest):
|
|||||||
</foo>
|
</foo>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.failUnless(stanza['extended'] == 'testing',
|
self.assertTrue(stanza['extended'] == 'testing',
|
||||||
"Could not retrieve stanza extension value.")
|
"Could not retrieve stanza extension value.")
|
||||||
|
|
||||||
del stanza['extended']
|
del stanza['extended']
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class TestErrorStanzas(SlixTest):
|
|||||||
</message>
|
</message>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
self.failUnless(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.")
|
self.assertTrue(msg['error']['condition'] == 'item-not-found', "Error condition doesn't match.")
|
||||||
|
|
||||||
msg['error']['condition'] = 'resource-constraint'
|
msg['error']['condition'] = 'resource-constraint'
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user