slidge 0.1.0b2__py3-none-any.whl → 0.1.1__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__init__.py +55 -31
- slidge/__main__.py +118 -116
- 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 +2 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +216 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +895 -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 +789 -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 +282 -116
- slidge/core/session.py +595 -372
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +458 -0
- slidge/group/room.py +1103 -0
- slidge/migration.py +18 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/{util/xep_0084 → slixfix/link_preview}/__init__.py +3 -5
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/{util → slixfix}/xep_0077/register.py +14 -2
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/{util → slixfix}/xep_0100/gateway.py +25 -15
- 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/{util → slixfix}/xep_0356_old/privilege.py +9 -7
- 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 +4 -6
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +57 -76
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +215 -25
- slidge/util/types.py +177 -4
- slidge/util/util.py +225 -59
- slidge-0.1.1.dist-info/METADATA +110 -0
- slidge-0.1.1.dist-info/RECORD +96 -0
- {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/WHEEL +1 -1
- slidge/core/contact.py +0 -891
- slidge/core/gateway.py +0 -916
- slidge/plugins/discord/__init__.py +0 -90
- slidge/plugins/discord/client.py +0 -108
- slidge/plugins/discord/session.py +0 -162
- slidge/plugins/dummy.py +0 -203
- slidge/plugins/facebook.py +0 -493
- slidge/plugins/hackernews.py +0 -213
- slidge/plugins/mattermost/__init__.py +0 -1
- slidge/plugins/mattermost/api.py +0 -280
- slidge/plugins/mattermost/gateway.py +0 -365
- slidge/plugins/mattermost/websocket.py +0 -252
- slidge/plugins/signal/__init__.py +0 -3
- slidge/plugins/signal/contact.py +0 -106
- slidge/plugins/signal/gateway.py +0 -282
- slidge/plugins/signal/session.py +0 -448
- slidge/plugins/signal/txt.py +0 -53
- slidge/plugins/skype.py +0 -325
- slidge/plugins/steam.py +0 -310
- slidge/plugins/telegram/__init__.py +0 -5
- slidge/plugins/telegram/client.py +0 -228
- slidge/plugins/telegram/config.py +0 -12
- slidge/plugins/telegram/contact.py +0 -176
- slidge/plugins/telegram/gateway.py +0 -150
- slidge/plugins/telegram/session.py +0 -256
- slidge/util/xep_0030/__init__.py +0 -13
- slidge/util/xep_0030/disco.py +0 -811
- slidge/util/xep_0030/stanza/__init__.py +0 -7
- slidge/util/xep_0030/stanza/info.py +0 -270
- slidge/util/xep_0030/stanza/items.py +0 -147
- slidge/util/xep_0030/static.py +0 -467
- slidge/util/xep_0055/__init__.py +0 -5
- slidge/util/xep_0055/search.py +0 -75
- slidge/util/xep_0055/stanza.py +0 -10
- slidge/util/xep_0077/stanza.py +0 -71
- slidge/util/xep_0084/avatar.py +0 -137
- slidge/util/xep_0084/stanza.py +0 -104
- slidge/util/xep_0115/__init__.py +0 -12
- slidge/util/xep_0115/caps.py +0 -379
- slidge/util/xep_0115/stanza.py +0 -16
- slidge/util/xep_0115/static.py +0 -137
- slidge/util/xep_0292/__init__.py +0 -1
- slidge/util/xep_0292/stanza.py +0 -167
- slidge/util/xep_0292/vcard4.py +0 -75
- slidge/util/xep_0333/__init__.py +0 -10
- slidge/util/xep_0333/markers.py +0 -96
- slidge/util/xep_0333/stanza.py +0 -34
- slidge/util/xep_0356/__init__.py +0 -7
- slidge/util/xep_0356/permissions.py +0 -35
- slidge/util/xep_0356/privilege.py +0 -160
- slidge/util/xep_0356/stanza.py +0 -44
- slidge/util/xep_0363/__init__.py +0 -16
- slidge/util/xep_0363/http_upload.py +0 -215
- slidge/util/xep_0363/stanza.py +0 -46
- slidge/util/xep_0461/__init__.py +0 -6
- slidge/util/xep_0461/reply.py +0 -48
- slidge/util/xep_0461/stanza.py +0 -47
- slidge-0.1.0b2.dist-info/METADATA +0 -171
- slidge-0.1.0b2.dist-info/RECORD +0 -81
- /slidge/{plugins/__init__.py → py.typed} +0 -0
- /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
- /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
- {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/LICENSE +0 -0
- {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/entry_points.txt +0 -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.session.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)
|