slidge 0.1.3__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.
Files changed (102) hide show
  1. slidge/__init__.py +3 -5
  2. slidge/__main__.py +2 -197
  3. slidge/__version__.py +5 -0
  4. slidge/command/adhoc.py +40 -17
  5. slidge/command/admin.py +24 -12
  6. slidge/command/base.py +10 -8
  7. slidge/command/categories.py +13 -3
  8. slidge/command/chat_command.py +29 -2
  9. slidge/command/register.py +32 -16
  10. slidge/command/user.py +106 -13
  11. slidge/contact/contact.py +254 -50
  12. slidge/contact/roster.py +124 -53
  13. slidge/core/config.py +19 -13
  14. slidge/core/dispatcher/__init__.py +3 -0
  15. slidge/core/{gateway → dispatcher}/caps.py +12 -8
  16. slidge/core/{gateway → dispatcher}/disco.py +10 -18
  17. slidge/core/dispatcher/message/__init__.py +10 -0
  18. slidge/core/dispatcher/message/chat_state.py +40 -0
  19. slidge/core/dispatcher/message/marker.py +62 -0
  20. slidge/core/dispatcher/message/message.py +397 -0
  21. slidge/core/dispatcher/muc/__init__.py +12 -0
  22. slidge/core/dispatcher/muc/admin.py +98 -0
  23. slidge/core/{gateway → dispatcher/muc}/mam.py +25 -17
  24. slidge/core/dispatcher/muc/misc.py +121 -0
  25. slidge/core/dispatcher/muc/owner.py +96 -0
  26. slidge/core/{gateway → dispatcher/muc}/ping.py +11 -17
  27. slidge/core/dispatcher/presence.py +176 -0
  28. slidge/core/dispatcher/registration.py +85 -0
  29. slidge/core/{gateway → dispatcher}/search.py +9 -16
  30. slidge/core/dispatcher/session_dispatcher.py +84 -0
  31. slidge/core/dispatcher/util.py +174 -0
  32. slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +35 -19
  33. slidge/core/{gateway/base.py → gateway.py} +176 -153
  34. slidge/core/mixins/__init__.py +11 -1
  35. slidge/core/mixins/attachment.py +106 -67
  36. slidge/core/mixins/avatar.py +94 -25
  37. slidge/core/mixins/base.py +10 -4
  38. slidge/core/mixins/db.py +18 -0
  39. slidge/core/mixins/disco.py +0 -10
  40. slidge/core/mixins/lock.py +10 -8
  41. slidge/core/mixins/message.py +11 -195
  42. slidge/core/mixins/message_maker.py +17 -9
  43. slidge/core/mixins/message_text.py +211 -0
  44. slidge/core/mixins/presence.py +17 -4
  45. slidge/core/pubsub.py +114 -288
  46. slidge/core/session.py +101 -40
  47. slidge/db/__init__.py +4 -0
  48. slidge/db/alembic/__init__.py +0 -0
  49. slidge/db/alembic/env.py +64 -0
  50. slidge/db/alembic/old_user_store.py +183 -0
  51. slidge/db/alembic/script.py.mako +26 -0
  52. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  53. slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +85 -0
  54. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
  55. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  56. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
  57. slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +52 -0
  58. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
  59. slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +61 -0
  60. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
  61. slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
  62. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +139 -0
  63. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +101 -0
  64. slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +79 -0
  65. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  66. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +52 -0
  67. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
  68. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  69. slidge/db/avatar.py +205 -0
  70. slidge/db/meta.py +72 -0
  71. slidge/db/models.py +405 -0
  72. slidge/db/store.py +1257 -0
  73. slidge/group/archive.py +58 -14
  74. slidge/group/bookmarks.py +89 -65
  75. slidge/group/participant.py +107 -40
  76. slidge/group/room.py +402 -213
  77. slidge/main.py +202 -0
  78. slidge/migration.py +45 -1
  79. slidge/slixfix/__init__.py +31 -1
  80. slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
  81. slidge/slixfix/roster.py +13 -4
  82. slidge/slixfix/xep_0292/vcard4.py +1 -87
  83. slidge/util/archive_msg.py +2 -1
  84. slidge/util/db.py +4 -228
  85. slidge/util/test.py +91 -4
  86. slidge/util/types.py +39 -4
  87. slidge/util/util.py +45 -2
  88. {slidge-0.1.3.dist-info → slidge-0.2.0.dist-info}/METADATA +10 -5
  89. slidge-0.2.0.dist-info/RECORD +131 -0
  90. slidge-0.2.0.dist-info/entry_points.txt +3 -0
  91. slidge/core/cache.py +0 -183
  92. slidge/core/gateway/__init__.py +0 -3
  93. slidge/core/gateway/muc_admin.py +0 -35
  94. slidge/core/gateway/presence.py +0 -95
  95. slidge/core/gateway/registration.py +0 -53
  96. slidge/core/gateway/session_dispatcher.py +0 -804
  97. slidge/util/schema.sql +0 -126
  98. slidge/util/sql.py +0 -508
  99. slidge-0.1.3.dist-info/RECORD +0 -96
  100. slidge-0.1.3.dist-info/entry_points.txt +0 -3
  101. {slidge-0.1.3.dist-info → slidge-0.2.0.dist-info}/LICENSE +0 -0
  102. {slidge-0.1.3.dist-info → slidge-0.2.0.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,40 +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
7
- from ...util.db import user_store
6
+ from ....group import LegacyMUC
7
+ from ..util import DispatcherMixin, exceptions_to_xmpp_errors
8
8
 
9
9
  if TYPE_CHECKING:
10
- from .base import BaseGateway
10
+ from slidge.core.gateway import BaseGateway
11
11
 
12
12
 
13
- class Ping:
13
+ class PingMixin(DispatcherMixin):
14
14
  def __init__(self, xmpp: "BaseGateway"):
15
- self.xmpp = xmpp
15
+ super().__init__(xmpp)
16
16
 
17
17
  xmpp.remove_handler("Ping")
18
18
  xmpp.register_handler(
19
19
  CoroutineCallback(
20
20
  "Ping",
21
21
  StanzaPath("iq@type=get/ping"),
22
- self.__handle_ping, # type:ignore
22
+ self.__handle_ping,
23
23
  )
24
24
  )
25
25
  xmpp.plugin["xep_0030"].add_feature("urn:xmpp:ping")
26
26
 
27
- async def __handle_ping(self, iq: Iq):
27
+ @exceptions_to_xmpp_errors
28
+ async def __handle_ping(self, iq: Iq) -> None:
28
29
  ito = iq.get_to()
29
-
30
30
  if ito == self.xmpp.boundjid.bare:
31
31
  iq.reply().send()
32
32
 
33
- ifrom = iq.get_from()
34
- user = user_store.get_by_jid(ifrom)
35
- if user is None:
36
- raise XMPPError("registration-required")
37
-
38
- session = self.xmpp.get_session_from_user(user)
39
- session.raise_if_not_logged()
33
+ session = await self._get_session(iq)
40
34
 
41
35
  try:
42
36
  muc = await session.bookmarks.by_jid(ito)
@@ -59,8 +53,8 @@ class Ping:
59
53
  )
60
54
 
61
55
  @staticmethod
62
- def __handle_muc_ping(muc: LegacyMUC, iq: Iq):
63
- if iq.get_from().resource in muc.user_resources:
56
+ def __handle_muc_ping(muc: LegacyMUC, iq: Iq) -> None:
57
+ if iq.get_from().resource in muc.get_user_resources():
64
58
  iq.reply().send()
65
59
  else:
66
60
  raise XMPPError("not-acceptable", etype="cancel", by=muc.jid)
@@ -0,0 +1,176 @@
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 XMPPError does not have a way to
158
+ # add the <x xmlns="http://jabber.org/protocol/muc" /> element
159
+
160
+ error_stanza = p.error()
161
+ error_stanza.set_to(p.get_from())
162
+ error_stanza.set_from(pto)
163
+ error_stanza.enable("muc_join") # <x xmlns="http://jabber.org/protocol/muc" />
164
+ error_stanza.enable("error")
165
+ error_stanza["error"]["type"] = "cancel"
166
+ error_stanza["error"]["by"] = muc.jid
167
+ error_stanza["error"]["condition"] = "not-acceptable"
168
+ error_stanza["error"][
169
+ "text"
170
+ ] = "Slidge does not let you change your nickname in groups."
171
+ error_stanza.send()
172
+
173
+
174
+ _USEFUL_PRESENCES = {"available", "unavailable", "away", "chat", "dnd", "xa"}
175
+
176
+ log = logging.getLogger(__name__)
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from typing import TYPE_CHECKING, Optional
5
+
6
+ from slixmpp import JID, Iq
7
+ from slixmpp.exceptions import XMPPError
8
+
9
+ from ...db import GatewayUser
10
+ from .. import config
11
+ from .util import DispatcherMixin
12
+
13
+ if TYPE_CHECKING:
14
+ from slidge.core.gateway import BaseGateway
15
+
16
+
17
+ class RegistrationMixin(DispatcherMixin):
18
+ def __init__(self, xmpp: "BaseGateway"):
19
+ super().__init__(xmpp)
20
+ self.xmpp = xmpp
21
+ xmpp["xep_0077"].api.register(
22
+ self.xmpp.make_registration_form, "make_registration_form"
23
+ )
24
+ xmpp["xep_0077"].api.register(self._user_get, "user_get")
25
+ xmpp["xep_0077"].api.register(self._user_validate, "user_validate")
26
+ xmpp["xep_0077"].api.register(self._user_modify, "user_modify")
27
+ # kept for slixmpp internal API compat
28
+ # TODO: either fully use slixmpp internal API or rewrite registration without it at all
29
+ xmpp["xep_0077"].api.register(lambda *a: None, "user_remove")
30
+
31
+ xmpp.add_event_handler("user_register", self._on_user_register)
32
+ xmpp.add_event_handler("user_unregister", self._on_user_unregister)
33
+
34
+ def get_user(self, jid: JID) -> GatewayUser | None:
35
+ return self.xmpp.store.users.get(jid)
36
+
37
+ async def _user_get(
38
+ self, _gateway_jid, _node, ifrom: JID, iq: Iq
39
+ ) -> GatewayUser | None:
40
+ if ifrom is None:
41
+ ifrom = iq.get_from()
42
+ return self.get_user(ifrom)
43
+
44
+ async def _user_validate(self, _gateway_jid, _node, ifrom: JID, iq: Iq):
45
+ xmpp = self.xmpp
46
+ log.debug("User validate: %s", ifrom.bare)
47
+ form_dict = {f.var: iq.get(f.var) for f in xmpp.REGISTRATION_FIELDS}
48
+ xmpp.raise_if_not_allowed_jid(ifrom)
49
+ legacy_module_data = await xmpp.user_prevalidate(ifrom, form_dict)
50
+ if legacy_module_data is None:
51
+ legacy_module_data = form_dict
52
+ user = self.xmpp.store.users.new(
53
+ jid=ifrom,
54
+ legacy_module_data=legacy_module_data, # type:ignore
55
+ )
56
+ log.info("New user: %s", user)
57
+
58
+ async def _user_modify(
59
+ self, _gateway_jid, _node, ifrom: JID, form_dict: dict[str, Optional[str]]
60
+ ):
61
+ await self.xmpp.user_prevalidate(ifrom, form_dict)
62
+ log.debug("Modify user: %s", ifrom)
63
+ user = self.xmpp.store.users.get(ifrom)
64
+ if user is None:
65
+ raise XMPPError("internal-server-error", "User not found")
66
+ user.legacy_module_data.update(form_dict)
67
+ self.xmpp.store.users.update(user)
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
+
84
+
85
+ log = logging.getLogger(__name__)
@@ -3,15 +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.db import user_store
6
+ from .util import DispatcherMixin, exceptions_to_xmpp_errors
7
7
 
