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/contact.py CHANGED
@@ -2,20 +2,19 @@ import datetime
2
2
  import logging
3
3
  import warnings
4
4
  from datetime import date
5
- from typing import TYPE_CHECKING, Generic, Iterable, Optional, Self, Union
5
+ from typing import TYPE_CHECKING, Generic, Iterable, Iterator, Optional, Union
6
6
  from xml.etree import ElementTree as ET
7
7
 
8
8
  from slixmpp import JID, Message, Presence
9
9
  from slixmpp.exceptions import IqError, IqTimeout
10
10
  from slixmpp.plugins.xep_0292.stanza import VCard4
11
11
  from slixmpp.types import MessageTypes
12
+ from sqlalchemy.exc import IntegrityError
12
13
 
13
- from ..core import config
14
- from ..core.mixins import AvatarMixin, FullCarbonMixin, StoredAttributeMixin
15
- from ..core.mixins.db import UpdateInfoMixin
14
+ from ..core.mixins import AvatarMixin, FullCarbonMixin
16
15
  from ..core.mixins.disco import ContactAccountDiscoMixin
17
16
  from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
18
- from ..db.models import Contact
17
+ from ..db.models import Contact, ContactSent, Participant
19
18
  from ..util import SubclassableOnce
20
19
  from ..util.types import ClientType, LegacyUserIdType, MessageOrPresenceTypeVar
21
20
 
@@ -26,13 +25,11 @@ if TYPE_CHECKING:
26
25
 
27
26
  class LegacyContact(
28
27
  Generic[LegacyUserIdType],
29
- StoredAttributeMixin,
30
28
  AvatarMixin,
31
29
  ContactAccountDiscoMixin,
32
30
  FullCarbonMixin,
33
31
  ReactionRecipientMixin,
34
32
  ThreadRecipientMixin,
35
- UpdateInfoMixin,
36
33
  metaclass=SubclassableOnce,
37
34
  ):
