slidge 0.1.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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)