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.
Files changed (164) hide show
  1. slidge/__init__.py +54 -31
  2. slidge/__main__.py +51 -5
  3. slidge/command/__init__.py +28 -0
  4. slidge/command/adhoc.py +258 -0
  5. slidge/command/admin.py +193 -0
  6. slidge/command/base.py +441 -0
  7. slidge/command/categories.py +3 -0
  8. slidge/command/chat_command.py +288 -0
  9. slidge/command/register.py +179 -0
  10. slidge/command/user.py +250 -0
  11. slidge/contact/__init__.py +8 -0
  12. slidge/contact/contact.py +452 -0
  13. slidge/contact/roster.py +192 -0
  14. slidge/core/__init__.py +2 -0
  15. slidge/core/cache.py +121 -39
  16. slidge/core/config.py +116 -11
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +895 -0
  19. slidge/core/gateway/caps.py +63 -0
  20. slidge/core/gateway/delivery_receipt.py +52 -0
  21. slidge/core/gateway/disco.py +80 -0
  22. slidge/core/gateway/mam.py +75 -0
  23. slidge/core/gateway/muc_admin.py +35 -0
  24. slidge/core/gateway/ping.py +66 -0
  25. slidge/core/gateway/presence.py +95 -0
  26. slidge/core/gateway/registration.py +53 -0
  27. slidge/core/gateway/search.py +102 -0
  28. slidge/core/gateway/session_dispatcher.py +795 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +9 -1
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +6 -19
  34. slidge/core/mixins/disco.py +66 -15
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +254 -252
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +128 -31
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +275 -116
  41. slidge/core/session.py +586 -518
  42. slidge/group/__init__.py +10 -0
  43. slidge/group/archive.py +125 -0
  44. slidge/group/bookmarks.py +163 -0
  45. slidge/group/participant.py +458 -0
  46. slidge/group/room.py +1103 -0
  47. slidge/migration.py +18 -0
  48. slidge/slixfix/__init__.py +68 -0
  49. slidge/{util/xep_0050 → slixfix/link_preview}/__init__.py +4 -5
  50. slidge/slixfix/link_preview/link_preview.py +17 -0
  51. slidge/slixfix/link_preview/stanza.py +99 -0
  52. slidge/slixfix/roster.py +60 -0
  53. slidge/{util → slixfix}/xep_0077/register.py +1 -2
  54. slidge/slixfix/xep_0077/stanza.py +104 -0
  55. slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
  56. slidge/slixfix/xep_0153/__init__.py +10 -0
  57. slidge/slixfix/xep_0153/stanza.py +25 -0
  58. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  59. slidge/slixfix/xep_0264/__init__.py +5 -0
  60. slidge/slixfix/xep_0264/stanza.py +36 -0
  61. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  62. slidge/slixfix/xep_0292/__init__.py +5 -0
  63. slidge/slixfix/xep_0292/vcard4.py +100 -0
  64. slidge/slixfix/xep_0313/__init__.py +12 -0
  65. slidge/slixfix/xep_0313/mam.py +262 -0
  66. slidge/slixfix/xep_0313/stanza.py +359 -0
  67. slidge/slixfix/xep_0317/__init__.py +5 -0
  68. slidge/slixfix/xep_0317/hats.py +17 -0
  69. slidge/slixfix/xep_0317/stanza.py +28 -0
  70. slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
  71. slidge/slixfix/xep_0424/__init__.py +9 -0
  72. slidge/slixfix/xep_0424/retraction.py +77 -0
  73. slidge/slixfix/xep_0424/stanza.py +28 -0
  74. slidge/slixfix/xep_0490/__init__.py +8 -0
  75. slidge/slixfix/xep_0490/mds.py +47 -0
  76. slidge/slixfix/xep_0490/stanza.py +17 -0
  77. slidge/util/__init__.py +4 -6
  78. slidge/util/archive_msg.py +61 -0
  79. slidge/util/conf.py +25 -4
  80. slidge/util/db.py +23 -69
  81. slidge/util/schema.sql +126 -0
  82. slidge/util/sql.py +508 -0
  83. slidge/util/test.py +136 -86
  84. slidge/util/types.py +155 -14
  85. slidge/util/util.py +225 -51
  86. slidge-0.1.2.dist-info/METADATA +111 -0
  87. slidge-0.1.2.dist-info/RECORD +96 -0
  88. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
  89. slidge/core/adhoc.py +0 -492
  90. slidge/core/chat_command.py +0 -197
  91. slidge/core/contact.py +0 -441
  92. slidge/core/disco.py +0 -59
  93. slidge/core/gateway.py +0 -899
  94. slidge/core/muc/__init__.py +0 -3
  95. slidge/core/muc/bookmarks.py +0 -74
  96. slidge/core/muc/participant.py +0 -152
  97. slidge/core/muc/room.py +0 -348
  98. slidge/plugins/discord/__init__.py +0 -121
  99. slidge/plugins/discord/client.py +0 -121
  100. slidge/plugins/discord/session.py +0 -172
  101. slidge/plugins/dummy.py +0 -334
  102. slidge/plugins/facebook.py +0 -591
  103. slidge/plugins/hackernews.py +0 -209
  104. slidge/plugins/mattermost/__init__.py +0 -1
  105. slidge/plugins/mattermost/api.py +0 -288
  106. slidge/plugins/mattermost/gateway.py +0 -417
  107. slidge/plugins/mattermost/websocket.py +0 -248
  108. slidge/plugins/signal/__init__.py +0 -4
  109. slidge/plugins/signal/config.py +0 -4
  110. slidge/plugins/signal/contact.py +0 -104
  111. slidge/plugins/signal/gateway.py +0 -379
  112. slidge/plugins/signal/group.py +0 -76
  113. slidge/plugins/signal/session.py +0 -515
  114. slidge/plugins/signal/txt.py +0 -13
  115. slidge/plugins/signal/util.py +0 -32
  116. slidge/plugins/skype.py +0 -310
  117. slidge/plugins/steam.py +0 -400
  118. slidge/plugins/telegram/__init__.py +0 -6
  119. slidge/plugins/telegram/client.py +0 -325
  120. slidge/plugins/telegram/config.py +0 -21
  121. slidge/plugins/telegram/contact.py +0 -154
  122. slidge/plugins/telegram/gateway.py +0 -182
  123. slidge/plugins/telegram/group.py +0 -184
  124. slidge/plugins/telegram/session.py +0 -275
  125. slidge/plugins/telegram/util.py +0 -153
  126. slidge/plugins/whatsapp/__init__.py +0 -6
  127. slidge/plugins/whatsapp/config.py +0 -17
  128. slidge/plugins/whatsapp/contact.py +0 -33
  129. slidge/plugins/whatsapp/event.go +0 -455
  130. slidge/plugins/whatsapp/gateway.go +0 -156
  131. slidge/plugins/whatsapp/gateway.py +0 -69
  132. slidge/plugins/whatsapp/go.mod +0 -17
  133. slidge/plugins/whatsapp/go.sum +0 -22
  134. slidge/plugins/whatsapp/session.go +0 -371
  135. slidge/plugins/whatsapp/session.py +0 -370
  136. slidge/util/xep_0030/__init__.py +0 -13
  137. slidge/util/xep_0030/disco.py +0 -811
  138. slidge/util/xep_0030/stanza/__init__.py +0 -7
  139. slidge/util/xep_0030/stanza/info.py +0 -270
  140. slidge/util/xep_0030/stanza/items.py +0 -147
  141. slidge/util/xep_0030/static.py +0 -467
  142. slidge/util/xep_0050/adhoc.py +0 -631
  143. slidge/util/xep_0050/stanza.py +0 -180
  144. slidge/util/xep_0077/stanza.py +0 -71
  145. slidge/util/xep_0292/__init__.py +0 -1
  146. slidge/util/xep_0292/stanza.py +0 -167
  147. slidge/util/xep_0292/vcard4.py +0 -74
  148. slidge/util/xep_0356/__init__.py +0 -7
  149. slidge/util/xep_0356/permissions.py +0 -35
  150. slidge/util/xep_0356/privilege.py +0 -160
  151. slidge/util/xep_0356/stanza.py +0 -44
  152. slidge/util/xep_0461/__init__.py +0 -6
  153. slidge/util/xep_0461/reply.py +0 -48
  154. slidge/util/xep_0461/stanza.py +0 -80
  155. slidge-0.1.0rc1.dist-info/METADATA +0 -171
  156. slidge-0.1.0rc1.dist-info/RECORD +0 -99
  157. /slidge/{plugins/__init__.py → py.typed} +0 -0
  158. /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
  159. /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
  160. /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
  161. /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
  162. /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
  163. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
  164. {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()