component AP gateway: handle attachments

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

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