Compare commits

...

74 Commits

Author SHA1 Message Date
mathieui
3dcb96d9d8 Update version to 1.5.0 2020-05-01 15:26:24 +02:00
Maxime Buquet
0a7a4c3abe Merge branch 'python-version' into 'master'
Update Python version minimum requirement to 3.7

See merge request poezio/slixmpp!42
2020-05-01 15:14:49 +02:00
Maxime “pep” Buquet
a4bbc404ed Update Python version minimum requirement to 3.7
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2020-05-01 15:13:19 +02:00
Maxime “pep” Buquet
c3fbc6cb80 xep_0196: Use correct tag local name (thanks ivucica)
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2020-04-06 12:57:12 +02:00
Maxime “pep” Buquet
355d789061 docs: remove andyet logo/link on every page
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2020-04-06 12:57:12 +02:00
mathieui
47ed67c04e Merge branch 'add-github-pr-template' into 'master'
Add a github pull request template

See merge request poezio/slixmpp!41
2020-04-04 20:06:23 +02:00
mathieui
34567f450a Add a github pull request template 2020-04-04 20:04:23 +02:00
mathieui
9126bd8392 Merge branch 'fix-deprecations' into 'master'
Fix deprecation warning regarding invalid escape sequences.

See merge request poezio/slixmpp!40
2020-04-04 18:53:15 +02:00
mathieui
02202f7cd8 Merge branch 'fix-nonetype-error' into 'master'
Fix an issue when deleting subelements: TypeError: 'NoneType' object is not an iterator

See merge request poezio/slixmpp!39
2020-04-04 18:44:25 +02:00
jheling
2add94f5b0 Fix TypeError: 'NoneType' object is not an iterator
When deleting sub-elements in a stanza.
2020-04-04 18:42:48 +02:00
Karthikeyan Singaravelan
5fc757f200 Fix deprecation warning regarding invalid escape sequences. 2020-04-04 16:08:44 +00:00
mathieui
98108d0445 Merge branch 'fix-celementtree-import' into 'master'
cElementTree has been deprecated since Python 3.3 and removed in Python 3.9.

See merge request poezio/slixmpp!38
2020-04-04 17:55:11 +02:00
Maxime Buquet
76f4fb49d6 Merge branch 'sync-fixes' into 'master'
Sync fixes

See merge request poezio/slixmpp!37
2020-04-04 15:28:59 +02:00
Georg Lukas
5be46a5e68 fire 'disconnected' callback from abort() 2020-04-04 13:16:33 +02:00
Georg Lukas
ab9040c30e expose is_connecting state based on connection attempt future 2020-04-04 13:16:31 +02:00
Maxime Buquet
a16e2a0f6c Merge branch 'fix-0198' into 'master'
XEP-0198: unset end_session_on_disconnect on resume/enable

See merge request poezio/slixmpp!36
2020-03-29 14:28:06 +02:00
Maxime Buquet
842aa3be8f Merge branch 'fix-reconnect-2.0' into 'master'
Reset reconnect delay on manual reconnect, add delay event

See merge request poezio/slixmpp!35
2020-03-29 14:26:15 +02:00
Georg Lukas
6c28b49e7f XEP-0198: unset end_session_on_disconnect on resume/enable 2020-03-29 14:21:40 +02:00
Georg Lukas
621255027d Reset reconnect delay on manual reconnect, add delay event
This is just a hotfix workaround for an underlying problem. The
`_connect_routine` code is "blocking" (in an async way) for
`connect_loop_wait` seconds, so that a fresh-started manual reconnect
will be silenty delayed. This code does the following changes:

1. It moves the delay to before the DNS resolution (with the exponential
   back-off it might well be that the DNS records are changed while
   slixmpp is waiting).

2. It adds a new event `reconnect_delay` that gets passed the number of
   seconds it will delay before actually reconnecting

3. It resets the `connect_loop_wait` timer on a manual connect/reconnect
   call to fix the interactive experience.

A *proper fix* would replace the sleep in `_connect_routine` with a
properly timered re-invocation of it, but I don't understand enough of
asyncio for pulling off that magic, and this is actually a proper
improvement. Also I tested this and it works!
2020-03-29 14:21:05 +02:00
Maxime Buquet
efe316dc8c Merge branch 'fix-0198' into 'master'
XEP-0198: properly disable on disconnect, fix reconnect-loop

See merge request poezio/slixmpp!34
2020-03-28 22:11:52 +01:00
Maxime Buquet
e9a87a0b77 Merge branch 'master' into 'master'
reconnect: fix callback when not currently connected

See merge request poezio/slixmpp!32
2020-03-28 22:11:34 +01:00
Georg Lukas
85c9967b9c XEP-0198: fix race conditions on enable/resume
This code splits out the `enabled` property into `enabled_in` and
`enabled_out` to reflect that client and server enable 0198
asynchronously.

This also moves the actual enabling code into the stanza processing
logic, because apparently, `enable.send()` was just added into the end
of the send queue, but `enable` got enabled immediately, so that poezio
requested ACKs for whatever happened to be in the queue before.

Async is hard, let's go get fishing.
2020-03-28 21:49:18 +01:00
Georg Lukas
deb6d4f176 XEP-0198: properly disable on disconnect, fix reconnect-loop
When the connection is disconnected (but the session didn't "end",
because 0198 resumption is enabled), poezio will reconnect and try to
send an <r/> element because the 0198 plugin doesn't realize that SM
wasn't re-enabled yet.
2020-03-28 20:50:55 +01:00
Karthikeyan Singaravelan
7218bb4499 cElementTree has been deprecated since Python 3.3 and removed in Python 3.9. 2020-03-28 04:42:23 +00:00
Georg Lukas
d85efec7a2 reconnect: fix callback when not currently connected
The 'disconnected' event is normally fired from connection_lost(), which
is called by the connection code when the connection is lost after being
established. However, if the connection wasn't successfully established,
a manual /reconnect no-ops because it waits for the 'disconnected'
callback which never fires. This patch does two things:

1. Immediately fire a 'disconnected' event in disconnect() if there is
   no transport.

2. Register the 'disconnected' event handler in reconnect() *before* it
   can be fired.
2020-03-23 18:59:29 +01:00
mathieui
115c234527 Merge branch 'async-filters' into 'master'
Add async filters on the send process

See merge request poezio/slixmpp!24
2019-12-27 15:35:00 +01:00
mathieui
a0f5cb6e09 Put the queue exception at toplevel 2019-12-27 15:27:29 +01:00
mathieui
110bbf8afc Improve the send queue code a bit 2019-12-27 15:27:29 +01:00
mathieui
d97efa0bd8 add a separate place for slow ass filters 2019-12-27 15:27:29 +01:00
mathieui
672f1b28f6 raise Exception 2019-12-27 15:27:29 +01:00
mathieui
27d3ae958b Try/except around outbound stanza processing
to avoid killing the send loop when a filter has an error
2019-12-27 15:27:29 +01:00
mathieui
a32794ec35 Remove trailing whitespace 2019-12-27 15:27:28 +01:00
mathieui
aa11ba463e Skip 0323 because 2019-12-27 15:27:28 +01:00
mathieui
a83c00e933 Update test framework to work with new filters
(eewww)
2019-12-27 15:27:28 +01:00
mathieui
31f6ef6814 Run the send queue in a separate coroutine
To be able to run async stream filters
2019-12-27 15:27:25 +01:00
Maxime “pep” Buquet
9b3874b5df xep_0048: Ensure Conference jid is of type str when given a JID
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-11-11 14:00:19 +01:00
mathieui
0139fb291e Fix a bug in the 0202 plugin where a time result would send back a result
poezio bug #3499
2019-09-19 22:54:53 +02:00
mathieui
e58988484a Match the sender JID as well as the queryid in MAM results 2019-09-10 23:13:04 +02:00
mathieui
5d5e5cda19 Add typing/docstring in the MAM plugin 2019-09-10 22:44:46 +02:00
root
11f707987d Added amount parameter, so that limit on the msgs received per query can be changed. 2019-09-08 14:22:48 +02:00
Maxime “pep” Buquet
db13794e0f Revert "Remove a block of compatibility code"
This reverts commit 37bc1bb9b3.

