Mercurial > libervia-backend
annotate sat/plugins/plugin_exp_pubsub_hook.py @ 3728:b15644cae50d
component AP gateway: JID/node ⟺ AP outbox conversion:
- convert a combination of JID and optional pubsub node to AP actor handle (see
`getJIDAndNode` for details) and vice versa
- the gateway now provides a Pubsub service
- retrieve pubsub node and convert it to AP collection, AP pagination is converted to RSM
- do the opposite: convert AP collection to pubsub and handle RSM request. Due to
ActivityStream collection pagination limitations, some RSM request produce inefficient
requests, but caching should be used most of the time in the future and avoid the
problem.
- set specific name to HTTP Server
- new `local_only` setting (`True` by default) to indicate if the gateway can request or
not XMPP Pubsub nodes from other servers
- disco info now specifies important features such as Pubsub RSM, and nodes metadata
ticket 363
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 25 Jan 2022 17:54:06 +0100 |
parents | be6d91572633 |
children | 524856bd7b19 |
rev | line source |
---|---|
3028 | 1 #!/usr/bin/env python3 |
3137 | 2 |
2307 | 3 |
4 # SAT plugin for Pubsub Hooks | |
3479 | 5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) |
2307 | 6 |
7 # This program is free software: you can redistribute it and/or modify | |
8 # it under the terms of the GNU Affero General Public License as published by | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
17 # You should have received a copy of the GNU Affero General Public License | |
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
19 | |
20 from sat.core.i18n import _ | |
21 from sat.core.constants import Const as C | |
22 from sat.core import exceptions | |
23 from sat.core.log import getLogger | |
24 from sat.memory import persistent | |
25 from twisted.words.protocols.jabber import jid | |
26 from twisted.internet import defer | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
27 |
2307 | 28 log = getLogger(__name__) |
29 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
30 NS_PUBSUB_HOOK = "PUBSUB_HOOK" |
2307 | 31 |
32 PLUGIN_INFO = { | |
33 C.PI_NAME: "PubSub Hook", | |
34 C.PI_IMPORT_NAME: NS_PUBSUB_HOOK, | |
35 C.PI_TYPE: "EXP", | |
36 C.PI_PROTOCOLS: [], | |
37 C.PI_DEPENDENCIES: ["XEP-0060"], | |
38 C.PI_MAIN: "PubsubHook", | |
39 C.PI_HANDLER: "no", | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
40 C.PI_DESCRIPTION: _( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
41 """Experimental plugin to launch on action on Pubsub notifications""" |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
42 ), |
2307 | 43 } |
44 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
45 # python module |
3028 | 46 HOOK_TYPE_PYTHON = "python" |
2307 | 47 # python file path |
3028 | 48 HOOK_TYPE_PYTHON_FILE = "python_file" |
2307 | 49 # python code directly |
3028 | 50 HOOK_TYPE_PYTHON_CODE = "python_code" |
2307 | 51 HOOK_TYPES = (HOOK_TYPE_PYTHON, HOOK_TYPE_PYTHON_FILE, HOOK_TYPE_PYTHON_CODE) |
52 | |
53 | |
54 class PubsubHook(object): | |
55 def __init__(self, host): | |
3028 | 56 log.info(_("PubSub Hook initialization")) |
2307 | 57 self.host = host |
58 self.node_hooks = {} # keep track of the number of hooks per node (for all profiles) | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
59 host.bridge.addMethod( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
60 "psHookAdd", ".plugin", in_sign="ssssbs", out_sign="", method=self._addHook |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
61 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
62 host.bridge.addMethod( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
63 "psHookRemove", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
64 ".plugin", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
65 in_sign="sssss", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
66 out_sign="i", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
67 method=self._removeHook, |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
68 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
69 host.bridge.addMethod( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
70 "psHookList", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
71 ".plugin", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
72 in_sign="s", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
73 out_sign="aa{ss}", |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
74 method=self._listHooks, |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
75 ) |
2307 | 76 |
77 @defer.inlineCallbacks | |
78 def profileConnected(self, client): | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
79 hooks = client._hooks = persistent.PersistentBinaryDict( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
80 NS_PUBSUB_HOOK, client.profile |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
81 ) |
2307 | 82 client._hooks_temporary = {} |
83 yield hooks.load() | |
84 for node in hooks: | |
85 self._installNodeManager(client, node) | |
86 | |
87 def profileDisconnected(self, client): | |
88 for node in client._hooks: | |
89 self._removeNodeManager(client, node) | |
90 | |
91 def _installNodeManager(self, client, node): | |
92 if node in self.node_hooks: | |
3028 | 93 log.debug(_("node manager already set for {node}").format(node=node)) |
2307 | 94 self.node_hooks[node] += 1 |
95 else: | |
96 # first hook on this node | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
97 self.host.plugins["XEP-0060"].addManagedNode( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
98 node, items_cb=self._itemsReceived |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
99 ) |
2307 | 100 self.node_hooks[node] = 0 |
3028 | 101 log.info(_("node manager installed on {node}").format(node=node)) |
2307 | 102 |
103 def _removeNodeManager(self, client, node): | |
104 try: | |
105 self.node_hooks[node] -= 1 | |
106 except KeyError: | |
3028 | 107 log.error(_("trying to remove a {node} without hook").format(node=node)) |
2307 | 108 else: |
109 if self.node_hooks[node] == 0: | |
110 del self.node_hooks[node] | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
111 self.host.plugins["XEP-0060"].removeManagedNode(node, self._itemsReceived) |
3028 | 112 log.debug(_("hook removed")) |
2307 | 113 else: |
3028 | 114 log.debug(_("node still needed for an other hook")) |
2307 | 115 |
116 def installHook(self, client, service, node, hook_type, hook_arg, persistent): | |
117 if hook_type not in HOOK_TYPES: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
118 raise exceptions.DataError( |
3028 | 119 _("{hook_type} is not handled").format(hook_type=hook_type) |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
120 ) |
2307 | 121 if hook_type != HOOK_TYPE_PYTHON_FILE: |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
122 raise NotImplementedError( |
3028 | 123 _("{hook_type} hook type not implemented yet").format( |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
124 hook_type=hook_type |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
125 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
126 ) |
2307 | 127 self._installNodeManager(client, node) |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
128 hook_data = {"service": service, "type": hook_type, "arg": hook_arg} |
2307 | 129 |
130 if persistent: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
131 hooks_list = client._hooks.setdefault(node, []) |
2307 | 132 hooks_list.append(hook_data) |
133 client._hooks.force(node) | |
134 else: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
135 hooks_list = client._hooks_temporary.setdefault(node, []) |
2307 | 136 hooks_list.append(hook_data) |
137 | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
138 log.info( |
3028 | 139 _("{persistent} hook installed on {node} for {profile}").format( |
140 persistent=_("persistent") if persistent else _("temporary"), | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
141 node=node, |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
142 profile=client.profile, |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
143 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
144 ) |
2307 | 145 |
146 def _itemsReceived(self, client, itemsEvent): | |
147 node = itemsEvent.nodeIdentifier | |
148 for hooks in (client._hooks, client._hooks_temporary): | |
149 if node not in hooks: | |
150 continue | |
151 hooks_list = hooks[node] | |
152 for hook_data in hooks_list[:]: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
153 if hook_data["service"] != itemsEvent.sender.userhostJID(): |
2307 | 154 continue |
155 try: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
156 callback = hook_data["callback"] |
2307 | 157 except KeyError: |
158 # first time we get this hook, we create the callback | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
159 hook_type = hook_data["type"] |
2307 | 160 try: |
161 if hook_type == HOOK_TYPE_PYTHON_FILE: | |
162 hook_globals = {} | |
3028 | 163 exec(compile(open(hook_data["arg"], "rb").read(), hook_data["arg"], 'exec'), hook_globals) |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
164 callback = hook_globals["hook"] |
2307 | 165 else: |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
166 raise NotImplementedError( |
3028 | 167 _("{hook_type} hook type not implemented yet").format( |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
168 hook_type=hook_type |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
169 ) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
170 ) |
2307 | 171 except Exception as e: |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
172 log.warning( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
173 _( |
3028 | 174 "Can't load Pubsub hook at node {node}, it will be removed: {reason}" |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
175 ).format(node=node, reason=e) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
176 ) |
2307 | 177 hooks_list.remove(hook_data) |
178 continue | |
179 | |
180 for item in itemsEvent.items: | |
181 try: | |
182 callback(self.host, client, item) | |
183 except Exception as e: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
184 log.warning( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
185 _( |
3028 | 186 "Error while running Pubsub hook for node {node}: {msg}" |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
187 ).format(node=node, msg=e) |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
188 ) |
2307 | 189 |
190 def _addHook(self, service, node, hook_type, hook_arg, persistent, profile): | |
191 client = self.host.getClient(profile) | |
192 service = jid.JID(service) if service else client.jid.userhostJID() | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
193 return self.addHook( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
194 client, |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
195 service, |
3028 | 196 str(node), |
197 str(hook_type), | |
198 str(hook_arg), | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
199 persistent, |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
200 ) |
2307 | 201 |
202 def addHook(self, client, service, node, hook_type, hook_arg, persistent): | |
203 r"""Add a hook which will be triggered on a pubsub notification | |
204 | |
205 @param service(jid.JID): service of the node | |
206 @param node(unicode): Pubsub node | |
207 @param hook_type(unicode): type of the hook, one of: | |
208 - HOOK_TYPE_PYTHON: a python module (must be in path) | |
209 module must have a "hook" method which will be called | |
210 - HOOK_TYPE_PYTHON_FILE: a python file | |
211 file must have a "hook" method which will be called | |
212 - HOOK_TYPE_PYTHON_CODE: direct python code | |
2444
30278ea1ca7c
plugin XEP-0060: added node watching methods to bridge:
Goffi <goffi@goffi.org>
parents:
2307
diff
changeset
|
213 /!\ Python hooks will be executed in SàT context, |
2307 | 214 with host, client and item as arguments, it means that: |
215 - they can do whatever they wants, so don't run untrusted hooks | |
216 - they MUST NOT BLOCK, they are run in Twisted async environment and blocking would block whole SàT process | |
217 - item are domish.Element | |
218 @param hook_arg(unicode): argument of the hook, depending on the hook_type | |
219 can be a module path, file path, python code | |
220 """ | |
221 assert service is not None | |
222 return self.installHook(client, service, node, hook_type, hook_arg, persistent) | |
223 | |
224 def _removeHook(self, service, node, hook_type, hook_arg, profile): | |
225 client = self.host.getClient(profile) | |
226 service = jid.JID(service) if service else client.jid.userhostJID() | |
227 return self.removeHook(client, service, node, hook_type or None, hook_arg or None) | |
228 | |
229 def removeHook(self, client, service, node, hook_type=None, hook_arg=None): | |
230 """Remove a persistent or temporaty root | |
231 | |
232 @param service(jid.JID): service of the node | |
233 @param node(unicode): Pubsub node | |
234 @param hook_type(unicode, None): same as for [addHook] | |
235 match all if None | |
236 @param hook_arg(unicode, None): same as for [addHook] | |
237 match all if None | |
238 @return(int): number of hooks removed | |
239 """ | |
240 removed = 0 | |
241 for hooks in (client._hooks, client._hooks_temporary): | |
242 if node in hooks: | |
243 for hook_data in hooks[node]: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
244 if ( |
3028 | 245 service != hook_data["service"] |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
246 or hook_type is not None |
3028 | 247 and hook_type != hook_data["type"] |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
248 or hook_arg is not None |
3028 | 249 and hook_arg != hook_data["arg"] |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
250 ): |
2307 | 251 continue |
252 hooks[node].remove(hook_data) | |
253 removed += 1 | |
254 if not hooks[node]: | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
255 # no more hooks, we can remove the node |
2307 | 256 del hooks[node] |
257 self._removeNodeManager(client, node) | |
258 else: | |
259 if hooks == client._hooks: | |
260 hooks.force(node) | |
261 return removed | |
262 | |
263 def _listHooks(self, profile): | |
264 hooks_list = self.listHooks(self.host.getClient(profile)) | |
265 for hook in hooks_list: | |
3028 | 266 hook["service"] = hook["service"].full() |
267 hook["persistent"] = C.boolConst(hook["persistent"]) | |
2307 | 268 return hooks_list |
269 | |
270 def listHooks(self, client): | |
271 """return list of registered hooks""" | |
272 hooks_list = [] | |
273 for hooks in (client._hooks, client._hooks_temporary): | |
274 persistent = hooks is client._hooks | |
3028 | 275 for node, hooks_data in hooks.items(): |
2307 | 276 for hook_data in hooks_data: |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
277 hooks_list.append( |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
278 { |
3028 | 279 "service": hook_data["service"], |
280 "node": node, | |
281 "type": hook_data["type"], | |
282 "arg": hook_data["arg"], | |
283 "persistent": persistent, | |
2624
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
284 } |
56f94936df1e
code style reformatting using black
Goffi <goffi@goffi.org>
parents:
2562
diff
changeset
|
285 ) |
2307 | 286 return hooks_list |