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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) 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 +123 -210
  8. slidge/contact/roster.py +108 -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 +120 -93
  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 +26 -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 +177 -87
  29. slidge/core/mixins/__init__.py +1 -11
  30. slidge/core/mixins/attachment.py +200 -147
  31. slidge/core/mixins/avatar.py +105 -177
  32. slidge/core/mixins/base.py +3 -1
  33. slidge/core/mixins/db.py +50 -2
  34. slidge/core/mixins/disco.py +1 -1
  35. slidge/core/mixins/message.py +19 -17
  36. slidge/core/mixins/message_maker.py +29 -15
  37. slidge/core/mixins/message_text.py +67 -30
  38. slidge/core/mixins/presence.py +94 -37
  39. slidge/core/pubsub.py +42 -47
  40. slidge/core/session.py +95 -60
  41. slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +361 -0
  42. slidge/db/avatar.py +150 -119
  43. slidge/db/meta.py +33 -22
  44. slidge/db/models.py +69 -117
  45. slidge/db/store.py +414 -1094
  46. slidge/group/archive.py +65 -55
  47. slidge/group/bookmarks.py +96 -59
  48. slidge/group/participant.py +150 -144
  49. slidge/group/room.py +345 -327
  50. slidge/main.py +34 -22
  51. slidge/migration.py +17 -29
  52. slidge/slixfix/__init__.py +20 -4
  53. slidge/slixfix/delivery_receipt.py +6 -4
  54. slidge/slixfix/link_preview/link_preview.py +1 -1
  55. slidge/slixfix/link_preview/stanza.py +1 -1
  56. slidge/slixfix/roster.py +5 -7
  57. slidge/slixfix/xep_0077/register.py +8 -8
  58. slidge/slixfix/xep_0077/stanza.py +7 -7
  59. slidge/slixfix/xep_0100/gateway.py +12 -13
  60. slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
  61. slidge/slixfix/xep_0292/vcard4.py +12 -2
  62. slidge/util/archive_msg.py +11 -5
  63. slidge/util/conf.py +27 -21
  64. slidge/util/jid_escaping.py +1 -1
  65. slidge/{core/mixins → util}/lock.py +6 -6
  66. slidge/util/test.py +30 -29
  67. slidge/util/types.py +24 -18
  68. slidge/util/util.py +26 -22
  69. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/METADATA +1 -1
  70. slidge-0.3.0.dist-info/RECORD +95 -0
  71. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/WHEEL +1 -1
  72. slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py +0 -33
  73. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -36
  74. slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +0 -85
  75. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -36
  76. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -37
  77. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -41
  78. slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +0 -52
  79. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -42
  80. slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -61
  81. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -48
  82. slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -43
  83. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -139
  84. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -50
  85. slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +0 -79
  86. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -214
  87. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -52
  88. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -34
  89. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -26
  90. slidge-0.2.12.dist-info/RECORD +0 -112
  91. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/entry_points.txt +0 -0
  92. {slidge-0.2.12.dist-info → slidge-0.3.0.dist-info}/licenses/LICENSE +0 -0
  93. {slidge-0.2.12.dist-info → slidge-0.3.0.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,44 @@ 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 and self.session is not NotImplemented:
145
+ with self.xmpp.store.session() as orm:
146
+ stored = (
147
+ orm.query(Attachment)
148
+ .filter_by(
149
+ legacy_file_id=str(attachment.legacy_file_id),
150
+ user_account_id=self.session.user_pk,
151
+ )
152
+ .one_or_none()
153
+ )
154
+ if stored is not None:
155
+ if not await self.__valid_url(stored.url):
156
+ stored.url = None # type:ignore
157
+ return stored
158
+ return Attachment(
159
+ user_account_id=None
160
+ if self.session is NotImplemented
161
+ else self.session.user_pk,
162
+ legacy_file_id=None
163
+ if attachment.legacy_file_id is None
164
+ else str(attachment.legacy_file_id),
165
+ url=attachment.url,
166
+ )
167
+
140
168
  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,
169
+ self, attachment: LegacyAttachment, stored: Attachment
150
170
  ) -> 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
