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.
- {slidge-0.2.0a2 → slidge-0.2.0a4}/PKG-INFO +1 -1
- {slidge-0.2.0a2 → slidge-0.2.0a4}/pyproject.toml +1 -1
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/__version__.py +1 -1
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/admin.py +7 -1
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/contact/contact.py +24 -1
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/contact/roster.py +41 -51
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/base.py +1 -3
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/session_dispatcher.py +19 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/vcard_temp.py +2 -2
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/pubsub.py +22 -18
- slidge-0.2.0a4/slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +44 -0
- slidge-0.2.0a4/slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +13 -1
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/avatar.py +5 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/meta.py +7 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/models.py +12 -2
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/store.py +37 -7
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/bookmarks.py +34 -47
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/participant.py +15 -7
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/room.py +3 -1
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/main.py +0 -4
- slidge-0.2.0a4/slidge/py.typed +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/roster.py +4 -2
- slidge-0.2.0a4/slidge/slixfix/xep_0292/vcard4.py +14 -0
- slidge-0.2.0a4/slidge/util/db.py +5 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/test.py +1 -1
- slidge-0.2.0a2/slidge/slixfix/xep_0292/vcard4.py +0 -103
- {slidge-0.2.0a2 → slidge-0.2.0a4}/LICENSE +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/README.md +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/__main__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/adhoc.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/base.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/categories.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/chat_command.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/register.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/command/user.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/contact/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/config.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/caps.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/delivery_receipt.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/disco.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/mam.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/muc_admin.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/ping.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/presence.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/registration.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/gateway/search.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/attachment.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/avatar.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/base.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/db.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/disco.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/lock.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/message.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/message_maker.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/presence.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/mixins/recipient.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/core/session.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/__init__.py +0 -0
- /slidge-0.2.0a2/slidge/py.typed → /slidge-0.2.0a4/slidge/db/alembic/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/env.py +0 -0
- /slidge-0.2.0a2/slidge/util/db.py → /slidge-0.2.0a4/slidge/db/alembic/old_user_store.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/script.py.mako +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/group/archive.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/migration.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/link_preview/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/link_preview/link_preview.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/link_preview/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0077/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0077/register.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0077/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0100/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0100/gateway.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0100/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0153/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0153/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0264/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0264/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0292/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0313/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0313/mam.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0313/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0317/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0317/hats.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0317/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0424/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0424/retraction.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0424/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0490/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0490/mds.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/slixfix/xep_0490/stanza.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/__init__.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/archive_msg.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/conf.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/types.py +0 -0
- {slidge-0.2.0a2 → slidge-0.2.0a4}/slidge/util/util.py +0 -0
@@ -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}
|
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.
|
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
|
-
|
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
|
-
|
166
|
-
|
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]
|
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.
|
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
|
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
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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(
|
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(
|
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
|
-
|
272
|
-
|
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.
|
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(
|
208
|
+
legacy_id: Mapped[str] = mapped_column(nullable=False)
|
199
209
|
|
200
|
-
jid: Mapped[JID] = mapped_column(
|
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")
|