component AP gateway: handle attachments
This commit is contained in:
parent
072f0cc723
commit
c3f97ec712
4 changed files with 168 additions and 97 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue