slidge 0.2.0a2__tar.gz → 0.2.0a4__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. {slidge-0.2.0a2 → slidge-0.2.0a4}/PKG-INFO +1 -1
  2. {slidge-0.2.0a2 → slidge-0.2.0a4}/pyproject.toml +1 -1
  3. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/__version__.py +1 -1
  4. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/admin.py +7 -1
  5. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/contact/contact.py +24 -1
  6. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/contact/roster.py +41 -51
  7. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/base.py +1 -3
  8. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/session_dispatcher.py +19 -0
  9. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/vcard_temp.py +2 -2
  10. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/pubsub.py +22 -18
  11. slidge-0.2.0a4/slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +44 -0
  12. slidge-0.2.0a4/slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
  13. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +13 -1
  14. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/avatar.py +5 -0
  15. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/meta.py +7 -0
  16. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/models.py +12 -2
  17. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/store.py +37 -7
  18. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/bookmarks.py +34 -47
  19. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/participant.py +15 -7
  20. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/room.py +3 -1
  21. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/main.py +0 -4
  22. slidge-0.2.0a4/slidge/py.typed +0 -0
  23. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/roster.py +4 -2
  24. slidge-0.2.0a4/slidge/slixfix/xep_0292/vcard4.py +14 -0
  25. slidge-0.2.0a4/slidge/util/db.py +5 -0
  26. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/test.py +1 -1
  27. slidge-0.2.0a2/slidge/slixfix/xep_0292/vcard4.py +0 -103
  28. {slidge-0.2.0a2 → slidge-0.2.0a4}/LICENSE +0 -0
  29. {slidge-0.2.0a2 → slidge-0.2.0a4}/README.md +0 -0
  30. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/__init__.py +0 -0
  31. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/__main__.py +0 -0
  32. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/__init__.py +0 -0
  33. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/adhoc.py +0 -0
  34. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/base.py +0 -0
  35. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/categories.py +0 -0
  36. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/chat_command.py +0 -0
  37. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/register.py +0 -0
  38. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/user.py +0 -0
  39. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/contact/__init__.py +0 -0
  40. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/__init__.py +0 -0
  41. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/config.py +0 -0
  42. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/__init__.py +0 -0
  43. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/caps.py +0 -0
  44. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/delivery_receipt.py +0 -0
  45. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/disco.py +0 -0
  46. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/mam.py +0 -0
  47. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/muc_admin.py +0 -0
  48. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/ping.py +0 -0
  49. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/presence.py +0 -0
  50. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/registration.py +0 -0
  51. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/search.py +0 -0
  52. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/__init__.py +0 -0
  53. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/attachment.py +0 -0
  54. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/avatar.py +0 -0
  55. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/base.py +0 -0
  56. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/db.py +0 -0
  57. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/disco.py +0 -0
  58. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/lock.py +0 -0
  59. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/message.py +0 -0
  60. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/message_maker.py +0 -0
  61. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/presence.py +0 -0
  62. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/recipient.py +0 -0
  63. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/session.py +0 -0
  64. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/__init__.py +0 -0
  65. /slidge-0.2.0a2/slidge/py.typed → /slidge-0.2.0a4/slidge/db/alembic/__init__.py +0 -0
  66. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/env.py +0 -0
  67. /slidge-0.2.0a2/slidge/util/db.py → /slidge-0.2.0a4/slidge/db/alembic/old_user_store.py +0 -0
  68. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/script.py.mako +0 -0
  69. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -0
  70. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -0
  71. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -0
  72. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -0
  73. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -0
  74. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -0
  75. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -0
  76. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -0
  77. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -0
  78. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -0
  79. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/__init__.py +0 -0
  80. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/archive.py +0 -0
  81. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/migration.py +0 -0
  82. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/__init__.py +0 -0
  83. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/link_preview/__init__.py +0 -0
  84. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/link_preview/link_preview.py +0 -0
  85. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/link_preview/stanza.py +0 -0
  86. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0077/__init__.py +0 -0
  87. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0077/register.py +0 -0
  88. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0077/stanza.py +0 -0
  89. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0100/__init__.py +0 -0
  90. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0100/gateway.py +0 -0
  91. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0100/stanza.py +0 -0
  92. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0153/__init__.py +0 -0
  93. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0153/stanza.py +0 -0
  94. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
  95. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0264/__init__.py +0 -0
  96. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0264/stanza.py +0 -0
  97. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
  98. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0292/__init__.py +0 -0
  99. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0313/__init__.py +0 -0
  100. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0313/mam.py +0 -0
  101. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0313/stanza.py +0 -0
  102. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0317/__init__.py +0 -0
  103. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0317/hats.py +0 -0
  104. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0317/stanza.py +0 -0
  105. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
  106. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
  107. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
  108. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0424/__init__.py +0 -0
  109. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0424/retraction.py +0 -0
  110. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0424/stanza.py +0 -0
  111. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0490/__init__.py +0 -0
  112. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0490/mds.py +0 -0
  113. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0490/stanza.py +0 -0
  114. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/__init__.py +0 -0
  115. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/archive_msg.py +0 -0
  116. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/conf.py +0 -0
  117. {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/types.py +0 -0
  118. {slidge-0.2.0a2 → slidge-0.2.0a4}/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.0a4
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.0alpha4"
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.0alpha4"
@@ -1,5 +1,6 @@
1
1
  # Commands only accessible for slidge admins
2
2
  import functools
3
+ import importlib
3
4
  import logging
4
5
  from datetime import datetime
5
6
  from typing import Any, Optional
@@ -7,6 +8,7 @@ from typing import Any, Optional
7
8
  from slixmpp import JID
8
9
  from slixmpp.exceptions import XMPPError
9
10
 
11
+ from ..core import config
10
12
  from ..util.types import AnyBaseSession
11
13
  from .base import (
12
14
  Command,
@@ -90,8 +92,12 @@ class SlidgeInfo(AdminCommand):
90
92
  [a for a in (days_ago, hours_ago, minutes_ago, seconds_ago) if a]
91
93
  )
92
94
 
95
+ legacy_module = importlib.import_module(config.LEGACY_MODULE)
96
+ version = getattr(legacy_module, "__version__", "No version")
97
+
93
98
  return (
94
- f"{self.xmpp.COMPONENT_NAME} version {__version__}\n"
99
+ f"{self.xmpp.COMPONENT_NAME} (slidge core {__version__},"
100
+ f" {config.LEGACY_MODULE} {version})\n"
95
101
  f"Up since {start:%Y-%m-%d %H:%M} ({ago} ago)"
96
102
  )
97
103
 
@@ -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, fetch=True) -> VCard4 | None:
133
+ if fetch and 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
 
@@ -8,6 +8,7 @@ from slixmpp.exceptions import IqError, XMPPError
8
8
  from slixmpp.jid import JID_UNESCAPE_TRANSFORMATIONS, _unescape_node
9
9
 
10
10
  from ..core.mixins.lock import NamedLockMixin
11
+ from ..db.models import Contact
11
12
  from ..db.store import ContactStore
12
13
  from ..util import SubclassableOnce
13
14
  from ..util.types import LegacyContactType, LegacyUserIdType
@@ -63,37 +64,6 @@ class LegacyRoster(
63
64
  for stored in self.__store.get_all(user_pk=self.session.user_pk):
64
65
  yield self._contact_cls.from_store(self.session, stored)
65
66
 
66
- async def __finish_init_contact(
67
- self, legacy_id: LegacyUserIdType, jid_username: str, *args, **kwargs
68
- ):
69
- async with self.lock(("finish", legacy_id)):
70
- with self.__store.session():
71
- stored = self.__store.get_by_legacy_id(
72
- self.session.user_pk, str(legacy_id)
73
- )
74
- if stored is not None:
75
- if stored.updated:
76
- return self._contact_cls.from_store(self.session, stored)
77
- c: LegacyContact = self._contact_cls(
78
- self.session, legacy_id, jid_username, *args, **kwargs
79
- )
80
- c.contact_pk = stored.id
81
- else:
82
- c = self._contact_cls(
83
- self.session, legacy_id, jid_username, *args, **kwargs
84
- )
85
- try:
86
- with c.updating_info():
87
- await c.avatar_wrap_update_info()
88
- except Exception as e:
89
- raise XMPPError("internal-server-error", str(e))
90
- c._caps_ver = await c.get_caps_ver(c.jid)
91
- need_avatar = c.contact_pk is None
92
- c.contact_pk = self.__store.update(c, commit=not self.__filling)
93
- if need_avatar:
94
- c._post_avatar_update()
95
- return c
96
-
97
67
  def known_contacts(self, only_friends=True) -> dict[str, LegacyContactType]:
98
68
  if only_friends:
99
69
  return {c.jid.bare: c for c in self if c.is_friend}
@@ -112,17 +82,15 @@ class LegacyRoster(
112
82
  # """
113
83
  username = contact_jid.node
114
84
  async with self.lock(("username", username)):
115
- with self.__store.session():
116
- stored = self.__store.get_by_jid(self.session.user_pk, contact_jid)
117
- if stored is not None and stored.updated:
118
- return self._contact_cls.from_store(self.session, stored)
119
-
120
85
  legacy_id = await self.jid_username_to_legacy_id(username)
121
86
  log.debug("Contact %s not found", contact_jid)
122
87
  if self.get_lock(("legacy_id", legacy_id)):
123
88
  log.debug("Already updating %s", contact_jid)
124
89
  return await self.by_legacy_id(legacy_id)
125
- return await self.__finish_init_contact(legacy_id, username)
90
+
91
+ with self.__store.session():
92
+ stored = self.__store.get_by_jid(self.session.user_pk, contact_jid)
93
+ return await self.__update_contact(stored, legacy_id, username)
126
94
 
127
95
  async def by_legacy_id(
128
96
  self, legacy_id: LegacyUserIdType, *args, **kwargs
@@ -145,26 +113,48 @@ class LegacyRoster(
145
113
  if legacy_id == self.user_legacy_id:
146
114
  raise ContactIsUser
147
115
  async with self.lock(("legacy_id", legacy_id)):
148
- with self.__store.session():
149
- stored = self.__store.get_by_legacy_id(
150
- self.session.user_pk, str(legacy_id)
151
- )
152
- if stored is not None and stored.updated:
153
- return self._contact_cls.from_store(
154
- self.session, stored, *args, **kwargs
155
- )
156
-
157
116
  username = await self.legacy_id_to_jid_username(legacy_id)
158
- log.debug("Contact %s not found", legacy_id)
159
117
  if self.get_lock(("username", username)):
160
118
  log.debug("Already updating %s", username)
161
119
  jid = JID()
162
120
  jid.node = username
163
121
  jid.domain = self.session.xmpp.boundjid.bare
164
122
  return await self.by_jid(jid)
165
- return await self.__finish_init_contact(
166
- legacy_id, username, *args, **kwargs
167
- )
123
+
124
+ with self.__store.session():
125
+ stored = self.__store.get_by_legacy_id(
126
+ self.session.user_pk, str(legacy_id)
127
+ )
128
+ return await self.__update_contact(
129
+ stored, legacy_id, username, *args, **kwargs
130
+ )
131
+
132
+ async def __update_contact(
133
+ self,
134
+ stored: Contact | None,
135
+ legacy_id: LegacyUserIdType,
136
+ username: str,
137
+ *a,
138
+ **kw,
139
+ ) -> LegacyContactType:
140
+ if stored is None:
141
+ contact = self._contact_cls(self.session, legacy_id, username, *a, **kw)
142
+ else:
143
+ contact = self._contact_cls.from_store(self.session, stored, *a, **kw)
144
+ if stored.updated:
145
+ return contact
146
+
147
+ try:
148
+ with contact.updating_info():
149
+ await contact.avatar_wrap_update_info()
150
+ except Exception as e:
151
+ raise XMPPError("internal-server-error", str(e))
152
+ contact._caps_ver = await contact.get_caps_ver(contact.jid)
153
+ need_avatar = contact.contact_pk is None
154
+ contact.contact_pk = self.__store.update(contact, commit=not self.__filling)
155
+ if need_avatar:
156
+ contact._post_avatar_update()
157
+ return contact
168
158
 
169
159
  async def by_stanza(self, s) -> LegacyContact:
170
160
  # """
@@ -225,7 +215,7 @@ class LegacyRoster(
225
215
  item = contact.get_roster_item()
226
216
  old = user_roster.get(contact.jid.bare)
227
217
  if old is not None and all(
228
- old[k] == item[contact.jid.bare][k]
218
+ old[k] == item[contact.jid.bare].get(k)
229
219
  for k in ("subscription", "groups", "name")
230
220
  ):
231
221
  self.log.debug("No need to update roster")
@@ -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
@@ -316,7 +315,7 @@ class BaseGateway(
316
315
 
317
316
  from ...group.room import LegacyMUC
318
317
 
319
- LegacyMUC.get_unique_subclass().xmpp = self
318
+ LegacyMUC.get_self_or_unique_subclass().xmpp = self
320
319
 
321
320
  self.get_session_from_stanza: Callable[
322
321
  [Union[Message, Presence, Iq]], BaseSession
@@ -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(fetch=False)
90
90
  else:
91
91
  vcard = None
92
92
  v = self.xmpp.plugin["xep_0054"].make_vcard()
@@ -22,6 +22,7 @@ from slixmpp.types import JidStr, OptJidStr
22
22
 
23
23
  from ..db.avatar import CachedAvatar, avatar_cache
24
24
  from ..db.store import ContactStore, SlidgeStore
25
+ from ..util.types import URL
25
26
  from .mixins.lock import NamedLockMixin
26
27
 
27
28
  if TYPE_CHECKING:
@@ -135,6 +136,7 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
135
136
 
136
137
  to = p.get_to()
137
138
 
139
+ contact = None
138
140
  # we don't want to push anything for contacts that are not in the user's roster
139
141
  if to != self.xmpp.boundjid.bare:
140
142
  session = self.xmpp.get_session_from_stanza(p)
@@ -170,14 +172,13 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
170
172
  except XMPPError:
171
173
  pass
172
174
  else:
173
- if pep_avatar.metadata is None:
174
- raise XMPPError("internal-server-error", "Avatar but no metadata?")
175
- await self.__broadcast(
176
- data=pep_avatar.metadata,
177
- from_=p.get_to(),
178
- to=from_,
179
- id=pep_avatar.metadata["info"]["id"],
180
- )
175
+ if pep_avatar.metadata is not None:
176
+ await self.__broadcast(
177
+ data=pep_avatar.metadata,
178
+ from_=p.get_to(),
179
+ to=from_,
180
+ id=pep_avatar.metadata["info"]["id"],
181
+ )
181
182
  if UserNick.namespace + "+notify" in features:
182
183
  try:
183
184
  pep_nick = await self._get_authorized_nick(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(
@@ -219,7 +222,9 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
219
222
  item = PepAvatar()
220
223
  avatar_id = entity.avatar_id
221
224
  if avatar_id is not None:
222
- stored = avatar_cache.get(str(avatar_id))
225
+ stored = avatar_cache.get(
226
+ avatar_id if isinstance(avatar_id, URL) else str(avatar_id)
227
+ )
223
228
  assert stored is not None
224
229
  item.set_avatar_from_cache(stored)
225
230
  return item
@@ -268,10 +273,9 @@ class PubSubComponent(NamedLockMixin, BasePlugin):
268
273
  # this is not the proper way that clients should retrieve VCards, but
269
274
  # gajim does it this way.
270
275
  # 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)
276
+ session = self.xmpp.get_session_from_stanza(iq)
277
+ contact = await session.contacts.by_jid(iq.get_to())
278
+ vcard = await contact.get_vcard()
275
279
  if vcard is None:
276
280
  raise XMPPError("item-not-found")
277
281
  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()
@@ -206,6 +206,11 @@ class AvatarCache:
206
206
  stored = orm.execute(select(Avatar).where(Avatar.hash == hash_)).scalar()
207
207
 
208
208
  if stored is not None:
209
+ if unique_id is not None:
210
+ log.warning("Updating 'unique' IDs of a known avatar.")
211
+ stored.legacy_id = str(unique_id)
212
+ orm.add(stored)
213
+ orm.commit()
209
214
  return CachedAvatar.from_store(stored, self.dir)
210
215
 
211
216
  stored = Avatar(
@@ -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")