slidge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. slidge/__init__.py +61 -0
  2. slidge/__main__.py +192 -0
  3. slidge/command/__init__.py +28 -0
  4. slidge/command/adhoc.py +258 -0
  5. slidge/command/admin.py +193 -0
  6. slidge/command/base.py +441 -0
  7. slidge/command/categories.py +3 -0
  8. slidge/command/chat_command.py +288 -0
  9. slidge/command/register.py +179 -0
  10. slidge/command/user.py +250 -0
  11. slidge/contact/__init__.py +8 -0
  12. slidge/contact/contact.py +452 -0
  13. slidge/contact/roster.py +192 -0
  14. slidge/core/__init__.py +3 -0
  15. slidge/core/cache.py +183 -0
  16. slidge/core/config.py +209 -0
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +892 -0
  19. slidge/core/gateway/caps.py +63 -0
  20. slidge/core/gateway/delivery_receipt.py +52 -0
  21. slidge/core/gateway/disco.py +80 -0
  22. slidge/core/gateway/mam.py +75 -0
  23. slidge/core/gateway/muc_admin.py +35 -0
  24. slidge/core/gateway/ping.py +66 -0
  25. slidge/core/gateway/presence.py +95 -0
  26. slidge/core/gateway/registration.py +53 -0
  27. slidge/core/gateway/search.py +102 -0
  28. slidge/core/gateway/session_dispatcher.py +757 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +19 -0
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +31 -0
  34. slidge/core/mixins/disco.py +130 -0
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +398 -0
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +217 -0
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +525 -0
  41. slidge/core/session.py +752 -0
  42. slidge/group/__init__.py +10 -0
  43. slidge/group/archive.py +125 -0
  44. slidge/group/bookmarks.py +163 -0
  45. slidge/group/participant.py +440 -0
  46. slidge/group/room.py +1095 -0
  47. slidge/migration.py +18 -0
  48. slidge/py.typed +0 -0
  49. slidge/slixfix/__init__.py +68 -0
  50. slidge/slixfix/link_preview/__init__.py +10 -0
  51. slidge/slixfix/link_preview/link_preview.py +17 -0
  52. slidge/slixfix/link_preview/stanza.py +99 -0
  53. slidge/slixfix/roster.py +60 -0
  54. slidge/slixfix/xep_0077/__init__.py +10 -0
  55. slidge/slixfix/xep_0077/register.py +289 -0
  56. slidge/slixfix/xep_0077/stanza.py +104 -0
  57. slidge/slixfix/xep_0100/__init__.py +5 -0
  58. slidge/slixfix/xep_0100/gateway.py +121 -0
  59. slidge/slixfix/xep_0100/stanza.py +9 -0
  60. slidge/slixfix/xep_0153/__init__.py +10 -0
  61. slidge/slixfix/xep_0153/stanza.py +25 -0
  62. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  63. slidge/slixfix/xep_0264/__init__.py +5 -0
  64. slidge/slixfix/xep_0264/stanza.py +36 -0
  65. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  66. slidge/slixfix/xep_0292/__init__.py +5 -0
  67. slidge/slixfix/xep_0292/vcard4.py +100 -0
  68. slidge/slixfix/xep_0313/__init__.py +12 -0
  69. slidge/slixfix/xep_0313/mam.py +262 -0
  70. slidge/slixfix/xep_0313/stanza.py +359 -0
  71. slidge/slixfix/xep_0317/__init__.py +5 -0
  72. slidge/slixfix/xep_0317/hats.py +17 -0
  73. slidge/slixfix/xep_0317/stanza.py +28 -0
  74. slidge/slixfix/xep_0356_old/__init__.py +7 -0
  75. slidge/slixfix/xep_0356_old/privilege.py +167 -0
  76. slidge/slixfix/xep_0356_old/stanza.py +44 -0
  77. slidge/slixfix/xep_0424/__init__.py +9 -0
  78. slidge/slixfix/xep_0424/retraction.py +77 -0
  79. slidge/slixfix/xep_0424/stanza.py +28 -0
  80. slidge/slixfix/xep_0490/__init__.py +8 -0
  81. slidge/slixfix/xep_0490/mds.py +47 -0
  82. slidge/slixfix/xep_0490/stanza.py +17 -0
  83. slidge/util/__init__.py +15 -0
  84. slidge/util/archive_msg.py +61 -0
  85. slidge/util/conf.py +206 -0
  86. slidge/util/db.py +229 -0
  87. slidge/util/schema.sql +126 -0
  88. slidge/util/sql.py +508 -0
  89. slidge/util/test.py +295 -0
  90. slidge/util/types.py +180 -0
  91. slidge/util/util.py +295 -0
  92. slidge-0.1.0.dist-info/LICENSE +661 -0
  93. slidge-0.1.0.dist-info/METADATA +109 -0
  94. slidge-0.1.0.dist-info/RECORD +96 -0
  95. slidge-0.1.0.dist-info/WHEEL +4 -0
  96. slidge-0.1.0.dist-info/entry_points.txt +3 -0
slidge/core/pubsub.py ADDED
@@ -0,0 +1,525 @@
1
+ import hashlib
2
+ import io
3
+ import logging
4
+ from copy import copy
5
+ from pathlib import Path
6
+ from typing import TYPE_CHECKING, Optional, Type, Union
7
+
8
+ from PIL import Image, UnidentifiedImageError
9
+ from PIL.Image import Image as PILImage
10
+ from slixmpp import (
11
+ JID,
12
+ CoroutineCallback,
13
+ Iq,
14
+ Presence,
15
+ StanzaPath,
16
+ register_stanza_plugin,
17
+ )
18
+ from slixmpp.exceptions import XMPPError
19
+ from slixmpp.plugins.base import BasePlugin, register_plugin
20
+ from slixmpp.plugins.xep_0060.stanza import Event, EventItem, EventItems, Item
21
+ from slixmpp.plugins.xep_0084 import Data as AvatarData
22
+ from slixmpp.plugins.xep_0084 import MetaData as AvatarMetadata
23
+ from slixmpp.plugins.xep_0172 import UserNick
24
+ from slixmpp.plugins.xep_0292.stanza import VCard4
25
+ from slixmpp.types import JidStr, OptJidStr
26
+
27
+ from ..contact.contact import LegacyContact
28
+ from ..contact.roster import ContactIsUser
29
+ from ..util.db import GatewayUser, user_store
30
+ from ..util.sql import db
31
+ from ..util.types import AvatarType, LegacyFileIdType, PepItemType
32
+ from .cache import CachedAvatar, avatar_cache
33
+ from .mixins.lock import NamedLockMixin
34
+
35
+ if TYPE_CHECKING:
36
+ from slidge import BaseGateway
37
+
38
+ VCARD4_NAMESPACE = "urn:xmpp:vcard4"
39
+
40
+
41
+ class PepItem:
42
+ @staticmethod
43
+ def from_db(jid: JID, user: Optional[GatewayUser] = None) -> Optional["PepItem"]:
44
+ raise NotImplementedError
45
+
46
+ def to_db(self, jid: JID, user: Optional[GatewayUser] = None):
47
+ raise NotImplementedError
48
+
49
+
50
+ class PepAvatar(PepItem):
51
+ def __init__(self, jid: JID):
52
+ self.metadata: Optional[AvatarMetadata] = None
53
+ self.id: Optional[str] = None
54
+ self.jid = jid
55
+ self._avatar_data_path: Optional[Path] = None
56
+
57
+ @property
58
+ def data(self) -> Optional[AvatarData]:
59
+ if self._avatar_data_path is None:
60
+ return None
61
+ data = AvatarData()
62
+ data.set_value(self._avatar_data_path.read_bytes())
63
+ return data
64
+
65
+ @staticmethod
66
+ def _sha(b: bytes):
67
+ return hashlib.sha1(b).hexdigest()
68
+
69
+ def _get_weak_unique_id(self, avatar: AvatarType):
70
+ if isinstance(avatar, str):
71
+ return avatar # url
72
+ elif isinstance(avatar, bytes):
73
+ return self._sha(avatar)
74
+ elif isinstance(avatar, Path):
75
+ return self._sha(avatar.read_bytes())
76
+
77
+ @staticmethod
78
+ async def _get_image(avatar: AvatarType) -> PILImage:
79
+ if isinstance(avatar, str):
80
+ # async with aiohttp.ClientSession() as session:
81
+ async with avatar_cache.http.get(avatar) as response:
82
+ return Image.open(io.BytesIO(await response.read()))
83
+ elif isinstance(avatar, bytes):
84
+ return Image.open(io.BytesIO(avatar))
85
+ elif isinstance(avatar, Path):
86
+ return Image.open(avatar)
87
+ else:
88
+ raise TypeError("Avatar must be bytes, a Path or a str (URL)", avatar)
89
+
90
+ async def set_avatar(
91
+ self, avatar: AvatarType, unique_id: Optional[LegacyFileIdType] = None
92
+ ):
93
+ if unique_id is None:
94
+ if isinstance(avatar, str):
95
+ await self._set_avatar_from_url_alone(avatar)
96
+ return
97
+ unique_id = self._get_weak_unique_id(avatar)
98
+
99
+ await self._set_avatar_from_unique_id(avatar, unique_id)
100
+
101
+ async def _set_avatar_from_unique_id(
102
+ self, avatar: AvatarType, unique_id: LegacyFileIdType
103
+ ):
104
+ cached_avatar = avatar_cache.get(unique_id)
105
+ if cached_avatar:
106
+ # this shouldn't be necessary but here to re-use avatars downloaded
107
+ # before the change introducing the JID to unique ID mapping
108
+ avatar_cache.store_jid(self.jid, unique_id)
109
+ else:
110
+ img = await self._get_image(avatar)
111
+ cached_avatar = await avatar_cache.convert_and_store(
112
+ img, unique_id, self.jid
113
+ )
114
+
115
+ self._set_avatar_from_cache(cached_avatar)
116
+
117
+ async def _set_avatar_from_url_alone(self, url: str):
118
+ cached_avatar = await avatar_cache.get_avatar_from_url_alone(url, self.jid)
119
+ self._set_avatar_from_cache(cached_avatar)
120
+
121
+ def _set_avatar_from_cache(self, cached_avatar: CachedAvatar):
122
+ metadata = AvatarMetadata()
123
+ self.id = cached_avatar.hash
124
+ metadata.add_info(
125
+ id=cached_avatar.hash,
126
+ itype="image/png",
127
+ ibytes=cached_avatar.path.stat().st_size,
128
+ height=str(cached_avatar.height),
129
+ width=str(cached_avatar.width),
130
+ )
131
+ self.metadata = metadata
132
+ self._avatar_data_path = cached_avatar.path
133
+
134
+ @staticmethod
135
+ def from_db(jid: JID, user: Optional[GatewayUser] = None) -> Optional["PepAvatar"]:
136
+ cached_id = db.avatar_get(jid)
137
+ if cached_id is None:
138
+ return None
139
+ item = PepAvatar(jid)
140
+ cached_avatar = avatar_cache.get(cached_id)
141
+ if cached_avatar is None:
142
+ raise XMPPError("internal-server-error")
143
+ item._set_avatar_from_cache(cached_avatar)
144
+ return item
145
+
146
+ def to_db(self, jid: JID, user=None):
147
+ cached_id = avatar_cache.get_cached_id_for(jid)
148
+ if cached_id is None:
149
+ log.warning("Could not store avatar for %s", jid)
150
+ return
151
+ db.avatar_store(jid, cached_id)
152
+
153
+ @staticmethod
154
+ def remove_from_db(jid: JID):
155
+ db.avatar_delete(jid)
156
+
157
+
158
+ class PepNick(PepItem):
159
+ def __init__(self, nick: Optional[str] = None):
160
+ nickname = UserNick()
161
+ if nick is not None:
162
+ nickname["nick"] = nick
163
+ self.nick = nickname
164
+ self.__nick_str = nick
165
+
166
+ @staticmethod
167
+ def from_db(jid: JID, user: Optional[GatewayUser] = None) -> Optional["PepNick"]:
168
+ if user is None:
169
+ raise XMPPError("not-allowed")
170
+ nick = db.nick_get(jid, user)
171
+ if nick is None:
172
+ return None
173
+ return PepNick(nick)
174
+
175
+ def to_db(self, jid: JID, user: Optional[GatewayUser] = None):
176
+ if user is None:
177
+ raise XMPPError("not-allowed")
178
+ db.nick_store(jid, str(self.__nick_str), user)
179
+
180
+
181
+ class PubSubComponent(NamedLockMixin, BasePlugin):
182
+ xmpp: "BaseGateway"
183
+
184
+ name = "pubsub"
185
+ description = "Pubsub component"
186
+ dependencies = {
187
+ "xep_0030",
188
+ "xep_0060",
189
+ # "xep_0084",
190
+ "xep_0115",
191
+ "xep_0163",
192
+ }
193
+ default_config = {"component_name": None}
194
+ component_name: str
195
+
196
+ def __init__(self, *a, **kw):
197
+ super(PubSubComponent, self).__init__(*a, **kw)
198
+ register_stanza_plugin(EventItem, UserNick)
199
+
200
+ def plugin_init(self):
201
+ self.xmpp.register_handler(
202
+ CoroutineCallback(
203
+ "pubsub_get_avatar_data",
204
+ StanzaPath(f"iq@type=get/pubsub/items@node={AvatarData.namespace}"),
205
+ self._get_avatar_data, # type:ignore
206
+ )
207
+ )
208
+ self.xmpp.register_handler(
209
+ CoroutineCallback(
210
+ "pubsub_get_avatar_metadata",
211
+ StanzaPath(f"iq@type=get/pubsub/items@node={AvatarMetadata.namespace}"),
212
+ self._get_avatar_metadata, # type:ignore
213
+ )
214
+ )
215
+ self.xmpp.register_handler(
216
+ CoroutineCallback(
217
+ "pubsub_get_vcard",
218
+ StanzaPath(f"iq@type=get/pubsub/items@node={VCARD4_NAMESPACE}"),
219
+ self._get_vcard, # type:ignore
220
+ )
221
+ )
222
+ self.xmpp.add_event_handler("presence_available", self._on_presence_available)
223
+
224
+ disco = self.xmpp.plugin["xep_0030"]
225
+ disco.add_identity("pubsub", "pep", self.component_name)
226
+ disco.add_identity("account", "registered", self.component_name)
227
+ disco.add_feature("http://jabber.org/protocol/pubsub#event")
228
+ disco.add_feature("http://jabber.org/protocol/pubsub#retrieve-items")
229
+ disco.add_feature("http://jabber.org/protocol/pubsub#persistent-items")
230
+
231
+ async def _on_presence_available(self, p: Presence):
232
+ if p.get_plugin("muc_join", check=True) is not None:
233
+ log.debug("Ignoring MUC presence here")
234
+ return
235
+
236
+ from_ = p.get_from()
237
+ ver_string = p["caps"]["ver"]
238
+ info = None
239
+
240
+ to = p.get_to()
241
+
242
+ # we don't want to push anything for contacts that are not in the user's roster
243
+ if to != self.xmpp.boundjid.bare:
244
+ session = self.xmpp.get_session_from_stanza(p)
245
+
246
+ if session is None:
247
+ return
248
+
249
+ await session.contacts.ready
250
+ try:
251
+ contact = await session.contacts.by_jid(to)
252
+ except XMPPError as e:
253
+ log.debug(
254
+ "Could not determine if %s was added to the roster: %s", to, e
255
+ )
256
+ return
257
+ except Exception as e:
258
+ log.warning("Could not determine if %s was added to the roster.", to)
259
+ log.exception(e)
260
+ return
261
+ if not contact.is_friend:
262
+ return
263
+
264
+ if ver_string:
265
+ info = await self.xmpp.plugin["xep_0115"].get_caps(from_)
266
+ if info is None:
267
+ async with self.lock(from_):
268
+ iq = await self.xmpp.plugin["xep_0030"].get_info(from_)
269
+ info = iq["disco_info"]
270
+ features = info["features"]
271
+ if AvatarMetadata.namespace + "+notify" in features:
272
+ try:
273
+ pep_avatar = await self._get_authorized_avatar(p)
274
+ except XMPPError:
275
+ pass
276
+ else:
277
+ if pep_avatar.metadata is None:
278
+ raise XMPPError("internal-server-error", "Avatar but no metadata?")
279
+ await self._broadcast(
280
+ data=pep_avatar.metadata,
281
+ from_=p.get_to(),
282
+ to=from_,
283
+ id=pep_avatar.metadata["info"]["id"],
284
+ )
285
+ if UserNick.namespace + "+notify" in features:
286
+ try:
287
+ pep_nick = await self._get_authorized_nick(p)
288
+ except XMPPError:
289
+ pass
290
+ else:
291
+ await self._broadcast(data=pep_nick.nick, from_=p.get_to(), to=from_)
292
+
293
+ if VCARD4_NAMESPACE + "+notify" in features:
294
+ await self.broadcast_vcard_event(p.get_to(), to=from_)
295
+
296
+ async def broadcast_vcard_event(self, from_, to):
297
+ item = Item()
298
+ item.namespace = VCARD4_NAMESPACE
299
+ item["id"] = "current"
300
+ vcard: VCard4 = await self.xmpp["xep_0292_provider"].get_vcard(from_, to)
301
+ # The vcard content should NOT be in this event according to the spec:
302
+ # https://xmpp.org/extensions/xep-0292.html#sect-idm45669698174224
303
+ # but movim expects it to be here, and I guess
304
+
305
+ log.debug("Broadcast vcard4 event: %s", vcard)
306
+ await self._broadcast(
307
+ data=vcard,
308
+ from_=JID(from_).bare,
309
+ to=to,
310
+ id="current",
311
+ node=VCARD4_NAMESPACE,
312
+ )
313
+
314
+ async def _get_authorized_item(
315
+ self, cls: Type[PepItemType], stanza: Union[Iq, Presence]
316
+ ) -> PepItemType:
317
+ sto = stanza.get_to()
318
+ user = user_store.get_by_jid(stanza.get_from())
319
+ item = cls.from_db(sto, user)
320
+ if item is None:
321
+ raise XMPPError("item-not-found")
322
+
323
+ if sto != self.xmpp.boundjid.bare:
324
+ session = self.xmpp.get_session_from_stanza(stanza)
325
+ await session.contacts.by_jid(sto)
326
+
327
+ return item # type:ignore
328
+
329
+ async def _get_authorized_avatar(self, stanza: Union[Iq, Presence]) -> PepAvatar:
330
+ return await self._get_authorized_item(PepAvatar, stanza)
331
+
332
+ async def _get_authorized_nick(self, stanza: Union[Iq, Presence]) -> PepNick:
333
+ return await self._get_authorized_item(PepNick, stanza)
334
+
335
+ async def _get_avatar_data(self, iq: Iq):
336
+ pep_avatar = await self._get_authorized_avatar(iq)
337
+
338
+ requested_items = iq["pubsub"]["items"]
339
+ if len(requested_items) == 0:
340
+ self._reply_with_payload(iq, pep_avatar.data, pep_avatar.id)
341
+ else:
342
+ for item in requested_items:
343
+ if item["id"] == pep_avatar.id:
344
+ self._reply_with_payload(iq, pep_avatar.data, pep_avatar.id)
345
+ return
346
+ else:
347
+ raise XMPPError("item-not-found")
348
+
349
+ async def _get_avatar_metadata(self, iq: Iq):
350
+ pep_avatar = await self._get_authorized_avatar(iq)
351
+
352
+ requested_items = iq["pubsub"]["items"]
353
+ if len(requested_items) == 0:
354
+ self._reply_with_payload(iq, pep_avatar.metadata, pep_avatar.id)
355
+ else:
356
+ for item in requested_items:
357
+ if item["id"] == pep_avatar.id:
358
+ self._reply_with_payload(iq, pep_avatar.metadata, pep_avatar.id)
359
+ return
360
+ else:
361
+ raise XMPPError("item-not-found")
362
+
363
+ async def _get_vcard(self, iq: Iq):
364
+ # this is not the proper way that clients should retrieve VCards, but
365
+ # gajim does it this way.
366
+ # https://xmpp.org/extensions/xep-0292.html#sect-idm45669698174224
367
+ vcard: VCard4 = await self.xmpp["xep_0292_provider"].get_vcard(
368
+ iq.get_to().bare, iq.get_from().bare
369
+ )
370
+ log.debug("VCARD: %s -- %s -- %s", iq.get_to().bare, iq.get_from().bare, vcard)
371
+ if vcard is None:
372
+ raise XMPPError("item-not-found")
373
+ self._reply_with_payload(iq, vcard, "current", VCARD4_NAMESPACE)
374
+
375
+ @staticmethod
376
+ def get_avatar(jid: JID):
377
+ return PepAvatar.from_db(jid)
378
+
379
+ @staticmethod
380
+ def _reply_with_payload(
381
+ iq: Iq,
382
+ payload: Optional[Union[AvatarMetadata, AvatarData, VCard4]],
383
+ id_: Optional[str],
384
+ namespace: Optional[str] = None,
385
+ ):
386
+ result = iq.reply()
387
+ item = Item()
388
+ if payload:
389
+ item.set_payload(payload.xml)
390
+ item["id"] = id_
391
+ result["pubsub"]["items"]["node"] = (
392
+ namespace if namespace else payload.namespace
393
+ )
394
+ result["pubsub"]["items"].append(item)
395
+ result.send()
396
+
397
+ async def _broadcast(self, data, from_: JidStr, to: OptJidStr = None, **kwargs):
398
+ from_ = JID(from_)
399
+ if from_ != self.xmpp.boundjid.bare and to is not None:
400
+ to = JID(to)
401
+ session = self.xmpp.get_session_from_jid(to)
402
+ if session is None:
403
+ return
404
+ await session.ready
405
+ try:
406
+ entity = await session.get_contact_or_group_or_participant(from_)
407
+ except ContactIsUser:
408
+ return
409
+ if isinstance(entity, LegacyContact) and not entity.is_friend:
410
+ return
411
+
412
+ item = EventItem()
413
+ if data:
414
+ item.set_payload(data.xml)
415
+ for k, v in kwargs.items():
416
+ item[k] = v
417
+
418
+ items = EventItems()
419
+ items.append(item)
420
+ items["node"] = kwargs.get("node") or data.namespace
421
+
422
+ event = Event()
423
+ event.append(items)
424
+
425
+ msg = self.xmpp.Message()
426
+ msg.set_type("headline")
427
+ msg.set_from(from_)
428
+ msg.append(event)
429
+
430
+ if to is None:
431
+ for u in user_store.get_all():
432
+ new_msg = copy(msg)
433
+ new_msg.set_to(u.bare_jid)
434
+ new_msg.send()
435
+ else:
436
+ msg.set_to(to)
437
+ msg.send()
438
+
439
+ async def set_avatar(
440
+ self,
441
+ jid: JidStr,
442
+ avatar: Optional[AvatarType] = None,
443
+ broadcast_to: OptJidStr = None,
444
+ unique_id=None,
445
+ broadcast=True,
446
+ ):
447
+ jid = JID(jid)
448
+ if avatar is None:
449
+ PepAvatar.remove_from_db(jid)
450
+ await self._broadcast(AvatarMetadata(), jid, broadcast_to)
451
+ avatar_cache.delete_jid(jid)
452
+ else:
453
+ pep_avatar = PepAvatar(jid)
454
+ try:
455
+ await pep_avatar.set_avatar(avatar, unique_id)
456
+ except (UnidentifiedImageError, FileNotFoundError) as e:
457
+ log.warning("Failed to set avatar for %s: %r", self, e)
458
+ return
459
+ pep_avatar.to_db(jid)
460
+ if pep_avatar.metadata is None:
461
+ raise RuntimeError
462
+ if not broadcast:
463
+ return
464
+ await self._broadcast(
465
+ pep_avatar.metadata,
466
+ jid,
467
+ broadcast_to,
468
+ id=pep_avatar.metadata["info"]["id"],
469
+ )
470
+
471
+ async def set_avatar_from_cache(
472
+ self, jid: JID, send_empty: bool, broadcast_to: OptJidStr = None, broadcast=True
473
+ ):
474
+ uid = avatar_cache.get_cached_id_for(jid)
475
+ if uid is None:
476
+ if not send_empty:
477
+ return
478
+ self.xmpp.loop.create_task(
479
+ self.set_avatar(jid, None, broadcast_to, uid, broadcast)
480
+ )
481
+ return
482
+ cached_avatar = avatar_cache.get(str(uid))
483
+ if cached_avatar is None:
484
+ # should not happen but well…
485
+ log.warning(
486
+ "Something is wrong with the avatar, %s won't have an "
487
+ "avatar because avatar not found in cache",
488
+ jid,
489
+ )
490
+ return
491
+ self.xmpp.loop.create_task(
492
+ self.set_avatar(jid, cached_avatar.path, broadcast_to, uid, broadcast)
493
+ )
494
+
495
+ def set_nick(
496
+ self,
497
+ user: GatewayUser,
498
+ jid: JidStr,
499
+ nick: Optional[str] = None,
500
+ ):
501
+ jid = JID(jid)
502
+ nickname = PepNick(nick)
503
+ nickname.to_db(jid, user)
504
+ log.debug("New nickname: %s", nickname.nick)
505
+ self.xmpp.loop.create_task(self._broadcast(nickname.nick, jid, user.bare_jid))
506
+
507
+ async def broadcast_all(self, from_: JID, to: JID):
508
+ """
509
+ Force push avatar and nick for a stored JID.
510
+ """
511
+ a = PepAvatar.from_db(from_)
512
+ if a:
513
+ if a.metadata:
514
+ await self._broadcast(
515
+ a.metadata, from_, to, id=a.metadata["info"]["id"]
516
+ )
517
+ else:
518
+ log.warning("No metadata associated to this cached avatar?!")
519
+ n = PepNick.from_db(from_, user_store.get_by_jid(to))
520
+ if n:
521
+ await self._broadcast(n.nick, from_, to)
522
+
523
+
524
+ log = logging.getLogger(__name__)
525
+ register_plugin(PubSubComponent)