slidge 0.2.12__py3-none-any.whl → 0.3.0a0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. slidge/__init__.py +5 -2
  2. slidge/command/adhoc.py +9 -3
  3. slidge/command/admin.py +16 -12
  4. slidge/command/base.py +16 -12
  5. slidge/command/chat_command.py +25 -16
  6. slidge/command/user.py +7 -8
  7. slidge/contact/contact.py +119 -209
  8. slidge/contact/roster.py +106 -105
  9. slidge/core/config.py +2 -43
  10. slidge/core/dispatcher/caps.py +9 -2
  11. slidge/core/dispatcher/disco.py +13 -3
  12. slidge/core/dispatcher/message/__init__.py +1 -1
  13. slidge/core/dispatcher/message/chat_state.py +17 -8
  14. slidge/core/dispatcher/message/marker.py +7 -5
  15. slidge/core/dispatcher/message/message.py +117 -92
  16. slidge/core/dispatcher/muc/__init__.py +1 -1
  17. slidge/core/dispatcher/muc/admin.py +4 -4
  18. slidge/core/dispatcher/muc/mam.py +10 -6
  19. slidge/core/dispatcher/muc/misc.py +4 -2
  20. slidge/core/dispatcher/muc/owner.py +5 -3
  21. slidge/core/dispatcher/muc/ping.py +3 -1
  22. slidge/core/dispatcher/presence.py +21 -15
  23. slidge/core/dispatcher/registration.py +20 -12
  24. slidge/core/dispatcher/search.py +7 -3
  25. slidge/core/dispatcher/session_dispatcher.py +13 -5
  26. slidge/core/dispatcher/util.py +37 -27
  27. slidge/core/dispatcher/vcard.py +7 -4
  28. slidge/core/gateway.py +168 -84
  29. slidge/core/mixins/__init__.py +1 -11
  30. slidge/core/mixins/attachment.py +163 -148
  31. slidge/core/mixins/avatar.py +100 -177
  32. slidge/core/mixins/db.py +50 -2
  33. slidge/core/mixins/message.py +19 -17
  34. slidge/core/mixins/message_maker.py +29 -15
  35. slidge/core/mixins/message_text.py +38 -30
  36. slidge/core/mixins/presence.py +91 -35
  37. slidge/core/pubsub.py +42 -47
  38. slidge/core/session.py +88 -57
  39. slidge/db/alembic/versions/0337c90c0b96_unify_legacy_xmpp_id_mappings.py +183 -0
  40. slidge/db/alembic/versions/4dbd23a3f868_new_avatar_store.py +56 -0
  41. slidge/db/alembic/versions/54ce3cde350c_use_hash_for_avatar_filenames.py +50 -0
  42. slidge/db/alembic/versions/58b98dacf819_refactor.py +118 -0
  43. slidge/db/alembic/versions/75a62b74b239_ditch_hats_table.py +74 -0
  44. slidge/db/avatar.py +150 -119
  45. slidge/db/meta.py +33 -22
  46. slidge/db/models.py +68 -117
  47. slidge/db/store.py +412 -1094
  48. slidge/group/archive.py +61 -54
  49. slidge/group/bookmarks.py +74 -55
  50. slidge/group/participant.py +135 -142
  51. slidge/group/room.py +315 -312
  52. slidge/main.py +28 -18
  53. slidge/migration.py +2 -12
  54. slidge/slixfix/__init__.py +20 -4
  55. slidge/slixfix/delivery_receipt.py +6 -4
  56. slidge/slixfix/link_preview/link_preview.py +1 -1
  57. slidge/slixfix/link_preview/stanza.py +1 -1
  58. slidge/slixfix/roster.py +5 -7
  59. slidge/slixfix/xep_0077/register.py +8 -8
  60. slidge/slixfix/xep_0077/stanza.py +7 -7
  61. slidge/slixfix/xep_0100/gateway.py +12 -13
  62. slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
  63. slidge/slixfix/xep_0292/vcard4.py +1 -1
  64. slidge/util/archive_msg.py +11 -5
  65. slidge/util/conf.py +23 -20
  66. slidge/util/jid_escaping.py +1 -1
  67. slidge/{core/mixins → util}/lock.py +6 -6
  68. slidge/util/test.py +30 -29
  69. slidge/util/types.py +22 -18
  70. slidge/util/util.py +19 -22
  71. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/METADATA +1 -1
  72. slidge-0.3.0a0.dist-info/RECORD +117 -0
  73. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/WHEEL +1 -1
  74. slidge-0.2.12.dist-info/RECORD +0 -112
  75. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/entry_points.txt +0 -0
  76. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
  77. {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,7 @@ from datetime import datetime
11
11
  from itertools import chain
12
12
  from mimetypes import guess_extension, guess_type
13
13
  from pathlib import Path
14
- from typing import IO, AsyncIterator, Collection, Optional, Sequence, Union
14
+ from typing import Collection, Optional, Sequence, Union
15
15
  from urllib.parse import quote as urlquote
16
16
  from uuid import uuid4
17
17
  from xml.etree import ElementTree as ET
@@ -25,6 +25,7 @@ from slixmpp.plugins.xep_0363 import FileUploadError
25
25
  from slixmpp.plugins.xep_0447.stanza import StatelessFileSharing
26
26
 
27
27
  from ...db.avatar import avatar_cache
28
+ from ...db.models import Attachment
28
29
  from ...util.types import (
29
30
  LegacyAttachment,
30
31
  LegacyMessageType,
@@ -37,9 +38,7 @@ from .message_text import TextMessageMixin
37
38
 
38
39
 
39
40
  class AttachmentMixin(TextMessageMixin):
40
- def __init__(self, *a, **kw):
41
- super().__init__(*a, **kw)
42
- self.__store = self.xmpp.store.attachments
41
+ is_group: bool
43
42
 
44
43
  async def __upload(
45
44
  self,
@@ -137,28 +136,41 @@ class AttachmentMixin(TextMessageMixin):
137
136
 
138
137
  return destination, uploaded_url
139
138
 
139
+ async def __valid_url(self, url: str) -> bool:
140
+ async with self.session.http.head(url) as r:
141
+ return r.status < 400
142
+
143
+ async def __get_stored(self, attachment: LegacyAttachment) -> Attachment:
144
+ if attachment.legacy_file_id is not None:
145
+ with self.xmpp.store.session() as orm:
146
+ stored = (
147
+ orm.query(Attachment)
148
+ .filter_by(legacy_file_id=str(attachment.legacy_file_id))
149
+ .one_or_none()
150
+ )
151
+ if stored is not None:
152
+ if not await self.__valid_url(stored.url):
153
+ stored.url = None # type:ignore
154
+ return stored
155
+ return Attachment(
156
+ user_account_id=None
157
+ if self.session is NotImplemented
158
+ else self.session.user_pk,
159
+ legacy_file_id=None
160
+ if attachment.legacy_file_id is None
161
+ else str(attachment.legacy_file_id),
162
+ url=attachment.url,
163
+ )
164
+
140
165
  async def __get_url(
141
- self,
142
- file_path: Optional[Path] = None,
143
- async_data_stream: Optional[AsyncIterator[bytes]] = None,
144
- data_stream: Optional[IO[bytes]] = None,
145
- data: Optional[bytes] = None,
146
- file_url: Optional[str] = None,
147
- file_name: Optional[str] = None,
148
- content_type: Optional[str] = None,
149
- legacy_file_id: Optional[Union[str, int]] = None,
166
+ self, attachment: LegacyAttachment, stored: Attachment
150
167
  ) -> tuple[bool, Optional[Path], str]:
151
- if legacy_file_id:
152
- cache = self.__store.get_url(str(legacy_file_id))
153
- if cache is not None:
154
- async with self.session.http.head(cache) as r:
155
- if r.status < 400:
156
- return False, None, cache
157
- else:
158
- self.__store.remove(str(legacy_file_id))
159
-
160
- if file_url and config.USE_ATTACHMENT_ORIGINAL_URLS:
161
- return False, None, file_url
168
+ if attachment.url and config.USE_ATTACHMENT_ORIGINAL_URLS:
169
+ return False, None, attachment.url
170
+
171
+ file_name = attachment.name
172
+ content_type = attachment.content_type
173
+ file_path = attachment.path
162
174
 
163
175
  if file_name and len(file_name) > config.ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH:
164
176
  log.debug("Trimming long filename: %s", file_name)
@@ -176,47 +188,48 @@ class AttachmentMixin(TextMessageMixin):
176
188
  file_name += ext
177
189
  temp_dir = Path(tempfile.mkdtemp())
178
190
  file_path = temp_dir / file_name
179
- if file_url:
180
- async with self.session.http.get(file_url) as r:
191
+ if attachment.url:
192
+ async with self.session.http.get(attachment.url) as r:
181
193
  r.raise_for_status()
182
194
  with file_path.open("wb") as f:
183
195
  f.write(await r.read())
184
196
 
185
- elif data_stream is not None:
186
- data = data_stream.read()
197
+ elif attachment.stream is not None:
198
+ data = attachment.stream.read()
187
199
  if data is None:
188
200
  raise RuntimeError
189
201
 
190
202
  with file_path.open("wb") as f:
191
203
  f.write(data)
192
- elif async_data_stream is not None:
204
+ elif attachment.aio_stream is not None:
193
205
  # TODO: patch slixmpp to allow this as data source for
194
206
  # upload_file() so we don't even have to write anything
195
207
  # to disk.
196
208
  with file_path.open("wb") as f:
197
- async for chunk in async_data_stream:
209
+ async for chunk in attachment.aio_stream:
198
210
  f.write(chunk)
199
- elif data is not None:
211
+ elif attachment.data is not None:
200
212
  with file_path.open("wb") as f:
201
- f.write(data)
213
+ f.write(attachment.data)
202
214
 
203
215
  is_temp = not bool(config.NO_UPLOAD_PATH)
204
216
  else:
205
217
  is_temp = False
206
218
 
219
+ assert isinstance(file_path, Path)
207
220
  if config.FIX_FILENAME_SUFFIX_MIME_TYPE:
208
221
  file_name = str(fix_suffix(file_path, content_type, file_name))
209
222
 
210
223
  if config.NO_UPLOAD_PATH:
211
224
  local_path, new_url = await self.__no_upload(
212
- file_path, file_name, legacy_file_id
225
+ file_path, file_name, stored.legacy_file_id
213
226
  )
214
227
  new_url = (config.NO_UPLOAD_URL_PREFIX or "") + "/" + urlquote(new_url)
215
228
  else:
216
229
  local_path = file_path
217
230
  new_url = await self.__upload(file_path, file_name, content_type)
218
- if legacy_file_id and new_url is not None:
219
- self.__store.set_url(self.session.user_pk, str(legacy_file_id), new_url)
231
+ if stored.legacy_file_id and new_url is not None:
232
+ stored.url = new_url
220
233
 
221
234
  return is_temp, local_path, new_url
222
235
 
@@ -225,13 +238,11 @@ class AttachmentMixin(TextMessageMixin):
225
238
  msg: Message,
226
239
  uploaded_url: str,
227
240
  path: Optional[Path],
228
- content_type: Optional[str] = None,
229
- caption: Optional[str] = None,
230
- file_name: Optional[str] = None,
241
+ attachment: LegacyAttachment,
242
+ stored: Attachment,
231
243
  ) -> Thumbnail | None:
232
- cache = self.__store.get_sims(uploaded_url)
233
- if cache:
234
- ref = self.xmpp["xep_0372"].stanza.Reference(xml=ET.fromstring(cache))
244
+ if stored.sims is not None:
245
+ ref = self.xmpp["xep_0372"].stanza.Reference(xml=ET.fromstring(stored.sims))
235
246
  msg.append(ref)
236
247
  if ref["sims"]["file"].get_plugin("thumbnail", check=True):
237
248
  return ref["sims"]["file"]["thumbnail"]
@@ -242,12 +253,14 @@ class AttachmentMixin(TextMessageMixin):
242
253
  return None
243
254
 
244
255
  ref = self.xmpp["xep_0385"].get_sims(
245
- path, [uploaded_url], content_type, caption
256
+ path, [uploaded_url], attachment.content_type, attachment.caption
246
257
  )
247
- if file_name:
248
- ref["sims"]["file"]["name"] = file_name
258
+ if attachment.name:
259
+ ref["sims"]["file"]["name"] = attachment.name
249
260
  thumbnail = None
250
- if content_type is not None and content_type.startswith("image"):
261
+ if attachment.content_type is not None and attachment.content_type.startswith(
262
+ "image"
263
+ ):
251
264
  try:
252
265
  h, x, y = await self.xmpp.loop.run_in_executor(
253
266
  avatar_cache._thread_pool, get_thumbhash, path
@@ -261,8 +274,7 @@ class AttachmentMixin(TextMessageMixin):
261
274
  thumbnail["media-type"] = "image/thumbhash"
262
275
  thumbnail["uri"] = "data:image/thumbhash;base64," + urlquote(h)
263
276
 
264
- self.__store.set_sims(uploaded_url, str(ref))
265
-
277
+ stored.sims = str(ref)
266
278
  msg.append(ref)
267
279
 
268
280
  return thumbnail
@@ -272,26 +284,25 @@ class AttachmentMixin(TextMessageMixin):
272
284
  msg: Message,
273
285
  uploaded_url: str,
274
286
  path: Optional[Path],
275
- content_type: Optional[str] = None,
276
- caption: Optional[str] = None,
277
- file_name: Optional[str] = None,
287
+ attachment: LegacyAttachment,
288
+ stored: Attachment,
278
289
  thumbnail: Optional[Thumbnail] = None,
279
- ):
280
- cache = self.__store.get_sfs(uploaded_url)
281
- if cache:
282
- msg.append(StatelessFileSharing(xml=ET.fromstring(cache)))
290
+ ) -> None:
291
+ if stored.sfs is not None:
292
+ msg.append(StatelessFileSharing(xml=ET.fromstring(stored.sfs)))
283
293
  return
284
294
 
285
295
  if not path:
286
296
  return
287
297
 
288
- sfs = self.xmpp["xep_0447"].get_sfs(path, [uploaded_url], content_type, caption)
289
- if file_name:
290
- sfs["file"]["name"] = file_name
298
+ sfs = self.xmpp["xep_0447"].get_sfs(
299
+ path, [uploaded_url], attachment.content_type, attachment.caption
300
+ )
301
+ if attachment.name:
302
+ sfs["file"]["name"] = attachment.name
291
303
  if thumbnail is not None:
292
304
  sfs["file"].append(thumbnail)
293
- self.__store.set_sfs(uploaded_url, str(sfs))
294
-
305
+ stored.sfs = str(sfs)
295
306
  msg.append(sfs)
296
307
 
297
308
  def __send_url(
@@ -300,9 +311,9 @@ class AttachmentMixin(TextMessageMixin):
300
311
  legacy_msg_id: LegacyMessageType,
301
312
  uploaded_url: str,
302
313
  caption: Optional[str] = None,
303
- carbon=False,
314
+ carbon: bool = False,
304
315
  when: Optional[datetime] = None,
305
- correction=False,
316
+ correction: bool = False,
306
317
  **kwargs,
307
318
  ) -> list[Message]:
308
319
  msg["oob"]["url"] = uploaded_url
@@ -325,51 +336,21 @@ class AttachmentMixin(TextMessageMixin):
325
336
  self._set_msg_id(msg, legacy_msg_id)
326
337
  return [self._send(msg, carbon=carbon, **kwargs)]
327
338
 
328
- async def send_file(
339
+ def __get_base_message(
329
340
  self,
330
- file_path: Optional[Union[Path, str]] = None,
331
341
  legacy_msg_id: Optional[LegacyMessageType] = None,
332
- *,
333
- async_data_stream: Optional[AsyncIterator[bytes]] = None,
334
- data_stream: Optional[IO[bytes]] = None,
335
- data: Optional[bytes] = None,
336
- file_url: Optional[str] = None,
337
- file_name: Optional[str] = None,
338
- content_type: Optional[str] = None,
339
342
  reply_to: Optional[MessageReference] = None,
340
343
  when: Optional[datetime] = None,
341
- caption: Optional[str] = None,
342
- legacy_file_id: Optional[Union[str, int]] = None,
343
344
  thread: Optional[LegacyThreadType] = None,
344
- **kwargs,
345
- ) -> tuple[Optional[str], list[Message]]:
346
- """
347
- Send a single file from this :term:`XMPP Entity`.
348
-
349
- :param file_path: Path to the attachment
350
- :param async_data_stream: Alternatively (and ideally) an AsyncIterator yielding bytes
351
- :param data_stream: Alternatively, a stream of bytes (such as a File object)
352
- :param data: Alternatively, a bytes object
353
- :param file_url: Alternatively, a URL
354
- :param file_name: How the file should be named.
355
- :param content_type: MIME type, inferred from filename if not given
356
- :param legacy_msg_id: If you want to be able to transport read markers from the gateway
357
- user to the legacy network, specify this
358
- :param reply_to: Quote another message (:xep:`0461`)
359
- :param when: when the file was sent, for a "delay" tag (:xep:`0203`)
360
- :param caption: an optional text that is linked to the file
361
- :param legacy_file_id: A unique identifier for the file on the legacy network.
362
- Plugins should try their best to provide it, to avoid duplicates.
363
- :param thread:
364
- """
365
- carbon = kwargs.pop("carbon", False)
366
- mto = kwargs.pop("mto", None)
367
- store_multi = kwargs.pop("store_multi", True)
368
- correction = kwargs.get("correction", False)
345
+ carbon: bool = False,
346
+ correction: bool = False,
347
+ mto: Optional[JID] = None,
348
+ ) -> Message:
369
349
  if correction and (original_xmpp_id := self._legacy_to_xmpp(legacy_msg_id)):
370
- xmpp_ids = self.xmpp.store.multi.get_xmpp_ids(
371
- self.session.user_pk, original_xmpp_id
372
- )
350
+ with self.xmpp.store.session() as orm:
351
+ xmpp_ids = self.xmpp.store.id_map.get_xmpp(
352
+ orm, self._recipient_pk(), str(legacy_msg_id), self.is_group
353
+ )
373
354
 
374
355
  for xmpp_id in xmpp_ids:
375
356
  if xmpp_id == original_xmpp_id:
@@ -386,7 +367,7 @@ class AttachmentMixin(TextMessageMixin):
386
367
  else:
387
368
  reply_to_for_attachment = reply_to
388
369
 
389
- msg = self._make_message(
370
+ return self._make_message(
390
371
  when=when,
391
372
  reply_to=reply_to_for_attachment,
392
373
  carbon=carbon,
@@ -394,43 +375,85 @@ class AttachmentMixin(TextMessageMixin):
394
375
  thread=thread,
395
376
  )
396
377
 
397
- if content_type is None and (name := (file_name or file_path or file_url)):
398
- content_type, _ = guess_type(name)
399
-
400
- is_temp, local_path, new_url = await self.__get_url(
401
- Path(file_path) if file_path else None,
402
- async_data_stream,
403
- data_stream,
404
- data,
405
- file_url,
406
- file_name,
407
- content_type,
408
- legacy_file_id,
409
- )
378
+ async def send_file(
379
+ self,
380
+ attachment: LegacyAttachment | Path | str,
381
+ legacy_msg_id: Optional[LegacyMessageType] = None,
382
+ *,
383
+ reply_to: Optional[MessageReference] = None,
384
+ when: Optional[datetime] = None,
385
+ thread: Optional[LegacyThreadType] = None,
386
+ **kwargs,
387
+ ) -> tuple[Optional[str], list[Message]]:
388
+ """
389
+ Send a single file from this :term:`XMPP Entity`.
410
390
 
411
- if new_url is None:
412
- msg["body"] = (
413
- "I tried to send a file, but something went wrong. "
414
- "Tell your slidge admin to check the logs."
415
- )
416
- self._set_msg_id(msg, legacy_msg_id)
417
- return None, [self._send(msg, **kwargs)]
391
+ :param attachment: The file to send.
392
+ Ideally, a :class:`.LegacyAttachment` with a unique ``legacy_file_id``
393
+ attribute set, to optimise potential future reuses.
394
+ It can also be:
395
+ - a :class:`pathlib.Path` instance to point to a local file, or
396
+ - a ``str``, representing a fetchable HTTP URL.
397
+ :param legacy_msg_id: If you want to be able to transport read markers from the gateway
398
+ user to the legacy network, specify this
399
+ :param reply_to: Quote another message (:xep:`0461`)
400
+ :param when: when the file was sent, for a "delay" tag (:xep:`0203`)
401
+ :param thread:
402
+ """
403
+ store_multi = kwargs.pop("store_multi", True)
404
+ carbon = kwargs.pop("carbon", False)
405
+ mto = kwargs.pop("mto", None)
406
+ correction = kwargs.get("correction", False)
418
407
 
419
- thumbnail = await self.__set_sims(
420
- msg, new_url, local_path, content_type, caption, file_name
421
- )
422
- self.__set_sfs(
423
- msg, new_url, local_path, content_type, caption, file_name, thumbnail
408
+ msg = self.__get_base_message(
409
+ legacy_msg_id, reply_to, when, thread, carbon, correction, mto
424
410
  )
411
+
412
+ if isinstance(attachment, str):
413
+ attachment = LegacyAttachment(url=attachment)
414
+ elif isinstance(attachment, Path):
415
+ attachment = LegacyAttachment(path=attachment)
416
+
417
+ stored = await self.__get_stored(attachment)
418
+
419
+ if attachment.content_type is None and (
420
+ name := (attachment.name or attachment.url or attachment.path)
421
+ ):
422
+ attachment.content_type, _ = guess_type(name)
423
+
424
+ if stored.url:
425
+ is_temp = False
426
+ local_path = None
427
+ new_url = stored.url
428
+ else:
429
+ is_temp, local_path, new_url = await self.__get_url(attachment, stored)
430
+ if new_url is None:
431
+ msg["body"] = (
432
+ "I tried to send a file, but something went wrong. "
433
+ "Tell your slidge admin to check the logs."
434
+ )
435
+ self._set_msg_id(msg, legacy_msg_id)
436
+ return None, [self._send(msg, **kwargs)]
437
+
438
+ stored.url = new_url
439
+ thumbnail = await self.__set_sims(msg, new_url, local_path, attachment, stored)
440
+ self.__set_sfs(msg, new_url, local_path, attachment, stored, thumbnail)
441
+
442
+ if self.session is not NotImplemented:
443
+ with self.xmpp.store.session(expire_on_commit=False) as orm:
444
+ orm.add(stored)
445
+ orm.commit()
446
+
425
447
  if is_temp and isinstance(local_path, Path):
426
448
  local_path.unlink()
427
449
  local_path.parent.rmdir()
428
450
 
429
451
  msgs = self.__send_url(
430
- msg, legacy_msg_id, new_url, caption, carbon, when, **kwargs
452
+ msg, legacy_msg_id, new_url, attachment.caption, carbon, when, **kwargs
431
453
  )
432
- if store_multi:
433
- self.__store_multi(legacy_msg_id, msgs)
454
+ if self.session is not NotImplemented:
455
+ if store_multi:
456
+ self.__store_multi(legacy_msg_id, msgs)
434
457
  return new_url, msgs
435
458
 
436
459
  def __send_body(
@@ -463,18 +486,15 @@ class AttachmentMixin(TextMessageMixin):
463
486
  reply_to: Optional[MessageReference] = None,
464
487
  when: Optional[datetime] = None,
465
488
  thread: Optional[LegacyThreadType] = None,
466
- body_first=False,
467
- correction=False,
489
+ body_first: bool = False,
490
+ correction: bool = False,
468
491
  correction_event_id: Optional[LegacyMessageType] = None,
469
492
  **kwargs,
470
- ):
493
+ ) -> None:
471
494
  # TODO: once the epic XEP-0385 vs XEP-0447 battle is over, pick
472
495
  # one and stop sending several attachments this way
473
496
  # we attach the legacy_message ID to the last message we send, because
474
497
  # we don't want several messages with the same ID (especially for MUC MAM)
475
- # TODO: refactor this so we limit the number of SQL calls, ie, if
476
- # the legacy file ID is known, only fetch the row once, and if it
477
- # is new, write it all in a single call
478
498
  if not attachments and not body:
479
499
  # ignoring empty message
480
500
  return
@@ -500,18 +520,11 @@ class AttachmentMixin(TextMessageMixin):
500
520
  else:
501
521
  legacy = None
502
522
  _url, msgs = await self.send_file(
503
- file_path=attachment.path,
504
- legacy_msg_id=legacy,
505
- file_url=attachment.url,
506
- data_stream=attachment.stream,
507
- data=attachment.data,
523
+ attachment,
524
+ legacy,
508
525
  reply_to=reply_to,
509
526
  when=when,
510
527
  thread=thread,
511
- file_name=attachment.name,
512
- content_type=attachment.content_type,
513
- legacy_file_id=attachment.legacy_file_id,
514
- caption=attachment.caption,
515
528
  store_multi=False,
516
529
  **kwargs,
517
530
  )
@@ -524,7 +537,7 @@ class AttachmentMixin(TextMessageMixin):
524
537
  self,
525
538
  legacy_msg_id: Optional[LegacyMessageType],
526
539
  all_msgs: Sequence[Optional[Message]],
527
- ):
540
+ ) -> None:
528
541
  if legacy_msg_id is None:
529
542
  return
530
543
  ids = []
@@ -535,9 +548,11 @@ class AttachmentMixin(TextMessageMixin):
535
548
  ids.append(stanza_id["id"])
536
549
  else:
537
550
  ids.append(msg.get_id())
538
- self.xmpp.store.multi.set_xmpp_ids(
539
- self.session.user_pk, str(legacy_msg_id), ids
540
- )
551
+ with self.xmpp.store.session() as orm:
552
+ self.xmpp.store.id_map.set_msg(
553
+ orm, self._recipient_pk(), str(legacy_msg_id), ids, self.is_group
554
+ )
555
+ orm.commit()
541
556
 
542
557
 
543
558
  def get_thumbhash(path: Path) -> tuple[str, int, int]: