slidge 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- slidge/__init__.py +3 -5
- slidge/__main__.py +2 -197
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +40 -17
- slidge/command/admin.py +24 -12
- slidge/command/base.py +10 -8
- slidge/command/categories.py +13 -3
- slidge/command/chat_command.py +29 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +106 -13
- slidge/contact/contact.py +254 -50
- slidge/contact/roster.py +124 -53
- slidge/core/config.py +19 -13
- slidge/core/dispatcher/__init__.py +3 -0
- slidge/core/{gateway → dispatcher}/caps.py +12 -8
- slidge/core/{gateway → dispatcher}/disco.py +10 -18
- slidge/core/dispatcher/message/__init__.py +10 -0
- slidge/core/dispatcher/message/chat_state.py +40 -0
- slidge/core/dispatcher/message/marker.py +62 -0
- slidge/core/dispatcher/message/message.py +397 -0
- slidge/core/dispatcher/muc/__init__.py +12 -0
- slidge/core/dispatcher/muc/admin.py +98 -0
- slidge/core/{gateway → dispatcher/muc}/mam.py +25 -17
- slidge/core/dispatcher/muc/misc.py +121 -0
- slidge/core/dispatcher/muc/owner.py +96 -0
- slidge/core/{gateway → dispatcher/muc}/ping.py +11 -17
- slidge/core/dispatcher/presence.py +176 -0
- slidge/core/dispatcher/registration.py +85 -0
- slidge/core/{gateway → dispatcher}/search.py +9 -16
- slidge/core/dispatcher/session_dispatcher.py +84 -0
- slidge/core/dispatcher/util.py +174 -0
- slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +35 -19
- slidge/core/{gateway/base.py → gateway.py} +176 -153
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +106 -67
- slidge/core/mixins/avatar.py +94 -25
- slidge/core/mixins/base.py +10 -4
- slidge/core/mixins/db.py +18 -0
- slidge/core/mixins/disco.py +0 -10
- slidge/core/mixins/lock.py +10 -8
- slidge/core/mixins/message.py +11 -195
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/message_text.py +211 -0
- slidge/core/mixins/presence.py +17 -4
- slidge/core/pubsub.py +114 -288
- slidge/core/session.py +101 -40
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/__init__.py +0 -0
- slidge/db/alembic/env.py +64 -0
- slidge/db/alembic/old_user_store.py +183 -0
- slidge/db/alembic/script.py.mako +26 -0
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +85 -0
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +52 -0
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +61 -0
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +139 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +101 -0
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +79 -0
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +52 -0
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
- slidge/db/avatar.py +205 -0
- slidge/db/meta.py +72 -0
- slidge/db/models.py +405 -0
- slidge/db/store.py +1257 -0
- slidge/group/archive.py +58 -14
- slidge/group/bookmarks.py +89 -65
- slidge/group/participant.py +111 -44
- slidge/group/room.py +402 -213
- slidge/main.py +202 -0
- slidge/migration.py +45 -1
- slidge/slixfix/__init__.py +31 -1
- slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
- slidge/slixfix/roster.py +13 -4
- slidge/slixfix/xep_0292/vcard4.py +1 -87
- slidge/util/archive_msg.py +2 -1
- slidge/util/db.py +4 -228
- slidge/util/test.py +91 -4
- slidge/util/types.py +39 -4
- slidge/util/util.py +45 -2
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/METADATA +10 -5
- slidge-0.2.0.dist-info/RECORD +131 -0
- slidge-0.2.0.dist-info/entry_points.txt +3 -0
- slidge/core/cache.py +0 -183
- slidge/core/gateway/__init__.py +0 -3
- slidge/core/gateway/muc_admin.py +0 -35
- slidge/core/gateway/presence.py +0 -95
- slidge/core/gateway/registration.py +0 -53
- slidge/core/gateway/session_dispatcher.py +0 -795
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.2.dist-info/RECORD +0 -96
- slidge-0.1.2.dist-info/entry_points.txt +0 -3
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/LICENSE +0 -0
- {slidge-0.1.2.dist-info → slidge-0.2.0.dist-info}/WHEEL +0 -0
slidge/group/archive.py
CHANGED
@@ -1,35 +1,39 @@
|
|
1
1
|
import logging
|
2
2
|
import uuid
|
3
3
|
from copy import copy
|
4
|
-
from datetime import datetime
|
4
|
+
from datetime import datetime, timezone
|
5
5
|
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
|
10
|
+
from ..db.store import MAMStore
|
9
11
|
from ..util.archive_msg import HistoryMessage
|
10
|
-
from ..util.
|
11
|
-
from ..util.sql import db
|
12
|
+
from ..util.types import HoleBound
|
12
13
|
|
13
14
|
if TYPE_CHECKING:
|
14
15
|
from .participant import LegacyParticipant
|
15
16
|
|
16
17
|
|
17
18
|
class MessageArchive:
|
18
|
-
def __init__(self,
|
19
|
-
self.
|
20
|
-
self.
|
21
|
-
db.mam_add_muc(db_id, user)
|
19
|
+
def __init__(self, room_pk: int, store: MAMStore):
|
20
|
+
self.room_pk = room_pk
|
21
|
+
self.__store = store
|
22
22
|
|
23
23
|
def add(
|
24
24
|
self,
|
25
25
|
msg: Message,
|
26
26
|
participant: Optional["LegacyParticipant"] = None,
|
27
|
+
archive_only=False,
|
28
|
+
legacy_msg_id=None,
|
27
29
|
):
|
28
30
|
"""
|
29
31
|
Add a message to the archive if it is deemed archivable
|
30
32
|
|
31
33
|
:param msg:
|
32
34
|
:param participant:
|
35
|
+
:param archive_only:
|
36
|
+
:param legacy_msg_id:
|
33
37
|
"""
|
34
38
|
if not archivable(msg):
|
35
39
|
return
|
@@ -40,7 +44,7 @@ class MessageArchive:
|
|
40
44
|
if participant.contact:
|
41
45
|
new_msg["muc"]["jid"] = participant.contact.jid.bare
|
42
46
|
elif participant.is_user:
|
43
|
-
new_msg["muc"]["jid"] = participant.
|
47
|
+
new_msg["muc"]["jid"] = participant.user_jid.bare
|
44
48
|
elif participant.is_system:
|
45
49
|
new_msg["muc"]["jid"] = participant.muc.jid
|
46
50
|
else:
|
@@ -49,11 +53,50 @@ class MessageArchive:
|
|
49
53
|
"jid"
|
50
54
|
] = f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
|
51
55
|
|
52
|
-
|
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
|
+
)
|
53
62
|
|
54
63
|
def __iter__(self):
|
55
64
|
return iter(self.get_all())
|
56
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
|
+
|
57
100
|
def get_all(
|
58
101
|
self,
|
59
102
|
start_date: Optional[datetime] = None,
|
@@ -65,9 +108,8 @@ class MessageArchive:
|
|
65
108
|
sender: Optional[str] = None,
|
66
109
|
flip=False,
|
67
110
|
):
|
68
|
-
for msg in
|
69
|
-
self.
|
70
|
-
self.db_id,
|
111
|
+
for msg in self.__store.get_messages(
|
112
|
+
self.room_pk,
|
71
113
|
before_id=before_id,
|
72
114
|
after_id=after_id,
|
73
115
|
ids=ids,
|
@@ -87,11 +129,13 @@ class MessageArchive:
|
|
87
129
|
:return:
|
88
130
|
"""
|
89
131
|
reply = iq.reply()
|
90
|
-
messages =
|
132
|
+
messages = self.__store.get_first_and_last(self.room_pk)
|
91
133
|
if messages:
|
92
134
|
for x, m in [("start", messages[0]), ("end", messages[-1])]:
|
93
135
|
reply["mam_metadata"][x]["id"] = m.id
|
94
|
-
reply["mam_metadata"][x]["timestamp"] = m.sent_on
|
136
|
+
reply["mam_metadata"][x]["timestamp"] = m.sent_on.replace(
|
137
|
+
tzinfo=timezone.utc
|
138
|
+
)
|
95
139
|
else:
|
96
140
|
reply.enable("mam_metadata")
|
97
141
|
reply.send()
|
slidge/group/bookmarks.py
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
import abc
|
2
2
|
import logging
|
3
|
-
from typing import TYPE_CHECKING, Generic, Type
|
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
|
9
10
|
from ..core.mixins.lock import NamedLockMixin
|
11
|
+
from ..db.models import Room
|
10
12
|
from ..util import SubclassableOnce
|
11
13
|
from ..util.types import LegacyGroupIdType, LegacyMUCType
|
14
|
+
from .archive import MessageArchive
|
12
15
|
from .room import LegacyMUC
|
13
16
|
|
14
17
|
if TYPE_CHECKING:
|
@@ -27,17 +30,15 @@ class LegacyBookmarks(
|
|
27
30
|
def __init__(self, session: "BaseSession"):
|
28
31
|
self.session = session
|
29
32
|
self.xmpp = session.xmpp
|
30
|
-
self.
|
31
|
-
|
32
|
-
self._mucs_by_legacy_id = dict[LegacyGroupIdType, LegacyMUCType]()
|
33
|
-
self._mucs_by_bare_jid = dict[str, LegacyMUCType]()
|
33
|
+
self.user_jid = session.user_jid
|
34
|
+
self.__store = self.xmpp.store.rooms
|
34
35
|
|
35
36
|
self._muc_class: Type[LegacyMUCType] = LegacyMUC.get_self_or_unique_subclass()
|
36
37
|
|
37
|
-
self._user_nick: str = self.session.
|
38
|
+
self._user_nick: str = self.session.user_jid.node
|
38
39
|
|
39
40
|
super().__init__()
|
40
|
-
self.log = logging.getLogger(f"{self.
|
41
|
+
self.log = logging.getLogger(f"{self.user_jid.bare}:bookmarks")
|
41
42
|
self.ready = self.session.xmpp.loop.create_future()
|
42
43
|
if not self.xmpp.GROUPS:
|
43
44
|
self.ready.set_result(True)
|
@@ -50,21 +51,12 @@ class LegacyBookmarks(
|
|
50
51
|
def user_nick(self, nick: str):
|
51
52
|
self._user_nick = nick
|
52
53
|
|
53
|
-
def __iter__(self):
|
54
|
-
|
54
|
+
def __iter__(self) -> Iterator[LegacyMUCType]:
|
55
|
+
for stored in self.__store.get_all(user_pk=self.session.user_pk):
|
56
|
+
yield self._muc_class.from_store(self.session, stored)
|
55
57
|
|
56
58
|
def __repr__(self):
|
57
|
-
return f"<Bookmarks of {self.
|
58
|
-
|
59
|
-
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
|
-
await muc.avatar_wrap_update_info()
|
62
|
-
if not muc.user_nick:
|
63
|
-
muc.user_nick = self._user_nick
|
64
|
-
self.log.debug("MUC created: %r", muc)
|
65
|
-
self._mucs_by_legacy_id[muc.legacy_id] = muc
|
66
|
-
self._mucs_by_bare_jid[muc.jid.bare] = muc
|
67
|
-
return muc
|
59
|
+
return f"<Bookmarks of {self.user_jid}>"
|
68
60
|
|
69
61
|
async def legacy_id_to_jid_local_part(self, legacy_id: LegacyGroupIdType):
|
70
62
|
return await self.legacy_id_to_jid_username(legacy_id)
|
@@ -94,38 +86,63 @@ class LegacyBookmarks(
|
|
94
86
|
return _unescape_node(username)
|
95
87
|
|
96
88
|
async def by_jid(self, jid: JID) -> LegacyMUCType:
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
self.
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
89
|
+
if jid.resource:
|
90
|
+
jid = JID(jid.bare)
|
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)
|
94
|
+
if self.get_lock(("legacy_id", legacy_id)):
|
95
|
+
self.log.debug("Not instantiating %s after all", jid)
|
96
|
+
return await self.by_legacy_id(legacy_id)
|
97
|
+
|
98
|
+
with self.__store.session():
|
99
|
+
stored = self.__store.get_by_jid(self.session.user_pk, jid)
|
100
|
+
return await self.__update_muc(stored, legacy_id, jid)
|
101
|
+
|
102
|
+
def by_jid_only_if_exists(self, jid: JID) -> Optional[LegacyMUCType]:
|
103
|
+
with self.__store.session():
|
104
|
+
stored = self.__store.get_by_jid(self.session.user_pk, jid)
|
105
|
+
if stored is not None and stored.updated:
|
106
|
+
return self._muc_class.from_store(self.session, stored)
|
107
|
+
return None
|
112
108
|
|
113
109
|
async def by_legacy_id(self, legacy_id: LegacyGroupIdType) -> LegacyMUCType:
|
114
110
|
async with self.lock(("legacy_id", legacy_id)):
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
111
|
+
local = await self.legacy_id_to_jid_local_part(legacy_id)
|
112
|
+
jid = JID(f"{local}@{self.xmpp.boundjid}")
|
113
|
+
if self.get_lock(("bare", jid.bare)):
|
114
|
+
self.log.debug("Not instantiating %s after all", legacy_id)
|
115
|
+
return await self.by_jid(jid)
|
116
|
+
|
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 XMPPError:
|
137
|
+
raise
|
138
|
+
except Exception as e:
|
139
|
+
raise XMPPError("internal-server-error", str(e))
|
140
|
+
if not muc.user_nick:
|
141
|
+
muc.user_nick = self._user_nick
|
142
|
+
self.log.debug("MUC created: %r", muc)
|
143
|
+
muc.pk = self.__store.update(muc)
|
144
|
+
muc.archive = MessageArchive(muc.pk, self.xmpp.store.mam)
|
145
|
+
return muc
|
129
146
|
|
130
147
|
@abc.abstractmethod
|
131
148
|
async def fill(self):
|
@@ -145,19 +162,26 @@ class LegacyBookmarks(
|
|
145
162
|
" LegacyBookmarks.fill() was not overridden."
|
146
163
|
)
|
147
164
|
|
148
|
-
def remove(
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
165
|
+
async def remove(
|
166
|
+
self,
|
167
|
+
muc: LegacyMUC,
|
168
|
+
reason="You left this group from the official client.",
|
169
|
+
kick=True,
|
170
|
+
) -> None:
|
171
|
+
"""
|
172
|
+
Delete everything about a specific group.
|
173
|
+
|
174
|
+
This should be called when the user leaves the group from the official
|
175
|
+
app.
|
176
|
+
|
177
|
+
:param muc: The MUC to remove.
|
178
|
+
:param reason: Optionally, a reason why this group was removed.
|
179
|
+
:param kick: Whether the user should be kicked from this group. Set this
|
180
|
+
to False in case you do this somewhere else in your code, eg, on
|
181
|
+
receiving the confirmation that the group was deleted.
|
182
|
+
"""
|
183
|
+
assert muc.pk is not None
|
184
|
+
if kick:
|
185
|
+
user_participant = await muc.get_user_participant()
|
186
|
+
user_participant.kick(reason)
|
187
|
+
self.__store.delete(muc.pk)
|
slidge/group/participant.py
CHANGED
@@ -6,7 +6,7 @@ import warnings
|
|
6
6
|
from copy import copy
|
7
7
|
from datetime import datetime
|
8
8
|
from functools import cached_property
|
9
|
-
from typing import TYPE_CHECKING, Optional, Union
|
9
|
+
from typing import TYPE_CHECKING, Optional, Self, Union
|
10
10
|
|
11
11
|
from slixmpp import JID, InvalidJID, Message, Presence
|
12
12
|
from slixmpp.plugins.xep_0045.stanza import MUCAdminItem
|
@@ -15,10 +15,16 @@ from slixmpp.types import MessageTypes, OptJid
|
|
15
15
|
from slixmpp.util.stringprep_profiles import StringPrepError, prohibit_output
|
16
16
|
|
17
17
|
from ..contact import LegacyContact
|
18
|
-
from ..core.mixins import
|
18
|
+
from ..core.mixins import (
|
19
|
+
ChatterDiscoMixin,
|
20
|
+
MessageMixin,
|
21
|
+
PresenceMixin,
|
22
|
+
StoredAttributeMixin,
|
23
|
+
)
|
24
|
+
from ..db.models import Participant
|
19
25
|
from ..util import SubclassableOnce, strip_illegal_chars
|
20
|
-
from ..util.sql import CachedPresence
|
21
26
|
from ..util.types import (
|
27
|
+
CachedPresence,
|
22
28
|
Hat,
|
23
29
|
LegacyMessageType,
|
24
30
|
MessageOrPresenceTypeVar,
|
@@ -40,6 +46,7 @@ def strip_non_printable(nickname: str):
|
|
40
46
|
|
41
47
|
|
42
48
|
class LegacyParticipant(
|
49
|
+
StoredAttributeMixin,
|
43
50
|
PresenceMixin,
|
44
51
|
MessageMixin,
|
45
52
|
ChatterDiscoMixin,
|
@@ -53,6 +60,7 @@ class LegacyParticipant(
|
|
53
60
|
_can_send_carbon = False
|
54
61
|
USE_STANZA_ID = True
|
55
62
|
STRIP_SHORT_DELAY = False
|
63
|
+
pk: int
|
56
64
|
|
57
65
|
def __init__(
|
58
66
|
self,
|
@@ -63,12 +71,11 @@ class LegacyParticipant(
|
|
63
71
|
role: MucRole = "participant",
|
64
72
|
affiliation: MucAffiliation = "member",
|
65
73
|
):
|
74
|
+
self.session = session = muc.session
|
75
|
+
self.xmpp = session.xmpp
|
66
76
|
super().__init__()
|
67
77
|
self._hats = list[Hat]()
|
68
78
|
self.muc = muc
|
69
|
-
self.session = session = muc.session
|
70
|
-
self.user = session.user
|
71
|
-
self.xmpp = session.xmpp
|
72
79
|
self._role = role
|
73
80
|
self._affiliation = affiliation
|
74
81
|
self.is_user: bool = is_user
|
@@ -84,8 +91,19 @@ class LegacyParticipant(
|
|
84
91
|
# if we didn't, we send it before the first message.
|
85
92
|
# this way, event in plugins that don't map "user has joined" events,
|
86
93
|
# we send a "join"-presence from the participant before the first message
|
87
|
-
self.
|
88
|
-
self.log = logging.getLogger(f"{self.
|
94
|
+
self._presence_sent: bool = False
|
95
|
+
self.log = logging.getLogger(f"{self.user_jid.bare}:{self.jid}")
|
96
|
+
self.__part_store = self.xmpp.store.participants
|
97
|
+
|
98
|
+
@property
|
99
|
+
def contact_pk(self) -> Optional[int]: # type:ignore
|
100
|
+
if self.contact:
|
101
|
+
return self.contact.contact_pk
|
102
|
+
return None
|
103
|
+
|
104
|
+
@property
|
105
|
+
def user_jid(self):
|
106
|
+
return self.session.user_jid
|
89
107
|
|
90
108
|
def __repr__(self):
|
91
109
|
return f"<Participant '{self.nickname}'/'{self.jid}' of '{self.muc}'>"
|
@@ -99,7 +117,10 @@ class LegacyParticipant(
|
|
99
117
|
if self._affiliation == affiliation:
|
100
118
|
return
|
101
119
|
self._affiliation = affiliation
|
102
|
-
if not self.
|
120
|
+
if not self.muc._participants_filled:
|
121
|
+
return
|
122
|
+
self.__part_store.set_affiliation(self.pk, affiliation)
|
123
|
+
if not self._presence_sent:
|
103
124
|
return
|
104
125
|
self.send_last_presence(force=True, no_cache_online=True)
|
105
126
|
|
@@ -126,7 +147,10 @@ class LegacyParticipant(
|
|
126
147
|
if self._role == role:
|
127
148
|
return
|
128
149
|
self._role = role
|
129
|
-
if not self.
|
150
|
+
if not self.muc._participants_filled:
|
151
|
+
return
|
152
|
+
self.__part_store.set_role(self.pk, role)
|
153
|
+
if not self._presence_sent:
|
130
154
|
return
|
131
155
|
self.send_last_presence(force=True, no_cache_online=True)
|
132
156
|
|
@@ -134,7 +158,10 @@ class LegacyParticipant(
|
|
134
158
|
if self._hats == hats:
|
135
159
|
return
|
136
160
|
self._hats = hats
|
137
|
-
if not self.
|
161
|
+
if not self.muc._participants_filled:
|
162
|
+
return
|
163
|
+
self.__part_store.set_hats(self.pk, hats)
|
164
|
+
if not self._presence_sent:
|
138
165
|
return
|
139
166
|
self.send_last_presence(force=True, no_cache_online=True)
|
140
167
|
|
@@ -143,6 +170,7 @@ class LegacyParticipant(
|
|
143
170
|
|
144
171
|
if self.is_system:
|
145
172
|
self.jid = j
|
173
|
+
self._nickname_no_illegal = ""
|
146
174
|
return
|
147
175
|
|
148
176
|
nickname = unescaped_nickname
|
@@ -170,9 +198,6 @@ class LegacyParticipant(
|
|
170
198
|
except InvalidJID:
|
171
199
|
j.resource = strip_non_printable(nickname)
|
172
200
|
|
173
|
-
if nickname != unescaped_nickname:
|
174
|
-
self.muc._participants_by_escaped_nicknames[nickname] = self # type:ignore
|
175
|
-
|
176
201
|
self.jid = j
|
177
202
|
|
178
203
|
def send_configuration_change(self, codes: tuple[int]):
|
@@ -213,12 +238,8 @@ class LegacyParticipant(
|
|
213
238
|
|
214
239
|
kwargs["status_codes"] = set()
|
215
240
|
p = self._make_presence(ptype="available", last_seen=last_seen, **kwargs)
|
216
|
-
self.__add_nick_element(p)
|
217
241
|
self._send(p)
|
218
242
|
|
219
|
-
if old:
|
220
|
-
self.muc.rename_participant(old, new_nickname)
|
221
|
-
|
222
243
|
def _make_presence(
|
223
244
|
self,
|
224
245
|
*,
|
@@ -240,14 +261,14 @@ class LegacyParticipant(
|
|
240
261
|
if user_full_jid:
|
241
262
|
p["muc"]["jid"] = user_full_jid
|
242
263
|
else:
|
243
|
-
jid = copy(self.
|
264
|
+
jid = copy(self.user_jid)
|
244
265
|
try:
|
245
266
|
jid.resource = next(
|
246
|
-
iter(self.muc.
|
267
|
+
iter(self.muc.get_user_resources()) # type:ignore
|
247
268
|
)
|
248
269
|
except StopIteration:
|
249
270
|
jid.resource = "pseudo-resource"
|
250
|
-
p["muc"]["jid"] = self.
|
271
|
+
p["muc"]["jid"] = self.user_jid
|
251
272
|
codes.add(100)
|
252
273
|
elif self.contact:
|
253
274
|
p["muc"]["jid"] = self.contact.jid
|
@@ -257,7 +278,7 @@ class LegacyParticipant(
|
|
257
278
|
warnings.warn(
|
258
279
|
f"Private group but no 1:1 JID associated to '{self}'",
|
259
280
|
)
|
260
|
-
if self.is_user and (hash_ := self.session.avatar_hash):
|
281
|
+
if self.is_user and (hash_ := self.session.user.avatar_hash):
|
261
282
|
p["vcard_temp_update"]["photo"] = hash_
|
262
283
|
p["muc"]["status_codes"] = codes
|
263
284
|
return p
|
@@ -273,7 +294,7 @@ class LegacyParticipant(
|
|
273
294
|
archive_only
|
274
295
|
or self.is_system
|
275
296
|
or self.is_user
|
276
|
-
or self.
|
297
|
+
or self._presence_sent
|
277
298
|
or stanza["subject"]
|
278
299
|
):
|
279
300
|
return
|
@@ -298,13 +319,16 @@ class LegacyParticipant(
|
|
298
319
|
stanza: MessageOrPresenceTypeVar,
|
299
320
|
full_jid: Optional[JID] = None,
|
300
321
|
archive_only=False,
|
322
|
+
legacy_msg_id=None,
|
301
323
|
**send_kwargs,
|
302
324
|
) -> MessageOrPresenceTypeVar:
|
303
325
|
stanza["occupant-id"]["id"] = self.__occupant_id
|
304
|
-
|
305
|
-
|
326
|
+
self.__add_nick_element(stanza)
|
327
|
+
if not self.is_user and isinstance(stanza, Presence):
|
328
|
+
if stanza["type"] == "unavailable" and not self._presence_sent:
|
306
329
|
return stanza # type:ignore
|
307
|
-
self.
|
330
|
+
self._presence_sent = True
|
331
|
+
self.__part_store.set_presence_sent(self.pk)
|
308
332
|
if full_jid:
|
309
333
|
stanza["to"] = full_jid
|
310
334
|
self.__send_presence_if_needed(stanza, full_jid, archive_only)
|
@@ -314,8 +338,8 @@ class LegacyParticipant(
|
|
314
338
|
else:
|
315
339
|
stanza.send()
|
316
340
|
else:
|
317
|
-
if isinstance(stanza, Message):
|
318
|
-
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)
|
319
343
|
if archive_only:
|
320
344
|
return stanza
|
321
345
|
for user_full_jid in self.muc.user_full_jids():
|
@@ -332,7 +356,7 @@ class LegacyParticipant(
|
|
332
356
|
item["role"] = self.role
|
333
357
|
if not self.muc.is_anonymous:
|
334
358
|
if self.is_user:
|
335
|
-
item["jid"] = self.
|
359
|
+
item["jid"] = self.user_jid.bare
|
336
360
|
elif self.contact:
|
337
361
|
item["jid"] = self.contact.jid.bare
|
338
362
|
else:
|
@@ -344,11 +368,11 @@ class LegacyParticipant(
|
|
344
368
|
)
|
345
369
|
return item
|
346
370
|
|
347
|
-
def __add_nick_element(self,
|
371
|
+
def __add_nick_element(self, stanza: Union[Presence, Message]):
|
348
372
|
if (nick := self._nickname_no_illegal) != self.jid.resource:
|
349
373
|
n = self.xmpp.plugin["xep_0172"].stanza.UserNick()
|
350
374
|
n["nick"] = nick
|
351
|
-
|
375
|
+
stanza.append(n)
|
352
376
|
|
353
377
|
def _get_last_presence(self) -> Optional[CachedPresence]:
|
354
378
|
own = super()._get_last_presence()
|
@@ -400,7 +424,6 @@ class LegacyParticipant(
|
|
400
424
|
)
|
401
425
|
if presence_id:
|
402
426
|
p["id"] = presence_id
|
403
|
-
self.__add_nick_element(p)
|
404
427
|
self._send(p, full_jid)
|
405
428
|
|
406
429
|
def leave(self):
|
@@ -409,17 +432,17 @@ class LegacyParticipant(
|
|
409
432
|
"""
|
410
433
|
self.muc.remove_participant(self)
|
411
434
|
|
412
|
-
def kick(self):
|
435
|
+
def kick(self, reason: str | None = None):
|
413
436
|
"""
|
414
437
|
Call this when the participant is kicked from the room
|
415
438
|
"""
|
416
|
-
self.muc.remove_participant(self, kick=True)
|
439
|
+
self.muc.remove_participant(self, kick=True, reason=reason)
|
417
440
|
|
418
|
-
def ban(self):
|
441
|
+
def ban(self, reason: str | None = None):
|
419
442
|
"""
|
420
443
|
Call this when the participant is banned from the room
|
421
444
|
"""
|
422
|
-
self.muc.remove_participant(self, ban=True)
|
445
|
+
self.muc.remove_participant(self, ban=True, reason=reason)
|
423
446
|
|
424
447
|
def get_disco_info(self, jid: OptJid = None, node: Optional[str] = None):
|
425
448
|
if self.contact is not None:
|
@@ -427,13 +450,21 @@ class LegacyParticipant(
|
|
427
450
|
return super().get_disco_info()
|
428
451
|
|
429
452
|
def moderate(self, legacy_msg_id: LegacyMessageType, reason: Optional[str] = None):
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
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)
|
437
468
|
|
438
469
|
def set_room_subject(
|
439
470
|
self,
|
@@ -444,7 +475,7 @@ class LegacyParticipant(
|
|
444
475
|
):
|
445
476
|
if update_muc:
|
446
477
|
self.muc._subject = subject # type: ignore
|
447
|
-
self.muc.subject_setter = self
|
478
|
+
self.muc.subject_setter = self.nickname
|
448
479
|
self.muc.subject_date = when
|
449
480
|
|
450
481
|
msg = self._make_message()
|
@@ -454,5 +485,41 @@ class LegacyParticipant(
|
|
454
485
|
msg["subject"] = subject or str(self.muc.name)
|
455
486
|
self._send(msg, full_jid)
|
456
487
|
|
488
|
+
@classmethod
|
489
|
+
def from_store(
|
490
|
+
cls,
|
491
|
+
session,
|
492
|
+
stored: Participant,
|
493
|
+
contact: Optional[LegacyContact] = None,
|
494
|
+
muc: Optional["LegacyMUC"] = None,
|
495
|
+
) -> Self:
|
496
|
+
from slidge.group.room import LegacyMUC
|
497
|
+
|
498
|
+
if muc is None:
|
499
|
+
muc = LegacyMUC.get_self_or_unique_subclass().from_store(
|
500
|
+
session, stored.room
|
501
|
+
)
|
502
|
+
part = cls(
|
503
|
+
muc,
|
504
|
+
stored.nickname,
|
505
|
+
role=stored.role,
|
506
|
+
affiliation=stored.affiliation,
|
507
|
+
)
|
508
|
+
part.pk = stored.id
|
509
|
+
if contact is not None:
|
510
|
+
part.contact = contact
|
511
|
+
elif stored.contact is not None:
|
512
|
+
contact = LegacyContact.get_self_or_unique_subclass().from_store(
|
513
|
+
session, stored.contact
|
514
|
+
)
|
515
|
+
part.contact = contact
|
516
|
+
|
517
|
+
part.is_user = stored.is_user
|
518
|
+
if (data := stored.extra_attributes) is not None:
|
519
|
+
muc.deserialize_extra_attributes(data)
|
520
|
+
part._presence_sent = stored.presence_sent
|
521
|
+
part._hats = [Hat(h.uri, h.title) for h in stored.hats]
|
522
|
+
return part
|
523
|
+
|
457
524
|
|
458
525
|
log = logging.getLogger(__name__)
|