diff --git a/doap.xml b/doap.xml index c22e64e9..20771694 100644 --- a/doap.xml +++ b/doap.xml @@ -909,6 +909,14 @@ no thumbnail support + + + + complete + 0.1.0 + 1.8.6 + + diff --git a/slixmpp/plugins/__init__.py b/slixmpp/plugins/__init__.py index e0c8f38d..eeb65084 100644 --- a/slixmpp/plugins/__init__.py +++ b/slixmpp/plugins/__init__.py @@ -121,6 +121,7 @@ PLUGINS = [ 'xep_0447', # Stateless file sharing 'xep_0461', # Message Replies 'xep_0469', # Bookmarks Pinning + 'xep_0490', # Message Displayed Synchronization # Meant to be imported by plugins ] diff --git a/slixmpp/plugins/xep_0223.py b/slixmpp/plugins/xep_0223.py index 6ed39285..2a684299 100644 --- a/slixmpp/plugins/xep_0223.py +++ b/slixmpp/plugins/xep_0223.py @@ -20,6 +20,18 @@ class XEP_0223(BasePlugin): """ XEP-0223: Persistent Storage of Private Data via PubSub + + If a specific pubsub node requires additional publish options, edit the + :attr:`.node_profile` attribute of this plugin: + + .. code-block:: python + + self.xmpp.plugin["xep_0223"].node_profiles["urn:some:node"] = { + "pubsub#max_items" = "max" + } + + This makes :meth:`.store` add these publish options whenever it is called + for the ``urn:some:node`` node. """ name = 'xep_0223' @@ -28,6 +40,7 @@ class XEP_0223(BasePlugin): profile = {'pubsub#persist_items': True, 'pubsub#access_model': 'whitelist'} + node_profiles = dict[str, dict[str, str]]() def configure(self, node: str, **iqkwargs) -> Future: """ @@ -70,7 +83,8 @@ class XEP_0223(BasePlugin): value='http://jabber.org/protocol/pubsub#publish-options') fields = options['fields'] - for field, value in self.profile.items(): + profile = self.profile | self.node_profiles.get(node, {}) + for field, value in profile.items(): if field not in fields: options.add_field(var=field) options.get_fields()[field]['value'] = value diff --git a/slixmpp/plugins/xep_0490/__init__.py b/slixmpp/plugins/xep_0490/__init__.py new file mode 100644 index 00000000..2706a307 --- /dev/null +++ b/slixmpp/plugins/xep_0490/__init__.py @@ -0,0 +1,8 @@ +from slixmpp.plugins.base import register_plugin + +from . import stanza +from .mds import XEP_0490 + +register_plugin(XEP_0490) + +__all__ = ['stanza', 'XEP_0490'] diff --git a/slixmpp/plugins/xep_0490/mds.py b/slixmpp/plugins/xep_0490/mds.py new file mode 100644 index 00000000..a4b6f0c7 --- /dev/null +++ b/slixmpp/plugins/xep_0490/mds.py @@ -0,0 +1,42 @@ +from asyncio import Future + +from slixmpp import Iq +from slixmpp.plugins import BasePlugin +from slixmpp.types import JidStr + +from . import stanza +from ..xep_0004 import Form + + +class XEP_0490(BasePlugin): + """ + XEP-0490: Message Displayed Synchronization + """ + + name = "xep_0490" + description = "XEP-0490: Message Displayed Synchronization" + dependencies = {"xep_0060", "xep_0163", "xep_0223", "xep_0359"} + stanza = stanza + + def plugin_init(self): + stanza.register_plugin() + self.xmpp.plugin["xep_0163"].register_pep( + "message_displayed_synchronization", + stanza.Displayed, + ) + self.xmpp.plugin["xep_0223"].node_profiles[self.stanza.NS] = { + "pubsub#max_items": "max", + "pubsub#send_last_published_item": "never", + } + + def flag_chat(self, chat: JidStr, stanza_id: str, **kwargs) -> Future[Iq]: + displayed = stanza.Displayed() + displayed["stanza_id"]["id"] = stanza_id + return self.xmpp.plugin["xep_0223"].store( + displayed, node=stanza.NS, id=str(chat), **kwargs + ) + + def catch_up(self, **kwargs): + return self.xmpp.plugin["xep_0060"].get_items( + self.xmpp.boundjid.bare, stanza.NS, **kwargs + ) diff --git a/slixmpp/plugins/xep_0490/stanza.py b/slixmpp/plugins/xep_0490/stanza.py new file mode 100644 index 00000000..42b50269 --- /dev/null +++ b/slixmpp/plugins/xep_0490/stanza.py @@ -0,0 +1,17 @@ +from slixmpp import register_stanza_plugin +from slixmpp.plugins.xep_0060.stanza import Item +from slixmpp.xmlstream import ElementBase +from slixmpp.plugins.xep_0359.stanza import StanzaID + +NS = "urn:xmpp:mds:displayed:0" + + +class Displayed(ElementBase): + namespace = NS + name = "displayed" + plugin_attrib = "displayed" + + +def register_plugin(): + register_stanza_plugin(Displayed, StanzaID) + register_stanza_plugin(Item, Displayed) diff --git a/slixmpp/pluginsdict.py b/slixmpp/pluginsdict.py index 4e5b7b9e..80f6a96a 100644 --- a/slixmpp/pluginsdict.py +++ b/slixmpp/pluginsdict.py @@ -103,6 +103,7 @@ from slixmpp.plugins.xep_0437 import XEP_0437 from slixmpp.plugins.xep_0439 import XEP_0439 from slixmpp.plugins.xep_0444 import XEP_0444 from slixmpp.plugins.xep_0461 import XEP_0461 +from slixmpp.plugins.xep_0490 import XEP_0490 class PluginsDict(TypedDict): @@ -199,3 +200,4 @@ class PluginsDict(TypedDict): xep_0439: XEP_0439 xep_0444: XEP_0444 xep_0461: XEP_0461 + xep_0490: XEP_0490 diff --git a/tests/test_stream_xep_0490.py b/tests/test_stream_xep_0490.py new file mode 100644 index 00000000..4932a9b3 --- /dev/null +++ b/tests/test_stream_xep_0490.py @@ -0,0 +1,135 @@ +import unittest.mock + +from slixmpp.test import SlixTest +# from slixmpp.plugins import xep_0490 + + +class TestMessageDisplaySynchronization(SlixTest): + def setUp(self): + self.stream_start(jid="juliet@capulet.lit", plugins={"xep_0490"}) + + def test_catch_up(self): + future = self.xmpp.plugin["xep_0490"].catch_up() + self.send( # language=XML + """ + + + + + + """ + ) + self.recv( # language=XML + """ + + + + + + + + + + + + + + + + + """ + ) + iq = future.result() + item = list(iq["pubsub"]["items"]) + self.assertEqual(item[0]["id"], "romeo@montegue.lit") + self.assertEqual( + item[0]["displayed"]["stanza_id"]["id"], + "0f710f2b-52ed-4d52-b928-784dad74a52b", + ) + + self.assertEqual(item[1]["id"], "example@conference.shakespeare.lit") + self.assertEqual( + item[1]["displayed"]["stanza_id"]["id"], + "ca21deaf-812c-48f1-8f16-339a674f2864", + ) + + def test_flag_chat(self): + self.xmpp.plugin["xep_0490"].flag_chat( + "romeo@montegue.lit", "0f710f2b-52ed-4d52-b928-784dad74a52b" + ) + self.send( # language=XML + """ + + + + + + + + + + + + + http://jabber.org/protocol/pubsub#publish-options + + + 1 + + + max + + + never + + + whitelist + + + + + + """, + use_values=False, + ) + + def test_notification(self): + handler = unittest.mock.Mock() + + self.xmpp.add_event_handler( + "message_displayed_synchronization_publish", handler + ) + self.recv( # language=XML + """ + + + + + + + + + + + + """ + ) + handler.assert_called() + msg = handler.call_args[0][0] + self.assertEqual( + msg["pubsub_event"]["items"]["item"]["id"], "romeo@montegue.lit" + ) + self.assertEqual( + msg["pubsub_event"]["items"]["item"]["displayed"]["stanza_id"]["id"], + "0423e3a9-d516-493d-bb06-bee0e51ab9fb", + ) + + +suite = unittest.TestLoader().loadTestsFromTestCase(TestMessageDisplaySynchronization)