slidge 0.2.0a1__tar.gz → 0.2.0a3__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. {slidge-0.2.0a1 → slidge-0.2.0a3}/PKG-INFO +1 -1
  2. {slidge-0.2.0a1 → slidge-0.2.0a3}/pyproject.toml +1 -1
  3. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/__version__.py +1 -1
  4. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/contact/contact.py +57 -6
  5. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/base.py +0 -2
  6. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/session_dispatcher.py +19 -0
  7. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/vcard_temp.py +2 -2
  8. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/pubsub.py +11 -9
  9. slidge-0.2.0a3/slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +44 -0
  10. slidge-0.2.0a3/slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
  11. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +13 -1
  12. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/meta.py +7 -0
  13. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/models.py +12 -2
  14. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/store.py +14 -1
  15. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/group/room.py +8 -2
  16. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/main.py +0 -4
  17. slidge-0.2.0a3/slidge/py.typed +0 -0
  18. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/roster.py +4 -2
  19. slidge-0.2.0a3/slidge/slixfix/xep_0292/vcard4.py +14 -0
  20. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/util/test.py +1 -1
  21. slidge-0.2.0a1/slidge/slixfix/xep_0292/vcard4.py +0 -103
  22. {slidge-0.2.0a1 → slidge-0.2.0a3}/LICENSE +0 -0
  23. {slidge-0.2.0a1 → slidge-0.2.0a3}/README.md +0 -0
  24. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/__init__.py +0 -0
  25. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/__main__.py +0 -0
  26. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/command/__init__.py +0 -0
  27. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/command/adhoc.py +0 -0
  28. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/command/admin.py +0 -0
  29. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/command/base.py +0 -0
  30. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/command/categories.py +0 -0
  31. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/command/chat_command.py +0 -0
  32. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/command/register.py +0 -0
  33. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/command/user.py +0 -0
  34. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/contact/__init__.py +0 -0
  35. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/contact/roster.py +0 -0
  36. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/__init__.py +0 -0
  37. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/config.py +0 -0
  38. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/__init__.py +0 -0
  39. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/caps.py +0 -0
  40. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/delivery_receipt.py +0 -0
  41. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/disco.py +0 -0
  42. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/mam.py +0 -0
  43. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/muc_admin.py +0 -0
  44. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/ping.py +0 -0
  45. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/presence.py +0 -0
  46. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/registration.py +0 -0
  47. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/gateway/search.py +0 -0
  48. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/__init__.py +0 -0
  49. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/attachment.py +0 -0
  50. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/avatar.py +0 -0
  51. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/base.py +0 -0
  52. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/db.py +0 -0
  53. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/disco.py +0 -0
  54. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/lock.py +0 -0
  55. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/message.py +0 -0
  56. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/message_maker.py +0 -0
  57. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/presence.py +0 -0
  58. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/mixins/recipient.py +0 -0
  59. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/core/session.py +0 -0
  60. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/__init__.py +0 -0
  61. /slidge-0.2.0a1/slidge/py.typed → /slidge-0.2.0a3/slidge/db/alembic/__init__.py +0 -0
  62. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/env.py +0 -0
  63. /slidge-0.2.0a1/slidge/util/db.py → /slidge-0.2.0a3/slidge/db/alembic/old_user_store.py +0 -0
  64. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/script.py.mako +0 -0
  65. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -0
  66. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -0
  67. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -0
  68. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -0
  69. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -0
  70. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -0
  71. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -0
  72. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -0
  73. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -0
  74. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -0
  75. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/db/avatar.py +0 -0
  76. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/group/__init__.py +0 -0
  77. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/group/archive.py +0 -0
  78. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/group/bookmarks.py +0 -0
  79. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/group/participant.py +0 -0
  80. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/migration.py +0 -0
  81. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/__init__.py +0 -0
  82. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/link_preview/__init__.py +0 -0
  83. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/link_preview/link_preview.py +0 -0
  84. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/link_preview/stanza.py +0 -0
  85. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0077/__init__.py +0 -0
  86. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0077/register.py +0 -0
  87. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0077/stanza.py +0 -0
  88. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0100/__init__.py +0 -0
  89. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0100/gateway.py +0 -0
  90. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0100/stanza.py +0 -0
  91. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0153/__init__.py +0 -0
  92. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0153/stanza.py +0 -0
  93. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
  94. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0264/__init__.py +0 -0
  95. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0264/stanza.py +0 -0
  96. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
  97. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0292/__init__.py +0 -0
  98. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0313/__init__.py +0 -0
  99. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0313/mam.py +0 -0
  100. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0313/stanza.py +0 -0
  101. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0317/__init__.py +0 -0
  102. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0317/hats.py +0 -0
  103. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0317/stanza.py +0 -0
  104. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
  105. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
  106. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
  107. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0424/__init__.py +0 -0
  108. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0424/retraction.py +0 -0
  109. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0424/stanza.py +0 -0
  110. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0490/__init__.py +0 -0
  111. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0490/mds.py +0 -0
  112. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/slixfix/xep_0490/stanza.py +0 -0
  113. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/util/__init__.py +0 -0
  114. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/util/archive_msg.py +0 -0
  115. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/util/conf.py +0 -0
  116. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/util/types.py +0 -0
  117. {slidge-0.2.0a1 → slidge-0.2.0a3}/slidge/util/util.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slidge
