slidge 0.1.0rc1__py3-none-any.whl → 0.1.2__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__init__.py +54 -31
- slidge/__main__.py +51 -5
- 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 +2 -0
- slidge/core/cache.py +121 -39
- slidge/core/config.py +116 -11
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +895 -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 +795 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +9 -1
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +6 -19
- slidge/core/mixins/disco.py +66 -15
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +254 -252
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +128 -31
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +275 -116
- slidge/core/session.py +586 -518
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +458 -0
- slidge/group/room.py +1103 -0
- slidge/migration.py +18 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/{util/xep_0050 → slixfix/link_preview}/__init__.py +4 -5
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/{util → slixfix}/xep_0077/register.py +1 -2
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
- 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/{util → slixfix}/xep_0356_old/privilege.py +9 -7
- 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 +4 -6
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +25 -4
- slidge/util/db.py +23 -69
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +136 -86
- slidge/util/types.py +155 -14
- slidge/util/util.py +225 -51
- slidge-0.1.2.dist-info/METADATA +111 -0
- slidge-0.1.2.dist-info/RECORD +96 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
- slidge/core/adhoc.py +0 -492
- slidge/core/chat_command.py +0 -197
- slidge/core/contact.py +0 -441
- slidge/core/disco.py +0 -59
- slidge/core/gateway.py +0 -899
- slidge/core/muc/__init__.py +0 -3
- slidge/core/muc/bookmarks.py +0 -74
- slidge/core/muc/participant.py +0 -152
- slidge/core/muc/room.py +0 -348
- slidge/plugins/discord/__init__.py +0 -121
- slidge/plugins/discord/client.py +0 -121
- slidge/plugins/discord/session.py +0 -172
- slidge/plugins/dummy.py +0 -334
- slidge/plugins/facebook.py +0 -591
- slidge/plugins/hackernews.py +0 -209
- slidge/plugins/mattermost/__init__.py +0 -1
- slidge/plugins/mattermost/api.py +0 -288
- slidge/plugins/mattermost/gateway.py +0 -417
- slidge/plugins/mattermost/websocket.py +0 -248
- slidge/plugins/signal/__init__.py +0 -4
- slidge/plugins/signal/config.py +0 -4
- slidge/plugins/signal/contact.py +0 -104
- slidge/plugins/signal/gateway.py +0 -379
- slidge/plugins/signal/group.py +0 -76
- slidge/plugins/signal/session.py +0 -515
- slidge/plugins/signal/txt.py +0 -13
- slidge/plugins/signal/util.py +0 -32
- slidge/plugins/skype.py +0 -310
- slidge/plugins/steam.py +0 -400
- slidge/plugins/telegram/__init__.py +0 -6
- slidge/plugins/telegram/client.py +0 -325
- slidge/plugins/telegram/config.py +0 -21
- slidge/plugins/telegram/contact.py +0 -154
- slidge/plugins/telegram/gateway.py +0 -182
- slidge/plugins/telegram/group.py +0 -184
- slidge/plugins/telegram/session.py +0 -275
- slidge/plugins/telegram/util.py +0 -153
- slidge/plugins/whatsapp/__init__.py +0 -6
- slidge/plugins/whatsapp/config.py +0 -17
- slidge/plugins/whatsapp/contact.py +0 -33
- slidge/plugins/whatsapp/event.go +0 -455
- slidge/plugins/whatsapp/gateway.go +0 -156
- slidge/plugins/whatsapp/gateway.py +0 -69
- slidge/plugins/whatsapp/go.mod +0 -17
- slidge/plugins/whatsapp/go.sum +0 -22
- slidge/plugins/whatsapp/session.go +0 -371
- slidge/plugins/whatsapp/session.py +0 -370
- slidge/util/xep_0030/__init__.py +0 -13
- slidge/util/xep_0030/disco.py +0 -811
- slidge/util/xep_0030/stanza/__init__.py +0 -7
- slidge/util/xep_0030/stanza/info.py +0 -270
- slidge/util/xep_0030/stanza/items.py +0 -147
- slidge/util/xep_0030/static.py +0 -467
- slidge/util/xep_0050/adhoc.py +0 -631
- slidge/util/xep_0050/stanza.py +0 -180
- slidge/util/xep_0077/stanza.py +0 -71
- slidge/util/xep_0292/__init__.py +0 -1
- slidge/util/xep_0292/stanza.py +0 -167
- slidge/util/xep_0292/vcard4.py +0 -74
- slidge/util/xep_0356/__init__.py +0 -7
- slidge/util/xep_0356/permissions.py +0 -35
- slidge/util/xep_0356/privilege.py +0 -160
- slidge/util/xep_0356/stanza.py +0 -44
- slidge/util/xep_0461/__init__.py +0 -6
- slidge/util/xep_0461/reply.py +0 -48
- slidge/util/xep_0461/stanza.py +0 -80
- slidge-0.1.0rc1.dist-info/METADATA +0 -171
- slidge-0.1.0rc1.dist-info/RECORD +0 -99
- /slidge/{plugins/__init__.py → py.typed} +0 -0
- /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
- {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/entry_points.txt +0 -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()
|