Compare commits
139 Commits
slix-1.4.0
...
slix-1.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3dcb96d9d8 | ||
|
|
0a7a4c3abe | ||
|
|
a4bbc404ed | ||
|
|
c3fbc6cb80 | ||
|
|
355d789061 | ||
|
|
47ed67c04e | ||
|
|
34567f450a | ||
|
|
9126bd8392 | ||
|
|
02202f7cd8 | ||
|
|
2add94f5b0 | ||
|
|
5fc757f200 | ||
|
|
98108d0445 | ||
|
|
76f4fb49d6 | ||
|
|
5be46a5e68 | ||
|
|
ab9040c30e | ||
|
|
a16e2a0f6c | ||
|
|
842aa3be8f | ||
|
|
6c28b49e7f | ||
|
|
621255027d | ||
|
|
efe316dc8c | ||
|
|
e9a87a0b77 | ||
|
|
85c9967b9c | ||
|
|
deb6d4f176 | ||
|
|
7218bb4499 | ||
|
|
d85efec7a2 | ||
|
|
115c234527 | ||
|
|
a0f5cb6e09 | ||
|
|
110bbf8afc | ||
|
|
d97efa0bd8 | ||
|
|
672f1b28f6 | ||
|
|
27d3ae958b | ||
|
|
a32794ec35 | ||
|
|
aa11ba463e | ||
|
|
a83c00e933 | ||
|
|
31f6ef6814 | ||
|
|
9b3874b5df | ||
|
|
0139fb291e | ||
|
|
e58988484a | ||
|
|
5d5e5cda19 | ||
|
|
11f707987d | ||
|
|
db13794e0f | ||
|
|
37bc1bb9b3 | ||
|
|
9be30e5291 | ||
|
|
9fe20a4056 | ||
|
|
3253d34c0a | ||
|
|
fef575ee1a | ||
|
|
540ff89427 | ||
|
|
dd8ac8fc87 | ||
|
|
2249d878d1 | ||
|
|
89fa9dc1dd | ||
|
|
d7729e8683 | ||
|
|
d618f55dea | ||
|
|
b0e688eb35 | ||
|
|
0e7176483b | ||
|
|
f35569a2c1 | ||
|
|
bec6f7c8f3 | ||
|
|
027ce2434d | ||
|
|
d57fbb57a2 | ||
|
|
85cd7a9166 | ||
|
|
d50d996c68 | ||
|
|
371ad20ca7 | ||
|
|
5f49df6b56 | ||
|
|
b50bfb2f34 | ||
|
|
b29bb30eb7 | ||
|
|
4435c81d77 | ||
|
|
2638ba2744 | ||
|
|
dbc9758311 | ||
|
|
47968963b1 | ||
|
|
4e8800f954 | ||
|
|
40053518aa | ||
|
|
1ee0f72ead | ||
|
|
4bb81228ae | ||
|
|
60a7a5b8df | ||
|
|
946674f424 | ||
|
|
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 |
13
.github/pull_request_template.md
vendored
Normal file
13
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
################ Please use Gitlab instead of Github ###################################
|
||||
|
||||
Hello, thank you for contributing to slixmpp!
|
||||
|
||||
You’re about to open a pull request on github. However this github repository is not the official place for contributions on slixmpp.
|
||||
|
||||
Please open your merge request on https://lab.louiz.org/poezio/slixmpp/
|
||||
|
||||
You should be able to log in there with your github credentials, clone the slixmpp repository in your namespace, push your existing pull request into a new branch, and then open a merge request with one click, within 3 minutes.
|
||||
|
||||
This will help us review your contribution, avoid spreading things everywhere and it will even run the tests automatically with your changes.
|
||||
|
||||
Thank you.
|
||||
@@ -1,4 +1,9 @@
|
||||
stages:
|
||||
- test
|
||||
- trigger
|
||||
|
||||
test:
|
||||
stage: test
|
||||
tags:
|
||||
- docker
|
||||
image: ubuntu:latest
|
||||
@@ -6,3 +11,11 @@ test:
|
||||
- apt update
|
||||
- apt install -y python3 cython3 gpg
|
||||
- ./run_tests.py
|
||||
|
||||
trigger_poezio:
|
||||
stage: trigger
|
||||
tags:
|
||||
- docker
|
||||
image: appropriate/curl:latest
|
||||
script:
|
||||
- curl --request POST -F token="$SLIXMPP_TRIGGER_TOKEN" -F ref=master https://lab.louiz.org/api/v4/projects/18/trigger/pipeline
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
language: python
|
||||
python:
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "3.7-dev"
|
||||
- "3.7"
|
||||
- "3.8-dev"
|
||||
install:
|
||||
- "pip install ."
|
||||
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
|
||||
<https://github.com/poezio/slixmpp>`_ or on your own repository) and to
|
||||
notify the developers with either:
|
||||
- a ticket `on the bug tracker <https://dev.poez.io/new>`_
|
||||
- a ticket `on the bug tracker <https://lab.louiz.org/poezio/slixmpp/issues/new>`_
|
||||
- a pull request on github
|
||||
- a simple message on `the XMPP MUC <xmpp:slixmpp@muc.poez.io>`_
|
||||
|
||||
|
||||
3
INSTALL
3
INSTALL
@@ -1,6 +1,7 @@
|
||||
Pre-requisites:
|
||||
- Python 3.5+
|
||||
- Python 3.7+
|
||||
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
|
||||
- GnuPG, for testing
|
||||
|
||||
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
|
||||
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
socksipy: A Python SOCKS client module.
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Copyright 2006 Dan-Haim. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
3. Neither the name of Dan Haim nor the names of his contributors may be used
|
||||
to endorse or promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
||||
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Slixmpp
|
||||
#########
|
||||
|
||||
Slixmpp is an MIT licensed XMPP library for Python 3.5+. It is a fork of
|
||||
Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of
|
||||
SleekXMPP.
|
||||
|
||||
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>`_)
|
||||
- Krzysztof Kotlenga (`Krzysztof Kotlenga <mailto:pocek@users.sf.net>`_)
|
||||
- Tsukasa Hiiragi (`Tsukasa Hiiragi <mailto:bakalolka@gmail.com>`_)
|
||||
- Maxime Buquet (`pep <xmpp:pep@bouah.net?message>`_)
|
||||
|
||||
Credits (SleekXMPP)
|
||||
-------------------
|
||||
|
||||
21
docs/_static/haiku.css
vendored
21
docs/_static/haiku.css
vendored
@@ -408,24 +408,3 @@ div.viewcode-block:target {
|
||||
margin: -1px -12px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
#from_andyet {
|
||||
-webkit-box-shadow: #CCC 0px 0px 3px;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
bottom: 0px;
|
||||
right: 17px;
|
||||
padding: 3px 10px;
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
#from_andyet h2 {
|
||||
background-image: url("images/from_&yet.png");
|
||||
background-repeat: no-repeat;
|
||||
height: 29px;
|
||||
line-height: 0;
|
||||
text-indent: -9999em;
|
||||
width: 79px;
|
||||
margin-top: 0;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
1
docs/_templates/layout.html
vendored
1
docs/_templates/layout.html
vendored
@@ -65,6 +65,5 @@
|
||||
<div class="bottomnav">
|
||||
{{ nav() }}
|
||||
</div>
|
||||
<a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ hides namespaces when able and does not introduce excessive namespace
|
||||
prefixes::
|
||||
|
||||
>>> from slixmpp.xmlstream.tostring import tostring
|
||||
>>> from xml.etree import cElementTree as ET
|
||||
>>> from xml.etree import ElementTree as ET
|
||||
>>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>')
|
||||
>>> ET.tostring(xml)
|
||||
'<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'
|
||||
|
||||
13
docs/conf.py
13
docs/conf.py
@@ -12,12 +12,17 @@
|
||||
# serve to show the default.
|
||||
|
||||
import sys, os
|
||||
import datetime
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# get version automagically from source tree
|
||||
from slixmpp.version import __version__ as version
|
||||
release = ".".join(version.split(".")[0:2])
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
@@ -41,16 +46,18 @@ master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Slixmpp'
|
||||
copyright = u'2011, Nathan Fritz, Lance Stout'
|
||||
year = datetime.datetime.now().year
|
||||
copyright = u'{}, Nathan Fritz, Lance Stout'.format(year)
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# auto imported from code!
|
||||
# The short X.Y version.
|
||||
version = '1.1'
|
||||
# version = '1.4'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '1.1'
|
||||
# release = '1.4.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
Differences from SleekXMPP
|
||||
==========================
|
||||
|
||||
**Python 3.5+ only**
|
||||
slixmpp will only work on python 3.5 and above.
|
||||
**Python 3.7+ only**
|
||||
slixmpp will work on python 3.7 and above. It may work with previous
|
||||
versions but we provide no guarantees.
|
||||
|
||||
**Stanza copies**
|
||||
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>`_.
|
||||
|
||||
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
|
||||
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>`_.
|
||||
|
||||
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
|
||||
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
|
||||
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::
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ Multi-User Chat (MUC) Bot
|
||||
<xmpp:slixmpp@muc.poez.io?join>`_.
|
||||
|
||||
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
|
||||
echobot example (:ref:`echobot`), we can use one of the bundled plugins
|
||||
|
||||
@@ -47,11 +47,11 @@ the roster. Next, we want to send our message, and to do that we will use :meth:
|
||||
self.send_message(mto=self.recipient, mbody=self.msg)
|
||||
|
||||
Finally, we need to disconnect the client using :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>`.
|
||||
Now, sent stanzas are placed in a queue to pass them to the send thread. If we were to call
|
||||
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
|
||||
for the client to disconnect before the send queue is processed and the message is actually
|
||||
sent on the wire. To ensure that our message is processed, we use
|
||||
:meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`.
|
||||
Now, sent stanzas are placed in a queue to pass them to the send thread.
|
||||
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` by default will wait for an
|
||||
acknowledgement from the server for at least `2.0` seconds. This time is configurable with
|
||||
the `wait` parameter. If `0.0` is passed for `wait`, :meth:`disconnect
|
||||
<slixmpp.xmlstream.XMLStream.disconnect>` will not close the connection gracefully.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -61,12 +61,12 @@ sent on the wire. To ensure that our message is processed, we use
|
||||
|
||||
self.send_message(mto=self.recipient, mbody=self.msg)
|
||||
|
||||
self.disconnect(wait=True)
|
||||
self.disconnect()
|
||||
|
||||
.. warning::
|
||||
|
||||
If you happen to be adding stanzas to the send queue faster than the send thread
|
||||
can process them, then :meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`
|
||||
can process them, then :meth:`disconnect() <slixmpp.xmlstream.XMLStream.disconnect>`
|
||||
will block and not disconnect.
|
||||
|
||||
Final Product
|
||||
|
||||
@@ -4,9 +4,9 @@ Slixmpp
|
||||
.. sidebar:: Get the Code
|
||||
|
||||
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.
|
||||
|
||||
@@ -14,14 +14,14 @@ Slixmpp
|
||||
`slixmpp@muc.poez.io <xmpp:slixmpp@muc.poez.io?join>`_
|
||||
|
||||
**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::
|
||||
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
|
||||
:ref:`differences`.
|
||||
|
||||
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.5+,
|
||||
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+,
|
||||
|
||||
Slixmpp's design goals and philosphy are:
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp import asyncio
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -51,18 +51,17 @@ class AskConfirm(slixmpp.ClientXMPP):
|
||||
else:
|
||||
self.confirmed.set_result(True)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
log.info('Sending confirm request %s to %s who wants to access %s using '
|
||||
'method %s...' % (self.id, self.recipient, self.url, self.method))
|
||||
try:
|
||||
confirmed = yield from self['xep_0070'].ask_confirm(self.recipient,
|
||||
confirmed = await self['xep_0070'].ask_confirm(self.recipient,
|
||||
id=self.id,
|
||||
url=self.url,
|
||||
method=self.method,
|
||||
message='Plz say yes or no for {method} {url} ({id}).')
|
||||
if isinstance(confirmed, slixmpp.Message):
|
||||
confirmed = yield from self.confirmed
|
||||
confirmed = await self.confirmed
|
||||
else:
|
||||
confirmed = True
|
||||
except IqError:
|
||||
|
||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp.xmlstream.asyncio import asyncio
|
||||
|
||||
|
||||
class Disco(slixmpp.ClientXMPP):
|
||||
@@ -54,8 +53,7 @@ class Disco(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -77,13 +75,13 @@ class Disco(slixmpp.ClientXMPP):
|
||||
try:
|
||||
if self.get in self.info_types:
|
||||
# function using the callback parameter.
|
||||
info = yield from self['xep_0030'].get_info(jid=self.target_jid,
|
||||
info = await self['xep_0030'].get_info(jid=self.target_jid,
|
||||
node=self.target_node)
|
||||
if self.get in self.items_types:
|
||||
# The same applies from above. Listen for the
|
||||
# disco_items event or pass a callback function
|
||||
# if you need to process a non-blocking request.
|
||||
items = yield from self['xep_0030'].get_items(jid=self.target_jid,
|
||||
items = await self['xep_0030'].get_items(jid=self.target_jid,
|
||||
node=self.target_node)
|
||||
if self.get not in self.info_types and self.get not in self.items_types:
|
||||
logging.error("Invalid disco request type.")
|
||||
|
||||
@@ -47,8 +47,7 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
self.roster_received.set()
|
||||
self.presences_received.clear()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -65,16 +64,15 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
self.get_roster(callback=self.roster_received_cb)
|
||||
|
||||
print('Waiting for presence updates...\n')
|
||||
yield from self.roster_received.wait()
|
||||
await self.roster_received.wait()
|
||||
print('Roster received')
|
||||
yield from self.presences_received.wait()
|
||||
await self.presences_received.wait()
|
||||
self.disconnect()
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_vcard_avatar(self, pres):
|
||||
async def on_vcard_avatar(self, pres):
|
||||
print("Received vCard avatar update from %s" % pres['from'].bare)
|
||||
try:
|
||||
result = yield from self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
|
||||
result = await self['xep_0054'].get_vcard(pres['from'].bare, cached=True,
|
||||
timeout=5)
|
||||
except XMPPError:
|
||||
print("Error retrieving avatar for %s" % pres['from'])
|
||||
@@ -89,14 +87,13 @@ class AvatarDownloader(slixmpp.ClientXMPP):
|
||||
with open(filename, 'wb+') as img:
|
||||
img.write(avatar['BINVAL'])
|
||||
|
||||
@asyncio.coroutine
|
||||
def on_avatar(self, msg):
|
||||
async def on_avatar(self, msg):
|
||||
print("Received avatar update from %s" % msg['from'])
|
||||
metadata = msg['pubsub_event']['items']['item']['avatar_metadata']
|
||||
for info in metadata['items']:
|
||||
if not info['url']:
|
||||
try:
|
||||
result = yield from self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
|
||||
result = await self['xep_0084'].retrieve_avatar(msg['from'].bare, info['id'],
|
||||
timeout=5)
|
||||
except XMPPError:
|
||||
print("Error retrieving avatar for %s" % msg['from'])
|
||||
|
||||
@@ -14,7 +14,6 @@ from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp import asyncio
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -25,18 +24,21 @@ 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):
|
||||
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)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
log.info('Uploading file %s...', self.filename)
|
||||
url = yield from self['xep_0363'].upload_file(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)
|
||||
@@ -70,6 +72,8 @@ if __name__ == '__main__':
|
||||
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()
|
||||
|
||||
@@ -82,7 +86,7 @@ if __name__ == '__main__':
|
||||
if args.password is None:
|
||||
args.password = getpass("Password: ")
|
||||
|
||||
xmpp = HttpUpload(args.jid, args.password, args.recipient, args.file)
|
||||
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')
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
@@ -39,8 +38,7 @@ class IBBSender(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -58,13 +56,13 @@ class IBBSender(slixmpp.ClientXMPP):
|
||||
|
||||
try:
|
||||
# Open the IBB stream in which to write to.
|
||||
stream = yield from self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
|
||||
stream = await self['xep_0047'].open_stream(self.receiver, use_messages=self.use_messages)
|
||||
|
||||
# If you want to send in-memory bytes, use stream.sendall() instead.
|
||||
yield from stream.sendfile(self.file, timeout=10)
|
||||
await stream.sendfile(self.file, timeout=10)
|
||||
|
||||
# And finally close the stream.
|
||||
yield from stream.close(timeout=10)
|
||||
await stream.close(timeout=10)
|
||||
except (IqError, IqTimeout):
|
||||
print('File transfer errored')
|
||||
else:
|
||||
|
||||
@@ -15,7 +15,6 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp import asyncio
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
from slixmpp.exceptions import IqError, IqTimeout
|
||||
from slixmpp import asyncio
|
||||
|
||||
import slixmpp
|
||||
|
||||
@@ -38,8 +37,7 @@ class PingTest(slixmpp.ClientXMPP):
|
||||
# our roster.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -56,7 +54,7 @@ class PingTest(slixmpp.ClientXMPP):
|
||||
self.get_roster()
|
||||
|
||||
try:
|
||||
rtt = yield from self['xep_0199'].ping(self.pingjid,
|
||||
rtt = await self['xep_0199'].ping(self.pingjid,
|
||||
timeout=10)
|
||||
logging.info("Success! RTT: %s", rtt)
|
||||
except IqError as e:
|
||||
|
||||
@@ -5,7 +5,6 @@ import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
import asyncio
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp.xmlstream import ET, tostring
|
||||
@@ -32,87 +31,86 @@ class PubsubClient(slixmpp.ClientXMPP):
|
||||
|
||||
self.add_event_handler('session_start', self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
self.get_roster()
|
||||
self.send_presence()
|
||||
|
||||
try:
|
||||
yield from getattr(self, self.action)()
|
||||
await getattr(self, self.action)()
|
||||
except:
|
||||
logging.exception('Could not execute %s:', self.action)
|
||||
self.disconnect()
|
||||
|
||||
def nodes(self):
|
||||
async def nodes(self):
|
||||
try:
|
||||
result = yield from self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
||||
result = await self['xep_0060'].get_nodes(self.pubsub_server, self.node)
|
||||
for item in result['disco_items']['items']:
|
||||
logging.info(' - %s', str(item))
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retrieve node list: %s', error.format())
|
||||
|
||||
def create(self):
|
||||
async def create(self):
|
||||
try:
|
||||
yield from self['xep_0060'].create_node(self.pubsub_server, self.node)
|
||||
await self['xep_0060'].create_node(self.pubsub_server, self.node)
|
||||
logging.info('Created node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not create node %s: %s', self.node, error.format())
|
||||
|
||||
def delete(self):
|
||||
async def delete(self):
|
||||
try:
|
||||
yield from self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
||||
await self['xep_0060'].delete_node(self.pubsub_server, self.node)
|
||||
logging.info('Deleted node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not delete node %s: %s', self.node, error.format())
|
||||
|
||||
def get_configure(self):
|
||||
async def get_configure(self):
|
||||
try:
|
||||
configuration_form = yield from self['xep_0060'].get_node_config(self.pubsub_server, self.node)
|
||||
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())
|
||||
|
||||
def publish(self):
|
||||
async def publish(self):
|
||||
payload = ET.fromstring("<test xmlns='test'>%s</test>" % self.data)
|
||||
try:
|
||||
result = yield from self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
||||
result = await self['xep_0060'].publish(self.pubsub_server, self.node, payload=payload)
|
||||
logging.info('Published at item id: %s', result['pubsub']['publish']['item']['id'])
|
||||
except XMPPError as error:
|
||||
logging.error('Could not publish to %s: %s', self.node, error.format())
|
||||
|
||||
def get(self):
|
||||
async def get(self):
|
||||
try:
|
||||
result = yield from self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
||||
result = await self['xep_0060'].get_item(self.pubsub_server, self.node, self.data)
|
||||
for item in result['pubsub']['items']['substanzas']:
|
||||
logging.info('Retrieved item %s: %s', item['id'], tostring(item['payload']))
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retrieve item %s from node %s: %s', self.data, self.node, error.format())
|
||||
|
||||
def retract(self):
|
||||
async def retract(self):
|
||||
try:
|
||||
yield from self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||
await self['xep_0060'].retract(self.pubsub_server, self.node, self.data)
|
||||
logging.info('Retracted item %s from node %s', self.data, self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not retract item %s from node %s: %s', self.data, self.node, error.format())
|
||||
|
||||
def purge(self):
|
||||
async def purge(self):
|
||||
try:
|
||||
yield from self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||
await self['xep_0060'].purge(self.pubsub_server, self.node)
|
||||
logging.info('Purged all items from node %s', self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not purge items from node %s: %s', self.node, error.format())
|
||||
|
||||
def subscribe(self):
|
||||
async def subscribe(self):
|
||||
try:
|
||||
iq = yield from self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||
iq = await self['xep_0060'].subscribe(self.pubsub_server, self.node)
|
||||
subscription = iq['pubsub']['subscription']
|
||||
logging.info('Subscribed %s to node %s', subscription['jid'], subscription['node'])
|
||||
except XMPPError as error:
|
||||
logging.error('Could not subscribe %s to node %s: %s', self.boundjid.bare, self.node, error.format())
|
||||
|
||||
def unsubscribe(self):
|
||||
async def unsubscribe(self):
|
||||
try:
|
||||
yield from self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||
await self['xep_0060'].unsubscribe(self.pubsub_server, self.node)
|
||||
logging.info('Unsubscribed %s from node %s', self.boundjid.bare, self.node)
|
||||
except XMPPError as error:
|
||||
logging.error('Could not unsubscribe %s from node %s: %s', self.boundjid.bare, self.node, error.format())
|
||||
|
||||
@@ -66,7 +66,7 @@ class RegisterBot(slixmpp.ClientXMPP):
|
||||
# We're only concerned about registering, so nothing more to do here.
|
||||
self.disconnect()
|
||||
|
||||
def register(self, iq):
|
||||
async def register(self, iq):
|
||||
"""
|
||||
Fill out and submit a registration form.
|
||||
|
||||
@@ -90,7 +90,7 @@ class RegisterBot(slixmpp.ClientXMPP):
|
||||
resp['register']['password'] = self.password
|
||||
|
||||
try:
|
||||
yield from resp.send()
|
||||
await resp.send()
|
||||
logging.info("Account created for %s!" % self.boundjid)
|
||||
except IqError as e:
|
||||
logging.error("Could not register account: %s" %
|
||||
|
||||
@@ -38,8 +38,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
self.received = set()
|
||||
self.presences_received = asyncio.Event()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -57,7 +56,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
future.set_result(None)
|
||||
try:
|
||||
self.get_roster(callback=callback)
|
||||
yield from future
|
||||
await future
|
||||
except IqError as err:
|
||||
print('Error: %s' % err.iq['error']['condition'])
|
||||
except IqTimeout:
|
||||
@@ -66,7 +65,7 @@ class RosterBrowser(slixmpp.ClientXMPP):
|
||||
|
||||
|
||||
print('Waiting for presence updates...\n')
|
||||
yield from asyncio.sleep(10)
|
||||
await asyncio.sleep(10)
|
||||
|
||||
print('Roster for %s' % self.boundjid.bare)
|
||||
groups = self.client_roster.groups()
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from getpass import getpass
|
||||
from argparse import ArgumentParser
|
||||
@@ -36,8 +35,7 @@ class S5BSender(slixmpp.ClientXMPP):
|
||||
# and the XML streams are ready for use.
|
||||
self.add_event_handler("session_start", self.start)
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -53,14 +51,14 @@ class S5BSender(slixmpp.ClientXMPP):
|
||||
|
||||
try:
|
||||
# Open the S5B stream in which to write to.
|
||||
proxy = yield from self['xep_0065'].handshake(self.receiver)
|
||||
proxy = await self['xep_0065'].handshake(self.receiver)
|
||||
|
||||
# Send the entire file.
|
||||
while True:
|
||||
data = self.file.read(1048576)
|
||||
if not data:
|
||||
break
|
||||
yield from proxy.write(data)
|
||||
await proxy.write(data)
|
||||
|
||||
# And finally close the stream.
|
||||
proxy.transport.write_eof()
|
||||
|
||||
@@ -18,7 +18,6 @@ from argparse import ArgumentParser
|
||||
|
||||
import slixmpp
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp import asyncio
|
||||
|
||||
class AvatarSetter(slixmpp.ClientXMPP):
|
||||
|
||||
@@ -33,8 +32,7 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
||||
|
||||
self.filepath = filepath
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self, event):
|
||||
async def start(self, event):
|
||||
"""
|
||||
Process the session_start event.
|
||||
|
||||
@@ -68,20 +66,20 @@ class AvatarSetter(slixmpp.ClientXMPP):
|
||||
used_xep84 = False
|
||||
|
||||
print('Publish XEP-0084 avatar data')
|
||||
result = yield from self['xep_0084'].publish_avatar(avatar)
|
||||
result = await self['xep_0084'].publish_avatar(avatar)
|
||||
if isinstance(result, XMPPError):
|
||||
print('Could not publish XEP-0084 avatar')
|
||||
else:
|
||||
used_xep84 = True
|
||||
|
||||
print('Update vCard with avatar')
|
||||
result = yield from self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||
result = await self['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type)
|
||||
if isinstance(result, XMPPError):
|
||||
print('Could not set vCard avatar')
|
||||
|
||||
if used_xep84:
|
||||
print('Advertise XEP-0084 avatar metadata')
|
||||
result = yield from self['xep_0084'].publish_avatar_metadata([
|
||||
result = await self['xep_0084'].publish_avatar_metadata([
|
||||
{'id': avatar_id,
|
||||
'type': avatar_type,
|
||||
'bytes': avatar_bytes}
|
||||
|
||||
10
setup.py
10
setup.py
@@ -20,8 +20,7 @@ from run_tests import TestCommand
|
||||
from slixmpp.version import __version__
|
||||
|
||||
VERSION = __version__
|
||||
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber, '
|
||||
'Google Talk, etc).')
|
||||
DESCRIPTION = ('Slixmpp is an elegant Python library for XMPP (aka Jabber).')
|
||||
with open('README.rst', encoding='utf8') as readme:
|
||||
LONG_DESCRIPTION = readme.read()
|
||||
|
||||
@@ -29,9 +28,8 @@ CLASSIFIERS = [
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Topic :: Internet :: XMPP',
|
||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||
]
|
||||
@@ -79,12 +77,12 @@ setup(
|
||||
long_description=LONG_DESCRIPTION,
|
||||
author='Florent Le Coz',
|
||||
author_email='louiz@louiz.org',
|
||||
url='https://dev.louiz.org/projects/slixmpp',
|
||||
url='https://lab.louiz.org/poezio/slixmpp',
|
||||
license='MIT',
|
||||
platforms=['any'],
|
||||
packages=packages,
|
||||
ext_modules=ext_modules,
|
||||
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules'],
|
||||
install_requires=['aiodns>=1.0', 'pyasn1', 'pyasn1_modules', 'aiohttp'],
|
||||
classifiers=CLASSIFIERS,
|
||||
cmdclass={'test': TestCommand}
|
||||
)
|
||||
|
||||
@@ -104,12 +104,15 @@ class BaseXMPP(XMLStream):
|
||||
#: :attr:`use_message_ids` to `True` will assign all outgoing
|
||||
#: messages an ID. Some plugin features require enabling
|
||||
#: this option.
|
||||
self.use_message_ids = False
|
||||
self.use_message_ids = True
|
||||
|
||||
#: Presence updates may optionally be tagged with ID values.
|
||||
#: Setting :attr:`use_message_ids` to `True` will assign all
|
||||
#: outgoing messages an ID.
|
||||
self.use_presence_ids = False
|
||||
self.use_presence_ids = True
|
||||
|
||||
#: XEP-0359 <origin-id/> tag that gets added to <message/> stanzas.
|
||||
self.use_origin_id = True
|
||||
|
||||
#: The API registry is a way to process callbacks based on
|
||||
#: JID+node combinations. Each callback in the registry is
|
||||
|
||||
@@ -97,7 +97,10 @@ class FeatureMechanisms(BasePlugin):
|
||||
jid = self.xmpp.requested_jid.bare
|
||||
result[value] = creds.get('email', jid)
|
||||
elif value == 'channel_binding':
|
||||
result[value] = self.xmpp.socket.get_channel_binding()
|
||||
if isinstance(self.xmpp.socket, (ssl.SSLSocket, ssl.SSLObject)):
|
||||
result[value] = self.xmpp.socket.get_channel_binding()
|
||||
else:
|
||||
result[value] = None
|
||||
elif value == 'host':
|
||||
result[value] = creds.get('host', self.xmpp.requested_jid.domain)
|
||||
elif value == 'realm':
|
||||
|
||||
102
slixmpp/jid.py
102
slixmpp/jid.py
@@ -16,6 +16,7 @@ import socket
|
||||
|
||||
from copy import deepcopy
|
||||
from functools import lru_cache
|
||||
from typing import Optional
|
||||
|
||||
from slixmpp.stringprep import nodeprep, resourceprep, idna, StringprepError
|
||||
|
||||
@@ -71,7 +72,7 @@ def _parse_jid(data):
|
||||
return node, domain, resource
|
||||
|
||||
|
||||
def _validate_node(node):
|
||||
def _validate_node(node: Optional[str]):
|
||||
"""Validate the local, or username, portion of a JID.
|
||||
|
||||
:raises InvalidJID:
|
||||
@@ -93,7 +94,7 @@ def _validate_node(node):
|
||||
return node
|
||||
|
||||
|
||||
def _validate_domain(domain):
|
||||
def _validate_domain(domain: str):
|
||||
"""Validate the domain portion of a JID.
|
||||
|
||||
IP literal addresses are left as-is, if valid. Domain names
|
||||
@@ -152,7 +153,7 @@ def _validate_domain(domain):
|
||||
return domain
|
||||
|
||||
|
||||
def _validate_resource(resource):
|
||||
def _validate_resource(resource: Optional[str]):
|
||||
"""Validate the resource portion of a JID.
|
||||
|
||||
:raises InvalidJID:
|
||||
@@ -174,7 +175,7 @@ def _validate_resource(resource):
|
||||
return resource
|
||||
|
||||
|
||||
def _unescape_node(node):
|
||||
def _unescape_node(node: str):
|
||||
"""Unescape a local portion of a JID.
|
||||
|
||||
.. note::
|
||||
@@ -199,7 +200,11 @@ def _unescape_node(node):
|
||||
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.
|
||||
|
||||
:param string local: Optional. The local portion of the JID.
|
||||
@@ -237,12 +242,17 @@ class UnescapedJID:
|
||||
|
||||
__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._domain = domain
|
||||
self._resource = resource
|
||||
|
||||
def __getattribute__(self, name):
|
||||
def __getattribute__(self, name: str):
|
||||
"""Retrieve the given JID component.
|
||||
|
||||
:param name: one of: user, server, domain, resource,
|
||||
@@ -301,7 +311,7 @@ class JID:
|
||||
|
||||
__slots__ = ('_node', '_domain', '_resource', '_bare', '_full')
|
||||
|
||||
def __init__(self, jid=None):
|
||||
def __init__(self, jid: Optional[str] = None):
|
||||
if not jid:
|
||||
self._node = ''
|
||||
self._domain = ''
|
||||
@@ -346,30 +356,10 @@ class JID:
|
||||
def node(self):
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def user(self):
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def local(self):
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self._node
|
||||
|
||||
@property
|
||||
def domain(self):
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def server(self):
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
return self._domain
|
||||
|
||||
@property
|
||||
def resource(self):
|
||||
return self._resource
|
||||
@@ -382,47 +372,18 @@ class JID:
|
||||
def full(self):
|
||||
return self._full
|
||||
|
||||
@property
|
||||
def jid(self):
|
||||
return self._full
|
||||
|
||||
@node.setter
|
||||
def node(self, value):
|
||||
self._node = _validate_node(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@user.setter
|
||||
def user(self, value):
|
||||
self._node = _validate_node(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@local.setter
|
||||
def local(self, value):
|
||||
self._node = _validate_node(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@username.setter
|
||||
def username(self, value):
|
||||
def node(self, value: str):
|
||||
self._node = _validate_node(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@domain.setter
|
||||
def domain(self, value):
|
||||
self._domain = _validate_domain(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@server.setter
|
||||
def server(self, value):
|
||||
self._domain = _validate_domain(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@host.setter
|
||||
def host(self, value):
|
||||
def domain(self, value: str):
|
||||
self._domain = _validate_domain(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@bare.setter
|
||||
def bare(self, value):
|
||||
def bare(self, value: str):
|
||||
node, domain, resource = _parse_jid(value)
|
||||
assert not resource
|
||||
self._node = node
|
||||
@@ -430,19 +391,23 @@ class JID:
|
||||
self._update_bare_full()
|
||||
|
||||
@resource.setter
|
||||
def resource(self, value):
|
||||
def resource(self, value: str):
|
||||
self._resource = _validate_resource(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@full.setter
|
||||
def full(self, value):
|
||||
def full(self, value: str):
|
||||
self._node, self._domain, self._resource = _parse_jid(value)
|
||||
self._update_bare_full()
|
||||
|
||||
@jid.setter
|
||||
def jid(self, value):
|
||||
self._node, self._domain, self._resource = _parse_jid(value)
|
||||
self._update_bare_full()
|
||||
user = node
|
||||
local = node
|
||||
username = node
|
||||
|
||||
server = domain
|
||||
host = domain
|
||||
|
||||
jid = full
|
||||
|
||||
def __str__(self):
|
||||
"""Use the full JID as the string value."""
|
||||
@@ -458,7 +423,10 @@ class JID:
|
||||
if isinstance(other, UnescapedJID):
|
||||
return False
|
||||
if not isinstance(other, JID):
|
||||
other = JID(other)
|
||||
try:
|
||||
other = JID(other)
|
||||
except InvalidJID:
|
||||
return NotImplemented
|
||||
|
||||
return (self._node == other._node and
|
||||
self._domain == other._domain and
|
||||
|
||||
@@ -85,4 +85,6 @@ __all__ = [
|
||||
'xep_0323', # IoT Systems Sensor Data
|
||||
'xep_0325', # IoT Systems Control
|
||||
'xep_0332', # HTTP Over XMPP Transport
|
||||
'protoxep_reactions', # https://dino.im/xeps/reactions.html
|
||||
'protoxep_occupantid', # https://dino.im/xeps/occupant-id.html
|
||||
]
|
||||
|
||||
12
slixmpp/plugins/protoxep_occupantid/__init__.py
Normal file
12
slixmpp/plugins/protoxep_occupantid/__init__.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
from slixmpp.plugins.protoxep_occupantid.occupantid import XEP_OccupantID
|
||||
from slixmpp.plugins.protoxep_occupantid.stanza import OccupantID
|
||||
|
||||
register_plugin(XEP_OccupantID)
|
||||
23
slixmpp/plugins/protoxep_occupantid/occupantid.py
Normal file
23
slixmpp/plugins/protoxep_occupantid/occupantid.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.stanza import Message, Presence
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
|
||||
from slixmpp.plugins.protoxep_occupantid import stanza
|
||||
|
||||
|
||||
class XEP_OccupantID(BasePlugin):
|
||||
name = 'protoxep_occupantid'
|
||||
description = 'XEP-XXXX: Anonymous unique occupant identifiers for MUCs'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
register_stanza_plugin(Message, stanza.OccupantID)
|
||||
register_stanza_plugin(Presence, stanza.OccupantID)
|
||||
16
slixmpp/plugins/protoxep_occupantid/stanza.py
Normal file
16
slixmpp/plugins/protoxep_occupantid/stanza.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase
|
||||
|
||||
|
||||
class OccupantID(ElementBase):
|
||||
name = 'occupant-id'
|
||||
plugin_attrib = 'occupant-id'
|
||||
namespace = 'urn:xmpp:occupant-id:0'
|
||||
interfaces = {'id'}
|
||||
11
slixmpp/plugins/protoxep_reactions/__init__.py
Normal file
11
slixmpp/plugins/protoxep_reactions/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
from slixmpp.plugins.protoxep_reactions.reactions import XEP_Reactions
|
||||
|
||||
register_plugin(XEP_Reactions)
|
||||
54
slixmpp/plugins/protoxep_reactions/reactions.py
Normal file
54
slixmpp/plugins/protoxep_reactions/reactions.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
from typing import Iterable
|
||||
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.stanza import Message
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.xmlstream.matcher import MatchXMLMask
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
|
||||
from slixmpp.plugins.protoxep_reactions import stanza
|
||||
|
||||
|
||||
class XEP_Reactions(BasePlugin):
|
||||
name = 'protoxep_reactions'
|
||||
description = 'XEP-XXXX: Message Reactions'
|
||||
dependencies = {'xep_0030'}
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
self.xmpp.register_handler(
|
||||
Callback(
|
||||
'Reaction received',
|
||||
MatchXMLMask('<message><reactions xmlns="urn:xmpp:reactions:0"/></message>'),
|
||||
self._handle_reactions,
|
||||
)
|
||||
)
|
||||
self.xmpp['xep_0030'].add_feature('urn:xmpp:reactions:0')
|
||||
register_stanza_plugin(Message, stanza.Reactions)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Reaction received')
|
||||
self.xmpp['xep_0030'].remove_feature('urn:xmpp:reactions:0')
|
||||
|
||||
def _handle_reactions(self, message: Message):
|
||||
self.xmpp.event('reactions', message)
|
||||
|
||||
@staticmethod
|
||||
def set_reactions(message: Message, to_id: str, reactions: Iterable[str]):
|
||||
"""
|
||||
Add reactions to a Message object.
|
||||
"""
|
||||
reactions_stanza = stanza.Reactions()
|
||||
reactions_stanza['to'] = to_id
|
||||
for reaction in reactions:
|
||||
reaction_stanza = stanza.Reaction()
|
||||
reaction_stanza['value'] = reaction
|
||||
reactions_stanza.append(reaction_stanza)
|
||||
message.append(reactions_stanza)
|
||||
31
slixmpp/plugins/protoxep_reactions/stanza.py
Normal file
31
slixmpp/plugins/protoxep_reactions/stanza.py
Normal file
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Slixmpp: The Slick XMPP Library
|
||||
Copyright (C) 2019 Mathieu Pasquet
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||
|
||||
|
||||
class Reactions(ElementBase):
|
||||
name = 'reactions'
|
||||
plugin_attrib = 'reactions'
|
||||
namespace = 'urn:xmpp:reactions:0'
|
||||
interfaces = {'to'}
|
||||
|
||||
|
||||
class Reaction(ElementBase):
|
||||
name = 'reaction'
|
||||
namespace = 'urn:xmpp:reactions:0'
|
||||
interfaces = {'value'}
|
||||
|
||||
def get_value(self) -> str:
|
||||
return self.xml.text
|
||||
|
||||
def set_value(self, value: str):
|
||||
self.xml.text = value
|
||||
|
||||
|
||||
register_stanza_plugin(Reactions, Reaction, iterable=True)
|
||||
@@ -7,7 +7,7 @@
|
||||
"""
|
||||
|
||||
from slixmpp.xmlstream.stanzabase import ElementBase
|
||||
from xml.etree import cElementTree as ET
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
|
||||
class RPCQuery(ElementBase):
|
||||
|
||||
@@ -299,23 +299,29 @@ class XEP_0030(BasePlugin):
|
||||
return self.api['has_identity'](jid, node, ifrom, data)
|
||||
|
||||
async def get_info_from_domain(self, domain=None, timeout=None,
|
||||
cached=True, callback=None, **kwargs):
|
||||
cached=True, callback=None):
|
||||
"""Fetch disco#info of specified domain and one disco#items level below"""
|
||||
|
||||
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, **kwargs)]
|
||||
domain, timeout=timeout)]
|
||||
iq_items = await self.get_items(
|
||||
domain, timeout=timeout, **kwargs)
|
||||
domain, timeout=timeout)
|
||||
items = iq_items['disco_items']['items']
|
||||
infos += [
|
||||
self.get_info(item[0], timeout=timeout, **kwargs)
|
||||
self.get_info(item[0], timeout=timeout)
|
||||
for item in items]
|
||||
info_futures, _ = await asyncio.wait(infos, timeout=timeout)
|
||||
info_futures, _ = await asyncio.wait(
|
||||
infos,
|
||||
timeout=timeout,
|
||||
loop=self.xmpp.loop
|
||||
)
|
||||
|
||||
self.domain_infos[domain] = [
|
||||
future.result() for future in info_futures]
|
||||
future.result() for future in info_futures if not future.exception()]
|
||||
|
||||
results = self.domain_infos[domain]
|
||||
|
||||
@@ -344,7 +350,7 @@ class XEP_0030(BasePlugin):
|
||||
combination handled by this Slixmpp instance and
|
||||
no stanzas need to be sent.
|
||||
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
|
||||
the local cache system. If no results are found,
|
||||
send the query as usual. The self.use_cache
|
||||
|
||||
@@ -9,7 +9,7 @@ from __future__ import with_statement
|
||||
|
||||
import logging
|
||||
|
||||
from slixmpp import Presence
|
||||
from slixmpp import Presence, Message
|
||||
from slixmpp.plugins import BasePlugin, register_plugin
|
||||
from slixmpp.xmlstream import register_stanza_plugin, ElementBase, JID, ET
|
||||
from slixmpp.xmlstream.handler.callback import Callback
|
||||
@@ -162,7 +162,7 @@ class XEP_0045(BasePlugin):
|
||||
return
|
||||
self.xmpp.roster[pr['from']].ignore_updates = True
|
||||
entry = pr['muc'].get_stanza_values()
|
||||
entry['show'] = pr['show']
|
||||
entry['show'] = pr['show'] if pr['show'] in pr.showtypes else None
|
||||
entry['status'] = pr['status']
|
||||
entry['alt_nick'] = pr['nick']
|
||||
if pr['type'] == 'unavailable':
|
||||
@@ -181,7 +181,7 @@ class XEP_0045(BasePlugin):
|
||||
if got_online:
|
||||
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.
|
||||
"""
|
||||
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
|
||||
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)
|
||||
|
||||
def jid_in_room(self, room, jid):
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
from slixmpp import JID
|
||||
from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
||||
|
||||
|
||||
@@ -52,6 +53,12 @@ class Conference(ElementBase):
|
||||
if value in ('1', 'true', True):
|
||||
self._set_attr('autojoin', 'true')
|
||||
|
||||
def set_jid(self, value):
|
||||
del self['jid']
|
||||
if isinstance(value, JID):
|
||||
value = value.full
|
||||
self._set_attr('jid', value)
|
||||
|
||||
|
||||
class URL(ElementBase):
|
||||
name = 'url'
|
||||
|
||||
@@ -89,31 +89,17 @@ class XEP_0050(BasePlugin):
|
||||
self.commands = {}
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback("Ad-Hoc Execute",
|
||||
StanzaPath('iq@type=set/command'),
|
||||
self._handle_command))
|
||||
Callback("Ad-Hoc Execute",
|
||||
StanzaPath('iq@type=set/command'),
|
||||
self._handle_command))
|
||||
|
||||
register_stanza_plugin(Iq, Command)
|
||||
register_stanza_plugin(Command, Form, iterable=True)
|
||||
|
||||
self.xmpp.add_event_handler('command_execute',
|
||||
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)
|
||||
self.xmpp.add_event_handler('command', self._handle_command_all)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.del_event_handler('command_execute',
|
||||
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.del_event_handler('command', self._handle_command_all)
|
||||
self.xmpp.remove_handler('Ad-Hoc Execute')
|
||||
self.xmpp['xep_0030'].del_feature(feature=Command.namespace)
|
||||
self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
|
||||
@@ -201,8 +187,27 @@ class XEP_0050(BasePlugin):
|
||||
|
||||
def _handle_command(self, iq):
|
||||
"""Raise command events based on the command action."""
|
||||
self.xmpp.event('command', 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):
|
||||
"""
|
||||
Process an initial request to execute a command.
|
||||
@@ -468,7 +473,7 @@ class XEP_0050(BasePlugin):
|
||||
**kwargs)
|
||||
|
||||
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
|
||||
workflow management APIs.
|
||||
|
||||
@@ -79,7 +79,8 @@ class ResultIterator:
|
||||
"""
|
||||
if self._stop:
|
||||
raise StopAsyncIteration
|
||||
self.query[self.interface]['rsm']['before'] = self.reverse
|
||||
if self.query[self.interface]['rsm']['before'] is None:
|
||||
self.query[self.interface]['rsm']['before'] = self.reverse
|
||||
self.query['id'] = self.query.stream.new_id()
|
||||
self.query[self.interface]['rsm']['max'] = str(self.amount)
|
||||
|
||||
@@ -141,7 +142,7 @@ class XEP_0059(BasePlugin):
|
||||
def session_bind(self, jid):
|
||||
self.xmpp['xep_0030'].add_feature(Set.namespace)
|
||||
|
||||
def iterate(self, stanza, interface, results='substanzas',
|
||||
def iterate(self, stanza, interface, results='substanzas', amount=10, reverse=False,
|
||||
recv_interface=None, pre_cb=None, post_cb=None):
|
||||
"""
|
||||
Create a new result set iterator for a given stanza query.
|
||||
@@ -169,6 +170,6 @@ class XEP_0059(BasePlugin):
|
||||
results -- The name of the interface containing the
|
||||
query results (typically just 'substanzas').
|
||||
"""
|
||||
return ResultIterator(stanza, interface, results,
|
||||
return ResultIterator(stanza, interface, results, amount, reverse=reverse,
|
||||
recv_interface=recv_interface, pre_cb=pre_cb,
|
||||
post_cb=post_cb)
|
||||
|
||||
@@ -59,7 +59,7 @@ class XEP_0077(BasePlugin):
|
||||
|
||||
def _force_stream_feature(self, stanza):
|
||||
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:
|
||||
return stanza
|
||||
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
|
||||
if micro is None:
|
||||
micro = now.microsecond
|
||||
if offset is None:
|
||||
if offset in (None, 0):
|
||||
offset = tzutc()
|
||||
elif not isinstance(offset, dt.tzinfo):
|
||||
offset = tzoffset(None, offset)
|
||||
@@ -177,7 +177,7 @@ def datetime(year=None, month=None, day=None, hour=None,
|
||||
sec = now.second
|
||||
if micro is None:
|
||||
micro = now.microsecond
|
||||
if offset is None:
|
||||
if offset in (None, 0):
|
||||
offset = tzutc()
|
||||
elif not isinstance(offset, dt.tzinfo):
|
||||
offset = tzoffset(None, offset)
|
||||
|
||||
@@ -167,10 +167,7 @@ class XEP_0153(BasePlugin):
|
||||
data = pres['vcard_temp_update']['photo']
|
||||
if data is None:
|
||||
return
|
||||
elif data == '' or data != self.api['get_hash'](pres['from']):
|
||||
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)
|
||||
self.xmpp.event('vcard_avatar_update', pres)
|
||||
|
||||
# =================================================================
|
||||
|
||||
|
||||
@@ -62,7 +62,10 @@ class XEP_0163(BasePlugin):
|
||||
for ns in namespace:
|
||||
self.xmpp['xep_0030'].add_feature('%s+notify' % ns,
|
||||
jid=jid)
|
||||
asyncio.ensure_future(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):
|
||||
"""
|
||||
@@ -81,7 +84,10 @@ class XEP_0163(BasePlugin):
|
||||
for ns in namespace:
|
||||
self.xmpp['xep_0030'].del_feature(jid=jid,
|
||||
feature='%s+notify' % namespace)
|
||||
asyncio.ensure_future(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,
|
||||
timeout_callback=None, callback=None, timeout=None):
|
||||
|
||||
@@ -11,10 +11,9 @@ from slixmpp.xmlstream import ElementBase, ET
|
||||
|
||||
class UserGaming(ElementBase):
|
||||
|
||||
name = 'gaming'
|
||||
name = 'game'
|
||||
namespace = 'urn:xmpp:gaming:0'
|
||||
plugin_attrib = 'gaming'
|
||||
interfaces = {'character_name', 'character_profile', 'name',
|
||||
'level', 'server_address', 'server_name', 'uri'}
|
||||
sub_interfaces = interfaces
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
self.window_counter = self.window
|
||||
|
||||
self.enabled = False
|
||||
self.enabled_in = False
|
||||
self.enabled_out = False
|
||||
self.unacked_queue = collections.deque()
|
||||
|
||||
register_stanza_plugin(StreamFeatures, stanza.StreamManagement)
|
||||
@@ -82,10 +83,6 @@ class XEP_0198(BasePlugin):
|
||||
self.xmpp.register_stanza(stanza.Ack)
|
||||
self.xmpp.register_stanza(stanza.RequestAck)
|
||||
|
||||
# Only end the session when a </stream> element is sent,
|
||||
# not just because the connection has died.
|
||||
self.xmpp.end_session_on_disconnect = False
|
||||
|
||||
# Register the feature twice because it may be ordered two
|
||||
# different ways: enabling after binding and resumption
|
||||
# before binding.
|
||||
@@ -131,6 +128,7 @@ class XEP_0198(BasePlugin):
|
||||
self.xmpp.add_filter('in', self._handle_incoming)
|
||||
self.xmpp.add_filter('out_sync', self._handle_outgoing)
|
||||
|
||||
self.xmpp.add_event_handler('disconnected', self.disconnected)
|
||||
self.xmpp.add_event_handler('session_end', self.session_end)
|
||||
|
||||
def plugin_end(self):
|
||||
@@ -139,6 +137,7 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
self.xmpp.unregister_feature('sm', self.order)
|
||||
self.xmpp.unregister_feature('sm', self.resume_order)
|
||||
self.xmpp.del_event_handler('disconnected', self.disconnected)
|
||||
self.xmpp.del_event_handler('session_end', self.session_end)
|
||||
self.xmpp.del_filter('in', self._handle_incoming)
|
||||
self.xmpp.del_filter('out_sync', self._handle_outgoing)
|
||||
@@ -154,9 +153,19 @@ class XEP_0198(BasePlugin):
|
||||
self.xmpp.remove_stanza(stanza.Ack)
|
||||
self.xmpp.remove_stanza(stanza.RequestAck)
|
||||
|
||||
def disconnected(self, event):
|
||||
"""Reset enabled state until we can resume/reenable."""
|
||||
log.debug("disconnected, disabling SM")
|
||||
self.xmpp.event('sm_disabled', event)
|
||||
self.enabled_in = False
|
||||
self.enabled_out = False
|
||||
|
||||
def session_end(self, event):
|
||||
"""Reset stream management state."""
|
||||
self.enabled = False
|
||||
log.debug("session_end, disabling SM")
|
||||
self.xmpp.event('sm_disabled', event)
|
||||
self.enabled_in = False
|
||||
self.enabled_out = False
|
||||
self.unacked_queue.clear()
|
||||
self.sm_id = None
|
||||
self.handled = 0
|
||||
@@ -171,6 +180,7 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
def request_ack(self, e=None):
|
||||
"""Request an ack from the server."""
|
||||
log.debug("requesting ack")
|
||||
req = stanza.RequestAck(self.xmpp)
|
||||
self.xmpp.send_raw(str(req))
|
||||
|
||||
@@ -193,9 +203,7 @@ class XEP_0198(BasePlugin):
|
||||
enable = stanza.Enable(self.xmpp)
|
||||
enable['resume'] = self.allow_resume
|
||||
enable.send()
|
||||
self.enabled = True
|
||||
self.handled = 0
|
||||
self.unacked_queue.clear()
|
||||
log.debug("enabling SM")
|
||||
|
||||
waiter = Waiter('enabled_or_failed',
|
||||
MatchMany([
|
||||
@@ -204,11 +212,11 @@ class XEP_0198(BasePlugin):
|
||||
self.xmpp.register_handler(waiter)
|
||||
result = await waiter.wait()
|
||||
elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
|
||||
self.enabled = True
|
||||
resume = stanza.Resume(self.xmpp)
|
||||
resume['h'] = self.handled
|
||||
resume['previd'] = self.sm_id
|
||||
resume.send()
|
||||
log.debug("resuming SM")
|
||||
|
||||
# Wait for a response before allowing stream feature processing
|
||||
# to continue. The actual result processing will be done in the
|
||||
@@ -231,7 +239,10 @@ class XEP_0198(BasePlugin):
|
||||
self.xmpp.features.add('stream_management')
|
||||
if stanza['id']:
|
||||
self.sm_id = stanza['id']
|
||||
self.enabled_in = True
|
||||
self.handled = 0
|
||||
self.xmpp.event('sm_enabled', stanza)
|
||||
self.xmpp.end_session_on_disconnect = False
|
||||
|
||||
def _handle_resumed(self, stanza):
|
||||
"""Finish resuming a stream by resending unacked stanzas.
|
||||
@@ -239,10 +250,12 @@ class XEP_0198(BasePlugin):
|
||||
Raises a :term:`session_resumed` event.
|
||||
"""
|
||||
self.xmpp.features.add('stream_management')
|
||||
self.enabled_in = True
|
||||
self._handle_ack(stanza)
|
||||
for id, stanza in self.unacked_queue:
|
||||
self.xmpp.send(stanza, use_filters=False)
|
||||
self.xmpp.event('session_resumed', stanza)
|
||||
self.xmpp.end_session_on_disconnect = False
|
||||
|
||||
def _handle_failed(self, stanza):
|
||||
"""
|
||||
@@ -252,7 +265,8 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
Raises an :term:`sm_failed` event.
|
||||
"""
|
||||
self.enabled = False
|
||||
self.enabled_in = False
|
||||
self.enabled_out = False
|
||||
self.unacked_queue.clear()
|
||||
self.xmpp.event('sm_failed', stanza)
|
||||
|
||||
@@ -289,7 +303,7 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
def _handle_incoming(self, stanza):
|
||||
"""Increment the handled counter for each inbound stanza."""
|
||||
if not self.enabled:
|
||||
if not self.enabled_in:
|
||||
return stanza
|
||||
|
||||
if isinstance(stanza, (Message, Presence, Iq)):
|
||||
@@ -299,7 +313,13 @@ class XEP_0198(BasePlugin):
|
||||
|
||||
def _handle_outgoing(self, stanza):
|
||||
"""Store outgoing stanzas in a queue to be acked."""
|
||||
if not self.enabled:
|
||||
from slixmpp.plugins.xep_0198 import stanza as st
|
||||
if isinstance(stanza, (st.Enable, st.Resume)):
|
||||
self.enabled_out = True
|
||||
self.unacked_queue.clear()
|
||||
log.debug("enabling outgoing SM: %s" % stanza)
|
||||
|
||||
if not self.enabled_out:
|
||||
return stanza
|
||||
|
||||
if isinstance(stanza, (Message, Presence, Iq)):
|
||||
|
||||
@@ -95,7 +95,10 @@ class XEP_0199(BasePlugin):
|
||||
self.timeout = timeout
|
||||
|
||||
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.interval,
|
||||
handler,
|
||||
@@ -109,9 +112,9 @@ class XEP_0199(BasePlugin):
|
||||
try:
|
||||
rtt = await self.ping(self.xmpp.boundjid.host, timeout=self.timeout)
|
||||
except IqTimeout:
|
||||
log.debug("Did not receive ping back in time." + \
|
||||
log.debug("Did not receive ping back in time. " + \
|
||||
"Requesting Reconnect.")
|
||||
self.xmpp.reconnect()
|
||||
self.xmpp.reconnect(0.0, "Ping timeout after %ds" % self.timeout)
|
||||
else:
|
||||
log.debug('Keepalive RTT: %s' % rtt)
|
||||
|
||||
|
||||
@@ -123,5 +123,5 @@ class EntityTime(ElementBase):
|
||||
if not isinstance(value, dt.datetime):
|
||||
date = xep_0082.parse(value)
|
||||
date = date.astimezone(tzutc())
|
||||
value = xep_0082.format_datetime(date)[:-1]
|
||||
value = xep_0082.format_datetime(date)
|
||||
self._set_sub_text('utc', value)
|
||||
|
||||
@@ -40,7 +40,7 @@ class XEP_0202(BasePlugin):
|
||||
}
|
||||
|
||||
def plugin_init(self):
|
||||
"""Start the XEP-0203 plugin."""
|
||||
"""Start the XEP-0202 plugin."""
|
||||
|
||||
if not self.local_time:
|
||||
def default_local_time(jid):
|
||||
@@ -50,7 +50,7 @@ class XEP_0202(BasePlugin):
|
||||
|
||||
self.xmpp.register_handler(
|
||||
Callback('Entity Time',
|
||||
StanzaPath('iq/entity_time'),
|
||||
StanzaPath('iq@type=get/entity_time'),
|
||||
self._handle_time_request))
|
||||
register_stanza_plugin(Iq, stanza.EntityTime)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class XEP_0223(BasePlugin):
|
||||
dependencies = {'xep_0163', 'xep_0060', 'xep_0004'}
|
||||
|
||||
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):
|
||||
"""
|
||||
|
||||
@@ -31,11 +31,11 @@ class XEP_0279(BasePlugin):
|
||||
def plugin_end(self):
|
||||
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0')
|
||||
|
||||
def check_ip(self, ifrom=None, block=True, timeout=None, callback=None,
|
||||
def check_ip(self, ifrom=None, timeout=None, callback=None,
|
||||
timeout_callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
iq['from'] = ifrom
|
||||
iq.enable('ip_check')
|
||||
return iq.send(block=block, timeout=timeout, callback=callback,
|
||||
return iq.send(timeout=timeout, callback=callback,
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
@@ -3,16 +3,18 @@
|
||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||
This file is part of Slixmpp.
|
||||
|
||||
See the file LICENSE for copying permissio
|
||||
See the file LICENSE for copying permission
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import slixmpp
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Callable, Optional, Awaitable
|
||||
|
||||
from slixmpp import JID
|
||||
from slixmpp.stanza import Message, Iq
|
||||
from slixmpp.exceptions import XMPPError
|
||||
from slixmpp.xmlstream.handler import Collector
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
from slixmpp.xmlstream.matcher import MatchXMLMask
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.xep_0313 import stanza
|
||||
@@ -41,8 +43,32 @@ class XEP_0313(BasePlugin):
|
||||
register_stanza_plugin(stanza.MAM, self.xmpp['xep_0059'].stanza.Set)
|
||||
register_stanza_plugin(stanza.Fin, self.xmpp['xep_0059'].stanza.Set)
|
||||
|
||||
def retrieve(self, jid=None, start=None, end=None, with_jid=None, ifrom=None,
|
||||
timeout=None, callback=None, iterator=False, rsm=None):
|
||||
def retrieve(
|
||||
self,
|
||||
jid: Optional[JID] = None,
|
||||
start: Optional[datetime] = None,
|
||||
end: Optional[datetime] = None,
|
||||
with_jid: Optional[JID] = None,
|
||||
ifrom: Optional[JID] = None,
|
||||
reverse: bool = False,
|
||||
timeout: int = None,
|
||||
callback: Callable[[Iq], None] = None,
|
||||
iterator: bool = False,
|
||||
rsm: Optional[Dict[str, Any]] = None
|
||||
) -> Awaitable:
|
||||
"""
|
||||
Send a MAM query and retrieve the results.
|
||||
|
||||
:param JID jid: Entity holding the MAM records
|
||||
:param datetime start,end: MAM query temporal boundaries
|
||||
:param JID with_jid: Filter results on this JID
|
||||
:param JID ifrom: To change the from address of the query
|
||||
:param bool reverse: Get the results in reverse order
|
||||
:param int timeout: IQ timeout
|
||||
:param func callback: Custom callback for handling results
|
||||
:param bool iterator: Use RSM and iterate over a paginated query
|
||||
:param dict rsm: RSM custom options
|
||||
"""
|
||||
iq = self.xmpp.Iq()
|
||||
query_id = iq['id']
|
||||
|
||||
@@ -53,35 +79,48 @@ class XEP_0313(BasePlugin):
|
||||
iq['mam']['start'] = start
|
||||
iq['mam']['end'] = end
|
||||
iq['mam']['with'] = with_jid
|
||||
amount = 10
|
||||
if rsm:
|
||||
for key, value in rsm.items():
|
||||
iq['mam']['rsm'][key] = str(value)
|
||||
|
||||
if key == 'max':
|
||||
amount = value
|
||||
cb_data = {}
|
||||
def pre_cb(query):
|
||||
|
||||
stanza_mask = self.xmpp.Message()
|
||||
stanza_mask.xml.remove(stanza_mask.xml.find('{urn:xmpp:sid:0}origin-id'))
|
||||
del stanza_mask['id']
|
||||
del stanza_mask['lang']
|
||||
stanza_mask['from'] = jid
|
||||
stanza_mask['mam_result']['queryid'] = query_id
|
||||
xml_mask = str(stanza_mask)
|
||||
|
||||
def pre_cb(query: Iq) -> None:
|
||||
stanza_mask['mam_result']['queryid'] = query['id']
|
||||
xml_mask = str(stanza_mask)
|
||||
query['mam']['queryid'] = query['id']
|
||||
collector = Collector(
|
||||
'MAM_Results_%s' % query_id,
|
||||
StanzaPath('message/mam_result@queryid=%s' % query['id']))
|
||||
MatchXMLMask(xml_mask))
|
||||
self.xmpp.register_handler(collector)
|
||||
cb_data['collector'] = collector
|
||||
|
||||
def post_cb(result):
|
||||
def post_cb(result: Iq) -> None:
|
||||
results = cb_data['collector'].stop()
|
||||
if result['type'] == 'result':
|
||||
result['mam']['results'] = results
|
||||
|
||||
if iterator:
|
||||
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results',
|
||||
recv_interface='mam_fin',
|
||||
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results', amount=amount,
|
||||
reverse=reverse, recv_interface='mam_fin',
|
||||
pre_cb=pre_cb, post_cb=post_cb)
|
||||
|
||||
collector = Collector(
|
||||
'MAM_Results_%s' % query_id,
|
||||
StanzaPath('message/mam_result@queryid=%s' % query_id))
|
||||
MatchXMLMask(xml_mask))
|
||||
self.xmpp.register_handler(collector)
|
||||
|
||||
def wrapped_cb(iq):
|
||||
def wrapped_cb(iq: Iq) -> None:
|
||||
results = collector.stop()
|
||||
if iq['type'] == 'result':
|
||||
iq['mam']['results'] = results
|
||||
@@ -90,8 +129,15 @@ class XEP_0313(BasePlugin):
|
||||
|
||||
return iq.send(timeout=timeout, callback=wrapped_cb)
|
||||
|
||||
def get_preferences(self, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'get'
|
||||
query_id = iq['id']
|
||||
iq['mam_prefs']['query_id'] = query_id
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def set_preferences(self, jid=None, default=None, always=None, never=None,
|
||||
ifrom=None, block=True, timeout=None, callback=None):
|
||||
ifrom=None, timeout=None, callback=None):
|
||||
iq = self.xmpp.Iq()
|
||||
iq['type'] = 'set'
|
||||
iq['to'] = jid
|
||||
@@ -99,7 +145,7 @@ class XEP_0313(BasePlugin):
|
||||
iq['mam_prefs']['default'] = default
|
||||
iq['mam_prefs']['always'] = always
|
||||
iq['mam_prefs']['never'] = never
|
||||
return iq.send(block=block, timeout=timeout, callback=callback)
|
||||
return iq.send(timeout=timeout, callback=callback)
|
||||
|
||||
def get_configuration_commands(self, jid, **kwargs):
|
||||
return self.xmpp['xep_0030'].get_items(
|
||||
|
||||
@@ -516,7 +516,7 @@ class Field(ElementBase):
|
||||
:param value: string
|
||||
"""
|
||||
|
||||
pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$")
|
||||
pattern = re.compile(r"^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$")
|
||||
if pattern.match(value) is not None:
|
||||
self.xml.stringIds = value
|
||||
else:
|
||||
|
||||
@@ -114,7 +114,6 @@ class XEP_0332(BasePlugin):
|
||||
iq['http-req']['data'] = data
|
||||
return iq.send(
|
||||
timeout=kwargs.get('timeout', None),
|
||||
block=kwargs.get('block', True),
|
||||
callback=kwargs.get('callback', None),
|
||||
timeout_callback=kwargs.get('timeout_callback', None)
|
||||
)
|
||||
@@ -135,7 +134,6 @@ class XEP_0332(BasePlugin):
|
||||
iq['http-resp']['data'] = data
|
||||
return iq.send(
|
||||
timeout=kwargs.get('timeout', None),
|
||||
block=kwargs.get('block', True),
|
||||
callback=kwargs.get('callback', None),
|
||||
timeout_callback=kwargs.get('timeout_callback', None)
|
||||
)
|
||||
@@ -153,7 +151,6 @@ class XEP_0332(BasePlugin):
|
||||
iq['id'] = kwargs["id"]
|
||||
return iq.send(
|
||||
timeout=kwargs.get('timeout', None),
|
||||
block=kwargs.get('block', True),
|
||||
callback=kwargs.get('callback', None),
|
||||
timeout_callback=kwargs.get('timeout_callback', None)
|
||||
)
|
||||
|
||||
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 = ''
|
||||
@@ -6,7 +6,6 @@
|
||||
See the file LICENSE for copying permission.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os.path
|
||||
|
||||
@@ -31,6 +30,10 @@ class UploadServiceNotFound(FileUploadError):
|
||||
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+ '''
|
||||
|
||||
@@ -68,12 +71,18 @@ class XEP_0363(BasePlugin):
|
||||
def _handle_request(self, iq):
|
||||
self.xmpp.event('http_upload_request', iq)
|
||||
|
||||
async def find_upload_service(self, timeout=None):
|
||||
results = await self.xmpp['xep_0030'].get_info_from_domain()
|
||||
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,
|
||||
@@ -90,11 +99,12 @@ class XEP_0363(BasePlugin):
|
||||
timeout_callback=timeout_callback)
|
||||
|
||||
async def upload_file(self, filename, size=None, content_type=None, *,
|
||||
input_file=None, ifrom=None, timeout=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(timeout=timeout)
|
||||
info_iq = await self.find_upload_service(
|
||||
domain=domain, timeout=timeout)
|
||||
if info_iq is None:
|
||||
raise UploadServiceNotFound()
|
||||
self.upload_service = info_iq['from']
|
||||
@@ -125,7 +135,8 @@ class XEP_0363(BasePlugin):
|
||||
|
||||
basename = os.path.basename(filename)
|
||||
slot_iq = await self.request_slot(self.upload_service, basename, size,
|
||||
content_type, ifrom, timeout)
|
||||
content_type, ifrom, timeout,
|
||||
timeout_callback=timeout_callback)
|
||||
slot = slot_iq['http_upload_slot']
|
||||
|
||||
headers = {
|
||||
@@ -141,6 +152,8 @@ class XEP_0363(BasePlugin):
|
||||
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']
|
||||
|
||||
@@ -49,15 +49,17 @@ class XEP_0380(BasePlugin):
|
||||
|
||||
register_stanza_plugin(Message, Encryption)
|
||||
|
||||
def plugin_end(self):
|
||||
self.xmpp.remove_handler('Chat State')
|
||||
|
||||
def session_bind(self, jid):
|
||||
self.xmpp.plugin['xep_0030'].add_feature(Encryption.namespace)
|
||||
|
||||
def has_eme(self, msg):
|
||||
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):
|
||||
eme = msg['eme']
|
||||
namespace = eme['namespace']
|
||||
|
||||
@@ -187,6 +187,10 @@ class Iq(RootStanza):
|
||||
|
||||
future = asyncio.Future()
|
||||
|
||||
# Prevents handlers from existing forever.
|
||||
if timeout is None:
|
||||
timeout = 120
|
||||
|
||||
def callback_success(result):
|
||||
type_ = result['type']
|
||||
if type_ == 'result':
|
||||
|
||||
@@ -10,6 +10,9 @@ from slixmpp.stanza.rootstanza import RootStanza
|
||||
from slixmpp.xmlstream import StanzaBase, ET
|
||||
|
||||
|
||||
ORIGIN_NAME = '{urn:xmpp:sid:0}origin-id'
|
||||
|
||||
|
||||
class Message(RootStanza):
|
||||
|
||||
"""
|
||||
@@ -63,6 +66,8 @@ class Message(RootStanza):
|
||||
if self['id'] == '':
|
||||
if self.stream is not None and self.stream.use_message_ids:
|
||||
self['id'] = self.stream.new_id()
|
||||
else:
|
||||
del self['origin_id']
|
||||
|
||||
def get_type(self):
|
||||
"""
|
||||
@@ -76,6 +81,43 @@ class Message(RootStanza):
|
||||
"""
|
||||
return self._get_attr('type', 'normal')
|
||||
|
||||
def get_id(self):
|
||||
return self._get_attr('id') or ''
|
||||
|
||||
def get_origin_id(self):
|
||||
sub = self.xml.find(ORIGIN_NAME)
|
||||
if sub is not None:
|
||||
return sub.attrib.get('id') or ''
|
||||
return ''
|
||||
|
||||
def _set_ids(self, value) -> None:
|
||||
if value is None or value == '':
|
||||
return None
|
||||
|
||||
self.xml.attrib['id'] = value
|
||||
|
||||
if not self.stream.use_origin_id:
|
||||
return None
|
||||
|
||||
sub = self.xml.find(ORIGIN_NAME)
|
||||
if sub is not None:
|
||||
sub.attrib['id'] = value
|
||||
else:
|
||||
sub = ET.Element(ORIGIN_NAME)
|
||||
sub.attrib['id'] = value
|
||||
self.xml.append(sub)
|
||||
|
||||
def set_id(self, value):
|
||||
return self._set_ids(value)
|
||||
|
||||
def set_origin_id(self, value: str):
|
||||
return self._set_ids(value)
|
||||
|
||||
def del_origin_id(self):
|
||||
sub = self.xml.find(ORIGIN_NAME)
|
||||
if sub is not None:
|
||||
self.xml.remove(sub)
|
||||
|
||||
def get_parent_thread(self):
|
||||
"""Return the message thread's parent thread.
|
||||
|
||||
@@ -140,6 +182,8 @@ class Message(RootStanza):
|
||||
new_message['parent_thread'] = self['parent_thread']
|
||||
|
||||
del new_message['id']
|
||||
if self.stream is not None and self.stream.use_message_ids:
|
||||
new_message['id'] = self.stream.new_id()
|
||||
|
||||
if body is not None:
|
||||
new_message['body'] = body
|
||||
|
||||
@@ -90,10 +90,10 @@ class Presence(RootStanza):
|
||||
def get_type(self):
|
||||
"""
|
||||
Return the value of the <presence> stanza's type attribute, or
|
||||
the value of the <show> element.
|
||||
the value of the <show> element if valid.
|
||||
"""
|
||||
out = self._get_attr('type')
|
||||
if not out:
|
||||
if not out and self['show'] in self.showtypes:
|
||||
out = self['show']
|
||||
if not out or out is None:
|
||||
out = 'available'
|
||||
|
||||
@@ -222,7 +222,7 @@ class SlixTest(unittest.TestCase):
|
||||
if Matcher is None:
|
||||
raise ValueError("Unknown matching method.")
|
||||
test = Matcher(criteria)
|
||||
self.failUnless(test.match(stanza),
|
||||
self.assertTrue(test.match(stanza),
|
||||
"Stanza did not match using %s method:\n" % method + \
|
||||
"Criteria:\n%s\n" % str(criteria) + \
|
||||
"Stanza:\n%s" % str(stanza))
|
||||
@@ -280,7 +280,7 @@ class SlixTest(unittest.TestCase):
|
||||
debug += "Generated stanza:\n%s\n" % highlight(tostring(stanza2.xml))
|
||||
result = self.compare(xml, stanza.xml, stanza2.xml)
|
||||
|
||||
self.failUnless(result, debug)
|
||||
self.assertTrue(result, debug)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Methods for simulating stanza streams.
|
||||
@@ -340,11 +340,19 @@ class SlixTest(unittest.TestCase):
|
||||
self.xmpp.default_lang = None
|
||||
self.xmpp.peer_default_lang = None
|
||||
|
||||
def new_id():
|
||||
self.xmpp._id += 1
|
||||
return str(self.xmpp._id)
|
||||
|
||||
self.xmpp._id = 0
|
||||
self.xmpp.new_id = new_id
|
||||
|
||||
# Must have the stream header ready for xmpp.process() to work.
|
||||
if not header:
|
||||
header = self.xmpp.stream_header
|
||||
|
||||
self.xmpp.data_received(header)
|
||||
self.wait_for_send_queue()
|
||||
|
||||
if skip:
|
||||
self.xmpp.socket.next_sent()
|
||||
@@ -361,6 +369,7 @@ class SlixTest(unittest.TestCase):
|
||||
# Some plugins require messages to have ID values. Set
|
||||
# this to True in tests related to those plugins.
|
||||
self.xmpp.use_message_ids = False
|
||||
self.xmpp.use_presence_ids = False
|
||||
|
||||
def make_header(self, sto='',
|
||||
sfrom='',
|
||||
@@ -487,7 +496,7 @@ class SlixTest(unittest.TestCase):
|
||||
recv_xml.clear()
|
||||
recv_xml.attrib = attrib
|
||||
|
||||
self.failUnless(
|
||||
self.assertTrue(
|
||||
self.compare(xml, recv_xml),
|
||||
"Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % (
|
||||
'%s %s' % (xml.tag, xml.attrib),
|
||||
@@ -543,7 +552,7 @@ class SlixTest(unittest.TestCase):
|
||||
xml = self.parse_xml(header2)
|
||||
sent_xml = self.parse_xml(sent_header2)
|
||||
|
||||
self.failUnless(
|
||||
self.assertTrue(
|
||||
self.compare(xml, sent_xml),
|
||||
"Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % (
|
||||
header, sent_header))
|
||||
@@ -557,12 +566,12 @@ class SlixTest(unittest.TestCase):
|
||||
if sent_data is None:
|
||||
self.fail("No stanza was sent.")
|
||||
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" % (
|
||||
highlight(tostring(xml)), highlight(tostring(sent_xml))))
|
||||
elif method == 'mask':
|
||||
matcher = MatchXMLMask(xml)
|
||||
self.failUnless(matcher.match(sent_xml),
|
||||
self.assertTrue(matcher.match(sent_xml),
|
||||
"Stanza did not match using %s method:\n" % method + \
|
||||
"Criteria:\n%s\n" % highlight(tostring(xml)) + \
|
||||
"Stanza:\n%s" % highlight(tostring(sent_xml)))
|
||||
@@ -591,6 +600,7 @@ class SlixTest(unittest.TestCase):
|
||||
'id', 'stanzapath', 'xpath', and 'mask'.
|
||||
Defaults to the value of self.match_method.
|
||||
"""
|
||||
self.wait_for_send_queue()
|
||||
sent = self.xmpp.socket.next_sent(timeout)
|
||||
if data is None and sent is None:
|
||||
return
|
||||
@@ -607,6 +617,14 @@ class SlixTest(unittest.TestCase):
|
||||
defaults=defaults,
|
||||
use_values=use_values)
|
||||
|
||||
def wait_for_send_queue(self):
|
||||
loop = asyncio.get_event_loop()
|
||||
future = asyncio.ensure_future(self.xmpp.run_filters(), loop=loop)
|
||||
queue = self.xmpp.waiting_queue
|
||||
print(queue)
|
||||
loop.run_until_complete(queue.join())
|
||||
future.cancel()
|
||||
|
||||
def stream_close(self):
|
||||
"""
|
||||
Disconnect the dummy XMPP client.
|
||||
|
||||
2
slixmpp/thirdparty/mini_dateutil.py
vendored
2
slixmpp/thirdparty/mini_dateutil.py
vendored
@@ -160,7 +160,7 @@ except:
|
||||
return _fixed_offset_tzs[offsetmins]
|
||||
|
||||
|
||||
_iso8601_parser = re.compile("""
|
||||
_iso8601_parser = re.compile(r"""
|
||||
^
|
||||
(?P<year> [0-9]{4})?(?P<ymdsep>-?)?
|
||||
(?P<month>[0-9]{2})?(?P=ymdsep)?
|
||||
|
||||
@@ -18,6 +18,9 @@ class Cache:
|
||||
def store(self, key, value):
|
||||
raise NotImplementedError
|
||||
|
||||
def remove(self, key):
|
||||
raise NotImplemented
|
||||
|
||||
class PerJidCache:
|
||||
def retrieve_by_jid(self, jid, key):
|
||||
raise NotImplementedError
|
||||
@@ -25,6 +28,9 @@ class PerJidCache:
|
||||
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 = {}
|
||||
@@ -36,6 +42,11 @@ class MemoryCache(Cache):
|
||||
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 = {}
|
||||
@@ -51,6 +62,12 @@ class MemoryPerJidCache(PerJidCache):
|
||||
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
|
||||
@@ -67,7 +84,10 @@ class FileSystemStorage:
|
||||
log.debug('%s not present in cache', key)
|
||||
except OSError:
|
||||
log.debug('Failed to read %s from cache:', key, exc_info=True)
|
||||
return None
|
||||
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('/', '_'))
|
||||
@@ -79,6 +99,17 @@ class FileSystemStorage:
|
||||
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):
|
||||
@@ -91,6 +122,9 @@ class FileSystemCache(Cache, FileSystemStorage):
|
||||
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)
|
||||
@@ -103,3 +137,7 @@ class FileSystemPerJidCache(PerJidCache, FileSystemStorage):
|
||||
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):
|
||||
authzid = self.credentials['authzid']
|
||||
if not authzid:
|
||||
authzid = 'xmpp@%s' % self.credentials['service-name']
|
||||
authzid = 'xmpp@' + self.credentials['service-name'].decode()
|
||||
|
||||
_, self.gss = kerberos.authGSSClientInit(authzid)
|
||||
self.step = 0
|
||||
|
||||
def process(self, challenge=b''):
|
||||
b64_challenge = b64encode(challenge)
|
||||
b64_challenge = b64encode(challenge).decode('ascii')
|
||||
try:
|
||||
if self.step == 0:
|
||||
result = kerberos.authGSSClientStep(self.gss, b64_challenge)
|
||||
@@ -536,7 +536,7 @@ else:
|
||||
|
||||
kerberos.authGSSClientUnwrap(self.gss, b64_challenge)
|
||||
resp = kerberos.authGSSClientResponse(self.gss)
|
||||
kerberos.authGSSClientWrap(self.gss, resp, username)
|
||||
kerberos.authGSSClientWrap(self.gss, resp, username.decode())
|
||||
|
||||
resp = kerberos.authGSSClientResponse(self.gss)
|
||||
except kerberos.GSSError as e:
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
# We don't want to have to import the entire library
|
||||
# just to get the version info for setup.py
|
||||
|
||||
__version__ = '1.4.0'
|
||||
__version_info__ = (1, 4, 0)
|
||||
__version__ = '1.5.0'
|
||||
__version_info__ = (1, 5, 0)
|
||||
|
||||
@@ -17,7 +17,7 @@ from __future__ import with_statement, unicode_literals
|
||||
import copy
|
||||
import logging
|
||||
import weakref
|
||||
from xml.etree import cElementTree as ET
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
from slixmpp.xmlstream import JID
|
||||
from slixmpp.xmlstream.tostring import tostring
|
||||
@@ -177,8 +177,9 @@ def fix_ns(xpath, split=False, propagate_ns=True, default_ns=''):
|
||||
if '}' in ns_block:
|
||||
# Apply the found namespace to following elements
|
||||
# that do not have namespaces.
|
||||
namespace = ns_block.split('}')[0]
|
||||
elements = ns_block.split('}')[1].split('/')
|
||||
ns_block_split = ns_block.split('}')
|
||||
namespace = ns_block_split[0]
|
||||
elements = ns_block_split[1].split('/')
|
||||
else:
|
||||
# Apply the stanza's namespace to the following
|
||||
# elements since no namespace was provided.
|
||||
@@ -202,7 +203,7 @@ class ElementBase(object):
|
||||
|
||||
"""
|
||||
The core of Slixmpp's stanza XML manipulation and handling is provided
|
||||
by ElementBase. ElementBase wraps XML cElementTree objects and enables
|
||||
by ElementBase. ElementBase wraps XML ElementTree objects and enables
|
||||
access to the XML contents through dictionary syntax, similar in style
|
||||
to the Ruby XMPP library Blather's stanza implementation.
|
||||
|
||||
@@ -386,7 +387,7 @@ class ElementBase(object):
|
||||
self._index = 0
|
||||
|
||||
#: The underlying XML object for the stanza. It is a standard
|
||||
#: :class:`xml.etree.cElementTree` object.
|
||||
#: :class:`xml.etree.ElementTree` object.
|
||||
self.xml = xml
|
||||
|
||||
#: An ordered dictionary of plugin stanzas, mapped by their
|
||||
@@ -1030,14 +1031,19 @@ class ElementBase(object):
|
||||
if not lang:
|
||||
lang = default_lang
|
||||
|
||||
parent = self.xml
|
||||
for level, _ in enumerate(path):
|
||||
# Generate the paths to the target elements and their parent.
|
||||
element_path = "/".join(path[:len(path) - level])
|
||||
parent_path = "/".join(path[:len(path) - level - 1])
|
||||
|
||||
elements = self.xml.findall(element_path)
|
||||
parent = self.xml.find(parent_path)
|
||||
|
||||
|
||||
if parent_path == '':
|
||||
parent_path = None
|
||||
if parent_path is not None:
|
||||
parent = self.xml.find(parent_path)
|
||||
|
||||
if elements:
|
||||
if parent is None:
|
||||
parent = self.xml
|
||||
@@ -1291,15 +1297,6 @@ class ElementBase(object):
|
||||
|
||||
def __bool__(self):
|
||||
"""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
|
||||
|
||||
@@ -1382,14 +1379,6 @@ class StanzaBase(ElementBase):
|
||||
#: The default XMPP client namespace
|
||||
namespace = 'jabber:client'
|
||||
|
||||
#: There is a small set of attributes which apply to all XMPP stanzas:
|
||||
#: the stanza type, the to and from JIDs, the stanza ID, and, especially
|
||||
#: in the case of an Iq stanza, a payload.
|
||||
interfaces = {'type', 'to', 'from', 'id', 'payload'}
|
||||
|
||||
#: A basic set of allowed values for the ``'type'`` interface.
|
||||
types = {'get', 'set', 'error', None, 'unavailable', 'normal', 'chat'}
|
||||
|
||||
def __init__(self, stream=None, xml=None, stype=None,
|
||||
sto=None, sfrom=None, sid=None, parent=None):
|
||||
self.stream = stream
|
||||
|
||||
@@ -45,11 +45,12 @@ def tostring(xml=None, xmlns='', stream=None, outbuffer='',
|
||||
output = [outbuffer]
|
||||
|
||||
# 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.
|
||||
if '}' in xml.tag:
|
||||
tag_xmlns = xml.tag.split('}', 1)[0][1:]
|
||||
tag_xmlns = tag_split[0][1:]
|
||||
else:
|
||||
tag_xmlns = ''
|
||||
|
||||
@@ -82,8 +83,9 @@ def tostring(xml=None, xmlns='', stream=None, outbuffer='',
|
||||
if '}' not in attrib:
|
||||
output.append(' %s="%s"' % (attrib, value))
|
||||
else:
|
||||
attrib_ns = attrib.split('}')[0][1:]
|
||||
attrib = attrib.split('}')[1]
|
||||
attrib_split = attrib.split('}')
|
||||
attrib_ns = attrib_split[0][1:]
|
||||
attrib = attrib_split[1]
|
||||
if attrib_ns == XML_NS:
|
||||
output.append(' xml:%s="%s"' % (attrib, value))
|
||||
elif stream and attrib_ns in stream.namespace_map:
|
||||
@@ -144,10 +146,7 @@ def escape(text, use_cdata=False):
|
||||
'"': '"'}
|
||||
|
||||
if not use_cdata:
|
||||
text = list(text)
|
||||
for i, c in enumerate(text):
|
||||
text[i] = escapes.get(c, c)
|
||||
return ''.join(text)
|
||||
return ''.join(escapes.get(c, c) for c in text)
|
||||
else:
|
||||
escape_needed = False
|
||||
for c in text:
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
:license: MIT, see LICENSE for more details
|
||||
"""
|
||||
|
||||
from typing import Optional, Set, Callable
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import socket as Socket
|
||||
@@ -19,6 +21,8 @@ import ssl
|
||||
import weakref
|
||||
import uuid
|
||||
|
||||
from asyncio import iscoroutinefunction, wait
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from slixmpp.xmlstream.asyncio import asyncio
|
||||
@@ -30,6 +34,10 @@ from slixmpp.xmlstream.resolver import resolve, default_resolver
|
||||
RESPONSE_TIMEOUT = 30
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
class ContinueQueue(Exception):
|
||||
"""
|
||||
Exception raised in the send queue to "continue" from within an inner loop
|
||||
"""
|
||||
|
||||
class NotConnectedError(Exception):
|
||||
"""
|
||||
@@ -81,6 +89,8 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
self.force_starttls = None
|
||||
self.disable_starttls = None
|
||||
|
||||
self.waiting_queue = asyncio.Queue()
|
||||
|
||||
# A dict of {name: handle}
|
||||
self.scheduled_events = {}
|
||||
|
||||
@@ -199,11 +209,6 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
self.__event_handlers = {}
|
||||
self.__filters = {'in': [], 'out': [], 'out_sync': []}
|
||||
|
||||
self._id = 0
|
||||
|
||||
#: We use an ID prefix to ensure that all ID values are unique.
|
||||
self._id_prefix = '%s-' % uuid.uuid4()
|
||||
|
||||
# Current connection attempt (Future)
|
||||
self._current_connection_attempt = None
|
||||
|
||||
@@ -215,6 +220,9 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
#: ``_xmpp-client._tcp`` service.
|
||||
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.
|
||||
self.disconnected = asyncio.Future()
|
||||
|
||||
@@ -238,12 +246,7 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
ID values. Using this method ensures that all new ID values
|
||||
are unique in this stream.
|
||||
"""
|
||||
self._id += 1
|
||||
return self.get_id()
|
||||
|
||||
def get_id(self):
|
||||
"""Return the current unique stream ID in hexadecimal form."""
|
||||
return "%s%X" % (self._id_prefix, self._id)
|
||||
return uuid.uuid4().hex
|
||||
|
||||
def connect(self, host='', port=0, use_ssl=False,
|
||||
force_starttls=True, disable_starttls=False):
|
||||
@@ -268,7 +271,13 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
localhost
|
||||
|
||||
"""
|
||||
asyncio.ensure_future(
|
||||
self.run_filters(),
|
||||
loop=self.loop,
|
||||
)
|
||||
self.disconnect_reason = None
|
||||
self.cancel_connection_attempt()
|
||||
self.connect_loop_wait = 0
|
||||
if host and port:
|
||||
self.address = (host, int(port))
|
||||
try:
|
||||
@@ -285,11 +294,18 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
self.disable_starttls = disable_starttls
|
||||
|
||||
self.event("connecting")
|
||||
self._current_connection_attempt = asyncio.ensure_future(self._connect_routine())
|
||||
self._current_connection_attempt = asyncio.ensure_future(
|
||||
self._connect_routine(),
|
||||
loop=self.loop,
|
||||
)
|
||||
|
||||
async def _connect_routine(self):
|
||||
self.event_when_connected = "connected"
|
||||
|
||||
if self.connect_loop_wait > 0:
|
||||
self.event('reconnect_delay', self.connect_loop_wait)
|
||||
await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
|
||||
|
||||
record = await self.pick_dns_answer(self.default_domain)
|
||||
if record is not None:
|
||||
host, address, dns_port = record
|
||||
@@ -306,7 +322,8 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
else:
|
||||
ssl_context = None
|
||||
|
||||
await asyncio.sleep(self.connect_loop_wait)
|
||||
if self._current_connection_attempt is None:
|
||||
return
|
||||
try:
|
||||
await self.loop.create_connection(lambda: self,
|
||||
self.address[0],
|
||||
@@ -320,8 +337,13 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
except OSError as e:
|
||||
log.debug('Connection failed: %s', 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._current_connection_attempt = asyncio.ensure_future(self._connect_routine())
|
||||
self._current_connection_attempt = asyncio.ensure_future(
|
||||
self._connect_routine(),
|
||||
loop=self.loop,
|
||||
)
|
||||
|
||||
def process(self, *, forever=True, timeout=None):
|
||||
"""Process all the available XMPP events (receiving or sending data on the
|
||||
@@ -336,10 +358,10 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
else:
|
||||
self.loop.run_until_complete(self.disconnected)
|
||||
else:
|
||||
tasks = [asyncio.sleep(timeout)]
|
||||
tasks = [asyncio.sleep(timeout, loop=self.loop)]
|
||||
if not forever:
|
||||
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):
|
||||
"""init the XML parser. The parser must always be reset for each new
|
||||
@@ -358,6 +380,7 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
"ssl_object",
|
||||
default=self.transport.get_extra_info("socket")
|
||||
)
|
||||
self._current_connection_attempt = None
|
||||
self.init_parser()
|
||||
self.send_raw(self.stream_header)
|
||||
self.dns_answers = None
|
||||
@@ -392,7 +415,9 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
if self.xml_depth == 0:
|
||||
# The stream's root element has closed,
|
||||
# terminating the stream.
|
||||
self.end_session_on_disconnect = True
|
||||
log.debug("End of stream received")
|
||||
self.disconnect_reason = "End of stream"
|
||||
self.abort()
|
||||
elif self.xml_depth == 1:
|
||||
# A stanza is an XML element that is a direct child of
|
||||
@@ -414,6 +439,9 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
self.send(error)
|
||||
self.disconnect()
|
||||
|
||||
def is_connecting(self):
|
||||
return self._current_connection_attempt is not None
|
||||
|
||||
def is_connected(self):
|
||||
return self.transport is not None
|
||||
|
||||
@@ -427,7 +455,7 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
closure of the TCP connection
|
||||
"""
|
||||
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:
|
||||
self.event('session_end')
|
||||
# All these objects are associated with one TCP connection. Since
|
||||
@@ -447,24 +475,35 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
self._current_connection_attempt.cancel()
|
||||
self._current_connection_attempt = None
|
||||
|
||||
def disconnect(self, wait=2.0):
|
||||
def disconnect(self, wait: float = 2.0, reason: Optional[str] = None) -> None:
|
||||
"""Close the XML stream and wait for an acknowldgement from the server for
|
||||
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 server, or when the server
|
||||
successfully responds with a closure of its own stream, abort() is
|
||||
called. If wait is 0.0, this is almost equivalent to calling abort()
|
||||
directly.
|
||||
called. If wait is 0.0, this will call abort() directly without closing
|
||||
the stream.
|
||||
|
||||
Does nothing if we are not connected.
|
||||
|
||||
:param wait: Time to wait for a response from the server.
|
||||
|
||||
"""
|
||||
# Compat: docs/getting_started/sendlogout.rst has been promoting
|
||||
# `disconnect(wait=True)` for ages. This doesn't mean anything to the
|
||||
# schedule call below. It would fortunately be converted to `1` later
|
||||
# down the call chain. Praise the implicit casts lord.
|
||||
if wait == True:
|
||||
wait = 2.0
|
||||
|
||||
self.disconnect_reason = reason
|
||||
self.cancel_connection_attempt()
|
||||
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.abort, repeat=False)
|
||||
else:
|
||||
self.event("disconnected", reason)
|
||||
|
||||
def abort(self):
|
||||
"""
|
||||
@@ -477,14 +516,15 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
self.event("killed")
|
||||
self.disconnected.set_result(True)
|
||||
self.disconnected = asyncio.Future()
|
||||
self.event("disconnected", self.disconnect_reason)
|
||||
|
||||
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
|
||||
when the server acknowledgement is received), call connect()
|
||||
"""
|
||||
log.debug("reconnecting...")
|
||||
self.disconnect(wait)
|
||||
self.add_event_handler('disconnected', self.connect, disposable=True)
|
||||
self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True)
|
||||
self.disconnect(wait, reason)
|
||||
|
||||
def configure_socket(self):
|
||||
"""Set timeout and other options for self.socket.
|
||||
@@ -772,7 +812,7 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
|
||||
# If the callback is a coroutine, schedule it instead of
|
||||
# running it directly
|
||||
if asyncio.iscoroutinefunction(handler_callback):
|
||||
if iscoroutinefunction(handler_callback):
|
||||
async def handler_callback_routine(cb):
|
||||
try:
|
||||
await cb(data)
|
||||
@@ -781,7 +821,10 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
old_exception(e)
|
||||
else:
|
||||
self.exception(e)
|
||||
asyncio.ensure_future(handler_callback_routine(handler_callback))
|
||||
asyncio.ensure_future(
|
||||
handler_callback_routine(handler_callback),
|
||||
loop=self.loop,
|
||||
)
|
||||
else:
|
||||
try:
|
||||
handler_callback(data)
|
||||
@@ -868,11 +911,93 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
"""
|
||||
return xml
|
||||
|
||||
async def _continue_slow_send(
|
||||
self,
|
||||
task: asyncio.Task,
|
||||
already_used: Set[Callable[[ElementBase], Optional[StanzaBase]]]
|
||||
) -> None:
|
||||
"""
|
||||
Used when an item in the send queue has taken too long to process.
|
||||
|
||||
This is away from the send queue and can take as much time as needed.
|
||||
:param asyncio.Task task: the Task wrapping the coroutine
|
||||
:param set already_used: Filters already used on this outgoing stanza
|
||||
"""
|
||||
data = await task
|
||||
for filter in self.__filters['out']:
|
||||
if filter in already_used:
|
||||
continue
|
||||
if iscoroutinefunction(filter):
|
||||
data = await task
|
||||
else:
|
||||
data = filter(data)
|
||||
if data is None:
|
||||
return
|
||||
|
||||
if isinstance(data, ElementBase):
|
||||
for filter in self.__filters['out_sync']:
|
||||
data = filter(data)
|
||||
if data is None:
|
||||
return
|
||||
str_data = tostring(data.xml, xmlns=self.default_ns,
|
||||
stream=self, top_level=True)
|
||||
self.send_raw(str_data)
|
||||
else:
|
||||
self.send_raw(data)
|
||||
|
||||
|
||||
async def run_filters(self):
|
||||
"""
|
||||
Background loop that processes stanzas to send.
|
||||
"""
|
||||
while True:
|
||||
(data, use_filters) = await self.waiting_queue.get()
|
||||
try:
|
||||
if isinstance(data, ElementBase):
|
||||
if use_filters:
|
||||
already_run_filters = set()
|
||||
for filter in self.__filters['out']:
|
||||
already_run_filters.add(filter)
|
||||
if iscoroutinefunction(filter):
|
||||
task = asyncio.create_task(filter(data))
|
||||
completed, pending = await wait(
|
||||
{task},
|
||||
timeout=1,
|
||||
)
|
||||
if pending:
|
||||
asyncio.ensure_future(
|
||||
self._continue_slow_send(
|
||||
task,
|
||||
already_run_filters
|
||||
)
|
||||
)
|
||||
raise Exception("Slow coro, rescheduling")
|
||||
data = task.result()
|
||||
else:
|
||||
data = filter(data)
|
||||
if data is None:
|
||||
raise ContinueQueue('Empty stanza')
|
||||
|
||||
if isinstance(data, ElementBase):
|
||||
if use_filters:
|
||||
for filter in self.__filters['out_sync']:
|
||||
data = filter(data)
|
||||
if data is None:
|
||||
raise ContinueQueue('Empty stanza')
|
||||
str_data = tostring(data.xml, xmlns=self.default_ns,
|
||||
stream=self, top_level=True)
|
||||
self.send_raw(str_data)
|
||||
else:
|
||||
self.send_raw(data)
|
||||
except ContinueQueue as exc:
|
||||
log.debug('Stanza in send queue not sent: %s', exc)
|
||||
except Exception:
|
||||
log.error('Exception raised in send queue:', exc_info=True)
|
||||
self.waiting_queue.task_done()
|
||||
|
||||
def send(self, data, use_filters=True):
|
||||
"""A wrapper for :meth:`send_raw()` for sending stanza objects.
|
||||
|
||||
May optionally block until an expected response is received.
|
||||
|
||||
:param data: The :class:`~slixmpp.xmlstream.stanzabase.ElementBase`
|
||||
stanza to send on the stream.
|
||||
:param bool use_filters: Indicates if outgoing filters should be
|
||||
@@ -880,24 +1005,7 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
filters is useful when resending stanzas.
|
||||
Defaults to ``True``.
|
||||
"""
|
||||
if isinstance(data, ElementBase):
|
||||
if use_filters:
|
||||
for filter in self.__filters['out']:
|
||||
data = filter(data)
|
||||
if data is None:
|
||||
return
|
||||
|
||||
if isinstance(data, ElementBase):
|
||||
if use_filters:
|
||||
for filter in self.__filters['out_sync']:
|
||||
data = filter(data)
|
||||
if data is None:
|
||||
return
|
||||
str_data = tostring(data.xml, xmlns=self.default_ns,
|
||||
stream=self, top_level=True)
|
||||
self.send_raw(str_data)
|
||||
else:
|
||||
self.send_raw(data)
|
||||
self.waiting_queue.put_nowait((data, use_filters))
|
||||
|
||||
def send_xml(self, data):
|
||||
"""Send an XML object on the stream
|
||||
@@ -995,4 +1103,3 @@ class XMLStream(asyncio.BaseProtocol):
|
||||
:param exception: An unhandled exception object.
|
||||
"""
|
||||
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")
|
||||
|
||||
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):
|
||||
"""Test handler working, then deleted and not triggered"""
|
||||
@@ -41,7 +41,7 @@ class TestEvents(SlixTest):
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
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):
|
||||
"""Test adding, then removing, then adding an event handler."""
|
||||
@@ -61,7 +61,7 @@ class TestEvents(SlixTest):
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
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):
|
||||
"""Test disposable handler working, then not being triggered again."""
|
||||
@@ -78,7 +78,7 @@ class TestEvents(SlixTest):
|
||||
self.xmpp.event("test_event", {})
|
||||
|
||||
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)
|
||||
|
||||
@@ -16,11 +16,11 @@ class TestOverall(unittest.TestCase):
|
||||
"""Testing all modules by compiling them"""
|
||||
src = '.%sslixmpp' % os.sep
|
||||
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):
|
||||
"""Testing that indentation is consistent"""
|
||||
self.failIf(tabnanny.check('..%sslixmpp' % os.sep))
|
||||
self.assertFalse(tabnanny.check('..%sslixmpp' % os.sep))
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestOverall)
|
||||
|
||||
@@ -9,37 +9,37 @@ class TestStanzaBase(SlixTest):
|
||||
"""Test the 'to' interface of StanzaBase."""
|
||||
stanza = StanzaBase()
|
||||
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.")
|
||||
|
||||
def testFrom(self):
|
||||
"""Test the 'from' interface of StanzaBase."""
|
||||
stanza = StanzaBase()
|
||||
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.")
|
||||
|
||||
def testPayload(self):
|
||||
"""Test the 'payload' interface of StanzaBase."""
|
||||
stanza = StanzaBase()
|
||||
self.failUnless(stanza['payload'] == [],
|
||||
self.assertTrue(stanza['payload'] == [],
|
||||
"Empty stanza does not have an empty payload.")
|
||||
|
||||
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['payload'] = ET.Element('{bar}bar')
|
||||
self.failUnless(len(stanza['payload']) == 2,
|
||||
self.assertTrue(len(stanza['payload']) == 2,
|
||||
"Stanza payload was not appended.")
|
||||
|
||||
del stanza['payload']
|
||||
self.failUnless(stanza['payload'] == [],
|
||||
self.assertTrue(stanza['payload'] == [],
|
||||
"Stanza payload not cleared after deletion.")
|
||||
|
||||
stanza['payload'] = [ET.Element('{foo}foo'),
|
||||
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.")
|
||||
|
||||
def testClear(self):
|
||||
@@ -49,9 +49,9 @@ class TestStanzaBase(SlixTest):
|
||||
stanza['payload'] = ET.Element("{foo}foo")
|
||||
stanza.clear()
|
||||
|
||||
self.failUnless(stanza['payload'] == [],
|
||||
self.assertTrue(stanza['payload'] == [],
|
||||
"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()")
|
||||
|
||||
def testReply(self):
|
||||
@@ -63,18 +63,10 @@ class TestStanzaBase(SlixTest):
|
||||
|
||||
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.")
|
||||
self.failUnless(stanza['payload'] == [],
|
||||
self.assertTrue(stanza['payload'] == [],
|
||||
"Stanza reply did not empty stanza payload.")
|
||||
|
||||
def testError(self):
|
||||
"""Test marking a stanza as an error."""
|
||||
stanza = StanzaBase()
|
||||
stanza['type'] = 'get'
|
||||
stanza.error()
|
||||
self.failUnless(stanza['type'] == 'error',
|
||||
"Stanza type is not 'error' after calling error()")
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestStanzaBase)
|
||||
|
||||
@@ -17,7 +17,7 @@ class TestElementBase(SlixTest):
|
||||
"{%s}bar" % ns,
|
||||
"{abc}baz",
|
||||
"{%s}more" % ns])
|
||||
self.failUnless(expected == result,
|
||||
self.assertTrue(expected == result,
|
||||
"Incorrect namespace fixing result: %s" % str(result))
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ class TestElementBase(SlixTest):
|
||||
'lang': '',
|
||||
'bar': 'c',
|
||||
'baz': ''}]}
|
||||
self.failUnless(values == expected,
|
||||
self.assertTrue(values == expected,
|
||||
"Unexpected stanza values:\n%s\n%s" % (str(expected), str(values)))
|
||||
|
||||
|
||||
@@ -170,13 +170,13 @@ class TestElementBase(SlixTest):
|
||||
'meh': ''}
|
||||
for interface, value in expected.items():
|
||||
result = stanza[interface]
|
||||
self.failUnless(result == value,
|
||||
self.assertTrue(result == value,
|
||||
"Incorrect stanza interface access result: %s" % result)
|
||||
|
||||
# Test plugin interfaces
|
||||
self.failUnless(isinstance(stanza['foobar'], TestStanzaPlugin),
|
||||
self.assertTrue(isinstance(stanza['foobar'], TestStanzaPlugin),
|
||||
"Incorrect plugin object result.")
|
||||
self.failUnless(stanza['foobar']['fizz'] == 'c',
|
||||
self.assertTrue(stanza['foobar']['fizz'] == 'c',
|
||||
"Incorrect plugin subvalue result.")
|
||||
|
||||
def testSetItem(self):
|
||||
@@ -269,7 +269,7 @@ class TestElementBase(SlixTest):
|
||||
<foo xmlns="foo" />
|
||||
""")
|
||||
|
||||
self.failUnless(stanza._get_attr('bar') == '',
|
||||
self.assertTrue(stanza._get_attr('bar') == '',
|
||||
"Incorrect value returned for an unset XML attribute.")
|
||||
|
||||
stanza._set_attr('bar', 'a')
|
||||
@@ -279,7 +279,7 @@ class TestElementBase(SlixTest):
|
||||
<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.")
|
||||
|
||||
stanza._set_attr('bar', None)
|
||||
@@ -289,7 +289,7 @@ class TestElementBase(SlixTest):
|
||||
<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.")
|
||||
|
||||
def testGetSubText(self):
|
||||
@@ -311,7 +311,7 @@ class TestElementBase(SlixTest):
|
||||
return self._get_sub_text("wrapper/bar", default="not found")
|
||||
|
||||
stanza = TestStanza()
|
||||
self.failUnless(stanza['bar'] == 'not found',
|
||||
self.assertTrue(stanza['bar'] == 'not found',
|
||||
"Default _get_sub_text value incorrect.")
|
||||
|
||||
stanza['bar'] = 'found'
|
||||
@@ -322,7 +322,7 @@ class TestElementBase(SlixTest):
|
||||
</wrapper>
|
||||
</foo>
|
||||
""")
|
||||
self.failUnless(stanza['bar'] == 'found',
|
||||
self.assertTrue(stanza['bar'] == 'found',
|
||||
"_get_sub_text value incorrect: %s." % stanza['bar'])
|
||||
|
||||
def testSubElement(self):
|
||||
@@ -481,45 +481,45 @@ class TestElementBase(SlixTest):
|
||||
register_stanza_plugin(TestStanza, TestStanzaPlugin)
|
||||
|
||||
stanza = TestStanza()
|
||||
self.failUnless(stanza.match("foo"),
|
||||
self.assertTrue(stanza.match("foo"),
|
||||
"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['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['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['qux'] = 'c'
|
||||
self.failUnless(stanza.match("foo/qux"),
|
||||
self.assertTrue(stanza.match("foo/qux"),
|
||||
"Stanza did not match with subelements.")
|
||||
|
||||
stanza['qux'] = ''
|
||||
self.failUnless(stanza.match("foo/qux") == False,
|
||||
self.assertTrue(stanza.match("foo/qux") == False,
|
||||
"Stanza matched missing subinterface element.")
|
||||
|
||||
self.failUnless(stanza.match("foo/bar") == False,
|
||||
self.assertTrue(stanza.match("foo/bar") == False,
|
||||
"Stanza matched nonexistent element.")
|
||||
|
||||
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.")
|
||||
|
||||
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.")
|
||||
|
||||
substanza = TestSubStanza()
|
||||
substanza['attrib'] = 'd'
|
||||
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.")
|
||||
|
||||
self.failUnless(stanza.match("foo/{baz}sub"),
|
||||
self.assertTrue(stanza.match("foo/{baz}sub"),
|
||||
"Stanza did not match with namespaced substanza.")
|
||||
|
||||
def testComparisons(self):
|
||||
@@ -533,19 +533,19 @@ class TestElementBase(SlixTest):
|
||||
stanza1 = TestStanza()
|
||||
stanza1['bar'] = 'a'
|
||||
|
||||
self.failUnless(stanza1,
|
||||
self.assertTrue(stanza1,
|
||||
"Stanza object does not evaluate to True")
|
||||
|
||||
stanza2 = TestStanza()
|
||||
stanza2['baz'] = 'b'
|
||||
|
||||
self.failUnless(stanza1 != stanza2,
|
||||
self.assertTrue(stanza1 != stanza2,
|
||||
"Different stanza objects incorrectly compared equal.")
|
||||
|
||||
stanza1['baz'] = 'b'
|
||||
stanza2['bar'] = 'a'
|
||||
|
||||
self.failUnless(stanza1 == stanza2,
|
||||
self.assertTrue(stanza1 == stanza2,
|
||||
"Equal stanzas incorrectly compared inequal.")
|
||||
|
||||
def testKeys(self):
|
||||
@@ -561,12 +561,12 @@ class TestElementBase(SlixTest):
|
||||
|
||||
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.")
|
||||
|
||||
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.")
|
||||
|
||||
def testGet(self):
|
||||
@@ -580,10 +580,10 @@ class TestElementBase(SlixTest):
|
||||
stanza = TestStanza()
|
||||
stanza['bar'] = 'a'
|
||||
|
||||
self.failUnless(stanza.get('bar') == 'a',
|
||||
self.assertTrue(stanza.get('bar') == 'a',
|
||||
"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")
|
||||
|
||||
def testSubStanzas(self):
|
||||
@@ -608,7 +608,7 @@ class TestElementBase(SlixTest):
|
||||
substanza2['qux'] = 'b'
|
||||
|
||||
# Test appending substanzas
|
||||
self.failUnless(len(stanza) == 0,
|
||||
self.assertTrue(len(stanza) == 0,
|
||||
"Incorrect empty stanza size.")
|
||||
|
||||
stanza.append(substanza1)
|
||||
@@ -617,7 +617,7 @@ class TestElementBase(SlixTest):
|
||||
<foobar qux="a" />
|
||||
</foo>
|
||||
""", use_values=False)
|
||||
self.failUnless(len(stanza) == 1,
|
||||
self.assertTrue(len(stanza) == 1,
|
||||
"Incorrect stanza size with 1 substanza.")
|
||||
|
||||
stanza.append(substanza2)
|
||||
@@ -627,7 +627,7 @@ class TestElementBase(SlixTest):
|
||||
<foobar qux="b" />
|
||||
</foo>
|
||||
""", use_values=False)
|
||||
self.failUnless(len(stanza) == 2,
|
||||
self.assertTrue(len(stanza) == 2,
|
||||
"Incorrect stanza size with 2 substanzas.")
|
||||
|
||||
# Test popping substanzas
|
||||
@@ -643,7 +643,7 @@ class TestElementBase(SlixTest):
|
||||
results = []
|
||||
for substanza in stanza:
|
||||
results.append(substanza['qux'])
|
||||
self.failUnless(results == ['b', 'a'],
|
||||
self.assertTrue(results == ['b', 'a'],
|
||||
"Iteration over substanzas failed: %s." % str(results))
|
||||
|
||||
def testCopy(self):
|
||||
@@ -659,11 +659,11 @@ class TestElementBase(SlixTest):
|
||||
|
||||
stanza2 = stanza1.__copy__()
|
||||
|
||||
self.failUnless(stanza1 == stanza2,
|
||||
self.assertTrue(stanza1 == stanza2,
|
||||
"Copied stanzas are not equal to each other.")
|
||||
|
||||
stanza1['baz'] = 'b'
|
||||
self.failUnless(stanza1 != stanza2,
|
||||
self.assertTrue(stanza1 != stanza2,
|
||||
"Divergent stanza copies incorrectly compared equal.")
|
||||
|
||||
def testExtension(self):
|
||||
@@ -701,7 +701,7 @@ class TestElementBase(SlixTest):
|
||||
</foo>
|
||||
""")
|
||||
|
||||
self.failUnless(stanza['extended'] == 'testing',
|
||||
self.assertTrue(stanza['extended'] == 'testing',
|
||||
"Could not retrieve stanza extension value.")
|
||||
|
||||
del stanza['extended']
|
||||
|
||||
@@ -34,7 +34,7 @@ class TestErrorStanzas(SlixTest):
|
||||
</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'
|
||||
|
||||
|
||||
@@ -62,30 +62,30 @@ class TestGmail(SlixTest):
|
||||
|
||||
iq = self.Iq(xml=xml)
|
||||
mailbox = iq['mailbox']
|
||||
self.failUnless(mailbox['result-time'] == '1118012394209', "result-time doesn't match")
|
||||
self.failUnless(mailbox['url'] == 'http://mail.google.com/mail', "url doesn't match")
|
||||
self.failUnless(mailbox['matched'] == '95', "total-matched incorrect")
|
||||
self.failUnless(mailbox['estimate'] == False, "total-estimate incorrect")
|
||||
self.failUnless(len(mailbox['threads']) == 1, "could not extract message threads")
|
||||
self.assertTrue(mailbox['result-time'] == '1118012394209', "result-time doesn't match")
|
||||
self.assertTrue(mailbox['url'] == 'http://mail.google.com/mail', "url doesn't match")
|
||||
self.assertTrue(mailbox['matched'] == '95', "total-matched incorrect")
|
||||
self.assertTrue(mailbox['estimate'] == False, "total-estimate incorrect")
|
||||
self.assertTrue(len(mailbox['threads']) == 1, "could not extract message threads")
|
||||
|
||||
thread = mailbox['threads'][0]
|
||||
self.failUnless(thread['tid'] == '1172320964060972012', "thread tid doesn't match")
|
||||
self.failUnless(thread['participation'] == '1', "thread participation incorrect")
|
||||
self.failUnless(thread['messages'] == '28', "thread message count incorrect")
|
||||
self.failUnless(thread['date'] == '1118012394209', "thread date doesn't match")
|
||||
self.failUnless(thread['url'] == 'http://mail.google.com/mail?view=cv', "thread url doesn't match")
|
||||
self.failUnless(thread['labels'] == 'act1scene3', "thread labels incorrect")
|
||||
self.failUnless(thread['subject'] == 'Put thy rapier up.', "thread subject doesn't match")
|
||||
self.failUnless(thread['snippet'] == "Ay, ay, a scratch, a scratch; marry, 'tis enough.", "snippet doesn't match")
|
||||
self.failUnless(len(thread['senders']) == 3, "could not extract senders")
|
||||
self.assertTrue(thread['tid'] == '1172320964060972012', "thread tid doesn't match")
|
||||
self.assertTrue(thread['participation'] == '1', "thread participation incorrect")
|
||||
self.assertTrue(thread['messages'] == '28', "thread message count incorrect")
|
||||
self.assertTrue(thread['date'] == '1118012394209', "thread date doesn't match")
|
||||
self.assertTrue(thread['url'] == 'http://mail.google.com/mail?view=cv', "thread url doesn't match")
|
||||
self.assertTrue(thread['labels'] == 'act1scene3', "thread labels incorrect")
|
||||
self.assertTrue(thread['subject'] == 'Put thy rapier up.', "thread subject doesn't match")
|
||||
self.assertTrue(thread['snippet'] == "Ay, ay, a scratch, a scratch; marry, 'tis enough.", "snippet doesn't match")
|
||||
self.assertTrue(len(thread['senders']) == 3, "could not extract senders")
|
||||
|
||||
sender1 = thread['senders'][0]
|
||||
self.failUnless(sender1['name'] == 'Me', "sender name doesn't match")
|
||||
self.failUnless(sender1['address'] == 'romeo@gmail.com', "sender address doesn't match")
|
||||
self.failUnless(sender1['originator'] == True, "sender originator incorrect")
|
||||
self.failUnless(sender1['unread'] == False, "sender unread incorrectly True")
|
||||
self.assertTrue(sender1['name'] == 'Me', "sender name doesn't match")
|
||||
self.assertTrue(sender1['address'] == 'romeo@gmail.com', "sender address doesn't match")
|
||||
self.assertTrue(sender1['originator'] == True, "sender originator incorrect")
|
||||
self.assertTrue(sender1['unread'] == False, "sender unread incorrectly True")
|
||||
|
||||
sender2 = thread['senders'][2]
|
||||
self.failUnless(sender2['unread'] == True, "sender unread incorrectly False")
|
||||
self.assertTrue(sender2['unread'] == True, "sender unread incorrectly False")
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestGmail)
|
||||
|
||||
@@ -70,7 +70,7 @@ class TestIqStanzas(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match")
|
||||
self.assertTrue(iq['query'] == 'query_ns2', "Query namespace doesn't match")
|
||||
|
||||
del iq['query']
|
||||
self.check(iq, """
|
||||
|
||||
@@ -18,7 +18,7 @@ class TestMessageStanzas(SlixTest):
|
||||
msg['type'] = 'groupchat'
|
||||
msg['body'] = "this is a message"
|
||||
msg = msg.reply()
|
||||
self.failUnless(str(msg['to']) == 'room@someservice.someserver.tld')
|
||||
self.assertTrue(str(msg['to']) == 'room@someservice.someserver.tld')
|
||||
|
||||
def testHTMLPlugin(self):
|
||||
"Test message/html/body stanza"
|
||||
|
||||
@@ -15,7 +15,7 @@ class TestPresenceStanzas(SlixTest):
|
||||
p = self.Presence()
|
||||
p['type'] = 'available'
|
||||
self.check(p, "<presence />")
|
||||
self.failUnless(p['type'] == 'available',
|
||||
self.assertTrue(p['type'] == 'available',
|
||||
"Incorrect presence['type'] for type 'available': %s" % p['type'])
|
||||
|
||||
for showtype in ['away', 'chat', 'dnd', 'xa']:
|
||||
@@ -23,7 +23,7 @@ class TestPresenceStanzas(SlixTest):
|
||||
self.check(p, """
|
||||
<presence><show>%s</show></presence>
|
||||
""" % showtype)
|
||||
self.failUnless(p['type'] == showtype,
|
||||
self.assertTrue(p['type'] == showtype,
|
||||
"Incorrect presence['type'] for type '%s'" % showtype)
|
||||
|
||||
p['type'] = None
|
||||
@@ -47,10 +47,10 @@ class TestPresenceStanzas(SlixTest):
|
||||
c.add_event_handler("changed_status", handlechangedpresence)
|
||||
c._handle_presence(p)
|
||||
|
||||
self.failUnless(happened == [],
|
||||
self.assertTrue(happened == [],
|
||||
"changed_status event triggered for extra unavailable presence")
|
||||
roster = c.roster['crap@wherever']
|
||||
self.failUnless(roster['bill@chadmore.com'].resources == {},
|
||||
self.assertTrue(roster['bill@chadmore.com'].resources == {},
|
||||
"Roster updated for superfulous unavailable presence")
|
||||
|
||||
def testNickPlugin(self):
|
||||
|
||||
@@ -61,7 +61,7 @@ class TestRosterStanzas(SlixTest):
|
||||
debug = "Roster items don't match after retrieval."
|
||||
debug += "\nReturned: %s" % str(iq['roster']['items'])
|
||||
debug += "\nExpected: %s" % str(expected)
|
||||
self.failUnless(iq['roster']['items'] == expected, debug)
|
||||
self.assertTrue(iq['roster']['items'] == expected, debug)
|
||||
|
||||
def testDelItems(self):
|
||||
"""Test clearing items from a roster stanza."""
|
||||
|
||||
@@ -258,7 +258,7 @@ class TestDisco(SlixTest):
|
||||
('client', 'pc', 'no', None),
|
||||
('client', 'pc', 'en', None),
|
||||
('client', 'pc', 'fr', None)}
|
||||
self.failUnless(iq['disco_info']['identities'] == expected,
|
||||
self.assertTrue(iq['disco_info']['identities'] == expected,
|
||||
"Identities do not match:\n%s\n%s" % (
|
||||
expected,
|
||||
iq['disco_info']['identities']))
|
||||
@@ -276,7 +276,7 @@ class TestDisco(SlixTest):
|
||||
|
||||
expected = {('client', 'pc', 'no', None)}
|
||||
result = iq['disco_info'].get_identities(lang='no')
|
||||
self.failUnless(result == expected,
|
||||
self.assertTrue(result == expected,
|
||||
"Identities do not match:\n%s\n%s" % (
|
||||
expected, result))
|
||||
|
||||
@@ -337,7 +337,7 @@ class TestDisco(SlixTest):
|
||||
iq['disco_info'].add_feature('baz')
|
||||
|
||||
expected = {'foo', 'bar', 'baz'}
|
||||
self.failUnless(iq['disco_info']['features'] == expected,
|
||||
self.assertTrue(iq['disco_info']['features'] == expected,
|
||||
"Features do not match:\n%s\n%s" % (
|
||||
expected,
|
||||
iq['disco_info']['features']))
|
||||
@@ -475,7 +475,7 @@ class TestDisco(SlixTest):
|
||||
expected = {('user@localhost', None, None),
|
||||
('user@localhost', 'foo', None),
|
||||
('test@localhost', 'bar', 'Tester')}
|
||||
self.failUnless(iq['disco_items']['items'] == expected,
|
||||
self.assertTrue(iq['disco_items']['items'] == expected,
|
||||
"Items do not match:\n%s\n%s" % (
|
||||
expected,
|
||||
iq['disco_items']['items']))
|
||||
|
||||
@@ -17,13 +17,13 @@ class TestAdHocCommandStanzas(SlixTest):
|
||||
iq['command']['node'] = 'foo'
|
||||
|
||||
iq['command']['action'] = 'execute'
|
||||
self.failUnless(iq['command']['action'] == 'execute')
|
||||
self.assertTrue(iq['command']['action'] == 'execute')
|
||||
|
||||
iq['command']['action'] = 'complete'
|
||||
self.failUnless(iq['command']['action'] == 'complete')
|
||||
self.assertTrue(iq['command']['action'] == 'complete')
|
||||
|
||||
iq['command']['action'] = 'cancel'
|
||||
self.failUnless(iq['command']['action'] == 'cancel')
|
||||
self.assertTrue(iq['command']['action'] == 'cancel')
|
||||
|
||||
def testSetActions(self):
|
||||
"""Test setting next actions in a command stanza."""
|
||||
@@ -98,7 +98,7 @@ class TestAdHocCommandStanzas(SlixTest):
|
||||
('error', "I can't let you do that")]
|
||||
iq['command']['notes'] = notes
|
||||
|
||||
self.failUnless(iq['command']['notes'] == notes,
|
||||
self.assertTrue(iq['command']['notes'] == notes,
|
||||
"Notes don't match: %s %s" % (notes, iq['command']['notes']))
|
||||
|
||||
self.check(iq, """
|
||||
|
||||
@@ -24,7 +24,7 @@ class TestSetStanzas(SlixTest):
|
||||
"""
|
||||
s = Set(ET.fromstring(xml_string))
|
||||
expected = '10'
|
||||
self.failUnless(s['first_index'] == expected)
|
||||
self.assertTrue(s['first_index'] == expected)
|
||||
|
||||
def testDelFirstIndex(self):
|
||||
xml_string = """
|
||||
@@ -57,7 +57,7 @@ class TestSetStanzas(SlixTest):
|
||||
"""
|
||||
s = Set(ET.fromstring(xml_string))
|
||||
expected = True
|
||||
self.failUnless(s['before'] == expected)
|
||||
self.assertTrue(s['before'] == expected)
|
||||
|
||||
def testGetBefore(self):
|
||||
xml_string = """
|
||||
@@ -89,7 +89,7 @@ class TestSetStanzas(SlixTest):
|
||||
"""
|
||||
s = Set(ET.fromstring(xml_string))
|
||||
expected = 'id'
|
||||
self.failUnless(s['before'] == expected)
|
||||
self.assertTrue(s['before'] == expected)
|
||||
|
||||
def testGetBeforeVal(self):
|
||||
xml_string = """
|
||||
|
||||
@@ -112,7 +112,7 @@ class TestHandlers(SlixTest):
|
||||
# Check that the waiter is no longer registered
|
||||
waiter_exists = self.xmpp.remove_handler('IqWait_test2')
|
||||
|
||||
self.failUnless(waiter_exists == False,
|
||||
self.assertTrue(waiter_exists == False,
|
||||
"Waiter handler was not removed.")
|
||||
|
||||
def testIqCallback(self):
|
||||
@@ -145,7 +145,7 @@ class TestHandlers(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(events == ['foo'],
|
||||
self.assertTrue(events == ['foo'],
|
||||
"Iq callback was not executed: %s" % events)
|
||||
|
||||
def testMultipleHandlersForStanza(self):
|
||||
|
||||
@@ -52,7 +52,7 @@ class TestStreamRoster(SlixTest):
|
||||
pending_out=True,
|
||||
groups=['Friends', 'Examples'])
|
||||
|
||||
self.failUnless(len(roster_updates) == 1,
|
||||
self.assertTrue(len(roster_updates) == 1,
|
||||
"Wrong number of roster_update events fired: %s (should be 1)" % len(roster_updates))
|
||||
|
||||
def testRosterSet(self):
|
||||
@@ -89,7 +89,7 @@ class TestStreamRoster(SlixTest):
|
||||
groups=['Friends', 'Examples'])
|
||||
|
||||
|
||||
self.failUnless('roster_update' in events,
|
||||
self.assertTrue('roster_update' in events,
|
||||
"Roster updated event not triggered: %s" % events)
|
||||
|
||||
def testRosterPushRemove(self):
|
||||
@@ -188,7 +188,7 @@ class TestStreamRoster(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(events == ['roster_callback'],
|
||||
self.assertTrue(events == ['roster_callback'],
|
||||
"Roster timeout event not triggered: %s." % events)
|
||||
|
||||
def testRosterUnicode(self):
|
||||
@@ -209,7 +209,7 @@ class TestStreamRoster(SlixTest):
|
||||
groups=['Unicode'])
|
||||
|
||||
jids = list(self.xmpp.client_roster.keys())
|
||||
self.failUnless(jids == ['andré@foo'],
|
||||
self.assertTrue(jids == ['andré@foo'],
|
||||
"Too many roster entries found: %s" % jids)
|
||||
|
||||
self.recv("""
|
||||
@@ -223,7 +223,7 @@ class TestStreamRoster(SlixTest):
|
||||
expected = {'bar': {'status':'Testing',
|
||||
'show':'away',
|
||||
'priority':0}}
|
||||
self.failUnless(result == expected,
|
||||
self.assertTrue(result == expected,
|
||||
"Unexpected roster values: %s" % result)
|
||||
|
||||
def testSendLastPresence(self):
|
||||
|
||||
@@ -79,8 +79,7 @@ class TestInBandByteStreams(SlixTest):
|
||||
|
||||
self.assertEqual(events, {'ibb_stream_start', 'callback'})
|
||||
|
||||
@asyncio.coroutine
|
||||
def testSendData(self):
|
||||
async def testSendData(self):
|
||||
"""Test sending data over an in-band bytestream."""
|
||||
|
||||
streams = []
|
||||
@@ -117,7 +116,7 @@ class TestInBandByteStreams(SlixTest):
|
||||
|
||||
|
||||
# Test sending data out
|
||||
yield from stream.send("Testing")
|
||||
await stream.send("Testing")
|
||||
|
||||
self.send("""
|
||||
<iq type="set" id="2"
|
||||
|
||||
@@ -626,7 +626,7 @@ class TestAdHocCommands(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(results == ['foo', 'bar', 'baz'],
|
||||
self.assertTrue(results == ['foo', 'bar', 'baz'],
|
||||
'Incomplete command workflow: %s' % results)
|
||||
|
||||
def testClientAPICancel(self):
|
||||
@@ -689,7 +689,7 @@ class TestAdHocCommands(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(results == ['foo', 'bar'],
|
||||
self.assertTrue(results == ['foo', 'bar'],
|
||||
'Incomplete command workflow: %s' % results)
|
||||
|
||||
def testClientAPIError(self):
|
||||
@@ -727,7 +727,7 @@ class TestAdHocCommands(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(results == ['foo'],
|
||||
self.assertTrue(results == ['foo'],
|
||||
'Incomplete command workflow: %s' % results)
|
||||
|
||||
def testClientAPIErrorStrippedResponse(self):
|
||||
@@ -762,7 +762,7 @@ class TestAdHocCommands(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(results == ['foo'],
|
||||
self.assertTrue(results == ['foo'],
|
||||
'Incomplete command workflow: %s' % results)
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ class TestStreamChatStates(SlixTest):
|
||||
""")
|
||||
|
||||
expected = ['active', 'inactive', 'paused', 'composing', 'gone']
|
||||
self.failUnless(results == expected,
|
||||
self.assertTrue(results == expected,
|
||||
"Chat state event not handled: %s" % results)
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestStreamDirectInvite(SlixTest):
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.failUnless(events == [True],
|
||||
self.assertTrue(events == [True],
|
||||
"Event not raised: %s" % events)
|
||||
|
||||
def testSentDirectInvite(self):
|
||||
|
||||
@@ -4,6 +4,7 @@ import sys
|
||||
import datetime
|
||||
import time
|
||||
import threading
|
||||
import unittest
|
||||
import re
|
||||
|
||||
from slixmpp.test import *
|
||||
@@ -11,6 +12,7 @@ from slixmpp.xmlstream import ElementBase
|
||||
from slixmpp.plugins.xep_0323.device import Device
|
||||
|
||||
|
||||
@unittest.skip('')
|
||||
class TestStreamSensorData(SlixTest):
|
||||
|
||||
"""
|
||||
@@ -456,7 +458,7 @@ class TestStreamSensorData(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(results == ["rejected"],
|
||||
self.assertTrue(results == ["rejected"],
|
||||
"Rejected callback was not properly executed")
|
||||
|
||||
def testRequestAcceptedAPI(self):
|
||||
@@ -493,7 +495,7 @@ class TestStreamSensorData(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnless(results == ["accepted"],
|
||||
self.assertTrue(results == ["accepted"],
|
||||
"Accepted callback was not properly executed")
|
||||
|
||||
def testRequestFieldsAPI(self):
|
||||
@@ -561,19 +563,19 @@ class TestStreamSensorData(SlixTest):
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.failUnlessEqual(results, ["accepted","fields","done"])
|
||||
self.assertEqual(results, ["accepted","fields","done"])
|
||||
# self.assertIn("nodeId", callback_data);
|
||||
self.assertTrue("nodeId" in callback_data)
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33")
|
||||
self.assertEqual(callback_data["nodeId"], "Device33")
|
||||
# self.assertIn("timestamp", callback_data);
|
||||
self.assertTrue("timestamp" in callback_data)
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
|
||||
self.assertEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
|
||||
#self.assertIn("field_Voltage", callback_data);
|
||||
self.assertTrue("field_Voltage" in callback_data)
|
||||
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
|
||||
self.assertEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
|
||||
#self.assertIn("field_TestBool", callback_data);
|
||||
self.assertTrue("field_TestBool" in callback_data)
|
||||
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
|
||||
self.assertEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
|
||||
|
||||
def testServiceDiscoveryClient(self):
|
||||
self.stream_start(mode='client',
|
||||
@@ -675,16 +677,16 @@ class TestStreamSensorData(SlixTest):
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.failUnlessEqual(results, ["accepted","failure"]);
|
||||
self.assertEqual(results, ["accepted","failure"]);
|
||||
# self.assertIn("nodeId", callback_data);
|
||||
self.assertTrue("nodeId" in callback_data)
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33")
|
||||
self.assertEqual(callback_data["nodeId"], "Device33")
|
||||
# self.assertIn("timestamp", callback_data);
|
||||
self.assertTrue("timestamp" in callback_data)
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2013-03-07T17:13:30")
|
||||
self.assertEqual(callback_data["timestamp"], "2013-03-07T17:13:30")
|
||||
# self.assertIn("error_msg", callback_data);
|
||||
self.assertTrue("error_msg" in callback_data)
|
||||
self.failUnlessEqual(callback_data["error_msg"], "Timeout.")
|
||||
self.assertEqual(callback_data["error_msg"], "Timeout.")
|
||||
|
||||
def testDelayedRequest(self):
|
||||
self.stream_start(mode='component',
|
||||
@@ -1071,19 +1073,19 @@ class TestStreamSensorData(SlixTest):
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.failUnlessEqual(results, ["queued","started","fields","done"]);
|
||||
self.assertEqual(results, ["queued","started","fields","done"]);
|
||||
# self.assertIn("nodeId", callback_data);
|
||||
self.assertTrue("nodeId" in callback_data)
|
||||
self.failUnlessEqual(callback_data["nodeId"], "Device33")
|
||||
self.assertEqual(callback_data["nodeId"], "Device33")
|
||||
# self.assertIn("timestamp", callback_data);
|
||||
self.assertTrue("timestamp" in callback_data)
|
||||
self.failUnlessEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
|
||||
self.assertEqual(callback_data["timestamp"], "2000-01-01T00:01:02")
|
||||
# self.assertIn("field_Voltage", callback_data);
|
||||
self.assertTrue("field_Voltage" in callback_data)
|
||||
self.failUnlessEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
|
||||
self.assertEqual(callback_data["field_Voltage"], {"name": "Voltage", "value": "230.4", "typename": "numeric", "unit": "V", "flags": {"invoiced": "true"}})
|
||||
# self.assertIn("field_TestBool", callback_data);
|
||||
self.assertTrue("field_TestBool" in callback_data)
|
||||
self.failUnlessEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
|
||||
self.assertEqual(callback_data["field_TestBool"], {"name": "TestBool", "value": "true", "typename": "boolean" })
|
||||
|
||||
|
||||
def testRequestFieldsCancelAPI(self):
|
||||
@@ -1139,7 +1141,7 @@ class TestStreamSensorData(SlixTest):
|
||||
</iq>
|
||||
""")
|
||||
|
||||
self.failUnlessEqual(results, ["accepted","cancelled"])
|
||||
self.assertEqual(results, ["accepted","cancelled"])
|
||||
|
||||
def testDelayedRequestCancel(self):
|
||||
self.stream_start(mode='component',
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestToString(SlixTest):
|
||||
else:
|
||||
xml=original
|
||||
result = tostring(xml, **kwargs)
|
||||
self.failUnless(result == expected, "%s: %s" % (message, result))
|
||||
self.assertTrue(result == expected, "%s: %s" % (message, result))
|
||||
|
||||
def testXMLEscape(self):
|
||||
"""Test escaping XML special characters."""
|
||||
@@ -34,7 +34,7 @@ class TestToString(SlixTest):
|
||||
desired = """<foo bar="baz">'Hi"""
|
||||
desired += """ & welcome!'</foo>"""
|
||||
|
||||
self.failUnless(escaped == desired,
|
||||
self.assertTrue(escaped == desired,
|
||||
"XML escaping did not work: %s." % escaped)
|
||||
|
||||
def testEmptyElement(self):
|
||||
@@ -99,7 +99,7 @@ class TestToString(SlixTest):
|
||||
msg['body'] = utf8_message.decode('utf-8')
|
||||
expected = '<message><body>\xe0\xb2\xa0_\xe0\xb2\xa0</body></message>'
|
||||
result = msg.__str__()
|
||||
self.failUnless(result == expected,
|
||||
self.assertTrue(result == expected,
|
||||
"Stanza Unicode handling is incorrect: %s" % result)
|
||||
|
||||
def testXMLLang(self):
|
||||
@@ -112,7 +112,7 @@ class TestToString(SlixTest):
|
||||
|
||||
expected = '<message xml:lang="no" />'
|
||||
result = msg.__str__()
|
||||
self.failUnless(expected == result,
|
||||
self.assertTrue(expected == result,
|
||||
"Serialization with xml:lang failed: %s" % result)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user