comparison sat/plugins/plugin_comp_ap_gateway/http_server.py @ 3869:c0bcbcf5b4b7

component AP gateway: handle `Like` and `Undo`/`Like` activities: rel 370
author Goffi <goffi@goffi.org>
date Thu, 21 Jul 2022 18:07:35 +0200
parents aaa4e7815ba8
children bd84d289fc94
comparison
equal deleted inserted replaced
3868:37d2c0282304 3869:c0bcbcf5b4b7
32 from wokkel import pubsub, rsm 32 from wokkel import pubsub, rsm
33 33
34 from sat.core import exceptions 34 from sat.core import exceptions
35 from sat.core.constants import Const as C 35 from sat.core.constants import Const as C
36 from sat.core.i18n import _ 36 from sat.core.i18n import _
37 from sat.core.core_types import SatXMPPEntity
37 from sat.core.log import getLogger 38 from sat.core.log import getLogger
38 from sat.tools.common import date_utils, uri 39 from sat.tools.common import date_utils, uri
39 from sat.memory.sqla_mapping import SubscriptionState 40 from sat.memory.sqla_mapping import SubscriptionState
40 41
41 from .constants import ( 42 from .constants import (
42 NS_AP, CONTENT_TYPE_AP, TYPE_ACTOR, TYPE_INBOX, TYPE_SHARED_INBOX, TYPE_OUTBOX, 43 NS_AP, CONTENT_TYPE_AP, TYPE_ACTOR, TYPE_INBOX, TYPE_SHARED_INBOX, TYPE_OUTBOX,
43 AP_REQUEST_TYPES, PAGE_SIZE, ACTIVITY_TYPES_LOWER, ACTIVIY_NO_ACCOUNT_ALLOWED, 44 AP_REQUEST_TYPES, PAGE_SIZE, ACTIVITY_TYPES_LOWER, ACTIVIY_NO_ACCOUNT_ALLOWED,
44 SIGN_HEADERS, HS2019, SIGN_EXP, TYPE_FOLLOWERS, TYPE_FOLLOWING 45 SIGN_HEADERS, HS2019, SIGN_EXP, TYPE_FOLLOWERS, TYPE_FOLLOWING, TYPE_ITEM, TYPE_LIKE
45 ) 46 )
46 from .regex import RE_SIG_PARAM 47 from .regex import RE_SIG_PARAM
47 48
48 49
49 log = getLogger(__name__) 50 log = getLogger(__name__)
147 ) 148 )
148 elif type_ == "Announce": 149 elif type_ == "Announce":
149 # we can use directly the Announce object, as only the "id" field is 150 # we can use directly the Announce object, as only the "id" field is
150 # needed 151 # needed
151 await self.apg.newAPDeleteItem(client, None, node, obj) 152 await self.apg.newAPDeleteItem(client, None, node, obj)
153 elif type_ == TYPE_LIKE:
154 await self.handleNewLikeItem(client, obj, True)
152 else: 155 else:
153 log.warning(f"Unmanaged undo type: {type_!r}") 156 log.warning(f"Unmanaged undo type: {type_!r}")
154 157
155 async def handleFollowActivity( 158 async def handleFollowActivity(
156 self, 159 self,
336 account_jid, 339 account_jid,
337 node, 340 node,
338 signing_actor, 341 signing_actor,
339 repeated=True 342 repeated=True
340 ) 343 )
344
345 async def handleNewLikeItem(
346 self,
347 client: SatXMPPEntity,
348 data: dict,
349 undo: bool = False,
350 ) -> None:
351 liked_ids = data.get("object")
352 if not liked_ids:
353 raise exceptions.DataError("object should be set")
354 elif isinstance(liked_ids, list):
355 try:
356 liked_ids = [o["id"] for o in liked_ids]
357 except (KeyError, TypeError):
358 raise exceptions.DataError(f"invalid object: {liked_ids!r}")
359 elif isinstance(liked_ids, dict):
360 obj_id = liked_ids.get("id")
361 if not obj_id or not isinstance(obj_id, str):
362 raise exceptions.DataError(f"invalid object: {liked_ids!r}")
363 liked_ids = [obj_id]
364 elif isinstance(liked_ids, str):
365 liked_ids = [liked_ids]
366
367 for liked_id in liked_ids:
368 if not self.apg.isLocalURL(liked_id):
369 log.debug(f"ignoring non local liked ID: {liked_id}")
370 continue
371 url_type, url_args = self.apg.parseAPURL(liked_id)
372 if url_type != TYPE_ITEM:
373 log.warning(f"unexpected local URL for liked item: {liked_id}")
374 continue
375 try:
376 account, item_id = url_args
377 except ValueError:
378 raise ValueError(f"invalid URL: {liked_id}")
379 author_jid, item_node = await self.apg.getJIDAndNode(account)
380 if item_node is None:
381 item_node = self.apg._m.namespace
382 attachment_node = self.apg._pa.getAttachmentNodeName(
383 author_jid, item_node, item_id
384 )
385 cached_node = await self.apg.host.memory.storage.getPubsubNode(
386 client,
387 author_jid,
388 attachment_node,
389 with_subscriptions=True,
390 create=True
391 )
392 found_items, __ = await self.apg.host.memory.storage.getItems(
393 cached_node, item_ids=[item_id]
394 )
395 if not found_items:
396 old_item_elt = None
397 else:
398 found_item = found_items[0]
399 old_item_elt = found_item.data
400
401 item_elt = self.apg._pa.applySetHandler(
402 client,
403 {"extra": {"noticed": not undo}},
404 old_item_elt,
405 [("noticed", self.apg._pa.namespace)]
406 )
407 # we reparse the element, as there can be other attachments
408 attachments_data = self.apg._pa.items2attachmentData(client, [item_elt])
409 # and we update the cache
410 await self.apg.host.memory.storage.cachePubsubItems(
411 client,
412 cached_node,
413 [item_elt],
414 attachments_data or [{}]
415 )
416
417 if self.apg.isVirtualJID(author_jid):
418 # the attachment is on t a virtual pubsub service (linking to an AP item),
419 # we notify all subscribers
420 for subscription in cached_node.subscriptions:
421 if subscription.state != SubscriptionState.SUBSCRIBED:
422 continue
423 self.apg.pubsub_service.notifyPublish(
424 author_jid,
425 attachment_node,
426 [(subscription.subscriber, None, [item_elt])]
427 )
428 else:
429 # the attachment is on an XMPP item, we publish it to the attachment node
430 await self.apg._p.sendItems(
431 client, author_jid, attachment_node, [item_elt]
432 )
433
434 async def handleLikeActivity(
435 self,
436 request: "HTTPRequest",
437 data: dict,
438 account_jid: Optional[jid.JID],
439 node: Optional[str],
440 ap_account: Optional[str],
441 ap_url: str,
442 signing_actor: str
443 ):
444 client = await self.apg.getVirtualClient(signing_actor)
445 await self.handleNewLikeItem(client, data)
341 446
342 async def APActorRequest( 447 async def APActorRequest(
343 self, 448 self,
344 request: "HTTPRequest", 449 request: "HTTPRequest",
345 account_jid: jid.JID, 450 account_jid: jid.JID,
621 subscribers = await self.apg._pps.getPublicNodeSubscriptions( 726 subscribers = await self.apg._pps.getPublicNodeSubscriptions(
622 client, account_jid, node 727 client, account_jid, node
623 ) 728 )
624 followers = [] 729 followers = []
625 for subscriber in subscribers.keys(): 730 for subscriber in subscribers.keys():
626 if subscriber.host == self.apg.client.jid.userhost(): 731 if self.apg.isVirtualJID(subscriber):
627 # the subscriber is an AP user subscribed with this gateway 732 # the subscriber is an AP user subscribed with this gateway
628 ap_account = self.apg._e.unescape(subscriber.user) 733 ap_account = self.apg._e.unescape(subscriber.user)
629 else: 734 else:
630 # regular XMPP user 735 # regular XMPP user
631 ap_account = await self.apg.getAPAccountFromJidAndNode(subscriber, node) 736 ap_account = await self.apg.getAPAccountFromJidAndNode(subscriber, node)
658 client, account_jid, node 763 client, account_jid, node
659 ) 764 )
660 following = [] 765 following = []
661 for sub_dict in subscriptions: 766 for sub_dict in subscriptions:
662 service = jid.JID(sub_dict["service"]) 767 service = jid.JID(sub_dict["service"])
663 if service.host == self.apg.client.jid.userhost(): 768 if self.apg.isVirtualJID(service):
664 # the subscription is to an AP actor with this gateway 769 # the subscription is to an AP actor with this gateway
665 ap_account = self.apg._e.unescape(service.user) 770 ap_account = self.apg._e.unescape(service.user)
666 else: 771 else:
667 # regular XMPP user 772 # regular XMPP user
668 ap_account = await self.apg.getAPAccountFromJidAndNode( 773 ap_account = await self.apg.getAPAccountFromJidAndNode(