slidge 0.1.0rc1__py3-none-any.whl → 0.1.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. slidge/__init__.py +54 -31
  2. slidge/__main__.py +51 -5
  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 +2 -0
  15. slidge/core/cache.py +121 -39
  16. slidge/core/config.py +116 -11
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +895 -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 +795 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +9 -1
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +6 -19
  34. slidge/core/mixins/disco.py +66 -15
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +254 -252
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +128 -31
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +275 -116
  41. slidge/core/session.py +586 -518
  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 +458 -0
  46. slidge/group/room.py +1103 -0
  47. slidge/migration.py +18 -0
  48. slidge/slixfix/__init__.py +68 -0
  49. slidge/{util/xep_0050 → slixfix/link_preview}/__init__.py +4 -5
  50. slidge/slixfix/link_preview/link_preview.py +17 -0
  51. slidge/slixfix/link_preview/stanza.py +99 -0
  52. slidge/slixfix/roster.py +60 -0
  53. slidge/{util → slixfix}/xep_0077/register.py +1 -2
  54. slidge/slixfix/xep_0077/stanza.py +104 -0
  55. slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
  56. slidge/slixfix/xep_0153/__init__.py +10 -0
  57. slidge/slixfix/xep_0153/stanza.py +25 -0
  58. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  59. slidge/slixfix/xep_0264/__init__.py +5 -0
  60. slidge/slixfix/xep_0264/stanza.py +36 -0
  61. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  62. slidge/slixfix/xep_0292/__init__.py +5 -0
  63. slidge/slixfix/xep_0292/vcard4.py +100 -0
  64. slidge/slixfix/xep_0313/__init__.py +12 -0
  65. slidge/slixfix/xep_0313/mam.py +262 -0
  66. slidge/slixfix/xep_0313/stanza.py +359 -0
  67. slidge/slixfix/xep_0317/__init__.py +5 -0
  68. slidge/slixfix/xep_0317/hats.py +17 -0
  69. slidge/slixfix/xep_0317/stanza.py +28 -0
  70. slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
  71. slidge/slixfix/xep_0424/__init__.py +9 -0
  72. slidge/slixfix/xep_0424/retraction.py +77 -0
  73. slidge/slixfix/xep_0424/stanza.py +28 -0
  74. slidge/slixfix/xep_0490/__init__.py +8 -0
  75. slidge/slixfix/xep_0490/mds.py +47 -0
  76. slidge/slixfix/xep_0490/stanza.py +17 -0
  77. slidge/util/__init__.py +4 -6
  78. slidge/util/archive_msg.py +61 -0
  79. slidge/util/conf.py +25 -4
  80. slidge/util/db.py +23 -69
  81. slidge/util/schema.sql +126 -0
  82. slidge/util/sql.py +508 -0
  83. slidge/util/test.py +136 -86
  84. slidge/util/types.py +155 -14
  85. slidge/util/util.py +225 -51
  86. slidge-0.1.2.dist-info/METADATA +111 -0
  87. slidge-0.1.2.dist-info/RECORD +96 -0
  88. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
  89. slidge/core/adhoc.py +0 -492
  90. slidge/core/chat_command.py +0 -197
  91. slidge/core/contact.py +0 -441
  92. slidge/core/disco.py +0 -59
  93. slidge/core/gateway.py +0 -899
  94. slidge/core/muc/__init__.py +0 -3
  95. slidge/core/muc/bookmarks.py +0 -74
  96. slidge/core/muc/participant.py +0 -152
  97. slidge/core/muc/room.py +0 -348
  98. slidge/plugins/discord/__init__.py +0 -121
  99. slidge/plugins/discord/client.py +0 -121
  100. slidge/plugins/discord/session.py +0 -172
  101. slidge/plugins/dummy.py +0 -334
  102. slidge/plugins/facebook.py +0 -591
  103. slidge/plugins/hackernews.py +0 -209
  104. slidge/plugins/mattermost/__init__.py +0 -1
  105. slidge/plugins/mattermost/api.py +0 -288
  106. slidge/plugins/mattermost/gateway.py +0 -417
  107. slidge/plugins/mattermost/websocket.py +0 -248
  108. slidge/plugins/signal/__init__.py +0 -4
  109. slidge/plugins/signal/config.py +0 -4
  110. slidge/plugins/signal/contact.py +0 -104
  111. slidge/plugins/signal/gateway.py +0 -379
  112. slidge/plugins/signal/group.py +0 -76
  113. slidge/plugins/signal/session.py +0 -515
  114. slidge/plugins/signal/txt.py +0 -13
  115. slidge/plugins/signal/util.py +0 -32
  116. slidge/plugins/skype.py +0 -310
  117. slidge/plugins/steam.py +0 -400
  118. slidge/plugins/telegram/__init__.py +0 -6
  119. slidge/plugins/telegram/client.py +0 -325
  120. slidge/plugins/telegram/config.py +0 -21
  121. slidge/plugins/telegram/contact.py +0 -154
  122. slidge/plugins/telegram/gateway.py +0 -182
  123. slidge/plugins/telegram/group.py +0 -184
  124. slidge/plugins/telegram/session.py +0 -275
  125. slidge/plugins/telegram/util.py +0 -153
  126. slidge/plugins/whatsapp/__init__.py +0 -6
  127. slidge/plugins/whatsapp/config.py +0 -17
  128. slidge/plugins/whatsapp/contact.py +0 -33
  129. slidge/plugins/whatsapp/event.go +0 -455
  130. slidge/plugins/whatsapp/gateway.go +0 -156
  131. slidge/plugins/whatsapp/gateway.py +0 -69
  132. slidge/plugins/whatsapp/go.mod +0 -17
  133. slidge/plugins/whatsapp/go.sum +0 -22
  134. slidge/plugins/whatsapp/session.go +0 -371
  135. slidge/plugins/whatsapp/session.py +0 -370
  136. slidge/util/xep_0030/__init__.py +0 -13
  137. slidge/util/xep_0030/disco.py +0 -811
  138. slidge/util/xep_0030/stanza/__init__.py +0 -7
  139. slidge/util/xep_0030/stanza/info.py +0 -270
  140. slidge/util/xep_0030/stanza/items.py +0 -147
  141. slidge/util/xep_0030/static.py +0 -467
  142. slidge/util/xep_0050/adhoc.py +0 -631
  143. slidge/util/xep_0050/stanza.py +0 -180
  144. slidge/util/xep_0077/stanza.py +0 -71
  145. slidge/util/xep_0292/__init__.py +0 -1
  146. slidge/util/xep_0292/stanza.py +0 -167
  147. slidge/util/xep_0292/vcard4.py +0 -74
  148. slidge/util/xep_0356/__init__.py +0 -7
  149. slidge/util/xep_0356/permissions.py +0 -35
  150. slidge/util/xep_0356/privilege.py +0 -160
  151. slidge/util/xep_0356/stanza.py +0 -44
  152. slidge/util/xep_0461/__init__.py +0 -6
  153. slidge/util/xep_0461/reply.py +0 -48
  154. slidge/util/xep_0461/stanza.py +0 -80
  155. slidge-0.1.0rc1.dist-info/METADATA +0 -171
  156. slidge-0.1.0rc1.dist-info/RECORD +0 -99
  157. /slidge/{plugins/__init__.py → py.typed} +0 -0
  158. /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
  159. /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
  160. /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
  161. /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
  162. /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
  163. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
  164. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -1,141 +1,75 @@
1
1
  import logging
2
- from datetime import datetime, timezone
3
- from io import BytesIO
4
- from pathlib import Path
5
- from typing import IO, Iterable, Optional, Union
6
-
7
- import aiohttp
8
- from slixmpp import JID, Message
9
- from slixmpp.plugins.xep_0363 import FileUploadError
10
- from slixmpp.types import MessageTypes
11
-
12
- from slidge.core import config
13
- from slidge.util.types import LegacyMessageType
14
-
15
- from ...util.types import ChatState, Marker, ProcessingHint
16
- from .base import BaseSender
17
-
18
-
19
- class MessageMaker(BaseSender):
20
- mtype: MessageTypes = NotImplemented
21
- STRIP_SHORT_DELAY = False
22
- USE_STANZA_ID = False
23
- _is_composing = False
24
-
25
- def _make_message(
26
- self,
27
- state: Optional[ChatState] = None,
28
- hints: Iterable[ProcessingHint] = (),
29
- legacy_msg_id: Optional[LegacyMessageType] = None,
30
- when: Optional[datetime] = None,
31
- reply_to_msg_id: Optional[LegacyMessageType] = None,
32
- reply_to_fallback_text: Optional[str] = None,
33
- reply_to_jid: Optional[JID] = None,
34
- carbon=False,
35
- **kwargs,
36
- ):
37
- body = kwargs.pop("mbody", None)
38
- mfrom = kwargs.pop("mfrom", self.jid)
39
- mto = kwargs.pop("mto", None)
40
- if carbon:
41
- # the msg needs to have jabber:client as xmlns, so
42
- # we don't want to associate with the XML stream
43
- msg_cls = Message # type:ignore
44
- else:
45
- msg_cls = self.xmpp.Message # type:ignore
46
- msg = msg_cls(sfrom=mfrom, stype=self.mtype, sto=mto, **kwargs)
47
- if body:
48
- if self._is_composing:
49
- state = "active"
50
- self._is_composing = False
51
- msg["body"] = body
52
- if state:
53
- self._is_composing = state == "composing"
54
- msg["chat_state"] = state
55
- for hint in hints:
56
- msg.enable(hint)
57
- self._set_msg_id(msg, legacy_msg_id)
58
- self._add_delay(msg, when)
59
- self._add_reply_to(msg, reply_to_msg_id, reply_to_fallback_text, reply_to_jid)
60
- return msg
61
-
62
- def _set_msg_id(
63
- self, msg: Message, legacy_msg_id: Optional[LegacyMessageType] = None
64
- ):
65
- if legacy_msg_id is not None:
66
- i = self._legacy_to_xmpp(legacy_msg_id)
67
- msg.set_id(i)
68
- if self.USE_STANZA_ID:
69
- msg["stanza_id"]["id"] = i
70
- msg["stanza_id"]["by"] = self.muc.jid # type: ignore
71
-
72
- def _legacy_to_xmpp(self, legacy_id: LegacyMessageType):
73
- return self.session.sent.get(
74
- legacy_id
75
- ) or self.session.legacy_msg_id_to_xmpp_msg_id(legacy_id)
76
-
77
- def _add_delay(self, msg: Message, when: Optional[datetime]):
78
- if when:
79
- if when.tzinfo is None:
80
- when = when.astimezone(timezone.utc)
81
- if self.STRIP_SHORT_DELAY:
82
- delay = datetime.now().astimezone(timezone.utc) - when
83
- if delay < config.IGNORE_DELAY_THRESHOLD:
84
- return
85
- msg["delay"].set_stamp(when)
86
- msg["delay"].set_from(self.xmpp.boundjid.bare)
87
-
88
- def _add_reply_to(
89
- self,
90
- msg: Message,
91
- reply_to_msg_id: Optional[LegacyMessageType] = None,
92
- reply_to_fallback_text: Optional[str] = None,
93
- reply_to_author: Optional[JID] = None,
94
- ):
95
- if reply_to_msg_id is not None:
96
- xmpp_id = self._legacy_to_xmpp(reply_to_msg_id)
97
- msg["reply"]["id"] = xmpp_id
98
- # FIXME: https://xmpp.org/extensions/xep-0461.html#usecases mentions that a full JID must be used here
99
- if reply_to_author:
100
- msg["reply"]["to"] = reply_to_author
101
- if reply_to_fallback_text:
102
- msg["feature_fallback"].add_quoted_fallback(reply_to_fallback_text)
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
103
24
 
