slidge 0.2.0a8__py3-none-any.whl → 0.2.0a10__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/__version__.py +1 -1
- slidge/command/adhoc.py +1 -1
- slidge/command/base.py +4 -4
- slidge/contact/contact.py +3 -2
- slidge/contact/roster.py +7 -0
- 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 +67 -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 +118 -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 +7 -2
- slidge/core/mixins/base.py +2 -2
- slidge/core/mixins/message.py +10 -4
- slidge/core/pubsub.py +2 -1
- slidge/core/session.py +28 -2
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge/db/models.py +13 -0
- slidge/db/store.py +128 -2
- slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
- slidge/util/test.py +5 -1
- slidge/util/types.py +6 -0
- slidge/util/util.py +5 -2
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/METADATA +2 -1
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/RECORD +42 -33
- 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.0a8.dist-info → slidge-0.2.0a10.dist-info}/LICENSE +0 -0
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/WHEEL +0 -0
- {slidge-0.2.0a8.dist-info → slidge-0.2.0a10.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
from .admin import MucAdminMixin
|
2
|
+
from .mam import MamMixin
|
3
|
+
from .misc import MucMiscMixin
|
4
|
+
from .owner import MucOwnerMixin
|
5
|
+
from .ping import PingMixin
|
6
|
+
|
7
|
+
|
8
|
+
class MucMixin(PingMixin, MamMixin, MucAdminMixin, MucOwnerMixin, MucMiscMixin):
|
9
|
+
pass
|
10
|
+
|
11
|
+
|
12
|
+
__all__ = ("MucMixin",)
|
@@ -0,0 +1,98 @@
|
|
1
|
+
from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
|
2
|
+
from slixmpp.exceptions import XMPPError
|
3
|
+
from slixmpp.xmlstream import StanzaBase
|
4
|
+
|
5
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
6
|
+
|
7
|
+
|
8
|
+
class MucAdminMixin(DispatcherMixin):
|
9
|
+
def __init__(self, xmpp) -> None:
|
10
|
+
super().__init__(xmpp)
|
11
|
+
self.xmpp.register_handler(
|
12
|
+
CoroutineCallback(
|
13
|
+
"MUCModerate",
|
14
|
+
StanzaPath("iq/apply_to/moderate"),
|
15
|
+
self.on_user_moderation,
|
16
|
+
)
|
17
|
+
)
|
18
|
+
self.xmpp.register_handler(
|
19
|
+
CoroutineCallback(
|
20
|
+
"MUCSetAffiliation",
|
21
|
+
StanzaPath("iq@type=set/mucadmin_query"),
|
22
|
+
self.on_user_set_affiliation,
|
23
|
+
)
|
24
|
+
)
|
25
|
+
self.xmpp.register_handler(
|
26
|
+
CoroutineCallback(
|
27
|
+
"MUCGetAffiliation",
|
28
|
+
StanzaPath("iq@type=get/mucadmin_query"),
|
29
|
+
self.on_muc_admin_query_get,
|
30
|
+
)
|
31
|
+
)
|
32
|
+
|
33
|
+
@exceptions_to_xmpp_errors
|
34
|
+
async def on_user_moderation(self, iq: StanzaBase) -> None:
|
35
|
+
assert isinstance(iq, Iq)
|
36
|
+
muc = await self.get_muc_from_stanza(iq)
|
37
|
+
|
38
|
+
apply_to = iq["apply_to"]
|
39
|
+
xmpp_id = apply_to["id"]
|
40
|
+
if not xmpp_id:
|
41
|
+
raise XMPPError("bad-request", "Missing moderated message ID")
|
42
|
+
|
43
|
+
moderate = apply_to["moderate"]
|
44
|
+
if not moderate["retract"]:
|
45
|
+
raise XMPPError(
|
46
|
+
"feature-not-implemented",
|
47
|
+
"Slidge only implements moderation/retraction",
|
48
|
+
)
|
49
|
+
|
50
|
+
legacy_id = self._xmpp_msg_id_to_legacy(muc.session, xmpp_id)
|
51
|
+
await muc.session.on_moderate(muc, legacy_id, moderate["reason"] or None)
|
52
|
+
iq.reply(clear=True).send()
|
53
|
+
|
54
|
+
@exceptions_to_xmpp_errors
|
55
|
+
async def on_user_set_affiliation(self, iq: StanzaBase) -> None:
|
56
|
+
assert isinstance(iq, Iq)
|
57
|
+
muc = await self.get_muc_from_stanza(iq)
|
58
|
+
|
59
|
+
item = iq["mucadmin_query"]["item"]
|
60
|
+
if item["jid"]:
|
61
|
+
contact = await muc.session.contacts.by_jid(JID(item["jid"]))
|
62
|
+
else:
|
63
|
+
part = await muc.get_participant(
|
64
|
+
item["nick"], fill_first=True, raise_if_not_found=True
|
65
|
+
)
|
66
|
+
assert part.contact is not None
|
67
|
+
contact = part.contact
|
68
|
+
|
69
|
+
if item["affiliation"]:
|
70
|
+
await muc.on_set_affiliation(
|
71
|
+
contact,
|
72
|
+
item["affiliation"],
|
73
|
+
item["reason"] or None,
|
74
|
+
item["nick"] or None,
|
75
|
+
)
|
76
|
+
elif item["role"] == "none":
|
77
|
+
await muc.on_kick(contact, item["reason"] or None)
|
78
|
+
|
79
|
+
iq.reply(clear=True).send()
|
80
|
+
|
81
|
+
@exceptions_to_xmpp_errors
|
82
|
+
async def on_muc_admin_query_get(self, iq: StanzaBase) -> None:
|
83
|
+
assert isinstance(iq, Iq)
|
84
|
+
affiliation = iq["mucadmin_query"]["item"]["affiliation"]
|
85
|
+
|
86
|
+
if not affiliation:
|
87
|
+
raise XMPPError("bad-request")
|
88
|
+
|
89
|
+
session = await self._get_session(iq, 1, logged=True)
|
90
|
+
muc = await session.bookmarks.by_jid(iq.get_to())
|
91
|
+
|
92
|
+
reply = iq.reply()
|
93
|
+
reply.enable("mucadmin_query")
|
94
|
+
async for participant in muc.get_participants():
|
95
|
+
if not participant.affiliation == affiliation:
|
96
|
+
continue
|
97
|
+
reply["mucadmin_query"].append(participant.mucadmin_item())
|
98
|
+
reply.send()
|
@@ -1,42 +1,57 @@
|
|
1
|
+
import asyncio
|
1
2
|
from typing import TYPE_CHECKING
|
2
3
|
|
3
4
|
from slixmpp import CoroutineCallback, Iq, StanzaPath
|
4
5
|
from slixmpp.exceptions import XMPPError
|
6
|
+
from slixmpp.xmlstream import StanzaBase
|
7
|
+
|
8
|
+
from ... import config
|
9
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
5
10
|
|
6
11
|
if TYPE_CHECKING:
|
7
|
-
from .
|
12
|
+
from slidge.core.gateway import BaseGateway
|
8
13
|
|
9
14
|
|
10
|
-
class
|
15
|
+
class MamMixin(DispatcherMixin):
|
11
16
|
def __init__(self, xmpp: "BaseGateway"):
|
12
|
-
|
17
|
+
super().__init__(xmpp)
|
18
|
+
self.__mam_cleanup_task = xmpp.loop.create_task(self.__mam_cleanup())
|
13
19
|
xmpp.register_handler(
|
14
20
|
CoroutineCallback(
|
15
21
|
"MAM_query",
|
16
22
|
StanzaPath("iq@type=set/mam"),
|
17
|
-
self.__handle_mam,
|
23
|
+
self.__handle_mam,
|
18
24
|
)
|
19
25
|
)
|
20
26
|
xmpp.register_handler(
|
21
27
|
CoroutineCallback(
|
22
28
|
"MAM_get_from",
|
23
29
|
StanzaPath("iq@type=get/mam"),
|
24
|
-
self.__handle_mam_get_form,
|
30
|
+
self.__handle_mam_get_form,
|
25
31
|
)
|
26
32
|
)
|
27
33
|
xmpp.register_handler(
|
28
34
|
CoroutineCallback(
|
29
35
|
"MAM_get_meta",
|
30
36
|
StanzaPath("iq@type=get/mam_metadata"),
|
31
|
-
self.__handle_mam_metadata,
|
37
|
+
self.__handle_mam_metadata,
|
32
38
|
)
|
33
39
|
)
|
34
40
|
|
41
|
+
async def __mam_cleanup(self):
|
42
|
+
if not config.MAM_MAX_DAYS:
|
43
|
+
return
|
44
|
+
while True:
|
45
|
+
await asyncio.sleep(3600 * 6)
|
46
|
+
self.xmpp.store.mam.nuke_older_than(config.MAM_MAX_DAYS)
|
47
|
+
|
48
|
+
@exceptions_to_xmpp_errors
|
35
49
|
async def __handle_mam(self, iq: Iq):
|
36
|
-
muc = await self.
|
50
|
+
muc = await self.get_muc_from_stanza(iq)
|
37
51
|
await muc.send_mam(iq)
|
38
52
|
|
39
|
-
async def __handle_mam_get_form(self, iq:
|
53
|
+
async def __handle_mam_get_form(self, iq: StanzaBase):
|
54
|
+
assert isinstance(iq, Iq)
|
40
55
|
ito = iq.get_to()
|
41
56
|
|
42
57
|
if ito == self.xmpp.boundjid.bare:
|
@@ -44,12 +59,7 @@ class Mam:
|
|
44
59
|
text="No MAM on the component itself, use a JID with a resource"
|
45
60
|
)
|
46
61
|
|
47
|
-
|
48
|
-
if user is None:
|
49
|
-
raise XMPPError("registration-required")
|
50
|
-
|
51
|
-
session = self.xmpp.get_session_from_user(user)
|
52
|
-
|
62
|
+
session = await self._get_session(iq, 0, logged=True)
|
53
63
|
await session.bookmarks.by_jid(ito)
|
54
64
|
|
55
65
|
reply = iq.reply()
|
@@ -67,6 +77,7 @@ class Mam:
|
|
67
77
|
reply["mam"].append(form)
|
68
78
|
reply.send()
|
69
79
|
|
80
|
+
@exceptions_to_xmpp_errors
|
70
81
|
async def __handle_mam_metadata(self, iq: Iq):
|
71
|
-
muc = await self.
|
82
|
+
muc = await self.get_muc_from_stanza(iq)
|
72
83
|
await muc.send_mam_metadata(iq)
|
@@ -0,0 +1,118 @@
|
|
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
|
+
return
|
58
|
+
|
59
|
+
raise XMPPError("feature-not-implemented")
|
60
|
+
|
61
|
+
@exceptions_to_xmpp_errors
|
62
|
+
async def on_groupchat_join(self, p: Presence):
|
63
|
+
if not self.xmpp.GROUPS:
|
64
|
+
raise XMPPError(
|
65
|
+
"feature-not-implemented",
|
66
|
+
"This gateway does not implement multi-user chats.",
|
67
|
+
)
|
68
|
+
muc = await self.get_muc_from_stanza(p)
|
69
|
+
await muc.join(p)
|
70
|
+
|
71
|
+
@exceptions_to_xmpp_errors
|
72
|
+
async def on_groupchat_direct_invite(self, msg: Message):
|
73
|
+
invite = msg["groupchat_invite"]
|
74
|
+
jid = JID(invite["jid"])
|
75
|
+
|
76
|
+
if jid.domain != self.xmpp.boundjid.bare:
|
77
|
+
raise XMPPError(
|
78
|
+
"bad-request",
|
79
|
+
"Legacy contacts can only be invited to legacy groups, not standard XMPP MUCs.",
|
80
|
+
)
|
81
|
+
|
82
|
+
if invite["password"]:
|
83
|
+
raise XMPPError(
|
84
|
+
"bad-request", "Password-protected groups are not supported"
|
85
|
+
)
|
86
|
+
|
87
|
+
session = await self._get_session(msg, logged=True)
|
88
|
+
contact = await session.contacts.by_jid(msg.get_to())
|
89
|
+
muc = await session.bookmarks.by_jid(jid)
|
90
|
+
|
91
|
+
await session.on_invitation(contact, muc, invite["reason"] or None)
|
92
|
+
|
93
|
+
@exceptions_to_xmpp_errors
|
94
|
+
async def on_groupchat_subject(self, msg: Message):
|
95
|
+
muc = await self.get_muc_from_stanza(msg)
|
96
|
+
if not muc.HAS_SUBJECT:
|
97
|
+
raise XMPPError(
|
98
|
+
"bad-request",
|
99
|
+
"There are no room subject in here. "
|
100
|
+
"Use the room configuration to update its name or description",
|
101
|
+
)
|
102
|
+
await muc.on_set_subject(msg["subject"])
|
103
|
+
|
104
|
+
|
105
|
+
KICKABLE_ERRORS = {
|
106
|
+
"gone",
|
107
|
+
"internal-server-error",
|
108
|
+
"item-not-found",
|
109
|
+
"jid-malformed",
|
110
|
+
"recipient-unavailable",
|
111
|
+
"redirect",
|
112
|
+
"remote-server-not-found",
|
113
|
+
"remote-server-timeout",
|
114
|
+
"service-unavailable",
|
115
|
+
"malformed error",
|
116
|
+
}
|
117
|
+
|
118
|
+
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
|
+
muc.session.bookmarks.remove(muc)
|
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__)
|