slidge 0.1.2__py3-none-any.whl → 0.2.0a0__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 (63) hide show
  1. slidge/__init__.py +3 -5
  2. slidge/__main__.py +2 -196
  3. slidge/__version__.py +5 -0
  4. slidge/command/adhoc.py +8 -1
  5. slidge/command/admin.py +5 -6
  6. slidge/command/base.py +1 -2
  7. slidge/command/register.py +32 -16
  8. slidge/command/user.py +85 -5
  9. slidge/contact/contact.py +93 -31
  10. slidge/contact/roster.py +54 -39
  11. slidge/core/config.py +13 -7
  12. slidge/core/gateway/base.py +139 -34
  13. slidge/core/gateway/disco.py +2 -4
  14. slidge/core/gateway/mam.py +1 -4
  15. slidge/core/gateway/ping.py +2 -3
  16. slidge/core/gateway/presence.py +1 -1
  17. slidge/core/gateway/registration.py +32 -21
  18. slidge/core/gateway/search.py +3 -5
  19. slidge/core/gateway/session_dispatcher.py +109 -51
  20. slidge/core/gateway/vcard_temp.py +6 -4
  21. slidge/core/mixins/__init__.py +11 -1
  22. slidge/core/mixins/attachment.py +15 -10
  23. slidge/core/mixins/avatar.py +66 -18
  24. slidge/core/mixins/base.py +8 -2
  25. slidge/core/mixins/message.py +11 -7
  26. slidge/core/mixins/message_maker.py +17 -9
  27. slidge/core/mixins/presence.py +14 -4
  28. slidge/core/pubsub.py +54 -212
  29. slidge/core/session.py +65 -33
  30. slidge/db/__init__.py +4 -0
  31. slidge/db/alembic/env.py +64 -0
  32. slidge/db/alembic/script.py.mako +26 -0
  33. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  34. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  35. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
  36. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +76 -0
  37. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  38. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  39. slidge/db/avatar.py +224 -0
  40. slidge/db/meta.py +65 -0
  41. slidge/db/models.py +365 -0
  42. slidge/db/store.py +976 -0
  43. slidge/group/archive.py +13 -14
  44. slidge/group/bookmarks.py +59 -56
  45. slidge/group/participant.py +81 -29
  46. slidge/group/room.py +242 -142
  47. slidge/main.py +201 -0
  48. slidge/migration.py +30 -0
  49. slidge/slixfix/__init__.py +35 -2
  50. slidge/slixfix/roster.py +11 -4
  51. slidge/slixfix/xep_0292/vcard4.py +1 -0
  52. slidge/util/db.py +1 -47
  53. slidge/util/test.py +21 -4
  54. slidge/util/types.py +24 -4
  55. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/METADATA +3 -1
  56. slidge-0.2.0a0.dist-info/RECORD +108 -0
  57. slidge/core/cache.py +0 -183
  58. slidge/util/schema.sql +0 -126
  59. slidge/util/sql.py +0 -508
  60. slidge-0.1.2.dist-info/RECORD +0 -96
  61. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/LICENSE +0 -0
  62. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/WHEEL +0 -0
  63. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/entry_points.txt +0 -0
slidge/group/archive.py CHANGED
@@ -1,24 +1,22 @@
1
1
  import logging
2
2
  import uuid
3
3
  from copy import copy
4
- from datetime import datetime
4
+ from datetime import datetime, timezone
5
5
  from typing import TYPE_CHECKING, Collection, Optional
6
6
 
7
7
  from slixmpp import Iq, Message
8
8
 
9
+ from ..db.store import MAMStore
9
10
  from ..util.archive_msg import HistoryMessage
10
- from ..util.db import GatewayUser
11
- from ..util.sql import db
12
11
 
13
12
  if TYPE_CHECKING:
14
13
  from .participant import LegacyParticipant
15
14
 
16
15
 
17
16
  class MessageArchive:
18
- def __init__(self, db_id: str, user: GatewayUser):
19
- self.db_id = db_id
20
- self.user = user
21
- db.mam_add_muc(db_id, user)
17
+ def __init__(self, room_pk: int, store: MAMStore):
18
+ self.room_pk = room_pk
19
+ self.__store = store
22
20
 
