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.
Files changed (96) hide show
  1. slidge/__init__.py +61 -0
  2. slidge/__main__.py +192 -0
  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 +3 -0
  15. slidge/core/cache.py +183 -0
  16. slidge/core/config.py +209 -0
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +892 -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 +757 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +19 -0
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +31 -0
  34. slidge/core/mixins/disco.py +130 -0
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +398 -0
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +217 -0
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +525 -0
  41. slidge/core/session.py +752 -0
  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 +440 -0
  46. slidge/group/room.py +1095 -0
  47. slidge/migration.py +18 -0
  48. slidge/py.typed +0 -0
  49. slidge/slixfix/__init__.py +68 -0
  50. slidge/slixfix/link_preview/__init__.py +10 -0
  51. slidge/slixfix/link_preview/link_preview.py +17 -0
  52. slidge/slixfix/link_preview/stanza.py +99 -0
  53. slidge/slixfix/roster.py +60 -0
  54. slidge/slixfix/xep_0077/__init__.py +10 -0
  55. slidge/slixfix/xep_0077/register.py +289 -0
  56. slidge/slixfix/xep_0077/stanza.py +104 -0
  57. slidge/slixfix/xep_0100/__init__.py +5 -0
  58. slidge/slixfix/xep_0100/gateway.py +121 -0
  59. slidge/slixfix/xep_0100/stanza.py +9 -0
  60. slidge/slixfix/xep_0153/__init__.py +10 -0
  61. slidge/slixfix/xep_0153/stanza.py +25 -0
  62. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  63. slidge/slixfix/xep_0264/__init__.py +5 -0
  64. slidge/slixfix/xep_0264/stanza.py +36 -0
  65. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  66. slidge/slixfix/xep_0292/__init__.py +5 -0
  67. slidge/slixfix/xep_0292/vcard4.py +100 -0
  68. slidge/slixfix/xep_0313/__init__.py +12 -0
  69. slidge/slixfix/xep_0313/mam.py +262 -0
  70. slidge/slixfix/xep_0313/stanza.py +359 -0
  71. slidge/slixfix/xep_0317/__init__.py +5 -0
  72. slidge/slixfix/xep_0317/hats.py +17 -0
  73. slidge/slixfix/xep_0317/stanza.py +28 -0
  74. slidge/slixfix/xep_0356_old/__init__.py +7 -0
  75. slidge/slixfix/xep_0356_old/privilege.py +167 -0
  76. slidge/slixfix/xep_0356_old/stanza.py +44 -0
  77. slidge/slixfix/xep_0424/__init__.py +9 -0
  78. slidge/slixfix/xep_0424/retraction.py +77 -0
  79. slidge/slixfix/xep_0424/stanza.py +28 -0
  80. slidge/slixfix/xep_0490/__init__.py +8 -0
  81. slidge/slixfix/xep_0490/mds.py +47 -0
  82. slidge/slixfix/xep_0490/stanza.py +17 -0
  83. slidge/util/__init__.py +15 -0
  84. slidge/util/archive_msg.py +61 -0
  85. slidge/util/conf.py +206 -0
  86. slidge/util/db.py +229 -0
  87. slidge/util/schema.sql +126 -0
  88. slidge/util/sql.py +508 -0
  89. slidge/util/test.py +295 -0
  90. slidge/util/types.py +180 -0
  91. slidge/util/util.py +295 -0
  92. slidge-0.1.0.dist-info/LICENSE +661 -0
  93. slidge-0.1.0.dist-info/METADATA +109 -0
  94. slidge-0.1.0.dist-info/RECORD +96 -0
  95. slidge-0.1.0.dist-info/WHEEL +4 -0
  96. slidge-0.1.0.dist-info/entry_points.txt +3 -0