171
+ if attachment.url and config.USE_ATTACHMENT_ORIGINAL_URLS:
172
+ return False, None, attachment.url
173
+
174
+ file_name = attachment.name
175
+ content_type = attachment.content_type
176
+ file_path = attachment.path
162
177
 
163
178
  if file_name and len(file_name) > config.ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH:
164
179
  log.debug("Trimming long filename: %s", file_name)
@@ -176,47 +191,48 @@ class AttachmentMixin(TextMessageMixin):
176
191
  file_name += ext
177
192
  temp_dir = Path(tempfile.mkdtemp())
178
193
  file_path = temp_dir / file_name
179
- if file_url:
180
- async with self.session.http.get(file_url) as r:
194
+ if attachment.url:
195
+ async with self.session.http.get(attachment.url) as r:
181
196
  r.raise_for_status()
182
197
  with file_path.open("wb") as f:
183
198
  f.write(await r.read())
184
199
 
185
- elif data_stream is not None:
186
- data = data_stream.read()
200
+ elif attachment.stream is not None:
201
+ data = attachment.stream.read()
187
202
  if data is None:
188
203
  raise RuntimeError
189
204
 
190
205
  with file_path.open("wb") as f:
191
206
  f.write(data)
192
- elif async_data_stream is not None:
207
+ elif attachment.aio_stream is not None:
193
208
  # TODO: patch slixmpp to allow this as data source for
194
209
  # upload_file() so we don't even have to write anything
195
210
  # to disk.
196
211
  with file_path.open("wb") as f:
197
- async for chunk in async_data_stream:
212
+ async for chunk in attachment.aio_stream:
198
213
  f.write(chunk)
199
- elif data is not None:
214
+ elif attachment.data is not None:
200
215
  with file_path.open("wb") as f:
201
- f.write(data)
216
+ f.write(attachment.data)
202
217
 
203
218
  is_temp = not bool(config.NO_UPLOAD_PATH)
204
219
  else:
205
220
  is_temp = False
206
221
 
222
+ assert isinstance(file_path, Path)
207
223
  if config.FIX_FILENAME_SUFFIX_MIME_TYPE:
208
224
  file_name = str(fix_suffix(file_path, content_type, file_name))
209
225
 
210
226
  if config.NO_UPLOAD_PATH:
211
227
  local_path, new_url = await self.__no_upload(
212
- file_path, file_name, legacy_file_id
228
+ file_path, file_name, stored.legacy_file_id
213
229
  )
214
230
  new_url = (config.NO_UPLOAD_URL_PREFIX or "") + "/" + urlquote(new_url)
215
231
  else:
216
232
  local_path = file_path
217
233
  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)
234
+ if stored.legacy_file_id and new_url is not None:
235
+ stored.url = new_url
220
236
 
221
237
  return is_temp, local_path, new_url
222
238
 
@@ -225,13 +241,11 @@ class AttachmentMixin(TextMessageMixin):
225
241
  msg: Message,
226
242
  uploaded_url: str,
227
243
  path: Optional[Path],
228
- content_type: Optional[str] = None,
229
- caption: Optional[str] = None,
230
- file_name: Optional[str] = None,
244
+ attachment: LegacyAttachment,
245
+ stored: Attachment,
231
246
  ) -> 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))
247
+ if stored.sims is not None:
248
+ ref = self.xmpp["xep_0372"].stanza.Reference(xml=ET.fromstring(stored.sims))
235
249
  msg.append(ref)
236
250
  if ref["sims"]["file"].get_plugin("thumbnail", check=True):
237
251
  return ref["sims"]["file"]["thumbnail"]
@@ -242,12 +256,14 @@ class AttachmentMixin(TextMessageMixin):
242
256
  return None
243
257
 
244
258
  ref = self.xmpp["xep_0385"].get_sims(
245
- path, [uploaded_url], content_type, caption
259
+ path, [uploaded_url], attachment.content_type, attachment.caption
246
260
  )
247
- if file_name:
248
- ref["sims"]["file"]["name"] = file_name
261
+ if attachment.name:
262
+ ref["sims"]["file"]["name"] = attachment.name
249
263
  thumbnail = None
250
- if content_type is not None and content_type.startswith("image"):
264
+ if attachment.content_type is not None and attachment.content_type.startswith(
265
+ "image"
266
+ ):
251
267
  try:
252
268
  h, x, y = await self.xmpp.loop.run_in_executor(
253
269
  avatar_cache._thread_pool, get_thumbhash, path
@@ -261,8 +277,7 @@ class AttachmentMixin(TextMessageMixin):
261
277
  thumbnail["media-type"] = "image/thumbhash"
262
278
  thumbnail["uri"] = "data:image/thumbhash;base64," + urlquote(h)
263
279
 
264
- self.__store.set_sims(uploaded_url, str(ref))
265
-
280
+ stored.sims = str(ref)
266
281
  msg.append(ref)
267
282
 
268
283
  return thumbnail
@@ -272,26 +287,29 @@ class AttachmentMixin(TextMessageMixin):
272
287
  msg: Message,
273
288
  uploaded_url: str,
274
289
  path: Optional[Path],
275
- content_type: Optional[str] = None,
276
- caption: Optional[str] = None,
277
- file_name: Optional[str] = None,
290
+ attachment: LegacyAttachment,
291
+ stored: Attachment,
278
292
  thumbnail: Optional[Thumbnail] = None,
279
- ):
280
- cache = self.__store.get_sfs(uploaded_url)
281
- if cache:
282
- msg.append(StatelessFileSharing(xml=ET.fromstring(cache)))
293
+ ) -> None:
294
+ if stored.sfs is not None:
295
+ msg.append(StatelessFileSharing(xml=ET.fromstring(stored.sfs)))
283
296
  return
284
297
 
285
298
  if not path:
286
299
  return
287
300
 
288
- sfs = self.xmpp["xep_0447"].get_sfs(path, [uploaded_url], content_type, caption)
289
- if file_name:
290
- sfs["file"]["name"] = file_name
301
+ sfs = self.xmpp["xep_0447"].get_sfs(
302
+ path, [uploaded_url], attachment.content_type, attachment.caption
303
+ )
304
+ if attachment.name:
305
+ sfs["file"]["name"] = attachment.name
306
+ if attachment.disposition:
307
+ sfs["disposition"] = attachment.disposition
308
+ else:
309
+ del sfs["disposition"]
291
310
  if thumbnail is not None:
292
311
  sfs["file"].append(thumbnail)
293
- self.__store.set_sfs(uploaded_url, str(sfs))
294
-
312
+ stored.sfs = str(sfs)
295
313
  msg.append(sfs)
296
314
 
297
315
  def __send_url(
@@ -300,9 +318,9 @@ class AttachmentMixin(TextMessageMixin):
300
318
  legacy_msg_id: LegacyMessageType,
301
319
  uploaded_url: str,
302
320
  caption: Optional[str] = None,
303
- carbon=False,
321
+ carbon: bool = False,
304
322
  when: Optional[datetime] = None,
305
- correction=False,
323
+ correction: bool = False,
306
324
  **kwargs,
307
325
  ) -> list[Message]:
308
326
  msg["oob"]["url"] = uploaded_url
@@ -325,51 +343,21 @@ class AttachmentMixin(TextMessageMixin):
325
343
  self._set_msg_id(msg, legacy_msg_id)
326
344
  return [self._send(msg, carbon=carbon, **kwargs)]
327
345
 
328
- async def send_file(
346
+ def __get_base_message(
329
347
  self,
330
- file_path: Optional[Union[Path, str]] = None,
331
348
  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
349
  reply_to: Optional[MessageReference] = None,
340
350
  when: Optional[datetime] = None,
341
- caption: Optional[str] = None,
342
- legacy_file_id: Optional[Union[str, int]] = None,
343
351
  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)
352
+ carbon: bool = False,
353
+ correction: bool = False,
354
+ mto: Optional[JID] = None,
355
+ ) -> Message:
369
356
  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
- )
357
+ with self.xmpp.store.session() as orm:
358
+ xmpp_ids = self.xmpp.store.id_map.get_xmpp(
359
+ orm, self._recipient_pk(), str(legacy_msg_id), self.is_group
360
+ )
373
361
 
374
362
  for xmpp_id in xmpp_ids:
375
363
  if xmpp_id == original_xmpp_id:
@@ -386,7 +374,7 @@ class AttachmentMixin(TextMessageMixin):
386
374
  else:
387
375
  reply_to_for_attachment = reply_to
388
376
 
389
- msg = self._make_message(
377
+ return self._make_message(
390
378
  when=when,
391
379
  reply_to=reply_to_for_attachment,
392
380
  carbon=carbon,
@@ -394,43 +382,116 @@ class AttachmentMixin(TextMessageMixin):
394
382
  thread=thread,
395
383
  )
396
384
 
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,
385
+ async def send_file(
386
+ self,
387
+ attachment: LegacyAttachment | Path | str,
388
+ legacy_msg_id: Optional[LegacyMessageType] = None,
389
+ *,
390
+ reply_to: Optional[MessageReference] = None,
391
+ when: Optional[datetime] = None,
392
+ thread: Optional[LegacyThreadType] = None,
393
+ **kwargs,
394
+ ) -> tuple[Optional[str], list[Message]]:
395
+ """
396
+ Send a single file from this :term:`XMPP Entity`.
397
+
398
+ :param attachment: The file to send.
399
+ Ideally, a :class:`.LegacyAttachment` with a unique ``legacy_file_id``
400
+ attribute set, to optimise potential future reuses.
401
+ It can also be:
402
+ - a :class:`pathlib.Path` instance to point to a local file, or
403
+ - a ``str``, representing a fetchable HTTP URL.
404
+ :param legacy_msg_id: If you want to be able to transport read markers from the gateway
405
+ user to the legacy network, specify this
406
+ :param reply_to: Quote another message (:xep:`0461`)
407
+ :param when: when the file was sent, for a "delay" tag (:xep:`0203`)
408
+ :param thread:
409
+ """
410
+ coro = self.__send_file(
411
+ attachment,
412
+ legacy_msg_id,
413
+ reply_to=reply_to,
414
+ when=when,
415
+ thread=thread,
416
+ **kwargs,
409
417
  )
418
+ if self.session is NotImplemented:
419
+ return await coro
420
+ elif not isinstance(attachment, LegacyAttachment):
421
+ return await coro
422
+ elif attachment.legacy_file_id is None:
423
+ return await coro
424
+ else:
425
+ # prevents race conditions where we download the same thing several time
426
+ # and end up attempting to insert it twice in the DB, raising an
427
+ # IntegrityError.
428
+ async with self.session.lock(("attachment", attachment.legacy_file_id)):
429
+ return await coro
410
430
 
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)]
431
+ async def __send_file(
432
+ self,
433
+ attachment: LegacyAttachment | Path | str,
434
+ legacy_msg_id: Optional[LegacyMessageType] = None,
435
+ *,
436
+ reply_to: Optional[MessageReference] = None,
437
+ when: Optional[datetime] = None,
438
+ thread: Optional[LegacyThreadType] = None,
439
+ **kwargs,
440
+ ) -> tuple[Optional[str], list[Message]]:
441
+ store_multi = kwargs.pop("store_multi", True)
442
+ carbon = kwargs.pop("carbon", False)
443
+ mto = kwargs.pop("mto", None)
444
+ correction = kwargs.get("correction", False)
418
445
 
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
446
+ msg = self.__get_base_message(
447
+ legacy_msg_id, reply_to, when, thread, carbon, correction, mto
424
448
  )
449
+
450
+ if isinstance(attachment, str):
451
+ attachment = LegacyAttachment(url=attachment)
452
+ elif isinstance(attachment, Path):
453
+ attachment = LegacyAttachment(path=attachment)
454
+
455
+ stored = await self.__get_stored(attachment)
456
+
457
+ if attachment.content_type is None and (
458
+ name := (attachment.name or attachment.url or attachment.path)
459
+ ):
460
+ attachment.content_type, _ = guess_type(name)
461
+
462
+ if stored.url:
463
+ is_temp = False
464
+ local_path = None
465
+ new_url = stored.url
466
+ else:
467
+ is_temp, local_path, new_url = await self.__get_url(attachment, stored)
468
+ if new_url is None:
469
+ msg["body"] = (
470
+ "I tried to send a file, but something went wrong. "
471
+ "Tell your slidge admin to check the logs."
472
+ )
473
+ self._set_msg_id(msg, legacy_msg_id)
474
+ return None, [self._send(msg, **kwargs)]
475
+
476
+ stored.url = new_url
477
+ thumbnail = await self.__set_sims(msg, new_url, local_path, attachment, stored)
478
+ self.__set_sfs(msg, new_url, local_path, attachment, stored, thumbnail)
479
+
480
+ if self.session is not NotImplemented:
481
+ with self.xmpp.store.session(expire_on_commit=False) as orm:
482
+ orm.add(stored)
483
+ orm.commit()
484
+
425
485
  if is_temp and isinstance(local_path, Path):
426
486
  local_path.unlink()
427
487
  local_path.parent.rmdir()
428
488
 
429
489
  msgs = self.__send_url(
430
- msg, legacy_msg_id, new_url, caption, carbon, when, **kwargs
490
+ msg, legacy_msg_id, new_url, attachment.caption, carbon, when, **kwargs
431
491
  )
432
- if store_multi:
433
- self.__store_multi(legacy_msg_id, msgs)
492
+ if self.session is not NotImplemented:
493
+ if store_multi:
494
+ self.__store_multi(legacy_msg_id, msgs)
434
495
  return new_url, msgs
435
496
 
436
497
  def __send_body(
@@ -463,18 +524,15 @@ class AttachmentMixin(TextMessageMixin):
463
524
  reply_to: Optional[MessageReference] = None,
464
525
  when: Optional[datetime] = None,
465
526
  thread: Optional[LegacyThreadType] = None,
466
- body_first=False,
467
- correction=False,
527
+ body_first: bool = False,
528
+ correction: bool = False,
468
529
  correction_event_id: Optional[LegacyMessageType] = None,
469
530
  **kwargs,
470
- ):
531
+ ) -> None:
471
532
  # TODO: once the epic XEP-0385 vs XEP-0447 battle is over, pick
472
533
  # one and stop sending several attachments this way
473
534
  # we attach the legacy_message ID to the last message we send, because
474
535
  # 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
536
  if not attachments and not body:
479
537
  # ignoring empty message
480
538
  return
@@ -500,18 +558,11 @@ class AttachmentMixin(TextMessageMixin):
500
558
  else:
501
559
  legacy = None
502
560
  _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,
561
+ attachment,
562
+ legacy,
508
563
  reply_to=reply_to,
509
564
  when=when,
510
565
  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
566
  store_multi=False,
516
567
  **kwargs,
517
568
  )
@@ -524,7 +575,7 @@ class AttachmentMixin(TextMessageMixin):
524
575
  self,
525
576
  legacy_msg_id: Optional[LegacyMessageType],
526
577
  all_msgs: Sequence[Optional[Message]],
527
- ):
578
+ ) -> None:
528
579
  if legacy_msg_id is None:
529
580
  return
530
581
  ids = []
@@ -535,9 +586,11 @@ class AttachmentMixin(TextMessageMixin):
535
586
  ids.append(stanza_id["id"])
536
587
  else:
537
588
  ids.append(msg.get_id())
538
- self.xmpp.store.multi.set_xmpp_ids(
539
- self.session.user_pk, str(legacy_msg_id), ids
540
- )
589
+ with self.xmpp.store.session() as orm:
590
+ self.xmpp.store.id_map.set_msg(
591
+ orm, self._recipient_pk(), str(legacy_msg_id), ids, self.is_group
592
+ )
593
+ orm.commit()
541
594
 
542
595
 
543
596
  def get_thumbhash(path: Path) -> tuple[str, int, int]: