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/contact/roster.py CHANGED
@@ -4,14 +4,16 @@ import warnings
4
4
  from typing import TYPE_CHECKING, AsyncIterator, Generic, Iterator, Optional, Type
5
5
 
6
6
  from slixmpp import JID
7
- from slixmpp.exceptions import IqError, IqTimeout, XMPPError
7
+ from slixmpp.exceptions import IqError, IqTimeout
8
+ from sqlalchemy.orm import Session
9
+ from sqlalchemy.orm import Session as OrmSession
8
10
 
9
- from ..core.mixins.lock import NamedLockMixin
10
- from ..db.models import Contact
11
- from ..db.store import ContactStore
11
+ from ..db.models import Contact, GatewayUser
12
12
  from ..util import SubclassableOnce
13
13
  from ..util.jid_escaping import ESCAPE_TABLE, unescape_node
14
+ from ..util.lock import NamedLockMixin
14
15
  from ..util.types import LegacyContactType, LegacyUserIdType
16
+ from ..util.util import timeit
15
17
  from .contact import LegacyContact
16
18
 
17
19
  if TYPE_CHECKING:
@@ -28,7 +30,7 @@ class LegacyRoster(
28
30
  metaclass=SubclassableOnce,
29
31
  ):
30
32
  """
31
- Virtual roster of a gateway user, that allows to represent all
33
+ Virtual roster of a gateway user that allows to represent all
32
34
  of their contacts as singleton instances (if used properly and not too bugged).
33
35
 
34
36
  Every :class:`.BaseSession` instance will have its own :class:`.LegacyRoster` instance
@@ -42,29 +44,38 @@ class LegacyRoster(
42
44
  if you need some characters when translation JID user parts and legacy IDs.
43
45
  """
44
46
 
45
- def __init__(self, session: "BaseSession"):
46
- self._contact_cls: Type[LegacyContactType] = (
47
- LegacyContact.get_self_or_unique_subclass()
48
- )
49
- self._contact_cls.xmpp = session.xmpp
50
- self.__store: ContactStore = session.xmpp.store.contacts
47
+ _contact_cls: Type[LegacyContactType]
51
48
 
52
- self.session = session
53
- self.log = logging.getLogger(f"{self.session.user_jid.bare}:roster")
49
+ def __init__(self, session: "BaseSession") -> None:
50
+ super().__init__()
51
+
52
+ self.log = logging.getLogger(f"{session.user_jid.bare}:roster")
54
53
  self.user_legacy_id: Optional[LegacyUserIdType] = None
55
- self.ready: asyncio.Future[bool] = self.session.xmpp.loop.create_future()
54
+ self.ready: asyncio.Future[bool] = session.xmpp.loop.create_future()
55
+
56
+ self.session = session
56
57
  self.__filling = False
57
- super().__init__()
58
58
 
59
- def __repr__(self):
59
+ @property
60
+ def user(self) -> GatewayUser:
61
+ return self.session.user
62
+
63
+ def orm(self) -> Session:
64
+ return self.session.xmpp.store.session()
65
+
66
+ def from_store(self, stored: Contact) -> LegacyContactType:
67
+ return self._contact_cls(self.session, stored=stored)
68
+
69
+ def __repr__(self) -> str:
60
70
  return f"<Roster of {self.session.user_jid}>"
61
71
 
62
72
  def __iter__(self) -> Iterator[LegacyContactType]:
63
- with self.__store.session():
64
- for stored in self.__store.get_all(user_pk=self.session.user_pk):
65
- yield self._contact_cls.from_store(self.session, stored)
73
+ with self.orm() as orm:
74
+ for stored in orm.query(Contact).filter_by(user=self.user).all():
75
+ if stored.updated:
76
+ yield self.from_store(stored)
66
77
 
67
- def known_contacts(self, only_friends=True) -> dict[str, LegacyContactType]:
78
+ def known_contacts(self, only_friends: bool = True) -> dict[str, LegacyContactType]:
68
79
  if only_friends:
69
80
  return {c.jid.bare: c for c in self if c.is_friend}
70
81
  return {c.jid.bare: c for c in self}