slidge/migration.py ADDED
@@ -0,0 +1,18 @@
1
+ import logging
2
+ import shutil
3
+
4
+ from .core import config
5
+
6
+
7
+ def remove_avatar_cache_v1():
8
+ old_dir = config.HOME_DIR / "slidge_avatars"
9
+ if old_dir.exists():
10
+ log.info("Avatar cache dir v1 found, clearing it.")
11
+ shutil.rmtree(old_dir)
12
+
13
+
14
+ def migrate():
15
+ remove_avatar_cache_v1()
16
+
17
+
18
+ log = logging.getLogger(__name__)
slidge/py.typed ADDED
File without changes
@@ -0,0 +1,68 @@
1
+ # This module contains patches for slixmpp; some have pending requests upstream
2
+ # and should be removed on the next slixmpp release.
3
+
4
+ # ruff: noqa: F401
5
+
6
+ import slixmpp.plugins
7
+ from slixmpp import Message
8
+ from slixmpp.plugins.xep_0050 import XEP_0050, Command
9
+ from slixmpp.xmlstream import StanzaBase
10
+
11
+ from . import ( # xep_0356,
12
+ link_preview,
13
+ xep_0077,
14
+ xep_0100,
15
+ xep_0153,
16
+ xep_0264,
17
+ xep_0292,
18
+ xep_0313,
19
+ xep_0317,
20
+ xep_0356_old,
21
+ xep_0424,
22
+ xep_0490,
23
+ )
24
+
25
+
26
+ def session_bind(self, jid):
27
+ self.xmpp["xep_0030"].add_feature(Command.namespace)
28
+ # awful hack to for the disco items: we need to comment this line
29
+ # related issue: https://todo.sr.ht/~nicoco/slidge/131
30
+ # self.xmpp['xep_0030'].set_items(node=Command.namespace, items=tuple())
31
+
32
+
33
+ XEP_0050.session_bind = session_bind # type:ignore
34
+
35
+
36
+ def reply(self, body=None, clear=True):
37
+ """
38
+ Overrides slixmpp's Message.reply(), since it strips to sender's resource
39
+ for mtype=groupchat, and we do not want that, because when we raise an XMPPError,
40
+ we actually want to preserve the resource.
41
+ (this is called in RootStanza.exception() to handle XMPPErrors)
42
+ """
43
+ new_message = StanzaBase.reply(self, clear)
44
+ new_message["thread"] = self["thread"]
45
+ new_message["parent_thread"] = self["parent_thread"]
46
+
47
+ del new_message["id"]
48
+ if self.stream is not None and self.stream.use_message_ids:
49
+ new_message["id"] = self.stream.new_id()
50
+
51
+ if body is not None:
52
+ new_message["body"] = body
53
+ return new_message
54
+
55
+
56
+ slixmpp.plugins.PLUGINS.extend(
57
+ [
58
+ "link_preview",
59
+ "xep_0264",
60
+ "xep_0292_provider",
61
+ "xep_0317",
62
+ "xep_0356_old",
63
+ "xep_0490",
64
+ ]
65
+ )
66
+
67
+
68
+ Message.reply = reply # type: ignore
@@ -0,0 +1,10 @@
1
+ # Slixmpp: The Slick XMPP Library
2
+ # Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
3
+ # This file is part of Slixmpp.
4
+ # See the file LICENSE for copying permission.
5
+
6
+ from slixmpp.plugins.base import register_plugin
7
+
8
+ from .link_preview import LinkPreview
9
+
10
+ register_plugin(LinkPreview)
@@ -0,0 +1,17 @@
1
+ # Slixmpp: The Slick XMPP Library
2
+ # Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
3
+ # This file is part of Slixmpp.
4
+ # See the file LICENSE for copying permission.
5
+ from slixmpp.plugins import BasePlugin
6
+
7
+ from . import stanza
8
+
9
+
10
+ class LinkPreview(BasePlugin):
11
+ name = "link_preview"
12
+ description = "Sender-generated link previews"
13
+ dependencies = set()
14
+ stanza = stanza
15
+
16
+ def plugin_init(self):
17
+ stanza.register_plugin()
@@ -0,0 +1,99 @@
1
+ # Slixmpp: The Slick XMPP Library
2
+ # Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
3
+ # This file is part of Slixmpp.
4
+ # See the file LICENSE for copying permission.
5
+ from typing import Optional, Type
6
+
7
+ from slixmpp.stanza.message import Message
8
+ from slixmpp.xmlstream import ElementBase, register_stanza_plugin
9
+
10
+
11
+ class LinkPreview(ElementBase):
12
+ name = "Description"
13
+ namespace = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
14
+ plugin_attrib = "link_preview"
15
+ plugin_multi_attrib = "link_previews"
16
+ interfaces = {"about", "title", "description", "url", "image", "type", "site_name"}
17
+
18
+ def _set_og(self, el: ElementBase, value: str) -> None:
19
+ el.xml.text = value
20
+ self.xml.append(el.xml)
21
+
22
+ def _get_og(self, el: Type[ElementBase]) -> Optional[str]:
23
+ child = self.xml.find(f"{{{el.namespace}}}{el.name}")
24
+ if child is None:
25
+ return None
26
+ return child.text
27
+
28
+ def set_title(self, v: str) -> None:
29
+ self._set_og(Title(), v)
30
+
31
+ def get_title(self) -> Optional[str]:
32
+ return self._get_og(Title)
33
+
34
+ def set_description(self, v: str) -> None:
35
+ self._set_og(Description(), v)
36
+
37
+ def get_description(self) -> Optional[str]:
38
+ return self._get_og(Description)
39
+
40
+ def set_url(self, v: str) -> None:
41
+ self._set_og(Url(), v)
42
+
43
+ def get_url(self) -> Optional[str]:
44
+ return self._get_og(Url)
45
+
46
+ def set_image(self, v: str) -> None:
47
+ self._set_og(Image(), v)
48
+
49
+ def get_image(self) -> Optional[str]:
50
+ return self._get_og(Image)
51
+
52
+ def set_type(self, v: str) -> None:
53
+ self._set_og(Type_(), v)
54
+
55
+ def get_type(self) -> Optional[str]:
56
+ return self._get_og(Type_)
57
+
58
+ def set_site_name(self, v: str) -> None:
59
+ self._set_og(SiteName(), v)
60
+
61
+ def get_site_name(self) -> Optional[str]:
62
+ return self._get_og(SiteName)
63
+
64
+ def get_about(self) -> Optional[str]:
65
+ return self.xml.attrib.get(f"{{{self.namespace}}}about")
66
+
67
+
68
+ class OpenGraphMixin(ElementBase):
69
+ namespace = "https://ogp.me/ns#"
70
+
71
+
72
+ class Title(OpenGraphMixin):
73
+ name = plugin_attrib = "title"
74
+
75
+
76
+ class Description(OpenGraphMixin):
77
+ name = plugin_attrib = "description"
78
+
79
+
80
+ class Url(OpenGraphMixin):
81
+ name = plugin_attrib = "url"
82
+
83
+
84
+ class Image(OpenGraphMixin):
85
+ name = plugin_attrib = "image"
86
+
87
+
88
+ class Type_(OpenGraphMixin):
89
+ name = plugin_attrib = "type"
90
+
91
+
92
+ class SiteName(OpenGraphMixin):
93
+ name = plugin_attrib = "site_name"
94
+
95
+
96
+ def register_plugin():
97
+ for plugin in Title, Description, Url, Image, Type_, SiteName:
98
+ register_stanza_plugin(plugin, Title)
99
+ register_stanza_plugin(Message, LinkPreview, iterable=True)
@@ -0,0 +1,60 @@
1
+ from slixmpp import JID
2
+
3
+ from ..util.db import log, user_store
4
+
5
+
6
+ class YesSet(set):
7
+ """
8
+ A pseudo-set which always test True for membership
9
+ """
10
+
11
+ def __contains__(self, item):
12
+ log.debug("Test in")
13
+ return True
14
+
15
+
16
+ class RosterBackend:
17
+ """
18
+ A pseudo-roster for the gateway component.
19
+
20
+ If a user is in the user store, this will behave as if the user is part of the
21
+ roster with subscription "both", and "none" otherwise.
22
+
23
+ This is rudimentary but the only sane way I could come up with so far.
24
+ """
25
+
26
+ @staticmethod
27
+ def entries(_owner_jid, _default=None):
28
+ return YesSet()
29
+
30
+ @staticmethod
31
+ def save(_owner_jid, _jid, _item_state, _db_state):
32
+ pass
33
+
34
+ @staticmethod
35
+ def load(_owner_jid, jid, _db_state):
36
+ log.debug("Load %s", jid)
37
+ user = user_store.get_by_jid(JID(jid))
38
+ log.debug("User %s", user)
39
+ if user is None:
40
+ return {
41
+ "name": "",
42
+ "groups": [],
43
+ "from": False,
44
+ "to": False,
45
+ "pending_in": False,
46
+ "pending_out": False,
47
+ "whitelisted": False,
48
+ "subscription": "both",
49
+ }
50
+ else:
51
+ return {
52
+ "name": "",
53
+ "groups": [],
54
+ "from": True,
55
+ "to": True,
56
+ "pending_in": False,
57
+ "pending_out": False,
58
+ "whitelisted": False,
59
+ "subscription": "none",
60
+ }
@@ -0,0 +1,10 @@
1
+ # Slixmpp: The Slick XMPP Library
2
+ # Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
3
+ # This file is part of Slixmpp.
4
+ # See the file LICENSE for copying permission.
5
+ from slixmpp.plugins.base import register_plugin
6
+
7
+ from .register import XEP_0077
8
+ from .stanza import Register, RegisterFeature
9
+
10
+ register_plugin(XEP_0077)
@@ -0,0 +1,289 @@
1
+ # Slixmpp: The Slick XMPP Library
2
+ # Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
3
+ # This file is part of Slixmpp.
4
+ # See the file LICENSE for copying permission.
5
+ import logging
6
+ import ssl
7
+
8
+ from slixmpp.exceptions import XMPPError
9
+ from slixmpp.plugins import BasePlugin
10
+ from slixmpp.stanza import Iq, StreamFeatures
11
+ from slixmpp.xmlstream import JID, StanzaBase, register_stanza_plugin
12
+ from slixmpp.xmlstream.handler import CoroutineCallback
13
+ from slixmpp.xmlstream.matcher import StanzaPath
14
+
15
+ from . import stanza
16
+ from .stanza import Register, RegisterFeature
17
+
18
+ log = logging.getLogger(__name__)
19
+
20
+
21
+ # noinspection PyPep8Naming
22
+ class XEP_0077(BasePlugin):
23
+ """
24
+ XEP-0077: In-Band Registration
25
+
26
+ Events:
27
+
28
+ ::
29
+
30
+ user_register -- After successful validation and add to the user store
31
+ in api["user_validate"]
32
+ user_unregister -- After successful user removal in api["user_remove"]
33
+ user_modify -- After successful user modify in api["user_modify"]
34
+
35
+ Config:
36
+
37
+ ::
38
+
39
+ form_fields and form_instructions are only used if api["make_registration_form"] is
40
+ not overridden; in this case form_fields MUST be None
41
+
42
+ API:
43
+
44
+ ::
45
+
46
+ user_get(jid, node, ifrom, iq)
47
+ Returns a dict-like object containing `form_fields` for this user or None
48
+ user_remove(jid, node, ifrom, iq)
49
+ Removes a user or raise KeyError in case the user is not found in the user store
50
+ make_registration_form(self, jid, node, ifrom, iq)
51
+ Returns an iq reply to a registration form request, pre-filled and with
52
+ <registered/> in case the requesting entity is already registered to us
53
+ user_validate((self, jid, node, ifrom, registration)
54
+ Add the user to the user store or raise ValueError(msg) if any problem is encountered
55
+ msg is sent back to the XMPP client as an error message.
56
+ user_modify(jid, node, ifrom, iq)
57
+ Modify the user in the user store or raise ValueError(msg) (similarly to user_validate)
58
+ """
59
+
60
+ name = "xep_0077"
61
+ description = "XEP-0077: In-Band Registration (slidge)"
62
+ dependencies = {"xep_0004", "xep_0066"}
63
+ stanza = stanza
64
+ default_config = {
65
+ "create_account": True,
66
+ "force_registration": False,
67
+ "order": 50,
68
+ "form_fields": {"username", "password"},
69
+ "form_instructions": "Enter your credentials",
70
+ "enable_subscription": True,
71
+ }
72
+ _user_store: dict[str, dict[str, str]]
73
+
74
+ def plugin_init(self):
75
+ register_stanza_plugin(StreamFeatures, RegisterFeature)
76
+ register_stanza_plugin(Iq, Register)
77
+
78
+ if self.xmpp.is_component:
79
+ self.xmpp["xep_0030"].add_feature("jabber:iq:register")
80
+ self.xmpp.register_handler(
81
+ CoroutineCallback(
82
+ "registration",
83
+ StanzaPath(f"/iq@to={self.xmpp.boundjid.bare}/register"),
84
+ self._handle_registration,
85
+ )
86
+ )
87
+ self._user_store = {}
88
+ self.api.register(self._user_get, "user_get")
89
+ self.api.register(self._user_remove, "user_remove")
90
+ self.api.register(self._user_modify, "user_modify")
91
+ self.api.register(self._make_registration_form, "make_registration_form")
92
+ self.api.register(self._user_validate, "user_validate")
93
+ else:
94
+ self.xmpp.register_feature(
95
+ "register",
96
+ self._handle_register_feature,
97
+ restart=False,
98
+ order=self.order,
99
+ )
100
+
101
+ register_stanza_plugin(Register, self.xmpp["xep_0004"].stanza.Form)
102
+ register_stanza_plugin(Register, self.xmpp["xep_0066"].stanza.OOB)
103
+
104
+ self.xmpp.add_event_handler("connected", self._force_registration)
105
+
106
+ def plugin_end(self):
107
+ if not self.xmpp.is_component:
108
+ self.xmpp.unregister_feature("register", self.order)
109
+
110
+ def _user_get(self, _jid, _node, _ifrom, iq):
111
+ return self._user_store.get(iq["from"].bare)
112
+
113
+ def _user_remove(self, _jid, _node, _ifrom, iq):
114
+ return self._user_store.pop(iq["from"].bare)
115
+
116
+ async def _make_registration_form(self, _jid, _node, _ifrom, iq: Iq):
117
+ reg = iq["register"]
118
+ user = await self.api["user_get"](None, None, iq["from"], iq)
119
+
120
+ if user is None:
121
+ user = {}
122
+ else:
123
+ reg["registered"] = True
124
+
125
+ reg["instructions"] = self.form_instructions
126
+
127
+ for field in self.form_fields:
128
+ data = user.get(field, "")
129
+ if data:
130
+ reg[field] = data
131
+ else:
132
+ # Add a blank field
133
+ reg.add_field(field)
134
+
135
+ reply = iq.reply()
136
+ reply.set_payload(reg.xml)
137
+ return reply
138
+
139
+ def _user_validate(self, _jid, _node, ifrom, registration):
140
+ self._user_store[ifrom.bare] = {
141
+ key: registration[key] for key in self.form_fields
142
+ }
143
+
144
+ def _user_modify(self, _jid, _node, ifrom, registration):
145
+ self._user_store[ifrom.bare] = {
146
+ key: registration[key] for key in self.form_fields
147
+ }
148
+
149
+ async def _handle_registration(self, iq: StanzaBase):
150
+ if iq["type"] == "get":
151
+ if not self.enable_subscription:
152
+ raise XMPPError(
153
+ "bad-request",
154
+ text="You must use adhoc commands to register to this gateway.",
155
+ )
156
+ await self._send_form(iq)
157
+ elif iq["type"] == "set":
158
+ form_dict = iq["register"]["form"].get_values() or iq["register"]
159
+
160
+ if form_dict.get("remove"):
161
+ try:
162
+ await self.api["user_remove"](None, None, iq["from"], iq)
163
+ except KeyError:
164
+ _send_error(
165
+ iq,
166
+ "404",
167
+ "cancel",
168
+ "item-not-found",
169
+ "User not found",
170
+ )
171
+ else:
172
+ reply = iq.reply()
173
+ reply.send()
174
+ self.xmpp.event("user_unregister", iq)
175
+ return
176
+
177
+ if not self.enable_subscription:
178
+ raise XMPPError(
179
+ "bad-request",
180
+ text="You must use adhoc commands to register to this gateway.",
181
+ )
182
+
183
+ if self.form_fields is not None:
184
+ for field in self.form_fields:
185
+ if not iq["register"][field]:
186
+ # Incomplete Registration
187
+ _send_error(
188
+ iq,
189
+ "406",
190
+ "modify",
191
+ "not-acceptable",
192
+ "Please fill in all fields.",
193
+ )
194
+ return
195
+
196
+ user = await self.api["user_get"](None, None, iq["from"], iq)
197
+
198
+ try:
199
+ if user is None:
200
+ await self.api["user_validate"](None, None, iq["from"], form_dict)
201
+ else:
202
+ await self.api["user_modify"](None, None, iq["from"], form_dict)
203
+ except ValueError as e:
204
+ _send_error(iq, "406", "modify", "not-acceptable", "\n".join(e.args))
205
+ return
206
+
207
+ reply = iq.reply()
208
+ reply.send()
209
+
210
+ if user is None:
211
+ self.xmpp.event("user_register", iq)
212
+ else:
213
+ self.xmpp.event("user_modify", iq)
214
+
215
+ async def _send_form(self, iq):
216
+ reply = await self.api["make_registration_form"](None, None, iq["from"], iq)
217
+ reply.send()
218
+
219
+ def _force_registration(self, _event):
220
+ if self.force_registration:
221
+ self.xmpp.add_filter("in", self._force_stream_feature)
222
+
223
+ def _force_stream_feature(self, stanza_):
224
+ if isinstance(stanza_, StreamFeatures):
225
+ if not self.xmpp.disable_starttls:
226
+ if "starttls" not in self.xmpp.features:
227
+ return stanza_
228
+ elif not isinstance(self.xmpp.socket, ssl.SSLSocket):
229
+ return stanza_
230
+ if "mechanisms" not in self.xmpp.features:
231
+ log.debug("Forced adding in-band registration stream feature")
232
+ stanza_.enable("register")
233
+ self.xmpp.del_filter("in", self._force_stream_feature)
234
+ return stanza_
235
+
236
+ async def _handle_register_feature(self, _features):
237
+ if "mechanisms" in self.xmpp.features:
238
+ # We have already logged in with an account
239
+ return False
240
+
241
+ if self.create_account and self.xmpp.event_handled("register"):
242
+ form = await self.get_registration()
243
+ await self.xmpp.event_async("register", form)
244
+ return True
245
+ return False
246
+
247
+ def get_registration(self, jid=None, ifrom=None, timeout=None, callback=None):
248
+ iq = self.xmpp.Iq()
249
+ iq["type"] = "get"
250
+ iq["to"] = jid
251
+ iq["from"] = ifrom
252
+ iq.enable("register")
253
+ return iq.send(timeout=timeout, callback=callback)
254
+
255
+ def cancel_registration(self, jid=None, ifrom=None, timeout=None, callback=None):
256
+ iq = self.xmpp.Iq()
257
+ iq["type"] = "set"
258
+ iq["to"] = jid
259
+ iq["from"] = ifrom
260
+ iq["register"]["remove"] = True
261
+ return iq.send(timeout=timeout, callback=callback)
262
+
263
+ def change_password(
264
+ self, password, jid=None, ifrom=None, timeout=None, callback=None
265
+ ):
266
+ iq = self.xmpp.Iq()
267
+ iq["type"] = "set"
268
+ iq["to"] = jid
269
+ iq["from"] = ifrom
270
+ if self.xmpp.is_component:
271
+ ifrom = JID(ifrom)
272
+ iq["register"]["username"] = ifrom.user
273
+ else:
274
+ iq["register"]["username"] = self.xmpp.boundjid.user
275
+ iq["register"]["password"] = password
276
+ return iq.send(timeout=timeout, callback=callback)
277
+
278
+
279
+ def _send_error(iq, code, error_type, name, text=""):
280
+ # It would be nice to raise XMPPError but the iq payload
281
+ # should include the register info
282
+ reply = iq.reply()
283
+ reply.set_payload(iq["register"].xml)
284
+ reply.error()
285
+ reply["error"]["code"] = code
286
+ reply["error"]["type"] = error_type
287
+ reply["error"]["condition"] = name
288
+ reply["error"]["text"] = text
289
+ reply.send()
@@ -0,0 +1,104 @@
1
+ # Slixmpp: The Slick XMPP Library
2
+ # Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
3
+ # This file is part of Slixmpp.
4
+ # See the file LICENSE for copying permission.
5
+ from __future__ import unicode_literals
6
+
7
+ from typing import ClassVar, Set
8
+
9
+ from slixmpp.xmlstream import ElementBase
10
+
11
+
12
+ class Register(ElementBase):
13
+ namespace = "jabber:iq:register"
14
+ name = "query"
15
+ plugin_attrib = "register"
16
+ interfaces = {
17
+ "username",
18
+ "password",
19
+ "email",
20
+ "nick",
21
+ "name",
22
+ "first",
23
+ "last",
24
+ "address",
25
+ "city",
26
+ "state",
27
+ "zip",
28
+ "phone",
29
+ "url",
30
+ "date",
31
+ "misc",
32
+ "text",
33
+ "key",
34
+ "registered",
35
+ "remove",
36
+ "instructions",
37
+ "fields",
38
+ }
39
+ sub_interfaces = interfaces
40
+ form_fields = {
41
+ "username",
42
+ "password",
43
+ "email",
44
+ "nick",
45
+ "name",
46
+ "first",
47
+ "last",
48
+ "address",
49
+ "city",
50
+ "state",
51
+ "zip",
52
+ "phone",
53
+ "url",
54
+ "date",
55
+ "misc",
56
+ "text",
57
+ "key",
58
+ }
59
+
60
+ def get_registered(self):
61
+ present = self.xml.find("{%s}registered" % self.namespace)
62
+ return present is not None
63
+
64
+ def get_remove(self):
65
+ present = self.xml.find("{%s}remove" % self.namespace)
66
+ return present is not None
67
+
68
+ def set_registered(self, value):
69
+ if value:
70
+ self.add_field("registered")
71
+ else:
72
+ del self["registered"]
73
+
74
+ def set_remove(self, value):
75
+ if value:
76
+ self.add_field("remove")
77
+ else:
78
+ del self["remove"]
79
+
80
+ def add_field(self, value):
81
+ self._set_sub_text(value, "", keep=True)
82
+
83
+ def get_fields(self):
84
+ fields = set()
85
+ for field in self.form_fields:
86
+ if self.xml.find("{%s}%s" % (self.namespace, field)) is not None:
87
+ fields.add(field)
88
+ return fields
89
+
90
+ def set_fields(self, fields):
91
+ del self["fields"]
92
+ for field in fields:
93
+ self._set_sub_text(field, "", keep=True)
94
+
95
+ def del_fields(self):
96
+ for field in self.form_fields:
97
+ self._del_sub(field)
98
+
99
+
100
+ class RegisterFeature(ElementBase):
101
+ name = "register"
102
+ namespace = "http://jabber.org/features/iq-register"
103
+ plugin_attrib = name
104
+ interfaces: ClassVar[Set[str]] = set()
@@ -0,0 +1,5 @@
1
+ from slixmpp.plugins.base import register_plugin
2
+
3
+ from .gateway import XEP_0100
4
+
5
+ register_plugin(XEP_0100)