slidge 0.2.12__py3-none-any.whl → 0.3.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.
- slidge/__init__.py +5 -2
- slidge/command/adhoc.py +9 -3
- slidge/command/admin.py +16 -12
- slidge/command/base.py +16 -12
- slidge/command/chat_command.py +25 -16
- slidge/command/user.py +7 -8
- slidge/contact/contact.py +119 -209
- slidge/contact/roster.py +106 -105
- slidge/core/config.py +2 -43
- slidge/core/dispatcher/caps.py +9 -2
- slidge/core/dispatcher/disco.py +13 -3
- slidge/core/dispatcher/message/__init__.py +1 -1
- slidge/core/dispatcher/message/chat_state.py +17 -8
- slidge/core/dispatcher/message/marker.py +7 -5
- slidge/core/dispatcher/message/message.py +117 -92
- slidge/core/dispatcher/muc/__init__.py +1 -1
- slidge/core/dispatcher/muc/admin.py +4 -4
- slidge/core/dispatcher/muc/mam.py +10 -6
- slidge/core/dispatcher/muc/misc.py +4 -2
- slidge/core/dispatcher/muc/owner.py +5 -3
- slidge/core/dispatcher/muc/ping.py +3 -1
- slidge/core/dispatcher/presence.py +21 -15
- slidge/core/dispatcher/registration.py +20 -12
- slidge/core/dispatcher/search.py +7 -3
- slidge/core/dispatcher/session_dispatcher.py +13 -5
- slidge/core/dispatcher/util.py +37 -27
- slidge/core/dispatcher/vcard.py +7 -4
- slidge/core/gateway.py +168 -84
- slidge/core/mixins/__init__.py +1 -11
- slidge/core/mixins/attachment.py +163 -148
- slidge/core/mixins/avatar.py +100 -177
- slidge/core/mixins/db.py +50 -2
- slidge/core/mixins/message.py +19 -17
- slidge/core/mixins/message_maker.py +29 -15
- slidge/core/mixins/message_text.py +38 -30
- slidge/core/mixins/presence.py +91 -35
- slidge/core/pubsub.py +42 -47
- slidge/core/session.py +88 -57
- slidge/db/alembic/versions/0337c90c0b96_unify_legacy_xmpp_id_mappings.py +183 -0
- slidge/db/alembic/versions/4dbd23a3f868_new_avatar_store.py +56 -0
- slidge/db/alembic/versions/54ce3cde350c_use_hash_for_avatar_filenames.py +50 -0
- slidge/db/alembic/versions/58b98dacf819_refactor.py +118 -0
- slidge/db/alembic/versions/75a62b74b239_ditch_hats_table.py +74 -0
- slidge/db/avatar.py +150 -119
- slidge/db/meta.py +33 -22
- slidge/db/models.py +68 -117
- slidge/db/store.py +412 -1094
- slidge/group/archive.py +61 -54
- slidge/group/bookmarks.py +74 -55
- slidge/group/participant.py +135 -142
- slidge/group/room.py +315 -312
- slidge/main.py +28 -18
- slidge/migration.py +2 -12
- slidge/slixfix/__init__.py +20 -4
- slidge/slixfix/delivery_receipt.py +6 -4
- slidge/slixfix/link_preview/link_preview.py +1 -1
- slidge/slixfix/link_preview/stanza.py +1 -1
- slidge/slixfix/roster.py +5 -7
- slidge/slixfix/xep_0077/register.py +8 -8
- slidge/slixfix/xep_0077/stanza.py +7 -7
- slidge/slixfix/xep_0100/gateway.py +12 -13
- slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
- slidge/slixfix/xep_0292/vcard4.py +1 -1
- slidge/util/archive_msg.py +11 -5
- slidge/util/conf.py +23 -20
- slidge/util/jid_escaping.py +1 -1
- slidge/{core/mixins → util}/lock.py +6 -6
- slidge/util/test.py +30 -29
- slidge/util/types.py +22 -18
- slidge/util/util.py +19 -22
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/METADATA +1 -1
- slidge-0.3.0a0.dist-info/RECORD +117 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/WHEEL +1 -1
- slidge-0.2.12.dist-info/RECORD +0 -112
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/entry_points.txt +0 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/top_level.txt +0 -0
slidge/group/participant.py
CHANGED
@@ -5,19 +5,17 @@ import warnings
|
|
5
5
|
from copy import copy
|
6
6
|
from datetime import datetime
|
7
7
|
from functools import cached_property
|
8
|
-
from typing import TYPE_CHECKING,
|
8
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
9
9
|
|
10
|
+
import sqlalchemy as sa
|
10
11
|
from slixmpp import JID, InvalidJID, Message, Presence
|
11
12
|
from slixmpp.plugins.xep_0045.stanza import MUCAdminItem
|
12
13
|
from slixmpp.types import MessageTypes, OptJid
|
14
|
+
from sqlalchemy.orm.exc import DetachedInstanceError
|
13
15
|
|
14
16
|
from ..contact import LegacyContact
|
15
|
-
from ..core.mixins import
|
16
|
-
|
17
|
-
MessageMixin,
|
18
|
-
PresenceMixin,
|
19
|
-
StoredAttributeMixin,
|
20
|
-
)
|
17
|
+
from ..core.mixins import ChatterDiscoMixin, MessageMixin, PresenceMixin
|
18
|
+
from ..core.mixins.db import DBMixin
|
21
19
|
from ..db.models import Participant
|
22
20
|
from ..util import SubclassableOnce, strip_illegal_chars
|
23
21
|
from ..util.types import (
|
@@ -43,10 +41,10 @@ def strip_non_printable(nickname: str):
|
|
43
41
|
|
44
42
|
|
45
43
|
class LegacyParticipant(
|
46
|
-
StoredAttributeMixin,
|
47
44
|
PresenceMixin,
|
48
45
|
MessageMixin,
|
49
46
|
ChatterDiscoMixin,
|
47
|
+
DBMixin,
|
50
48
|
metaclass=SubclassableOnce,
|
51
49
|
):
|
52
50
|
"""
|
@@ -57,83 +55,111 @@ class LegacyParticipant(
|
|
57
55
|
_can_send_carbon = False
|
58
56
|
USE_STANZA_ID = True
|
59
57
|
STRIP_SHORT_DELAY = False
|
60
|
-
|
58
|
+
stored: Participant
|
59
|
+
contact: LegacyContact[Any] | None
|
61
60
|
|
62
61
|
def __init__(
|
63
62
|
self,
|
64
63
|
muc: "LegacyMUC",
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
affiliation: MucAffiliation = "member",
|
70
|
-
resource: str | None = None,
|
71
|
-
nickname_no_illegal: str | None = None,
|
72
|
-
):
|
73
|
-
self.session = session = muc.session
|
74
|
-
self.xmpp = session.xmpp
|
75
|
-
super().__init__()
|
76
|
-
self._hats = list[Hat]()
|
64
|
+
stored: Participant,
|
65
|
+
is_system: bool = False,
|
66
|
+
contact: LegacyContact[Any] | None = None,
|
67
|
+
) -> None:
|
77
68
|
self.muc = muc
|
78
|
-
self.
|
79
|
-
self.
|
80
|
-
self.
|
81
|
-
self.is_system: bool = is_system
|
69
|
+
self.session = muc.session
|
70
|
+
self.xmpp = muc.session.xmpp
|
71
|
+
self.is_system = is_system
|
82
72
|
|
83
|
-
|
73
|
+
if contact is None and stored.contact is not None:
|
74
|
+
contact = self.session.contacts.from_store(stored=stored.contact)
|
75
|
+
if contact is not None and stored.contact is None:
|
76
|
+
stored.contact = contact.stored
|
84
77
|
|
85
|
-
|
86
|
-
|
87
|
-
else:
|
88
|
-
assert nickname_no_illegal is not None
|
89
|
-
self._nickname_no_illegal = nickname_no_illegal
|
90
|
-
self.jid = JID(self.muc.jid)
|
91
|
-
self.jid.resource = resource
|
78
|
+
self.stored = stored
|
79
|
+
self.contact = contact
|
92
80
|
|
93
|
-
|
81
|
+
super().__init__()
|
82
|
+
|
83
|
+
if stored.resource is None:
|
84
|
+
self.__update_resource(stored.nickname)
|
94
85
|
|
95
|
-
self.contact: Optional["LegacyContact"] = None
|
96
|
-
# we track if we already sent a presence for this participant.
|
97
|
-
# if we didn't, we send it before the first message.
|
98
|
-
# this way, event in plugins that don't map "user has joined" events,
|
99
|
-
# we send a "join"-presence from the participant before the first message
|
100
|
-
self._presence_sent: bool = False
|
101
86
|
self.log = logging.getLogger(f"{self.user_jid.bare}:{self.jid}")
|
102
|
-
self.__part_store = self.xmpp.store.participants
|
103
87
|
|
104
88
|
@property
|
105
|
-
def
|
106
|
-
|
107
|
-
return self.
|
108
|
-
|
89
|
+
def is_user(self) -> bool:
|
90
|
+
try:
|
91
|
+
return self.stored.is_user
|
92
|
+
except DetachedInstanceError:
|
93
|
+
self.merge()
|
94
|
+
return self.stored.is_user
|
95
|
+
|
96
|
+
@property
|
97
|
+
def jid(self) -> JID:
|
98
|
+
jid = JID(self.muc.jid)
|
99
|
+
if self.stored.resource:
|
100
|
+
jid.resource = self.stored.resource
|
101
|
+
return jid
|
102
|
+
|
103
|
+
@jid.setter
|
104
|
+
def jid(self, x: JID):
|
105
|
+
# FIXME: without this, mypy yields
|
106
|
+
# "Cannot override writeable attribute with read-only property"
|
107
|
+
# But it does not happen for LegacyContact. WTF?
|
108
|
+
raise RuntimeError
|
109
|
+
|
110
|
+
def commit(self, *args, **kwargs) -> None:
|
111
|
+
if self.is_system:
|
112
|
+
return
|
113
|
+
if self.muc.get_lock("fill participants") or self.muc.get_lock("fill history"):
|
114
|
+
return
|
115
|
+
super().commit(*args, **kwargs)
|
109
116
|
|
110
117
|
@property
|
111
118
|
def user_jid(self):
|
112
119
|
return self.session.user_jid
|
113
120
|
|
114
|
-
def __repr__(self):
|
121
|
+
def __repr__(self) -> str:
|
115
122
|
return f"<Participant '{self.nickname}'/'{self.jid}' of '{self.muc}'>"
|
116
123
|
|
124
|
+
@property
|
125
|
+
def _presence_sent(self) -> bool:
|
126
|
+
# we track if we already sent a presence for this participant.
|
127
|
+
# if we didn't, we send it before the first message.
|
128
|
+
# this way, event in plugins that don't map "user has joined" events,
|
129
|
+
# we send a "join"-presence from the participant before the first message
|
130
|
+
return self.stored.presence_sent
|
131
|
+
|
132
|
+
@_presence_sent.setter
|
133
|
+
def _presence_sent(self, val: bool) -> None:
|
134
|
+
if self._presence_sent == val:
|
135
|
+
return
|
136
|
+
self.stored.presence_sent = val
|
137
|
+
self.commit(merge=True)
|
138
|
+
|
139
|
+
@property
|
140
|
+
def nickname_no_illegal(self) -> str:
|
141
|
+
return self.stored.nickname_no_illegal
|
142
|
+
|
117
143
|
@property
|
118
144
|
def affiliation(self):
|
119
|
-
return self.
|
145
|
+
return self.stored.affiliation
|
120
146
|
|
121
147
|
@affiliation.setter
|
122
|
-
def affiliation(self, affiliation: MucAffiliation):
|
123
|
-
if self.
|
148
|
+
def affiliation(self, affiliation: MucAffiliation) -> None:
|
149
|
+
if self.affiliation == affiliation:
|
124
150
|
return
|
125
|
-
self.
|
126
|
-
if not self.muc.
|
151
|
+
self.stored.affiliation = affiliation
|
152
|
+
if not self.muc.participants_filled:
|
127
153
|
return
|
128
|
-
self.
|
154
|
+
self.commit()
|
129
155
|
if not self._presence_sent:
|
130
156
|
return
|
131
157
|
self.send_last_presence(force=True, no_cache_online=True)
|
132
158
|
|
133
|
-
def send_affiliation_change(self):
|
159
|
+
def send_affiliation_change(self) -> None:
|
134
160
|
# internal use by slidge
|
135
161
|
msg = self._make_message()
|
136
|
-
msg["muc"]["affiliation"] = self.
|
162
|
+
msg["muc"]["affiliation"] = self.affiliation
|
137
163
|
msg["type"] = "normal"
|
138
164
|
if not self.muc.is_anonymous and not self.is_system:
|
139
165
|
if self.contact:
|
@@ -146,48 +172,53 @@ class LegacyParticipant(
|
|
146
172
|
|
147
173
|
@property
|
148
174
|
def role(self):
|
149
|
-
return self.
|
175
|
+
return self.stored.role
|
150
176
|
|
151
177
|
@role.setter
|
152
|
-
def role(self, role: MucRole):
|
153
|
-
if self.
|
178
|
+
def role(self, role: MucRole) -> None:
|
179
|
+
if self.role == role:
|
154
180
|
return
|
155
|
-
self.
|
156
|
-
if not self.muc.
|
181
|
+
self.stored.role = role
|
182
|
+
if not self.muc.participants_filled:
|
157
183
|
return
|
158
|
-
self.
|
184
|
+
self.commit()
|
159
185
|
if not self._presence_sent:
|
160
186
|
return
|
161
187
|
self.send_last_presence(force=True, no_cache_online=True)
|
162
188
|
|
163
|
-
|
164
|
-
|
189
|
+
@property
|
190
|
+
def hats(self) -> list[Hat]:
|
191
|
+
return [Hat(*h) for h in self.stored.hats] if self.stored.hats else []
|
192
|
+
|
193
|
+
def set_hats(self, hats: list[Hat]) -> None:
|
194
|
+
if self.hats == hats:
|
165
195
|
return
|
166
|
-
self.
|
167
|
-
if not self.muc.
|
196
|
+
self.stored.hats = hats # type:ignore[assignment]
|
197
|
+
if not self.muc.participants_filled:
|
168
198
|
return
|
169
|
-
self.
|
199
|
+
self.commit(merge=True)
|
170
200
|
if not self._presence_sent:
|
171
201
|
return
|
172
202
|
self.send_last_presence(force=True, no_cache_online=True)
|
173
203
|
|
174
|
-
def
|
204
|
+
def __update_resource(self, unescaped_nickname: Optional[str]) -> None:
|
175
205
|
if not unescaped_nickname:
|
176
|
-
self.
|
206
|
+
self.stored.resource = ""
|
177
207
|
if self.is_system:
|
178
|
-
self.
|
208
|
+
self.stored.nickname_no_illegal = ""
|
179
209
|
else:
|
180
210
|
warnings.warn(
|
181
211
|
"Only the system participant is allowed to not have a nickname"
|
182
212
|
)
|
183
213
|
nickname = f"unnamed-{uuid.uuid4()}"
|
184
|
-
self.
|
214
|
+
self.stored.resource = self.stored.nickname_no_illegal = nickname
|
185
215
|
return
|
186
216
|
|
187
|
-
self.
|
217
|
+
self.stored.nickname_no_illegal, jid = escape_nickname(
|
188
218
|
self.muc.jid,
|
189
219
|
unescaped_nickname,
|
190
220
|
)
|
221
|
+
self.stored.resource = jid.resource
|
191
222
|
|
192
223
|
def send_configuration_change(self, codes: tuple[int]):
|
193
224
|
if not self.is_system:
|
@@ -198,11 +229,11 @@ class LegacyParticipant(
|
|
198
229
|
|
199
230
|
@property
|
200
231
|
def nickname(self):
|
201
|
-
return self.
|
232
|
+
return self.stored.nickname
|
202
233
|
|
203
234
|
@nickname.setter
|
204
|
-
def nickname(self, new_nickname: str):
|
205
|
-
old = self.
|
235
|
+
def nickname(self, new_nickname: str) -> None:
|
236
|
+
old = self.nickname
|
206
237
|
if new_nickname == old:
|
207
238
|
return
|
208
239
|
|
@@ -219,18 +250,16 @@ class LegacyParticipant(
|
|
219
250
|
p = self._make_presence(ptype="unavailable", last_seen=last_seen, **kwargs)
|
220
251
|
# in this order so pfrom=old resource and we actually use the escaped nick
|
221
252
|
# in the muc/item/nick element
|
222
|
-
self.
|
253
|
+
self.__update_resource(new_nickname)
|
223
254
|
p["muc"]["item"]["nick"] = self.jid.resource
|
224
255
|
self._send(p)
|
225
256
|
|
226
|
-
self.
|
227
|
-
|
257
|
+
self.stored.nickname = new_nickname
|
258
|
+
self.commit()
|
228
259
|
kwargs["status_codes"] = set()
|
229
260
|
p = self._make_presence(ptype="available", last_seen=last_seen, **kwargs)
|
230
261
|
self._send(p)
|
231
262
|
|
232
|
-
self.__part_store.update(self)
|
233
|
-
|
234
263
|
def _make_presence(
|
235
264
|
self,
|
236
265
|
*,
|
@@ -242,8 +271,8 @@ class LegacyParticipant(
|
|
242
271
|
p = super()._make_presence(last_seen=last_seen, **presence_kwargs)
|
243
272
|
p["muc"]["affiliation"] = self.affiliation
|
244
273
|
p["muc"]["role"] = self.role
|
245
|
-
if self.
|
246
|
-
p["hats"].add_hats(self.
|
274
|
+
if self.hats:
|
275
|
+
p["hats"].add_hats(self.hats)
|
247
276
|
codes = status_codes or set()
|
248
277
|
if self.is_user:
|
249
278
|
codes.add(110)
|
@@ -254,9 +283,7 @@ class LegacyParticipant(
|
|
254
283
|
else:
|
255
284
|
jid = JID(self.user_jid)
|
256
285
|
try:
|
257
|
-
jid.resource = next(
|
258
|
-
iter(self.muc.get_user_resources()) # type:ignore
|
259
|
-
)
|
286
|
+
jid.resource = next(iter(self.muc.get_user_resources()))
|
260
287
|
except StopIteration:
|
261
288
|
jid.resource = "pseudo-resource"
|
262
289
|
p["muc"]["jid"] = self.user_jid
|
@@ -280,7 +307,7 @@ class LegacyParticipant(
|
|
280
307
|
|
281
308
|
def __send_presence_if_needed(
|
282
309
|
self, stanza: Union[Message, Presence], full_jid: JID, archive_only: bool
|
283
|
-
):
|
310
|
+
) -> None:
|
284
311
|
if (
|
285
312
|
archive_only
|
286
313
|
or self.is_system
|
@@ -309,8 +336,9 @@ class LegacyParticipant(
|
|
309
336
|
self,
|
310
337
|
stanza: MessageOrPresenceTypeVar,
|
311
338
|
full_jid: Optional[JID] = None,
|
312
|
-
archive_only=False,
|
339
|
+
archive_only: bool = False,
|
313
340
|
legacy_msg_id=None,
|
341
|
+
initial_presence=False,
|
314
342
|
**send_kwargs,
|
315
343
|
) -> MessageOrPresenceTypeVar:
|
316
344
|
if stanza.get_from().resource:
|
@@ -321,8 +349,10 @@ class LegacyParticipant(
|
|
321
349
|
if not self.is_user and isinstance(stanza, Presence):
|
322
350
|
if stanza["type"] == "unavailable" and not self._presence_sent:
|
323
351
|
return stanza # type:ignore
|
324
|
-
|
325
|
-
|
352
|
+
if initial_presence:
|
353
|
+
self.stored.presence_sent = True
|
354
|
+
else:
|
355
|
+
self._presence_sent = True
|
326
356
|
if full_jid:
|
327
357
|
stanza["to"] = full_jid
|
328
358
|
self.__send_presence_if_needed(stanza, full_jid, archive_only)
|
@@ -362,8 +392,8 @@ class LegacyParticipant(
|
|
362
392
|
)
|
363
393
|
return item
|
364
394
|
|
365
|
-
def __add_nick_element(self, stanza: Union[Presence, Message]):
|
366
|
-
if (nick := self.
|
395
|
+
def __add_nick_element(self, stanza: Union[Presence, Message]) -> None:
|
396
|
+
if (nick := self.nickname_no_illegal) != self.jid.resource:
|
367
397
|
n = self.xmpp.plugin["xep_0172"].stanza.UserNick()
|
368
398
|
n["nick"] = nick
|
369
399
|
stanza.append(n)
|
@@ -377,9 +407,9 @@ class LegacyParticipant(
|
|
377
407
|
def send_initial_presence(
|
378
408
|
self,
|
379
409
|
full_jid: JID,
|
380
|
-
nick_change=False,
|
410
|
+
nick_change: bool = False,
|
381
411
|
presence_id: Optional[str] = None,
|
382
|
-
):
|
412
|
+
) -> None:
|
383
413
|
"""
|
384
414
|
Called when the user joins a MUC, as a mechanism
|
385
415
|
to indicate to the joining XMPP client the list of "participants".
|
@@ -418,21 +448,21 @@ class LegacyParticipant(
|
|
418
448
|
)
|
419
449
|
if presence_id:
|
420
450
|
p["id"] = presence_id
|
421
|
-
self._send(p, full_jid)
|
451
|
+
self._send(p, full_jid, initial_presence=True)
|
422
452
|
|
423
|
-
def leave(self):
|
453
|
+
def leave(self) -> None:
|
424
454
|
"""
|
425
455
|
Call this when the participant leaves the room
|
426
456
|
"""
|
427
457
|
self.muc.remove_participant(self)
|
428
458
|
|
429
|
-
def kick(self, reason: str | None = None):
|
459
|
+
def kick(self, reason: str | None = None) -> None:
|
430
460
|
"""
|
431
461
|
Call this when the participant is kicked from the room
|
432
462
|
"""
|
433
463
|
self.muc.remove_participant(self, kick=True, reason=reason)
|
434
464
|
|
435
|
-
def ban(self, reason: str | None = None):
|
465
|
+
def ban(self, reason: str | None = None) -> None:
|
436
466
|
"""
|
437
467
|
Call this when the participant is banned from the room
|
438
468
|
"""
|
@@ -443,15 +473,16 @@ class LegacyParticipant(
|
|
443
473
|
return self.contact.get_disco_info()
|
444
474
|
return super().get_disco_info()
|
445
475
|
|
446
|
-
def moderate(
|
476
|
+
def moderate(
|
477
|
+
self, legacy_msg_id: LegacyMessageType, reason: Optional[str] = None
|
478
|
+
) -> None:
|
447
479
|
xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
msg_ids = multi + [xmpp_id]
|
480
|
+
with self.xmpp.store.session() as orm:
|
481
|
+
msg_ids = self.xmpp.store.id_map.get_xmpp(
|
482
|
+
orm, self.muc.stored.id, str(legacy_msg_id), True
|
483
|
+
)
|
453
484
|
|
454
|
-
for i in msg_ids:
|
485
|
+
for i in set(msg_ids + [xmpp_id]):
|
455
486
|
m = self.muc.get_system_participant()._make_message()
|
456
487
|
m["retract"]["id"] = i
|
457
488
|
if self.is_system:
|
@@ -468,8 +499,8 @@ class LegacyParticipant(
|
|
468
499
|
subject: str,
|
469
500
|
full_jid: Optional[JID] = None,
|
470
501
|
when: Optional[datetime] = None,
|
471
|
-
update_muc=True,
|
472
|
-
):
|
502
|
+
update_muc: bool = True,
|
503
|
+
) -> None:
|
473
504
|
if update_muc:
|
474
505
|
self.muc._subject = subject # type: ignore
|
475
506
|
self.muc.subject_setter = self.nickname
|
@@ -482,44 +513,6 @@ class LegacyParticipant(
|
|
482
513
|
msg["subject"] = subject or str(self.muc.name)
|
483
514
|
self._send(msg, full_jid)
|
484
515
|
|
485
|
-
@classmethod
|
486
|
-
def from_store(
|
487
|
-
cls,
|
488
|
-
session,
|
489
|
-
stored: Participant,
|
490
|
-
contact: Optional[LegacyContact] = None,
|
491
|
-
muc: Optional["LegacyMUC"] = None,
|
492
|
-
) -> Self:
|
493
|
-
from slidge.group.room import LegacyMUC
|
494
|
-
|
495
|
-
if muc is None:
|
496
|
-
muc = LegacyMUC.get_self_or_unique_subclass().from_store(
|
497
|
-
session, stored.room
|
498
|
-
)
|
499
|
-
part = cls(
|
500
|
-
muc,
|
501
|
-
stored.nickname,
|
502
|
-
role=stored.role,
|
503
|
-
affiliation=stored.affiliation,
|
504
|
-
resource=stored.resource,
|
505
|
-
nickname_no_illegal=stored.nickname_no_illegal,
|
506
|
-
)
|
507
|
-
part.pk = stored.id
|
508
|
-
if contact is not None:
|
509
|
-
part.contact = contact
|
510
|
-
elif stored.contact is not None:
|
511
|
-
contact = LegacyContact.get_self_or_unique_subclass().from_store(
|
512
|
-
session, stored.contact
|
513
|
-
)
|
514
|
-
part.contact = contact
|
515
|
-
|
516
|
-
part.is_user = stored.is_user
|
517
|
-
if (data := stored.extra_attributes) is not None:
|
518
|
-
muc.deserialize_extra_attributes(data)
|
519
|
-
part._presence_sent = stored.presence_sent
|
520
|
-
part._hats = [Hat(h.uri, h.title) for h in stored.hats]
|
521
|
-
return part
|
522
|
-
|
523
516
|
|
524
517
|
def escape_nickname(muc_jid: JID, nickname: str) -> tuple[str, JID]:
|
525
518
|
nickname = nickname_no_illegal = strip_illegal_chars(nickname)
|