slidge 0.2.0a2__py3-none-any.whl → 0.2.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidge/__version__.py +1 -1
- slidge/command/admin.py +7 -1
- slidge/contact/contact.py +24 -1
- slidge/contact/roster.py +41 -51
- slidge/core/gateway/base.py +1 -3
- slidge/core/gateway/session_dispatcher.py +19 -0
- slidge/core/gateway/vcard_temp.py +2 -2
- slidge/core/pubsub.py +22 -18
- slidge/db/alembic/__init__.py +0 -0
- slidge/db/alembic/old_user_store.py +183 -0
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +44 -0
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +13 -1
- slidge/db/avatar.py +5 -0
- slidge/db/meta.py +7 -0
- slidge/db/models.py +12 -2
- slidge/db/store.py +37 -7
- slidge/group/bookmarks.py +34 -47
- slidge/group/participant.py +15 -7
- slidge/group/room.py +3 -1
- slidge/main.py +0 -4
- slidge/slixfix/roster.py +4 -2
- slidge/slixfix/xep_0292/vcard4.py +1 -90
- slidge/util/db.py +4 -182
- slidge/util/test.py +1 -1
- {slidge-0.2.0a2.dist-info → slidge-0.2.0a4.dist-info}/METADATA +1 -1
- {slidge-0.2.0a2.dist-info → slidge-0.2.0a4.dist-info}/RECORD +30 -26
- {slidge-0.2.0a2.dist-info → slidge-0.2.0a4.dist-info}/LICENSE +0 -0
- {slidge-0.2.0a2.dist-info → slidge-0.2.0a4.dist-info}/WHEEL +0 -0
- {slidge-0.2.0a2.dist-info → slidge-0.2.0a4.dist-info}/entry_points.txt +0 -0
@@ -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()
|
slidge/db/avatar.py
CHANGED
@@ -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(
|
slidge/db/meta.py
CHANGED
@@ -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:
|
slidge/db/models.py
CHANGED
@@ -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")
|
slidge/db/store.py
CHANGED
@@ -162,12 +162,20 @@ class AvatarStore(EngineMixin):
|
|
162
162
|
class SentStore(EngineMixin):
|
163
163
|
def set_message(self, user_pk: int, legacy_id: str, xmpp_id: str) -> None:
|
164
164
|
with self.session() as session:
|
165
|
-
msg =
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
165
|
+
msg = (
|
166
|
+
session.query(XmppToLegacyIds)
|
167
|
+
.filter(XmppToLegacyIds.user_account_id == user_pk)
|
168
|
+
.filter(XmppToLegacyIds.legacy_id == legacy_id)
|
169
|
+
.filter(XmppToLegacyIds.xmpp_id == xmpp_id)
|
170
|
+
.scalar()
|
170
171
|
)
|
172
|
+
if msg is None:
|
173
|
+
msg = XmppToLegacyIds(user_account_id=user_pk)
|
174
|
+
else:
|
175
|
+
log.debug("Resetting a DM from sent store")
|
176
|
+
msg.legacy_id = legacy_id
|
177
|
+
msg.xmpp_id = xmpp_id
|
178
|
+
msg.type = XmppToLegacyEnum.DM
|
171
179
|
session.add(msg)
|
172
180
|
session.commit()
|
173
181
|
|
@@ -380,11 +388,22 @@ class ContactStore(UpdatedMixin):
|
|
380
388
|
row.updated = True
|
381
389
|
row.extra_attributes = contact.serialize_extra_attributes()
|
382
390
|
row.caps_ver = contact._caps_ver
|
391
|
+
row.vcard = contact._vcard
|
392
|
+
row.vcard_fetched = contact._vcard_fetched
|
383
393
|
session.add(row)
|
384
394
|
if commit:
|
385
395
|
session.commit()
|
386
396
|
return row.id
|
387
397
|
|
398
|
+
def set_vcard(self, contact_pk: int, vcard: str | None) -> None:
|
399
|
+
with self.session() as session:
|
400
|
+
session.execute(
|
401
|
+
update(Contact)
|
402
|
+
.where(Contact.id == contact_pk)
|
403
|
+
.values(vcard=vcard, vcard_fetched=True)
|
404
|
+
)
|
405
|
+
session.commit()
|
406
|
+
|
388
407
|
def add_to_sent(self, contact_pk: int, msg_id: str) -> None:
|
389
408
|
with self.session() as session:
|
390
409
|
new = ContactSent(contact_id=contact_pk, msg_id=msg_id)
|
@@ -614,6 +633,7 @@ class MAMStore(EngineMixin):
|
|
614
633
|
with self.session() as session:
|
615
634
|
after_timestamp = (
|
616
635
|
session.query(ArchivedMessage.timestamp)
|
636
|
+
.filter(ArchivedMessage.room_id == room_pk)
|
617
637
|
.filter(ArchivedMessage.legacy_id == after_id)
|
618
638
|
.scalar()
|
619
639
|
)
|
@@ -660,7 +680,7 @@ class MultiStore(EngineMixin):
|
|
660
680
|
if existing is not None:
|
661
681
|
if fail:
|
662
682
|
raise
|
663
|
-
log.
|
683
|
+
log.debug("Resetting multi for %s", legacy_msg_id)
|
664
684
|
session.execute(
|
665
685
|
delete(LegacyIdsMulti)
|
666
686
|
.where(LegacyIdsMulti.user_account_id == user_pk)
|
@@ -767,7 +787,10 @@ class RoomStore(UpdatedMixin):
|
|
767
787
|
with self.session() as session:
|
768
788
|
session.execute(
|
769
789
|
update(Room).values(
|
770
|
-
subject_setter=None,
|
790
|
+
subject_setter=None,
|
791
|
+
user_resources=None,
|
792
|
+
history_filled=False,
|
793
|
+
participants_filled=False,
|
771
794
|
)
|
772
795
|
)
|
773
796
|
session.commit()
|
@@ -879,6 +902,13 @@ class RoomStore(UpdatedMixin):
|
|
879
902
|
)
|
880
903
|
session.commit()
|
881
904
|
|
905
|
+
def update_user_nick(self, room_pk, nick: str) -> None:
|
906
|
+
with self.session() as session:
|
907
|
+
session.execute(
|
908
|
+
update(Room).where(Room.id == room_pk).values(user_nick=nick)
|
909
|
+
)
|
910
|
+
session.commit()
|
911
|
+
|
882
912
|
def delete(self, room_pk: int) -> None:
|
883
913
|
with self.session() as session:
|
884
914
|
session.execute(delete(Room).where(Room.id == room_pk))
|
slidge/group/bookmarks.py
CHANGED
@@ -8,6 +8,7 @@ from slixmpp.jid import _unescape_node
|
|
8
8
|
|
9
9
|
from ..contact.roster import ESCAPE_TABLE
|
10
10
|
from ..core.mixins.lock import NamedLockMixin
|
11
|
+
from ..db.models import Room
|
11
12
|
from ..util import SubclassableOnce
|
12
13
|
from ..util.types import LegacyGroupIdType, LegacyMUCType
|
13
14
|
from .archive import MessageArchive
|
@@ -57,29 +58,6 @@ class LegacyBookmarks(
|
|
57
58
|
def __repr__(self):
|
58
59
|
return f"<Bookmarks of {self.user_jid}>"
|
59
60
|
|
60
|
-
async def __finish_init_muc(self, legacy_id: LegacyGroupIdType, jid: JID):
|
61
|
-
with self.__store.session():
|
62
|
-
stored = self.__store.get_by_legacy_id(self.session.user_pk, str(legacy_id))
|
63
|
-
if stored is not None:
|
64
|
-
if stored.updated:
|
65
|
-
return self._muc_class.from_store(self.session, stored)
|
66
|
-
muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
|
67
|
-
muc.pk = stored.id
|
68
|
-
else:
|
69
|
-
muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
|
70
|
-
|
71
|
-
try:
|
72
|
-
with muc.updating_info():
|
73
|
-
await muc.avatar_wrap_update_info()
|
74
|
-
except Exception as e:
|
75
|
-
raise XMPPError("internal-server-error", str(e))
|
76
|
-
if not muc.user_nick:
|
77
|
-
muc.user_nick = self._user_nick
|
78
|
-
self.log.debug("MUC created: %r", muc)
|
79
|
-
muc.pk = self.__store.update(muc)
|
80
|
-
muc.archive = MessageArchive(muc.pk, self.xmpp.store.mam)
|
81
|
-
return muc
|
82
|
-
|
83
61
|
async def legacy_id_to_jid_local_part(self, legacy_id: LegacyGroupIdType):
|
84
62
|
return await self.legacy_id_to_jid_username(legacy_id)
|
85
63
|
|
@@ -110,24 +88,16 @@ class LegacyBookmarks(
|
|
110
88
|
async def by_jid(self, jid: JID) -> LegacyMUCType:
|
111
89
|
if jid.resource:
|
112
90
|
jid = JID(jid.bare)
|
113
|
-
bare
|
114
|
-
|
115
|
-
|
116
|
-
legacy_id = await self.jid_local_part_to_legacy_id(jid.username)
|
91
|
+
async with self.lock(("bare", jid.bare)):
|
92
|
+
assert isinstance(jid.local, str)
|
93
|
+
legacy_id = await self.jid_local_part_to_legacy_id(jid.local)
|
117
94
|
if self.get_lock(("legacy_id", legacy_id)):
|
118
95
|
self.log.debug("Not instantiating %s after all", jid)
|
119
96
|
return await self.by_legacy_id(legacy_id)
|
120
97
|
|
121
98
|
with self.__store.session():
|
122
99
|
stored = self.__store.get_by_jid(self.session.user_pk, jid)
|
123
|
-
|
124
|
-
return self._muc_class.from_store(self.session, stored)
|
125
|
-
|
126
|
-
self.log.debug("Attempting to instantiate a new MUC for JID %s", jid)
|
127
|
-
local_part = jid.node
|
128
|
-
|
129
|
-
self.log.debug("%r is group %r", local_part, legacy_id)
|
130
|
-
return await self.__finish_init_muc(legacy_id, JID(bare))
|
100
|
+
return await self.__update_muc(stored, legacy_id, jid)
|
131
101
|
|
132
102
|
def by_jid_only_if_exists(self, jid: JID) -> Optional[LegacyMUCType]:
|
133
103
|
with self.__store.session():
|
@@ -138,22 +108,39 @@ class LegacyBookmarks(
|
|
138
108
|
|
139
109
|
async def by_legacy_id(self, legacy_id: LegacyGroupIdType) -> LegacyMUCType:
|
140
110
|
async with self.lock(("legacy_id", legacy_id)):
|
141
|
-
with self.__store.session():
|
142
|
-
stored = self.__store.get_by_legacy_id(
|
143
|
-
self.session.user_pk, str(legacy_id)
|
144
|
-
)
|
145
|
-
if stored is not None and stored.updated:
|
146
|
-
return self._muc_class.from_store(self.session, stored)
|
147
|
-
self.log.debug("Create new MUC instance for legacy ID %s", legacy_id)
|
148
111
|
local = await self.legacy_id_to_jid_local_part(legacy_id)
|
149
|
-
|
150
|
-
|
151
|
-
if self.get_lock(("bare", bare)):
|
112
|
+
jid = JID(f"{local}@{self.xmpp.boundjid}")
|
113
|
+
if self.get_lock(("bare", jid.bare)):
|
152
114
|
self.log.debug("Not instantiating %s after all", legacy_id)
|
153
115
|
return await self.by_jid(jid)
|
154
|
-
muc = await self.__finish_init_muc(legacy_id, jid)
|
155
116
|
|
156
|
-
|
117
|
+
with self.__store.session():
|
118
|
+
stored = self.__store.get_by_legacy_id(
|
119
|
+
self.session.user_pk, str(legacy_id)
|
120
|
+
)
|
121
|
+
return await self.__update_muc(stored, legacy_id, jid)
|
122
|
+
|
123
|
+
async def __update_muc(
|
124
|
+
self, stored: Room | None, legacy_id: LegacyGroupIdType, jid: JID
|
125
|
+
):
|
126
|
+
if stored is None:
|
127
|
+
muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
|
128
|
+
else:
|
129
|
+
muc = self._muc_class.from_store(self.session, stored)
|
130
|
+
if stored.updated:
|
131
|
+
return muc
|
132
|
+
|
133
|
+
try:
|
134
|
+
with muc.updating_info():
|
135
|
+
await muc.avatar_wrap_update_info()
|
136
|
+
except Exception as e:
|
137
|
+
raise XMPPError("internal-server-error", str(e))
|
138
|
+
if not muc.user_nick:
|
139
|
+
muc.user_nick = self._user_nick
|
140
|
+
self.log.debug("MUC created: %r", muc)
|
141
|
+
muc.pk = self.__store.update(muc)
|
142
|
+
muc.archive = MessageArchive(muc.pk, self.xmpp.store.mam)
|
143
|
+
return muc
|
157
144
|
|
158
145
|
@abc.abstractmethod
|
159
146
|
async def fill(self):
|
slidge/group/participant.py
CHANGED
@@ -450,13 +450,21 @@ class LegacyParticipant(
|
|
450
450
|
return super().get_disco_info()
|
451
451
|
|
452
452
|
def moderate(self, legacy_msg_id: LegacyMessageType, reason: Optional[str] = None):
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
453
|
+
xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
|
454
|
+
multi = self.xmpp.store.multi.get_xmpp_ids(self.session.user_pk, xmpp_id)
|
455
|
+
if multi is None:
|
456
|
+
msg_ids = [xmpp_id]
|
457
|
+
else:
|
458
|
+
msg_ids = multi + [xmpp_id]
|
459
|
+
|
460
|
+
for i in msg_ids:
|
461
|
+
m = self.muc.get_system_participant()._make_message()
|
462
|
+
m["apply_to"]["id"] = i
|
463
|
+
m["apply_to"]["moderated"].enable("retract")
|
464
|
+
m["apply_to"]["moderated"]["by"] = self.jid
|
465
|
+
if reason:
|
466
|
+
m["apply_to"]["moderated"]["reason"] = reason
|
467
|
+
self._send(m)
|
460
468
|
|
461
469
|
def set_room_subject(
|
462
470
|
self,
|
slidge/group/room.py
CHANGED
@@ -206,6 +206,8 @@ class LegacyMUC(
|
|
206
206
|
@user_nick.setter
|
207
207
|
def user_nick(self, nick: str):
|
208
208
|
self._user_nick = nick
|
209
|
+
if not self._updating_info:
|
210
|
+
self.__store.update_user_nick(self.pk, nick)
|
209
211
|
|
210
212
|
def add_user_resource(self, resource: str) -> None:
|
211
213
|
self._user_resources.add(resource)
|
@@ -1195,7 +1197,7 @@ class LegacyMUC(
|
|
1195
1197
|
)
|
1196
1198
|
muc.pk = stored.id
|
1197
1199
|
muc.type = stored.muc_type # type: ignore
|
1198
|
-
muc.
|
1200
|
+
muc._user_nick = stored.user_nick
|
1199
1201
|
if stored.name:
|
1200
1202
|
muc.DISCO_NAME = stored.name
|
1201
1203
|
if stored.description:
|
slidge/main.py
CHANGED
@@ -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
|
slidge/slixfix/roster.py
CHANGED
@@ -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__)
|
@@ -1,103 +1,14 @@
|
|
1
|
-
import logging
|
2
|
-
from typing import TYPE_CHECKING, NamedTuple, Optional
|
3
|
-
|
4
|
-
from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
|
5
1
|
from slixmpp.plugins.base import BasePlugin, register_plugin
|
6
|
-
from slixmpp.plugins.xep_0292.stanza import NS
|
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]
|
2
|
+
from slixmpp.plugins.xep_0292.stanza import NS
|
18
3
|
|
19
4
|
|
20
5
|
class VCard4Provider(BasePlugin):
|
21
|
-
xmpp: "BaseGateway"
|
22
|
-
|
23
6
|
name = "xep_0292_provider"
|
24
7
|
description = "VCard4 Provider"
|
25
8
|
dependencies = {"xep_0030"}
|
26
9
|
|
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
10
|
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
11
|
self.xmpp.plugin["xep_0030"].add_feature(NS)
|
42
12
|
|
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
13
|
|
102
14
|
register_plugin(VCard4Provider)
|
103
|
-
log = logging.getLogger(__name__)
|
slidge/util/db.py
CHANGED
@@ -1,183 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
"""
|
1
|
+
# here to allow migration of the user store from v0.1
|
2
|
+
# since it relies on shelf, which relies on pickle, we need to keep objects
|
3
|
+
# importable where they were when the shelf was written
|
5
4
|
|
6
|
-
import
|
7
|
-
import datetime
|
8
|
-
import logging
|
9
|
-
import os.path
|
10
|
-
import shelve
|
11
|
-
from io import BytesIO
|
12
|
-
from os import PathLike
|
13
|
-
from typing import Iterable, Optional, Union
|
14
|
-
|
15
|
-
from pickle_secure import Pickler, Unpickler
|
16
|
-
from slixmpp import JID, Iq, Message, Presence
|
17
|
-
|
18
|
-
|
19
|
-
# noinspection PyUnresolvedReferences
|
20
|
-
class EncryptedShelf(shelve.DbfilenameShelf):
|
21
|
-
cache: dict
|
22
|
-
dict: dict
|
23
|
-
writeback: bool
|
24
|
-
keyencoding: str
|
25
|
-
_protocol: int
|
26
|
-
|
27
|
-
def __init__(
|
28
|
-
self, filename: PathLike, key: str, flag="c", protocol=None, writeback=False
|
29
|
-
):
|
30
|
-
super().__init__(str(filename), flag, protocol, writeback)
|
31
|
-
self.secret_key = key
|
32
|
-
|
33
|
-
def __getitem__(self, key):
|
34
|
-
try:
|
35
|
-
value = self.cache[key]
|
36
|
-
except KeyError:
|
37
|
-
f = BytesIO(self.dict[key.encode(self.keyencoding)])
|
38
|
-
value = Unpickler(f, key=self.secret_key).load() # type:ignore
|
39
|
-
if self.writeback:
|
40
|
-
self.cache[key] = value
|
41
|
-
return value
|
42
|
-
|
43
|
-
def __setitem__(self, key, value):
|
44
|
-
if self.writeback:
|
45
|
-
self.cache[key] = value
|
46
|
-
f = BytesIO()
|
47
|
-
p = Pickler(f, self._protocol, key=self.secret_key) # type:ignore
|
48
|
-
p.dump(value)
|
49
|
-
self.dict[key.encode(self.keyencoding)] = f.getvalue()
|
50
|
-
|
51
|
-
|
52
|
-
@dataclasses.dataclass
|
53
|
-
class GatewayUser:
|
54
|
-
"""
|
55
|
-
A gateway user
|
56
|
-
"""
|
57
|
-
|
58
|
-
bare_jid: str
|
59
|
-
"""Bare JID of the user"""
|
60
|
-
registration_form: dict[str, Optional[str]]
|
61
|
-
"""Content of the registration form, as a dict"""
|
62
|
-
plugin_data: Optional[dict] = None
|
63
|
-
registration_date: Optional[datetime.datetime] = None
|
64
|
-
|
65
|
-
def __hash__(self):
|
66
|
-
return hash(self.bare_jid)
|
67
|
-
|
68
|
-
def __repr__(self):
|
69
|
-
return f"<User {self.bare_jid}>"
|
70
|
-
|
71
|
-
def __post_init__(self):
|
72
|
-
if self.registration_date is None:
|
73
|
-
self.registration_date = datetime.datetime.now()
|
74
|
-
|
75
|
-
@property
|
76
|
-
def jid(self) -> JID:
|
77
|
-
"""
|
78
|
-
The user's (bare) JID
|
79
|
-
|
80
|
-
:return:
|
81
|
-
"""
|
82
|
-
return JID(self.bare_jid)
|
83
|
-
|
84
|
-
def get(self, field: str, default: str = "") -> Optional[str]:
|
85
|
-
# """
|
86
|
-
# Get fields from the registration form (required to comply with slixmpp backend protocol)
|
87
|
-
#
|
88
|
-
# :param field: Name of the field
|
89
|
-
# :param default: Default value to return if the field is not present
|
90
|
-
#
|
91
|
-
# :return: Value of the field
|
92
|
-
# """
|
93
|
-
return self.registration_form.get(field, default)
|
94
|
-
|
95
|
-
|
96
|
-
class UserStore:
|
97
|
-
"""
|
98
|
-
Basic user store implementation using shelve from the python standard library
|
99
|
-
|
100
|
-
Set_file must be called before it is usable
|
101
|
-
"""
|
102
|
-
|
103
|
-
def __init__(self):
|
104
|
-
self._users: shelve.Shelf[GatewayUser] = None # type: ignore
|
105
|
-
|
106
|
-
def set_file(self, filename: PathLike, secret_key: Optional[str] = None):
|
107
|
-
"""
|
108
|
-
Set the file to use to store user data
|
109
|
-
|
110
|
-
:param filename: Path to the shelf file
|
111
|
-
:param secret_key: Secret key to store files encrypted on disk
|
112
|
-
"""
|
113
|
-
if self._users is not None:
|
114
|
-
raise RuntimeError("Shelf file already set!")
|
115
|
-
if os.path.exists(filename):
|
116
|
-
log.info("Using existing slidge DB: %s", filename)
|
117
|
-
else:
|
118
|
-
log.info("Creating a new slidge DB: %s", filename)
|
119
|
-
if secret_key:
|
120
|
-
self._users = EncryptedShelf(filename, key=secret_key)
|
121
|
-
else:
|
122
|
-
self._users = shelve.open(str(filename))
|
123
|
-
log.info("Registered users in the DB: %s", list(self._users.keys()))
|
124
|
-
|
125
|
-
def get_all(self) -> Iterable[GatewayUser]:
|
126
|
-
"""
|
127
|
-
Get all users in the store
|
128
|
-
|
129
|
-
:return: An iterable of GatewayUsers
|
130
|
-
"""
|
131
|
-
return self._users.values()
|
132
|
-
|
133
|
-
def commit(self, user: GatewayUser):
|
134
|
-
self._users[user.jid.bare] = user
|
135
|
-
self._users.sync()
|
136
|
-
|
137
|
-
def get(self, _gateway_jid, _node, ifrom: JID, iq) -> Optional[GatewayUser]:
|
138
|
-
"""
|
139
|
-
Get a user from the store
|
140
|
-
|
141
|
-
NB: there is no reason to call this, it is used by SliXMPP internal API
|
142
|
-
|
143
|
-
:param _gateway_jid:
|
144
|
-
:param _node:
|
145
|
-
:param ifrom:
|
146
|
-
:param iq:
|
147
|
-
:return:
|
148
|
-
"""
|
149
|
-
if ifrom is None: # bug in SliXMPP's XEP_0100 plugin
|
150
|
-
ifrom = iq["from"]
|
151
|
-
log.debug("Getting user %s", ifrom.bare)
|
152
|
-
return self._users.get(ifrom.bare)
|
153
|
-
|
154
|
-
def get_by_jid(self, jid: JID) -> Optional[GatewayUser]:
|
155
|
-
"""
|
156
|
-
Convenience function to get a user from their JID.
|
157
|
-
|
158
|
-
:param jid: JID of the gateway user
|
159
|
-
:return:
|
160
|
-
"""
|
161
|
-
return self._users.get(jid.bare)
|
162
|
-
|
163
|
-
def get_by_stanza(self, s: Union[Presence, Message, Iq]) -> Optional[GatewayUser]:
|
164
|
-
"""
|
165
|
-
Convenience function to get a user from a stanza they sent.
|
166
|
-
|
167
|
-
:param s: A stanza sent by the gateway user
|
168
|
-
:return:
|
169
|
-
"""
|
170
|
-
return self.get_by_jid(s.get_from())
|
171
|
-
|
172
|
-
def close(self):
|
173
|
-
self._users.sync()
|
174
|
-
self._users.close()
|
175
|
-
|
176
|
-
|
177
|
-
user_store = UserStore()
|
178
|
-
"""
|
179
|
-
A persistent store for slidge users. Not public, but I didn't find how to hide
|
180
|
-
it from the docs!
|
181
|
-
"""
|
182
|
-
|
183
|
-
log = logging.getLogger(__name__)
|
5
|
+
from ..db.alembic.old_user_store import * # noqa:F403
|
slidge/util/test.py
CHANGED
@@ -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(
|