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/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):
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
301
  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)
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,7 @@ class LegacyContact(
382
325
  email: Optional[str] = None,
383
326
  country: Optional[str] = None,
384
327
  locality: Optional[str] = None,
385
- ):
328
+ ) -> None:
386
329
  vcard = VCard4()
387
330
  vcard.add_impp(f"xmpp:{self.jid.bare}")
388
331
 
@@ -415,17 +358,13 @@ class LegacyContact(
415
358
  elif country:
416
359
  vcard.add_address(country, locality)
417
360
 
418
- self._vcard = str(vcard)
419
- self._vcard_fetched = True
361
+ self.stored.vcard = str(vcard)
362
+ self.stored.vcard_fetched = True
420
363
  self.session.create_task(
421
364
  self.xmpp.pubsub.broadcast_vcard_event(self.jid, self.user_jid, vcard)
422
365
  )
423
366
 
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)
367
+ self.commit()
429
368
 
430
369
  def get_roster_item(self):
431
370
  item = {
@@ -436,7 +375,7 @@ class LegacyContact(
436
375
  item["name"] = n
437
376
  return {self.jid.bare: item}
438
377
 
439
- async def add_to_roster(self, force=False):
378
+ async def add_to_roster(self, force: bool = False) -> None:
440
379
  """
441
380
  Add this contact to the user roster using :xep:`0356`
442
381
 
@@ -444,7 +383,7 @@ class LegacyContact(
444
383
  """
445
384
  if self.added_to_roster and not force:
446
385
  return
447
- if config.NO_ROSTER_PUSH:
386
+ if not self.session.user.preferences.get("roster_push", True):
448
387
  log.debug("Roster push request by plugin ignored (--no-roster-push)")
449
388
  return
450
389
  try:
@@ -452,16 +391,16 @@ class LegacyContact(
452
391
  jid=self.user_jid, roster_items=self.get_roster_item()
453
392
  )
454
393
  except PermissionError:
394
+ from slidge import __version__
395
+
455
396
  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."
397
+ "Slidge does not have the privilege to manage rosters. See "
398
+ f"https://slidge.im/docs/slidge/{__version__}/admin/privilege.html"
399
+ )
400
+ self.send_friend_request(
401
+ f"I'm already your friend on {self.xmpp.COMPONENT_TYPE}, but "
402
+ "slidge is not allowed to manage your roster."
459
403
  )
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
404
  return
466
405
  except (IqError, IqTimeout) as e:
467
406
  self.log.warning("Could not add to roster", exc_info=e)
@@ -471,7 +410,7 @@ class LegacyContact(
471
410
  self.added_to_roster = True
472
411
  self.send_last_presence()
473
412
 
474
- async def __broadcast_pubsub_items(self):
413
+ async def __broadcast_pubsub_items(self) -> None:
475
414
  if not self.is_friend:
476
415
  return
477
416
  if not self.added_to_roster:
@@ -490,11 +429,11 @@ class LegacyContact(
490
429
  nick,
491
430
  )
492
431
 
493
- def send_friend_request(self, text: Optional[str] = None):
432
+ def send_friend_request(self, text: Optional[str] = None) -> None:
494
433
  presence = self._make_presence(ptype="subscribe", pstatus=text, bare=True)
495
434
  self._send(presence, nick=True)
496
435
 
497
- async def accept_friend_request(self, text: Optional[str] = None):
436
+ async def accept_friend_request(self, text: Optional[str] = None) -> None:
498
437
  """
499
438
  Call this to signify that this Contact has accepted to be a friend
500
439
  of the user.
@@ -503,7 +442,6 @@ class LegacyContact(
503
442
  """
504
443
  self.is_friend = True
505
444
  self.added_to_roster = True
506
- self.__ensure_pk()
507
445
  self.log.debug("Accepting friend request")
508
446
  presence = self._make_presence(ptype="subscribed", pstatus=text, bare=True)
509
447
  self._send(presence, nick=True)
@@ -511,7 +449,7 @@ class LegacyContact(
511
449
  await self.__broadcast_pubsub_items()
512
450
  self.log.debug("Accepted friend request")
513
451
 
514
- def reject_friend_request(self, text: Optional[str] = None):
452
+ def reject_friend_request(self, text: Optional[str] = None) -> None:
515
453
  """
516
454
  Call this to signify that this Contact has refused to be a contact
517
455
  of the user (or that they don't want to be friends anymore)
@@ -523,7 +461,7 @@ class LegacyContact(
523
461
  self._send(presence, nick=True)
524
462
  self.is_friend = False
525
463
 
526
- async def on_friend_request(self, text=""):
464
+ async def on_friend_request(self, text: str = "") -> None:
527
465
  """
528
466
  Called when receiving a "subscribe" presence, ie, "I would like to add
529
467
  you to my contacts/friends", from the user to this contact.
@@ -540,7 +478,7 @@ class LegacyContact(
540
478
  """
541
479
  pass
542
480
 
543
- async def on_friend_delete(self, text=""):
481
+ async def on_friend_delete(self, text: str = "") -> None:
544
482
  """
545
483
  Called when receiving an "unsubscribed" presence, ie, "I would like to
546
484
  remove you to my contacts/friends" or "I refuse your friend request"
@@ -551,7 +489,7 @@ class LegacyContact(
551
489
  """
552
490
  pass
553
491
 
554
- async def on_friend_accept(self):
492
+ async def on_friend_accept(self) -> None:
555
493
  """
556
494
  Called when receiving a "subscribed" presence, ie, "I accept to be
557
495
  your/confirm that you are my friend" from the user to this contact.
@@ -560,7 +498,7 @@ class LegacyContact(
560
498
  """
561
499
  pass
562
500
 
563
- def unsubscribe(self):
501
+ def unsubscribe(self) -> None:
564
502
  """
565
503
  (internal use by slidge)
566
504
 
@@ -569,9 +507,9 @@ class LegacyContact(
569
507
  their 'friends'".
570
508
  """
571
509
  for ptype in "unsubscribe", "unsubscribed", "unavailable":
572
- self.xmpp.send_presence(pfrom=self.jid, pto=self.user_jid.bare, ptype=ptype) # type: ignore
510
+ self.xmpp.send_presence(pfrom=self.jid, pto=self.user_jid.bare, ptype=ptype)
573
511
 
574
- async def update_info(self):
512
+ async def update_info(self) -> None:
575
513
  """
576
514
  Fetch information about this contact from the legacy network
577
515
 
@@ -587,7 +525,7 @@ class LegacyContact(
587
525
  """
588
526
  pass
589
527
 
590
- async def fetch_vcard(self):
528
+ async def fetch_vcard(self) -> None:
591
529
  """
592
530
  It the legacy network doesn't like that you fetch too many profiles on startup,
593
531
  it's also possible to fetch it here, which will be called when XMPP clients
@@ -606,40 +544,12 @@ class LegacyContact(
606
544
  ):
607
545
  p = super()._make_presence(last_seen=last_seen, **presence_kwargs)
608
546
  caps = self.xmpp.plugin["xep_0115"]
609
- if p.get_from().resource and self._caps_ver:
547
+ if p.get_from().resource and self.stored.caps_ver:
610
548
  p["caps"]["node"] = caps.caps_node
611
549
  p["caps"]["hash"] = caps.hash
612
- p["caps"]["ver"] = self._caps_ver
550
+ p["caps"]["ver"] = self.stored.caps_ver
613
551
  return p
614
552
 
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
553
 
644
554
  def is_markable(stanza: Union[Message, Presence]):
645
555
  if isinstance(stanza, Presence):