plugin XEP-0060: add_managed_node now do strict matching by default:

`add_managed_node` now match node when they are exactly the same by default, but if the
new `strict_match` argument is set to `False`, the old behaviour of matching all node
which start with the given `node` argument is used.

rel 475
This commit is contained in:
Goffi 2026-02-23 13:07:59 +01:00
commit c21ac3af16
4 changed files with 37 additions and 19 deletions

View file

@ -97,7 +97,7 @@ class PubsubHook(object):
else:
# first hook on this node
self.host.plugins["XEP-0060"].add_managed_node(
node, items_cb=self._items_received
node, strict_match=False, items_cb=self._items_received
)
self.node_hooks[node] = 0
log.info(_("node manager installed on {node}").format(node=node))

View file

@ -81,6 +81,7 @@ class PubsubCache:
self.host.trigger.add("XEP-0060_getItems", self._get_items_trigger)
self._p.add_managed_node(
"",
strict_match=False,
items_cb=self.on_items_event,
delete_cb=self.on_delete_event,
purge_db=self.on_purge_event,

View file

@ -18,6 +18,7 @@
from collections import namedtuple
from dataclasses import dataclass, field
from functools import reduce
from typing import (
Any,
@ -58,7 +59,6 @@ from libervia.backend.tools import sat_defer
from libervia.backend.tools import xml_tools
from libervia.backend.tools.common import data_format
log = getLogger(__name__)
PLUGIN_INFO = {
@ -225,6 +225,12 @@ class NodeMetadata(BaseModel):
return self.to_data_form().toElement()
@dataclass
class NodeMonitorData:
strict_match: bool = True
callbacks: dict[str, list[tuple[Callable, int]]] = field(default_factory=dict)
class XEP_0060:
OPT_ACCESS_MODEL = "pubsub#access_model"
OPT_PERSIST_ITEMS = "pubsub#persist_items"
@ -260,9 +266,7 @@ class XEP_0060:
self.host = host
self._rsm = host.plugins.get("XEP-0059")
self._mam = host.plugins.get("XEP-0313")
self._node_cb = (
{}
) # dictionnary of callbacks for node (key: node, value: list of callbacks)
self._node_cb: dict[str, NodeMonitorData] = {}
self.rt_sessions = sat_defer.RTDeferredSessions()
host.bridge.add_method(
"ps_node_create",
@ -576,13 +580,17 @@ class XEP_0060:
return Extra(rsm_request, extra)
def add_managed_node(self, node: str, priority: int = 0, **kwargs: Callable):
def add_managed_node(
self, node: str, priority: int = 0, strict_match=True, **kwargs: Callable
):
"""Add a handler for a node
@param node: node to monitor
all node *prefixed* with this one will be triggered
@param priority: priority of the callback. Callbacks with higher priority will be
called first.
@param strict_match: if True, will match exactly "node". Otherwise, will match all
nodes which start with "node"
@param **kwargs: method(s) to call when the node is found
the method must be named after PubSub constants in lower case
and suffixed with "_cb"
@ -591,11 +599,13 @@ class XEP_0060:
"""
assert node is not None
assert kwargs
callbacks = self._node_cb.setdefault(node, {})
monitor_data = self._node_cb.setdefault(
node, NodeMonitorData(strict_match=strict_match)
)
for event, cb in kwargs.items():
event_name = event[:-3]
assert event_name in C.PS_EVENTS
cb_list = callbacks.setdefault(event_name, [])
cb_list = monitor_data.callbacks.setdefault(event_name, [])
cb_list.append((cb, priority))
cb_list.sort(key=lambda c: c[1], reverse=True)
@ -607,13 +617,13 @@ class XEP_0060:
"""
assert args
try:
registred_cb = self._node_cb[node]
monitor_data = self._node_cb[node]
except KeyError:
pass
else:
removed = False
for callback in args:
for event, cb_list in registred_cb.items():
for event, cb_list in monitor_data.callbacks.items():
to_remove = []
for cb in cb_list:
if cb[0] == callback:
@ -621,8 +631,8 @@ class XEP_0060:
for cb in to_remove:
cb_list.remove(cb)
if not cb_list:
del registred_cb[event]
if not registred_cb:
del monitor_data[event]
if not monitor_data:
del self._node_cb[node]
removed = True
break
@ -850,7 +860,7 @@ class XEP_0060:
try:
return [
item["id"]
for item in iq_result.pubsub.publish.elements(pubsub.NS_PUBSUB, "item", extra=extra)
for item in iq_result.pubsub.publish.elements(pubsub.NS_PUBSUB, "item")
]
except AttributeError:
return []
@ -988,7 +998,10 @@ class XEP_0060:
# possible with XEP-0059's plugin RSMRequest, and convert to Wokkel
# rsm.RSMRequest only when we need to work with wokkel directly.
if isinstance(rsm_request, rsm.RSMRequest):
log.warning("Use for rsm.RSMRequest is deprecated")
log.warning(
"Use of rsm.RSMRequest is deprecated, use plugin_xep_0059.RSMRequest "
"instead."
)
else:
rsm_request = rsm_request.to_wokkel_request()
if item_ids and max_items is not None:
@ -1966,11 +1979,14 @@ class SatPubSubClient(rsm.PubSubClient):
@param event(unicode): one of C.PS_ITEMS, C.PS_RETRACT, C.PS_DELETE
@return (iterator[callable]): callbacks for this node/event
"""
for registered_node, callbacks_dict in self.parent_plugin._node_cb.items():
if not node.startswith(registered_node):
for registered_node, monitor_data in self.parent_plugin._node_cb.items():
if monitor_data.strict_match:
if node != registered_node:
continue
elif not node.startswith(registered_node):
continue
try:
for callback_data in callbacks_dict[event]:
for callback_data in monitor_data.callbacks[event]:
yield callback_data[0]
except KeyError:
continue

View file

@ -56,7 +56,6 @@ from libervia.backend.tools.common import data_format
from libervia.backend.tools.common import uri as xmpp_uri
from libervia.backend.tools.common import regex
log = getLogger(__name__)
@ -761,7 +760,9 @@ class XEP_0277:
}
)
self.rt_sessions = sat_defer.RTDeferredSessions()
self._p.add_managed_node(NS_MICROBLOG, items_cb=self._items_received)
self._p.add_managed_node(
NS_MICROBLOG, strict_match=False, items_cb=self._items_received
)
host.bridge.add_method(
"mb_send",