slidge 0.1.2__py3-none-any.whl → 0.2.0__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 -197
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +40 -17
- slidge/command/admin.py +24 -12
- slidge/command/base.py +10 -8
- slidge/command/categories.py +13 -3
- slidge/command/chat_command.py +29 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +106 -13
- slidge/contact/contact.py +254 -50
- slidge/contact/roster.py +124 -53
- slidge/core/config.py +19 -13
- slidge/core/dispatcher/__init__.py +3 -0
- slidge/core/{gateway → dispatcher}/caps.py +12 -8
- slidge/core/{gateway → dispatcher}/disco.py +10 -18
- slidge/core/dispatcher/message/__init__.py +10 -0
- slidge/core/dispatcher/message/chat_state.py +40 -0
- slidge/core/dispatcher/message/marker.py +62 -0
- slidge/core/dispatcher/message/message.py +397 -0
- slidge/core/dispatcher/muc/__init__.py +12 -0
- slidge/core/dispatcher/muc/admin.py +98 -0
- slidge/core/{gateway → dispatcher/muc}/mam.py +25 -17
- slidge/core/dispatcher/muc/misc.py +121 -0
- slidge/core/dispatcher/muc/owner.py +96 -0
- slidge/core/{gateway → dispatcher/muc}/ping.py +11 -17
- slidge/core/dispatcher/presence.py +176 -0
- slidge/core/dispatcher/registration.py +85 -0
- slidge/core/{gateway → dispatcher}/search.py +9 -16
- slidge/core/dispatcher/session_dispatcher.py +84 -0
- slidge/core/dispatcher/util.py +174 -0
- slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +35 -19
- slidge/core/{gateway/base.py → gateway.py} +176 -153
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +106 -67
- slidge/core/mixins/avatar.py +94 -25
- slidge/core/mixins/base.py +10 -4
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/lock.py +10 -8
- slidge/core/mixins/message.py +11 -195
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/message_text.py +211 -0
- slidge/core/mixins/presence.py +17 -4
- slidge/core/pubsub.py +114 -288
- slidge/core/session.py +101 -40
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/__init__.py +0 -0
- slidge/db/alembic/env.py +64 -0
- slidge/db/alembic/old_user_store.py +183 -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/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +85 -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/3071e0fa69d4_add_contact_client_type.py +52 -0
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +61 -0
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +139 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +101 -0
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +79 -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 +52 -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 +205 -0
- slidge/db/meta.py +72 -0
- slidge/db/models.py +405 -0
- slidge/db/store.py +1257 -0
- slidge/group/archive.py +58 -14
- slidge/group/bookmarks.py +89 -65
- slidge/group/participant.py +111 -44
- slidge/group/room.py +402 -213
- slidge/main.py +202 -0
- slidge/migration.py +45 -1
- slidge/slixfix/__init__.py +31 -1
- slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
- slidge/slixfix/roster.py +13 -4
- slidge/slixfix/xep_0292/vcard4.py +1 -87
- slidge/util/archive_msg.py +2 -1
- slidge/util/db.py +4 -228
- slidge/util/test.py +91 -4
- slidge/util/types.py +39 -4
- slidge/util/util.py +45 -2
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/METADATA +10 -5
- slidge-0.2.0.dist-info/RECORD +131 -0
- slidge-0.2.0.dist-info/entry_points.txt +3 -0
- slidge/core/cache.py +0 -183
- slidge/core/gateway/__init__.py +0 -3
- slidge/core/gateway/muc_admin.py +0 -35
- slidge/core/gateway/presence.py +0 -95
- slidge/core/gateway/registration.py +0 -53
- slidge/core/gateway/session_dispatcher.py +0 -795
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.2.dist-info/RECORD +0 -96
- slidge-0.1.2.dist-info/entry_points.txt +0 -3
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/LICENSE +0 -0
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/WHEEL +0 -0
@@ -8,7 +8,7 @@ import re
|
|
8
8
|
import tempfile
|
9
9
|
from copy import copy
|
10
10
|
from datetime import datetime
|
11
|
-
from typing import TYPE_CHECKING, Callable,
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Sequence, Union
|
12
12
|
|
13
13
|
import aiohttp
|
14
14
|
import qrcode
|
@@ -18,40 +18,30 @@ from slixmpp.plugins.xep_0060.stanza import OwnerAffiliation
|
|
18
18
|
from slixmpp.types import MessageTypes
|
19
19
|
from slixmpp.xmlstream.xmlstream import NotConnectedError
|
20
20
|
|
21
|
-
from
|
22
|
-
from
|
23
|
-
from
|
24
|
-
from
|
25
|
-
from
|
26
|
-
from
|
27
|
-
from
|
28
|
-
from
|
29
|
-
from
|
30
|
-
from
|
31
|
-
from
|
32
|
-
from
|
33
|
-
from
|
34
|
-
from
|
35
|
-
from
|
36
|
-
from
|
37
|
-
from .
|
38
|
-
from .
|
39
|
-
from .disco import Disco
|
40
|
-
from .mam import Mam
|
41
|
-
from .muc_admin import MucAdmin
|
42
|
-
from .ping import Ping
|
43
|
-
from .presence import PresenceHandlerMixin
|
44
|
-
from .registration import Registration
|
45
|
-
from .search import Search
|
46
|
-
from .session_dispatcher import SessionDispatcher
|
47
|
-
from .vcard_temp import VCardTemp
|
21
|
+
from slidge import command # noqa: F401
|
22
|
+
from slidge.command.adhoc import AdhocProvider
|
23
|
+
from slidge.command.admin import Exec
|
24
|
+
from slidge.command.base import Command, FormField
|
25
|
+
from slidge.command.chat_command import ChatCommandProvider
|
26
|
+
from slidge.command.register import RegistrationType
|
27
|
+
from slidge.core import config
|
28
|
+
from slidge.core.dispatcher.session_dispatcher import SessionDispatcher
|
29
|
+
from slidge.core.mixins import MessageMixin
|
30
|
+
from slidge.core.pubsub import PubSubComponent
|
31
|
+
from slidge.core.session import BaseSession
|
32
|
+
from slidge.db import GatewayUser, SlidgeStore
|
33
|
+
from slidge.db.avatar import avatar_cache
|
34
|
+
from slidge.slixfix.delivery_receipt import DeliveryReceipt
|
35
|
+
from slidge.slixfix.roster import RosterBackend
|
36
|
+
from slidge.util import ABCSubclassableOnceAtMost
|
37
|
+
from slidge.util.types import AvatarType, MessageOrPresenceTypeVar
|
38
|
+
from slidge.util.util import timeit
|
48
39
|
|
49
40
|
if TYPE_CHECKING:
|
50
|
-
|
41
|
+
pass
|
51
42
|
|
52
43
|
|
53
44
|
class BaseGateway(
|
54
|
-
PresenceHandlerMixin,
|
55
45
|
ComponentXMPP,
|
56
46
|
MessageMixin,
|
57
47
|
metaclass=ABCSubclassableOnceAtMost,
|
@@ -109,7 +99,7 @@ class BaseGateway(
|
|
109
99
|
Path, bytes or URL used by the component as an avatar.
|
110
100
|
"""
|
111
101
|
|
112
|
-
REGISTRATION_FIELDS:
|
102
|
+
REGISTRATION_FIELDS: Sequence[FormField] = [
|
113
103
|
FormField(var="username", label="User name", required=True),
|
114
104
|
FormField(var="password", label="Password", required=True, private=True),
|
115
105
|
]
|
@@ -141,6 +131,23 @@ class BaseGateway(
|
|
141
131
|
)
|
142
132
|
REGISTRATION_QR_INSTRUCTIONS = "Flash this code or follow this link"
|
143
133
|
|
134
|
+
PREFERENCES = [
|
135
|
+
FormField(
|
136
|
+
var="sync_presence",
|
137
|
+
label="Propagate your XMPP presence to the legacy network.",
|
138
|
+
value="true",
|
139
|
+
required=True,
|
140
|
+
type="boolean",
|
141
|
+
),
|
142
|
+
FormField(
|
143
|
+
var="sync_avatar",
|
144
|
+
label="Propagate your XMPP avatar to the legacy network.",
|
145
|
+
value="true",
|
146
|
+
required=True,
|
147
|
+
type="boolean",
|
148
|
+
),
|
149
|
+
]
|
150
|
+
|
144
151
|
ROSTER_GROUP: str = "slidge"
|
145
152
|
"""
|
146
153
|
Name of the group assigned to a :class:`.LegacyContact` automagically
|
@@ -202,8 +209,52 @@ class BaseGateway(
|
|
202
209
|
mtype: MessageTypes = "chat"
|
203
210
|
is_group = False
|
204
211
|
_can_send_carbon = False
|
212
|
+
store: SlidgeStore
|
213
|
+
avatar_pk: int
|
214
|
+
|
215
|
+
AVATAR_ID_TYPE: Callable[[str], Any] = str
|
216
|
+
"""
|
217
|
+
Modify this if the legacy network uses unique avatar IDs that are not strings.
|
218
|
+
|
219
|
+
This is required because we store those IDs as TEXT in the persistent SQL DB.
|
220
|
+
The callable specified here will receive is responsible for converting the
|
221
|
+
serialised-as-text version of the avatar unique ID back to the proper type.
|
222
|
+
Common example: ``int``.
|
223
|
+
"""
|
224
|
+
# FIXME: do we really need this since we have session.xmpp_to_legacy_msg_id?
|
225
|
+
# (maybe we do)
|
226
|
+
LEGACY_MSG_ID_TYPE: Callable[[str], Any] = str
|
227
|
+
"""
|
228
|
+
Modify this if the legacy network uses unique message IDs that are not strings.
|
229
|
+
|
230
|
+
This is required because we store those IDs as TEXT in the persistent SQL DB.
|
231
|
+
The callable specified here will receive is responsible for converting the
|
232
|
+
serialised-as-text version of the message unique ID back to the proper type.
|
233
|
+
Common example: ``int``.
|
234
|
+
"""
|
235
|
+
LEGACY_CONTACT_ID_TYPE: Callable[[str], Any] = str
|
236
|
+
"""
|
237
|
+
Modify this if the legacy network uses unique contact 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 is responsible for converting the
|
241
|
+
serialised-as-text version of the contact unique ID back to the proper type.
|
242
|
+
Common example: ``int``.
|
243
|
+
"""
|
244
|
+
LEGACY_ROOM_ID_TYPE: Callable[[str], Any] = str
|
245
|
+
"""
|
246
|
+
Modify this if the legacy network uses unique room IDs that are not strings.
|
247
|
+
|
248
|
+
This is required because we store those IDs as TEXT in the persistent SQL DB.
|
249
|
+
The callable specified here is responsible for converting the
|
250
|
+
serialised-as-text version of the room unique ID back to the proper type.
|
251
|
+
Common example: ``int``.
|
252
|
+
"""
|
253
|
+
|
254
|
+
http: aiohttp.ClientSession
|
205
255
|
|
206
256
|
def __init__(self):
|
257
|
+
self.log = log
|
207
258
|
self.datetime_started = datetime.now()
|
208
259
|
self.xmpp = self # ugly hack to work with the BaseSender mixin :/
|
209
260
|
self.default_ns = "jabber:component:accept"
|
@@ -222,7 +273,6 @@ class BaseGateway(
|
|
222
273
|
},
|
223
274
|
"xep_0100": {
|
224
275
|
"component_name": self.COMPONENT_NAME,
|
225
|
-
"user_store": user_store,
|
226
276
|
"type": self.COMPONENT_TYPE,
|
227
277
|
},
|
228
278
|
"xep_0184": {
|
@@ -236,16 +286,20 @@ class BaseGateway(
|
|
236
286
|
fix_error_ns=True,
|
237
287
|
)
|
238
288
|
self.loop.set_exception_handler(self.__exception_handler)
|
239
|
-
self.
|
289
|
+
self.loop.create_task(self.__set_http())
|
240
290
|
self.has_crashed: bool = False
|
241
291
|
self.use_origin_id = False
|
242
292
|
|
243
293
|
self.jid_validator: re.Pattern = re.compile(config.USER_JID_VALIDATOR)
|
244
|
-
self.qr_pending_registrations = dict[str, asyncio.Future[
|
294
|
+
self.qr_pending_registrations = dict[str, asyncio.Future[Optional[dict]]]()
|
245
295
|
|
246
296
|
self.session_cls: BaseSession = BaseSession.get_unique_subclass()
|
247
297
|
self.session_cls.xmpp = self
|
248
298
|
|
299
|
+
from ..group.room import LegacyMUC
|
300
|
+
|
301
|
+
LegacyMUC.get_self_or_unique_subclass().xmpp = self
|
302
|
+
|
249
303
|
self.get_session_from_stanza: Callable[
|
250
304
|
[Union[Message, Presence, Iq]], BaseSession
|
251
305
|
] = self.session_cls.from_stanza # type: ignore
|
@@ -255,16 +309,18 @@ class BaseGateway(
|
|
255
309
|
|
256
310
|
self.register_plugins()
|
257
311
|
self.__register_slixmpp_events()
|
258
|
-
self.
|
312
|
+
self.__register_slixmpp_api()
|
313
|
+
self.roster.set_backend(RosterBackend(self))
|
259
314
|
|
260
315
|
self.register_plugin("pubsub", {"component_name": self.COMPONENT_NAME})
|
261
316
|
self.pubsub: PubSubComponent = self["pubsub"]
|
262
|
-
self.vcard: VCard4Provider = self["xep_0292_provider"]
|
263
317
|
self.delivery_receipt: DeliveryReceipt = DeliveryReceipt(self)
|
264
318
|
|
265
319
|
# with this we receive user avatar updates
|
266
320
|
self.plugin["xep_0030"].add_feature("urn:xmpp:avatar:metadata+notify")
|
267
321
|
|
322
|
+
self.plugin["xep_0030"].add_feature("urn:xmpp:chat-markers:0")
|
323
|
+
|
268
324
|
if self.GROUPS:
|
269
325
|
self.plugin["xep_0030"].add_feature("http://jabber.org/protocol/muc")
|
270
326
|
self.plugin["xep_0030"].add_feature("urn:xmpp:mam:2")
|
@@ -281,20 +337,18 @@ class BaseGateway(
|
|
281
337
|
# why does mypy need these type annotations? no idea
|
282
338
|
self.__adhoc_handler: AdhocProvider = AdhocProvider(self)
|
283
339
|
self.__chat_commands_handler: ChatCommandProvider = ChatCommandProvider(self)
|
284
|
-
|
285
|
-
|
286
|
-
self.__ping_handler = Ping(self)
|
287
|
-
self.__mam_handler = Mam(self)
|
288
|
-
self.__search_handler = Search(self)
|
289
|
-
self.__caps_handler = Caps(self)
|
290
|
-
self.__vcard_temp_handler = VCardTemp(self)
|
291
|
-
self.__muc_admin_handler = MucAdmin(self)
|
292
|
-
self.__registration = Registration(self)
|
340
|
+
|
293
341
|
self.__dispatcher = SessionDispatcher(self)
|
294
342
|
|
295
343
|
self.__register_commands()
|
296
344
|
|
297
|
-
|
345
|
+
MessageMixin.__init__(self) # ComponentXMPP does not call super().__init__()
|
346
|
+
|
347
|
+
async def __set_http(self):
|
348
|
+
self.http = aiohttp.ClientSession()
|
349
|
+
if getattr(self, "_test_mode", False):
|
350
|
+
return
|
351
|
+
avatar_cache.http = self.http
|
298
352
|
|
299
353
|
def __register_commands(self):
|
300
354
|
for cls in Command.subclasses:
|
@@ -303,7 +357,7 @@ class BaseGateway(
|
|
303
357
|
continue
|
304
358
|
if cls is Exec:
|
305
359
|
if config.DEV_MODE:
|
306
|
-
log.warning("/!\ DEV MODE ENABLED /!\\")
|
360
|
+
log.warning(r"/!\ DEV MODE ENABLED /!\\")
|
307
361
|
else:
|
308
362
|
continue
|
309
363
|
c = cls(self)
|
@@ -322,7 +376,7 @@ class BaseGateway(
|
|
322
376
|
log.debug("Context in the exception handler: %s", context)
|
323
377
|
exc = context.get("exception")
|
324
378
|
if exc is None:
|
325
|
-
log.
|
379
|
+
log.debug("No exception in this context: %s", context)
|
326
380
|
elif isinstance(exc, SystemExit):
|
327
381
|
log.debug("SystemExit called in an asyncio task")
|
328
382
|
else:
|
@@ -332,40 +386,27 @@ class BaseGateway(
|
|
332
386
|
loop.stop()
|
333
387
|
|
334
388
|
def __register_slixmpp_events(self):
|
389
|
+
self.del_event_handler("presence_subscribe", self._handle_subscribe)
|
390
|
+
self.del_event_handler("presence_unsubscribe", self._handle_unsubscribe)
|
391
|
+
self.del_event_handler("presence_subscribed", self._handle_subscribed)
|
392
|
+
self.del_event_handler("presence_unsubscribed", self._handle_unsubscribed)
|
393
|
+
self.del_event_handler(
|
394
|
+
"roster_subscription_request", self._handle_new_subscription
|
395
|
+
)
|
396
|
+
self.del_event_handler("presence_probe", self._handle_probe)
|
335
397
|
self.add_event_handler("session_start", self.__on_session_start)
|
336
398
|
self.add_event_handler("disconnected", self.connect)
|
337
|
-
|
338
|
-
|
339
|
-
self.
|
399
|
+
|
400
|
+
def __register_slixmpp_api(self) -> None:
|
401
|
+
self.plugin["xep_0231"].api.register(self.store.bob.get_bob, "get_bob")
|
402
|
+
self.plugin["xep_0231"].api.register(self.store.bob.set_bob, "set_bob")
|
403
|
+
self.plugin["xep_0231"].api.register(self.store.bob.del_bob, "del_bob")
|
340
404
|
|
341
405
|
@property # type: ignore
|
342
406
|
def jid(self):
|
343
407
|
# Override to avoid slixmpp deprecation warnings.
|
344
408
|
return self.boundjid
|
345
409
|
|
346
|
-
async def __on_group_chat_error(self, msg: Message):
|
347
|
-
condition = msg["error"].get_condition()
|
348
|
-
if condition not in KICKABLE_ERRORS:
|
349
|
-
return
|
350
|
-
|
351
|
-
try:
|
352
|
-
muc = await self.get_muc_from_stanza(msg)
|
353
|
-
except XMPPError as e:
|
354
|
-
log.debug("Not removing resource", exc_info=e)
|
355
|
-
return
|
356
|
-
mfrom = msg.get_from()
|
357
|
-
resource = mfrom.resource
|
358
|
-
try:
|
359
|
-
muc.user_resources.remove(resource)
|
360
|
-
except KeyError:
|
361
|
-
# this actually happens quite frequently on for both beagle and monal
|
362
|
-
# (not sure why?), but is of no consequence
|
363
|
-
log.debug("%s was not in the resources of %s", resource, muc)
|
364
|
-
else:
|
365
|
-
log.info(
|
366
|
-
"Removed %s from the resources of %s because of error", resource, muc
|
367
|
-
)
|
368
|
-
|
369
410
|
async def __on_session_start(self, event):
|
370
411
|
log.debug("Gateway session start: %s", event)
|
371
412
|
|
@@ -374,11 +415,13 @@ class BaseGateway(
|
|
374
415
|
await disco.del_feature(feature="urn:xmpp:http:upload:0", jid=self.boundjid)
|
375
416
|
await self.plugin["xep_0115"].update_caps(jid=self.boundjid)
|
376
417
|
|
377
|
-
|
378
|
-
|
379
|
-
|
418
|
+
if self.COMPONENT_AVATAR is not None:
|
419
|
+
cached_avatar = await avatar_cache.convert_or_get(self.COMPONENT_AVATAR)
|
420
|
+
self.avatar_pk = cached_avatar.pk
|
421
|
+
else:
|
422
|
+
cached_avatar = None
|
380
423
|
|
381
|
-
for user in
|
424
|
+
for user in self.store.users.get_all():
|
382
425
|
# TODO: before this, we should check if the user has removed us from their roster
|
383
426
|
# while we were offline and trigger unregister from there. Presence probe does not seem
|
384
427
|
# to work in this case, there must be another way. privileged entity could be used
|
@@ -396,10 +439,14 @@ class BaseGateway(
|
|
396
439
|
)
|
397
440
|
continue
|
398
441
|
self.send_presence(
|
399
|
-
pto=user.
|
442
|
+
pto=user.jid.bare, ptype="probe"
|
400
443
|
) # ensure we get all resources for user
|
401
444
|
session = self.session_cls.from_user(user)
|
402
|
-
session.create_task(self.
|
445
|
+
session.create_task(self.login_wrap(session))
|
446
|
+
if cached_avatar is not None:
|
447
|
+
await self.pubsub.broadcast_avatar(
|
448
|
+
self.boundjid.bare, session.user_jid, cached_avatar
|
449
|
+
)
|
403
450
|
|
404
451
|
log.info("Slidge has successfully started")
|
405
452
|
|
@@ -455,12 +502,13 @@ class BaseGateway(
|
|
455
502
|
exc_info=e,
|
456
503
|
)
|
457
504
|
|
458
|
-
|
505
|
+
@timeit
|
506
|
+
async def login_wrap(self, session: "BaseSession"):
|
459
507
|
session.send_gateway_status("Logging in…", show="dnd")
|
460
508
|
try:
|
461
509
|
status = await session.login()
|
462
510
|
except Exception as e:
|
463
|
-
log.warning("Login problem for %s", session.
|
511
|
+
log.warning("Login problem for %s", session.user_jid, exc_info=e)
|
464
512
|
log.exception(e)
|
465
513
|
session.send_gateway_status(f"Could not login: {e}", show="busy")
|
466
514
|
session.send_gateway_message(
|
@@ -469,10 +517,10 @@ class BaseGateway(
|
|
469
517
|
)
|
470
518
|
return
|
471
519
|
|
472
|
-
log.info("Login success for %s", session.
|
520
|
+
log.info("Login success for %s", session.user_jid)
|
473
521
|
session.logged = True
|
474
522
|
session.send_gateway_status("Syncing contacts…", show="dnd")
|
475
|
-
await session.contacts.
|
523
|
+
await session.contacts._fill()
|
476
524
|
if not (r := session.contacts.ready).done():
|
477
525
|
r.set_result(True)
|
478
526
|
if self.GROUPS:
|
@@ -483,24 +531,25 @@ class BaseGateway(
|
|
483
531
|
for c in session.contacts:
|
484
532
|
# we need to receive presences directed at the contacts, in
|
485
533
|
# order to send pubsub events for their +notify features
|
486
|
-
self.send_presence(pfrom=c.jid, pto=session.
|
534
|
+
self.send_presence(pfrom=c.jid, pto=session.user_jid.bare, ptype="probe")
|
487
535
|
if status is None:
|
488
536
|
session.send_gateway_status("Logged in", show="chat")
|
489
537
|
else:
|
490
538
|
session.send_gateway_status(status, show="chat")
|
491
|
-
|
492
|
-
|
493
|
-
|
539
|
+
if session.user.preferences.get("sync_avatar", False):
|
540
|
+
session.create_task(self.fetch_user_avatar(session))
|
541
|
+
else:
|
542
|
+
self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
|
494
543
|
|
495
|
-
async def
|
544
|
+
async def fetch_user_avatar(self, session: BaseSession):
|
496
545
|
try:
|
497
546
|
iq = await self.xmpp.plugin["xep_0060"].get_items(
|
498
|
-
session.
|
547
|
+
session.user_jid.bare,
|
499
548
|
self.xmpp.plugin["xep_0084"].stanza.MetaData.namespace,
|
500
549
|
ifrom=self.boundjid.bare,
|
501
550
|
)
|
502
|
-
except IqError
|
503
|
-
|
551
|
+
except IqError:
|
552
|
+
self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
|
504
553
|
return
|
505
554
|
await self.__dispatcher.on_avatar_metadata_info(
|
506
555
|
session, iq["pubsub"]["items"]["item"]["avatar_metadata"]["info"]
|
@@ -515,21 +564,6 @@ class BaseGateway(
|
|
515
564
|
stanza.send()
|
516
565
|
return stanza
|
517
566
|
|
518
|
-
async def _on_user_register(self, iq: Iq):
|
519
|
-
session = self.get_session_from_stanza(iq)
|
520
|
-
for jid in config.ADMINS:
|
521
|
-
self.send_message(
|
522
|
-
mto=jid,
|
523
|
-
mbody=f"{iq.get_from()} has registered",
|
524
|
-
mtype="chat",
|
525
|
-
mfrom=self.boundjid.bare,
|
526
|
-
)
|
527
|
-
session.send_gateway_message(self.WELCOME_MESSAGE)
|
528
|
-
await self.__login_wrap(session)
|
529
|
-
|
530
|
-
async def _on_user_unregister(self, iq: Iq):
|
531
|
-
await self.session_cls.kill_by_jid(iq.get_from())
|
532
|
-
|
533
567
|
def raise_if_not_allowed_jid(self, jid: JID):
|
534
568
|
if not self.jid_validator.match(jid.bare):
|
535
569
|
raise XMPPError(
|
@@ -568,26 +602,6 @@ class BaseGateway(
|
|
568
602
|
except XMPPError:
|
569
603
|
pass
|
570
604
|
|
571
|
-
async def get_muc_from_stanza(self, iq: Union[Iq, Message]) -> "LegacyMUC":
|
572
|
-
ito = iq.get_to()
|
573
|
-
|
574
|
-
if ito == self.boundjid.bare:
|
575
|
-
raise XMPPError(
|
576
|
-
text="No MAM on the component itself, use a JID with a resource"
|
577
|
-
)
|
578
|
-
|
579
|
-
ifrom = iq.get_from()
|
580
|
-
user = user_store.get_by_jid(ifrom)
|
581
|
-
if user is None:
|
582
|
-
raise XMPPError("registration-required")
|
583
|
-
|
584
|
-
session = self.get_session_from_user(user)
|
585
|
-
session.raise_if_not_logged()
|
586
|
-
|
587
|
-
muc = await session.bookmarks.by_jid(ito)
|
588
|
-
|
589
|
-
return muc
|
590
|
-
|
591
605
|
def exception(self, exception: Exception):
|
592
606
|
# """
|
593
607
|
# Called when a task created by slixmpp's internal (eg, on slix events) raises an Exception.
|
@@ -622,14 +636,14 @@ class BaseGateway(
|
|
622
636
|
async def w():
|
623
637
|
session.cancel_all_tasks()
|
624
638
|
await session.logout()
|
625
|
-
await self.
|
639
|
+
await self.login_wrap(session)
|
626
640
|
|
627
641
|
session.create_task(w())
|
628
642
|
|
629
643
|
async def make_registration_form(self, _jid, _node, _ifrom, iq: Iq):
|
630
644
|
self.raise_if_not_allowed_jid(iq.get_from())
|
631
645
|
reg = iq["register"]
|
632
|
-
user =
|
646
|
+
user = self.store.users.get_by_stanza(iq)
|
633
647
|
log.debug("User found: %s", user)
|
634
648
|
|
635
649
|
form = reg["form"]
|
@@ -675,18 +689,20 @@ class BaseGateway(
|
|
675
689
|
reply.set_payload(reg)
|
676
690
|
return reply
|
677
691
|
|
678
|
-
async def user_prevalidate(
|
692
|
+
async def user_prevalidate(
|
693
|
+
self, ifrom: JID, form_dict: dict[str, Optional[str]]
|
694
|
+
) -> Optional[Mapping]:
|
679
695
|
# Pre validate a registration form using the content of self.REGISTRATION_FIELDS
|
680
696
|
# before passing it to the plugin custom validation logic.
|
681
697
|
for field in self.REGISTRATION_FIELDS:
|
682
698
|
if field.required and not form_dict.get(field.var):
|
683
699
|
raise ValueError(f"Missing field: '{field.label}'")
|
684
700
|
|
685
|
-
await self.validate(ifrom, form_dict)
|
701
|
+
return await self.validate(ifrom, form_dict)
|
686
702
|
|
687
703
|
async def validate(
|
688
704
|
self, user_jid: JID, registration_form: dict[str, Optional[str]]
|
689
|
-
):
|
705
|
+
) -> Optional[Mapping]:
|
690
706
|
"""
|
691
707
|
Validate a user's initial registration form.
|
692
708
|
|
@@ -706,11 +722,19 @@ class BaseGateway(
|
|
706
722
|
|
707
723
|
:param user_jid: JID of the user that has just registered
|
708
724
|
:param registration_form: A dict where keys are the :attr:`.FormField.var` attributes
|
709
|
-
|
725
|
+
of the :attr:`.BaseGateway.REGISTRATION_FIELDS` iterable.
|
726
|
+
This dict can be modified and will be accessible as the ``legacy_module_data``
|
727
|
+
of the
|
728
|
+
|
729
|
+
:return : A dict that will be stored as the persistent "legacy_module_data"
|
730
|
+
for this user. If you don't return anything here, the whole registration_form
|
731
|
+
content will be stored.
|
710
732
|
"""
|
711
733
|
raise NotImplementedError
|
712
734
|
|
713
|
-
async def validate_two_factor_code(
|
735
|
+
async def validate_two_factor_code(
|
736
|
+
self, user: GatewayUser, code: str
|
737
|
+
) -> Optional[dict]:
|
714
738
|
"""
|
715
739
|
Called when the user enters their 2FA code.
|
716
740
|
|
@@ -725,6 +749,9 @@ class BaseGateway(
|
|
725
749
|
:attr:`.registration_form` attributes to get what you need.
|
726
750
|
:param code: The code they entered, either via "chatbot" message or
|
727
751
|
adhoc command
|
752
|
+
|
753
|
+
:return : A dict which keys and values will be added to the persistent "legacy_module_data"
|
754
|
+
for this user.
|
728
755
|
"""
|
729
756
|
raise NotImplementedError
|
730
757
|
|
@@ -744,7 +771,10 @@ class BaseGateway(
|
|
744
771
|
raise NotImplementedError
|
745
772
|
|
746
773
|
async def confirm_qr(
|
747
|
-
self,
|
774
|
+
self,
|
775
|
+
user_bare_jid: str,
|
776
|
+
exception: Optional[Exception] = None,
|
777
|
+
legacy_data: Optional[dict] = None,
|
748
778
|
):
|
749
779
|
"""
|
750
780
|
This method is meant to be called to finalize QR code-based registration
|
@@ -757,10 +787,12 @@ class BaseGateway(
|
|
757
787
|
:class:`GatewayUser` instance
|
758
788
|
:param exception: Optionally, an XMPPError to be raised to **not** confirm
|
759
789
|
QR code flashing.
|
790
|
+
:param legacy_data: dict which keys and values will be added to the persistent
|
791
|
+
"legacy_module_data" for this user.
|
760
792
|
"""
|
761
793
|
fut = self.qr_pending_registrations[user_bare_jid]
|
762
794
|
if exception is None:
|
763
|
-
fut.set_result(
|
795
|
+
fut.set_result(legacy_data)
|
764
796
|
else:
|
765
797
|
fut.set_exception(exception)
|
766
798
|
|
@@ -771,14 +803,17 @@ class BaseGateway(
|
|
771
803
|
async def unregister(self, user: GatewayUser):
|
772
804
|
"""
|
773
805
|
Optionally override this if you need to clean additional
|
774
|
-
stuff after a user has been removed from the
|
806
|
+
stuff after a user has been removed from the persistent user store.
|
775
807
|
|
776
808
|
By default, this just calls :meth:`BaseSession.logout`.
|
777
809
|
|
778
810
|
:param user:
|
779
811
|
"""
|
780
812
|
session = self.get_session_from_user(user)
|
781
|
-
|
813
|
+
try:
|
814
|
+
await session.logout()
|
815
|
+
except NotImplementedError:
|
816
|
+
pass
|
782
817
|
|
783
818
|
async def input(
|
784
819
|
self, jid: JID, text=None, mtype: MessageTypes = "chat", **msg_kwargs
|
@@ -825,26 +860,12 @@ class BaseGateway(
|
|
825
860
|
# """
|
826
861
|
log.debug("Shutting down")
|
827
862
|
tasks = []
|
828
|
-
for user in
|
863
|
+
for user in self.store.users.get_all():
|
829
864
|
tasks.append(self.session_cls.from_jid(user.jid).shutdown())
|
830
865
|
self.send_presence(ptype="unavailable", pto=user.jid)
|
831
866
|
return tasks
|
832
867
|
|
833
868
|
|
834
|
-
KICKABLE_ERRORS = {
|
835
|
-
"gone",
|
836
|
-
"internal-server-error",
|
837
|
-
"item-not-found",
|
838
|
-
"jid-malformed",
|
839
|
-
"recipient-unavailable",
|
840
|
-
"redirect",
|
841
|
-
"remote-server-not-found",
|
842
|
-
"remote-server-timeout",
|
843
|
-
"service-unavailable",
|
844
|
-
"malformed error",
|
845
|
-
}
|
846
|
-
|
847
|
-
|
848
869
|
SLIXMPP_PLUGINS = [
|
849
870
|
"link_preview", # https://wiki.soprani.ca/CheogramApp/LinkPreviews
|
850
871
|
"xep_0030", # Service discovery
|
@@ -854,6 +875,7 @@ SLIXMPP_PLUGINS = [
|
|
854
875
|
"xep_0055", # Jabber search
|
855
876
|
"xep_0059", # Result Set Management
|
856
877
|
"xep_0066", # Out of Band Data
|
878
|
+
"xep_0071", # XHTML-IM (for stickers and custom emojis maybe later)
|
857
879
|
"xep_0077", # In-band registration
|
858
880
|
"xep_0084", # User Avatar
|
859
881
|
"xep_0085", # Chat state notifications
|
@@ -866,6 +888,7 @@ SLIXMPP_PLUGINS = [
|
|
866
888
|
"xep_0184", # Message Delivery Receipts
|
867
889
|
"xep_0199", # XMPP Ping
|
868
890
|
"xep_0221", # Data Forms Media Element
|
891
|
+
"xep_0231", # Bits of Binary (for stickers and custom emojis maybe later)
|
869
892
|
"xep_0249", # Direct MUC Invitations
|
870
893
|
"xep_0264", # Jingle Content Thumbnails
|
871
894
|
"xep_0280", # Carbons
|
slidge/core/mixins/__init__.py
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
Mixins
|
3
3
|
"""
|
4
4
|
|
5
|
+
from typing import Optional
|
6
|
+
|
5
7
|
from .avatar import AvatarMixin
|
6
8
|
from .disco import ChatterDiscoMixin
|
7
9
|
from .message import MessageCarbonMixin, MessageMixin
|
@@ -16,4 +18,12 @@ class FullCarbonMixin(ChatterDiscoMixin, MessageCarbonMixin, PresenceMixin):
|
|
16
18
|
pass
|
17
19
|
|
18
20
|
|
19
|
-
|
21
|
+
class StoredAttributeMixin:
|
22
|
+
def serialize_extra_attributes(self) -> Optional[dict]:
|
23
|
+
return None
|
24
|
+
|
25
|
+
def deserialize_extra_attributes(self, data: dict) -> None:
|
26
|
+
pass
|
27
|
+
|
28
|
+
|
29
|
+
__all__ = ("AvatarMixin", "FullCarbonMixin", "StoredAttributeMixin")
|