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/room.py
CHANGED
@@ -5,9 +5,10 @@ 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, AsyncIterator, Generic, Optional,
|
8
|
+
from typing import TYPE_CHECKING, AsyncIterator, Generic, Optional, Type, Union
|
9
9
|
from uuid import uuid4
|
10
10
|
|
11
|
+
import sqlalchemy as sa
|
11
12
|
from slixmpp import JID, Iq, Message, Presence
|
12
13
|
from slixmpp.exceptions import IqError, IqTimeout, XMPPError
|
13
14
|
from slixmpp.plugins.xep_0004 import Form
|
@@ -17,19 +18,16 @@ from slixmpp.plugins.xep_0469.stanza import NS as PINNING_NS
|
|
17
18
|
from slixmpp.plugins.xep_0492.stanza import NS as NOTIFY_NS
|
18
19
|
from slixmpp.plugins.xep_0492.stanza import WhenLiteral
|
19
20
|
from slixmpp.xmlstream import ET
|
21
|
+
from sqlalchemy.orm import Session as OrmSession
|
20
22
|
|
21
23
|
from ..contact.contact import LegacyContact
|
22
24
|
from ..contact.roster import ContactIsUser
|
23
|
-
from ..core import config
|
24
|
-
from ..core.mixins import StoredAttributeMixin
|
25
25
|
from ..core.mixins.avatar import AvatarMixin
|
26
|
-
from ..core.mixins.db import UpdateInfoMixin
|
27
26
|
from ..core.mixins.disco import ChatterDiscoMixin
|
28
|
-
from ..core.mixins.lock import NamedLockMixin
|
29
27
|
from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
|
30
|
-
from ..db.models import Room
|
31
|
-
from ..util import ABCSubclassableOnceAtMost
|
28
|
+
from ..db.models import Participant, Room
|
32
29
|
from ..util.jid_escaping import unescape_node
|
30
|
+
from ..util.lock import NamedLockMixin
|
33
31
|
from ..util.types import (
|
34
32
|
HoleBound,
|
35
33
|
LegacyGroupIdType,
|
@@ -40,12 +38,11 @@ from ..util.types import (
|
|
40
38
|
MucAffiliation,
|
41
39
|
MucType,
|
42
40
|
)
|
43
|
-
from ..util.util import deprecated, timeit
|
41
|
+
from ..util.util import SubclassableOnce, deprecated, timeit
|
44
42
|
from .archive import MessageArchive
|
45
43
|
from .participant import LegacyParticipant, escape_nickname
|
46
44
|
|
47
45
|
if TYPE_CHECKING:
|
48
|
-
from ..core.gateway import BaseGateway
|
49
46
|
from ..core.session import BaseSession
|
50
47
|
|
51
48
|
ADMIN_NS = "http://jabber.org/protocol/muc#admin"
|
@@ -57,14 +54,12 @@ class LegacyMUC(
|
|
57
54
|
Generic[
|
58
55
|
LegacyGroupIdType, LegacyMessageType, LegacyParticipantType, LegacyUserIdType
|
59
56
|
],
|
60
|
-
UpdateInfoMixin,
|
61
|
-
StoredAttributeMixin,
|
62
57
|
AvatarMixin,
|
63
58
|
NamedLockMixin,
|
64
59
|
ChatterDiscoMixin,
|
65
60
|
ReactionRecipientMixin,
|
66
61
|
ThreadRecipientMixin,
|
67
|
-
metaclass=
|
62
|
+
metaclass=SubclassableOnce,
|
68
63
|
):
|
69
64
|
"""
|
70
65
|
A room, a.k.a. a Multi-User Chat.
|
@@ -75,12 +70,10 @@ class LegacyMUC(
|
|
75
70
|
|
76
71
|
max_history_fetch = 100
|
77
72
|
|
78
|
-
type = MucType.CHANNEL
|
79
73
|
is_group = True
|
80
74
|
|
81
75
|
DISCO_TYPE = "text"
|
82
76
|
DISCO_CATEGORY = "conference"
|
83
|
-
DISCO_NAME = "unnamed-room"
|
84
77
|
|
85
78
|
STABLE_ARCHIVE = False
|
86
79
|
"""
|
@@ -128,152 +121,209 @@ class LegacyMUC(
|
|
128
121
|
tries to set the room subject.
|
129
122
|
"""
|
130
123
|
|
131
|
-
_avatar_bare_jid = True
|
132
124
|
archive: MessageArchive
|
125
|
+
session: "BaseSession"
|
133
126
|
|
134
|
-
|
135
|
-
self.session = session
|
136
|
-
self.xmpp: "BaseGateway" = session.xmpp
|
127
|
+
stored: Room
|
137
128
|
|
138
|
-
|
139
|
-
self.jid = jid
|
129
|
+
_participant_cls: Type[LegacyParticipantType]
|
140
130
|
|
141
|
-
|
131
|
+
def __init__(self, session: "BaseSession", stored: Room) -> None:
|
132
|
+
self.session = session
|
133
|
+
self.xmpp = session.xmpp
|
134
|
+
self.stored = stored
|
135
|
+
self._set_logger()
|
136
|
+
super().__init__()
|
142
137
|
|
143
|
-
self.
|
138
|
+
self.archive = MessageArchive(stored, self.xmpp.store.mam)
|
144
139
|
|
145
|
-
|
146
|
-
self
|
140
|
+
def participant_from_store(
|
141
|
+
self, stored: Participant, contact: LegacyContact | None = None
|
142
|
+
) -> LegacyParticipantType:
|
143
|
+
if contact is None and stored.contact is not None:
|
144
|
+
contact = self.session.contacts.from_store(stored.contact)
|
145
|
+
return self._participant_cls(self, stored=stored, contact=contact)
|
146
|
+
|
147
|
+
@property
|
148
|
+
def jid(self) -> JID:
|
149
|
+
return self.stored.jid
|
147
150
|
|
148
|
-
|
149
|
-
|
151
|
+
@jid.setter
|
152
|
+
def jid(self, x: JID):
|
153
|
+
# FIXME: without this, mypy yields
|
154
|
+
# "Cannot override writeable attribute with read-only property"
|
155
|
+
# But it does not happen for LegacyContact. WTF?
|
156
|
+
raise RuntimeError
|
150
157
|
|
151
|
-
|
152
|
-
|
153
|
-
self.
|
154
|
-
self._subject_date: Optional[datetime] = None
|
158
|
+
@property
|
159
|
+
def legacy_id(self):
|
160
|
+
return self.xmpp.LEGACY_ROOM_ID_TYPE(self.stored.legacy_id)
|
155
161
|
|
156
|
-
|
157
|
-
|
162
|
+
def orm(self) -> OrmSession:
|
163
|
+
return self.xmpp.store.session()
|
158
164
|
|
159
|
-
|
165
|
+
@property
|
166
|
+
def type(self) -> MucType:
|
167
|
+
return self.stored.muc_type
|
160
168
|
|
161
|
-
|
162
|
-
|
163
|
-
|
169
|
+
@type.setter
|
170
|
+
def type(self, type_: MucType) -> None:
|
171
|
+
if self.type == type_:
|
172
|
+
return
|
173
|
+
self.stored.muc_type = type_
|
174
|
+
self.commit()
|
164
175
|
|
165
176
|
@property
|
166
177
|
def n_participants(self):
|
167
|
-
return self.
|
178
|
+
return self.stored.n_participants
|
168
179
|
|
169
180
|
@n_participants.setter
|
170
|
-
def n_participants(self, n_participants: Optional[int]):
|
171
|
-
if self.
|
172
|
-
return
|
173
|
-
self._n_participants = n_participants
|
174
|
-
if self._updating_info:
|
181
|
+
def n_participants(self, n_participants: Optional[int]) -> None:
|
182
|
+
if self.stored.n_participants == n_participants:
|
175
183
|
return
|
176
|
-
|
177
|
-
self.
|
184
|
+
self.stored.n_participants = n_participants
|
185
|
+
self.commit()
|
178
186
|
|
179
187
|
@property
|
180
188
|
def user_jid(self):
|
181
189
|
return self.session.user_jid
|
182
190
|
|
183
|
-
def
|
191
|
+
def _set_logger(self) -> None:
|
184
192
|
self.log = logging.getLogger(f"{self.user_jid}:muc:{self}")
|
185
193
|
|
186
|
-
def __repr__(self):
|
187
|
-
return f"<MUC #{self.
|
194
|
+
def __repr__(self) -> str:
|
195
|
+
return f"<MUC #{self.stored.id} '{self.name}' ({self.stored.legacy_id} - {self.jid.user})'>"
|
188
196
|
|
189
197
|
@property
|
190
198
|
def subject_date(self) -> Optional[datetime]:
|
191
|
-
|
199
|
+
if self.stored.subject_date is None:
|
200
|
+
return None
|
201
|
+
return self.stored.subject_date.replace(tzinfo=timezone.utc)
|
192
202
|
|
193
203
|
@subject_date.setter
|
194
204
|
def subject_date(self, when: Optional[datetime]) -> None:
|
195
|
-
self.
|
196
|
-
if self._updating_info:
|
205
|
+
if self.subject_date == when:
|
197
206
|
return
|
198
|
-
|
199
|
-
self.
|
207
|
+
self.stored.subject_date = when
|
208
|
+
self.commit()
|
200
209
|
|
201
|
-
def __send_configuration_change(self, codes):
|
210
|
+
def __send_configuration_change(self, codes) -> None:
|
202
211
|
part = self.get_system_participant()
|
203
212
|
part.send_configuration_change(codes)
|
204
213
|
|
205
214
|
@property
|
206
215
|
def user_nick(self):
|
207
|
-
return
|
216
|
+
return (
|
217
|
+
self.stored.user_nick
|
218
|
+
or self.session.bookmarks.user_nick
|
219
|
+
or self.user_jid.node
|
220
|
+
)
|
208
221
|
|
209
222
|
@user_nick.setter
|
210
|
-
def user_nick(self, nick: str):
|
211
|
-
self.
|
212
|
-
|
213
|
-
|
223
|
+
def user_nick(self, nick: str) -> None:
|
224
|
+
if nick == self.user_nick:
|
225
|
+
return
|
226
|
+
self.stored.user_nick = nick
|
227
|
+
self.commit()
|
214
228
|
|
215
229
|
def add_user_resource(self, resource: str) -> None:
|
216
|
-
self.
|
217
|
-
|
218
|
-
|
230
|
+
stored_set = self.get_user_resources()
|
231
|
+
if resource in stored_set:
|
232
|
+
return
|
233
|
+
stored_set.add(resource)
|
234
|
+
self.stored.user_resources = (
|
235
|
+
json.dumps(list(stored_set)) if stored_set else None
|
236
|
+
)
|
237
|
+
self.commit()
|
219
238
|
|
220
239
|
def get_user_resources(self) -> set[str]:
|
221
|
-
|
240
|
+
stored_str = self.stored.user_resources
|
241
|
+
if stored_str is None:
|
242
|
+
return set()
|
243
|
+
return set(json.loads(stored_str))
|
222
244
|
|
223
245
|
def remove_user_resource(self, resource: str) -> None:
|
224
|
-
self.
|
225
|
-
|
226
|
-
self.__store.set_resource(self.pk, self._user_resources)
|
227
|
-
|
228
|
-
async def __fill_participants(self):
|
229
|
-
if self._participants_filled:
|
230
|
-
return
|
231
|
-
assert self.pk is not None
|
232
|
-
async with self.lock("fill participants"):
|
233
|
-
self._participants_filled = True
|
234
|
-
async for p in self.fill_participants():
|
235
|
-
self.__participants_store.update(p)
|
236
|
-
self.__store.set_participants_filled(self.pk)
|
237
|
-
|
238
|
-
async def get_participants(self) -> AsyncIterator[LegacyParticipant]:
|
239
|
-
assert self.pk is not None
|
240
|
-
if self._participants_filled:
|
241
|
-
for db_participant in self.xmpp.store.participants.get_all(
|
242
|
-
self.pk, user_included=True
|
243
|
-
):
|
244
|
-
participant = self.Participant.from_store(
|
245
|
-
self.session, db_participant, muc=self
|
246
|
-
)
|
247
|
-
yield participant
|
246
|
+
stored_set = self.get_user_resources()
|
247
|
+
if resource not in stored_set:
|
248
248
|
return
|
249
|
+
stored_set.remove(resource)
|
250
|
+
self.stored.user_resources = (
|
251
|
+
json.dumps(list(stored_set)) if stored_set else None
|
252
|
+
)
|
253
|
+
self.commit()
|
249
254
|
|
255
|
+
async def __fill_participants(self) -> None:
|
256
|
+
if self.participants_filled:
|
257
|
+
return
|
250
258
|
async with self.lock("fill participants"):
|
251
|
-
|
252
|
-
|
253
|
-
# joined by an XMPP client. But we may have instantiated some before.
|
254
|
-
resources = set[str]()
|
259
|
+
parts: list[Participant] = []
|
260
|
+
resources: set[str] = set()
|
255
261
|
async for participant in self.fill_participants():
|
256
|
-
|
257
|
-
|
258
|
-
|
262
|
+
if participant.stored.id is not None:
|
263
|
+
continue
|
264
|
+
# During fill_participants(), self.get_participant*() methods may
|
265
|
+
# return a participant with a conflicting nick/resource. There is
|
266
|
+
# a better way to fix this than the logic below, but this better way
|
267
|
+
# has not been found yet.
|
268
|
+
if participant.jid.resource in resources:
|
269
|
+
if participant.contact is None:
|
270
|
+
self.log.warning(
|
271
|
+
"Ditching participant %s", participant.nickname
|
272
|
+
)
|
273
|
+
del participant
|
274
|
+
continue
|
275
|
+
else:
|
276
|
+
nickname = (
|
277
|
+
f"{participant.nickname} ({participant.contact.jid.node})"
|
278
|
+
)
|
279
|
+
participant = self._participant_cls(
|
280
|
+
self,
|
281
|
+
Participant(nickname=nickname, room=self.stored),
|
282
|
+
contact=participant.contact,
|
283
|
+
)
|
259
284
|
resources.add(participant.jid.resource)
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
self.
|
269
|
-
|
285
|
+
parts.append(participant.stored)
|
286
|
+
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
287
|
+
# FIXME: something must be wrong with all these refreshes and merge,
|
288
|
+
# but I did not manage to get rid of them without getting various
|
289
|
+
# sqlalchemy exceptions raised everywhere
|
290
|
+
orm.add(self.stored)
|
291
|
+
orm.refresh(self.stored)
|
292
|
+
known = {p.resource for p in self.stored.participants}
|
293
|
+
self.stored.participants_filled = True
|
294
|
+
for part in parts:
|
295
|
+
if part.resource in known:
|
296
|
+
continue
|
297
|
+
part = orm.merge(part)
|
298
|
+
orm.add(part)
|
299
|
+
self.stored.participants.append(part)
|
300
|
+
orm.commit()
|
301
|
+
orm.refresh(self.stored)
|
302
|
+
|
303
|
+
async def get_participants(
|
304
|
+
self, affiliation: Optional[MucAffiliation] = None
|
305
|
+
) -> AsyncIterator[LegacyParticipantType]:
|
306
|
+
await self.__fill_participants()
|
307
|
+
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
308
|
+
orm.add(self.stored)
|
309
|
+
for db_participant in self.stored.participants:
|
310
|
+
if (
|
311
|
+
affiliation is not None
|
312
|
+
and db_participant.affiliation != affiliation
|
313
|
+
):
|
314
|
+
continue
|
315
|
+
yield self.participant_from_store(db_participant)
|
270
316
|
|
271
|
-
async def __fill_history(self):
|
317
|
+
async def __fill_history(self) -> None:
|
318
|
+
if self.stored.history_filled:
|
319
|
+
self.log.debug("History has already been fetched.")
|
320
|
+
return
|
272
321
|
async with self.lock("fill history"):
|
273
|
-
if self._history_filled:
|
274
|
-
log.debug("History has already been fetched %s", self)
|
275
|
-
return
|
276
322
|
log.debug("Fetching history for %s", self)
|
323
|
+
if not self.KEEP_BACKFILLED_PARTICIPANTS:
|
324
|
+
with self.xmpp.store.session() as orm:
|
325
|
+
orm.add(self.stored)
|
326
|
+
participants = list(self.stored.participants)
|
277
327
|
try:
|
278
328
|
before, after = self.archive.get_hole_bounds()
|
279
329
|
if before is not None:
|
@@ -288,43 +338,42 @@ class LegacyMUC(
|
|
288
338
|
except NotImplementedError:
|
289
339
|
return
|
290
340
|
except Exception as e:
|
291
|
-
log.exception("Could not backfill
|
292
|
-
|
293
|
-
|
294
|
-
self.
|
341
|
+
self.log.exception("Could not backfill", exc_info=e)
|
342
|
+
if not self.KEEP_BACKFILLED_PARTICIPANTS:
|
343
|
+
self.stored.participants = participants
|
344
|
+
self.stored.history_filled = True
|
345
|
+
self.commit(merge=True)
|
346
|
+
|
347
|
+
@property
|
348
|
+
def DISCO_NAME(self) -> str: # type:ignore
|
349
|
+
return self.name or "unnamed-room"
|
295
350
|
|
296
351
|
@property
|
297
|
-
def name(self):
|
298
|
-
return self.
|
352
|
+
def name(self) -> str:
|
353
|
+
return self.stored.name or "unnamed-room"
|
299
354
|
|
300
355
|
@name.setter
|
301
|
-
def name(self, n: str):
|
302
|
-
if self.
|
356
|
+
def name(self, n: str) -> None:
|
357
|
+
if self.name == n:
|
303
358
|
return
|
304
|
-
self.
|
305
|
-
self.
|
359
|
+
self.stored.name = n
|
360
|
+
self.commit()
|
361
|
+
self._set_logger()
|
306
362
|
self.__send_configuration_change((104,))
|
307
|
-
if self._updating_info:
|
308
|
-
return
|
309
|
-
assert self.pk is not None
|
310
|
-
self.__store.update_name(self.pk, n)
|
311
363
|
|
312
364
|
@property
|
313
365
|
def description(self):
|
314
|
-
return self.
|
366
|
+
return self.stored.description or ""
|
315
367
|
|
316
368
|
@description.setter
|
317
|
-
def description(self, d: str):
|
318
|
-
if self.
|
369
|
+
def description(self, d: str) -> None:
|
370
|
+
if self.description == d:
|
319
371
|
return
|
320
|
-
self.
|
372
|
+
self.stored.description = d
|
373
|
+
self.commit()
|
321
374
|
self.__send_configuration_change((104,))
|
322
|
-
if self._updating_info:
|
323
|
-
return
|
324
|
-
assert self.pk is not None
|
325
|
-
self.__store.update_description(self.pk, d)
|
326
375
|
|
327
|
-
def on_presence_unavailable(self, p: Presence):
|
376
|
+
def on_presence_unavailable(self, p: Presence) -> None:
|
328
377
|
pto = p.get_to()
|
329
378
|
if pto.bare != self.jid.bare:
|
330
379
|
return
|
@@ -332,7 +381,7 @@ class LegacyMUC(
|
|
332
381
|
pfrom = p.get_from()
|
333
382
|
if pfrom.bare != self.user_jid.bare:
|
334
383
|
return
|
335
|
-
if (resource := pfrom.resource) in self.
|
384
|
+
if (resource := pfrom.resource) in self.get_user_resources():
|
336
385
|
if pto.resource != self.user_nick:
|
337
386
|
self.log.debug(
|
338
387
|
"Received 'leave group' request but with wrong nickname. %s", p
|
@@ -381,7 +430,7 @@ class LegacyMUC(
|
|
381
430
|
"""
|
382
431
|
raise NotImplementedError
|
383
432
|
|
384
|
-
async def fill_participants(self) -> AsyncIterator[
|
433
|
+
async def fill_participants(self) -> AsyncIterator[LegacyParticipantType]:
|
385
434
|
"""
|
386
435
|
This method should yield the list of all members of this group.
|
387
436
|
|
@@ -394,29 +443,26 @@ class LegacyMUC(
|
|
394
443
|
|
395
444
|
@property
|
396
445
|
def subject(self):
|
397
|
-
return self.
|
446
|
+
return self.stored.subject
|
398
447
|
|
399
448
|
@subject.setter
|
400
|
-
def subject(self, s: str):
|
401
|
-
if s == self.
|
449
|
+
def subject(self, s: str) -> None:
|
450
|
+
if s == self.subject:
|
402
451
|
return
|
452
|
+
|
453
|
+
self.stored.subject = s
|
454
|
+
self.commit()
|
403
455
|
self.__get_subject_setter_participant().set_room_subject(
|
404
456
|
s, None, self.subject_date, False
|
405
457
|
)
|
406
458
|
|
407
|
-
self._subject = s
|
408
|
-
if self._updating_info:
|
409
|
-
return
|
410
|
-
assert self.pk is not None
|
411
|
-
self.__store.update_subject(self.pk, s)
|
412
|
-
|
413
459
|
@property
|
414
460
|
def is_anonymous(self):
|
415
461
|
return self.type == MucType.CHANNEL
|
416
462
|
|
417
463
|
@property
|
418
464
|
def subject_setter(self) -> Optional[str]:
|
419
|
-
return self.
|
465
|
+
return self.stored.subject_setter
|
420
466
|
|
421
467
|
@subject_setter.setter
|
422
468
|
def subject_setter(self, subject_setter: SubjectSetterType) -> None:
|
@@ -425,19 +471,16 @@ class LegacyMUC(
|
|
425
471
|
elif isinstance(subject_setter, LegacyParticipant):
|
426
472
|
subject_setter = subject_setter.nickname
|
427
473
|
|
428
|
-
if subject_setter == self.
|
474
|
+
if subject_setter == self.subject_setter:
|
429
475
|
return
|
430
476
|
assert isinstance(subject_setter, str | None)
|
431
|
-
self.
|
432
|
-
|
433
|
-
return
|
434
|
-
assert self.pk is not None
|
435
|
-
self.__store.update_subject_setter(self.pk, subject_setter)
|
477
|
+
self.stored.subject_setter = subject_setter
|
478
|
+
self.commit()
|
436
479
|
|
437
480
|
def __get_subject_setter_participant(self) -> LegacyParticipant:
|
438
|
-
if self.
|
481
|
+
if self.subject_setter is None:
|
439
482
|
return self.get_system_participant()
|
440
|
-
return self.
|
483
|
+
return self._participant_cls(self, Participant(nickname=self.subject_setter))
|
441
484
|
|
442
485
|
def features(self):
|
443
486
|
features = [
|
@@ -475,11 +518,13 @@ class LegacyMUC(
|
|
475
518
|
form.add_field("muc#maxhistoryfetch", value=str(self.max_history_fetch))
|
476
519
|
form.add_field("muc#roominfo_subjectmod", "boolean", value=False)
|
477
520
|
|
478
|
-
if self._ALL_INFO_FILLED_ON_STARTUP or self.
|
479
|
-
|
480
|
-
|
521
|
+
if self._ALL_INFO_FILLED_ON_STARTUP or self.stored.participants_filled:
|
522
|
+
with self.xmpp.store.session() as orm:
|
523
|
+
n = orm.scalar(
|
524
|
+
sa.select(sa.func.count(Participant.id)).filter_by(room=self.stored)
|
525
|
+
)
|
481
526
|
else:
|
482
|
-
n = self.
|
527
|
+
n = self.n_participants
|
483
528
|
if n is not None:
|
484
529
|
form.add_field("muc#roominfo_occupants", value=str(n))
|
485
530
|
|
@@ -489,14 +534,14 @@ class LegacyMUC(
|
|
489
534
|
if s := self.subject:
|
490
535
|
form.add_field("muc#roominfo_subject", value=s)
|
491
536
|
|
492
|
-
if self._set_avatar_task:
|
537
|
+
if self._set_avatar_task is not None:
|
493
538
|
await self._set_avatar_task
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
539
|
+
avatar = self.get_avatar()
|
540
|
+
if avatar and (h := avatar.id):
|
541
|
+
form.add_field(
|
542
|
+
"{http://modules.prosody.im/mod_vcard_muc}avatar#sha1", value=h
|
543
|
+
)
|
544
|
+
form.add_field("muc#roominfo_avatarhash", "text-multi", value=[h])
|
500
545
|
|
501
546
|
form.add_field("muc#roomconfig_membersonly", "boolean", value=is_group)
|
502
547
|
form.add_field(
|
@@ -514,7 +559,7 @@ class LegacyMUC(
|
|
514
559
|
|
515
560
|
return r
|
516
561
|
|
517
|
-
def shutdown(self):
|
562
|
+
def shutdown(self) -> None:
|
518
563
|
_, user_jid = escape_nickname(self.jid, self.user_nick)
|
519
564
|
for user_full_jid in self.user_full_jids():
|
520
565
|
presence = self.xmpp.make_presence(
|
@@ -526,7 +571,7 @@ class LegacyMUC(
|
|
526
571
|
presence.send()
|
527
572
|
|
528
573
|
def user_full_jids(self):
|
529
|
-
for r in self.
|
574
|
+
for r in self.get_user_resources():
|
530
575
|
j = JID(self.user_jid)
|
531
576
|
j.resource = r
|
532
577
|
yield j
|
@@ -536,14 +581,9 @@ class LegacyMUC(
|
|
536
581
|
_, user_muc_jid = escape_nickname(self.jid, self.user_nick)
|
537
582
|
return user_muc_jid
|
538
583
|
|
539
|
-
def _legacy_to_xmpp(self, legacy_id: LegacyMessageType):
|
540
|
-
return self.xmpp.store.sent.get_group_xmpp_id(
|
541
|
-
self.session.user_pk, str(legacy_id)
|
542
|
-
) or self.session.legacy_to_xmpp_msg_id(legacy_id)
|
543
|
-
|
544
584
|
async def echo(
|
545
585
|
self, msg: Message, legacy_msg_id: Optional[LegacyMessageType] = None
|
546
|
-
):
|
586
|
+
) -> None:
|
547
587
|
origin_id = msg.get_origin_id()
|
548
588
|
|
549
589
|
msg.set_from(self.user_muc_jid)
|
@@ -568,24 +608,11 @@ class LegacyMUC(
|
|
568
608
|
|
569
609
|
msg.send()
|
570
610
|
|
571
|
-
def
|
572
|
-
if self.pk is None:
|
573
|
-
return None
|
574
|
-
return self.xmpp.store.rooms.get_avatar_legacy_id(self.pk)
|
575
|
-
|
576
|
-
def _post_avatar_update(self) -> None:
|
577
|
-
if self.pk is None:
|
578
|
-
return
|
579
|
-
assert self.pk is not None
|
580
|
-
self.xmpp.store.rooms.set_avatar(
|
581
|
-
self.pk,
|
582
|
-
self._avatar_pk,
|
583
|
-
None if self.avatar_id is None else str(self.avatar_id),
|
584
|
-
)
|
611
|
+
def _post_avatar_update(self, cached_avatar) -> None:
|
585
612
|
self.__send_configuration_change((104,))
|
586
613
|
self._send_room_presence()
|
587
614
|
|
588
|
-
def _send_room_presence(self, user_full_jid: Optional[JID] = None):
|
615
|
+
def _send_room_presence(self, user_full_jid: Optional[JID] = None) -> None:
|
589
616
|
if user_full_jid is None:
|
590
617
|
tos = self.user_full_jids()
|
591
618
|
else:
|
@@ -599,20 +626,19 @@ class LegacyMUC(
|
|
599
626
|
p.send()
|
600
627
|
|
601
628
|
@timeit
|
602
|
-
@with_session
|
603
629
|
async def join(self, join_presence: Presence):
|
604
630
|
user_full_jid = join_presence.get_from()
|
605
631
|
requested_nickname = join_presence.get_to().resource
|
606
632
|
client_resource = user_full_jid.resource
|
607
633
|
|
608
|
-
if client_resource in self.
|
634
|
+
if client_resource in self.get_user_resources():
|
609
635
|
self.log.debug("Received join from a resource that is already joined.")
|
610
636
|
|
611
|
-
self.add_user_resource(client_resource)
|
612
|
-
|
613
637
|
if not requested_nickname or not client_resource:
|
614
638
|
raise XMPPError("jid-malformed", by=self.jid)
|
615
639
|
|
640
|
+
self.add_user_resource(client_resource)
|
641
|
+
|
616
642
|
self.log.debug(
|
617
643
|
"Resource %s of %s wants to join room %s with nickname %s",
|
618
644
|
client_resource,
|
@@ -631,7 +657,7 @@ class LegacyMUC(
|
|
631
657
|
|
632
658
|
if user_participant is None:
|
633
659
|
user_participant = await self.get_user_participant()
|
634
|
-
if not user_participant.is_user:
|
660
|
+
if not user_participant.is_user:
|
635
661
|
self.log.warning("is_user flag not set participant on user_participant")
|
636
662
|
user_participant.is_user = True # type:ignore
|
637
663
|
user_participant.send_initial_presence(
|
@@ -662,7 +688,7 @@ class LegacyMUC(
|
|
662
688
|
since=since,
|
663
689
|
)
|
664
690
|
self.__get_subject_setter_participant().set_room_subject(
|
665
|
-
self.
|
691
|
+
self.subject if self.HAS_SUBJECT else (self.description or self.name),
|
666
692
|
user_full_jid,
|
667
693
|
self.subject_date,
|
668
694
|
)
|
@@ -683,22 +709,17 @@ class LegacyMUC(
|
|
683
709
|
return p
|
684
710
|
|
685
711
|
def __store_participant(self, p: "LegacyParticipantType") -> None:
|
686
|
-
|
687
|
-
if not self.KEEP_BACKFILLED_PARTICIPANTS and self.get_lock("fill history"):
|
712
|
+
if self.get_lock("fill participants"):
|
688
713
|
return
|
689
|
-
|
690
|
-
p.pk = self.__participants_store.add(self.pk, p.nickname)
|
691
|
-
self.__participants_store.update(p)
|
692
|
-
if p._hats:
|
693
|
-
self.__participants_store.set_hats(p.pk, p._hats)
|
714
|
+
p.commit(merge=True)
|
694
715
|
|
695
716
|
async def get_participant(
|
696
717
|
self,
|
697
718
|
nickname: str,
|
698
|
-
raise_if_not_found=False,
|
699
|
-
fill_first=False,
|
700
|
-
store=True,
|
701
|
-
|
719
|
+
raise_if_not_found: bool = False,
|
720
|
+
fill_first: bool = False,
|
721
|
+
store: bool = True,
|
722
|
+
is_user: bool = False,
|
702
723
|
) -> "LegacyParticipantType":
|
703
724
|
"""
|
704
725
|
Get a participant by their nickname.
|
@@ -713,30 +734,34 @@ class LegacyMUC(
|
|
713
734
|
:param fill_first: Ensure :meth:`.LegacyMUC.fill_participants()` has been called first
|
714
735
|
(internal use by slidge, plugins should not need that)
|
715
736
|
:param store: persistently store the user in the list of MUC participants
|
716
|
-
:param kwargs: additional parameters for the :class:`.Participant`
|
717
|
-
construction (optional)
|
718
737
|
:return:
|
719
738
|
"""
|
720
|
-
if fill_first
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
self.
|
727
|
-
|
728
|
-
|
729
|
-
|
739
|
+
if fill_first:
|
740
|
+
await self.__fill_participants()
|
741
|
+
with self.xmpp.store.session() as orm:
|
742
|
+
stored = (
|
743
|
+
orm.query(Participant)
|
744
|
+
.filter(
|
745
|
+
Participant.room == self.stored,
|
746
|
+
(Participant.nickname == nickname)
|
747
|
+
| (Participant.resource == nickname),
|
748
|
+
)
|
749
|
+
.one_or_none()
|
750
|
+
)
|
751
|
+
if stored is not None:
|
752
|
+
return self.participant_from_store(stored)
|
730
753
|
|
731
754
|
if raise_if_not_found:
|
732
755
|
raise XMPPError("item-not-found")
|
733
|
-
p = self.
|
734
|
-
|
756
|
+
p = self._participant_cls(
|
757
|
+
self, Participant(room=self.stored, nickname=nickname, is_user=is_user)
|
758
|
+
)
|
759
|
+
if store:
|
735
760
|
self.__store_participant(p)
|
736
761
|
if (
|
737
762
|
not self.get_lock("fill participants")
|
738
763
|
and not self.get_lock("fill history")
|
739
|
-
and self.
|
764
|
+
and self.stored.participants_filled
|
740
765
|
and not p.is_user
|
741
766
|
and not p.is_system
|
742
767
|
):
|
@@ -752,10 +777,10 @@ class LegacyMUC(
|
|
752
777
|
service
|
753
778
|
:return:
|
754
779
|
"""
|
755
|
-
return self.
|
780
|
+
return self._participant_cls(self, Participant(), is_system=True)
|
756
781
|
|
757
782
|
async def get_participant_by_contact(
|
758
|
-
self, c: "LegacyContact"
|
783
|
+
self, c: "LegacyContact"
|
759
784
|
) -> "LegacyParticipantType":
|
760
785
|
"""
|
761
786
|
Get a non-anonymous participant.
|
@@ -764,37 +789,39 @@ class LegacyMUC(
|
|
764
789
|
that the Contact jid is associated to this participant
|
765
790
|
|
766
791
|
:param c: The :class:`.LegacyContact` instance corresponding to this contact
|
767
|
-
:param kwargs: additional parameters for the :class:`.Participant`
|
768
|
-
construction (optional)
|
769
792
|
:return:
|
770
793
|
"""
|
771
794
|
await self.session.contacts.ready
|
772
795
|
|
773
|
-
if self.
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
796
|
+
if not self.get_lock("fill participants"):
|
797
|
+
with self.xmpp.store.session() as orm:
|
798
|
+
self.stored = orm.merge(self.stored)
|
799
|
+
stored = (
|
800
|
+
orm.query(Participant)
|
801
|
+
.filter_by(contact=c.stored, room=self.stored)
|
802
|
+
.one_or_none()
|
803
|
+
)
|
778
804
|
if stored is not None:
|
779
|
-
return self.
|
780
|
-
self.session, stored, muc=self, contact=c
|
781
|
-
)
|
805
|
+
return self.participant_from_store(stored=stored, contact=c)
|
782
806
|
|
783
|
-
nickname = c.name or unescape_node(c.
|
807
|
+
nickname = c.name or unescape_node(c.jid.node)
|
784
808
|
|
785
|
-
if self.
|
809
|
+
if self.stored.id is None:
|
786
810
|
nick_available = True
|
787
811
|
else:
|
788
|
-
|
812
|
+
with self.xmpp.store.session() as orm:
|
813
|
+
nick_available = (
|
814
|
+
orm.query(Participant.id).filter_by(
|
815
|
+
room=self.stored, nickname=nickname
|
816
|
+
)
|
817
|
+
).one_or_none() is None
|
789
818
|
|
790
819
|
if not nick_available:
|
791
820
|
self.log.debug("Nickname conflict")
|
792
|
-
nickname = f"{nickname} ({c.
|
793
|
-
p = self.
|
794
|
-
|
795
|
-
|
796
|
-
if self._updating_info:
|
797
|
-
return p
|
821
|
+
nickname = f"{nickname} ({c.jid.node})"
|
822
|
+
p = self._participant_cls(
|
823
|
+
self, Participant(nickname=nickname, room=self.stored), contact=c
|
824
|
+
)
|
798
825
|
|
799
826
|
self.__store_participant(p)
|
800
827
|
# FIXME: this is not great but given the current design,
|
@@ -803,7 +830,7 @@ class LegacyMUC(
|
|
803
830
|
# and role afterwards.
|
804
831
|
# We need a refactor of the MUC class… later™
|
805
832
|
if (
|
806
|
-
self.
|
833
|
+
self.stored.participants_filled
|
807
834
|
and not self.get_lock("fill participants")
|
808
835
|
and not self.get_lock("fill history")
|
809
836
|
):
|
@@ -811,19 +838,19 @@ class LegacyMUC(
|
|
811
838
|
return p
|
812
839
|
|
813
840
|
async def get_participant_by_legacy_id(
|
814
|
-
self, legacy_id: LegacyUserIdType
|
841
|
+
self, legacy_id: LegacyUserIdType
|
815
842
|
) -> "LegacyParticipantType":
|
816
843
|
try:
|
817
844
|
c = await self.session.contacts.by_legacy_id(legacy_id)
|
818
845
|
except ContactIsUser:
|
819
|
-
return await self.get_user_participant(
|
820
|
-
return await self.get_participant_by_contact(c
|
846
|
+
return await self.get_user_participant()
|
847
|
+
return await self.get_participant_by_contact(c)
|
821
848
|
|
822
849
|
def remove_participant(
|
823
850
|
self,
|
824
851
|
p: "LegacyParticipantType",
|
825
|
-
kick=False,
|
826
|
-
ban=False,
|
852
|
+
kick: bool = False,
|
853
|
+
ban: bool = False,
|
827
854
|
reason: str | None = None,
|
828
855
|
):
|
829
856
|
"""
|
@@ -836,7 +863,9 @@ class LegacyMUC(
|
|
836
863
|
"""
|
837
864
|
if kick and ban:
|
838
865
|
raise TypeError("Either kick or ban")
|
839
|
-
self.
|
866
|
+
with self.xmpp.store.session() as orm:
|
867
|
+
orm.delete(p.stored)
|
868
|
+
orm.commit()
|
840
869
|
if kick:
|
841
870
|
codes = {307}
|
842
871
|
elif ban:
|
@@ -844,20 +873,23 @@ class LegacyMUC(
|
|
844
873
|
else:
|
845
874
|
codes = None
|
846
875
|
presence = p._make_presence(ptype="unavailable", status_codes=codes)
|
847
|
-
p.
|
848
|
-
p.
|
876
|
+
p.stored.affiliation = "outcast" if ban else "none"
|
877
|
+
p.stored.role = "none"
|
849
878
|
if reason:
|
850
879
|
presence["muc"].set_item_attr("reason", reason)
|
851
880
|
p._send(presence)
|
852
881
|
|
853
|
-
def rename_participant(self, old_nickname: str, new_nickname: str):
|
854
|
-
|
855
|
-
|
856
|
-
|
882
|
+
def rename_participant(self, old_nickname: str, new_nickname: str) -> None:
|
883
|
+
with self.xmpp.store.session() as orm:
|
884
|
+
stored = (
|
885
|
+
orm.query(Participant)
|
886
|
+
.filter_by(room=self.stored, nickname=old_nickname)
|
887
|
+
.one_or_none()
|
888
|
+
)
|
857
889
|
if stored is None:
|
858
890
|
self.log.debug("Tried to rename a participant that we didn't know")
|
859
891
|
return
|
860
|
-
p = self.
|
892
|
+
p = self.participant_from_store(stored)
|
861
893
|
if p.nickname == old_nickname:
|
862
894
|
p.nickname = new_nickname
|
863
895
|
|
@@ -868,7 +900,7 @@ class LegacyMUC(
|
|
868
900
|
maxstanzas: Optional[int] = None,
|
869
901
|
seconds: Optional[int] = None,
|
870
902
|
since: Optional[datetime] = None,
|
871
|
-
):
|
903
|
+
) -> None:
|
872
904
|
"""
|
873
905
|
Old-style history join (internal slidge use)
|
874
906
|
|
@@ -895,7 +927,7 @@ class LegacyMUC(
|
|
895
927
|
msg.set_to(full_jid)
|
896
928
|
self.xmpp.send(msg, False)
|
897
929
|
|
898
|
-
async def send_mam(self, iq: Iq):
|
930
|
+
async def send_mam(self, iq: Iq) -> None:
|
899
931
|
await self.__fill_history()
|
900
932
|
|
901
933
|
form_values = iq["mam"]["form"].get_values()
|
@@ -983,11 +1015,11 @@ class LegacyMUC(
|
|
983
1015
|
reply["mam_fin"]["rsm"]["count"] = str(count)
|
984
1016
|
reply.send()
|
985
1017
|
|
986
|
-
async def send_mam_metadata(self, iq: Iq):
|
1018
|
+
async def send_mam_metadata(self, iq: Iq) -> None:
|
987
1019
|
await self.__fill_history()
|
988
1020
|
await self.archive.send_metadata(iq)
|
989
1021
|
|
990
|
-
async def kick_resource(self, r: str):
|
1022
|
+
async def kick_resource(self, r: str) -> None:
|
991
1023
|
"""
|
992
1024
|
Kick a XMPP client of the user. (slidge internal use)
|
993
1025
|
|
@@ -1035,12 +1067,11 @@ class LegacyMUC(
|
|
1035
1067
|
|
1036
1068
|
async def add_to_bookmarks(
|
1037
1069
|
self,
|
1038
|
-
auto_join=True,
|
1039
|
-
|
1040
|
-
preserve=True,
|
1070
|
+
auto_join: bool = True,
|
1071
|
+
preserve: bool = True,
|
1041
1072
|
pin: bool | None = None,
|
1042
1073
|
notify: WhenLiteral | None = None,
|
1043
|
-
):
|
1074
|
+
) -> None:
|
1044
1075
|
"""
|
1045
1076
|
Add the MUC to the user's XMPP bookmarks (:xep:`0402')
|
1046
1077
|
|
@@ -1051,11 +1082,6 @@ class LegacyMUC(
|
|
1051
1082
|
this MUC on startup. In theory, XMPP clients will receive
|
1052
1083
|
a "push" notification when this is called, and they will
|
1053
1084
|
join if they are online.
|
1054
|
-
:param invite: send an invitation to join this MUC emanating from
|
1055
|
-
the gateway. While this should not be strictly necessary,
|
1056
|
-
it can help for clients that do not support :xep:`0402`, or
|
1057
|
-
that have 'do not honor bookmarks auto-join' turned on in their
|
1058
|
-
settings.
|
1059
1085
|
:param preserve: preserve auto-join and bookmarks extensions
|
1060
1086
|
set by the user outside slidge
|
1061
1087
|
:param pin: Pin the group chat bookmark :xep:`0469`. Requires privileged entity.
|
@@ -1136,20 +1162,32 @@ class LegacyMUC(
|
|
1136
1162
|
"IQ privileges (XEP0356) are not set, we cannot add bookmarks for the user"
|
1137
1163
|
)
|
1138
1164
|
# fallback by forcing invitation
|
1139
|
-
|
1165
|
+
bookmark_add_fail = True
|
1140
1166
|
except IqError as e:
|
1141
1167
|
warnings.warn(
|
1142
1168
|
f"Something went wrong while trying to set the bookmarks: {e}"
|
1143
1169
|
)
|
1144
1170
|
# fallback by forcing invitation
|
1145
|
-
|
1171
|
+
bookmark_add_fail = True
|
1172
|
+
else:
|
1173
|
+
bookmark_add_fail = False
|
1146
1174
|
else:
|
1147
1175
|
self.log.debug("Bookmark does not need updating.")
|
1148
1176
|
return
|
1149
1177
|
|
1150
|
-
if
|
1178
|
+
if bookmark_add_fail:
|
1151
1179
|
self.session.send_gateway_invite(
|
1152
|
-
self,
|
1180
|
+
self,
|
1181
|
+
reason="This group could not be added automatically for you, most"
|
1182
|
+
"likely because this gateway is not configured as a privileged entity. "
|
1183
|
+
"Contact your administrator.",
|
1184
|
+
)
|
1185
|
+
elif existing is None and self.session.user.preferences.get(
|
1186
|
+
"always_invite_when_adding_bookmarks", True
|
1187
|
+
):
|
1188
|
+
self.session.send_gateway_invite(
|
1189
|
+
self,
|
1190
|
+
reason="The gateway is configured to always send invitations for groups.",
|
1153
1191
|
)
|
1154
1192
|
|
1155
1193
|
async def on_avatar(
|
@@ -1238,12 +1276,10 @@ class LegacyMUC(
|
|
1238
1276
|
raise NotImplementedError
|
1239
1277
|
|
1240
1278
|
async def parse_mentions(self, text: str) -> list[Mention]:
|
1241
|
-
with self.
|
1279
|
+
with self.xmpp.store.session() as orm:
|
1242
1280
|
await self.__fill_participants()
|
1243
|
-
|
1244
|
-
participants = {
|
1245
|
-
p.nickname: p for p in self.__participants_store.get_all(self.pk)
|
1246
|
-
}
|
1281
|
+
orm.add(self.stored)
|
1282
|
+
participants = {p.nickname: p for p in self.stored.participants}
|
1247
1283
|
|
1248
1284
|
if len(participants) == 0:
|
1249
1285
|
return []
|
@@ -1264,8 +1300,8 @@ class LegacyMUC(
|
|
1264
1300
|
if span[0] != 0 and text[span[0] - 1] not in _WHITESPACE_OR_PUNCTUATION:
|
1265
1301
|
continue
|
1266
1302
|
if span[1] == len(text) or text[span[1]] in _WHITESPACE_OR_PUNCTUATION:
|
1267
|
-
participant = self.
|
1268
|
-
|
1303
|
+
participant = self.participant_from_store(
|
1304
|
+
stored=participants[nick],
|
1269
1305
|
)
|
1270
1306
|
if contact := participant.contact:
|
1271
1307
|
result.append(
|
@@ -1284,45 +1320,12 @@ class LegacyMUC(
|
|
1284
1320
|
"""
|
1285
1321
|
raise NotImplementedError
|
1286
1322
|
|
1287
|
-
@
|
1288
|
-
def
|
1289
|
-
|
1290
|
-
session,
|
1291
|
-
cls.xmpp.LEGACY_ROOM_ID_TYPE(stored.legacy_id),
|
1292
|
-
stored.jid,
|
1293
|
-
*args, # type: ignore
|
1294
|
-
**kwargs, # type: ignore
|
1295
|
-
)
|
1296
|
-
muc.pk = stored.id
|
1297
|
-
muc.type = stored.muc_type # type: ignore
|
1298
|
-
muc._user_nick = stored.user_nick
|
1299
|
-
if stored.name:
|
1300
|
-
muc.DISCO_NAME = stored.name
|
1301
|
-
if stored.description:
|
1302
|
-
muc._description = stored.description
|
1303
|
-
if (data := stored.extra_attributes) is not None:
|
1304
|
-
muc.deserialize_extra_attributes(data)
|
1305
|
-
muc._subject = stored.subject or ""
|
1306
|
-
if stored.subject_date is not None:
|
1307
|
-
muc._subject_date = stored.subject_date.replace(tzinfo=timezone.utc)
|
1308
|
-
muc._participants_filled = stored.participants_filled
|
1309
|
-
muc._n_participants = stored.n_participants
|
1310
|
-
muc._history_filled = stored.history_filled
|
1311
|
-
if stored.user_resources is not None:
|
1312
|
-
muc._user_resources = set(json.loads(stored.user_resources))
|
1313
|
-
muc._subject_setter = stored.subject_setter
|
1314
|
-
muc.archive = MessageArchive(muc.pk, session.xmpp.store.mam)
|
1315
|
-
muc._set_logger_name()
|
1316
|
-
muc._AvatarMixin__avatar_unique_id = ( # type:ignore
|
1317
|
-
None
|
1318
|
-
if stored.avatar_legacy_id is None
|
1319
|
-
else session.xmpp.AVATAR_ID_TYPE(stored.avatar_legacy_id)
|
1320
|
-
)
|
1321
|
-
muc._avatar_pk = stored.avatar_id
|
1322
|
-
return muc
|
1323
|
+
@property
|
1324
|
+
def participants_filled(self) -> bool:
|
1325
|
+
return self.stored.participants_filled
|
1323
1326
|
|
1324
1327
|
|
1325
|
-
def set_origin_id(msg: Message, origin_id: str):
|
1328
|
+
def set_origin_id(msg: Message, origin_id: str) -> None:
|
1326
1329
|
sub = ET.Element("{urn:xmpp:sid:0}origin-id")
|
1327
1330
|
sub.attrib["id"] = origin_id
|
1328
1331
|
msg.xml.append(sub)
|