slidge 0.2.12__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.
Files changed (93) hide show
  1. slidge/__init__.py +5 -2
  2. slidge/command/adhoc.py +9 -3
  3. slidge/command/admin.py +16 -12
  4. slidge/command/base.py +16 -12
  5. slidge/command/chat_command.py +25 -16
  6. slidge/command/user.py +7 -8
  7. slidge/contact/contact.py +123 -210
  8. slidge/contact/roster.py +108 -105
  9. slidge/core/config.py +2 -43
  10. slidge/core/dispatcher/caps.py +9 -2
  11. slidge/core/dispatcher/disco.py +13 -3
  12. slidge/core/dispatcher/message/__init__.py +1 -1
  13. slidge/core/dispatcher/message/chat_state.py +17 -8
  14. slidge/core/dispatcher/message/marker.py +7 -5
  15. slidge/core/dispatcher/message/message.py +120 -93
  16. slidge/core/dispatcher/muc/__init__.py +1 -1
  17. slidge/core/dispatcher/muc/admin.py +4 -4
  18. slidge/core/dispatcher/muc/mam.py +10 -6
  19. slidge/core/dispatcher/muc/misc.py +4 -2
  20. slidge/core/dispatcher/muc/owner.py +5 -3
  21. slidge/core/dispatcher/muc/ping.py +3 -1
  22. slidge/core/dispatcher/presence.py +26 -15
  23. slidge/core/dispatcher/registration.py +20 -12
  24. slidge/core/dispatcher/search.py +7 -3
  25. slidge/core/dispatcher/session_dispatcher.py +13 -5
  26. slidge/core/dispatcher/util.py +37 -27
  27. slidge/core/dispatcher/vcard.py +7 -4
  28. slidge/core/gateway.py +177 -87
  29. slidge/core/mixins/__init__.py +1 -11
  30. slidge/core/mixins/attachment.py +200 -147
  31. slidge/core/mixins/avatar.py +105 -177
  32. slidge/core/mixins/base.py +3 -1
  33. slidge/core/mixins/db.py +50 -2
  34. slidge/core/mixins/disco.py +1 -1
  35. slidge/core/mixins/message.py +19 -17
  36. slidge/core/mixins/message_maker.py +29 -15
  37. slidge/core/mixins/message_text.py +67 -30
  38. slidge/core/mixins/presence.py +94 -37
  39. slidge/core/pubsub.py +42 -47
  40. slidge/core/session.py +95 -60
  41. slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +361 -0
  42. slidge/db/avatar.py +150 -119
  43. slidge/db/meta.py +33 -22
  44. slidge/db/models.py +69 -117
  45. slidge/db/store.py +414 -1094
  46. slidge/group/archive.py +65 -55
  47. slidge/group/bookmarks.py +96 -59
  48. slidge/group/participant.py +150 -144
  49. slidge/group/room.py +345 -327
  50. slidge/main.py +34 -22
  51. slidge/migration.py +17 -29
  52. slidge/slixfix/__init__.py +20 -4
  53. slidge/slixfix/delivery_receipt.py +6 -4
  54. slidge/slixfix/link_preview/link_preview.py +1 -1
  55. slidge/slixfix/link_preview/stanza.py +1 -1
  56. slidge/slixfix/roster.py +5 -7
  57. slidge/slixfix/xep_0077/register.py +8 -8
  58. slidge/slixfix/xep_0077/stanza.py +7 -7
  59. slidge/slixfix/xep_0100/gateway.py +12 -13
  60. slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
  61. slidge/slixfix/xep_0292/vcard4.py +12 -2
  62. slidge/util/archive_msg.py +11 -5
  63. slidge/util/conf.py +27 -21
  64. slidge/util/jid_escaping.py +1 -1
  65. slidge/{core/mixins → util}/lock.py +6 -6
  66. slidge/util/test.py +30 -29
  67. slidge/util/types.py +24 -18
  68. slidge/util/util.py +26 -22
  69. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/METADATA +1 -1
  70. slidge-0.3.0.dist-info/RECORD +95 -0
  71. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/WHEEL +1 -1
  72. slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py +0 -33
  73. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -36
  74. slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +0 -85
  75. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -36
  76. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -37
  77. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -41
  78. slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +0 -52
  79. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -42
  80. slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -61
  81. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -48
  82. slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -43
  83. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -139
  84. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -50
  85. slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +0 -79
  86. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -214
  87. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -52
  88. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -34
  89. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -26
  90. slidge-0.2.12.dist-info/RECORD +0 -112
  91. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/entry_points.txt +0 -0
  92. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/licenses/LICENSE +0 -0
  93. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/top_level.txt +0 -0
