component AP gateway: handle attachments

This commit is contained in:
Goffi 2023-03-23 15:42:21 +01:00
commit c3f97ec712
13 changed files with 168 additions and 97 deletions

View file

@ -132,11 +132,6 @@ class Const(object):
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 @@ class Const(object):
## 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"

View file

@ -821,7 +821,7 @@ class SatXMPPEntity(core_types.SatXMPPEntity):
"""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 @@ class SatMessageProtocol(xmppim.MessageProtocol):
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

View file

@ -109,7 +109,7 @@ PLUGIN_INFO = {
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 @@ class APGateway:
# 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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
)
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
)
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 @@ class APGateway:
"""
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
)
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 @@ class APGateway:
)
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
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 @@ class APGateway:
}
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 @@ class APGateway:
target_jid,
{'': mb_data.get("content", "")},
mb_data.get("title"),
extra={"origin_id": mb_data["id"]}
extra=extra
)
)
await defer.DeferredList(defer_l)

View file

@ -230,7 +230,7 @@ class APEvents:
"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)

View file

@ -215,7 +215,7 @@ class HTTPAPGServer(web_resource.Resource):
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 @@ class HTTPAPGServer(web_resource.Resource):
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,

View file

@ -514,7 +514,7 @@ class APPubsubService(rsm.PubSubService):
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 @@ class APPubsubService(rsm.PubSubService):
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

View file

@ -59,6 +59,7 @@ class AttachPlugin:
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 @@ class AttachPlugin:
"""
# 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 @@ class AttachPlugin:
_("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 @@ class AttachPlugin:
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 @@ class AttachPlugin:
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 @@ class AttachPlugin:
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():

View file

@ -156,7 +156,7 @@ class AESGCM(object):
# 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 @@ class AESGCM(object):
# 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 @@ class AESGCM(object):
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 @@ class AESGCM(object):
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 @@ class AESGCM(object):
}
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

View file

@ -97,7 +97,7 @@ class XEP_0277(object):
"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 @@ class XEP_0277(object):
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 @@ class XEP_0277(object):
## 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 @@ class XEP_0277(object):
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 @@ class XEP_0277(object):
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 @@ class XEP_0277(object):
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 @@ class XEP_0277(object):
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 @@ class XEP_0277(object):
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 @@ class XEP_0277(object):
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 @@ class XEP_0277(object):
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)

View file

@ -267,7 +267,7 @@ class XEP_0447:
@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 @@ class XEP_0447:
data: Dict[str, Any]
) -> Dict[str, Any]:
"""Check <message> 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
# <file-sharing> 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 <message/>, 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 @@ class XEP_0447:
# 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 @@ class XEP_0447:
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 @@ class XEP_0447:
message={'': ''},
subject=data['subject'],
mess_type=data['type'],
extra={C.MESS_KEY_ATTACHMENTS: [attachment]},
extra={C.KEY_ATTACHMENTS: [attachment]},
)
if ((not data['extra']

View file

@ -263,7 +263,7 @@ class XEP_0448:
# XXX: for now, XEP-0447/XEP-0448 only allow to send one file per <message/>, 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 @@ class XEP_0448:
# 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 @@ class XEP_0448:
message={'': ''},
subject=data['subject'],
mess_type=data['type'],
extra={C.MESS_KEY_ATTACHMENTS: [attachment]},
extra={C.KEY_ATTACHMENTS: [attachment]},
)
if ((not data['extra']

View file

@ -145,7 +145,7 @@ class Send(base.CommandBase):
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 @@ class Send(base.CommandBase):
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,

View file

@ -223,7 +223,7 @@ class Message:
@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 @@ class QuickChat(quick_widgets.QuickWidget):
)
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