comparison sat/plugins/plugin_comp_ap_gateway/__init__.py @ 4023:78b5f356900c

component AP gateway: handle attachments
author Goffi <goffi@goffi.org>
date Thu, 23 Mar 2023 15:42:21 +0100
parents 4a2c261646b6
children 44abce96ac6b
comparison
equal deleted inserted replaced
4022:cdb7de398c85 4023:78b5f356900c
107 C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT, 107 C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT,
108 C.PI_PROTOCOLS: [], 108 C.PI_PROTOCOLS: [],
109 C.PI_DEPENDENCIES: [ 109 C.PI_DEPENDENCIES: [
110 "XEP-0050", "XEP-0054", "XEP-0060", "XEP-0084", "XEP-0106", "XEP-0277", 110 "XEP-0050", "XEP-0054", "XEP-0060", "XEP-0084", "XEP-0106", "XEP-0277",
111 "XEP-0292", "XEP-0329", "XEP-0372", "XEP-0424", "XEP-0465", "XEP-0470", 111 "XEP-0292", "XEP-0329", "XEP-0372", "XEP-0424", "XEP-0465", "XEP-0470",
112 "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY", "EVENTS" 112 "XEP-0447", "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY", "EVENTS"
113 ], 113 ],
114 C.PI_RECOMMENDATIONS: [], 114 C.PI_RECOMMENDATIONS: [],
115 C.PI_MAIN: "APGateway", 115 C.PI_MAIN: "APGateway",
116 C.PI_HANDLER: C.BOOL_TRUE, 116 C.PI_HANDLER: C.BOOL_TRUE,
117 C.PI_DESCRIPTION: _( 117 C.PI_DESCRIPTION: _(
129 IMPORT_NAME = IMPORT_NAME 129 IMPORT_NAME = IMPORT_NAME
130 # show data send or received through HTTP, used for debugging 130 # show data send or received through HTTP, used for debugging
131 # 1: log POST objects 131 # 1: log POST objects
132 # 2: log POST and GET objects 132 # 2: log POST and GET objects
133 # 3: log POST and GET objects with HTTP headers for GET requests 133 # 3: log POST and GET objects with HTTP headers for GET requests
134 verbose = 0 134 verbose = 3
135 135
136 def __init__(self, host): 136 def __init__(self, host):
137 self.host = host 137 self.host = host
138 self.initialised = False 138 self.initialised = False
139 self.client = None 139 self.client = None
142 self._e = host.plugins["XEP-0106"] 142 self._e = host.plugins["XEP-0106"]
143 self._m = host.plugins["XEP-0277"] 143 self._m = host.plugins["XEP-0277"]
144 self._v = host.plugins["XEP-0292"] 144 self._v = host.plugins["XEP-0292"]
145 self._refs = host.plugins["XEP-0372"] 145 self._refs = host.plugins["XEP-0372"]
146 self._r = host.plugins["XEP-0424"] 146 self._r = host.plugins["XEP-0424"]
147 self._sfs = host.plugins["XEP-0447"]
147 self._pps = host.plugins["XEP-0465"] 148 self._pps = host.plugins["XEP-0465"]
149 self._pa = host.plugins["XEP-0470"]
148 self._c = host.plugins["PUBSUB_CACHE"] 150 self._c = host.plugins["PUBSUB_CACHE"]
149 self._t = host.plugins["TEXT_SYNTAXES"] 151 self._t = host.plugins["TEXT_SYNTAXES"]
150 self._i = host.plugins["IDENTITY"] 152 self._i = host.plugins["IDENTITY"]
151 self._pa = host.plugins["XEP-0470"]
152 self._events = host.plugins["EVENTS"] 153 self._events = host.plugins["EVENTS"]
153 self._p.addManagedNode( 154 self._p.addManagedNode(
154 "", 155 "",
155 items_cb=self._itemsReceived, 156 items_cb=self._itemsReceived,
156 # we want to be sure that the callbacks are launched before pubsub cache's 157 # we want to be sure that the callbacks are launched before pubsub cache's
485 # the URL must return the object and not the activity 486 # the URL must return the object and not the activity
486 ap_item["object"]["@context"] = ap_item["@context"] 487 ap_item["object"]["@context"] = ap_item["@context"]
487 return ap_item["object"] 488 return ap_item["object"]
488 else: 489 else:
489 # this is a blog item 490 # this is a blog item
490 mb_data = await self._m.item2mbdata( 491 mb_data = await self._m.item_2_mb_data(
491 self.client, found_item, author_jid, node 492 self.client, found_item, author_jid, node
492 ) 493 )
493 ap_item = await self.mb_data_2_ap_item(self.client, mb_data) 494 ap_item = await self.mb_data_2_ap_item(self.client, mb_data)
494 # the URL must return the object and not the activity 495 # the URL must return the object and not the activity
495 return ap_item["object"] 496 return ap_item["object"]
899 owner = pub_key_data["owner"] 900 owner = pub_key_data["owner"]
900 pub_key_pem = pub_key_data["publicKeyPem"] 901 pub_key_pem = pub_key_data["publicKeyPem"]
901 pub_key = serialization.load_pem_public_key(pub_key_pem.encode()) 902 pub_key = serialization.load_pem_public_key(pub_key_pem.encode())
902 return key_id, owner, pub_key 903 return key_id, owner, pub_key
903 904
904 def createActivity( 905 def create_activity(
905 self, 906 self,
906 activity: str, 907 activity: str,
907 actor_id: str, 908 actor_id: str,
908 object_: Optional[Union[str, dict]] = None, 909 object_: Optional[Union[str, dict]] = None,
909 target: Optional[Union[str, dict]] = None, 910 target: Optional[Union[str, dict]] = None,
1092 ap_item = await self.ap_events.event_data_2_ap_item( 1093 ap_item = await self.ap_events.event_data_2_ap_item(
1093 event_data, author_jid, is_new=is_new 1094 event_data, author_jid, is_new=is_new
1094 ) 1095 )
1095 else: 1096 else:
1096 # blog item 1097 # blog item
1097 mb_data = await self._m.item2mbdata(client, item, service, node) 1098 mb_data = await self._m.item_2_mb_data(client, item, service, node)
1098 author_jid = jid.JID(mb_data["author_jid"]) 1099 author_jid = jid.JID(mb_data["author_jid"])
1099 if subscribe_extra_nodes and not self.isVirtualJID(author_jid): 1100 if subscribe_extra_nodes and not self.isVirtualJID(author_jid):
1100 # we subscribe automatically to comment nodes if any 1101 # we subscribe automatically to comment nodes if any
1101 recipient_jid = self.getLocalJIDFromAccount(ap_account) 1102 recipient_jid = self.getLocalJIDFromAccount(ap_account)
1102 recipient_client = self.client.getVirtualClient(recipient_jid) 1103 recipient_client = self.client.getVirtualClient(recipient_jid)
1230 # noticed 1231 # noticed
1231 if "noticed" in attachments: 1232 if "noticed" in attachments:
1232 if not "noticed" in old_attachment: 1233 if not "noticed" in old_attachment:
1233 # new "noticed" attachment, we translate to "Like" activity 1234 # new "noticed" attachment, we translate to "Like" activity
1234 activity_id = self.buildAPURL("like", item_account, item_id) 1235 activity_id = self.buildAPURL("like", item_account, item_id)
1235 activity = self.createActivity( 1236 activity = self.create_activity(
1236 TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id 1237 TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id
1237 ) 1238 )
1238 activity["to"] = [ap_account] 1239 activity["to"] = [ap_account]
1239 activity["cc"] = [NS_AP_PUBLIC] 1240 activity["cc"] = [NS_AP_PUBLIC]
1240 await self.signAndPost(inbox, publisher_actor_id, activity) 1241 await self.signAndPost(inbox, publisher_actor_id, activity)
1241 else: 1242 else:
1242 if "noticed" in old_attachment: 1243 if "noticed" in old_attachment:
1243 # "noticed" attachment has been removed, we undo the "Like" activity 1244 # "noticed" attachment has been removed, we undo the "Like" activity
1244 activity_id = self.buildAPURL("like", item_account, item_id) 1245 activity_id = self.buildAPURL("like", item_account, item_id)
1245 activity = self.createActivity( 1246 activity = self.create_activity(
1246 TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id 1247 TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id
1247 ) 1248 )
1248 activity["to"] = [ap_account] 1249 activity["to"] = [ap_account]
1249 activity["cc"] = [NS_AP_PUBLIC] 1250 activity["cc"] = [NS_AP_PUBLIC]
1250 undo = self.createActivity("Undo", publisher_actor_id, activity) 1251 undo = self.create_activity("Undo", publisher_actor_id, activity)
1251 await self.signAndPost(inbox, publisher_actor_id, undo) 1252 await self.signAndPost(inbox, publisher_actor_id, undo)
1252 1253
1253 # reactions 1254 # reactions
1254 new_reactions = set(attachments.get("reactions", {}).get("reactions", [])) 1255 new_reactions = set(attachments.get("reactions", {}).get("reactions", []))
1255 old_reactions = set(old_attachment.get("reactions", {}).get("reactions", [])) 1256 old_reactions = set(old_attachment.get("reactions", {}).get("reactions", []))
1258 for reactions, undo in ((reactions_remove, True), (reactions_add, False)): 1259 for reactions, undo in ((reactions_remove, True), (reactions_add, False)):
1259 for reaction in reactions: 1260 for reaction in reactions:
1260 activity_id = self.buildAPURL( 1261 activity_id = self.buildAPURL(
1261 "reaction", item_account, item_id, reaction.encode().hex() 1262 "reaction", item_account, item_id, reaction.encode().hex()
1262 ) 1263 )
1263 reaction_activity = self.createActivity( 1264 reaction_activity = self.create_activity(
1264 TYPE_REACTION, publisher_actor_id, item_url, 1265 TYPE_REACTION, publisher_actor_id, item_url,
1265 activity_id=activity_id 1266 activity_id=activity_id
1266 ) 1267 )
1267 reaction_activity["content"] = reaction 1268 reaction_activity["content"] = reaction
1268 reaction_activity["to"] = [ap_account] 1269 reaction_activity["to"] = [ap_account]
1269 reaction_activity["cc"] = [NS_AP_PUBLIC] 1270 reaction_activity["cc"] = [NS_AP_PUBLIC]
1270 if undo: 1271 if undo:
1271 activy = self.createActivity( 1272 activy = self.create_activity(
1272 "Undo", publisher_actor_id, reaction_activity 1273 "Undo", publisher_actor_id, reaction_activity
1273 ) 1274 )
1274 else: 1275 else:
1275 activy = reaction_activity 1276 activy = reaction_activity
1276 await self.signAndPost(inbox, publisher_actor_id, activy) 1277 await self.signAndPost(inbox, publisher_actor_id, activy)
1280 attending = attachments["rsvp"].get("attending", "no") 1281 attending = attachments["rsvp"].get("attending", "no")
1281 old_attending = old_attachment.get("rsvp", {}).get("attending", "no") 1282 old_attending = old_attachment.get("rsvp", {}).get("attending", "no")
1282 if attending != old_attending: 1283 if attending != old_attending:
1283 activity_type = TYPE_JOIN if attending == "yes" else TYPE_LEAVE 1284 activity_type = TYPE_JOIN if attending == "yes" else TYPE_LEAVE
1284 activity_id = self.buildAPURL(activity_type.lower(), item_account, item_id) 1285 activity_id = self.buildAPURL(activity_type.lower(), item_account, item_id)
1285 activity = self.createActivity( 1286 activity = self.create_activity(
1286 activity_type, publisher_actor_id, item_url, activity_id=activity_id 1287 activity_type, publisher_actor_id, item_url, activity_id=activity_id
1287 ) 1288 )
1288 activity["to"] = [ap_account] 1289 activity["to"] = [ap_account]
1289 activity["cc"] = [NS_AP_PUBLIC] 1290 activity["cc"] = [NS_AP_PUBLIC]
1290 await self.signAndPost(inbox, publisher_actor_id, activity) 1291 await self.signAndPost(inbox, publisher_actor_id, activity)
1291 else: 1292 else:
1292 if "rsvp" in old_attachment: 1293 if "rsvp" in old_attachment:
1293 old_attending = old_attachment.get("rsvp", {}).get("attending", "no") 1294 old_attending = old_attachment.get("rsvp", {}).get("attending", "no")
1294 if old_attending == "yes": 1295 if old_attending == "yes":
1295 activity_id = self.buildAPURL(TYPE_LEAVE.lower(), item_account, item_id) 1296 activity_id = self.buildAPURL(TYPE_LEAVE.lower(), item_account, item_id)
1296 activity = self.createActivity( 1297 activity = self.create_activity(
1297 TYPE_LEAVE, publisher_actor_id, item_url, activity_id=activity_id 1298 TYPE_LEAVE, publisher_actor_id, item_url, activity_id=activity_id
1298 ) 1299 )
1299 activity["to"] = [ap_account] 1300 activity["to"] = [ap_account]
1300 activity["cc"] = [NS_AP_PUBLIC] 1301 activity["cc"] = [NS_AP_PUBLIC]
1301 await self.signAndPost(inbox, publisher_actor_id, activity) 1302 await self.signAndPost(inbox, publisher_actor_id, activity)
1643 1644
1644 return items, rsm.RSMResponse(**rsm_resp) 1645 return items, rsm.RSMResponse(**rsm_resp)
1645 1646
1646 async def ap_item_2_mb_data_and_elt(self, ap_item: dict) -> Tuple[dict, domish.Element]: 1647 async def ap_item_2_mb_data_and_elt(self, ap_item: dict) -> Tuple[dict, domish.Element]:
1647 """Convert AP item to parsed microblog data and corresponding item element""" 1648 """Convert AP item to parsed microblog data and corresponding item element"""
1648 mb_data = await self.apItem2MBdata(ap_item) 1649 mb_data = await self.ap_item_2_mb_data(ap_item)
1649 item_elt = await self._m.data2entry( 1650 item_elt = await self._m.mb_data_2_entry_elt(
1650 self.client, mb_data, mb_data["id"], None, self._m.namespace 1651 self.client, mb_data, mb_data["id"], None, self._m.namespace
1651 ) 1652 )
1652 if "repeated" in mb_data["extra"]: 1653 if "repeated" in mb_data["extra"]:
1653 item_elt["publisher"] = mb_data["extra"]["repeated"]["by"] 1654 item_elt["publisher"] = mb_data["extra"]["repeated"]["by"]
1654 else: 1655 else:
1740 return ( 1741 return (
1741 self._m.getCommentsNode(last_level_item["id"]), 1742 self._m.getCommentsNode(last_level_item["id"]),
1742 None 1743 None
1743 ) 1744 )
1744 1745
1745 async def apItem2MBdata(self, ap_item: dict) -> dict: 1746 async def ap_item_2_mb_data(self, ap_item: dict) -> dict:
1746 """Convert AP activity or object to microblog data 1747 """Convert AP activity or object to microblog data
1747 1748
1748 @param ap_item: ActivityPub item to convert 1749 @param ap_item: ActivityPub item to convert
1749 Can be either an activity of an object 1750 Can be either an activity of an object
1750 @return: AP Item's Object and microblog data 1751 @return: AP Item's Object and microblog data
1783 mb_data["content_xhtml"], 1784 mb_data["content_xhtml"],
1784 self._t.SYNTAX_XHTML, 1785 self._t.SYNTAX_XHTML,
1785 self._t.SYNTAX_TEXT, 1786 self._t.SYNTAX_TEXT,
1786 False, 1787 False,
1787 ) 1788 )
1789
1790 if "attachment" in ap_object:
1791 attachments = mb_data["extra"][C.KEY_ATTACHMENTS] = []
1792 for ap_attachment in ap_object["attachment"]:
1793 try:
1794 url = ap_attachment["url"]
1795 except KeyError:
1796 log.warning(
1797 f'"url" missing in AP attachment, ignoring: {ap_attachment}'
1798 )
1799 continue
1800
1801 if not url.startswith("http"):
1802 log.warning(f"non HTTP URL in attachment, ignoring: {ap_attachment}")
1803 continue
1804 attachment = {"url": url}
1805 for ap_key, key in (
1806 ("mediaType", "media_type"),
1807 # XXX: as weird as it seems, "name" is actually used for description
1808 # in AP world
1809 ("name", "desc"),
1810 ):
1811 value = ap_attachment.get(ap_key)
1812 if value:
1813 attachment[key] = value
1814 attachments.append(attachment)
1788 1815
1789 # author 1816 # author
1790 if is_activity: 1817 if is_activity:
1791 authors = await self.apGetActors(ap_item, "actor") 1818 authors = await self.apGetActors(ap_item, "actor")
1792 else: 1819 else:
1897 1924
1898 return self.buildAPURL( 1925 return self.buildAPURL(
1899 TYPE_ITEM, parent_ap_account, parent_item 1926 TYPE_ITEM, parent_ap_account, parent_item
1900 ) 1927 )
1901 1928
1902 async def repeatedMB2APItem( 1929 async def repeated_mb_2_ap_item(
1903 self, 1930 self,
1904 mb_data: dict 1931 mb_data: dict
1905 ) -> dict: 1932 ) -> dict:
1906 """Convert repeated blog item to suitable AP Announce activity 1933 """Convert repeated blog item to suitable AP Announce activity
1907 1934
1950 repeated_account = await self.getAPAccountFromJidAndNode( 1977 repeated_account = await self.getAPAccountFromJidAndNode(
1951 rep_service, rep_node 1978 rep_service, rep_node
1952 ) 1979 )
1953 announced_uri = self.buildAPURL("item", repeated_account, rep_item) 1980 announced_uri = self.buildAPURL("item", repeated_account, rep_item)
1954 1981
1955 announce = self.createActivity( 1982 announce = self.create_activity(
1956 "Announce", repeater_id, announced_uri, activity_id=activity_id 1983 "Announce", repeater_id, announced_uri, activity_id=activity_id
1957 ) 1984 )
1958 announce["to"] = [NS_AP_PUBLIC] 1985 announce["to"] = [NS_AP_PUBLIC]
1959 announce["cc"] = [ 1986 announce["cc"] = [
1960 self.buildAPURL(TYPE_FOLLOWERS, repeater_account), 1987 self.buildAPURL(TYPE_FOLLOWERS, repeater_account),
1986 be. 2013 be.
1987 @return: Activity item 2014 @return: Activity item
1988 """ 2015 """
1989 extra = mb_data.get("extra", {}) 2016 extra = mb_data.get("extra", {})
1990 if "repeated" in extra: 2017 if "repeated" in extra:
1991 return await self.repeatedMB2APItem(mb_data) 2018 return await self.repeated_mb_2_ap_item(mb_data)
1992 if not mb_data.get("id"): 2019 if not mb_data.get("id"):
1993 mb_data["id"] = shortuuid.uuid() 2020 mb_data["id"] = shortuuid.uuid()
1994 if not mb_data.get("author_jid"): 2021 if not mb_data.get("author_jid"):
1995 mb_data["author_jid"] = client.jid.userhost() 2022 mb_data["author_jid"] = client.jid.userhost()
1996 ap_account = await self.getAPAccountFromJidAndNode( 2023 ap_account = await self.getAPAccountFromJidAndNode(
2008 } 2035 }
2009 2036
2010 language = mb_data.get("language") 2037 language = mb_data.get("language")
2011 if language: 2038 if language:
2012 ap_object["contentMap"] = {language: ap_object["content"]} 2039 ap_object["contentMap"] = {language: ap_object["content"]}
2040
2041 attachments = extra.get(C.KEY_ATTACHMENTS)
2042 if attachments:
2043 ap_attachments = ap_object["attachment"] = []
2044 for attachment in attachments:
2045 try:
2046 url = next(
2047 s['url'] for s in attachment["sources"] if 'url' in s
2048 )
2049 except (StopIteration, KeyError):
2050 log.warning(
2051 f"Ignoring attachment without URL: {attachment}"
2052 )
2053 continue
2054 ap_attachment = {
2055 "url": url
2056 }
2057 for key, ap_key in (
2058 ("media_type", "mediaType"),
2059 # XXX: yes "name", cf. [ap_item_2_mb_data]
2060 ("desc", "name"),
2061 ):
2062 value = attachment.get(key)
2063 if value:
2064 ap_attachment[ap_key] = value
2065 ap_attachments.append(ap_attachment)
2013 2066
2014 if public: 2067 if public:
2015 ap_object["to"] = [NS_AP_PUBLIC] 2068 ap_object["to"] = [NS_AP_PUBLIC]
2016 if self.auto_mentions: 2069 if self.auto_mentions:
2017 for m in RE_MENTION.finditer(ap_object["content"]): 2070 for m in RE_MENTION.finditer(ap_object["content"]):
2066 ap_account, 2119 ap_account,
2067 parent_item, 2120 parent_item,
2068 mb_data 2121 mb_data
2069 ) 2122 )
2070 2123
2071 return self.createActivity( 2124 return self.create_activity(
2072 "Create" if is_new else "Update", url_actor, ap_object, activity_id=url_item 2125 "Create" if is_new else "Update", url_actor, ap_object, activity_id=url_item
2073 ) 2126 )
2074 2127
2075 async def publishMessage( 2128 async def publishMessage(
2076 self, 2129 self,
2142 f"Deleting an unknown item at service {jid_}, node {node} and id " 2195 f"Deleting an unknown item at service {jid_}, node {node} and id "
2143 f"{item_id}" 2196 f"{item_id}"
2144 ) 2197 )
2145 else: 2198 else:
2146 try: 2199 try:
2147 mb_data = await self._m.item2mbdata(self.client, items[0].data, jid_, node) 2200 mb_data = await self._m.item_2_mb_data(self.client, items[0].data, jid_, node)
2148 if "repeated" in mb_data["extra"]: 2201 if "repeated" in mb_data["extra"]:
2149 # we are deleting a repeated item, we must translate this to an 2202 # we are deleting a repeated item, we must translate this to an
2150 # "Undo" of the "Announce" activity instead of a "Delete" one 2203 # "Undo" of the "Announce" activity instead of a "Delete" one
2151 announce = await self.repeatedMB2APItem(mb_data) 2204 announce = await self.repeated_mb_2_ap_item(mb_data)
2152 undo = self.createActivity("Undo", author_actor_id, announce) 2205 undo = self.create_activity("Undo", author_actor_id, announce)
2153 return author_actor_id, undo 2206 return author_actor_id, undo
2154 except Exception as e: 2207 except Exception as e:
2155 log.debug( 2208 log.debug(
2156 f"Can't parse item, maybe it's not a blog item: {e}\n" 2209 f"Can't parse item, maybe it's not a blog item: {e}\n"
2157 f"{items[0].toXml()}" 2210 f"{items[0].toXml()}"
2158 ) 2211 )
2159 2212
2160 url_item = self.buildAPURL(TYPE_ITEM, author_account, item_id) 2213 url_item = self.buildAPURL(TYPE_ITEM, author_account, item_id)
2161 ap_item = self.createActivity( 2214 ap_item = self.create_activity(
2162 "Delete", 2215 "Delete",
2163 author_actor_id, 2216 author_actor_id,
2164 { 2217 {
2165 "id": url_item, 2218 "id": url_item,
2166 "type": TYPE_TOMBSTONE 2219 "type": TYPE_TOMBSTONE
2221 mb_data["language"] = language 2274 mb_data["language"] = language
2222 origin_id = mess_data["extra"].get("origin_id") 2275 origin_id = mess_data["extra"].get("origin_id")
2223 if origin_id: 2276 if origin_id:
2224 # we need to use origin ID when present to be able to retract the message 2277 # we need to use origin ID when present to be able to retract the message
2225 mb_data["id"] = origin_id 2278 mb_data["id"] = origin_id
2279 attachments = mess_data["extra"].get(C.KEY_ATTACHMENTS)
2280 if attachments:
2281 mb_data["extra"] = {
2282 C.KEY_ATTACHMENTS: attachments
2283 }
2284
2226 client = self.client.getVirtualClient(mess_data["from"]) 2285 client = self.client.getVirtualClient(mess_data["from"])
2227 ap_item = await self.mb_data_2_ap_item(client, mb_data, public=False) 2286 ap_item = await self.mb_data_2_ap_item(client, mb_data, public=False)
2228 ap_object = ap_item["object"] 2287 ap_object = ap_item["object"]
2229 ap_object["to"] = ap_item["to"] = [actor_id] 2288 ap_object["to"] = ap_item["to"] = [actor_id]
2230 # we add a mention to direct message, otherwise peer is not notified in some AP 2289 # we add a mention to direct message, otherwise peer is not notified in some AP
2337 ) 2396 )
2338 return False 2397 return False
2339 2398
2340 cached_item = cached_items[0] 2399 cached_item = cached_items[0]
2341 2400
2342 mb_data = await self._m.item2mbdata( 2401 mb_data = await self._m.item_2_mb_data(
2343 client, cached_item.data, pubsub_service, pubsub_node 2402 client, cached_item.data, pubsub_service, pubsub_node
2344 ) 2403 )
2345 ap_item = await self.mb_data_2_ap_item(client, mb_data) 2404 ap_item = await self.mb_data_2_ap_item(client, mb_data)
2346 ap_object = ap_item["object"] 2405 ap_object = ap_item["object"]
2347 ap_object["to"] = [actor_id] 2406 ap_object["to"] = [actor_id]
2394 except IndexError: 2453 except IndexError:
2395 log.warning( 2454 log.warning(
2396 f"Can't find parent item at {parent_item_service} (node " 2455 f"Can't find parent item at {parent_item_service} (node "
2397 f"{parent_item_node!r})\n{pformat(ap_item)}") 2456 f"{parent_item_node!r})\n{pformat(ap_item)}")
2398 return 2457 return
2399 parent_item_parsed = await self._m.item2mbdata( 2458 parent_item_parsed = await self._m.item_2_mb_data(
2400 client, parent_item_elt, parent_item_service, parent_item_node 2459 client, parent_item_elt, parent_item_service, parent_item_node
2401 ) 2460 )
2402 try: 2461 try:
2403 comment_service = jid.JID(parent_item_parsed["comments"][0]["service"]) 2462 comment_service = jid.JID(parent_item_parsed["comments"][0]["service"])
2404 comment_node = parent_item_parsed["comments"][0]["node"] 2463 comment_node = parent_item_parsed["comments"][0]["node"]
2486 @param item: AP object payload 2545 @param item: AP object payload
2487 """ 2546 """
2488 is_public, targets, mentions = self.getAPItemTargets(item) 2547 is_public, targets, mentions = self.getAPItemTargets(item)
2489 if not is_public and targets.keys() == {TYPE_ACTOR}: 2548 if not is_public and targets.keys() == {TYPE_ACTOR}:
2490 # this is a direct message 2549 # this is a direct message
2491 await self.handleMessageAPItem( 2550 await self.handle_message_ap_item(
2492 client, targets, mentions, destinee, item 2551 client, targets, mentions, destinee, item
2493 ) 2552 )
2494 else: 2553 else:
2495 await self.handlePubsubAPItem( 2554 await self.handlePubsubAPItem(
2496 client, targets, mentions, destinee, node, item, is_public 2555 client, targets, mentions, destinee, node, item, is_public
2497 ) 2556 )
2498 2557
2499 async def handleMessageAPItem( 2558 async def handle_message_ap_item(
2500 self, 2559 self,
2501 client: SatXMPPEntity, 2560 client: SatXMPPEntity,
2502 targets: Dict[str, Set[str]], 2561 targets: Dict[str, Set[str]],
2503 mentions: List[Dict[str, str]], 2562 mentions: List[Dict[str, str]],
2504 destinee: Optional[jid.JID], 2563 destinee: Optional[jid.JID],
2515 for t_set in targets.values() 2574 for t_set in targets.values()
2516 for t in t_set 2575 for t in t_set
2517 } 2576 }
2518 if destinee is not None: 2577 if destinee is not None:
2519 targets_jids.add(destinee) 2578 targets_jids.add(destinee)
2520 mb_data = await self.apItem2MBdata(item) 2579 mb_data = await self.ap_item_2_mb_data(item)
2580 extra = {
2581 "origin_id": mb_data["id"]
2582 }
2583 attachments = mb_data["extra"].get(C.KEY_ATTACHMENTS)
2584 if attachments:
2585 extra[C.KEY_ATTACHMENTS] = attachments
2586
2521 defer_l = [] 2587 defer_l = []
2522 for target_jid in targets_jids: 2588 for target_jid in targets_jids:
2523 defer_l.append( 2589 defer_l.append(
2524 client.sendMessage( 2590 client.sendMessage(
2525 target_jid, 2591 target_jid,
2526 {'': mb_data.get("content", "")}, 2592 {'': mb_data.get("content", "")},
2527 mb_data.get("title"), 2593 mb_data.get("title"),
2528 extra={"origin_id": mb_data["id"]} 2594 extra=extra
2529 ) 2595 )
2530 ) 2596 )
2531 await defer.DeferredList(defer_l) 2597 await defer.DeferredList(defer_l)
2532 2598
2533 async def notifyMentions( 2599 async def notifyMentions(