comparison sat/plugins/plugin_xep_0277.py @ 3308:384283adcce1

plugins XEP-0059, XEP-0060, XEP-0277, XEP-0313: better serialisation: `data_format.serialise` is now used for `mbGet`, and RSM/MAM values are not transtyped to strings anymore. A serialised dict is now used, items are put in the `items` key. Comments handling has been refactored to use a list for the potentially multiple comments nodes. `rsm` data are now in a `rsm` key of the dict, and `mam` data are merged with other metadata.
author Goffi <goffi@goffi.org>
date Thu, 16 Jul 2020 09:07:20 +0200
parents 84a94b385760
children d49607e3a066
comparison
equal deleted inserted replaced
3307:9f0e28137cd0 3308:384283adcce1
101 ) 101 )
102 host.bridge.addMethod( 102 host.bridge.addMethod(
103 "mbGet", 103 "mbGet",
104 ".plugin", 104 ".plugin",
105 in_sign="ssiasa{ss}s", 105 in_sign="ssiasa{ss}s",
106 out_sign="(asa{ss})", 106 out_sign="s",
107 method=self._mbGet, 107 method=self._mbGet,
108 async_=True, 108 async_=True,
109 ) 109 )
110 host.bridge.addMethod( 110 host.bridge.addMethod(
111 "mbSetAccess", 111 "mbSetAccess",
355 microblog_data["published"] = calendar.timegm( 355 microblog_data["published"] = calendar.timegm(
356 dateutil.parser.parse(str(published_elt)).utctimetuple() 356 dateutil.parser.parse(str(published_elt)).utctimetuple()
357 ) 357 )
358 358
359 # links 359 # links
360 comments = microblog_data['comments'] = []
360 for link_elt in entry_elt.elements(NS_ATOM, "link"): 361 for link_elt in entry_elt.elements(NS_ATOM, "link"):
361 if ( 362 if (
362 link_elt.getAttribute("rel") == "replies" 363 link_elt.getAttribute("rel") == "replies"
363 and link_elt.getAttribute("title") == "comments" 364 and link_elt.getAttribute("title") == "comments"
364 ): 365 ):
365 key = check_conflict("comments", True) 366 uri = link_elt["href"]
366 microblog_data[key] = link_elt["href"] 367 comments_data = {
368 "uri": uri,
369 }
367 try: 370 try:
368 service, node = self.parseCommentUrl(microblog_data[key]) 371 service, node = self.parseCommentUrl(uri)
369 except Exception as e: 372 except Exception as e:
370 log.warning(f"Can't parse url {microblog_data[key]}: {e}") 373 log.warning(f"Can't parse comments url: {e}")
371 del microblog_data[key] 374 continue
372 else: 375 else:
373 microblog_data["{}_service".format(key)] = service.full() 376 comments_data["service"] = service.full()
374 microblog_data["{}_node".format(key)] = node 377 comments_data["node"] = node
378 comments.append(comments_data)
375 else: 379 else:
376 rel = link_elt.getAttribute("rel", "") 380 rel = link_elt.getAttribute("rel", "")
377 title = link_elt.getAttribute("title", "") 381 title = link_elt.getAttribute("title", "")
378 href = link_elt.getAttribute("href", "") 382 href = link_elt.getAttribute("href", "")
379 log.warning( 383 log.warning(
587 ), 591 ),
588 ) 592 )
589 entry_elt.addElement("id", content=entry_id) # 593 entry_elt.addElement("id", content=entry_id) #
590 594
591 ## comments ## 595 ## comments ##
592 if "comments" in data: 596 for comments_data in data.get('comments', []):
593 link_elt = entry_elt.addElement("link") 597 link_elt = entry_elt.addElement("link")
594 link_elt["href"] = data["comments"] 598 # XXX: "uri" is set in self._manageComments if not already existing
599 link_elt["href"] = comments_data["uri"]
595 link_elt["rel"] = "replies" 600 link_elt["rel"] = "replies"
596 link_elt["title"] = "comments" 601 link_elt["title"] = "comments"
597 602
598 ## final item building ## 603 ## final item building ##
599 item_elt = pubsub.Item(id=item_id, payload=entry_elt) 604 item_elt = pubsub.Item(id=item_id, payload=entry_elt)
644 649
645 @defer.inlineCallbacks 650 @defer.inlineCallbacks
646 def _manageComments(self, client, mb_data, service, node, item_id, access=None): 651 def _manageComments(self, client, mb_data, service, node, item_id, access=None):
647 """Check comments keys in mb_data and create comments node if necessary 652 """Check comments keys in mb_data and create comments node if necessary
648 653
649 if mb_data['comments'] exists, it is used (or mb_data['comments_service'] and/or mb_data['comments_node']), 654 if a comments node metadata is set in the mb_data['comments'] list, it is used
650 else it is generated (if allow_comments is True). 655 otherwise it is generated (if allow_comments is True).
651 @param mb_data(dict): microblog mb_data 656 @param mb_data(dict): microblog mb_data
652 @param service(jid.JID, None): PubSub service of the parent item 657 @param service(jid.JID, None): PubSub service of the parent item
653 @param node(unicode): node of the parent item 658 @param node(unicode): node of the parent item
654 @param item_id(unicode): id of the parent item 659 @param item_id(unicode): id of the parent item
655 @param access(unicode, None): access model 660 @param access(unicode, None): access model
656 None to use same access model as parent item 661 None to use same access model as parent item
657 """ 662 """
658 # FIXME: if 'comments' already exists in mb_data,
659 # it is not used to create the Node
660 allow_comments = mb_data.pop("allow_comments", None) 663 allow_comments = mb_data.pop("allow_comments", None)
661 if allow_comments is None: 664 if allow_comments is None:
662 return 665 if "comments" in mb_data:
666 mb_data["allow_comments"] = True
667 else:
668 # no comments set or requested, nothing to do
669 return
663 elif allow_comments == False: 670 elif allow_comments == False:
664 if "comments" in mb_data: 671 if "comments" in mb_data:
665 log.warning( 672 log.warning(
666 "comments are not allowed but there is already a comments node, " 673 "comments are not allowed but there is already a comments node, "
667 "it may be lost: {uri}".format( 674 "it may be lost: {uri}".format(
668 uri=mb_data["comments"] 675 uri=mb_data["comments"]
669 ) 676 )
670 ) 677 )
671 del mb_data["comments"] 678 del mb_data["comments"]
672 return 679 return
680
681 # we have usually a single comment node, but the spec allow several, so we need to
682 # handle this in a list
683 if len(mb_data.setdefault('comments', [])) == 0:
684 # we need at least one comment node
685 comments_data = {}
686 mb_data['comments'].append({})
673 687
674 if access is None: 688 if access is None:
675 # TODO: cache access models per service/node 689 # TODO: cache access models per service/node
676 parent_node_config = yield self._p.getConfiguration(client, service, node) 690 parent_node_config = yield self._p.getConfiguration(client, service, node)
677 access = parent_node_config.get(self._p.OPT_ACCESS_MODEL, self._p.ACCESS_OPEN) 691 access = parent_node_config.get(self._p.OPT_ACCESS_MODEL, self._p.ACCESS_OPEN)
687 } 701 }
688 702
689 # if other plugins need to change the options 703 # if other plugins need to change the options
690 yield self.host.trigger.point("XEP-0277_comments", client, mb_data, options) 704 yield self.host.trigger.point("XEP-0277_comments", client, mb_data, options)
691 705
692 try: 706 for comments_data in mb_data['comments']:
693 comments_node = mb_data["comments_node"] 707 uri = comments_data.get('uri')
694 except KeyError: 708 comments_node = comments_data.get('node')
695 comments_node = self.getCommentsNode(item_id) 709 try:
696 else: 710 comments_service = jid.JID(comments_data["service"])
697 if not comments_node: 711 except KeyError:
698 raise exceptions.DataError( 712 comments_service = None
699 "if comments_node is present, it must not be empty" 713
714 if uri:
715 uri_service, uri_node = self.parseCommentUrl(uri)
716 if ((comments_node is not None and comments_node!=uri_node)
717 or (comments_service is not None and comments_service!=uri_service)):
718 raise ValueError(
719 f"Incoherence between comments URI ({uri}) and comments_service "
720 f"({comments_service}) or comments_node ({comments_node})")
721 comments_data['service'] = comments_service = uri_service
722 comments_data['node'] = comments_node = uri_node
723 else:
724 if not comments_node:
725 comments_node = self.getCommentsNode(item_id)
726 comments_data['node'] = comments_node
727 if comments_service is None:
728 comments_service = yield self.getCommentsService(client, service)
729 if comments_service is None:
730 comments_service = client.jid.userhostJID()
731 comments_data['service'] = comments_service
732
733 comments_data['uri'] = xmpp_uri.buildXMPPUri(
734 "pubsub",
735 path=comments_service.full(),
736 node=comments_node,
700 ) 737 )
701 738
702 try: 739 try:
703 comments_service = jid.JID(mb_data["comments_service"]) 740 yield self._p.createNode(client, comments_service, comments_node, options)
704 except KeyError: 741 except error.StanzaError as e:
705 comments_service = yield self.getCommentsService(client, service) 742 if e.condition == "conflict":
706 743 log.info(
707 try: 744 "node {} already exists on service {}".format(
708 yield self._p.createNode(client, comments_service, comments_node, options) 745 comments_node, comments_service
709 except error.StanzaError as e: 746 )
710 if e.condition == "conflict": 747 )
711 log.info( 748 else:
712 "node {} already exists on service {}".format( 749 raise e
713 comments_node, comments_service
714 )
715 )
716 else: 750 else:
717 raise e 751 if access == self._p.ACCESS_WHITELIST:
718 else: 752 # for whitelist access we need to copy affiliations from parent item
719 if access == self._p.ACCESS_WHITELIST: 753 comments_affiliations = yield self._p.getNodeAffiliations(
720 # for whitelist access we need to copy affiliations from parent item 754 client, service, node
721 comments_affiliations = yield self._p.getNodeAffiliations( 755 )
722 client, service, node 756 # …except for "member", that we transform to publisher
723 ) 757 # because we wants members to be able to write to comments
724 # …except for "member", that we transform to publisher 758 for jid_, affiliation in list(comments_affiliations.items()):
725 # because we wants members to be able to write to comments 759 if affiliation == "member":
726 for jid_, affiliation in list(comments_affiliations.items()): 760 comments_affiliations[jid_] == "publisher"
727 if affiliation == "member": 761
728 comments_affiliations[jid_] == "publisher" 762 yield self._p.setNodeAffiliations(
729 763 client, comments_service, comments_node, comments_affiliations
730 yield self._p.setNodeAffiliations( 764 )
731 client, comments_service, comments_node, comments_affiliations
732 )
733
734 if comments_service is None:
735 comments_service = client.jid.userhostJID()
736
737 if "comments" in mb_data:
738 if not mb_data["comments"]:
739 raise exceptions.DataError(
740 "if comments is present, it must not be empty"
741 )
742 if "comments_node" in mb_data or "comments_service" in mb_data:
743 raise exceptions.DataError(
744 "You can't use comments_service/comments_node and comments at the "
745 "same time"
746 )
747 else:
748 mb_data["comments"] = self._p.getNodeURI(comments_service, comments_node)
749 765
750 def _mbSend(self, service, node, data, profile_key): 766 def _mbSend(self, service, node, data, profile_key):
751 service = jid.JID(service) if service else None 767 service = jid.JID(service) if service else None
752 node = node if node else NS_MICROBLOG 768 node = node if node else NS_MICROBLOG
753 client = self.host.getClient(profile_key) 769 client = self.host.getClient(profile_key)
794 810
795 ## get ## 811 ## get ##
796 812
797 def _mbGetSerialise(self, data): 813 def _mbGetSerialise(self, data):
798 items, metadata = data 814 items, metadata = data
799 items = [data_format.serialise(item) for item in items] 815 metadata['items'] = items
800 return items, metadata 816 return data_format.serialise(metadata)
801 817
802 def _mbGet(self, service="", node="", max_items=10, item_ids=None, extra_dict=None, 818 def _mbGet(self, service="", node="", max_items=10, item_ids=None, extra_dict=None,
803 profile_key=C.PROF_KEY_NONE): 819 profile_key=C.PROF_KEY_NONE):
804 """ 820 """
805 @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit 821 @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit
838 max_items=max_items, 854 max_items=max_items,
839 item_ids=item_ids, 855 item_ids=item_ids,
840 rsm_request=rsm_request, 856 rsm_request=rsm_request,
841 extra=extra, 857 extra=extra,
842 ) 858 )
843 mb_data = yield self._p.transItemsDataD(items_data, self.item2mbdata) 859 mb_data = yield self._p.transItemsDataD(
860 items_data, self.item2mbdata)
844 defer.returnValue(mb_data) 861 defer.returnValue(mb_data)
845 862
846 def parseCommentUrl(self, node_url): 863 def parseCommentUrl(self, node_url):
847 """Parse a XMPP URI 864 """Parse a XMPP URI
848 865
1257 for service, node in node_data: 1274 for service, node in node_data:
1258 d = deferreds[(service, node)] = self._p.getItems( 1275 d = deferreds[(service, node)] = self._p.getItems(
1259 client, service, node, max_items, rsm_request=rsm_request, extra=extra 1276 client, service, node, max_items, rsm_request=rsm_request, extra=extra
1260 ) 1277 )
1261 d.addCallback( 1278 d.addCallback(
1262 lambda items_data: self._p.transItemsDataD(items_data, self.item2mbdata) 1279 lambda items_data: self._p.transItemsDataD(
1280 items_data, self.item2mbdata)
1263 ) 1281 )
1264 d.addCallback(getComments) 1282 d.addCallback(getComments)
1265 d.addCallback(lambda items_comments_data: ("", items_comments_data)) 1283 d.addCallback(lambda items_comments_data: ("", items_comments_data))
1266 d.addErrback(lambda failure: (str(failure.value), ([], {}))) 1284 d.addErrback(lambda failure: (str(failure.value), ([], {})))
1267 1285