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.
- slidge/__main__.py +2 -3
- slidge/__version__.py +1 -1
- slidge/command/adhoc.py +1 -1
- slidge/command/base.py +4 -4
- slidge/command/user.py +5 -1
- slidge/contact/roster.py +9 -0
- slidge/core/config.py +0 -3
- slidge/core/dispatcher/__init__.py +3 -0
- slidge/core/{gateway → dispatcher}/caps.py +6 -4
- slidge/core/{gateway → dispatcher}/disco.py +11 -17
- 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 +26 -15
- slidge/core/dispatcher/muc/misc.py +121 -0
- slidge/core/dispatcher/muc/owner.py +96 -0
- slidge/core/{gateway → dispatcher/muc}/ping.py +10 -15
- slidge/core/dispatcher/presence.py +177 -0
- slidge/core/{gateway → dispatcher}/registration.py +23 -2
- slidge/core/{gateway → dispatcher}/search.py +9 -14
- slidge/core/dispatcher/session_dispatcher.py +84 -0
- slidge/core/dispatcher/util.py +174 -0
- slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +26 -12
- slidge/core/{gateway/base.py → gateway.py} +42 -137
- slidge/core/mixins/attachment.py +24 -8
- slidge/core/mixins/base.py +2 -2
- slidge/core/mixins/lock.py +10 -8
- slidge/core/mixins/message.py +9 -203
- slidge/core/mixins/message_text.py +211 -0
- slidge/core/pubsub.py +2 -1
- slidge/core/session.py +28 -2
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +83 -0
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +12 -1
- slidge/db/models.py +16 -1
- slidge/db/store.py +144 -11
- slidge/group/bookmarks.py +23 -1
- slidge/group/participant.py +5 -5
- slidge/group/room.py +10 -1
- slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
- slidge/util/test.py +9 -5
- slidge/util/types.py +6 -0
- slidge/util/util.py +5 -2
- {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/METADATA +2 -1
- {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/RECORD +51 -40
- slidge-0.2.0b0.dist-info/entry_points.txt +3 -0
- 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/session_dispatcher.py +0 -895
- slidge-0.2.0a9.dist-info/entry_points.txt +0 -3
- {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/LICENSE +0 -0
- {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
|
6
|
+
from ....group import LegacyMUC
|
7
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
7
8
|
|
8
9
|
if TYPE_CHECKING:
|
9
|
-
from .
|
10
|
+
from slidge.core.gateway import BaseGateway
|
10
11
|
|
11
12
|
|
12
|
-
class
|
13
|
+
class PingMixin(DispatcherMixin):
|
13
14
|
def __init__(self, xmpp: "BaseGateway"):
|
14
|
-
|
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,
|
22
|
+
self.__handle_ping,
|
22
23
|
)
|
23
24
|
)
|
24
25
|
xmpp.plugin["xep_0030"].add_feature("urn:xmpp:ping")
|
25
26
|
|
26
|
-
|
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
|
-
|
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 .
|
14
|
+
from slidge.core.gateway import BaseGateway
|
13
15
|
|
14
16
|
|
15
|
-
class
|
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 .
|
9
|
+
from slidge.core.gateway import BaseGateway
|
8
10
|
|
9
11
|
|
10
|
-
class
|
12
|
+
class SearchMixin(DispatcherMixin):
|
11
13
|
def __init__(self, xmpp: "BaseGateway"):
|
12
|
-
|
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
|
-
|
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
|
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__)
|