Coverage for slidge/core/dispatcher/vcard.py: 89%
100 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-09 03:59 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-09 03:59 +0000
1from typing import TYPE_CHECKING
3from slixmpp import CoroutineCallback, Iq, StanzaPath, register_stanza_plugin
4from slixmpp.exceptions import XMPPError
5from slixmpp.plugins.xep_0084.stanza import MetaData
6from slixmpp.plugins.xep_0292.stanza import NS as VCard4NS
8from slidge.util.util import fix_namespaces
10from ...contact import LegacyContact
11from ...group import LegacyParticipant
12from ...util.types import AnySession, Avatar
13from .util import DispatcherMixin, exceptions_to_xmpp_errors
15if TYPE_CHECKING:
16 from slidge.util.types import AnyGateway
19class VCardMixin(DispatcherMixin):
20 __slots__: list[str] = []
22 def __init__(self, xmpp: "AnyGateway") -> None:
23 super().__init__(xmpp)
24 xmpp.register_handler(
25 CoroutineCallback(
26 "get_vcard", StanzaPath("iq@type=get/vcard"), self.on_get_vcard
27 )
28 )
29 xmpp.remove_handler("VCardTemp")
30 xmpp.register_handler(
31 CoroutineCallback(
32 "VCardTemp",
33 StanzaPath("iq/vcard_temp"),
34 self.__vcard_temp_handler,
35 )
36 )
37 # TODO: MR to slixmpp adding this to XEP-0084
38 register_stanza_plugin(
39 self.xmpp.plugin["xep_0060"].stanza.Item,
40 MetaData,
41 )
43 @exceptions_to_xmpp_errors
44 async def on_get_vcard(self, iq: Iq) -> None:
45 session = await self._get_session(iq, logged=True)
46 contact = await session.contacts.by_jid(iq.get_to())
47 vcard = await contact.get_vcard()
48 reply = iq.reply()
49 if vcard:
50 reply.append(vcard)
51 else:
52 reply.enable("vcard")
53 reply.send()
55 @exceptions_to_xmpp_errors
56 async def __vcard_temp_handler(self, iq: Iq) -> None:
57 if iq["type"] == "get":
58 return await self.__handle_get_vcard_temp(iq)
60 if iq["type"] == "set":
61 return await self.__handle_set_vcard_temp(iq)
63 async def __fetch_user_avatar(self, session: AnySession) -> tuple[bytes, str]:
64 hash_ = session.user.avatar_hash
65 if not hash_:
66 raise XMPPError(
67 "item-not-found", "The slidge user does not have any avatar set"
68 )
69 meta_iq = await self.xmpp.plugin["xep_0060"].get_item(
70 session.user_jid,
71 MetaData.namespace,
72 hash_,
73 ifrom=self.xmpp.boundjid.bare,
74 )
75 info = meta_iq["pubsub"]["items"]["item"]["avatar_metadata"]["info"]
76 type_ = info["type"]
77 data_iq = await self.xmpp.plugin["xep_0084"].retrieve_avatar(
78 session.user_jid, hash_, ifrom=self.xmpp.boundjid.bare
79 )
80 bytes_ = data_iq["pubsub"]["items"]["item"]["avatar_data"]["value"]
81 return bytes_, type_
83 async def __handle_get_vcard_temp(self, iq: Iq) -> None:
84 session = self.xmpp.get_session_from_stanza(iq)
85 entity = await session.get_contact_or_group_or_participant(iq.get_to(), False)
86 if not entity:
87 raise XMPPError("item-not-found")
89 bytes_ = None
90 if isinstance(entity, LegacyParticipant):
91 if entity.is_user:
92 bytes_, type_ = await self.__fetch_user_avatar(session)
93 if not bytes_:
94 raise XMPPError(
95 "internal-server-error",
96 "Could not fetch the slidge user's avatar",
97 )
98 avatar = None
99 vcard = None
100 elif not (contact := entity.contact):
101 raise XMPPError("item-not-found", "This participant has no contact")
102 else:
103 vcard = await contact.get_vcard()
104 avatar = contact.get_avatar()
105 type_ = "image/png"
106 else:
107 avatar = entity.get_avatar()
108 type_ = "image/png"
109 if isinstance(entity, LegacyContact):
110 vcard = await entity.get_vcard()
111 else:
112 vcard = None
113 v = self.xmpp.plugin["xep_0054"].make_vcard()
114 if avatar is not None and avatar.data:
115 bytes_ = avatar.data.get_value()
116 if bytes_:
117 v["PHOTO"]["BINVAL"] = bytes_
118 v["PHOTO"]["TYPE"] = type_
119 if vcard:
120 fix_namespaces(
121 vcard.xml,
122 VCard4NS,
123 self.xmpp.plugin["xep_0054"].stanza.VCardTemp.namespace,
124 )
125 for el in vcard.xml:
126 v.append(el)
127 reply = iq.reply()
128 reply.append(v)
129 reply.send()
131 async def __handle_set_vcard_temp(self, iq: Iq) -> None:
132 muc = await self.get_muc_from_stanza(iq)
133 to = iq.get_to()
135 if to.resource:
136 raise XMPPError("bad-request", "You cannot set participants avatars")
138 data = iq["vcard_temp"]["PHOTO"]["BINVAL"] or None
139 try:
140 legacy_id = await muc.on_avatar(
141 data, iq["vcard_temp"]["PHOTO"]["TYPE"] or None
142 )
143 except XMPPError:
144 raise
145 except Exception as e:
146 raise XMPPError("internal-server-error", str(e))
147 reply = iq.reply(clear=True)
148 reply.enable("vcard_temp")
149 reply.send()
151 if not data:
152 await muc.set_avatar(None)
153 return
155 if legacy_id:
156 await muc.set_avatar(Avatar(data=data, unique_id=legacy_id))