3
- Version: 0.2.0a1
3
+ Version: 0.2.0a3
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
  [tool.poetry]
2
2
  name = "slidge"
3
- version = "0.2.0alpha1"
3
+ version = "0.2.0alpha3"
4
4
  description = "XMPP bridging framework"
5
5
  authors = ["Nicolas Cedilnik <nicoco@nicoco.fr>"]
6
6
  readme = "README.md"
@@ -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.0alpha1"
5
+ __version__ = "0.2.0alpha3"
@@ -3,6 +3,7 @@ import logging
3
3
  import warnings
4
4
  from datetime import date
5
5
  from typing import TYPE_CHECKING, Generic, Iterable, Optional, Self, Union
6
+ from xml.etree import ElementTree as ET
6
7
 
7
8
  from slixmpp import JID, Message, Presence
8
9
  from slixmpp.exceptions import IqError
@@ -120,10 +121,21 @@ class LegacyContact(
120
121
  self.xmpp = session.xmpp
121
122
  self.jid = JID(self.jid_username + "@" + self.xmpp.boundjid.bare)
122
123
  self.jid.resource = self.RESOURCE
123
- self.log = logging.getLogger(f"{self.user_jid.bare}:{self.jid.bare}")
124
+ self.log = logging.getLogger(self.jid.bare)
125
+ self._set_logger_name()
124
126
  self._is_friend: bool = False
125
127
  self._added_to_roster = False
126
128
  self._caps_ver: str | None = None
129
+ self._vcard_fetched = False
130
+ self._vcard: str | None = None
131
+
132
+ async def get_vcard(self) -> VCard4 | None:
133
+ if not self._vcard_fetched:
134
+ await self.fetch_vcard()
135
+ if self._vcard is None:
136
+ return None
137
+
138
+ return VCard4(xml=ET.fromstring(self._vcard))
127
139
 
128
140
  @property
129
141
  def is_friend(self):
@@ -136,6 +148,7 @@ class LegacyContact(
136
148
  self._is_friend = value
137
149
  if self._updating_info:
138
150
  return
151
+ self.__ensure_pk()
139
152
  assert self.contact_pk is not None
140
153
  self.xmpp.store.contacts.set_friend(self.contact_pk, value)
141
154
 
@@ -157,7 +170,10 @@ class LegacyContact(
157
170
 
158
171
  @property
159
172
  def participants(self) -> list["LegacyParticipant"]:
160
- assert self.contact_pk is not None
173
+ if self.contact_pk is None:
174
+ return []
175
+
176
+ self.__ensure_pk()
161
177
  from ..group.participant import LegacyParticipant
162
178
 
163
179
  return [
@@ -171,8 +187,25 @@ class LegacyContact(
171
187
  def user_jid(self):
172
188
  return self.session.user_jid
173
189
 
190
+ def _set_logger_name(self):
191
+ self.log.name = f"{self.user_jid.bare}:contact:{self}"
192
+
174
193
  def __repr__(self):
175
- return f"<Contact {self.jid.bare} - {self.name or self.legacy_id}'>"
194
+ return f"<Contact #{self.contact_pk} '{self.name}' ({self.legacy_id} - {self.jid.local})'>"
195
+
196
+ def __ensure_pk(self):
197
+ if self.contact_pk is not None:
198
+ return
199
+ with self.xmpp.store.session() as orm:
200
+ orm.commit()
201
+ stored = self.xmpp.store.contacts.get_by_legacy_id(
202
+ self.user_pk, str(self.legacy_id)
203
+ )
204
+ if stored is None:
205
+ self.log.error("Cannot find our primary key!", stack_info=True)
206
+ raise RuntimeError("Cannot find our primary key!")
207
+ self.contact_pk = stored.id
208
+ assert self.contact_pk is not None
176
209
 
177
210
  def __get_subscription_string(self):
178
211
  if self.is_friend:
@@ -234,6 +267,7 @@ class LegacyContact(
234
267
  and self.xmpp.MARK_ALL_MESSAGES
235
268
  and is_markable(stanza)
236
269
  ):
270
+ self.__ensure_pk()
237
271
  assert self.contact_pk is not None
238
272
  self.xmpp.store.contacts.add_to_sent(self.contact_pk, stanza["id"])
239
273
  stanza["to"] = self.user_jid
@@ -254,6 +288,7 @@ class LegacyContact(
254
288
  :param horizon_xmpp_id: The latest message
255
289
  :return: A list of XMPP ids or None if horizon_xmpp_id was not found
256
290
  """
291
+ self.__ensure_pk()
257
292
  assert self.contact_pk is not None
258
293
  return self.xmpp.store.contacts.pop_sent_up_to(self.contact_pk, horizon_xmpp_id)
259
294
 
@@ -269,6 +304,7 @@ class LegacyContact(
269
304
  if self._name == n:
270
305
  return
271
306
  self._name = n
307
+ self._set_logger_name()
272
308
  if self.is_friend and self.added_to_roster:
273
309
  self.xmpp.pubsub.broadcast_nick(
274
310
  user_jid=self.user_jid, jid=self.jid.bare, nick=n
@@ -279,6 +315,7 @@ class LegacyContact(
279
315
  return
280
316
  for p in self.participants:
281
317
  p.nickname = n
318
+ self.__ensure_pk()
282
319
  assert self.contact_pk is not None
283
320
  self.xmpp.store.contacts.update_nick(self.contact_pk, n)
284
321
 
@@ -293,6 +330,7 @@ class LegacyContact(
293
330
  if self.contact_pk is None:
294
331
  # happens in LegacyRoster.fill(), the contact primary key is not
295
332
  # set yet, but this will eventually be called in LegacyRoster.__finish_init_contact
333
+ self.log.debug("Not setting avatar PK")
296
334
  return
297
335
  self.xmpp.store.contacts.set_avatar(self.contact_pk, self._avatar_pk)
298
336
  for p in self.participants:
@@ -346,7 +384,17 @@ class LegacyContact(
346
384
  elif country:
347
385
  vcard.add_address(country, locality)
348
386
 
349
- self.xmpp.vcard.set_vcard(self.jid.bare, vcard, {self.user_jid.bare})
387
+ self._vcard = str(vcard)
388
+ self._vcard_fetched = True
389
+ self.session.create_task(
390
+ self.xmpp.pubsub.broadcast_vcard_event(self.jid, self.user_jid, vcard)
391
+ )
392
+
393
+ if self._updating_info:
394
+ return
395
+
396
+ assert self.contact_pk is not None
397
+ self.xmpp.store.contacts.set_vcard(self.contact_pk, self._vcard)
350
398
 
351
399
  def get_roster_item(self):
352
400
  item = {
@@ -430,7 +478,7 @@ class LegacyContact(
430
478
  """
431
479
  self.is_friend = True
432
480
  self.added_to_roster = True
433
- assert self.contact_pk is not None
481
+ self.__ensure_pk()
434
482
  self.log.debug("Accepting friend request")
435
483
  presence = self._make_presence(ptype="subscribed", pstatus=text, bare=True)
436
484
  self._send(presence, nick=True)
@@ -554,8 +602,11 @@ class LegacyContact(
554
602
  contact.added_to_roster = stored.added_to_roster
555
603
  if (data := stored.extra_attributes) is not None:
556
604
  contact.deserialize_extra_attributes(data)
557
- contact._set_avatar_from_store(stored)
558
605
  contact._caps_ver = stored.caps_ver
606
+ contact._set_logger_name()
607
+ contact._set_avatar_from_store(stored)
608
+ contact._vcard = stored.vcard
609
+ contact._vcard_fetched = stored.vcard_fetched
559
610
  return contact
560
611
 
561
612
 
@@ -36,7 +36,6 @@ from ...command.register import RegistrationType
36
36
  from ...db import GatewayUser, SlidgeStore
37
37
  from ...db.avatar import avatar_cache
38
38
  from ...slixfix.roster import RosterBackend
39
- from ...slixfix.xep_0292.vcard4 import VCard4Provider
40
39
  from ...util import ABCSubclassableOnceAtMost
41
40
  from ...util.types import AvatarType, MessageOrPresenceTypeVar
42
41
  from ...util.util import timeit
@@ -331,7 +330,6 @@ class BaseGateway(
331
330
 
332
331
  self.register_plugin("pubsub", {"component_name": self.COMPONENT_NAME})
333
332
  self.pubsub: PubSubComponent = self["pubsub"]
334
- self.vcard: VCard4Provider = self["xep_0292_provider"]
335
333
  self.delivery_receipt: DeliveryReceipt = DeliveryReceipt(self)
336
334
 
337
335
  # with this we receive user avatar updates
@@ -68,6 +68,13 @@ class SessionDispatcher:
68
68
  _exceptions_to_xmpp_errors(self.on_ibr_remove), # type: ignore
69
69
  )
70
70
  )
71
+ self.xmpp.register_handler(
72
+ CoroutineCallback(
73
+ "get_vcard",
74
+ StanzaPath("iq@type=get/vcard"),
75
+ _exceptions_to_xmpp_errors(self.on_get_vcard), # type:ignore
76
+ )
77
+ )
71
78
 
72
79
  for event in (
73
80
  "legacy_message",
@@ -778,6 +785,18 @@ class SessionDispatcher:
778
785
 
779
786
  raise XMPPError("feature-not-implemented")
780
787
 
788
+ async def on_get_vcard(self, iq: Iq):
789
+ session = await self.__get_session(iq)
790
+ session.raise_if_not_logged()
791
+ contact = await session.contacts.by_jid(iq.get_to())
792
+ vcard = await contact.get_vcard()
793
+ reply = iq.reply()
794
+ if vcard:
795
+ reply.append(vcard)
796
+ else:
797
+ reply.enable("vcard")
798
+ reply.send()
799
+
781
800
  def _xmpp_msg_id_to_legacy(self, session: "BaseSession", xmpp_id: str):
782
801
  sent = self.xmpp.store.sent.get_legacy_id(session.user_pk, xmpp_id)
783
802
  if sent is not None:
@@ -79,14 +79,14 @@ class VCardTemp:
79
79
  elif not (contact := entity.contact):
80
80
  raise XMPPError("item-not-found", "This participant has no contact")
81
81
  else:
82
- vcard = await self.xmpp.vcard.get_vcard(contact.jid, iq.get_from())
82
+ vcard = await contact.get_vcard()
83
83
  avatar = contact.get_avatar()
84
84
  type_ = "image/png"
85
85
  else:
86
86
  avatar = entity.get_avatar()
87
87
  type_ = "image/png"
88
88
  if isinstance(entity, LegacyContact):
89
- vcard = await self.xmpp.vcard.get_vcard(entity.jid, iq.get_from())
89
+ vcard = await entity.get_vcard()
90
90
  else:
91
91
  vcard = None
92
92
  v = self.xmpp.plugin["xep_0054"].make_vcard()
@@ -135,6 +135,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
135
135
 
136
136
  to = p.get_to()
137
137
 
138
+ contact = None
138
139
  # we don't want to push anything for contacts that are not in the user's roster
139
140
  if to != self.xmpp.boundjid.bare:
140
141
  session = self.xmpp.get_session_from_stanza(p)
@@ -186,17 +187,19 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
186
187
  else:
187
188
  await self.__broadcast(data=pep_nick.nick, from_=p.get_to(), to=from_)
188
189
 
189
- if VCARD4_NAMESPACE + "+notify" in features:
190
- await self.broadcast_vcard_event(p.get_to(), to=from_)
190
+ if contact is not None and VCARD4_NAMESPACE + "+notify" in features:
191
+ await self.broadcast_vcard_event(
192
+ p.get_to(), from_, await contact.get_vcard()
193
+ )
191
194
 
192
- async def broadcast_vcard_event(self, from_, to):
195
+ async def broadcast_vcard_event(self, from_: JID, to: JID, vcard: VCard4 | None):
193
196
  item = Item()
194
197
  item.namespace = VCARD4_NAMESPACE
195
198
  item["id"] = "current"
196
- vcard: VCard4 = await self.xmpp["xep_0292_provider"].get_vcard(from_, to)
199
+ # vcard: VCard4 = await self.xmpp["xep_0292_provider"].get_vcard(from_, to)
197
200
  # The vcard content should NOT be in this event according to the spec:
198
201
  # https://xmpp.org/extensions/xep-0292.html#sect-idm45669698174224
199
- # but movim expects it to be here, and I guess
202
+ # but movim expects it to be here, and I guess it does not hurt
200
203
 
201
204
  log.debug("Broadcast vcard4 event: %s", vcard)
202
205
  await self.__broadcast(
@@ -268,10 +271,9 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
268
271
  # this is not the proper way that clients should retrieve VCards, but
269
272
  # gajim does it this way.
270
273
  # https://xmpp.org/extensions/xep-0292.html#sect-idm45669698174224
271
- vcard: VCard4 = await self.xmpp["xep_0292_provider"].get_vcard(
272
- iq.get_to().bare, iq.get_from().bare
273
- )
274
- log.debug("VCARD: %s -- %s -- %s", iq.get_to().bare, iq.get_from().bare, vcard)
274
+ session = self.xmpp.get_session_from_stanza(iq)
275
+ contact = await session.contacts.by_jid(iq.get_to())
276
+ vcard = await contact.get_vcard()
275
277
  if vcard is None:
276
278
  raise XMPPError("item-not-found")
277
279
  self._reply_with_payload(iq, vcard, "current", VCARD4_NAMESPACE)
@@ -0,0 +1,44 @@
1
+ """Lift room legacy ID constraint
2
+
3
+ Revision ID: 5bd48bfdffa2
4
+ Revises: b64b1a793483
5
+ Create Date: 2024-07-24 10:29:23.467851
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ from alembic import op
12
+
13
+ from slidge.db.models import Room
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "5bd48bfdffa2"
17
+ down_revision: Union[str, None] = "b64b1a793483"
18
+ branch_labels: Union[str, Sequence[str], None] = None
19
+ depends_on: Union[str, Sequence[str], None] = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ with op.batch_alter_table(
24
+ "room",
25
+ schema=None,
26
+ # without copy_from, the newly created table keeps the constraints
27
+ # we actually want to ditch.
28
+ copy_from=Room.__table__, # type:ignore
29
+ ) as batch_op:
30
+ batch_op.create_unique_constraint(
31
+ "uq_room_user_account_id_jid", ["user_account_id", "jid"]
32
+ )
33
+ batch_op.create_unique_constraint(
34
+ "uq_room_user_account_id_legacy_id", ["user_account_id", "legacy_id"]
35
+ )
36
+
37
+
38
+ def downgrade() -> None:
39
+ # ### commands auto generated by Alembic - please adjust! ###
40
+ with op.batch_alter_table("room", schema=None) as batch_op:
41
+ batch_op.drop_constraint("uq_room_user_account_id_legacy_id", type_="unique")
42
+ batch_op.drop_constraint("uq_room_user_account_id_jid", type_="unique")
43
+
44
+ # ### end Alembic commands ###
@@ -0,0 +1,43 @@
1
+ """Add vcard content to contact table
2
+
3
+ Revision ID: 8b993243a536
4
+ Revises: 5bd48bfdffa2
5
+ Create Date: 2024-07-24 07:02:47.770894
6
+
7
+ """
8
+
9
+ from typing import Sequence, Union
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "8b993243a536"
16
+ down_revision: Union[str, None] = "5bd48bfdffa2"
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ with op.batch_alter_table("contact", schema=None) as batch_op:
24
+ batch_op.add_column(sa.Column("vcard", sa.String(), nullable=True))
25
+ batch_op.add_column(
26
+ sa.Column(
27
+ "vcard_fetched",
28
+ sa.Boolean(),
29
+ nullable=False,
30
+ server_default=sa.sql.true(),
31
+ )
32
+ )
33
+
34
+ # ### end Alembic commands ###
35
+
36
+
37
+ def downgrade() -> None:
38
+ # ### commands auto generated by Alembic - please adjust! ###
39
+ with op.batch_alter_table("contact", schema=None) as batch_op:
40
+ batch_op.drop_column("vcard_fetched")
41
+ batch_op.drop_column("vcard")
42
+
43
+ # ### end Alembic commands ###
@@ -58,10 +58,19 @@ def downgrade() -> None:
58
58
 
59
59
 
60
60
  def migrate_from_shelf(accounts: sa.Table) -> None:
61
+ from slidge import global_config
62
+
63
+ db_file = global_config.HOME_DIR / "slidge.db"
64
+ if not db_file.exists():
65
+ return
66
+
61
67
  try:
62
- from slidge.util.db import user_store
68
+ from slidge.db.alembic.old_user_store import user_store
63
69
  except ImportError:
64
70
  return
71
+
72
+ user_store.set_file(db_file, global_config.SECRET_KEY)
73
+
65
74
  try:
66
75
  users = list(user_store.get_all())
67
76
  except AttributeError:
@@ -83,3 +92,6 @@ def migrate_from_shelf(accounts: sa.Table) -> None:
83
92
  for user in users
84
93
  ],
85
94
  )
95
+
96
+ user_store.close()
97
+ db_file.unlink()
@@ -58,6 +58,13 @@ JSONSerializable = dict[str, JSONSerializableTypes]
58
58
 
59
59
  class Base(sa.orm.DeclarativeBase):
60
60
  type_annotation_map = {JSONSerializable: JSONEncodedDict, JID: JIDType}
61
+ naming_convention = {
62
+ "ix": "ix_%(column_0_label)s",
63
+ "uq": "uq_%(table_name)s_%(column_0_name)s",
64
+ "ck": "ck_%(table_name)s_`%(constraint_name)s`",
65
+ "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
66
+ "pk": "pk_%(table_name)s",
67
+ }
61
68
 
62
69
 
63
70
  def get_engine(path: str) -> sa.Engine:
@@ -166,6 +166,9 @@ class Contact(Base):
166
166
  )
167
167
  updated: Mapped[bool] = mapped_column(default=False)
168
168
 
169
+ vcard: Mapped[Optional[str]] = mapped_column()
170
+ vcard_fetched: Mapped[bool] = mapped_column(default=False)
171
+
169
172
  participants: Mapped[list["Participant"]] = relationship(back_populates="contact")
170
173
 
171
174
 
@@ -191,13 +194,20 @@ class Room(Base):
191
194
  Legacy room
192
195
  """
193
196
 
197
+ __table_args__ = (
198
+ UniqueConstraint(
199
+ "user_account_id", "legacy_id", name="uq_room_user_account_id_legacy_id"
200
+ ),
201
+ UniqueConstraint("user_account_id", "jid", name="uq_room_user_account_id_jid"),
202
+ )
203
+
194
204
  __tablename__ = "room"
195
205
  id: Mapped[int] = mapped_column(primary_key=True)
196
206
  user_account_id: Mapped[int] = mapped_column(ForeignKey("user_account.id"))
197
207
  user: Mapped[GatewayUser] = relationship(back_populates="rooms")
198
- legacy_id: Mapped[str] = mapped_column(unique=True, nullable=False)
208
+ legacy_id: Mapped[str] = mapped_column(nullable=False)
199
209
 
200
- jid: Mapped[JID] = mapped_column(unique=True)
210
+ jid: Mapped[JID] = mapped_column(nullable=False)
201
211
 
202
212
  avatar_id: Mapped[int] = mapped_column(ForeignKey("avatar.id"), nullable=True)
203
213
  avatar: Mapped[Avatar] = relationship(back_populates="rooms")
@@ -32,6 +32,7 @@ from .models import (
32
32
  XmppIdsMulti,
33
33
  XmppToLegacyEnum,
34
34
  XmppToLegacyIds,
35
+ participant_hats,
35
36
  )
36
37
 
37
38
  if TYPE_CHECKING:
@@ -379,11 +380,22 @@ class ContactStore(UpdatedMixin):
379
380
  row.updated = True
380
381
  row.extra_attributes = contact.serialize_extra_attributes()
381
382
  row.caps_ver = contact._caps_ver
383
+ row.vcard = contact._vcard
384
+ row.vcard_fetched = contact._vcard_fetched
382
385
  session.add(row)
383
386
  if commit:
384
387
  session.commit()
385
388
  return row.id
386
389
 
390
+ def set_vcard(self, contact_pk: int, vcard: str | None) -> None:
391
+ with self.session() as session:
392
+ session.execute(
393
+ update(Contact)
394
+ .where(Contact.id == contact_pk)
395
+ .values(vcard=vcard, vcard_fetched=True)
396
+ )
397
+ session.commit()
398
+
387
399
  def add_to_sent(self, contact_pk: int, msg_id: str) -> None:
388
400
  with self.session() as session:
389
401
  new = ContactSent(contact_id=contact_pk, msg_id=msg_id)
@@ -659,7 +671,7 @@ class MultiStore(EngineMixin):
659
671
  if existing is not None:
660
672
  if fail:
661
673
  raise
662
- log.warning("Resetting multi for %s", legacy_msg_id)
674
+ log.debug("Resetting multi for %s", legacy_msg_id)
663
675
  session.execute(
664
676
  delete(LegacyIdsMulti)
665
677
  .where(LegacyIdsMulti.user_account_id == user_pk)
@@ -935,6 +947,7 @@ class ParticipantStore(EngineMixin):
935
947
  with self.session() as session:
936
948
  session.execute(delete(Participant))
937
949
  session.execute(delete(Hat))
950
+ session.execute(delete(participant_hats))
938
951
  session.commit()
939
952
 
940
953
  def add(self, room_pk: int, nickname: str) -> int:
@@ -131,7 +131,6 @@ class LegacyMUC(
131
131
  def __init__(self, session: "BaseSession", legacy_id: LegacyGroupIdType, jid: JID):
132
132
  self.session = session
133
133
  self.xmpp: "BaseGateway" = session.xmpp
134
- self.log = logging.getLogger(f"{self.user_jid.bare}:muc:{jid}")
135
134
 
136
135
  self.legacy_id = legacy_id
137
136
  self.jid = jid
@@ -156,6 +155,8 @@ class LegacyMUC(
156
155
 
157
156
  self._n_participants: Optional[int] = None
158
157
 
158
+ self.log = logging.getLogger(self.jid.bare)
159
+ self._set_logger_name()
159
160
  super().__init__()
160
161
 
161
162
  @property
@@ -176,8 +177,11 @@ class LegacyMUC(
176
177
  def user_jid(self):
177
178
  return self.session.user_jid
178
179
 
180
+ def _set_logger_name(self):
181
+ self.log = logging.getLogger(f"{self.user_jid}:muc:{self}")
182
+
179
183
  def __repr__(self):
180
- return f"<MUC {self.legacy_id}/{self.jid}/{self.name}>"
184
+ return f"<MUC #{self.pk} '{self.name}' ({self.legacy_id} - {self.jid.local})'>"
181
185
 
182
186
  @property
183
187
  def subject_date(self) -> Optional[datetime]:
@@ -287,6 +291,7 @@ class LegacyMUC(
287
291
  if self.DISCO_NAME == n:
288
292
  return
289
293
  self.DISCO_NAME = n
294
+ self._set_logger_name()
290
295
  self.__send_configuration_change((104,))
291
296
  if self._updating_info:
292
297
  return
@@ -1207,6 +1212,7 @@ class LegacyMUC(
1207
1212
  muc._user_resources = set(json.loads(stored.user_resources))
1208
1213
  muc._subject_setter = stored.subject_setter
1209
1214
  muc.archive = MessageArchive(muc.pk, session.xmpp.store.mam)
1215
+ muc._set_logger_name()
1210
1216
  muc._set_avatar_from_store(stored)
1211
1217
  return muc
1212
1218
 
@@ -32,7 +32,6 @@ from slidge.db.avatar import avatar_cache
32
32
  from slidge.db.meta import get_engine
33
33
  from slidge.migration import migrate
34
34
  from slidge.util.conf import ConfigModule
35
- from slidge.util.db import user_store
36
35
 
37
36
 
38
37
  class MainConfig(ConfigModule):
@@ -114,9 +113,6 @@ def configure():
114
113
  logging.info("Creating directory '%s'", h)
115
114
  h.mkdir()
116
115
 
117
- db_file = config.HOME_DIR / "slidge.db"
118
- user_store.set_file(db_file, args.secret_key)
119
-
120
116
  config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare
121
117
 
122
118
  return unknown_argv
File without changes
@@ -1,9 +1,8 @@
1
+ import logging
1
2
  from typing import TYPE_CHECKING
2
3
 
3
4
  from slixmpp import JID
4
5
 
5
- from ..util.db import log
6
-
7
6
  if TYPE_CHECKING:
8
7
  from .. import BaseGateway
9
8
 
@@ -65,3 +64,6 @@ class RosterBackend:
65
64
  "whitelisted": False,
66
65
  "subscription": "none",
67
66
  }
67
+
68
+
69
+ log = logging.getLogger(__name__)
@@ -0,0 +1,14 @@
1
+ from slixmpp.plugins.base import BasePlugin, register_plugin
2
+ from slixmpp.plugins.xep_0292.stanza import NS
3
+
4
+
5
+ class VCard4Provider(BasePlugin):
6
+ name = "xep_0292_provider"
7
+ description = "VCard4 Provider"
8
+ dependencies = {"xep_0030"}
9
+
10
+ def plugin_init(self):
11
+ self.xmpp.plugin["xep_0030"].add_feature(NS)
12
+
13
+
14
+ register_plugin(VCard4Provider)
@@ -287,7 +287,7 @@ class SlidgeTest(SlixTestPlus):
287
287
  stanza = self.next_sent()
288
288
  assert "yup" in stanza["status"].lower(), stanza
289
289
 
290
- self.romeo = BaseSession.get_self_or_unique_subclass().from_jid(
290
+ self.romeo: BaseSession = BaseSession.get_self_or_unique_subclass().from_jid(
291
291
  JID("romeo@montague.lit")
292
292
  )
293
293
  self.juliet: LegacyContact = self.run_coro(
@@ -1,103 +0,0 @@
1
- import logging
2
- from typing import TYPE_CHECKING, NamedTuple, Optional
3
-
4
- from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
5
- from slixmpp.plugins.base import BasePlugin, register_plugin
6
- from slixmpp.plugins.xep_0292.stanza import NS, VCard4
7
- from slixmpp.types import JidStr
8
-
9
- from slidge.contact import LegacyContact
10
-
11
- if TYPE_CHECKING:
12
- from slidge.core.gateway import BaseGateway
13
-
14
-
15
- class StoredVCard(NamedTuple):
16
- content: VCard4
17
- authorized_jids: set[JidStr]
18
-
19
-
20
- class VCard4Provider(BasePlugin):
21
- xmpp: "BaseGateway"
22
-
23
- name = "xep_0292_provider"
24
- description = "VCard4 Provider"
25
- dependencies = {"xep_0030"}
26
-
27
- def __init__(self, *a, **k):
28
- super(VCard4Provider, self).__init__(*a, **k)
29
- # TODO: store that in DB and not in RAM
30
- self._vcards = dict[JidStr, StoredVCard]()
31
-
32
- def plugin_init(self):
33
- self.xmpp.register_handler(
34
- CoroutineCallback(
35
- "get_vcard",
36
- StanzaPath(f"iq@type=get/vcard"),
37
- self.handle_vcard_get, # type:ignore
38
- )
39
- )
40
-
41
- self.xmpp.plugin["xep_0030"].add_feature(NS)
42
-
43
- def _get_cached_vcard(self, jid: JidStr, requested_by: JidStr) -> Optional[VCard4]:
44
- vcard = self._vcards.get(JID(jid).bare)
45
- if vcard:
46
- if auth := vcard.authorized_jids:
47
- if JID(requested_by).bare in auth:
48
- return vcard.content
49
- else:
50
- return vcard.content
51
- return None
52
-
53
- async def get_vcard(self, jid: JidStr, requested_by: JidStr) -> Optional[VCard4]:
54
- if vcard := self._get_cached_vcard(jid, requested_by):
55
- log.debug("Found a cached vcard")
56
- return vcard
57
- if not hasattr(self.xmpp, "get_session_from_jid"):
58
- return None
59
- jid = JID(jid)
60
- if not jid.local:
61
- return None
62
- requested_by = JID(requested_by)
63
- session = self.xmpp.get_session_from_jid(requested_by)
64
- if session is None:
65
- return
66
- entity = await session.get_contact_or_group_or_participant(jid)
67
- if isinstance(entity, LegacyContact):
68
- log.debug("Fetching vcard")
69
- await entity.fetch_vcard()
70
- return self._get_cached_vcard(jid, requested_by)
71
- return None
72
-
73
- async def handle_vcard_get(self, iq: Iq):
74
- r = iq.reply()
75
- if vcard := await self.get_vcard(iq.get_to().bare, iq.get_from().bare):
76
- r.append(vcard)
77
- else:
78
- r.enable("vcard")
79
- r.send()
80
-
81
- def set_vcard(
82
- self,
83
- jid: JidStr,
84
- vcard: VCard4,
85
- /,
86
- authorized_jids: Optional[set[JidStr]] = None,
87
- ):
88
- cache = self._vcards.get(jid)
89
- new = StoredVCard(
90
- vcard, authorized_jids if authorized_jids is not None else set()
91
- )
92
- self._vcards[jid] = new
93
- if cache == new:
94
- return
95
- if self.xmpp["pubsub"] and authorized_jids:
96
- for to in authorized_jids:
97
- self.xmpp.loop.create_task(
98
- self.xmpp["pubsub"].broadcast_vcard_event(jid, to)
99
- )
100
-
101
-
102
- register_plugin(VCard4Provider)
103
- log = logging.getLogger(__name__)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes