slidge 0.2.12__py3-none-any.whl → 0.3.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) 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 +119 -209
  8. slidge/contact/roster.py +106 -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 +117 -92
  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 +21 -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 +168 -84
  29. slidge/core/mixins/__init__.py +1 -11
  30. slidge/core/mixins/attachment.py +163 -148
  31. slidge/core/mixins/avatar.py +100 -177
  32. slidge/core/mixins/db.py +50 -2
  33. slidge/core/mixins/message.py +19 -17
  34. slidge/core/mixins/message_maker.py +29 -15
  35. slidge/core/mixins/message_text.py +38 -30
  36. slidge/core/mixins/presence.py +91 -35
  37. slidge/core/pubsub.py +42 -47
  38. slidge/core/session.py +88 -57
  39. slidge/db/alembic/versions/0337c90c0b96_unify_legacy_xmpp_id_mappings.py +183 -0
  40. slidge/db/alembic/versions/4dbd23a3f868_new_avatar_store.py +56 -0
  41. slidge/db/alembic/versions/54ce3cde350c_use_hash_for_avatar_filenames.py +50 -0
  42. slidge/db/alembic/versions/58b98dacf819_refactor.py +118 -0
  43. slidge/db/alembic/versions/75a62b74b239_ditch_hats_table.py +74 -0
  44. slidge/db/avatar.py +150 -119
  45. slidge/db/meta.py +33 -22
  46. slidge/db/models.py +68 -117
  47. slidge/db/store.py +412 -1094
  48. slidge/group/archive.py +61 -54
  49. slidge/group/bookmarks.py +74 -55
  50. slidge/group/participant.py +135 -142
  51. slidge/group/room.py +315 -312
  52. slidge/main.py +28 -18
  53. slidge/migration.py +2 -12
  54. slidge/slixfix/__init__.py +20 -4
  55. slidge/slixfix/delivery_receipt.py +6 -4
  56. slidge/slixfix/link_preview/link_preview.py +1 -1
  57. slidge/slixfix/link_preview/stanza.py +1 -1
  58. slidge/slixfix/roster.py +5 -7
  59. slidge/slixfix/xep_0077/register.py +8 -8
  60. slidge/slixfix/xep_0077/stanza.py +7 -7
  61. slidge/slixfix/xep_0100/gateway.py +12 -13
  62. slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
  63. slidge/slixfix/xep_0292/vcard4.py +1 -1
  64. slidge/util/archive_msg.py +11 -5
  65. slidge/util/conf.py +23 -20
  66. slidge/util/jid_escaping.py +1 -1
  67. slidge/{core/mixins → util}/lock.py +6 -6
  68. slidge/util/test.py +30 -29
  69. slidge/util/types.py +22 -18
  70. slidge/util/util.py +19 -22
  71. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/METADATA +1 -1
  72. slidge-0.3.0a0.dist-info/RECORD +117 -0
  73. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/WHEEL +1 -1
  74. slidge-0.2.12.dist-info/RECORD +0 -112
  75. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/entry_points.txt +0 -0
  76. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
  77. {slidge-0.2.12.dist-info → slidge-0.3.0a0.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,52 @@ 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)
87
98
  if self.get_lock(("legacy_id", legacy_id)):
88
- log.debug("Already updating %s", contact_jid)
99
+ self.log.debug("Already updating %s via by_legacy_id()", contact_jid)
89
100
  return await self.by_legacy_id(legacy_id)
90
101
 
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)
102
+ with self.orm() as orm:
103
+ stored = (
104
+ orm.query(Contact)
105
+ .filter_by(user=self.user, jid=contact_jid)
106
+ .one_or_none()
107
+ )
108
+ if stored is None:
109
+ stored = Contact(
110
+ user_account_id=self.session.user_pk,
111
+ legacy_id=legacy_id,
112
+ jid=contact_jid,
113
+ )
114
+ return await self.__update_if_needed(stored)
115
+
116
+ async def __update_if_needed(self, stored: Contact) -> LegacyContactType:
117
+ contact = self.from_store(stored)
118
+ if contact.stored.updated:
119
+ return contact
120
+
121
+ with contact.updating_info():
122
+ await contact.update_info()
123
+
124
+ if contact.cached_presence is not None:
125
+ contact._store_last_presence(contact.cached_presence)
126
+ return contact
94
127
 