104
25
 
105
26
  class ChatStateMixin(MessageMaker):
106
- def _chat_state(self, state: ChatState, **kwargs):
107
- msg = self._make_message(
108
- state=state, hints={"no-store"}, carbon=kwargs.get("carbon")
109
- )
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"})
110
37
  self._send(msg, **kwargs)
111
38
 
112
39
  def active(self, **kwargs):
113
40
  """
114
- Send an "active" chat state (:xep:`0085`) from this contact to the user.
41
+ Send an "active" chat state (:xep:`0085`) from this
42
+ :term:`XMPP Entity`.
115
43
  """
116
44
  self._chat_state("active", **kwargs)
117
45
 
118
46
  def composing(self, **kwargs):
119
47
  """
120
- Send a "composing" (ie "typing notification") chat state (:xep:`0085`) from this contact to the user.
48
+ Send a "composing" (ie "typing notification") chat state (:xep:`0085`)
49
+ from this :term:`XMPP Entity`.
121
50
  """
122
- self._chat_state("composing", **kwargs)
51
+ self._chat_state("composing", forced=True, **kwargs)
123
52
 
124
53
  def paused(self, **kwargs):
125
54
  """
126
- Send a "paused" (ie "typing paused notification") chat state (:xep:`0085`) from this contact to the user.
55
+ Send a "paused" (ie "typing paused notification") chat state
56
+ (:xep:`0085`) from this :term:`XMPP Entity`.
127
57
  """
128
58
  self._chat_state("paused", **kwargs)
129
59
 
130
60
  def inactive(self, **kwargs):
131
61
  """
132
- Send an "inactive" (ie "typing paused notification") chat state (:xep:`0085`) from this contact to the user.
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`.
133
65
  """
134
66
  self._chat_state("inactive", **kwargs)
135
67
 
136
68
  def gone(self, **kwargs):
137
69
  """
138
- Send an "inactive" (ie "typing paused notification") chat state (:xep:`0085`) from this contact to the user.
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`.
139
73
  """
140
74
  self._chat_state("gone", **kwargs)
141
75
 
@@ -150,14 +84,9 @@ class MarkerMixin(MessageMaker):
150
84
  msg[marker]["id"] = self._legacy_to_xmpp(legacy_msg_id)
151
85
  return msg
152
86
 
153
- def _make_receipt(self, legacy_msg_id: LegacyMessageType, carbon=False):
154
- msg = self._make_message(carbon=carbon)
155
- msg["receipt"] = self._legacy_to_xmpp(legacy_msg_id)
156
- return msg
157
-
158
87
  def ack(self, legacy_msg_id: LegacyMessageType, **kwargs):
159
88
  """
160
- Send an "acknowledged" message marker (:xep:`0333`) from this contact to the user.
89
+ Send an "acknowledged" message marker (:xep:`0333`) from this :term:`XMPP Entity`.
161
90
 
162
91
  :param legacy_msg_id: The message this marker refers to
163
92
  """
@@ -170,23 +99,28 @@ class MarkerMixin(MessageMaker):
170
99
 
171
100
  def received(self, legacy_msg_id: LegacyMessageType, **kwargs):
172
101
  """
173
- Send a "received" message marker (:xep:`0333`) and a "message delivery receipt"
174
- (:xep:`0184`)
175
- from this contact to the user
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`).
176
105
 
177
106
  :param legacy_msg_id: The message this marker refers to
178
107
  """
179
108
  carbon = kwargs.get("carbon")
180
- if not self.is_group:
181
- # msg receipts are NOT RECOMMENDED for MUCs
182
- self._send(self._make_receipt(legacy_msg_id, carbon=carbon), **kwargs)
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
+ )
183
117
  self._send(
184
118
  self._make_marker(legacy_msg_id, "received", carbon=carbon), **kwargs
185
119
  )
186
120
 
187
121
  def displayed(self, legacy_msg_id: LegacyMessageType, **kwargs):
188
122
  """
189
- Send a "displayed" message marker (:xep:`0333`) from this contact to the user.
123
+ Send a "displayed" message marker (:xep:`0333`) from this :term:`XMPP Entity`.
190
124
 
191
125
  :param legacy_msg_id: The message this marker refers to
192
126
  """
@@ -194,34 +128,52 @@ class MarkerMixin(MessageMaker):
194
128
  self._make_marker(legacy_msg_id, "displayed", carbon=kwargs.get("carbon")),
195
129
  **kwargs,
196
130
  )
131
+ if getattr(self, "is_user", False):
132
+ self.session.create_task(self.__send_mds(legacy_msg_id))
197
133
 
198
-
199
- class ContentMessageMixin(MessageMaker):
200
- async def _upload(
201
- self,
202
- filename: Union[Path, str],
203
- content_type: Optional[str] = None,
204
- input_file: Optional[IO[bytes]] = None,
205
- url: Optional[str] = None,
206
- ):
207
- if url is not None:
208
- if input_file is not None:
209
- raise TypeError("Either a URL or a file-like object")
210
- async with aiohttp.ClientSession() as session:
211
- async with session.get(url) as r:
212
- input_file = BytesIO(await r.read())
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
213
155
  try:
214
- return await self.xmpp["xep_0363"].upload_file(
215
- filename=filename,
216
- content_type=content_type,
217
- input_file=input_file,
218
- ifrom=config.UPLOAD_REQUESTER or self.xmpp.boundjid,
219
- )
220
- except FileUploadError as e:
221
- log.warning(
222
- "Something is wrong with the upload service, see the traceback below"
223
- )
224
- log.exception(e)
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)
225
177
 
226
178
  def send_text(
227
179
  self,
@@ -229,167 +181,217 @@ class ContentMessageMixin(MessageMaker):
229
181
  legacy_msg_id: Optional[LegacyMessageType] = None,
230
182
  *,
231
183
  when: Optional[datetime] = None,
232
- reply_to_msg_id: Optional[LegacyMessageType] = None,
233
- reply_to_fallback_text: Optional[str] = None,
234
- reply_to_jid: Optional[JID] = None,
235
- **kwargs,
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,
236
193
  ):