38
35
  """
@@ -85,104 +82,68 @@ class LegacyContact(
85
82
  STRIP_SHORT_DELAY = True
86
83
  _NON_FRIEND_PRESENCES_FILTER = {"subscribe", "unsubscribed"}
87
84
 
88
- _avatar_bare_jid = True
89
-
90
85
  INVITATION_RECIPIENT = True
91
86
 
92
- def __init__(
93
- self,
94
- session: "BaseSession",
95
- legacy_id: LegacyUserIdType,
96
- jid_username: str,
97
- ):
98
- """
99
- :param session: The session this contact is part of
100
- :param legacy_id: The contact's legacy ID
101
- :param jid_username: User part of this contact's 'puppet' JID.
102
- NB: case-insensitive, and some special characters are not allowed
103
- """
104
- super().__init__()
87
+ stored: Contact
88
+ model: Contact
89
+
90
+ def __init__(self, session: "BaseSession", stored: Contact) -> None:
105
91
  self.session = session
106
- self.legacy_id: LegacyUserIdType = legacy_id
107
- """
108
- The legacy identifier of the :term:`Legacy Contact`.
109
- By default, this is the :term:`JID Local Part` of this
110
- :term:`XMPP Entity`.
111
-
112
- Controlling what values are valid and how they are translated from a
113
- :term:`JID Local Part` is done in :meth:`.jid_username_to_legacy_id`.
114
- Reciprocally, in :meth:`legacy_id_to_jid_username` the inverse
115
- transformation is defined.
116
- """
117
- self.jid_username = jid_username
92
+ self.xmpp = session.xmpp
93
+ self.stored = stored
94
+ self._set_logger()
95
+ super().__init__()
118
96
 
119
- self._name: Optional[str] = None
97
+ @property
98
+ def jid(self): # type:ignore[override]
99
+ jid = JID(self.stored.jid)
100
+ jid.resource = self.RESOURCE
101
+ return jid
120
102
 
121
- self.xmpp = session.xmpp
122
- self.jid = JID(self.jid_username + "@" + self.xmpp.boundjid.bare)
123
- self.jid.resource = self.RESOURCE
124
- self.log = logging.getLogger(self.jid.bare)
125
- self._set_logger_name()
126
- self._is_friend: bool = False
127
- self._added_to_roster = False
128
- self._caps_ver: str | None = None
129
- self._vcard_fetched = False
130
- self._vcard: str | None = None
131
- self._client_type: ClientType = "pc"
132
-
133
- async def get_vcard(self, fetch=True) -> VCard4 | None:
134
- if fetch and not self._vcard_fetched:
103
+ @property
104
+ def legacy_id(self):
105
+ return self.xmpp.LEGACY_CONTACT_ID_TYPE(self.stored.legacy_id)
106
+
107
+ async def get_vcard(self, fetch: bool = True) -> VCard4 | None:
108
+ if fetch and not self.stored.vcard_fetched:
135
109
  await self.fetch_vcard()
136
- if self._vcard is None:
110
+ if self.stored.vcard is None:
137
111
  return None
138
112
 
139
- return VCard4(xml=ET.fromstring(self._vcard))
113
+ return VCard4(xml=ET.fromstring(self.stored.vcard))
140
114
 
141
115
  @property
142
- def is_friend(self):
143
- return self._is_friend
116
+ def is_friend(self) -> bool:
117
+ return self.stored.is_friend
144
118
 
145
119
  @is_friend.setter
146
- def is_friend(self, value: bool):
147
- if value == self._is_friend:
120
+ def is_friend(self, value: bool) -> None:
121
+ if value == self.is_friend:
148
122
  return
149
- self._is_friend = value
150
- if self._updating_info:
151
- return
152
- self.__ensure_pk()
153
- assert self.contact_pk is not None
154
- self.xmpp.store.contacts.set_friend(self.contact_pk, value)
123
+ self.stored.is_friend = value
124
+ self.commit()
155
125
 
156
126
  @property
157
- def added_to_roster(self):
158
- return self._added_to_roster
127
+ def added_to_roster(self) -> bool:
128
+ return self.stored.added_to_roster
159
129
 
160
130
  @added_to_roster.setter
161
- def added_to_roster(self, value: bool):
162
- if value == self._added_to_roster:
163
- return
164
- self._added_to_roster = value
165
- if self._updating_info:
166
- return
167
- if self.contact_pk is None:
168
- # during LegacyRoster.fill()
131
+ def added_to_roster(self, value: bool) -> None:
132
+ if value == self.added_to_roster:
169
133
  return
170
- self.xmpp.store.contacts.set_added_to_roster(self.contact_pk, value)
134
+ self.stored.added_to_roster = value
135
+ self.commit()
171
136
 
172
137
  @property
173
- def participants(self) -> list["LegacyParticipant"]:
174
- if self.contact_pk is None:
175
- return []
176
-
177
- self.__ensure_pk()
178
- from ..group.participant import LegacyParticipant
179
-
180
- return [
181
- LegacyParticipant.get_self_or_unique_subclass().from_store(
182
- self.session, stored, contact=self
183
- )
184
- for stored in self.xmpp.store.participants.get_for_contact(self.contact_pk)
185
- ]
138
+ def participants(self) -> Iterator["LegacyParticipant"]:
139
+ with self.xmpp.store.session() as orm:
140
+ self.stored = orm.merge(self.stored)
141
+ participants = self.stored.participants
142
+ for p in participants:
143
+ with self.xmpp.store.session() as orm:
144
+ orm.add(p)
145
+ muc = self.session.bookmarks.from_store(p.room)
146
+ yield muc.participant_from_store(p, contact=self)
186
147
 
187
148
  @property
188
149
  def user_jid(self):
@@ -190,7 +151,7 @@ class LegacyContact(
190
151
 
191
152
  @property # type:ignore
192
153
  def DISCO_TYPE(self) -> ClientType:
193
- return self._client_type
154
+ return self.client_type
194
155
 
195
156
  @DISCO_TYPE.setter
196
157
  def DISCO_TYPE(self, value: ClientType) -> None:
@@ -203,47 +164,27 @@ class LegacyContact(
203
164
 
204
165
  Default is "pc".
205
166
  """
206
- return self._client_type
167
+ return self.stored.client_type
207
168
 
208
169
  @client_type.setter
