core, dependencies: Update Tx-XMPP to 0.12:

Tx-XMPP 0.12 has beaking change in `discoFeature.identities` because it now supports
`xml:lang` attribute, so this update implies updating the code everywhere were this is
used.
This commit is contained in:
Goffi 2026-04-17 15:27:40 +02:00
commit f6db575ee4
10 changed files with 747 additions and 696 deletions

View file

@ -23,7 +23,7 @@ import hashlib
import copy
from pathlib import Path
import shutil
from typing import Optional, List, Tuple, Dict, cast
from typing import Optional, cast
from libervia import backend
from libervia.backend.core.i18n import _, D_, language_switch
@ -960,15 +960,16 @@ class LiberviaBackend(service.Service):
disco = await self.get_disco_infos(client, res_jid)
for (category, type_), name in disco.identities.items():
for (category, type_, lang), name in disco.identities.items():
identities = res_data.setdefault("identities", [])
identities.append(
{
"name": name,
"category": category,
"type": type_,
}
)
identity_entry = {
"name": name,
"category": category,
"type": type_,
}
if lang:
identity_entry["lang"] = lang
identities.append(identity_entry)
ret_data.append(res_data)
@ -1334,22 +1335,31 @@ class LiberviaBackend(service.Service):
def _find_by_features(
self,
namespaces,
identities,
bare_jids,
service,
roster,
own_jid,
local_device,
profile_key,
):
namespaces: list[str],
identities: list[list[str]] | None,
bare_jids: bool,
service: bool,
roster: bool,
own_jid: bool,
local_device: bool,
profile_key: str,
) -> defer.Deferred[
tuple[
dict[jid.JID, list[tuple[str, str, str, str]]],
dict[jid.JID, list[tuple[str, str, str, str]]],
dict[jid.JID, list[tuple[str, str, str, str]]],
]
]:
client = self.get_client(profile_key)
identities = [tuple(i) for i in identities] if identities else None
identities_typed = cast(
list[tuple[str, str]] | None,
[tuple(i) for i in identities] if identities else None,
)
return defer.ensureDeferred(
self.find_by_features(
client,
namespaces,
identities,
identities_typed,
bare_jids,
service,
roster,
@ -1361,17 +1371,17 @@ class LiberviaBackend(service.Service):
async def find_by_features(
self,
client: SatXMPPEntity,
namespaces: List[str],
identities: Optional[List[Tuple[str, str]]] = None,
namespaces: list[str],
identities: list[tuple[str, str]] | None = None,
bare_jids: bool = False,
service: bool = True,
roster: bool = True,
own_jid: bool = True,
local_device: bool = False,
) -> Tuple[
Dict[jid.JID, Tuple[str, str, str]],
Dict[jid.JID, Tuple[str, str, str]],
Dict[jid.JID, Tuple[str, str, str]],
) -> tuple[
dict[jid.JID, list[tuple[str, str, str, str]]],
dict[jid.JID, list[tuple[str, str, str, str]]],
dict[jid.JID, list[tuple[str, str, str, str]]],
]:
"""Retrieve all services or contacts managing a set a features
@ -1391,7 +1401,7 @@ class LiberviaBackend(service.Service):
- service entities
- own entities
- roster entities
Each element is a dict mapping from jid to a tuple with category, type and
Each element is a dict mapping from jid to a tuple with category, type, lang and
name of the entity
"""
assert isinstance(namespaces, list)
@ -1423,13 +1433,13 @@ class LiberviaBackend(service.Service):
).format(service_jid=service_jid.full())
)
continue
if identities is not None and not set(infos.identities.keys()).issuperset(
identities
):
if identities is not None and not set(
k[:2] for k in infos.identities.keys()
).issuperset(identities):
continue
found_identities = [
(cat, type_, name or "")
for (cat, type_), name in infos.identities.items()
(cat, type_, lang, name or "")
for (cat, type_, lang), name in infos.identities.items()
]
found_service[service_jid.full()] = found_identities
@ -1487,12 +1497,12 @@ class LiberviaBackend(service.Service):
continue
if infos.features.issuperset(namespaces):
if identities is not None and not set(
infos.identities.keys()
k[:2] for k in infos.identities.keys()
).issuperset(identities):
continue
found_identities = [
(cat, type_, name or "")
for (cat, type_), name in infos.identities.items()
(cat, type_, lang, name or "")
for (cat, type_, lang), name in infos.identities.items()
]
found[full_jid.full()] = found_identities

View file

@ -1489,9 +1489,9 @@ class SatMessageProtocol(xmppim.MessageProtocol):
)
else:
domain_disco_infos = disco_infos
if ("client", "bot") in disco_infos.identities:
if any(k[:2] == ("client", "bot") for k in disco_infos.identities):
extra["is-bot"] = True
for (category, type_), label in domain_disco_infos.identities.items():
for (category, type_, lang), label in domain_disco_infos.identities.items():
if category == "gateway":
extra["origin"] = type_
if label:

View file

@ -181,7 +181,9 @@ class Discovery(object):
if not set(features).issubset(disco_infos.features):
raise exceptions.FeatureNotFound()
if identity is not None and identity not in disco_infos.identities:
if identity is not None and not any(
k[:2] == identity for k in disco_infos.identities
):
raise exceptions.FeatureNotFound()
async def has_identity(
@ -201,7 +203,7 @@ class Discovery(object):
@return: True if the entity has the given identity
"""
disco_infos = await self.get_infos(client, jid_, node)
return (category, type_) in disco_infos.identities
return any(k[:2] == (category, type_) for k in disco_infos.identities)
def get_infos(
self,
@ -342,7 +344,7 @@ class Discovery(object):
found_entities = set()
def infos_cb(infos, entity_jid):
if (category, type_) in infos.identities:
if any(k[:2] == (category, type_) for k in infos.identities):
found_entities.add(entity_jid)
def got_items(items):
@ -387,7 +389,9 @@ class Discovery(object):
if entity is None:
log.warning(_("Received an item without JID"))
return
if identity is not None and identity not in infos.identities:
if identity is not None and not any(
k[:2] == identity for k in infos.identities
):
return
if features.issubset(infos.features):
found_entities.add(entity)
@ -489,8 +493,8 @@ class Discovery(object):
return (
[str(f) for f in disco_infos.features],
[
(cat, type_, name or "")
for (cat, type_), name in list(disco_infos.identities.items())
(cat, type_, lang, name or "")
for (cat, type_, lang), name in list(disco_infos.identities.items())
],
extensions,
)

View file

@ -728,10 +728,9 @@ class APGateway:
async def is_pubsub(self, jid_: jid.JID) -> bool:
"""Indicate if a JID is a Pubsub service"""
host_disco = await self.host.get_disco_infos(self.client, jid_)
return ("pubsub", "service") in host_disco.identities and not (
"pubsub",
"pep",
) in host_disco.identities
return any(
k[:2] == ("pubsub", "service") for k in host_disco.identities
) and not any(k[:2] == ("pubsub", "pep") for k in host_disco.identities)
async def get_jid_and_node(self, ap_account: str) -> tuple[jid.JID, str | None]:
"""Decode raw AP account handle to get XMPP JID and Pubsub Node

View file

@ -753,8 +753,11 @@ class Identity:
except Exception as e:
log.exception(f"Can't get disco infos for {entity}.")
else:
for (category, type_), name in disco_infos.identities.items():
identities.append({"category": category, "type": type_, "name": name})
for (category, type_, lang), name in disco_infos.identities.items():
identity_entry = {"category": category, "type": type_, "name": name}
if lang:
identity_entry["lang"] = lang
identities.append(identity_entry)
return id_data

View file

@ -141,7 +141,7 @@ class XEP_0353:
else:
raise e
else:
categories = {c for c, __ in infos.identities}
categories = {c for c, __, __ in infos.identities}
if "component" in categories:
# we don't use message initiation with components
return True

View file

@ -1108,10 +1108,10 @@ class XEP_0373:
"Error performing service discovery on the own bare JID."
) from e
identities = cast(dict[tuple[str, str], str], infos.identities)
identities = cast(dict[tuple[str, str, str], str], infos.identities)
features = cast(set[DiscoFeature], infos.features)
if ("pubsub", "pep") not in identities:
if not any(k[:2] == ("pubsub", "pep") for k in identities):
raise exceptions.FeatureNotFound("Server doesn't support PEP.")
if "http://jabber.org/protocol/pubsub#access-whitelist" not in features:

View file

@ -60,7 +60,7 @@ dependencies = [
"urwid >= 1.2.0, < 3",
"urwid-satext",
"xmlschema < 4.3.1",
"tx-xmpp>=0.10",
"tx-xmpp>=0.12",
]
keywords = [
"XMPP",

View file

@ -777,9 +777,7 @@ class TestPubsubResourceItems:
subscriptionTo=False,
subscriptionFrom=True,
)
mock_xep_0356.get_roster = AsyncMock(
return_value={requestor_jid: roster_item}
)
mock_xep_0356.get_roster = AsyncMock(return_value={requestor_jid: roster_item})
host.plugins["XEP-0356"] = mock_xep_0356
with raises(StanzaError, match="forbidden"):
@ -814,9 +812,7 @@ class TestPubsubResourceItems:
subscriptionTo=False,
subscriptionFrom=True,
)
mock_xep_0356.get_roster = AsyncMock(
return_value={requestor_jid: roster_item}
)
mock_xep_0356.get_roster = AsyncMock(return_value={requestor_jid: roster_item})
await resource._items(request)
@ -1478,6 +1474,6 @@ class TestPubsubAutoSubscribe:
call_args = host.memory.storage.get_pep_last_items.call_args
# We want the second positional argument (list of publishers)
publishers_arg = call_args[0][1]
assert publishers_arg == [requestor_jid], (
f"Expected only self-subscription, got {publishers_arg}"
)
assert publishers_arg == [
requestor_jid
], f"Expected only self-subscription, got {publishers_arg}"

1275
uv.lock generated

File diff suppressed because it is too large Load diff