slidge 0.2.0a0__py3-none-any.whl → 0.2.0a1__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 +1 -1
- slidge/command/user.py +0 -1
- slidge/contact/contact.py +86 -32
- slidge/contact/roster.py +79 -19
- slidge/core/config.py +1 -4
- slidge/core/gateway/base.py +9 -2
- slidge/core/gateway/caps.py +7 -5
- slidge/core/gateway/muc_admin.py +1 -1
- slidge/core/gateway/session_dispatcher.py +20 -6
- slidge/core/gateway/vcard_temp.py +1 -1
- slidge/core/mixins/attachment.py +17 -4
- slidge/core/mixins/avatar.py +26 -9
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/message.py +7 -1
- slidge/core/mixins/presence.py +6 -3
- slidge/core/pubsub.py +7 -15
- slidge/core/session.py +6 -3
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +11 -2
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- slidge/db/avatar.py +20 -9
- slidge/db/models.py +16 -6
- slidge/db/store.py +217 -115
- slidge/group/archive.py +46 -1
- slidge/group/bookmarks.py +17 -5
- slidge/group/participant.py +10 -3
- slidge/group/room.py +183 -125
- slidge/main.py +3 -3
- slidge/slixfix/xep_0292/vcard4.py +2 -0
- slidge/util/archive_msg.py +2 -1
- slidge/util/test.py +54 -4
- slidge/util/types.py +5 -0
- slidge/util/util.py +22 -0
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/METADATA +2 -4
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/RECORD +43 -37
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/LICENSE +0 -0
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/WHEEL +0 -0
- {slidge-0.2.0a0.dist-info → slidge-0.2.0a1.dist-info}/entry_points.txt +0 -0
slidge/db/store.py
CHANGED
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import json
|
4
4
|
import logging
|
5
|
-
import warnings
|
6
5
|
from contextlib import contextmanager
|
7
6
|
from datetime import datetime, timedelta, timezone
|
8
7
|
from typing import TYPE_CHECKING, Collection, Iterator, Optional, Type
|
@@ -11,6 +10,7 @@ from slixmpp import JID, Iq, Message, Presence
|
|
11
10
|
from slixmpp.exceptions import XMPPError
|
12
11
|
from sqlalchemy import Engine, delete, select, update
|
13
12
|
from sqlalchemy.orm import Session, attributes
|
13
|
+
from sqlalchemy.sql.functions import count
|
14
14
|
|
15
15
|
from ..util.archive_msg import HistoryMessage
|
16
16
|
from ..util.types import URL, CachedPresence
|
@@ -19,6 +19,7 @@ from ..util.types import MamMetadata, MucAffiliation, MucRole
|
|
19
19
|
from .meta import Base
|
20
20
|
from .models import (
|
21
21
|
ArchivedMessage,
|
22
|
+
ArchivedMessageSource,
|
22
23
|
Attachment,
|
23
24
|
Avatar,
|
24
25
|
Contact,
|
@@ -63,9 +64,18 @@ class UpdatedMixin(EngineMixin):
|
|
63
64
|
def __init__(self, *a, **kw):
|
64
65
|
super().__init__(*a, **kw)
|
65
66
|
with self.session() as session:
|
67
|
+
session.execute(
|
68
|
+
delete(self.model).where(~self.model.updated) # type:ignore
|
69
|
+
)
|
66
70
|
session.execute(update(self.model).values(updated=False)) # type:ignore
|
67
71
|
session.commit()
|
68
72
|
|
73
|
+
def get_by_pk(self, pk: int) -> Optional[Base]:
|
74
|
+
with self.session() as session:
|
75
|
+
return session.execute(
|
76
|
+
select(self.model).where(self.model.id == pk) # type:ignore
|
77
|
+
).scalar()
|
78
|
+
|
69
79
|
|
70
80
|
class SlidgeStore(EngineMixin):
|
71
81
|
def __init__(self, engine: Engine):
|
@@ -134,34 +144,18 @@ class AvatarStore(EngineMixin):
|
|
134
144
|
select(Avatar).where(Avatar.legacy_id == legacy_id)
|
135
145
|
).scalar()
|
136
146
|
|
137
|
-
def
|
147
|
+
def get_by_pk(self, pk: int) -> Optional[Avatar]:
|
138
148
|
with self.session() as session:
|
139
|
-
|
140
|
-
if pk is None:
|
141
|
-
warnings.warn("avatar not found")
|
142
|
-
return
|
143
|
-
session.execute(
|
144
|
-
update(Contact).where(Contact.jid == jid.bare).values(avatar_id=pk)
|
145
|
-
)
|
146
|
-
session.execute(
|
147
|
-
update(Room).where(Room.jid == jid.bare).values(avatar_id=pk)
|
148
|
-
)
|
149
|
-
session.commit()
|
149
|
+
return session.execute(select(Avatar).where(Avatar.id == pk)).scalar()
|
150
150
|
|
151
|
-
def
|
151
|
+
def delete_by_pk(self, pk: int):
|
152
152
|
with self.session() as session:
|
153
|
-
|
154
|
-
|
155
|
-
).scalar()
|
156
|
-
if avatar is not None:
|
157
|
-
return avatar
|
158
|
-
return session.execute(
|
159
|
-
select(Avatar).where(Avatar.rooms.any(Room.jid == jid))
|
160
|
-
).scalar()
|
153
|
+
session.execute(delete(Avatar).where(Avatar.id == pk))
|
154
|
+
session.commit()
|
161
155
|
|
162
|
-
def
|
156
|
+
def get_all(self) -> Iterator[Avatar]:
|
163
157
|
with self.session() as session:
|
164
|
-
|
158
|
+
yield from session.execute(select(Avatar)).scalars()
|
165
159
|
|
166
160
|
|
167
161
|
class SentStore(EngineMixin):
|
@@ -273,22 +267,6 @@ class ContactStore(UpdatedMixin):
|
|
273
267
|
session.execute(update(Contact).values(cached_presence=False))
|
274
268
|
session.commit()
|
275
269
|
|
276
|
-
def add(self, user_pk: int, legacy_id: str, contact_jid: JID) -> int:
|
277
|
-
with self.session() as session:
|
278
|
-
existing = session.execute(
|
279
|
-
select(Contact)
|
280
|
-
.where(Contact.legacy_id == legacy_id)
|
281
|
-
.where(Contact.jid == contact_jid.bare)
|
282
|
-
).scalar()
|
283
|
-
if existing is not None:
|
284
|
-
return existing.id
|
285
|
-
contact = Contact(
|
286
|
-
jid=contact_jid.bare, legacy_id=legacy_id, user_account_id=user_pk
|
287
|
-
)
|
288
|
-
session.add(contact)
|
289
|
-
session.commit()
|
290
|
-
return contact.id
|
291
|
-
|
292
270
|
def get_all(self, user_pk: int) -> Iterator[Contact]:
|
293
271
|
with self.session() as session:
|
294
272
|
yield from session.execute(
|
@@ -375,20 +353,36 @@ class ContactStore(UpdatedMixin):
|
|
375
353
|
return None
|
376
354
|
return contact.avatar.legacy_id
|
377
355
|
|
378
|
-
def update(self, contact: "LegacyContact"):
|
356
|
+
def update(self, contact: "LegacyContact", commit=True) -> int:
|
379
357
|
with self.session() as session:
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
358
|
+
if contact.contact_pk is None:
|
359
|
+
if contact.cached_presence is not None:
|
360
|
+
presence_kwargs = contact.cached_presence._asdict()
|
361
|
+
presence_kwargs["cached_presence"] = True
|
362
|
+
else:
|
363
|
+
presence_kwargs = {}
|
364
|
+
row = Contact(
|
365
|
+
jid=contact.jid.bare,
|
366
|
+
legacy_id=str(contact.legacy_id),
|
367
|
+
user_account_id=contact.user_pk,
|
368
|
+
**presence_kwargs,
|
389
369
|
)
|
390
|
-
|
391
|
-
|
370
|
+
else:
|
371
|
+
row = (
|
372
|
+
session.query(Contact)
|
373
|
+
.filter(Contact.id == contact.contact_pk)
|
374
|
+
.one()
|
375
|
+
)
|
376
|
+
row.nick = contact.name
|
377
|
+
row.is_friend = contact.is_friend
|
378
|
+
row.added_to_roster = contact.added_to_roster
|
379
|
+
row.updated = True
|
380
|
+
row.extra_attributes = contact.serialize_extra_attributes()
|
381
|
+
row.caps_ver = contact._caps_ver
|
382
|
+
session.add(row)
|
383
|
+
if commit:
|
384
|
+
session.commit()
|
385
|
+
return row.id
|
392
386
|
|
393
387
|
def add_to_sent(self, contact_pk: int, msg_id: str) -> None:
|
394
388
|
with self.session() as session:
|
@@ -422,19 +416,52 @@ class ContactStore(UpdatedMixin):
|
|
422
416
|
)
|
423
417
|
session.commit()
|
424
418
|
|
419
|
+
def set_added_to_roster(self, contact_pk: int, value: bool) -> None:
|
420
|
+
with self.session() as session:
|
421
|
+
session.execute(
|
422
|
+
update(Contact)
|
423
|
+
.where(Contact.id == contact_pk)
|
424
|
+
.values(added_to_roster=value)
|
425
|
+
)
|
426
|
+
session.commit()
|
427
|
+
|
428
|
+
def delete(self, contact_pk: int) -> None:
|
429
|
+
with self.session() as session:
|
430
|
+
session.execute(delete(Contact).where(Contact.id == contact_pk))
|
431
|
+
session.commit()
|
432
|
+
|
425
433
|
|
426
434
|
class MAMStore(EngineMixin):
|
435
|
+
def __init__(self, *a, **kw):
|
436
|
+
super().__init__(*a, **kw)
|
437
|
+
with self.session() as session:
|
438
|
+
session.execute(
|
439
|
+
update(ArchivedMessage).values(source=ArchivedMessageSource.BACKFILL)
|
440
|
+
)
|
441
|
+
session.commit()
|
442
|
+
|
427
443
|
def nuke_older_than(self, days: int) -> None:
|
428
444
|
with self.session() as session:
|
429
445
|
session.execute(
|
430
446
|
delete(ArchivedMessage).where(
|
431
|
-
ArchivedMessage.timestamp
|
447
|
+
ArchivedMessage.timestamp < datetime.now() - timedelta(days=days)
|
432
448
|
)
|
433
449
|
)
|
434
450
|
session.commit()
|
435
451
|
|
436
|
-
def add_message(
|
452
|
+
def add_message(
|
453
|
+
self,
|
454
|
+
room_pk: int,
|
455
|
+
message: HistoryMessage,
|
456
|
+
archive_only: bool,
|
457
|
+
legacy_msg_id: str | None,
|
458
|
+
) -> None:
|
437
459
|
with self.session() as session:
|
460
|
+
source = (
|
461
|
+
ArchivedMessageSource.BACKFILL
|
462
|
+
if archive_only
|
463
|
+
else ArchivedMessageSource.LIVE
|
464
|
+
)
|
438
465
|
existing = session.execute(
|
439
466
|
select(ArchivedMessage)
|
440
467
|
.where(ArchivedMessage.room_id == room_pk)
|
@@ -445,6 +472,8 @@ class MAMStore(EngineMixin):
|
|
445
472
|
existing.timestamp = message.when
|
446
473
|
existing.stanza = str(message.stanza)
|
447
474
|
existing.author_jid = message.stanza.get_from()
|
475
|
+
existing.source = source
|
476
|
+
existing.legacy_id = legacy_msg_id
|
448
477
|
session.add(existing)
|
449
478
|
session.commit()
|
450
479
|
return
|
@@ -454,6 +483,8 @@ class MAMStore(EngineMixin):
|
|
454
483
|
stanza=str(message.stanza),
|
455
484
|
author_jid=message.stanza.get_from(),
|
456
485
|
room_id=room_pk,
|
486
|
+
source=source,
|
487
|
+
legacy_id=legacy_msg_id,
|
457
488
|
)
|
458
489
|
session.add(mam_msg)
|
459
490
|
session.commit()
|
@@ -526,25 +557,83 @@ class MAMStore(EngineMixin):
|
|
526
557
|
stanza=str(h.stanza), when=h.timestamp.replace(tzinfo=timezone.utc)
|
527
558
|
)
|
528
559
|
|
529
|
-
def
|
530
|
-
r = []
|
560
|
+
def get_first(self, room_pk: int, with_legacy_id=False) -> ArchivedMessage | None:
|
531
561
|
with self.session() as session:
|
532
|
-
|
533
|
-
select(ArchivedMessage
|
562
|
+
q = (
|
563
|
+
select(ArchivedMessage)
|
534
564
|
.where(ArchivedMessage.room_id == room_pk)
|
535
565
|
.order_by(ArchivedMessage.timestamp.asc())
|
536
|
-
)
|
566
|
+
)
|
567
|
+
if with_legacy_id:
|
568
|
+
q = q.filter(ArchivedMessage.legacy_id.isnot(None))
|
569
|
+
return session.execute(q).scalar()
|
570
|
+
|
571
|
+
def get_last(
|
572
|
+
self, room_pk: int, source: ArchivedMessageSource | None = None
|
573
|
+
) -> ArchivedMessage | None:
|
574
|
+
with self.session() as session:
|
575
|
+
q = select(ArchivedMessage).where(ArchivedMessage.room_id == room_pk)
|
576
|
+
|
577
|
+
if source is not None:
|
578
|
+
q = q.where(ArchivedMessage.source == source)
|
579
|
+
|
580
|
+
return session.execute(
|
581
|
+
q.order_by(ArchivedMessage.timestamp.desc())
|
582
|
+
).scalar()
|
583
|
+
|
584
|
+
def get_first_and_last(self, room_pk: int) -> list[MamMetadata]:
|
585
|
+
r = []
|
586
|
+
with self.session():
|
587
|
+
first = self.get_first(room_pk)
|
537
588
|
if first is not None:
|
538
|
-
r.append(MamMetadata(
|
539
|
-
last =
|
540
|
-
select(ArchivedMessage.stanza_id, ArchivedMessage.timestamp)
|
541
|
-
.where(ArchivedMessage.room_id == room_pk)
|
542
|
-
.order_by(ArchivedMessage.timestamp.desc())
|
543
|
-
).first()
|
589
|
+
r.append(MamMetadata(first.stanza_id, first.timestamp))
|
590
|
+
last = self.get_last(room_pk)
|
544
591
|
if last is not None:
|
545
|
-
r.append(MamMetadata(
|
592
|
+
r.append(MamMetadata(last.stanza_id, last.timestamp))
|
546
593
|
return r
|
547
594
|
|
595
|
+
def get_most_recent_with_legacy_id(
|
596
|
+
self, room_pk: int, source: ArchivedMessageSource | None = None
|
597
|
+
) -> ArchivedMessage | None:
|
598
|
+
with self.session() as session:
|
599
|
+
q = (
|
600
|
+
select(ArchivedMessage)
|
601
|
+
.where(ArchivedMessage.room_id == room_pk)
|
602
|
+
.where(ArchivedMessage.legacy_id.isnot(None))
|
603
|
+
)
|
604
|
+
if source is not None:
|
605
|
+
q = q.where(ArchivedMessage.source == source)
|
606
|
+
return session.execute(
|
607
|
+
q.order_by(ArchivedMessage.timestamp.desc())
|
608
|
+
).scalar()
|
609
|
+
|
610
|
+
def get_least_recent_with_legacy_id_after(
|
611
|
+
self, room_pk: int, after_id: str, source=ArchivedMessageSource.LIVE
|
612
|
+
) -> ArchivedMessage | None:
|
613
|
+
with self.session() as session:
|
614
|
+
after_timestamp = (
|
615
|
+
session.query(ArchivedMessage.timestamp)
|
616
|
+
.filter(ArchivedMessage.legacy_id == after_id)
|
617
|
+
.scalar()
|
618
|
+
)
|
619
|
+
q = (
|
620
|
+
select(ArchivedMessage)
|
621
|
+
.where(ArchivedMessage.room_id == room_pk)
|
622
|
+
.where(ArchivedMessage.legacy_id.isnot(None))
|
623
|
+
.where(ArchivedMessage.source == source)
|
624
|
+
.where(ArchivedMessage.timestamp > after_timestamp)
|
625
|
+
)
|
626
|
+
return session.execute(q.order_by(ArchivedMessage.timestamp.asc())).scalar()
|
627
|
+
|
628
|
+
def get_by_legacy_id(self, room_pk: int, legacy_id: str) -> ArchivedMessage | None:
|
629
|
+
with self.session() as session:
|
630
|
+
return (
|
631
|
+
session.query(ArchivedMessage)
|
632
|
+
.filter(ArchivedMessage.room_id == room_pk)
|
633
|
+
.filter(ArchivedMessage.legacy_id == legacy_id)
|
634
|
+
.first()
|
635
|
+
)
|
636
|
+
|
548
637
|
|
549
638
|
class MultiStore(EngineMixin):
|
550
639
|
def get_xmpp_ids(self, user_pk: int, xmpp_id: str) -> list[str]:
|
@@ -676,27 +765,13 @@ class RoomStore(UpdatedMixin):
|
|
676
765
|
super().__init__(*a, **kw)
|
677
766
|
with self.session() as session:
|
678
767
|
session.execute(
|
679
|
-
update(Room).values(
|
768
|
+
update(Room).values(
|
769
|
+
subject_setter=None, user_resources=None, history_filled=False
|
770
|
+
)
|
680
771
|
)
|
681
772
|
session.commit()
|
682
773
|
|
683
|
-
def
|
684
|
-
if jid.resource:
|
685
|
-
raise TypeError
|
686
|
-
with self.session() as session:
|
687
|
-
existing = session.execute(
|
688
|
-
select(Room.id)
|
689
|
-
.where(Room.user_account_id == user_pk)
|
690
|
-
.where(Room.legacy_id == legacy_id)
|
691
|
-
).scalar()
|
692
|
-
if existing is not None:
|
693
|
-
return existing
|
694
|
-
room = Room(jid=jid, user_account_id=user_pk, legacy_id=legacy_id)
|
695
|
-
session.add(room)
|
696
|
-
session.commit()
|
697
|
-
return room.id
|
698
|
-
|
699
|
-
def set_avatar(self, room_pk: int, avatar_pk: int) -> None:
|
774
|
+
def set_avatar(self, room_pk: int, avatar_pk: int | None) -> None:
|
700
775
|
with self.session() as session:
|
701
776
|
session.execute(
|
702
777
|
update(Room).where(Room.id == room_pk).values(avatar_id=avatar_pk)
|
@@ -728,43 +803,45 @@ class RoomStore(UpdatedMixin):
|
|
728
803
|
.where(Room.legacy_id == legacy_id)
|
729
804
|
).scalar()
|
730
805
|
|
731
|
-
def
|
732
|
-
from slidge.contact import LegacyContact
|
733
|
-
|
806
|
+
def update_subject_setter(self, room_pk: int, subject_setter: str | None):
|
734
807
|
with self.session() as session:
|
735
|
-
if room.subject_setter is None:
|
736
|
-
subject_setter_id = None
|
737
|
-
elif isinstance(room.subject_setter, str):
|
738
|
-
subject_setter_id = None
|
739
|
-
elif isinstance(room.subject_setter, LegacyContact):
|
740
|
-
subject_setter_id = None
|
741
|
-
elif room.subject_setter.is_system:
|
742
|
-
subject_setter_id = None
|
743
|
-
else:
|
744
|
-
subject_setter_id = room.subject_setter.pk
|
745
|
-
|
746
808
|
session.execute(
|
747
809
|
update(Room)
|
748
|
-
.where(Room.id ==
|
749
|
-
.values(
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
),
|
759
|
-
|
760
|
-
subject=room.subject,
|
761
|
-
subject_date=room.subject_date,
|
762
|
-
subject_setter_id=subject_setter_id,
|
763
|
-
participants_filled=room._participants_filled,
|
764
|
-
n_participants=room._n_participants,
|
810
|
+
.where(Room.id == room_pk)
|
811
|
+
.values(subject_setter=subject_setter)
|
812
|
+
)
|
813
|
+
session.commit()
|
814
|
+
|
815
|
+
def update(self, muc: "LegacyMUC") -> int:
|
816
|
+
with self.session() as session:
|
817
|
+
if muc.pk is None:
|
818
|
+
row = Room(
|
819
|
+
jid=muc.jid,
|
820
|
+
legacy_id=str(muc.legacy_id),
|
821
|
+
user_account_id=muc.user_pk,
|
765
822
|
)
|
823
|
+
else:
|
824
|
+
row = session.query(Room).filter(Room.id == muc.pk).one()
|
825
|
+
|
826
|
+
row.updated = True
|
827
|
+
row.extra_attributes = muc.serialize_extra_attributes()
|
828
|
+
row.name = muc.name
|
829
|
+
row.description = muc.description
|
830
|
+
row.user_resources = (
|
831
|
+
None
|
832
|
+
if not muc._user_resources
|
833
|
+
else json.dumps(list(muc._user_resources))
|
766
834
|
)
|
835
|
+
row.muc_type = muc.type
|
836
|
+
row.subject = muc.subject
|
837
|
+
row.subject_date = muc.subject_date
|
838
|
+
row.subject_setter = muc.subject_setter
|
839
|
+
row.participants_filled = muc._participants_filled
|
840
|
+
row.n_participants = muc._n_participants
|
841
|
+
row.user_nick = muc.user_nick
|
842
|
+
session.add(row)
|
767
843
|
session.commit()
|
844
|
+
return row.id
|
768
845
|
|
769
846
|
def update_subject_date(
|
770
847
|
self, room_pk: int, subject_date: Optional[datetime]
|
@@ -789,6 +866,11 @@ class RoomStore(UpdatedMixin):
|
|
789
866
|
)
|
790
867
|
session.commit()
|
791
868
|
|
869
|
+
def update_name(self, room_pk: int, name: Optional[str]) -> None:
|
870
|
+
with self.session() as session:
|
871
|
+
session.execute(update(Room).where(Room.id == room_pk).values(name=name))
|
872
|
+
session.commit()
|
873
|
+
|
792
874
|
def update_n_participants(self, room_pk: int, n: Optional[int]) -> None:
|
793
875
|
with self.session() as session:
|
794
876
|
session.execute(
|
@@ -826,6 +908,20 @@ class RoomStore(UpdatedMixin):
|
|
826
908
|
is None
|
827
909
|
)
|
828
910
|
|
911
|
+
def set_participants_filled(self, room_pk: int, val=True) -> None:
|
912
|
+
with self.session() as session:
|
913
|
+
session.execute(
|
914
|
+
update(Room).where(Room.id == room_pk).values(participants_filled=val)
|
915
|
+
)
|
916
|
+
session.commit()
|
917
|
+
|
918
|
+
def set_history_filled(self, room_pk: int, val=True) -> None:
|
919
|
+
with self.session() as session:
|
920
|
+
session.execute(
|
921
|
+
update(Room).where(Room.id == room_pk).values(history_filled=True)
|
922
|
+
)
|
923
|
+
session.commit()
|
924
|
+
|
829
925
|
def get_all(self, user_pk: int) -> Iterator[Room]:
|
830
926
|
with self.session() as session:
|
831
927
|
yield from session.execute(
|
@@ -971,6 +1067,12 @@ class ParticipantStore(EngineMixin):
|
|
971
1067
|
with self.session() as session:
|
972
1068
|
session.execute(delete(Participant).where(Participant.id == participant_pk))
|
973
1069
|
|
1070
|
+
def get_count(self, room_pk: int) -> int:
|
1071
|
+
with self.session() as session:
|
1072
|
+
return session.query(
|
1073
|
+
count(Participant.id).filter(Participant.room_id == room_pk)
|
1074
|
+
).scalar()
|
1075
|
+
|
974
1076
|
|
975
1077
|
log = logging.getLogger(__name__)
|
976
1078
|
_session: Optional[Session] = None
|
slidge/group/archive.py
CHANGED
@@ -6,8 +6,10 @@ from typing import TYPE_CHECKING, Collection, Optional
|
|
6
6
|
|
7
7
|
from slixmpp import Iq, Message
|
8
8
|
|
9
|
+
from ..db.models import ArchivedMessage, ArchivedMessageSource
|
9
10
|
from ..db.store import MAMStore
|
10
11
|
from ..util.archive_msg import HistoryMessage
|
12
|
+
from ..util.types import HoleBound
|
11
13
|
|
12
14
|
if TYPE_CHECKING:
|
13
15
|
from .participant import LegacyParticipant
|
@@ -22,12 +24,16 @@ class MessageArchive:
|
|
22
24
|
self,
|
23
25
|
msg: Message,
|
24
26
|
participant: Optional["LegacyParticipant"] = None,
|
27
|
+
archive_only=False,
|
28
|
+
legacy_msg_id=None,
|
25
29
|
):
|
26
30
|
"""
|
27
31
|
Add a message to the archive if it is deemed archivable
|
28
32
|
|
29
33
|
:param msg:
|
30
34
|
:param participant:
|
35
|
+
:param archive_only:
|
36
|
+
:param legacy_msg_id:
|
31
37
|
"""
|
32
38
|
if not archivable(msg):
|
33
39
|
return
|
@@ -47,11 +53,50 @@ class MessageArchive:
|
|
47
53
|
"jid"
|
48
54
|
] = f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
|
49
55
|
|
50
|
-
self.__store.add_message(
|
56
|
+
self.__store.add_message(
|
57
|
+
self.room_pk,
|
58
|
+
HistoryMessage(new_msg),
|
59
|
+
archive_only,
|
60
|
+
None if legacy_msg_id is None else str(legacy_msg_id),
|
61
|
+
)
|
51
62
|
|
52
63
|
def __iter__(self):
|
53
64
|
return iter(self.get_all())
|
54
65
|
|
66
|
+
@staticmethod
|
67
|
+
def __to_bound(stored: ArchivedMessage):
|
68
|
+
return HoleBound(
|
69
|
+
stored.legacy_id, # type:ignore
|
70
|
+
stored.timestamp.replace(tzinfo=timezone.utc),
|
71
|
+
)
|
72
|
+
|
73
|
+
def get_hole_bounds(self) -> tuple[HoleBound | None, HoleBound | None]:
|
74
|
+
most_recent = self.__store.get_most_recent_with_legacy_id(self.room_pk)
|
75
|
+
if most_recent is None:
|
76
|
+
return None, None
|
77
|
+
if most_recent.source == ArchivedMessageSource.BACKFILL:
|
78
|
+
# most recent = only backfill, fetch everything since last backfill
|
79
|
+
return self.__to_bound(most_recent), None
|
80
|
+
|
81
|
+
most_recent_back_filled = self.__store.get_most_recent_with_legacy_id(
|
82
|
+
self.room_pk, ArchivedMessageSource.BACKFILL
|
83
|
+
)
|
84
|
+
if most_recent_back_filled is None:
|
85
|
+
# group was never back-filled, fetch everything before first live
|
86
|
+
least_recent_live = self.__store.get_first(self.room_pk, True)
|
87
|
+
assert least_recent_live is not None
|
88
|
+
return None, self.__to_bound(least_recent_live)
|
89
|
+
|
90
|
+
assert most_recent_back_filled.legacy_id is not None
|
91
|
+
least_recent_live = self.__store.get_least_recent_with_legacy_id_after(
|
92
|
+
self.room_pk, most_recent_back_filled.legacy_id
|
93
|
+
)
|
94
|
+
assert least_recent_live is not None
|
95
|
+
# this is a hole caused by slidge downtime
|
96
|
+
return self.__to_bound(most_recent_back_filled), self.__to_bound(
|
97
|
+
least_recent_live
|
98
|
+
)
|
99
|
+
|
55
100
|
def get_all(
|
56
101
|
self,
|
57
102
|
start_date: Optional[datetime] = None,
|
slidge/group/bookmarks.py
CHANGED
@@ -3,6 +3,7 @@ import logging
|
|
3
3
|
from typing import TYPE_CHECKING, Generic, Iterator, Optional, Type
|
4
4
|
|
5
5
|
from slixmpp import JID
|
6
|
+
from slixmpp.exceptions import XMPPError
|
6
7
|
from slixmpp.jid import _unescape_node
|
7
8
|
|
8
9
|
from ..contact.roster import ESCAPE_TABLE
|
@@ -57,15 +58,26 @@ class LegacyBookmarks(
|
|
57
58
|
return f"<Bookmarks of {self.user_jid}>"
|
58
59
|
|
59
60
|
async def __finish_init_muc(self, legacy_id: LegacyGroupIdType, jid: JID):
|
60
|
-
muc = self._muc_class(self.session, legacy_id=legacy_id, jid=jid)
|
61
61
|
with self.__store.session():
|
62
|
-
|
63
|
-
|
64
|
-
|
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))
|
65
76
|
if not muc.user_nick:
|
66
77
|
muc.user_nick = self._user_nick
|
67
78
|
self.log.debug("MUC created: %r", muc)
|
68
|
-
self.__store.update(muc)
|
79
|
+
muc.pk = self.__store.update(muc)
|
80
|
+
muc.archive = MessageArchive(muc.pk, self.xmpp.store.mam)
|
69
81
|
return muc
|
70
82
|
|
71
83
|
async def legacy_id_to_jid_local_part(self, legacy_id: LegacyGroupIdType):
|
slidge/group/participant.py
CHANGED
@@ -117,6 +117,8 @@ class LegacyParticipant(
|
|
117
117
|
if self._affiliation == affiliation:
|
118
118
|
return
|
119
119
|
self._affiliation = affiliation
|
120
|
+
if not self.muc._participants_filled:
|
121
|
+
return
|
120
122
|
self.__part_store.set_affiliation(self.pk, affiliation)
|
121
123
|
if not self._presence_sent:
|
122
124
|
return
|
@@ -145,6 +147,8 @@ class LegacyParticipant(
|
|
145
147
|
if self._role == role:
|
146
148
|
return
|
147
149
|
self._role = role
|
150
|
+
if not self.muc._participants_filled:
|
151
|
+
return
|
148
152
|
self.__part_store.set_role(self.pk, role)
|
149
153
|
if not self._presence_sent:
|
150
154
|
return
|
@@ -154,6 +158,8 @@ class LegacyParticipant(
|
|
154
158
|
if self._hats == hats:
|
155
159
|
return
|
156
160
|
self._hats = hats
|
161
|
+
if not self.muc._participants_filled:
|
162
|
+
return
|
157
163
|
self.__part_store.set_hats(self.pk, hats)
|
158
164
|
if not self._presence_sent:
|
159
165
|
return
|
@@ -313,6 +319,7 @@ class LegacyParticipant(
|
|
313
319
|
stanza: MessageOrPresenceTypeVar,
|
314
320
|
full_jid: Optional[JID] = None,
|
315
321
|
archive_only=False,
|
322
|
+
legacy_msg_id=None,
|
316
323
|
**send_kwargs,
|
317
324
|
) -> MessageOrPresenceTypeVar:
|
318
325
|
stanza["occupant-id"]["id"] = self.__occupant_id
|
@@ -331,8 +338,8 @@ class LegacyParticipant(
|
|
331
338
|
else:
|
332
339
|
stanza.send()
|
333
340
|
else:
|
334
|
-
if isinstance(stanza, Message):
|
335
|
-
self.muc.archive.add(stanza, self)
|
341
|
+
if hasattr(self.muc, "archive") and isinstance(stanza, Message):
|
342
|
+
self.muc.archive.add(stanza, self, archive_only, legacy_msg_id)
|
336
343
|
if archive_only:
|
337
344
|
return stanza
|
338
345
|
for user_full_jid in self.muc.user_full_jids():
|
@@ -460,7 +467,7 @@ class LegacyParticipant(
|
|
460
467
|
):
|
461
468
|
if update_muc:
|
462
469
|
self.muc._subject = subject # type: ignore
|
463
|
-
self.muc.subject_setter = self
|
470
|
+
self.muc.subject_setter = self.nickname
|
464
471
|
self.muc.subject_date = when
|
465
472
|
|
466
473
|
msg = self._make_message()
|