209
170
  def client_type(self, value: ClientType) -> None:
210
- self._client_type = value
211
- if self._updating_info:
171
+ if self.stored.client_type == value:
212
172
  return
213
- self.__ensure_pk()
214
- assert self.contact_pk is not None
215
- self.xmpp.store.contacts.set_client_type(self.contact_pk, value)
216
-
217
- def _set_logger_name(self):
218
- self.log.name = f"{self.user_jid.bare}:contact:{self}"
173
+ self.stored.client_type = value
174
+ self.commit()
219
175
 
220
- def __repr__(self):
221
- return f"<Contact #{self.contact_pk} '{self.name}' ({self.legacy_id} - {self.jid.user})'>"
176
+ def _set_logger(self) -> None:
177
+ self.log = logging.getLogger(f"{self.user_jid.bare}:contact:{self}")
222
178
 
223
- def __ensure_pk(self):
224
- if self.contact_pk is not None:
225
- return
226
- # This happens for legacy modules that don't follow the Roster.fill /
227
- # populate contact attributes in Contact.update_info() method.
228
- # This results in (even) less optimised SQL writes and read, but
229
- # we allow it because it fits some legacy network libs better.
230
- with self.xmpp.store.session() as orm:
231
- orm.commit()
232
- stored = self.xmpp.store.contacts.get_by_legacy_id(
233
- self.user_pk, str(self.legacy_id)
234
- )
235
- if stored is None:
236
- self.contact_pk = self.xmpp.store.contacts.update(self, commit=True)
237
- else:
238
- self.contact_pk = stored.id
239
- assert self.contact_pk is not None
179
+ def __repr__(self) -> str:
180
+ return f"<Contact #{self.stored.id} '{self.name}' ({self.legacy_id} - {self.jid.user})'>"
240
181
 
241
- def __get_subscription_string(self):
182
+ def __get_subscription_string(self) -> str:
242
183
  if self.is_friend:
243
184
  return "both"
244
185
  return "none"
245
186
 
246
- def __propagate_to_participants(self, stanza: Presence):
187
+ def __propagate_to_participants(self, stanza: Presence) -> None:
247
188
  if not self.PROPAGATE_PRESENCE_TO_GROUPS:
248
189
  return
249
190
 
@@ -274,7 +215,11 @@ class LegacyContact(
274
215
  func(**kw)
275
216
 
276
217
  def _send(
277
- self, stanza: MessageOrPresenceTypeVar, carbon=False, nick=False, **send_kwargs
218
+ self,
219
+ stanza: MessageOrPresenceTypeVar,
220
+ carbon: bool = False,
221
+ nick: bool = False,
222
+ **send_kwargs,
278
223
  ) -> MessageOrPresenceTypeVar:
279
224
  if carbon and isinstance(stanza, Message):
280
225
  stanza["to"] = self.jid.bare
@@ -299,9 +244,15 @@ class LegacyContact(
299
244
  and self.xmpp.MARK_ALL_MESSAGES
300
245
  and is_markable(stanza)
301
246
  ):
302
- self.__ensure_pk()
303
- assert self.contact_pk is not None
304
- self.xmpp.store.contacts.add_to_sent(self.contact_pk, stanza["id"])
247
+ try:
248
+ with self.xmpp.store.session(expire_on_commit=False) as orm:
249
+ self.stored = orm.merge(self.stored)
250
+ new = ContactSent(contact=self.stored, msg_id=stanza["id"])
251
+ orm.add(new)
252
+ self.stored.sent_order.append(new)
253
+ orm.commit()
254
+ except IntegrityError:
255
+ self.log.warning("Contact has already sent message %s", stanza["id"])
305
256
  stanza["to"] = self.user_jid
306
257
  stanza.send()
307
258
  return stanza
@@ -320,50 +271,42 @@ class LegacyContact(
320
271
  :param horizon_xmpp_id: The latest message
321
272
  :return: A list of XMPP ids or None if horizon_xmpp_id was not found
322
273
  """
323
- self.__ensure_pk()
324
- assert self.contact_pk is not None
325
- return self.xmpp.store.contacts.pop_sent_up_to(self.contact_pk, horizon_xmpp_id)
274
+ with self.xmpp.store.session() as orm:
275
+ assert self.stored.id is not None
276
+ ids = self.xmpp.store.contacts.pop_sent_up_to(
277
+ orm, self.stored.id, horizon_xmpp_id
278
+ )
279
+ orm.commit()
280
+ return ids
326
281
 
327
282
  @property
328
- def name(self):
283
+ def name(self) -> str:
329
284
  """
330
285
  Friendly name of the contact, as it should appear in the user's roster
331
286
  """
332
- return self._name
287
+ return self.stored.nick or ""
333
288
 
334
289
  @name.setter
335
- def name(self, n: Optional[str]):
336
- if self._name == n:
290
+ def name(self, n: Optional[str]) -> None:
291
+ if self.stored.nick == n:
337
292
  return
338
- self._name = n
339
- self._set_logger_name()
293
+ self.stored.nick = n
294
+ self._set_logger()
340
295
  if self.is_friend and self.added_to_roster:
341
296
  self.xmpp.pubsub.broadcast_nick(
342
297
  user_jid=self.user_jid, jid=self.jid.bare, nick=n
343
298
  )
344
- if self._updating_info:
345
- # means we're in update_info(), so no participants, and no need
346
- # to write to DB now, it will be called in Roster.__finish_init_contact
347
- return
299
+ self.commit()
348
300
  for p in self.participants:
349
- p.nickname = n
350
- self.__ensure_pk()
351
- assert self.contact_pk is not None
352
- self.xmpp.store.contacts.update_nick(self.contact_pk, n)
301
+ p.nickname = n or str(self.legacy_id)
353
302
 
354
- def _get_cached_avatar_id(self) -> Optional[str]:
355
- if self.contact_pk is None:
356
- return None
357
- return self.xmpp.store.contacts.get_avatar_legacy_id(self.contact_pk)
358
-
359
- def _post_avatar_update(self):
360
- self.__ensure_pk()
361
- assert self.contact_pk is not None
362
- self.xmpp.store.contacts.set_avatar(
363
- self.contact_pk,
364
- self._avatar_pk,
365
- None if self.avatar_id is None else str(self.avatar_id),
366
- )
303
+ def _post_avatar_update(self, cached_avatar) -> None:
304
+ if self.is_friend and self.added_to_roster:
305
+ self.session.create_task(
306
+ self.session.xmpp.pubsub.broadcast_avatar(
307
+ self.jid.bare, self.session.user_jid, cached_avatar
308
+ )
309
+ )
367
310
  for p in self.participants:
368
311
  self.log.debug("Propagating new avatar to %s", p.muc)
369
312
  p.send_last_presence(force=True, no_cache_online=True)
@@ -382,7 +325,8 @@ class LegacyContact(
382
325
  email: Optional[str] = None,
383
326
  country: Optional[str] = None,
384
327
  locality: Optional[str] = None,
385
- ):
328
+ pronouns: Optional[str] = None,
329
+ ) -> None:
386
330
  vcard = VCard4()
387
331
  vcard.add_impp(f"xmpp:{self.jid.bare}")
388
332
 
@@ -414,18 +358,16 @@ class LegacyContact(
414
358
  vcard.add_address(country, locality)
415
359
  elif country:
416
360
  vcard.add_address(country, locality)
361
+ if pronouns:
362
+ vcard["pronouns"]["text"] = pronouns
417
363
 
418
- self._vcard = str(vcard)
419
- self._vcard_fetched = True
364
+ self.stored.vcard = str(vcard)
365
+ self.stored.vcard_fetched = True
420
366
  self.session.create_task(
421
367
  self.xmpp.pubsub.broadcast_vcard_event(self.jid, self.user_jid, vcard)
422
368
  )
423
369
 
424
- if self._updating_info:
425
- return
426
-
427
- assert self.contact_pk is not None
428
- self.xmpp.store.contacts.set_vcard(self.contact_pk, self._vcard)
370
+ self.commit()
429
371
 
430
372
  def get_roster_item(self):
431
373
  item = {
@@ -436,7 +378,7 @@ class LegacyContact(
436
378
  item["name"] = n
437
379
  return {self.jid.bare: item}
438
380
 
439
- async def add_to_roster(self, force=False):
381
+ async def add_to_roster(self, force: bool = False) -> None:
440
382
  """
441
383
  Add this contact to the user roster using :xep:`0356`
442
384
 
@@ -444,7 +386,7 @@ class LegacyContact(
444
386
  """
445
387
  if self.added_to_roster and not force:
446
388
  return
447
- if config.NO_ROSTER_PUSH:
389
+ if not self.session.user.preferences.get("roster_push", True):
448
390
  log.debug("Roster push request by plugin ignored (--no-roster-push)")
449
391
  return
450
392
  try:
@@ -452,16 +394,16 @@ class LegacyContact(
452
394
  jid=self.user_jid, roster_items=self.get_roster_item()
453
395
  )
454
396
  except PermissionError:
397
+ from slidge import __version__
398
+
455
399
  warnings.warn(
456
- "Slidge does not have privileges to add contacts to the roster. Refer"
457
- " to https://slidge.im/docs/slidge/main/admin/privilege.html for"
458
- " more info."
400
+ "Slidge does not have the privilege to manage rosters. See "
401
+ f"https://slidge.im/docs/slidge/{__version__}/admin/privilege.html"
402
+ )
403
+ self.send_friend_request(
404
+ f"I'm already your friend on {self.xmpp.COMPONENT_TYPE}, but "
405
+ "slidge is not allowed to manage your roster."
459
406
  )
460
- if config.ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK:
461
- self.send_friend_request(
462
- f"I'm already your friend on {self.xmpp.COMPONENT_TYPE}, but "
463
- "slidge is not allowed to manage your roster."
464
- )
465
407
  return
466
408
  except (IqError, IqTimeout) as e:
467
409
  self.log.warning("Could not add to roster", exc_info=e)
@@ -471,7 +413,7 @@ class LegacyContact(
471
413
  self.added_to_roster = True
472
414
  self.send_last_presence()
473
415
 
474
- async def __broadcast_pubsub_items(self):
416
+ async def __broadcast_pubsub_items(self) -> None:
475
417
  if not self.is_friend:
476
418
  return
477
419
  if not self.added_to_roster:
@@ -490,11 +432,11 @@ class LegacyContact(
490
432
  nick,
491
433
  )
492
434
 
493
- def send_friend_request(self, text: Optional[str] = None):
435
+ def send_friend_request(self, text: Optional[str] = None) -> None:
494
436
  presence = self._make_presence(ptype="subscribe", pstatus=text, bare=True)
495
437
  self._send(presence, nick=True)
496
438
 
497
- async def accept_friend_request(self, text: Optional[str] = None):
439
+ async def accept_friend_request(self, text: Optional[str] = None) -> None:
498
440
  """
499
441
  Call this to signify that this Contact has accepted to be a friend
500
442
  of the user.
@@ -503,7 +445,6 @@ class LegacyContact(
503
445
  """
504
446
  self.is_friend = True
505
447
  self.added_to_roster = True
506
- self.__ensure_pk()
507
448
  self.log.debug("Accepting friend request")
508
449
  presence = self._make_presence(ptype="subscribed", pstatus=text, bare=True)
509
450
  self._send(presence, nick=True)
@@ -511,7 +452,7 @@ class LegacyContact(
511
452
  await self.__broadcast_pubsub_items()
512
453
  self.log.debug("Accepted friend request")
513
454
 
514
- def reject_friend_request(self, text: Optional[str] = None):
455
+ def reject_friend_request(self, text: Optional[str] = None) -> None:
515
456
  """
516
457
  Call this to signify that this Contact has refused to be a contact
517
458
  of the user (or that they don't want to be friends anymore)
@@ -523,7 +464,7 @@ class LegacyContact(
523
464
  self._send(presence, nick=True)
524
465
  self.is_friend = False
525
466
 
526
- async def on_friend_request(self, text=""):
467
+ async def on_friend_request(self, text: str = "") -> None:
527
468
  """
528
469
  Called when receiving a "subscribe" presence, ie, "I would like to add
529
470
  you to my contacts/friends", from the user to this contact.
@@ -540,7 +481,7 @@ class LegacyContact(
540
481
  """
541
482
  pass
542
483
 
543
- async def on_friend_delete(self, text=""):
484
+ async def on_friend_delete(self, text: str = "") -> None:
544
485
  """
545
486
  Called when receiving an "unsubscribed" presence, ie, "I would like to
546
487
  remove you to my contacts/friends" or "I refuse your friend request"
@@ -551,7 +492,7 @@ class LegacyContact(
551
492
  """
552
493
  pass
553
494
 
554
- async def on_friend_accept(self):
495
+ async def on_friend_accept(self) -> None:
555
496
  """
556
497
  Called when receiving a "subscribed" presence, ie, "I accept to be
557
498
  your/confirm that you are my friend" from the user to this contact.
@@ -560,7 +501,7 @@ class LegacyContact(
560
501
  """
561
502
  pass
562
503
 
563
- def unsubscribe(self):
504
+ def unsubscribe(self) -> None:
564
505
  """
565
506
  (internal use by slidge)
566
507
 
@@ -569,9 +510,9 @@ class LegacyContact(
569
510
  their 'friends'".
570
511
  """
571
512
  for ptype in "unsubscribe", "unsubscribed", "unavailable":
572
- self.xmpp.send_presence(pfrom=self.jid, pto=self.user_jid.bare, ptype=ptype) # type: ignore
513
+ self.xmpp.send_presence(pfrom=self.jid, pto=self.user_jid.bare, ptype=ptype)
573
514
 
574
- async def update_info(self):
515
+ async def update_info(self) -> None:
575
516
  """
576
517
  Fetch information about this contact from the legacy network
577
518
 
@@ -587,7 +528,7 @@ class LegacyContact(
587
528
  """
588
529
  pass
589
530
 
590
- async def fetch_vcard(self):
531
+ async def fetch_vcard(self) -> None:
591
532
  """
592
533
  It the legacy network doesn't like that you fetch too many profiles on startup,
593
534
  it's also possible to fetch it here, which will be called when XMPP clients
@@ -606,40 +547,12 @@ class LegacyContact(
606
547
  ):
607
548
  p = super()._make_presence(last_seen=last_seen, **presence_kwargs)
608
549
  caps = self.xmpp.plugin["xep_0115"]
609
- if p.get_from().resource and self._caps_ver:
550
+ if p.get_from().resource and self.stored.caps_ver:
610
551
  p["caps"]["node"] = caps.caps_node
611
552
  p["caps"]["hash"] = caps.hash
612
- p["caps"]["ver"] = self._caps_ver
553
+ p["caps"]["ver"] = self.stored.caps_ver
613
554
  return p
614
555
 
615
- @classmethod
616
- def from_store(cls, session, stored: Contact, *args, **kwargs) -> Self:
617
- contact = cls(
618
- session,
619
- cls.xmpp.LEGACY_CONTACT_ID_TYPE(stored.legacy_id),
620
- stored.jid.user, # type: ignore
621
- *args, # type: ignore
622
- **kwargs, # type: ignore
623
- )
624
- contact.contact_pk = stored.id
625
- contact._name = stored.nick
626
- contact._is_friend = stored.is_friend
627
- contact._added_to_roster = stored.added_to_roster
628
- if (data := stored.extra_attributes) is not None:
629
- contact.deserialize_extra_attributes(data)
630
- contact._caps_ver = stored.caps_ver
631
- contact._set_logger_name()
632
- contact._AvatarMixin__avatar_unique_id = ( # type:ignore
633
- None
634
- if stored.avatar_legacy_id is None
635
- else session.xmpp.AVATAR_ID_TYPE(stored.avatar_legacy_id)
636
- )
637
- contact._avatar_pk = stored.avatar_id
638
- contact._vcard = stored.vcard
639
- contact._vcard_fetched = stored.vcard_fetched
640
- contact._client_type = stored.client_type
641
- return contact
642
-
643
556
 
644
557
  def is_markable(stanza: Union[Message, Presence]):
645
558
  if isinstance(stanza, Presence):