diff options
-rw-r--r-- | docs/getting_started/sendlogout.rst | 14 | ||||
-rw-r--r-- | slixmpp/plugins/__init__.py | 2 | ||||
-rw-r--r-- | slixmpp/plugins/protoxep_occupantid/__init__.py | 12 | ||||
-rw-r--r-- | slixmpp/plugins/protoxep_occupantid/occupantid.py | 23 | ||||
-rw-r--r-- | slixmpp/plugins/protoxep_occupantid/stanza.py | 16 | ||||
-rw-r--r-- | slixmpp/plugins/protoxep_reactions/__init__.py | 11 | ||||
-rw-r--r-- | slixmpp/plugins/protoxep_reactions/reactions.py | 54 | ||||
-rw-r--r-- | slixmpp/plugins/protoxep_reactions/stanza.py | 31 | ||||
-rw-r--r-- | slixmpp/test/slixtest.py | 7 | ||||
-rw-r--r-- | slixmpp/xmlstream/stanzabase.py | 8 | ||||
-rw-r--r-- | slixmpp/xmlstream/xmlstream.py | 19 | ||||
-rw-r--r-- | tests/test_stanza_base.py | 8 |
12 files changed, 171 insertions, 34 deletions
diff --git a/docs/getting_started/sendlogout.rst b/docs/getting_started/sendlogout.rst index a27976c5..3b5d6d5a 100644 --- a/docs/getting_started/sendlogout.rst +++ b/docs/getting_started/sendlogout.rst @@ -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 diff --git a/slixmpp/plugins/__init__.py b/slixmpp/plugins/__init__.py index d28cf281..716b15c3 100644 --- a/slixmpp/plugins/__init__.py +++ b/slixmpp/plugins/__init__.py @@ -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 ] diff --git a/slixmpp/plugins/protoxep_occupantid/__init__.py b/slixmpp/plugins/protoxep_occupantid/__init__.py new file mode 100644 index 00000000..1bd374b6 --- /dev/null +++ b/slixmpp/plugins/protoxep_occupantid/__init__.py @@ -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) diff --git a/slixmpp/plugins/protoxep_occupantid/occupantid.py b/slixmpp/plugins/protoxep_occupantid/occupantid.py new file mode 100644 index 00000000..7f4a9d4a --- /dev/null +++ b/slixmpp/plugins/protoxep_occupantid/occupantid.py @@ -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) diff --git a/slixmpp/plugins/protoxep_occupantid/stanza.py b/slixmpp/plugins/protoxep_occupantid/stanza.py new file mode 100644 index 00000000..e5853111 --- /dev/null +++ b/slixmpp/plugins/protoxep_occupantid/stanza.py @@ -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'} diff --git a/slixmpp/plugins/protoxep_reactions/__init__.py b/slixmpp/plugins/protoxep_reactions/__init__.py new file mode 100644 index 00000000..e107bd16 --- /dev/null +++ b/slixmpp/plugins/protoxep_reactions/__init__.py @@ -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) diff --git a/slixmpp/plugins/protoxep_reactions/reactions.py b/slixmpp/plugins/protoxep_reactions/reactions.py new file mode 100644 index 00000000..e7af8fcb --- /dev/null +++ b/slixmpp/plugins/protoxep_reactions/reactions.py @@ -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) diff --git a/slixmpp/plugins/protoxep_reactions/stanza.py b/slixmpp/plugins/protoxep_reactions/stanza.py new file mode 100644 index 00000000..45414a37 --- /dev/null +++ b/slixmpp/plugins/protoxep_reactions/stanza.py @@ -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) diff --git a/slixmpp/test/slixtest.py b/slixmpp/test/slixtest.py index 3953d77d..802df73c 100644 --- a/slixmpp/test/slixtest.py +++ b/slixmpp/test/slixtest.py @@ -340,6 +340,13 @@ 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 diff --git a/slixmpp/xmlstream/stanzabase.py b/slixmpp/xmlstream/stanzabase.py index 1c000b69..3e45f613 100644 --- a/slixmpp/xmlstream/stanzabase.py +++ b/slixmpp/xmlstream/stanzabase.py @@ -1374,14 +1374,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 diff --git a/slixmpp/xmlstream/xmlstream.py b/slixmpp/xmlstream/xmlstream.py index 98b0744c..9f6f3083 100644 --- a/slixmpp/xmlstream/xmlstream.py +++ b/slixmpp/xmlstream/xmlstream.py @@ -201,11 +201,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 @@ -243,12 +238,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): @@ -478,6 +468,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: diff --git a/tests/test_stanza_base.py b/tests/test_stanza_base.py index 35fa5e99..55554aa9 100644 --- a/tests/test_stanza_base.py +++ b/tests/test_stanza_base.py @@ -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) |