slidge 0.2.0a9__py3-none-any.whl → 0.2.0b0__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 (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,121 @@
1
+ import logging
2
+
3
+ from slixmpp import JID, CoroutineCallback, Iq, Message, Presence, StanzaPath
4
+ from slixmpp.exceptions import XMPPError
5
+
6
+ from ..util import DispatcherMixin, exceptions_to_xmpp_errors
7
+
8
+
9
+ class MucMiscMixin(DispatcherMixin):
10
+ def __init__(self, xmpp):
11
+ super().__init__(xmpp)
12
+ xmpp.register_handler(
13
+ CoroutineCallback(
14
+ "ibr_remove", StanzaPath("/iq/register"), self.on_ibr_remove
15
+ )
16
+ )
17
+
18
+ xmpp.add_event_handler("groupchat_join", self.on_groupchat_join)
19
+ xmpp.add_event_handler(
20
+ "groupchat_direct_invite", self.on_groupchat_direct_invite
21
+ )
22
+ xmpp.add_event_handler("groupchat_subject", self.on_groupchat_subject)
23
+ xmpp.add_event_handler("groupchat_message_error", self.__on_group_chat_error)
24
+
25
+ async def __on_group_chat_error(self, msg: Message):
26
+ condition = msg["error"].get_condition()
27
+ if condition not in KICKABLE_ERRORS:
28
+ return
29
+
30
+ try:
31
+ muc = await self.get_muc_from_stanza(msg)
32
+ except XMPPError as e:
33
+ log.debug("Not removing resource", exc_info=e)
34
+ return
35
+ mfrom = msg.get_from()
36
+ resource = mfrom.resource
37
+ try:
38
+ muc.remove_user_resource(resource)
39
+ except KeyError:
40
+ # this actually happens quite frequently on for both beagle and monal
41
+ # (not sure why?), but is of no consequence
42
+ log.debug("%s was not in the resources of %s", resource, muc)
43
+ else:
44
+ log.info(
45
+ "Removed %s from the resources of %s because of error", resource, muc
46
+ )
47
+
48
+ @exceptions_to_xmpp_errors
49
+ async def on_ibr_remove(self, iq: Iq):
50
+ if iq.get_to() == self.xmpp.boundjid.bare:
51
+ return
52
+
53
+ if iq["type"] == "set" and iq["register"]["remove"]:
54
+ muc = await self.get_muc_from_stanza(iq)
55
+ await muc.session.on_leave_group(muc.legacy_id)
56
+ iq.reply().send()
57
+ await muc.session.bookmarks.remove(
58
+ muc, "You left this chat from an XMPP client."
59
+ )
60
+ return
61
+
62
+ raise XMPPError("feature-not-implemented")
63
+
64
+ @exceptions_to_xmpp_errors
65
+ async def on_groupchat_join(self, p: Presence):
66
+ if not self.xmpp.GROUPS:
67
+ raise XMPPError(
68
+ "feature-not-implemented",
69
+ "This gateway does not implement multi-user chats.",
70
+ )
71
+ muc = await self.get_muc_from_stanza(p)
72
+ await muc.join(p)
73
+
74
+ @exceptions_to_xmpp_errors
75
+ async def on_groupchat_direct_invite(self, msg: Message):
76
+ invite = msg["groupchat_invite"]
77
+ jid = JID(invite["jid"])
78
+
79
+ if jid.domain != self.xmpp.boundjid.bare:
80
+ raise XMPPError(
81
+ "bad-request",
82
+ "Legacy contacts can only be invited to legacy groups, not standard XMPP MUCs.",
83
+ )
84
+
85
+ if invite["password"]:
86
+ raise XMPPError(
87
+ "bad-request", "Password-protected groups are not supported"
88
+ )
89
+
90
+ session = await self._get_session(msg, logged=True)
91
+ contact = await session.contacts.by_jid(msg.get_to())
92
+ muc = await session.bookmarks.by_jid(jid)
93
+
94
+ await session.on_invitation(contact, muc, invite["reason"] or None)
95
+
96
+ @exceptions_to_xmpp_errors
97
+ async def on_groupchat_subject(self, msg: Message):
98
+ muc = await self.get_muc_from_stanza(msg)
99
+ if not muc.HAS_SUBJECT:
100
+ raise XMPPError(
101
+ "bad-request",
102
+ "There are no room subject in here. "
103
+ "Use the room configuration to update its name or description",
104
+ )
105
+ await muc.on_set_subject(msg["subject"])
106
+
107
+
108
+ KICKABLE_ERRORS = {
109
+ "gone",
110
+ "internal-server-error",
111
+ "item-not-found",
112
+ "jid-malformed",
113
+ "recipient-unavailable",
114
+ "redirect",
115
+ "remote-server-not-found",
116
+ "remote-server-timeout",
117
+ "service-unavailable",
118
+ "malformed error",
119
+ }
120
+
121
+ log = logging.getLogger(__name__)
@@ -0,0 +1,96 @@
1
+ from slixmpp import CoroutineCallback, Iq, StanzaPath
2
+ from slixmpp.exceptions import XMPPError
3
+ from slixmpp.plugins.xep_0004 import Form
4
+ from slixmpp.xmlstream import StanzaBase
5
+
6
+ from ..util import DispatcherMixin, exceptions_to_xmpp_errors
7
+
8
+
9
+ class MucOwnerMixin(DispatcherMixin):
10
+ def __init__(self, xmpp):
11
+ super().__init__(xmpp)
12
+ xmpp.register_handler(
13
+ CoroutineCallback(
14
+ "MUCOwnerGet",
15
+ StanzaPath("iq@type=get/mucowner_query"),
16
+ self.on_muc_owner_query,
17
+ )
18
+ )
19
+ xmpp.register_handler(
20
+ CoroutineCallback(
21
+ "MUCOwnerSet",
22
+ StanzaPath("iq@type=set/mucowner_query"),
23
+ self.on_muc_owner_set,
24
+ )
25
+ )
26
+
27
+ @exceptions_to_xmpp_errors
28
+ async def on_muc_owner_query(self, iq: StanzaBase) -> None:
29
+ assert isinstance(iq, Iq)
30
+ muc = await self.get_muc_from_stanza(iq)
31
+
32
+ reply = iq.reply()
33
+
34
+ form = Form(title="Slidge room configuration")
35
+ form["instructions"] = (
36
+ "Complete this form to modify the configuration of your room."
37
+ )
38
+ form.add_field(
39
+ var="FORM_TYPE",
40
+ type="hidden",
41
+ value="http://jabber.org/protocol/muc#roomconfig",
42
+ )
43
+ form.add_field(
44
+ var="muc#roomconfig_roomname",
45
+ label="Natural-Language Room Name",
46
+ type="text-single",
47
+ value=muc.name,
48
+ )
49
+ if muc.HAS_DESCRIPTION:
50
+ form.add_field(
51
+ var="muc#roomconfig_roomdesc",
52
+ label="Short Description of Room",
53
+ type="text-single",
54
+ value=muc.description,
55
+ )
56
+
57
+ muc_owner = iq["mucowner_query"]
58
+ muc_owner.append(form)
59
+ reply.append(muc_owner)
60
+ reply.send()
61
+
62
+ @exceptions_to_xmpp_errors
63
+ async def on_muc_owner_set(self, iq: StanzaBase) -> None:
64
+ assert isinstance(iq, Iq)
65
+ muc = await self.get_muc_from_stanza(iq)
66
+ query = iq["mucowner_query"]
67
+
68
+ if form := query.get_plugin("form", check=True):
69
+ values = form.get_values()
70
+ await muc.on_set_config(
71
+ name=values.get("muc#roomconfig_roomname"),
72
+ description=(
73
+ values.get("muc#roomconfig_roomdesc")
74
+ if muc.HAS_DESCRIPTION
75
+ else None
76
+ ),
77
+ )
78
+ form["type"] = "result"
79
+ clear = False
80
+ elif destroy := query.get_plugin("destroy", check=True):
81
+ reason = destroy["reason"] or None
82
+ await muc.on_destroy_request(reason)
83
+ user_participant = await muc.get_user_participant()
84
+ user_participant._affiliation = "none"
85
+ user_participant._role = "none"
86
+ presence = user_participant._make_presence(ptype="unavailable", force=True)
87
+ presence["muc"].enable("destroy")
88
+ if reason is not None:
89
+ presence["muc"]["destroy"]["reason"] = reason
90
+ user_participant._send(presence)
91
+ await muc.session.bookmarks.remove(muc, kick=False)
92
+ clear = True
93
+ else:
94
+ raise XMPPError("bad-request")
95
+
96
+ iq.reply(clear=clear).send()
@@ -3,39 +3,34 @@ from typing import TYPE_CHECKING
3
3
  from slixmpp import CoroutineCallback, Iq, StanzaPath
4
4
  from slixmpp.exceptions import XMPPError
5
5
 
6
- from ...group import LegacyMUC
6
+ from ....group import LegacyMUC
7
+ from ..util import DispatcherMixin, exceptions_to_xmpp_errors
7
8
 
8
9
  if TYPE_CHECKING:
9
- from .base import BaseGateway
10
+ from slidge.core.gateway import BaseGateway
10
11
 
11
12
 
12
- class Ping:
13
+ class PingMixin(DispatcherMixin):
13
14
  def __init__(self, xmpp: "BaseGateway"):
14
- self.xmpp = xmpp
15
+ super().__init__(xmpp)
15
16
 
16
17
  xmpp.remove_handler("Ping")
17
18
  xmpp.register_handler(
18
19
  CoroutineCallback(
19
20
  "Ping",
20
21
  StanzaPath("iq@type=get/ping"),
21
- self.__handle_ping, # type:ignore
22
+ self.__handle_ping,
22
23
  )
23
24
  )
24
25
  xmpp.plugin["xep_0030"].add_feature("urn:xmpp:ping")
25
26
 
26
- async def __handle_ping(self, iq: Iq):
27
+ @exceptions_to_xmpp_errors
28
+ async def __handle_ping(self, iq: Iq) -> None:
27
29
  ito = iq.get_to()
28
-
29
30
  if ito == self.xmpp.boundjid.bare:
30
31
  iq.reply().send()
31
32
 
32
- ifrom = iq.get_from()
33
- user = self.xmpp.store.users.get(ifrom)
34
- if user is None:
35
- raise XMPPError("registration-required")
36
-
37
- session = self.xmpp.get_session_from_user(user)
38
- session.raise_if_not_logged()
33
+ session = await self._get_session(iq)
39
34
 
40
35
  try:
41
36
  muc = await session.bookmarks.by_jid(ito)
@@ -58,7 +53,7 @@ class Ping:
58
53
  )
