slidge 0.2.11__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.
- slidge/__init__.py +5 -2
- slidge/command/adhoc.py +9 -3
- slidge/command/admin.py +16 -12
- slidge/command/base.py +16 -12
- slidge/command/chat_command.py +25 -16
- slidge/command/user.py +7 -8
- slidge/contact/contact.py +123 -210
- slidge/contact/roster.py +108 -105
- slidge/core/config.py +2 -43
- slidge/core/dispatcher/caps.py +9 -2
- slidge/core/dispatcher/disco.py +13 -3
- slidge/core/dispatcher/message/__init__.py +1 -1
- slidge/core/dispatcher/message/chat_state.py +17 -8
- slidge/core/dispatcher/message/marker.py +7 -5
- slidge/core/dispatcher/message/message.py +120 -93
- slidge/core/dispatcher/muc/__init__.py +1 -1
- slidge/core/dispatcher/muc/admin.py +4 -4
- slidge/core/dispatcher/muc/mam.py +10 -6
- slidge/core/dispatcher/muc/misc.py +4 -2
- slidge/core/dispatcher/muc/owner.py +5 -3
- slidge/core/dispatcher/muc/ping.py +3 -1
- slidge/core/dispatcher/presence.py +26 -15
- slidge/core/dispatcher/registration.py +20 -12
- slidge/core/dispatcher/search.py +7 -3
- slidge/core/dispatcher/session_dispatcher.py +13 -5
- slidge/core/dispatcher/util.py +37 -27
- slidge/core/dispatcher/vcard.py +7 -4
- slidge/core/gateway.py +177 -87
- slidge/core/mixins/__init__.py +1 -11
- slidge/core/mixins/attachment.py +200 -147
- slidge/core/mixins/avatar.py +105 -177
- slidge/core/mixins/base.py +3 -1
- slidge/core/mixins/db.py +50 -2
- slidge/core/mixins/disco.py +1 -1
- slidge/core/mixins/message.py +19 -17
- slidge/core/mixins/message_maker.py +29 -15
- slidge/core/mixins/message_text.py +67 -30
- slidge/core/mixins/presence.py +94 -37
- slidge/core/pubsub.py +42 -47
- slidge/core/session.py +95 -60
- slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +361 -0
- slidge/db/avatar.py +150 -119
- slidge/db/meta.py +33 -22
- slidge/db/models.py +69 -117
- slidge/db/store.py +414 -1094
- slidge/group/archive.py +65 -55
- slidge/group/bookmarks.py +96 -59
- slidge/group/participant.py +150 -144
- slidge/group/room.py +351 -328
- slidge/main.py +34 -22
- slidge/migration.py +17 -29
- slidge/slixfix/__init__.py +20 -4
- slidge/slixfix/delivery_receipt.py +6 -4
- slidge/slixfix/link_preview/link_preview.py +1 -1
- slidge/slixfix/link_preview/stanza.py +1 -1
- slidge/slixfix/roster.py +5 -7
- slidge/slixfix/xep_0077/register.py +8 -8
- slidge/slixfix/xep_0077/stanza.py +7 -7
- slidge/slixfix/xep_0100/gateway.py +12 -13
- slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
- slidge/slixfix/xep_0292/vcard4.py +12 -2
- slidge/util/archive_msg.py +11 -5
- slidge/util/conf.py +27 -21
- slidge/util/jid_escaping.py +1 -1
- slidge/{core/mixins → util}/lock.py +6 -6
- slidge/util/test.py +30 -29
- slidge/util/types.py +24 -18
- slidge/util/util.py +26 -22
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/METADATA +1 -1
- slidge-0.3.0.dist-info/RECORD +95 -0
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/WHEEL +1 -1
- slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py +0 -33
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -36
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +0 -85
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -36
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -37
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -41
- slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +0 -52
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -42
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -61
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -48
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -43
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -139
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -50
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +0 -79
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -214
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -52
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -34
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -26
- slidge-0.2.11.dist-info/RECORD +0 -112
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/entry_points.txt +0 -0
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {slidge-0.2.11.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,
|
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
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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.
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
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
|
-
|
122
|
-
|
123
|
-
self.
|
124
|
-
|
125
|
-
|
126
|
-
self.
|
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.
|
110
|
+
if self.stored.vcard is None:
|
137
111
|
return None
|
138
112
|
|
139
|
-
return VCard4(xml=ET.fromstring(self.
|
113
|
+
return VCard4(xml=ET.fromstring(self.stored.vcard))
|
140
114
|
|
141
115
|
@property
|
142
|
-
def is_friend(self):
|
143
|
-
return self.
|
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.
|
120
|
+
def is_friend(self, value: bool) -> None:
|
121
|
+
if value == self.is_friend:
|
148
122
|
return
|
149
|
-
self.
|
150
|
-
|
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.
|
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.
|
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.
|
134
|
+
self.stored.added_to_roster = value
|
135
|
+
self.commit()
|
171
136
|
|
172
137
|
@property
|
173
|
-
def participants(self) ->
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
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.
|
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.
|
167
|
+
return self.stored.client_type
|
207
168
|
|
208
169
|
@client_type.setter
|
209
170
|
def client_type(self, value: ClientType) -> None:
|
210
|
-
self.
|
211
|
-
if self._updating_info:
|
171
|
+
if self.stored.client_type == value:
|
212
172
|
return
|
213
|
-
self.
|
214
|
-
|
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
|
221
|
-
|
176
|
+
def _set_logger(self) -> None:
|
177
|
+
self.log = logging.getLogger(f"{self.user_jid.bare}:contact:{self}")
|
222
178
|
|
223
|
-
def
|
224
|
-
|
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,
|
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
|
-
|
303
|
-
|
304
|
-
|
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.
|
324
|
-
|
325
|
-
|
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.
|
287
|
+
return self.stored.nick or ""
|
333
288
|
|
334
289
|
@name.setter
|
335
|
-
def name(self, n: Optional[str]):
|
336
|
-
if self.
|
290
|
+
def name(self, n: Optional[str]) -> None:
|
291
|
+
if self.stored.nick == n:
|
337
292
|
return
|
338
|
-
self.
|
339
|
-
self.
|
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
|
-
|
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
|
355
|
-
if self.
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
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.
|
419
|
-
self.
|
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
|
-
|
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
|
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
|
457
|
-
"
|
458
|
-
|
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)
|
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.
|
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.
|
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):
|