slidge 0.1.2__py3-none-any.whl → 0.2.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. slidge/__init__.py +3 -5
  2. slidge/__main__.py +2 -196
  3. slidge/__version__.py +5 -0
  4. slidge/command/adhoc.py +8 -1
  5. slidge/command/admin.py +5 -6
  6. slidge/command/base.py +1 -2
  7. slidge/command/register.py +32 -16
  8. slidge/command/user.py +85 -5
  9. slidge/contact/contact.py +93 -31
  10. slidge/contact/roster.py +54 -39
  11. slidge/core/config.py +13 -7
  12. slidge/core/gateway/base.py +139 -34
  13. slidge/core/gateway/disco.py +2 -4
  14. slidge/core/gateway/mam.py +1 -4
  15. slidge/core/gateway/ping.py +2 -3
  16. slidge/core/gateway/presence.py +1 -1
  17. slidge/core/gateway/registration.py +32 -21
  18. slidge/core/gateway/search.py +3 -5
  19. slidge/core/gateway/session_dispatcher.py +109 -51
  20. slidge/core/gateway/vcard_temp.py +6 -4
  21. slidge/core/mixins/__init__.py +11 -1
  22. slidge/core/mixins/attachment.py +15 -10
  23. slidge/core/mixins/avatar.py +66 -18
  24. slidge/core/mixins/base.py +8 -2
  25. slidge/core/mixins/message.py +11 -7
  26. slidge/core/mixins/message_maker.py +17 -9
  27. slidge/core/mixins/presence.py +14 -4
  28. slidge/core/pubsub.py +54 -212
  29. slidge/core/session.py +65 -33
  30. slidge/db/__init__.py +4 -0
  31. slidge/db/alembic/env.py +64 -0
  32. slidge/db/alembic/script.py.mako +26 -0
  33. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  34. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  35. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
  36. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +76 -0
  37. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  38. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  39. slidge/db/avatar.py +224 -0
  40. slidge/db/meta.py +65 -0
  41. slidge/db/models.py +365 -0
  42. slidge/db/store.py +976 -0
  43. slidge/group/archive.py +13 -14
  44. slidge/group/bookmarks.py +59 -56
  45. slidge/group/participant.py +81 -29
  46. slidge/group/room.py +242 -142
  47. slidge/main.py +201 -0
  48. slidge/migration.py +30 -0
  49. slidge/slixfix/__init__.py +35 -2
  50. slidge/slixfix/roster.py +11 -4
  51. slidge/slixfix/xep_0292/vcard4.py +1 -0
  52. slidge/util/db.py +1 -47
  53. slidge/util/test.py +21 -4
  54. slidge/util/types.py +24 -4
  55. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/METADATA +3 -1
  56. slidge-0.2.0a0.dist-info/RECORD +108 -0
  57. slidge/core/cache.py +0 -183
  58. slidge/util/schema.sql +0 -126
  59. slidge/util/sql.py +0 -508
  60. slidge-0.1.2.dist-info/RECORD +0 -96
  61. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/LICENSE +0 -0
  62. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/WHEEL +0 -0
  63. {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/entry_points.txt +0 -0
@@ -8,7 +8,16 @@ import re
8
8
  import tempfile
9
9
  from copy import copy
10
10
  from datetime import datetime
11
- from typing import TYPE_CHECKING, Callable, Collection, Optional, Sequence, Union
11
+ from typing import (
12
+ TYPE_CHECKING,
13
+ Any,
14
+ Callable,
15
+ Collection,
16
+ Mapping,
17
+ Optional,
18
+ Sequence,
19
+ Union,
20
+ )
12
21
 
13
22
  import aiohttp
14
23
  import qrcode
@@ -24,11 +33,11 @@ from ...command.admin import Exec
24
33
  from ...command.base import Command, FormField
25
34
  from ...command.chat_command import ChatCommandProvider
26
35
  from ...command.register import RegistrationType
36
+ from ...db import GatewayUser, SlidgeStore
37
+ from ...db.avatar import avatar_cache
27
38
  from ...slixfix.roster import RosterBackend
28
39
  from ...slixfix.xep_0292.vcard4 import VCard4Provider
29
40
  from ...util import ABCSubclassableOnceAtMost
30
- from ...util.db import GatewayUser, user_store
31
- from ...util.sql import db
32
41
  from ...util.types import AvatarType, MessageOrPresenceTypeVar
33
42
  from .. import config
34
43
  from ..mixins import MessageMixin
@@ -141,6 +150,23 @@ class BaseGateway(
141
150
  )
142
151
  REGISTRATION_QR_INSTRUCTIONS = "Flash this code or follow this link"
143
152
 
153
+ PREFERENCES = [
154
+ FormField(
155
+ var="sync_presence",
156
+ label="Propagate your XMPP presence to the legacy network.",
157
+ value="true",
158
+ required=True,
159
+ type="boolean",
160
+ ),
161
+ FormField(
162
+ var="sync_avatar",
163
+ label="Propagate your XMPP avatar to the legacy network.",
164
+ value="true",
165
+ required=True,
166
+ type="boolean",
167
+ ),
168
+ ]
169
+
144
170
  ROSTER_GROUP: str = "slidge"
145
171
  """
146
172
  Name of the group assigned to a :class:`.LegacyContact` automagically
@@ -202,6 +228,47 @@ class BaseGateway(
202
228
  mtype: MessageTypes = "chat"
203
229
  is_group = False
204
230
  _can_send_carbon = False
231
+ store: SlidgeStore
232
+ avatar_pk: int
233
+
234
+ AVATAR_ID_TYPE: Callable[[str], Any] = str
235
+ """
236
+ Modify this if the legacy network uses unique avatar IDs that are not strings.
237
+
238
+ This is required because we store those IDs as TEXT in the persistent SQL DB.
239
+ The callable specified here will receive is responsible for converting the
240
+ serialised-as-text version of the avatar unique ID back to the proper type.
241
+ Common example: ``int``.
242
+ """
243
+ # FIXME: do we really need this since we have session.xmpp_to_legacy_msg_id?
244
+ # (maybe we do)
245
+ LEGACY_MSG_ID_TYPE: Callable[[str], Any] = str
246
+ """
247
+ Modify this if the legacy network uses unique message IDs that are not strings.
248
+
249
+ This is required because we store those IDs as TEXT in the persistent SQL DB.
250
+ The callable specified here will receive is responsible for converting the
251
+ serialised-as-text version of the message unique ID back to the proper type.
252
+ Common example: ``int``.
253
+ """
254
+ LEGACY_CONTACT_ID_TYPE: Callable[[str], Any] = str
255
+ """
256
+ Modify this if the legacy network uses unique contact IDs that are not strings.
257
+
258
+ This is required because we store those IDs as TEXT in the persistent SQL DB.
259
+ The callable specified here is responsible for converting the
260
+ serialised-as-text version of the contact unique ID back to the proper type.
261
+ Common example: ``int``.
262
+ """
263
+ LEGACY_ROOM_ID_TYPE: Callable[[str], Any] = str
264
+ """
265
+ Modify this if the legacy network uses unique room IDs that are not strings.
266
+
267
+ This is required because we store those IDs as TEXT in the persistent SQL DB.
268
+ The callable specified here is responsible for converting the
269
+ serialised-as-text version of the room unique ID back to the proper type.
270
+ Common example: ``int``.
271
+ """
205
272
 
206
273
  def __init__(self):
207
274
  self.datetime_started = datetime.now()
@@ -222,7 +289,6 @@ class BaseGateway(
222
289
  },
223
290
  "xep_0100": {
224
291
  "component_name": self.COMPONENT_NAME,
225
- "user_store": user_store,
226
292
  "type": self.COMPONENT_TYPE,
227
293
  },
228
294
  "xep_0184": {
@@ -241,11 +307,15 @@ class BaseGateway(
241
307
  self.use_origin_id = False
242
308
 
243
309
  self.jid_validator: re.Pattern = re.compile(config.USER_JID_VALIDATOR)
244
- self.qr_pending_registrations = dict[str, asyncio.Future[bool]]()
310
+ self.qr_pending_registrations = dict[str, asyncio.Future[Optional[dict]]]()
245
311
 
246
312
  self.session_cls: BaseSession = BaseSession.get_unique_subclass()
247
313
  self.session_cls.xmpp = self
248
314
 
315
+ from ...group.room import LegacyMUC
316
+
317
+ LegacyMUC.get_unique_subclass().xmpp = self
318
+
249
319
  self.get_session_from_stanza: Callable[
250
320
  [Union[Message, Presence, Iq]], BaseSession
251
321
  ] = self.session_cls.from_stanza # type: ignore
@@ -255,7 +325,7 @@ class BaseGateway(
255
325
 
256
326
  self.register_plugins()
257
327
  self.__register_slixmpp_events()
258
- self.roster.set_backend(RosterBackend)
328
+ self.roster.set_backend(RosterBackend(self))
259
329
 
260
330
  self.register_plugin("pubsub", {"component_name": self.COMPONENT_NAME})
261
331
  self.pubsub: PubSubComponent = self["pubsub"]
@@ -294,7 +364,14 @@ class BaseGateway(
294
364
 
295
365
  self.__register_commands()
296
366
 
297
- db.mam_launch_cleanup_task(self.loop)
367
+ self.__mam_cleanup_task = self.loop.create_task(self.__mam_cleanup())
368
+
369
+ async def __mam_cleanup(self):
370
+ if not config.MAM_MAX_DAYS:
371
+ return
372
+ while True:
373
+ await asyncio.sleep(3600 * 6)
374
+ self.store.mam.nuke_older_than(config.MAM_MAX_DAYS)
298
375
 
299
376
  def __register_commands(self):
300
377
  for cls in Command.subclasses:
@@ -303,7 +380,7 @@ class BaseGateway(
303
380
  continue
304
381
  if cls is Exec:
305
382
  if config.DEV_MODE:
306
- log.warning("/!\ DEV MODE ENABLED /!\\")
383
+ log.warning(r"/!\ DEV MODE ENABLED /!\\")
307
384
  else:
308
385
  continue
309
386
  c = cls(self)
@@ -356,7 +433,7 @@ class BaseGateway(
356
433
  mfrom = msg.get_from()
357
434
  resource = mfrom.resource
358
435
  try:
359
- muc.user_resources.remove(resource)
436
+ muc.remove_user_resource(resource)
360
437
  except KeyError:
361
438
  # this actually happens quite frequently on for both beagle and monal
362
439
  # (not sure why?), but is of no consequence
@@ -374,11 +451,15 @@ class BaseGateway(
374
451
  await disco.del_feature(feature="urn:xmpp:http:upload:0", jid=self.boundjid)
375
452
  await self.plugin["xep_0115"].update_caps(jid=self.boundjid)
376
453
 
377
- await self.pubsub.set_avatar(
378
- jid=self.boundjid.bare, avatar=self.COMPONENT_AVATAR
379
- )
454
+ if self.COMPONENT_AVATAR:
455
+ cached_avatar = await avatar_cache.convert_or_get(
456
+ self.COMPONENT_AVATAR, None
457
+ )
458
+ self.avatar_pk = cached_avatar.pk
459
+ else:
460
+ cached_avatar = None
380
461
 
381
- for user in user_store.get_all():
462
+ for user in self.store.users.get_all():
382
463
  # TODO: before this, we should check if the user has removed us from their roster
383
464
  # while we were offline and trigger unregister from there. Presence probe does not seem
384
465
  # to work in this case, there must be another way. privileged entity could be used
@@ -396,10 +477,14 @@ class BaseGateway(
396
477
  )
397
478
  continue
398
479
  self.send_presence(
399
- pto=user.bare_jid, ptype="probe"
480
+ pto=user.jid.bare, ptype="probe"
400
481
  ) # ensure we get all resources for user
401
482
  session = self.session_cls.from_user(user)
402
483
  session.create_task(self.__login_wrap(session))
484
+ if cached_avatar is not None:
485
+ await self.pubsub.broadcast_avatar(
486
+ self.boundjid.bare, session.user_jid, cached_avatar
487
+ )
403
488
 
404
489
  log.info("Slidge has successfully started")
405
490
 
@@ -460,7 +545,7 @@ class BaseGateway(
460
545
  try:
461
546
  status = await session.login()
462
547
  except Exception as e:
463
- log.warning("Login problem for %s", session.user, exc_info=e)
548
+ log.warning("Login problem for %s", session.user_jid, exc_info=e)
464
549
  log.exception(e)
465
550
  session.send_gateway_status(f"Could not login: {e}", show="busy")
466
551
  session.send_gateway_message(
@@ -469,7 +554,7 @@ class BaseGateway(
469
554
  )
470
555
  return
471
556
 
472
- log.info("Login success for %s", session.user)
557
+ log.info("Login success for %s", session.user_jid)
473
558
  session.logged = True
474
559
  session.send_gateway_status("Syncing contacts…", show="dnd")
475
560
  await session.contacts.fill()
@@ -483,19 +568,18 @@ class BaseGateway(
483
568
  for c in session.contacts:
484
569
  # we need to receive presences directed at the contacts, in
485
570
  # order to send pubsub events for their +notify features
486
- self.send_presence(pfrom=c.jid, pto=session.user.bare_jid, ptype="probe")
571
+ self.send_presence(pfrom=c.jid, pto=session.user_jid.bare, ptype="probe")
487
572
  if status is None:
488
573
  session.send_gateway_status("Logged in", show="chat")
489
574
  else:
490
575
  session.send_gateway_status(status, show="chat")
491
- # If we stored users avatars (or their hash) persistently across slidge
492
- # restarts, we would not need to fetch it on startup
493
- session.create_task(self.__fetch_user_avatar(session))
576
+ if session.user.preferences.get("sync_avatar", False):
577
+ session.create_task(self.fetch_user_avatar(session))
494
578
 
495
- async def __fetch_user_avatar(self, session: BaseSession):
579
+ async def fetch_user_avatar(self, session: BaseSession):
496
580
  try:
497
581
  iq = await self.xmpp.plugin["xep_0060"].get_items(
498
- session.user.bare_jid,
582
+ session.user_jid.bare,
499
583
  self.xmpp.plugin["xep_0084"].stanza.MetaData.namespace,
500
584
  ifrom=self.boundjid.bare,
501
585
  )
@@ -577,7 +661,7 @@ class BaseGateway(
577
661
  )
578
662
 
579
663
  ifrom = iq.get_from()
580
- user = user_store.get_by_jid(ifrom)
664
+ user = self.store.users.get(ifrom)
581
665
  if user is None:
582
666
  raise XMPPError("registration-required")
583
667
 
@@ -629,7 +713,7 @@ class BaseGateway(
629
713
  async def make_registration_form(self, _jid, _node, _ifrom, iq: Iq):
630
714
  self.raise_if_not_allowed_jid(iq.get_from())
631
715
  reg = iq["register"]
632
- user = user_store.get_by_stanza(iq)
716
+ user = self.store.users.get_by_stanza(iq)
633
717
  log.debug("User found: %s", user)
634
718
 
635
719
  form = reg["form"]
@@ -675,18 +759,20 @@ class BaseGateway(
675
759
  reply.set_payload(reg)
676
760
  return reply
677
761
 
678
- async def user_prevalidate(self, ifrom: JID, form_dict: dict[str, Optional[str]]):
762
+ async def user_prevalidate(
763
+ self, ifrom: JID, form_dict: dict[str, Optional[str]]
764
+ ) -> Optional[Mapping]:
679
765
  # Pre validate a registration form using the content of self.REGISTRATION_FIELDS
680
766
  # before passing it to the plugin custom validation logic.
681
767
  for field in self.REGISTRATION_FIELDS:
682
768
  if field.required and not form_dict.get(field.var):
683
769
  raise ValueError(f"Missing field: '{field.label}'")
684
770
 
685
- await self.validate(ifrom, form_dict)
771
+ return await self.validate(ifrom, form_dict)
686
772
 
687
773
  async def validate(
688
774
  self, user_jid: JID, registration_form: dict[str, Optional[str]]
689
- ):
775
+ ) -> Optional[Mapping]:
690
776
  """
691
777
  Validate a user's initial registration form.
692
778
 
@@ -706,11 +792,19 @@ class BaseGateway(
706
792
 
707
793
  :param user_jid: JID of the user that has just registered
708
794
  :param registration_form: A dict where keys are the :attr:`.FormField.var` attributes
709
- of the :attr:`.BaseGateway.REGISTRATION_FIELDS` iterable
795
+ of the :attr:`.BaseGateway.REGISTRATION_FIELDS` iterable.
796
+ This dict can be modified and will be accessible as the ``legacy_module_data``
797
+ of the
798
+
799
+ :return : A dict that will be stored as the persistent "legacy_module_data"
800
+ for this user. If you don't return anything here, the whole registration_form
801
+ content will be stored.
710
802
  """
711
803
  raise NotImplementedError
712
804
 
713
- async def validate_two_factor_code(self, user: GatewayUser, code: str):
805
+ async def validate_two_factor_code(
806
+ self, user: GatewayUser, code: str
807
+ ) -> Optional[dict]:
714
808
  """
715
809
  Called when the user enters their 2FA code.
716
810
 
@@ -725,6 +819,9 @@ class BaseGateway(
725
819
  :attr:`.registration_form` attributes to get what you need.
726
820
  :param code: The code they entered, either via "chatbot" message or
727
821
  adhoc command
822
+
823
+ :return : A dict which keys and values will be added to the persistent "legacy_module_data"
824
+ for this user.
728
825
  """
729
826
  raise NotImplementedError
730
827
 
@@ -744,7 +841,10 @@ class BaseGateway(
744
841
  raise NotImplementedError
745
842
 
746
843
  async def confirm_qr(
747
- self, user_bare_jid: str, exception: Optional[Exception] = None
844
+ self,
845
+ user_bare_jid: str,
846
+ exception: Optional[Exception] = None,
847
+ legacy_data: Optional[dict] = None,
748
848
  ):
749
849
  """
750
850
  This method is meant to be called to finalize QR code-based registration
@@ -757,10 +857,12 @@ class BaseGateway(
757
857
  :class:`GatewayUser` instance
758
858
  :param exception: Optionally, an XMPPError to be raised to **not** confirm
759
859
  QR code flashing.
860
+ :param legacy_data: dict which keys and values will be added to the persistent
861
+ "legacy_module_data" for this user.
760
862
  """
761
863
  fut = self.qr_pending_registrations[user_bare_jid]
762
864
  if exception is None:
763
- fut.set_result(True)
865
+ fut.set_result(legacy_data)
764
866
  else:
765
867
  fut.set_exception(exception)
766
868
 
@@ -771,14 +873,17 @@ class BaseGateway(
771
873
  async def unregister(self, user: GatewayUser):
772
874
  """
773
875
  Optionally override this if you need to clean additional
774
- stuff after a user has been removed from the permanent user_store.
876
+ stuff after a user has been removed from the persistent user store.
775
877
 
776
878
  By default, this just calls :meth:`BaseSession.logout`.
777
879
 
778
880
  :param user:
779
881
  """
780
882
  session = self.get_session_from_user(user)
781
- await session.logout()
883
+ try:
884
+ await session.logout()
885
+ except NotImplementedError:
886
+ pass
782
887
 
783
888
  async def input(
784
889
  self, jid: JID, text=None, mtype: MessageTypes = "chat", **msg_kwargs
@@ -825,7 +930,7 @@ class BaseGateway(
825
930
  # """
826
931
  log.debug("Shutting down")
827
932
  tasks = []
828
- for user in user_store.get_all():
933
+ for user in self.store.users.get_all():
829
934
  tasks.append(self.session_cls.from_jid(user.jid).shutdown())
830
935
  self.send_presence(ptype="unavailable", pto=user.jid)
831
936
  return tasks
@@ -5,8 +5,6 @@ from slixmpp.exceptions import XMPPError
5
5
  from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
6
6
  from slixmpp.types import OptJid
7
7
 
8
- from ...util.db import user_store
9
-
10
8
  if TYPE_CHECKING:
11
9
  from .base import BaseGateway
12
10
 
@@ -38,7 +36,7 @@ class Disco:
38
36
  if ifrom is None:
39
37
  raise XMPPError("subscription-required")
40
38
 
41
- user = user_store.get_by_jid(ifrom)
39
+ user = self.xmpp.store.users.get(ifrom)
42
40
  if user is None:
43
41
  raise XMPPError("registration-required")
44
42
  session = self.xmpp.get_session_from_user(user)
@@ -63,7 +61,7 @@ class Disco:
63
61
  if jid != self.xmpp.boundjid.bare:
64
62
  return DiscoItems()
65
63
 
66
- user = user_store.get_by_jid(ifrom)
64
+ user = self.xmpp.store.users.get(ifrom)
67
65
  if user is None:
68
66
  raise XMPPError("registration-required")
69
67
 
@@ -3,8 +3,6 @@ from typing import TYPE_CHECKING
3
3
  from slixmpp import CoroutineCallback, Iq, StanzaPath
4
4
  from slixmpp.exceptions import XMPPError
5
5
 
6
- from ...util.db import user_store
7
-
8
6
  if TYPE_CHECKING:
9
7
  from .base import BaseGateway
10
8
 
@@ -46,8 +44,7 @@ class Mam:
46
44
  text="No MAM on the component itself, use a JID with a resource"
47
45
  )
48
46
 
49
- ifrom = iq.get_from()
50
- user = user_store.get_by_jid(ifrom)
47
+ user = self.xmpp.store.users.get(iq.get_from())
51
48
  if user is None:
52
49
  raise XMPPError("registration-required")
53
50
 
@@ -4,7 +4,6 @@ from slixmpp import CoroutineCallback, Iq, StanzaPath
4
4
  from slixmpp.exceptions import XMPPError
5
5
 
6
6
  from ...group import LegacyMUC
7
- from ...util.db import user_store
8
7
 
9
8
  if TYPE_CHECKING:
10
9
  from .base import BaseGateway
@@ -31,7 +30,7 @@ class Ping:
31
30
  iq.reply().send()
32
31
 
33
32
  ifrom = iq.get_from()
34
- user = user_store.get_by_jid(ifrom)
33
+ user = self.xmpp.store.users.get(ifrom)
35
34
  if user is None:
36
35
  raise XMPPError("registration-required")
37
36
 
@@ -60,7 +59,7 @@ class Ping:
60
59
 
61
60
  @staticmethod
62
61
  def __handle_muc_ping(muc: LegacyMUC, iq: Iq):
63
- if iq.get_from().resource in muc.user_resources:
62
+ if iq.get_from().resource in muc.get_user_resources():
64
63
  iq.reply().send()
65
64
  else:
66
65
  raise XMPPError("not-acceptable", etype="cancel", by=muc.jid)
@@ -47,7 +47,7 @@ class PresenceHandlerMixin:
47
47
  contact = await self.__get_contact(pres)
48
48
  except _IsDirectedAtComponent as e:
49
49
  e.session.send_gateway_message("Bye bye!")
50
- await e.session.kill_by_jid(e.session.user.jid)
50
+ await e.session.kill_by_jid(e.session.user_jid)
51
51
  return
52
52
 
53
53
  contact.is_friend = False
@@ -1,9 +1,12 @@
1
+ from __future__ import annotations
2
+
1
3
  import logging
2
4
  from typing import TYPE_CHECKING, Optional
3
5
 
4
6
  from slixmpp import JID, Iq
7
+ from slixmpp.exceptions import XMPPError
5
8
 
6
- from ...util.db import user_store
9
+ from ...db import GatewayUser
7
10
 
8
11
  if TYPE_CHECKING:
9
12
  from .base import BaseGateway
@@ -12,42 +15,50 @@ if TYPE_CHECKING:
12
15
  class Registration:
13
16
  def __init__(self, xmpp: "BaseGateway"):
14
17
  self.xmpp = xmpp
15
- xmpp["xep_0077"].api.register(
16
- user_store.get,
17
- "user_get",
18
- )
19
- xmpp["xep_0077"].api.register(
20
- user_store.remove,
21
- "user_remove",
22
- )
23
18
  xmpp["xep_0077"].api.register(
24
19
  self.xmpp.make_registration_form, "make_registration_form"
25
20
  )
21
+ xmpp["xep_0077"].api.register(self._user_get, "user_get")
26
22
  xmpp["xep_0077"].api.register(self._user_validate, "user_validate")
27
23
  xmpp["xep_0077"].api.register(self._user_modify, "user_modify")
24
+ # kept for slixmpp internal API compat
25
+ # TODO: either fully use slixmpp internal API or rewrite registration without it at all
26
+ xmpp["xep_0077"].api.register(lambda *a: None, "user_remove")
27
+
28
+ def get_user(self, jid: JID) -> GatewayUser | None:
29
+ return self.xmpp.store.users.get(jid)
30
+
31
+ async def _user_get(
32
+ self, _gateway_jid, _node, ifrom: JID, iq: Iq
33
+ ) -> GatewayUser | None:
34
+ if ifrom is None:
35
+ ifrom = iq.get_from()
36
+ return self.get_user(ifrom)
28
37
 
29
38
  async def _user_validate(self, _gateway_jid, _node, ifrom: JID, iq: Iq):
30
- """
31
- SliXMPP internal API stuff
32
- """
33
39
  xmpp = self.xmpp
34
40
  log.debug("User validate: %s", ifrom.bare)
35
41
  form_dict = {f.var: iq.get(f.var) for f in xmpp.REGISTRATION_FIELDS}
36
42
  xmpp.raise_if_not_allowed_jid(ifrom)
37
- await xmpp.user_prevalidate(ifrom, form_dict)
38
- log.info("New user: %s", ifrom.bare)
39
- user_store.add(ifrom, form_dict)
43
+ legacy_module_data = await xmpp.user_prevalidate(ifrom, form_dict)
44
+ if legacy_module_data is None:
45
+ legacy_module_data = form_dict
46
+ user = self.xmpp.store.users.new(
47
+ jid=ifrom,
48
+ legacy_module_data=legacy_module_data, # type:ignore
49
+ )
50
+ log.info("New user: %s", user)
40
51
 
41
52
  async def _user_modify(
42
53
  self, _gateway_jid, _node, ifrom: JID, form_dict: dict[str, Optional[str]]
43
54
  ):
44
- """
45
- SliXMPP internal API stuff
46
- """
47
- user = user_store.get_by_jid(ifrom)
48
- log.debug("Modify user: %s", user)
49
55
  await self.xmpp.user_prevalidate(ifrom, form_dict)
50
- user_store.add(ifrom, form_dict)
56
+ log.debug("Modify user: %s", ifrom)
57
+ user = self.xmpp.store.users.get(ifrom)
58
+ if user is None:
59
+ raise XMPPError("internal-server-error", "User not found")
60
+ user.legacy_module_data.update(form_dict)
61
+ self.xmpp.store.users.update(user)
51
62
 
52
63
 
53
64
  log = logging.getLogger(__name__)
@@ -3,8 +3,6 @@ from typing import TYPE_CHECKING
3
3
  from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
4
4
  from slixmpp.exceptions import XMPPError
5
5
 
6
- from ...util.db import user_store
7
-
8
6
  if TYPE_CHECKING:
9
7
  from .base import BaseGateway
10
8
 
@@ -29,7 +27,7 @@ class Search:
29
27
  """
30
28
  Prepare the search form using :attr:`.BaseSession.SEARCH_FIELDS`
31
29
  """
32
- user = user_store.get_by_jid(ifrom)
30
+ user = self.xmpp.store.users.get(ifrom)
33
31
  if user is None:
34
32
  raise XMPPError(text="Search is only allowed for registered users")
35
33
 
@@ -47,7 +45,7 @@ class Search:
47
45
  """
48
46
  Handles a search request
49
47
  """
50
- user = user_store.get_by_jid(ifrom)
48
+ user = self.xmpp.store.users.get(ifrom)
51
49
  if user is None:
52
50
  raise XMPPError(text="Search is only allowed for registered users")
53
51
 
@@ -70,7 +68,7 @@ class Search:
70
68
  if iq.get_to() != self.xmpp.boundjid.bare:
71
69
  raise XMPPError("bad-request", "This can only be used on the component JID")
72
70
 
73
- user = user_store.get_by_jid(iq.get_from())
71
+ user = self.xmpp.store.users.get(iq.get_from())
74
72
  if user is None:
75
73
  raise XMPPError("not-authorized", "Register to the gateway first")
76
74