component email gateway: implement pubsub relationships and pubsub extended discovery:

- Pubsub relationships are used to set node hierarchy (comments nodes are set as children of
corresponding pubsub nodes).
- XEP-0499 Extended Pubsub Discovery is used to return hierarchy of pubsub nodes and
  items.

rel 463
This commit is contained in:
Goffi 2025-08-31 12:34:37 +02:00
commit 57e71c326d
2 changed files with 48 additions and 1 deletions

View file

@ -72,6 +72,7 @@ from libervia.backend.plugins.plugin_xep_0131 import HeadersData, Urgency, XEP_0
from libervia.backend.plugins.plugin_xep_0277 import Comment, MbData, XEP_0277
from libervia.backend.plugins.plugin_xep_0373 import binary_to_ascii_armor
from libervia.backend.plugins.plugin_xep_0498 import XEP_0498
from libervia.backend.plugins.plugin_xep_0499 import XEP_0499, ExtDiscoOptions
from libervia.backend.tools.common import date_utils, regex, uri
from libervia.backend.tools.utils import aio
@ -94,7 +95,7 @@ PLUGIN_INFO = {
C.PI_TYPE: C.PLUG_TYPE_ENTRY_POINT,
C.PI_PROTOCOLS: [],
C.PI_DEPENDENCIES: [
"XEP-0033", "XEP-0077", "XEP-0106", "XEP-0277", "XEP-0498", "GRE", "GRE-MIME",
"XEP-0033", "XEP-0077", "XEP-0106", "XEP-0277", "XEP-0498", "XEP-0499", "GRE", "GRE-MIME",
"GRE-OpenPGP", "PUBSUB_CACHE", "TEXT_SYNTAXES"
],
C.PI_RECOMMENDATIONS: [],
@ -146,6 +147,7 @@ class EmailGatewayComponent(GetDataHandler):
self._shim = cast(XEP_0131, host.plugins["XEP-0131"])
self._mb = cast(XEP_0277, host.plugins["XEP-0277"])
self._pfs = cast(XEP_0498, host.plugins["XEP-0498"])
self._ext_disco = cast(XEP_0499, host.plugins["XEP-0499"])
self._gre = cast(GRE, host.plugins["GRE"])
self._syntax = cast(TextSyntaxes, host.plugins["TEXT_SYNTAXES"])
# TODO: For the moment, all credentials are kept in cache; we should only keep the
@ -1284,6 +1286,7 @@ class EmailGatewayComponent(GetDataHandler):
entity = user_jid,
affiliation = Affiliation.owner
)],
"parent_node": root_node
},
)
else:

View file

@ -31,6 +31,7 @@ from libervia.backend.core.log import getLogger
from libervia.backend.memory.sqla_mapping import AccessModel, Affiliation
from libervia.backend.plugins.plugin_pubsub_cache import PubsubCache
from libervia.backend.plugins.plugin_xep_0498 import NodeData
from libervia.backend.plugins.plugin_xep_0499 import ExtDiscoOptions, ExtDiscoResult, DiscoPubsubItem, DiscoPubsubNode
from libervia.backend.tools.utils import ensure_deferred
if TYPE_CHECKING:
from . import EmailGatewayComponent
@ -220,6 +221,7 @@ class EmailGWPubsubService(rsm.PubSubService):
def __init__(self, gateway: "EmailGatewayComponent"):
self.gateway = gateway
self._pfs = gateway._pfs
gateway._ext_disco.register_handler(self.on_extended_disco)
resource = EmailGWPubsubResource(self)
super().__init__(resource)
self.host = gateway.host
@ -229,6 +231,48 @@ class EmailGWPubsubService(rsm.PubSubService):
"name": "Libervia Email Gateway",
}
@property
def client(self) -> SatXMPPComponent:
client = self.gateway.client
assert client is not None
return client
async def on_extended_disco(
self,
client: SatXMPPComponent,
iq_elt: domish.Element,
ext_disco_options: ExtDiscoOptions
) -> ExtDiscoResult:
"""Handle pubsub extended disco"""
query_elt = iq_elt.query
assert query_elt is not None
node = query_elt.getAttribute("node")
if not "nodes" in ext_disco_options.type:
raise NotImplementedError("Returning items only is not yet supported.")
depth = ext_disco_options.depth
nodes = await G.storage.get_pubsub_nodes(
client,
service=client.jid,
node_name = node,
linked_node=ext_disco_options.linked_nodes,
depth=depth,
with_items="items" in ext_disco_options.type
)
if depth == 0:
# If depth == 0, we don't want to use `from_sqlalchemy_full_hierarchy` to
# avoid returning children, and because children `parent` and `child_nodes`
# fields are not loaded.
return ExtDiscoResult([
DiscoPubsubNode.from_sqlalchemy(node, self.client.jid)
for node in nodes
])
else:
return ExtDiscoResult([
DiscoPubsubNode.from_sqlalchemy_full_hierarchy(node, self.client.jid)
for node in nodes
])
@ensure_deferred
async def getDiscoInfo(
self, requestor: jid.JID, target: jid.JID, nodeIdentifier: str = ""