diff sat/plugins/plugin_xep_0277.py @ 3308:384283adcce1

plugins XEP-0059, XEP-0060, XEP-0277, XEP-0313: better serialisation: `data_format.serialise` is now used for `mbGet`, and RSM/MAM values are not transtyped to strings anymore. A serialised dict is now used, items are put in the `items` key. Comments handling has been refactored to use a list for the potentially multiple comments nodes. `rsm` data are now in a `rsm` key of the dict, and `mam` data are merged with other metadata.
author Goffi <goffi@goffi.org>
date Thu, 16 Jul 2020 09:07:20 +0200
parents 84a94b385760
children d49607e3a066
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0277.py	Fri Jun 19 15:47:16 2020 +0200
+++ b/sat/plugins/plugin_xep_0277.py	Thu Jul 16 09:07:20 2020 +0200
@@ -103,7 +103,7 @@
             "mbGet",
             ".plugin",
             in_sign="ssiasa{ss}s",
-            out_sign="(asa{ss})",
+            out_sign="s",
             method=self._mbGet,
             async_=True,
         )
@@ -357,21 +357,25 @@
             )
 
         # links
+        comments = microblog_data['comments'] = []
         for link_elt in entry_elt.elements(NS_ATOM, "link"):
             if (
                 link_elt.getAttribute("rel") == "replies"
                 and link_elt.getAttribute("title") == "comments"
             ):
-                key = check_conflict("comments", True)
-                microblog_data[key] = link_elt["href"]
+                uri = link_elt["href"]
+                comments_data = {
+                    "uri": uri,
+                }
                 try:
-                    service, node = self.parseCommentUrl(microblog_data[key])
+                    service, node = self.parseCommentUrl(uri)
                 except Exception as e:
-                    log.warning(f"Can't parse url {microblog_data[key]}: {e}")
-                    del microblog_data[key]
+                    log.warning(f"Can't parse comments url: {e}")
+                    continue
                 else:
-                    microblog_data["{}_service".format(key)] = service.full()
-                    microblog_data["{}_node".format(key)] = node
+                    comments_data["service"] = service.full()
+                    comments_data["node"] = node
+                comments.append(comments_data)
             else:
                 rel = link_elt.getAttribute("rel", "")
                 title = link_elt.getAttribute("title", "")
@@ -589,9 +593,10 @@
         entry_elt.addElement("id", content=entry_id)  #
 
         ## comments ##
-        if "comments" in data:
+        for comments_data in data.get('comments', []):
             link_elt = entry_elt.addElement("link")
-            link_elt["href"] = data["comments"]
+            # XXX: "uri" is set in self._manageComments if not already existing
+            link_elt["href"] = comments_data["uri"]
             link_elt["rel"] = "replies"
             link_elt["title"] = "comments"
 
@@ -646,8 +651,8 @@
     def _manageComments(self, client, mb_data, service, node, item_id, access=None):
         """Check comments keys in mb_data and create comments node if necessary
 
-        if mb_data['comments'] exists, it is used (or mb_data['comments_service'] and/or mb_data['comments_node']),
-        else it is generated (if allow_comments is True).
+        if a comments node metadata is set in the mb_data['comments'] list, it is used
+        otherwise it is generated (if allow_comments is True).
         @param mb_data(dict): microblog mb_data
         @param service(jid.JID, None): PubSub service of the parent item
         @param node(unicode): node of the parent item
@@ -655,11 +660,13 @@
         @param access(unicode, None): access model
             None to use same access model as parent item
         """
-        # FIXME: if 'comments' already exists in mb_data,
-        #        it is not used to create the Node
         allow_comments = mb_data.pop("allow_comments", None)
         if allow_comments is None:
