slidge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. slidge/__init__.py +61 -0
  2. slidge/__main__.py +192 -0
  3. slidge/command/__init__.py +28 -0
  4. slidge/command/adhoc.py +258 -0
  5. slidge/command/admin.py +193 -0
  6. slidge/command/base.py +441 -0
  7. slidge/command/categories.py +3 -0
  8. slidge/command/chat_command.py +288 -0
  9. slidge/command/register.py +179 -0
  10. slidge/command/user.py +250 -0
  11. slidge/contact/__init__.py +8 -0
  12. slidge/contact/contact.py +452 -0
  13. slidge/contact/roster.py +192 -0
  14. slidge/core/__init__.py +3 -0
  15. slidge/core/cache.py +183 -0
  16. slidge/core/config.py +209 -0
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +892 -0
  19. slidge/core/gateway/caps.py +63 -0
  20. slidge/core/gateway/delivery_receipt.py +52 -0
  21. slidge/core/gateway/disco.py +80 -0
  22. slidge/core/gateway/mam.py +75 -0
  23. slidge/core/gateway/muc_admin.py +35 -0
  24. slidge/core/gateway/ping.py +66 -0
  25. slidge/core/gateway/presence.py +95 -0
  26. slidge/core/gateway/registration.py +53 -0
  27. slidge/core/gateway/search.py +102 -0
  28. slidge/core/gateway/session_dispatcher.py +757 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +19 -0
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +31 -0
  34. slidge/core/mixins/disco.py +130 -0
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +398 -0
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +217 -0
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +525 -0
  41. slidge/core/session.py +752 -0
  42. slidge/group/__init__.py +10 -0
  43. slidge/group/archive.py +125 -0
  44. slidge/group/bookmarks.py +163 -0
  45. slidge/group/participant.py +440 -0
  46. slidge/group/room.py +1095 -0
  47. slidge/migration.py +18 -0
  48. slidge/py.typed +0 -0
  49. slidge/slixfix/__init__.py +68 -0
  50. slidge/slixfix/link_preview/__init__.py +10 -0
  51. slidge/slixfix/link_preview/link_preview.py +17 -0
  52. slidge/slixfix/link_preview/stanza.py +99 -0
  53. slidge/slixfix/roster.py +60 -0
  54. slidge/slixfix/xep_0077/__init__.py +10 -0
  55. slidge/slixfix/xep_0077/register.py +289 -0
  56. slidge/slixfix/xep_0077/stanza.py +104 -0
  57. slidge/slixfix/xep_0100/__init__.py +5 -0
  58. slidge/slixfix/xep_0100/gateway.py +121 -0
  59. slidge/slixfix/xep_0100/stanza.py +9 -0
  60. slidge/slixfix/xep_0153/__init__.py +10 -0
  61. slidge/slixfix/xep_0153/stanza.py +25 -0
  62. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  63. slidge/slixfix/xep_0264/__init__.py +5 -0
  64. slidge/slixfix/xep_0264/stanza.py +36 -0
  65. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  66. slidge/slixfix/xep_0292/__init__.py +5 -0
  67. slidge/slixfix/xep_0292/vcard4.py +100 -0
  68. slidge/slixfix/xep_0313/__init__.py +12 -0
  69. slidge/slixfix/xep_0313/mam.py +262 -0
  70. slidge/slixfix/xep_0313/stanza.py +359 -0
  71. slidge/slixfix/xep_0317/__init__.py +5 -0
  72. slidge/slixfix/xep_0317/hats.py +17 -0
  73. slidge/slixfix/xep_0317/stanza.py +28 -0
  74. slidge/slixfix/xep_0356_old/__init__.py +7 -0
  75. slidge/slixfix/xep_0356_old/privilege.py +167 -0
  76. slidge/slixfix/xep_0356_old/stanza.py +44 -0
  77. slidge/slixfix/xep_0424/__init__.py +9 -0
  78. slidge/slixfix/xep_0424/retraction.py +77 -0
  79. slidge/slixfix/xep_0424/stanza.py +28 -0
  80. slidge/slixfix/xep_0490/__init__.py +8 -0
  81. slidge/slixfix/xep_0490/mds.py +47 -0
  82. slidge/slixfix/xep_0490/stanza.py +17 -0
  83. slidge/util/__init__.py +15 -0
  84. slidge/util/archive_msg.py +61 -0
  85. slidge/util/conf.py +206 -0
  86. slidge/util/db.py +229 -0
  87. slidge/util/schema.sql +126 -0
  88. slidge/util/sql.py +508 -0
  89. slidge/util/test.py +295 -0
  90. slidge/util/types.py +180 -0
  91. slidge/util/util.py +295 -0
  92. slidge-0.1.0.dist-info/LICENSE +661 -0
  93. slidge-0.1.0.dist-info/METADATA +109 -0
  94. slidge-0.1.0.dist-info/RECORD +96 -0
  95. slidge-0.1.0.dist-info/WHEEL +4 -0
  96. slidge-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,398 @@
