Compare commits
74 Commits
adhoc-exec
...
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 |
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,9 +1,7 @@
|
|||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "3.4"
|
- "3.7"
|
||||||
- "3.5"
|
- "3.8-dev"
|
||||||
- "3.6"
|
|
||||||
- "3.7-dev"
|
|
||||||
install:
|
install:
|
||||||
- "pip install ."
|
- "pip install ."
|
||||||
script: testall.py
|
script: testall.py
|
||||||
|
|||||||
2
INSTALL
2
INSTALL
@@ -1,5 +1,5 @@
|
|||||||
Pre-requisites:
|
Pre-requisites:
|
||||||
- Python 3.5+
|
- Python 3.7+
|
||||||
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
|
- Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module)
|
||||||
- GnuPG, for testing
|
- GnuPG, for testing
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Slixmpp
|
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.
|
SleekXMPP.
|
||||||
|
|
||||||
Slixmpp's goals is to only rewrite the core of the library (the low level
|
Slixmpp's goals is to only rewrite the core of the library (the low level
|
||||||
|
|||||||
21
docs/_static/haiku.css
vendored
21
docs/_static/haiku.css
vendored
@@ -408,24 +408,3 @@ div.viewcode-block:target {
|
|||||||
margin: -1px -12px;
|
margin: -1px -12px;
|
||||||
padding: 0 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">
|
<div class="bottomnav">
|
||||||
{{ nav() }}
|
{{ nav() }}
|
||||||
</div>
|
</div>
|
||||||
<a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ hides namespaces when able and does not introduce excessive namespace
|
|||||||
prefixes::
|
prefixes::
|
||||||
|
|
||||||
>>> from slixmpp.xmlstream.tostring import tostring
|
>>> 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>')
|
>>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>')
|
||||||
>>> ET.tostring(xml)
|
>>> ET.tostring(xml)
|
||||||
'<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'
|
'<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>'
|
||||||
|
|||||||
@@ -3,8 +3,9 @@
|
|||||||
Differences from SleekXMPP
|
Differences from SleekXMPP
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
**Python 3.5+ only**
|
**Python 3.7+ only**
|
||||||
slixmpp will only work on python 3.5 and above.
|
slixmpp will work on python 3.7 and above. It may work with previous
|
||||||
|
versions but we provide no guarantees.
|
||||||
|
|
||||||
**Stanza copies**
|
**Stanza copies**
|
||||||
The same stanza object is given through all the handlers; a handler that
|
The same stanza object is given through all the handlers; a handler that
|
||||||
|
|||||||
@@ -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)
|
self.send_message(mto=self.recipient, mbody=self.msg)
|
||||||
|
|
||||||
Finally, we need to disconnect the client using :meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>`.
|
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
|
Now, sent stanzas are placed in a queue to pass them to the send thread.
|
||||||
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` without any parameters, then it is possible
|
:meth:`disconnect <slixmpp.xmlstream.XMLStream.disconnect>` by default will wait for an
|
||||||
for the client to disconnect before the send queue is processed and the message is actually
|
acknowledgement from the server for at least `2.0` seconds. This time is configurable with
|
||||||
sent on the wire. To ensure that our message is processed, we use
|
the `wait` parameter. If `0.0` is passed for `wait`, :meth:`disconnect
|
||||||
:meth:`disconnect(wait=True) <slixmpp.xmlstream.XMLStream.disconnect>`.
|
<slixmpp.xmlstream.XMLStream.disconnect>` will not close the connection gracefully.
|
||||||
|
|
||||||
.. code-block:: python
|
.. 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.send_message(mto=self.recipient, mbody=self.msg)
|
||||||
|
|
||||||
self.disconnect(wait=True)
|
self.disconnect()
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
If you happen to be adding stanzas to the send queue faster than the send thread
|
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.
|
will block and not disconnect.
|
||||||
|
|
||||||
Final Product
|
Final Product
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Slixmpp
|
|||||||
which goal is to use asyncio instead of threads to handle networking. See
|
which goal is to use asyncio instead of threads to handle networking. See
|
||||||
:ref:`differences`.
|
:ref:`differences`.
|
||||||
|
|
||||||
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.5+,
|
Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+,
|
||||||
|
|
||||||
Slixmpp's design goals and philosphy are:
|
Slixmpp's design goals and philosphy are:
|
||||||
|
|
||||||
|
|||||||
3
setup.py
3
setup.py
@@ -28,9 +28,8 @@ CLASSIFIERS = [
|
|||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 3.5',
|
|
||||||
'Programming Language :: Python :: 3.6',
|
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
|
'Programming Language :: Python :: 3.8',
|
||||||
'Topic :: Internet :: XMPP',
|
'Topic :: Internet :: XMPP',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -111,6 +111,9 @@ class BaseXMPP(XMLStream):
|
|||||||
#: outgoing messages an ID.
|
#: outgoing messages an ID.
|
||||||
self.use_presence_ids = True
|
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
|
#: The API registry is a way to process callbacks based on
|
||||||
#: JID+node combinations. Each callback in the registry is
|
#: JID+node combinations. Each callback in the registry is
|
||||||
#: marked with:
|
#: marked with:
|
||||||
|
|||||||
@@ -423,7 +423,10 @@ class JID:
|
|||||||
if isinstance(other, UnescapedJID):
|
if isinstance(other, UnescapedJID):
|
||||||
return False
|
return False
|
||||||
if not isinstance(other, JID):
|
if not isinstance(other, JID):
|
||||||
other = JID(other)
|
try:
|
||||||
|
other = JID(other)
|
||||||
|
except InvalidJID:
|
||||||
|
return NotImplemented
|
||||||
|
|
||||||
return (self._node == other._node and
|
return (self._node == other._node and
|
||||||
self._domain == other._domain and
|
self._domain == other._domain and
|
||||||
|
|||||||
@@ -85,4 +85,6 @@ __all__ = [
|
|||||||
'xep_0323', # IoT Systems Sensor Data
|
'xep_0323', # IoT Systems Sensor Data
|
||||||
'xep_0325', # IoT Systems Control
|
'xep_0325', # IoT Systems Control
|
||||||
'xep_0332', # HTTP Over XMPP Transport
|
'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 slixmpp.xmlstream.stanzabase import ElementBase
|
||||||
from xml.etree import cElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
|
|
||||||
class RPCQuery(ElementBase):
|
class RPCQuery(ElementBase):
|
||||||
|
|||||||
@@ -300,6 +300,8 @@ class XEP_0030(BasePlugin):
|
|||||||
|
|
||||||
async def get_info_from_domain(self, domain=None, timeout=None,
|
async def get_info_from_domain(self, domain=None, timeout=None,
|
||||||
cached=True, callback=None):
|
cached=True, callback=None):
|
||||||
|
"""Fetch disco#info of specified domain and one disco#items level below"""
|
||||||
|
|
||||||
if domain is None:
|
if domain is None:
|
||||||
domain = self.xmpp.boundjid.domain
|
domain = self.xmpp.boundjid.domain
|
||||||
|
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ class XEP_0045(BasePlugin):
|
|||||||
return
|
return
|
||||||
self.xmpp.roster[pr['from']].ignore_updates = True
|
self.xmpp.roster[pr['from']].ignore_updates = True
|
||||||
entry = pr['muc'].get_stanza_values()
|
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['status'] = pr['status']
|
||||||
entry['alt_nick'] = pr['nick']
|
entry['alt_nick'] = pr['nick']
|
||||||
if pr['type'] == 'unavailable':
|
if pr['type'] == 'unavailable':
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
See the file LICENSE for copying permission.
|
See the file LICENSE for copying permission.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from slixmpp import JID
|
||||||
from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
from slixmpp.xmlstream import ET, ElementBase, register_stanza_plugin
|
||||||
|
|
||||||
|
|
||||||
@@ -52,6 +53,12 @@ class Conference(ElementBase):
|
|||||||
if value in ('1', 'true', True):
|
if value in ('1', 'true', True):
|
||||||
self._set_attr('autojoin', '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):
|
class URL(ElementBase):
|
||||||
name = 'url'
|
name = 'url'
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ class ResultIterator:
|
|||||||
"""
|
"""
|
||||||
if self._stop:
|
if self._stop:
|
||||||
raise StopAsyncIteration
|
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['id'] = self.query.stream.new_id()
|
||||||
self.query[self.interface]['rsm']['max'] = str(self.amount)
|
self.query[self.interface]['rsm']['max'] = str(self.amount)
|
||||||
|
|
||||||
@@ -141,7 +142,7 @@ class XEP_0059(BasePlugin):
|
|||||||
def session_bind(self, jid):
|
def session_bind(self, jid):
|
||||||
self.xmpp['xep_0030'].add_feature(Set.namespace)
|
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):
|
recv_interface=None, pre_cb=None, post_cb=None):
|
||||||
"""
|
"""
|
||||||
Create a new result set iterator for a given stanza query.
|
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
|
results -- The name of the interface containing the
|
||||||
query results (typically just 'substanzas').
|
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,
|
recv_interface=recv_interface, pre_cb=pre_cb,
|
||||||
post_cb=post_cb)
|
post_cb=post_cb)
|
||||||
|
|||||||
@@ -11,10 +11,9 @@ from slixmpp.xmlstream import ElementBase, ET
|
|||||||
|
|
||||||
class UserGaming(ElementBase):
|
class UserGaming(ElementBase):
|
||||||
|
|
||||||
name = 'gaming'
|
name = 'game'
|
||||||
namespace = 'urn:xmpp:gaming:0'
|
namespace = 'urn:xmpp:gaming:0'
|
||||||
plugin_attrib = 'gaming'
|
plugin_attrib = 'gaming'
|
||||||
interfaces = {'character_name', 'character_profile', 'name',
|
interfaces = {'character_name', 'character_profile', 'name',
|
||||||
'level', 'server_address', 'server_name', 'uri'}
|
'level', 'server_address', 'server_name', 'uri'}
|
||||||
sub_interfaces = interfaces
|
sub_interfaces = interfaces
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ class XEP_0198(BasePlugin):
|
|||||||
|
|
||||||
self.window_counter = self.window
|
self.window_counter = self.window
|
||||||
|
|
||||||
self.enabled = False
|
self.enabled_in = False
|
||||||
|
self.enabled_out = False
|
||||||
self.unacked_queue = collections.deque()
|
self.unacked_queue = collections.deque()
|
||||||
|
|
||||||
register_stanza_plugin(StreamFeatures, stanza.StreamManagement)
|
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.Ack)
|
||||||
self.xmpp.register_stanza(stanza.RequestAck)
|
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
|
# Register the feature twice because it may be ordered two
|
||||||
# different ways: enabling after binding and resumption
|
# different ways: enabling after binding and resumption
|
||||||
# before binding.
|
# before binding.
|
||||||
@@ -131,6 +128,7 @@ class XEP_0198(BasePlugin):
|
|||||||
self.xmpp.add_filter('in', self._handle_incoming)
|
self.xmpp.add_filter('in', self._handle_incoming)
|
||||||
self.xmpp.add_filter('out_sync', self._handle_outgoing)
|
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)
|
self.xmpp.add_event_handler('session_end', self.session_end)
|
||||||
|
|
||||||
def plugin_end(self):
|
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.order)
|
||||||
self.xmpp.unregister_feature('sm', self.resume_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_event_handler('session_end', self.session_end)
|
||||||
self.xmpp.del_filter('in', self._handle_incoming)
|
self.xmpp.del_filter('in', self._handle_incoming)
|
||||||
self.xmpp.del_filter('out_sync', self._handle_outgoing)
|
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.Ack)
|
||||||
self.xmpp.remove_stanza(stanza.RequestAck)
|
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):
|
def session_end(self, event):
|
||||||
"""Reset stream management state."""
|
"""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.unacked_queue.clear()
|
||||||
self.sm_id = None
|
self.sm_id = None
|
||||||
self.handled = 0
|
self.handled = 0
|
||||||
@@ -171,6 +180,7 @@ class XEP_0198(BasePlugin):
|
|||||||
|
|
||||||
def request_ack(self, e=None):
|
def request_ack(self, e=None):
|
||||||
"""Request an ack from the server."""
|
"""Request an ack from the server."""
|
||||||
|
log.debug("requesting ack")
|
||||||
req = stanza.RequestAck(self.xmpp)
|
req = stanza.RequestAck(self.xmpp)
|
||||||
self.xmpp.send_raw(str(req))
|
self.xmpp.send_raw(str(req))
|
||||||
|
|
||||||
@@ -193,9 +203,7 @@ class XEP_0198(BasePlugin):
|
|||||||
enable = stanza.Enable(self.xmpp)
|
enable = stanza.Enable(self.xmpp)
|
||||||
enable['resume'] = self.allow_resume
|
enable['resume'] = self.allow_resume
|
||||||
enable.send()
|
enable.send()
|
||||||
self.enabled = True
|
log.debug("enabling SM")
|
||||||
self.handled = 0
|
|
||||||
self.unacked_queue.clear()
|
|
||||||
|
|
||||||
waiter = Waiter('enabled_or_failed',
|
waiter = Waiter('enabled_or_failed',
|
||||||
MatchMany([
|
MatchMany([
|
||||||
@@ -204,11 +212,11 @@ class XEP_0198(BasePlugin):
|
|||||||
self.xmpp.register_handler(waiter)
|
self.xmpp.register_handler(waiter)
|
||||||
result = await waiter.wait()
|
result = await waiter.wait()
|
||||||
elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
|
elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features:
|
||||||
self.enabled = True
|
|
||||||
resume = stanza.Resume(self.xmpp)
|
resume = stanza.Resume(self.xmpp)
|
||||||
resume['h'] = self.handled
|
resume['h'] = self.handled
|
||||||
resume['previd'] = self.sm_id
|
resume['previd'] = self.sm_id
|
||||||
resume.send()
|
resume.send()
|
||||||
|
log.debug("resuming SM")
|
||||||
|
|
||||||
# Wait for a response before allowing stream feature processing
|
# Wait for a response before allowing stream feature processing
|
||||||
# to continue. The actual result processing will be done in the
|
# 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')
|
self.xmpp.features.add('stream_management')
|
||||||
if stanza['id']:
|
if stanza['id']:
|
||||||
self.sm_id = stanza['id']
|
self.sm_id = stanza['id']
|
||||||
|
self.enabled_in = True
|
||||||
|
self.handled = 0
|
||||||
self.xmpp.event('sm_enabled', stanza)
|
self.xmpp.event('sm_enabled', stanza)
|
||||||
|
self.xmpp.end_session_on_disconnect = False
|
||||||
|
|
||||||
def _handle_resumed(self, stanza):
|
def _handle_resumed(self, stanza):
|
||||||
"""Finish resuming a stream by resending unacked stanzas.
|
"""Finish resuming a stream by resending unacked stanzas.
|
||||||
@@ -239,10 +250,12 @@ class XEP_0198(BasePlugin):
|
|||||||
Raises a :term:`session_resumed` event.
|
Raises a :term:`session_resumed` event.
|
||||||
"""
|
"""
|
||||||
self.xmpp.features.add('stream_management')
|
self.xmpp.features.add('stream_management')
|
||||||
|
self.enabled_in = True
|
||||||
self._handle_ack(stanza)
|
self._handle_ack(stanza)
|
||||||
for id, stanza in self.unacked_queue:
|
for id, stanza in self.unacked_queue:
|
||||||
self.xmpp.send(stanza, use_filters=False)
|
self.xmpp.send(stanza, use_filters=False)
|
||||||
self.xmpp.event('session_resumed', stanza)
|
self.xmpp.event('session_resumed', stanza)
|
||||||
|
self.xmpp.end_session_on_disconnect = False
|
||||||
|
|
||||||
def _handle_failed(self, stanza):
|
def _handle_failed(self, stanza):
|
||||||
"""
|
"""
|
||||||
@@ -252,7 +265,8 @@ class XEP_0198(BasePlugin):
|
|||||||
|
|
||||||
Raises an :term:`sm_failed` event.
|
Raises an :term:`sm_failed` event.
|
||||||
"""
|
"""
|
||||||
self.enabled = False
|
self.enabled_in = False
|
||||||
|
self.enabled_out = False
|
||||||
self.unacked_queue.clear()
|
self.unacked_queue.clear()
|
||||||
self.xmpp.event('sm_failed', stanza)
|
self.xmpp.event('sm_failed', stanza)
|
||||||
|
|
||||||
@@ -289,7 +303,7 @@ class XEP_0198(BasePlugin):
|
|||||||
|
|
||||||
def _handle_incoming(self, stanza):
|
def _handle_incoming(self, stanza):
|
||||||
"""Increment the handled counter for each inbound stanza."""
|
"""Increment the handled counter for each inbound stanza."""
|
||||||
if not self.enabled:
|
if not self.enabled_in:
|
||||||
return stanza
|
return stanza
|
||||||
|
|
||||||
if isinstance(stanza, (Message, Presence, Iq)):
|
if isinstance(stanza, (Message, Presence, Iq)):
|
||||||
@@ -299,7 +313,13 @@ class XEP_0198(BasePlugin):
|
|||||||
|
|
||||||
def _handle_outgoing(self, stanza):
|
def _handle_outgoing(self, stanza):
|
||||||
"""Store outgoing stanzas in a queue to be acked."""
|
"""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
|
return stanza
|
||||||
|
|
||||||
if isinstance(stanza, (Message, Presence, Iq)):
|
if isinstance(stanza, (Message, Presence, Iq)):
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class XEP_0202(BasePlugin):
|
|||||||
|
|
||||||
self.xmpp.register_handler(
|
self.xmpp.register_handler(
|
||||||
Callback('Entity Time',
|
Callback('Entity Time',
|
||||||
StanzaPath('iq/entity_time'),
|
StanzaPath('iq@type=get/entity_time'),
|
||||||
self._handle_time_request))
|
self._handle_time_request))
|
||||||
register_stanza_plugin(Iq, stanza.EntityTime)
|
register_stanza_plugin(Iq, stanza.EntityTime)
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ class XEP_0279(BasePlugin):
|
|||||||
def plugin_end(self):
|
def plugin_end(self):
|
||||||
self.xmpp['xep_0030'].del_feature(feature='urn:xmpp:sic:0')
|
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):
|
timeout_callback=None):
|
||||||
iq = self.xmpp.Iq()
|
iq = self.xmpp.Iq()
|
||||||
iq['type'] = 'get'
|
iq['type'] = 'get'
|
||||||
iq['from'] = ifrom
|
iq['from'] = ifrom
|
||||||
iq.enable('ip_check')
|
iq.enable('ip_check')
|
||||||
return iq.send(block=block, timeout=timeout, callback=callback,
|
return iq.send(timeout=timeout, callback=callback,
|
||||||
timeout_callback=timeout_callback)
|
timeout_callback=timeout_callback)
|
||||||
|
|||||||
@@ -3,16 +3,18 @@
|
|||||||
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
|
||||||
This file is part of Slixmpp.
|
This file is part of Slixmpp.
|
||||||
|
|
||||||
See the file LICENSE for copying permissio
|
See the file LICENSE for copying permission
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
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.stanza import Message, Iq
|
||||||
from slixmpp.exceptions import XMPPError
|
|
||||||
from slixmpp.xmlstream.handler import Collector
|
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.xmlstream import register_stanza_plugin
|
||||||
from slixmpp.plugins import BasePlugin
|
from slixmpp.plugins import BasePlugin
|
||||||
from slixmpp.plugins.xep_0313 import stanza
|
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.MAM, self.xmpp['xep_0059'].stanza.Set)
|
||||||
register_stanza_plugin(stanza.Fin, 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,
|
def retrieve(
|
||||||
timeout=None, callback=None, iterator=False, rsm=None):
|
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()
|
iq = self.xmpp.Iq()
|
||||||
query_id = iq['id']
|
query_id = iq['id']
|
||||||
|
|
||||||
@@ -53,35 +79,48 @@ class XEP_0313(BasePlugin):
|
|||||||
iq['mam']['start'] = start
|
iq['mam']['start'] = start
|
||||||
iq['mam']['end'] = end
|
iq['mam']['end'] = end
|
||||||
iq['mam']['with'] = with_jid
|
iq['mam']['with'] = with_jid
|
||||||
|
amount = 10
|
||||||
if rsm:
|
if rsm:
|
||||||
for key, value in rsm.items():
|
for key, value in rsm.items():
|
||||||
iq['mam']['rsm'][key] = str(value)
|
iq['mam']['rsm'][key] = str(value)
|
||||||
|
if key == 'max':
|
||||||
|
amount = value
|
||||||
cb_data = {}
|
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']
|
query['mam']['queryid'] = query['id']
|
||||||
collector = Collector(
|
collector = Collector(
|
||||||
'MAM_Results_%s' % query_id,
|
'MAM_Results_%s' % query_id,
|
||||||
StanzaPath('message/mam_result@queryid=%s' % query['id']))
|
MatchXMLMask(xml_mask))
|
||||||
self.xmpp.register_handler(collector)
|
self.xmpp.register_handler(collector)
|
||||||
cb_data['collector'] = collector
|
cb_data['collector'] = collector
|
||||||
|
|
||||||
def post_cb(result):
|
def post_cb(result: Iq) -> None:
|
||||||
results = cb_data['collector'].stop()
|
results = cb_data['collector'].stop()
|
||||||
if result['type'] == 'result':
|
if result['type'] == 'result':
|
||||||
result['mam']['results'] = results
|
result['mam']['results'] = results
|
||||||
|
|
||||||
if iterator:
|
if iterator:
|
||||||
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results',
|
return self.xmpp['xep_0059'].iterate(iq, 'mam', 'results', amount=amount,
|
||||||
recv_interface='mam_fin',
|
reverse=reverse, recv_interface='mam_fin',
|
||||||
pre_cb=pre_cb, post_cb=post_cb)
|
pre_cb=pre_cb, post_cb=post_cb)
|
||||||
|
|
||||||
collector = Collector(
|
collector = Collector(
|
||||||
'MAM_Results_%s' % query_id,
|
'MAM_Results_%s' % query_id,
|
||||||
StanzaPath('message/mam_result@queryid=%s' % query_id))
|
MatchXMLMask(xml_mask))
|
||||||
self.xmpp.register_handler(collector)
|
self.xmpp.register_handler(collector)
|
||||||
|
|
||||||
def wrapped_cb(iq):
|
def wrapped_cb(iq: Iq) -> None:
|
||||||
results = collector.stop()
|
results = collector.stop()
|
||||||
if iq['type'] == 'result':
|
if iq['type'] == 'result':
|
||||||
iq['mam']['results'] = results
|
iq['mam']['results'] = results
|
||||||
@@ -90,8 +129,15 @@ class XEP_0313(BasePlugin):
|
|||||||
|
|
||||||
return iq.send(timeout=timeout, callback=wrapped_cb)
|
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,
|
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 = self.xmpp.Iq()
|
||||||
iq['type'] = 'set'
|
iq['type'] = 'set'
|
||||||
iq['to'] = jid
|
iq['to'] = jid
|
||||||
@@ -99,7 +145,7 @@ class XEP_0313(BasePlugin):
|
|||||||
iq['mam_prefs']['default'] = default
|
iq['mam_prefs']['default'] = default
|
||||||
iq['mam_prefs']['always'] = always
|
iq['mam_prefs']['always'] = always
|
||||||
iq['mam_prefs']['never'] = never
|
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):
|
def get_configuration_commands(self, jid, **kwargs):
|
||||||
return self.xmpp['xep_0030'].get_items(
|
return self.xmpp['xep_0030'].get_items(
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ class Field(ElementBase):
|
|||||||
:param value: string
|
: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:
|
if pattern.match(value) is not None:
|
||||||
self.xml.stringIds = value
|
self.xml.stringIds = value
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -114,7 +114,6 @@ class XEP_0332(BasePlugin):
|
|||||||
iq['http-req']['data'] = data
|
iq['http-req']['data'] = data
|
||||||
return iq.send(
|
return iq.send(
|
||||||
timeout=kwargs.get('timeout', None),
|
timeout=kwargs.get('timeout', None),
|
||||||
block=kwargs.get('block', True),
|
|
||||||
callback=kwargs.get('callback', None),
|
callback=kwargs.get('callback', None),
|
||||||
timeout_callback=kwargs.get('timeout_callback', None)
|
timeout_callback=kwargs.get('timeout_callback', None)
|
||||||
)
|
)
|
||||||
@@ -135,7 +134,6 @@ class XEP_0332(BasePlugin):
|
|||||||
iq['http-resp']['data'] = data
|
iq['http-resp']['data'] = data
|
||||||
return iq.send(
|
return iq.send(
|
||||||
timeout=kwargs.get('timeout', None),
|
timeout=kwargs.get('timeout', None),
|
||||||
block=kwargs.get('block', True),
|
|
||||||
callback=kwargs.get('callback', None),
|
callback=kwargs.get('callback', None),
|
||||||
timeout_callback=kwargs.get('timeout_callback', None)
|
timeout_callback=kwargs.get('timeout_callback', None)
|
||||||
)
|
)
|
||||||
@@ -153,7 +151,6 @@ class XEP_0332(BasePlugin):
|
|||||||
iq['id'] = kwargs["id"]
|
iq['id'] = kwargs["id"]
|
||||||
return iq.send(
|
return iq.send(
|
||||||
timeout=kwargs.get('timeout', None),
|
timeout=kwargs.get('timeout', None),
|
||||||
block=kwargs.get('block', True),
|
|
||||||
callback=kwargs.get('callback', None),
|
callback=kwargs.get('callback', None),
|
||||||
timeout_callback=kwargs.get('timeout_callback', None)
|
timeout_callback=kwargs.get('timeout_callback', None)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ from slixmpp.stanza.rootstanza import RootStanza
|
|||||||
from slixmpp.xmlstream import StanzaBase, ET
|
from slixmpp.xmlstream import StanzaBase, ET
|
||||||
|
|
||||||
|
|
||||||
|
ORIGIN_NAME = '{urn:xmpp:sid:0}origin-id'
|
||||||
|
|
||||||
|
|
||||||
class Message(RootStanza):
|
class Message(RootStanza):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -63,6 +66,8 @@ class Message(RootStanza):
|
|||||||
if self['id'] == '':
|
if self['id'] == '':
|
||||||
if self.stream is not None and self.stream.use_message_ids:
|
if self.stream is not None and self.stream.use_message_ids:
|
||||||
self['id'] = self.stream.new_id()
|
self['id'] = self.stream.new_id()
|
||||||
|
else:
|
||||||
|
del self['origin_id']
|
||||||
|
|
||||||
def get_type(self):
|
def get_type(self):
|
||||||
"""
|
"""
|
||||||
@@ -76,6 +81,43 @@ class Message(RootStanza):
|
|||||||
"""
|
"""
|
||||||
return self._get_attr('type', 'normal')
|
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):
|
def get_parent_thread(self):
|
||||||
"""Return the message thread's parent thread.
|
"""Return the message thread's parent thread.
|
||||||
|
|
||||||
@@ -140,6 +182,8 @@ class Message(RootStanza):
|
|||||||
new_message['parent_thread'] = self['parent_thread']
|
new_message['parent_thread'] = self['parent_thread']
|
||||||
|
|
||||||
del new_message['id']
|
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:
|
if body is not None:
|
||||||
new_message['body'] = body
|
new_message['body'] = body
|
||||||
|
|||||||
@@ -90,10 +90,10 @@ class Presence(RootStanza):
|
|||||||
def get_type(self):
|
def get_type(self):
|
||||||
"""
|
"""
|
||||||
Return the value of the <presence> stanza's type attribute, or
|
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')
|
out = self._get_attr('type')
|
||||||
if not out:
|
if not out and self['show'] in self.showtypes:
|
||||||
out = self['show']
|
out = self['show']
|
||||||
if not out or out is None:
|
if not out or out is None:
|
||||||
out = 'available'
|
out = 'available'
|
||||||
|
|||||||
@@ -340,11 +340,19 @@ class SlixTest(unittest.TestCase):
|
|||||||
self.xmpp.default_lang = None
|
self.xmpp.default_lang = None
|
||||||
self.xmpp.peer_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.
|
# Must have the stream header ready for xmpp.process() to work.
|
||||||
if not header:
|
if not header:
|
||||||
header = self.xmpp.stream_header
|
header = self.xmpp.stream_header
|
||||||
|
|
||||||
self.xmpp.data_received(header)
|
self.xmpp.data_received(header)
|
||||||
|
self.wait_for_send_queue()
|
||||||
|
|
||||||
if skip:
|
if skip:
|
||||||
self.xmpp.socket.next_sent()
|
self.xmpp.socket.next_sent()
|
||||||
@@ -592,6 +600,7 @@ class SlixTest(unittest.TestCase):
|
|||||||
'id', 'stanzapath', 'xpath', and 'mask'.
|
'id', 'stanzapath', 'xpath', and 'mask'.
|
||||||
Defaults to the value of self.match_method.
|
Defaults to the value of self.match_method.
|
||||||
"""
|
"""
|
||||||
|
self.wait_for_send_queue()
|
||||||
sent = self.xmpp.socket.next_sent(timeout)
|
sent = self.xmpp.socket.next_sent(timeout)
|
||||||
if data is None and sent is None:
|
if data is None and sent is None:
|
||||||
return
|
return
|
||||||
@@ -608,6 +617,14 @@ class SlixTest(unittest.TestCase):
|
|||||||
defaults=defaults,
|
defaults=defaults,
|
||||||
use_values=use_values)
|
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):
|
def stream_close(self):
|
||||||
"""
|
"""
|
||||||
Disconnect the dummy XMPP client.
|
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]
|
return _fixed_offset_tzs[offsetmins]
|
||||||
|
|
||||||
|
|
||||||
_iso8601_parser = re.compile("""
|
_iso8601_parser = re.compile(r"""
|
||||||
^
|
^
|
||||||
(?P<year> [0-9]{4})?(?P<ymdsep>-?)?
|
(?P<year> [0-9]{4})?(?P<ymdsep>-?)?
|
||||||
(?P<month>[0-9]{2})?(?P=ymdsep)?
|
(?P<month>[0-9]{2})?(?P=ymdsep)?
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
# We don't want to have to import the entire library
|
# We don't want to have to import the entire library
|
||||||
# just to get the version info for setup.py
|
# just to get the version info for setup.py
|
||||||
|
|
||||||
__version__ = '1.4.2'
|
__version__ = '1.5.0'
|
||||||
__version_info__ = (1, 4, 2)
|
__version_info__ = (1, 5, 0)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from __future__ import with_statement, unicode_literals
|
|||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import weakref
|
import weakref
|
||||||
from xml.etree import cElementTree as ET
|
from xml.etree import ElementTree as ET
|
||||||
|
|
||||||
from slixmpp.xmlstream import JID
|
from slixmpp.xmlstream import JID
|
||||||
from slixmpp.xmlstream.tostring import tostring
|
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
|
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
|
access to the XML contents through dictionary syntax, similar in style
|
||||||
to the Ruby XMPP library Blather's stanza implementation.
|
to the Ruby XMPP library Blather's stanza implementation.
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ class ElementBase(object):
|
|||||||
self._index = 0
|
self._index = 0
|
||||||
|
|
||||||
#: The underlying XML object for the stanza. It is a standard
|
#: The underlying XML object for the stanza. It is a standard
|
||||||
#: :class:`xml.etree.cElementTree` object.
|
#: :class:`xml.etree.ElementTree` object.
|
||||||
self.xml = xml
|
self.xml = xml
|
||||||
|
|
||||||
#: An ordered dictionary of plugin stanzas, mapped by their
|
#: An ordered dictionary of plugin stanzas, mapped by their
|
||||||
@@ -1031,14 +1031,19 @@ class ElementBase(object):
|
|||||||
if not lang:
|
if not lang:
|
||||||
lang = default_lang
|
lang = default_lang
|
||||||
|
|
||||||
|
parent = self.xml
|
||||||
for level, _ in enumerate(path):
|
for level, _ in enumerate(path):
|
||||||
# Generate the paths to the target elements and their parent.
|
# Generate the paths to the target elements and their parent.
|
||||||
element_path = "/".join(path[:len(path) - level])
|
element_path = "/".join(path[:len(path) - level])
|
||||||
parent_path = "/".join(path[:len(path) - level - 1])
|
parent_path = "/".join(path[:len(path) - level - 1])
|
||||||
|
|
||||||
elements = self.xml.findall(element_path)
|
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 elements:
|
||||||
if parent is None:
|
if parent is None:
|
||||||
parent = self.xml
|
parent = self.xml
|
||||||
@@ -1374,14 +1379,6 @@ class StanzaBase(ElementBase):
|
|||||||
#: The default XMPP client namespace
|
#: The default XMPP client namespace
|
||||||
namespace = 'jabber:client'
|
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,
|
def __init__(self, stream=None, xml=None, stype=None,
|
||||||
sto=None, sfrom=None, sid=None, parent=None):
|
sto=None, sfrom=None, sid=None, parent=None):
|
||||||
self.stream = stream
|
self.stream = stream
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
:license: MIT, see LICENSE for more details
|
:license: MIT, see LICENSE for more details
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from typing import Optional, Set, Callable
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import socket as Socket
|
import socket as Socket
|
||||||
@@ -19,6 +21,8 @@ import ssl
|
|||||||
import weakref
|
import weakref
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
from asyncio import iscoroutinefunction, wait
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from slixmpp.xmlstream.asyncio import asyncio
|
from slixmpp.xmlstream.asyncio import asyncio
|
||||||
@@ -30,6 +34,10 @@ from slixmpp.xmlstream.resolver import resolve, default_resolver
|
|||||||
RESPONSE_TIMEOUT = 30
|
RESPONSE_TIMEOUT = 30
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
class ContinueQueue(Exception):
|
||||||
|
"""
|
||||||
|
Exception raised in the send queue to "continue" from within an inner loop
|
||||||
|
"""
|
||||||
|
|
||||||
class NotConnectedError(Exception):
|
class NotConnectedError(Exception):
|
||||||
"""
|
"""
|
||||||
@@ -81,6 +89,8 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.force_starttls = None
|
self.force_starttls = None
|
||||||
self.disable_starttls = None
|
self.disable_starttls = None
|
||||||
|
|
||||||
|
self.waiting_queue = asyncio.Queue()
|
||||||
|
|
||||||
# A dict of {name: handle}
|
# A dict of {name: handle}
|
||||||
self.scheduled_events = {}
|
self.scheduled_events = {}
|
||||||
|
|
||||||
@@ -199,11 +209,6 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.__event_handlers = {}
|
self.__event_handlers = {}
|
||||||
self.__filters = {'in': [], 'out': [], 'out_sync': []}
|
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)
|
# Current connection attempt (Future)
|
||||||
self._current_connection_attempt = None
|
self._current_connection_attempt = None
|
||||||
|
|
||||||
@@ -241,12 +246,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
ID values. Using this method ensures that all new ID values
|
ID values. Using this method ensures that all new ID values
|
||||||
are unique in this stream.
|
are unique in this stream.
|
||||||
"""
|
"""
|
||||||
self._id += 1
|
return uuid.uuid4().hex
|
||||||
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)
|
|
||||||
|
|
||||||
def connect(self, host='', port=0, use_ssl=False,
|
def connect(self, host='', port=0, use_ssl=False,
|
||||||
force_starttls=True, disable_starttls=False):
|
force_starttls=True, disable_starttls=False):
|
||||||
@@ -271,8 +271,13 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
localhost
|
localhost
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
asyncio.ensure_future(
|
||||||
|
self.run_filters(),
|
||||||
|
loop=self.loop,
|
||||||
|
)
|
||||||
self.disconnect_reason = None
|
self.disconnect_reason = None
|
||||||
self.cancel_connection_attempt()
|
self.cancel_connection_attempt()
|
||||||
|
self.connect_loop_wait = 0
|
||||||
if host and port:
|
if host and port:
|
||||||
self.address = (host, int(port))
|
self.address = (host, int(port))
|
||||||
try:
|
try:
|
||||||
@@ -297,6 +302,10 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
async def _connect_routine(self):
|
async def _connect_routine(self):
|
||||||
self.event_when_connected = "connected"
|
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)
|
record = await self.pick_dns_answer(self.default_domain)
|
||||||
if record is not None:
|
if record is not None:
|
||||||
host, address, dns_port = record
|
host, address, dns_port = record
|
||||||
@@ -313,7 +322,6 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
else:
|
else:
|
||||||
ssl_context = None
|
ssl_context = None
|
||||||
|
|
||||||
await asyncio.sleep(self.connect_loop_wait, loop=self.loop)
|
|
||||||
if self._current_connection_attempt is None:
|
if self._current_connection_attempt is None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -372,6 +380,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
"ssl_object",
|
"ssl_object",
|
||||||
default=self.transport.get_extra_info("socket")
|
default=self.transport.get_extra_info("socket")
|
||||||
)
|
)
|
||||||
|
self._current_connection_attempt = None
|
||||||
self.init_parser()
|
self.init_parser()
|
||||||
self.send_raw(self.stream_header)
|
self.send_raw(self.stream_header)
|
||||||
self.dns_answers = None
|
self.dns_answers = None
|
||||||
@@ -430,6 +439,9 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.send(error)
|
self.send(error)
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
|
|
||||||
|
def is_connecting(self):
|
||||||
|
return self._current_connection_attempt is not None
|
||||||
|
|
||||||
def is_connected(self):
|
def is_connected(self):
|
||||||
return self.transport is not None
|
return self.transport is not None
|
||||||
|
|
||||||
@@ -463,10 +475,10 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self._current_connection_attempt.cancel()
|
self._current_connection_attempt.cancel()
|
||||||
self._current_connection_attempt = None
|
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
|
"""Close the XML stream and wait for an acknowldgement from the server for
|
||||||
at most `wait` seconds. After the given number of seconds has
|
at most `wait` seconds. After the given number of seconds has
|
||||||
passed without a response from the serveur, or when the server
|
passed without a response from the server, or when the server
|
||||||
successfully responds with a closure of its own stream, abort() is
|
successfully responds with a closure of its own stream, abort() is
|
||||||
called. If wait is 0.0, this will call abort() directly without closing
|
called. If wait is 0.0, this will call abort() directly without closing
|
||||||
the stream.
|
the stream.
|
||||||
@@ -476,6 +488,13 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
:param wait: Time to wait for a response from the server.
|
: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.disconnect_reason = reason
|
||||||
self.cancel_connection_attempt()
|
self.cancel_connection_attempt()
|
||||||
if self.transport:
|
if self.transport:
|
||||||
@@ -483,6 +502,8 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.send_raw(self.stream_footer)
|
self.send_raw(self.stream_footer)
|
||||||
self.schedule('Disconnect wait', wait,
|
self.schedule('Disconnect wait', wait,
|
||||||
self.abort, repeat=False)
|
self.abort, repeat=False)
|
||||||
|
else:
|
||||||
|
self.event("disconnected", reason)
|
||||||
|
|
||||||
def abort(self):
|
def abort(self):
|
||||||
"""
|
"""
|
||||||
@@ -495,14 +516,15 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
self.event("killed")
|
self.event("killed")
|
||||||
self.disconnected.set_result(True)
|
self.disconnected.set_result(True)
|
||||||
self.disconnected = asyncio.Future()
|
self.disconnected = asyncio.Future()
|
||||||
|
self.event("disconnected", self.disconnect_reason)
|
||||||
|
|
||||||
def reconnect(self, wait=2.0, reason="Reconnecting"):
|
def reconnect(self, wait=2.0, reason="Reconnecting"):
|
||||||
"""Calls disconnect(), and once we are disconnected (after the timeout, or
|
"""Calls disconnect(), and once we are disconnected (after the timeout, or
|
||||||
when the server acknowledgement is received), call connect()
|
when the server acknowledgement is received), call connect()
|
||||||
"""
|
"""
|
||||||
log.debug("reconnecting...")
|
log.debug("reconnecting...")
|
||||||
self.disconnect(wait, reason)
|
|
||||||
self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True)
|
self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True)
|
||||||
|
self.disconnect(wait, reason)
|
||||||
|
|
||||||
def configure_socket(self):
|
def configure_socket(self):
|
||||||
"""Set timeout and other options for self.socket.
|
"""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
|
# If the callback is a coroutine, schedule it instead of
|
||||||
# running it directly
|
# running it directly
|
||||||
if asyncio.iscoroutinefunction(handler_callback):
|
if iscoroutinefunction(handler_callback):
|
||||||
async def handler_callback_routine(cb):
|
async def handler_callback_routine(cb):
|
||||||
try:
|
try:
|
||||||
await cb(data)
|
await cb(data)
|
||||||
@@ -889,11 +911,93 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
"""
|
"""
|
||||||
return xml
|
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):
|
def send(self, data, use_filters=True):
|
||||||
"""A wrapper for :meth:`send_raw()` for sending stanza objects.
|
"""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`
|
:param data: The :class:`~slixmpp.xmlstream.stanzabase.ElementBase`
|
||||||
stanza to send on the stream.
|
stanza to send on the stream.
|
||||||
:param bool use_filters: Indicates if outgoing filters should be
|
:param bool use_filters: Indicates if outgoing filters should be
|
||||||
@@ -901,24 +1005,7 @@ class XMLStream(asyncio.BaseProtocol):
|
|||||||
filters is useful when resending stanzas.
|
filters is useful when resending stanzas.
|
||||||
Defaults to ``True``.
|
Defaults to ``True``.
|
||||||
"""
|
"""
|
||||||
if isinstance(data, ElementBase):
|
self.waiting_queue.put_nowait((data, use_filters))
|
||||||
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)
|
|
||||||
|
|
||||||
def send_xml(self, data):
|
def send_xml(self, data):
|
||||||
"""Send an XML object on the stream
|
"""Send an XML object on the stream
|
||||||
|
|||||||
@@ -68,13 +68,5 @@ class TestStanzaBase(SlixTest):
|
|||||||
self.assertTrue(stanza['payload'] == [],
|
self.assertTrue(stanza['payload'] == [],
|
||||||
"Stanza reply did not empty 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)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestStanzaBase)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import sys
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
import unittest
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from slixmpp.test import *
|
from slixmpp.test import *
|
||||||
@@ -11,6 +12,7 @@ from slixmpp.xmlstream import ElementBase
|
|||||||
from slixmpp.plugins.xep_0323.device import Device
|
from slixmpp.plugins.xep_0323.device import Device
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skip('')
|
||||||
class TestStreamSensorData(SlixTest):
|
class TestStreamSensorData(SlixTest):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user