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/session.py CHANGED
@@ -7,21 +7,24 @@ from typing import (
7
7
  Iterable,
8
8
  NamedTuple,
9
9
  Optional,
10
+ Type,
10
11
  Union,
11
12
  cast,
12
13
  )
13
14
 
14
15
  import aiohttp
16
+ import sqlalchemy as sa
15
17
  from slixmpp import JID, Message
16
18
  from slixmpp.exceptions import XMPPError
17
19
  from slixmpp.types import PresenceShows
18
20
 
19
21
  from ..command import SearchResult
20
22
  from ..contact import LegacyContact, LegacyRoster
21
- from ..db.models import GatewayUser
23
+ from ..db.models import Contact, GatewayUser
22
24
  from ..group.bookmarks import LegacyBookmarks
23
25
  from ..group.room import LegacyMUC
24
26
  from ..util import ABCSubclassableOnceAtMost
27
+ from ..util.lock import NamedLockMixin
25
28
  from ..util.types import (
26
29
  LegacyGroupIdType,
27
30
  LegacyMessageType,
@@ -48,7 +51,9 @@ class CachedPresence(NamedTuple):
48
51
 
49
52
 
50
53
  class BaseSession(
51
- Generic[LegacyMessageType, RecipientType], metaclass=ABCSubclassableOnceAtMost
54
+ Generic[LegacyMessageType, RecipientType],
55
+ NamedLockMixin,
56
+ metaclass=ABCSubclassableOnceAtMost,
52
57
  ):
53
58
  """
54
59
  The session of a registered :term:`User`.
@@ -89,22 +94,22 @@ class BaseSession(
89
94
  This can be used to implement voting in polls in a hacky way.
90
95
  """
91
96
 
92
- def __init__(self, user: GatewayUser):
93
- self.log = logging.getLogger(user.jid.bare)
97
+ _roster_cls: Type[LegacyRoster]
98
+ _bookmarks_cls: Type[LegacyBookmarks]
94
99
 
95
- self.user_jid = user.jid
96
- self.user_pk = user.id
100
+ def __init__(self, user: GatewayUser) -> None:
101
+ super().__init__()
102
+ self.user = user
103
+ self.log = logging.getLogger(user.jid.bare)
97
104
 
98
105
  self.ignore_messages = set[str]()
99
106
 
100
- self.contacts: LegacyRoster = LegacyRoster.get_self_or_unique_subclass()(self)
107
+ self.contacts = self._roster_cls(self)
101
108
  self.is_logging_in = False
102
109
  self._logged = False
103
110
  self.__reset_ready()
104
111
 
105
- self.bookmarks: LegacyBookmarks = LegacyBookmarks.get_self_or_unique_subclass()(
106
- self
107
- )
112
+ self.bookmarks = self._bookmarks_cls(self)
108
113
 
109
114
  self.thread_creation_lock = asyncio.Lock()
110
115
 
@@ -113,25 +118,29 @@ class BaseSession(
113
118
  self.__tasks = set[asyncio.Task]()
114
119
 
115
120
  @property
116
- def user(self) -> GatewayUser:
117
- return self.xmpp.store.users.get(self.user_jid) # type:ignore
121
+ def user_jid(self) -> JID:
122
+ return self.user.jid
123
+
124
+ @property
125
+ def user_pk(self) -> int:
126
+ return self.user.id
118
127
 
119
128
  @property
120
129
  def http(self) -> aiohttp.ClientSession:
121
130
  return self.xmpp.http
122
131
 
123
- def __remove_task(self, fut):
132
+ def __remove_task(self, fut) -> None:
124
133
  self.log.debug("Removing fut %s", fut)
125
134
  self.__tasks.remove(fut)
126
135
 
127
- def create_task(self, coro) -> asyncio.Task:
128
- task = self.xmpp.loop.create_task(coro)
136
+ def create_task(self, coro, name: str | None = None) -> asyncio.Task:
137
+ task = self.xmpp.loop.create_task(coro, name=name)
129
138
  self.__tasks.add(task)
130
139
  self.log.debug("Creating task %s", task)
131
140
  task.add_done_callback(lambda _: self.__remove_task(task))
132
141
  return task
133
142
 
134
- def cancel_all_tasks(self):
143
+ def cancel_all_tasks(self) -> None:
135
144
  for task in self.__tasks:
136
145
  task.cancel()
137
146
 
@@ -148,7 +157,7 @@ class BaseSession(
148
157
  """
149
158
  raise NotImplementedError
150
159
 
151
- async def logout(self):
160
+ async def logout(self) -> None:
152
161
  """
153
162
  Logs out the gateway user from the legacy network.
154
163
 
@@ -304,6 +313,17 @@ class BaseSession(
304
313
 
305
314
  paused = deprecated("BaseSession.paused", on_paused)
306
315
 
316
+ async def on_gone(
317
+ self, chat: RecipientType, thread: Optional[LegacyThreadType] = None
318
+ ):
319
+ """
320
+ Triggered when the user is "gone" in a legacy chat (:xep:`0085`)
321
+
322
+ :param chat: See :meth:`.BaseSession.on_text`
323
+ :param thread:
324
+ """
325
+ raise NotImplementedError
326
+
307
327
  async def on_displayed(
308
328
  self,
309
329
  chat: RecipientType,
@@ -493,7 +513,7 @@ class BaseSession(
493
513
 
494
514
  async def on_invitation(
495
515
  self, contact: LegacyContact, muc: LegacyMUC, reason: Optional[str]
496
- ):
516
+ ) -> None:
497
517
  """
498
518
  Triggered when the user invites a :term:`Contact` to a legacy MUC via
499
519
  :xep:`0249`.
@@ -519,7 +539,7 @@ class BaseSession(
519
539
  """
520
540
  raise NotImplementedError
521
541
 
522
- def __reset_ready(self):
542
+ def __reset_ready(self) -> None:
523
543
  self.ready = self.xmpp.loop.create_future()
524
544
 
525
545
  @property
@@ -527,7 +547,7 @@ class BaseSession(
527
547
  return self._logged
528
548
 
529
549
  @logged.setter
530
- def logged(self, v: bool):
550
+ def logged(self, v: bool) -> None:
531
551
  self.is_logging_in = False
532
552
  self._logged = v
533
553
  if self.ready.done():
@@ -539,19 +559,34 @@ class BaseSession(
539
559
  if v:
540
560
  self.ready.set_result(True)
541
561
 
542
- def __repr__(self):
562
+ def __repr__(self) -> str:
543
563
  return f"<Session of {self.user_jid}>"
544
564
 
545
- def shutdown(self, logout=True) -> asyncio.Task:
565
+ def shutdown(self, logout: bool = True) -> asyncio.Task:
546
566
  for m in self.bookmarks:
547
567
  m.shutdown()
548
- for c in self.contacts:
549
- c.offline()
568
+ with self.xmpp.store.session() as orm:
569
+ for jid in orm.execute(
570
+ sa.select(Contact.jid).filter_by(user=self.user, is_friend=True)
571
+ ).scalars():
572
+ pres = self.xmpp.make_presence(
573
+ pfrom=jid,
574
+ pto=self.user_jid,
575
+ ptype="unavailable",
576
+ pstatus="Gateway has shut down.",
577
+ )
578
+ pres.send()
550
579
  if logout:
551
- return self.xmpp.loop.create_task(self.logout())
580
+ return self.xmpp.loop.create_task(self.__logout())
552
581
  else:
553
582
  return self.xmpp.loop.create_task(noop_coro())
554
583
 
584
+ async def __logout(self) -> None:
585
+ try:
586
+ await self.logout()
587
+ except NotImplementedError:
588
+ pass
589
+
555
590
  @staticmethod
556
591
  def legacy_to_xmpp_msg_id(legacy_msg_id: LegacyMessageType) -> str:
557
592
  """
@@ -604,7 +639,7 @@ class BaseSession(
604
639
  @classmethod
605
640
  def _from_user_or_none(cls, user):
606
641
  if user is None:
607
- log.debug("user not found", stack_info=True)
642
+ log.debug("user not found")
608
643
  raise XMPPError(text="User not found", condition="subscription-required")
609
644
 
610
645
  session = _sessions.get(user.jid.bare)
@@ -641,11 +676,12 @@ class BaseSession(
641
676
  session = _sessions.get(jid.bare)
642
677
  if session is not None:
643
678
  return session
644
- user = cls.xmpp.store.users.get(jid)
679
+ with cls.xmpp.store.session() as orm:
680
+ user = orm.query(GatewayUser).filter_by(jid=jid.bare).one_or_none()
645
681
  return cls._from_user_or_none(user)
646
682
 
647
683
  @classmethod
648
- async def kill_by_jid(cls, jid: JID):
684
+ async def kill_by_jid(cls, jid: JID) -> None:
649
685
  # """
650
686
  # Terminate a user session.
651
687
  #
@@ -665,17 +701,19 @@ class BaseSession(
665
701
  c.unsubscribe()
666
702
  for m in session.bookmarks:
667
703
  m.shutdown()
668
- user = cls.xmpp.store.users.get(jid)
669
- if user is None:
704
+
705
+ try:
706
+ session = _sessions.pop(jid.bare)
707
+ except KeyError:
670
708
  log.warning("User not found during unregistration")
671
709
  return
672
- await cls.xmpp.unregister(user)
673
- cls.xmpp.store.users.delete(user.jid)
674
- del _sessions[user.jid.bare]
675
- del user
676
- del session
677
710
 
678
- def __ack(self, msg: Message):
711
+ await cls.xmpp.unregister(session)
712
+ with cls.xmpp.store.session() as orm:
713
+ orm.delete(session.user)
714
+ orm.commit()
715
+
716
+ def __ack(self, msg: Message) -> None:
679
717
  if not self.xmpp.PROPER_RECEIPTS:
680
718
  self.xmpp.delivery_receipt.ack(msg)
681
719
 
@@ -684,7 +722,7 @@ class BaseSession(
684
722
  status: Optional[str] = None,
685
723
  show=Optional[PresenceShows],
686
724
  **kwargs,
687
- ):
725
+ ) -> None:
688
726
  """
689
727
  Send a presence from the gateway to the user.
690
728
 
@@ -699,7 +737,7 @@ class BaseSession(
699
737
  pto=self.user_jid.bare, pstatus=status, pshow=show, **kwargs
700
738
  )
701
739
 
702
- def send_cached_presence(self, to: JID):
740
+ def send_cached_presence(self, to: JID) -> None:
703
741
  if not self.__cached_presence:
704
742
  self.xmpp.send_presence(pto=to, ptype="unavailable")
705
743
  return
@@ -710,7 +748,7 @@ class BaseSession(
710
748
  **self.__cached_presence.kwargs,
711
749
  )
712
750
 
713
- def send_gateway_message(self, text: str, **msg_kwargs):
751
+ def send_gateway_message(self, text: str, **msg_kwargs) -> None:
714
752
  """
715
753
  Send a message from the gateway component to the user.
716
754
 
@@ -725,7 +763,7 @@ class BaseSession(
725
763
  muc: LegacyMUC,
726
764
  reason: Optional[str] = None,
727
765
  password: Optional[str] = None,
728
- ):
766
+ ) -> None:
729
767
  """
730
768
  Send an invitation to join a MUC, emanating from the gateway component.
731
769
 
@@ -747,7 +785,7 @@ class BaseSession(
747
785
  """
748
786
  return await self.xmpp.input(self.user_jid, text, **msg_kwargs)
749
787
 
750
- async def send_qr(self, text: str):
788
+ async def send_qr(self, text: str) -> None:
751
789
  """
752
790
  Sends a QR code generated from 'text' via HTTP Upload and send the URL to
753
791
  ``self.user``
@@ -756,13 +794,13 @@ class BaseSession(
756
794
  """
757
795
  await self.xmpp.send_qr(text, mto=self.user_jid)
758
796
 
759
- def re_login(self):
797
+ def re_login(self) -> None:
760
798
  # Logout then re-login
761
799
  #
762
800
  # No reason to override this
763
801
  self.xmpp.re_login(self)
764
802
 
765
- async def get_contact_or_group_or_participant(self, jid: JID, create=True):
803
+ async def get_contact_or_group_or_participant(self, jid: JID, create: bool = True):
766
804
  if (contact := self.contacts.by_jid_only_if_exists(jid)) is not None:
767
805
  return contact
768
806
  if (muc := self.bookmarks.by_jid_only_if_exists(JID(jid.bare))) is not None:
@@ -813,23 +851,20 @@ class BaseSession(
813
851
  "Legacy session is not fully initialized, retry later",
814
852
  )
815
853
 
816
- def legacy_module_data_update(self, data: dict):
817
- with self.xmpp.store.session():
818
- user = self.user
819
- user.legacy_module_data.update(data)
820
- self.xmpp.store.users.update(user)
821
-
822
- def legacy_module_data_set(self, data: dict):
823
- with self.xmpp.store.session():
824
- user = self.user
825
- user.legacy_module_data = data
826
- self.xmpp.store.users.update(user)
827
-
828
- def legacy_module_data_clear(self):
829
- with self.xmpp.store.session():
830
- user = self.user
831
- user.legacy_module_data.clear()
832
- self.xmpp.store.users.update(user)
854
+ def legacy_module_data_update(self, data: dict) -> None:
855
+ user = self.user
856
+ user.legacy_module_data.update(data)
857
+ self.xmpp.store.users.update(user)
858
+
859
+ def legacy_module_data_set(self, data: dict) -> None:
860
+ user = self.user
861
+ user.legacy_module_data = data
862
+ self.xmpp.store.users.update(user)
863
+
864
+ def legacy_module_data_clear(self) -> None:
865
+ user = self.user
866
+ user.legacy_module_data.clear()
867
+ self.xmpp.store.users.update(user)
833
868
 
834
869
 
835
870
  # keys = user.jid.bare