1
+ import logging
2
+ import uuid
3
+ import warnings
4
+ from datetime import datetime
5
+ from typing import TYPE_CHECKING, Iterable, Optional
6
+
7
+ from slixmpp import Iq, Message
8
+
9
+ from ...slixfix.xep_0490.mds import PUBLISH_OPTIONS
10
+ from ...util.types import (
11
+ ChatState,
12
+ LegacyMessageType,
13
+ LegacyThreadType,
14
+ LinkPreview,
15
+ Marker,
16
+ MessageReference,
17
+ ProcessingHint,
18
+ )
19
+ from .attachment import AttachmentMixin
20
+ from .message_maker import MessageMaker
21
+
22
+ if TYPE_CHECKING:
23
+ from ...group import LegacyMUC
24
+
25
+
26
+ class ChatStateMixin(MessageMaker):
27
+ def __init__(self):
28
+ super().__init__()
29
+ self.__last_chat_state: Optional[ChatState] = None
30
+
31
+ def _chat_state(self, state: ChatState, forced=False, **kwargs):
32
+ carbon = kwargs.get("carbon", False)
33
+ if carbon or (state == self.__last_chat_state and not forced):
34
+ return
35
+ self.__last_chat_state = state
36
+ msg = self._make_message(state=state, hints={"no-store"})
37
+ self._send(msg, **kwargs)
38
+
39
+ def active(self, **kwargs):
40
+ """
41
+ Send an "active" chat state (:xep:`0085`) from this
42
+ :term:`XMPP Entity`.
43
+ """
44
+ self._chat_state("active", **kwargs)
45
+
46
+ def composing(self, **kwargs):
47
+ """
48
+ Send a "composing" (ie "typing notification") chat state (:xep:`0085`)
49
+ from this :term:`XMPP Entity`.
50
+ """
51
+ self._chat_state("composing", forced=True, **kwargs)
52
+
53
+ def paused(self, **kwargs):
54
+ """
55
+ Send a "paused" (ie "typing paused notification") chat state
56
+ (:xep:`0085`) from this :term:`XMPP Entity`.
57
+ """
58
+ self._chat_state("paused", **kwargs)
59
+
60
+ def inactive(self, **kwargs):
61
+ """
62
+ Send an "inactive" (ie "contact has not interacted with the chat session
63
+ interface for an intermediate period of time") chat state (:xep:`0085`)
64
+ from this :term:`XMPP Entity`.
65
+ """
66
+ self._chat_state("inactive", **kwargs)
67
+
68
+ def gone(self, **kwargs):
69
+ """
70
+ Send a "gone" (ie "contact has not interacted with the chat session interface,
71
+ system, or device for a relatively long period of time") chat state
72
+ (:xep:`0085`) from this :term:`XMPP Entity`.
73
+ """
74
+ self._chat_state("gone", **kwargs)
75
+
76
+
77
+ class MarkerMixin(MessageMaker):
78
+ is_group: bool = NotImplemented
79
+
80
+ def _make_marker(
81
+ self, legacy_msg_id: LegacyMessageType, marker: Marker, carbon=False
82
+ ):
83
+ msg = self._make_message(carbon=carbon)
84
+ msg[marker]["id"] = self._legacy_to_xmpp(legacy_msg_id)
85
+ return msg
86
+
87
+ def ack(self, legacy_msg_id: LegacyMessageType, **kwargs):
88
+ """
89
+ Send an "acknowledged" message marker (:xep:`0333`) from this :term:`XMPP Entity`.
90
+
91
+ :param legacy_msg_id: The message this marker refers to
92
+ """
93
+ self._send(
94
+ self._make_marker(
95
+ legacy_msg_id, "acknowledged", carbon=kwargs.get("carbon")
96
+ ),
97
+ **kwargs,
98
+ )
99
+
100
+ def received(self, legacy_msg_id: LegacyMessageType, **kwargs):
101
+ """
102
+ Send a "received" message marker (:xep:`0333`) from this :term:`XMPP Entity`.
103
+ If called on a :class:`LegacyContact`, also send a delivery receipt
104
+ marker (:xep:`0184`).
105
+
106
+ :param legacy_msg_id: The message this marker refers to
107
+ """
108
+ carbon = kwargs.get("carbon")
109
+ if self.mtype == "chat":
110
+ self._send(
111
+ self.xmpp.delivery_receipt.make_ack(
112
+ self._legacy_to_xmpp(legacy_msg_id),
113
+ mfrom=self.jid,
114
+ mto=self.user.jid,
115
+ )
116
+ )
117
+ self._send(
118
+ self._make_marker(legacy_msg_id, "received", carbon=carbon), **kwargs
119
+ )
120
+
121
+ def displayed(self, legacy_msg_id: LegacyMessageType, **kwargs):
122
+ """
123
+ Send a "displayed" message marker (:xep:`0333`) from this :term:`XMPP Entity`.
124
+
125
+ :param legacy_msg_id: The message this marker refers to
126
+ """
127
+ self._send(
128
+ self._make_marker(legacy_msg_id, "displayed", carbon=kwargs.get("carbon")),
129
+ **kwargs,
130
+ )
131
+ if getattr(self, "is_user", False):
132
+ self.xmpp.loop.create_task(self.__send_mds(legacy_msg_id))
133
+
134
+ async def __send_mds(self, legacy_msg_id: LegacyMessageType):
135
+ # Send a MDS displayed marker on behalf of the user for a group chat
136
+ if muc := getattr(self, "muc", None):
137
+ muc_jid = muc.jid.bare
138
+ else:
139
+ # This is not implemented for 1:1 chat because it would rely on
140
+ # storing the XMPP-server injected stanza-id, which we don't track
141
+ # ATM.
142
+ # In practice, MDS should mostly be useful for public group chats,
143
+ # so it should not be an issue.
144
+ # We'll see if we need to implement that later
145
+ return
146
+ xmpp_msg_id = self._legacy_to_xmpp(legacy_msg_id)
147
+ iq = Iq(sto=self.user.bare_jid, sfrom=self.user.bare_jid, stype="set")
148
+ iq["pubsub"]["publish"]["node"] = self.xmpp["xep_0490"].stanza.NS
149
+ iq["pubsub"]["publish"]["item"]["id"] = muc_jid
150
+ displayed = self.xmpp["xep_0490"].stanza.Displayed()
151
+ displayed["stanza_id"]["id"] = xmpp_msg_id
152
+ displayed["stanza_id"]["by"] = muc_jid
153
+ iq["pubsub"]["publish"]["item"]["payload"] = displayed
154
+ iq["pubsub"]["publish_options"] = PUBLISH_OPTIONS
155
+ try:
156
+ await self.xmpp["xep_0356"].send_privileged_iq(iq)
157
+ except Exception as e:
158
+ self.session.log.debug("Could not MDS mark", exc_info=e)
159
+
160
+
161
+ class ContentMessageMixin(AttachmentMixin):
162
+ def __default_hints(self, hints: Optional[Iterable[ProcessingHint]] = None):
163
+ if hints is not None:
164
+ return hints
165
+ elif self.mtype == "chat":
166
+ return {"markable", "store"}
167
+ elif self.mtype == "groupchat":
168
+ return {"markable"}
169
+
170
+ def __replace_id(self, legacy_msg_id: LegacyMessageType):
171
+ if self.mtype == "groupchat":
172
+ return self.session.muc_sent_msg_ids.get(
173
+ legacy_msg_id
174
+ ) or self._legacy_to_xmpp(legacy_msg_id)
175
+ else:
176
+ return self._legacy_to_xmpp(legacy_msg_id)
177
+
178
+ def send_text(
179
+ self,
180
+ body: str,
181
+ legacy_msg_id: Optional[LegacyMessageType] = None,
182
+ *,
183
+ when: Optional[datetime] = None,
184
+ reply_to: Optional[MessageReference] = None,
185
+ thread: Optional[LegacyThreadType] = None,
186
+ hints: Optional[Iterable[ProcessingHint]] = None,
187
+ carbon=False,
188
+ archive_only=False,
189
+ correction=False,
190
+ correction_event_id: Optional[LegacyMessageType] = None,
191
+ link_previews: Optional[list[LinkPreview]] = None,
192
+ **send_kwargs,
193
+ ):
194
+ """
195
+ Send a text message from this :term:`XMPP Entity`.
196
+
197
+ :param body: Content of the message
198
+ :param legacy_msg_id: If you want to be able to transport read markers from the gateway
199
+ user to the legacy network, specify this
200
+ :param when: when the message was sent, for a "delay" tag (:xep:`0203`)
201
+ :param reply_to: Quote another message (:xep:`0461`)
202
+ :param hints:
203
+ :param thread:
204
+ :param carbon: (only used if called on a :class:`LegacyContact`)
205
+ Set this to ``True`` if this is actually a message sent **to** the
206
+ :class:`LegacyContact` by the :term:`User`.
207
+ Use this to synchronize outgoing history for legacy official apps.
208
+ :param correction: whether this message is a correction or not
209
+ :param correction_event_id: in the case where an ID is associated with the legacy
210
+ 'correction event', specify it here to use it on the XMPP side. If not specified,
211
+ a random ID will be used.
212
+ :param link_previews: A little of sender (or server, or gateway)-generated
213
+ previews of URLs linked in the body.
214
+ :param archive_only: (only in groups) Do not send this message to user,
215
+ but store it in the archive. Meant to be used during ``MUC.backfill()``
216
+ """
217
+ if carbon:
218
+ if not correction and legacy_msg_id in self.session.sent:
219
+ log.warning(
220
+ "Carbon message for a message an XMPP has sent? This is a bug! %s",
221
+ legacy_msg_id,
222
+ )
223
+ return
224
+ self.session.sent[legacy_msg_id] = self.session.legacy_to_xmpp_msg_id(
225
+ legacy_msg_id
226
+ )
227
+ hints = self.__default_hints(hints)
228
+ msg = self._make_message(
229
+ mbody=body,
230
+ legacy_msg_id=correction_event_id if correction else legacy_msg_id,
231
+ when=when,
232
+ reply_to=reply_to,
233
+ hints=hints or (),
234
+ carbon=carbon,
235
+ thread=thread,
236
+ link_previews=link_previews,
237
+ )
238
+ if correction:
239
+ msg["replace"]["id"] = self.__replace_id(legacy_msg_id)
240
+ return self._send(msg, archive_only=archive_only, carbon=carbon, **send_kwargs)
241
+
242
+ def correct(
243
+ self,
244
+ legacy_msg_id: LegacyMessageType,
245
+ new_text: str,
246
+ *,
247
+ when: Optional[datetime] = None,
248
+ reply_to: Optional[MessageReference] = None,
249
+ thread: Optional[LegacyThreadType] = None,
250
+ hints: Optional[Iterable[ProcessingHint]] = None,
251
+ carbon=False,
252
+ archive_only=False,
253
+ correction_event_id: Optional[LegacyMessageType] = None,
254
+ link_previews: Optional[list[LinkPreview]] = None,
255
+ **send_kwargs,
256
+ ):
257
+ """
258
+ Modify a message that was previously sent by this :term:`XMPP Entity`.
259
+
260
+ Uses last message correction (:xep:`0308`)
261
+
262
+ :param new_text: New content of the message
263
+ :param legacy_msg_id: The legacy message ID of the message to correct
264
+ :param when: when the message was sent, for a "delay" tag (:xep:`0203`)
265
+ :param reply_to: Quote another message (:xep:`0461`)
266
+ :param hints:
267
+ :param thread:
268
+ :param carbon: (only in 1:1) Reflect a message sent to this ``Contact`` by the user.
269
+ Use this to synchronize outgoing history for legacy official apps.
270
+ :param archive_only: (only in groups) Do not send this message to user,
271
+ but store it in the archive. Meant to be used during ``MUC.backfill()``
272
+ :param correction_event_id: in the case where an ID is associated with the legacy
273
+ 'correction event', specify it here to use it on the XMPP side. If not specified,
274
+ a random ID will be used.
275
+ :param link_previews: A little of sender (or server, or gateway)-generated
276
+ previews of URLs linked in the body.
277
+ """
278
+ self.send_text(
279
+ new_text,
280
+ legacy_msg_id,
281
+ when=when,
282
+ reply_to=reply_to,
283
+ hints=hints,
284
+ carbon=carbon,
285
+ thread=thread,
286
+ correction=True,
287
+ archive_only=archive_only,
288
+ correction_event_id=correction_event_id,
289
+ link_previews=link_previews,
290
+ **send_kwargs,
291
+ )
292
+
293
+ def react(
294
+ self,
295
+ legacy_msg_id: LegacyMessageType,
296
+ emojis: Iterable[str] = (),
297
+ thread: Optional[LegacyThreadType] = None,
298
+ **kwargs,
299
+ ):
300
+ """
301
+ Send a reaction (:xep:`0444`) from this :term:`XMPP Entity`.
302
+
303
+ :param legacy_msg_id: The message which the reaction refers to.
304
+ :param emojis: An iterable of emojis used as reactions
305
+ :param thread:
306
+ """
307
+ msg = self._make_message(
308
+ hints={"store"}, carbon=kwargs.get("carbon"), thread=thread
309
+ )
310
+ xmpp_id = kwargs.pop("xmpp_id", None)
311
+ if not xmpp_id:
312
+ xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
313
+ self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=emojis)
314
+ self._send(msg, **kwargs)
315
+
316
+ def retract(
317
+ self,
318
+ legacy_msg_id: LegacyMessageType,
319
+ thread: Optional[LegacyThreadType] = None,
320
+ **kwargs,
321
+ ):
322
+ """
323
+ Send a message retraction (:XEP:`0424`) from this :term:`XMPP Entity`.
324
+
325
+ :param legacy_msg_id: Legacy ID of the message to delete
326
+ :param thread:
327
+ """
328
+ msg = self._make_message(
329
+ state=None,
330
+ hints={"store"},
331
+ mbody=f"/me retracted the message {legacy_msg_id}",
332
+ carbon=kwargs.get("carbon"),
333
+ thread=thread,
334
+ )
335
+ msg.enable("fallback")
336
+ # namespace version mismatch between slidge and slixmpp, update me later
337
+ msg["fallback"]["for"] = self.xmpp["xep_0424"].namespace[:-1] + "1"
338
+ msg["retract"]["id"] = msg["replace"]["id"] = self.__replace_id(legacy_msg_id)
339
+ self._send(msg, **kwargs)
340
+
341
+
342
+ class CarbonMessageMixin(ContentMessageMixin, MarkerMixin):
343
+ def _privileged_send(self, msg: Message):
344
+ i = msg.get_id()
345
+ if not i:
346
+ i = str(uuid.uuid4())
347
+ msg.set_id(i)
348
+ msg.del_origin_id()
349
+ self.session.ignore_messages.add(i)
350
+ try:
351
+ self.xmpp["xep_0356"].send_privileged_message(msg)
352
+ except PermissionError:
353
+ try:
354
+ self.xmpp["xep_0356_old"].send_privileged_message(msg)
355
+ except PermissionError:
356
+ warnings.warn(
357
+ "Slidge does not have privileges to send message on behalf of"
358
+ " user.Refer to"
359
+ " https://slidge.readthedocs.io/en/latest/admin/xmpp_server.html"
360
+ " for more info."
361
+ )
362
+
363
+
364
+ class InviteMixin(MessageMaker):
365
+ def invite_to(
366
+ self,
367
+ muc: "LegacyMUC",
368
+ reason: Optional[str] = None,
369
+ password: Optional[str] = None,
370
+ **send_kwargs,
371
+ ):
372
+ """
373
+ Send an invitation to join a group (:xep:`0249`) from this :term:`XMPP Entity`.
374
+
375
+ :param muc: the muc the user is invited to
376
+ :param reason: a text explaining why the user should join this muc
377
+ :param password: maybe this will make sense later? not sure
378
+ :param send_kwargs: additional kwargs to be passed to _send()
379
+ (internal use by slidge)
380
+ """
381
+ msg = self._make_message(mtype="normal")
382
+ msg["groupchat_invite"]["jid"] = muc.jid
383
+ if reason:
384
+ msg["groupchat_invite"]["reason"] = reason
385
+ if password:
386
+ msg["groupchat_invite"]["password"] = password
387
+ self._send(msg, **send_kwargs)
388
+
389
+
390
+ class MessageMixin(InviteMixin, ChatStateMixin, MarkerMixin, ContentMessageMixin):
391
+ pass
392
+
393
+
394
+ class MessageCarbonMixin(InviteMixin, ChatStateMixin, CarbonMessageMixin):
395
+ pass
396
+
397
+
398
+ log = logging.getLogger(__name__)
@@ -0,0 +1,154 @@
1
+ import warnings
2
+ from copy import copy
3
+ from datetime import datetime, timezone
4
+ from typing import TYPE_CHECKING, Iterable, Optional, cast
5
+ from uuid import uuid4
6
+
7
+ from slixmpp import Message
8
+ from slixmpp.types import MessageTypes
9
+
10
+ from ...slixfix.link_preview.stanza import LinkPreview as LinkPreviewStanza
11
+ from ...util.db import GatewayUser
12
+ from ...util.types import (
13
+ ChatState,
14
+ LegacyMessageType,
15
+ LinkPreview,
16
+ MessageReference,
17
+ ProcessingHint,
18
+ )
19
+ from .. import config
20
+ from .base import BaseSender
21
+
22
+ if TYPE_CHECKING:
23
+ from ...group.participant import LegacyParticipant
24
+
25
+
26
+ class MessageMaker(BaseSender):
27
+ mtype: MessageTypes = NotImplemented
28
+ _can_send_carbon: bool = NotImplemented
29
+ STRIP_SHORT_DELAY = False
30
+ USE_STANZA_ID = False
31
+
32
+ def _make_message(
33
+ self,
34
+ state: Optional[ChatState] = None,
35
+ hints: Iterable[ProcessingHint] = (),
36
+ legacy_msg_id: Optional[LegacyMessageType] = None,
37
+ when: Optional[datetime] = None,
38
+ reply_to: Optional[MessageReference] = None,
39
+ carbon=False,
40
+ link_previews: Optional[Iterable[LinkPreview]] = None,
41
+ **kwargs,
42
+ ):
43
+ body = kwargs.pop("mbody", None)
44
+ mfrom = kwargs.pop("mfrom", self.jid)
45
+ mto = kwargs.pop("mto", None)
46
+ thread = kwargs.pop("thread", None)
47
+ if carbon and self._can_send_carbon:
48
+ # the msg needs to have jabber:client as xmlns, so
49
+ # we don't want to associate with the XML stream
50
+ msg_cls = Message
51
+ else:
52
+ msg_cls = self.xmpp.Message # type:ignore
53
+ msg = msg_cls(
54
+ sfrom=mfrom,
55
+ stype=kwargs.pop("mtype", None) or self.mtype,
56
+ sto=mto,
57
+ **kwargs,
58
+ )
59
+ if body:
60
+ msg["body"] = body
61
+ state = "active"
62
+ if thread:
63
+ known_threads = self.session.threads.inverse # type:ignore
64
+ msg["thread"] = known_threads.get(thread) or str(thread)
65
+ if state:
66
+ msg["chat_state"] = state
67
+ for hint in hints:
68
+ msg.enable(hint)
69
+ self._set_msg_id(msg, legacy_msg_id)
70
+ self._add_delay(msg, when)
71
+ if link_previews:
72
+ self._add_link_previews(msg, link_previews)
73
+ if reply_to:
74
+ self._add_reply_to(msg, reply_to)
75
+ return msg
76
+
77
+ def _set_msg_id(
78
+ self, msg: Message, legacy_msg_id: Optional[LegacyMessageType] = None
79
+ ):
80
+ if legacy_msg_id is not None:
81
+ i = self._legacy_to_xmpp(legacy_msg_id)
82
+ msg.set_id(i)
83
+ if self.USE_STANZA_ID:
84
+ msg["stanza_id"]["id"] = i
85
+ msg["stanza_id"]["by"] = self.muc.jid # type: ignore
86
+ elif self.USE_STANZA_ID:
87
+ msg["stanza_id"]["id"] = str(uuid4())
88
+ msg["stanza_id"]["by"] = self.muc.jid # type: ignore
89
+
90
+ def _legacy_to_xmpp(self, legacy_id: LegacyMessageType):
91
+ return self.session.sent.get(legacy_id) or self.session.legacy_to_xmpp_msg_id(
92
+ legacy_id
93
+ )
94
+
95
+ def _add_delay(self, msg: Message, when: Optional[datetime]):
96
+ if when:
97
+ if when.tzinfo is None:
98
+ when = when.astimezone(timezone.utc)
99
+ if self.STRIP_SHORT_DELAY:
100
+ delay = datetime.now().astimezone(timezone.utc) - when
101
+ if delay < config.IGNORE_DELAY_THRESHOLD:
102
+ return
103
+ msg["delay"].set_stamp(when)
104
+ msg["delay"].set_from(self.xmpp.boundjid.bare)
105
+
106
+ def _add_reply_to(self, msg: Message, reply_to: MessageReference):
107
+ xmpp_id = self._legacy_to_xmpp(reply_to.legacy_id)
108
+ msg["reply"]["id"] = xmpp_id
109
+
110
+ muc = getattr(self, "muc", None)
111
+
112
+ if entity := reply_to.author:
113
+ if isinstance(entity, GatewayUser):
114
+ if muc:
115
+ jid = copy(muc.jid)
116
+ jid.resource = fallback_nick = muc.user_nick
117
+ msg["reply"]["to"] = jid
118
+ else:
119
+ msg["reply"]["to"] = entity.jid
120
+ # TODO: here we should use preferably use the PEP nick of the user
121
+ # (but it doesn't matter much)
122
+ fallback_nick = entity.jid.local
123
+ else:
124
+ if muc:
125
+ if hasattr(entity, "muc"):
126
+ # TODO: accept a Contact here and use muc.get_participant_by_legacy_id()
127
+ # a bit of work because right now this is a sync function
128
+ entity = cast("LegacyParticipant", entity)
129
+ fallback_nick = entity.nickname
130
+ else:
131
+ warnings.warn(
132
+ "The author of a message reference in a MUC must be a"
133
+ " Participant instance, not a Contact"
134
+ )
135
+ fallback_nick = entity.name
136
+ else:
137
+ fallback_nick = entity.name
138
+ msg["reply"]["to"] = entity.jid
139
+ else:
140
+ fallback_nick = None
141
+
142
+ if fallback := reply_to.body:
143
+ msg["reply"].add_quoted_fallback(fallback, fallback_nick)
144
+
145
+ @staticmethod
146
+ def _add_link_previews(msg: Message, link_previews: Iterable[LinkPreview]):
147
+ for preview in link_previews:
148
+ element = LinkPreviewStanza()
149
+ for i, name in enumerate(preview._fields):
150
+ val = preview[i]
151
+ if not val:
152
+ continue
153
+ element[name] = val
154
+ msg.append(element)