slidge 0.1.3__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/__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
|
|