slidge 0.2.0a0__py3-none-any.whl → 0.2.0a1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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,
|