slidge 0.2.0a8__py3-none-any.whl → 0.2.0a10__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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: