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.
Files changed (56) hide show
  1. slidge/__main__.py +2 -3
  2. slidge/__version__.py +1 -1
  3. slidge/command/adhoc.py +1 -1
  4. slidge/command/base.py +4 -4
  5. slidge/command/user.py +5 -1
  6. slidge/contact/roster.py +9 -0
  7. slidge/core/config.py +0 -3
  8. slidge/core/dispatcher/__init__.py +3 -0
  9. slidge/core/{gateway → dispatcher}/caps.py +6 -4
  10. slidge/core/{gateway → dispatcher}/disco.py +11 -17
  11. slidge/core/dispatcher/message/__init__.py +10 -0
  12. slidge/core/dispatcher/message/chat_state.py +40 -0
  13. slidge/core/dispatcher/message/marker.py +62 -0
  14. slidge/core/dispatcher/message/message.py +397 -0
  15. slidge/core/dispatcher/muc/__init__.py +12 -0
  16. slidge/core/dispatcher/muc/admin.py +98 -0
  17. slidge/core/{gateway → dispatcher/muc}/mam.py +26 -15
  18. slidge/core/dispatcher/muc/misc.py +121 -0
  19. slidge/core/dispatcher/muc/owner.py +96 -0
  20. slidge/core/{gateway → dispatcher/muc}/ping.py +10 -15
  21. slidge/core/dispatcher/presence.py +177 -0
  22. slidge/core/{gateway → dispatcher}/registration.py +23 -2
  23. slidge/core/{gateway → dispatcher}/search.py +9 -14
  24. slidge/core/dispatcher/session_dispatcher.py +84 -0
  25. slidge/core/dispatcher/util.py +174 -0
  26. slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +26 -12
  27. slidge/core/{gateway/base.py → gateway.py} +42 -137
  28. slidge/core/mixins/attachment.py +24 -8
  29. slidge/core/mixins/base.py +2 -2
  30. slidge/core/mixins/lock.py +10 -8
  31. slidge/core/mixins/message.py +9 -203
  32. slidge/core/mixins/message_text.py +211 -0
  33. slidge/core/pubsub.py +2 -1
  34. slidge/core/session.py +28 -2
  35. slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +83 -0
  36. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
  37. slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +12 -1
  38. slidge/db/models.py +16 -1
  39. slidge/db/store.py +144 -11
  40. slidge/group/bookmarks.py +23 -1
  41. slidge/group/participant.py +5 -5
  42. slidge/group/room.py +10 -1
  43. slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
  44. slidge/util/test.py +9 -5
  45. slidge/util/types.py +6 -0
  46. slidge/util/util.py +5 -2
  47. {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/METADATA +2 -1
  48. {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/RECORD +51 -40
  49. slidge-0.2.0b0.dist-info/entry_points.txt +3 -0
  50. slidge/core/gateway/__init__.py +0 -3
  51. slidge/core/gateway/muc_admin.py +0 -35
  52. slidge/core/gateway/presence.py +0 -95
  53. slidge/core/gateway/session_dispatcher.py +0 -895
  54. slidge-0.2.0a9.dist-info/entry_points.txt +0 -3
  55. {slidge-0.2.0a9.dist-info → slidge-0.2.0b0.dist-info}/LICENSE +0 -0
  56. {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 .base import BaseGateway
12
+ from slidge.core.gateway import BaseGateway
8
13
 
9
14
 
10
- class Mam:
15
+ class MamMixin(DispatcherMixin):
11
16
  def __init__(self, xmpp: "BaseGateway"):
12
- self.xmpp = xmpp
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, # type:ignore
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, # type:ignore
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, # type:ignore
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.xmpp.get_muc_from_stanza(iq)
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: 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
- user = self.xmpp.store.users.get(iq.get_from())
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.xmpp.get_muc_from_stanza(iq)
82
+ muc = await self.get_muc_from_stanza(iq)
72
83
  await muc.send_mam_metadata(iq)