slidge 0.2.0a0__py3-none-any.whl → 0.2.0a1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. slidge/__version__.py +1 -1
  2. slidge/command/admin.py +1 -1
  3. slidge/command/user.py +0 -1
  4. slidge/contact/contact.py +86 -32
  5. slidge/contact/roster.py +79 -19
  6. slidge/core/config.py +1 -4
  7. slidge/core/gateway/base.py +9 -2
  8. slidge/core/gateway/caps.py +7 -5
  9. slidge/core/gateway/muc_admin.py +1 -1
  10. slidge/core/gateway/session_dispatcher.py +20 -6
  11. slidge/core/gateway/vcard_temp.py +1 -1
  12. slidge/core/mixins/attachment.py +17 -4
  13. slidge/core/mixins/avatar.py +26 -9
  14. slidge/core/mixins/db.py +18 -0
  15. slidge/core/mixins/disco.py +0 -10
  16. slidge/core/mixins/message.py +7 -1
  17. slidge/core/mixins/presence.py +6 -3
  18. slidge/core/pubsub.py +7 -15
  19. slidge/core/session.py +6 -3
  20. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
  21. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
  22. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
  23. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +11 -2
  24. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
  25. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
  26. slidge/db/avatar.py +20 -9
  27. slidge/db/models.py +16 -6
  28. slidge/db/store.py +217 -115
  29. slidge/group/archive.py +46 -1
  30. slidge/group/bookmarks.py +17 -5
  31. slidge/group/participant.py +10 -3
  32. slidge/group/room.py +183 -125
  33. slidge/main.py +3 -3
  34. slidge/slixfix/xep_0292/vcard4.py +2 -0
  35. slidge/util/archive_msg.py +2 -1
  36. slidge/util/test.py +54 -4
  37. slidge/util/types.py +5 -0
  38. slidge/util/util.py +22 -0
  39. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/METADATA +2 -4
  40. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/RECORD +43 -37
  41. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
  42. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
  43. {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
slidge/group/room.py CHANGED
@@ -5,7 +5,7 @@ import string
5
5
  import warnings
6
6
  from copy import copy
7
7
  from datetime import datetime, timedelta, timezone
8
- from typing import TYPE_CHECKING, Generic, Optional, Self, Union
8
+ from typing import TYPE_CHECKING, AsyncIterator, Generic, Optional, Self, Union
9
9
  from uuid import uuid4
10
10
 
11
11
  from slixmpp import JID, Iq, Message, Presence
@@ -21,12 +21,14 @@ from ..contact.roster import ContactIsUser
21
21
  from ..core import config
22
22
  from ..core.mixins import StoredAttributeMixin
23
23
  from ..core.mixins.avatar import AvatarMixin
24
+ from ..core.mixins.db import UpdateInfoMixin
24
25
  from ..core.mixins.disco import ChatterDiscoMixin
25
26
  from ..core.mixins.lock import NamedLockMixin
26
27
  from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
27
28
  from ..db.models import Room
28
29
  from ..util import ABCSubclassableOnceAtMost
29
30
  from ..util.types import (
31
+ HoleBound,
30
32
  LegacyGroupIdType,
31
33
  LegacyMessageType,
32
34
  LegacyParticipantType,
@@ -35,7 +37,7 @@ from ..util.types import (
35
37
  MucAffiliation,
36
38
  MucType,
37
39
  )
38
- from ..util.util import deprecated
40
+ from ..util.util import deprecated, timeit, with_session
39
41
  from .archive import MessageArchive
40
42
  from .participant import LegacyParticipant
41
43
 
@@ -45,11 +47,14 @@ if TYPE_CHECKING:
45
47
 
46
48
  ADMIN_NS = "http://jabber.org/protocol/muc#admin"
47
49
 
50
+ SubjectSetterType = Union[str, None, "LegacyContact", "LegacyParticipant"]
51
+
48
52
 
49
53
  class LegacyMUC(
50
54
  Generic[
51
55
  LegacyGroupIdType, LegacyMessageType, LegacyParticipantType, LegacyUserIdType
52
56
  ],
57
+ UpdateInfoMixin,
53
58
  StoredAttributeMixin,
54
59
  AvatarMixin,
55
60
  NamedLockMixin,
@@ -120,7 +125,6 @@ class LegacyMUC(
120
125
  tries to set the room subject.
121
126
  """
122
127
 
123
- _avatar_pubsub_broadcast = False
124
128
  _avatar_bare_jid = True
125
129
  archive: MessageArchive
126
130
 
@@ -137,15 +141,13 @@ class LegacyMUC(
137
141
  self.Participant = LegacyParticipant.get_self_or_unique_subclass()
138
142
 
139
143
  self._subject = ""
140
- self._subject_setter: Union[str, None, "LegacyContact", "LegacyParticipant"] = (
141
- None
142
- )
144
+ self._subject_setter: Optional[str] = None
143
145
 
144
146
  self.pk: Optional[int] = None
145
147
  self._user_nick: Optional[str] = None
146
148
 
147
149
  self._participants_filled = False
148
- self.__history_filled = False
150
+ self._history_filled = False
149
151
  self._description = ""
150
152
  self._subject_date: Optional[datetime] = None
151
153
 
@@ -165,6 +167,8 @@ class LegacyMUC(
165
167
  if self._n_participants == n_participants:
166
168
  return
167
169
  self._n_participants = n_participants
170
+ if self._updating_info:
171
+ return
168
172
  assert self.pk is not None
169
173
  self.__store.update_n_participants(self.pk, n_participants)
170
174
 
@@ -182,6 +186,8 @@ class LegacyMUC(
182
186
  @subject_date.setter
183
187
  def subject_date(self, when: Optional[datetime]) -> None:
184
188
  self._subject_date = when
189
+ if self._updating_info:
190
+ return
185
191
  assert self.pk is not None
186
192
  self.__store.update_subject_date(self.pk, when)
187
193
 
@@ -211,39 +217,66 @@ class LegacyMUC(
211
217
  self.__store.set_resource(self.pk, self._user_resources)
212
218
 
213
219
  async def __fill_participants(self):
220
+ if self._participants_filled:
221
+ return
222
+ assert self.pk is not None
214
223
  async with self.lock("fill participants"):
215
- if self._participants_filled:
216
- return
217
224
  self._participants_filled = True
218
- try:
219
- await self.fill_participants()
220
- except NotImplementedError:
221
- pass
225
+ async for p in self.fill_participants():
226
+ self.__participants_store.update(p)
227
+ self.__store.set_participants_filled(self.pk)
228
+
229
+ async def get_participants(self) -> AsyncIterator[LegacyParticipant]:
230
+ assert self.pk is not None
231
+ if self._participants_filled:
232
+ for db_participant in self.xmpp.store.participants.get_all(
233
+ self.pk, user_included=True
234
+ ):
235
+ participant = self.Participant.from_store(self.session, db_participant)
236
+ yield participant
237
+ return
238
+
239
+ async with self.lock("fill participants"):
240
+ self._participants_filled = True
241
+ # We only fill the participants list if/when the MUC is first
242
+ # joined by an XMPP client. But we may have instantiated
243
+ resources = set[str]()
244
+ for db_participant in self.xmpp.store.participants.get_all(
245
+ self.pk, user_included=True
246
+ ):
247
+ participant = self.Participant.from_store(self.session, db_participant)
248
+ resources.add(participant.jid.resource)
249
+ yield participant
250
+ async for p in self.fill_participants():
251
+ if p.jid.resource not in resources:
252
+ yield p
253
+ self.__store.set_participants_filled(self.pk)
254
+ return
222
255
 
223
256
  async def __fill_history(self):
224
257
  async with self.lock("fill history"):
225
- if self.__history_filled:
258
+ if self._history_filled:
226
259
  log.debug("History has already been fetched %s", self)
227
260
  return
228
261
  log.debug("Fetching history for %s", self)
229
- for msg in self.archive:
230
- try:
231
- legacy_id = self.session.xmpp_to_legacy_msg_id(msg.id)
232
- oldest_date = msg.when
233
- except Exception as e:
234
- # not all archived stanzas have a valid legacy msg ID, eg
235
- # reactions, corrections, message with multiple attachments…
236
- self.log.debug(f"Could not convert during history back-filling {e}")
237
- else:
238
- break
239
- else:
240
- legacy_id = None
241
- oldest_date = None
242
262
  try:
243
- await self.backfill(legacy_id, oldest_date)
263
+ before, after = self.archive.get_hole_bounds()
264
+ if before is not None:
265
+ before = before._replace(
266
+ id=self.xmpp.LEGACY_MSG_ID_TYPE(before.id) # type:ignore
267
+ )
268
+ if after is not None:
269
+ after = after._replace(
270
+ id=self.xmpp.LEGACY_MSG_ID_TYPE(after.id) # type:ignore
271
+ )
272
+ await self.backfill(before, after)
244
273
  except NotImplementedError:
245
274
  return
246
- self.__history_filled = True
275
+ except Exception as e:
276
+ log.exception("Could not backfill: %s", e)
277
+ assert self.pk is not None
278
+ self.__store.set_history_filled(self.pk, True)
279
+ self._history_filled = True
247
280
 
248
281
  @property
249
282
  def name(self):
@@ -255,6 +288,10 @@ class LegacyMUC(
255
288
  return
256
289
  self.DISCO_NAME = n
257
290
  self.__send_configuration_change((104,))
291
+ if self._updating_info:
292
+ return
293
+ assert self.pk is not None
294
+ self.__store.update_name(self.pk, n)
258
295
 
259
296
  @property
260
297
  def description(self):
@@ -265,9 +302,11 @@ class LegacyMUC(
265
302
  if self._description == d:
266
303
  return
267
304
  self._description = d
305
+ self.__send_configuration_change((104,))
306
+ if self._updating_info:
307
+ return
268
308
  assert self.pk is not None
269
309
  self.__store.update_description(self.pk, d)
270
- self.__send_configuration_change((104,))
271
310
 
272
311
  def on_presence_unavailable(self, p: Presence):
273
312
  pto = p.get_to()
@@ -307,27 +346,35 @@ class LegacyMUC(
307
346
 
308
347
  async def backfill(
309
348
  self,
310
- oldest_message_id: Optional[LegacyMessageType] = None,
311
- oldest_message_date: Optional[datetime] = None,
349
+ after: Optional[HoleBound] = None,
350
+ before: Optional[HoleBound] = None,
312
351
  ):
313
352
  """
314
- Override this if the legacy network provide server-side archive.
315
- In it, send history messages using ``self.get_participant().send*``,
316
- with the ``archive_only=True`` kwarg.
317
-
318
- You only need to fetch messages older than ``oldest_message_id``.
319
-
320
- :param oldest_message_id: The oldest message ID already present in the archive
321
- :param oldest_message_date: The oldest message date already present in the archive
353
+ Override this if the legacy network provide server-side group archives.
354
+
355
+ In it, send history messages using ``self.get_participant(xxx).send_xxxx``,
356
+ with the ``archive_only=True`` kwarg. This is only called once per slidge
357
+ run for a given group.
358
+
359
+ :param after: Fetch messages after this one. If ``None``, it's up to you
360
+ to decide how far you want to go in the archive. If it's not ``None``,
361
+ it means slidge has some messages in this archive and you should really try
362
+ to complete it to avoid "holes" in the history of this group.
363
+ :param before: Fetch messages before this one. If ``None``, fetch all messages
364
+ up to the most recent one
322
365
  """
323
366
  raise NotImplementedError
324
367
 
325
- async def fill_participants(self):
368
+ async def fill_participants(self) -> AsyncIterator[LegacyParticipant]:
326
369
  """
327
- In here, call self.get_participant(), self.get_participant_by_contact(),
328
- of self.get_user_participant() to make an initial list of participants.
370
+ This method should yield the list of all members of this group.
371
+
372
+ Typically, use ``participant = self.get_participant()``, self.get_participant_by_contact(),
373
+ of self.get_user_participant(), and update their affiliation, hats, etc.
374
+ before yielding them.
329
375
  """
330
- raise NotImplementedError
376
+ return
377
+ yield
331
378
 
332
379
  @property
333
380
  def subject(self):
@@ -337,14 +384,13 @@ class LegacyMUC(
337
384
  def subject(self, s: str):
338
385
  if s == self._subject:
339
386
  return
340
- self.session.create_task(
341
- self.__get_subject_setter_participant()
342
- ).add_done_callback(
343
- lambda task: task.result().set_room_subject(
344
- s, None, self.subject_date, False
345
- )
387
+ self.__get_subject_setter_participant().set_room_subject(
388
+ s, None, self.subject_date, False
346
389
  )
390
+
347
391
  self._subject = s
392
+ if self._updating_info:
393
+ return
348
394
  assert self.pk is not None
349
395
  self.__store.update_subject(self.pk, s)
350
396
 
@@ -353,25 +399,29 @@ class LegacyMUC(
353
399
  return self.type == MucType.CHANNEL
354
400
 
355
401
  @property
356
- def subject_setter(self):
402
+ def subject_setter(self) -> Optional[str]:
357
403
  return self._subject_setter
358
404
 
359
405
  @subject_setter.setter
360
- def subject_setter(self, subject_setter):
361
- self._subject_setter = subject_setter
362
- self.__store.update(self)
406
+ def subject_setter(self, subject_setter: SubjectSetterType) -> None:
407
+ if isinstance(subject_setter, LegacyContact):
408
+ subject_setter = subject_setter.name
409
+ elif isinstance(subject_setter, LegacyParticipant):
410
+ subject_setter = subject_setter.nickname
363
411
 
364
- async def __get_subject_setter_participant(self):
365
- who = self.subject_setter
412
+ if subject_setter == self._subject_setter:
413
+ return
414
+ assert isinstance(subject_setter, str)
415
+ self._subject_setter = subject_setter
416
+ if self._updating_info:
417
+ return
418
+ assert self.pk is not None
419
+ self.__store.update_subject_setter(self.pk, subject_setter)
366
420
 
367
- if isinstance(who, LegacyParticipant):
368
- return who
369
- elif isinstance(who, str):
370
- return await self.get_participant(who, store=False)
371
- elif isinstance(self.subject_setter, LegacyContact):
372
- return await self.get_participant_by_contact(who)
373
- else:
421
+ def __get_subject_setter_participant(self) -> LegacyParticipant:
422
+ if self._subject_setter is None:
374
423
  return self.get_system_participant()
424
+ return self.Participant(self, self._subject_setter)
375
425
 
376
426
  def features(self):
377
427
  features = [
@@ -410,7 +460,8 @@ class LegacyMUC(
410
460
  form.add_field("muc#roominfo_subjectmod", "boolean", value=False)
411
461
 
412
462
  if self._ALL_INFO_FILLED_ON_STARTUP or self._participants_filled:
413
- n: Optional[int] = len(await self.get_participants())
463
+ assert self.pk is not None
464
+ n: Optional[int] = self.__participants_store.get_count(self.pk)
414
465
  else:
415
466
  n = self._n_participants
416
467
  if n is not None:
@@ -504,12 +555,14 @@ class LegacyMUC(
504
555
  msg.send()
505
556
 
506
557
  def _get_cached_avatar_id(self):
507
- assert self.pk is not None
558
+ if self.pk is None:
559
+ return None
508
560
  return self.xmpp.store.rooms.get_avatar_legacy_id(self.pk)
509
561
 
510
562
  def _post_avatar_update(self) -> None:
511
- if self.pk is None or self._avatar_pk is None:
563
+ if self.pk is None:
512
564
  return
565
+ assert self.pk is not None
513
566
  self.xmpp.store.rooms.set_avatar(self.pk, self._avatar_pk)
514
567
  self.__send_configuration_change((104,))
515
568
  self._send_room_presence()
@@ -527,6 +580,8 @@ class LegacyMUC(
527
580
  p["vcard_temp_update"]["photo"] = ""
528
581
  p.send()
529
582
 
583
+ @timeit
584
+ @with_session
530
585
  async def join(self, join_presence: Presence):
531
586
  user_full_jid = join_presence.get_from()
532
587
  requested_nickname = join_presence.get_to().resource
@@ -548,17 +603,16 @@ class LegacyMUC(
548
603
  requested_nickname,
549
604
  )
550
605
 
551
- await self.__fill_participants()
552
-
553
- assert self.pk is not None
554
- for db_participant in self.xmpp.store.participants.get_all(
555
- self.pk, user_included=False
556
- ):
557
- participant = self.Participant.from_store(self.session, db_participant)
606
+ user_nick = self.user_nick
607
+ user_participant = None
608
+ async for participant in self.get_participants():
609
+ if participant.is_user:
610
+ user_participant = participant
611
+ continue
558
612
  participant.send_initial_presence(full_jid=user_full_jid)
559
613
 
560
- user_nick = self.user_nick
561
- user_participant = await self.get_user_participant()
614
+ if user_participant is None:
615
+ user_participant = await self.get_user_participant()
562
616
  if not user_participant.is_user: # type:ignore
563
617
  self.log.warning("is_user flag not set participant on user_participant")
564
618
  user_participant.is_user = True # type:ignore
@@ -589,7 +643,7 @@ class LegacyMUC(
589
643
  maxstanzas=maxstanzas,
590
644
  since=since,
591
645
  )
592
- (await self.__get_subject_setter_participant()).set_room_subject(
646
+ self.__get_subject_setter_participant().set_room_subject(
593
647
  self._subject if self.HAS_SUBJECT else (self.description or self.name),
594
648
  user_full_jid,
595
649
  self.subject_date,
@@ -643,20 +697,21 @@ class LegacyMUC(
643
697
  construction (optional)
644
698
  :return:
645
699
  """
646
- if fill_first:
647
- await self.__fill_participants()
648
- assert self.pk is not None
649
- with self.xmpp.store.session():
650
- stored = self.__participants_store.get_by_nickname(
651
- self.pk, nickname
652
- ) or self.__participants_store.get_by_resource(self.pk, nickname)
653
- if stored is not None:
654
- return self.Participant.from_store(self.session, stored)
700
+ if fill_first and not self._participants_filled:
701
+ async for _ in self.get_participants():
702
+ pass
703
+ if self.pk is not None:
704
+ with self.xmpp.store.session():
705
+ stored = self.__participants_store.get_by_nickname(
706
+ self.pk, nickname
707
+ ) or self.__participants_store.get_by_resource(self.pk, nickname)
708
+ if stored is not None:
709
+ return self.Participant.from_store(self.session, stored)
655
710
 
656
711
  if raise_if_not_found:
657
712
  raise XMPPError("item-not-found")
658
713
  p = self.Participant(self, nickname, **kwargs)
659
- if store:
714
+ if store and not self._updating_info:
660
715
  self.__store_participant(p)
661
716
  if (
662
717
  not self.get_lock("fill participants")
@@ -694,29 +749,43 @@ class LegacyMUC(
694
749
  :return:
695
750
  """
696
751
  await self.session.contacts.ready
697
- assert self.pk is not None
698
- assert c.contact_pk is not None
699
- with self.__store.session():
700
- stored = self.__participants_store.get_by_contact(self.pk, c.contact_pk)
701
- if stored is not None:
702
- return self.Participant.from_store(
703
- self.session, stored, muc=self, contact=c
704
- )
752
+
753
+ if self.pk is not None:
754
+ assert c.contact_pk is not None
755
+ with self.__store.session():
756
+ stored = self.__participants_store.get_by_contact(self.pk, c.contact_pk)
757
+ if stored is not None:
758
+ return self.Participant.from_store(
759
+ self.session, stored, muc=self, contact=c
760
+ )
705
761
 
706
762
  nickname = c.name or _unescape_node(c.jid_username)
707
- if not self.__store.nickname_is_available(self.pk, nickname):
763
+
764
+ if self.pk is None:
765
+ nick_available = True
766
+ else:
767
+ nick_available = self.__store.nickname_is_available(self.pk, nickname)
768
+
769
+ if not nick_available:
708
770
  self.log.debug("Nickname conflict")
709
771
  nickname = f"{nickname} ({c.jid_username})"
710
772
  p = self.Participant(self, nickname, **kwargs)
711
773
  p.contact = c
712
774
 
775
+ if self._updating_info:
776
+ return p
777
+
778
+ self.__store_participant(p)
713
779
  # FIXME: this is not great but given the current design,
714
780
  # during participants fill and history backfill we do not
715
- # want to send presence, because we might update affiliation
781
+ # want to send presence, because we might :update affiliation
716
782
  # and role afterwards.
717
783
  # We need a refactor of the MUC class… later™
718
- self.__store_participant(p)
719
- if not self.get_lock("fill participants") and not self.get_lock("fill history"):
784
+ if (
785
+ self._participants_filled
786
+ and not self.get_lock("fill participants")
787
+ and not self.get_lock("fill history")
788
+ ):
720
789
  p.send_last_presence(force=True, no_cache_online=True)
721
790
  return p
722
791
 
@@ -729,21 +798,6 @@ class LegacyMUC(
729
798
  return await self.get_user_participant(**kwargs)
730
799
  return await self.get_participant_by_contact(c, **kwargs)
731
800
 
732
- async def get_participants(self, fill_first=True):
733
- """
734
- Get all known participants of the group, ensure :meth:`.LegacyMUC.fill_participants`
735
- has been awaited once before. Plugins should not use that, internal
736
- slidge use only.
737
- :return:
738
- """
739
- if fill_first:
740
- await self.__fill_participants()
741
- assert self.pk is not None
742
- return [
743
- self.Participant.from_store(self.session, s)
744
- for s in self.__participants_store.get_all(self.pk)
745
- ]
746
-
747
801
  def remove_participant(self, p: "LegacyParticipantType", kick=False, ban=False):
748
802
  """
749
803
  Call this when a participant leaves the room
@@ -1029,10 +1083,9 @@ class LegacyMUC(
1029
1083
  ):
1030
1084
  """
1031
1085
  Triggered when the user requests changing the affiliation of a contact
1032
- for this group,
1086
+ for this group.
1033
1087
 
1034
- Examples: promotion them to moderator, kick (affiliation=none),
1035
- ban (affiliation=outcast).
1088
+ Examples: promotion them to moderator, ban (affiliation=outcast).
1036
1089
 
1037
1090
  :param contact: The contact whose affiliation change is requested
1038
1091
  :param affiliation: The new affiliation
@@ -1041,6 +1094,16 @@ class LegacyMUC(
1041
1094
  """
1042
1095
  raise NotImplementedError
1043
1096
 
1097
+ async def on_kick(self, contact: "LegacyContact", reason: Optional[str]):
1098
+ """
1099
+ Triggered when the user requests changing the role of a contact
1100
+ to "none" for this group. Action commonly known as "kick".
1101
+
1102
+ :param contact: Contact to be kicked
1103
+ :param reason: A reason for this kick
1104
+ """
1105
+ raise NotImplementedError
1106
+
1044
1107
  async def on_set_config(
1045
1108
  self,
1046
1109
  name: Optional[str],
@@ -1127,6 +1190,7 @@ class LegacyMUC(
1127
1190
  )
1128
1191
  muc.pk = stored.id
1129
1192
  muc.type = stored.muc_type # type: ignore
1193
+ muc.user_nick = stored.user_nick
1130
1194
  if stored.name:
1131
1195
  muc.DISCO_NAME = stored.name
1132
1196
  if stored.description:
@@ -1137,17 +1201,11 @@ class LegacyMUC(
1137
1201
  if stored.subject_date is not None:
1138
1202
  muc._subject_date = stored.subject_date.replace(tzinfo=timezone.utc)
1139
1203
  muc._participants_filled = stored.participants_filled
1140
- muc.__history_filled = True
1204
+ muc._n_participants = stored.n_participants
1205
+ muc._history_filled = stored.history_filled
1141
1206
  if stored.user_resources is not None:
1142
1207
  muc._user_resources = set(json.loads(stored.user_resources))
1143
- if stored.subject_setter is not None:
1144
- muc.subject_setter = (
1145
- LegacyParticipant.get_self_or_unique_subclass().from_store(
1146
- session,
1147
- stored.subject_setter,
1148
- muc=muc,
1149
- )
1150
- )
1208
+ muc._subject_setter = stored.subject_setter
1151
1209
  muc.archive = MessageArchive(muc.pk, session.xmpp.store.mam)
1152
1210
  muc._set_avatar_from_store(stored)
1153
1211
  return muc
slidge/main.py CHANGED
@@ -17,6 +17,7 @@ import asyncio
17
17
  import importlib
18
18
  import logging
19
19
  import os
20
+ import re
20
21
  import signal
21
22
  from pathlib import Path
22
23
 
@@ -49,7 +50,7 @@ class MainConfig(ConfigModule):
49
50
  args.home_dir = Path("/var/lib/slidge") / str(args.jid)
50
51
 
51
52
  if args.user_jid_validator is None:
52
- args.user_jid_validator = ".*@" + args.server
53
+ args.user_jid_validator = ".*@" + re.escape(args.server)
53
54
 
54
55
  if args.db_url is None:
55
56
  args.db_url = f"sqlite:///{args.home_dir}/slidge.sqlite"
@@ -116,8 +117,6 @@ def configure():
116
117
  db_file = config.HOME_DIR / "slidge.db"
117
118
  user_store.set_file(db_file, args.secret_key)
118
119
 
119
- avatar_cache.set_dir(h / "slidge_avatars_v3")
120
-
121
120
  config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare
122
121
 
123
122
  return unknown_argv
@@ -161,6 +160,7 @@ def main():
161
160
  gateway: BaseGateway = BaseGateway.get_unique_subclass()()
162
161
  avatar_cache.http = gateway.http
163
162
  avatar_cache.store = gateway.store.avatars
163
+ avatar_cache.set_dir(config.HOME_DIR / "slidge_avatars_v3")
164
164
  avatar_cache.legacy_avatar_type = gateway.AVATAR_ID_TYPE
165
165
 
166
166
  PepAvatar.store = gateway.store
@@ -57,6 +57,8 @@ class VCard4Provider(BasePlugin):
57
57
  if not hasattr(self.xmpp, "get_session_from_jid"):
58
58
  return None
59
59
  jid = JID(jid)
60
+ if not jid.local:
61
+ return None
60
62
  requested_by = JID(requested_by)
61
63
  session = self.xmpp.get_session_from_jid(requested_by)
62
64
  if session is None:
@@ -1,6 +1,7 @@
1
1
  from copy import copy
2
2
  from datetime import datetime, timezone
3
3
  from typing import Optional, Union
4
+ from uuid import uuid4
4
5
  from xml.etree import ElementTree as ET
5
6
 
6
7
  from slixmpp import Message
@@ -30,7 +31,7 @@ class HistoryMessage:
30
31
  else:
31
32
  from_db = False
32
33
 
33
- self.id = stanza["stanza_id"]["id"]
34
+ self.id = stanza["stanza_id"]["id"] or uuid4().hex
34
35
  self.when: datetime = (
35
36
  when or stanza["delay"]["stamp"] or datetime.now(tz=timezone.utc)
36
37
  )