23
21
  def add(
24
22
  self,
@@ -40,7 +38,7 @@ class MessageArchive:
40
38
  if participant.contact:
41
39
  new_msg["muc"]["jid"] = participant.contact.jid.bare
42
40
  elif participant.is_user:
43
- new_msg["muc"]["jid"] = participant.user.jid.bare
41
+ new_msg["muc"]["jid"] = participant.user_jid.bare
44
42
  elif participant.is_system:
45
43
  new_msg["muc"]["jid"] = participant.muc.jid
46
44
  else:
@@ -49,7 +47,7 @@ class MessageArchive:
49
47
  "jid"
50
48
  ] = f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
51
49
 
52
- db.mam_add_msg(self.db_id, HistoryMessage(new_msg), self.user)
50
+ self.__store.add_message(self.room_pk, HistoryMessage(new_msg))
53
51
 
54
52
  def __iter__(self):
55
53
  return iter(self.get_all())
@@ -65,9 +63,8 @@ class MessageArchive:
65
63
  sender: Optional[str] = None,
66
64
  flip=False,
67
65
  ):
68
- for msg in db.mam_get_messages(
69
- self.user,
70
- self.db_id,
66
+ for msg in self.__store.get_messages(
67
+ self.room_pk,
71
68
  before_id=before_id,
72
69
  after_id=after_id,
73
70
  ids=ids,
@@ -87,11 +84,13 @@ class MessageArchive:
87
84
  :return:
88
85
  """
89
86
  reply = iq.reply()
90
- messages = db.mam_get_first_and_last(self.db_id)
87
+ messages = self.__store.get_first_and_last(self.room_pk)
91
88
  if messages:
92
89
  for x, m in [("start", messages[0]), ("end", messages[-1])]:
93
90
  reply["mam_metadata"][x]["id"] = m.id
94
- reply["mam_metadata"][x]["timestamp"] = m.sent_on
91
+ reply["mam_metadata"][x]["timestamp"] = m.sent_on.replace(
92
+ tzinfo=timezone.utc
93
+ )
95
94
  else:
96
95
  reply.enable("mam_metadata")
97
96
  reply.send()
slidge/group/bookmarks.py CHANGED
@@ -1,6 +1,6 @@
1
1
  import abc
2
2
  import logging
3
- from typing import TYPE_CHECKING, Generic, Type
3
+ from typing import TYPE_CHECKING, Generic, Iterator, Optional, Type
4
4
 
5
5
  from slixmpp import JID
6
6
  from slixmpp.jid import _unescape_node
@@ -9,6 +9,7 @@ from ..contact.roster import ESCAPE_TABLE
9
9
  from ..core.mixins.lock import NamedLockMixin
10
10
  from ..util import SubclassableOnce
11
11
  from ..util.types import LegacyGroupIdType, LegacyMUCType
12
+ from .archive import MessageArchive
12
13
  from .room import LegacyMUC
13
14
 
14
15
  if TYPE_CHECKING:
@@ -27,17 +28,15 @@ class LegacyBookmarks(
27
28
  def __init__(self, session: "BaseSession"):
28
29
  self.session = session
29
30
  self.xmpp = session.xmpp
30
- self.user = session.user
31
-
32
- self._mucs_by_legacy_id = dict[LegacyGroupIdType, LegacyMUCType]()
33
- self._mucs_by_bare_jid = dict[str, LegacyMUCType]()
31
+ self.user_jid = session.user_jid
32
+ self.__store = self.xmpp.store.rooms
34
33
 
35
34
  self._muc_class: Type[LegacyMUCType] = LegacyMUC.get_self_or_unique_subclass()
36
35
 
37
- self._user_nick: str = self.session.user.jid.node
36
+ self._user_nick: str = self.session.user_jid.node
38
37
 
39
38
  super().__init__()
40
- self.log = logging.getLogger(f"{self.user.bare_jid}:bookmarks")
39
+ self.log = logging.getLogger(f"{self.user_jid.bare}:bookmarks")
41
40
  self.ready = self.session.xmpp.loop.create_future()
42
41
  if not self.xmpp.GROUPS:
43
42
  self.ready.set_result(True)
@@ -50,20 +49,23 @@ class LegacyBookmarks(
50
49
  def user_nick(self, nick: str):
51
50
  self._user_nick = nick
52
51
 
53
- def __iter__(self):
54
- return iter(self._mucs_by_legacy_id.values())
52
+ def __iter__(self) -> Iterator[LegacyMUCType]:
53
+ for stored in self.__store.get_all(user_pk=self.session.user_pk):
54
+ yield self._muc_class.from_store(self.session, stored)
55
55
 
56
56
  def __repr__(self):
57
- return f"<Bookmarks of {self.user}>"
57
+ return f"<Bookmarks of {self.user_jid}>"
58
58
 
59
59
  async def __finish_init_muc(self, legacy_id: LegacyGroupIdType, jid: JID):
60
60
  muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
61
- await muc.avatar_wrap_update_info()
62
- if not muc.user_nick:
63
- muc.user_nick = self._user_nick
64
- self.log.debug("MUC created: %r", muc)
65
- self._mucs_by_legacy_id[muc.legacy_id] = muc
66
- self._mucs_by_bare_jid[muc.jid.bare] = muc
61
+ with self.__store.session():
62
+ muc.pk = self.__store.add(self.session.user_pk, str(muc.legacy_id), muc.jid)
63
+ muc.archive = MessageArchive(muc.pk, self.xmpp.store.mam)
64
+ await muc.avatar_wrap_update_info()
65
+ if not muc.user_nick:
66
+ muc.user_nick = self._user_nick
67
+ self.log.debug("MUC created: %r", muc)
68
+ self.__store.update(muc)
67
69
  return muc
68
70
 
69
71
  async def legacy_id_to_jid_local_part(self, legacy_id: LegacyGroupIdType):
@@ -94,36 +96,50 @@ class LegacyBookmarks(
94
96
  return _unescape_node(username)
95
97
 
96
98
  async def by_jid(self, jid: JID) -> LegacyMUCType:
99
+ if jid.resource:
100
+ jid = JID(jid.bare)
97
101
  bare = jid.bare
98
102
  async with self.lock(("bare", bare)):
99
- muc = self._mucs_by_bare_jid.get(bare)
100
- if muc is None:
101
- self.log.debug("Attempting to instantiate a new MUC for JID %s", jid)
102
- local_part = jid.node
103
- legacy_id = await self.jid_local_part_to_legacy_id(local_part)
104
- if self.get_lock(("legacy_id", legacy_id)):
105
- self.log.debug("Not instantiating %s after all", jid)
106
- return await self.by_legacy_id(legacy_id)
107
- self.log.debug("%r is group %r", local_part, legacy_id)
108
- muc = await self.__finish_init_muc(legacy_id, JID(bare))
109
- else:
110
- self.log.trace("Found an existing MUC instance: %s", muc) # type:ignore
111
- return muc
103
+ assert isinstance(jid.username, str)
104
+ legacy_id = await self.jid_local_part_to_legacy_id(jid.username)
105
+ if self.get_lock(("legacy_id", legacy_id)):
106
+ self.log.debug("Not instantiating %s after all", jid)
107
+ return await self.by_legacy_id(legacy_id)
108
+
109
+ with self.__store.session():
110
+ stored = self.__store.get_by_jid(self.session.user_pk, jid)
111
+ if stored is not None and stored.updated:
112
+ return self._muc_class.from_store(self.session, stored)
113
+
114
+ self.log.debug("Attempting to instantiate a new MUC for JID %s", jid)
115
+ local_part = jid.node
116
+
117
+ self.log.debug("%r is group %r", local_part, legacy_id)
118
+ return await self.__finish_init_muc(legacy_id, JID(bare))
119
+
120
+ def by_jid_only_if_exists(self, jid: JID) -> Optional[LegacyMUCType]:
121
+ with self.__store.session():
122
+ stored = self.__store.get_by_jid(self.session.user_pk, jid)
123
+ if stored is not None and stored.updated:
124
+ return self._muc_class.from_store(self.session, stored)
125
+ return None
112
126
 
113
127
  async def by_legacy_id(self, legacy_id: LegacyGroupIdType) -> LegacyMUCType:
114
128
  async with self.lock(("legacy_id", legacy_id)):
115
- muc = self._mucs_by_legacy_id.get(legacy_id)
116
- if muc is None:
117
- self.log.debug("Create new MUC instance for legacy ID %s", legacy_id)
118
- local = await self.legacy_id_to_jid_local_part(legacy_id)
119
- bare = f"{local}@{self.xmpp.boundjid}"
120
- jid = JID(bare)
121
- if self.get_lock(("bare", bare)):
122
- self.log.debug("Not instantiating %s after all", legacy_id)
123
- return await self.by_jid(jid)
124
- muc = await self.__finish_init_muc(legacy_id, jid)
125
- else:
126
- self.log.trace("Found an existing MUC instance: %s", muc) # type:ignore
129
+ with self.__store.session():
130
+ stored = self.__store.get_by_legacy_id(
131
+ self.session.user_pk, str(legacy_id)
132
+ )
133
+ if stored is not None and stored.updated:
134
+ return self._muc_class.from_store(self.session, stored)
135
+ self.log.debug("Create new MUC instance for legacy ID %s", legacy_id)
136
+ local = await self.legacy_id_to_jid_local_part(legacy_id)
137
+ bare = f"{local}@{self.xmpp.boundjid}"
138
+ jid = JID(bare)
139
+ if self.get_lock(("bare", bare)):
140
+ self.log.debug("Not instantiating %s after all", legacy_id)
141
+ return await self.by_jid(jid)
142
+ muc = await self.__finish_init_muc(legacy_id, jid)
127
143
 
128
144
  return muc
129
145
 
@@ -146,18 +162,5 @@ class LegacyBookmarks(
146
162
  )
147
163
 
148
164
  def remove(self, muc: LegacyMUC):
149
- try:
150
- del self._mucs_by_legacy_id[muc.legacy_id]
151
- except KeyError:
152
- self.log.warning("Removed a MUC that we didn't store by legacy ID")
153
- try:
154
- del self._mucs_by_bare_jid[muc.jid.bare]
155
- except KeyError:
156
- self.log.warning("Removed a MUC that we didn't store by JID")
157
- for part in muc._participants_by_contacts.values():
158
- try:
159
- part.contact.participants.remove(part)
160
- except KeyError:
161
- part.log.warning(
162
- "That participant wasn't stored in the contact's participants attribute"
163
- )
165
+ assert muc.pk is not None
166
+ self.__store.delete(muc.pk)
@@ -6,7 +6,7 @@ import warnings
6
6
  from copy import copy
7
7
  from datetime import datetime
8
8
  from functools import cached_property
9
- from typing import TYPE_CHECKING, Optional, Union
9
+ from typing import TYPE_CHECKING, Optional, Self, Union
10
10
 
11
11
  from slixmpp import JID, InvalidJID, Message, Presence
12
12
  from slixmpp.plugins.xep_0045.stanza import MUCAdminItem
@@ -15,10 +15,16 @@ from slixmpp.types import MessageTypes, OptJid
15
15
  from slixmpp.util.stringprep_profiles import StringPrepError, prohibit_output
16
16
 
17
17
  from ..contact import LegacyContact
18
- from ..core.mixins import ChatterDiscoMixin, MessageMixin, PresenceMixin
18
+ from ..core.mixins import (
19
+ ChatterDiscoMixin,
20
+ MessageMixin,
21
+ PresenceMixin,
22
+ StoredAttributeMixin,
23
+ )
24
+ from ..db.models import Participant
19
25
  from ..util import SubclassableOnce, strip_illegal_chars
20
- from ..util.sql import CachedPresence
21
26
  from ..util.types import (
27
+ CachedPresence,
22
28
  Hat,
23
29
  LegacyMessageType,
24
30
  MessageOrPresenceTypeVar,
@@ -40,6 +46,7 @@ def strip_non_printable(nickname: str):
40
46
 
41
47
 
42
48
  class LegacyParticipant(
49
+ StoredAttributeMixin,
43
50
  PresenceMixin,
44
51
  MessageMixin,
45
52
  ChatterDiscoMixin,
@@ -53,6 +60,7 @@ class LegacyParticipant(
53
60
  _can_send_carbon = False
54
61
  USE_STANZA_ID = True
55
62
  STRIP_SHORT_DELAY = False
63
+ pk: int
56
64
 
57
65
  def __init__(
58
66
  self,
@@ -63,12 +71,11 @@ class LegacyParticipant(
63
71
  role: MucRole = "participant",
64
72
  affiliation: MucAffiliation = "member",
65
73
  ):
74
+ self.session = session = muc.session
75
+ self.xmpp = session.xmpp
66
76
  super().__init__()
67
77
  self._hats = list[Hat]()
68
78
  self.muc = muc
69
- self.session = session = muc.session
70
- self.user = session.user
71
- self.xmpp = session.xmpp
72
79
  self._role = role
73
80
  self._affiliation = affiliation
74
81
  self.is_user: bool = is_user
@@ -84,8 +91,19 @@ class LegacyParticipant(
84
91
  # if we didn't, we send it before the first message.
85
92
  # this way, event in plugins that don't map "user has joined" events,
86
93
  # we send a "join"-presence from the participant before the first message
87
- self.__presence_sent = False
88
- self.log = logging.getLogger(f"{self.user.bare_jid}:{self.jid}")
94
+ self._presence_sent: bool = False
95
+ self.log = logging.getLogger(f"{self.user_jid.bare}:{self.jid}")
96
+ self.__part_store = self.xmpp.store.participants
97
+
98
+ @property
99
+ def contact_pk(self) -> Optional[int]: # type:ignore
100
+ if self.contact:
101
+ return self.contact.contact_pk
102
+ return None
103
+
104
+ @property
105
+ def user_jid(self):
106
+ return self.session.user_jid
89
107
 
90
108
  def __repr__(self):
91
109
  return f"<Participant '{self.nickname}'/'{self.jid}' of '{self.muc}'>"
@@ -99,7 +117,8 @@ class LegacyParticipant(
99
117
  if self._affiliation == affiliation:
100
118
  return
101
119
  self._affiliation = affiliation
102
- if not self.__presence_sent:
120
+ self.__part_store.set_affiliation(self.pk, affiliation)
121
+ if not self._presence_sent:
103
122
  return
104
123
  self.send_last_presence(force=True, no_cache_online=True)
105
124
 
@@ -126,7 +145,8 @@ class LegacyParticipant(
126
145
  if self._role == role:
127
146
  return
128
147
  self._role = role
129
- if not self.__presence_sent:
148
+ self.__part_store.set_role(self.pk, role)
149
+ if not self._presence_sent:
130
150
  return
131
151
  self.send_last_presence(force=True, no_cache_online=True)
132
152
 
@@ -134,7 +154,8 @@ class LegacyParticipant(
134
154
  if self._hats == hats:
135
155
  return
136
156
  self._hats = hats
137
- if not self.__presence_sent:
157
+ self.__part_store.set_hats(self.pk, hats)
158
+ if not self._presence_sent:
138
159
  return
139
160
  self.send_last_presence(force=True, no_cache_online=True)
140
161
 
@@ -143,6 +164,7 @@ class LegacyParticipant(
143
164
 
144
165
  if self.is_system:
145
166
  self.jid = j
167
+ self._nickname_no_illegal = ""
146
168
  return
147
169
 
148
170
  nickname = unescaped_nickname
@@ -170,9 +192,6 @@ class LegacyParticipant(
170
192
  except InvalidJID:
171
193
  j.resource = strip_non_printable(nickname)
172
194
 
173
- if nickname != unescaped_nickname:
174
- self.muc._participants_by_escaped_nicknames[nickname] = self # type:ignore
175
-
176
195
  self.jid = j
177
196
 
178
197
  def send_configuration_change(self, codes: tuple[int]):
@@ -213,12 +232,8 @@ class LegacyParticipant(
213
232
 
214
233
  kwargs["status_codes"] = set()
215
234
  p = self._make_presence(ptype="available", last_seen=last_seen, **kwargs)
216
- self.__add_nick_element(p)
217
235
  self._send(p)
218
236
 
219
- if old:
220
- self.muc.rename_participant(old, new_nickname)
221
-
222
237
  def _make_presence(
223
238
  self,
224
239
  *,
@@ -240,14 +255,14 @@ class LegacyParticipant(
240
255
  if user_full_jid:
241
256
  p["muc"]["jid"] = user_full_jid
242
257
  else:
243
- jid = copy(self.user.jid)
258
+ jid = copy(self.user_jid)
244
259
  try:
245
260
  jid.resource = next(
246
- iter(self.muc.user_resources) # type:ignore
261
+ iter(self.muc.get_user_resources()) # type:ignore
247
262
  )
248
263
  except StopIteration:
249
264
  jid.resource = "pseudo-resource"
250
- p["muc"]["jid"] = self.user.jid
265
+ p["muc"]["jid"] = self.user_jid
251
266
  codes.add(100)
252
267
  elif self.contact:
253
268
  p["muc"]["jid"] = self.contact.jid
@@ -257,7 +272,7 @@ class LegacyParticipant(
257
272
  warnings.warn(
258
273
  f"Private group but no 1:1 JID associated to '{self}'",
259
274
  )
260
- if self.is_user and (hash_ := self.session.avatar_hash):
275
+ if self.is_user and (hash_ := self.session.user.avatar_hash):
261
276
  p["vcard_temp_update"]["photo"] = hash_
262
277
  p["muc"]["status_codes"] = codes
263
278
  return p
@@ -273,7 +288,7 @@ class LegacyParticipant(
273
288
  archive_only
274
289
  or self.is_system
275
290
  or self.is_user
276
- or self.__presence_sent
291
+ or self._presence_sent
277
292
  or stanza["subject"]
278
293
  ):
279
294
  return
@@ -301,10 +316,12 @@ class LegacyParticipant(
301
316
  **send_kwargs,
302
317
  ) -> MessageOrPresenceTypeVar:
303
318
  stanza["occupant-id"]["id"] = self.__occupant_id
319
+ self.__add_nick_element(stanza)
304
320
  if isinstance(stanza, Presence):
305
- if stanza["type"] == "unavailable" and not self.__presence_sent:
321
+ if stanza["type"] == "unavailable" and not self._presence_sent:
306
322
  return stanza # type:ignore
307
- self.__presence_sent = True
323
+ self._presence_sent = True
324
+ self.__part_store.set_presence_sent(self.pk)
308
325
  if full_jid:
309
326
  stanza["to"] = full_jid
310
327
  self.__send_presence_if_needed(stanza, full_jid, archive_only)
@@ -332,7 +349,7 @@ class LegacyParticipant(
332
349
  item["role"] = self.role
333
350
  if not self.muc.is_anonymous:
334
351
  if self.is_user:
335
- item["jid"] = self.user.bare_jid
352
+ item["jid"] = self.user_jid.bare
336
353
  elif self.contact:
337
354
  item["jid"] = self.contact.jid.bare
338
355
  else:
@@ -344,11 +361,11 @@ class LegacyParticipant(
344
361
  )
345
362
  return item
346
363
 
347
- def __add_nick_element(self, p: Presence):
364
+ def __add_nick_element(self, stanza: Union[Presence, Message]):
348
365
  if (nick := self._nickname_no_illegal) != self.jid.resource:
349
366
  n = self.xmpp.plugin["xep_0172"].stanza.UserNick()
350
367
  n["nick"] = nick
351
- p.append(n)
368
+ stanza.append(n)
352
369
 
353
370
  def _get_last_presence(self) -> Optional[CachedPresence]:
354
371
  own = super()._get_last_presence()
@@ -400,7 +417,6 @@ class LegacyParticipant(
400
417
  )
401
418
  if presence_id:
402
419
  p["id"] = presence_id
403
- self.__add_nick_element(p)
404
420
  self._send(p, full_jid)
405
421
 
406
422
  def leave(self):
@@ -454,5 +470,41 @@ class LegacyParticipant(
454
470
  msg["subject"] = subject or str(self.muc.name)
455
471
  self._send(msg, full_jid)
456
472
 
473
+ @classmethod
474
+ def from_store(
475
+ cls,
476
+ session,
477
+ stored: Participant,
478
+ contact: Optional[LegacyContact] = None,
479
+ muc: Optional["LegacyMUC"] = None,
480
+ ) -> Self:
481
+ from slidge.group.room import LegacyMUC
482
+
483
+ if muc is None:
484
+ muc = LegacyMUC.get_self_or_unique_subclass().from_store(
485
+ session, stored.room
486
+ )
487
+ part = cls(
488
+ muc,
489
+ stored.nickname,
490
+ role=stored.role,
491
+ affiliation=stored.affiliation,
492
+ )
493
+ part.pk = stored.id
494
+ if contact is not None:
495
+ part.contact = contact
496
+ elif stored.contact is not None:
497
+ contact = LegacyContact.get_self_or_unique_subclass().from_store(
498
+ session, stored.contact
499
+ )
500
+ part.contact = contact
501
+
502
+ part.is_user = stored.is_user
503
+ if (data := stored.extra_attributes) is not None:
504
+ muc.deserialize_extra_attributes(data)
505
+ part._presence_sent = stored.presence_sent
506
+ part._hats = [Hat(h.uri, h.title) for h in stored.hats]
507
+ return part
508
+
457
509
 
458
510
  log = logging.getLogger(__name__)