slidge 0.2.0a10__py3-none-any.whl → 0.2.0b0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
slidge/__main__.py CHANGED
@@ -1,4 +1,3 @@
1
- if __name__ == "__main__":
2
- from slidge.main import main
1
+ from slidge.main import main
3
2
 
4
- main()
3
+ main()
slidge/__version__.py CHANGED
@@ -2,4 +2,4 @@ from slidge.util.util import get_version # noqa: F401
2
2
 
3
3
  # this is modified before publish, but if someone cloned from the repo,
4
4
  # it can help
5
- __version__ = "0.2.0alpha10"
5
+ __version__ = "0.2.0beta0"
slidge/command/user.py CHANGED
@@ -305,7 +305,10 @@ class LeaveGroup(Command):
305
305
  FormField(
306
306
  "group",
307
307
  "Group name",
308
- options=[{"label": g.name, "value": g.name} for g in groups],
308
+ type="list-single",
309
+ options=[
310
+ {"label": g.name, "value": str(i)} for i, g in enumerate(groups)
311
+ ],
309
312
  )
310
313
  ],
311
314
  handler=self.confirm, # type:ignore
@@ -329,3 +332,4 @@ class LeaveGroup(Command):
329
332
  @staticmethod
330
333
  async def finish(session: AnyBaseSession, _ifrom, group: LegacyMUC):
331
334
  await session.on_leave_group(group.legacy_id)
