slidge 0.1.0__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/__init__.py +61 -0
- slidge/__main__.py +192 -0
- slidge/command/__init__.py +28 -0
- slidge/command/adhoc.py +258 -0
- slidge/command/admin.py +193 -0
- slidge/command/base.py +441 -0
- slidge/command/categories.py +3 -0
- slidge/command/chat_command.py +288 -0
- slidge/command/register.py +179 -0
- slidge/command/user.py +250 -0
- slidge/contact/__init__.py +8 -0
- slidge/contact/contact.py +452 -0
- slidge/contact/roster.py +192 -0
- slidge/core/__init__.py +3 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +209 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +892 -0
- slidge/core/gateway/caps.py +63 -0
- slidge/core/gateway/delivery_receipt.py +52 -0
- slidge/core/gateway/disco.py +80 -0
- slidge/core/gateway/mam.py +75 -0
- slidge/core/gateway/muc_admin.py +35 -0
- slidge/core/gateway/ping.py +66 -0
- slidge/core/gateway/presence.py +95 -0
- slidge/core/gateway/registration.py +53 -0
- slidge/core/gateway/search.py +102 -0
- slidge/core/gateway/session_dispatcher.py +757 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +19 -0
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +31 -0
- slidge/core/mixins/disco.py +130 -0
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +398 -0
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +217 -0
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +525 -0
- slidge/core/session.py +752 -0
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +440 -0
- slidge/group/room.py +1095 -0
- slidge/migration.py +18 -0
- slidge/py.typed +0 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/slixfix/link_preview/__init__.py +10 -0
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/slixfix/xep_0077/__init__.py +10 -0
- slidge/slixfix/xep_0077/register.py +289 -0
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/slixfix/xep_0100/__init__.py +5 -0
- slidge/slixfix/xep_0100/gateway.py +121 -0
- slidge/slixfix/xep_0100/stanza.py +9 -0
- slidge/slixfix/xep_0153/__init__.py +10 -0
- slidge/slixfix/xep_0153/stanza.py +25 -0
- slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
- slidge/slixfix/xep_0264/__init__.py +5 -0
- slidge/slixfix/xep_0264/stanza.py +36 -0
- slidge/slixfix/xep_0264/thumbnail.py +23 -0
- slidge/slixfix/xep_0292/__init__.py +5 -0
- slidge/slixfix/xep_0292/vcard4.py +100 -0
- slidge/slixfix/xep_0313/__init__.py +12 -0
- slidge/slixfix/xep_0313/mam.py +262 -0
- slidge/slixfix/xep_0313/stanza.py +359 -0
- slidge/slixfix/xep_0317/__init__.py +5 -0
- slidge/slixfix/xep_0317/hats.py +17 -0
- slidge/slixfix/xep_0317/stanza.py +28 -0
- slidge/slixfix/xep_0356_old/__init__.py +7 -0
- slidge/slixfix/xep_0356_old/privilege.py +167 -0
- slidge/slixfix/xep_0356_old/stanza.py +44 -0
- slidge/slixfix/xep_0424/__init__.py +9 -0
- slidge/slixfix/xep_0424/retraction.py +77 -0
- slidge/slixfix/xep_0424/stanza.py +28 -0
- slidge/slixfix/xep_0490/__init__.py +8 -0
- slidge/slixfix/xep_0490/mds.py +47 -0
- slidge/slixfix/xep_0490/stanza.py +17 -0
- slidge/util/__init__.py +15 -0
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +229 -0
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +295 -0
- slidge/util/types.py +180 -0
- slidge/util/util.py +295 -0
- slidge-0.1.0.dist-info/LICENSE +661 -0
- slidge-0.1.0.dist-info/METADATA +109 -0
- slidge-0.1.0.dist-info/RECORD +96 -0
- slidge-0.1.0.dist-info/WHEEL +4 -0
- slidge-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import TYPE_CHECKING
|
3
|
+
|
4
|
+
from slixmpp import Presence
|
5
|
+
from slixmpp.exceptions import XMPPError
|
6
|
+
from slixmpp.xmlstream import StanzaBase
|
7
|
+
|
8
|
+
from ...contact import LegacyContact
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from .base import BaseGateway
|
12
|
+
|
13
|
+
|
14
|
+
class Caps:
|
15
|
+
def __init__(self, xmpp: "BaseGateway"):
|
16
|
+
self.xmpp = xmpp
|
17
|
+
xmpp.del_filter("out", xmpp.plugin["xep_0115"]._filter_add_caps)
|
18
|
+
xmpp.add_filter("out", self._filter_add_caps) # type:ignore
|
19
|
+
|
20
|
+
async def _filter_add_caps(self, stanza: StanzaBase):
|
21
|
+
# we rolled our own "add caps on presences" filter because
|
22
|
+
# there is too much magic happening in slixmpp
|
23
|
+
# anyway, we probably want to roll our own "dynamic disco"/caps
|
24
|
+
# module in the long run, so it's a step in this direction
|
25
|
+
if not isinstance(stanza, Presence):
|
26
|
+
return stanza
|
27
|
+
|
28
|
+
if stanza["type"] not in ("available", "chat", "away", "dnd", "xa"):
|
29
|
+
return stanza
|
30
|
+
|
31
|
+
pfrom = stanza.get_from()
|
32
|
+
|
33
|
+
caps = self.xmpp.plugin["xep_0115"]
|
34
|
+
|
35
|
+
if pfrom != self.xmpp.boundjid.bare:
|
36
|
+
try:
|
37
|
+
session = self.xmpp.get_session_from_jid(stanza.get_to())
|
38
|
+
except XMPPError:
|
39
|
+
log.debug("not adding caps 1")
|
40
|
+
return stanza
|
41
|
+
|
42
|
+
if session is None:
|
43
|
+
return stanza
|
44
|
+
|
45
|
+
await session.ready
|
46
|
+
|
47
|
+
entity = await session.get_contact_or_group_or_participant(pfrom)
|
48
|
+
if not isinstance(entity, LegacyContact):
|
49
|
+
return stanza
|
50
|
+
ver = await entity.get_caps_ver(pfrom)
|
51
|
+
else:
|
52
|
+
ver = await caps.get_verstring(pfrom)
|
53
|
+
|
54
|
+
log.debug("Ver: %s", ver)
|
55
|
+
|
56
|
+
if ver:
|
57
|
+
stanza["caps"]["node"] = caps.caps_node
|
58
|
+
stanza["caps"]["hash"] = caps.hash
|
59
|
+
stanza["caps"]["ver"] = ver
|
60
|
+
return stanza
|
61
|
+
|
62
|
+
|
63
|
+
log = logging.getLogger(__name__)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
"""
|
2
|
+
XEP-0184 Delivery Receipts
|
3
|
+
|
4
|
+
The corresponding slixmpp module is a bit too rigid, this is our implementation
|
5
|
+
to selectively choose when we send delivery receipts
|
6
|
+
"""
|
7
|
+
|
8
|
+
from typing import TYPE_CHECKING
|
9
|
+
|
10
|
+
from slixmpp import JID, Message
|
11
|
+
from slixmpp.types import MessageTypes
|
12
|
+
|
13
|
+
if TYPE_CHECKING:
|
14
|
+
from .base import BaseGateway
|
15
|
+
|
16
|
+
|
17
|
+
class DeliveryReceipt:
|
18
|
+
def __init__(self, xmpp: "BaseGateway"):
|
19
|
+
self.xmpp = xmpp
|
20
|
+
|
21
|
+
def ack(self, msg: Message):
|
22
|
+
"""
|
23
|
+
Send a XEP-0184 (delivery receipt) in response to a message,
|
24
|
+
if appropriate.
|
25
|
+
|
26
|
+
:param msg:
|
27
|
+
"""
|
28
|
+
if not self.requires_receipt(msg):
|
29
|
+
return
|
30
|
+
ack = self.make_ack(msg["id"], msg["to"], msg["from"].bare, msg["type"])
|
31
|
+
ack.send()
|
32
|
+
|
33
|
+
def make_ack(self, msg_id: str, mfrom: JID, mto: JID, mtype: MessageTypes = "chat"):
|
34
|
+
ack = self.xmpp.Message()
|
35
|
+
ack["type"] = mtype
|
36
|
+
ack["to"] = mto
|
37
|
+
ack["from"] = mfrom
|
38
|
+
ack["receipt"] = msg_id
|
39
|
+
return ack
|
40
|
+
|
41
|
+
def requires_receipt(self, msg: Message):
|
42
|
+
"""
|
43
|
+
Check if a message is eligible for a delivery receipt.
|
44
|
+
|
45
|
+
:param msg:
|
46
|
+
:return:
|
47
|
+
"""
|
48
|
+
return (
|
49
|
+
msg["request_receipt"]
|
50
|
+
and msg["type"] in self.xmpp.plugin["xep_0184"].ack_types
|
51
|
+
and not msg["receipt"]
|
52
|
+
)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import TYPE_CHECKING, Any, Optional
|
3
|
+
|
4
|
+
from slixmpp.exceptions import XMPPError
|
5
|
+
from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
|
6
|
+
from slixmpp.types import OptJid
|
7
|
+
|
8
|
+
from ...util.db import user_store
|
9
|
+
|
10
|
+
if TYPE_CHECKING:
|
11
|
+
from .base import BaseGateway
|
12
|
+
|
13
|
+
|
14
|
+
class Disco:
|
15
|
+
def __init__(self, xmpp: "BaseGateway"):
|
16
|
+
self.xmpp = xmpp
|
17
|
+
|
18
|
+
xmpp.plugin["xep_0030"].set_node_handler(
|
19
|
+
"get_info",
|
20
|
+
jid=None,
|
21
|
+
node=None,
|
22
|
+
handler=self.get_info,
|
23
|
+
)
|
24
|
+
|
25
|
+
xmpp.plugin["xep_0030"].set_node_handler(
|
26
|
+
"get_items",
|
27
|
+
jid=None,
|
28
|
+
node=None,
|
29
|
+
handler=self.get_items,
|
30
|
+
)
|
31
|
+
|
32
|
+
async def get_info(
|
33
|
+
self, jid: OptJid, node: Optional[str], ifrom: OptJid, data: Any
|
34
|
+
):
|
35
|
+
if ifrom == self.xmpp.boundjid.bare or jid in (self.xmpp.boundjid.bare, None):
|
36
|
+
return self.xmpp.plugin["xep_0030"].static.get_info(jid, node, ifrom, data)
|
37
|
+
|
38
|
+
if ifrom is None:
|
39
|
+
raise XMPPError("subscription-required")
|
40
|
+
|
41
|
+
user = user_store.get_by_jid(ifrom)
|
42
|
+
if user is None:
|
43
|
+
raise XMPPError("registration-required")
|
44
|
+
session = self.xmpp.get_session_from_user(user)
|
45
|
+
await session.wait_for_ready()
|
46
|
+
|
47
|
+
log.debug("Looking for entity: %s", jid)
|
48
|
+
|
49
|
+
assert jid is not None
|
50
|
+
entity = await session.get_contact_or_group_or_participant(jid)
|
51
|
+
|
52
|
+
if entity is None:
|
53
|
+
raise XMPPError("item-not-found")
|
54
|
+
|
55
|
+
return await entity.get_disco_info(jid, node)
|
56
|
+
|
57
|
+
async def get_items(
|
58
|
+
self, jid: OptJid, node: Optional[str], ifrom: OptJid, data: Any
|
59
|
+
):
|
60
|
+
if ifrom is None:
|
61
|
+
raise XMPPError("bad-request")
|
62
|
+
|
63
|
+
if jid != self.xmpp.boundjid.bare:
|
64
|
+
return DiscoItems()
|
65
|
+
|
66
|
+
user = user_store.get_by_jid(ifrom)
|
67
|
+
if user is None:
|
68
|
+
raise XMPPError("registration-required")
|
69
|
+
|
70
|
+
session = self.xmpp.get_session_from_user(user)
|
71
|
+
await session.wait_for_ready()
|
72
|
+
|
73
|
+
d = DiscoItems()
|
74
|
+
for muc in sorted(session.bookmarks, key=lambda m: m.name):
|
75
|
+
d.add_item(muc.jid, name=muc.name)
|
76
|
+
|
77
|
+
return d
|
78
|
+
|
79
|
+
|
80
|
+
log = logging.getLogger(__name__)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from slixmpp import CoroutineCallback, Iq, StanzaPath
|
4
|
+
from slixmpp.exceptions import XMPPError
|
5
|
+
|
6
|
+
from ...util.db import user_store
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from .base import BaseGateway
|
10
|
+
|
11
|
+
|
12
|
+
class Mam:
|
13
|
+
def __init__(self, xmpp: "BaseGateway"):
|
14
|
+
self.xmpp = xmpp
|
15
|
+
xmpp.register_handler(
|
16
|
+
CoroutineCallback(
|
17
|
+
"MAM_query",
|
18
|
+
StanzaPath("iq@type=set/mam"),
|
19
|
+
self.__handle_mam, # type:ignore
|
20
|
+
)
|
21
|
+
)
|
22
|
+
xmpp.register_handler(
|
23
|
+
CoroutineCallback(
|
24
|
+
"MAM_get_from",
|
25
|
+
StanzaPath("iq@type=get/mam"),
|
26
|
+
self.__handle_mam_get_form, # type:ignore
|
27
|
+
)
|
28
|
+
)
|
29
|
+
xmpp.register_handler(
|
30
|
+
CoroutineCallback(
|
31
|
+
"MAM_get_meta",
|
32
|
+
StanzaPath("iq@type=get/mam_metadata"),
|
33
|
+
self.__handle_mam_metadata, # type:ignore
|
34
|
+
)
|
35
|
+
)
|
36
|
+
|
37
|
+
async def __handle_mam(self, iq: Iq):
|
38
|
+
muc = await self.xmpp.get_muc_from_stanza(iq)
|
39
|
+
await muc.send_mam(iq)
|
40
|
+
|
41
|
+
async def __handle_mam_get_form(self, iq: Iq):
|
42
|
+
ito = iq.get_to()
|
43
|
+
|
44
|
+
if ito == self.xmpp.boundjid.bare:
|
45
|
+
raise XMPPError(
|
46
|
+
text="No MAM on the component itself, use a JID with a resource"
|
47
|
+
)
|
48
|
+
|
49
|
+
ifrom = iq.get_from()
|
50
|
+
user = user_store.get_by_jid(ifrom)
|
51
|
+
if user is None:
|
52
|
+
raise XMPPError("registration-required")
|
53
|
+
|
54
|
+
session = self.xmpp.get_session_from_user(user)
|
55
|
+
|
56
|
+
await session.bookmarks.by_jid(ito)
|
57
|
+
|
58
|
+
reply = iq.reply()
|
59
|
+
form = self.xmpp.plugin["xep_0004"].make_form()
|
60
|
+
form.add_field(ftype="hidden", var="FORM_TYPE", value="urn:xmpp:mam:2")
|
61
|
+
form.add_field(ftype="jid-single", var="with")
|
62
|
+
form.add_field(ftype="text-single", var="start")
|
63
|
+
form.add_field(ftype="text-single", var="end")
|
64
|
+
form.add_field(ftype="text-single", var="before-id")
|
65
|
+
form.add_field(ftype="text-single", var="after-id")
|
66
|
+
form.add_field(ftype="boolean", var="include-groupchat")
|
67
|
+
field = form.add_field(ftype="list-multi", var="ids")
|
68
|
+
field["validate"]["datatype"] = "xs:string"
|
69
|
+
field["validate"]["open"] = True
|
70
|
+
reply["mam"].append(form)
|
71
|
+
reply.send()
|
72
|
+
|
73
|
+
async def __handle_mam_metadata(self, iq: Iq):
|
74
|
+
muc = await self.xmpp.get_muc_from_stanza(iq)
|
75
|
+
await muc.send_mam_metadata(iq)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from slixmpp import CoroutineCallback, Iq, StanzaPath
|
4
|
+
from slixmpp.exceptions import XMPPError
|
5
|
+
|
6
|
+
if TYPE_CHECKING:
|
7
|
+
from .base import BaseGateway
|
8
|
+
|
9
|
+
|
10
|
+
class MucAdmin:
|
11
|
+
def __init__(self, xmpp: "BaseGateway"):
|
12
|
+
self.xmpp = xmpp
|
13
|
+
xmpp.register_handler(
|
14
|
+
CoroutineCallback(
|
15
|
+
"muc#admin",
|
16
|
+
StanzaPath("iq@type=get/mucadmin_query"),
|
17
|
+
self._handle_admin, # type: ignore
|
18
|
+
)
|
19
|
+
)
|
20
|
+
|
21
|
+
async def _handle_admin(self, iq: Iq):
|
22
|
+
muc = await self.xmpp.get_muc_from_stanza(iq)
|
23
|
+
|
24
|
+
affiliation = iq["mucadmin_query"]["item"]["affiliation"]
|
25
|
+
|
26
|
+
if not affiliation:
|
27
|
+
raise XMPPError("bad-request")
|
28
|
+
|
29
|
+
reply = iq.reply()
|
30
|
+
reply.enable("mucadmin_query")
|
31
|
+
for participant in await muc.get_participants():
|
32
|
+
if not participant.affiliation == affiliation:
|
33
|
+
continue
|
34
|
+
reply["mucadmin_query"].append(participant.mucadmin_item())
|
35
|
+
reply.send()
|
@@ -0,0 +1,66 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from slixmpp import CoroutineCallback, Iq, StanzaPath
|
4
|
+
from slixmpp.exceptions import XMPPError
|
5
|
+
|
6
|
+
from ...group import LegacyMUC
|
7
|
+
from ...util.db import user_store
|
8
|
+
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from .base import BaseGateway
|
11
|
+
|
12
|
+
|
13
|
+
class Ping:
|
14
|
+
def __init__(self, xmpp: "BaseGateway"):
|
15
|
+
self.xmpp = xmpp
|
16
|
+
|
17
|
+
xmpp.remove_handler("Ping")
|
18
|
+
xmpp.register_handler(
|
19
|
+
CoroutineCallback(
|
20
|
+
"Ping",
|
21
|
+
StanzaPath("iq@type=get/ping"),
|
22
|
+
self.__handle_ping, # type:ignore
|
23
|
+
)
|
24
|
+
)
|
25
|
+
xmpp.plugin["xep_0030"].add_feature("urn:xmpp:ping")
|
26
|
+
|
27
|
+
async def __handle_ping(self, iq: Iq):
|
28
|
+
ito = iq.get_to()
|
29
|
+
|
30
|
+
if ito == self.xmpp.boundjid.bare:
|
31
|
+
iq.reply().send()
|
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()
|
40
|
+
|
41
|
+
try:
|
42
|
+
muc = await session.bookmarks.by_jid(ito)
|
43
|
+
except XMPPError:
|
44
|
+
pass
|
45
|
+
else:
|
46
|
+
self.__handle_muc_ping(muc, iq)
|
47
|
+
return
|
48
|
+
|
49
|
+
try:
|
50
|
+
await session.contacts.by_jid(ito)
|
51
|
+
except XMPPError:
|
52
|
+
pass
|
53
|
+
else:
|
54
|
+
iq.reply().send()
|
55
|
+
return
|
56
|
+
|
57
|
+
raise XMPPError(
|
58
|
+
"item-not-found", f"This JID does not match anything slidge knows: {ito}"
|
59
|
+
)
|
60
|
+
|
61
|
+
@staticmethod
|
62
|
+
def __handle_muc_ping(muc: LegacyMUC, iq: Iq):
|
63
|
+
if iq.get_from().resource in muc.user_resources:
|
64
|
+
iq.reply().send()
|
65
|
+
else:
|
66
|
+
raise XMPPError("not-acceptable", etype="cancel", by=muc.jid)
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import logging
|
2
|
+
|
3
|
+
from slixmpp import JID, Presence
|
4
|
+
|
5
|
+
from ..session import BaseSession
|
6
|
+
|
7
|
+
|
8
|
+
class _IsDirectedAtComponent(Exception):
|
9
|
+
def __init__(self, session: BaseSession):
|
10
|
+
self.session = session
|
11
|
+
|
12
|
+
|
13
|
+
class PresenceHandlerMixin:
|
14
|
+
boundjid: JID
|
15
|
+
|
16
|
+
def get_session_from_stanza(self, s) -> BaseSession:
|
17
|
+
raise NotImplementedError
|
18
|
+
|
19
|
+
async def __get_contact(self, pres: Presence):
|
20
|
+
sess = await self.__get_session(pres)
|
21
|
+
pto = pres.get_to()
|
22
|
+
if pto == self.boundjid.bare:
|
23
|
+
raise _IsDirectedAtComponent(sess)
|
24
|
+
await sess.contacts.ready
|
25
|
+
return await sess.contacts.by_jid(pto)
|
26
|
+
|
27
|
+
async def __get_session(self, p: Presence):
|
28
|
+
sess = self.get_session_from_stanza(p)
|
29
|
+
return sess
|
30
|
+
|
31
|
+
async def _handle_subscribe(self, pres: Presence):
|
32
|
+
try:
|
33
|
+
contact = await self.__get_contact(pres)
|
34
|
+
except _IsDirectedAtComponent:
|
35
|
+
pres.reply().send()
|
36
|
+
return
|
37
|
+
|
38
|
+
if contact.is_friend:
|
39
|
+
pres.reply().send()
|
40
|
+
else:
|
41
|
+
await contact.on_friend_request(pres["status"])
|
42
|
+
|
43
|
+
async def _handle_unsubscribe(self, pres: Presence):
|
44
|
+
pres.reply().send()
|
45
|
+
|
46
|
+
try:
|
47
|
+
contact = await self.__get_contact(pres)
|
48
|
+
except _IsDirectedAtComponent as e:
|
49
|
+
e.session.send_gateway_message("Bye bye!")
|
50
|
+
await e.session.kill_by_jid(e.session.user.jid)
|
51
|
+
return
|
52
|
+
|
53
|
+
contact.is_friend = False
|
54
|
+
await contact.on_friend_delete(pres["status"])
|
55
|
+
|
56
|
+
async def _handle_subscribed(self, pres: Presence):
|
57
|
+
try:
|
58
|
+
contact = await self.__get_contact(pres)
|
59
|
+
except _IsDirectedAtComponent:
|
60
|
+
return
|
61
|
+
|
62
|
+
await contact.on_friend_accept()
|
63
|
+
|
64
|
+
async def _handle_unsubscribed(self, pres: Presence):
|
65
|
+
try:
|
66
|
+
contact = await self.__get_contact(pres)
|
67
|
+
except _IsDirectedAtComponent:
|
68
|
+
return
|
69
|
+
|
70
|
+
if contact.is_friend:
|
71
|
+
contact.is_friend = False
|
72
|
+
await contact.on_friend_delete(pres["status"])
|
73
|
+
|
74
|
+
async def _handle_probe(self, pres: Presence):
|
75
|
+
try:
|
76
|
+
contact = await self.__get_contact(pres)
|
77
|
+
except _IsDirectedAtComponent:
|
78
|
+
session = await self.__get_session(pres)
|
79
|
+
session.send_cached_presence(pres.get_from())
|
80
|
+
return
|
81
|
+
if contact.is_friend:
|
82
|
+
contact.send_last_presence(force=True)
|
83
|
+
else:
|
84
|
+
reply = pres.reply()
|
85
|
+
reply["type"] = "unsubscribed"
|
86
|
+
reply.send()
|
87
|
+
|
88
|
+
async def _handle_new_subscription(self, pres: Presence):
|
89
|
+
pass
|
90
|
+
|
91
|
+
async def _handle_removed_subscription(self, pres: Presence):
|
92
|
+
pass
|
93
|
+
|
94
|
+
|
95
|
+
log = logging.getLogger(__name__)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import logging
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
3
|
+
|
4
|
+
from slixmpp import JID, Iq
|
5
|
+
|
6
|
+
from ...util.db import user_store
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from .base import BaseGateway
|
10
|
+
|
11
|
+
|
12
|
+
class Registration:
|
13
|
+
def __init__(self, xmpp: "BaseGateway"):
|
14
|
+
self.xmpp = xmpp
|
15
|
+
xmpp["xep_0077"].api.register(
|
16
|
+
user_store.get,
|
17
|
+
"user_get",
|
18
|
+
)
|
19
|
+
xmpp["xep_0077"].api.register(
|
20
|
+
user_store.remove,
|
21
|
+
"user_remove",
|
22
|
+
)
|
23
|
+
xmpp["xep_0077"].api.register(
|
24
|
+
self.xmpp.make_registration_form, "make_registration_form"
|
25
|
+
)
|
26
|
+
xmpp["xep_0077"].api.register(self._user_validate, "user_validate")
|
27
|
+
xmpp["xep_0077"].api.register(self._user_modify, "user_modify")
|
28
|
+
|
29
|
+
async def _user_validate(self, _gateway_jid, _node, ifrom: JID, iq: Iq):
|
30
|
+
"""
|
31
|
+
SliXMPP internal API stuff
|
32
|
+
"""
|
33
|
+
xmpp = self.xmpp
|
34
|
+
log.debug("User validate: %s", ifrom.bare)
|
35
|
+
form_dict = {f.var: iq.get(f.var) for f in xmpp.REGISTRATION_FIELDS}
|
36
|
+
xmpp.raise_if_not_allowed_jid(ifrom)
|
37
|
+
await xmpp.user_prevalidate(ifrom, form_dict)
|
38
|
+
log.info("New user: %s", ifrom.bare)
|
39
|
+
user_store.add(ifrom, form_dict)
|
40
|
+
|
41
|
+
async def _user_modify(
|
42
|
+
self, _gateway_jid, _node, ifrom: JID, form_dict: dict[str, Optional[str]]
|
43
|
+
):
|
44
|
+
"""
|
45
|
+
SliXMPP internal API stuff
|
46
|
+
"""
|
47
|
+
user = user_store.get_by_jid(ifrom)
|
48
|
+
log.debug("Modify user: %s", user)
|
49
|
+
await self.xmpp.user_prevalidate(ifrom, form_dict)
|
50
|
+
user_store.add(ifrom, form_dict)
|
51
|
+
|
52
|
+
|
53
|
+
log = logging.getLogger(__name__)
|
@@ -0,0 +1,102 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
3
|
+
from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
|
4
|
+
from slixmpp.exceptions import XMPPError
|
5
|
+
|
6
|
+
from ...util.db import user_store
|
7
|
+
|
8
|
+
if TYPE_CHECKING:
|
9
|
+
from .base import BaseGateway
|
10
|
+
|
11
|
+
|
12
|
+
class Search:
|
13
|
+
def __init__(self, xmpp: "BaseGateway"):
|
14
|
+
self.xmpp = xmpp
|
15
|
+
|
16
|
+
xmpp["xep_0055"].api.register(self.search_get_form, "search_get_form")
|
17
|
+
xmpp["xep_0055"].api.register(self._search_query, "search_query")
|
18
|
+
|
19
|
+
xmpp.plugin["xep_0030"].add_feature("jabber:iq:gateway")
|
20
|
+
xmpp.register_handler(
|
21
|
+
CoroutineCallback(
|
22
|
+
"iq:gateway",
|
23
|
+
StanzaPath("iq/gateway"),
|
24
|
+
self._handle_gateway_iq, # type: ignore
|
25
|
+
)
|
26
|
+
)
|
27
|
+
|
28
|
+
async def search_get_form(self, _gateway_jid, _node, ifrom: JID, iq: Iq):
|
29
|
+
"""
|
30
|
+
Prepare the search form using :attr:`.BaseSession.SEARCH_FIELDS`
|
31
|
+
"""
|
32
|
+
user = user_store.get_by_jid(ifrom)
|
33
|
+
if user is None:
|
34
|
+
raise XMPPError(text="Search is only allowed for registered users")
|
35
|
+
|
36
|
+
xmpp = self.xmpp
|
37
|
+
|
38
|
+
reply = iq.reply()
|
39
|
+
form = reply["search"]["form"]
|
40
|
+
form["title"] = xmpp.SEARCH_TITLE
|
41
|
+
form["instructions"] = xmpp.SEARCH_INSTRUCTIONS
|
42
|
+
for field in xmpp.SEARCH_FIELDS:
|
43
|
+
form.append(field.get_xml())
|
44
|
+
return reply
|
45
|
+
|
46
|
+
async def _search_query(self, _gateway_jid, _node, ifrom: JID, iq: Iq):
|
47
|
+
"""
|
48
|
+
Handles a search request
|
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")
|
53
|
+
|
54
|
+
result = await self.xmpp.get_session_from_stanza(iq).on_search(
|
55
|
+
iq["search"]["form"].get_values()
|
56
|
+
)
|
57
|
+
|
58
|
+
if not result:
|
59
|
+
raise XMPPError("item-not-found", text="Nothing was found")
|
60
|
+
|
61
|
+
reply = iq.reply()
|
62
|
+
form = reply["search"]["form"]
|
63
|
+
for field in result.fields:
|
64
|
+
form.add_reported(field.var, label=field.label, type=field.type)
|
65
|
+
for item in result.items:
|
66
|
+
form.add_item(item)
|
67
|
+
return reply
|
68
|
+
|
69
|
+
async def _handle_gateway_iq(self, iq: Iq):
|
70
|
+
if iq.get_to() != self.xmpp.boundjid.bare:
|
71
|
+
raise XMPPError("bad-request", "This can only be used on the component JID")
|
72
|
+
|
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
|
+
if len(self.xmpp.SEARCH_FIELDS) > 1:
|
78
|
+
raise XMPPError(
|
79
|
+
"feature-not-implemented", "Use jabber search for this gateway"
|
80
|
+
)
|
81
|
+
|
82
|
+
field = self.xmpp.SEARCH_FIELDS[0]
|
83
|
+
|
84
|
+
reply = iq.reply()
|
85
|
+
if iq["type"] == "get":
|
86
|
+
reply["gateway"]["desc"] = self.xmpp.SEARCH_TITLE
|
87
|
+
reply["gateway"]["prompt"] = field.label
|
88
|
+
elif iq["type"] == "set":
|
89
|
+
prompt = iq["gateway"]["prompt"]
|
90
|
+
session = self.xmpp.session_cls.from_user(user)
|
91
|
+
result = await session.on_search({field.var: prompt})
|
92
|
+
if result is None or not result.items:
|
93
|
+
raise XMPPError(
|
94
|
+
"item-not-found", "No contact was found with the info you provided."
|
95
|
+
)
|
96
|
+
if len(result.items) > 1:
|
97
|
+
raise XMPPError(
|
98
|
+
"bad-request", "Your search yielded more than one result."
|
99
|
+
)
|
100
|
+
reply["gateway"]["jid"] = result.items[0]["jid"]
|
101
|
+
|
102
|
+
reply.send()
|