slidge 0.2.0a2__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.0a2 → slidge-0.2.0a3}/PKG-INFO +1 -1
  2. {slidge-0.2.0a2 → slidge-0.2.0a3}/pyproject.toml +1 -1
  3. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/__version__.py +1 -1
  4. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/contact/contact.py +24 -1
  5. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/base.py +0 -2
  6. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/session_dispatcher.py +19 -0
  7. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/vcard_temp.py +2 -2
  8. {slidge-0.2.0a2 → 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.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +13 -1
  12. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/meta.py +7 -0
  13. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/models.py +12 -2
  14. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/store.py +12 -1
  15. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/main.py +0 -4
  16. slidge-0.2.0a3/slidge/py.typed +0 -0
  17. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/roster.py +4 -2
  18. slidge-0.2.0a3/slidge/slixfix/xep_0292/vcard4.py +14 -0
  19. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/util/test.py +1 -1
  20. slidge-0.2.0a2/slidge/slixfix/xep_0292/vcard4.py +0 -103
  21. {slidge-0.2.0a2 → slidge-0.2.0a3}/LICENSE +0 -0
  22. {slidge-0.2.0a2 → slidge-0.2.0a3}/README.md +0 -0
  23. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/__init__.py +0 -0
  24. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/__main__.py +0 -0
  25. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/command/__init__.py +0 -0
  26. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/command/adhoc.py +0 -0
  27. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/command/admin.py +0 -0
  28. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/command/base.py +0 -0
  29. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/command/categories.py +0 -0
  30. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/command/chat_command.py +0 -0
  31. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/command/register.py +0 -0
  32. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/command/user.py +0 -0
  33. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/contact/__init__.py +0 -0
  34. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/contact/roster.py +0 -0
  35. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/__init__.py +0 -0
  36. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/config.py +0 -0
  37. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/__init__.py +0 -0
  38. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/caps.py +0 -0
  39. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/delivery_receipt.py +0 -0
  40. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/disco.py +0 -0
  41. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/mam.py +0 -0
  42. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/muc_admin.py +0 -0
  43. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/ping.py +0 -0
  44. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/presence.py +0 -0
  45. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/registration.py +0 -0
  46. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/gateway/search.py +0 -0
  47. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/__init__.py +0 -0
  48. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/attachment.py +0 -0
  49. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/avatar.py +0 -0
  50. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/base.py +0 -0
  51. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/db.py +0 -0
  52. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/disco.py +0 -0
  53. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/lock.py +0 -0
  54. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/message.py +0 -0
  55. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/message_maker.py +0 -0
  56. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/presence.py +0 -0
  57. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/mixins/recipient.py +0 -0
  58. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/core/session.py +0 -0
  59. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/__init__.py +0 -0
  60. /slidge-0.2.0a2/slidge/py.typed → /slidge-0.2.0a3/slidge/db/alembic/__init__.py +0 -0
  61. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/env.py +0 -0
  62. /slidge-0.2.0a2/slidge/util/db.py → /slidge-0.2.0a3/slidge/db/alembic/old_user_store.py +0 -0
  63. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/script.py.mako +0 -0
  64. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -0
  65. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -0
  66. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -0
  67. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -0
  68. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -0
  69. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -0
  70. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -0
  71. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -0
  72. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -0
  73. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -0
  74. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/db/avatar.py +0 -0
  75. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/group/__init__.py +0 -0
  76. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/group/archive.py +0 -0
  77. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/group/bookmarks.py +0 -0
  78. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/group/participant.py +0 -0
  79. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/group/room.py +0 -0
  80. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/migration.py +0 -0
  81. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/__init__.py +0 -0
  82. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/link_preview/__init__.py +0 -0
  83. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/link_preview/link_preview.py +0 -0
  84. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/link_preview/stanza.py +0 -0
  85. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0077/__init__.py +0 -0
  86. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0077/register.py +0 -0
  87. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0077/stanza.py +0 -0
  88. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0100/__init__.py +0 -0
  89. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0100/gateway.py +0 -0
  90. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0100/stanza.py +0 -0
  91. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0153/__init__.py +0 -0
  92. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0153/stanza.py +0 -0
  93. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
  94. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0264/__init__.py +0 -0
  95. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0264/stanza.py +0 -0
  96. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
  97. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0292/__init__.py +0 -0
  98. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0313/__init__.py +0 -0
  99. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0313/mam.py +0 -0
  100. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0313/stanza.py +0 -0
  101. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0317/__init__.py +0 -0
  102. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0317/hats.py +0 -0
  103. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0317/stanza.py +0 -0
  104. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
  105. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
  106. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
  107. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0424/__init__.py +0 -0
  108. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0424/retraction.py +0 -0
  109. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0424/stanza.py +0 -0
  110. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0490/__init__.py +0 -0
  111. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0490/mds.py +0 -0
  112. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/slixfix/xep_0490/stanza.py +0 -0
  113. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/util/__init__.py +0 -0
  114. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/util/archive_msg.py +0 -0
  115. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/util/conf.py +0 -0
  116. {slidge-0.2.0a2 → slidge-0.2.0a3}/slidge/util/types.py +0 -0
  117. {slidge-0.2.0a2 → 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.0a2
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.0alpha2"
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.0alpha2"
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
@@ -125,6 +126,16 @@ class LegacyContact(
125
126
  self._is_friend: bool = False
126
127
  self._added_to_roster = False
127
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))
128
139
 
129
140
  @property
130
141
  def is_friend(self):
@@ -373,7 +384,17 @@ class LegacyContact(
373
384
  elif country:
374
385
  vcard.add_address(country, locality)
375
386
 
376
- 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)
377
398
 
378
399
  def get_roster_item(self):
379
400
  item = {
@@ -584,6 +605,8 @@ class LegacyContact(
584
605
  contact._caps_ver = stored.caps_ver
585
606
  contact._set_logger_name()
586
607
  contact._set_avatar_from_store(stored)
608
+ contact._vcard = stored.vcard
609
+ contact._vcard_fetched = stored.vcard_fetched
587
610
  return contact
588
611
 
589
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")
@@ -380,11 +380,22 @@ class ContactStore(UpdatedMixin):
380
380
  row.updated = True
381
381
  row.extra_attributes = contact.serialize_extra_attributes()
382
382
  row.caps_ver = contact._caps_ver
383
+ row.vcard = contact._vcard
384
+ row.vcard_fetched = contact._vcard_fetched
383
385
  session.add(row)
384
386
  if commit:
385
387
  session.commit()
386
388
  return row.id
387
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
+
388
399
  def add_to_sent(self, contact_pk: int, msg_id: str) -> None:
389
400
  with self.session() as session:
390
401
  new = ContactSent(contact_id=contact_pk, msg_id=msg_id)
@@ -660,7 +671,7 @@ class MultiStore(EngineMixin):
660
671
  if existing is not None:
661
672
  if fail:
662
673
  raise
663
- log.warning("Resetting multi for %s", legacy_msg_id)
674
+ log.debug("Resetting multi for %s", legacy_msg_id)
664
675
  session.execute(
665
676
  delete(LegacyIdsMulti)
666
677
  .where(LegacyIdsMulti.user_account_id == user_pk)
@@ -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
File without changes