237
194
  """
238
- Transmit a message from the entity to the user
195
+ Send a text message from this :term:`XMPP Entity`.
239
196
 
240
- :param body: Context of the message
197
+ :param body: Content of the message
241
198
  :param legacy_msg_id: If you want to be able to transport read markers from the gateway
242
199
  user to the legacy network, specify this
243
200
  :param when: when the message was sent, for a "delay" tag (:xep:`0203`)
244
- :param reply_to_msg_id: Quote another message (:xep:`0461`)
245
- :param reply_to_fallback_text: Fallback text for clients not supporting :xep:`0461`
246
- :param reply_to_jid: JID of the quoted message author
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()``
247
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)
248
228
  msg = self._make_message(
249
229
  mbody=body,
250
- legacy_msg_id=legacy_msg_id,
230
+ legacy_msg_id=correction_event_id if correction else legacy_msg_id,
251
231
  when=when,
252
- reply_to_msg_id=reply_to_msg_id,
253
- reply_to_fallback_text=reply_to_fallback_text,
254
- reply_to_jid=reply_to_jid,
255
- hints=kwargs.get("hints") or {"markable", "store"},
256
- carbon=kwargs.get("carbon"),
232
+ reply_to=reply_to,
233
+ hints=hints or (),
234
+ carbon=carbon,
235
+ thread=thread,
236
+ link_previews=link_previews,
257
237
  )
258
- self._send(msg, **kwargs)
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)
259
241
 
260
- async def send_file(
242
+ def correct(
261
243
  self,
262
- filename: Union[Path, str],
263
- legacy_msg_id: Optional[LegacyMessageType] = None,
244
+ legacy_msg_id: LegacyMessageType,
245
+ new_text: str,
264
246
  *,
265
- content_type: Optional[str] = None,
266
- input_file: Optional[IO[bytes]] = None,
267
- url: Optional[str] = None,
268
- reply_to_msg_id: Optional[LegacyMessageType] = None,
269
- reply_to_fallback_text: Optional[str] = None,
270
- reply_to_jid: Optional[JID] = None,
271
247
  when: Optional[datetime] = None,
272
- caption: Optional[str] = None,
273
- **kwargs,
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,
274
256
  ):
275
257
  """
276
- Send a file using HTTP upload (:xep:`0363`)
258
+ Modify a message that was previously sent by this :term:`XMPP Entity`.
277
259
 
278
- :param filename: Filename to use or location on disk to the file to upload
279
- :param content_type: MIME type, inferred from filename if not given
280
- :param input_file: Optionally, a file like object instead of a file on disk.
281
- filename will still be used to give the uploaded file a name
282
- :param legacy_msg_id: If you want to be able to transport read markers from the gateway
283
- user to the legacy network, specify this
284
- :param url: Optionally, a URL of a file that slidge will download and upload to the
285
- default file upload service on the xmpp server it's running on. url and input_file
286
- are mutually exclusive.
287
- :param reply_to_msg_id: Quote another message (:xep:`0461`)
288
- :param reply_to_fallback_text: Fallback text for clients not supporting :xep:`0461`
289
- :param reply_to_jid: JID of the quoted message author
290
- :param when: when the file was sent, for a "delay" tag (:xep:`0203`)
291
- :param caption: an optional text that is linked to the file
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.
292
277
  """
293
- carbon = kwargs.pop("carbon", False)
294
- msg = self._make_message(
278
+ self.send_text(
279
+ new_text,
280
+ legacy_msg_id,
295
281
  when=when,
296
- reply_to_msg_id=reply_to_msg_id,
297
- reply_to_fallback_text=reply_to_fallback_text,
298
- reply_to_jid=reply_to_jid,
282
+ reply_to=reply_to,
283
+ hints=hints,
299
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,
300
291
  )
301
- uploaded_url = await self._upload(filename, content_type, input_file, url)
302
- if uploaded_url is None:
303
- if url is not None:
304
- uploaded_url = url
305
- else:
306
- msg["body"] = (
307
- "I tried to send a file, but something went wrong. "
308
- "Tell your XMPP admin to check slidge logs."
309
- )
310
- self._set_msg_id(msg, legacy_msg_id)
311
- self._send(msg, **kwargs)
312
- return
313
-
314
- msg["oob"]["url"] = uploaded_url
315
- msg["body"] = uploaded_url
316
- if caption:
317
- self._send(msg, carbon=carbon, **kwargs)
318
- self.send_text(
319
- caption, legacy_msg_id=legacy_msg_id, when=when, carbon=carbon, **kwargs
320
- )
321
- else:
322
- self._set_msg_id(msg, legacy_msg_id)
323
- self._send(msg, **kwargs)
324
-
325
- def correct(self, legacy_msg_id: LegacyMessageType, new_text: str, **kwargs):
326
- """
327
- Call this when a legacy contact has modified his last message content.
328
-
329
- Uses last message correction (:xep:`0308`)
330
-
331
- :param legacy_msg_id: Legacy message ID this correction refers to
332
- :param new_text: The new text
333
- """
334
- msg = self._make_message(mbody=new_text, carbon=kwargs.get("carbon"))
335
- msg["replace"]["id"] = self._legacy_to_xmpp(legacy_msg_id)
336
- self._send(msg, **kwargs)
337
292
 
338
293
  def react(
339
- self, legacy_msg_id: LegacyMessageType, emojis: Iterable[str] = (), **kwargs
294
+ self,
295
+ legacy_msg_id: LegacyMessageType,
296
+ emojis: Iterable[str] = (),
297
+ thread: Optional[LegacyThreadType] = None,
298
+ **kwargs,
340
299
  ):
341
300
  """
342
- Call this when a legacy contact reacts to a message
301
+ Send a reaction (:xep:`0444`) from this :term:`XMPP Entity`.
343
302
 
344
303
  :param legacy_msg_id: The message which the reaction refers to.
345
- :param emojis: A iterable of emojis used as reactions
346
- :return:
304
+ :param emojis: An iterable of emojis used as reactions
305
+ :param thread:
347
306
  """
348
- msg = self._make_message(hints={"store"}, carbon=kwargs.get("carbon"))
349
- xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
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)
350
313
  self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=emojis)
351
314
  self._send(msg, **kwargs)
352
315
 
353
- def retract(self, legacy_msg_id: LegacyMessageType, **kwargs):
316
+ def retract(
317
+ self,
318
+ legacy_msg_id: LegacyMessageType,
319
+ thread: Optional[LegacyThreadType] = None,
320
+ **kwargs,
321
+ ):
354
322
  """
355
- Call this when a legacy contact retracts (:XEP:`0424`) a message
323
+ Send a message retraction (:XEP:`0424`) from this :term:`XMPP Entity`.
356
324
 
357
325
  :param legacy_msg_id: Legacy ID of the message to delete
326
+ :param thread:
358
327
  """
359
328
  msg = self._make_message(
360
329
  state=None,
361
330
  hints={"store"},
362
- mbody=f"I have deleted the message {legacy_msg_id}, "
363
- "but your XMPP client does not support that",
331
+ mbody=f"/me retracted the message {legacy_msg_id}",
364
332
  carbon=kwargs.get("carbon"),
333
+ thread=thread,
365
334
  )
366
335
  msg.enable("fallback")
367
- msg["apply_to"]["id"] = self._legacy_to_xmpp(legacy_msg_id)
368
- msg["apply_to"].enable("retract")
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)
369
339
  self._send(msg, **kwargs)
370
340
 
371
341
 
372
342
  class CarbonMessageMixin(ContentMessageMixin, MarkerMixin):
373
343
  def _privileged_send(self, msg: Message):
374
- self.session.ignore_messages.add(msg.get_id())
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)
375
350
  try:
376
351
  self.xmpp["xep_0356"].send_privileged_message(msg)
377
352
  except PermissionError:
378
353
  try:
379
354
  self.xmpp["xep_0356_old"].send_privileged_message(msg)
380
355
  except PermissionError:
381
- log.warning(
382
- "Slidge does not have privileges to send message on behalf of user."
383
- "Refer to https://slidge.readthedocs.io/en/latest/admin/xmpp_server.html "
384
- "for more info."
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."
385
361
  )
386
362
 
387
363
 
388
- class MessageMixin(ChatStateMixin, MarkerMixin, ContentMessageMixin):
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):
389
391
  pass
390
392
 
391
393
 
392
- class MessageCarbonMixin(ChatStateMixin, CarbonMessageMixin):
394
+ class MessageCarbonMixin(InviteMixin, ChatStateMixin, CarbonMessageMixin):
393
395
  pass
394
396
 
395
397