slidge 0.2.0a9__py3-none-any.whl → 0.2.0b0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. slidge/__main__.py +2 -3
  2. slidge/__version__.py +1 -1
  3. slidge/command/adhoc.py +1 -1
  4. slidge/command/base.py +4 -4
  5. slidge/command/user.py +5 -1
  6. slidge/contact/roster.py +9 -0
  7. slidge/core/config.py +0 -3
  8. slidge/core/dispatcher/__init__.py +3 -0
  9. slidge/core/{gateway → dispatcher}/caps.py +6 -4
  10. slidge/core/{gateway → dispatcher}/disco.py +11 -17
  11. slidge/core/dispatcher/message/__init__.py +10 -0
  12. slidge/core/dispatcher/message/chat_state.py +40 -0
  13. slidge/core/dispatcher/message/marker.py +62 -0
  14. slidge/core/dispatcher/message/message.py +397 -0
  15. slidge/core/dispatcher/muc/__init__.py +12 -0
  16. slidge/core/dispatcher/muc/admin.py +98 -0
  17. slidge/core/{gateway → dispatcher/muc}/mam.py +26 -15
  18. slidge/core/dispatcher/muc/misc.py +121 -0
  19. slidge/core/dispatcher/muc/owner.py +96 -0
  20. slidge/core/{gateway → dispatcher/muc}/ping.py +10 -15
  21. slidge/core/dispatcher/presence.py +177 -0
  22. slidge/core/{gateway → dispatcher}/registration.py +23 -2
  23. slidge/core/{gateway → dispatcher}/search.py +9 -14
  24. slidge/core/dispatcher/session_dispatcher.py +84 -0
  25. slidge/core/dispatcher/util.py +174 -0
  26. slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +26 -12
  27. slidge/core/{gateway/base.py → gateway.py} +42 -137
  28. slidge/core/mixins/attachment.py +24 -8
  29. slidge/core/mixins/base.py +2 -2
  30. slidge/core/mixins/lock.py +10 -8
  31. slidge/core/mixins/message.py +9 -203
  32. slidge/core/mixins/message_text.py +211 -0
  33. slidge/core/pubsub.py +2 -1
  34. slidge/core/session.py +28 -2
  35. slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +83 -0
  36. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
  37. slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +12 -1
  38. slidge/db/models.py +16 -1
  39. slidge/db/store.py +144 -11
  40. slidge/group/bookmarks.py +23 -1
  41. slidge/group/participant.py +5 -5
  42. slidge/group/room.py +10 -1
  43. slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
  44. slidge/util/test.py +9 -5
  45. slidge/util/types.py +6 -0
  46. slidge/util/util.py +5 -2
  47. {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/METADATA +2 -1
  48. {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/RECORD +51 -40
  49. slidge-0.2.0b0.dist-info/entry_points.txt +3 -0
  50. slidge/core/gateway/__init__.py +0 -3
  51. slidge/core/gateway/muc_admin.py +0 -35
  52. slidge/core/gateway/presence.py +0 -95
  53. slidge/core/gateway/session_dispatcher.py +0 -895
  54. slidge-0.2.0a9.dist-info/entry_points.txt +0 -3
  55. {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/LICENSE +0 -0
  56. {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,174 @@
1
+ import logging
2
+ from functools import wraps
3
+ from typing import TYPE_CHECKING, Any, Awaitable, Callable, TypeVar
4
+
5
+ from slixmpp import JID, Iq, Message, Presence
6
+ from slixmpp.exceptions import XMPPError
7
+ from slixmpp.xmlstream import StanzaBase
8
+
9
+ from ...util.types import Recipient, RecipientType
10
+ from ..session import BaseSession
11
+
12
+ if TYPE_CHECKING:
13
+ from slidge import BaseGateway
14
+ from slidge.group import LegacyMUC
15
+
16
+
17
+ class Ignore(BaseException):
18
+ pass
19
+
20
+
21
+ class DispatcherMixin:
22
+ def __init__(self, xmpp: "BaseGateway"):
23
+ self.xmpp = xmpp
24
+
25
+ async def _get_session(
26
+ self,
27
+ stanza: Message | Presence | Iq,
28
+ timeout: int | None = 10,
29
+ wait_for_ready=True,
30
+ logged=False,
31
+ ) -> BaseSession:
32
+ xmpp = self.xmpp
33
+ if stanza.get_from().server == xmpp.boundjid.bare:
34
+ log.debug("Ignoring echo")
35
+ raise Ignore
36
+ if (
37
+ isinstance(stanza, Message)
38
+ and stanza.get_type() == "chat"
39
+ and stanza.get_to() == xmpp.boundjid.bare
40
+ ):
41
+ log.debug("Ignoring message to component")
42
+ raise Ignore
43
+ session = await self._get_session_from_jid(
44
+ stanza.get_from(), timeout, wait_for_ready, logged
45
+ )
46
+ if isinstance(stanza, Message) and _ignore(session, stanza):
47
+ raise Ignore
48
+ return session
49
+
50
+ async def _get_session_from_jid(
51
+ self,
52
+ jid: JID,
53
+ timeout: int | None = 10,
54
+ wait_for_ready=True,
55
+ logged=False,
56
+ ) -> BaseSession:
57
+ session = self.xmpp.get_session_from_jid(jid)
58
+ if session is None:
59
+ raise XMPPError("registration-required")
60
+ if logged:
61
+ session.raise_if_not_logged()
62
+ if wait_for_ready:
63
+ await session.wait_for_ready(timeout)
64
+ return session
65
+
66
+ async def get_muc_from_stanza(self, iq: Iq | Message | Presence) -> "LegacyMUC":
67
+ ito = iq.get_to()
68
+ if ito == self.xmpp.boundjid.bare:
69
+ raise XMPPError("bad-request", text="This is only handled for MUCs")
70
+
71
+ session = await self._get_session(iq, logged=True)
72
+ muc = await session.bookmarks.by_jid(ito)
73
+ return muc
74
+
75
+ def _xmpp_msg_id_to_legacy(self, session: "BaseSession", xmpp_id: str):
76
+ sent = self.xmpp.store.sent.get_legacy_id(session.user_pk, xmpp_id)
77
+ if sent is not None:
78
+ return self.xmpp.LEGACY_MSG_ID_TYPE(sent)
79
+
80
+ multi = self.xmpp.store.multi.get_legacy_id(session.user_pk, xmpp_id)
81
+ if multi:
82
+ return self.xmpp.LEGACY_MSG_ID_TYPE(multi)
83
+
84
+ try:
85
+ return session.xmpp_to_legacy_msg_id(xmpp_id)
86
+ except XMPPError:
87
+ raise
88
+ except Exception as e:
89
+ log.debug("Couldn't convert xmpp msg ID to legacy ID.", exc_info=e)
90
+ raise XMPPError(
91
+ "internal-server-error", "Couldn't convert xmpp msg ID to legacy ID."
92
+ )
93
+
94
+ async def _get_session_entity_thread(
95
+ self, msg: Message
96
+ ) -> tuple["BaseSession", Recipient, int | str]:
97
+ session = await self._get_session(msg)
98
+ e: Recipient = await _get_entity(session, msg)
99
+ legacy_thread = await self._xmpp_to_legacy_thread(session, msg, e)
100
+ return session, e, legacy_thread
101
+
102
+ async def _xmpp_to_legacy_thread(
103
+ self, session: "BaseSession", msg: Message, recipient: RecipientType
104
+ ):
105
+ xmpp_thread = msg["thread"]
106
+ if not xmpp_thread:
107
+ return None
108
+
109
+ if session.MESSAGE_IDS_ARE_THREAD_IDS:
110
+ return self._xmpp_msg_id_to_legacy(session, xmpp_thread)
111
+
112
+ legacy_thread_str = session.xmpp.store.sent.get_legacy_thread(
113
+ session.user_pk, xmpp_thread
114
+ )
115
+ if legacy_thread_str is not None:
116
+ return session.xmpp.LEGACY_MSG_ID_TYPE(legacy_thread_str)
117
+ async with session.thread_creation_lock:
118
+ legacy_thread = await recipient.create_thread(xmpp_thread)
119
+ session.xmpp.store.sent.set_thread(
120
+ session.user_pk, str(legacy_thread), xmpp_thread
121
+ )
122
+ return legacy_thread
123
+
124
+
125
+ def _ignore(session: "BaseSession", msg: Message):
126
+ i = msg.get_id()
127
+ if i.startswith("slidge-carbon-"):
128
+ return True
129
+ if i not in session.ignore_messages:
130
+ return False
131
+ session.log.debug("Ignored sent carbon: %s", i)
132
+ session.ignore_messages.remove(i)
133
+ return True
134
+
135
+
136
+ async def _get_entity(session: "BaseSession", m: Message) -> RecipientType:
137
+ session.raise_if_not_logged()
138
+ if m.get_type() == "groupchat":
139
+ muc = await session.bookmarks.by_jid(m.get_to())
140
+ r = m.get_from().resource
141
+ if r not in muc.get_user_resources():
142
+ session.create_task(muc.kick_resource(r))
143
+ raise XMPPError("not-acceptable", "You are not connected to this chat")
144
+ return muc
145
+ else:
146
+ return await session.contacts.by_jid(m.get_to())
147
+
148
+
149
+ StanzaType = TypeVar("StanzaType", bound=StanzaBase)
150
+ HandlerType = Callable[[Any, StanzaType], Awaitable[None]]
151
+
152
+
153
+ def exceptions_to_xmpp_errors(cb: HandlerType) -> HandlerType:
154
+ @wraps(cb)
155
+ async def wrapped(*args):
156
+ try:
157
+ await cb(*args)
158
+ except Ignore:
159
+ pass
160
+ except XMPPError:
161
+ raise
162
+ except NotImplementedError:
163
+ log.debug("NotImplementedError raised in %s", cb)
164
+ raise XMPPError(
165
+ "feature-not-implemented", "Not implemented by the legacy module"
166
+ )
167
+ except Exception as e:
168
+ log.error("Failed to handle incoming stanza: %s", args, exc_info=e)
169
+ raise XMPPError("internal-server-error", str(e))
170
+
171
+ return wrapped
172
+
173
+
174
+ log = logging.getLogger(__name__)
@@ -1,5 +1,4 @@
1
1
  from copy import copy
2
- from typing import TYPE_CHECKING
3
2
 
4
3
  from slixmpp import CoroutineCallback, Iq, StanzaPath, register_stanza_plugin
5
4
  from slixmpp.exceptions import XMPPError
@@ -9,21 +8,23 @@ from slixmpp.plugins.xep_0292.stanza import NS as VCard4NS
9
8
  from ...contact import LegacyContact
10
9
  from ...core.session import BaseSession
11
10
  from ...group import LegacyParticipant
11
+ from .util import DispatcherMixin, exceptions_to_xmpp_errors
12
12
 
13
- if TYPE_CHECKING:
14
- from .base import BaseGateway
15
13
 
16
-
17
- class VCardTemp:
18
- def __init__(self, xmpp: "BaseGateway"):
19
- self.xmpp = xmpp
20
- # remove slixmpp's default handler to replace with our own
21
- self.xmpp.remove_handler("VCardTemp")
14
+ class VCardMixin(DispatcherMixin):
15
+ def __init__(self, xmpp):
16
+ super().__init__(xmpp)
17
+ xmpp.register_handler(
18
+ CoroutineCallback(
19
+ "get_vcard", StanzaPath("iq@type=get/vcard"), self.on_get_vcard
20
+ )
21
+ )
22
+ xmpp.remove_handler("VCardTemp")
22
23
  xmpp.register_handler(
23
24
  CoroutineCallback(
24
25
  "VCardTemp",
25
26
  StanzaPath("iq/vcard_temp"),
26
- self.__handler, # type:ignore
27
+ self.__vcard_temp_handler,
27
28
  )
28
29
  )
29
30
  # TODO: MR to slixmpp adding this to XEP-0084
@@ -32,7 +33,20 @@ class VCardTemp:
32
33
  MetaData,
33
34
  )
34
35
 
35
- async def __handler(self, iq: Iq):
36
+ @exceptions_to_xmpp_errors
37
+ async def on_get_vcard(self, iq: Iq):
38
+ session = await self._get_session(iq, logged=True)
39
+ contact = await session.contacts.by_jid(iq.get_to())
40
+ vcard = await contact.get_vcard()
41
+ reply = iq.reply()
42
+ if vcard:
43
+ reply.append(vcard)
44
+ else:
45
+ reply.enable("vcard")
46
+ reply.send()
47
+
48
+ @exceptions_to_xmpp_errors
49
+ async def __vcard_temp_handler(self, iq: Iq):
36
50
  if iq["type"] == "get":
37
51
  return await self.__handle_get_vcard_temp(iq)
38
52
 
@@ -105,7 +119,7 @@ class VCardTemp:
105
119
  reply.send()
106
120
 
107
121
  async def __handle_set_vcard_temp(self, iq: Iq):
108
- muc = await self.xmpp.get_muc_from_stanza(iq)
122
+ muc = await self.get_muc_from_stanza(iq)
109
123
  to = iq.get_to()
110
124
 
111
125
  if to.resource:
@@ -8,16 +8,7 @@ import re
8
8
  import tempfile
9
9
  from copy import copy
10
10
  from datetime import datetime
11
- from typing import (
12
- TYPE_CHECKING,
13
- Any,
14
- Callable,
15
- Collection,
16
- Mapping,
17
- Optional,
18
- Sequence,
19
- Union,
20
- )
11
+ from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, Sequence, Union
21
12
 
22
13
  import aiohttp
23
14
  import qrcode
@@ -27,40 +18,30 @@ from slixmpp.plugins.xep_0060.stanza import OwnerAffiliation
27
18
  from slixmpp.types import MessageTypes
28
19
  from slixmpp.xmlstream.xmlstream import NotConnectedError
29
20
 
30
- from ... import command # noqa: F401
31
- from ...command.adhoc import AdhocProvider
32
- from ...command.admin import Exec
33
- from ...command.base import Command, FormField
34
- from ...command.chat_command import ChatCommandProvider
35
- from ...command.register import RegistrationType
36
- from ...db import GatewayUser, SlidgeStore
37
- from ...db.avatar import avatar_cache
38
- from ...slixfix.roster import RosterBackend
39
- from ...util import ABCSubclassableOnceAtMost
40
- from ...util.types import AvatarType, MessageOrPresenceTypeVar
41
- from ...util.util import timeit
42
- from .. import config
43
- from ..mixins import MessageMixin
44
- from ..pubsub import PubSubComponent
45
- from ..session import BaseSession
46
- from .caps import Caps
47
- from .delivery_receipt import DeliveryReceipt
48
- from .disco import Disco
49
- from .mam import Mam
50
- from .muc_admin import MucAdmin
51
- from .ping import Ping
52
- from .presence import PresenceHandlerMixin
53
- from .registration import Registration
54
- from .search import Search
55
- from .session_dispatcher import SessionDispatcher
56
- 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
57
39
 
58
40
  if TYPE_CHECKING:
59
- from ...group.room import LegacyMUC
41
+ pass
60
42
 
61
43
 
62
44
  class BaseGateway(
63
- PresenceHandlerMixin,
64
45
  ComponentXMPP,
65
46
  MessageMixin,
66
47
  metaclass=ABCSubclassableOnceAtMost,
@@ -118,7 +99,7 @@ class BaseGateway(
118
99
  Path, bytes or URL used by the component as an avatar.
119
100
  """
120
101
 
121
- REGISTRATION_FIELDS: Collection[FormField] = [
102
+ REGISTRATION_FIELDS: Sequence[FormField] = [
122
103
  FormField(var="username", label="User name", required=True),
123
104
  FormField(var="password", label="Password", required=True, private=True),
124
105
  ]
@@ -315,7 +296,7 @@ class BaseGateway(
315
296
  self.session_cls: BaseSession = BaseSession.get_unique_subclass()
316
297
  self.session_cls.xmpp = self
317
298
 
318
- from ...group.room import LegacyMUC
299
+ from ..group.room import LegacyMUC
319
300
 
320
301
  LegacyMUC.get_self_or_unique_subclass().xmpp = self
321
302
 
@@ -328,6 +309,7 @@ class BaseGateway(
328
309
 
329
310
  self.register_plugins()
330
311
  self.__register_slixmpp_events()
312
+ self.__register_slixmpp_api()
331
313
  self.roster.set_backend(RosterBackend(self))
332
314
 
333
315
  self.register_plugin("pubsub", {"component_name": self.COMPONENT_NAME})
@@ -355,21 +337,11 @@ class BaseGateway(
355
337
  # why does mypy need these type annotations? no idea
356
338
  self.__adhoc_handler: AdhocProvider = AdhocProvider(self)
357
339
  self.__chat_commands_handler: ChatCommandProvider = ChatCommandProvider(self)
358
- self.__disco_handler = Disco(self)
359
-
360
- self.__ping_handler = Ping(self)
361
- self.__mam_handler = Mam(self)
362
- self.__search_handler = Search(self)
363
- self.__caps_handler = Caps(self)
364
- self.__vcard_temp_handler = VCardTemp(self)
365
- self.__muc_admin_handler = MucAdmin(self)
366
- self.__registration = Registration(self)
340
+
367
341
  self.__dispatcher = SessionDispatcher(self)
368
342
 
369
343
  self.__register_commands()
370
344
 
371
- self.__mam_cleanup_task = self.loop.create_task(self.__mam_cleanup())
372
-
373
345
  MessageMixin.__init__(self) # ComponentXMPP does not call super().__init__()
374
346
 
375
347
  async def __set_http(self):
@@ -378,13 +350,6 @@ class BaseGateway(
378
350
  return
379
351
  avatar_cache.http = self.http
380
352
 
381
- async def __mam_cleanup(self):
382
- if not config.MAM_MAX_DAYS:
383
- return
384
- while True:
385
- await asyncio.sleep(3600 * 6)
386
- self.store.mam.nuke_older_than(config.MAM_MAX_DAYS)
387
-
388
353
  def __register_commands(self):
389
354
  for cls in Command.subclasses:
390
355
  if any(x is NotImplemented for x in [cls.CHAT_COMMAND, cls.NODE, cls.NAME]):
@@ -421,40 +386,27 @@ class BaseGateway(
421
386
  loop.stop()
422
387
 
423
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)
424
397
  self.add_event_handler("session_start", self.__on_session_start)
425
398
  self.add_event_handler("disconnected", self.connect)
426
- self.add_event_handler("user_register", self._on_user_register)
427
- self.add_event_handler("user_unregister", self._on_user_unregister)
428
- self.add_event_handler("groupchat_message_error", self.__on_group_chat_error)
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")
429
404
 
430
405
  @property # type: ignore
431
406
  def jid(self):
432
407
  # Override to avoid slixmpp deprecation warnings.
433
408
  return self.boundjid
434
409
 
435
- async def __on_group_chat_error(self, msg: Message):
436
- condition = msg["error"].get_condition()
437
- if condition not in KICKABLE_ERRORS:
438
- return
439
-
440
- try:
441
- muc = await self.get_muc_from_stanza(msg)
442
- except XMPPError as e:
443
- log.debug("Not removing resource", exc_info=e)
444
- return
445
- mfrom = msg.get_from()
446
- resource = mfrom.resource
447
- try:
448
- muc.remove_user_resource(resource)
449
- except KeyError:
450
- # this actually happens quite frequently on for both beagle and monal
451
- # (not sure why?), but is of no consequence
452
- log.debug("%s was not in the resources of %s", resource, muc)
453
- else:
454
- log.info(
455
- "Removed %s from the resources of %s because of error", resource, muc
456
- )
457
-
458
410
  async def __on_session_start(self, event):
459
411
  log.debug("Gateway session start: %s", event)
460
412
 
@@ -490,7 +442,7 @@ class BaseGateway(
490
442
  pto=user.jid.bare, ptype="probe"
491
443
  ) # ensure we get all resources for user
492
444
  session = self.session_cls.from_user(user)
493
- session.create_task(self.__login_wrap(session))
445
+ session.create_task(self.login_wrap(session))
494
446
  if cached_avatar is not None:
495
447
  await self.pubsub.broadcast_avatar(
496
448
  self.boundjid.bare, session.user_jid, cached_avatar
@@ -551,7 +503,7 @@ class BaseGateway(
551
503
  )
552
504
 
553
505
  @timeit
554
- async def __login_wrap(self, session: "BaseSession"):
506
+ async def login_wrap(self, session: "BaseSession"):
555
507
  session.send_gateway_status("Logging in…", show="dnd")
556
508
  try:
557
509
  status = await session.login()
@@ -612,21 +564,6 @@ class BaseGateway(
612
564
  stanza.send()
613
565
  return stanza
614
566
 
615
- async def _on_user_register(self, iq: Iq):
616
- session = self.get_session_from_stanza(iq)
617
- for jid in config.ADMINS:
618
- self.send_message(
619
- mto=jid,
620
- mbody=f"{iq.get_from()} has registered",
621
- mtype="chat",
622
- mfrom=self.boundjid.bare,
623
- )
624
- session.send_gateway_message(self.WELCOME_MESSAGE)
625
- await self.__login_wrap(session)
626
-
627
- async def _on_user_unregister(self, iq: Iq):
628
- await self.session_cls.kill_by_jid(iq.get_from())
629
-
630
567
  def raise_if_not_allowed_jid(self, jid: JID):
631
568
  if not self.jid_validator.match(jid.bare):
632
569
  raise XMPPError(
@@ -665,26 +602,6 @@ class BaseGateway(
665
602
  except XMPPError:
666
603
  pass
667
604
 
668
- async def get_muc_from_stanza(self, iq: Union[Iq, Message]) -> "LegacyMUC":
669
- ito = iq.get_to()
670
-
671
- if ito == self.boundjid.bare:
672
- raise XMPPError(
673
- text="No MAM on the component itself, use a JID with a resource"
674
- )
675
-
676
- ifrom = iq.get_from()
677
- user = self.store.users.get(ifrom)
678
- if user is None:
679
- raise XMPPError("registration-required")
680
-
681
- session = self.get_session_from_user(user)
682
- session.raise_if_not_logged()
683
-
684
- muc = await session.bookmarks.by_jid(ito)
685
-
686
- return muc
687
-
688
605
  def exception(self, exception: Exception):
689
606
  # """
690
607
  # Called when a task created by slixmpp's internal (eg, on slix events) raises an Exception.
@@ -719,7 +636,7 @@ class BaseGateway(
719
636
  async def w():
720
637
  session.cancel_all_tasks()
721
638
  await session.logout()
722
- await self.__login_wrap(session)
639
+ await self.login_wrap(session)
723
640
 
724
641
  session.create_task(w())
725
642
 
@@ -949,20 +866,6 @@ class BaseGateway(
949
866
  return tasks
950
867
 
951
868
 
952
- KICKABLE_ERRORS = {
953
- "gone",
954
- "internal-server-error",
955
- "item-not-found",
956
- "jid-malformed",
957
- "recipient-unavailable",
958
- "redirect",
959
- "remote-server-not-found",
960
- "remote-server-timeout",
961
- "service-unavailable",
962
- "malformed error",
963
- }
964
-
965
-
966
869
  SLIXMPP_PLUGINS = [
967
870
  "link_preview", # https://wiki.soprani.ca/CheogramApp/LinkPreviews
968
871
  "xep_0030", # Service discovery
@@ -972,6 +875,7 @@ SLIXMPP_PLUGINS = [
972
875
  "xep_0055", # Jabber search
973
876
  "xep_0059", # Result Set Management
974
877
  "xep_0066", # Out of Band Data
878
+ "xep_0071", # XHTML-IM (for stickers and custom emojis maybe later)
975
879
  "xep_0077", # In-band registration
976
880
  "xep_0084", # User Avatar
977
881
  "xep_0085", # Chat state notifications
@@ -984,6 +888,7 @@ SLIXMPP_PLUGINS = [
984
888
  "xep_0184", # Message Delivery Receipts
985
889
  "xep_0199", # XMPP Ping
986
890
  "xep_0221", # Data Forms Media Element
891
+ "xep_0231", # Bits of Binary (for stickers and custom emojis maybe later)
987
892
  "xep_0249", # Direct MUC Invitations
988
893
  "xep_0264", # Jingle Content Thumbnails
989
894
  "xep_0280", # Carbons
@@ -33,17 +33,14 @@ from ...util.types import (
33
33
  )
34
34
  from ...util.util import fix_suffix
35
35
  from .. import config
36
- from .message_maker import MessageMaker
36
+ from .message_text import TextMessageMixin
37
37
 
38
38
 
39
- class AttachmentMixin(MessageMaker):
39
+ class AttachmentMixin(TextMessageMixin):
40
40
  def __init__(self, *a, **kw):
41
41
  super().__init__(*a, **kw)
42
42
  self.__store = self.xmpp.store.attachments
43
43
 
44
- def send_text(self, *_, **k) -> Optional[Message]:
45
- raise NotImplementedError
46
-
47
44
  async def __upload(
48
45
  self,
49
46
  file_path: Path,
@@ -261,7 +258,7 @@ class AttachmentMixin(MessageMaker):
261
258
  thumbnail["width"] = x
262
259
  thumbnail["height"] = y
263
260
  thumbnail["media-type"] = "image/thumbhash"
264
- thumbnail["uri"] = "data:image/thumbhash," + urlquote(h)
261
+ thumbnail["uri"] = "data:image/thumbhash;base64," + urlquote(h)
265
262
 
266
263
  self.__store.set_sims(uploaded_url, str(ref))
267
264
 
@@ -304,6 +301,7 @@ class AttachmentMixin(MessageMaker):
304
301
  caption: Optional[str] = None,
305
302
  carbon=False,
306
303
  when: Optional[datetime] = None,
304
+ correction=False,
307
305
  **kwargs,
308
306
  ) -> list[Message]:
309
307
  msg["oob"]["url"] = uploaded_url
@@ -311,11 +309,19 @@ class AttachmentMixin(MessageMaker):
311
309
  if caption:
312
310
  m1 = self._send(msg, carbon=carbon, **kwargs)
313
311
  m2 = self.send_text(
314
- caption, legacy_msg_id=legacy_msg_id, when=when, carbon=carbon, **kwargs
312
+ caption,
313
+ legacy_msg_id=legacy_msg_id,
314
+ when=when,
315
+ carbon=carbon,
316
+ correction=correction,
317
+ **kwargs,
315
318
  )
316
319
  return [m1, m2] if m2 else [m1]
317
320
  else:
318
- self._set_msg_id(msg, legacy_msg_id)
321
+ if correction:
322
+ msg["replace"]["id"] = self._replace_id(legacy_msg_id)
323
+ else:
324
+ self._set_msg_id(msg, legacy_msg_id)
319
325
  return [self._send(msg, carbon=carbon, **kwargs)]
320
326
 
321
327
  async def send_file(
@@ -358,6 +364,16 @@ class AttachmentMixin(MessageMaker):
358
364
  carbon = kwargs.pop("carbon", False)
359
365
  mto = kwargs.pop("mto", None)
360
366
  store_multi = kwargs.pop("store_multi", True)
367
+ correction = kwargs.get("correction", False)
368
+ if correction and (original_xmpp_id := self._legacy_to_xmpp(legacy_msg_id)):
369
+ xmpp_ids = self.xmpp.store.multi.get_xmpp_ids(
370
+ self.session.user_pk, original_xmpp_id
371
+ )
372
+
373
+ for xmpp_id in xmpp_ids:
374
+ if xmpp_id == original_xmpp_id:
375
+ continue
376
+ self.retract(xmpp_id, thread)
361
377
  msg = self._make_message(
362
378
  when=when,
363
379
  reply_to=reply_to,
@@ -6,8 +6,8 @@ from slixmpp import JID
6
6
  from ...util.types import MessageOrPresenceTypeVar
7
7
 
8
8
  if TYPE_CHECKING:
9
- from slidge.core.gateway import BaseGateway
10
- from slidge.core.session import BaseSession
9
+ from ..gateway import BaseGateway
10
+ from ..session import BaseSession
11
11
 
12
12
 
13
13
  class MetaBase(ABCMeta):
@@ -15,14 +15,16 @@ class NamedLockMixin:
15
15
  locks = self.__locks
16
16
  if not locks.get(id_):
17
17
  locks[id_] = asyncio.Lock()
18
- async with locks[id_]:
19
- log.trace("acquired %s", id_) # type:ignore
20
- yield
21
- log.trace("releasing %s", id_) # type:ignore
22
- waiters = locks[id_]._waiters # type:ignore
23
- if not waiters:
24
- del locks[id_]
25
- log.trace("erasing %s", id_) # type:ignore
18
+ try:
19
+ async with locks[id_]:
20
+ log.trace("acquired %s", id_) # type:ignore
21
+ yield
22
+ finally:
23
+ log.trace("releasing %s", id_) # type:ignore
24
+ waiters = locks[id_]._waiters # type:ignore
25
+ if not waiters:
26
+ del locks[id_]
27
+ log.trace("erasing %s", id_) # type:ignore
26
28
 
27
29
  def get_lock(self, id_: Hashable):
28
30
  return self.__locks.get(id_)