diff sat/plugins/plugin_exp_events.py @ 3462:12dc234f698c

plugin invitation: pubsub invitations: - new Pubsub invitation plugin, to have a generic way to manage invitation on Pubsub based features - `invitePreflight` and `onInvitationPreflight` method can be implemented to customise invitation for a namespace - refactored events invitations to use the new plugin - a Pubsub invitation can now be for a whole node instead of a specific item - if invitation is for a node, a namespace can be specified to indicate what this node is about. It is then added in `extra` data - an element (domish.Element) can be added in `extra` data, it will then be added in the invitation - some code modernisation
author Goffi <goffi@goffi.org>
date Fri, 19 Feb 2021 15:50:22 +0100
parents 559a625a236b
children be6d91572633
line wrap: on
line diff
--- a/sat/plugins/plugin_exp_events.py	Fri Feb 19 15:49:59 2021 +0100
+++ b/sat/plugins/plugin_exp_events.py	Fri Feb 19 15:50:22 2021 +0100
@@ -17,11 +17,13 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+from typing import Optional
 import shortuuid
 from sat.core.i18n import _
 from sat.core import exceptions
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
+from sat.core.xmpp import SatXMPPEntity
 from sat.tools import utils
 from sat.tools.common import uri as xmpp_uri
 from sat.tools.common import date_utils
@@ -41,7 +43,7 @@
     C.PI_IMPORT_NAME: "EVENTS",
     C.PI_TYPE: "EXP",
     C.PI_PROTOCOLS: [],
-    C.PI_DEPENDENCIES: ["XEP-0060", "INVITATION", "LIST_INTEREST"],
+    C.PI_DEPENDENCIES: ["XEP-0060", "INVITATION", "PUBSUB_INVITATION", "LIST_INTEREST"],
     C.PI_RECOMMENDATIONS: ["XEP-0277", "EMAIL_INVITATION"],
     C.PI_MAIN: "Events",
     C.PI_HANDLER: "yes",
@@ -61,8 +63,7 @@
         self._i = self.host.plugins.get("EMAIL_INVITATION")
         self._b = self.host.plugins.get("XEP-0277")
         self.host.registerNamespace("event", NS_EVENT)
-        self.host.plugins["INVITATION"].registerNamespace(NS_EVENT,
-                                                           self.register)
+        self.host.plugins["PUBSUB_INVITATION"].register(NS_EVENT, self)
         host.bridge.addMethod(
             "eventGet",
             ".plugin",
@@ -232,32 +233,6 @@
             raise exceptions.NotFound(_("No event with this id has been found"))
         defer.returnValue(event_elt)
 
-    def register(self, client, name, extra, service, node, event_id, item_elt,
-                 creator=False):
-        """Register evenement in personal events list
-
-        @param service(jid.JID): pubsub service of the event
-        @param node(unicode): event node
-        @param event_id(unicode): event id
-        @param event_elt(domish.Element): event element
-            note that this element will be modified in place
-        @param creator(bool): True if client's profile is the creator of the node
-        """
-        event_elt = item_elt.event
-        link_elt = event_elt.addElement("link")
-        link_elt["service"] = service.full()
-        link_elt["node"] = node
-        link_elt["item"] = event_id
-        __, event_data = self._parseEventElt(event_elt)
-        name = event_data.get('name')
-        if 'image' in event_data:
-            extra = {'thumb_url': event_data['image']}
-        else:
-            extra = None
-        return self.host.plugins['LIST_INTEREST'].registerPubsub(
-            client, NS_EVENT, service, node, event_id, creator,
-            name=name, element=event_elt, extra=extra)
-
     def _eventGet(self, service, node, id_="", profile_key=C.PROF_KEY_NONE):
         service = jid.JID(service) if service else None
         node = node if node else NS_EVENT
@@ -289,10 +264,11 @@
         node = node or None
         client = self.host.getClient(profile_key)
         data["register"] = C.bool(data.get("register", C.BOOL_FALSE))
-        return self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT)
+        return defer.ensureDeferred(
+            self.eventCreate(client, timestamp, data, service, node, id_ or NS_EVENT)
+        )
 
-    @defer.inlineCallbacks
-    def eventCreate(self, client, timestamp, data, service, node=None, event_id=NS_EVENT):
+    async def eventCreate(self, client, timestamp, data, service, node=None, event_id=NS_EVENT):
         """Create or replace an event
 
         @param service(jid.JID, None): PubSub service
@@ -341,8 +317,8 @@
                     del data[k]
             if key not in data:
                 # FIXME: affiliate invitees
-                uri_node = yield self._p.createNode(client, service)
-                yield self._p.setConfiguration(
+                uri_node = await self._p.createNode(client, service)
+                await self._p.setConfiguration(
                     client,
                     service,
                     uri_node,
@@ -372,17 +348,23 @@
         item_elt = pubsub.Item(id=event_id, payload=event_elt)
         try:
             # TODO: check auto-create, no need to create node first if available
-            node = yield self._p.createNode(client, service, nodeIdentifier=node)
+            node = await self._p.createNode(client, service, nodeIdentifier=node)
         except error.StanzaError as e:
             if e.condition == "conflict":
                 log.debug(_("requested node already exists"))
 
-        yield self._p.publish(client, service, node, items=[item_elt])
+        await self._p.publish(client, service, node, items=[item_elt])
 
         if register:
-            yield self.register(
-                client, None, {}, service, node, event_id, item_elt, creator=True)
-        defer.returnValue(node)
+            extra = {}
+            self.onInvitationPreflight(
+                client, "", extra, service, node, event_id, item_elt
+            )
+            await self.host.plugins['LIST_INTEREST'].registerPubsub(
+                client, NS_EVENT, service, node, event_id, True,
+                extra.pop("name", ""), extra.pop("element"), extra
+            )
+        return node
 
     def _eventModify(self, service, node, id_, timestamp_update, data_update,
                      profile_key=C.PROF_KEY_NONE):
@@ -390,12 +372,14 @@
         if not node:
             raise ValueError(_("missing node"))
         client = self.host.getClient(profile_key)
-        return self.eventModify(
-            client, service, node, id_ or NS_EVENT, timestamp_update or None, data_update
+        return defer.ensureDeferred(
+            self.eventModify(
+                client, service, node, id_ or NS_EVENT, timestamp_update or None,
+                data_update
+            )
         )
 
-    @defer.inlineCallbacks
-    def eventModify(
+    async def eventModify(
         self, client, service, node, id_=NS_EVENT, timestamp_update=None, data_update=None
     ):
         """Update an event
