slidge 0.2.0a8__py3-none-any.whl → 0.2.0a10__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.
Files changed (46) hide show
  1. slidge/__version__.py +1 -1
  2. slidge/command/adhoc.py +1 -1
  3. slidge/command/base.py +4 -4
  4. slidge/contact/contact.py +3 -2
  5. slidge/contact/roster.py +7 -0
  6. slidge/core/dispatcher/__init__.py +3 -0
  7. slidge/core/{gateway → dispatcher}/caps.py +6 -4
  8. slidge/core/{gateway → dispatcher}/disco.py +11 -17
  9. slidge/core/dispatcher/message/__init__.py +10 -0
  10. slidge/core/dispatcher/message/chat_state.py +40 -0
  11. slidge/core/dispatcher/message/marker.py +67 -0
  12. slidge/core/dispatcher/message/message.py +397 -0
  13. slidge/core/dispatcher/muc/__init__.py +12 -0
  14. slidge/core/dispatcher/muc/admin.py +98 -0
  15. slidge/core/{gateway → dispatcher/muc}/mam.py +26 -15
  16. slidge/core/dispatcher/muc/misc.py +118 -0
  17. slidge/core/dispatcher/muc/owner.py +96 -0
  18. slidge/core/{gateway → dispatcher/muc}/ping.py +10 -15
  19. slidge/core/dispatcher/presence.py +177 -0
  20. slidge/core/{gateway → dispatcher}/registration.py +23 -2
  21. slidge/core/{gateway → dispatcher}/search.py +9 -14
  22. slidge/core/dispatcher/session_dispatcher.py +84 -0
  23. slidge/core/dispatcher/util.py +174 -0
  24. slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +26 -12
  25. slidge/core/{gateway/base.py → gateway.py} +42 -137
  26. slidge/core/mixins/attachment.py +7 -2
  27. slidge/core/mixins/base.py +2 -2
  28. slidge/core/mixins/message.py +10 -4
  29. slidge/core/pubsub.py +2 -1
  30. slidge/core/session.py +28 -2
  31. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
  32. slidge/db/models.py +13 -0
  33. slidge/db/store.py +128 -2
  34. slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
  35. slidge/util/test.py +5 -1
  36. slidge/util/types.py +6 -0
  37. slidge/util/util.py +5 -2
  38. {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/METADATA +2 -1
  39. {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/RECORD +42 -33
  40. slidge/core/gateway/__init__.py +0 -3
  41. slidge/core/gateway/muc_admin.py +0 -35
  42. slidge/core/gateway/presence.py +0 -95
  43. slidge/core/gateway/session_dispatcher.py +0 -895
  44. {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/LICENSE +0 -0
  45. {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/WHEEL +0 -0
  46. {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/entry_points.txt +0 -0
@@ -7,13 +7,16 @@ from slixmpp import JID, Iq
7
7
  from slixmpp.exceptions import XMPPError
8
8
 
9
9
  from ...db import GatewayUser
10
+ from .. import config
11
+ from .util import DispatcherMixin
10
12
 
11
13
  if TYPE_CHECKING:
12
- from .base import BaseGateway
14
+ from slidge.core.gateway import BaseGateway
13
15
 
14
16
 
15
- class Registration:
17
+ class RegistrationMixin(DispatcherMixin):
16
18
  def __init__(self, xmpp: "BaseGateway"):
19
+ super().__init__(xmpp)
17
20
  self.xmpp = xmpp
18
21
  xmpp["xep_0077"].api.register(
19
22
  self.xmpp.make_registration_form, "make_registration_form"
@@ -25,6 +28,9 @@ class Registration:
25
28
  # TODO: either fully use slixmpp internal API or rewrite registration without it at all
26
29
  xmpp["xep_0077"].api.register(lambda *a: None, "user_remove")
27
30
 
31
+ xmpp.add_event_handler("user_register", self._on_user_register)
32
+ xmpp.add_event_handler("user_unregister", self._on_user_unregister)
33
+
28
34
  def get_user(self, jid: JID) -> GatewayUser | None:
29
35
  return self.xmpp.store.users.get(jid)
30
36
 
@@ -60,5 +66,20 @@ class Registration:
60
66
  user.legacy_module_data.update(form_dict)
61
67
  self.xmpp.store.users.update(user)
62
68
 
69
+ async def _on_user_register(self, iq: Iq):
70
+ session = await self._get_session(iq, wait_for_ready=False)
71
+ for jid in config.ADMINS:
72
+ self.xmpp.send_message(
73
+ mto=jid,
74
+ mbody=f"{iq.get_from()} has registered",
75
+ mtype="chat",
76
+ mfrom=self.xmpp.boundjid.bare,
77
+ )
78
+ session.send_gateway_message(self.xmpp.WELCOME_MESSAGE)
79
+ await self.xmpp.login_wrap(session)
80
+
81
+ async def _on_user_unregister(self, iq: Iq):
82
+ await self.xmpp.session_cls.kill_by_jid(iq.get_from())
83
+
63
84
 
64
85
  log = logging.getLogger(__name__)
@@ -3,13 +3,15 @@ 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 import DispatcherMixin, exceptions_to_xmpp_errors
7
+
6
8
  if TYPE_CHECKING:
7
- from .base import BaseGateway
9
+ from slidge.core.gateway import BaseGateway
8
10
 
9
11
 
10
- class Search:
12
+ class SearchMixin(DispatcherMixin):
11
13
  def __init__(self, xmpp: "BaseGateway"):
12
- self.xmpp = xmpp
14
+ super().__init__(xmpp)
13
15
 
14
16
  xmpp["xep_0055"].api.register(self.search_get_form, "search_get_form")
15
17
  xmpp["xep_0055"].api.register(self._search_query, "search_query")
@@ -45,13 +47,9 @@ class Search:
45
47
  """
46
48
  Handles a search request
47
49
  """
48
- user = self.xmpp.store.users.get(ifrom)
49
- if user is None:
50
- raise XMPPError(text="Search is only allowed for registered users")
50
+ session = await self._get_session(iq)
51
51
 
52
- result = await self.xmpp.get_session_from_stanza(iq).on_search(
53
- iq["search"]["form"].get_values()
54
- )
52
+ result = await session.on_search(iq["search"]["form"].get_values())
55
53
 
56
54
  if not result:
57
55
  raise XMPPError("item-not-found", text="Nothing was found")
@@ -64,19 +62,17 @@ class Search:
64
62
  form.add_item(item)
65
63
  return reply
66
64
 
65
+ @exceptions_to_xmpp_errors
67
66
  async def _handle_gateway_iq(self, iq: Iq):
68
67
  if iq.get_to() != self.xmpp.boundjid.bare:
69
68
  raise XMPPError("bad-request", "This can only be used on the component JID")
70
69
 
71
- user = self.xmpp.store.users.get(iq.get_from())
72
- if user is None:
73
- raise XMPPError("not-authorized", "Register to the gateway first")
74
-
75
70
  if len(self.xmpp.SEARCH_FIELDS) > 1:
76
71
  raise XMPPError(
77
72
  "feature-not-implemented", "Use jabber search for this gateway"
78
73
  )
79
74
 
75
+ session = await self._get_session(iq)
80
76
  field = self.xmpp.SEARCH_FIELDS[0]
81
77
 
82
78
  reply = iq.reply()
@@ -85,7 +81,6 @@ class Search:
85
81
  reply["gateway"]["prompt"] = field.label
86
82
  elif iq["type"] == "set":
87
83
  prompt = iq["gateway"]["prompt"]
88
- session = self.xmpp.session_cls.from_user(user)
89
84
  result = await session.on_search({field.var: prompt})
90
85
  if result is None or not result.items:
91
86
  raise XMPPError(
@@ -0,0 +1,84 @@
1
+ import logging
2
+ from typing import TYPE_CHECKING
3
+
4
+ from slixmpp import Message
5
+ from slixmpp.exceptions import IqError
6
+ from slixmpp.plugins.xep_0084.stanza import Info
7
+
8
+ from ..session import BaseSession
9
+ from .caps import CapsMixin
10
+ from .disco import DiscoMixin
11
+ from .message import MessageMixin
12
+ from .muc import MucMixin
13
+ from .presence import PresenceHandlerMixin
14
+ from .registration import RegistrationMixin
15
+ from .search import SearchMixin
16
+ from .util import exceptions_to_xmpp_errors
17
+ from .vcard import VCardMixin
18
+
19
+ if TYPE_CHECKING:
20
+ from slidge.core.gateway import BaseGateway
21
+
22
+
23
+ class SessionDispatcher(
24
+ CapsMixin,
25
+ DiscoMixin,
26
+ RegistrationMixin,
27
+ MessageMixin,
28
+ MucMixin,
29
+ PresenceHandlerMixin,
30
+ SearchMixin,
31
+ VCardMixin,
32
+ ):
33
+ def __init__(self, xmpp: "BaseGateway"):
34
+ super().__init__(xmpp)
35
+ xmpp.add_event_handler(
36
+ "avatar_metadata_publish", self.on_avatar_metadata_publish
37
+ )
38
+
39
+ @exceptions_to_xmpp_errors
40
+ async def on_avatar_metadata_publish(self, m: Message):
41
+ session = await self._get_session(m, timeout=None)
42
+ if not session.user.preferences.get("sync_avatar", False):
43
+ session.log.debug("User does not want to sync their avatar")
44
+ return
45
+ info = m["pubsub_event"]["items"]["item"]["avatar_metadata"]["info"]
46
+
47
+ await self.on_avatar_metadata_info(session, info)
48
+
49
+ async def on_avatar_metadata_info(self, session: BaseSession, info: Info):
50
+ hash_ = info["id"]
51
+
52
+ if session.user.avatar_hash == hash_:
53
+ session.log.debug("We already know this avatar hash")
54
+ return
55
+ self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
56
+
57
+ if hash_:
58
+ try:
59
+ iq = await self.xmpp.plugin["xep_0084"].retrieve_avatar(
60
+ session.user_jid, hash_, ifrom=self.xmpp.boundjid.bare
61
+ )
62
+ except IqError as e:
63
+ session.log.warning("Could not fetch the user's avatar: %s", e)
64
+ return
65
+ bytes_ = iq["pubsub"]["items"]["item"]["avatar_data"]["value"]
66
+ type_ = info["type"]
67
+ height = info["height"]
68
+ width = info["width"]
69
+ else:
70
+ bytes_ = type_ = height = width = hash_ = None
71
+ try:
72
+ await session.on_avatar(bytes_, hash_, type_, width, height)
73
+ except NotImplementedError:
74
+ pass
75
+ except Exception as e:
76
+ # If something goes wrong here, replying an error stanza will to the
77
+ # avatar update will likely not show in most clients, so let's send
78
+ # a normal message from the component to the user.
79
+ session.send_gateway_message(
80
+ f"Something went wrong trying to set your avatar: {e!r}"
81
+ )
82
+
83
+
84
+ log = logging.getLogger(__name__)
@@ -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 _xmpp_to_legacy_thread(session, msg, e)
100
+ return session, e, legacy_thread
101
+
102
+
103
+ def _ignore(session: "BaseSession", msg: Message):
104
+ i = msg.get_id()
105
+ if i.startswith("slidge-carbon-"):
106
+ return True
107
+ if i not in session.ignore_messages:
108
+ return False
109
+ session.log.debug("Ignored sent carbon: %s", i)
110
+ session.ignore_messages.remove(i)
111
+ return True
112
+
113
+
114
+ async def _xmpp_to_legacy_thread(
115
+ session: "BaseSession", msg: Message, recipient: RecipientType
116
+ ):
117
+ xmpp_thread = msg["thread"]
118
+ if not xmpp_thread:
119
+ return
120
+
121
+ if session.MESSAGE_IDS_ARE_THREAD_IDS:
122
+ return session.xmpp.store.sent.get_legacy_thread(session.user_pk, xmpp_thread)
123
+
124
+ async with session.thread_creation_lock:
125
+ legacy_thread_str = session.xmpp.store.sent.get_legacy_thread(
126
+ session.user_pk, xmpp_thread
127
+ )
128
+ if legacy_thread_str is None:
129
+ legacy_thread = str(await recipient.create_thread(xmpp_thread))
130
+ session.xmpp.store.sent.set_thread(
131
+ session.user_pk, xmpp_thread, legacy_thread
132
+ )
133
+ return session.xmpp.LEGACY_MSG_ID_TYPE(legacy_thread)
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: