diff sat/plugins/plugin_comp_ap_gateway/http_server.py @ 3904:0aa7023dcd08

component AP gateway: events: - XMPP Events <=> AP Events conversion - `Join`/`Leave` activities are converted to RSVP attachments and vice versa - fix caching/notification on item published on a virtual pubsub node - add Ad-Hoc command to convert XMPP Jid/Node to virtual AP Account - handle `Update` activity - on `convertAndPostItems`, `Update` activity is used instead of `Create` if a version of the item is already present in cache - `events` field is added to actor data (and to `endpoints`), it links the `outbox` of the actor mapping the same JID with the Events node (i.e. it links to the Events node of the entity) - fix subscription to nodes which are not the microblog one rel 372
author Goffi <goffi@goffi.org>
date Thu, 22 Sep 2022 00:01:41 +0200
parents aa7197b67c26
children 6fa4ca0c047e
line wrap: on
line diff
--- a/sat/plugins/plugin_comp_ap_gateway/http_server.py	Wed Sep 21 22:43:55 2022 +0200
+++ b/sat/plugins/plugin_comp_ap_gateway/http_server.py	Thu Sep 22 00:01:41 2022 +0200
@@ -41,9 +41,9 @@
 
 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,
-    TYPE_REACTION, ST_AP_CACHE
+    TYPE_EVENT, 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, TYPE_REACTION, ST_AP_CACHE
 )
 from .regex import RE_SIG_PARAM
 
@@ -84,6 +84,7 @@
             f"internal error: {failure_.value}"
         )
         request.finish()
+        raise failure_
 
     async def webfinger(self, request):
         url_parsed = parse.urlparse(request.uri.decode())
@@ -295,11 +296,14 @@
                 f"happen. Ignoring object from {signing_actor}\n{data}"
             )
             raise exceptions.DataError("unexpected field in item")
-        if node is None:
-            node = self.apg._m.namespace
         client = await self.apg.getVirtualClient(signing_actor)
         objects = await self.apg.apGetList(data, "object")
         for obj in objects:
+            if node is None:
+                if obj.get("type") == TYPE_EVENT:
+                    node = self.apg._events.namespace
+                else:
+                    node = self.apg._m.namespace
             sender = await self.apg.apGetSenderActor(obj)
             if repeated:
                 # we don't check sender when item is repeated, as it should be different
@@ -353,6 +357,7 @@
                         "Ignoring object not attributed to signing actor: {obj}"
                     )
                     continue
+
             await self.apg.newAPItem(client, account_jid, node, obj)
 
     async def handleCreateActivity(
@@ -367,6 +372,20 @@
     ):
         await self.handleNewAPItems(request, data, account_jid, node, signing_actor)
 
+    async def handleUpdateActivity(
+        self,
+        request: "HTTPRequest",
+        data: dict,
+        account_jid: Optional[jid.JID],
+        node: Optional[str],
+        ap_account: Optional[str],
+        ap_url: str,
+        signing_actor: str
+    ):
+        # Update is the same as create: the item ID stays the same, thus the item will be
+        # overwritten
+        await self.handleNewAPItems(request, data, account_jid, node, signing_actor)
+
     async def handleAnnounceActivity(
         self,
         request: "HTTPRequest",
@@ -511,6 +530,32 @@
             "reactions": {"operation": "update", "add": [data["content"]]}
         })
 
+    async def handleJoinActivity(
+        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, {"rsvp": {"attending": "yes"}})
+
+    async def handleLeaveActivity(
+        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, {"rsvp": {"attending": "no"}})
+
     async def APActorRequest(
         self,
         request: "HTTPRequest",
@@ -531,6 +576,13 @@
         preferred_username = ap_account.split("@", 1)[0]
 
         identity_data = await self.apg._i.getIdentity(self.apg.client, account_jid)
+        if node and node.startswith(self.apg._events.namespace):
+            events = outbox
+        else:
+            events_account = await self.apg.getAPAccountFromJidAndNode(
+                account_jid, self.apg._events.namespace
+            )
+            events = self.apg.buildAPURL(TYPE_OUTBOX, events_account)
 
         actor_data = {
             "@context": [
@@ -543,6 +595,7 @@
             "preferredUsername": preferred_username,
             "inbox": inbox,
             "outbox": outbox,
+            "events": events,
             "followers": followers,
             "following": following,
             "publicKey": {
@@ -551,7 +604,8 @@
                 "publicKeyPem": self.apg.public_key_pem
             },
             "endpoints": {
-                "sharedInbox": shared_inbox
+                "sharedInbox": shared_inbox,
+                "events": events,
             },
         }
 
@@ -633,13 +687,17 @@
 
         base_url = self.getCanonicalURL(request)
         url = f"{base_url}?{parse.urlencode(query_data, True)}"
-        data = {
-            "@context": "https://www.w3.org/ns/activitystreams",
-            "id": url,
-            "type": "OrderedCollectionPage",
-            "partOf": base_url,
-            "orderedItems" : [
-                await self.apg.mbdata2APitem(
+        if node and node.startswith(self.apg._events.namespace):
+            ordered_items = [
+                await self.apg.ap_events.event_data_2_ap_item(
+                    self.apg._events.event_elt_2_event_data(item),
+                    account_jid
+                )
+                for item in reversed(items)
+            ]
+        else:
+            ordered_items = [
+                await self.apg.mb_data_2_ap_item(
                     self.apg.client,
                     await self.apg._m.item2mbdata(
                         self.apg.client,
@@ -650,6 +708,12 @@
                 )
                 for item in reversed(items)
             ]
+        data = {
+            "@context": ["https://www.w3.org/ns/activitystreams"],
+            "id": url,
+            "type": "OrderedCollectionPage",
+            "partOf": base_url,
+            "orderedItems": ordered_items
         }
 
         # AP OrderedCollection must be in reversed chronological order, thus the opposite
@@ -718,7 +782,7 @@
         url_first_page = f"{url}?{parse.urlencode({'page': 'first'})}"
         url_last_page = f"{url}?{parse.urlencode({'page': 'last'})}"
         return {
-            "@context": "https://www.w3.org/ns/activitystreams",
+            "@context": ["https://www.w3.org/ns/activitystreams"],
             "id": url,
             "totalItems": items_count,
             "type": "OrderedCollection",
@@ -737,8 +801,6 @@
     ) -> None:
         if signing_actor is None:
             raise exceptions.InternalError("signing_actor must be set for inbox requests")
-        if node is None:
-            node = self.apg._m.namespace
         try:
             data = json.load(request.content)
             if not isinstance(data, dict):
@@ -805,7 +867,7 @@
 
         url = self.getCanonicalURL(request)
         return {
-          "@context": "https://www.w3.org/ns/activitystreams",
+          "@context": ["https://www.w3.org/ns/activitystreams"],
           "type": "OrderedCollection",
           "id": url,
           "totalItems": len(subscribers),
@@ -844,7 +906,7 @@
 
         url = self.getCanonicalURL(request)
         return {
-          "@context": "https://www.w3.org/ns/activitystreams",
+          "@context": ["https://www.w3.org/ns/activitystreams"],
           "type": "OrderedCollection",
           "id": url,
           "totalItems": len(subscriptions),
@@ -901,7 +963,8 @@
             return static.File(str(avatar_path)).render(request)
         elif request_type == "item":
             ret_data = await self.apg.apGetLocalObject(ap_url)
-            ret_data["@context"] = NS_AP
+            if "@context" not in ret_data:
+                ret_data["@context"] = [NS_AP]
         else:
             if len(extra_args) > 1:
                 log.warning(f"unexpected extra arguments: {extra_args!r}")