Confusion confusion. Mathieui thought this was a sleekxmpp thing when
it's actually been added not so long ago.
2019-08-28 00:12:10 +02:00
mathieui
37bc1bb9b3 Remove a block of compatibility code
even if the user makes that mistake, it does not cause problems down the
line.
2019-08-26 12:00:38 +02:00
mathieui
9be30e5291 fix a typo in the invalidjid exception name case 2019-08-25 01:42:00 +02:00
Maxime Buquet
9fe20a4056 Merge branch 'origin-id' into 'master'
Implement Origin-id (XEP-0359)

See merge request poezio/slixmpp!21
2019-08-23 17:10:42 +02:00
Maxime “pep” Buquet
3253d34c0a basexmpp: Make origin-id opt-out
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-08-23 17:09:21 +02:00
Maxime “pep” Buquet
fef575ee1a Implement origin-id (XEP-0359)
This XEP is not implemented as a plugin but directly into Message.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-08-23 17:09:21 +02:00
Maxime Buquet
540ff89427 Merge branch 'mam' into 'master'
Assign 'True' to 'before' tag if it's value is 'None'.

See merge request poezio/slixmpp!26
2019-08-23 16:37:12 +02:00
root
dd8ac8fc87 Assign True to the 'before' tag if it's value is None (eg:at the start no msg is there in the group, so no stanza-id) 2019-08-23 04:39:20 +05:30
Maxime Buquet
2249d878d1 Merge branch 'mam' into 'master'
Removed assigning 'reverse' value to the 'before' tag

See merge request poezio/slixmpp!25
2019-08-23 00:03:07 +02:00
root
89fa9dc1dd Removed before tag. (Code for setting it is already there) 2019-08-23 00:21:42 +05:30
root
d7729e8683 Removed assigning 'reverse' value to the 'before' tag (It's value is set in xep_0313 (mam.py file) and if not then by default it is takes as 'None'). 2019-08-23 00:15:56 +05:30
louiz’
d618f55dea Merge branch 'mam' into 'master'
Added <before> tag for querying messages before a stanza-id.

See merge request poezio/slixmpp!23
2019-08-13 09:54:43 +02:00
Madhur Garg
b0e688eb35 Added <before> tag for querying messages before a stanza-id. 2019-08-12 23:39:01 +05:30
Maxime “pep” Buquet
0e7176483b xep_0030: add docstring to get_info_from_domain
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-08-03 16:05:09 +02:00
Emmanuel Gil Peyrot
f35569a2c1 Remove the last instances of a block argument to iq.send().
Thanks Madhur Garg for spotting this in
027ce2434d7fd3cf4a286dd373cb761c0d114c66!
2019-08-01 22:36:59 +02:00
Link Mauve
bec6f7c8f3 Merge branch 'mam' into 'master'
Removed 'block' from set_preferences as it was giving a TypeError while sending the staza.

See merge request poezio/slixmpp!20
2019-08-01 22:30:58 +02:00
Madhur Garg
027ce2434d Removed 'block' from set_preferences as it was giving a TypeError while sending the staza. 2019-08-02 01:57:36 +05:30
Maxime Buquet
d57fbb57a2 Merge branch 'mam' into 'master'
Added a function in xep313 plugin to get current MAM preferences.

See merge request poezio/slixmpp!19
2019-07-22 22:05:05 +02:00
Madhur Garg
85cd7a9166 Added a function to get current MAM preferences. 2019-07-18 16:55:11 +05:30
Maxime “pep” Buquet
d50d996c68 xmlstream/stanzabase: remove unused interfaces and types attributes
These are already on each stanza, and were not applicable to all stanzas
anyway.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-07-16 11:26:54 +02:00
mathieui
371ad20ca7 Do not add disco#info for occupantid, it’s a server thing 2019-07-14 13:25:22 +02:00
mathieui
5f49df6b56 Add the occupant id stanza elements 2019-07-14 13:13:59 +02:00
mathieui
b50bfb2f34 Initial commit for reactions protoxep 2019-07-13 19:44:32 +02:00
Maxime “pep” Buquet
b29bb30eb7 Make generated stanza id truly random
Fix long-standing security issues where stanza @id be predictable.

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-07-13 14:07:31 +02:00
Maxime “pep” Buquet
4435c81d77 xmlstream.disconnect: add compat behaviour, set wait to default 2.0 when True is passed. Update documentation
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-07-03 21:36:17 +02:00
Madhur Garg
2638ba2744 Added 'reverse' argument. 2019-07-03 14:18:33 +05:30
Madhur Garg
dbc9758311 Added 'reverse' parameter in mam and rsm plugins 2019-07-03 10:31:18 +05:30
Maxime “pep” Buquet
47968963b1 Revert part of previous commit. Return NotImplemented when object is not a valid JID
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-05-07 11:56:39 +01:00
Maxime “pep” Buquet
4e8800f954 jid: return not equal if value can't be converted to JID
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-05-07 10:08:46 +01:00
Maxime “pep” Buquet
40053518aa Merge remote-tracking branch 'origin/mr/13' 2019-04-27 12:59:23 +01:00
Maxime “pep” Buquet
1ee0f72ead xmlstream.disconnect: fix frenchism in docstring
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-24 23:35:33 +01:00
Maxime “pep” Buquet
4bb81228ae xmlstream.disconnect: typing hints
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-24 23:35:06 +01:00
Maxime “pep” Buquet
60a7a5b8df presence: Ensure <show/> value is valid when returned as presence @type value
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-24 20:29:54 +01:00
Maxime “pep” Buquet
946674f424 xep_0045: Ensure <show/> value is valid.
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
2019-04-24 20:28:58 +01:00
41 changed files with 502 additions and 147 deletions

13
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,13 @@
################ Please use Gitlab instead of Github ###################################
Hello, thank you for contributing to slixmpp!
Youre 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.

View File

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

View File

@@ -1,5 +1,5 @@
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

View File

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

View File

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

View File

@@ -65,6 +65,5 @@
<div class="bottomnav">
{{ nav() }}
</div>
<a id="from_andyet" href="http://andyet.net"><h2>From &amp;yet</h2></a>
{% endblock %}

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,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',
]

View File

@@ -111,6 +111,9 @@ class BaseXMPP(XMLStream):
#: outgoing messages an ID.
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
#: marked with:

View File

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

View File

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

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

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

View 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'}

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

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

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

View File

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

View File

@@ -300,6 +300,8 @@ class XEP_0030(BasePlugin):
async def get_info_from_domain(self, domain=None, timeout=None,
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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()
@@ -592,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
@@ -608,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.

View File

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

View File

@@ -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.2'
__version_info__ = (1, 4, 2)
__version__ = '1.5.0'
__version_info__ = (1, 5, 0)

View File

@@ -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
@@ -203,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.
@@ -387,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
@@ -1031,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
@@ -1374,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

View File

@@ -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
@@ -241,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):
@@ -271,8 +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:
@@ -297,6 +302,10 @@ class XMLStream(asyncio.BaseProtocol):
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
@@ -313,7 +322,6 @@ class XMLStream(asyncio.BaseProtocol):
else:
ssl_context = None
await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
if self._current_connection_attempt is None:
return
try:
@@ -372,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
@@ -430,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
@@ -463,10 +475,10 @@ class XMLStream(asyncio.BaseProtocol):
self._current_connection_attempt.cancel()
self._current_connection_attempt = None
def disconnect(self, wait=2.0, reason=None):
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 will call abort() directly without closing
the stream.
@@ -476,6 +488,13 @@ class XMLStream(asyncio.BaseProtocol):
: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:
@@ -483,6 +502,8 @@ class XMLStream(asyncio.BaseProtocol):
self.send_raw(self.stream_footer)
self.schedule('Disconnect wait', wait,
self.abort, repeat=False)
else:
self.event("disconnected", reason)
def abort(self):
"""
@@ -495,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, 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, reason)
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.
@@ -790,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)
@@ -889,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
@@ -901,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

View File

@@ -68,13 +68,5 @@ class TestStanzaBase(SlixTest):
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.assertTrue(stanza['type'] == 'error',
"Stanza type is not 'error' after calling error()")
suite = unittest.TestLoader().loadTestsFromTestCase(TestStanzaBase)

View File

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