@@ -81,27 +92,54 @@ class LegacyRoster(
81
92
  # :return:
82
93
  # """
83
94
  username = contact_jid.node
95
+ contact_jid = JID(contact_jid.bare)
84
96
  async with self.lock(("username", username)):
85
97
  legacy_id = await self.jid_username_to_legacy_id(username)
86
- log.debug("Contact %s not found", contact_jid)
98
+ if legacy_id == self.user_legacy_id:
99
+ raise ContactIsUser
87
100
  if self.get_lock(("legacy_id", legacy_id)):
88
- log.debug("Already updating %s", contact_jid)
101
+ self.log.debug("Already updating %s via by_legacy_id()", contact_jid)
89
102
  return await self.by_legacy_id(legacy_id)
90
103
 
91
- with self.__store.session():
92
- stored = self.__store.get_by_jid(self.session.user_pk, contact_jid)
93
- return await self.__update_contact(stored, legacy_id, username)
104
+ with self.orm() as orm:
105
+ stored = (
106
+ orm.query(Contact)
107
+ .filter_by(user=self.user, jid=contact_jid)
108
+ .one_or_none()
109
+ )
110
+ if stored is None:
111
+ stored = Contact(
112
+ user_account_id=self.session.user_pk,
113
+ legacy_id=legacy_id,
114
+ jid=contact_jid,
115
+ )
116
+ return await self.__update_if_needed(stored)
117
+
118
+ async def __update_if_needed(self, stored: Contact) -> LegacyContactType:
119
+ contact = self.from_store(stored)
120
+ if contact.stored.updated:
121
+ return contact
122
+
123
+ with contact.updating_info():
124
+ await contact.update_info()
125
+
126
+ if contact.cached_presence is not None:
127
+ contact._store_last_presence(contact.cached_presence)
128
+ return contact
94
129
 
95
130
  def by_jid_only_if_exists(self, contact_jid: JID) -> LegacyContactType | None:
96
- with self.__store.session():
97
- stored = self.__store.get_by_jid(self.session.user_pk, contact_jid)
131
+ with self.orm() as orm:
132
+ stored = (
133
+ orm.query(Contact)
134
+ .filter_by(user=self.user, jid=contact_jid)
135
+ .one_or_none()
136
+ )
98
137
  if stored is not None and stored.updated:
99
- return self._contact_cls.from_store(self.session, stored)
138
+ return self.from_store(stored)
100
139
  return None
101
140
 
102
- async def by_legacy_id(
103
- self, legacy_id: LegacyUserIdType, *args, **kwargs
104
- ) -> LegacyContactType:
141
+ @timeit
142
+ async def by_legacy_id(self, legacy_id: LegacyUserIdType) -> LegacyContactType:
105
143
  """
106
144
  Retrieve a contact by their legacy_id
107
145
 
@@ -110,11 +148,6 @@ class LegacyRoster(
110
148
  legacy user ID.
111
149
 
112
150
  :param legacy_id:
113
- :param args: arbitrary additional positional arguments passed to the contact constructor.
114
- Requires subclassing LegacyContact.__init__ to accept those.
115
- This is useful for networks where you fetch the contact list and information
116
- about these contacts in a single request
117
- :param kwargs: arbitrary keyword arguments passed to the contact constructor
118
151
  :return:
119
152
  """
120
153
  if legacy_id == self.user_legacy_id:
@@ -122,56 +155,26 @@ class LegacyRoster(
122
155
  async with self.lock(("legacy_id", legacy_id)):
123
156
  username = await self.legacy_id_to_jid_username(legacy_id)
124
157
  if self.get_lock(("username", username)):
125
- log.debug("Already updating %s", username)
158
+ self.log.debug("Already updating %s via by_jid()", username)
159
+
126
160
  jid = JID()
127
161
  jid.node = username
128
162
  jid.domain = self.session.xmpp.boundjid.bare
129
163
  return await self.by_jid(jid)
130
164
 
131
- with self.__store.session():
132
- stored = self.__store.get_by_legacy_id(
133
- self.session.user_pk, str(legacy_id)
165
+ with self.orm() as orm:
166
+ stored = (
167
+ orm.query(Contact)
168
+ .filter_by(user=self.user, legacy_id=str(legacy_id))
169
+ .one_or_none()
134
170
  )
135
- return await self.__update_contact(
136
- stored, legacy_id, username, *args, **kwargs
171
+ if stored is None:
172
+ stored = Contact(
173
+ user_account_id=self.session.user_pk,
174
+ legacy_id=str(legacy_id),
175
+ jid=JID(f"{username}@{self.session.xmpp.boundjid.bare}"),
137
176
  )
138
-
139
- async def __update_contact(
140
- self,
141
- stored: Contact | None,
142
- legacy_id: LegacyUserIdType,
143
- username: str,
144
- *a,
145
- **kw,
146
- ) -> LegacyContactType:
147
- if stored is None:
148
- contact = self._contact_cls(self.session, legacy_id, username, *a, **kw)
149
- else:
150
- contact = self._contact_cls.from_store(self.session, stored, *a, **kw)
151
- if stored.updated:
152
- return contact
153
-
154
- try:
155
- with contact.updating_info():
156
- await contact.avatar_wrap_update_info()
157
- except XMPPError:
158
- raise
159
- except Exception as e:
160
- raise XMPPError("internal-server-error", str(e))
161
- contact._caps_ver = await contact.get_caps_ver(contact.jid)
162
- contact.contact_pk = self.__store.update(contact, commit=not self.__filling)
163
- return contact
164
-
165
- async def by_stanza(self, s) -> LegacyContact:
166
- # """
167
- # Retrieve a contact by the destination of a stanza
168
- #
169
- # See :meth:`slidge.Roster.by_legacy_id` for more info.
170
- #
171
- # :param s:
172
- # :return:
173
- # """
174
- return await self.by_jid(s.get_to())
177
+ return await self.__update_if_needed(stored)
175
178
 
176
179
  async def legacy_id_to_jid_username(self, legacy_id: LegacyUserIdType) -> str:
177
180
  """
@@ -198,9 +201,10 @@ class LegacyRoster(
198
201
  :param jid_username: User part of a JID, ie "user" in "user@example.com"
199
202
  :return: An identifier for the user on the legacy network.
200
203
  """
201
- return unescape_node(jid_username)
204
+ return unescape_node(jid_username) # type:ignore
202
205
 
203
- async def _fill(self):
206
+ @timeit
207
+ async def _fill(self, orm: OrmSession):
204
208
  try:
205
209
  if hasattr(self.session.xmpp, "TEST_MODE"):
206
210
  # dirty hack to avoid mocking xmpp server replies to this
@@ -213,30 +217,29 @@ class LegacyRoster(
213
217
  except (PermissionError, IqError, IqTimeout):
214
218
  user_roster = None
215
219
 
216
- with self.__store.session() as orm:
217
- self.__filling = True
218
- async for contact in self.fill():
219
- if user_roster is None:
220
- continue
221
- item = contact.get_roster_item()
222
- old = user_roster.get(contact.jid.bare)
223
- if old is not None and all(
224
- old[k] == item[contact.jid.bare].get(k)
225
- for k in ("subscription", "groups", "name")
226
- ):
227
- self.log.debug("No need to update roster")
228
- continue
229
- self.log.debug("Updating roster")
230
- try:
231
- await self.session.xmpp["xep_0356"].set_roster(
232
- self.session.user_jid.bare,
233
- item,
234
- )
235
- except (PermissionError, IqError, IqTimeout) as e:
236
- warnings.warn(f"Could not add to roster: {e}")
237
- else:
238
- contact._added_to_roster = True
239
- orm.commit()
220
+ self.__filling = True
221
+ async for contact in self.fill():
222
+ if user_roster is None:
223
+ continue
224
+ item = contact.get_roster_item()
225
+ old = user_roster.get(contact.jid.bare)
226
+ if old is not None and all(
227
+ old[k] == item[contact.jid.bare].get(k)
228
+ for k in ("subscription", "groups", "name")
229
+ ):
230
+ self.log.debug("No need to update roster")
231
+ continue
232
+ self.log.debug("Updating roster")
233
+ try:
234
+ await self.session.xmpp["xep_0356"].set_roster(
235
+ self.session.user_jid.bare,
236
+ item,
237
+ )
238
+ except (PermissionError, IqError, IqTimeout) as e:
239
+ warnings.warn(f"Could not add to roster: {e}")
240
+ else:
241
+ contact.added_to_roster = True
242
+ orm.commit()
240
243
  self.__filling = False
241
244
 
242
245
  async def fill(self) -> AsyncIterator[LegacyContact]:
slidge/core/config.py CHANGED
@@ -1,12 +1,12 @@
1
1
  from datetime import timedelta
2
2
  from pathlib import Path
3
- from typing import Optional
3
+ from typing import Optional, Self
4
4
 
5
5
  from slixmpp import JID as JIDType
6
6
 
7
7
 
8
8
  class _TimedeltaSeconds(timedelta):
9
- def __new__(cls, s: str):
9
+ def __new__(cls, s: str) -> Self:
10
10
  return super().__new__(cls, seconds=int(s))
11
11
 
12
12
 
@@ -75,16 +75,6 @@ UPLOAD_SERVICE__DOC = (
75
75
  "discovery."
76
76
  )
77
77
 
78
- NO_ROSTER_PUSH = False
79
- NO_ROSTER_PUSH__DOC = "Do not fill users' rosters with legacy contacts automatically"
80
-
81
- ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK = True
82
- ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK__DOC = (
83
- "If True, legacy contacts will send a presence request subscription "
84
- "when privileged roster push does not work, eg, if XEP-0356 (privileged "
85
- "entity) is not available for the component."
86
- )
87
-
88
78
  AVATAR_SIZE = 200
89
79
  AVATAR_SIZE__DOC = (
90
80
  "Maximum image size (width and height), image ratio will be preserved"
@@ -138,27 +128,9 @@ PARTIAL_REGISTRATION_TIMEOUT__DOC = (
138
128
  "a single step registration process is not enough."
139
129
  )
140
130
 
141
- LAST_SEEN_FALLBACK = False
142
- LAST_SEEN_FALLBACK__DOC = (
143
- "When using XEP-0319 (Last User Interaction in Presence), use the presence status"
144
- " to display the last seen information in the presence status. Useful for clients"
145
- " that do not implement XEP-0319. Because of implementation details, this can increase"
146
- " RAM usage and might be deprecated in the future. Ask your client dev for XEP-0319"
147
- " support ;o)."
148
- )
149
-
150
131
  QR_TIMEOUT = 60
151
132
  QR_TIMEOUT__DOC = "Timeout for QR code flashing confirmation."
152
133
 
153
- LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND = False
154
- LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND__DOC = (
155
- "If the legacy service does not support last message correction but supports"
156
- " message retractions, slidge can 'retract' the edited message when you edit from"
157
- " an XMPP client, as a workaround. This may only work for editing messages"
158
- " **once**. If the legacy service does not support retractions and this is set to"
159
- " true, when XMPP clients attempt to correct, this will send a new message."
160
- )
161
-
162
134
  FIX_FILENAME_SUFFIX_MIME_TYPE = False
163
135
  FIX_FILENAME_SUFFIX_MIME_TYPE__DOC = (
164
136
  "Fix the Filename suffix based on the Mime Type of the file. Some clients (eg"
@@ -180,25 +152,12 @@ LOG_FORMAT__DOC = (
180
152
  MAM_MAX_DAYS = 7
181
153
  MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
182
154
 
183
- CORRECTION_EMPTY_BODY_AS_RETRACTION = True
184
- CORRECTION_EMPTY_BODY_AS_RETRACTION__DOC = (
185
- "Treat last message correction to empty message as a retraction. "
186
- "(this is what cheogram does for retraction)"
187
- )
188
-
189
155
  ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH = 200
190
156
  ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH__DOC = (
191
157
  "Some legacy network provide ridiculously long filenames, strip above this limit, "
192
158
  "preserving suffix."
193
159
  )
194
160
 
195
- ALWAYS_INVITE_WHEN_ADDING_BOOKMARKS = True
196
- ALWAYS_INVITE_WHEN_ADDING_BOOKMARKS__DOC = (
197
- "Send an invitation to join MUCs when adding them to the bookmarks. While this "
198
- "should not be necessary, it helps with clients that do not support :xep:`0402` "
199
- "or that do not respect the auto-join flag."
200
- )
201
-
202
161
  AVATAR_RESAMPLING_THREADS = 2
203
162
  AVATAR_RESAMPLING_THREADS__DOC = (
204
163
  "Number of additional threads to use for avatar resampling. Even in a single-core "
@@ -12,7 +12,9 @@ if TYPE_CHECKING:
12
12
 
13
13
 
14
14
  class CapsMixin(DispatcherMixin):
15
- def __init__(self, xmpp: "BaseGateway"):
15
+ __slots__: list[str] = []
16
+
17
+ def __init__(self, xmpp: "BaseGateway") -> None:
16
18
  super().__init__(xmpp)
17
19
  xmpp.del_filter("out", xmpp.plugin["xep_0115"]._filter_add_caps)
18
20
  xmpp.add_filter("out", self._filter_add_caps) # type:ignore
@@ -51,7 +53,12 @@ class CapsMixin(DispatcherMixin):
51
53
  contact = await session.contacts.by_jid(pfrom)
52
54
  except XMPPError:
53
55
  return stanza
54
- ver = await contact.get_caps_ver(pfrom)
56
+ if contact.stored.caps_ver:
57
+ ver = contact.stored.caps_ver
58
+ else:
59
+ ver = await contact.get_caps_ver(pfrom)
60
+ contact.stored.caps_ver = ver
61
+ contact.commit()
55
62
  else:
56
63
  ver = await caps.get_verstring(pfrom)
57
64
 
@@ -1,10 +1,12 @@
1
1
  import logging
2
2
  from typing import TYPE_CHECKING, Any, Optional
3
3
 
4
+ import sqlalchemy as sa
4
5
  from slixmpp.exceptions import XMPPError
5
6
  from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
6
7
  from slixmpp.types import OptJid
7
8
 
9
+ from ...db.models import Room
8
10
  from .util import DispatcherMixin
9
11
 
10
12
  if TYPE_CHECKING:
@@ -12,7 +14,9 @@ if TYPE_CHECKING:
12
14
 
13
15
 
14
16
  class DiscoMixin(DispatcherMixin):
15
- def __init__(self, xmpp: "BaseGateway"):
17
+ __slots__: list[str] = []
18
+
19
+ def __init__(self, xmpp: "BaseGateway") -> None:
16
20
  super().__init__(xmpp)
17
21
 
18
22
  xmpp.plugin["xep_0030"].set_node_handler(
@@ -63,8 +67,14 @@ class DiscoMixin(DispatcherMixin):
63
67
  session = await self._get_session_from_jid(ifrom)
64
68
 
65
69
  d = DiscoItems()
66
- for room in self.xmpp.store.rooms.get_all_jid_and_names(session.user_pk):
67
- d.add_item(room.jid, name=room.name)
70
+ with self.xmpp.store.session() as orm:
71
+ for room in orm.execute(
72
+ sa.select(Room)
73
+ .options(sa.orm.load_only(Room.jid, Room.name))
74
+ .filter_by(user=session.user)
75
+ .order_by(Room.name)
76
+ ).scalars():
77
+ d.add_item(room.jid, name=room.name)
68
78
 
69
79
  return d
70
80
 
@@ -4,7 +4,7 @@ from .message import MessageContentMixin
4
4
 
5
5
 
6
6
  class MessageMixin(ChatStateMixin, MarkerMixin, MessageContentMixin):
7
- pass
7
+ __slots__: list[str] = []
8
8
 
9
9
 
10
10
  __all__ = ("MessageMixin",)
@@ -5,12 +5,15 @@ from ..util import DispatcherMixin, exceptions_to_xmpp_errors
5
5
 
6
6
 
7
7
  class ChatStateMixin(DispatcherMixin):
8
+ __slots__: list[str] = []
9
+
8
10
  def __init__(self, xmpp) -> None:
9
11
  super().__init__(xmpp)
10
12
  xmpp.add_event_handler("chatstate_active", self.on_chatstate_active)
11
13
  xmpp.add_event_handler("chatstate_inactive", self.on_chatstate_inactive)
12
14
  xmpp.add_event_handler("chatstate_composing", self.on_chatstate_composing)
13
15
  xmpp.add_event_handler("chatstate_paused", self.on_chatstate_paused)
16
+ xmpp.add_event_handler("chatstate_gone", self.on_chatstate_gone)
14
17
 
15
18
  @exceptions_to_xmpp_errors
16
19
  async def on_chatstate_active(self, msg: StanzaBase) -> None:
@@ -18,23 +21,29 @@ class ChatStateMixin(DispatcherMixin):
18
21
  if msg["body"]:
19
22
  # if there is a body, it's handled in on_legacy_message()
20
23
  return
21
- session, entity, thread = await self._get_session_entity_thread(msg)
22
- await session.on_active(entity, thread)
24
+ session, recipient, thread = await self._get_session_recipient_thread(msg)
25
+ await session.on_active(recipient, thread)
23
26
 
24
27
  @exceptions_to_xmpp_errors
25
28
  async def on_chatstate_inactive(self, msg: StanzaBase) -> None:
26
29
  assert isinstance(msg, Message)
27
- session, entity, thread = await self._get_session_entity_thread(msg)
28
- await session.on_inactive(entity, thread)
30
+ session, recipient, thread = await self._get_session_recipient_thread(msg)
31
+ await session.on_inactive(recipient, thread)
29
32
 
30
33
  @exceptions_to_xmpp_errors
31
34
  async def on_chatstate_composing(self, msg: StanzaBase) -> None:
32
35
  assert isinstance(msg, Message)
33
- session, entity, thread = await self._get_session_entity_thread(msg)
34
- await session.on_composing(entity, thread)
36
+ session, recipient, thread = await self._get_session_recipient_thread(msg)
37
+ await session.on_composing(recipient, thread)
35
38
 
36
39
  @exceptions_to_xmpp_errors
37
40
  async def on_chatstate_paused(self, msg: StanzaBase) -> None:
38
41
  assert isinstance(msg, Message)
39
- session, entity, thread = await self._get_session_entity_thread(msg)
40
- await session.on_paused(entity, thread)
42
+ session, recipient, thread = await self._get_session_recipient_thread(msg)
43
+ await session.on_paused(recipient, thread)
44
+
45
+ @exceptions_to_xmpp_errors
46
+ async def on_chatstate_gone(self, msg: StanzaBase) -> None:
47
+ assert isinstance(msg, Message)
48
+ session, recipient, thread = await self._get_session_recipient_thread(msg)
49
+ await session.on_gone(recipient, thread)
@@ -3,10 +3,12 @@ from slixmpp.xmlstream import StanzaBase
3
3
 
4
4
  from ....group.room import LegacyMUC
5
5
  from ....util.types import Recipient
6
- from ..util import DispatcherMixin, _get_entity, exceptions_to_xmpp_errors
6
+ from ..util import DispatcherMixin, exceptions_to_xmpp_errors, get_recipient
7
7
 
8
8
 
9
9
  class MarkerMixin(DispatcherMixin):
10
+ __slots__: list[str] = []
11
+
10
12
  def __init__(self, xmpp) -> None:
11
13
  super().__init__(xmpp)
12
14
  xmpp.add_event_handler("marker_displayed", self.on_marker_displayed)
@@ -20,11 +22,11 @@ class MarkerMixin(DispatcherMixin):
20
22
  assert isinstance(msg, Message)
21
23
  session = await self._get_session(msg)
22
24
 
23
- e: Recipient = await _get_entity(session, msg)
25
+ e: Recipient = await get_recipient(session, msg)
24
26
  legacy_thread = await self._xmpp_to_legacy_thread(session, msg, e)
25
27
  displayed_msg_id = msg["displayed"]["id"]
26
28
  if not isinstance(e, LegacyMUC) and self.xmpp.MARK_ALL_MESSAGES:
27
- to_mark = e.get_msg_xmpp_id_up_to(displayed_msg_id) # type: ignore
29
+ to_mark = e.get_msg_xmpp_id_up_to(displayed_msg_id)
28
30
  if to_mark is None:
29
31
  session.log.debug("Can't mark all messages up to %s", displayed_msg_id)
30
32
  to_mark = [displayed_msg_id]
@@ -32,7 +34,7 @@ class MarkerMixin(DispatcherMixin):
32
34
  to_mark = [displayed_msg_id]
33
35
  for xmpp_id in to_mark:
34
36
  await session.on_displayed(
35
- e, self._xmpp_msg_id_to_legacy(session, xmpp_id), legacy_thread
37
+ e, self._xmpp_msg_id_to_legacy(session, xmpp_id, e), legacy_thread
36
38
  )
37
39
  if isinstance(e, LegacyMUC):
38
40
  await e.echo(msg, None)
@@ -58,5 +60,5 @@ class MarkerMixin(DispatcherMixin):
58
60
 
59
61
  stanza_id = msg["pubsub_event"]["items"]["item"]["displayed"]["stanza_id"]["id"]
60
62
  await session.on_displayed(
61
- chat, self._xmpp_msg_id_to_legacy(session, stanza_id)
63
+ chat, self._xmpp_msg_id_to_legacy(session, stanza_id, chat)
62
64
  )