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

Sign up to get free protection for your applications and to get access to all the features.
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 +100 -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 +77 -25
  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.3.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.3.dist-info/RECORD +0 -96
  61. {slidge-0.1.3.dist-info → slidge-0.2.0a0.dist-info}/LICENSE +0 -0
  62. {slidge-0.1.3.dist-info → slidge-0.2.0a0.dist-info}/WHEEL +0 -0
  63. {slidge-0.1.3.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