slidge 0.2.0a2__py3-none-any.whl → 0.2.0a4__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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(
|