95
128
  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)
129
+ with self.orm() as orm:
130
+ stored = (
131
+ orm.query(Contact)
132
+ .filter_by(user=self.user, jid=contact_jid)
133
+ .one_or_none()
134
+ )
98
135
  if stored is not None and stored.updated:
99
- return self._contact_cls.from_store(self.session, stored)
136
+ return self.from_store(stored)
100
137
  return None
101
138
 
102
- async def by_legacy_id(
103
- self, legacy_id: LegacyUserIdType, *args, **kwargs
104
- ) -> LegacyContactType:
139
+ @timeit
140
+ async def by_legacy_id(self, legacy_id: LegacyUserIdType) -> LegacyContactType:
105
141
  """
106
142
  Retrieve a contact by their legacy_id
107
143
 
@@ -110,11 +146,6 @@ class LegacyRoster(
110
146
  legacy user ID.
111
147
 
112
148
  :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
149
  :return:
119
150
  """
120
151
  if legacy_id == self.user_legacy_id:
@@ -122,56 +153,26 @@ class LegacyRoster(
122
153
  async with self.lock(("legacy_id", legacy_id)):
123
154
  username = await self.legacy_id_to_jid_username(legacy_id)
124
155
  if self.get_lock(("username", username)):
125
- log.debug("Already updating %s", username)
156
+ self.log.debug("Already updating %s via by_jid()", username)
157
+
126
158
  jid = JID()
127
159
  jid.node = username
128
160
  jid.domain = self.session.xmpp.boundjid.bare
129
161
  return await self.by_jid(jid)
130
162
 
131
- with self.__store.session():
132
- stored = self.__store.get_by_legacy_id(
133
- self.session.user_pk, str(legacy_id)
163
+ with self.orm() as orm:
164
+ stored = (
165
+ orm.query(Contact)
166
+ .filter_by(user=self.user, legacy_id=str(legacy_id))
167
+ .one_or_none()
134
168
  )
135
- return await self.__update_contact(
136
- stored, legacy_id, username, *args, **kwargs
169
+ if stored is None:
170
+ stored = Contact(
171
+ user_account_id=self.session.user_pk,
172
+ legacy_id=str(legacy_id),
173
+ jid=JID(f"{username}@{self.session.xmpp.boundjid.bare}"),
137
174
  )
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())
175
+ return await self.__update_if_needed(stored)
175
176
 
176
177
  async def legacy_id_to_jid_username(self, legacy_id: LegacyUserIdType) -> str:
177
178
  """
@@ -198,9 +199,10 @@ class LegacyRoster(
198
199
  :param jid_username: User part of a JID, ie "user" in "user@example.com"
199
200
  :return: An identifier for the user on the legacy network.
200
201
  """
201
- return unescape_node(jid_username)
202
+ return unescape_node(jid_username) # type:ignore
202
203
 
203
- async def _fill(self):
204
+ @timeit
205
+ async def _fill(self, orm: OrmSession):
204
206
  try:
205
207
  if hasattr(self.session.xmpp, "TEST_MODE"):
206
208
  # dirty hack to avoid mocking xmpp server replies to this
@@ -213,30 +215,29 @@ class LegacyRoster(
213
215
  except (PermissionError, IqError, IqTimeout):
214
216
  user_roster = None
215
217
 
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()
218
+ self.__filling = True
219
+ async for contact in self.fill():
220
+ if user_roster is None:
221
+ continue
222
+ item = contact.get_roster_item()
223
+ old = user_roster.get(contact.jid.bare)
224
+ if old is not None and all(
225
+ old[k] == item[contact.jid.bare].get(k)
226
+ for k in ("subscription", "groups", "name")
227
+ ):
228
+ self.log.debug("No need to update roster")
229
+ continue
230
+ self.log.debug("Updating roster")
231
+ try:
232
+ await self.session.xmpp["xep_0356"].set_roster(
233
+ self.session.user_jid.bare,
234
+ item,
235
+ )
236
+ except (PermissionError, IqError, IqTimeout) as e:
237
+ warnings.warn(f"Could not add to roster: {e}")
238
+ else:
239
+ contact.added_to_roster = True
240
+ orm.commit()
240
241
  self.__filling = False
241
242
 
242
243
  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
  )