Mercurial > libervia-backend
comparison sat/plugins/plugin_comp_ap_gateway/__init__.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 | 59fbb66b2923 |
children | 03761f8ba8bb |
comparison
equal
deleted
inserted
replaced
3868:37d2c0282304 | 3869:c0bcbcf5b4b7 |
---|---|
291 ap_account = self._e.unescape(recipient.user) | 291 ap_account = self._e.unescape(recipient.user) |
292 | 292 |
293 if self._pa.isAttachmentNode(itemsEvent.nodeIdentifier): | 293 if self._pa.isAttachmentNode(itemsEvent.nodeIdentifier): |
294 await self.convertAndPostAttachments( | 294 await self.convertAndPostAttachments( |
295 client, ap_account, itemsEvent.sender, itemsEvent.nodeIdentifier, | 295 client, ap_account, itemsEvent.sender, itemsEvent.nodeIdentifier, |
296 itemsEvent.items, publisher=itemsEvent.sender | 296 itemsEvent.items |
297 ) | 297 ) |
298 else: | 298 else: |
299 await self.convertAndPostItems( | 299 await self.convertAndPostItems( |
300 client, ap_account, itemsEvent.sender, itemsEvent.nodeIdentifier, | 300 client, ap_account, itemsEvent.sender, itemsEvent.nodeIdentifier, |
301 itemsEvent.items | 301 itemsEvent.items |
579 node = None | 579 node = None |
580 | 580 |
581 if self.client is None: | 581 if self.client is None: |
582 raise exceptions.InternalError("Client is not set yet") | 582 raise exceptions.InternalError("Client is not set yet") |
583 | 583 |
584 if jid_.host == self.client.jid.userhost(): | 584 if self.isVirtualJID(jid_): |
585 # this is an proxy JID to an AP Actor | 585 # this is an proxy JID to an AP Actor |
586 return self._e.unescape(jid_.user) | 586 return self._e.unescape(jid_.user) |
587 | 587 |
588 if node and not jid_.user and not self.mustEncode(node): | 588 if node and not jid_.user and not self.mustEncode(node): |
589 is_pubsub = await self.isPubsub(jid_) | 589 is_pubsub = await self.isPubsub(jid_) |
783 """Tells if an URL link to this component | 783 """Tells if an URL link to this component |
784 | 784 |
785 ``public_url`` and ``ap_path`` are used to check the URL | 785 ``public_url`` and ``ap_path`` are used to check the URL |
786 """ | 786 """ |
787 return url.startswith(self.base_ap_url) | 787 return url.startswith(self.base_ap_url) |
788 | |
789 def isVirtualJID(self, jid_: jid.JID) -> bool: | |
790 """Tell if a JID is an AP actor mapped through this gateway""" | |
791 return jid_.host == self.client.jid.userhost() | |
788 | 792 |
789 def buildSignatureHeader(self, values: Dict[str, str]) -> str: | 793 def buildSignatureHeader(self, values: Dict[str, str]) -> str: |
790 """Build key="<value>" signature header from signature data""" | 794 """Build key="<value>" signature header from signature data""" |
791 fields = [] | 795 fields = [] |
792 for key, value in values.items(): | 796 for key, value in values.items(): |
981 actor_id = await self.getAPActorIdFromAccount(ap_account) | 985 actor_id = await self.getAPActorIdFromAccount(ap_account) |
982 inbox = await self.getAPInboxFromId(actor_id) | 986 inbox = await self.getAPInboxFromId(actor_id) |
983 for item in items: | 987 for item in items: |
984 if item.name == "item": | 988 if item.name == "item": |
985 mb_data = await self._m.item2mbdata(client, item, service, node) | 989 mb_data = await self._m.item2mbdata(client, item, service, node) |
986 if subscribe_extra_nodes: | 990 author_jid = jid.JID(mb_data["author_jid"]) |
991 if subscribe_extra_nodes and not self.isVirtualJID(author_jid): | |
987 # we subscribe automatically to comment nodes if any | 992 # we subscribe automatically to comment nodes if any |
988 recipient_jid = self.getLocalJIDFromAccount(ap_account) | 993 recipient_jid = self.getLocalJIDFromAccount(ap_account) |
989 recipient_client = self.client.getVirtualClient(recipient_jid) | 994 recipient_client = self.client.getVirtualClient(recipient_jid) |
990 for comment_data in mb_data.get("comments", []): | 995 for comment_data in mb_data.get("comments", []): |
991 comment_service = jid.JID(comment_data["service"]) | 996 comment_service = jid.JID(comment_data["service"]) |
997 if self.isVirtualJID(comment_service): | |
998 log.debug(f"ignoring virtual comment service: {comment_data}") | |
999 continue | |
992 comment_node = comment_data["node"] | 1000 comment_node = comment_data["node"] |
993 await self._p.subscribe( | 1001 await self._p.subscribe( |
994 recipient_client, comment_service, comment_node | 1002 recipient_client, comment_service, comment_node |
995 ) | 1003 ) |
996 try: | 1004 try: |
1017 client: SatXMPPEntity, | 1025 client: SatXMPPEntity, |
1018 ap_account: str, | 1026 ap_account: str, |
1019 service: jid.JID, | 1027 service: jid.JID, |
1020 node: str, | 1028 node: str, |
1021 items: List[domish.Element], | 1029 items: List[domish.Element], |
1022 publisher: Optional[jid.JID] | 1030 publisher: Optional[jid.JID] = None |
1023 ) -> None: | 1031 ) -> None: |
1024 """Convert XMPP item attachments to AP activities and post them to actor inbox | 1032 """Convert XMPP item attachments to AP activities and post them to actor inbox |
1025 | 1033 |
1026 @param ap_account: account of ActivityPub actor receiving the item | 1034 @param ap_account: account of ActivityPub actor receiving the item |
1027 @param service: JID of the (virtual) pubsub service where the item has been | 1035 @param service: JID of the (virtual) pubsub service where the item has been |
1028 published | 1036 published |
1029 @param node: (virtual) node corresponding where the item has been published | 1037 @param node: (virtual) node corresponding where the item has been published |
1030 @param subscribe_extra_nodes: if True, extra data nodes will be automatically | 1038 subscribed, that is comment nodes if present and attachments nodes. |
1031 subscribed, that is comment nodes if present and attachments nodes. | 1039 @param items: attachments items |
1040 @param publisher: publisher of the attachments item (it's NOT the PEP/Pubsub | |
1041 service, it's the publisher of the item). To be filled only when the publisher | |
1042 is known for sure, otherwise publisher will be determined either if | |
1043 "publisher" attribute is set by pubsub service, or as a last resort, using | |
1044 item's ID (which MUST be publisher bare JID according to pubsub-attachments | |
1045 specification). | |
1032 """ | 1046 """ |
1033 if len(items) != 1: | 1047 if len(items) != 1: |
1034 log.warning( | 1048 log.warning( |
1035 "we should get exactly one attachment item for an entity, got " | 1049 "we should get exactly one attachment item for an entity, got " |
1036 f"{len(items)})" | 1050 f"{len(items)})" |
1038 | 1052 |
1039 actor_id = await self.getAPActorIdFromAccount(ap_account) | 1053 actor_id = await self.getAPActorIdFromAccount(ap_account) |
1040 inbox = await self.getAPInboxFromId(actor_id) | 1054 inbox = await self.getAPInboxFromId(actor_id) |
1041 | 1055 |
1042 item_elt = items[0] | 1056 item_elt = items[0] |
1057 item_id = item_elt["id"] | |
1058 | |
1059 if publisher is None: | |
1060 item_pub_s = item_elt.getAttribute("publisher") | |
1061 publisher = jid.JID(item_pub_s) if item_pub_s else jid.JID(item_id) | |
1062 | |
1063 if publisher.userhost() != item_id: | |
1064 log.warning( | |
1065 "attachments item ID must be publisher's bare JID, ignoring: " | |
1066 f"{item_elt.toXml()}" | |
1067 ) | |
1068 return | |
1069 | |
1070 if self.isVirtualJID(publisher): | |
1071 log.debug(f"ignoring item coming from local virtual JID {publisher}") | |
1072 return | |
1073 | |
1043 if publisher is not None: | 1074 if publisher is not None: |
1044 item_elt["publisher"] = publisher.userhost() | 1075 item_elt["publisher"] = publisher.userhost() |
1076 | |
1045 item_service, item_node, item_id = self._pa.attachmentNode2Item(node) | 1077 item_service, item_node, item_id = self._pa.attachmentNode2Item(node) |
1046 item_account = await self.getAPAccountFromJidAndNode(item_service, item_node) | 1078 item_account = await self.getAPAccountFromJidAndNode(item_service, item_node) |
1047 if item_service.host == self.client.jid.userhost(): | 1079 if self.isVirtualJID(item_service): |
1048 # it's a virtual JID mapping to an external AP actor, we can use the | 1080 # it's a virtual JID mapping to an external AP actor, we can use the |
1049 # item_id directly | 1081 # item_id directly |
1050 item_url = item_id | 1082 item_url = item_id |
1051 if not item_url.startswith("https:"): | 1083 if not item_url.startswith("https:"): |
1052 log.warning( | 1084 log.warning( |
1070 try: | 1102 try: |
1071 old_attachment = old_attachments[0] | 1103 old_attachment = old_attachments[0] |
1072 except IndexError: | 1104 except IndexError: |
1073 # no known element was present in attachments | 1105 # no known element was present in attachments |
1074 old_attachment = {} | 1106 old_attachment = {} |
1075 sender_account = await self.getAPAccountFromJidAndNode( | 1107 publisher_account = await self.getAPAccountFromJidAndNode( |
1076 client.jid, | 1108 publisher, |
1077 None | 1109 None |
1078 ) | 1110 ) |
1079 sender_actor_id = self.buildAPURL(TYPE_ACTOR, sender_account) | 1111 publisher_actor_id = self.buildAPURL(TYPE_ACTOR, publisher_account) |
1080 try: | 1112 try: |
1081 attachments = self._pa.items2attachmentData(client, [item_elt])[0] | 1113 attachments = self._pa.items2attachmentData(client, [item_elt])[0] |
1082 except IndexError: | 1114 except IndexError: |
1083 # no known element was present in attachments | 1115 # no known element was present in attachments |
1084 attachments = {} | 1116 attachments = {} |
1086 if "noticed" in attachments: | 1118 if "noticed" in attachments: |
1087 if not "noticed" in old_attachment: | 1119 if not "noticed" in old_attachment: |
1088 # new "noticed" attachment, we translate to "Like" activity | 1120 # new "noticed" attachment, we translate to "Like" activity |
1089 activity_id = self.buildAPURL("like", item_account, item_id) | 1121 activity_id = self.buildAPURL("like", item_account, item_id) |
1090 like = self.createActivity( | 1122 like = self.createActivity( |
1091 TYPE_LIKE, sender_actor_id, item_url, activity_id=activity_id | 1123 TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id |
1092 ) | 1124 ) |
1093 like["to"] = [NS_AP_PUBLIC] | 1125 like["to"] = [NS_AP_PUBLIC] |
1094 await self.signAndPost(inbox, sender_actor_id, like) | 1126 await self.signAndPost(inbox, publisher_actor_id, like) |
1095 else: | 1127 else: |
1096 if "noticed" in old_attachment: | 1128 if "noticed" in old_attachment: |
1097 # "noticed" attachment has been removed, we undo the "Like" activity | 1129 # "noticed" attachment has been removed, we undo the "Like" activity |
1098 activity_id = self.buildAPURL("like", item_account, item_id) | 1130 activity_id = self.buildAPURL("like", item_account, item_id) |
1099 like = self.createActivity( | 1131 like = self.createActivity( |
1100 TYPE_LIKE, sender_actor_id, item_url, activity_id=activity_id | 1132 TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id |
1101 ) | 1133 ) |
1102 like["to"] = [NS_AP_PUBLIC] | 1134 like["to"] = [NS_AP_PUBLIC] |
1103 undo = self.createActivity("Undo", sender_actor_id, like) | 1135 undo = self.createActivity("Undo", publisher_actor_id, like) |
1104 await self.signAndPost(inbox, sender_actor_id, undo) | 1136 await self.signAndPost(inbox, publisher_actor_id, undo) |
1105 | 1137 |
1106 if service.user and service.host == self.client.jid.userhost(): | 1138 if service.user and self.isVirtualJID(service): |
1107 # the item is on a virtual service, we need to store it in cache | 1139 # the item is on a virtual service, we need to store it in cache |
1108 log.debug("storing attachments item in cache") | 1140 log.debug("storing attachments item in cache") |
1109 cached_node = await self.host.memory.storage.getPubsubNode( | 1141 cached_node = await self.host.memory.storage.getPubsubNode( |
1110 client, service, node, with_subscriptions=True, create=True | 1142 client, service, node, with_subscriptions=True, create=True |
1111 ) | 1143 ) |
1718 raise NotImplementedError | 1750 raise NotImplementedError |
1719 rep_service = jid.JID(parsed_url["path"]) | 1751 rep_service = jid.JID(parsed_url["path"]) |
1720 rep_item = parsed_url["item"] | 1752 rep_item = parsed_url["item"] |
1721 activity_id = self.buildAPURL("item", repeater.userhost(), mb_data["id"]) | 1753 activity_id = self.buildAPURL("item", repeater.userhost(), mb_data["id"]) |
1722 | 1754 |
1723 if rep_service.host == self.client.jid.userhost(): | 1755 if self.isVirtualJID(rep_service): |
1724 # it's an AP actor linked through this gateway | 1756 # it's an AP actor linked through this gateway |
1725 # in this case we can simply use the item ID | 1757 # in this case we can simply use the item ID |
1726 if not rep_item.startswith("https:"): | 1758 if not rep_item.startswith("https:"): |
1727 log.warning( | 1759 log.warning( |
1728 f"Was expecting an HTTPS url as item ID and got {rep_item!r}\n" | 1760 f"Was expecting an HTTPS url as item ID and got {rep_item!r}\n" |
1823 "node or service is missing in mb_data" | 1855 "node or service is missing in mb_data" |
1824 ) | 1856 ) |
1825 target_ap_account = await self.getAPAccountFromJidAndNode( | 1857 target_ap_account = await self.getAPAccountFromJidAndNode( |
1826 service, node | 1858 service, node |
1827 ) | 1859 ) |
1828 if service.host == self.client.jid.userhost(): | 1860 if self.isVirtualJID(service): |
1829 # service is a proxy JID for AP account | 1861 # service is a proxy JID for AP account |
1830 actor_data = await self.getAPActorDataFromAccount(target_ap_account) | 1862 actor_data = await self.getAPActorDataFromAccount(target_ap_account) |
1831 followers = actor_data.get("followers") | 1863 followers = actor_data.get("followers") |
1832 else: | 1864 else: |
1833 # service is a real XMPP entity | 1865 # service is a real XMPP entity |
1834 followers = self.buildAPURL(TYPE_FOLLOWERS, target_ap_account) | 1866 followers = self.buildAPURL(TYPE_FOLLOWERS, target_ap_account) |
1835 if followers: | 1867 if followers: |
1836 ap_object["cc"] = [followers] | 1868 ap_object["cc"] = [followers] |
1837 if self._m.isCommentNode(node): | 1869 if self._m.isCommentNode(node): |
1838 parent_item = self._m.getParentItem(node) | 1870 parent_item = self._m.getParentItem(node) |
1839 if service.host == self.client.jid.userhost(): | 1871 if self.isVirtualJID(service): |
1840 # the publication is on a virtual node (i.e. an XMPP node managed by | 1872 # the publication is on a virtual node (i.e. an XMPP node managed by |
1841 # this gateway and linking to an ActivityPub actor) | 1873 # this gateway and linking to an ActivityPub actor) |
1842 ap_object["inReplyTo"] = parent_item | 1874 ap_object["inReplyTo"] = parent_item |
1843 else: | 1875 else: |
1844 # the publication is from a followed real XMPP node | 1876 # the publication is from a followed real XMPP node |