slidge 0.2.0a9__py3-none-any.whl → 0.2.0b0__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/__main__.py +2 -3
- slidge/__version__.py +1 -1
- slidge/command/adhoc.py +1 -1
- slidge/command/base.py +4 -4
- slidge/command/user.py +5 -1
- slidge/contact/roster.py +9 -0
- slidge/core/config.py +0 -3
- slidge/core/dispatcher/__init__.py +3 -0
- slidge/core/{gateway → dispatcher}/caps.py +6 -4
- slidge/core/{gateway → dispatcher}/disco.py +11 -17
- slidge/core/dispatcher/message/__init__.py +10 -0
- slidge/core/dispatcher/message/chat_state.py +40 -0
- slidge/core/dispatcher/message/marker.py +62 -0
- slidge/core/dispatcher/message/message.py +397 -0
- slidge/core/dispatcher/muc/__init__.py +12 -0
- slidge/core/dispatcher/muc/admin.py +98 -0
- slidge/core/{gateway → dispatcher/muc}/mam.py +26 -15
- slidge/core/dispatcher/muc/misc.py +121 -0
- slidge/core/dispatcher/muc/owner.py +96 -0
- slidge/core/{gateway → dispatcher/muc}/ping.py +10 -15
- slidge/core/dispatcher/presence.py +177 -0
- slidge/core/{gateway → dispatcher}/registration.py +23 -2
- slidge/core/{gateway → dispatcher}/search.py +9 -14
- slidge/core/dispatcher/session_dispatcher.py +84 -0
- slidge/core/dispatcher/util.py +174 -0
- slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +26 -12
- slidge/core/{gateway/base.py → gateway.py} +42 -137
- slidge/core/mixins/attachment.py +24 -8
- slidge/core/mixins/base.py +2 -2
- slidge/core/mixins/lock.py +10 -8
- slidge/core/mixins/message.py +9 -203
- slidge/core/mixins/message_text.py +211 -0
- slidge/core/pubsub.py +2 -1
- slidge/core/session.py +28 -2
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +83 -0
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +12 -1
- slidge/db/models.py +16 -1
- slidge/db/store.py +144 -11
- slidge/group/bookmarks.py +23 -1
- slidge/group/participant.py +5 -5
- slidge/group/room.py +10 -1
- slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
- slidge/util/test.py +9 -5
- slidge/util/types.py +6 -0
- slidge/util/util.py +5 -2
- {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/METADATA +2 -1
- {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/RECORD +51 -40
- slidge-0.2.0b0.dist-info/entry_points.txt +3 -0
- slidge/core/gateway/__init__.py +0 -3
- slidge/core/gateway/muc_admin.py +0 -35
- slidge/core/gateway/presence.py +0 -95
- slidge/core/gateway/session_dispatcher.py +0 -895
- slidge-0.2.0a9.dist-info/entry_points.txt +0 -3
- {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/LICENSE +0 -0
- {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,397 @@
|
|
1
|
+
import logging
|
2
|
+
from copy import copy
|
3
|
+
from xml.etree import ElementTree
|
4
|
+
|
5
|
+
from slixmpp import JID, Message
|
6
|
+
from slixmpp.exceptions import XMPPError
|
7
|
+
|
8
|
+
from ....contact.contact import LegacyContact
|
9
|
+
from ....group.participant import LegacyParticipant
|
10
|
+
from ....group.room import LegacyMUC
|
11
|
+
from ....util.types import LinkPreview, Recipient
|
12
|
+
from ....util.util import dict_to_named_tuple, remove_emoji_variation_selector_16
|
13
|
+
from ... import config
|
14
|
+
from ...session import BaseSession
|
15
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
16
|
+
|
17
|
+
|
18
|
+
class MessageContentMixin(DispatcherMixin):
|
19
|
+
def __init__(self, xmpp):
|
20
|
+
super().__init__(xmpp)
|
21
|
+
xmpp.add_event_handler("legacy_message", self.on_legacy_message)
|
22
|
+
xmpp.add_event_handler("message_correction", self.on_message_correction)
|
23
|
+
xmpp.add_event_handler("message_retract", self.on_message_retract)
|
24
|
+
xmpp.add_event_handler("groupchat_message", self.on_groupchat_message)
|
25
|
+
xmpp.add_event_handler("reactions", self.on_reactions)
|
26
|
+
|
27
|
+
async def on_groupchat_message(self, msg: Message) -> None:
|
28
|
+
await self.on_legacy_message(msg)
|
29
|
+
|
30
|
+
@exceptions_to_xmpp_errors
|
31
|
+
async def on_legacy_message(self, msg: Message):
|
32
|
+
"""
|
33
|
+
Meant to be called from :class:`BaseGateway` only.
|
34
|
+
|
35
|
+
:param msg:
|
36
|
+
:return:
|
37
|
+
"""
|
38
|
+
# we MUST not use `if m["replace"]["id"]` because it adds the tag if not
|
39
|
+
# present. this is a problem for MUC echoed messages
|
40
|
+
if msg.get_plugin("replace", check=True) is not None:
|
41
|
+
# ignore last message correction (handled by a specific method)
|
42
|
+
return
|
43
|
+
if msg.get_plugin("apply_to", check=True) is not None:
|
44
|
+
# ignore message retraction (handled by a specific method)
|
45
|
+
return
|
46
|
+
if msg.get_plugin("reactions", check=True) is not None:
|
47
|
+
# ignore message reaction fallback.
|
48
|
+
# the reaction itself is handled by self.react_from_msg().
|
49
|
+
return
|
50
|
+
if msg.get_plugin("retract", check=True) is not None:
|
51
|
+
# ignore message retraction fallback.
|
52
|
+
# the retraction itself is handled by self.on_retract
|
53
|
+
return
|
54
|
+
cid = None
|
55
|
+
if msg.get_plugin("html", check=True) is not None:
|
56
|
+
body = ElementTree.fromstring("<body>" + msg["html"].get_body() + "</body>")
|
57
|
+
p = body.findall("p")
|
58
|
+
if p is not None and len(p) == 1:
|
59
|
+
if p[0].text is None or not p[0].text.strip():
|
60
|
+
images = p[0].findall("img")
|
61
|
+
if len(images) == 1:
|
62
|
+
# no text, single img ⇒ this is a sticker
|
63
|
+
# other cases should be interpreted as "custom emojis" in text
|
64
|
+
src = images[0].get("src")
|
65
|
+
if src is not None and src.startswith("cid:"):
|
66
|
+
cid = src.removeprefix("cid:")
|
67
|
+
|
68
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
69
|
+
|
70
|
+
if msg.get_plugin("oob", check=True) is not None:
|
71
|
+
url = msg["oob"]["url"]
|
72
|
+
else:
|
73
|
+
url = None
|
74
|
+
|
75
|
+
if msg.get_plugin("reply", check=True):
|
76
|
+
text, reply_to_msg_id, reply_to, reply_fallback = await self.__get_reply(
|
77
|
+
msg, session, entity
|
78
|
+
)
|
79
|
+
else:
|
80
|
+
text = msg["body"]
|
81
|
+
reply_to_msg_id = None
|
82
|
+
reply_to = None
|
83
|
+
reply_fallback = None
|
84
|
+
|
85
|
+
if msg.get_plugin("link_previews", check=True):
|
86
|
+
link_previews = [
|
87
|
+
dict_to_named_tuple(p, LinkPreview) for p in msg["link_previews"]
|
88
|
+
]
|
89
|
+
else:
|
90
|
+
link_previews = []
|
91
|
+
|
92
|
+
if url:
|
93
|
+
legacy_msg_id = await self.__send_url(
|
94
|
+
url,
|
95
|
+
session,
|
96
|
+
entity,
|
97
|
+
reply_to_msg_id=reply_to_msg_id,
|
98
|
+
reply_to_fallback_text=reply_fallback,
|
99
|
+
reply_to=reply_to,
|
100
|
+
thread=thread,
|
101
|
+
)
|
102
|
+
elif cid:
|
103
|
+
legacy_msg_id = await self.__send_bob(
|
104
|
+
msg.get_from(),
|
105
|
+
cid,
|
106
|
+
session,
|
107
|
+
entity,
|
108
|
+
reply_to_msg_id=reply_to_msg_id,
|
109
|
+
reply_to_fallback_text=reply_fallback,
|
110
|
+
reply_to=reply_to,
|
111
|
+
thread=thread,
|
112
|
+
)
|
113
|
+
elif text:
|
114
|
+
if isinstance(entity, LegacyMUC):
|
115
|
+
mentions = {"mentions": await entity.parse_mentions(text)}
|
116
|
+
else:
|
117
|
+
mentions = {}
|
118
|
+
legacy_msg_id = await session.on_text(
|
119
|
+
entity,
|
120
|
+
text,
|
121
|
+
reply_to_msg_id=reply_to_msg_id,
|
122
|
+
reply_to_fallback_text=reply_fallback,
|
123
|
+
reply_to=reply_to,
|
124
|
+
thread=thread,
|
125
|
+
link_previews=link_previews,
|
126
|
+
**mentions,
|
127
|
+
)
|
128
|
+
else:
|
129
|
+
log.debug("Ignoring %s", msg.get_id())
|
130
|
+
return
|
131
|
+
|
132
|
+
if isinstance(entity, LegacyMUC):
|
133
|
+
await entity.echo(msg, legacy_msg_id)
|
134
|
+
if legacy_msg_id is not None:
|
135
|
+
self.xmpp.store.sent.set_group_message(
|
136
|
+
session.user_pk, str(legacy_msg_id), msg.get_id()
|
137
|
+
)
|
138
|
+
else:
|
139
|
+
self.__ack(msg)
|
140
|
+
if legacy_msg_id is not None:
|
141
|
+
self.xmpp.store.sent.set_message(
|
142
|
+
session.user_pk, str(legacy_msg_id), msg.get_id()
|
143
|
+
)
|
144
|
+
if session.MESSAGE_IDS_ARE_THREAD_IDS and (t := msg["thread"]):
|
145
|
+
self.xmpp.store.sent.set_thread(
|
146
|
+
session.user_pk, t, str(legacy_msg_id)
|
147
|
+
)
|
148
|
+
|
149
|
+
@exceptions_to_xmpp_errors
|
150
|
+
async def on_message_correction(self, msg: Message):
|
151
|
+
if msg.get_plugin("retract", check=True) is not None:
|
152
|
+
# ignore message retraction fallback (fallback=last msg correction)
|
153
|
+
return
|
154
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
155
|
+
xmpp_id = msg["replace"]["id"]
|
156
|
+
if isinstance(entity, LegacyMUC):
|
157
|
+
legacy_id_str = self.xmpp.store.sent.get_group_legacy_id(
|
158
|
+
session.user_pk, xmpp_id
|
159
|
+
)
|
160
|
+
if legacy_id_str is None:
|
161
|
+
legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
|
162
|
+
else:
|
163
|
+
legacy_id = self.xmpp.LEGACY_MSG_ID_TYPE(legacy_id_str)
|
164
|
+
else:
|
165
|
+
legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
|
166
|
+
|
167
|
+
if isinstance(entity, LegacyMUC):
|
168
|
+
mentions = await entity.parse_mentions(msg["body"])
|
169
|
+
else:
|
170
|
+
mentions = None
|
171
|
+
|
172
|
+
if previews := msg["link_previews"]:
|
173
|
+
link_previews = [dict_to_named_tuple(p, LinkPreview) for p in previews]
|
174
|
+
else:
|
175
|
+
link_previews = []
|
176
|
+
|
177
|
+
if legacy_id is None:
|
178
|
+
log.debug("Did not find legacy ID to correct")
|
179
|
+
new_legacy_msg_id = await session.on_text(
|
180
|
+
entity,
|
181
|
+
"Correction:" + msg["body"],
|
182
|
+
thread=thread,
|
183
|
+
mentions=mentions,
|
184
|
+
link_previews=link_previews,
|
185
|
+
)
|
186
|
+
elif (
|
187
|
+
not msg["body"].strip()
|
188
|
+
and config.CORRECTION_EMPTY_BODY_AS_RETRACTION
|
189
|
+
and entity.RETRACTION
|
190
|
+
):
|
191
|
+
await session.on_retract(entity, legacy_id, thread=thread)
|
192
|
+
new_legacy_msg_id = None
|
193
|
+
elif entity.CORRECTION:
|
194
|
+
new_legacy_msg_id = await session.on_correct(
|
195
|
+
entity,
|
196
|
+
msg["body"],
|
197
|
+
legacy_id,
|
198
|
+
thread=thread,
|
199
|
+
mentions=mentions,
|
200
|
+
link_previews=link_previews,
|
201
|
+
)
|
202
|
+
else:
|
203
|
+
session.send_gateway_message(
|
204
|
+
"Last message correction is not supported by this legacy service. "
|
205
|
+
"Slidge will send your correction as new message."
|
206
|
+
)
|
207
|
+
if (
|
208
|
+
config.LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND
|
209
|
+
and entity.RETRACTION
|
210
|
+
and legacy_id is not None
|
211
|
+
):
|
212
|
+
if legacy_id is not None:
|
213
|
+
session.send_gateway_message(
|
214
|
+
"Slidge will attempt to retract the original message you wanted"
|
215
|
+
" to edit."
|
216
|
+
)
|
217
|
+
await session.on_retract(entity, legacy_id, thread=thread)
|
218
|
+
|
219
|
+
new_legacy_msg_id = await session.on_text(
|
220
|
+
entity,
|
221
|
+
"Correction: " + msg["body"],
|
222
|
+
thread=thread,
|
223
|
+
mentions=mentions,
|
224
|
+
link_previews=link_previews,
|
225
|
+
)
|
226
|
+
|
227
|
+
if isinstance(entity, LegacyMUC):
|
228
|
+
if new_legacy_msg_id is not None:
|
229
|
+
self.xmpp.store.sent.set_group_message(
|
230
|
+
session.user_pk, new_legacy_msg_id, msg.get_id()
|
231
|
+
)
|
232
|
+
await entity.echo(msg, new_legacy_msg_id)
|
233
|
+
else:
|
234
|
+
self.__ack(msg)
|
235
|
+
if new_legacy_msg_id is not None:
|
236
|
+
self.xmpp.store.sent.set_message(
|
237
|
+
session.user_pk, new_legacy_msg_id, msg.get_id()
|
238
|
+
)
|
239
|
+
|
240
|
+
@exceptions_to_xmpp_errors
|
241
|
+
async def on_message_retract(self, msg: Message):
|
242
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
243
|
+
if not entity.RETRACTION:
|
244
|
+
raise XMPPError(
|
245
|
+
"bad-request",
|
246
|
+
"This legacy service does not support message retraction.",
|
247
|
+
)
|
248
|
+
xmpp_id: str = msg["retract"]["id"]
|
249
|
+
legacy_id = self._xmpp_msg_id_to_legacy(session, xmpp_id)
|
250
|
+
if legacy_id:
|
251
|
+
await session.on_retract(entity, legacy_id, thread=thread)
|
252
|
+
if isinstance(entity, LegacyMUC):
|
253
|
+
await entity.echo(msg, None)
|
254
|
+
else:
|
255
|
+
log.debug("Ignored retraction from user")
|
256
|
+
self.__ack(msg)
|
257
|
+
|
258
|
+
@exceptions_to_xmpp_errors
|
259
|
+
async def on_reactions(self, msg: Message):
|
260
|
+
session, entity, thread = await self._get_session_entity_thread(msg)
|
261
|
+
react_to: str = msg["reactions"]["id"]
|
262
|
+
|
263
|
+
special_msg = session.SPECIAL_MSG_ID_PREFIX and react_to.startswith(
|
264
|
+
session.SPECIAL_MSG_ID_PREFIX
|
265
|
+
)
|
266
|
+
|
267
|
+
if special_msg:
|
268
|
+
legacy_id = react_to
|
269
|
+
else:
|
270
|
+
legacy_id = self._xmpp_msg_id_to_legacy(session, react_to)
|
271
|
+
|
272
|
+
if not legacy_id:
|
273
|
+
log.debug("Ignored reaction from user")
|
274
|
+
raise XMPPError(
|
275
|
+
"internal-server-error",
|
276
|
+
"Could not convert the XMPP msg ID to a legacy ID",
|
277
|
+
)
|
278
|
+
|
279
|
+
emojis = [
|
280
|
+
remove_emoji_variation_selector_16(r["value"]) for r in msg["reactions"]
|
281
|
+
]
|
282
|
+
error_msg = None
|
283
|
+
entity = entity
|
284
|
+
|
285
|
+
if not special_msg:
|
286
|
+
if entity.REACTIONS_SINGLE_EMOJI and len(emojis) > 1:
|
287
|
+
error_msg = "Maximum 1 emoji/message"
|
288
|
+
|
289
|
+
if not error_msg and (subset := await entity.available_emojis(legacy_id)):
|
290
|
+
if not set(emojis).issubset(subset):
|
291
|
+
error_msg = f"You can only react with the following emojis: {''.join(subset)}"
|
292
|
+
|
293
|
+
if error_msg:
|
294
|
+
session.send_gateway_message(error_msg)
|
295
|
+
if not isinstance(entity, LegacyMUC):
|
296
|
+
# no need to carbon for groups, we just don't echo the stanza
|
297
|
+
entity.react(legacy_id, carbon=True) # type: ignore
|
298
|
+
await session.on_react(entity, legacy_id, [], thread=thread)
|
299
|
+
raise XMPPError("not-acceptable", text=error_msg)
|
300
|
+
|
301
|
+
await session.on_react(entity, legacy_id, emojis, thread=thread)
|
302
|
+
if isinstance(entity, LegacyMUC):
|
303
|
+
await entity.echo(msg, None)
|
304
|
+
else:
|
305
|
+
self.__ack(msg)
|
306
|
+
|
307
|
+
multi = self.xmpp.store.multi.get_xmpp_ids(session.user_pk, react_to)
|
308
|
+
if not multi:
|
309
|
+
return
|
310
|
+
multi = [m for m in multi if react_to != m]
|
311
|
+
|
312
|
+
if isinstance(entity, LegacyMUC):
|
313
|
+
for xmpp_id in multi:
|
314
|
+
mc = copy(msg)
|
315
|
+
mc["reactions"]["id"] = xmpp_id
|
316
|
+
await entity.echo(mc)
|
317
|
+
elif isinstance(entity, LegacyContact):
|
318
|
+
for xmpp_id in multi:
|
319
|
+
entity.react(legacy_id, emojis, xmpp_id=xmpp_id, carbon=True)
|
320
|
+
|
321
|
+
def __ack(self, msg: Message):
|
322
|
+
if not self.xmpp.PROPER_RECEIPTS:
|
323
|
+
self.xmpp.delivery_receipt.ack(msg)
|
324
|
+
|
325
|
+
async def __get_reply(
|
326
|
+
self, msg: Message, session: BaseSession, entity: Recipient
|
327
|
+
) -> tuple[
|
328
|
+
str, str | int | None, LegacyContact | LegacyParticipant | None, str | None
|
329
|
+
]:
|
330
|
+
try:
|
331
|
+
reply_to_msg_id = self._xmpp_msg_id_to_legacy(session, msg["reply"]["id"])
|
332
|
+
except XMPPError:
|
333
|
+
session.log.debug(
|
334
|
+
"Could not determine reply-to legacy msg ID, sending quote instead."
|
335
|
+
)
|
336
|
+
return msg["body"], None, None, None
|
337
|
+
|
338
|
+
reply_to_jid = JID(msg["reply"]["to"])
|
339
|
+
reply_to = None
|
340
|
+
if msg["type"] == "chat":
|
341
|
+
if reply_to_jid.bare != session.user_jid.bare:
|
342
|
+
try:
|
343
|
+
reply_to = await session.contacts.by_jid(reply_to_jid)
|
344
|
+
except XMPPError:
|
345
|
+
pass
|
346
|
+
elif msg["type"] == "groupchat":
|
347
|
+
nick = reply_to_jid.resource
|
348
|
+
try:
|
349
|
+
muc = await session.bookmarks.by_jid(reply_to_jid)
|
350
|
+
except XMPPError:
|
351
|
+
pass
|
352
|
+
else:
|
353
|
+
if nick != muc.user_nick:
|
354
|
+
reply_to = await muc.get_participant(
|
355
|
+
reply_to_jid.resource, store=False
|
356
|
+
)
|
357
|
+
|
358
|
+
if msg.get_plugin("fallback", check=True) and (
|
359
|
+
isinstance(entity, LegacyMUC) or entity.REPLIES
|
360
|
+
):
|
361
|
+
text = msg["fallback"].get_stripped_body(self.xmpp["xep_0461"].namespace)
|
362
|
+
try:
|
363
|
+
reply_fallback = msg["reply"].get_fallback_body()
|
364
|
+
except AttributeError:
|
365
|
+
reply_fallback = None
|
366
|
+
else:
|
367
|
+
text = msg["body"]
|
368
|
+
reply_fallback = None
|
369
|
+
|
370
|
+
return text, reply_to_msg_id, reply_to, reply_fallback
|
371
|
+
|
372
|
+
async def __send_url(
|
373
|
+
self, url: str, session: BaseSession, entity: Recipient, **kwargs
|
374
|
+
) -> int | str | None:
|
375
|
+
async with self.xmpp.http.get(url) as response:
|
376
|
+
if response.status >= 400:
|
377
|
+
session.log.warning(
|
378
|
+
"OOB url cannot be downloaded: %s, sending the URL as text"
|
379
|
+
" instead.",
|
380
|
+
response,
|
381
|
+
)
|
382
|
+
return await session.on_text(entity, url, **kwargs)
|
383
|
+
|
384
|
+
return await session.on_file(entity, url, http_response=response, **kwargs)
|
385
|
+
|
386
|
+
async def __send_bob(
|
387
|
+
self, from_: JID, cid: str, session: BaseSession, entity: Recipient, **kwargs
|
388
|
+
) -> int | str | None:
|
389
|
+
sticker = self.xmpp.store.bob.get_sticker(cid)
|
390
|
+
if sticker is None:
|
391
|
+
await self.xmpp.plugin["xep_0231"].get_bob(from_, cid)
|
392
|
+
sticker = self.xmpp.store.bob.get_sticker(cid)
|
393
|
+
assert sticker is not None
|
394
|
+
return await session.on_sticker(entity, sticker, **kwargs)
|
395
|
+
|
396
|
+
|
397
|
+
log = logging.getLogger(__name__)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from .admin import MucAdminMixin
|
2
|
+
from .mam import MamMixin
|
3
|
+
from .misc import MucMiscMixin
|
4
|
+
from .owner import MucOwnerMixin
|
5
|
+
from .ping import PingMixin
|
6
|
+
|
7
|
+
|
8
|
+
class MucMixin(PingMixin, MamMixin, MucAdminMixin, MucOwnerMixin, MucMiscMixin):
|
9
|
+
pass
|
10
|
+
|
11
|
+
|
12
|
+
__all__ = ("MucMixin",)
|
@@ -0,0 +1,98 @@
|
|
1
|
+
from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
|
2
|
+
from slixmpp.exceptions import XMPPError
|
3
|
+
from slixmpp.xmlstream import StanzaBase
|
4
|
+
|
5
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
6
|
+
|
7
|
+
|
8
|
+
class MucAdminMixin(DispatcherMixin):
|
9
|
+
def __init__(self, xmpp) -> None:
|
10
|
+
super().__init__(xmpp)
|
11
|
+
self.xmpp.register_handler(
|
12
|
+
CoroutineCallback(
|
13
|
+
"MUCModerate",
|
14
|
+
StanzaPath("iq/apply_to/moderate"),
|
15
|
+
self.on_user_moderation,
|
16
|
+
)
|
17
|
+
)
|
18
|
+
self.xmpp.register_handler(
|
19
|
+
CoroutineCallback(
|
20
|
+
"MUCSetAffiliation",
|
21
|
+
StanzaPath("iq@type=set/mucadmin_query"),
|
22
|
+
self.on_user_set_affiliation,
|
23
|
+
)
|
24
|
+
)
|
25
|
+
self.xmpp.register_handler(
|
26
|
+
CoroutineCallback(
|
27
|
+
"MUCGetAffiliation",
|
28
|
+
StanzaPath("iq@type=get/mucadmin_query"),
|
29
|
+
self.on_muc_admin_query_get,
|
30
|
+
)
|
31
|
+
)
|
32
|
+
|
33
|
+
@exceptions_to_xmpp_errors
|
34
|
+
async def on_user_moderation(self, iq: StanzaBase) -> None:
|
35
|
+
assert isinstance(iq, Iq)
|
36
|
+
muc = await self.get_muc_from_stanza(iq)
|
37
|
+
|
38
|
+
apply_to = iq["apply_to"]
|
39
|
+
xmpp_id = apply_to["id"]
|
40
|
+
if not xmpp_id:
|
41
|
+
raise XMPPError("bad-request", "Missing moderated message ID")
|
42
|
+
|
43
|
+
moderate = apply_to["moderate"]
|
44
|
+
if not moderate["retract"]:
|
45
|
+
raise XMPPError(
|
46
|
+
"feature-not-implemented",
|
47
|
+
"Slidge only implements moderation/retraction",
|
48
|
+
)
|
49
|
+
|
50
|
+
legacy_id = self._xmpp_msg_id_to_legacy(muc.session, xmpp_id)
|
51
|
+
await muc.session.on_moderate(muc, legacy_id, moderate["reason"] or None)
|
52
|
+
iq.reply(clear=True).send()
|
53
|
+
|
54
|
+
@exceptions_to_xmpp_errors
|
55
|
+
async def on_user_set_affiliation(self, iq: StanzaBase) -> None:
|
56
|
+
assert isinstance(iq, Iq)
|
57
|
+
muc = await self.get_muc_from_stanza(iq)
|
58
|
+
|
59
|
+
item = iq["mucadmin_query"]["item"]
|
60
|
+
if item["jid"]:
|
61
|
+
contact = await muc.session.contacts.by_jid(JID(item["jid"]))
|
62
|
+
else:
|
63
|
+
part = await muc.get_participant(
|
64
|
+
item["nick"], fill_first=True, raise_if_not_found=True
|
65
|
+
)
|
66
|
+
assert part.contact is not None
|
67
|
+
contact = part.contact
|
68
|
+
|
69
|
+
if item["affiliation"]:
|
70
|
+
await muc.on_set_affiliation(
|
71
|
+
contact,
|
72
|
+
item["affiliation"],
|
73
|
+
item["reason"] or None,
|
74
|
+
item["nick"] or None,
|
75
|
+
)
|
76
|
+
elif item["role"] == "none":
|
77
|
+
await muc.on_kick(contact, item["reason"] or None)
|
78
|
+
|
79
|
+
iq.reply(clear=True).send()
|
80
|
+
|
81
|
+
@exceptions_to_xmpp_errors
|
82
|
+
async def on_muc_admin_query_get(self, iq: StanzaBase) -> None:
|
83
|
+
assert isinstance(iq, Iq)
|
84
|
+
affiliation = iq["mucadmin_query"]["item"]["affiliation"]
|
85
|
+
|
86
|
+
if not affiliation:
|
87
|
+
raise XMPPError("bad-request")
|
88
|
+
|
89
|
+
session = await self._get_session(iq, 1, logged=True)
|
90
|
+
muc = await session.bookmarks.by_jid(iq.get_to())
|
91
|
+
|
92
|
+
reply = iq.reply()
|
93
|
+
reply.enable("mucadmin_query")
|
94
|
+
async for participant in muc.get_participants():
|
95
|
+
if not participant.affiliation == affiliation:
|
96
|
+
continue
|
97
|
+
reply["mucadmin_query"].append(participant.mucadmin_item())
|
98
|
+
reply.send()
|
@@ -1,42 +1,57 @@
|
|
1
|
+
import asyncio
|
1
2
|
from typing import TYPE_CHECKING
|
2
3
|
|
3
4
|
from slixmpp import CoroutineCallback, Iq, StanzaPath
|
4
5
|
from slixmpp.exceptions import XMPPError
|
6
|
+
from slixmpp.xmlstream import StanzaBase
|
7
|
+
|
8
|
+
from ... import config
|
9
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
5
10
|
|
6
11
|
if TYPE_CHECKING:
|
7
|
-
from .
|
12
|
+
from slidge.core.gateway import BaseGateway
|
8
13
|
|
9
14
|
|
10
|
-
class
|
15
|
+
class MamMixin(DispatcherMixin):
|
11
16
|
def __init__(self, xmpp: "BaseGateway"):
|
12
|
-
|
17
|
+
super().__init__(xmpp)
|
18
|
+
self.__mam_cleanup_task = xmpp.loop.create_task(self.__mam_cleanup())
|
13
19
|
xmpp.register_handler(
|
14
20
|
CoroutineCallback(
|
15
21
|
"MAM_query",
|
16
22
|
StanzaPath("iq@type=set/mam"),
|
17
|
-
self.__handle_mam,
|
23
|
+
self.__handle_mam,
|
18
24
|
)
|
19
25
|
)
|
20
26
|
xmpp.register_handler(
|
21
27
|
CoroutineCallback(
|
22
28
|
"MAM_get_from",
|
23
29
|
StanzaPath("iq@type=get/mam"),
|
24
|
-
self.__handle_mam_get_form,
|
30
|
+
self.__handle_mam_get_form,
|
25
31
|
)
|
26
32
|
)
|
27
33
|
xmpp.register_handler(
|
28
34
|
CoroutineCallback(
|
29
35
|
"MAM_get_meta",
|
30
36
|
StanzaPath("iq@type=get/mam_metadata"),
|
31
|
-
self.__handle_mam_metadata,
|
37
|
+
self.__handle_mam_metadata,
|
32
38
|
)
|
33
39
|
)
|
34
40
|
|
41
|
+
async def __mam_cleanup(self):
|
42
|
+
if not config.MAM_MAX_DAYS:
|
43
|
+
return
|
44
|
+
while True:
|
45
|
+
await asyncio.sleep(3600 * 6)
|
46
|
+
self.xmpp.store.mam.nuke_older_than(config.MAM_MAX_DAYS)
|
47
|
+
|
48
|
+
@exceptions_to_xmpp_errors
|
35
49
|
async def __handle_mam(self, iq: Iq):
|
36
|
-
muc = await self.
|
50
|
+
muc = await self.get_muc_from_stanza(iq)
|
37
51
|
await muc.send_mam(iq)
|
38
52
|
|
39
|
-
async def __handle_mam_get_form(self, iq:
|
53
|
+
async def __handle_mam_get_form(self, iq: StanzaBase):
|
54
|
+
assert isinstance(iq, Iq)
|
40
55
|
ito = iq.get_to()
|
41
56
|
|
42
57
|
if ito == self.xmpp.boundjid.bare:
|
@@ -44,12 +59,7 @@ class Mam:
|
|
44
59
|
text="No MAM on the component itself, use a JID with a resource"
|
45
60
|
)
|
46
61
|
|
47
|
-
|
48
|
-
if user is None:
|
49
|
-
raise XMPPError("registration-required")
|
50
|
-
|
51
|
-
session = self.xmpp.get_session_from_user(user)
|
52
|
-
|
62
|
+
session = await self._get_session(iq, 0, logged=True)
|
53
63
|
await session.bookmarks.by_jid(ito)
|
54
64
|
|
55
65
|
reply = iq.reply()
|
@@ -67,6 +77,7 @@ class Mam:
|
|
67
77
|
reply["mam"].append(form)
|
68
78
|
reply.send()
|
69
79
|
|
80
|
+
@exceptions_to_xmpp_errors
|
70
81
|
async def __handle_mam_metadata(self, iq: Iq):
|
71
|
-
muc = await self.
|
82
|
+
muc = await self.get_muc_from_stanza(iq)
|
72
83
|
await muc.send_mam_metadata(iq)
|