slidge 0.2.0a0__py3-none-any.whl → 0.2.0a1__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/__version__.py +1 -1
- slidge/command/admin.py +1 -1
- slidge/command/user.py +0 -1
- slidge/contact/contact.py +86 -32
- slidge/contact/roster.py +79 -19
- slidge/core/config.py +1 -4
- slidge/core/gateway/base.py +9 -2
- slidge/core/gateway/caps.py +7 -5
- slidge/core/gateway/muc_admin.py +1 -1
- slidge/core/gateway/session_dispatcher.py +20 -6
- slidge/core/gateway/vcard_temp.py +1 -1
- slidge/core/mixins/attachment.py +17 -4
- slidge/core/mixins/avatar.py +26 -9
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/message.py +7 -1
- slidge/core/mixins/presence.py +6 -3
- slidge/core/pubsub.py +7 -15
- slidge/core/session.py +6 -3
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +11 -2
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- slidge/db/avatar.py +20 -9
- slidge/db/models.py +16 -6
- slidge/db/store.py +217 -115
- slidge/group/archive.py +46 -1
- slidge/group/bookmarks.py +17 -5
- slidge/group/participant.py +10 -3
- slidge/group/room.py +183 -125
- slidge/main.py +3 -3
- slidge/slixfix/xep_0292/vcard4.py +2 -0
- slidge/util/archive_msg.py +2 -1
- slidge/util/test.py +54 -4
- slidge/util/types.py +5 -0
- slidge/util/util.py +22 -0
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/METADATA +2 -4
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/RECORD +43 -37
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
slidge/__version__.py
CHANGED
slidge/command/admin.py
CHANGED
@@ -55,7 +55,7 @@ class SlidgeInfo(AdminCommand):
|
|
55
55
|
async def run(self, _session, _ifrom, *_):
|
56
56
|
from slidge.__version__ import __version__
|
57
57
|
|
58
|
-
start = self.xmpp.datetime_started
|
58
|
+
start = self.xmpp.datetime_started # type:ignore
|
59
59
|
uptime = datetime.now() - start
|
60
60
|
|
61
61
|
if uptime.days:
|
slidge/command/user.py
CHANGED
@@ -131,7 +131,6 @@ class ListContacts(Command):
|
|
131
131
|
self, session: Optional[AnyBaseSession], _ifrom: JID, *_
|
132
132
|
) -> TableResult:
|
133
133
|
assert session is not None
|
134
|
-
await session.contacts.fill()
|
135
134
|
contacts = sorted(
|
136
135
|
session.contacts, key=lambda c: c.name.casefold() if c.name else ""
|
137
136
|
)
|
slidge/contact/contact.py
CHANGED
@@ -11,6 +11,7 @@ from slixmpp.types import MessageTypes
|
|
11
11
|
|
12
12
|
from ..core import config
|
13
13
|
from ..core.mixins import AvatarMixin, FullCarbonMixin, StoredAttributeMixin
|
14
|
+
from ..core.mixins.db import UpdateInfoMixin
|
14
15
|
from ..core.mixins.disco import ContactAccountDiscoMixin
|
15
16
|
from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
|
16
17
|
from ..db.models import Contact
|
@@ -30,6 +31,7 @@ class LegacyContact(
|
|
30
31
|
FullCarbonMixin,
|
31
32
|
ReactionRecipientMixin,
|
32
33
|
ThreadRecipientMixin,
|
34
|
+
UpdateInfoMixin,
|
33
35
|
metaclass=SubclassableOnce,
|
34
36
|
):
|
35
37
|
"""
|
@@ -82,7 +84,6 @@ class LegacyContact(
|
|
82
84
|
STRIP_SHORT_DELAY = True
|
83
85
|
_NON_FRIEND_PRESENCES_FILTER = {"subscribe", "unsubscribed"}
|
84
86
|
|
85
|
-
_avatar_pubsub_broadcast = True
|
86
87
|
_avatar_bare_jid = True
|
87
88
|
|
88
89
|
INVITATION_RECIPIENT = True
|
@@ -121,7 +122,8 @@ class LegacyContact(
|
|
121
122
|
self.jid.resource = self.RESOURCE
|
122
123
|
self.log = logging.getLogger(f"{self.user_jid.bare}:{self.jid.bare}")
|
123
124
|
self._is_friend: bool = False
|
124
|
-
self.
|
125
|
+
self._added_to_roster = False
|
126
|
+
self._caps_ver: str | None = None
|
125
127
|
|
126
128
|
@property
|
127
129
|
def is_friend(self):
|
@@ -132,9 +134,27 @@ class LegacyContact(
|
|
132
134
|
if value == self._is_friend:
|
133
135
|
return
|
134
136
|
self._is_friend = value
|
137
|
+
if self._updating_info:
|
138
|
+
return
|
135
139
|
assert self.contact_pk is not None
|
136
140
|
self.xmpp.store.contacts.set_friend(self.contact_pk, value)
|
137
141
|
|
142
|
+
@property
|
143
|
+
def added_to_roster(self):
|
144
|
+
return self._added_to_roster
|
145
|
+
|
146
|
+
@added_to_roster.setter
|
147
|
+
def added_to_roster(self, value: bool):
|
148
|
+
if value == self._added_to_roster:
|
149
|
+
return
|
150
|
+
self._added_to_roster = value
|
151
|
+
if self._updating_info:
|
152
|
+
return
|
153
|
+
if self.contact_pk is None:
|
154
|
+
# during LegacyRoster.fill()
|
155
|
+
return
|
156
|
+
self.xmpp.store.contacts.set_added_to_roster(self.contact_pk, value)
|
157
|
+
|
138
158
|
@property
|
139
159
|
def participants(self) -> list["LegacyParticipant"]:
|
140
160
|
assert self.contact_pk is not None
|
@@ -152,7 +172,7 @@ class LegacyContact(
|
|
152
172
|
return self.session.user_jid
|
153
173
|
|
154
174
|
def __repr__(self):
|
155
|
-
return f"<Contact
|
175
|
+
return f"<Contact {self.jid.bare} - {self.name or self.legacy_id}'>"
|
156
176
|
|
157
177
|
def __get_subscription_string(self):
|
158
178
|
if self.is_friend:
|
@@ -198,7 +218,7 @@ class LegacyContact(
|
|
198
218
|
self._privileged_send(stanza)
|
199
219
|
return stanza # type:ignore
|
200
220
|
|
201
|
-
if isinstance(stanza, Presence):
|
221
|
+
if not self._updating_info and isinstance(stanza, Presence):
|
202
222
|
self.__propagate_to_participants(stanza)
|
203
223
|
if (
|
204
224
|
not self.is_friend
|
@@ -209,7 +229,11 @@ class LegacyContact(
|
|
209
229
|
n = self.xmpp.plugin["xep_0172"].stanza.UserNick()
|
210
230
|
n["nick"] = self.name
|
211
231
|
stanza.append(n)
|
212
|
-
if
|
232
|
+
if (
|
233
|
+
not self._updating_info
|
234
|
+
and self.xmpp.MARK_ALL_MESSAGES
|
235
|
+
and is_markable(stanza)
|
236
|
+
):
|
213
237
|
assert self.contact_pk is not None
|
214
238
|
self.xmpp.store.contacts.add_to_sent(self.contact_pk, stanza["id"])
|
215
239
|
stanza["to"] = self.user_jid
|
@@ -244,21 +268,32 @@ class LegacyContact(
|
|
244
268
|
def name(self, n: Optional[str]):
|
245
269
|
if self._name == n:
|
246
270
|
return
|
271
|
+
self._name = n
|
272
|
+
if self.is_friend and self.added_to_roster:
|
273
|
+
self.xmpp.pubsub.broadcast_nick(
|
274
|
+
user_jid=self.user_jid, jid=self.jid.bare, nick=n
|
275
|
+
)
|
276
|
+
if self._updating_info:
|
277
|
+
# means we're in update_info(), so no participants, and no need
|
278
|
+
# to write to DB now, it will be called in Roster.__finish_init_contact
|
279
|
+
return
|
247
280
|
for p in self.participants:
|
248
281
|
p.nickname = n
|
249
|
-
self._name = n
|
250
282
|
assert self.contact_pk is not None
|
251
283
|
self.xmpp.store.contacts.update_nick(self.contact_pk, n)
|
252
|
-
self.xmpp.pubsub.broadcast_nick(
|
253
|
-
user_jid=self.user_jid, jid=self.jid.bare, nick=n
|
254
|
-
)
|
255
284
|
|
256
|
-
def _get_cached_avatar_id(self):
|
257
|
-
|
285
|
+
def _get_cached_avatar_id(self) -> Optional[str]:
|
286
|
+
if self.contact_pk is None:
|
287
|
+
return None
|
258
288
|
return self.xmpp.store.contacts.get_avatar_legacy_id(self.contact_pk)
|
259
289
|
|
260
290
|
def _post_avatar_update(self):
|
261
|
-
|
291
|
+
if self._updating_info:
|
292
|
+
return
|
293
|
+
if self.contact_pk is None:
|
294
|
+
# happens in LegacyRoster.fill(), the contact primary key is not
|
295
|
+
# set yet, but this will eventually be called in LegacyRoster.__finish_init_contact
|
296
|
+
return
|
262
297
|
self.xmpp.store.contacts.set_avatar(self.contact_pk, self._avatar_pk)
|
263
298
|
for p in self.participants:
|
264
299
|
self.log.debug("Propagating new avatar to %s", p.muc)
|
@@ -313,6 +348,15 @@ class LegacyContact(
|
|
313
348
|
|
314
349
|
self.xmpp.vcard.set_vcard(self.jid.bare, vcard, {self.user_jid.bare})
|
315
350
|
|
351
|
+
def get_roster_item(self):
|
352
|
+
item = {
|
353
|
+
"subscription": self.__get_subscription_string(),
|
354
|
+
"groups": [self.xmpp.ROSTER_GROUP],
|
355
|
+
}
|
356
|
+
if (n := self.name) is not None:
|
357
|
+
item["name"] = n
|
358
|
+
return {self.jid.bare: item}
|
359
|
+
|
316
360
|
async def add_to_roster(self, force=False):
|
317
361
|
"""
|
318
362
|
Add this contact to the user roster using :xep:`0356`
|
@@ -324,18 +368,10 @@ class LegacyContact(
|
|
324
368
|
if config.NO_ROSTER_PUSH:
|
325
369
|
log.debug("Roster push request by plugin ignored (--no-roster-push)")
|
326
370
|
return
|
327
|
-
item = {
|
328
|
-
"subscription": self.__get_subscription_string(),
|
329
|
-
"groups": [self.xmpp.ROSTER_GROUP],
|
330
|
-
}
|
331
|
-
if (n := self.name) is not None:
|
332
|
-
item["name"] = n
|
333
|
-
kw = dict(
|
334
|
-
jid=self.user_jid,
|
335
|
-
roster_items={self.jid.bare: item},
|
336
|
-
)
|
337
371
|
try:
|
338
|
-
await self._set_roster(
|
372
|
+
await self._set_roster(
|
373
|
+
jid=self.user_jid, roster_items=self.get_roster_item()
|
374
|
+
)
|
339
375
|
except PermissionError:
|
340
376
|
warnings.warn(
|
341
377
|
"Slidge does not have privileges to add contacts to the roster. Refer"
|
@@ -354,31 +390,32 @@ class LegacyContact(
|
|
354
390
|
# we only broadcast pubsub events for contacts added to the roster
|
355
391
|
# so if something was set before, we need to push it now
|
356
392
|
self.added_to_roster = True
|
357
|
-
self.session.create_task(self.__broadcast_pubsub_items())
|
358
393
|
self.send_last_presence()
|
359
394
|
|
360
395
|
async def __broadcast_pubsub_items(self):
|
396
|
+
if not self.is_friend:
|
397
|
+
return
|
398
|
+
if not self.added_to_roster:
|
399
|
+
return
|
361
400
|
cached_avatar = self.get_cached_avatar()
|
362
401
|
if cached_avatar is not None:
|
363
402
|
await self.xmpp.pubsub.broadcast_avatar(
|
364
403
|
self.jid.bare, self.session.user_jid, cached_avatar
|
365
404
|
)
|
366
405
|
nick = self.name
|
367
|
-
from ..core.pubsub import PepNick
|
368
406
|
|
369
407
|
if nick is not None:
|
370
|
-
|
371
|
-
await self.xmpp.pubsub.broadcast(
|
372
|
-
pep_nick.nick,
|
373
|
-
self.jid.bare,
|
408
|
+
self.xmpp.pubsub.broadcast_nick(
|
374
409
|
self.session.user_jid,
|
410
|
+
self.jid.bare,
|
411
|
+
nick,
|
375
412
|
)
|
376
413
|
|
377
414
|
async def _set_roster(self, **kw):
|
378
415
|
try:
|
379
|
-
|
416
|
+
await self.xmpp["xep_0356"].set_roster(**kw)
|
380
417
|
except PermissionError:
|
381
|
-
|
418
|
+
await self.xmpp["xep_0356_old"].set_roster(**kw)
|
382
419
|
|
383
420
|
def send_friend_request(self, text: Optional[str] = None):
|
384
421
|
presence = self._make_presence(ptype="subscribe", pstatus=text, bare=True)
|
@@ -392,8 +429,8 @@ class LegacyContact(
|
|
392
429
|
:param text: Optional message from the friend to the user
|
393
430
|
"""
|
394
431
|
self.is_friend = True
|
432
|
+
self.added_to_roster = True
|
395
433
|
assert self.contact_pk is not None
|
396
|
-
self.xmpp.store.contacts.set_friend(self.contact_pk, True)
|
397
434
|
self.log.debug("Accepting friend request")
|
398
435
|
presence = self._make_presence(ptype="subscribed", pstatus=text, bare=True)
|
399
436
|
self._send(presence, nick=True)
|
@@ -486,6 +523,22 @@ class LegacyContact(
|
|
486
523
|
"""
|
487
524
|
pass
|
488
525
|
|
526
|
+
def _make_presence(
|
527
|
+
self,
|
528
|
+
*,
|
529
|
+
last_seen: Optional[datetime.datetime] = None,
|
530
|
+
status_codes: Optional[set[int]] = None,
|
531
|
+
user_full_jid: Optional[JID] = None,
|
532
|
+
**presence_kwargs,
|
533
|
+
):
|
534
|
+
p = super()._make_presence(last_seen=last_seen, **presence_kwargs)
|
535
|
+
caps = self.xmpp.plugin["xep_0115"]
|
536
|
+
if p.get_from().resource and self._caps_ver:
|
537
|
+
p["caps"]["node"] = caps.caps_node
|
538
|
+
p["caps"]["hash"] = caps.hash
|
539
|
+
p["caps"]["ver"] = self._caps_ver
|
540
|
+
return p
|
541
|
+
|
489
542
|
@classmethod
|
490
543
|
def from_store(cls, session, stored: Contact, *args, **kwargs) -> Self:
|
491
544
|
contact = cls(
|
@@ -502,6 +555,7 @@ class LegacyContact(
|
|
502
555
|
if (data := stored.extra_attributes) is not None:
|
503
556
|
contact.deserialize_extra_attributes(data)
|
504
557
|
contact._set_avatar_from_store(stored)
|
558
|
+
contact._caps_ver = stored.caps_ver
|
505
559
|
return contact
|
506
560
|
|
507
561
|
|
slidge/contact/roster.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
import asyncio
|
2
2
|
import logging
|
3
|
-
|
3
|
+
import warnings
|
4
|
+
from typing import TYPE_CHECKING, AsyncIterator, Generic, Iterator, Optional, Type
|
4
5
|
|
5
6
|
from slixmpp import JID
|
7
|
+
from slixmpp.exceptions import IqError, XMPPError
|
6
8
|
from slixmpp.jid import JID_UNESCAPE_TRANSFORMATIONS, _unescape_node
|
7
9
|
|
8
10
|
from ..core.mixins.lock import NamedLockMixin
|
@@ -50,6 +52,7 @@ class LegacyRoster(
|
|
50
52
|
self.log = logging.getLogger(f"{self.session.user_jid.bare}:roster")
|
51
53
|
self.user_legacy_id: Optional[LegacyUserIdType] = None
|
52
54
|
self.ready: asyncio.Future[bool] = self.session.xmpp.loop.create_future()
|
55
|
+
self.__filling = False
|
53
56
|
super().__init__()
|
54
57
|
|
55
58
|
def __repr__(self):
|
@@ -63,25 +66,37 @@ class LegacyRoster(
|
|
63
66
|
async def __finish_init_contact(
|
64
67
|
self, legacy_id: LegacyUserIdType, jid_username: str, *args, **kwargs
|
65
68
|
):
|
66
|
-
|
67
|
-
async with self.lock(("finish", c.legacy_id)):
|
69
|
+
async with self.lock(("finish", legacy_id)):
|
68
70
|
with self.__store.session():
|
69
71
|
stored = self.__store.get_by_legacy_id(
|
70
72
|
self.session.user_pk, str(legacy_id)
|
71
73
|
)
|
72
|
-
if stored is not None
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
if stored is not None:
|
75
|
+
if stored.updated:
|
76
|
+
return self._contact_cls.from_store(self.session, stored)
|
77
|
+
c: LegacyContact = self._contact_cls(
|
78
|
+
self.session, legacy_id, jid_username, *args, **kwargs
|
79
|
+
)
|
80
|
+
c.contact_pk = stored.id
|
81
|
+
else:
|
82
|
+
c = self._contact_cls(
|
83
|
+
self.session, legacy_id, jid_username, *args, **kwargs
|
84
|
+
)
|
85
|
+
try:
|
86
|
+
with c.updating_info():
|
87
|
+
await c.avatar_wrap_update_info()
|
88
|
+
except Exception as e:
|
89
|
+
raise XMPPError("internal-server-error", str(e))
|
90
|
+
c._caps_ver = await c.get_caps_ver(c.jid)
|
91
|
+
need_avatar = c.contact_pk is None
|
92
|
+
c.contact_pk = self.__store.update(c, commit=not self.__filling)
|
93
|
+
if need_avatar:
|
94
|
+
c._post_avatar_update()
|
80
95
|
return c
|
81
96
|
|
82
97
|
def known_contacts(self, only_friends=True) -> dict[str, LegacyContactType]:
|
83
98
|
if only_friends:
|
84
|
-
return {
|
99
|
+
return {c.jid.bare: c for c in self if c.is_friend}
|
85
100
|
return {c.jid.bare: c for c in self}
|
86
101
|
|
87
102
|
async def by_jid(self, contact_jid: JID) -> LegacyContactType:
|
@@ -189,18 +204,63 @@ class LegacyRoster(
|
|
189
204
|
"""
|
190
205
|
return _unescape_node(jid_username)
|
191
206
|
|
192
|
-
async def
|
207
|
+
async def _fill(self):
|
208
|
+
try:
|
209
|
+
if hasattr(self.session.xmpp, "TEST_MODE"):
|
210
|
+
# dirty hack to avoid mocking xmpp server replies to this
|
211
|
+
# during tests
|
212
|
+
raise PermissionError
|
213
|
+
iq = await self.session.xmpp["xep_0356"].get_roster(
|
214
|
+
self.session.user_jid.bare
|
215
|
+
)
|
216
|
+
user_roster = iq["roster"]["items"]
|
217
|
+
except (PermissionError, IqError):
|
218
|
+
user_roster = None
|
219
|
+
|
220
|
+
with self.__store.session() as orm:
|
221
|
+
self.__filling = True
|
222
|
+
async for contact in self.fill():
|
223
|
+
if user_roster is None:
|
224
|
+
continue
|
225
|
+
item = contact.get_roster_item()
|
226
|
+
old = user_roster.get(contact.jid.bare)
|
227
|
+
if old is not None and all(
|
228
|
+
old[k] == item[contact.jid.bare][k]
|
229
|
+
for k in ("subscription", "groups", "name")
|
230
|
+
):
|
231
|
+
self.log.debug("No need to update roster")
|
232
|
+
continue
|
233
|
+
self.log.debug("Updating roster")
|
234
|
+
try:
|
235
|
+
await self.session.xmpp["xep_0356"].set_roster(
|
236
|
+
self.session.user_jid.bare,
|
237
|
+
item,
|
238
|
+
)
|
239
|
+
except (PermissionError, IqError) as e:
|
240
|
+
warnings.warn(f"Could not add to roster: {e}")
|
241
|
+
else:
|
242
|
+
contact._added_to_roster = True
|
243
|
+
orm.commit()
|
244
|
+
self.__filling = False
|
245
|
+
|
246
|
+
async def fill(self) -> AsyncIterator[LegacyContact]:
|
193
247
|
"""
|
194
248
|
Populate slidge's "virtual roster".
|
195
249
|
|
196
|
-
|
197
|
-
|
198
|
-
|
250
|
+
This should yield contacts that are meant to be added to the user's
|
251
|
+
roster, typically by using ``await self.by_legacy_id(contact_id)``.
|
252
|
+
Setting the contact nicknames, avatar, etc. should be in
|
253
|
+
:meth:`LegacyContact.update_info()`
|
254
|
+
|
255
|
+
It's not mandatory to override this method, but it is recommended way
|
256
|
+
to populate "friends" of the user. Calling
|
257
|
+
``await (await self.by_legacy_id(contact_id)).add_to_roster()``
|
258
|
+
accomplishes the same thing, but doing it in here allows to batch
|
259
|
+
DB queries and is better performance-wise.
|
199
260
|
|
200
|
-
Await ``Contact.add_to_roster()`` in here to add the contact to the
|
201
|
-
user's XMPP roster.
|
202
261
|
"""
|
203
|
-
|
262
|
+
return
|
263
|
+
yield
|
204
264
|
|
205
265
|
|
206
266
|
ESCAPE_TABLE = "".maketrans({v: k for k, v in JID_UNESCAPE_TRANSFORMATIONS.items()})
|
slidge/core/config.py
CHANGED
@@ -185,10 +185,7 @@ LOG_FORMAT__DOC = (
|
|
185
185
|
)
|
186
186
|
|
187
187
|
MAM_MAX_DAYS = 7
|
188
|
-
MAM_MAX_DAYS__DOC =
|
189
|
-
"Maximum number of days for group archive retention. "
|
190
|
-
"Since all text content stored in RAM right now, "
|
191
|
-
)
|
188
|
+
MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
|
192
189
|
|
193
190
|
CORRECTION_EMPTY_BODY_AS_RETRACTION = True
|
194
191
|
CORRECTION_EMPTY_BODY_AS_RETRACTION__DOC = (
|
slidge/core/gateway/base.py
CHANGED
@@ -39,6 +39,7 @@ from ...slixfix.roster import RosterBackend
|
|
39
39
|
from ...slixfix.xep_0292.vcard4 import VCard4Provider
|
40
40
|
from ...util import ABCSubclassableOnceAtMost
|
41
41
|
from ...util.types import AvatarType, MessageOrPresenceTypeVar
|
42
|
+
from ...util.util import timeit
|
42
43
|
from .. import config
|
43
44
|
from ..mixins import MessageMixin
|
44
45
|
from ..pubsub import PubSubComponent
|
@@ -271,6 +272,7 @@ class BaseGateway(
|
|
271
272
|
"""
|
272
273
|
|
273
274
|
def __init__(self):
|
275
|
+
self.log = log
|
274
276
|
self.datetime_started = datetime.now()
|
275
277
|
self.xmpp = self # ugly hack to work with the BaseSender mixin :/
|
276
278
|
self.default_ns = "jabber:component:accept"
|
@@ -335,6 +337,8 @@ class BaseGateway(
|
|
335
337
|
# with this we receive user avatar updates
|
336
338
|
self.plugin["xep_0030"].add_feature("urn:xmpp:avatar:metadata+notify")
|
337
339
|
|
340
|
+
self.plugin["xep_0030"].add_feature("urn:xmpp:chat-markers:0")
|
341
|
+
|
338
342
|
if self.GROUPS:
|
339
343
|
self.plugin["xep_0030"].add_feature("http://jabber.org/protocol/muc")
|
340
344
|
self.plugin["xep_0030"].add_feature("urn:xmpp:mam:2")
|
@@ -366,6 +370,8 @@ class BaseGateway(
|
|
366
370
|
|
367
371
|
self.__mam_cleanup_task = self.loop.create_task(self.__mam_cleanup())
|
368
372
|
|
373
|
+
MessageMixin.__init__(self) # ComponentXMPP does not call super().__init__()
|
374
|
+
|
369
375
|
async def __mam_cleanup(self):
|
370
376
|
if not config.MAM_MAX_DAYS:
|
371
377
|
return
|
@@ -399,7 +405,7 @@ class BaseGateway(
|
|
399
405
|
log.debug("Context in the exception handler: %s", context)
|
400
406
|
exc = context.get("exception")
|
401
407
|
if exc is None:
|
402
|
-
log.
|
408
|
+
log.debug("No exception in this context: %s", context)
|
403
409
|
elif isinstance(exc, SystemExit):
|
404
410
|
log.debug("SystemExit called in an asyncio task")
|
405
411
|
else:
|
@@ -540,6 +546,7 @@ class BaseGateway(
|
|
540
546
|
exc_info=e,
|
541
547
|
)
|
542
548
|
|
549
|
+
@timeit
|
543
550
|
async def __login_wrap(self, session: "BaseSession"):
|
544
551
|
session.send_gateway_status("Logging in…", show="dnd")
|
545
552
|
try:
|
@@ -557,7 +564,7 @@ class BaseGateway(
|
|
557
564
|
log.info("Login success for %s", session.user_jid)
|
558
565
|
session.logged = True
|
559
566
|
session.send_gateway_status("Syncing contacts…", show="dnd")
|
560
|
-
await session.contacts.
|
567
|
+
await session.contacts._fill()
|
561
568
|
if not (r := session.contacts.ready).done():
|
562
569
|
r.set_result(True)
|
563
570
|
if self.GROUPS:
|
slidge/core/gateway/caps.py
CHANGED
@@ -5,8 +5,6 @@ from slixmpp import Presence
|
|
5
5
|
from slixmpp.exceptions import XMPPError
|
6
6
|
from slixmpp.xmlstream import StanzaBase
|
7
7
|
|
8
|
-
from ...contact import LegacyContact
|
9
|
-
|
10
8
|
if TYPE_CHECKING:
|
11
9
|
from .base import BaseGateway
|
12
10
|
|
@@ -25,6 +23,9 @@ class Caps:
|
|
25
23
|
if not isinstance(stanza, Presence):
|
26
24
|
return stanza
|
27
25
|
|
26
|
+
if stanza.get_plugin("caps", check=True):
|
27
|
+
return stanza
|
28
|
+
|
28
29
|
if stanza["type"] not in ("available", "chat", "away", "dnd", "xa"):
|
29
30
|
return stanza
|
30
31
|
|
@@ -44,10 +45,11 @@ class Caps:
|
|
44
45
|
|
45
46
|
await session.ready
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
try:
|
49
|
+
contact = await session.contacts.by_jid(pfrom)
|
50
|
+
except XMPPError:
|
49
51
|
return stanza
|
50
|
-
ver = await
|
52
|
+
ver = await contact.get_caps_ver(pfrom)
|
51
53
|
else:
|
52
54
|
ver = await caps.get_verstring(pfrom)
|
53
55
|
|
slidge/core/gateway/muc_admin.py
CHANGED
@@ -28,7 +28,7 @@ class MucAdmin:
|
|
28
28
|
|
29
29
|
reply = iq.reply()
|
30
30
|
reply.enable("mucadmin_query")
|
31
|
-
for participant in
|
31
|
+
async for participant in muc.get_participants():
|
32
32
|
if not participant.affiliation == affiliation:
|
33
33
|
continue
|
34
34
|
reply["mucadmin_query"].append(participant.mucadmin_item())
|
@@ -632,14 +632,28 @@ class SessionDispatcher:
|
|
632
632
|
session = await self.__get_session(iq)
|
633
633
|
session.raise_if_not_logged()
|
634
634
|
|
635
|
-
item = iq["mucadmin_query"]["item"]
|
636
|
-
contact = await session.contacts.by_jid(JID(item["jid"]))
|
637
|
-
|
638
635
|
muc = await session.bookmarks.by_jid(iq.get_to())
|
639
636
|
|
640
|
-
|
641
|
-
|
642
|
-
|
637
|
+
item = iq["mucadmin_query"]["item"]
|
638
|
+
if item["jid"]:
|
639
|
+
contact = await session.contacts.by_jid(JID(item["jid"]))
|
640
|
+
else:
|
641
|
+
part = await muc.get_participant(
|
642
|
+
item["nick"], fill_first=True, raise_if_not_found=True
|
643
|
+
)
|
644
|
+
assert part.contact is not None
|
645
|
+
contact = part.contact
|
646
|
+
|
647
|
+
if item["affiliation"]:
|
648
|
+
await muc.on_set_affiliation(
|
649
|
+
contact,
|
650
|
+
item["affiliation"],
|
651
|
+
item["reason"] or None,
|
652
|
+
item["nick"] or None,
|
653
|
+
)
|
654
|
+
elif item["role"] == "none":
|
655
|
+
await muc.on_kick(contact, item["reason"] or None)
|
656
|
+
|
643
657
|
iq.reply(clear=True).send()
|
644
658
|
|
645
659
|
async def on_groupchat_direct_invite(self, msg: Message):
|
@@ -61,7 +61,7 @@ class VCardTemp:
|
|
61
61
|
|
62
62
|
async def __handle_get_vcard_temp(self, iq: Iq):
|
63
63
|
session = self.xmpp.get_session_from_stanza(iq)
|
64
|
-
entity = await session.get_contact_or_group_or_participant(iq.get_to())
|
64
|
+
entity = await session.get_contact_or_group_or_participant(iq.get_to(), False)
|
65
65
|
if not entity:
|
66
66
|
raise XMPPError("item-not-found")
|
67
67
|
|
slidge/core/mixins/attachment.py
CHANGED
@@ -9,7 +9,7 @@ import warnings
|
|
9
9
|
from datetime import datetime
|
10
10
|
from mimetypes import guess_type
|
11
11
|
from pathlib import Path
|
12
|
-
from typing import IO, Collection, Optional, Sequence, Union
|
12
|
+
from typing import IO, AsyncIterator, Collection, Optional, Sequence, Union
|
13
13
|
from urllib.parse import quote as urlquote
|
14
14
|
from uuid import uuid4
|
15
15
|
from xml.etree import ElementTree as ET
|
@@ -141,6 +141,7 @@ class AttachmentMixin(MessageMaker):
|
|
141
141
|
async def __get_url(
|
142
142
|
self,
|
143
143
|
file_path: Optional[Path] = None,
|
144
|
+
async_data_stream: Optional[AsyncIterator[bytes]] = None,
|
144
145
|
data_stream: Optional[IO[bytes]] = None,
|
145
146
|
data: Optional[bytes] = None,
|
146
147
|
file_url: Optional[str] = None,
|
@@ -176,14 +177,23 @@ class AttachmentMixin(MessageMaker):
|
|
176
177
|
with file_path.open("wb") as f:
|
177
178
|
f.write(await r.read())
|
178
179
|
|
179
|
-
|
180
|
-
|
181
|
-
data = data_stream.read()
|
180
|
+
elif data_stream is not None:
|
181
|
+
data = data_stream.read()
|
182
182
|
if data is None:
|
183
183
|
raise RuntimeError
|
184
184
|
|
185
185
|
with file_path.open("wb") as f:
|
186
186
|
f.write(data)
|
187
|
+
elif async_data_stream is not None:
|
188
|
+
# TODO: patch slixmpp to allow this as data source for
|
189
|
+
# upload_file() so we don't even have to write anything
|
190
|
+
# to disk.
|
191
|
+
with file_path.open("wb") as f:
|
192
|
+
async for chunk in async_data_stream:
|
193
|
+
f.write(chunk)
|
194
|
+
elif data is not None:
|
195
|
+
with file_path.open("wb") as f:
|
196
|
+
f.write(data)
|
187
197
|
|
188
198
|
is_temp = not bool(config.NO_UPLOAD_PATH)
|
189
199
|
else:
|
@@ -296,6 +306,7 @@ class AttachmentMixin(MessageMaker):
|
|
296
306
|
file_path: Optional[Union[Path, str]] = None,
|
297
307
|
legacy_msg_id: Optional[LegacyMessageType] = None,
|
298
308
|
*,
|
309
|
+
async_data_stream: Optional[AsyncIterator[bytes]] = None,
|
299
310
|
data_stream: Optional[IO[bytes]] = None,
|
300
311
|
data: Optional[bytes] = None,
|
301
312
|
file_url: Optional[str] = None,
|
@@ -312,6 +323,7 @@ class AttachmentMixin(MessageMaker):
|
|
312
323
|
Send a single file from this :term:`XMPP Entity`.
|
313
324
|
|
314
325
|
:param file_path: Path to the attachment
|
326
|
+
:param async_data_stream: Alternatively (and ideally) an AsyncIterator yielding bytes
|
315
327
|
:param data_stream: Alternatively, a stream of bytes (such as a File object)
|
316
328
|
:param data: Alternatively, a bytes object
|
317
329
|
:param file_url: Alternatively, a URL
|
@@ -342,6 +354,7 @@ class AttachmentMixin(MessageMaker):
|
|
342
354
|
|
343
355
|
is_temp, local_path, new_url = await self.__get_url(
|
344
356
|
Path(file_path) if file_path else None,
|
357
|
+
async_data_stream,
|
345
358
|
data_stream,
|
346
359
|
data,
|
347
360
|
file_url,
|