slidge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidge/__init__.py +61 -0
- slidge/__main__.py +192 -0
- slidge/command/__init__.py +28 -0
- slidge/command/adhoc.py +258 -0
- slidge/command/admin.py +193 -0
- slidge/command/base.py +441 -0
- slidge/command/categories.py +3 -0
- slidge/command/chat_command.py +288 -0
- slidge/command/register.py +179 -0
- slidge/command/user.py +250 -0
- slidge/contact/__init__.py +8 -0
- slidge/contact/contact.py +452 -0
- slidge/contact/roster.py +192 -0
- slidge/core/__init__.py +3 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +209 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +892 -0
- slidge/core/gateway/caps.py +63 -0
- slidge/core/gateway/delivery_receipt.py +52 -0
- slidge/core/gateway/disco.py +80 -0
- slidge/core/gateway/mam.py +75 -0
- slidge/core/gateway/muc_admin.py +35 -0
- slidge/core/gateway/ping.py +66 -0
- slidge/core/gateway/presence.py +95 -0
- slidge/core/gateway/registration.py +53 -0
- slidge/core/gateway/search.py +102 -0
- slidge/core/gateway/session_dispatcher.py +757 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +19 -0
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +31 -0
- slidge/core/mixins/disco.py +130 -0
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +398 -0
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +217 -0
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +525 -0
- slidge/core/session.py +752 -0
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +440 -0
- slidge/group/room.py +1095 -0
- slidge/migration.py +18 -0
- slidge/py.typed +0 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/slixfix/link_preview/__init__.py +10 -0
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/slixfix/xep_0077/__init__.py +10 -0
- slidge/slixfix/xep_0077/register.py +289 -0
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/slixfix/xep_0100/__init__.py +5 -0
- slidge/slixfix/xep_0100/gateway.py +121 -0
- slidge/slixfix/xep_0100/stanza.py +9 -0
- slidge/slixfix/xep_0153/__init__.py +10 -0
- slidge/slixfix/xep_0153/stanza.py +25 -0
- slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
- slidge/slixfix/xep_0264/__init__.py +5 -0
- slidge/slixfix/xep_0264/stanza.py +36 -0
- slidge/slixfix/xep_0264/thumbnail.py +23 -0
- slidge/slixfix/xep_0292/__init__.py +5 -0
- slidge/slixfix/xep_0292/vcard4.py +100 -0
- slidge/slixfix/xep_0313/__init__.py +12 -0
- slidge/slixfix/xep_0313/mam.py +262 -0
- slidge/slixfix/xep_0313/stanza.py +359 -0
- slidge/slixfix/xep_0317/__init__.py +5 -0
- slidge/slixfix/xep_0317/hats.py +17 -0
- slidge/slixfix/xep_0317/stanza.py +28 -0
- slidge/slixfix/xep_0356_old/__init__.py +7 -0
- slidge/slixfix/xep_0356_old/privilege.py +167 -0
- slidge/slixfix/xep_0356_old/stanza.py +44 -0
- slidge/slixfix/xep_0424/__init__.py +9 -0
- slidge/slixfix/xep_0424/retraction.py +77 -0
- slidge/slixfix/xep_0424/stanza.py +28 -0
- slidge/slixfix/xep_0490/__init__.py +8 -0
- slidge/slixfix/xep_0490/mds.py +47 -0
- slidge/slixfix/xep_0490/stanza.py +17 -0
- slidge/util/__init__.py +15 -0
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +229 -0
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +295 -0
- slidge/util/types.py +180 -0
- slidge/util/util.py +295 -0
- slidge-0.1.0.dist-info/LICENSE +661 -0
- slidge-0.1.0.dist-info/METADATA +109 -0
- slidge-0.1.0.dist-info/RECORD +96 -0
- slidge-0.1.0.dist-info/WHEEL +4 -0
- slidge-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,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)
|