diff sat/plugins/plugin_comp_ap_gateway/http_server.py @ 3869:c0bcbcf5b4b7

component AP gateway: handle `Like` and `Undo`/`Like` activities: rel 370
author Goffi <goffi@goffi.org>
date Thu, 21 Jul 2022 18:07:35 +0200
parents aaa4e7815ba8
children bd84d289fc94
line wrap: on
line diff
--- a/sat/plugins/plugin_comp_ap_gateway/http_server.py	Thu Jul 21 18:05:20 2022 +0200
+++ b/sat/plugins/plugin_comp_ap_gateway/http_server.py	Thu Jul 21 18:07:35 2022 +0200
@@ -34,6 +34,7 @@
 from sat.core import exceptions
 from sat.core.constants import Const as C
 from sat.core.i18n import _
+from sat.core.core_types import SatXMPPEntity
 from sat.core.log import getLogger
 from sat.tools.common import date_utils, uri
 from sat.memory.sqla_mapping import SubscriptionState
@@ -41,7 +42,7 @@
 from .constants import (
     NS_AP, CONTENT_TYPE_AP, TYPE_ACTOR, TYPE_INBOX, TYPE_SHARED_INBOX, TYPE_OUTBOX,
     AP_REQUEST_TYPES, PAGE_SIZE, ACTIVITY_TYPES_LOWER, ACTIVIY_NO_ACCOUNT_ALLOWED,
-    SIGN_HEADERS, HS2019, SIGN_EXP, TYPE_FOLLOWERS, TYPE_FOLLOWING
+    SIGN_HEADERS, HS2019, SIGN_EXP, TYPE_FOLLOWERS, TYPE_FOLLOWING, TYPE_ITEM, TYPE_LIKE
 )
 from .regex import RE_SIG_PARAM
 
@@ -149,6 +150,8 @@
                 # we can use directly the Announce object, as only the "id" field is
                 # needed
                 await self.apg.newAPDeleteItem(client, None, node, obj)
+            elif type_ == TYPE_LIKE:
+                await self.handleNewLikeItem(client, obj, True)
             else:
                 log.warning(f"Unmanaged undo type: {type_!r}")
 
@@ -339,6 +342,108 @@
             repeated=True
         )
 
+    async def handleNewLikeItem(
+        self,
+        client: SatXMPPEntity,
+        data: dict,
+        undo: bool = False,
+    ) -> None:
+        liked_ids = data.get("object")
+        if not liked_ids:
+            raise exceptions.DataError("object should be set")
+        elif isinstance(liked_ids, list):
+            try:
+                liked_ids = [o["id"] for o in liked_ids]
+            except (KeyError, TypeError):
+                raise exceptions.DataError(f"invalid object: {liked_ids!r}")
+        elif isinstance(liked_ids, dict):
+            obj_id = liked_ids.get("id")
+            if not obj_id or not isinstance(obj_id, str):
+                raise exceptions.DataError(f"invalid object: {liked_ids!r}")
+            liked_ids = [obj_id]
+        elif isinstance(liked_ids, str):
+            liked_ids = [liked_ids]
+
+        for liked_id in liked_ids:
+            if not self.apg.isLocalURL(liked_id):
+                log.debug(f"ignoring non local liked ID: {liked_id}")
+                continue
+            url_type, url_args = self.apg.parseAPURL(liked_id)
+            if url_type != TYPE_ITEM:
+                log.warning(f"unexpected local URL for liked item: {liked_id}")
+                continue
+            try:
+                account, item_id = url_args
+            except ValueError:
+                raise ValueError(f"invalid URL: {liked_id}")
+            author_jid, item_node = await self.apg.getJIDAndNode(account)
+            if item_node is None:
+                item_node = self.apg._m.namespace
+            attachment_node = self.apg._pa.getAttachmentNodeName(
+                author_jid, item_node, item_id
+            )
+            cached_node = await self.apg.host.memory.storage.getPubsubNode(
+                client,
+                author_jid,
+                attachment_node,
+                with_subscriptions=True,
+                create=True
+            )
+            found_items, __ = await self.apg.host.memory.storage.getItems(
+                cached_node, item_ids=[item_id]
+            )
+            if not found_items:
+                old_item_elt = None
+            else:
+                found_item = found_items[0]
+                old_item_elt = found_item.data
+
+            item_elt = self.apg._pa.applySetHandler(
+                client,
+                {"extra": {"noticed": not undo}},
+                old_item_elt,
+                [("noticed", self.apg._pa.namespace)]
+            )
+            # we reparse the element, as there can be other attachments
+            attachments_data = self.apg._pa.items2attachmentData(client, [item_elt])
+            # and we update the cache
+            await self.apg.host.memory.storage.cachePubsubItems(
+                client,
+                cached_node,
+                [item_elt],
+                attachments_data or [{}]
+            )
+
+            if self.apg.isVirtualJID(author_jid):
+                # the attachment is on t a virtual pubsub service (linking to an AP item),
+                # we notify all subscribers
+                for subscription in cached_node.subscriptions:
+                    if subscription.state != SubscriptionState.SUBSCRIBED:
+                        continue
+                    self.apg.pubsub_service.notifyPublish(
+                        author_jid,
+                        attachment_node,
+                        [(subscription.subscriber, None, [item_elt])]
+                    )
+            else:
+                # the attachment is on an XMPP item, we publish it to the attachment node
+                await self.apg._p.sendItems(
+                    client, author_jid, attachment_node, [item_elt]
+                )
+
+    async def handleLikeActivity(
+        self,
+        request: "HTTPRequest",
+        data: dict,
+        account_jid: Optional[jid.JID],
+        node: Optional[str],
+        ap_account: Optional[str],
+        ap_url: str,
+        signing_actor: str
+    ):
+        client = await self.apg.getVirtualClient(signing_actor)
+        await self.handleNewLikeItem(client, data)
+
     async def APActorRequest(
         self,
         request: "HTTPRequest",
@@ -623,7 +728,7 @@
         )
         followers = []
         for subscriber in subscribers.keys():
-            if subscriber.host == self.apg.client.jid.userhost():
+            if self.apg.isVirtualJID(subscriber):
                 # the subscriber is an AP user subscribed with this gateway
                 ap_account = self.apg._e.unescape(subscriber.user)
             else:
@@ -660,7 +765,7 @@
         following = []
         for sub_dict in subscriptions:
             service = jid.JID(sub_dict["service"])
-            if service.host == self.apg.client.jid.userhost():
+            if self.apg.isVirtualJID(service):
                 # the subscription is to an AP actor with this gateway
                 ap_account = self.apg._e.unescape(service.user)
             else: