diff --git a/libervia/backend/plugins/plugin_xep_0084.py b/libervia/backend/plugins/plugin_xep_0084.py index 6465bdc2..5b0347d0 100644 --- a/libervia/backend/plugins/plugin_xep_0084.py +++ b/libervia/backend/plugins/plugin_xep_0084.py @@ -35,6 +35,9 @@ from tx_xmpp import disco, itx_xmpp, pubsub log = getLogger(__name__) +# Supported image formats, in priority order +FORMATS = ("png", "jpeg", "webp", "gif", "svg+xml") + IMPORT_NAME = "XEP-0084" PLUGIN_INFO = { @@ -79,6 +82,9 @@ class XEP_0084: client = self.host.get_client(profile) defer.ensureDeferred(self.on_metadata_update(client, itemsEvent)) + class UnsupportedFormat(Exception): + """Avatar metadata exists but no supported format is found.""" + @staticmethod def _parse_metadata_info( item_elt: domish.Element, @@ -86,8 +92,8 @@ class XEP_0084: """Parse avatar metadata item and extract ID, media type, and size. @param item_elt: pubsub item element containing metadata. - @return: (avatar_id, media_type, size) if a valid info element is found, - None if avatar is disabled or metadata is invalid. + @return: (avatar_id, media_type, size) if a supported format is found, + None if avatar is disabled or no supported format is found. """ try: metadata_elt = next(item_elt.elements(NS_AVATAR_METADATA, "metadata")) @@ -95,6 +101,8 @@ class XEP_0084: log.warning(f"missing metadata element: {item_elt.toXml()}") return None + # (priority, id, type, size) + candidates: list[tuple[int, str, str, int]] = [] for info_elt in metadata_elt.elements(NS_AVATAR_METADATA, "info"): try: avatar_id = str(info_elt["id"]) @@ -104,14 +112,26 @@ class XEP_0084: log.warning(f"invalid element: {item_elt.toXml()}") continue if not avatar_id: + # Avatar is explicitly disabled + # (https://xmpp.org/extensions/xep-0084.html#pub-disable) + return None + if not media_type.startswith("image/"): continue - # FIXME: to simplify, we only handle image/png for now - if media_type == "image/png": - return (avatar_id, media_type, size) + subtype = media_type.split("/", 1)[1].lower() + try: + priority = FORMATS.index(subtype) + except ValueError: + continue + candidates.append((priority, avatar_id, media_type, size)) - # mandatory image/png is missing, or avatar is disabled - # (https://xmpp.org/extensions/xep-0084.html#pub-disable) - return None + if not candidates: + raise XEP_0084.UnsupportedFormat( + f"no supported format found in avatar metadata" + ) + + candidates.sort(key=lambda c: c[0]) + _, avatar_id, media_type, size = candidates[0] + return (avatar_id, media_type, size) async def on_metadata_update( self, client: SatXMPPEntity, itemsEvent: pubsub.ItemsEvent @@ -130,7 +150,11 @@ class XEP_0084: await self._i.update(client, IMPORT_NAME, "avatar", None, entity) return - parsed = self._parse_metadata_info(items[0]) + try: + parsed = self._parse_metadata_info(items[0]) + except XEP_0084.UnsupportedFormat: + log.warning(f"Avatar for {entity.full()} is in unsupported format.") + parsed = None if parsed is None: log.debug(f"Avatar disabled for {entity.full()}") await self._i.update(client, IMPORT_NAME, "avatar", None, entity) @@ -223,7 +247,10 @@ class XEP_0084: if not data_items: return None - parsed = self._parse_metadata_info(data_items[0]) + try: + parsed = self._parse_metadata_info(data_items[0]) + except XEP_0084.UnsupportedFormat: + return None if parsed is None: return None