Mercurial > libervia-backend
comparison sat/plugins/plugin_comp_ap_gateway/__init__.py @ 3832:201a22bfbb74
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
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 10 Jul 2022 15:16:15 +0200 |
parents | 6329ee6b6df4 |
children | 381340b9a9ee |
comparison
equal
deleted
inserted
replaced
3831:604b6acaee22 | 3832:201a22bfbb74 |
---|---|
68 MEDIA_TYPE_AP, | 68 MEDIA_TYPE_AP, |
69 TYPE_ACTOR, | 69 TYPE_ACTOR, |
70 TYPE_ITEM, | 70 TYPE_ITEM, |
71 TYPE_FOLLOWERS, | 71 TYPE_FOLLOWERS, |
72 TYPE_TOMBSTONE, | 72 TYPE_TOMBSTONE, |
73 TYPE_MENTION, | |
73 NS_AP_PUBLIC, | 74 NS_AP_PUBLIC, |
74 PUBLIC_TUPLE | 75 PUBLIC_TUPLE |
75 ) | 76 ) |
76 from .http_server import HTTPServer | 77 from .http_server import HTTPServer |
77 from .pubsub_service import APPubsubService | 78 from .pubsub_service import APPubsubService |
87 C.PI_MODES: [C.PLUG_MODE_COMPONENT], | 88 C.PI_MODES: [C.PLUG_MODE_COMPONENT], |
88 C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT, | 89 C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT, |
89 C.PI_PROTOCOLS: [], | 90 C.PI_PROTOCOLS: [], |
90 C.PI_DEPENDENCIES: [ | 91 C.PI_DEPENDENCIES: [ |
91 "XEP-0060", "XEP-0084", "XEP-0106", "XEP-0277", "XEP-0292", "XEP-0329", | 92 "XEP-0060", "XEP-0084", "XEP-0106", "XEP-0277", "XEP-0292", "XEP-0329", |
92 "XEP-0424", "XEP-0465", "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY", "XEP-0054" | 93 "XEP-0372", "XEP-0424", "XEP-0465", "PUBSUB_CACHE", "TEXT_SYNTAXES", "IDENTITY", |
94 "XEP-0054" | |
93 ], | 95 ], |
94 C.PI_RECOMMENDATIONS: [], | 96 C.PI_RECOMMENDATIONS: [], |
95 C.PI_MAIN: "APGateway", | 97 C.PI_MAIN: "APGateway", |
96 C.PI_HANDLER: C.BOOL_TRUE, | 98 C.PI_HANDLER: C.BOOL_TRUE, |
97 C.PI_DESCRIPTION: _( | 99 C.PI_DESCRIPTION: _( |
115 self._p = host.plugins["XEP-0060"] | 117 self._p = host.plugins["XEP-0060"] |
116 self._a = host.plugins["XEP-0084"] | 118 self._a = host.plugins["XEP-0084"] |
117 self._e = host.plugins["XEP-0106"] | 119 self._e = host.plugins["XEP-0106"] |
118 self._m = host.plugins["XEP-0277"] | 120 self._m = host.plugins["XEP-0277"] |
119 self._v = host.plugins["XEP-0292"] | 121 self._v = host.plugins["XEP-0292"] |
122 self._refs = host.plugins["XEP-0372"] | |
120 self._r = host.plugins["XEP-0424"] | 123 self._r = host.plugins["XEP-0424"] |
121 self._pps = host.plugins["XEP-0465"] | 124 self._pps = host.plugins["XEP-0465"] |
122 self._c = host.plugins["PUBSUB_CACHE"] | 125 self._c = host.plugins["PUBSUB_CACHE"] |
123 self._t = host.plugins["TEXT_SYNTAXES"] | 126 self._t = host.plugins["TEXT_SYNTAXES"] |
124 self._i = host.plugins["IDENTITY"] | 127 self._i = host.plugins["IDENTITY"] |
1692 | 1695 |
1693 async def newReplyToXMPPItem( | 1696 async def newReplyToXMPPItem( |
1694 self, | 1697 self, |
1695 client: SatXMPPEntity, | 1698 client: SatXMPPEntity, |
1696 ap_item: dict, | 1699 ap_item: dict, |
1700 targets: Dict[str, Set[str]], | |
1701 mentions: List[Dict[str, str]], | |
1697 ) -> None: | 1702 ) -> None: |
1698 """We got an AP item which is a reply to an XMPP item""" | 1703 """We got an AP item which is a reply to an XMPP item""" |
1699 in_reply_to = ap_item["inReplyTo"] | 1704 in_reply_to = ap_item["inReplyTo"] |
1700 url_type, url_args = self.parseAPURL(in_reply_to) | 1705 url_type, url_args = self.parseAPURL(in_reply_to) |
1701 if url_type != "item": | 1706 if url_type != "item": |
1739 log.info(f"{ppElt(parent_item_elt.toXml())}") | 1744 log.info(f"{ppElt(parent_item_elt.toXml())}") |
1740 raise NotImplemented() | 1745 raise NotImplemented() |
1741 else: | 1746 else: |
1742 __, item_elt = await self.apItem2MbDataAndElt(ap_item) | 1747 __, item_elt = await self.apItem2MbDataAndElt(ap_item) |
1743 await self._p.publish(client, comment_service, comment_node, [item_elt]) | 1748 await self._p.publish(client, comment_service, comment_node, [item_elt]) |
1744 | 1749 await self.notifyMentions( |
1745 def getAPItemTargets(self, item: Dict[str, Any]) -> Tuple[bool, Set[str], Set[str]]: | 1750 targets, mentions, comment_service, comment_node, item_elt["id"] |
1751 ) | |
1752 | |
1753 def getAPItemTargets( | |
1754 self, | |
1755 item: Dict[str, Any] | |
1756 ) -> Tuple[bool, Dict[str, Set[str]], List[Dict[str, str]]]: | |
1746 """Retrieve targets of an AP item, and indicate if it's a public one | 1757 """Retrieve targets of an AP item, and indicate if it's a public one |
1747 | 1758 |
1748 @param item: AP object payload | 1759 @param item: AP object payload |
1749 @return: Are returned: | 1760 @return: Are returned: |
1750 - is_public flag, indicating if the item is world-readable | 1761 - is_public flag, indicating if the item is world-readable |
1751 - targets of the item | 1762 - a dict mapping target type to targets |
1752 - targets of the items | 1763 """ |
1753 """ | 1764 targets: Dict[str, Set[str]] = {} |
1754 targets: Set[str] = set() | |
1755 is_public = False | 1765 is_public = False |
1756 # TODO: handle "audience" | 1766 # TODO: handle "audience" |
1757 for key in ("to", "bto", "cc", "bcc"): | 1767 for key in ("to", "bto", "cc", "bcc"): |
1758 values = item.get(key) | 1768 values = item.get(key) |
1759 if not values: | 1769 if not values: |
1766 continue | 1776 continue |
1767 if not value: | 1777 if not value: |
1768 continue | 1778 continue |
1769 if not self.isLocalURL(value): | 1779 if not self.isLocalURL(value): |
1770 continue | 1780 continue |
1771 targets.add(value) | 1781 target_type = self.parseAPURL(value)[0] |
1772 | 1782 if target_type != TYPE_ACTOR: |
1773 targets_types = {self.parseAPURL(t)[0] for t in targets} | 1783 log.debug(f"ignoring non actor type as a target: {href}") |
1774 return is_public, targets, targets_types | 1784 else: |
1785 targets.setdefault(target_type, set()).add(value) | |
1786 | |
1787 mentions = [] | |
1788 tags = item.get("tag") | |
1789 if tags: | |
1790 for tag in tags: | |
1791 if tag.get("type") != TYPE_MENTION: | |
1792 continue | |
1793 href = tag.get("href") | |
1794 if not href: | |
1795 log.warning('Missing "href" field from mention object: {tag!r}') | |
1796 continue | |
1797 if not self.isLocalURL(href): | |
1798 continue | |
1799 uri_type = self.parseAPURL(href)[0] | |
1800 if uri_type != TYPE_ACTOR: | |
1801 log.debug(f"ignoring non actor URI as a target: {href}") | |
1802 continue | |
1803 mention = {"uri": href} | |
1804 mentions.append(mention) | |
1805 name = tag.get("name") | |
1806 if name: | |
1807 mention["content"] = name | |
1808 | |
1809 return is_public, targets, mentions | |
1775 | 1810 |
1776 async def newAPItem( | 1811 async def newAPItem( |
1777 self, | 1812 self, |
1778 client: SatXMPPEntity, | 1813 client: SatXMPPEntity, |
1779 destinee: Optional[jid.JID], | 1814 destinee: Optional[jid.JID], |
1784 | 1819 |
1785 @param destinee: jid of the destinee, | 1820 @param destinee: jid of the destinee, |
1786 @param node: XMPP pubsub node | 1821 @param node: XMPP pubsub node |
1787 @param item: AP object payload | 1822 @param item: AP object payload |
1788 """ | 1823 """ |
1789 is_public, targets, targets_types = self.getAPItemTargets(item) | 1824 is_public, targets, mentions = self.getAPItemTargets(item) |
1790 if not is_public and targets_types == {TYPE_ACTOR}: | 1825 if not is_public and targets.keys() == {TYPE_ACTOR}: |
1791 # this is a direct message | 1826 # this is a direct message |
1792 await self.handleMessageAPItem( | 1827 await self.handleMessageAPItem( |
1793 client, targets, destinee, item | 1828 client, targets, mentions, destinee, item |
1794 ) | 1829 ) |
1795 else: | 1830 else: |
1796 await self.handlePubsubAPItem( | 1831 await self.handlePubsubAPItem( |
1797 client, targets, destinee, node, item, is_public | 1832 client, targets, mentions, destinee, node, item, is_public |
1798 ) | 1833 ) |
1799 | 1834 |
1800 async def handleMessageAPItem( | 1835 async def handleMessageAPItem( |
1801 self, | 1836 self, |
1802 client: SatXMPPEntity, | 1837 client: SatXMPPEntity, |
1803 targets: Set[str], | 1838 targets: Dict[str, Set[str]], |
1839 mentions: List[Dict[str, str]], | |
1804 destinee: Optional[jid.JID], | 1840 destinee: Optional[jid.JID], |
1805 item: dict, | 1841 item: dict, |
1806 ) -> None: | 1842 ) -> None: |
1807 """Parse and deliver direct AP items translating to XMPP messages | 1843 """Parse and deliver direct AP items translating to XMPP messages |
1808 | 1844 |
1809 @param targets: actors where the item must be delivered | 1845 @param targets: actors where the item must be delivered |
1810 @param destinee: jid of the destinee, | 1846 @param destinee: jid of the destinee, |
1811 @param item: AP object payload | 1847 @param item: AP object payload |
1812 """ | 1848 """ |
1813 targets_jids = {await self.getJIDFromId(t) for t in targets} | 1849 targets_jids = { |
1850 await self.getJIDFromId(t) | |
1851 for t_set in targets.values() | |
1852 for t in t_set | |
1853 } | |
1814 if destinee is not None: | 1854 if destinee is not None: |
1815 targets_jids.add(destinee) | 1855 targets_jids.add(destinee) |
1816 mb_data = await self.apItem2MBdata(item) | 1856 mb_data = await self.apItem2MBdata(item) |
1817 defer_l = [] | 1857 defer_l = [] |
1818 for target_jid in targets_jids: | 1858 for target_jid in targets_jids: |
1824 extra={"origin_id": mb_data["id"]} | 1864 extra={"origin_id": mb_data["id"]} |
1825 ) | 1865 ) |
1826 ) | 1866 ) |
1827 await defer.DeferredList(defer_l) | 1867 await defer.DeferredList(defer_l) |
1828 | 1868 |
1869 async def notifyMentions( | |
1870 self, | |
1871 targets: Dict[str, Set[str]], | |
1872 mentions: List[Dict[str, str]], | |
1873 service: jid.JID, | |
1874 node: str, | |
1875 item_id: str, | |
1876 ) -> None: | |
1877 """Send mention notifications to recipients and mentioned entities | |
1878 | |
1879 XEP-0372 (References) is used. | |
1880 | |
1881 Mentions are also sent to recipients as they are primary audience (see | |
1882 https://www.w3.org/TR/activitystreams-vocabulary/#microsyntaxes). | |
1883 | |
1884 """ | |
1885 anchor = uri.buildXMPPUri("pubsub", path=service.full(), node=node, item=item_id) | |
1886 seen = set() | |
1887 # we start with explicit mentions because mentions' content will be used in the | |
1888 # future to fill "begin" and "end" reference attributes (we can't do it at the | |
1889 # moment as there is no way to specify the XML element to use in the blog item). | |
1890 for mention in mentions: | |
1891 mentioned_jid = await self.getJIDFromId(mention["uri"]) | |
1892 self._refs.sendReference( | |
1893 self.client, | |
1894 to_jid=mentioned_jid, | |
1895 anchor=anchor | |
1896 ) | |
1897 seen.add(mentioned_jid) | |
1898 | |
1899 remaining = { | |
1900 await self.getJIDFromId(t) | |
1901 for t_set in targets.values() | |
1902 for t in t_set | |
1903 } - seen | |
1904 for target in remaining: | |
1905 self._refs.sendReference( | |
1906 self.client, | |
1907 to_jid=target, | |
1908 anchor=anchor | |
1909 ) | |
1910 | |
1829 async def handlePubsubAPItem( | 1911 async def handlePubsubAPItem( |
1830 self, | 1912 self, |
1831 client: SatXMPPEntity, | 1913 client: SatXMPPEntity, |
1832 targets: Set[str], | 1914 targets: Dict[str, Set[str]], |
1915 mentions: List[Dict[str, str]], | |
1833 destinee: Optional[jid.JID], | 1916 destinee: Optional[jid.JID], |
1834 node: str, | 1917 node: str, |
1835 item: dict, | 1918 item: dict, |
1836 public: bool | 1919 public: bool |
1837 ) -> None: | 1920 ) -> None: |
1851 if in_reply_to and isinstance(in_reply_to, list): | 1934 if in_reply_to and isinstance(in_reply_to, list): |
1852 in_reply_to = in_reply_to[0] | 1935 in_reply_to = in_reply_to[0] |
1853 if in_reply_to and isinstance(in_reply_to, str): | 1936 if in_reply_to and isinstance(in_reply_to, str): |
1854 if self.isLocalURL(in_reply_to): | 1937 if self.isLocalURL(in_reply_to): |
1855 # this is a reply to an XMPP item | 1938 # this is a reply to an XMPP item |
1856 return await self.newReplyToXMPPItem(client, item) | 1939 await self.newReplyToXMPPItem(client, item, targets, mentions) |
1940 return | |
1857 | 1941 |
1858 # this item is a reply to an AP item, we use or create a corresponding node | 1942 # this item is a reply to an AP item, we use or create a corresponding node |
1859 # for comments | 1943 # for comments |
1860 parent_node, __ = await self.getCommentsNodes(item["id"], in_reply_to) | 1944 parent_node, __ = await self.getCommentsNodes(item["id"], in_reply_to) |
1861 node = parent_node or node | 1945 node = parent_node or node |
1908 service, | 1992 service, |
1909 node, | 1993 node, |
1910 [(subscription.subscriber, None, [item_elt])] | 1994 [(subscription.subscriber, None, [item_elt])] |
1911 ) | 1995 ) |
1912 | 1996 |
1997 await self.notifyMentions(targets, mentions, service, node, item_elt["id"]) | |
1998 | |
1913 async def newAPDeleteItem( | 1999 async def newAPDeleteItem( |
1914 self, | 2000 self, |
1915 client: SatXMPPEntity, | 2001 client: SatXMPPEntity, |
1916 destinee: Optional[jid.JID], | 2002 destinee: Optional[jid.JID], |
1917 node: str, | 2003 node: str, |