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