comparison sat/plugins/plugin_xep_0191.py @ 3787:f8a0f3b65371

plugin XEP-0191: Blocking Command implementation: rel 367
author Goffi <goffi@goffi.org>
date Fri, 27 May 2022 12:12:16 +0200
parents
children 24fbc4cad534
comparison
equal deleted inserted replaced
3786:cebfdfff3e99 3787:f8a0f3b65371
1 #!/usr/bin/env python3
2
3 # Libervia plugin for XEP-0191
4 # Copyright (C) 2009-2022 Jérôme Poisson (goffi@goffi.org)
5
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU Affero General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Affero General Public License for more details.
15
16 # You should have received a copy of the GNU Affero General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 from typing import List, Set
20
21 from twisted.words.protocols.jabber import xmlstream, jid
22 from twisted.words.xish import domish
23 from twisted.internet import defer
24 from zope.interface import implementer
25 from wokkel import disco, iwokkel
26
27 from sat.core.constants import Const as C
28 from sat.core.i18n import _
29 from sat.core.log import getLogger
30 from sat.core.core_types import SatXMPPEntity
31 from sat.tools.utils import ensure_deferred
32
33 log = getLogger(__name__)
34
35 PLUGIN_INFO = {
36 C.PI_NAME: "Pubsub Public Subscriptions",
37 C.PI_IMPORT_NAME: "XEP-0191",
38 C.PI_TYPE: C.PLUG_TYPE_XEP,
39 C.PI_MODES: C.PLUG_MODE_BOTH,
40 C.PI_PROTOCOLS: ["XEP-0191"],
41 C.PI_DEPENDENCIES: ["XEP-0060", "XEP-0376"],
42 C.PI_MAIN: "XEP_0191",
43 C.PI_HANDLER: "yes",
44 C.PI_DESCRIPTION: _("""Pubsub Public Subscriptions implementation"""),
45 }
46
47 NS_BLOCKING = "urn:xmpp:blocking"
48 IQ_BLOCK_PUSH = f'{C.IQ_SET}/block[@xmlns="{NS_BLOCKING}"]'
49 IQ_UNBLOCK_PUSH = f'{C.IQ_SET}/unblock[@xmlns="{NS_BLOCKING}"]'
50
51
52 class XEP_0191:
53
54 def __init__(self, host):
55 log.info(_("Blocking Command initialization"))
56 host.registerNamespace("blocking", NS_BLOCKING)
57 self.host = host
58 host.bridge.addMethod(
59 "blockingList",
60 ".plugin",
61 in_sign="s",
62 out_sign="as",
63 method=self._blockList,
64 async_=True,
65 )
66 host.bridge.addMethod(
67 "blockingBlock",
68 ".plugin",
69 in_sign="ass",
70 out_sign="",
71 method=self._block,
72 async_=True,
73 )
74 host.bridge.addMethod(
75 "blockingUnblock",
76 ".plugin",
77 in_sign="ass",
78 out_sign="",
79 method=self._unblock,
80 async_=True,
81 )
82
83 def getHandler(self, client):
84 return XEP_0191_Handler(self)
85
86 @ensure_deferred
87 async def _blockList(
88 self,
89 profile_key=C.PROF_KEY_NONE
90 ) -> List[str]:
91 client = self.host.getClient(profile_key)
92 blocked_jids = await self.blockList(client)
93 return [j.full() for j in blocked_jids]
94
95 async def blockList(self, client: SatXMPPEntity) -> Set[jid.JID]:
96 await self.host.checkFeature(client, NS_BLOCKING)
97 iq_elt = client.IQ("get")
98 iq_elt.addElement((NS_BLOCKING, "blocklist"))
99 iq_result_elt = await iq_elt.send()
100 try:
101 blocklist_elt = next(iq_result_elt.elements(NS_BLOCKING, "blocklist"))
102 except StopIteration:
103 log.warning(f"missing <blocklist> element: {iq_result_elt.toXml()}")
104 return []
105 blocked_jids = set()
106 for item_elt in blocklist_elt.elements(NS_BLOCKING, "item"):
107 try:
108 blocked_jid = jid.JID(item_elt["jid"])
109 except (RuntimeError, AttributeError):
110 log.warning(f"Invalid <item> element in block list: {item_elt.toXml()}")
111 else:
112 blocked_jids.add(blocked_jid)
113
114 return blocked_jids
115
116 def _block(
117 self,
118 entities: List[str],
119 profile_key: str = C.PROF_KEY_NONE
120 ) -> str:
121 client = self.host.getClient(profile_key)
122 return defer.ensureDeferred(
123 self.block(client, [jid.JID(entity) for entity in entities])
124 )
125
126 async def block(self, client: SatXMPPEntity, entities: List[jid.JID]) -> None:
127 await self.host.checkFeature(client, NS_BLOCKING)
128 iq_elt = client.IQ("set")
129 block_elt = iq_elt.addElement((NS_BLOCKING, "block"))
130 for entity in entities:
131 item_elt = block_elt.addElement("item")
132 item_elt["jid"] = entity.full()
133 await iq_elt.send()
134
135 def _unblock(
136 self,
137 entities: List[str],
138 profile_key: str = C.PROF_KEY_NONE
139 ) -> None:
140 client = self.host.getClient(profile_key)
141 return defer.ensureDeferred(
142 self.unblock(client, [jid.JID(e) for e in entities])
143 )
144
145 async def unblock(self, client: SatXMPPEntity, entities: List[jid.JID]) -> None:
146 await self.host.checkFeature(client, NS_BLOCKING)
147 iq_elt = client.IQ("set")
148 unblock_elt = iq_elt.addElement((NS_BLOCKING, "unblock"))
149 for entity in entities:
150 item_elt = unblock_elt.addElement("item")
151 item_elt["jid"] = entity.full()
152 await iq_elt.send()
153
154 def onBlockPush(self, iq_elt: domish.Element, client: SatXMPPEntity) -> None:
155 # TODO: send notification to user
156 iq_elt.handled = True
157 for item_elt in iq_elt.block.elements(NS_BLOCKING, "item"):
158 try:
159 entity = jid.JID(item_elt["jid"])
160 except (KeyError, RuntimeError):
161 log.warning(f"invalid item received in block push: {item_elt.toXml()}")
162 else:
163 log.info(f"{entity.full()} has been blocked for {client.profile}")
164 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
165 client.send(iq_result_elt)
166
167 def onUnblockPush(self, iq_elt: domish.Element, client: SatXMPPEntity) -> None:
168 # TODO: send notification to user
169 iq_elt.handled = True
170 items = list(iq_elt.unblock.elements(NS_BLOCKING, "item"))
171 if not items:
172 log.info(f"All entities have been unblocked for {client.profile}")
173 else:
174 for item_elt in items:
175 try:
176 entity = jid.JID(item_elt["jid"])
177 except (KeyError, RuntimeError):
178 log.warning(
179 f"invalid item received in unblock push: {item_elt.toXml()}"
180 )
181 else:
182 log.info(f"{entity.full()} has been unblocked for {client.profile}")
183 iq_result_elt = xmlstream.toResponse(iq_elt, "result")
184 client.send(iq_result_elt)
185
186
187 @implementer(iwokkel.IDisco)
188 class XEP_0191_Handler(xmlstream.XMPPHandler):
189
190 def __init__(self, plugin_parent: XEP_0191):
191 self.plugin_parent = plugin_parent
192
193 def connectionInitialized(self):
194 self.xmlstream.addObserver(
195 IQ_BLOCK_PUSH,
196 self.plugin_parent.onBlockPush,
197 client=self.parent
198
199 )
200 self.xmlstream.addObserver(
201 IQ_UNBLOCK_PUSH,
202 self.plugin_parent.onUnblockPush,
203 client=self.parent
204 )
205
206 def getDiscoInfo(self, requestor, service, nodeIdentifier=""):
207 return [disco.DiscoFeature(NS_BLOCKING)]
208
209 def getDiscoItems(self, requestor, service, nodeIdentifier=""):
210 return []