slidge 0.2.0a9__py3-none-any.whl → 0.2.0b0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|