-            return
+            if "comments" in mb_data:
+                mb_data["allow_comments"] = True
+            else:
+                # no comments set or requested, nothing to do
+                return
         elif allow_comments == False:
             if "comments" in mb_data:
                 log.warning(
@@ -671,6 +678,13 @@
                 del mb_data["comments"]
             return
 
+        # we have usually a single comment node, but the spec allow several, so we need to
+        # handle this in a list
+        if len(mb_data.setdefault('comments', [])) == 0:
+            # we need at least one comment node
+            comments_data = {}
+            mb_data['comments'].append({})
+
         if access is None:
             # TODO: cache access models per service/node
             parent_node_config = yield self._p.getConfiguration(client, service, node)
@@ -689,63 +703,65 @@
         # if other plugins need to change the options
         yield self.host.trigger.point("XEP-0277_comments", client, mb_data, options)
 
-        try:
-            comments_node = mb_data["comments_node"]
-        except KeyError:
-            comments_node = self.getCommentsNode(item_id)
-        else:
-            if not comments_node:
-                raise exceptions.DataError(
-                    "if comments_node is present, it must not be empty"
+        for comments_data in mb_data['comments']:
+            uri = comments_data.get('uri')
+            comments_node = comments_data.get('node')
+            try:
+                comments_service = jid.JID(comments_data["service"])
+            except KeyError:
+                comments_service = None
+
+            if uri:
+                uri_service, uri_node = self.parseCommentUrl(uri)
+                if ((comments_node is not None and comments_node!=uri_node)
+                     or (comments_service is not None and comments_service!=uri_service)):
+                    raise ValueError(
+                        f"Incoherence between comments URI ({uri}) and comments_service "
+                        f"({comments_service}) or comments_node ({comments_node})")
+                comments_data['service'] = comments_service = uri_service
+                comments_data['node'] = comments_node = uri_node
+            else:
+                if not comments_node:
+                    comments_node = self.getCommentsNode(item_id)
+                comments_data['node'] = comments_node
+                if comments_service is None:
+                    comments_service = yield self.getCommentsService(client, service)
+                    if comments_service is None:
+                        comments_service = client.jid.userhostJID()
+                comments_data['service'] = comments_service
+
+                comments_data['uri'] = xmpp_uri.buildXMPPUri(
+                    "pubsub",
+                    path=comments_service.full(),
+                    node=comments_node,
                 )
 
-        try:
-            comments_service = jid.JID(mb_data["comments_service"])
-        except KeyError:
-            comments_service = yield self.getCommentsService(client, service)
-
-        try:
-            yield self._p.createNode(client, comments_service, comments_node, options)
-        except error.StanzaError as e:
-            if e.condition == "conflict":
-                log.info(
-                    "node {} already exists on service {}".format(
-                        comments_node, comments_service
+            try:
+                yield self._p.createNode(client, comments_service, comments_node, options)
+            except error.StanzaError as e:
+                if e.condition == "conflict":
+                    log.info(
+                        "node {} already exists on service {}".format(
+                            comments_node, comments_service
+                        )
                     )
-                )
+                else:
+                    raise e
             else:
-                raise e
-        else:
-            if access == self._p.ACCESS_WHITELIST:
-                # for whitelist access we need to copy affiliations from parent item
-                comments_affiliations = yield self._p.getNodeAffiliations(
-                    client, service, node
-                )
-                # …except for "member", that we transform to publisher
-                # because we wants members to be able to write to comments
-                for jid_, affiliation in list(comments_affiliations.items()):
-                    if affiliation == "member":
-                        comments_affiliations[jid_] == "publisher"
+                if access == self._p.ACCESS_WHITELIST:
+                    # for whitelist access we need to copy affiliations from parent item
+                    comments_affiliations = yield self._p.getNodeAffiliations(
+                        client, service, node
+                    )
+                    # …except for "member", that we transform to publisher
+                    # because we wants members to be able to write to comments
+                    for jid_, affiliation in list(comments_affiliations.items()):
+                        if affiliation == "member":
+                            comments_affiliations[jid_] == "publisher"
 
-                yield self._p.setNodeAffiliations(
-                    client, comments_service, comments_node, comments_affiliations
-                )
-
-        if comments_service is None:
-            comments_service = client.jid.userhostJID()
-
-        if "comments" in mb_data:
-            if not mb_data["comments"]:
-                raise exceptions.DataError(
-                    "if comments is present, it must not be empty"
-                )
-            if "comments_node" in mb_data or "comments_service" in mb_data:
-                raise exceptions.DataError(
-                    "You can't use comments_service/comments_node and comments at the "
-                    "same time"
-                )
-        else:
-            mb_data["comments"] = self._p.getNodeURI(comments_service, comments_node)
+                    yield self._p.setNodeAffiliations(
+                        client, comments_service, comments_node, comments_affiliations
+                    )
 
     def _mbSend(self, service, node, data, profile_key):
         service = jid.JID(service) if service else None
@@ -796,8 +812,8 @@
 
     def _mbGetSerialise(self, data):
         items, metadata = data
-        items = [data_format.serialise(item) for item in items]
-        return items, metadata
+        metadata['items'] = items
+        return data_format.serialise(metadata)
 
     def _mbGet(self, service="", node="", max_items=10, item_ids=None, extra_dict=None,
                profile_key=C.PROF_KEY_NONE):
@@ -840,7 +856,8 @@
             rsm_request=rsm_request,
             extra=extra,
         )
-        mb_data = yield self._p.transItemsDataD(items_data, self.item2mbdata)
+        mb_data = yield self._p.transItemsDataD(
+            items_data, self.item2mbdata)
         defer.returnValue(mb_data)
 
     def parseCommentUrl(self, node_url):
@@ -1259,7 +1276,8 @@
                 client, service, node, max_items, rsm_request=rsm_request, extra=extra
             )
             d.addCallback(
-                lambda items_data: self._p.transItemsDataD(items_data, self.item2mbdata)
+                lambda items_data: self._p.transItemsDataD(
+                    items_data, self.item2mbdata)
             )
             d.addCallback(getComments)
             d.addCallback(lambda items_comments_data: ("", items_comments_data))