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
@@ -0,0 +1,359 @@
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 permissio
5
+ from datetime import datetime
6
+ from typing import Any, Iterable, List, Optional, Set, Union
7
+
8
+ from slixmpp.jid import JID
9
+ from slixmpp.plugins import xep_0082
10
+ from slixmpp.stanza import Message
11
+ from slixmpp.xmlstream import ET, ElementBase
12
+
13
+
14
+ class MAM(ElementBase):
15
+ """A MAM Query element.
16
+
17
+ .. code-block:: xml
18
+
19
+ <iq type='set' id='juliet1'>
20
+ <query xmlns='urn:xmpp:mam:2'>
21
+ <x xmlns='jabber:x:data' type='submit'>
22
+ <field var='FORM_TYPE' type='hidden'>
23
+ <value>urn:xmpp:mam:2</value>
24
+ </field>
25
+ <field var='with'>
26
+ <value>juliet@capulet.lit</value>
27
+ </field>
28
+ </x>
29
+ </query>
30
+ </iq>
31
+
32
+ """
33
+
34
+ name = "query"
35
+ namespace = "urn:xmpp:mam:2"
36
+ plugin_attrib = "mam"
37
+ #: Available interfaces:
38
+ #:
39
+ #: - ``queryid``: The MAM query id
40
+ #: - ``start`` and ``end``: Temporal boundaries of the query
41
+ #: - ``with``: JID of the other entity the conversation is with
42
+ #: - ``after_id``: Fetch stanzas after this specific ID
43
+ #: - ``before_id``: Fetch stanzas before this specific ID
44
+ #: - ``ids``: Fetch the stanzas matching those IDs
45
+ #: - ``results``: pseudo-interface used to accumulate MAM results during
46
+ #: fetch, not relevant for the stanza itself.
47
+ interfaces = {
48
+ "queryid",
49
+ "start",
50
+ "end",
51
+ "with",
52
+ "results",
53
+ "before_id",
54
+ "after_id",
55
+ "ids",
56
+ "flip_page",
57
+ }
58
+ sub_interfaces = {
59
+ "start",
60
+ "end",
61
+ "with",
62
+ "before_id",
63
+ "after_id",
64
+ "ids",
65
+ "flip_page",
66
+ }
67
+
68
+ def setup(self, xml=None):
69
+ ElementBase.setup(self, xml)
70
+ self._results: List[Message] = []
71
+
72
+ def _setup_form(self):
73
+ found = self.xml.find(
74
+ "{jabber:x:data}x/"
75
+ '{jabber:x:data}field[@var="FORM_TYPE"]/'
76
+ "{jabber:x:data}value[.='urn:xmpp:mam:2']"
77
+ )
78
+ if found is None:
79
+ self["form"]["type"] = "submit"
80
+ self["form"].add_field(
81
+ var="FORM_TYPE", ftype="hidden", value="urn:xmpp:mam:2"
82
+ )
83
+
84
+ def get_fields(self):
85
+ form = self.get_plugin("form", check=True)
86
+ if not form:
87
+ return {}
88
+ return form.get_fields()
89
+
90
+ def get_start(self) -> Optional[datetime]:
91
+ fields = self.get_fields()
92
+ field = fields.get("start")
93
+ if field and field["value"]:
94
+ return xep_0082.parse(field["value"])
95
+ return None
96
+
97
+ def set_start(self, value: Union[str, datetime]):
98
+ self._setup_form()
99
+ if isinstance(value, datetime):
100
+ value = xep_0082.format_datetime(value)
101
+ self.set_custom_field("start", value)
102
+
103
+ def get_end(self) -> Optional[datetime]:
104
+ fields = self.get_fields()
105
+ field = fields.get("end")
106
+ if field and field["value"]:
107
+ return xep_0082.parse(field["value"])
108
+ return None
109
+
110
+ def set_end(self, value: Union[str, datetime]):
111
+ if isinstance(value, datetime):
112
+ value = xep_0082.format_datetime(value)
113
+ self.set_custom_field("end", value)
114
+
115
+ def get_with(self) -> Optional[JID]:
116
+ fields = self.get_fields()
117
+ field = fields.get("with")
118
+ if field:
119
+ return JID(field["value"])
120
+ return None
121
+
122
+ def set_with(self, value: JID):
123
+ self.set_custom_field("with", value)
124
+
125
+ def set_custom_field(self, fieldname: str, value: Any):
126
+ self._setup_form()
127
+ fields = self.get_fields()
128
+ field = fields.get(fieldname)
129
+ if field:
130
+ field["value"] = str(value)
131
+ else:
132
+ field = self["form"].add_field(var=fieldname)
133
+ field["value"] = str(value)
134
+
135
+ def get_custom_field(self, fieldname: str) -> Optional[str]:
136
+ fields = self.get_fields()
137
+ field = fields.get(fieldname)
138
+ if field:
139
+ return field["value"]
140
+ return None
141
+
142
+ def set_before_id(self, value: str):
143
+ self.set_custom_field("before-id", value)
144
+
145
+ def get_before_id(self):
146
+ self.get_custom_field("before-id")
147
+
148
+ def set_after_id(self, value: str):
149
+ self.set_custom_field("after-id", value)
150
+
151
+ def get_after_id(self):
152
+ self.get_custom_field("after-id")
153
+
154
+ def set_ids(self, value: List[str]):
155
+ self._setup_form()
156
+ fields = self.get_fields()
157
+ field = fields.get("ids")
158
+ if field:
159
+ field["ids"] = value
160
+ else:
161
+ field = self["form"].add_field(var="ids")
162
+ field["value"] = value
163
+
164
+ def get_ids(self):
165
+ self.get_custom_field("id")
166
+
167
+ # The results interface is meant only as an easy
168
+ # way to access the set of collected message responses
169
+ # from the query.
170
+
171
+ def get_results(self) -> List[Message]:
172
+ return self._results
173
+
174
+ def set_results(self, values: List[Message]):
175
+ self._results = values
176
+
177
+ def del_results(self):
178
+ self._results = []
179
+
180
+ def get_flip_page(self):
181
+ return self.xml.find(f"{{{self.namespace}}}flip-page") is not None
182
+
183
+
184
+ class Fin(ElementBase):
185
+ """A MAM fin element (end of query).
186
+
187
+ .. code-block:: xml
188
+
189
+ <iq type='result' id='juliet1'>
190
+ <fin xmlns='urn:xmpp:mam:2'>
191
+ <set xmlns='http://jabber.org/protocol/rsm'>
192
+ <first index='0'>28482-98726-73623</first>
193
+ <last>09af3-cc343-b409f</last>
194
+ </set>
195
+ </fin>
196
+ </iq>
197
+
198
+ """
199
+
200
+ name = "fin"
201
+ namespace = "urn:xmpp:mam:2"
202
+ plugin_attrib = "mam_fin"
203
+ interfaces = {"results", "stable", "complete"}
204
+
205
+ def setup(self, xml=None):
206
+ ElementBase.setup(self, xml)
207
+ self._results: List[Message] = []
208
+
209
+ # The results interface is meant only as an easy
210
+ # way to access the set of collected message responses
211
+ # from the query.
212
+
213
+ def get_results(self) -> List[Message]:
214
+ return self._results
215
+
216
+ def set_results(self, values: List[Message]):
217
+ self._results = values
218
+
219
+ def del_results(self):
220
+ self._results = []
221
+
222
+
223
+ class Result(ElementBase):
224
+ """A MAM result payload.
225
+
226
+ .. code-block:: xml
227
+
228
+ <message id='aeb213' to='juliet@capulet.lit/chamber'>
229
+ <result xmlns='urn:xmpp:mam:2' queryid='f27' id='28482-98726-73623'>
230
+ <forwarded xmlns='urn:xmpp:forward:0'>
231
+ <delay xmlns='urn:xmpp:delay' stamp='2010-07-10T23:08:25Z'/>
232
+ <message xmlns='jabber:client' from="witch@shakespeare.lit"
233
+ to="macbeth@shakespeare.lit">
234
+ <body>Hail to thee</body>
235
+ </message>
236
+ </forwarded>
237
+ </result>
238
+ </message>
239
+ """
240
+
241
+ name = "result"
242
+ namespace = "urn:xmpp:mam:2"
243
+ plugin_attrib = "mam_result"
244
+ #: Available interfaces:
245
+ #:
246
+ #: - ``queryid``: MAM queryid
247
+ #: - ``id``: ID of the result
248
+ interfaces = {"queryid", "id"}
249
+
250
+
251
+ class Metadata(ElementBase):
252
+ """Element containing archive metadata
253
+
254
+ .. code-block:: xml
255
+
256
+ <iq type='result' id='jui8921rr9'>
257
+ <metadata xmlns='urn:xmpp:mam:2'>
258
+ <start id='YWxwaGEg' timestamp='2008-08-22T21:09:04Z' />
259
+ <end id='b21lZ2Eg' timestamp='2020-04-20T14:34:21Z' />
260
+ </metadata>
261
+ </iq>
262
+
263
+ """
264
+
265
+ name = "metadata"
266
+ namespace = "urn:xmpp:mam:2"
267
+ plugin_attrib = "mam_metadata"
268
+
269
+
270
+ class Start(ElementBase):
271
+ """Metadata about the start of an archive.
272
+
273
+ .. code-block:: xml
274
+
275
+ <iq type='result' id='jui8921rr9'>
276
+ <metadata xmlns='urn:xmpp:mam:2'>
277
+ <start id='YWxwaGEg' timestamp='2008-08-22T21:09:04Z' />
278
+ <end id='b21lZ2Eg' timestamp='2020-04-20T14:34:21Z' />
279
+ </metadata>
280
+ </iq>
281
+
282
+ """
283
+
284
+ name = "start"
285
+ namespace = "urn:xmpp:mam:2"
286
+ plugin_attrib = name
287
+ #: Available interfaces:
288
+ #:
289
+ #: - ``id``: ID of the first message of the archive
290
+ #: - ``timestamp`` (``datetime``): timestamp of the first message of the
291
+ #: archive
292
+ interfaces = {"id", "timestamp"}
293
+
294
+ def get_timestamp(self) -> Optional[datetime]:
295
+ """Get the timestamp.
296
+
297
+ :returns: The timestamp.
298
+ """
299
+ stamp = self.xml.attrib.get("timestamp", None)
300
+ if stamp is not None:
301
+ return xep_0082.parse(stamp)
302
+ return stamp
303
+
304
+ def set_timestamp(self, value: Union[datetime, str]):
305
+ """Set the timestamp.
306
+
307
+ :param value: Value of the timestamp (either a datetime or a
308
+ XEP-0082 timestamp string.
309
+ """
310
+ if isinstance(value, str):
311
+ value = xep_0082.parse(value)
312
+ value = xep_0082.format_datetime(value)
313
+ self.xml.attrib["timestamp"] = value
314
+
315
+
316
+ class End(ElementBase):
317
+ """Metadata about the end of an archive.
318
+
319
+ .. code-block:: xml
320
+
321
+ <iq type='result' id='jui8921rr9'>
322
+ <metadata xmlns='urn:xmpp:mam:2'>
323
+ <start id='YWxwaGEg' timestamp='2008-08-22T21:09:04Z' />
324
+ <end id='b21lZ2Eg' timestamp='2020-04-20T14:34:21Z' />
325
+ </metadata>
326
+ </iq>
327
+
328
+ """
329
+
330
+ name = "end"
331
+ namespace = "urn:xmpp:mam:2"
332
+ plugin_attrib = name
333
+ #: Available interfaces:
334
+ #:
335
+ #: - ``id``: ID of the first message of the archive
336
+ #: - ``timestamp`` (``datetime``): timestamp of the first message of the
337
+ #: archive
338
+ interfaces = {"id", "timestamp"}
339
+
340
+ def get_timestamp(self) -> Optional[datetime]:
341
+ """Get the timestamp.
342
+
343
+ :returns: The timestamp.
344
+ """
345
+ stamp = self.xml.attrib.get("timestamp", None)
346
+ if stamp is not None:
347
+ return xep_0082.parse(stamp)
348
+ return stamp
349
+
350
+ def set_timestamp(self, value: Union[datetime, str]):
351
+ """Set the timestamp.
352
+
353
+ :param value: Value of the timestamp (either a datetime or a
354
+ XEP-0082 timestamp string.
355
+ """
356
+ if isinstance(value, str):
357
+ value = xep_0082.parse(value)
358
+ value = xep_0082.format_datetime(value)
359
+ self.xml.attrib["timestamp"] = value
@@ -0,0 +1,5 @@
1
+ from slixmpp.plugins.base import register_plugin
2
+
3
+ from .hats import XEP_0317
4
+
5
+ register_plugin(XEP_0317)
@@ -0,0 +1,17 @@
1
+ from slixmpp.plugins import BasePlugin
2
+
3
+ from . import stanza
4
+
5
+
6
+ class XEP_0317(BasePlugin):
7
+ """
8
+ XEP-0317: Hats
9
+ """
10
+
11
+ name = "xep_0317"
12
+ description = "XEP-0317: Hats"
13
+ dependencies = {"xep_0045"}
14
+ stanza = stanza
15
+
16
+ def plugin_init(self):
17
+ stanza.register()
@@ -0,0 +1,28 @@
1
+ from slixmpp import Presence
2
+ from slixmpp.xmlstream import ElementBase, register_stanza_plugin
3
+
4
+ NS = "urn:xmpp:hats:0"
5
+
6
+
7
+ class Hats(ElementBase):
8
+ name = plugin_attrib = "hats"
9
+ namespace = NS
10
+
11
+ def add_hats(self, data: list[tuple[str, str]]):
12
+ for uri, title in data:
13
+ hat = Hat()
14
+ hat["uri"] = uri
15
+ hat["title"] = title
16
+ self.append(hat)
17
+
18
+
19
+ class Hat(ElementBase):
20
+ name = plugin_attrib = "hat"
21
+ namespace = NS
22
+ interfaces = {"uri", "title"}
23
+ plugin_multi_attrib = "hats"
24
+
25
+
26
+ def register():
27
+ register_stanza_plugin(Hats, Hat, iterable=True)
28
+ register_stanza_plugin(Presence, Hats)
@@ -0,0 +1,7 @@
1
+ from slixmpp.plugins.base import register_plugin
2
+
3
+ from . import stanza
4
+ from .privilege import XEP_0356_OLD
5
+ from .stanza import PermOld, PrivilegeOld
6
+
7
+ register_plugin(XEP_0356_OLD)
@@ -0,0 +1,167 @@
1
+ import logging
2
+ import typing
3
+ from collections import defaultdict
4
+
5
+ from slixmpp import JID, Iq, Message
6
+ from slixmpp.plugins.base import BasePlugin
7
+ from slixmpp.plugins.xep_0356.permissions import (
8
+ MessagePermission,
9
+ Permissions,
10
+ RosterAccess,
11
+ )
12
+ from slixmpp.types import JidStr
13
+ from slixmpp.xmlstream import StanzaBase
14
+ from slixmpp.xmlstream.handler import Callback
15
+ from slixmpp.xmlstream.matcher import StanzaPath
16
+
17
+ from . import stanza
18
+
19
+ log = logging.getLogger(__name__)
20
+
21
+
22
+ # noinspection PyPep8Naming
23
+ class XEP_0356_OLD(BasePlugin):
24
+ """
25
+ XEP-0356: Privileged Entity
26
+
27
+ Events:
28
+
29
+ ::
30
+
31
+ privileges_advertised_old -- Received message/privilege from the server
32
+ """
33
+
34
+ name = "xep_0356_old"
35
+ description = "XEP-0356: Privileged Entity (slidge - old namespace)"
36
+ dependencies = {"xep_0297"}
37
+ stanza = stanza
38
+
39
+ granted_privileges: defaultdict[JidStr, Permissions] = defaultdict(Permissions)
40
+
41
+ def plugin_init(self):
42
+ if not self.xmpp.is_component:
43
+ log.error("XEP 0356 is only available for components")
44
+ return
45
+
46
+ stanza.register()
47
+
48
+ self.xmpp.register_handler(
49
+ Callback(
50
+ "Privileges_old",
51
+ StanzaPath("message/privilege_old"),
52
+ self._handle_privilege,
53
+ )
54
+ )
55
+
56
+ def plugin_end(self):
57
+ self.xmpp.remove_handler("Privileges_old")
58
+
59
+ def _handle_privilege(self, msg: StanzaBase):
60
+ """
61
+ Called when the XMPP server advertise the component's privileges.
62
+
63
+ Stores the privileges in this instance's granted_privileges attribute (a dict)
64
+ and raises the privileges_advertised event
65
+ """
66
+ for perm in msg["privilege_old"]["perms"]:
67
+ setattr(
68
+ self.granted_privileges[msg.get_from()], perm["access"], perm["type"]
69
+ )
70
+ log.debug(f"Privileges (old): {self.granted_privileges}")
71
+ self.xmpp.event("privileges_advertised_old")
72
+
73
+ def send_privileged_message(self, msg: Message):
74
+ if (
75
+ self.granted_privileges[msg.get_from().domain].message
76
+ != MessagePermission.OUTGOING
77
+ ):
78
+ raise PermissionError(
79
+ "The server hasn't authorized us to send messages on behalf of other users"
80
+ )
81
+ else:
82
+ self._make_privileged_message(msg).send()
83
+
84
+ def _make_privileged_message(self, msg: Message):
85
+ server = msg.get_from().domain
86
+ wrapped = self.xmpp.make_message(mto=server, mfrom=self.xmpp.boundjid.bare)
87
+ wrapped["privilege_old"]["forwarded"].append(msg)
88
+ return wrapped
89
+
90
+ def _make_get_roster(self, jid: typing.Union[JID, str], **iq_kwargs):
91
+ return self.xmpp.make_iq_get(
92
+ queryxmlns="jabber:iq:roster",
93
+ ifrom=self.xmpp.boundjid.bare,
94
+ ito=jid,
95
+ **iq_kwargs,
96
+ )
97
+
98
+ def _make_set_roster(
99
+ self,
100
+ jid: typing.Union[JID, str],
101
+ roster_items: dict,
102
+ **iq_kwargs,
103
+ ):
104
+ iq = self.xmpp.make_iq_set(
105
+ ifrom=self.xmpp.boundjid.bare,
106
+ ito=jid,
107
+ **iq_kwargs,
108
+ )
109
+ iq["roster"]["items"] = roster_items
110
+ return iq
111
+
112
+ async def get_roster(self, jid: typing.Union[JID, str], **send_kwargs) -> Iq:
113
+ """
114
+ Return the roster of user on the server the component has privileged access to.
115
+
116
+ Raises ValueError if the server did not advertise the corresponding privileges
117
+
118
+ :param jid: user we want to fetch the roster from
119
+ """
120
+ if isinstance(jid, str):
121
+ jid = JID(jid)
122
+ if self.granted_privileges[jid.domain].roster not in (
123
+ RosterAccess.GET,
124
+ RosterAccess.BOTH,
125
+ ):
126
+ raise PermissionError(
127
+ "The server did not grant us privileges to get rosters"
128
+ )
129
+ else:
130
+ return await self._make_get_roster(jid).send(**send_kwargs)
131
+
132
+ async def set_roster(
133
+ self, jid: typing.Union[JID, str], roster_items: dict, **send_kwargs
134
+ ) -> Iq:
135
+ """
136
+ Return the roster of user on the server the component has privileged access to.
137
+
138
+ Raises ValueError if the server did not advertise the corresponding privileges
139
+
140
+ :param jid: user we want to add or modify roster items
141
+ :param roster_items: a dict containing the roster items' JIDs as keys and
142
+ nested dicts containing names, subscriptions and groups.
143
+ Example:
144
+ {
145
+ "friend1@example.com": {
146
+ "name": "Friend 1",
147
+ "subscription": "both",
148
+ "groups": ["group1", "group2"],
149
+ },
150
+ "friend2@example.com": {
151
+ "name": "Friend 2",
152
+ "subscription": "from",
153
+ "groups": ["group3"],
154
+ },
155
+ }
156
+ """
157
+ if isinstance(jid, str):
158
+ jid = JID(jid)
159
+ if self.granted_privileges[jid.domain].roster not in (
160
+ RosterAccess.GET,
161
+ RosterAccess.BOTH,
162
+ ):
163
+ raise PermissionError(
164
+ "The server did not grant us privileges to set rosters"
165
+ )
166
+ else:
167
+ return await self._make_set_roster(jid, roster_items).send(**send_kwargs)
@@ -0,0 +1,44 @@
1
+ from slixmpp.plugins.xep_0297 import Forwarded
2
+ from slixmpp.stanza import Message
3
+ from slixmpp.xmlstream import ElementBase, register_stanza_plugin
4
+
5
+
6
+ class PrivilegeOld(ElementBase):
7
+ namespace = "urn:xmpp:privilege:1"
8
+ name = "privilege"
9
+ plugin_attrib = "privilege_old"
10
+
11
+ def permission(self, access):
12
+ for perm in self["perms"]:
13
+ if perm["access"] == access:
14
+ return perm["type"]
15
+
16
+ def roster(self):
17
+ return self.permission("roster")
18
+
19
+ def message(self):
20
+ return self.permission("message")
21
+
22
+ def presence(self):
23
+ return self.permission("presence")
24
+
25
+ def add_perm(self, access, type):
26
+ # This should only be needed for servers, so maybe out of scope for slixmpp
27
+ perm = PermOld()
28
+ perm["type"] = type
29
+ perm["access"] = access
30
+ self.append(perm)
31
+
32
+
33
+ class PermOld(ElementBase):
34
+ namespace = "urn:xmpp:privilege:1"
35
+ name = "perm"
36
+ plugin_attrib = "perm"
37
+ plugin_multi_attrib = "perms"
38
+ interfaces = {"type", "access"}
39
+
40
+
41
+ def register():
42
+ register_stanza_plugin(Message, PrivilegeOld)
43
+ register_stanza_plugin(PrivilegeOld, Forwarded)
44
+ register_stanza_plugin(PrivilegeOld, PermOld, iterable=True)
@@ -0,0 +1,9 @@
1
+ # Slixmpp: The Slick XMPP Library
2
+ # Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
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 .retraction import XEP_0424
8
+
9
+ register_plugin(XEP_0424)