slidge 0.1.3__py3-none-any.whl → 0.2.0a1__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 +3 -5
- slidge/__main__.py +2 -196
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +8 -1
- slidge/command/admin.py +6 -7
- slidge/command/base.py +1 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +85 -6
- slidge/contact/contact.py +165 -49
- slidge/contact/roster.py +122 -47
- slidge/core/config.py +14 -11
- slidge/core/gateway/base.py +148 -36
- slidge/core/gateway/caps.py +7 -5
- slidge/core/gateway/disco.py +2 -4
- slidge/core/gateway/mam.py +1 -4
- slidge/core/gateway/muc_admin.py +1 -1
- slidge/core/gateway/ping.py +2 -3
- slidge/core/gateway/presence.py +1 -1
- slidge/core/gateway/registration.py +32 -21
- slidge/core/gateway/search.py +3 -5
- slidge/core/gateway/session_dispatcher.py +120 -57
- slidge/core/gateway/vcard_temp.py +7 -5
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +32 -14
- slidge/core/mixins/avatar.py +90 -25
- slidge/core/mixins/base.py +8 -2
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/message.py +18 -8
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/presence.py +17 -4
- slidge/core/pubsub.py +54 -220
- slidge/core/session.py +69 -34
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/env.py +64 -0
- slidge/db/alembic/script.py.mako +26 -0
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +85 -0
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
- slidge/db/avatar.py +235 -0
- slidge/db/meta.py +65 -0
- slidge/db/models.py +375 -0
- slidge/db/store.py +1078 -0
- slidge/group/archive.py +58 -14
- slidge/group/bookmarks.py +72 -57
- slidge/group/participant.py +87 -28
- slidge/group/room.py +369 -211
- slidge/main.py +201 -0
- slidge/migration.py +30 -0
- slidge/slixfix/__init__.py +35 -2
- slidge/slixfix/roster.py +11 -4
- slidge/slixfix/xep_0292/vcard4.py +3 -0
- slidge/util/archive_msg.py +2 -1
- slidge/util/db.py +1 -47
- slidge/util/test.py +71 -4
- slidge/util/types.py +29 -4
- slidge/util/util.py +22 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/METADATA +4 -4
- slidge-0.2.0a1.dist-info/RECORD +114 -0
- slidge/core/cache.py +0 -183
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.3.dist-info/RECORD +0 -96
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
slidge/contact/contact.py
CHANGED
@@ -2,7 +2,7 @@ import datetime
|
|
2
2
|
import logging
|
3
3
|
import warnings
|
4
4
|
from datetime import date
|
5
|
-
from typing import TYPE_CHECKING, Generic, Iterable, Optional, Union
|
5
|
+
from typing import TYPE_CHECKING, Generic, Iterable, Optional, Self, Union
|
6
6
|
|
7
7
|
from slixmpp import JID, Message, Presence
|
8
8
|
from slixmpp.exceptions import IqError
|
@@ -10,10 +10,11 @@ from slixmpp.plugins.xep_0292.stanza import VCard4
|
|
10
10
|
from slixmpp.types import MessageTypes
|
11
11
|
|
12
12
|
from ..core import config
|
13
|
-
from ..core.mixins import FullCarbonMixin
|
14
|
-
from ..core.mixins.
|
13
|
+
from ..core.mixins import AvatarMixin, FullCarbonMixin, StoredAttributeMixin
|
14
|
+
from ..core.mixins.db import UpdateInfoMixin
|
15
15
|
from ..core.mixins.disco import ContactAccountDiscoMixin
|
16
16
|
from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
|
17
|
+
from ..db.models import Contact
|
17
18
|
from ..util import SubclassableOnce
|
18
19
|
from ..util.types import LegacyUserIdType, MessageOrPresenceTypeVar
|
19
20
|
|
@@ -24,11 +25,13 @@ if TYPE_CHECKING:
|
|
24
25
|
|
25
26
|
class LegacyContact(
|
26
27
|
Generic[LegacyUserIdType],
|
28
|
+
StoredAttributeMixin,
|
27
29
|
AvatarMixin,
|
28
30
|
ContactAccountDiscoMixin,
|
29
31
|
FullCarbonMixin,
|
30
32
|
ReactionRecipientMixin,
|
31
33
|
ThreadRecipientMixin,
|
34
|
+
UpdateInfoMixin,
|
32
35
|
metaclass=SubclassableOnce,
|
33
36
|
):
|
34
37
|
"""
|
@@ -81,7 +84,6 @@ class LegacyContact(
|
|
81
84
|
STRIP_SHORT_DELAY = True
|
82
85
|
_NON_FRIEND_PRESENCES_FILTER = {"subscribe", "unsubscribed"}
|
83
86
|
|
84
|
-
_avatar_pubsub_broadcast = True
|
85
87
|
_avatar_bare_jid = True
|
86
88
|
|
87
89
|
INVITATION_RECIPIENT = True
|
@@ -100,7 +102,6 @@ class LegacyContact(
|
|
100
102
|
"""
|
101
103
|
super().__init__()
|
102
104
|
self.session = session
|
103
|
-
self.user = session.user
|
104
105
|
self.legacy_id: LegacyUserIdType = legacy_id
|
105
106
|
"""
|
106
107
|
The legacy identifier of the :term:`Legacy Contact`.
|
@@ -116,19 +117,62 @@ class LegacyContact(
|
|
116
117
|
|
117
118
|
self._name: Optional[str] = None
|
118
119
|
|
119
|
-
if self.xmpp.MARK_ALL_MESSAGES:
|
120
|
-
self._sent_order = list[str]()
|
121
|
-
|
122
120
|
self.xmpp = session.xmpp
|
123
121
|
self.jid = JID(self.jid_username + "@" + self.xmpp.boundjid.bare)
|
124
122
|
self.jid.resource = self.RESOURCE
|
125
|
-
self.log = logging.getLogger(f"{self.
|
126
|
-
self.
|
127
|
-
self.
|
128
|
-
self.
|
123
|
+
self.log = logging.getLogger(f"{self.user_jid.bare}:{self.jid.bare}")
|
124
|
+
self._is_friend: bool = False
|
125
|
+
self._added_to_roster = False
|
126
|
+
self._caps_ver: str | None = None
|
127
|
+
|
128
|
+
@property
|
129
|
+
def is_friend(self):
|
130
|
+
return self._is_friend
|
131
|
+
|
132
|
+
@is_friend.setter
|
133
|
+
def is_friend(self, value: bool):
|
134
|
+
if value == self._is_friend:
|
135
|
+
return
|
136
|
+
self._is_friend = value
|
137
|
+
if self._updating_info:
|
138
|
+
return
|
139
|
+
assert self.contact_pk is not None
|
140
|
+
self.xmpp.store.contacts.set_friend(self.contact_pk, value)
|
141
|
+
|
142
|
+
@property
|
143
|
+
def added_to_roster(self):
|
144
|
+
return self._added_to_roster
|
145
|
+
|
146
|
+
@added_to_roster.setter
|
147
|
+
def added_to_roster(self, value: bool):
|
148
|
+
if value == self._added_to_roster:
|
149
|
+
return
|
150
|
+
self._added_to_roster = value
|
151
|
+
if self._updating_info:
|
152
|
+
return
|
153
|
+
if self.contact_pk is None:
|
154
|
+
# during LegacyRoster.fill()
|
155
|
+
return
|
156
|
+
self.xmpp.store.contacts.set_added_to_roster(self.contact_pk, value)
|
157
|
+
|
158
|
+
@property
|
159
|
+
def participants(self) -> list["LegacyParticipant"]:
|
160
|
+
assert self.contact_pk is not None
|
161
|
+
from ..group.participant import LegacyParticipant
|
162
|
+
|
163
|
+
return [
|
164
|
+
LegacyParticipant.get_self_or_unique_subclass().from_store(
|
165
|
+
self.session, stored, contact=self
|
166
|
+
)
|
167
|
+
for stored in self.xmpp.store.participants.get_for_contact(self.contact_pk)
|
168
|
+
]
|
169
|
+
|
170
|
+
@property
|
171
|
+
def user_jid(self):
|
172
|
+
return self.session.user_jid
|
129
173
|
|
130
174
|
def __repr__(self):
|
131
|
-
return f"<Contact
|
175
|
+
return f"<Contact {self.jid.bare} - {self.name or self.legacy_id}'>"
|
132
176
|
|
133
177
|
def __get_subscription_string(self):
|
134
178
|
if self.is_friend:
|
@@ -170,11 +214,11 @@ class LegacyContact(
|
|
170
214
|
) -> MessageOrPresenceTypeVar:
|
171
215
|
if carbon and isinstance(stanza, Message):
|
172
216
|
stanza["to"] = self.jid.bare
|
173
|
-
stanza["from"] = self.
|
217
|
+
stanza["from"] = self.user_jid
|
174
218
|
self._privileged_send(stanza)
|
175
219
|
return stanza # type:ignore
|
176
220
|
|
177
|
-
if isinstance(stanza, Presence):
|
221
|
+
if not self._updating_info and isinstance(stanza, Presence):
|
178
222
|
self.__propagate_to_participants(stanza)
|
179
223
|
if (
|
180
224
|
not self.is_friend
|
@@ -185,13 +229,18 @@ class LegacyContact(
|
|
185
229
|
n = self.xmpp.plugin["xep_0172"].stanza.UserNick()
|
186
230
|
n["nick"] = self.name
|
187
231
|
stanza.append(n)
|
188
|
-
if
|
189
|
-
self.
|
190
|
-
|
232
|
+
if (
|
233
|
+
not self._updating_info
|
234
|
+
and self.xmpp.MARK_ALL_MESSAGES
|
235
|
+
and is_markable(stanza)
|
236
|
+
):
|
237
|
+
assert self.contact_pk is not None
|
238
|
+
self.xmpp.store.contacts.add_to_sent(self.contact_pk, stanza["id"])
|
239
|
+
stanza["to"] = self.user_jid
|
191
240
|
stanza.send()
|
192
241
|
return stanza
|
193
242
|
|
194
|
-
def get_msg_xmpp_id_up_to(self, horizon_xmpp_id: str):
|
243
|
+
def get_msg_xmpp_id_up_to(self, horizon_xmpp_id: str) -> list[str]:
|
195
244
|
"""
|
196
245
|
Return XMPP msg ids sent by this contact up to a given XMPP msg id.
|
197
246
|
|
@@ -205,15 +254,8 @@ class LegacyContact(
|
|
205
254
|
:param horizon_xmpp_id: The latest message
|
206
255
|
:return: A list of XMPP ids or None if horizon_xmpp_id was not found
|
207
256
|
"""
|
208
|
-
|
209
|
-
|
210
|
-
break
|
211
|
-
else:
|
212
|
-
return
|
213
|
-
i += 1
|
214
|
-
res = self._sent_order[:i]
|
215
|
-
self._sent_order = self._sent_order[i:]
|
216
|
-
return res
|
257
|
+
assert self.contact_pk is not None
|
258
|
+
return self.xmpp.store.contacts.pop_sent_up_to(self.contact_pk, horizon_xmpp_id)
|
217
259
|
|
218
260
|
@property
|
219
261
|
def name(self):
|
@@ -226,12 +268,33 @@ class LegacyContact(
|
|
226
268
|
def name(self, n: Optional[str]):
|
227
269
|
if self._name == n:
|
228
270
|
return
|
271
|
+
self._name = n
|
272
|
+
if self.is_friend and self.added_to_roster:
|
273
|
+
self.xmpp.pubsub.broadcast_nick(
|
274
|
+
user_jid=self.user_jid, jid=self.jid.bare, nick=n
|
275
|
+
)
|
276
|
+
if self._updating_info:
|
277
|
+
# means we're in update_info(), so no participants, and no need
|
278
|
+
# to write to DB now, it will be called in Roster.__finish_init_contact
|
279
|
+
return
|
229
280
|
for p in self.participants:
|
230
281
|
p.nickname = n
|
231
|
-
self.
|
232
|
-
self.xmpp.
|
282
|
+
assert self.contact_pk is not None
|
283
|
+
self.xmpp.store.contacts.update_nick(self.contact_pk, n)
|
284
|
+
|
285
|
+
def _get_cached_avatar_id(self) -> Optional[str]:
|
286
|
+
if self.contact_pk is None:
|
287
|
+
return None
|
288
|
+
return self.xmpp.store.contacts.get_avatar_legacy_id(self.contact_pk)
|
233
289
|
|
234
290
|
def _post_avatar_update(self):
|
291
|
+
if self._updating_info:
|
292
|
+
return
|
293
|
+
if self.contact_pk is None:
|
294
|
+
# happens in LegacyRoster.fill(), the contact primary key is not
|
295
|
+
# set yet, but this will eventually be called in LegacyRoster.__finish_init_contact
|
296
|
+
return
|
297
|
+
self.xmpp.store.contacts.set_avatar(self.contact_pk, self._avatar_pk)
|
235
298
|
for p in self.participants:
|
236
299
|
self.log.debug("Propagating new avatar to %s", p.muc)
|
237
300
|
p.send_last_presence(force=True, no_cache_online=True)
|
@@ -283,7 +346,16 @@ class LegacyContact(
|
|
283
346
|
elif country:
|
284
347
|
vcard.add_address(country, locality)
|
285
348
|
|
286
|
-
self.xmpp.vcard.set_vcard(self.jid.bare, vcard, {self.
|
349
|
+
self.xmpp.vcard.set_vcard(self.jid.bare, vcard, {self.user_jid.bare})
|
350
|
+
|
351
|
+
def get_roster_item(self):
|
352
|
+
item = {
|
353
|
+
"subscription": self.__get_subscription_string(),
|
354
|
+
"groups": [self.xmpp.ROSTER_GROUP],
|
355
|
+
}
|
356
|
+
if (n := self.name) is not None:
|
357
|
+
item["name"] = n
|
358
|
+
return {self.jid.bare: item}
|
287
359
|
|
288
360
|
async def add_to_roster(self, force=False):
|
289
361
|
"""
|
@@ -291,23 +363,15 @@ class LegacyContact(
|
|
291
363
|
|
292
364
|
:param force: add even if the contact was already added successfully
|
293
365
|
"""
|
294
|
-
if self.
|
366
|
+
if self.added_to_roster and not force:
|
295
367
|
return
|
296
368
|
if config.NO_ROSTER_PUSH:
|
297
369
|
log.debug("Roster push request by plugin ignored (--no-roster-push)")
|
298
370
|
return
|
299
|
-
item = {
|
300
|
-
"subscription": self.__get_subscription_string(),
|
301
|
-
"groups": [self.xmpp.ROSTER_GROUP],
|
302
|
-
}
|
303
|
-
if (n := self.name) is not None:
|
304
|
-
item["name"] = n
|
305
|
-
kw = dict(
|
306
|
-
jid=self.user.jid,
|
307
|
-
roster_items={self.jid.bare: item},
|
308
|
-
)
|
309
371
|
try:
|
310
|
-
await self._set_roster(
|
372
|
+
await self._set_roster(
|
373
|
+
jid=self.user_jid, roster_items=self.get_roster_item()
|
374
|
+
)
|
311
375
|
except PermissionError:
|
312
376
|
warnings.warn(
|
313
377
|
"Slidge does not have privileges to add contacts to the roster. Refer"
|
@@ -325,18 +389,33 @@ class LegacyContact(
|
|
325
389
|
else:
|
326
390
|
# we only broadcast pubsub events for contacts added to the roster
|
327
391
|
# so if something was set before, we need to push it now
|
328
|
-
self.
|
329
|
-
self.session.create_task(self.__broadcast_pubsub_items())
|
392
|
+
self.added_to_roster = True
|
330
393
|
self.send_last_presence()
|
331
394
|
|
332
395
|
async def __broadcast_pubsub_items(self):
|
333
|
-
|
396
|
+
if not self.is_friend:
|
397
|
+
return
|
398
|
+
if not self.added_to_roster:
|
399
|
+
return
|
400
|
+
cached_avatar = self.get_cached_avatar()
|
401
|
+
if cached_avatar is not None:
|
402
|
+
await self.xmpp.pubsub.broadcast_avatar(
|
403
|
+
self.jid.bare, self.session.user_jid, cached_avatar
|
404
|
+
)
|
405
|
+
nick = self.name
|
406
|
+
|
407
|
+
if nick is not None:
|
408
|
+
self.xmpp.pubsub.broadcast_nick(
|
409
|
+
self.session.user_jid,
|
410
|
+
self.jid.bare,
|
411
|
+
nick,
|
412
|
+
)
|
334
413
|
|
335
414
|
async def _set_roster(self, **kw):
|
336
415
|
try:
|
337
|
-
|
416
|
+
await self.xmpp["xep_0356"].set_roster(**kw)
|
338
417
|
except PermissionError:
|
339
|
-
|
418
|
+
await self.xmpp["xep_0356_old"].set_roster(**kw)
|
340
419
|
|
341
420
|
def send_friend_request(self, text: Optional[str] = None):
|
342
421
|
presence = self._make_presence(ptype="subscribe", pstatus=text, bare=True)
|
@@ -350,6 +429,8 @@ class LegacyContact(
|
|
350
429
|
:param text: Optional message from the friend to the user
|
351
430
|
"""
|
352
431
|
self.is_friend = True
|
432
|
+
self.added_to_roster = True
|
433
|
+
assert self.contact_pk is not None
|
353
434
|
self.log.debug("Accepting friend request")
|
354
435
|
presence = self._make_presence(ptype="subscribed", pstatus=text, bare=True)
|
355
436
|
self._send(presence, nick=True)
|
@@ -415,7 +496,7 @@ class LegacyContact(
|
|
415
496
|
their 'friends'".
|
416
497
|
"""
|
417
498
|
for ptype in "unsubscribe", "unsubscribed", "unavailable":
|
418
|
-
self.xmpp.send_presence(pfrom=self.jid, pto=self.
|
499
|
+
self.xmpp.send_presence(pfrom=self.jid, pto=self.user_jid.bare, ptype=ptype) # type: ignore
|
419
500
|
|
420
501
|
async def update_info(self):
|
421
502
|
"""
|
@@ -442,6 +523,41 @@ class LegacyContact(
|
|
442
523
|
"""
|
443
524
|
pass
|
444
525
|
|
526
|
+
def _make_presence(
|
527
|
+
self,
|
528
|
+
*,
|
529
|
+
last_seen: Optional[datetime.datetime] = None,
|
530
|
+
status_codes: Optional[set[int]] = None,
|
531
|
+
user_full_jid: Optional[JID] = None,
|
532
|
+
**presence_kwargs,
|
533
|
+
):
|
534
|
+
p = super()._make_presence(last_seen=last_seen, **presence_kwargs)
|
535
|
+
caps = self.xmpp.plugin["xep_0115"]
|
536
|
+
if p.get_from().resource and self._caps_ver:
|
537
|
+
p["caps"]["node"] = caps.caps_node
|
538
|
+
p["caps"]["hash"] = caps.hash
|
539
|
+
p["caps"]["ver"] = self._caps_ver
|
540
|
+
return p
|
541
|
+
|
542
|
+
@classmethod
|
543
|
+
def from_store(cls, session, stored: Contact, *args, **kwargs) -> Self:
|
544
|
+
contact = cls(
|
545
|
+
session,
|
546
|
+
cls.xmpp.LEGACY_CONTACT_ID_TYPE(stored.legacy_id),
|
547
|
+
stored.jid.username, # type: ignore
|
548
|
+
*args, # type: ignore
|
549
|
+
**kwargs, # type: ignore
|
550
|
+
)
|
551
|
+
contact.contact_pk = stored.id
|
552
|
+
contact._name = stored.nick
|
553
|
+
contact._is_friend = stored.is_friend
|
554
|
+
contact.added_to_roster = stored.added_to_roster
|
555
|
+
if (data := stored.extra_attributes) is not None:
|
556
|
+
contact.deserialize_extra_attributes(data)
|
557
|
+
contact._set_avatar_from_store(stored)
|
558
|
+
contact._caps_ver = stored.caps_ver
|
559
|
+
return contact
|
560
|
+
|
445
561
|
|
446
562
|
def is_markable(stanza: Union[Message, Presence]):
|
447
563
|
if isinstance(stanza, Presence):
|
slidge/contact/roster.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
import asyncio
|
2
2
|
import logging
|
3
|
-
|
3
|
+
import warnings
|
4
|
+
from typing import TYPE_CHECKING, AsyncIterator, Generic, Iterator, Optional, Type
|
4
5
|
|
5
6
|
from slixmpp import JID
|
7
|
+
from slixmpp.exceptions import IqError, XMPPError
|
6
8
|
from slixmpp.jid import JID_UNESCAPE_TRANSFORMATIONS, _unescape_node
|
7
9
|
|
8
10
|
from ..core.mixins.lock import NamedLockMixin
|
11
|
+
from ..db.store import ContactStore
|
9
12
|
from ..util import SubclassableOnce
|
10
13
|
from ..util.types import LegacyContactType, LegacyUserIdType
|
11
14
|
from .contact import LegacyContact
|
@@ -43,38 +46,58 @@ class LegacyRoster(
|
|
43
46
|
LegacyContact.get_self_or_unique_subclass()
|
44
47
|
)
|
45
48
|
self._contact_cls.xmpp = session.xmpp
|
49
|
+
self.__store: ContactStore = session.xmpp.store.contacts
|
46
50
|
|
47
51
|
self.session = session
|
48
|
-
self.
|
49
|
-
self._contacts_by_legacy_id: dict[LegacyUserIdType, LegacyContactType] = {}
|
50
|
-
self.log = logging.getLogger(f"{self.session.user.bare_jid}:roster")
|
52
|
+
self.log = logging.getLogger(f"{self.session.user_jid.bare}:roster")
|
51
53
|
self.user_legacy_id: Optional[LegacyUserIdType] = None
|
52
54
|
self.ready: asyncio.Future[bool] = self.session.xmpp.loop.create_future()
|
55
|
+
self.__filling = False
|
53
56
|
super().__init__()
|
54
57
|
|
55
58
|
def __repr__(self):
|
56
|
-
return f"<Roster of {self.session.
|
59
|
+
return f"<Roster of {self.session.user_jid}>"
|
57
60
|
|
58
|
-
def __iter__(self):
|
59
|
-
|
61
|
+
def __iter__(self) -> Iterator[LegacyContactType]:
|
62
|
+
with self.__store.session():
|
63
|
+
for stored in self.__store.get_all(user_pk=self.session.user_pk):
|
64
|
+
yield self._contact_cls.from_store(self.session, stored)
|
60
65
|
|
61
66
|
async def __finish_init_contact(
|
62
67
|
self, legacy_id: LegacyUserIdType, jid_username: str, *args, **kwargs
|
63
68
|
):
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
async with self.lock(("finish", legacy_id)):
|
70
|
+
with self.__store.session():
|
71
|
+
stored = self.__store.get_by_legacy_id(
|
72
|
+
self.session.user_pk, str(legacy_id)
|
73
|
+
)
|
74
|
+
if stored is not None:
|
75
|
+
if stored.updated:
|
76
|
+
return self._contact_cls.from_store(self.session, stored)
|
77
|
+
c: LegacyContact = self._contact_cls(
|
78
|
+
self.session, legacy_id, jid_username, *args, **kwargs
|
79
|
+
)
|
80
|
+
c.contact_pk = stored.id
|
81
|
+
else:
|
82
|
+
c = self._contact_cls(
|
83
|
+
self.session, legacy_id, jid_username, *args, **kwargs
|
84
|
+
)
|
85
|
+
try:
|
86
|
+
with c.updating_info():
|
87
|
+
await c.avatar_wrap_update_info()
|
88
|
+
except Exception as e:
|
89
|
+
raise XMPPError("internal-server-error", str(e))
|
90
|
+
c._caps_ver = await c.get_caps_ver(c.jid)
|
91
|
+
need_avatar = c.contact_pk is None
|
92
|
+
c.contact_pk = self.__store.update(c, commit=not self.__filling)
|
93
|
+
if need_avatar:
|
94
|
+
c._post_avatar_update()
|
72
95
|
return c
|
73
96
|
|
74
97
|
def known_contacts(self, only_friends=True) -> dict[str, LegacyContactType]:
|
75
98
|
if only_friends:
|
76
|
-
return {
|
77
|
-
return self
|
99
|
+
return {c.jid.bare: c for c in self if c.is_friend}
|
100
|
+
return {c.jid.bare: c for c in self}
|
78
101
|
|
79
102
|
async def by_jid(self, contact_jid: JID) -> LegacyContactType:
|
80
103
|
# """
|
@@ -89,16 +112,17 @@ class LegacyRoster(
|
|
89
112
|
# """
|
90
113
|
username = contact_jid.node
|
91
114
|
async with self.lock(("username", username)):
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
115
|
+
with self.__store.session():
|
116
|
+
stored = self.__store.get_by_jid(self.session.user_pk, contact_jid)
|
117
|
+
if stored is not None and stored.updated:
|
118
|
+
return self._contact_cls.from_store(self.session, stored)
|
119
|
+
|
120
|
+
legacy_id = await self.jid_username_to_legacy_id(username)
|
121
|
+
log.debug("Contact %s not found", contact_jid)
|
122
|
+
if self.get_lock(("legacy_id", legacy_id)):
|
123
|
+
log.debug("Already updating %s", contact_jid)
|
124
|
+
return await self.by_legacy_id(legacy_id)
|
125
|
+
return await self.__finish_init_contact(legacy_id, username)
|
102
126
|
|
103
127
|
async def by_legacy_id(
|
104
128
|
self, legacy_id: LegacyUserIdType, *args, **kwargs
|
@@ -121,20 +145,26 @@ class LegacyRoster(
|
|
121
145
|
if legacy_id == self.user_legacy_id:
|
122
146
|
raise ContactIsUser
|
123
147
|
async with self.lock(("legacy_id", legacy_id)):
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
log.debug("Contact %s not found", legacy_id)
|
128
|
-
if self.get_lock(("username", username)):
|
129
|
-
log.debug("Already updating %s", username)
|
130
|
-
jid = JID()
|
131
|
-
jid.node = username
|
132
|
-
jid.domain = self.session.xmpp.boundjid.bare
|
133
|
-
return await self.by_jid(jid)
|
134
|
-
c = await self.__finish_init_contact(
|
135
|
-
legacy_id, username, *args, **kwargs
|
148
|
+
with self.__store.session():
|
149
|
+
stored = self.__store.get_by_legacy_id(
|
150
|
+
self.session.user_pk, str(legacy_id)
|
136
151
|
)
|
137
|
-
|
152
|
+
if stored is not None and stored.updated:
|
153
|
+
return self._contact_cls.from_store(
|
154
|
+
self.session, stored, *args, **kwargs
|
155
|
+
)
|
156
|
+
|
157
|
+
username = await self.legacy_id_to_jid_username(legacy_id)
|
158
|
+
log.debug("Contact %s not found", legacy_id)
|
159
|
+
if self.get_lock(("username", username)):
|
160
|
+
log.debug("Already updating %s", username)
|
161
|
+
jid = JID()
|
162
|
+
jid.node = username
|
163
|
+
jid.domain = self.session.xmpp.boundjid.bare
|
164
|
+
return await self.by_jid(jid)
|
165
|
+
return await self.__finish_init_contact(
|
166
|
+
legacy_id, username, *args, **kwargs
|
167
|
+
)
|
138
168
|
|
139
169
|
async def by_stanza(self, s) -> LegacyContact:
|
140
170
|
# """
|
@@ -174,18 +204,63 @@ class LegacyRoster(
|
|
174
204
|
"""
|
175
205
|
return _unescape_node(jid_username)
|
176
206
|
|
177
|
-
async def
|
207
|
+
async def _fill(self):
|
208
|
+
try:
|
209
|
+
if hasattr(self.session.xmpp, "TEST_MODE"):
|
210
|
+
# dirty hack to avoid mocking xmpp server replies to this
|
211
|
+
# during tests
|
212
|
+
raise PermissionError
|
213
|
+
iq = await self.session.xmpp["xep_0356"].get_roster(
|
214
|
+
self.session.user_jid.bare
|
215
|
+
)
|
216
|
+
user_roster = iq["roster"]["items"]
|
217
|
+
except (PermissionError, IqError):
|
218
|
+
user_roster = None
|
219
|
+
|
220
|
+
with self.__store.session() as orm:
|
221
|
+
self.__filling = True
|
222
|
+
async for contact in self.fill():
|
223
|
+
if user_roster is None:
|
224
|
+
continue
|
225
|
+
item = contact.get_roster_item()
|
226
|
+
old = user_roster.get(contact.jid.bare)
|
227
|
+
if old is not None and all(
|
228
|
+
old[k] == item[contact.jid.bare][k]
|
229
|
+
for k in ("subscription", "groups", "name")
|
230
|
+
):
|
231
|
+
self.log.debug("No need to update roster")
|
232
|
+
continue
|
233
|
+
self.log.debug("Updating roster")
|
234
|
+
try:
|
235
|
+
await self.session.xmpp["xep_0356"].set_roster(
|
236
|
+
self.session.user_jid.bare,
|
237
|
+
item,
|
238
|
+
)
|
239
|
+
except (PermissionError, IqError) as e:
|
240
|
+
warnings.warn(f"Could not add to roster: {e}")
|
241
|
+
else:
|
242
|
+
contact._added_to_roster = True
|
243
|
+
orm.commit()
|
244
|
+
self.__filling = False
|
245
|
+
|
246
|
+
async def fill(self) -> AsyncIterator[LegacyContact]:
|
178
247
|
"""
|
179
248
|
Populate slidge's "virtual roster".
|
180
249
|
|
181
|
-
|
182
|
-
|
183
|
-
|
250
|
+
This should yield contacts that are meant to be added to the user's
|
251
|
+
roster, typically by using ``await self.by_legacy_id(contact_id)``.
|
252
|
+
Setting the contact nicknames, avatar, etc. should be in
|
253
|
+
:meth:`LegacyContact.update_info()`
|
254
|
+
|
255
|
+
It's not mandatory to override this method, but it is recommended way
|
256
|
+
to populate "friends" of the user. Calling
|
257
|
+
``await (await self.by_legacy_id(contact_id)).add_to_roster()``
|
258
|
+
accomplishes the same thing, but doing it in here allows to batch
|
259
|
+
DB queries and is better performance-wise.
|
184
260
|
|
185
|
-
Await ``Contact.add_to_roster()`` in here to add the contact to the
|
186
|
-
user's XMPP roster.
|
187
261
|
"""
|
188
|
-
|
262
|
+
return
|
263
|
+
yield
|
189
264
|
|
190
265
|
|
191
266
|
ESCAPE_TABLE = "".maketrans({v: k for k, v in JID_UNESCAPE_TRANSFORMATIONS.items()})
|
slidge/core/config.py
CHANGED
@@ -43,11 +43,18 @@ PORT__SHORT = "p"
|
|
43
43
|
|
44
44
|
HOME_DIR: Path
|
45
45
|
HOME_DIR__DOC = (
|
46
|
-
"
|
46
|
+
"Directory where slidge will writes it persistent data and cache. "
|
47
47
|
"Defaults to /var/lib/slidge/${SLIDGE_JID}. "
|
48
48
|
)
|
49
49
|
HOME_DIR__DYNAMIC_DEFAULT = True
|
50
50
|
|
51
|
+
DB_URL: str
|
52
|
+
DB_URL__DOC = (
|
53
|
+
"Database URL, see <https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls>. "
|
54
|
+
"Defaults to sqlite:///${HOME_DIR}/slidge.sqlite"
|
55
|
+
)
|
56
|
+
DB_URL__DYNAMIC_DEFAULT = True
|
57
|
+
|
51
58
|
USER_JID_VALIDATOR: str
|
52
59
|
USER_JID_VALIDATOR__DOC = (
|
53
60
|
"Regular expression to restrict users that can register to the gateway, by JID. "
|
@@ -70,7 +77,7 @@ UPLOAD_SERVICE__DOC = (
|
|
70
77
|
)
|
71
78
|
|
72
79
|
SECRET_KEY: Optional[str] = None
|
73
|
-
SECRET_KEY__DOC = "Encryption for disk storage"
|
80
|
+
SECRET_KEY__DOC = "Encryption for disk storage. Deprecated."
|
74
81
|
|
75
82
|
NO_ROSTER_PUSH = False
|
76
83
|
NO_ROSTER_PUSH__DOC = "Do not fill users' rosters with legacy contacts automatically"
|
@@ -135,11 +142,13 @@ PARTIAL_REGISTRATION_TIMEOUT__DOC = (
|
|
135
142
|
"a single step registration process is not enough."
|
136
143
|
)
|
137
144
|
|
138
|
-
LAST_SEEN_FALLBACK =
|
145
|
+
LAST_SEEN_FALLBACK = False
|
139
146
|
LAST_SEEN_FALLBACK__DOC = (
|
140
147
|
"When using XEP-0319 (Last User Interaction in Presence), use the presence status"
|
141
148
|
" to display the last seen information in the presence status. Useful for clients"
|
142
|
-
" that do not implement XEP-0319."
|
149
|
+
" that do not implement XEP-0319. Because of implementation details, this can increase"
|
150
|
+
" RAM usage and might be deprecated in the future. Ask your client dev for XEP-0319"
|
151
|
+
" support ;o)."
|
143
152
|
)
|
144
153
|
|
145
154
|
QR_TIMEOUT = 60
|
@@ -176,10 +185,7 @@ LOG_FORMAT__DOC = (
|
|
176
185
|
)
|
177
186
|
|
178
187
|
MAM_MAX_DAYS = 7
|
179
|
-
MAM_MAX_DAYS__DOC =
|
180
|
-
"Maximum number of days for group archive retention. "
|
181
|
-
"Since all text content stored in RAM right now, "
|
182
|
-
)
|
188
|
+
MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
|
183
189
|
|
184
190
|
CORRECTION_EMPTY_BODY_AS_RETRACTION = True
|
185
191
|
CORRECTION_EMPTY_BODY_AS_RETRACTION__DOC = (
|
@@ -211,6 +217,3 @@ DEV_MODE__DOC = (
|
|
211
217
|
"Enables an interactive python shell via chat commands, for admins."
|
212
218
|
"Not safe to use in prod, but great during dev."
|
213
219
|
)
|
214
|
-
|
215
|
-
SYNC_AVATAR = True
|
216
|
-
SYNC_AVATAR__DOC = "Sync the user XMPP avatar to legacy network (if supported)."
|