8
8
  if TYPE_CHECKING:
9
- from .base import BaseGateway
9
+ from slidge.core.gateway import BaseGateway
10
10
 
11
11
 
12
- class Search:
12
+ class SearchMixin(DispatcherMixin):
13
13
  def __init__(self, xmpp: "BaseGateway"):
14
- self.xmpp = xmpp
14
+ super().__init__(xmpp)
15
15
 
16
16
  xmpp["xep_0055"].api.register(self.search_get_form, "search_get_form")
17
17
  xmpp["xep_0055"].api.register(self._search_query, "search_query")
@@ -29,7 +29,7 @@ class Search:
29
29
  """
30
30
  Prepare the search form using :attr:`.BaseSession.SEARCH_FIELDS`
31
31
  """
32
- user = user_store.get_by_jid(ifrom)
32
+ user = self.xmpp.store.users.get(ifrom)
33
33
  if user is None:
34
34
  raise XMPPError(text="Search is only allowed for registered users")
35
35
 
@@ -47,13 +47,9 @@ class Search:
47
47
  """
48
48
  Handles a search request
49
49
  """
50
- user = user_store.get_by_jid(ifrom)
51
- if user is None:
52
- raise XMPPError(text="Search is only allowed for registered users")
50
+ session = await self._get_session(iq)
53
51
 
54
- result = await self.xmpp.get_session_from_stanza(iq).on_search(
55
- iq["search"]["form"].get_values()
56
- )
52
+ result = await session.on_search(iq["search"]["form"].get_values())
57
53
 
58
54
  if not result:
59
55
  raise XMPPError("item-not-found", text="Nothing was found")
@@ -66,19 +62,17 @@ class Search:
66
62
  form.add_item(item)
67
63
  return reply
68
64
 
65
+ @exceptions_to_xmpp_errors
69
66
  async def _handle_gateway_iq(self, iq: Iq):
70
67
  if iq.get_to() != self.xmpp.boundjid.bare:
71
68
  raise XMPPError("bad-request", "This can only be used on the component JID")
72
69
 
73
- user = user_store.get_by_jid(iq.get_from())
74
- if user is None:
75
- raise XMPPError("not-authorized", "Register to the gateway first")
76
-
77
70
  if len(self.xmpp.SEARCH_FIELDS) > 1:
78
71
  raise XMPPError(
79
72
  "feature-not-implemented", "Use jabber search for this gateway"
80
73
  )
81
74
 
75
+ session = await self._get_session(iq)
82
76
  field = self.xmpp.SEARCH_FIELDS[0]
83
77
 
84
78
  reply = iq.reply()
@@ -87,7 +81,6 @@ class Search:
87
81
  reply["gateway"]["prompt"] = field.label
88
82
  elif iq["type"] == "set":
89
83
  prompt = iq["gateway"]["prompt"]
90
- session = self.xmpp.session_cls.from_user(user)
91
84
  result = await session.on_search({field.var: prompt})
92
85
  if result is None or not result.items:
93
86
  raise XMPPError(