slidge 0.1.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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)