slidge/core/gateway.py CHANGED
@@ -8,17 +8,19 @@ import re
8
8
  import tempfile
9
9
  from copy import copy
10
10
  from datetime import datetime
11
- from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Sequence, Union
11
+ from pathlib import Path
12
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Sequence, Type, Union, cast
12
13
 
13
14
  import aiohttp
14
15
  import qrcode
15
16
  from slixmpp import JID, ComponentXMPP, Iq, Message, Presence
16
17
  from slixmpp.exceptions import IqError, IqTimeout, XMPPError
18
+ from slixmpp.plugins.xep_0004 import Form
17
19
  from slixmpp.plugins.xep_0060.stanza import OwnerAffiliation
18
20
  from slixmpp.types import MessageTypes
19
21
  from slixmpp.xmlstream.xmlstream import NotConnectedError
20
22
 
21
- from slidge import command # noqa: F401
23
+ from slidge import LegacyContact, command # noqa: F401
22
24
  from slidge.command.adhoc import AdhocProvider
23
25
  from slidge.command.admin import Exec
24
26
  from slidge.command.base import Command, FormField
@@ -27,15 +29,17 @@ from slidge.command.register import RegistrationType
27
29
  from slidge.core import config
28
30
  from slidge.core.dispatcher.session_dispatcher import SessionDispatcher
29
31
  from slidge.core.mixins import MessageMixin
32
+ from slidge.core.mixins.avatar import convert_avatar
30
33
  from slidge.core.pubsub import PubSubComponent
31
34
  from slidge.core.session import BaseSession
32
35
  from slidge.db import GatewayUser, SlidgeStore
33
- from slidge.db.avatar import avatar_cache
36
+ from slidge.db.avatar import CachedAvatar, avatar_cache
37
+ from slidge.db.meta import JSONSerializable
34
38
  from slidge.slixfix import PrivilegedIqError
35
39
  from slidge.slixfix.delivery_receipt import DeliveryReceipt
36
40
  from slidge.slixfix.roster import RosterBackend
37
41
  from slidge.util import ABCSubclassableOnceAtMost
38
- from slidge.util.types import AvatarType, MessageOrPresenceTypeVar
42
+ from slidge.util.types import Avatar, MessageOrPresenceTypeVar
39
43
  from slidge.util.util import timeit
40
44
 
41
45
  if TYPE_CHECKING:
@@ -95,7 +99,7 @@ class BaseGateway(
95
99
  """Name of the component, as seen in service discovery by XMPP clients"""
96
100
  COMPONENT_TYPE: str = ""
97
101
  """Type of the gateway, should follow https://xmpp.org/registrar/disco-categories.html"""
98
- COMPONENT_AVATAR: Optional[AvatarType] = None
102
+ COMPONENT_AVATAR: Optional[Avatar | Path | str] = None
99
103
  """
100
104
  Path, bytes or URL used by the component as an avatar.
101
105
  """
@@ -147,6 +151,34 @@ class BaseGateway(
147
151
  required=True,
148
152
  type="boolean",
149
153
  ),
154
+ FormField(
155
+ var="always_invite_when_adding_bookmarks",
156
+ label="Send an invitation to join MUCs after adding them to the bookmarks.",
157
+ value="true",
158
+ required=True,
159
+ type="boolean",
160
+ ),
161
+ FormField(
162
+ var="last_seen_fallback",
163
+ label="Use contact presence status message to show when they were last seen.",
164
+ value="true",
165
+ required=True,
166
+ type="boolean",
167
+ ),
168
+ FormField(
169
+ var="roster_push",
170
+ label="Add contacts to your roster.",
171
+ value="true",
172
+ required=True,
173
+ type="boolean",
174
+ ),
175
+ FormField(
176
+ var="reaction_fallback",
177
+ label="Receive fallback messages for reactions (for legacy XMPP clients)",
178
+ value="false",
179
+ required=True,
180
+ type="boolean",
181
+ ),
150
182
  ]