59
54
 
60
55
  @staticmethod
61
- def __handle_muc_ping(muc: LegacyMUC, iq: Iq):
56
+ def __handle_muc_ping(muc: LegacyMUC, iq: Iq) -> None:
62
57
  if iq.get_from().resource in muc.get_user_resources():
63
58
  iq.reply().send()
64
59
  else:
@@ -0,0 +1,177 @@
1
+ import logging
2
+
3
+ from slixmpp import JID, Presence
4
+ from slixmpp.exceptions import XMPPError
5
+
6
+ from ...util.util import merge_resources
7
+ from ..session import BaseSession
8
+ from .util import DispatcherMixin, exceptions_to_xmpp_errors
9
+
10
+
11
+ class _IsDirectedAtComponent(Exception):
12
+ def __init__(self, session: BaseSession):
13
+ self.session = session
14
+
15
+
16
+ class PresenceHandlerMixin(DispatcherMixin):
17
+ def __init__(self, xmpp):
18
+ super().__init__(xmpp)
19
+
20
+ xmpp.add_event_handler("presence_subscribe", self._handle_subscribe)
21
+ xmpp.add_event_handler("presence_subscribed", self._handle_subscribed)
22
+ xmpp.add_event_handler("presence_unsubscribe", self._handle_unsubscribe)
23
+ xmpp.add_event_handler("presence_unsubscribed", self._handle_unsubscribed)
24
+ xmpp.add_event_handler("presence_probe", self._handle_probe)
25
+ xmpp.add_event_handler("presence", self.on_presence)
26
+
27
+ async def __get_contact(self, pres: Presence):
28
+ sess = await self._get_session(pres)
29
+ pto = pres.get_to()
30
+ if pto == self.xmpp.boundjid.bare:
31
+ raise _IsDirectedAtComponent(sess)
32
+ await sess.contacts.ready
33
+ return await sess.contacts.by_jid(pto)
34
+
35
+ @exceptions_to_xmpp_errors
36
+ async def _handle_subscribe(self, pres: Presence):
37
+ try:
38
+ contact = await self.__get_contact(pres)
39
+ except _IsDirectedAtComponent:
40
+ pres.reply().send()
41
+ return
42
+
43
+ if contact.is_friend:
44
+ pres.reply().send()
45
+ else:
46
+ await contact.on_friend_request(pres["status"])
47
+
48
+ @exceptions_to_xmpp_errors
49
+ async def _handle_unsubscribe(self, pres: Presence):
50
+ pres.reply().send()
51
+
52
+ try:
53
+ contact = await self.__get_contact(pres)
54
+ except _IsDirectedAtComponent as e:
55
+ e.session.send_gateway_message("Bye bye!")
56
+ await e.session.kill_by_jid(e.session.user_jid)
57
+ return
58
+
59
+ contact.is_friend = False
60
+ await contact.on_friend_delete(pres["status"])
61
+
62
+ @exceptions_to_xmpp_errors
63
+ async def _handle_subscribed(self, pres: Presence):
64
+ try:
65
+ contact = await self.__get_contact(pres)
66
+ except _IsDirectedAtComponent:
67
+ return
68
+
69
+ await contact.on_friend_accept()
70
+
71
+ @exceptions_to_xmpp_errors
72
+ async def _handle_unsubscribed(self, pres: Presence):
73
+ try:
74
+ contact = await self.__get_contact(pres)
75
+ except _IsDirectedAtComponent:
76
+ return
77
+
78
+ if contact.is_friend:
79
+ contact.is_friend = False
80
+ await contact.on_friend_delete(pres["status"])
81
+
82
+ @exceptions_to_xmpp_errors
83
+ async def _handle_probe(self, pres: Presence):
84
+ try:
85
+ contact = await self.__get_contact(pres)
86
+ except _IsDirectedAtComponent:
87
+ session = await self._get_session(pres)
88
+ session.send_cached_presence(pres.get_from())
89
+ return
90
+ if contact.is_friend:
91
+ contact.send_last_presence(force=True)
92
+ else:
93
+ reply = pres.reply()
94
+ reply["type"] = "unsubscribed"
95
+ reply.send()
96
+
97
+ @exceptions_to_xmpp_errors
98
+ async def on_presence(self, p: Presence):
99
+ if p.get_plugin("muc_join", check=True):
100
+ # handled in on_groupchat_join
101
+ # without this early return, since we switch from and to in this
102
+ # presence stanza, on_groupchat_join ends up trying to instantiate
103
+ # a MUC with the user's JID, which in turn leads to slidge sending
104
+ # a (error) presence from=the user's JID, which terminates the
105
+ # XML stream.
106
+ return
107
+
108
+ session = await self._get_session(p)
109
+
110
+ pto = p.get_to()
111
+ if pto == self.xmpp.boundjid.bare:
112
+ session.log.debug("Received a presence from %s", p.get_from())
113
+ if (ptype := p.get_type()) not in _USEFUL_PRESENCES:
114
+ return
115
+ if not session.user.preferences.get("sync_presence", False):
116
+ session.log.debug("User does not want to sync their presence")
117
+ return
118
+ # NB: get_type() returns either a proper presence type or
119
+ # a presence show if available. Weird, weird, weird slix.
120
+ resources = self.xmpp.roster[self.xmpp.boundjid.bare][
121
+ p.get_from()
122
+ ].resources
123
+ await session.on_presence(
124
+ p.get_from().resource,
125
+ ptype, # type: ignore
126
+ p["status"],
127
+ resources,
128
+ merge_resources(resources),
129
+ )
130
+ if p.get_type() == "available":
131
+ await self.xmpp.pubsub.on_presence_available(p, None)
132
+ return
133
+
134
+ if p.get_type() == "available":
135
+ try:
136
+ contact = await session.contacts.by_jid(pto)
137
+ except XMPPError:
138
+ contact = None
139
+ if contact is not None:
140
+ await self.xmpp.pubsub.on_presence_available(p, contact)
141
+ return
142
+
143
+ muc = session.bookmarks.by_jid_only_if_exists(JID(pto.bare))
144
+
145
+ if muc is not None and p.get_type() == "unavailable":
146
+ return muc.on_presence_unavailable(p)
147
+
148
+ if muc is None or p.get_from().resource not in muc.get_user_resources():
149
+ return
150
+
151
+ if pto.resource == muc.user_nick:
152
+ # Ignore presence stanzas with the valid nick.
153
+ # even if joined to the group, we might receive those from clients,
154
+ # when setting a status message, or going away, etc.
155
+ return
156
+
157
+ # We can't use XMPPError here because from must be room@slidge/VALID-USER-NICK
158
+
159
+ error_from = JID(muc.jid)
160
+ error_from.resource = muc.user_nick
161
+ error_stanza = p.error()
162
+ error_stanza.set_to(p.get_from())
163
+ error_stanza.set_from(error_from)
164
+ error_stanza.enable("muc_join")
165
+ error_stanza.enable("error")
166
+ error_stanza["error"]["type"] = "cancel"
167
+ error_stanza["error"]["by"] = muc.jid
168
+ error_stanza["error"]["condition"] = "not-acceptable"
169
+ error_stanza["error"][
170
+ "text"
171
+ ] = "Slidge does not let you change your nickname in groups."
172
+ error_stanza.send()
173
+
174
+
175
+ _USEFUL_PRESENCES = {"available", "unavailable", "away", "chat", "dnd", "xa"}
176
+
177
+ log = logging.getLogger(__name__)
@@ -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__)