diff sat/plugins/plugin_comp_ap_gateway/http_server.py @ 3888:aa7197b67c26

component AP gateway: AP <=> XMPP reactions conversions: - Pubsub Attachments plugin has been renamed to XEP-0470 following publication - XEP-0470 has been updated to follow 0.2 changes - AP reactions (as implemented in Pleroma) are converted to XEP-0470 - XEP-0470 events are converted to AP reactions (again, using "EmojiReact" from Pleroma) - AP activities related to attachments (like/reactions) are cached in Libervia because it's not possible to retrieve them from Pleroma instances once they have been emitted (doing an HTTP get on their ID returns a 404). For now those cache are not flushed, this should be improved in the future. - `sharedInbox` is used when available. Pleroma returns a 500 HTTP error when ``to`` or ``cc`` are used in a direct inbox. - reactions and like are not currently used for direct messages, because they can't be emitted from Pleroma in this case, thus there is no point in implementing them for the moment. rel 371
author Goffi <goffi@goffi.org>
date Wed, 31 Aug 2022 17:07:03 +0200
parents cea52400623d
children 0aa7023dcd08
line wrap: on
line diff
--- a/sat/plugins/plugin_comp_ap_gateway/http_server.py	Wed Aug 31 17:07:03 2022 +0200
+++ b/sat/plugins/plugin_comp_ap_gateway/http_server.py	Wed Aug 31 17:07:03 2022 +0200
@@ -42,7 +42,8 @@
 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, TYPE_ITEM, TYPE_LIKE
+    SIGN_HEADERS, HS2019, SIGN_EXP, TYPE_FOLLOWERS, TYPE_FOLLOWING, TYPE_ITEM, TYPE_LIKE,
+    TYPE_REACTION, ST_AP_CACHE
 )
 from .regex import RE_SIG_PARAM
 
@@ -124,7 +125,19 @@
         if node is None:
             node = self.apg._m.namespace
         client = await self.apg.getVirtualClient(signing_actor)
-        objects = await self.apg.apGetList(data, "object")
+        object_ = data.get("object")
+        if isinstance(object_, str):
+            # we check first if it's not a cached object
+            ap_cache_key = f"{ST_AP_CACHE}{object_}"
+            value = await self.apg.client._ap_storage.get(ap_cache_key)
+        else:
+            value = None
+        if value is not None:
+            objects = [value]
+            # because we'll undo the activity, we can remove it from cache
+            await self.apg.client._ap_storage.remove(ap_cache_key)
+        else:
+            objects = await self.apg.apGetList(data, "object")
         for obj in objects:
             type_ = obj.get("type")
             actor = await self.apg.apGetSenderActor(obj)
@@ -152,7 +165,11 @@
                 # needed
                 await self.apg.newAPDeleteItem(client, None, node, obj)
             elif type_ == TYPE_LIKE:
-                await self.handleNewLikeItem(client, obj, True)
+                await self.handleAttachmentItem(client, obj, {"noticed": False})
+            elif type_ == TYPE_REACTION:
+                await self.handleAttachmentItem(client, obj, {
+                    "reactions": {"operation": "update", "remove": [obj["content"]]}
+                })
             else:
                 log.warning(f"Unmanaged undo type: {type_!r}")
 
@@ -184,7 +201,7 @@
             if subscription.state != "subscribed":
                 # other states should raise an Exception
                 raise exceptions.InternalError('"subscribed" state was expected')
-            inbox = await self.apg.getAPInboxFromId(signing_actor)
+            inbox = await self.apg.getAPInboxFromId(signing_actor, use_shared=False)
             actor_id = self.apg.buildAPURL(TYPE_ACTOR, ap_account)
             accept_data = self.apg.createActivity(
                 "Accept", actor_id, object_=data
@@ -370,40 +387,47 @@
             repeated=True
         )
 
-    async def handleNewLikeItem(
+    async def handleAttachmentItem(
         self,
         client: SatXMPPEntity,
         data: dict,
-        undo: bool = False,
+        attachment_data: dict
     ) -> None:
-        liked_ids = data.get("object")
-        if not liked_ids:
+        target_ids = data.get("object")
+        if not target_ids:
             raise exceptions.DataError("object should be set")
-        elif isinstance(liked_ids, list):
+        elif isinstance(target_ids, list):
             try:
-                liked_ids = [o["id"] for o in liked_ids]
+                target_ids = [o["id"] for o in target_ids]
             except (KeyError, TypeError):
-                raise exceptions.DataError(f"invalid object: {liked_ids!r}")
-        elif isinstance(liked_ids, dict):
-            obj_id = liked_ids.get("id")
+                raise exceptions.DataError(f"invalid object: {target_ids!r}")
+        elif isinstance(target_ids, dict):
+            obj_id = target_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]
+                raise exceptions.DataError(f"invalid object: {target_ids!r}")
+            target_ids = [obj_id]
+        elif isinstance(target_ids, str):
+            target_ids = [target_ids]
 
-        for liked_id in liked_ids:
-            if not self.apg.isLocalURL(liked_id):
-                log.debug(f"ignoring non local liked ID: {liked_id}")
+        # XXX: we have to cache AP items because some implementation (Pleroma notably)
+        #   don't keep object accessible, and we need to be able to retrieve them for
+        #   UNDO. Current implementation will grow, we need to add a way to flush it after
+        #   a while.
+        # TODO: add a way to flush old cached AP items.
+        await client._ap_storage.aset(f"{ST_AP_CACHE}{data['id']}", data)
+
+        for target_id in target_ids:
+            if not self.apg.isLocalURL(target_id):
+                log.debug(f"ignoring non local target ID: {target_id}")
                 continue
-            url_type, url_args = self.apg.parseAPURL(liked_id)
+            url_type, url_args = self.apg.parseAPURL(target_id)
             if url_type != TYPE_ITEM:
-                log.warning(f"unexpected local URL for liked item: {liked_id}")
+                log.warning(f"unexpected local URL for attachment on item {target_id}")
                 continue
             try:
                 account, item_id = url_args
             except ValueError:
-                raise ValueError(f"invalid URL: {liked_id}")
+                raise ValueError(f"invalid URL: {target_id}")
             author_jid, item_node = await self.apg.getJIDAndNode(account)
             if item_node is None:
                 item_node = self.apg._m.namespace
@@ -418,7 +442,7 @@
                 create=True
             )
             found_items, __ = await self.apg.host.memory.storage.getItems(
-                cached_node, item_ids=[item_id]
+                cached_node, item_ids=[client.jid.userhost()]
             )
             if not found_items:
                 old_item_elt = None
@@ -428,9 +452,9 @@
 
             item_elt = self.apg._pa.applySetHandler(
                 client,
-                {"extra": {"noticed": not undo}},
+                {"extra": attachment_data},
                 old_item_elt,
-                [("noticed", self.apg._pa.namespace)]
+                None
             )
             # we reparse the element, as there can be other attachments
             attachments_data = self.apg._pa.items2attachmentData(client, [item_elt])
@@ -468,9 +492,24 @@
         ap_account: Optional[str],
         ap_url: str,
         signing_actor: str
-    ):
+    ) -> None:
         client = await self.apg.getVirtualClient(signing_actor)
-        await self.handleNewLikeItem(client, data)
+        await self.handleAttachmentItem(client, data, {"noticed": True})
+
+    async def handleEmojireactActivity(
+        self,
+        request: "HTTPRequest",
+        data: dict,
+        account_jid: Optional[jid.JID],
+        node: Optional[str],
+        ap_account: Optional[str],
+        ap_url: str,
+        signing_actor: str
+    ) -> None:
+        client = await self.apg.getVirtualClient(signing_actor)
+        await self.handleAttachmentItem(client, data, {
+            "reactions": {"operation": "update", "add": [data["content"]]}
+        })
 
     async def APActorRequest(
         self,