335
+ await session.bookmarks.remove(group, reason="You left this group via slidge.")
slidge/contact/roster.py CHANGED
@@ -154,6 +154,8 @@ class LegacyRoster(
154
154
  try:
155
155
  with contact.updating_info():
156
156
  await contact.avatar_wrap_update_info()
157
+ except XMPPError:
158
+ raise
157
159
  except Exception as e:
158
160
  raise XMPPError("internal-server-error", str(e))
159
161
  contact._caps_ver = await contact.get_caps_ver(contact.jid)
slidge/core/config.py CHANGED
@@ -154,9 +154,6 @@ LAST_SEEN_FALLBACK__DOC = (
154
154
  QR_TIMEOUT = 60
155
155
  QR_TIMEOUT__DOC = "Timeout for QR code flashing confirmation."
156
156
 
157
- DOWNLOAD_CHUNK_SIZE = 1024
158
- DOWNLOAD_CHUNK_SIZE__DOC = "Chunk size when slidge needs to download files using HTTP."
159
-
160
157
  LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND = False
161
158
  LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND__DOC = (
162
159
  "If the legacy service does not support last message correction but supports"
@@ -3,12 +3,7 @@ from slixmpp.xmlstream import StanzaBase
3
3
 
4
4
  from ....group.room import LegacyMUC
5
5
  from ....util.types import Recipient
6
- from ..util import (
7
- DispatcherMixin,
8
- _get_entity,
9
- _xmpp_to_legacy_thread,
10
- exceptions_to_xmpp_errors,
11
- )
6
+ from ..util import DispatcherMixin, _get_entity, exceptions_to_xmpp_errors
12
7
 
13
8
 
14
9
  class MarkerMixin(DispatcherMixin):
@@ -26,7 +21,7 @@ class MarkerMixin(DispatcherMixin):
26
21
  session = await self._get_session(msg)
27
22
 
28
23
  e: Recipient = await _get_entity(session, msg)
29
- legacy_thread = await _xmpp_to_legacy_thread(session, msg, e)
24
+ legacy_thread = await self._xmpp_to_legacy_thread(session, msg, e)
30
25
  displayed_msg_id = msg["displayed"]["id"]
31
26
  if not isinstance(e, LegacyMUC) and self.xmpp.MARK_ALL_MESSAGES:
32
27
  to_mark = e.get_msg_xmpp_id_up_to(displayed_msg_id) # type: ignore
@@ -54,6 +54,9 @@ class MucMiscMixin(DispatcherMixin):
54
54
  muc = await self.get_muc_from_stanza(iq)
55
55
  await muc.session.on_leave_group(muc.legacy_id)
56
56
  iq.reply().send()
57
+ await muc.session.bookmarks.remove(
58
+ muc, "You left this chat from an XMPP client."
59
+ )
57
60
  return
58
61
 
59
62
  raise XMPPError("feature-not-implemented")
@@ -88,7 +88,7 @@ class MucOwnerMixin(DispatcherMixin):
88
88
  if reason is not None:
89
89
  presence["muc"]["destroy"]["reason"] = reason
90
90
  user_participant._send(presence)
91
- muc.session.bookmarks.remove(muc)
91
+ await muc.session.bookmarks.remove(muc, kick=False)
92
92
  clear = True
93
93
  else:
94
94
  raise XMPPError("bad-request")
@@ -96,9 +96,31 @@ class DispatcherMixin:
96
96
  ) -> tuple["BaseSession", Recipient, int | str]:
97
97
  session = await self._get_session(msg)
98
98
  e: Recipient = await _get_entity(session, msg)
99
- legacy_thread = await _xmpp_to_legacy_thread(session, msg, e)
99
+ legacy_thread = await self._xmpp_to_legacy_thread(session, msg, e)
100
100
  return session, e, legacy_thread
101
101
 
102
+ async def _xmpp_to_legacy_thread(
103
+ self, session: "BaseSession", msg: Message, recipient: RecipientType
104
+ ):
105
+ xmpp_thread = msg["thread"]
106
+ if not xmpp_thread:
107
+ return None
108
+
109
+ if session.MESSAGE_IDS_ARE_THREAD_IDS:
110
+ return self._xmpp_msg_id_to_legacy(session, xmpp_thread)
111
+
112
+ legacy_thread_str = session.xmpp.store.sent.get_legacy_thread(
113
+ session.user_pk, xmpp_thread
114
+ )
115
+ if legacy_thread_str is not None:
116
+ return session.xmpp.LEGACY_MSG_ID_TYPE(legacy_thread_str)
117
+ async with session.thread_creation_lock:
118
+ legacy_thread = await recipient.create_thread(xmpp_thread)
119
+ session.xmpp.store.sent.set_thread(
120
+ session.user_pk, str(legacy_thread), xmpp_thread
121
+ )
122
+ return legacy_thread
123
+
102
124
 
103
125
  def _ignore(session: "BaseSession", msg: Message):
104
126
  i = msg.get_id()
@@ -111,28 +133,6 @@ def _ignore(session: "BaseSession", msg: Message):
111
133
  return True
112
134
 
113
135
 
114
- async def _xmpp_to_legacy_thread(
115
- session: "BaseSession", msg: Message, recipient: RecipientType
116
- ):
117
- xmpp_thread = msg["thread"]
118
- if not xmpp_thread:
119
- return
120
-
121
- if session.MESSAGE_IDS_ARE_THREAD_IDS:
122
- return session.xmpp.store.sent.get_legacy_thread(session.user_pk, xmpp_thread)
123
-
124
- async with session.thread_creation_lock:
125
- legacy_thread_str = session.xmpp.store.sent.get_legacy_thread(
126
- session.user_pk, xmpp_thread
127
- )
128
- if legacy_thread_str is None:
129
- legacy_thread = str(await recipient.create_thread(xmpp_thread))
130
- session.xmpp.store.sent.set_thread(
131
- session.user_pk, xmpp_thread, legacy_thread
132
- )
133
- return session.xmpp.LEGACY_MSG_ID_TYPE(legacy_thread)
134
-
135
-
136
136
  async def _get_entity(session: "BaseSession", m: Message) -> RecipientType:
137
137
  session.raise_if_not_logged()
138
138
  if m.get_type() == "groupchat":
@@ -33,17 +33,14 @@ from ...util.types import (
33
33
  )
34
34
  from ...util.util import fix_suffix
35
35
  from .. import config
36
- from .message_maker import MessageMaker
36
+ from .message_text import TextMessageMixin
37
37
 
38
38
 
39
- class AttachmentMixin(MessageMaker):
39
+ class AttachmentMixin(TextMessageMixin):
40
40
  def __init__(self, *a, **kw):
41
41
  super().__init__(*a, **kw)
42
42
  self.__store = self.xmpp.store.attachments
43
43
 
44
- def send_text(self, *_, **k) -> Optional[Message]:
45
- raise NotImplementedError
46
-
47
44
  async def __upload(
48
45
  self,
49
46
  file_path: Path,
@@ -261,7 +258,7 @@ class AttachmentMixin(MessageMaker):
261
258
  thumbnail["width"] = x
262
259
  thumbnail["height"] = y
263
260
  thumbnail["media-type"] = "image/thumbhash"
264
- thumbnail["uri"] = "data:image/thumbhash," + urlquote(h)
261
+ thumbnail["uri"] = "data:image/thumbhash;base64," + urlquote(h)
265
262
 
266
263
  self.__store.set_sims(uploaded_url, str(ref))
267
264
 
@@ -304,6 +301,7 @@ class AttachmentMixin(MessageMaker):
304
301
  caption: Optional[str] = None,
305
302
  carbon=False,
306
303
  when: Optional[datetime] = None,
304
+ correction=False,
307
305
  **kwargs,
308
306
  ) -> list[Message]:
309
307
  msg["oob"]["url"] = uploaded_url
@@ -311,11 +309,19 @@ class AttachmentMixin(MessageMaker):
311
309
  if caption:
312
310
  m1 = self._send(msg, carbon=carbon, **kwargs)
313
311
  m2 = self.send_text(
314
- caption, legacy_msg_id=legacy_msg_id, when=when, carbon=carbon, **kwargs
312
+ caption,
313
+ legacy_msg_id=legacy_msg_id,
314
+ when=when,
315
+ carbon=carbon,
316
+ correction=correction,
317
+ **kwargs,
315
318
  )
316
319
  return [m1, m2] if m2 else [m1]
317
320
  else:
318
- self._set_msg_id(msg, legacy_msg_id)
321
+ if correction:
322
+ msg["replace"]["id"] = self._replace_id(legacy_msg_id)
323
+ else:
324
+ self._set_msg_id(msg, legacy_msg_id)
319
325
  return [self._send(msg, carbon=carbon, **kwargs)]
320
326
 
321
327
  async def send_file(
@@ -358,6 +364,16 @@ class AttachmentMixin(MessageMaker):
358
364
  carbon = kwargs.pop("carbon", False)
359
365
  mto = kwargs.pop("mto", None)
360
366
  store_multi = kwargs.pop("store_multi", True)
367
+ correction = kwargs.get("correction", False)
368
+ if correction and (original_xmpp_id := self._legacy_to_xmpp(legacy_msg_id)):
369
+ xmpp_ids = self.xmpp.store.multi.get_xmpp_ids(
370
+ self.session.user_pk, original_xmpp_id
371
+ )
372
+
373
+ for xmpp_id in xmpp_ids:
374
+ if xmpp_id == original_xmpp_id:
375
+ continue
376
+ self.retract(xmpp_id, thread)
361
377
  msg = self._make_message(
362
378
  when=when,
363
379
  reply_to=reply_to,
@@ -15,14 +15,16 @@ class NamedLockMixin:
15
15
  locks = self.__locks
16
16
  if not locks.get(id_):
17
17
  locks[id_] = asyncio.Lock()
18
- async with locks[id_]:
19
- log.trace("acquired %s", id_) # type:ignore
20
- yield
21
- log.trace("releasing %s", id_) # type:ignore
22
- waiters = locks[id_]._waiters # type:ignore
23
- if not waiters:
24
- del locks[id_]
25
- log.trace("erasing %s", id_) # type:ignore
18
+ try:
19
+ async with locks[id_]:
20
+ log.trace("acquired %s", id_) # type:ignore
21
+ yield
22
+ finally:
23
+ log.trace("releasing %s", id_) # type:ignore
24
+ waiters = locks[id_]._waiters # type:ignore
25
+ if not waiters:
26
+ del locks[id_]
27
+ log.trace("erasing %s", id_) # type:ignore
26
28
 
27
29
  def get_lock(self, id_: Hashable):
28
30
  return self.__locks.get(id_)
@@ -1,23 +1,15 @@
1
1
  import logging
2
2
  import uuid
3
3
  import warnings
4
- from datetime import datetime
5
- from typing import TYPE_CHECKING, Iterable, Optional
4
+ from typing import TYPE_CHECKING, Optional
6
5
 
7
6
  from slixmpp import Iq, Message
8
7
 
9
8
  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
- )
9
+ from ...util.types import ChatState, LegacyMessageType, Marker
19
10
  from .attachment import AttachmentMixin
20
11
  from .message_maker import MessageMaker
12
+ from .message_text import TextMessageMixin
21
13
 
22
14
  if TYPE_CHECKING:
23
15
  from ...group import LegacyMUC
@@ -158,200 +150,8 @@ class MarkerMixin(MessageMaker):
158
150
  self.session.log.debug("Could not MDS mark", exc_info=e)
159
151
 
160
152
 
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.xmpp.store.sent.get_group_xmpp_id(
173
- self.session.user_pk, str(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 and not hasattr(self, "muc"):
218
- if not correction and self.xmpp.store.sent.was_sent_by_user(
219
- self.session.user_pk, str(legacy_msg_id)
220
- ):
221
- log.warning(
222
- "Carbon message for a message an XMPP has sent? This is a bug! %s",
223
- legacy_msg_id,
224
- )
225
- return
226
- if hasattr(self, "muc") and not self.is_user: # type:ignore
227
- log.warning(
228
- "send_text() called with carbon=True on a participant who is not the user",
229
- legacy_msg_id,
230
- )
231
- self.xmpp.store.sent.set_message(
232
- self.session.user_pk,
233
- str(legacy_msg_id),
234
- self.session.legacy_to_xmpp_msg_id(legacy_msg_id),
235
- )
236
- hints = self.__default_hints(hints)
237
- msg = self._make_message(
238
- mbody=body,
239
- legacy_msg_id=correction_event_id if correction else legacy_msg_id,
240
- when=when,
241
- reply_to=reply_to,
242
- hints=hints or (),
243
- carbon=carbon,
244
- thread=thread,
245
- link_previews=link_previews,
246
- )
247
- if correction:
248
- msg["replace"]["id"] = self.__replace_id(legacy_msg_id)
249
- return self._send(
250
- msg,
251
- archive_only=archive_only,
252
- carbon=carbon,
253
- legacy_msg_id=legacy_msg_id,
254
- **send_kwargs,
255
- )
256
-
257
- def correct(
258
- self,
259
- legacy_msg_id: LegacyMessageType,
260
- new_text: str,
261
- *,
262
- when: Optional[datetime] = None,
263
- reply_to: Optional[MessageReference] = None,
264
- thread: Optional[LegacyThreadType] = None,
265
- hints: Optional[Iterable[ProcessingHint]] = None,
266
- carbon=False,
267
- archive_only=False,
268
- correction_event_id: Optional[LegacyMessageType] = None,
269
- link_previews: Optional[list[LinkPreview]] = None,
270
- **send_kwargs,
271
- ):
272
- """
273
- Modify a message that was previously sent by this :term:`XMPP Entity`.
274
-
275
- Uses last message correction (:xep:`0308`)
276
-
277
- :param new_text: New content of the message
278
- :param legacy_msg_id: The legacy message ID of the message to correct
279
- :param when: when the message was sent, for a "delay" tag (:xep:`0203`)
280
- :param reply_to: Quote another message (:xep:`0461`)
281
- :param hints:
282
- :param thread:
283
- :param carbon: (only in 1:1) Reflect a message sent to this ``Contact`` by the user.
284
- Use this to synchronize outgoing history for legacy official apps.
285
- :param archive_only: (only in groups) Do not send this message to user,
286
- but store it in the archive. Meant to be used during ``MUC.backfill()``
287
- :param correction_event_id: in the case where an ID is associated with the legacy
288
- 'correction event', specify it here to use it on the XMPP side. If not specified,
289
- a random ID will be used.
290
- :param link_previews: A little of sender (or server, or gateway)-generated
291
- previews of URLs linked in the body.
292
- """
293
- self.send_text(
294
- new_text,
295
- legacy_msg_id,
296
- when=when,
297
- reply_to=reply_to,
298
- hints=hints,
299
- carbon=carbon,
300
- thread=thread,
301
- correction=True,
302
- archive_only=archive_only,
303
- correction_event_id=correction_event_id,
304
- link_previews=link_previews,
305
- **send_kwargs,
306
- )
307
-
308
- def react(
309
- self,
310
- legacy_msg_id: LegacyMessageType,
311
- emojis: Iterable[str] = (),
312
- thread: Optional[LegacyThreadType] = None,
313
- **kwargs,
314
- ):
315
- """
316
- Send a reaction (:xep:`0444`) from this :term:`XMPP Entity`.
317
-
318
- :param legacy_msg_id: The message which the reaction refers to.
319
- :param emojis: An iterable of emojis used as reactions
320
- :param thread:
321
- """
322
- msg = self._make_message(
323
- hints={"store"}, carbon=kwargs.get("carbon"), thread=thread
324
- )
325
- xmpp_id = kwargs.pop("xmpp_id", None)
326
- if not xmpp_id:
327
- xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
328
- self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=emojis)
329
- self._send(msg, **kwargs)
330
-
331
- def retract(
332
- self,
333
- legacy_msg_id: LegacyMessageType,
334
- thread: Optional[LegacyThreadType] = None,
335
- **kwargs,
336
- ):
337
- """
338
- Send a message retraction (:XEP:`0424`) from this :term:`XMPP Entity`.
339
-
340
- :param legacy_msg_id: Legacy ID of the message to delete
341
- :param thread:
342
- """
343
- msg = self._make_message(
344
- state=None,
345
- hints={"store"},
346
- mbody=f"/me retracted the message {legacy_msg_id}",
347
- carbon=kwargs.get("carbon"),
348
- thread=thread,
349
- )
350
- msg.enable("fallback")
351
- # namespace version mismatch between slidge and slixmpp, update me later
352
- msg["fallback"]["for"] = self.xmpp["xep_0424"].namespace[:-1] + "1"
353
- msg["retract"]["id"] = msg["replace"]["id"] = self.__replace_id(legacy_msg_id)
354
- self._send(msg, **kwargs)
153
+ class ContentMessageMixin(AttachmentMixin, TextMessageMixin):
154
+ pass
355
155
 
356
156
 
357
157
  class CarbonMessageMixin(ContentMessageMixin, MarkerMixin):
@@ -0,0 +1,211 @@
1
+ import logging
2
+ from datetime import datetime
3
+ from typing import Iterable, Optional
4
+
5
+ from ...util.types import (
6
+ LegacyMessageType,
7
+ LegacyThreadType,
8
+ LinkPreview,
9
+ MessageReference,
10
+ ProcessingHint,
11
+ )
12
+ from .message_maker import MessageMaker
13
+
14
+
15
+ class TextMessageMixin(MessageMaker):
16
+ def __default_hints(self, hints: Optional[Iterable[ProcessingHint]] = None):
17
+ if hints is not None:
18
+ return hints
19
+ elif self.mtype == "chat":
20
+ return {"markable", "store"}
21
+ elif self.mtype == "groupchat":
22
+ return {"markable"}
23
+
24
+ def _replace_id(self, legacy_msg_id: LegacyMessageType):
25
+ if self.mtype == "groupchat":
26
+ return self.xmpp.store.sent.get_group_xmpp_id(
27
+ self.session.user_pk, str(legacy_msg_id)
28
+ ) or self._legacy_to_xmpp(legacy_msg_id)
29
+ else:
30
+ return self._legacy_to_xmpp(legacy_msg_id)
31
+
32
+ def send_text(
33
+ self,
34
+ body: str,
35
+ legacy_msg_id: Optional[LegacyMessageType] = None,
36
+ *,
37
+ when: Optional[datetime] = None,
38
+ reply_to: Optional[MessageReference] = None,
39
+ thread: Optional[LegacyThreadType] = None,
40
+ hints: Optional[Iterable[ProcessingHint]] = None,
41
+ carbon=False,
42
+ archive_only=False,
43
+ correction=False,
44
+ correction_event_id: Optional[LegacyMessageType] = None,
45
+ link_previews: Optional[list[LinkPreview]] = None,
46
+ **send_kwargs,
47
+ ):
48
+ """
49
+ Send a text message from this :term:`XMPP Entity`.
50
+
51
+ :param body: Content of the message
52
+ :param legacy_msg_id: If you want to be able to transport read markers from the gateway
53
+ user to the legacy network, specify this
54
+ :param when: when the message was sent, for a "delay" tag (:xep:`0203`)
55
+ :param reply_to: Quote another message (:xep:`0461`)
56
+ :param hints:
57
+ :param thread:
58
+ :param carbon: (only used if called on a :class:`LegacyContact`)
59
+ Set this to ``True`` if this is actually a message sent **to** the
60
+ :class:`LegacyContact` by the :term:`User`.
61
+ Use this to synchronize outgoing history for legacy official apps.
62
+ :param correction: whether this message is a correction or not
63
+ :param correction_event_id: in the case where an ID is associated with the legacy
64
+ 'correction event', specify it here to use it on the XMPP side. If not specified,
65
+ a random ID will be used.
66
+ :param link_previews: A little of sender (or server, or gateway)-generated
67
+ previews of URLs linked in the body.
68
+ :param archive_only: (only in groups) Do not send this message to user,
69
+ but store it in the archive. Meant to be used during ``MUC.backfill()``
70
+ """
71
+ if carbon and not hasattr(self, "muc"):
72
+ if not correction and self.xmpp.store.sent.was_sent_by_user(
73
+ self.session.user_pk, str(legacy_msg_id)
74
+ ):
75
+ log.warning(
76
+ "Carbon message for a message an XMPP has sent? This is a bug! %s",
77
+ legacy_msg_id,
78
+ )
79
+ return
80
+ if hasattr(self, "muc") and not self.is_user: # type:ignore
81
+ log.warning(
82
+ "send_text() called with carbon=True on a participant who is not the user",
83
+ legacy_msg_id,
84
+ )
85
+ self.xmpp.store.sent.set_message(
86
+ self.session.user_pk,
87
+ str(legacy_msg_id),
88
+ self.session.legacy_to_xmpp_msg_id(legacy_msg_id),
89
+ )
90
+ hints = self.__default_hints(hints)
91
+ msg = self._make_message(
92
+ mbody=body,
93
+ legacy_msg_id=correction_event_id if correction else legacy_msg_id,
94
+ when=when,
95
+ reply_to=reply_to,
96
+ hints=hints or (),
97
+ carbon=carbon,
98
+ thread=thread,
99
+ link_previews=link_previews,
100
+ )
101
+ if correction:
102
+ msg["replace"]["id"] = self._replace_id(legacy_msg_id)
103
+ return self._send(
104
+ msg,
105
+ archive_only=archive_only,
106
+ carbon=carbon,
107
+ legacy_msg_id=legacy_msg_id,
108
+ **send_kwargs,
109
+ )
110
+
111
+ def correct(
112
+ self,
113
+ legacy_msg_id: LegacyMessageType,
114
+ new_text: str,
115
+ *,
116
+ when: Optional[datetime] = None,
117
+ reply_to: Optional[MessageReference] = None,
118
+ thread: Optional[LegacyThreadType] = None,
119
+ hints: Optional[Iterable[ProcessingHint]] = None,
120
+ carbon=False,
121
+ archive_only=False,
122
+ correction_event_id: Optional[LegacyMessageType] = None,
123
+ link_previews: Optional[list[LinkPreview]] = None,
124
+ **send_kwargs,
125
+ ):
126
+ """
127
+ Modify a message that was previously sent by this :term:`XMPP Entity`.
128
+
129
+ Uses last message correction (:xep:`0308`)
130
+
131
+ :param new_text: New content of the message
132
+ :param legacy_msg_id: The legacy message ID of the message to correct
133
+ :param when: when the message was sent, for a "delay" tag (:xep:`0203`)
134
+ :param reply_to: Quote another message (:xep:`0461`)
135
+ :param hints:
136
+ :param thread:
137
+ :param carbon: (only in 1:1) Reflect a message sent to this ``Contact`` by the user.
138
+ Use this to synchronize outgoing history for legacy official apps.
139
+ :param archive_only: (only in groups) Do not send this message to user,
140
+ but store it in the archive. Meant to be used during ``MUC.backfill()``
141
+ :param correction_event_id: in the case where an ID is associated with the legacy
142
+ 'correction event', specify it here to use it on the XMPP side. If not specified,
143
+ a random ID will be used.
144
+ :param link_previews: A little of sender (or server, or gateway)-generated
145
+ previews of URLs linked in the body.
146
+ """
147
+ self.send_text(
148
+ new_text,
149
+ legacy_msg_id,
150
+ when=when,
151
+ reply_to=reply_to,
152
+ hints=hints,
153
+ carbon=carbon,
154
+ thread=thread,
155
+ correction=True,
156
+ archive_only=archive_only,
157
+ correction_event_id=correction_event_id,
158
+ link_previews=link_previews,
159
+ **send_kwargs,
160
+ )
161
+
162
+ def react(
163
+ self,
164
+ legacy_msg_id: LegacyMessageType,
165
+ emojis: Iterable[str] = (),
166
+ thread: Optional[LegacyThreadType] = None,
167
+ **kwargs,
168
+ ):
169
+ """
170
+ Send a reaction (:xep:`0444`) from this :term:`XMPP Entity`.
171
+
172
+ :param legacy_msg_id: The message which the reaction refers to.
173
+ :param emojis: An iterable of emojis used as reactions
174
+ :param thread:
175
+ """
176
+ msg = self._make_message(
177
+ hints={"store"}, carbon=kwargs.get("carbon"), thread=thread
178
+ )
179
+ xmpp_id = kwargs.pop("xmpp_id", None)
180
+ if not xmpp_id:
181
+ xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
182
+ self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=emojis)
183
+ self._send(msg, **kwargs)
184
+
185
+ def retract(
186
+ self,
187
+ legacy_msg_id: LegacyMessageType,
188
+ thread: Optional[LegacyThreadType] = None,
189
+ **kwargs,
190
+ ):
191
+ """
192
+ Send a message retraction (:XEP:`0424`) from this :term:`XMPP Entity`.
193
+
194
+ :param legacy_msg_id: Legacy ID of the message to delete
195
+ :param thread:
196
+ """
197
+ msg = self._make_message(
198
+ state=None,
199
+ hints={"store"},
200
+ mbody=f"/me retracted the message {legacy_msg_id}",
201
+ carbon=kwargs.get("carbon"),
202
+ thread=thread,
203
+ )
204
+ msg.enable("fallback")
205
+ # namespace version mismatch between slidge and slixmpp, update me later
206
+ msg["fallback"]["for"] = self.xmpp["xep_0424"].namespace[:-1] + "1"
207
+ msg["retract"]["id"] = msg["replace"]["id"] = self._replace_id(legacy_msg_id)
208
+ self._send(msg, **kwargs)
209
+
210
+
211
+ log = logging.getLogger(__name__)
@@ -0,0 +1,83 @@
1
+ """Remove bogus unique constraints on room table
2
+
3
+ Revision ID: 15b0bd83407a
4
+ Revises: 45c24cc73c91
5
+ Create Date: 2024-08-28 06:57:25.022994
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ import slidge.db.meta
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "15b0bd83407a"
18
+ down_revision: Union[str, None] = "45c24cc73c91"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+ meta = sa.MetaData()
23
+ room_table = sa.Table(
24
+ "room",
25
+ meta,
26
+ sa.Column("id", sa.Integer(), nullable=False),
27
+ sa.Column("user_account_id", sa.Integer(), nullable=False),
28
+ sa.Column("legacy_id", sa.String(), nullable=False),
29
+ sa.Column("jid", slidge.db.meta.JIDType(), nullable=False),
30
+ sa.Column("avatar_id", sa.Integer(), nullable=True),
31
+ sa.Column("name", sa.String(), nullable=True),
32
+ sa.Column("description", sa.String(), nullable=True),
33
+ sa.Column("subject", sa.String(), nullable=True),
34
+ sa.Column("subject_date", sa.DateTime(), nullable=True),
35
+ sa.Column("subject_setter", sa.String(), nullable=True),
36
+ sa.Column("n_participants", sa.Integer(), nullable=True),
37
+ sa.Column(
38
+ "muc_type",
39
+ sa.Enum("GROUP", "CHANNEL", "CHANNEL_NON_ANONYMOUS", name="muctype"),
40
+ nullable=True,
41
+ ),
42
+ sa.Column("user_nick", sa.String(), nullable=True),
43
+ sa.Column("user_resources", sa.String(), nullable=True),
44
+ sa.Column("participants_filled", sa.Boolean(), nullable=False),
45
+ sa.Column("history_filled", sa.Boolean(), nullable=False),
46
+ sa.Column("extra_attributes", slidge.db.meta.JSONEncodedDict(), nullable=True),
47
+ sa.Column("updated", sa.Boolean(), nullable=False),
48
+ sa.Column("avatar_legacy_id", sa.String(), nullable=True),
49
+ sa.ForeignKeyConstraint(
50
+ ["avatar_id"],
51
+ ["avatar.id"],
52
+ ),
53
+ sa.ForeignKeyConstraint(
54
+ ["user_account_id"],
55
+ ["user_account.id"],
56
+ ),
57
+ sa.PrimaryKeyConstraint("id"),
58
+ )
59
+
60
+
61
+ def upgrade() -> None:
62
+ with op.batch_alter_table(
63
+ "room",
64
+ schema=None,
65
+ # without copy_from, the newly created table keeps the constraints
66
+ # we actually want to ditch.
67
+ copy_from=room_table,
68
+ ) as batch_op:
69
+ batch_op.create_unique_constraint(
70
+ "uq_room_user_account_id_jid", ["user_account_id", "jid"]
71
+ )
72
+ batch_op.create_unique_constraint(
73
+ "uq_room_user_account_id_legacy_id", ["user_account_id", "legacy_id"]
74
+ )
75
+
76
+
77
+ def downgrade() -> None:
78
+ # ### commands auto generated by Alembic - please adjust! ###
79
+ with op.batch_alter_table("room", schema=None) as batch_op:
80
+ batch_op.drop_constraint("uq_room_user_account_id_legacy_id", type_="unique")
81
+ batch_op.drop_constraint("uq_room_user_account_id_jid", type_="unique")
82
+
83
+ # ### end Alembic commands ###
@@ -4,8 +4,12 @@ Revision ID: 5bd48bfdffa2
4
4
  Revises: b64b1a793483
5
5
  Create Date: 2024-07-24 10:29:23.467851
6
6
 
7
+ Broken; fixed by "Remove bogus unique constraints on room table",
8
+ rev 15b0bd83407a.
9
+
7
10
  """
8
11
 
12
+ import logging
9
13
  from typing import Sequence, Union
10
14
 
11
15
  from alembic import op
@@ -26,6 +30,8 @@ def upgrade() -> None:
26
30
  schema=None,
27
31
  # without copy_from, the newly created table keeps the constraints
28
32
  # we actually want to ditch.
33
+ # LATER EDIT: this actually does not work, I should have copied the
34
+ # schema in here.
29
35
  copy_from=Room.__table__, # type:ignore
30
36
  ) as batch_op:
31
37
  batch_op.create_unique_constraint(
@@ -35,9 +41,11 @@ def upgrade() -> None:
35
41
  "uq_room_user_account_id_legacy_id", ["user_account_id", "legacy_id"]
36
42
  )
37
43
  except Exception:
38
- # happens when migration is not needed
44
+ # This only works when upgrading rev by rev because I messed up. It
39
45
  # wouldn't be necessary if the constraint was named in the first place,
40
46
  # cf https://alembic.sqlalchemy.org/en/latest/naming.html
47
+ # This is fixed by rev 15b0bd83407a
48
+ log.info("Skipping")
41
49
  pass
42
50
 
43
51
 
@@ -48,3 +56,6 @@ def downgrade() -> None:
48
56
  batch_op.drop_constraint("uq_room_user_account_id_jid", type_="unique")
49
57
 
50
58
  # ### end Alembic commands ###
59
+
60
+
61
+ log = logging.getLogger(__name__)
slidge/db/models.py CHANGED
@@ -156,7 +156,9 @@ class Contact(Base):
156
156
 
157
157
  is_friend: Mapped[bool] = mapped_column(default=False)
158
158
  added_to_roster: Mapped[bool] = mapped_column(default=False)
159
- sent_order: Mapped[list["ContactSent"]] = relationship(back_populates="contact")
159
+ sent_order: Mapped[list["ContactSent"]] = relationship(
160
+ back_populates="contact", cascade="all, delete-orphan"
161
+ )
160
162
 
161
163
  extra_attributes: Mapped[Optional[JSONSerializable]] = mapped_column(
162
164
  default=None, nullable=True
slidge/db/store.py CHANGED
@@ -251,15 +251,6 @@ class SentStore(EngineMixin):
251
251
  .where(XmppToLegacyIds.type == XmppToLegacyEnum.THREAD)
252
252
  ).scalar()
253
253
 
254
- def get_xmpp_thread(self, user_pk: int, legacy_id: str) -> Optional[str]:
255
- with self.session() as session:
256
- return session.execute(
257
- select(XmppToLegacyIds.xmpp_id)
258
- .where(XmppToLegacyIds.user_account_id == user_pk)
259
- .where(XmppToLegacyIds.legacy_id == legacy_id)
260
- .where(XmppToLegacyIds.type == XmppToLegacyEnum.THREAD)
261
- ).scalar()
262
-
263
254
  def was_sent_by_user(self, user_pk: int, legacy_id: str) -> bool:
264
255
  with self.session() as session:
265
256
  return (
@@ -414,6 +405,16 @@ class ContactStore(UpdatedMixin):
414
405
 
415
406
  def add_to_sent(self, contact_pk: int, msg_id: str) -> None:
416
407
  with self.session() as session:
408
+ if (
409
+ session.query(ContactSent.id)
410
+ .where(ContactSent.contact_id == contact_pk)
411
+ .where(ContactSent.msg_id == msg_id)
412
+ .first()
413
+ ) is not None:
414
+ log.warning(
415
+ "Contact %s has already sent message %s", contact_pk, msg_id
416
+ )
417
+ return
417
418
  new = ContactSent(contact_id=contact_pk, msg_id=msg_id)
418
419
  session.add(new)
419
420
  session.commit()
@@ -504,6 +505,12 @@ class MAMStore(EngineMixin):
504
505
  .where(ArchivedMessage.room_id == room_pk)
505
506
  .where(ArchivedMessage.stanza_id == message.id)
506
507
  ).scalar()
508
+ if existing is None and legacy_msg_id is not None:
509
+ existing = session.execute(
510
+ select(ArchivedMessage)
511
+ .where(ArchivedMessage.room_id == room_pk)
512
+ .where(ArchivedMessage.legacy_id == legacy_msg_id)
513
+ ).scalar()
507
514
  if existing is not None:
508
515
  log.debug("Updating message %s in room %s", message.id, room_pk)
509
516
  existing.timestamp = message.when
slidge/group/bookmarks.py CHANGED
@@ -133,6 +133,8 @@ class LegacyBookmarks(
133
133
  try:
134
134
  with muc.updating_info():
135
135
  await muc.avatar_wrap_update_info()
136
+ except XMPPError:
137
+ raise
136
138
  except Exception as e:
137
139
  raise XMPPError("internal-server-error", str(e))
138
140
  if not muc.user_nick:
@@ -160,6 +162,26 @@ class LegacyBookmarks(
160
162
  " LegacyBookmarks.fill() was not overridden."
161
163
  )
162
164
 
163
- def remove(self, muc: LegacyMUC):
165
+ async def remove(
166
+ self,
167
+ muc: LegacyMUC,
168
+ reason="You left this group from the official client.",
169
+ kick=True,
170
+ ) -> None:
171
+ """
172
+ Delete everything about a specific group.
173
+
174
+ This should be called when the user leaves the group from the official
175
+ app.
176
+
177
+ :param muc: The MUC to remove.
178
+ :param reason: Optionally, a reason why this group was removed.
179
+ :param kick: Whether the user should be kicked from this group. Set this
180
+ to False in case you do this somewhere else in your code, eg, on
181
+ receiving the confirmation that the group was deleted.
182
+ """
164
183
  assert muc.pk is not None
184
+ if kick:
185
+ user_participant = await muc.get_user_participant()
186
+ user_participant.kick(reason)
165
187
  self.__store.delete(muc.pk)
@@ -324,7 +324,7 @@ class LegacyParticipant(
324
324
  ) -> MessageOrPresenceTypeVar:
325
325
  stanza["occupant-id"]["id"] = self.__occupant_id
326
326
  self.__add_nick_element(stanza)
327
- if isinstance(stanza, Presence):
327
+ if not self.is_user and isinstance(stanza, Presence):
328
328
  if stanza["type"] == "unavailable" and not self._presence_sent:
329
329
  return stanza # type:ignore
330
330
  self._presence_sent = True
@@ -432,17 +432,17 @@ class LegacyParticipant(
432
432
  """
433
433
  self.muc.remove_participant(self)
434
434
 
435
- def kick(self):
435
+ def kick(self, reason: str | None = None):
436
436
  """
437
437
  Call this when the participant is kicked from the room
438
438
  """
439
- self.muc.remove_participant(self, kick=True)
439
+ self.muc.remove_participant(self, kick=True, reason=reason)
440
440
 
441
- def ban(self):
441
+ def ban(self, reason: str | None = None):
442
442
  """
443
443
  Call this when the participant is banned from the room
444
444
  """
445
- self.muc.remove_participant(self, ban=True)
445
+ self.muc.remove_participant(self, ban=True, reason=reason)
446
446
 
447
447
  def get_disco_info(self, jid: OptJid = None, node: Optional[str] = None):
448
448
  if self.contact is not None:
slidge/group/room.py CHANGED
@@ -814,13 +814,20 @@ class LegacyMUC(
814
814
  return await self.get_user_participant(**kwargs)
815
815
  return await self.get_participant_by_contact(c, **kwargs)
816
816
 
817
- def remove_participant(self, p: "LegacyParticipantType", kick=False, ban=False):
817
+ def remove_participant(
818
+ self,
819
+ p: "LegacyParticipantType",
820
+ kick=False,
821
+ ban=False,
822
+ reason: str | None = None,
823
+ ):
818
824
  """
819
825
  Call this when a participant leaves the room
820
826
 
821
827
  :param p: The participant
822
828
  :param kick: Whether the participant left because they were kicked
823
829
  :param ban: Whether the participant left because they were banned
830
+ :param reason: Optionally, a reason why the participant was removed.
824
831
  """
825
832
  if kick and ban:
826
833
  raise TypeError("Either kick or ban")
@@ -834,6 +841,8 @@ class LegacyMUC(
834
841
  presence = p._make_presence(ptype="unavailable", status_codes=codes)
835
842
  p._affiliation = "outcast" if ban else "none"
836
843
  p._role = "none"
844
+ if reason:
845
+ presence["muc"].set_item_attr("reason", reason)
837
846
  p._send(presence)
838
847
 
839
848
  def rename_participant(self, old_nickname: str, new_nickname: str):
slidge/util/test.py CHANGED
@@ -215,13 +215,13 @@ class SlidgeTest(SlixTestPlus):
215
215
  self.plugin, LegacyBookmarks, base_ok=True
216
216
  )
217
217
 
218
+ # workaround for duplicate output of sql alchemy's log, cf
219
+ # https://stackoverflow.com/a/76498428/5902284
218
220
  from sqlalchemy import log as sqlalchemy_log
219
221
 
220
222
  sqlalchemy_log._add_default_handler = lambda x: None
221
223
 
222
- engine = self.db_engine = create_engine(
223
- "sqlite+pysqlite:///:memory:", echo=True
224
- )
224
+ engine = self.db_engine = create_engine("sqlite+pysqlite:///:memory:")
225
225
  Base.metadata.create_all(engine)
226
226
  BaseGateway.store = SlidgeStore(engine)
227
227
  BaseGateway._test_mode = True
@@ -293,7 +293,7 @@ class SlidgeTest(SlixTestPlus):
293
293
  )
294
294
  )
295
295
  welcome = self.next_sent()
296
- assert welcome["body"]
296
+ assert welcome["body"], welcome
297
297
  stanza = self.next_sent()
298
298
  assert "logging in" in stanza["status"].lower(), stanza
299
299
  stanza = self.next_sent()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slidge
3
- Version: 0.2.0a10
3
+ Version: 0.2.0b0
4
4
  Summary: XMPP bridging framework
5
5
  Home-page: https://sr.ht/~nicoco/slidge/
6
6
  License: AGPL-3.0-or-later
@@ -1,6 +1,6 @@
1
1
  slidge/__init__.py,sha256=S0tUjqpZlzsr8G4Y_1Xt-KCYB07qaknTB0OwHU8k29U,1587
2
- slidge/__main__.py,sha256=Y12eh1TD_C5MB50KgEAuMffGnRFCvKYFKHD4UYSmHA0,72
3
- slidge/__version__.py,sha256=05G747WHKOvpl1x4M5U-3t3qszlgbNfnUxAxrATG8ck,171
2
+ slidge/__main__.py,sha256=ydjUklOoavS4YlGfjRX_8BQN2DaSbaXPMi47RkOgcFI,37
3
+ slidge/__version__.py,sha256=9HCdKNn7jsnGlM4hiADRgDmlQEfeHOYi3XRNJ7Fe-2w,169
4
4
  slidge/command/__init__.py,sha256=UYf1mjCYbZ5G7PIgaFTWSQRAzEJkQ6dTH8Fu_e_XnO0,613
5
5
  slidge/command/adhoc.py,sha256=9PsTsGMPKAK_YXQpwdcH9SSDki8YQ49OZ5p65W5HA6k,9412
6
6
  slidge/command/admin.py,sha256=x_kJ0TJhzf6d3OBIOXFjudZFO8bRYUG919td7OjMCug,6008
@@ -8,41 +8,42 @@ slidge/command/base.py,sha256=7NSzPZdBLZElrm3smzvFKgP0GUggxXdkhclxIKCjtT8,13036
8
8
  slidge/command/categories.py,sha256=BJCfaga2qoAxnHfgHD7I_RKZuBA5nnNOukkWHJwsUFE,99
9
9
  slidge/command/chat_command.py,sha256=VBs6IuDka1IyyMzz0ZyE9zMImaEzUZLcnffxq_vwb4M,10565
10
10
  slidge/command/register.py,sha256=fzPcGUoJtainnDOiC13gWV-uYLuJcsmdKGJ-jXT1qIo,6697
11
- slidge/command/user.py,sha256=uFheYOprhypkHEEl6qSTEM7T2N28xXaDi7v1he-AET8,11512
11
+ slidge/command/user.py,sha256=P4mU1wn1ywtquo0KKQsWddOhIKMV4HOueZAXOgmVvek,11700
12
12
  slidge/contact/__init__.py,sha256=WMMaHk7UW7YT9EH2LtPdkU0bHQaOp4ikBhbBQskmoc8,191
13
13
  slidge/contact/contact.py,sha256=kKtJ9NPLS9DPVyyahx_K-Mtp5k5UQdQJZavC1XCmWlc,23104
14
- slidge/contact/roster.py,sha256=AQTfmM4hrlxkXSM7ZX-C8P9NDN5RqNMNuPrSjt_3MJM,10223
14
+ slidge/contact/roster.py,sha256=-Ei0f0cXX1LFpY29u4Ik68ikno3m2WRA5n5l8Nbjd_E,10267
15
15
  slidge/core/__init__.py,sha256=RG7Jj5JCJERjhqJ31lOLYV-7bH_oblClQD1KF9LsTXo,68
16
- slidge/core/config.py,sha256=leNcN_TI0Ka1hhzOHx7cBW3fNj5xZwsiv9l8AfRY_vU,7630
16
+ slidge/core/config.py,sha256=voRFIlVDtKTZCdjc-zbwgnngFZrGvJjJ1dxRZm0BJK0,7514
17
17
  slidge/core/dispatcher/__init__.py,sha256=1EXcjXietUKlxEqdrCWCV3xZ3q_DSsjHoqWrPMbtYao,84
18
18
  slidge/core/dispatcher/caps.py,sha256=vzCAXo_bhALuLEpJWtyJTzVfWx96g1AsWD8_wkoDl0Y,2028
19
19
  slidge/core/dispatcher/disco.py,sha256=j56VY9NIFzwPEWFKQQZ7YIqS9GdD-ZaF_K8a2L-JvRk,2006
20
20
  slidge/core/dispatcher/message/__init__.py,sha256=vpDGOc_U9XvkUU_ws9n9-5M2NPJ87XGTVpuIxM7Z99k,223
21
21
  slidge/core/dispatcher/message/chat_state.py,sha256=sCdEpzbgmvBmTovNOCv9uY6v0eJZcWVvDYAGlAV3FJ4,1735
22
- slidge/core/dispatcher/message/marker.py,sha256=VbD1-2QeOCC4EbrS-wpJxHwIcmvPF4Q4qd0eiv95c7I,2451
22
+ slidge/core/dispatcher/message/marker.py,sha256=f1ezaMoHupBFZY7aUMsWLAQG7G1J9b3ihxICCkpGtis,2411
23
23
  slidge/core/dispatcher/message/message.py,sha256=HwauW2kGionLyDWG01OSa9a14gYzoovJuJvGbfB4nt4,15296
24
24
  slidge/core/dispatcher/muc/__init__.py,sha256=V8URHLJ_y7mk-7Id6FzRuczb1Uq_Z69fhxvzHuVLH1w,269
25
25
  slidge/core/dispatcher/muc/admin.py,sha256=s21V2LEqc0e_DIpipEhhQdpae762lW1lVqj4wjFhX8M,3364
26
26
  slidge/core/dispatcher/muc/mam.py,sha256=1ROVP4ZPEVEH-HR5qRV4YwHz-V15uu5gyhv1ZwwKhk8,2821
27
- slidge/core/dispatcher/muc/misc.py,sha256=L5I2nt23qvQ-2qS5qgW3hSg6Z7DKnGvwxgXqn_WLvpQ,3943
28
- slidge/core/dispatcher/muc/owner.py,sha256=Vwwz15fdLq40XTnyDcMmRtMOXdY3T3_BwpSV3Nr8FNw,3290
27
+ slidge/core/dispatcher/muc/misc.py,sha256=bHBjMC-Pu3jR5hAPGMzXf-C05UbACIwg38YbJUxHIxk,4068
28
+ slidge/core/dispatcher/muc/owner.py,sha256=1a6YV7b_mmi1jC6q1ko8weeL8imQA-s-hYGPLIHd10I,3308
29
29
  slidge/core/dispatcher/muc/ping.py,sha256=lb1VQPhiUPZ19KhbofRXMVCcY6wwQ2w-asnqtANaAwA,1660
30
30
  slidge/core/dispatcher/presence.py,sha256=ZxAmC34yxKxbk_-h6g_S8pTssL7ovULm3q2ishpYaB4,6393
31
31
  slidge/core/dispatcher/registration.py,sha256=Xmbw9NF3LUppCOa3XzreopdKDitZnwl_5HE-kds74n8,3155
32
32
  slidge/core/dispatcher/search.py,sha256=9cGj0wwvyYlP_Yk440Y12sgo4Y1p-JWUDSJP5Zxch0M,3296
33
33
  slidge/core/dispatcher/session_dispatcher.py,sha256=_njTftgpUKKMP-hgAo99Hu0YrIa6E9OTzSYdiMW000w,2844
34
- slidge/core/dispatcher/util.py,sha256=x_vC7OXcaDaSz3RPPGnK9fOvimmMaKPM3RkgmHvU0Qw,5676
34
+ slidge/core/dispatcher/util.py,sha256=YtXyVxM3orE7aYWs-GbJumtLTI63OpaQY_t4FMTjoZo,5754
35
35
  slidge/core/dispatcher/vcard.py,sha256=Rmx-wCz6Lps0mXCO48HppNQlS3GOgMuzuw9hZYBdlVU,5130
36
36
  slidge/core/gateway.py,sha256=NhIgxZKPnOpwsx50OKgyZyk9nfU8ZlUSMddwIDIhFcw,36351
37
37
  slidge/core/mixins/__init__.py,sha256=muReAzgvENgMvlfm0Fpe6BQFfm2EMjoDe9ZhGgo6Vig,627
38
- slidge/core/mixins/attachment.py,sha256=5Xa_GkSL_rRTC45TRjW98jztk__4M5P-3EG4IF91K0c,19022
38
+ slidge/core/mixins/attachment.py,sha256=qHtv2I1buTmPO1jwRIpq2rixq5XTAljeWYj2eMWSw2k,19623
39
39
  slidge/core/mixins/avatar.py,sha256=kGIIZzLSNuxF9bIvt5Bv03_uT_pU5QV1kS7cRu6-GUA,7874
40
40
  slidge/core/mixins/base.py,sha256=MOd-pas38_52VawQVlxWtBtmTKC6My9G0ZaCeQxOJbs,748
41
41
  slidge/core/mixins/db.py,sha256=5Qpegd7D8e5TLXLLINYcf_DuVdN-7wNmsfztUuFYPcU,442
42
42
  slidge/core/mixins/disco.py,sha256=jk3Z1B6zTuisHv8VKNRJodIo0ee5btYHh2ZrlflPj_Q,3670
43
- slidge/core/mixins/lock.py,sha256=mVzwVVEoq1hrAMgGLh4K84BTLt7JTJ33B8HSGSorTdY,913
44
- slidge/core/mixins/message.py,sha256=6X0MskF1fC9Ih8aLRfdqNe7H3jpnFGEJb0EykRKze0c,15384
43
+ slidge/core/mixins/lock.py,sha256=Vf1rrkbyNbSprr38WGfZiMgTB7AdbqH8ppFHY8N2yXE,975
44
+ slidge/core/mixins/message.py,sha256=FB3VoaT81xUNVnaBMSwNJoHfrVv4Iv2678yDQH-23Rw,7551
45
45
  slidge/core/mixins/message_maker.py,sha256=TcCutHi0sIwL6beJNkN7XyR0aDIbA0xZyxd2Gc9ulG4,6022
46
+ slidge/core/mixins/message_text.py,sha256=pCY4tezEuwB2ZuUyUi72i4v9AJkxp_SWF1jrFsn94Ns,8096
46
47
  slidge/core/mixins/presence.py,sha256=yywo6KAw8C7GaZSMrSMuioNfhW08MrnobHt8XbHd0q8,7891
47
48
  slidge/core/mixins/recipient.py,sha256=U-YppozUO8pA94jmD3-qmhkykTebPNaOVWc3JDPC9w8,1302
48
49
  slidge/core/pubsub.py,sha256=oTiS5KFQJAmsgkhOsvfvthT-LkuZGQSCrrUG0JskNkI,11907
@@ -53,12 +54,13 @@ slidge/db/alembic/env.py,sha256=hsBlRNs0zF5diSHGRSa8Fi3qRVQDA2rJdR41AEIdvxc,1642
53
54
  slidge/db/alembic/old_user_store.py,sha256=zFOv0JEWQQK0_TMRlU4Z0G5Mc9pxvEErLyOzXmRAe5Q,5209
54
55
  slidge/db/alembic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
55
56
  slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py,sha256=mUL-0Io6ZPd_QbnKfwGYyjdMcM2uxQ0Wg72H23-2t_E,1033
57
+ slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py,sha256=bc_H_tPCVjiiUDqWE3oQtnIvsv2tlrzt0NB2f24mbdk,2862
56
58
  slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py,sha256=CLB-kOP9Rc0FJIKDLef912L5sYkjpTIPC8fhrIdrC7k,1084
57
59
  slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py,sha256=f8TFS28CXjGhvIn41UYMoHYeODfqhKfo4O7gk-JwA1E,1134
58
60
  slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py,sha256=CMVP2wFz6s7t57eWdSaGtck8BXzfVPJhHE5AoWi34tI,1359
59
61
  slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py,sha256=O5BY1vpbtuYT5j6i3EMuuJAf6loIYT1kco8c-c6TF5g,1391
60
62
  slidge/db/alembic/versions/45c24cc73c91_add_bob.py,sha256=UjMySZ5LaInyPt0KbAxx0rF4GQhZh8CwBeqHtNPdG1c,1249
61
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py,sha256=cObfqUzqdNezIeJdHe7YKFwRwtelXk8y1PwZ75chXDc,1629
63
+ slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py,sha256=m3USa76h0O2Xut-NePXIOZfkXl0bx0d5FyjOYpd34Jo,1977
62
64
  slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py,sha256=g37po0ydp8ZmzJrE5oFV7GscnploxjCtPDpw28SqVGk,1429
63
65
  slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py,sha256=18tG8B03Kq8Qz_-mMd28Beed6jow8XNTtrz7gT5QY3g,1210
64
66
  slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py,sha256=olXaOEEsUSasqaaKdlP1cBODsMhmV1i90qbpDM2vTm4,4696
@@ -70,13 +72,13 @@ slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py,sha256=jjQmlRv6nqd
70
72
  slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py,sha256=8Ga3VFgKrzMs_-B8OPtfP-0rey_MFaDg-QGtSbaft3o,640
71
73
  slidge/db/avatar.py,sha256=FfRt2Vu11ZKD9F3x1_drawvUd-TDE3mp7SE3BZ9hOOg,6467
72
74
  slidge/db/meta.py,sha256=v1Jf-npZ28QwdGpsLQWLBHEbEP3-jnPrygRg05tJ_Iw,1831
73
- slidge/db/models.py,sha256=bsZCEtBDkvwiZiJNyp0cqxLHphtCUBIGnOPIaaG9opc,13738
74
- slidge/db/store.py,sha256=1p-wAgG4K_QUehnmqRz-zvmW_PbXYYVq5qYMViF0cNY,46432
75
+ slidge/db/models.py,sha256=mazginFllRNsC2w-SW_Y9HUMtruYnzSDCGGjsJwwsp8,13782
76
+ slidge/db/store.py,sha256=7-HIml_wmgwwKe1AxI9yXbtWGz7yxH0cMc_IY4p1Wl4,46696
75
77
  slidge/group/__init__.py,sha256=yFt7cHqeaKIMN6f9ZyhhspOcJJvBtLedGv-iICG7lto,258
76
78
  slidge/group/archive.py,sha256=xGPkdSk8-BT6t6lNVo1FEwiFVAttoxCma8Tsyk5r8Kg,5279
77
- slidge/group/bookmarks.py,sha256=_LDf7A7aWkwPH88v7c-mOp8VJs3gSFM1-uCqSb4ThO8,5825
78
- slidge/group/participant.py,sha256=VNMtqr98QVuYgiTsJ9BaaIG1noz-xe3ewyKhLeDRhBk,17033
79
- slidge/group/room.py,sha256=v5mCV7ZrCdgXtDu_K7oDTdtjNYJV-y9wPmlg_RN_4s4,45789
79
+ slidge/group/bookmarks.py,sha256=AvFL34bEX6n3OP1Np309T5hrLK9GnjkjdyLJ3uiLZyc,6616
80
+ slidge/group/participant.py,sha256=Wtq03Ix55AxlK4pvYVIalLwmKklJiIAsZdeLADJNJgU,17138
81
+ slidge/group/room.py,sha256=IizSwUBoKLvcvLpDseHIW_2KAky38uWsSv-poJuCAF0,46019
80
82
  slidge/main.py,sha256=8oND7xpR3eLw7b62fT61UhYlmNp_9gv3tNz2N3xR7-c,6232
81
83
  slidge/migration.py,sha256=4BJmPIRB56_WIhRTqBFIIBXuvnhhBjjOMl4CE7jY6oc,1541
82
84
  slidge/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -119,11 +121,11 @@ slidge/util/__init__.py,sha256=BELovoTMPcPPGz3D48esBr8A4BRRHXTvavfgnArBgEc,301
119
121
  slidge/util/archive_msg.py,sha256=xXAR0BI5r3d6KKWjae9594izCOv6iI03z2WLuTecNw8,1724
120
122
  slidge/util/conf.py,sha256=1j2OnOsCBar1tOObErhXR5RC3Vl3faliOZ1U8J3My58,6613
121
123
  slidge/util/db.py,sha256=4LxZj8oBYgiSnyBUnF_ALjr0TblkfNQq_p28sCfkHMY,242
122
- slidge/util/test.py,sha256=movqj7rxDPZG4RF46CN1z73PSYAHIc_LuudquGRwLVU,13909
124
+ slidge/util/test.py,sha256=xnGXK0wvua49ncQm4linIfH24Ux6oCkm5A71k2V80zI,14007
123
125
  slidge/util/types.py,sha256=R_xfS5mRL0XUJIoDpnaAkZlTOoLPerduXBFftaVwIAI,5489
124
126
  slidge/util/util.py,sha256=DyJWO2pmE-RiB9Rsy6TUTcvB-BDlmLZBW4PELx4arFQ,9156
125
- slidge-0.2.0a10.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
126
- slidge-0.2.0a10.dist-info/METADATA,sha256=_sFwLHhxETI7mHO1Hoetd6FVtNVYpyHHnboPxJucnkU,5006
127
- slidge-0.2.0a10.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
128
- slidge-0.2.0a10.dist-info/entry_points.txt,sha256=SNl72KSocF5plsu_67xyH6wVWfGTXQbzkQgXbLtzDrQ,47
129
- slidge-0.2.0a10.dist-info/RECORD,,
127
+ slidge-0.2.0b0.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
128
+ slidge-0.2.0b0.dist-info/METADATA,sha256=JrkUpw6lNEWr3ESfNKv-NMuvLPIwqz-3G2Gt7i9nkkk,5005
129
+ slidge-0.2.0b0.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
130
+ slidge-0.2.0b0.dist-info/entry_points.txt,sha256=btz6mbzx1X6fjFWAS_Bo5qNi8PtxUsDgunt-6r4JDHw,43
131
+ slidge-0.2.0b0.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ slidge=slidge.main:main
3
+
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- slidge=slidge.__main__:main
3
-