slidge 0.1.3__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/__init__.py +3 -5
- slidge/__main__.py +2 -196
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +8 -1
- slidge/command/admin.py +6 -7
- slidge/command/base.py +1 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +85 -6
- slidge/contact/contact.py +165 -49
- slidge/contact/roster.py +122 -47
- slidge/core/config.py +14 -11
- slidge/core/gateway/base.py +148 -36
- slidge/core/gateway/caps.py +7 -5
- slidge/core/gateway/disco.py +2 -4
- slidge/core/gateway/mam.py +1 -4
- slidge/core/gateway/muc_admin.py +1 -1
- slidge/core/gateway/ping.py +2 -3
- slidge/core/gateway/presence.py +1 -1
- slidge/core/gateway/registration.py +32 -21
- slidge/core/gateway/search.py +3 -5
- slidge/core/gateway/session_dispatcher.py +120 -57
- slidge/core/gateway/vcard_temp.py +7 -5
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +32 -14
- slidge/core/mixins/avatar.py +90 -25
- slidge/core/mixins/base.py +8 -2
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/message.py +18 -8
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/presence.py +17 -4
- slidge/core/pubsub.py +54 -220
- slidge/core/session.py +69 -34
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/env.py +64 -0
- slidge/db/alembic/script.py.mako +26 -0
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -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/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +85 -0
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
- 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/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
- slidge/db/avatar.py +235 -0
- slidge/db/meta.py +65 -0
- slidge/db/models.py +375 -0
- slidge/db/store.py +1078 -0
- slidge/group/archive.py +58 -14
- slidge/group/bookmarks.py +72 -57
- slidge/group/participant.py +87 -28
- slidge/group/room.py +369 -211
- slidge/main.py +201 -0
- slidge/migration.py +30 -0
- slidge/slixfix/__init__.py +35 -2
- slidge/slixfix/roster.py +11 -4
- slidge/slixfix/xep_0292/vcard4.py +3 -0
- slidge/util/archive_msg.py +2 -1
- slidge/util/db.py +1 -47
- slidge/util/test.py +71 -4
- slidge/util/types.py +29 -4
- slidge/util/util.py +22 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/METADATA +4 -4
- slidge-0.2.0a1.dist-info/RECORD +114 -0
- slidge/core/cache.py +0 -183
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.3.dist-info/RECORD +0 -96
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
- {slidge-0.1.3.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
slidge/core/gateway/base.py
CHANGED
@@ -8,7 +8,16 @@ import re
|
|
8
8
|
import tempfile
|
9
9
|
from copy import copy
|
10
10
|
from datetime import datetime
|
11
|
-
from typing import
|
11
|
+
from typing import (
|
12
|
+
TYPE_CHECKING,
|
13
|
+
Any,
|
14
|
+
Callable,
|
15
|
+
Collection,
|
16
|
+
Mapping,
|
17
|
+
Optional,
|
18
|
+
Sequence,
|
19
|
+
Union,
|
20
|
+
)
|
12
21
|
|
13
22
|
import aiohttp
|
14
23
|
import qrcode
|
@@ -24,12 +33,13 @@ from ...command.admin import Exec
|
|
24
33
|
from ...command.base import Command, FormField
|
25
34
|
from ...command.chat_command import ChatCommandProvider
|
26
35
|
from ...command.register import RegistrationType
|
36
|
+
from ...db import GatewayUser, SlidgeStore
|
37
|
+
from ...db.avatar import avatar_cache
|
27
38
|
from ...slixfix.roster import RosterBackend
|
28
39
|
from ...slixfix.xep_0292.vcard4 import VCard4Provider
|
29
40
|
from ...util import ABCSubclassableOnceAtMost
|
30
|
-
from ...util.db import GatewayUser, user_store
|
31
|
-
from ...util.sql import db
|
32
41
|
from ...util.types import AvatarType, MessageOrPresenceTypeVar
|
42
|
+
from ...util.util import timeit
|
33
43
|
from .. import config
|
34
44
|
from ..mixins import MessageMixin
|
35
45
|
from ..pubsub import PubSubComponent
|
@@ -141,6 +151,23 @@ class BaseGateway(
|
|
141
151
|
)
|
142
152
|
REGISTRATION_QR_INSTRUCTIONS = "Flash this code or follow this link"
|
143
153
|
|
154
|
+
PREFERENCES = [
|
155
|
+
FormField(
|
156
|
+
var="sync_presence",
|
157
|
+
label="Propagate your XMPP presence to the legacy network.",
|
158
|
+
value="true",
|
159
|
+
required=True,
|
160
|
+
type="boolean",
|
161
|
+
),
|
162
|
+
FormField(
|
163
|
+
var="sync_avatar",
|
164
|
+
label="Propagate your XMPP avatar to the legacy network.",
|
165
|
+
value="true",
|
166
|
+
required=True,
|
167
|
+
type="boolean",
|
168
|
+
),
|
169
|
+
]
|
170
|
+
|
144
171
|
ROSTER_GROUP: str = "slidge"
|
145
172
|
"""
|
146
173
|
Name of the group assigned to a :class:`.LegacyContact` automagically
|
@@ -202,8 +229,50 @@ class BaseGateway(
|
|
202
229
|
mtype: MessageTypes = "chat"
|
203
230
|
is_group = False
|
204
231
|
_can_send_carbon = False
|
232
|
+
store: SlidgeStore
|
233
|
+
avatar_pk: int
|
234
|
+
|
235
|
+
AVATAR_ID_TYPE: Callable[[str], Any] = str
|
236
|
+
"""
|
237
|
+
Modify this if the legacy network uses unique avatar IDs that are not strings.
|
238
|
+
|
239
|
+
This is required because we store those IDs as TEXT in the persistent SQL DB.
|
240
|
+
The callable specified here will receive is responsible for converting the
|
241
|
+
serialised-as-text version of the avatar unique ID back to the proper type.
|
242
|
+
Common example: ``int``.
|
243
|
+
"""
|
244
|
+
# FIXME: do we really need this since we have session.xmpp_to_legacy_msg_id?
|
245
|
+
# (maybe we do)
|
246
|
+
LEGACY_MSG_ID_TYPE: Callable[[str], Any] = str
|
247
|
+
"""
|
248
|
+
Modify this if the legacy network uses unique message IDs that are not strings.
|
249
|
+
|
250
|
+
This is required because we store those IDs as TEXT in the persistent SQL DB.
|
251
|
+
The callable specified here will receive is responsible for converting the
|
252
|
+
serialised-as-text version of the message unique ID back to the proper type.
|
253
|
+
Common example: ``int``.
|
254
|
+
"""
|
255
|
+
LEGACY_CONTACT_ID_TYPE: Callable[[str], Any] = str
|
256
|
+
"""
|
257
|
+
Modify this if the legacy network uses unique contact IDs that are not strings.
|
258
|
+
|
259
|
+
This is required because we store those IDs as TEXT in the persistent SQL DB.
|
260
|
+
The callable specified here is responsible for converting the
|
261
|
+
serialised-as-text version of the contact unique ID back to the proper type.
|
262
|
+
Common example: ``int``.
|
263
|
+
"""
|
264
|
+
LEGACY_ROOM_ID_TYPE: Callable[[str], Any] = str
|
265
|
+
"""
|
266
|
+
Modify this if the legacy network uses unique room IDs that are not strings.
|
267
|
+
|
268
|
+
This is required because we store those IDs as TEXT in the persistent SQL DB.
|
269
|
+
The callable specified here is responsible for converting the
|
270
|
+
serialised-as-text version of the room unique ID back to the proper type.
|
271
|
+
Common example: ``int``.
|
272
|
+
"""
|
205
273
|
|
206
274
|
def __init__(self):
|
275
|
+
self.log = log
|
207
276
|
self.datetime_started = datetime.now()
|
208
277
|
self.xmpp = self # ugly hack to work with the BaseSender mixin :/
|
209
278
|
self.default_ns = "jabber:component:accept"
|
@@ -222,7 +291,6 @@ class BaseGateway(
|
|
222
291
|
},
|
223
292
|
"xep_0100": {
|
224
293
|
"component_name": self.COMPONENT_NAME,
|
225
|
-
"user_store": user_store,
|
226
294
|
"type": self.COMPONENT_TYPE,
|
227
295
|
},
|
228
296
|
"xep_0184": {
|
@@ -241,11 +309,15 @@ class BaseGateway(
|
|
241
309
|
self.use_origin_id = False
|
242
310
|
|
243
311
|
self.jid_validator: re.Pattern = re.compile(config.USER_JID_VALIDATOR)
|
244
|
-
self.qr_pending_registrations = dict[str, asyncio.Future[
|
312
|
+
self.qr_pending_registrations = dict[str, asyncio.Future[Optional[dict]]]()
|
245
313
|
|
246
314
|
self.session_cls: BaseSession = BaseSession.get_unique_subclass()
|
247
315
|
self.session_cls.xmpp = self
|
248
316
|
|
317
|
+
from ...group.room import LegacyMUC
|
318
|
+
|
319
|
+
LegacyMUC.get_unique_subclass().xmpp = self
|
320
|
+
|
249
321
|
self.get_session_from_stanza: Callable[
|
250
322
|
[Union[Message, Presence, Iq]], BaseSession
|
251
323
|
] = self.session_cls.from_stanza # type: ignore
|
@@ -255,7 +327,7 @@ class BaseGateway(
|
|
255
327
|
|
256
328
|
self.register_plugins()
|
257
329
|
self.__register_slixmpp_events()
|
258
|
-
self.roster.set_backend(RosterBackend)
|
330
|
+
self.roster.set_backend(RosterBackend(self))
|
259
331
|
|
260
332
|
self.register_plugin("pubsub", {"component_name": self.COMPONENT_NAME})
|
261
333
|
self.pubsub: PubSubComponent = self["pubsub"]
|
@@ -265,6 +337,8 @@ class BaseGateway(
|
|
265
337
|
# with this we receive user avatar updates
|
266
338
|
self.plugin["xep_0030"].add_feature("urn:xmpp:avatar:metadata+notify")
|
267
339
|
|
340
|
+
self.plugin["xep_0030"].add_feature("urn:xmpp:chat-markers:0")
|
341
|
+
|
268
342
|
if self.GROUPS:
|
269
343
|
self.plugin["xep_0030"].add_feature("http://jabber.org/protocol/muc")
|
270
344
|
self.plugin["xep_0030"].add_feature("urn:xmpp:mam:2")
|
@@ -294,7 +368,16 @@ class BaseGateway(
|
|
294
368
|
|
295
369
|
self.__register_commands()
|
296
370
|
|
297
|
-
|
371
|
+
self.__mam_cleanup_task = self.loop.create_task(self.__mam_cleanup())
|
372
|
+
|
373
|
+
MessageMixin.__init__(self) # ComponentXMPP does not call super().__init__()
|
374
|
+
|
375
|
+
async def __mam_cleanup(self):
|
376
|
+
if not config.MAM_MAX_DAYS:
|
377
|
+
return
|
378
|
+
while True:
|
379
|
+
await asyncio.sleep(3600 * 6)
|
380
|
+
self.store.mam.nuke_older_than(config.MAM_MAX_DAYS)
|
298
381
|
|
299
382
|
def __register_commands(self):
|
300
383
|
for cls in Command.subclasses:
|
@@ -303,7 +386,7 @@ class BaseGateway(
|
|
303
386
|
continue
|
304
387
|
if cls is Exec:
|
305
388
|
if config.DEV_MODE:
|
306
|
-
log.warning("/!\ DEV MODE ENABLED /!\\")
|
389
|
+
log.warning(r"/!\ DEV MODE ENABLED /!\\")
|
307
390
|
else:
|
308
391
|
continue
|
309
392
|
c = cls(self)
|
@@ -322,7 +405,7 @@ class BaseGateway(
|
|
322
405
|
log.debug("Context in the exception handler: %s", context)
|
323
406
|
exc = context.get("exception")
|
324
407
|
if exc is None:
|
325
|
-
log.
|
408
|
+
log.debug("No exception in this context: %s", context)
|
326
409
|
elif isinstance(exc, SystemExit):
|
327
410
|
log.debug("SystemExit called in an asyncio task")
|
328
411
|
else:
|
@@ -356,7 +439,7 @@ class BaseGateway(
|
|
356
439
|
mfrom = msg.get_from()
|
357
440
|
resource = mfrom.resource
|
358
441
|
try:
|
359
|
-
muc.
|
442
|
+
muc.remove_user_resource(resource)
|
360
443
|
except KeyError:
|
361
444
|
# this actually happens quite frequently on for both beagle and monal
|
362
445
|
# (not sure why?), but is of no consequence
|
@@ -374,11 +457,15 @@ class BaseGateway(
|
|
374
457
|
await disco.del_feature(feature="urn:xmpp:http:upload:0", jid=self.boundjid)
|
375
458
|
await self.plugin["xep_0115"].update_caps(jid=self.boundjid)
|
376
459
|
|
377
|
-
|
378
|
-
|
379
|
-
|
460
|
+
if self.COMPONENT_AVATAR:
|
461
|
+
cached_avatar = await avatar_cache.convert_or_get(
|
462
|
+
self.COMPONENT_AVATAR, None
|
463
|
+
)
|
464
|
+
self.avatar_pk = cached_avatar.pk
|
465
|
+
else:
|
466
|
+
cached_avatar = None
|
380
467
|
|
381
|
-
for user in
|
468
|
+
for user in self.store.users.get_all():
|
382
469
|
# TODO: before this, we should check if the user has removed us from their roster
|
383
470
|
# while we were offline and trigger unregister from there. Presence probe does not seem
|
384
471
|
# to work in this case, there must be another way. privileged entity could be used
|
@@ -396,10 +483,14 @@ class BaseGateway(
|
|
396
483
|
)
|
397
484
|
continue
|
398
485
|
self.send_presence(
|
399
|
-
pto=user.
|
486
|
+
pto=user.jid.bare, ptype="probe"
|
400
487
|
) # ensure we get all resources for user
|
401
488
|
session = self.session_cls.from_user(user)
|
402
489
|
session.create_task(self.__login_wrap(session))
|
490
|
+
if cached_avatar is not None:
|
491
|
+
await self.pubsub.broadcast_avatar(
|
492
|
+
self.boundjid.bare, session.user_jid, cached_avatar
|
493
|
+
)
|
403
494
|
|
404
495
|
log.info("Slidge has successfully started")
|
405
496
|
|
@@ -455,12 +546,13 @@ class BaseGateway(
|
|
455
546
|
exc_info=e,
|
456
547
|
)
|
457
548
|
|
549
|
+
@timeit
|
458
550
|
async def __login_wrap(self, session: "BaseSession"):
|
459
551
|
session.send_gateway_status("Logging in…", show="dnd")
|
460
552
|
try:
|
461
553
|
status = await session.login()
|
462
554
|
except Exception as e:
|
463
|
-
log.warning("Login problem for %s", session.
|
555
|
+
log.warning("Login problem for %s", session.user_jid, exc_info=e)
|
464
556
|
log.exception(e)
|
465
557
|
session.send_gateway_status(f"Could not login: {e}", show="busy")
|
466
558
|
session.send_gateway_message(
|
@@ -469,10 +561,10 @@ class BaseGateway(
|
|
469
561
|
)
|
470
562
|
return
|
471
563
|
|
472
|
-
log.info("Login success for %s", session.
|
564
|
+
log.info("Login success for %s", session.user_jid)
|
473
565
|
session.logged = True
|
474
566
|
session.send_gateway_status("Syncing contacts…", show="dnd")
|
475
|
-
await session.contacts.
|
567
|
+
await session.contacts._fill()
|
476
568
|
if not (r := session.contacts.ready).done():
|
477
569
|
r.set_result(True)
|
478
570
|
if self.GROUPS:
|
@@ -483,19 +575,18 @@ class BaseGateway(
|
|
483
575
|
for c in session.contacts:
|
484
576
|
# we need to receive presences directed at the contacts, in
|
485
577
|
# order to send pubsub events for their +notify features
|
486
|
-
self.send_presence(pfrom=c.jid, pto=session.
|
578
|
+
self.send_presence(pfrom=c.jid, pto=session.user_jid.bare, ptype="probe")
|
487
579
|
if status is None:
|
488
580
|
session.send_gateway_status("Logged in", show="chat")
|
489
581
|
else:
|
490
582
|
session.send_gateway_status(status, show="chat")
|
491
|
-
|
492
|
-
|
493
|
-
session.create_task(self.__fetch_user_avatar(session))
|
583
|
+
if session.user.preferences.get("sync_avatar", False):
|
584
|
+
session.create_task(self.fetch_user_avatar(session))
|
494
585
|
|
495
|
-
async def
|
586
|
+
async def fetch_user_avatar(self, session: BaseSession):
|
496
587
|
try:
|
497
588
|
iq = await self.xmpp.plugin["xep_0060"].get_items(
|
498
|
-
session.
|
589
|
+
session.user_jid.bare,
|
499
590
|
self.xmpp.plugin["xep_0084"].stanza.MetaData.namespace,
|
500
591
|
ifrom=self.boundjid.bare,
|
501
592
|
)
|
@@ -577,7 +668,7 @@ class BaseGateway(
|
|
577
668
|
)
|
578
669
|
|
579
670
|
ifrom = iq.get_from()
|
580
|
-
user =
|
671
|
+
user = self.store.users.get(ifrom)
|
581
672
|
if user is None:
|
582
673
|
raise XMPPError("registration-required")
|
583
674
|
|
@@ -629,7 +720,7 @@ class BaseGateway(
|
|
629
720
|
async def make_registration_form(self, _jid, _node, _ifrom, iq: Iq):
|
630
721
|
self.raise_if_not_allowed_jid(iq.get_from())
|
631
722
|
reg = iq["register"]
|
632
|
-
user =
|
723
|
+
user = self.store.users.get_by_stanza(iq)
|
633
724
|
log.debug("User found: %s", user)
|
634
725
|
|
635
726
|
form = reg["form"]
|
@@ -675,18 +766,20 @@ class BaseGateway(
|
|
675
766
|
reply.set_payload(reg)
|
676
767
|
return reply
|
677
768
|
|
678
|
-
async def user_prevalidate(
|
769
|
+
async def user_prevalidate(
|
770
|
+
self, ifrom: JID, form_dict: dict[str, Optional[str]]
|
771
|
+
) -> Optional[Mapping]:
|
679
772
|
# Pre validate a registration form using the content of self.REGISTRATION_FIELDS
|
680
773
|
# before passing it to the plugin custom validation logic.
|
681
774
|
for field in self.REGISTRATION_FIELDS:
|
682
775
|
if field.required and not form_dict.get(field.var):
|
683
776
|
raise ValueError(f"Missing field: '{field.label}'")
|
684
777
|
|
685
|
-
await self.validate(ifrom, form_dict)
|
778
|
+
return await self.validate(ifrom, form_dict)
|
686
779
|
|
687
780
|
async def validate(
|
688
781
|
self, user_jid: JID, registration_form: dict[str, Optional[str]]
|
689
|
-
):
|
782
|
+
) -> Optional[Mapping]:
|
690
783
|
"""
|
691
784
|
Validate a user's initial registration form.
|
692
785
|
|
@@ -706,11 +799,19 @@ class BaseGateway(
|
|
706
799
|
|
707
800
|
:param user_jid: JID of the user that has just registered
|
708
801
|
:param registration_form: A dict where keys are the :attr:`.FormField.var` attributes
|
709
|
-
|
802
|
+
of the :attr:`.BaseGateway.REGISTRATION_FIELDS` iterable.
|
803
|
+
This dict can be modified and will be accessible as the ``legacy_module_data``
|
804
|
+
of the
|
805
|
+
|
806
|
+
:return : A dict that will be stored as the persistent "legacy_module_data"
|
807
|
+
for this user. If you don't return anything here, the whole registration_form
|
808
|
+
content will be stored.
|
710
809
|
"""
|
711
810
|
raise NotImplementedError
|
712
811
|
|
713
|
-
async def validate_two_factor_code(
|
812
|
+
async def validate_two_factor_code(
|
813
|
+
self, user: GatewayUser, code: str
|
814
|
+
) -> Optional[dict]:
|
714
815
|
"""
|
715
816
|
Called when the user enters their 2FA code.
|
716
817
|
|
@@ -725,6 +826,9 @@ class BaseGateway(
|
|
725
826
|
:attr:`.registration_form` attributes to get what you need.
|
726
827
|
:param code: The code they entered, either via "chatbot" message or
|
727
828
|
adhoc command
|
829
|
+
|
830
|
+
:return : A dict which keys and values will be added to the persistent "legacy_module_data"
|
831
|
+
for this user.
|
728
832
|
"""
|
729
833
|
raise NotImplementedError
|
730
834
|
|
@@ -744,7 +848,10 @@ class BaseGateway(
|
|
744
848
|
raise NotImplementedError
|
745
849
|
|
746
850
|
async def confirm_qr(
|
747
|
-
self,
|
851
|
+
self,
|
852
|
+
user_bare_jid: str,
|
853
|
+
exception: Optional[Exception] = None,
|
854
|
+
legacy_data: Optional[dict] = None,
|
748
855
|
):
|
749
856
|
"""
|
750
857
|
This method is meant to be called to finalize QR code-based registration
|
@@ -757,10 +864,12 @@ class BaseGateway(
|
|
757
864
|
:class:`GatewayUser` instance
|
758
865
|
:param exception: Optionally, an XMPPError to be raised to **not** confirm
|
759
866
|
QR code flashing.
|
867
|
+
:param legacy_data: dict which keys and values will be added to the persistent
|
868
|
+
"legacy_module_data" for this user.
|
760
869
|
"""
|
761
870
|
fut = self.qr_pending_registrations[user_bare_jid]
|
762
871
|
if exception is None:
|
763
|
-
fut.set_result(
|
872
|
+
fut.set_result(legacy_data)
|
764
873
|
else:
|
765
874
|
fut.set_exception(exception)
|
766
875
|
|
@@ -771,14 +880,17 @@ class BaseGateway(
|
|
771
880
|
async def unregister(self, user: GatewayUser):
|
772
881
|
"""
|
773
882
|
Optionally override this if you need to clean additional
|
774
|
-
stuff after a user has been removed from the
|
883
|
+
stuff after a user has been removed from the persistent user store.
|
775
884
|
|
776
885
|
By default, this just calls :meth:`BaseSession.logout`.
|
777
886
|
|
778
887
|
:param user:
|
779
888
|
"""
|
780
889
|
session = self.get_session_from_user(user)
|
781
|
-
|
890
|
+
try:
|
891
|
+
await session.logout()
|
892
|
+
except NotImplementedError:
|
893
|
+
pass
|
782
894
|
|
783
895
|
async def input(
|
784
896
|
self, jid: JID, text=None, mtype: MessageTypes = "chat", **msg_kwargs
|
@@ -825,7 +937,7 @@ class BaseGateway(
|
|
825
937
|
# """
|
826
938
|
log.debug("Shutting down")
|
827
939
|
tasks = []
|
828
|
-
for user in
|
940
|
+
for user in self.store.users.get_all():
|
829
941
|
tasks.append(self.session_cls.from_jid(user.jid).shutdown())
|
830
942
|
self.send_presence(ptype="unavailable", pto=user.jid)
|
831
943
|
return tasks
|
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/disco.py
CHANGED
@@ -5,8 +5,6 @@ from slixmpp.exceptions import XMPPError
|
|
5
5
|
from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
|
6
6
|
from slixmpp.types import OptJid
|
7
7
|
|
8
|
-
from ...util.db import user_store
|
9
|
-
|
10
8
|
if TYPE_CHECKING:
|
11
9
|
from .base import BaseGateway
|
12
10
|
|
@@ -38,7 +36,7 @@ class Disco:
|
|
38
36
|
if ifrom is None:
|
39
37
|
raise XMPPError("subscription-required")
|
40
38
|
|
41
|
-
user =
|
39
|
+
user = self.xmpp.store.users.get(ifrom)
|
42
40
|
if user is None:
|
43
41
|
raise XMPPError("registration-required")
|
44
42
|
session = self.xmpp.get_session_from_user(user)
|
@@ -63,7 +61,7 @@ class Disco:
|
|
63
61
|
if jid != self.xmpp.boundjid.bare:
|
64
62
|
return DiscoItems()
|
65
63
|
|
66
|
-
user =
|
64
|
+
user = self.xmpp.store.users.get(ifrom)
|
67
65
|
if user is None:
|
68
66
|
raise XMPPError("registration-required")
|
69
67
|
|
slidge/core/gateway/mam.py
CHANGED
@@ -3,8 +3,6 @@ from typing import TYPE_CHECKING
|
|
3
3
|
from slixmpp import CoroutineCallback, Iq, StanzaPath
|
4
4
|
from slixmpp.exceptions import XMPPError
|
5
5
|
|
6
|
-
from ...util.db import user_store
|
7
|
-
|
8
6
|
if TYPE_CHECKING:
|
9
7
|
from .base import BaseGateway
|
10
8
|
|
@@ -46,8 +44,7 @@ class Mam:
|
|
46
44
|
text="No MAM on the component itself, use a JID with a resource"
|
47
45
|
)
|
48
46
|
|
49
|
-
|
50
|
-
user = user_store.get_by_jid(ifrom)
|
47
|
+
user = self.xmpp.store.users.get(iq.get_from())
|
51
48
|
if user is None:
|
52
49
|
raise XMPPError("registration-required")
|
53
50
|
|
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())
|
slidge/core/gateway/ping.py
CHANGED
@@ -4,7 +4,6 @@ from slixmpp import CoroutineCallback, Iq, StanzaPath
|
|
4
4
|
from slixmpp.exceptions import XMPPError
|
5
5
|
|
6
6
|
from ...group import LegacyMUC
|
7
|
-
from ...util.db import user_store
|
8
7
|
|
9
8
|
if TYPE_CHECKING:
|
10
9
|
from .base import BaseGateway
|
@@ -31,7 +30,7 @@ class Ping:
|
|
31
30
|
iq.reply().send()
|
32
31
|
|
33
32
|
ifrom = iq.get_from()
|
34
|
-
user =
|
33
|
+
user = self.xmpp.store.users.get(ifrom)
|
35
34
|
if user is None:
|
36
35
|
raise XMPPError("registration-required")
|
37
36
|
|
@@ -60,7 +59,7 @@ class Ping:
|
|
60
59
|
|
61
60
|
@staticmethod
|
62
61
|
def __handle_muc_ping(muc: LegacyMUC, iq: Iq):
|
63
|
-
if iq.get_from().resource in muc.
|
62
|
+
if iq.get_from().resource in muc.get_user_resources():
|
64
63
|
iq.reply().send()
|
65
64
|
else:
|
66
65
|
raise XMPPError("not-acceptable", etype="cancel", by=muc.jid)
|
slidge/core/gateway/presence.py
CHANGED
@@ -47,7 +47,7 @@ class PresenceHandlerMixin:
|
|
47
47
|
contact = await self.__get_contact(pres)
|
48
48
|
except _IsDirectedAtComponent as e:
|
49
49
|
e.session.send_gateway_message("Bye bye!")
|
50
|
-
await e.session.kill_by_jid(e.session.
|
50
|
+
await e.session.kill_by_jid(e.session.user_jid)
|
51
51
|
return
|
52
52
|
|
53
53
|
contact.is_friend = False
|
@@ -1,9 +1,12 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import logging
|
2
4
|
from typing import TYPE_CHECKING, Optional
|
3
5
|
|
4
6
|
from slixmpp import JID, Iq
|
7
|
+
from slixmpp.exceptions import XMPPError
|
5
8
|
|
6
|
-
from ...
|
9
|
+
from ...db import GatewayUser
|
7
10
|
|
8
11
|
if TYPE_CHECKING:
|
9
12
|
from .base import BaseGateway
|
@@ -12,42 +15,50 @@ if TYPE_CHECKING:
|
|
12
15
|
class Registration:
|
13
16
|
def __init__(self, xmpp: "BaseGateway"):
|
14
17
|
self.xmpp = xmpp
|
15
|
-
xmpp["xep_0077"].api.register(
|
16
|
-
user_store.get,
|
17
|
-
"user_get",
|
18
|
-
)
|
19
|
-
xmpp["xep_0077"].api.register(
|
20
|
-
user_store.remove,
|
21
|
-
"user_remove",
|
22
|
-
)
|
23
18
|
xmpp["xep_0077"].api.register(
|
24
19
|
self.xmpp.make_registration_form, "make_registration_form"
|
25
20
|
)
|
21
|
+
xmpp["xep_0077"].api.register(self._user_get, "user_get")
|
26
22
|
xmpp["xep_0077"].api.register(self._user_validate, "user_validate")
|
27
23
|
xmpp["xep_0077"].api.register(self._user_modify, "user_modify")
|
24
|
+
# kept for slixmpp internal API compat
|
25
|
+
# TODO: either fully use slixmpp internal API or rewrite registration without it at all
|
26
|
+
xmpp["xep_0077"].api.register(lambda *a: None, "user_remove")
|
27
|
+
|
28
|
+
def get_user(self, jid: JID) -> GatewayUser | None:
|
29
|
+
return self.xmpp.store.users.get(jid)
|
30
|
+
|
31
|
+
async def _user_get(
|
32
|
+
self, _gateway_jid, _node, ifrom: JID, iq: Iq
|
33
|
+
) -> GatewayUser | None:
|
34
|
+
if ifrom is None:
|
35
|
+
ifrom = iq.get_from()
|
36
|
+
return self.get_user(ifrom)
|
28
37
|
|
29
38
|
async def _user_validate(self, _gateway_jid, _node, ifrom: JID, iq: Iq):
|
30
|
-
"""
|
31
|
-
SliXMPP internal API stuff
|
32
|
-
"""
|
33
39
|
xmpp = self.xmpp
|
34
40
|
log.debug("User validate: %s", ifrom.bare)
|
35
41
|
form_dict = {f.var: iq.get(f.var) for f in xmpp.REGISTRATION_FIELDS}
|
36
42
|
xmpp.raise_if_not_allowed_jid(ifrom)
|
37
|
-
await xmpp.user_prevalidate(ifrom, form_dict)
|
38
|
-
|
39
|
-
|
43
|
+
legacy_module_data = await xmpp.user_prevalidate(ifrom, form_dict)
|
44
|
+
if legacy_module_data is None:
|
45
|
+
legacy_module_data = form_dict
|
46
|
+
user = self.xmpp.store.users.new(
|
47
|
+
jid=ifrom,
|
48
|
+
legacy_module_data=legacy_module_data, # type:ignore
|
49
|
+
)
|
50
|
+
log.info("New user: %s", user)
|
40
51
|
|
41
52
|
async def _user_modify(
|
42
53
|
self, _gateway_jid, _node, ifrom: JID, form_dict: dict[str, Optional[str]]
|
43
54
|
):
|
44
|
-
"""
|
45
|
-
SliXMPP internal API stuff
|
46
|
-
"""
|
47
|
-
user = user_store.get_by_jid(ifrom)
|
48
|
-
log.debug("Modify user: %s", user)
|
49
55
|
await self.xmpp.user_prevalidate(ifrom, form_dict)
|
50
|
-
|
56
|
+
log.debug("Modify user: %s", ifrom)
|
57
|
+
user = self.xmpp.store.users.get(ifrom)
|
58
|
+
if user is None:
|
59
|
+
raise XMPPError("internal-server-error", "User not found")
|
60
|
+
user.legacy_module_data.update(form_dict)
|
61
|
+
self.xmpp.store.users.update(user)
|
51
62
|
|
52
63
|
|
53
64
|
log = logging.getLogger(__name__)
|
slidge/core/gateway/search.py
CHANGED
@@ -3,8 +3,6 @@ from typing import TYPE_CHECKING
|
|
3
3
|
from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
|
4
4
|
from slixmpp.exceptions import XMPPError
|
5
5
|
|
6
|
-
from ...util.db import user_store
|
7
|
-
|
8
6
|
if TYPE_CHECKING:
|
9
7
|
from .base import BaseGateway
|
10
8
|
|
@@ -29,7 +27,7 @@ class Search:
|
|
29
27
|
"""
|
30
28
|
Prepare the search form using :attr:`.BaseSession.SEARCH_FIELDS`
|
31
29
|
"""
|
32
|
-
user =
|
30
|
+
user = self.xmpp.store.users.get(ifrom)
|
33
31
|
if user is None:
|
34
32
|
raise XMPPError(text="Search is only allowed for registered users")
|
35
33
|
|
@@ -47,7 +45,7 @@ class Search:
|
|
47
45
|
"""
|
48
46
|
Handles a search request
|
49
47
|
"""
|
50
|
-
user =
|
48
|
+
user = self.xmpp.store.users.get(ifrom)
|
51
49
|
if user is None:
|
52
50
|
raise XMPPError(text="Search is only allowed for registered users")
|
53
51
|
|
@@ -70,7 +68,7 @@ class Search:
|
|
70
68
|
if iq.get_to() != self.xmpp.boundjid.bare:
|
71
69
|
raise XMPPError("bad-request", "This can only be used on the component JID")
|
72
70
|
|
73
|
-
user =
|
71
|
+
user = self.xmpp.store.users.get(iq.get_from())
|
74
72
|
if user is None:
|
75
73
|
raise XMPPError("not-authorized", "Register to the gateway first")
|
76
74
|
|