# HG changeset patch # User Goffi # Date 1679582541 -3600 # Node ID 78b5f356900c16058980e048211158ba6fa18421 # Parent cdb7de398c85150badcdb4be03223d3c4e7379ad component AP gateway: handle attachments diff -r cdb7de398c85 -r 78b5f356900c sat/core/constants.py --- a/sat/core/constants.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/core/constants.py Thu Mar 23 15:42:21 2023 +0100 @@ -132,11 +132,6 @@ MESS_KEY_ENCRYPTED = "encrypted" MESS_KEY_TRUSTED = "trusted" - MESS_KEY_ATTACHMENTS = "attachments" - MESS_KEY_ATTACHMENTS_MEDIA_TYPE = "media_type" - MESS_KEY_ATTACHMENTS_PREVIEW = "preview" - MESS_KEY_ATTACHMENTS_RESIZE = "resize" - # File encryption algorithms ENC_AES_GCM = "AES-GCM" @@ -368,6 +363,11 @@ ## Common data keys ## KEY_THUMBNAILS = "thumbnails" KEY_PROGRESS_ID = "progress_id" + KEY_ATTACHMENTS = "attachments" + KEY_ATTACHMENTS_MEDIA_TYPE = "media_type" + KEY_ATTACHMENTS_PREVIEW = "preview" + KEY_ATTACHMENTS_RESIZE = "resize" + ## Common extra keys/values ## KEY_ORDER_BY = "order_by" diff -r cdb7de398c85 -r 78b5f356900c sat/core/xmpp.py --- a/sat/core/xmpp.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/core/xmpp.py Thu Mar 23 15:42:21 2023 +0100 @@ -821,7 +821,7 @@ """Return True if a message contain payload to show in frontends""" return ( mess_data["message"] or mess_data["subject"] - or mess_data["extra"].get(C.MESS_KEY_ATTACHMENTS) + or mess_data["extra"].get(C.KEY_ATTACHMENTS) or mess_data["type"] == C.MESS_TYPE_INFO ) @@ -1361,16 +1361,16 @@ def completeAttachments(self, data): """Complete missing metadata of attachments""" - for attachment in data['extra'].get(C.MESS_KEY_ATTACHMENTS, []): + for attachment in data['extra'].get(C.KEY_ATTACHMENTS, []): if "name" not in attachment and "url" in attachment: name = (Path(unquote(urlparse(attachment['url']).path)).name or C.FILE_DEFAULT_NAME) attachment["name"] = name - if ((C.MESS_KEY_ATTACHMENTS_MEDIA_TYPE not in attachment + if ((C.KEY_ATTACHMENTS_MEDIA_TYPE not in attachment and "name" in attachment)): media_type = mimetypes.guess_type(attachment['name'], strict=False)[0] if media_type: - attachment[C.MESS_KEY_ATTACHMENTS_MEDIA_TYPE] = media_type + attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type return data diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_comp_ap_gateway/__init__.py --- a/sat/plugins/plugin_comp_ap_gateway/__init__.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_comp_ap_gateway/__init__.py Thu Mar 23 15:42:21 2023 +0100 @@ -109,7 +109,7 @@ C.PI_DEPENDENCIES: [ "XEP-0050", "XEP-0054", "XEP-0060", "XEP-0084", "XEP-0106", "XEP-0277", "XEP-0292", "XEP-0329", "XEP-0372", "XEP-0424", "XEP-0465", "XEP-0470", - "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY", "EVENTS" + "XEP-0447", "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY", "EVENTS" ], C.PI_RECOMMENDATIONS: [], C.PI_MAIN: "APGateway", @@ -131,7 +131,7 @@ # 1: log POST objects # 2: log POST and GET objects # 3: log POST and GET objects with HTTP headers for GET requests - verbose = 0 + verbose = 3 def __init__(self, host): self.host = host @@ -144,11 +144,12 @@ self._v = host.plugins["XEP-0292"] self._refs = host.plugins["XEP-0372"] self._r = host.plugins["XEP-0424"] + self._sfs = host.plugins["XEP-0447"] self._pps = host.plugins["XEP-0465"] + self._pa = host.plugins["XEP-0470"] self._c = host.plugins["PUBSUB_CACHE"] self._t = host.plugins["TEXT_SYNTAXES"] self._i = host.plugins["IDENTITY"] - self._pa = host.plugins["XEP-0470"] self._events = host.plugins["EVENTS"] self._p.addManagedNode( "", @@ -487,7 +488,7 @@ return ap_item["object"] else: # this is a blog item - mb_data = await self._m.item2mbdata( + mb_data = await self._m.item_2_mb_data( self.client, found_item, author_jid, node ) ap_item = await self.mb_data_2_ap_item(self.client, mb_data) @@ -901,7 +902,7 @@ pub_key = serialization.load_pem_public_key(pub_key_pem.encode()) return key_id, owner, pub_key - def createActivity( + def create_activity( self, activity: str, actor_id: str, @@ -1094,7 +1095,7 @@ ) else: # blog item - mb_data = await self._m.item2mbdata(client, item, service, node) + mb_data = await self._m.item_2_mb_data(client, item, service, node) author_jid = jid.JID(mb_data["author_jid"]) if subscribe_extra_nodes and not self.isVirtualJID(author_jid): # we subscribe automatically to comment nodes if any @@ -1232,7 +1233,7 @@ if not "noticed" in old_attachment: # new "noticed" attachment, we translate to "Like" activity activity_id = self.buildAPURL("like", item_account, item_id) - activity = self.createActivity( + activity = self.create_activity( TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id ) activity["to"] = [ap_account] @@ -1242,12 +1243,12 @@ if "noticed" in old_attachment: # "noticed" attachment has been removed, we undo the "Like" activity activity_id = self.buildAPURL("like", item_account, item_id) - activity = self.createActivity( + activity = self.create_activity( TYPE_LIKE, publisher_actor_id, item_url, activity_id=activity_id ) activity["to"] = [ap_account] activity["cc"] = [NS_AP_PUBLIC] - undo = self.createActivity("Undo", publisher_actor_id, activity) + undo = self.create_activity("Undo", publisher_actor_id, activity) await self.signAndPost(inbox, publisher_actor_id, undo) # reactions @@ -1260,7 +1261,7 @@ activity_id = self.buildAPURL( "reaction", item_account, item_id, reaction.encode().hex() ) - reaction_activity = self.createActivity( + reaction_activity = self.create_activity( TYPE_REACTION, publisher_actor_id, item_url, activity_id=activity_id ) @@ -1268,7 +1269,7 @@ reaction_activity["to"] = [ap_account] reaction_activity["cc"] = [NS_AP_PUBLIC] if undo: - activy = self.createActivity( + activy = self.create_activity( "Undo", publisher_actor_id, reaction_activity ) else: @@ -1282,7 +1283,7 @@ if attending != old_attending: activity_type = TYPE_JOIN if attending == "yes" else TYPE_LEAVE activity_id = self.buildAPURL(activity_type.lower(), item_account, item_id) - activity = self.createActivity( + activity = self.create_activity( activity_type, publisher_actor_id, item_url, activity_id=activity_id ) activity["to"] = [ap_account] @@ -1293,7 +1294,7 @@ old_attending = old_attachment.get("rsvp", {}).get("attending", "no") if old_attending == "yes": activity_id = self.buildAPURL(TYPE_LEAVE.lower(), item_account, item_id) - activity = self.createActivity( + activity = self.create_activity( TYPE_LEAVE, publisher_actor_id, item_url, activity_id=activity_id ) activity["to"] = [ap_account] @@ -1645,8 +1646,8 @@ async def ap_item_2_mb_data_and_elt(self, ap_item: dict) -> Tuple[dict, domish.Element]: """Convert AP item to parsed microblog data and corresponding item element""" - mb_data = await self.apItem2MBdata(ap_item) - item_elt = await self._m.data2entry( + mb_data = await self.ap_item_2_mb_data(ap_item) + item_elt = await self._m.mb_data_2_entry_elt( self.client, mb_data, mb_data["id"], None, self._m.namespace ) if "repeated" in mb_data["extra"]: @@ -1742,7 +1743,7 @@ None ) - async def apItem2MBdata(self, ap_item: dict) -> dict: + async def ap_item_2_mb_data(self, ap_item: dict) -> dict: """Convert AP activity or object to microblog data @param ap_item: ActivityPub item to convert @@ -1786,6 +1787,32 @@ False, ) + if "attachment" in ap_object: + attachments = mb_data["extra"][C.KEY_ATTACHMENTS] = [] + for ap_attachment in ap_object["attachment"]: + try: + url = ap_attachment["url"] + except KeyError: + log.warning( + f'"url" missing in AP attachment, ignoring: {ap_attachment}' + ) + continue + + if not url.startswith("http"): + log.warning(f"non HTTP URL in attachment, ignoring: {ap_attachment}") + continue + attachment = {"url": url} + for ap_key, key in ( + ("mediaType", "media_type"), + # XXX: as weird as it seems, "name" is actually used for description + # in AP world + ("name", "desc"), + ): + value = ap_attachment.get(ap_key) + if value: + attachment[key] = value + attachments.append(attachment) + # author if is_activity: authors = await self.apGetActors(ap_item, "actor") @@ -1899,7 +1926,7 @@ TYPE_ITEM, parent_ap_account, parent_item ) - async def repeatedMB2APItem( + async def repeated_mb_2_ap_item( self, mb_data: dict ) -> dict: @@ -1952,7 +1979,7 @@ ) announced_uri = self.buildAPURL("item", repeated_account, rep_item) - announce = self.createActivity( + announce = self.create_activity( "Announce", repeater_id, announced_uri, activity_id=activity_id ) announce["to"] = [NS_AP_PUBLIC] @@ -1988,7 +2015,7 @@ """ extra = mb_data.get("extra", {}) if "repeated" in extra: - return await self.repeatedMB2APItem(mb_data) + return await self.repeated_mb_2_ap_item(mb_data) if not mb_data.get("id"): mb_data["id"] = shortuuid.uuid() if not mb_data.get("author_jid"): @@ -2011,6 +2038,32 @@ if language: ap_object["contentMap"] = {language: ap_object["content"]} + attachments = extra.get(C.KEY_ATTACHMENTS) + if attachments: + ap_attachments = ap_object["attachment"] = [] + for attachment in attachments: + try: + url = next( + s['url'] for s in attachment["sources"] if 'url' in s + ) + except (StopIteration, KeyError): + log.warning( + f"Ignoring attachment without URL: {attachment}" + ) + continue + ap_attachment = { + "url": url + } + for key, ap_key in ( + ("media_type", "mediaType"), + # XXX: yes "name", cf. [ap_item_2_mb_data] + ("desc", "name"), + ): + value = attachment.get(key) + if value: + ap_attachment[ap_key] = value + ap_attachments.append(ap_attachment) + if public: ap_object["to"] = [NS_AP_PUBLIC] if self.auto_mentions: @@ -2068,7 +2121,7 @@ mb_data ) - return self.createActivity( + return self.create_activity( "Create" if is_new else "Update", url_actor, ap_object, activity_id=url_item ) @@ -2144,12 +2197,12 @@ ) else: try: - mb_data = await self._m.item2mbdata(self.client, items[0].data, jid_, node) + mb_data = await self._m.item_2_mb_data(self.client, items[0].data, jid_, node) if "repeated" in mb_data["extra"]: # we are deleting a repeated item, we must translate this to an # "Undo" of the "Announce" activity instead of a "Delete" one - announce = await self.repeatedMB2APItem(mb_data) - undo = self.createActivity("Undo", author_actor_id, announce) + announce = await self.repeated_mb_2_ap_item(mb_data) + undo = self.create_activity("Undo", author_actor_id, announce) return author_actor_id, undo except Exception as e: log.debug( @@ -2158,7 +2211,7 @@ ) url_item = self.buildAPURL(TYPE_ITEM, author_account, item_id) - ap_item = self.createActivity( + ap_item = self.create_activity( "Delete", author_actor_id, { @@ -2223,6 +2276,12 @@ if origin_id: # we need to use origin ID when present to be able to retract the message mb_data["id"] = origin_id + attachments = mess_data["extra"].get(C.KEY_ATTACHMENTS) + if attachments: + mb_data["extra"] = { + C.KEY_ATTACHMENTS: attachments + } + client = self.client.getVirtualClient(mess_data["from"]) ap_item = await self.mb_data_2_ap_item(client, mb_data, public=False) ap_object = ap_item["object"] @@ -2339,7 +2398,7 @@ cached_item = cached_items[0] - mb_data = await self._m.item2mbdata( + mb_data = await self._m.item_2_mb_data( client, cached_item.data, pubsub_service, pubsub_node ) ap_item = await self.mb_data_2_ap_item(client, mb_data) @@ -2396,7 +2455,7 @@ f"Can't find parent item at {parent_item_service} (node " f"{parent_item_node!r})\n{pformat(ap_item)}") return - parent_item_parsed = await self._m.item2mbdata( + parent_item_parsed = await self._m.item_2_mb_data( client, parent_item_elt, parent_item_service, parent_item_node ) try: @@ -2488,7 +2547,7 @@ is_public, targets, mentions = self.getAPItemTargets(item) if not is_public and targets.keys() == {TYPE_ACTOR}: # this is a direct message - await self.handleMessageAPItem( + await self.handle_message_ap_item( client, targets, mentions, destinee, item ) else: @@ -2496,7 +2555,7 @@ client, targets, mentions, destinee, node, item, is_public ) - async def handleMessageAPItem( + async def handle_message_ap_item( self, client: SatXMPPEntity, targets: Dict[str, Set[str]], @@ -2517,7 +2576,14 @@ } if destinee is not None: targets_jids.add(destinee) - mb_data = await self.apItem2MBdata(item) + mb_data = await self.ap_item_2_mb_data(item) + extra = { + "origin_id": mb_data["id"] + } + attachments = mb_data["extra"].get(C.KEY_ATTACHMENTS) + if attachments: + extra[C.KEY_ATTACHMENTS] = attachments + defer_l = [] for target_jid in targets_jids: defer_l.append( @@ -2525,7 +2591,7 @@ target_jid, {'': mb_data.get("content", "")}, mb_data.get("title"), - extra={"origin_id": mb_data["id"]} + extra=extra ) ) await defer.DeferredList(defer_l) diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_comp_ap_gateway/events.py --- a/sat/plugins/plugin_comp_ap_gateway/events.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_comp_ap_gateway/events.py Thu Mar 23 15:42:21 2023 +0100 @@ -230,7 +230,7 @@ "value": ap_wc_value }) - activity = self.apg.createActivity( + activity = self.apg.create_activity( "Create" if is_new else "Update", url_actor, ap_object, activity_id=url_item ) activity["@context"].append(AP_EVENTS_CONTEXT) diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_comp_ap_gateway/http_server.py --- a/sat/plugins/plugin_comp_ap_gateway/http_server.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_comp_ap_gateway/http_server.py Thu Mar 23 15:42:21 2023 +0100 @@ -215,7 +215,7 @@ raise exceptions.InternalError('"subscribed" state was expected') 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_data = self.apg.create_activity( "Accept", actor_id, object_=data ) await self.apg.signAndPost(inbox, actor_id, accept_data) @@ -715,7 +715,7 @@ ordered_items = [ await self.apg.mb_data_2_ap_item( self.apg.client, - await self.apg._m.item2mbdata( + await self.apg._m.item_2_mb_data( self.apg.client, item, account_jid, diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_comp_ap_gateway/pubsub_service.py --- a/sat/plugins/plugin_comp_ap_gateway/pubsub_service.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_comp_ap_gateway/pubsub_service.py Thu Mar 23 15:42:21 2023 +0100 @@ -514,7 +514,7 @@ requestor, service ) - data = self.apg.createActivity("Follow", req_actor_id, recip_actor_id) + data = self.apg.create_activity("Follow", req_actor_id, recip_actor_id) resp = await self.apg.signAndPost(inbox, req_actor_id, data) if resp.code >= 300: @@ -527,10 +527,10 @@ req_actor_id, recip_actor_id, inbox = await self.getAPActorIdsAndInbox( requestor, service ) - data = self.apg.createActivity( + data = self.apg.create_activity( "Undo", req_actor_id, - self.apg.createActivity( + self.apg.create_activity( "Follow", req_actor_id, recip_actor_id diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_misc_attach.py --- a/sat/plugins/plugin_misc_attach.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_misc_attach.py Thu Mar 23 15:42:21 2023 +0100 @@ -59,6 +59,7 @@ self.host = host self._u = host.plugins["UPLOAD"] host.trigger.add("sendMessage", self._sendMessageTrigger) + host.trigger.add("sendMessageComponent", self._sendMessageTrigger) self._attachments_handlers = {'clear': [], 'encrypted': []} self.register(self.defaultCanHandle, self.defaultAttach, False, -1000) @@ -100,13 +101,13 @@ """ # we check attachment for pre-treatment like large image resizing # media_type will be added if missing (and if it can be guessed from path) - attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] + attachments = data["extra"][C.KEY_ATTACHMENTS] tmp_dirs_to_clean = [] for attachment in attachments: - if attachment.get(C.MESS_KEY_ATTACHMENTS_RESIZE, False): + if attachment.get(C.KEY_ATTACHMENTS_RESIZE, False): path = Path(attachment["path"]) try: - media_type = attachment[C.MESS_KEY_ATTACHMENTS_MEDIA_TYPE] + media_type = attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] except KeyError: media_type = mimetypes.guess_type(path, strict=False)[0] if media_type is None: @@ -114,7 +115,7 @@ _("Can't resize attachment of unknown type: {attachment}") .format(attachment=attachment)) continue - attachment[C.MESS_KEY_ATTACHMENTS_MEDIA_TYPE] = media_type + attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type main_type = media_type.split('/')[0] if main_type == "image": @@ -188,6 +189,9 @@ attachments = data["extra"]["attachments"] for attachment in attachments: + if "url" in attachment and not "path" in attachment: + log.debug(f"attachment is external, we don't upload it: {attachment}") + continue try: # we pop path because we don't want it to be stored, as the file can be # only in a temporary location @@ -252,7 +256,7 @@ def _sendMessageTrigger( self, client, mess_data, pre_xml_treatments, post_xml_treatments): - if mess_data['extra'].get(C.MESS_KEY_ATTACHMENTS): + if mess_data['extra'].get(C.KEY_ATTACHMENTS): post_xml_treatments.addCallback(self._attachFiles, client=client) return True @@ -265,7 +269,7 @@ body_elt = data["xml"].body if body_elt is None: body_elt = data["xml"].addElement("body") - attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] + attachments = data["extra"][C.KEY_ATTACHMENTS] if attachments: body_links = '\n'.join(a['url'] for a in attachments) if str(body_elt).strip(): diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_sec_aesgcm.py --- a/sat/plugins/plugin_sec_aesgcm.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_sec_aesgcm.py Thu Mar 23 15:42:21 2023 +0100 @@ -156,7 +156,7 @@ # TODO: this is to be removed when a better mechanism is available with OMEMO (now # possible with the 0.4 version of OMEMO, it's possible to encrypt other stanza # elements than body). - attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] + attachments = data["extra"][C.KEY_ATTACHMENTS] if not data['message'] or data['message'] == {'': ''}: extra_attachments = attachments[1:] del attachments[1:] @@ -165,7 +165,7 @@ # we have a message, we must send first attachment separately extra_attachments = attachments[:] attachments.clear() - del data["extra"][C.MESS_KEY_ATTACHMENTS] + del data["extra"][C.KEY_ATTACHMENTS] body_elt = data["xml"].body if body_elt is None: @@ -181,7 +181,7 @@ message={'': ''}, subject=data['subject'], mess_type=data['type'], - extra={C.MESS_KEY_ATTACHMENTS: [attachment]}, + extra={C.KEY_ATTACHMENTS: [attachment]}, ) if ((not data['extra'] @@ -299,7 +299,7 @@ else: data['message'][lang] = message mess_encrypted = client.encryption.isEncrypted(data) - attachments = data['extra'].setdefault(C.MESS_KEY_ATTACHMENTS, []) + attachments = data['extra'].setdefault(C.KEY_ATTACHMENTS, []) for link in links: path = parse.urlparse(link).path attachment = { @@ -307,7 +307,7 @@ } media_type = mimetypes.guess_type(path, strict=False)[0] if media_type is not None: - attachment[C.MESS_KEY_ATTACHMENTS_MEDIA_TYPE] = media_type + attachment[C.KEY_ATTACHMENTS_MEDIA_TYPE] = media_type if mess_encrypted: # we don't add the encrypted flag if the message itself is not diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_xep_0277.py --- a/sat/plugins/plugin_xep_0277.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_xep_0277.py Thu Mar 23 15:42:21 2023 +0100 @@ -97,7 +97,7 @@ "namespace": NS_ATOM, "type": "blog", "to_sync": True, - "parser": self.item2mbdata, + "parser": self.item_2_mb_data, "match_cb": self._cacheNodeMatchCb, } ) @@ -249,7 +249,7 @@ for item in itemsEvent.items: if item.name == C.PS_ITEM: # FIXME: service and node should be used here - self.item2mbdata(client, item, None, None).addCallbacks( + self.item_2_mb_data(client, item, None, None).addCallbacks( manageItem, lambda failure: None, (C.PS_PUBLISH,) ) elif item.name == C.PS_RETRACT: @@ -260,12 +260,12 @@ ## data/item transformation ## @defer.inlineCallbacks - def item2mbdata( + def item_2_mb_data( self, client: SatXMPPEntity, item_elt: domish.Element, service: Optional[jid.JID], - # FIXME: node is Optional until all calls to item2mbdata set properly service + # FIXME: node is Optional until all calls to item_2_mb_data set properly service # and node. Once done, the Optional must be removed here node: Optional[str] ) -> dict: @@ -615,7 +615,7 @@ defer.returnValue(microblog_data) - async def data2entry(self, client, mb_data, item_id, service, node): + async def mb_data_2_entry_elt(self, client, mb_data, item_id, service, node): """Convert a data dict to en entry usable to create an item @param mb_data: data dict as given by bridge method. @@ -1036,7 +1036,7 @@ await self._manageComments(client, data, service, node, item_id, access=None) except error.StanzaError: log.warning("Can't create comments node for item {}".format(item_id)) - item = await self.data2entry(client, data, item_id, service, node) + item = await self.mb_data_2_entry_elt(client, data, item_id, service, node) if not await self.host.trigger.asyncPoint( "XEP-0277_send", client, service, node, item, data @@ -1165,9 +1165,9 @@ item_id = data.get("id", "") # we have to serialise then deserialise to be sure that all triggers are called - item_elt = await self.data2entry(client, data, item_id, service, node) + item_elt = await self.mb_data_2_entry_elt(client, data, item_id, service, node) item_elt.uri = pubsub.NS_PUBSUB - return await self.item2mbdata(client, item_elt, service, node) + return await self.item_2_mb_data(client, item_elt, service, node) ## retract ## @@ -1243,7 +1243,7 @@ extra=extra, ) mb_data_list, metadata = await self._p.transItemsDataD( - items_data, partial(self.item2mbdata, client, service=service, node=node)) + items_data, partial(self.item_2_mb_data, client, service=service, node=node)) encrypted = metadata.pop("encrypted", None) if encrypted is not None: for mb_data in mb_data_list: @@ -1461,7 +1461,7 @@ d = self._p.transItemsDataD( items_data, # FIXME: service and node should be used here - partial(self.item2mbdata, client), + partial(self.item_2_mb_data, client), serialise=True ) d.addCallback(lambda serialised: ("", serialised)) @@ -1663,7 +1663,7 @@ lambda items_data: self._p.transItemsDataD( items_data, partial( - self.item2mbdata, client, service=service, node=node + self.item_2_mb_data, client, service=service, node=node ), serialise=True ) @@ -1704,7 +1704,7 @@ d.addCallback( lambda items_data: self._p.transItemsDataD( items_data, - partial(self.item2mbdata, client, service=service, node=node), + partial(self.item_2_mb_data, client, service=service, node=node), ) ) d.addCallback(getComments) diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_xep_0447.py --- a/sat/plugins/plugin_xep_0447.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_xep_0447.py Thu Mar 23 15:42:21 2023 +0100 @@ -267,7 +267,7 @@ @return: file-sharing data. It a dict whose keys correspond to [get_file_sharing_elt] parameters """ - if file_sharing_elt.name != "file-sharing": + if file_sharing_elt.name != "file-sharing" or file_sharing_elt.uri != NS_SFS: try: file_sharing_elt = next( file_sharing_elt.elements(NS_SFS, "file-sharing") @@ -295,31 +295,30 @@ data: Dict[str, Any] ) -> Dict[str, Any]: """Check for a shared file, and add it as an attachment""" - # XXX: XEP-0447 doesn't support several attachments in a single message, thus only - # one attachment can be added - try: + # XXX: XEP-0447 doesn't support several attachments in a single message, for now + # however that should be fixed in future version, and so we accept several + # element in a message. + for file_sharing_elt in message_elt.elements(NS_SFS, "file-sharing"): attachment = self.parse_file_sharing_elt(message_elt) - except exceptions.NotFound: - return data - if any( - s.get(C.MESS_KEY_ENCRYPTED, False) - for s in attachment["sources"] - ) and client.encryption.isEncrypted(data): - # we don't add the encrypted flag if the message itself is not encrypted, - # because the decryption key is part of the link, so sending it over - # unencrypted channel is like having no encryption at all. - attachment[C.MESS_KEY_ENCRYPTED] = True + if any( + s.get(C.MESS_KEY_ENCRYPTED, False) + for s in attachment["sources"] + ) and client.encryption.isEncrypted(data): + # we don't add the encrypted flag if the message itself is not encrypted, + # because the decryption key is part of the link, so sending it over + # unencrypted channel is like having no encryption at all. + attachment[C.MESS_KEY_ENCRYPTED] = True - attachments = data['extra'].setdefault(C.MESS_KEY_ATTACHMENTS, []) - attachments.append(attachment) + attachments = data['extra'].setdefault(C.KEY_ATTACHMENTS, []) + attachments.append(attachment) return data async def attach(self, client, data): # XXX: for now, XEP-0447 only allow to send one file per , thus we need # to send each file in a separate message - attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] + attachments = data["extra"][C.KEY_ATTACHMENTS] if not data['message'] or data['message'] == {'': ''}: extra_attachments = attachments[1:] del attachments[1:] @@ -327,7 +326,7 @@ # we have a message, we must send first attachment separately extra_attachments = attachments[:] attachments.clear() - del data["extra"][C.MESS_KEY_ATTACHMENTS] + del data["extra"][C.KEY_ATTACHMENTS] if attachments: if len(attachments) > 1: @@ -343,8 +342,10 @@ file_hash = None file_sharing_elt = self.get_file_sharing_elt( [{"url": attachment["url"]}], - name=attachment["name"], - size=attachment["size"], + name=attachment.get("name"), + size=attachment.get("size"), + desc=attachment.get("desc"), + media_type=attachment.get("media_type"), file_hash=file_hash ) data["xml"].addChild(file_sharing_elt) @@ -356,7 +357,7 @@ message={'': ''}, subject=data['subject'], mess_type=data['type'], - extra={C.MESS_KEY_ATTACHMENTS: [attachment]}, + extra={C.KEY_ATTACHMENTS: [attachment]}, ) if ((not data['extra'] diff -r cdb7de398c85 -r 78b5f356900c sat/plugins/plugin_xep_0448.py --- a/sat/plugins/plugin_xep_0448.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat/plugins/plugin_xep_0448.py Thu Mar 23 15:42:21 2023 +0100 @@ -263,7 +263,7 @@ # XXX: for now, XEP-0447/XEP-0448 only allow to send one file per , thus # we need to send each file in a separate message, in the same way as for # plugin_sec_aesgcm. - attachments = data["extra"][C.MESS_KEY_ATTACHMENTS] + attachments = data["extra"][C.KEY_ATTACHMENTS] if not data['message'] or data['message'] == {'': ''}: extra_attachments = attachments[1:] del attachments[1:] @@ -271,7 +271,7 @@ # we have a message, we must send first attachment separately extra_attachments = attachments[:] attachments.clear() - del data["extra"][C.MESS_KEY_ATTACHMENTS] + del data["extra"][C.KEY_ATTACHMENTS] if attachments: if len(attachments) > 1: @@ -319,7 +319,7 @@ message={'': ''}, subject=data['subject'], mess_type=data['type'], - extra={C.MESS_KEY_ATTACHMENTS: [attachment]}, + extra={C.KEY_ATTACHMENTS: [attachment]}, ) if ((not data['extra'] diff -r cdb7de398c85 -r 78b5f356900c sat_frontends/jp/cmd_message.py --- a/sat_frontends/jp/cmd_message.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat_frontends/jp/cmd_message.py Thu Mar 23 15:42:21 2023 +0100 @@ -145,7 +145,7 @@ to_send.append(msg) if self.args.attachments: - attachments = extra[C.MESS_KEY_ATTACHMENTS] = [] + attachments = extra[C.KEY_ATTACHMENTS] = [] for attachment in self.args.attachments: try: file_path = str(Path(attachment).resolve(strict=True)) @@ -155,10 +155,10 @@ attachments.append({"path": file_path}) for idx, msg in enumerate(to_send): - if idx > 0 and C.MESS_KEY_ATTACHMENTS in extra: + if idx > 0 and C.KEY_ATTACHMENTS in extra: # if we send several messages, we only want to send attachments with the # first one - del extra[C.MESS_KEY_ATTACHMENTS] + del extra[C.KEY_ATTACHMENTS] try: await self.host.bridge.messageSend( dest_jid, diff -r cdb7de398c85 -r 78b5f356900c sat_frontends/quick_frontend/quick_chat.py --- a/sat_frontends/quick_frontend/quick_chat.py Thu Mar 23 15:39:48 2023 +0100 +++ b/sat_frontends/quick_frontend/quick_chat.py Thu Mar 23 15:42:21 2023 +0100 @@ -223,7 +223,7 @@ @property def attachments(self): - return self.extra.get(C.MESS_KEY_ATTACHMENTS) + return self.extra.get(C.KEY_ATTACHMENTS) class MessageWidget: @@ -733,7 +733,7 @@ ) return - if ((not msg and not subject and not extra[C.MESS_KEY_ATTACHMENTS] + if ((not msg and not subject and not extra[C.KEY_ATTACHMENTS] and type_ != C.MESS_TYPE_INFO)): log.warning("Received an empty message for uid {}".format(uid)) return