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.
- slidge/__init__.py +3 -5
- slidge/__main__.py +2 -197
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +40 -17
- slidge/command/admin.py +24 -12
- slidge/command/base.py +10 -8
- slidge/command/categories.py +13 -3
- slidge/command/chat_command.py +29 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +106 -13
- slidge/contact/contact.py +254 -50
- slidge/contact/roster.py +124 -53
- slidge/core/config.py +19 -13
- slidge/core/dispatcher/__init__.py +3 -0
- slidge/core/{gateway → dispatcher}/caps.py +12 -8
- slidge/core/{gateway → dispatcher}/disco.py +10 -18
- slidge/core/dispatcher/message/__init__.py +10 -0
- slidge/core/dispatcher/message/chat_state.py +40 -0
- slidge/core/dispatcher/message/marker.py +62 -0
- slidge/core/dispatcher/message/message.py +397 -0
- slidge/core/dispatcher/muc/__init__.py +12 -0
- slidge/core/dispatcher/muc/admin.py +98 -0
- slidge/core/{gateway → dispatcher/muc}/mam.py +25 -17
- slidge/core/dispatcher/muc/misc.py +121 -0
- slidge/core/dispatcher/muc/owner.py +96 -0
- slidge/core/{gateway → dispatcher/muc}/ping.py +11 -17
- slidge/core/dispatcher/presence.py +176 -0
- slidge/core/dispatcher/registration.py +85 -0
- slidge/core/{gateway → dispatcher}/search.py +9 -16
- slidge/core/dispatcher/session_dispatcher.py +84 -0
- slidge/core/dispatcher/util.py +174 -0
- slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +35 -19
- slidge/core/{gateway/base.py → gateway.py} +176 -153
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +106 -67
- slidge/core/mixins/avatar.py +94 -25
- slidge/core/mixins/base.py +10 -4
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/lock.py +10 -8
- slidge/core/mixins/message.py +11 -195
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/message_text.py +211 -0
- slidge/core/mixins/presence.py +17 -4
- slidge/core/pubsub.py +114 -288
- slidge/core/session.py +101 -40
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/__init__.py +0 -0
- slidge/db/alembic/env.py +64 -0
- slidge/db/alembic/old_user_store.py +183 -0
- slidge/db/alembic/script.py.mako +26 -0
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +85 -0
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +52 -0
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +61 -0
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +139 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +101 -0
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +79 -0
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +52 -0
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
- slidge/db/avatar.py +205 -0
- slidge/db/meta.py +72 -0
- slidge/db/models.py +405 -0
- slidge/db/store.py +1257 -0
- slidge/group/archive.py +58 -14
- slidge/group/bookmarks.py +89 -65
- slidge/group/participant.py +107 -40
- slidge/group/room.py +402 -213
- slidge/main.py +202 -0
- slidge/migration.py +45 -1
- slidge/slixfix/__init__.py +31 -1
- slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
- slidge/slixfix/roster.py +13 -4
- slidge/slixfix/xep_0292/vcard4.py +1 -87
- slidge/util/archive_msg.py +2 -1
- slidge/util/db.py +4 -228
- slidge/util/test.py +91 -4
- slidge/util/types.py +39 -4
- slidge/util/util.py +45 -2
- {slidge-0.1.3.dist-info → slidge-0.2.0.dist-info}/METADATA +10 -5
- slidge-0.2.0.dist-info/RECORD +131 -0
- slidge-0.2.0.dist-info/entry_points.txt +3 -0
- slidge/core/cache.py +0 -183
- slidge/core/gateway/__init__.py +0 -3
- slidge/core/gateway/muc_admin.py +0 -35
- slidge/core/gateway/presence.py +0 -95
- slidge/core/gateway/registration.py +0 -53
- slidge/core/gateway/session_dispatcher.py +0 -804
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.3.dist-info/RECORD +0 -96
- slidge-0.1.3.dist-info/entry_points.txt +0 -3
- {slidge-0.1.3.dist-info → slidge-0.2.0.dist-info}/LICENSE +0 -0
- {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
|
7
|
-
from
|
6
|
+
from ....group import LegacyMUC
|
7
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
8
8
|
|
9
9
|
if TYPE_CHECKING:
|
10
|
-
from .
|
10
|
+
from slidge.core.gateway import BaseGateway
|
11
11
|
|
12
12
|
|
13
|
-
class
|
13
|
+
class PingMixin(DispatcherMixin):
|
14
14
|
def __init__(self, xmpp: "BaseGateway"):
|
15
|
-
|
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,
|
22
|
+
self.__handle_ping,
|
23
23
|
)
|
24
24
|
)
|
25
25
|
xmpp.plugin["xep_0030"].add_feature("urn:xmpp:ping")
|
26
26
|
|
27
|
-
|
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
|
-
|
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.
|
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
|
6
|
+
from .util import DispatcherMixin, exceptions_to_xmpp_errors
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
|
-
from .
|
9
|
+
from slidge.core.gateway import BaseGateway
|
10
10
|
|
11
11
|
|
12
|
-
class
|
12
|
+
class SearchMixin(DispatcherMixin):
|
13
13
|
def __init__(self, xmpp: "BaseGateway"):
|
14
|
-
|
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 =
|
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
|
-
|
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
|
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(
|