component AP gateway: convert AP mention to XEP-0372 mentions:

when a mentions are found in AP items (either with people specified directly as target, of
with `mention` tags as in
https://www.w3.org/TR/activitystreams-vocabulary/#microsyntaxes), they are converted to
XEP-0372 `mention` references.

This is only done for Pubsub items (i.e. not for private messages).

rel 369
This commit is contained in:
Goffi 2022-07-10 15:16:15 +02:00
commit 2c654e017d
2 changed files with 103 additions and 16 deletions

View file

@ -70,6 +70,7 @@ from .constants import (
TYPE_ITEM,
TYPE_FOLLOWERS,
TYPE_TOMBSTONE,
TYPE_MENTION,
NS_AP_PUBLIC,
PUBLIC_TUPLE
)
@ -89,7 +90,8 @@ PLUGIN_INFO = {
C.PI_PROTOCOLS: [],
C.PI_DEPENDENCIES: [
"XEP-0060", "XEP-0084", "XEP-0106", "XEP-0277", "XEP-0292", "XEP-0329",
"XEP-0424", "XEP-0465", "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY", "XEP-0054"
"XEP-0372", "XEP-0424", "XEP-0465", "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY",
"XEP-0054"
],
C.PI_RECOMMENDATIONS: [],
C.PI_MAIN: "APGateway",
@ -117,6 +119,7 @@ class APGateway:
self._e = host.plugins["XEP-0106"]
self._m = host.plugins["XEP-0277"]
self._v = host.plugins["XEP-0292"]
self._refs = host.plugins["XEP-0372"]
self._r = host.plugins["XEP-0424"]
self._pps = host.plugins["XEP-0465"]
self._c = host.plugins["PUBSUB_CACHE"]
@ -1694,6 +1697,8 @@ class APGateway:
self,
client: SatXMPPEntity,
ap_item: dict,
targets: Dict[str, Set[str]],
mentions: List[Dict[str, str]],
) -> None:
"""We got an AP item which is a reply to an XMPP item"""
in_reply_to = ap_item["inReplyTo"]
@ -1741,17 +1746,22 @@ class APGateway:
else:
__, item_elt = await self.apItem2MbDataAndElt(ap_item)
await self._p.publish(client, comment_service, comment_node, [item_elt])
await self.notifyMentions(
targets, mentions, comment_service, comment_node, item_elt["id"]
)
def getAPItemTargets(self, item: Dict[str, Any]) -> Tuple[bool, Set[str], Set[str]]:
def getAPItemTargets(
self,
item: Dict[str, Any]
) -> Tuple[bool, Dict[str, Set[str]], List[Dict[str, str]]]:
"""Retrieve targets of an AP item, and indicate if it's a public one
@param item: AP object payload
@return: Are returned:
- is_public flag, indicating if the item is world-readable
- targets of the item
- targets of the items
- a dict mapping target type to targets
"""
targets: Set[str] = set()
targets: Dict[str, Set[str]] = {}
is_public = False
# TODO: handle "audience"
for key in ("to", "bto", "cc", "bcc"):
@ -1768,10 +1778,35 @@ class APGateway:
continue
if not self.isLocalURL(value):
continue
targets.add(value)
target_type = self.parseAPURL(value)[0]
if target_type != TYPE_ACTOR:
log.debug(f"ignoring non actor type as a target: {href}")
else:
targets.setdefault(target_type, set()).add(value)
targets_types = {self.parseAPURL(t)[0] for t in targets}
return is_public, targets, targets_types
mentions = []
tags = item.get("tag")
if tags:
for tag in tags:
if tag.get("type") != TYPE_MENTION:
continue
href = tag.get("href")
if not href:
log.warning('Missing "href" field from mention object: {tag!r}')
continue
if not self.isLocalURL(href):
continue
uri_type = self.parseAPURL(href)[0]
if uri_type != TYPE_ACTOR:
log.debug(f"ignoring non actor URI as a target: {href}")
continue
mention = {"uri": href}
mentions.append(mention)
name = tag.get("name")
if name:
mention["content"] = name
return is_public, targets, mentions
async def newAPItem(
self,
@ -1786,21 +1821,22 @@ class APGateway:
@param node: XMPP pubsub node
@param item: AP object payload
"""
is_public, targets, targets_types = self.getAPItemTargets(item)
if not is_public and targets_types == {TYPE_ACTOR}:
is_public, targets, mentions = self.getAPItemTargets(item)
if not is_public and targets.keys() == {TYPE_ACTOR}:
# this is a direct message
await self.handleMessageAPItem(
client, targets, destinee, item
client, targets, mentions, destinee, item
)
else:
await self.handlePubsubAPItem(
client, targets, destinee, node, item, is_public
client, targets, mentions, destinee, node, item, is_public
)
async def handleMessageAPItem(
self,
client: SatXMPPEntity,
targets: Set[str],
targets: Dict[str, Set[str]],
mentions: List[Dict[str, str]],
destinee: Optional[jid.JID],
item: dict,
) -> None:
@ -1810,7 +1846,11 @@ class APGateway:
@param destinee: jid of the destinee,
@param item: AP object payload
"""
targets_jids = {await self.getJIDFromId(t) for t in targets}
targets_jids = {
await self.getJIDFromId(t)
for t_set in targets.values()
for t in t_set
}
if destinee is not None:
targets_jids.add(destinee)
mb_data = await self.apItem2MBdata(item)
@ -1826,10 +1866,53 @@ class APGateway:
)
await defer.DeferredList(defer_l)
async def notifyMentions(
self,
targets: Dict[str, Set[str]],
mentions: List[Dict[str, str]],
service: jid.JID,
node: str,
item_id: str,
) -> None:
"""Send mention notifications to recipients and mentioned entities
XEP-0372 (References) is used.
Mentions are also sent to recipients as they are primary audience (see
https://www.w3.org/TR/activitystreams-vocabulary/#microsyntaxes).
"""
anchor = uri.buildXMPPUri("pubsub", path=service.full(), node=node, item=item_id)
seen = set()
# we start with explicit mentions because mentions' content will be used in the
# future to fill "begin" and "end" reference attributes (we can't do it at the
# moment as there is no way to specify the XML element to use in the blog item).
for mention in mentions:
mentioned_jid = await self.getJIDFromId(mention["uri"])
self._refs.sendReference(
self.client,
to_jid=mentioned_jid,
anchor=anchor
)
seen.add(mentioned_jid)
remaining = {
await self.getJIDFromId(t)
for t_set in targets.values()
for t in t_set
} - seen
for target in remaining:
self._refs.sendReference(
self.client,
to_jid=target,
anchor=anchor
)
async def handlePubsubAPItem(
self,
client: SatXMPPEntity,
targets: Set[str],
targets: Dict[str, Set[str]],
mentions: List[Dict[str, str]],
destinee: Optional[jid.JID],
node: str,
item: dict,
@ -1853,7 +1936,8 @@ class APGateway:
if in_reply_to and isinstance(in_reply_to, str):
if self.isLocalURL(in_reply_to):
# this is a reply to an XMPP item
return await self.newReplyToXMPPItem(client, item)
await self.newReplyToXMPPItem(client, item, targets, mentions)
return
# this item is a reply to an AP item, we use or create a corresponding node
# for comments
@ -1910,6 +1994,8 @@ class APGateway:
[(subscription.subscriber, None, [item_elt])]
)
await self.notifyMentions(targets, mentions, service, node, item_elt["id"])
async def newAPDeleteItem(
self,
client: SatXMPPEntity,

View file

@ -28,6 +28,7 @@ TYPE_FOLLOWERS = "followers"
TYPE_FOLLOWING = "following"
TYPE_ITEM = "item"
TYPE_TOMBSTONE = "Tombstone"
TYPE_MENTION = "Mention"
MEDIA_TYPE_AP = "application/activity+json"
NS_AP_PUBLIC = "https://www.w3.org/ns/activitystreams#Public"
# 3 values can be used, see https://www.w3.org/TR/activitypub/#public-addressing