@@ -403,13 +387,13 @@
         Similar as create instead that it update existing item instead of
         creating or replacing it. Params are the same as for [eventCreate].
         """
-        event_timestamp, event_metadata = yield self.eventGet(client, service, node, id_)
+        event_timestamp, event_metadata = await self.eventGet(client, service, node, id_)
         new_timestamp = event_timestamp if timestamp_update is None else timestamp_update
         new_data = event_metadata
         if data_update:
             for k, v in data_update.items():
                 new_data[k] = v
-        yield self.eventCreate(client, new_timestamp, new_data, service, node, id_)
+        await self.eventCreate(client, new_timestamp, new_data, service, node, id_)
 
     def _eventsListSerialise(self, events):
         for timestamp, data in events:
@@ -543,52 +527,40 @@
                 invitees[item["id"]] = data
         defer.returnValue(invitees)
 
-    def _invite(self, invitee_jid, service, node, item_id, profile):
-        client = self.host.getClient(profile)
-        service = jid.JID(service) if service else None
-        node = node or None
-        item_id = item_id or None
-        invitee_jid = jid.JID(invitee_jid)
-        return self.invite(client, invitee_jid, service, node, item_id)
-
-    @defer.inlineCallbacks
-    def invite(self, client, invitee_jid, service, node, item_id=NS_EVENT):
-        """Invite an entity to the event
-
-        This will set permission to let the entity access everything needed
-        @pararm invitee_jid(jid.JID): entity to invite
-        @param service(jid.JID, None): pubsub service
-            None to use client's PEP
-        @param node(unicode): event node
-        @param item_id(unicode): event id
-        """
-        # FIXME: handle name and extra
-        name = ''
-        extra = {}
+    async def invitePreflight(
+        self,
+        client: SatXMPPEntity,
+        invitee_jid: jid.JID,
+        service: jid.JID,
+        node: str,
+        item_id: Optional[str] = None,
+        name: str = '',
+        extra: Optional[dict] = None,
+    ) -> None:
         if self._b is None:
             raise exceptions.FeatureNotFound(
                 _('"XEP-0277" (blog) plugin is needed for this feature')
             )
         if item_id is None:
-            item_id = NS_EVENT
+            item_id = extra["default_item_id"] = NS_EVENT
 
-        # first we authorize our invitee to see the nodes of interest
-        yield self._p.setNodeAffiliations(client, service, node, {invitee_jid: "member"})
-        log.debug(_("affiliation set on event node"))
-        __, event_data = yield self.eventGet(client, service, node, item_id)
+        __, event_data = await self.eventGet(client, service, node, item_id)
         log.debug(_("got event data"))
         invitees_service = jid.JID(event_data["invitees_service"])
         invitees_node = event_data["invitees_node"]
         blog_service = jid.JID(event_data["blog_service"])
         blog_node = event_data["blog_node"]
-        yield self._p.setNodeAffiliations(
+        await self._p.setNodeAffiliations(
             client, invitees_service, invitees_node, {invitee_jid: "publisher"}
         )
-        log.debug(_("affiliation set on invitee node"))
-        yield self._p.setNodeAffiliations(
+        log.debug(
+            f"affiliation set on invitee node (jid: {invitees_service}, "
+            f"node: {invitees_node!r})"
+        )
+        await self._p.setNodeAffiliations(
             client, blog_service, blog_node, {invitee_jid: "member"}
         )
-        blog_items, __ = yield self._b.mbGet(client, blog_service, blog_node, None)
+        blog_items, __ = await self._b.mbGet(client, blog_service, blog_node, None)
 
         for item in blog_items:
             try:
@@ -601,15 +573,15 @@
                     )
                 )
             else:
-                yield self._p.setNodeAffiliations(
+                await self._p.setNodeAffiliations(
                     client, comments_service, comments_node, {invitee_jid: "publisher"}
                 )
         log.debug(_("affiliation set on blog and comments nodes"))
 
-        # now we send the invitation
-        pubsub_invitation = self.host.plugins['INVITATION']
-        pubsub_invitation.sendPubsubInvitation(client, invitee_jid, service, node,
-                                               item_id, name, extra)
+    def _invite(self, invitee_jid, service, node, item_id, profile):
+        return self.host.plugins["PUBSUB_INVITATION"]._sendPubsubInvitation(
+            invitee_jid, service, node, item_id or NS_EVENT, profile_key=profile
+        )
 
     def _inviteByEmail(self, service, node, id_=NS_EVENT, email="", emails_extra=None,
                        name="", host_name="", language="", url_template="",
@@ -631,12 +603,11 @@
         ):
             value = locals()[key]
             kwargs[key] = str(value)
-        return self.inviteByEmail(
+        return defer.ensureDeferred(self.inviteByEmail(
             client, jid.JID(service) if service else None, node, id_ or NS_EVENT, **kwargs
-        )
+        ))
 
-    @defer.inlineCallbacks
-    def inviteByEmail(self, client, service, node, id_=NS_EVENT, **kwargs):
+    async def inviteByEmail(self, client, service, node, id_=NS_EVENT, **kwargs):
         """High level method to create an email invitation to an event
 
         @param service(unicode, None): PubSub service
@@ -656,11 +627,37 @@
             "pubsub", path=service.full(), node=node, item=id_
         )
         kwargs["extra"] = {"event_uri": event_uri}
-        invitation_data = yield self._i.create(**kwargs)
+        invitation_data = await self._i.create(**kwargs)
         invitee_jid = invitation_data["jid"]
         log.debug(_("invitation created"))
         # now that we have a jid, we can send normal invitation
-        yield self.invite(client, invitee_jid, service, node, id_)
+        await self.invite(client, invitee_jid, service, node, id_)
+
+    def onInvitationPreflight(
+        self,
+        client: SatXMPPEntity,
+        name: str,
+        extra: dict,
+        service: jid.JID,
+        node: str,
+        item_id: Optional[str],
+        item_elt: domish.Element
+    ) -> None:
+        event_elt = item_elt.event
+        link_elt = event_elt.addElement("link")
+        link_elt["service"] = service.full()
+        link_elt["node"] = node
+        link_elt["item"] = item_id
+        __, event_data = self._parseEventElt(event_elt)
+        try:
+            name = event_data["name"]
+        except KeyError:
+            pass
+        else:
+            extra["name"] = name
+        if 'image' in event_data:
+            extra["thumb_url"] = event_data['image']
+        extra["element"] = event_elt
 
 
 @implementer(iwokkel.IDisco)