151
183
 
152
184
  ROSTER_GROUP: str = "slidge"
@@ -211,7 +243,6 @@ class BaseGateway(
211
243
  is_group = False
212
244
  _can_send_carbon = False
213
245
  store: SlidgeStore
214
- avatar_pk: int
215
246
 
216
247
  AVATAR_ID_TYPE: Callable[[str], Any] = str
217
248
  """
@@ -253,8 +284,9 @@ class BaseGateway(
253
284
  """
254
285
 
255
286
  http: aiohttp.ClientSession
287
+ avatar: CachedAvatar | None = None
256
288
 
257
- def __init__(self):
289
+ def __init__(self) -> None:
258
290
  if config.COMPONENT_NAME:
259
291
  self.COMPONENT_NAME = config.COMPONENT_NAME
260
292
  if config.WELCOME_MESSAGE:
@@ -298,18 +330,13 @@ class BaseGateway(
298
330
  self.jid_validator: re.Pattern = re.compile(config.USER_JID_VALIDATOR)
299
331
  self.qr_pending_registrations = dict[str, asyncio.Future[Optional[dict]]]()
300
332
 
301
- self.session_cls: BaseSession = BaseSession.get_unique_subclass()
302
- self.session_cls.xmpp = self
303
-
304
- from ..group.room import LegacyMUC
305
-
306
- LegacyMUC.get_self_or_unique_subclass().xmpp = self
333
+ self.__setup_legacy_module_subclasses()
307
334
 
308
335
  self.get_session_from_stanza: Callable[
309
336
  [Union[Message, Presence, Iq]], BaseSession
310
- ] = self.session_cls.from_stanza # type: ignore
337
+ ] = self._session_cls.from_stanza
311
338
  self.get_session_from_user: Callable[[GatewayUser], BaseSession] = (
312
- self.session_cls.from_user
339
+ self._session_cls.from_user
313
340
  )
314
341
 
315
342
  self.register_plugins()
@@ -349,13 +376,41 @@ class BaseGateway(
349
376
 
350
377
  MessageMixin.__init__(self) # ComponentXMPP does not call super().__init__()
351
378
 
352
- async def __set_http(self):
379
+ def __setup_legacy_module_subclasses(self):
380
+ from ..contact.roster import LegacyRoster
381
+ from ..group.bookmarks import LegacyBookmarks
382
+ from ..group.room import LegacyMUC, LegacyParticipant
383
+
384
+ session_cls: Type[BaseSession] = cast(
385
+ Type[BaseSession], BaseSession.get_unique_subclass()
386
+ )
387
+ contact_cls = LegacyContact.get_self_or_unique_subclass()
388
+ muc_cls = LegacyMUC.get_self_or_unique_subclass()
389
+ participant_cls = LegacyParticipant.get_self_or_unique_subclass()
390
+ bookmarks_cls = LegacyBookmarks.get_self_or_unique_subclass()
391
+ roster_cls = LegacyRoster.get_self_or_unique_subclass()
392
+
393
+ session_cls.xmpp = self # type:ignore[attr-defined]
394
+ contact_cls.xmpp = self # type:ignore[attr-defined]
395
+ muc_cls.xmpp = self # type:ignore[attr-defined]
396
+
397
+ self._session_cls = session_cls
398
+ session_cls._bookmarks_cls = bookmarks_cls # type:ignore[misc,assignment]
399
+ session_cls._roster_cls = roster_cls # type:ignore[misc,assignment]
400
+ LegacyRoster._contact_cls = contact_cls # type:ignore[misc]
401
+ LegacyBookmarks._muc_cls = muc_cls # type:ignore[misc]
402
+ LegacyMUC._participant_cls = participant_cls # type:ignore[misc]
403
+
404
+ async def kill_session(self, jid: JID) -> None:
405
+ await self._session_cls.kill_by_jid(jid)
406
+
407
+ async def __set_http(self) -> None:
353
408
  self.http = aiohttp.ClientSession()
354
409
  if getattr(self, "_test_mode", False):
355
410
  return
356
411
  avatar_cache.http = self.http
357
412
 
358
- def __register_commands(self):
413
+ def __register_commands(self) -> None:
359
414
  for cls in Command.subclasses:
360
415
  if any(x is NotImplemented for x in [cls.CHAT_COMMAND, cls.NODE, cls.NAME]):
361
416
  log.debug("Not adding command '%s' because it looks abstract", cls)
@@ -370,7 +425,7 @@ class BaseGateway(
370
425
  self.__adhoc_handler.register(c)
371
426
  self.__chat_commands_handler.register(c)
372
427
 
373
- def __exception_handler(self, loop: asyncio.AbstractEventLoop, context):
428
+ def __exception_handler(self, loop: asyncio.AbstractEventLoop, context) -> None:
374
429
  """
375
430
  Called when a task created by loop.create_task() raises an Exception
376
431
 
@@ -390,7 +445,7 @@ class BaseGateway(
390
445
  self.has_crashed = True
391
446
  loop.stop()
392
447
 
393
- def __register_slixmpp_events(self):
448
+ def __register_slixmpp_events(self) -> None:
394
449
  self.del_event_handler("presence_subscribe", self._handle_subscribe)
395
450
  self.del_event_handler("presence_unsubscribe", self._handle_unsubscribe)
396
451
  self.del_event_handler("presence_subscribed", self._handle_subscribed)
@@ -403,16 +458,32 @@ class BaseGateway(
403
458
  self.add_event_handler("disconnected", self.connect)
404
459
 
405
460
  def __register_slixmpp_api(self) -> None:
406
- self.plugin["xep_0231"].api.register(self.store.bob.get_bob, "get_bob")
407
- self.plugin["xep_0231"].api.register(self.store.bob.set_bob, "set_bob")
408
- self.plugin["xep_0231"].api.register(self.store.bob.del_bob, "del_bob")
461
+ def with_session(func, commit: bool = True):
462
+ def wrapped(*a, **kw):
463
+ with self.store.session() as orm:
464
+ res = func(orm, *a, **kw)
465
+ if commit:
466
+ orm.commit()
467
+ return res
468
+
469
+ return wrapped
470
+
471
+ self.plugin["xep_0231"].api.register(
472
+ with_session(self.store.bob.get_bob, False), "get_bob"
473
+ )
474
+ self.plugin["xep_0231"].api.register(
475
+ with_session(self.store.bob.set_bob), "set_bob"
476
+ )
477
+ self.plugin["xep_0231"].api.register(
478
+ with_session(self.store.bob.del_bob), "del_bob"
479
+ )
409
480
 
410
481
  @property # type: ignore
411
- def jid(self):
482
+ def jid(self): # type:ignore[override]
412
483
  # Override to avoid slixmpp deprecation warnings.
413
484
  return self.boundjid
414
485
 
415
- async def __on_session_start(self, event):
486
+ async def __on_session_start(self, event) -> None:
416
487
  log.debug("Gateway session start: %s", event)
417
488
 
418
489
  # prevents XMPP clients from considering the gateway as an HTTP upload
@@ -421,47 +492,48 @@ class BaseGateway(
421
492
  await self.plugin["xep_0115"].update_caps(jid=self.boundjid)
422
493
 
423
494
  if self.COMPONENT_AVATAR is not None:
495
+ log.debug("Setting gateway avatar…")
496
+ avatar = convert_avatar(self.COMPONENT_AVATAR, "!!---slidge---special---")
497
+ assert avatar is not None
424
498
  try:
425
- cached_avatar = await avatar_cache.convert_or_get(self.COMPONENT_AVATAR)
499
+ cached_avatar = await avatar_cache.convert_or_get(avatar)
426
500
  except Exception as e:
427
501
  log.exception("Could not set the component avatar.", exc_info=e)
428
502
  cached_avatar = None
429
503
  else:
430
504
  assert cached_avatar is not None
431
- self.avatar_pk = cached_avatar.pk
505
+ self.avatar = cached_avatar
432
506
  else:
433
507
  cached_avatar = None
434
508
 
435
- for user in self.store.users.get_all():
436
- # TODO: before this, we should check if the user has removed us from their roster
437
- # while we were offline and trigger unregister from there. Presence probe does not seem
438
- # to work in this case, there must be another way. privileged entity could be used
439
- # as last resort.
440
- try:
441
- await self["xep_0100"].add_component_to_roster(user.jid)
442
- await self.__add_component_to_mds_whitelist(user.jid)
443
- except (IqError, IqTimeout) as e:
444
- # TODO: remove the user when this happens? or at least
445
- # this can happen when the user has unsubscribed from the XMPP server
446
- log.warning(
447
- "Error with user %s, not logging them automatically",
448
- user,
449
- exc_info=e,
450
- )
451
- continue
452
- self.send_presence(
453
- pto=user.jid.bare, ptype="probe"
454
- ) # ensure we get all resources for user
455
- session = self.session_cls.from_user(user)
456
- session.create_task(self.login_wrap(session))
457
- if cached_avatar is not None:
458
- await self.pubsub.broadcast_avatar(
459
- self.boundjid.bare, session.user_jid, cached_avatar
460
- )
509
+ with self.store.session() as orm:
510
+ for user in orm.query(GatewayUser).all():
511
+ # TODO: before this, we should check if the user has removed us from their roster
512
+ # while we were offline and trigger unregister from there. Presence probe does not seem
513
+ # to work in this case, there must be another way. privileged entity could be used
514
+ # as last resort.
515
+ try:
516
+ await self["xep_0100"].add_component_to_roster(user.jid)
517
+ await self.__add_component_to_mds_whitelist(user.jid)
518
+ except (IqError, IqTimeout) as e:
519
+ # TODO: remove the user when this happens? or at least
520
+ # this can happen when the user has unsubscribed from the XMPP server
521
+ log.warning(
522
+ "Error with user %s, not logging them automatically",
523
+ user,
524
+ exc_info=e,
525
+ )
526
+ continue
527
+ session = self._session_cls.from_user(user)
528
+ session.create_task(self.login_wrap(session))
529
+ if cached_avatar is not None:
530
+ await self.pubsub.broadcast_avatar(
531
+ self.boundjid.bare, session.user_jid, cached_avatar
532
+ )
461
533
 
462
534
  log.info("Slidge has successfully started")
463
535
 
464
- async def __add_component_to_mds_whitelist(self, user_jid: JID):
536
+ async def __add_component_to_mds_whitelist(self, user_jid: JID) -> None:
465
537
  # Uses privileged entity to add ourselves to the whitelist of the PEP
466
538
  # MDS node so we receive MDS events
467
539
  iq_creation = Iq(sto=user_jid.bare, sfrom=user_jid, stype="set")
@@ -515,7 +587,7 @@ class BaseGateway(
515
587
  )
516
588
 
517
589
  @timeit
518
- async def login_wrap(self, session: "BaseSession"):
590
+ async def login_wrap(self, session: "BaseSession") -> None:
519
591
  session.send_gateway_status("Logging in…", show="dnd")
520
592
  session.is_logging_in = True
521
593
  try:
@@ -534,7 +606,8 @@ class BaseGateway(
534
606
  log.info("Login success for %s", session.user_jid)
535
607
  session.logged = True
536
608
  session.send_gateway_status("Syncing contacts…", show="dnd")
537
- await session.contacts._fill()
609
+ with self.store.session() as orm:
610
+ await session.contacts._fill(orm)
538
611
  if not (r := session.contacts.ready).done():
539
612
  r.set_result(True)
540
613
  if self.GROUPS:
@@ -542,10 +615,7 @@ class BaseGateway(
542
615
  await session.bookmarks.fill()
543
616
  if not (r := session.bookmarks.ready).done():
544
617
  r.set_result(True)
545
- for c in session.contacts:
546
- # we need to receive presences directed at the contacts, in
547
- # order to send pubsub events for their +notify features
548
- self.send_presence(pfrom=c.jid, pto=session.user_jid.bare, ptype="probe")
618
+ self.send_presence(pto=session.user.jid.bare, ptype="probe")
549
619
  if status is None:
550
620
  session.send_gateway_status("Logged in", show="chat")
551
621
  else:
@@ -553,9 +623,12 @@ class BaseGateway(
553
623
  if session.user.preferences.get("sync_avatar", False):
554
624
  session.create_task(self.fetch_user_avatar(session))
555
625
  else:
556
- self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
626
+ with self.store.session(expire_on_commit=False) as orm:
627
+ session.user.avatar_hash = None
628
+ orm.add(session.user)
629
+ orm.commit()
557
630
 
558
- async def fetch_user_avatar(self, session: BaseSession):
631
+ async def fetch_user_avatar(self, session: BaseSession) -> None:
559
632
  try:
560
633
  iq = await self.xmpp.plugin["xep_0060"].get_items(
561
634
  session.user_jid.bare,
@@ -573,7 +646,10 @@ class BaseGateway(
573
646
  except NotImplementedError:
574
647
  pass
575
648
  else:
576
- self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
649
+ with self.store.session(expire_on_commit=False) as orm:
650
+ session.user.avatar_hash = None
651
+ orm.add(session.user)
652
+ orm.commit()
577
653
  return
578
654
  await self.__dispatcher.on_avatar_metadata_info(
579
655
  session, iq["pubsub"]["items"]["item"]["avatar_metadata"]["info"]
@@ -622,11 +698,11 @@ class BaseGateway(
622
698
 
623
699
  def get_session_from_jid(self, j: JID):
624
700
  try:
625
- return self.session_cls.from_jid(j)
701
+ return self._session_cls.from_jid(j)
626
702
  except XMPPError:
627
703
  pass
628
704
 
629
- def exception(self, exception: Exception):
705
+ def exception(self, exception: Exception) -> None:
630
706
  # """
631
707
  # Called when a task created by slixmpp's internal (eg, on slix events) raises an Exception.
632
708
  #
@@ -656,18 +732,26 @@ class BaseGateway(
656
732
  self.loop.stop()
657
733
  exit(1)
658
734
 
659
- def re_login(self, session: "BaseSession"):
660
- async def w():
735
+ def re_login(self, session: "BaseSession") -> None:
736
+ async def w() -> None:
661
737
  session.cancel_all_tasks()
662
- await session.logout()
738
+ try:
739
+ await session.logout()
740
+ except NotImplementedError:
741
+ pass
663
742
  await self.login_wrap(session)
664
743
 
665
744
  session.create_task(w())
666
745
 
667
- async def make_registration_form(self, _jid, _node, _ifrom, iq: Iq):
746
+ async def make_registration_form(
747
+ self, _jid: JID, _node: str, _ifrom: JID, iq: Iq
748
+ ) -> Form:
668
749
  self.raise_if_not_allowed_jid(iq.get_from())
669
750
  reg = iq["register"]
670
- user = self.store.users.get_by_stanza(iq)
751
+ with self.store.session() as orm:
752
+ user = (
753
+ orm.query(GatewayUser).filter_by(jid=iq.get_from().bare).one_or_none()
754
+ )
671
755
  log.debug("User found: %s", user)
672
756
 
673
757
  form = reg["form"]
@@ -715,7 +799,7 @@ class BaseGateway(
715
799
 
716
800
  async def user_prevalidate(
717
801
  self, ifrom: JID, form_dict: dict[str, Optional[str]]
718
- ) -> Optional[Mapping]:
802
+ ) -> JSONSerializable | None:
719
803
  # Pre validate a registration form using the content of self.REGISTRATION_FIELDS
720
804
  # before passing it to the plugin custom validation logic.
721
805
  for field in self.REGISTRATION_FIELDS:
@@ -726,7 +810,7 @@ class BaseGateway(
726
810
 
727
811
  async def validate(
728
812
  self, user_jid: JID, registration_form: dict[str, Optional[str]]
729
- ) -> Optional[Mapping]:
813
+ ) -> JSONSerializable | None:
730
814
  """
731
815
  Validate a user's initial registration form.
732
816
 
@@ -758,7 +842,7 @@ class BaseGateway(
758
842
 
759
843
  async def validate_two_factor_code(
760
844
  self, user: GatewayUser, code: str
761
- ) -> Optional[dict]:
845
+ ) -> JSONSerializable | None:
762
846
  """
763
847
  Called when the user enters their 2FA code.
764
848
 
@@ -798,8 +882,8 @@ class BaseGateway(
798
882
  self,
799
883
  user_bare_jid: str,
800
884
  exception: Optional[Exception] = None,
801
- legacy_data: Optional[dict] = None,
802
- ):
885
+ legacy_data: Optional[JSONSerializable] = None,
886
+ ) -> None:
803
887
  """
804
888
  This method is meant to be called to finalize QR code-based registration
805
889
  flows, once the legacy service confirms the QR flashing.
@@ -820,32 +904,35 @@ class BaseGateway(
820
904
  else:
821
905
  fut.set_exception(exception)
822
906
 
823
- async def unregister_user(self, user: GatewayUser):
907
+ async def unregister_user(self, user: GatewayUser) -> None:
824
908
  self.send_presence(
825
909
  pshow="dnd",
826
910
  pstatus="You unregistered from this gateway.",
827
911
  pto=user.jid,
828
912
  )
829
913
  await self.xmpp.plugin["xep_0077"].api["user_remove"](None, None, user.jid)
830
- await self.xmpp.session_cls.kill_by_jid(user.jid)
914
+ await self.xmpp._session_cls.kill_by_jid(user.jid)
831
915
 
832
- async def unregister(self, user: GatewayUser):
916
+ async def unregister(self, session: BaseSession) -> None:
833
917
  """
834
918
  Optionally override this if you need to clean additional
835
919
  stuff after a user has been removed from the persistent user store.
836
920
 
837
921
  By default, this just calls :meth:`BaseSession.logout`.
838
922
 
839
- :param user:
923
+ :param session: The session of the user who just unregistered
840
924
  """
841
- session = self.get_session_from_user(user)
842
925
  try:
843
926
  await session.logout()
844
927
  except NotImplementedError:
845
928
  pass
846
929
 
847
930
  async def input(
848
- self, jid: JID, text=None, mtype: MessageTypes = "chat", **msg_kwargs
931
+ self,
932
+ jid: JID,
933
+ text: str | None = None,
934
+ mtype: MessageTypes = "chat",
935
+ **input_kwargs: Any,
849
936
  ) -> str:
850
937
  """
851
938
  Request arbitrary user input using a simple chat message, and await the result.
@@ -858,9 +945,11 @@ class BaseGateway(
858
945
  :param mtype: Message type
859
946
  :return: The user's reply
860
947
  """
861
- return await self.__chat_commands_handler.input(jid, text, mtype, **msg_kwargs)
948
+ return await self.__chat_commands_handler.input(
949
+ jid, text, mtype=mtype, **input_kwargs
950
+ )
862
951
 
863
- async def send_qr(self, text: str, **msg_kwargs):
952
+ async def send_qr(self, text: str, **msg_kwargs: Any) -> None:
864
953
  """
865
954
  Sends a QR Code to a JID
866
955
 
@@ -877,9 +966,9 @@ class BaseGateway(
877
966
  suffix=".png", delete=config.NO_UPLOAD_METHOD != "move"
878
967
  ) as f:
879
968
  qr.save(f.name)
880
- await self.send_file(f.name, **msg_kwargs)
969
+ await self.send_file(Path(f.name), **msg_kwargs)
881
970
 
882
- def shutdown(self) -> list[asyncio.Task]:
971
+ def shutdown(self) -> list[asyncio.Task[None]]:
883
972
  # """
884
973
  # Called by the slidge entrypoint on normal exit.
885
974
  #
@@ -889,9 +978,10 @@ class BaseGateway(
889
978
  # """
890
979
  log.debug("Shutting down")
891
980
  tasks = []
892
- for user in self.store.users.get_all():
893
- tasks.append(self.session_cls.from_jid(user.jid).shutdown())
894
- self.send_presence(ptype="unavailable", pto=user.jid)
981
+ with self.store.session() as orm:
982
+ for user in orm.query(GatewayUser).all():
983
+ tasks.append(self._session_cls.from_jid(user.jid).shutdown())
984
+ self.send_presence(ptype="unavailable", pto=user.jid)
895
985
  return tasks
896
986
 
897
987
 
@@ -2,8 +2,6 @@
2
2
  Mixins
3
3
  """
4
4
 
5
- from typing import Optional
6
-
7
5
  from .avatar import AvatarMixin
8
6
  from .disco import ChatterDiscoMixin
9
7
  from .message import MessageCarbonMixin, MessageMixin
@@ -18,12 +16,4 @@ class FullCarbonMixin(ChatterDiscoMixin, MessageCarbonMixin, PresenceMixin):
18
16
  pass
19
17
 
20
18
 
21
- class StoredAttributeMixin:
22
- def serialize_extra_attributes(self) -> Optional[dict]:
23
- return None
24
-
25
- def deserialize_extra_attributes(self, data: dict) -> None:
26
- pass
27
-
28
-
29
- __all__ = ("AvatarMixin", "FullCarbonMixin", "StoredAttributeMixin")
19
+ __all__ = ("AvatarMixin", "FullCarbonMixin")