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.
- slidge/__init__.py +61 -0
- slidge/__main__.py +192 -0
- slidge/command/__init__.py +28 -0
- slidge/command/adhoc.py +258 -0
- slidge/command/admin.py +193 -0
- slidge/command/base.py +441 -0
- slidge/command/categories.py +3 -0
- slidge/command/chat_command.py +288 -0
- slidge/command/register.py +179 -0
- slidge/command/user.py +250 -0
- slidge/contact/__init__.py +8 -0
- slidge/contact/contact.py +452 -0
- slidge/contact/roster.py +192 -0
- slidge/core/__init__.py +3 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +209 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +892 -0
- slidge/core/gateway/caps.py +63 -0
- slidge/core/gateway/delivery_receipt.py +52 -0
- slidge/core/gateway/disco.py +80 -0
- slidge/core/gateway/mam.py +75 -0
- slidge/core/gateway/muc_admin.py +35 -0
- slidge/core/gateway/ping.py +66 -0
- slidge/core/gateway/presence.py +95 -0
- slidge/core/gateway/registration.py +53 -0
- slidge/core/gateway/search.py +102 -0
- slidge/core/gateway/session_dispatcher.py +757 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +19 -0
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +31 -0
- slidge/core/mixins/disco.py +130 -0
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +398 -0
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +217 -0
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +525 -0
- slidge/core/session.py +752 -0
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +440 -0
- slidge/group/room.py +1095 -0
- slidge/migration.py +18 -0
- slidge/py.typed +0 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/slixfix/link_preview/__init__.py +10 -0
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/slixfix/xep_0077/__init__.py +10 -0
- slidge/slixfix/xep_0077/register.py +289 -0
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/slixfix/xep_0100/__init__.py +5 -0
- slidge/slixfix/xep_0100/gateway.py +121 -0
- slidge/slixfix/xep_0100/stanza.py +9 -0
- slidge/slixfix/xep_0153/__init__.py +10 -0
- slidge/slixfix/xep_0153/stanza.py +25 -0
- slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
- slidge/slixfix/xep_0264/__init__.py +5 -0
- slidge/slixfix/xep_0264/stanza.py +36 -0
- slidge/slixfix/xep_0264/thumbnail.py +23 -0
- slidge/slixfix/xep_0292/__init__.py +5 -0
- slidge/slixfix/xep_0292/vcard4.py +100 -0
- slidge/slixfix/xep_0313/__init__.py +12 -0
- slidge/slixfix/xep_0313/mam.py +262 -0
- slidge/slixfix/xep_0313/stanza.py +359 -0
- slidge/slixfix/xep_0317/__init__.py +5 -0
- slidge/slixfix/xep_0317/hats.py +17 -0
- slidge/slixfix/xep_0317/stanza.py +28 -0
- slidge/slixfix/xep_0356_old/__init__.py +7 -0
- slidge/slixfix/xep_0356_old/privilege.py +167 -0
- slidge/slixfix/xep_0356_old/stanza.py +44 -0
- slidge/slixfix/xep_0424/__init__.py +9 -0
- slidge/slixfix/xep_0424/retraction.py +77 -0
- slidge/slixfix/xep_0424/stanza.py +28 -0
- slidge/slixfix/xep_0490/__init__.py +8 -0
- slidge/slixfix/xep_0490/mds.py +47 -0
- slidge/slixfix/xep_0490/stanza.py +17 -0
- slidge/util/__init__.py +15 -0
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +229 -0
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +295 -0
- slidge/util/types.py +180 -0
- slidge/util/util.py +295 -0
- slidge-0.1.0.dist-info/LICENSE +661 -0
- slidge-0.1.0.dist-info/METADATA +109 -0
- slidge-0.1.0.dist-info/RECORD +96 -0
- slidge-0.1.0.dist-info/WHEEL +4 -0
- 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,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,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)
|