slidge 0.2.11__py3-none-any.whl → 0.3.0__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 +123 -210
- slidge/contact/roster.py +108 -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 +120 -93
- 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 +26 -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 +177 -87
- slidge/core/mixins/__init__.py +1 -11
- slidge/core/mixins/attachment.py +200 -147
- slidge/core/mixins/avatar.py +105 -177
- slidge/core/mixins/base.py +3 -1
- slidge/core/mixins/db.py +50 -2
- slidge/core/mixins/disco.py +1 -1
- slidge/core/mixins/message.py +19 -17
- slidge/core/mixins/message_maker.py +29 -15
- slidge/core/mixins/message_text.py +67 -30
- slidge/core/mixins/presence.py +94 -37
- slidge/core/pubsub.py +42 -47
- slidge/core/session.py +95 -60
- slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +361 -0
- slidge/db/avatar.py +150 -119
- slidge/db/meta.py +33 -22
- slidge/db/models.py +69 -117
- slidge/db/store.py +414 -1094
- slidge/group/archive.py +65 -55
- slidge/group/bookmarks.py +96 -59
- slidge/group/participant.py +150 -144
- slidge/group/room.py +351 -328
- slidge/main.py +34 -22
- slidge/migration.py +17 -29
- 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 +12 -2
- slidge/util/archive_msg.py +11 -5
- slidge/util/conf.py +27 -21
- 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 +24 -18
- slidge/util/util.py +26 -22
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/METADATA +1 -1
- slidge-0.3.0.dist-info/RECORD +95 -0
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/WHEEL +1 -1
- slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py +0 -33
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -36
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +0 -85
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -36
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -37
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -41
- slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +0 -52
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -42
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -61
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -48
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -43
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -139
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -50
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +0 -79
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -214
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -52
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -34
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -26
- slidge-0.2.11.dist-info/RECORD +0 -112
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/entry_points.txt +0 -0
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/top_level.txt +0 -0
slidge/group/room.py
CHANGED
@@ -3,11 +3,23 @@ import logging
|
|
3
3
|
import re
|
4
4
|
import string
|
5
5
|
import warnings
|
6
|
+
from asyncio import Lock
|
7
|
+
from contextlib import asynccontextmanager
|
6
8
|
from copy import copy
|
7
9
|
from datetime import datetime, timedelta, timezone
|
8
|
-
from typing import
|
10
|
+
from typing import (
|
11
|
+
TYPE_CHECKING,
|
12
|
+
AsyncIterator,
|
13
|
+
Generic,
|
14
|
+
Literal,
|
15
|
+
Optional,
|
16
|
+
Type,
|
17
|
+
Union,
|
18
|
+
overload,
|
19
|
+
)
|
9
20
|
from uuid import uuid4
|
10
21
|
|
22
|
+
import sqlalchemy as sa
|
11
23
|
from slixmpp import JID, Iq, Message, Presence
|
12
24
|
from slixmpp.exceptions import IqError, IqTimeout, XMPPError
|
13
25
|
from slixmpp.plugins.xep_0004 import Form
|
@@ -17,18 +29,14 @@ from slixmpp.plugins.xep_0469.stanza import NS as PINNING_NS
|
|
17
29
|
from slixmpp.plugins.xep_0492.stanza import NS as NOTIFY_NS
|
18
30
|
from slixmpp.plugins.xep_0492.stanza import WhenLiteral
|
19
31
|
from slixmpp.xmlstream import ET
|
32
|
+
from sqlalchemy.orm import Session as OrmSession
|
20
33
|
|
21
34
|
from ..contact.contact import LegacyContact
|
22
35
|
from ..contact.roster import ContactIsUser
|
23
|
-
from ..core import config
|
24
|
-
from ..core.mixins import StoredAttributeMixin
|
25
36
|
from ..core.mixins.avatar import AvatarMixin
|
26
|
-
from ..core.mixins.db import UpdateInfoMixin
|
27
37
|
from ..core.mixins.disco import ChatterDiscoMixin
|
28
|
-
from ..core.mixins.lock import NamedLockMixin
|
29
38
|
from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
|
30
|
-
from ..db.models import Room
|
31
|
-
from ..util import ABCSubclassableOnceAtMost
|
39
|
+
from ..db.models import Participant, Room
|
32
40
|
from ..util.jid_escaping import unescape_node
|
33
41
|
from ..util.types import (
|
34
42
|
HoleBound,
|
@@ -40,12 +48,11 @@ from ..util.types import (
|
|
40
48
|
MucAffiliation,
|
41
49
|
MucType,
|
42
50
|
)
|
43
|
-
from ..util.util import deprecated, timeit
|
51
|
+
from ..util.util import SubclassableOnce, deprecated, timeit
|
44
52
|
from .archive import MessageArchive
|
45
53
|
from .participant import LegacyParticipant, escape_nickname
|
46
54
|
|
47
55
|
if TYPE_CHECKING:
|
48
|
-
from ..core.gateway import BaseGateway
|
49
56
|
from ..core.session import BaseSession
|
50
57
|
|
51
58
|
ADMIN_NS = "http://jabber.org/protocol/muc#admin"
|
@@ -57,14 +64,11 @@ class LegacyMUC(
|
|
57
64
|
Generic[
|
58
65
|
LegacyGroupIdType, LegacyMessageType, LegacyParticipantType, LegacyUserIdType
|
59
66
|
],
|
60
|
-
UpdateInfoMixin,
|
61
|
-
StoredAttributeMixin,
|
62
67
|
AvatarMixin,
|
63
|
-
NamedLockMixin,
|
64
68
|
ChatterDiscoMixin,
|
65
69
|
ReactionRecipientMixin,
|
66
70
|
ThreadRecipientMixin,
|
67
|
-
metaclass=
|
71
|
+
metaclass=SubclassableOnce,
|
68
72
|
):
|
69
73
|
"""
|
70
74
|
A room, a.k.a. a Multi-User Chat.
|
@@ -75,12 +79,10 @@ class LegacyMUC(
|
|
75
79
|
|
76
80
|
max_history_fetch = 100
|
77
81
|
|
78
|
-
type = MucType.CHANNEL
|
79
82
|
is_group = True
|
80
83
|
|
81
84
|
DISCO_TYPE = "text"
|
82
85
|
DISCO_CATEGORY = "conference"
|
83
|
-
DISCO_NAME = "unnamed-room"
|
84
86
|
|
85
87
|
STABLE_ARCHIVE = False
|
86
88
|
"""
|
@@ -94,17 +96,6 @@ class LegacyMUC(
|
|
94
96
|
This is just a flag on archive responses that most clients ignore anyway.
|
95
97
|
"""
|
96
98
|
|
97
|
-
KEEP_BACKFILLED_PARTICIPANTS = False
|
98
|
-
"""
|
99
|
-
Set this to ``True`` if the participant list is not full after calling
|
100
|
-
``fill_participants()``. This is a workaround for networks with huge
|
101
|
-
participant lists which do not map really well the MUCs where all presences
|
102
|
-
are sent on join.
|
103
|
-
It allows to ensure that the participants that last spoke (within the
|
104
|
-
``fill_history()`` method are effectively participants, thus making possible
|
105
|
-
for XMPP clients to fetch their avatars.
|
106
|
-
"""
|
107
|
-
|
108
99
|
_ALL_INFO_FILLED_ON_STARTUP = False
|
109
100
|
"""
|
110
101
|
Set this to true if the fill_participants() / fill_participants() design does not
|
@@ -128,150 +119,200 @@ class LegacyMUC(
|
|
128
119
|
tries to set the room subject.
|
129
120
|
"""
|
130
121
|
|
131
|
-
_avatar_bare_jid = True
|
132
122
|
archive: MessageArchive
|
123
|
+
session: "BaseSession"
|
133
124
|
|
134
|
-
|
135
|
-
self.session = session
|
136
|
-
self.xmpp: "BaseGateway" = session.xmpp
|
125
|
+
stored: Room
|
137
126
|
|
138
|
-
|
139
|
-
self.jid = jid
|
127
|
+
_participant_cls: Type[LegacyParticipantType]
|
140
128
|
|
141
|
-
|
129
|
+
def __init__(self, session: "BaseSession", stored: Room) -> None:
|
130
|
+
self.session = session
|
131
|
+
self.xmpp = session.xmpp
|
132
|
+
self.stored = stored
|
133
|
+
self._set_logger()
|
134
|
+
super().__init__()
|
142
135
|
|
143
|
-
self.
|
136
|
+
self.archive = MessageArchive(stored, self.xmpp.store.mam)
|
144
137
|
|
145
|
-
|
146
|
-
self
|
138
|
+
def participant_from_store(
|
139
|
+
self, stored: Participant, contact: LegacyContact | None = None
|
140
|
+
) -> LegacyParticipantType:
|
141
|
+
if contact is None and stored.contact is not None:
|
142
|
+
contact = self.session.contacts.from_store(stored.contact)
|
143
|
+
return self._participant_cls(self, stored=stored, contact=contact)
|
147
144
|
|
148
|
-
|
149
|
-
|
145
|
+
@property
|
146
|
+
def jid(self) -> JID:
|
147
|
+
return self.stored.jid
|
150
148
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
149
|
+
@jid.setter
|
150
|
+
def jid(self, x: JID):
|
151
|
+
# FIXME: without this, mypy yields
|
152
|
+
# "Cannot override writeable attribute with read-only property"
|
153
|
+
# But it does not happen for LegacyContact. WTF?
|
154
|
+
raise RuntimeError
|
155
155
|
|
156
|
-
|
157
|
-
|
156
|
+
@property
|
157
|
+
def legacy_id(self):
|
158
|
+
return self.xmpp.LEGACY_ROOM_ID_TYPE(self.stored.legacy_id)
|
158
159
|
|
159
|
-
|
160
|
+
def orm(self) -> OrmSession:
|
161
|
+
return self.xmpp.store.session()
|
160
162
|
|
161
|
-
|
162
|
-
|
163
|
-
|
163
|
+
@property
|
164
|
+
def type(self) -> MucType:
|
165
|
+
return self.stored.muc_type
|
166
|
+
|
167
|
+
@type.setter
|
168
|
+
def type(self, type_: MucType) -> None:
|
169
|
+
if self.type == type_:
|
170
|
+
return
|
171
|
+
self.stored.muc_type = type_
|
172
|
+
self.commit()
|
164
173
|
|
165
174
|
@property
|
166
175
|
def n_participants(self):
|
167
|
-
return self.
|
176
|
+
return self.stored.n_participants
|
168
177
|
|
169
178
|
@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:
|
179
|
+
def n_participants(self, n_participants: Optional[int]) -> None:
|
180
|
+
if self.stored.n_participants == n_participants:
|
175
181
|
return
|
176
|
-
|
177
|
-
self.
|
182
|
+
self.stored.n_participants = n_participants
|
183
|
+
self.commit()
|
178
184
|
|
179
185
|
@property
|
180
186
|
def user_jid(self):
|
181
187
|
return self.session.user_jid
|
182
188
|
|
183
|
-
def
|
189
|
+
def _set_logger(self) -> None:
|
184
190
|
self.log = logging.getLogger(f"{self.user_jid}:muc:{self}")
|
185
191
|
|
186
|
-
def __repr__(self):
|
187
|
-
return f"<MUC #{self.
|
192
|
+
def __repr__(self) -> str:
|
193
|
+
return f"<MUC #{self.stored.id} '{self.name}' ({self.stored.legacy_id} - {self.jid.user})'>"
|
188
194
|
|
189
195
|
@property
|
190
196
|
def subject_date(self) -> Optional[datetime]:
|
191
|
-
|
197
|
+
if self.stored.subject_date is None:
|
198
|
+
return None
|
199
|
+
return self.stored.subject_date.replace(tzinfo=timezone.utc)
|
192
200
|
|
193
201
|
@subject_date.setter
|
194
202
|
def subject_date(self, when: Optional[datetime]) -> None:
|
195
|
-
self.
|
196
|
-
if self._updating_info:
|
203
|
+
if self.subject_date == when:
|
197
204
|
return
|
198
|
-
|
199
|
-
self.
|
205
|
+
self.stored.subject_date = when
|
206
|
+
self.commit()
|
200
207
|
|
201
|
-
def __send_configuration_change(self, codes):
|
208
|
+
def __send_configuration_change(self, codes) -> None:
|
202
209
|
part = self.get_system_participant()
|
203
210
|
part.send_configuration_change(codes)
|
204
211
|
|
205
212
|
@property
|
206
213
|
def user_nick(self):
|
207
|
-
return
|
214
|
+
return (
|
215
|
+
self.stored.user_nick
|
216
|
+
or self.session.bookmarks.user_nick
|
217
|
+
or self.user_jid.node
|
218
|
+
)
|
208
219
|
|
209
220
|
@user_nick.setter
|
210
|
-
def user_nick(self, nick: str):
|
211
|
-
self.
|
212
|
-
|
213
|
-
|
221
|
+
def user_nick(self, nick: str) -> None:
|
222
|
+
if nick == self.user_nick:
|
223
|
+
return
|
224
|
+
self.stored.user_nick = nick
|
225
|
+
self.commit()
|
214
226
|
|
215
227
|
def add_user_resource(self, resource: str) -> None:
|
216
|
-
self.
|
217
|
-
|
218
|
-
|
228
|
+
stored_set = self.get_user_resources()
|
229
|
+
if resource in stored_set:
|
230
|
+
return
|
231
|
+
stored_set.add(resource)
|
232
|
+
self.stored.user_resources = (
|
233
|
+
json.dumps(list(stored_set)) if stored_set else None
|
234
|
+
)
|
235
|
+
self.commit()
|
219
236
|
|
220
237
|
def get_user_resources(self) -> set[str]:
|
221
|
-
|
238
|
+
stored_str = self.stored.user_resources
|
239
|
+
if stored_str is None:
|
240
|
+
return set()
|
241
|
+
return set(json.loads(stored_str))
|
222
242
|
|
223
243
|
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
|
244
|
+
stored_set = self.get_user_resources()
|
245
|
+
if resource not in stored_set:
|
248
246
|
return
|
247
|
+
stored_set.remove(resource)
|
248
|
+
self.stored.user_resources = (
|
249
|
+
json.dumps(list(stored_set)) if stored_set else None
|
250
|
+
)
|
251
|
+
self.commit()
|
249
252
|
|
253
|
+
@asynccontextmanager
|
254
|
+
async def lock(self, id_: str) -> AsyncIterator[None]:
|
255
|
+
async with self.session.lock((self.legacy_id, id_)):
|
256
|
+
yield
|
257
|
+
|
258
|
+
def get_lock(self, id_: str) -> Lock | None:
|
259
|
+
return self.session.get_lock((self.legacy_id, id_))
|
260
|
+
|
261
|
+
async def __fill_participants(self) -> None:
|
250
262
|
async with self.lock("fill participants"):
|
251
|
-
self.
|
252
|
-
|
253
|
-
|
263
|
+
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
264
|
+
orm.add(self.stored)
|
265
|
+
with orm.no_autoflush:
|
266
|
+
orm.refresh(self.stored, ["participants_filled"])
|
267
|
+
if self.participants_filled:
|
268
|
+
return
|
269
|
+
parts: list[Participant] = []
|
254
270
|
resources = set[str]()
|
271
|
+
# During fill_participants(), self.get_participant*() methods may
|
272
|
+
# return a participant with a conflicting nick/resource.
|
255
273
|
async for participant in self.fill_participants():
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
participant
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
274
|
+
if participant.stored.resource in resources:
|
275
|
+
self.log.warning(
|
276
|
+
"Participant '%s' was yielded more than once by fill_participants()",
|
277
|
+
participant.stored.resource,
|
278
|
+
)
|
279
|
+
continue
|
280
|
+
parts.append(participant.stored)
|
281
|
+
resources.add(participant.stored.resource)
|
282
|
+
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
283
|
+
orm.add(self.stored)
|
284
|
+
# because self.fill_participants() is async, self.stored may be stale at
|
285
|
+
# this point, and the only thing we want to update is the participant list
|
286
|
+
# and the participant_filled attribute.
|
287
|
+
with orm.no_autoflush:
|
288
|
+
orm.refresh(self.stored)
|
289
|
+
for part in parts:
|
290
|
+
orm.merge(part)
|
291
|
+
self.stored.participants_filled = True
|
292
|
+
orm.commit()
|
293
|
+
|
294
|
+
async def get_participants(
|
295
|
+
self, affiliation: Optional[MucAffiliation] = None
|
296
|
+
) -> AsyncIterator[LegacyParticipantType]:
|
297
|
+
await self.__fill_participants()
|
298
|
+
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
299
|
+
orm.add(self.stored)
|
300
|
+
for db_participant in self.stored.participants:
|
301
|
+
if (
|
302
|
+
affiliation is not None
|
303
|
+
and db_participant.affiliation != affiliation
|
304
|
+
):
|
305
|
+
continue
|
306
|
+
yield self.participant_from_store(db_participant)
|
270
307
|
|
271
|
-
async def __fill_history(self):
|
308
|
+
async def __fill_history(self) -> None:
|
272
309
|
async with self.lock("fill history"):
|
273
|
-
|
274
|
-
|
310
|
+
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
311
|
+
orm.add(self.stored)
|
312
|
+
with orm.no_autoflush:
|
313
|
+
orm.refresh(self.stored, ["history_filled"])
|
314
|
+
if self.stored.history_filled:
|
315
|
+
self.log.debug("History has already been fetched.")
|
275
316
|
return
|
276
317
|
log.debug("Fetching history for %s", self)
|
277
318
|
try:
|
@@ -288,43 +329,40 @@ class LegacyMUC(
|
|
288
329
|
except NotImplementedError:
|
289
330
|
return
|
290
331
|
except Exception as e:
|
291
|
-
log.exception("Could not backfill
|
292
|
-
|
293
|
-
self.
|
294
|
-
self.
|
332
|
+
self.log.exception("Could not backfill", exc_info=e)
|
333
|
+
|
334
|
+
self.stored.history_filled = True
|
335
|
+
self.commit(merge=True)
|
336
|
+
|
337
|
+
def _get_disco_name(self) -> str | None:
|
338
|
+
return self.name
|
295
339
|
|
296
340
|
@property
|
297
|
-
def name(self):
|
298
|
-
return self.
|
341
|
+
def name(self) -> str | None:
|
342
|
+
return self.stored.name
|
299
343
|
|
300
344
|
@name.setter
|
301
|
-
def name(self, n: str):
|
302
|
-
if self.
|
345
|
+
def name(self, n: str | None) -> None:
|
346
|
+
if self.name == n:
|
303
347
|
return
|
304
|
-
self.
|
305
|
-
self.
|
348
|
+
self.stored.name = n
|
349
|
+
self.commit()
|
350
|
+
self._set_logger()
|
306
351
|
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
352
|
|
312
353
|
@property
|
313
354
|
def description(self):
|
314
|
-
return self.
|
355
|
+
return self.stored.description or ""
|
315
356
|
|
316
357
|
@description.setter
|
317
|
-
def description(self, d: str):
|
318
|
-
if self.
|
358
|
+
def description(self, d: str) -> None:
|
359
|
+
if self.description == d:
|
319
360
|
return
|
320
|
-
self.
|
361
|
+
self.stored.description = d
|
362
|
+
self.commit()
|
321
363
|
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
364
|
|
327
|
-
def on_presence_unavailable(self, p: Presence):
|
365
|
+
def on_presence_unavailable(self, p: Presence) -> None:
|
328
366
|
pto = p.get_to()
|
329
367
|
if pto.bare != self.jid.bare:
|
330
368
|
return
|
@@ -332,7 +370,7 @@ class LegacyMUC(
|
|
332
370
|
pfrom = p.get_from()
|
333
371
|
if pfrom.bare != self.user_jid.bare:
|
334
372
|
return
|
335
|
-
if (resource := pfrom.resource) in self.
|
373
|
+
if (resource := pfrom.resource) in self.get_user_resources():
|
336
374
|
if pto.resource != self.user_nick:
|
337
375
|
self.log.debug(
|
338
376
|
"Received 'leave group' request but with wrong nickname. %s", p
|
@@ -381,7 +419,7 @@ class LegacyMUC(
|
|
381
419
|
"""
|
382
420
|
raise NotImplementedError
|
383
421
|
|
384
|
-
async def fill_participants(self) -> AsyncIterator[
|
422
|
+
async def fill_participants(self) -> AsyncIterator[LegacyParticipantType]:
|
385
423
|
"""
|
386
424
|
This method should yield the list of all members of this group.
|
387
425
|
|
@@ -393,30 +431,27 @@ class LegacyMUC(
|
|
393
431
|
yield
|
394
432
|
|
395
433
|
@property
|
396
|
-
def subject(self):
|
397
|
-
return self.
|
434
|
+
def subject(self) -> str:
|
435
|
+
return self.stored.subject or ""
|
398
436
|
|
399
437
|
@subject.setter
|
400
|
-
def subject(self, s: str):
|
401
|
-
if s == self.
|
438
|
+
def subject(self, s: str) -> None:
|
439
|
+
if s == self.subject:
|
402
440
|
return
|
441
|
+
|
442
|
+
self.stored.subject = s
|
443
|
+
self.commit()
|
403
444
|
self.__get_subject_setter_participant().set_room_subject(
|
404
445
|
s, None, self.subject_date, False
|
405
446
|
)
|
406
447
|
|
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
448
|
@property
|
414
449
|
def is_anonymous(self):
|
415
450
|
return self.type == MucType.CHANNEL
|
416
451
|
|
417
452
|
@property
|
418
453
|
def subject_setter(self) -> Optional[str]:
|
419
|
-
return self.
|
454
|
+
return self.stored.subject_setter
|
420
455
|
|
421
456
|
@subject_setter.setter
|
422
457
|
def subject_setter(self, subject_setter: SubjectSetterType) -> None:
|
@@ -425,19 +460,16 @@ class LegacyMUC(
|
|
425
460
|
elif isinstance(subject_setter, LegacyParticipant):
|
426
461
|
subject_setter = subject_setter.nickname
|
427
462
|
|
428
|
-
if subject_setter == self.
|
463
|
+
if subject_setter == self.subject_setter:
|
429
464
|
return
|
430
465
|
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)
|
466
|
+
self.stored.subject_setter = subject_setter
|
467
|
+
self.commit()
|
436
468
|
|
437
469
|
def __get_subject_setter_participant(self) -> LegacyParticipant:
|
438
|
-
if self.
|
470
|
+
if self.subject_setter is None:
|
439
471
|
return self.get_system_participant()
|
440
|
-
return self.
|
472
|
+
return self._participant_cls(self, Participant(nickname=self.subject_setter))
|
441
473
|
|
442
474
|
def features(self):
|
443
475
|
features = [
|
@@ -475,11 +507,13 @@ class LegacyMUC(
|
|
475
507
|
form.add_field("muc#maxhistoryfetch", value=str(self.max_history_fetch))
|
476
508
|
form.add_field("muc#roominfo_subjectmod", "boolean", value=False)
|
477
509
|
|
478
|
-
if self._ALL_INFO_FILLED_ON_STARTUP or self.
|
479
|
-
|
480
|
-
|
510
|
+
if self._ALL_INFO_FILLED_ON_STARTUP or self.stored.participants_filled:
|
511
|
+
with self.xmpp.store.session() as orm:
|
512
|
+
n = orm.scalar(
|
513
|
+
sa.select(sa.func.count(Participant.id)).filter_by(room=self.stored)
|
514
|
+
)
|
481
515
|
else:
|
482
|
-
n = self.
|
516
|
+
n = self.n_participants
|
483
517
|
if n is not None:
|
484
518
|
form.add_field("muc#roominfo_occupants", value=str(n))
|
485
519
|
|
@@ -489,14 +523,14 @@ class LegacyMUC(
|
|
489
523
|
if s := self.subject:
|
490
524
|
form.add_field("muc#roominfo_subject", value=s)
|
491
525
|
|
492
|
-
if self._set_avatar_task:
|
526
|
+
if self._set_avatar_task is not None:
|
493
527
|
await self._set_avatar_task
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
528
|
+
avatar = self.get_avatar()
|
529
|
+
if avatar and (h := avatar.id):
|
530
|
+
form.add_field(
|
531
|
+
"{http://modules.prosody.im/mod_vcard_muc}avatar#sha1", value=h
|
532
|
+
)
|
533
|
+
form.add_field("muc#roominfo_avatarhash", "text-multi", value=[h])
|
500
534
|
|
501
535
|
form.add_field("muc#roomconfig_membersonly", "boolean", value=is_group)
|
502
536
|
form.add_field(
|
@@ -514,7 +548,7 @@ class LegacyMUC(
|
|
514
548
|
|
515
549
|
return r
|
516
550
|
|
517
|
-
def shutdown(self):
|
551
|
+
def shutdown(self) -> None:
|
518
552
|
_, user_jid = escape_nickname(self.jid, self.user_nick)
|
519
553
|
for user_full_jid in self.user_full_jids():
|
520
554
|
presence = self.xmpp.make_presence(
|
@@ -526,7 +560,7 @@ class LegacyMUC(
|
|
526
560
|
presence.send()
|
527
561
|
|
528
562
|
def user_full_jids(self):
|
529
|
-
for r in self.
|
563
|
+
for r in self.get_user_resources():
|
530
564
|
j = JID(self.user_jid)
|
531
565
|
j.resource = r
|
532
566
|
yield j
|
@@ -536,14 +570,9 @@ class LegacyMUC(
|
|
536
570
|
_, user_muc_jid = escape_nickname(self.jid, self.user_nick)
|
537
571
|
return user_muc_jid
|
538
572
|
|
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
573
|
async def echo(
|
545
574
|
self, msg: Message, legacy_msg_id: Optional[LegacyMessageType] = None
|
546
|
-
):
|
575
|
+
) -> None:
|
547
576
|
origin_id = msg.get_origin_id()
|
548
577
|
|
549
578
|
msg.set_from(self.user_muc_jid)
|
@@ -568,24 +597,11 @@ class LegacyMUC(
|
|
568
597
|
|
569
598
|
msg.send()
|
570
599
|
|
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
|
-
)
|
600
|
+
def _post_avatar_update(self, cached_avatar) -> None:
|
585
601
|
self.__send_configuration_change((104,))
|
586
602
|
self._send_room_presence()
|
587
603
|
|
588
|
-
def _send_room_presence(self, user_full_jid: Optional[JID] = None):
|
604
|
+
def _send_room_presence(self, user_full_jid: Optional[JID] = None) -> None:
|
589
605
|
if user_full_jid is None:
|
590
606
|
tos = self.user_full_jids()
|
591
607
|
else:
|
@@ -599,20 +615,19 @@ class LegacyMUC(
|
|
599
615
|
p.send()
|
600
616
|
|
601
617
|
@timeit
|
602
|
-
@with_session
|
603
618
|
async def join(self, join_presence: Presence):
|
604
619
|
user_full_jid = join_presence.get_from()
|
605
620
|
requested_nickname = join_presence.get_to().resource
|
606
621
|
client_resource = user_full_jid.resource
|
607
622
|
|
608
|
-
if client_resource in self.
|
623
|
+
if client_resource in self.get_user_resources():
|
609
624
|
self.log.debug("Received join from a resource that is already joined.")
|
610
625
|
|
611
|
-
self.add_user_resource(client_resource)
|
612
|
-
|
613
626
|
if not requested_nickname or not client_resource:
|
614
627
|
raise XMPPError("jid-malformed", by=self.jid)
|
615
628
|
|
629
|
+
self.add_user_resource(client_resource)
|
630
|
+
|
616
631
|
self.log.debug(
|
617
632
|
"Resource %s of %s wants to join room %s with nickname %s",
|
618
633
|
client_resource,
|
@@ -631,9 +646,13 @@ class LegacyMUC(
|
|
631
646
|
|
632
647
|
if user_participant is None:
|
633
648
|
user_participant = await self.get_user_participant()
|
634
|
-
|
649
|
+
with self.xmpp.store.session() as orm:
|
650
|
+
orm.add(self.stored)
|
651
|
+
with orm.no_autoflush:
|
652
|
+
orm.refresh(self.stored, ["participants"])
|
653
|
+
if not user_participant.is_user:
|
635
654
|
self.log.warning("is_user flag not set participant on user_participant")
|
636
|
-
user_participant.is_user = True
|
655
|
+
user_participant.is_user = True
|
637
656
|
user_participant.send_initial_presence(
|
638
657
|
user_full_jid,
|
639
658
|
presence_id=join_presence["id"],
|
@@ -661,8 +680,12 @@ class LegacyMUC(
|
|
661
680
|
maxstanzas=maxstanzas,
|
662
681
|
since=since,
|
663
682
|
)
|
683
|
+
if self.HAS_SUBJECT:
|
684
|
+
subject = self.subject or ""
|
685
|
+
else:
|
686
|
+
subject = self.description or self.name or ""
|
664
687
|
self.__get_subject_setter_participant().set_room_subject(
|
665
|
-
|
688
|
+
subject,
|
666
689
|
user_full_jid,
|
667
690
|
self.subject_date,
|
668
691
|
)
|
@@ -683,22 +706,17 @@ class LegacyMUC(
|
|
683
706
|
return p
|
684
707
|
|
685
708
|
def __store_participant(self, p: "LegacyParticipantType") -> None:
|
686
|
-
|
687
|
-
if not self.KEEP_BACKFILLED_PARTICIPANTS and self.get_lock("fill history"):
|
709
|
+
if self.get_lock("fill participants"):
|
688
710
|
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)
|
711
|
+
p.commit(merge=True)
|
694
712
|
|
695
713
|
async def get_participant(
|
696
714
|
self,
|
697
715
|
nickname: str,
|
698
|
-
raise_if_not_found=False,
|
699
|
-
fill_first=False,
|
700
|
-
store=True,
|
701
|
-
|
716
|
+
raise_if_not_found: bool = False,
|
717
|
+
fill_first: bool = False,
|
718
|
+
store: bool = True,
|
719
|
+
is_user: bool = False,
|
702
720
|
) -> "LegacyParticipantType":
|
703
721
|
"""
|
704
722
|
Get a participant by their nickname.
|
@@ -713,30 +731,34 @@ class LegacyMUC(
|
|
713
731
|
:param fill_first: Ensure :meth:`.LegacyMUC.fill_participants()` has been called first
|
714
732
|
(internal use by slidge, plugins should not need that)
|
715
733
|
: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
734
|
:return:
|
719
735
|
"""
|
720
|
-
if fill_first
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
self.
|
727
|
-
|
728
|
-
|
729
|
-
|
736
|
+
if fill_first:
|
737
|
+
await self.__fill_participants()
|
738
|
+
with self.xmpp.store.session() as orm:
|
739
|
+
stored = (
|
740
|
+
orm.query(Participant)
|
741
|
+
.filter(
|
742
|
+
Participant.room == self.stored,
|
743
|
+
(Participant.nickname == nickname)
|
744
|
+
| (Participant.resource == nickname),
|
745
|
+
)
|
746
|
+
.one_or_none()
|
747
|
+
)
|
748
|
+
if stored is not None:
|
749
|
+
return self.participant_from_store(stored)
|
730
750
|
|
731
751
|
if raise_if_not_found:
|
732
752
|
raise XMPPError("item-not-found")
|
733
|
-
p = self.
|
734
|
-
|
753
|
+
p = self._participant_cls(
|
754
|
+
self, Participant(room=self.stored, nickname=nickname, is_user=is_user)
|
755
|
+
)
|
756
|
+
if store:
|
735
757
|
self.__store_participant(p)
|
736
758
|
if (
|
737
759
|
not self.get_lock("fill participants")
|
738
760
|
and not self.get_lock("fill history")
|
739
|
-
and self.
|
761
|
+
and self.stored.participants_filled
|
740
762
|
and not p.is_user
|
741
763
|
and not p.is_system
|
742
764
|
):
|
@@ -752,11 +774,26 @@ class LegacyMUC(
|
|
752
774
|
service
|
753
775
|
:return:
|
754
776
|
"""
|
755
|
-
return self.
|
777
|
+
return self._participant_cls(self, Participant(), is_system=True)
|
756
778
|
|
779
|
+
@overload
|
757
780
|
async def get_participant_by_contact(
|
758
|
-
self, c: "LegacyContact"
|
759
|
-
) -> "LegacyParticipantType":
|
781
|
+
self, c: "LegacyContact"
|
782
|
+
) -> "LegacyParticipantType": ...
|
783
|
+
|
784
|
+
@overload
|
785
|
+
async def get_participant_by_contact(
|
786
|
+
self, c: "LegacyContact", create: Literal[False]
|
787
|
+
) -> "LegacyParticipantType | None": ...
|
788
|
+
|
789
|
+
@overload
|
790
|
+
async def get_participant_by_contact(
|
791
|
+
self, c: "LegacyContact", create: Literal[True]
|
792
|
+
) -> "LegacyParticipantType": ...
|
793
|
+
|
794
|
+
async def get_participant_by_contact(
|
795
|
+
self, c: "LegacyContact", create: bool = True
|
796
|
+
) -> "LegacyParticipantType | None":
|
760
797
|
"""
|
761
798
|
Get a non-anonymous participant.
|
762
799
|
|
@@ -764,37 +801,42 @@ class LegacyMUC(
|
|
764
801
|
that the Contact jid is associated to this participant
|
765
802
|
|
766
803
|
:param c: The :class:`.LegacyContact` instance corresponding to this contact
|
767
|
-
:param
|
768
|
-
construction (optional)
|
804
|
+
:param create: Creates the participant if it does not exist.
|
769
805
|
:return:
|
770
806
|
"""
|
771
807
|
await self.session.contacts.ready
|
772
808
|
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
809
|
+
with self.xmpp.store.session() as orm:
|
810
|
+
self.stored = orm.merge(self.stored)
|
811
|
+
stored = (
|
812
|
+
orm.query(Participant)
|
813
|
+
.filter_by(contact=c.stored, room=self.stored)
|
814
|
+
.one_or_none()
|
815
|
+
)
|
816
|
+
if stored is None:
|
817
|
+
if not create:
|
818
|
+
return None
|
819
|
+
else:
|
820
|
+
return self.participant_from_store(stored=stored, contact=c)
|
782
821
|
|
783
|
-
nickname = c.name or unescape_node(c.
|
822
|
+
nickname = c.name or unescape_node(c.jid.node)
|
784
823
|
|
785
|
-
if self.
|
824
|
+
if self.stored.id is None:
|
786
825
|
nick_available = True
|
787
826
|
else:
|
788
|
-
|
827
|
+
with self.xmpp.store.session() as orm:
|
828
|
+
nick_available = (
|
829
|
+
orm.query(Participant.id).filter_by(
|
830
|
+
room=self.stored, nickname=nickname
|
831
|
+
)
|
832
|
+
).one_or_none() is None
|
789
833
|
|
790
834
|
if not nick_available:
|
791
835
|
self.log.debug("Nickname conflict")
|
792
|
-
nickname = f"{nickname} ({c.
|
793
|
-
p = self.
|
794
|
-
|
795
|
-
|
796
|
-
if self._updating_info:
|
797
|
-
return p
|
836
|
+
nickname = f"{nickname} ({c.jid.node})"
|
837
|
+
p = self._participant_cls(
|
838
|
+
self, Participant(nickname=nickname, room=self.stored), contact=c
|
839
|
+
)
|
798
840
|
|
799
841
|
self.__store_participant(p)
|
800
842
|
# FIXME: this is not great but given the current design,
|
@@ -803,7 +845,7 @@ class LegacyMUC(
|
|
803
845
|
# and role afterwards.
|
804
846
|
# We need a refactor of the MUC class… later™
|
805
847
|
if (
|
806
|
-
self.
|
848
|
+
self.stored.participants_filled
|
807
849
|
and not self.get_lock("fill participants")
|
808
850
|
and not self.get_lock("fill history")
|
809
851
|
):
|
@@ -811,19 +853,19 @@ class LegacyMUC(
|
|
811
853
|
return p
|
812
854
|
|
813
855
|
async def get_participant_by_legacy_id(
|
814
|
-
self, legacy_id: LegacyUserIdType
|
856
|
+
self, legacy_id: LegacyUserIdType
|
815
857
|
) -> "LegacyParticipantType":
|
816
858
|
try:
|
817
859
|
c = await self.session.contacts.by_legacy_id(legacy_id)
|
818
860
|
except ContactIsUser:
|
819
|
-
return await self.get_user_participant(
|
820
|
-
return await self.get_participant_by_contact(c
|
861
|
+
return await self.get_user_participant()
|
862
|
+
return await self.get_participant_by_contact(c)
|
821
863
|
|
822
864
|
def remove_participant(
|
823
865
|
self,
|
824
866
|
p: "LegacyParticipantType",
|
825
|
-
kick=False,
|
826
|
-
ban=False,
|
867
|
+
kick: bool = False,
|
868
|
+
ban: bool = False,
|
827
869
|
reason: str | None = None,
|
828
870
|
):
|
829
871
|
"""
|
@@ -836,7 +878,9 @@ class LegacyMUC(
|
|
836
878
|
"""
|
837
879
|
if kick and ban:
|
838
880
|
raise TypeError("Either kick or ban")
|
839
|
-
self.
|
881
|
+
with self.xmpp.store.session() as orm:
|
882
|
+
orm.delete(p.stored)
|
883
|
+
orm.commit()
|
840
884
|
if kick:
|
841
885
|
codes = {307}
|
842
886
|
elif ban:
|
@@ -844,20 +888,23 @@ class LegacyMUC(
|
|
844
888
|
else:
|
845
889
|
codes = None
|
846
890
|
presence = p._make_presence(ptype="unavailable", status_codes=codes)
|
847
|
-
p.
|
848
|
-
p.
|
891
|
+
p.stored.affiliation = "outcast" if ban else "none"
|
892
|
+
p.stored.role = "none"
|
849
893
|
if reason:
|
850
894
|
presence["muc"].set_item_attr("reason", reason)
|
851
895
|
p._send(presence)
|
852
896
|
|
853
|
-
def rename_participant(self, old_nickname: str, new_nickname: str):
|
854
|
-
|
855
|
-
|
856
|
-
|
897
|
+
def rename_participant(self, old_nickname: str, new_nickname: str) -> None:
|
898
|
+
with self.xmpp.store.session() as orm:
|
899
|
+
stored = (
|
900
|
+
orm.query(Participant)
|
901
|
+
.filter_by(room=self.stored, nickname=old_nickname)
|
902
|
+
.one_or_none()
|
903
|
+
)
|
857
904
|
if stored is None:
|
858
905
|
self.log.debug("Tried to rename a participant that we didn't know")
|
859
906
|
return
|
860
|
-
p = self.
|
907
|
+
p = self.participant_from_store(stored)
|
861
908
|
if p.nickname == old_nickname:
|
862
909
|
p.nickname = new_nickname
|
863
910
|
|
@@ -868,7 +915,7 @@ class LegacyMUC(
|
|
868
915
|
maxstanzas: Optional[int] = None,
|
869
916
|
seconds: Optional[int] = None,
|
870
917
|
since: Optional[datetime] = None,
|
871
|
-
):
|
918
|
+
) -> None:
|
872
919
|
"""
|
873
920
|
Old-style history join (internal slidge use)
|
874
921
|
|
@@ -895,7 +942,7 @@ class LegacyMUC(
|
|
895
942
|
msg.set_to(full_jid)
|
896
943
|
self.xmpp.send(msg, False)
|
897
944
|
|
898
|
-
async def send_mam(self, iq: Iq):
|
945
|
+
async def send_mam(self, iq: Iq) -> None:
|
899
946
|
await self.__fill_history()
|
900
947
|
|
901
948
|
form_values = iq["mam"]["form"].get_values()
|
@@ -922,8 +969,13 @@ class LegacyMUC(
|
|
922
969
|
after_id = after_id_rsm or after_id
|
923
970
|
|
924
971
|
before_rsm = iq["mam"]["rsm"]["before"]
|
925
|
-
if before_rsm is
|
972
|
+
if before_rsm is not None and max_results is not None:
|
926
973
|
last_page_n = max_results
|
974
|
+
# - before_rsm is True means the empty element <before />, which means
|
975
|
+
# "last page in chronological order", cf https://xmpp.org/extensions/xep-0059.html#backwards
|
976
|
+
# - before_rsm == "an ID" means <before>an ID</before>
|
977
|
+
if before_rsm is not True:
|
978
|
+
before_id = before_rsm
|
927
979
|
else:
|
928
980
|
last_page_n = None
|
929
981
|
|
@@ -978,11 +1030,11 @@ class LegacyMUC(
|
|
978
1030
|
reply["mam_fin"]["rsm"]["count"] = str(count)
|
979
1031
|
reply.send()
|
980
1032
|
|
981
|
-
async def send_mam_metadata(self, iq: Iq):
|
1033
|
+
async def send_mam_metadata(self, iq: Iq) -> None:
|
982
1034
|
await self.__fill_history()
|
983
1035
|
await self.archive.send_metadata(iq)
|
984
1036
|
|
985
|
-
async def kick_resource(self, r: str):
|
1037
|
+
async def kick_resource(self, r: str) -> None:
|
986
1038
|
"""
|
987
1039
|
Kick a XMPP client of the user. (slidge internal use)
|
988
1040
|
|
@@ -1030,12 +1082,11 @@ class LegacyMUC(
|
|
1030
1082
|
|
1031
1083
|
async def add_to_bookmarks(
|
1032
1084
|
self,
|
1033
|
-
auto_join=True,
|
1034
|
-
|
1035
|
-
preserve=True,
|
1085
|
+
auto_join: bool = True,
|
1086
|
+
preserve: bool = True,
|
1036
1087
|
pin: bool | None = None,
|
1037
1088
|
notify: WhenLiteral | None = None,
|
1038
|
-
):
|
1089
|
+
) -> None:
|
1039
1090
|
"""
|
1040
1091
|
Add the MUC to the user's XMPP bookmarks (:xep:`0402')
|
1041
1092
|
|
@@ -1046,11 +1097,6 @@ class LegacyMUC(
|
|
1046
1097
|
this MUC on startup. In theory, XMPP clients will receive
|
1047
1098
|
a "push" notification when this is called, and they will
|
1048
1099
|
join if they are online.
|
1049
|
-
:param invite: send an invitation to join this MUC emanating from
|
1050
|
-
the gateway. While this should not be strictly necessary,
|
1051
|
-
it can help for clients that do not support :xep:`0402`, or
|
1052
|
-
that have 'do not honor bookmarks auto-join' turned on in their
|
1053
|
-
settings.
|
1054
1100
|
:param preserve: preserve auto-join and bookmarks extensions
|
1055
1101
|
set by the user outside slidge
|
1056
1102
|
:param pin: Pin the group chat bookmark :xep:`0469`. Requires privileged entity.
|
@@ -1131,20 +1177,32 @@ class LegacyMUC(
|
|
1131
1177
|
"IQ privileges (XEP0356) are not set, we cannot add bookmarks for the user"
|
1132
1178
|
)
|
1133
1179
|
# fallback by forcing invitation
|
1134
|
-
|
1180
|
+
bookmark_add_fail = True
|
1135
1181
|
except IqError as e:
|
1136
1182
|
warnings.warn(
|
1137
1183
|
f"Something went wrong while trying to set the bookmarks: {e}"
|
1138
1184
|
)
|
1139
1185
|
# fallback by forcing invitation
|
1140
|
-
|
1186
|
+
bookmark_add_fail = True
|
1187
|
+
else:
|
1188
|
+
bookmark_add_fail = False
|
1141
1189
|
else:
|
1142
1190
|
self.log.debug("Bookmark does not need updating.")
|
1143
1191
|
return
|
1144
1192
|
|
1145
|
-
if
|
1193
|
+
if bookmark_add_fail:
|
1146
1194
|
self.session.send_gateway_invite(
|
1147
|
-
self,
|
1195
|
+
self,
|
1196
|
+
reason="This group could not be added automatically for you, most"
|
1197
|
+
"likely because this gateway is not configured as a privileged entity. "
|
1198
|
+
"Contact your administrator.",
|
1199
|
+
)
|
1200
|
+
elif existing is None and self.session.user.preferences.get(
|
1201
|
+
"always_invite_when_adding_bookmarks", True
|
1202
|
+
):
|
1203
|
+
self.session.send_gateway_invite(
|
1204
|
+
self,
|
1205
|
+
reason="The gateway is configured to always send invitations for groups.",
|
1148
1206
|
)
|
1149
1207
|
|
1150
1208
|
async def on_avatar(
|
@@ -1233,12 +1291,10 @@ class LegacyMUC(
|
|
1233
1291
|
raise NotImplementedError
|
1234
1292
|
|
1235
1293
|
async def parse_mentions(self, text: str) -> list[Mention]:
|
1236
|
-
with self.
|
1294
|
+
with self.xmpp.store.session() as orm:
|
1237
1295
|
await self.__fill_participants()
|
1238
|
-
|
1239
|
-
participants = {
|
1240
|
-
p.nickname: p for p in self.__participants_store.get_all(self.pk)
|
1241
|
-
}
|
1296
|
+
orm.add(self.stored)
|
1297
|
+
participants = {p.nickname: p for p in self.stored.participants}
|
1242
1298
|
|
1243
1299
|
if len(participants) == 0:
|
1244
1300
|
return []
|
@@ -1259,8 +1315,8 @@ class LegacyMUC(
|
|
1259
1315
|
if span[0] != 0 and text[span[0] - 1] not in _WHITESPACE_OR_PUNCTUATION:
|
1260
1316
|
continue
|
1261
1317
|
if span[1] == len(text) or text[span[1]] in _WHITESPACE_OR_PUNCTUATION:
|
1262
|
-
participant = self.
|
1263
|
-
|
1318
|
+
participant = self.participant_from_store(
|
1319
|
+
stored=participants[nick],
|
1264
1320
|
)
|
1265
1321
|
if contact := participant.contact:
|
1266
1322
|
result.append(
|
@@ -1279,45 +1335,12 @@ class LegacyMUC(
|
|
1279
1335
|
"""
|
1280
1336
|
raise NotImplementedError
|
1281
1337
|
|
1282
|
-
@
|
1283
|
-
def
|
1284
|
-
|
1285
|
-
session,
|
1286
|
-
cls.xmpp.LEGACY_ROOM_ID_TYPE(stored.legacy_id),
|
1287
|
-
stored.jid,
|
1288
|
-
*args, # type: ignore
|
1289
|
-
**kwargs, # type: ignore
|
1290
|
-
)
|
1291
|
-
muc.pk = stored.id
|
1292
|
-
muc.type = stored.muc_type # type: ignore
|
1293
|
-
muc._user_nick = stored.user_nick
|
1294
|
-
if stored.name:
|
1295
|
-
muc.DISCO_NAME = stored.name
|
1296
|
-
if stored.description:
|
1297
|
-
muc._description = stored.description
|
1298
|
-
if (data := stored.extra_attributes) is not None:
|
1299
|
-
muc.deserialize_extra_attributes(data)
|
1300
|
-
muc._subject = stored.subject or ""
|
1301
|
-
if stored.subject_date is not None:
|
1302
|
-
muc._subject_date = stored.subject_date.replace(tzinfo=timezone.utc)
|
1303
|
-
muc._participants_filled = stored.participants_filled
|
1304
|
-
muc._n_participants = stored.n_participants
|
1305
|
-
muc._history_filled = stored.history_filled
|
1306
|
-
if stored.user_resources is not None:
|
1307
|
-
muc._user_resources = set(json.loads(stored.user_resources))
|
1308
|
-
muc._subject_setter = stored.subject_setter
|
1309
|
-
muc.archive = MessageArchive(muc.pk, session.xmpp.store.mam)
|
1310
|
-
muc._set_logger_name()
|
1311
|
-
muc._AvatarMixin__avatar_unique_id = ( # type:ignore
|
1312
|
-
None
|
1313
|
-
if stored.avatar_legacy_id is None
|
1314
|
-
else session.xmpp.AVATAR_ID_TYPE(stored.avatar_legacy_id)
|
1315
|
-
)
|
1316
|
-
muc._avatar_pk = stored.avatar_id
|
1317
|
-
return muc
|
1338
|
+
@property
|
1339
|
+
def participants_filled(self) -> bool:
|
1340
|
+
return self.stored.participants_filled
|
1318
1341
|
|
1319
1342
|
|
1320
|
-
def set_origin_id(msg: Message, origin_id: str):
|
1343
|
+
def set_origin_id(msg: Message, origin_id: str) -> None:
|
1321
1344
|
sub = ET.Element("{urn:xmpp:sid:0}origin-id")
|
1322
1345
|
sub.attrib["id"] = origin_id
|
1323
1346
|
msg.xml.append(sub)
|