slidge 0.2.12__py3-none-any.whl → 0.3.0a0__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/__init__.py +5 -2
- slidge/command/adhoc.py +9 -3
- slidge/command/admin.py +16 -12
- slidge/command/base.py +16 -12
- slidge/command/chat_command.py +25 -16
- slidge/command/user.py +7 -8
- slidge/contact/contact.py +119 -209
- slidge/contact/roster.py +106 -105
- slidge/core/config.py +2 -43
- slidge/core/dispatcher/caps.py +9 -2
- slidge/core/dispatcher/disco.py +13 -3
- slidge/core/dispatcher/message/__init__.py +1 -1
- slidge/core/dispatcher/message/chat_state.py +17 -8
- slidge/core/dispatcher/message/marker.py +7 -5
- slidge/core/dispatcher/message/message.py +117 -92
- slidge/core/dispatcher/muc/__init__.py +1 -1
- slidge/core/dispatcher/muc/admin.py +4 -4
- slidge/core/dispatcher/muc/mam.py +10 -6
- slidge/core/dispatcher/muc/misc.py +4 -2
- slidge/core/dispatcher/muc/owner.py +5 -3
- slidge/core/dispatcher/muc/ping.py +3 -1
- slidge/core/dispatcher/presence.py +21 -15
- slidge/core/dispatcher/registration.py +20 -12
- slidge/core/dispatcher/search.py +7 -3
- slidge/core/dispatcher/session_dispatcher.py +13 -5
- slidge/core/dispatcher/util.py +37 -27
- slidge/core/dispatcher/vcard.py +7 -4
- slidge/core/gateway.py +168 -84
- slidge/core/mixins/__init__.py +1 -11
- slidge/core/mixins/attachment.py +163 -148
- slidge/core/mixins/avatar.py +100 -177
- slidge/core/mixins/db.py +50 -2
- slidge/core/mixins/message.py +19 -17
- slidge/core/mixins/message_maker.py +29 -15
- slidge/core/mixins/message_text.py +38 -30
- slidge/core/mixins/presence.py +91 -35
- slidge/core/pubsub.py +42 -47
- slidge/core/session.py +88 -57
- slidge/db/alembic/versions/0337c90c0b96_unify_legacy_xmpp_id_mappings.py +183 -0
- slidge/db/alembic/versions/4dbd23a3f868_new_avatar_store.py +56 -0
- slidge/db/alembic/versions/54ce3cde350c_use_hash_for_avatar_filenames.py +50 -0
- slidge/db/alembic/versions/58b98dacf819_refactor.py +118 -0
- slidge/db/alembic/versions/75a62b74b239_ditch_hats_table.py +74 -0
- slidge/db/avatar.py +150 -119
- slidge/db/meta.py +33 -22
- slidge/db/models.py +68 -117
- slidge/db/store.py +412 -1094
- slidge/group/archive.py +61 -54
- slidge/group/bookmarks.py +74 -55
- slidge/group/participant.py +135 -142
- slidge/group/room.py +315 -312
- slidge/main.py +28 -18
- slidge/migration.py +2 -12
- slidge/slixfix/__init__.py +20 -4
- slidge/slixfix/delivery_receipt.py +6 -4
- slidge/slixfix/link_preview/link_preview.py +1 -1
- slidge/slixfix/link_preview/stanza.py +1 -1
- slidge/slixfix/roster.py +5 -7
- slidge/slixfix/xep_0077/register.py +8 -8
- slidge/slixfix/xep_0077/stanza.py +7 -7
- slidge/slixfix/xep_0100/gateway.py +12 -13
- slidge/slixfix/xep_0153/vcard_avatar.py +1 -1
- slidge/slixfix/xep_0292/vcard4.py +1 -1
- slidge/util/archive_msg.py +11 -5
- slidge/util/conf.py +23 -20
- slidge/util/jid_escaping.py +1 -1
- slidge/{core/mixins → util}/lock.py +6 -6
- slidge/util/test.py +30 -29
- slidge/util/types.py +22 -18
- slidge/util/util.py +19 -22
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/METADATA +1 -1
- slidge-0.3.0a0.dist-info/RECORD +117 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/WHEEL +1 -1
- slidge-0.2.12.dist-info/RECORD +0 -112
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/entry_points.txt +0 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/licenses/LICENSE +0 -0
- {slidge-0.2.12.dist-info → slidge-0.3.0a0.dist-info}/top_level.txt +0 -0
slidge/contact/roster.py
CHANGED
@@ -4,14 +4,16 @@ import warnings
|
|
4
4
|
from typing import TYPE_CHECKING, AsyncIterator, Generic, Iterator, Optional, Type
|
5
5
|
|
6
6
|
from slixmpp import JID
|
7
|
-
from slixmpp.exceptions import IqError, IqTimeout
|
7
|
+
from slixmpp.exceptions import IqError, IqTimeout
|
8
|
+
from sqlalchemy.orm import Session
|
9
|
+
from sqlalchemy.orm import Session as OrmSession
|
8
10
|
|
9
|
-
from ..
|
10
|
-
from ..db.models import Contact
|
11
|
-
from ..db.store import ContactStore
|
11
|
+
from ..db.models import Contact, GatewayUser
|
12
12
|
from ..util import SubclassableOnce
|
13
13
|
from ..util.jid_escaping import ESCAPE_TABLE, unescape_node
|
14
|
+
from ..util.lock import NamedLockMixin
|
14
15
|
from ..util.types import LegacyContactType, LegacyUserIdType
|
16
|
+
from ..util.util import timeit
|
15
17
|
from .contact import LegacyContact
|
16
18
|
|
17
19
|
if TYPE_CHECKING:
|
@@ -28,7 +30,7 @@ class LegacyRoster(
|
|
28
30
|
metaclass=SubclassableOnce,
|
29
31
|
):
|
30
32
|
"""
|
31
|
-
Virtual roster of a gateway user
|
33
|
+
Virtual roster of a gateway user that allows to represent all
|
32
34
|
of their contacts as singleton instances (if used properly and not too bugged).
|
33
35
|
|
34
36
|
Every :class:`.BaseSession` instance will have its own :class:`.LegacyRoster` instance
|
@@ -42,29 +44,38 @@ class LegacyRoster(
|
|
42
44
|
if you need some characters when translation JID user parts and legacy IDs.
|
43
45
|
"""
|
44
46
|
|
45
|
-
|
46
|
-
self._contact_cls: Type[LegacyContactType] = (
|
47
|
-
LegacyContact.get_self_or_unique_subclass()
|
48
|
-
)
|
49
|
-
self._contact_cls.xmpp = session.xmpp
|
50
|
-
self.__store: ContactStore = session.xmpp.store.contacts
|
47
|
+
_contact_cls: Type[LegacyContactType]
|
51
48
|
|
52
|
-
|
53
|
-
|
49
|
+
def __init__(self, session: "BaseSession") -> None:
|
50
|
+
super().__init__()
|
51
|
+
|
52
|
+
self.log = logging.getLogger(f"{session.user_jid.bare}:roster")
|
54
53
|
self.user_legacy_id: Optional[LegacyUserIdType] = None
|
55
|
-
self.ready: asyncio.Future[bool] =
|
54
|
+
self.ready: asyncio.Future[bool] = session.xmpp.loop.create_future()
|
55
|
+
|
56
|
+
self.session = session
|
56
57
|
self.__filling = False
|
57
|
-
super().__init__()
|
58
58
|
|
59
|
-
|
59
|
+
@property
|
60
|
+
def user(self) -> GatewayUser:
|
61
|
+
return self.session.user
|
62
|
+
|
63
|
+
def orm(self) -> Session:
|
64
|
+
return self.session.xmpp.store.session()
|
65
|
+
|
66
|
+
def from_store(self, stored: Contact) -> LegacyContactType:
|
67
|
+
return self._contact_cls(self.session, stored=stored)
|
68
|
+
|
69
|
+
def __repr__(self) -> str:
|
60
70
|
return f"<Roster of {self.session.user_jid}>"
|
61
71
|
|
62
72
|
def __iter__(self) -> Iterator[LegacyContactType]:
|
63
|
-
with self.
|
64
|
-
for stored in
|
65
|
-
|
73
|
+
with self.orm() as orm:
|
74
|
+
for stored in orm.query(Contact).filter_by(user=self.user).all():
|
75
|
+
if stored.updated:
|
76
|
+
yield self.from_store(stored)
|
66
77
|
|
67
|
-
def known_contacts(self, only_friends=True) -> dict[str, LegacyContactType]:
|
78
|
+
def known_contacts(self, only_friends: bool = True) -> dict[str, LegacyContactType]:
|
68
79
|
if only_friends:
|
69
80
|
return {c.jid.bare: c for c in self if c.is_friend}
|
70
81
|
return {c.jid.bare: c for c in self}
|
@@ -81,27 +92,52 @@ class LegacyRoster(
|
|
81
92
|
# :return:
|
82
93
|
# """
|
83
94
|
username = contact_jid.node
|
95
|
+
contact_jid = JID(contact_jid.bare)
|
84
96
|
async with self.lock(("username", username)):
|
85
97
|
legacy_id = await self.jid_username_to_legacy_id(username)
|
86
|
-
log.debug("Contact %s not found", contact_jid)
|
87
98
|
if self.get_lock(("legacy_id", legacy_id)):
|
88
|
-
log.debug("Already updating %s", contact_jid)
|
99
|
+
self.log.debug("Already updating %s via by_legacy_id()", contact_jid)
|
89
100
|
return await self.by_legacy_id(legacy_id)
|
90
101
|
|
91
|
-
with self.
|
92
|
-
stored =
|
93
|
-
|
102
|
+
with self.orm() as orm:
|
103
|
+
stored = (
|
104
|
+
orm.query(Contact)
|
105
|
+
.filter_by(user=self.user, jid=contact_jid)
|
106
|
+
.one_or_none()
|
107
|
+
)
|
108
|
+
if stored is None:
|
109
|
+
stored = Contact(
|
110
|
+
user_account_id=self.session.user_pk,
|
111
|
+
legacy_id=legacy_id,
|
112
|
+
jid=contact_jid,
|
113
|
+
)
|
114
|
+
return await self.__update_if_needed(stored)
|
115
|
+
|
116
|
+
async def __update_if_needed(self, stored: Contact) -> LegacyContactType:
|
117
|
+
contact = self.from_store(stored)
|
118
|
+
if contact.stored.updated:
|
119
|
+
return contact
|
120
|
+
|
121
|
+
with contact.updating_info():
|
122
|
+
await contact.update_info()
|
123
|
+
|
124
|
+
if contact.cached_presence is not None:
|
125
|
+
contact._store_last_presence(contact.cached_presence)
|
126
|
+
return contact
|
94
127
|
|
95
128
|
def by_jid_only_if_exists(self, contact_jid: JID) -> LegacyContactType | None:
|
96
|
-
with self.
|
97
|
-
stored =
|
129
|
+
with self.orm() as orm:
|
130
|
+
stored = (
|
131
|
+
orm.query(Contact)
|
132
|
+
.filter_by(user=self.user, jid=contact_jid)
|
133
|
+
.one_or_none()
|
134
|
+
)
|
98
135
|
if stored is not None and stored.updated:
|
99
|
-
return self.
|
136
|
+
return self.from_store(stored)
|
100
137
|
return None
|
101
138
|
|
102
|
-
|
103
|
-
|
104
|
-
) -> LegacyContactType:
|
139
|
+
@timeit
|
140
|
+
async def by_legacy_id(self, legacy_id: LegacyUserIdType) -> LegacyContactType:
|
105
141
|
"""
|
106
142
|
Retrieve a contact by their legacy_id
|
107
143
|
|
@@ -110,11 +146,6 @@ class LegacyRoster(
|
|
110
146
|
legacy user ID.
|
111
147
|
|
112
148
|
:param legacy_id:
|
113
|
-
:param args: arbitrary additional positional arguments passed to the contact constructor.
|
114
|
-
Requires subclassing LegacyContact.__init__ to accept those.
|
115
|
-
This is useful for networks where you fetch the contact list and information
|
116
|
-
about these contacts in a single request
|
117
|
-
:param kwargs: arbitrary keyword arguments passed to the contact constructor
|
118
149
|
:return:
|
119
150
|
"""
|
120
151
|
if legacy_id == self.user_legacy_id:
|
@@ -122,56 +153,26 @@ class LegacyRoster(
|
|
122
153
|
async with self.lock(("legacy_id", legacy_id)):
|
123
154
|
username = await self.legacy_id_to_jid_username(legacy_id)
|
124
155
|
if self.get_lock(("username", username)):
|
125
|
-
log.debug("Already updating %s", username)
|
156
|
+
self.log.debug("Already updating %s via by_jid()", username)
|
157
|
+
|
126
158
|
jid = JID()
|
127
159
|
jid.node = username
|
128
160
|
jid.domain = self.session.xmpp.boundjid.bare
|
129
161
|
return await self.by_jid(jid)
|
130
162
|
|
131
|
-
with self.
|
132
|
-
stored =
|
133
|
-
|
163
|
+
with self.orm() as orm:
|
164
|
+
stored = (
|
165
|
+
orm.query(Contact)
|
166
|
+
.filter_by(user=self.user, legacy_id=str(legacy_id))
|
167
|
+
.one_or_none()
|
134
168
|
)
|
135
|
-
|
136
|
-
|
169
|
+
if stored is None:
|
170
|
+
stored = Contact(
|
171
|
+
user_account_id=self.session.user_pk,
|
172
|
+
legacy_id=str(legacy_id),
|
173
|
+
jid=JID(f"{username}@{self.session.xmpp.boundjid.bare}"),
|
137
174
|
)
|
138
|
-
|
139
|
-
async def __update_contact(
|
140
|
-
self,
|
141
|
-
stored: Contact | None,
|
142
|
-
legacy_id: LegacyUserIdType,
|
143
|
-
username: str,
|
144
|
-
*a,
|
145
|
-
**kw,
|
146
|
-
) -> LegacyContactType:
|
147
|
-
if stored is None:
|
148
|
-
contact = self._contact_cls(self.session, legacy_id, username, *a, **kw)
|
149
|
-
else:
|
150
|
-
contact = self._contact_cls.from_store(self.session, stored, *a, **kw)
|
151
|
-
if stored.updated:
|
152
|
-
return contact
|
153
|
-
|
154
|
-
try:
|
155
|
-
with contact.updating_info():
|
156
|
-
await contact.avatar_wrap_update_info()
|
157
|
-
except XMPPError:
|
158
|
-
raise
|
159
|
-
except Exception as e:
|
160
|
-
raise XMPPError("internal-server-error", str(e))
|
161
|
-
contact._caps_ver = await contact.get_caps_ver(contact.jid)
|
162
|
-
contact.contact_pk = self.__store.update(contact, commit=not self.__filling)
|
163
|
-
return contact
|
164
|
-
|
165
|
-
async def by_stanza(self, s) -> LegacyContact:
|
166
|
-
# """
|
167
|
-
# Retrieve a contact by the destination of a stanza
|
168
|
-
#
|
169
|
-
# See :meth:`slidge.Roster.by_legacy_id` for more info.
|
170
|
-
#
|
171
|
-
# :param s:
|
172
|
-
# :return:
|
173
|
-
# """
|
174
|
-
return await self.by_jid(s.get_to())
|
175
|
+
return await self.__update_if_needed(stored)
|
175
176
|
|
176
177
|
async def legacy_id_to_jid_username(self, legacy_id: LegacyUserIdType) -> str:
|
177
178
|
"""
|
@@ -198,9 +199,10 @@ class LegacyRoster(
|
|
198
199
|
:param jid_username: User part of a JID, ie "user" in "user@example.com"
|
199
200
|
:return: An identifier for the user on the legacy network.
|
200
201
|
"""
|
201
|
-
return unescape_node(jid_username)
|
202
|
+
return unescape_node(jid_username) # type:ignore
|
202
203
|
|
203
|
-
|
204
|
+
@timeit
|
205
|
+
async def _fill(self, orm: OrmSession):
|
204
206
|
try:
|
205
207
|
if hasattr(self.session.xmpp, "TEST_MODE"):
|
206
208
|
# dirty hack to avoid mocking xmpp server replies to this
|
@@ -213,30 +215,29 @@ class LegacyRoster(
|
|
213
215
|
except (PermissionError, IqError, IqTimeout):
|
214
216
|
user_roster = None
|
215
217
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
)
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
orm.commit()
|
218
|
+
self.__filling = True
|
219
|
+
async for contact in self.fill():
|
220
|
+
if user_roster is None:
|
221
|
+
continue
|
222
|
+
item = contact.get_roster_item()
|
223
|
+
old = user_roster.get(contact.jid.bare)
|
224
|
+
if old is not None and all(
|
225
|
+
old[k] == item[contact.jid.bare].get(k)
|
226
|
+
for k in ("subscription", "groups", "name")
|
227
|
+
):
|
228
|
+
self.log.debug("No need to update roster")
|
229
|
+
continue
|
230
|
+
self.log.debug("Updating roster")
|
231
|
+
try:
|
232
|
+
await self.session.xmpp["xep_0356"].set_roster(
|
233
|
+
self.session.user_jid.bare,
|
234
|
+
item,
|
235
|
+
)
|
236
|
+
except (PermissionError, IqError, IqTimeout) as e:
|
237
|
+
warnings.warn(f"Could not add to roster: {e}")
|
238
|
+
else:
|
239
|
+
contact.added_to_roster = True
|
240
|
+
orm.commit()
|
240
241
|
self.__filling = False
|
241
242
|
|
242
243
|
async def fill(self) -> AsyncIterator[LegacyContact]:
|
slidge/core/config.py
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
from datetime import timedelta
|
2
2
|
from pathlib import Path
|
3
|
-
from typing import Optional
|
3
|
+
from typing import Optional, Self
|
4
4
|
|
5
5
|
from slixmpp import JID as JIDType
|
6
6
|
|
7
7
|
|
8
8
|
class _TimedeltaSeconds(timedelta):
|
9
|
-
def __new__(cls, s: str):
|
9
|
+
def __new__(cls, s: str) -> Self:
|
10
10
|
return super().__new__(cls, seconds=int(s))
|
11
11
|
|
12
12
|
|
@@ -75,16 +75,6 @@ UPLOAD_SERVICE__DOC = (
|
|
75
75
|
"discovery."
|
76
76
|
)
|
77
77
|
|
78
|
-
NO_ROSTER_PUSH = False
|
79
|
-
NO_ROSTER_PUSH__DOC = "Do not fill users' rosters with legacy contacts automatically"
|
80
|
-
|
81
|
-
ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK = True
|
82
|
-
ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK__DOC = (
|
83
|
-
"If True, legacy contacts will send a presence request subscription "
|
84
|
-
"when privileged roster push does not work, eg, if XEP-0356 (privileged "
|
85
|
-
"entity) is not available for the component."
|
86
|
-
)
|
87
|
-
|
88
78
|
AVATAR_SIZE = 200
|
89
79
|
AVATAR_SIZE__DOC = (
|
90
80
|
"Maximum image size (width and height), image ratio will be preserved"
|
@@ -138,27 +128,9 @@ PARTIAL_REGISTRATION_TIMEOUT__DOC = (
|
|
138
128
|
"a single step registration process is not enough."
|
139
129
|
)
|
140
130
|
|
141
|
-
LAST_SEEN_FALLBACK = False
|
142
|
-
LAST_SEEN_FALLBACK__DOC = (
|
143
|
-
"When using XEP-0319 (Last User Interaction in Presence), use the presence status"
|
144
|
-
" to display the last seen information in the presence status. Useful for clients"
|
145
|
-
" that do not implement XEP-0319. Because of implementation details, this can increase"
|
146
|
-
" RAM usage and might be deprecated in the future. Ask your client dev for XEP-0319"
|
147
|
-
" support ;o)."
|
148
|
-
)
|
149
|
-
|
150
131
|
QR_TIMEOUT = 60
|
151
132
|
QR_TIMEOUT__DOC = "Timeout for QR code flashing confirmation."
|
152
133
|
|
153
|
-
LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND = False
|
154
|
-
LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND__DOC = (
|
155
|
-
"If the legacy service does not support last message correction but supports"
|
156
|
-
" message retractions, slidge can 'retract' the edited message when you edit from"
|
157
|
-
" an XMPP client, as a workaround. This may only work for editing messages"
|
158
|
-
" **once**. If the legacy service does not support retractions and this is set to"
|
159
|
-
" true, when XMPP clients attempt to correct, this will send a new message."
|
160
|
-
)
|
161
|
-
|
162
134
|
FIX_FILENAME_SUFFIX_MIME_TYPE = False
|
163
135
|
FIX_FILENAME_SUFFIX_MIME_TYPE__DOC = (
|
164
136
|
"Fix the Filename suffix based on the Mime Type of the file. Some clients (eg"
|
@@ -180,25 +152,12 @@ LOG_FORMAT__DOC = (
|
|
180
152
|
MAM_MAX_DAYS = 7
|
181
153
|
MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
|
182
154
|
|
183
|
-
CORRECTION_EMPTY_BODY_AS_RETRACTION = True
|
184
|
-
CORRECTION_EMPTY_BODY_AS_RETRACTION__DOC = (
|
185
|
-
"Treat last message correction to empty message as a retraction. "
|
186
|
-
"(this is what cheogram does for retraction)"
|
187
|
-
)
|
188
|
-
|
189
155
|
ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH = 200
|
190
156
|
ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH__DOC = (
|
191
157
|
"Some legacy network provide ridiculously long filenames, strip above this limit, "
|
192
158
|
"preserving suffix."
|
193
159
|
)
|
194
160
|
|
195
|
-
ALWAYS_INVITE_WHEN_ADDING_BOOKMARKS = True
|
196
|
-
ALWAYS_INVITE_WHEN_ADDING_BOOKMARKS__DOC = (
|
197
|
-
"Send an invitation to join MUCs when adding them to the bookmarks. While this "
|
198
|
-
"should not be necessary, it helps with clients that do not support :xep:`0402` "
|
199
|
-
"or that do not respect the auto-join flag."
|
200
|
-
)
|
201
|
-
|
202
161
|
AVATAR_RESAMPLING_THREADS = 2
|
203
162
|
AVATAR_RESAMPLING_THREADS__DOC = (
|
204
163
|
"Number of additional threads to use for avatar resampling. Even in a single-core "
|
slidge/core/dispatcher/caps.py
CHANGED
@@ -12,7 +12,9 @@ if TYPE_CHECKING:
|
|
12
12
|
|
13
13
|
|
14
14
|
class CapsMixin(DispatcherMixin):
|
15
|
-
|
15
|
+
__slots__: list[str] = []
|
16
|
+
|
17
|
+
def __init__(self, xmpp: "BaseGateway") -> None:
|
16
18
|
super().__init__(xmpp)
|
17
19
|
xmpp.del_filter("out", xmpp.plugin["xep_0115"]._filter_add_caps)
|
18
20
|
xmpp.add_filter("out", self._filter_add_caps) # type:ignore
|
@@ -51,7 +53,12 @@ class CapsMixin(DispatcherMixin):
|
|
51
53
|
contact = await session.contacts.by_jid(pfrom)
|
52
54
|
except XMPPError:
|
53
55
|
return stanza
|
54
|
-
|
56
|
+
if contact.stored.caps_ver:
|
57
|
+
ver = contact.stored.caps_ver
|
58
|
+
else:
|
59
|
+
ver = await contact.get_caps_ver(pfrom)
|
60
|
+
contact.stored.caps_ver = ver
|
61
|
+
contact.commit()
|
55
62
|
else:
|
56
63
|
ver = await caps.get_verstring(pfrom)
|
57
64
|
|
slidge/core/dispatcher/disco.py
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
import logging
|
2
2
|
from typing import TYPE_CHECKING, Any, Optional
|
3
3
|
|
4
|
+
import sqlalchemy as sa
|
4
5
|
from slixmpp.exceptions import XMPPError
|
5
6
|
from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
|
6
7
|
from slixmpp.types import OptJid
|
7
8
|
|
9
|
+
from ...db.models import Room
|
8
10
|
from .util import DispatcherMixin
|
9
11
|
|
10
12
|
if TYPE_CHECKING:
|
@@ -12,7 +14,9 @@ if TYPE_CHECKING:
|
|
12
14
|
|
13
15
|
|
14
16
|
class DiscoMixin(DispatcherMixin):
|
15
|
-
|
17
|
+
__slots__: list[str] = []
|
18
|
+
|
19
|
+
def __init__(self, xmpp: "BaseGateway") -> None:
|
16
20
|
super().__init__(xmpp)
|
17
21
|
|
18
22
|
xmpp.plugin["xep_0030"].set_node_handler(
|
@@ -63,8 +67,14 @@ class DiscoMixin(DispatcherMixin):
|
|
63
67
|
session = await self._get_session_from_jid(ifrom)
|
64
68
|
|
65
69
|
d = DiscoItems()
|
66
|
-
|
67
|
-
|
70
|
+
with self.xmpp.store.session() as orm:
|
71
|
+
for room in orm.execute(
|
72
|
+
sa.select(Room)
|
73
|
+
.options(sa.orm.load_only(Room.jid, Room.name))
|
74
|
+
.filter_by(user=session.user)
|
75
|
+
.order_by(Room.name)
|
76
|
+
).scalars():
|
77
|
+
d.add_item(room.jid, name=room.name)
|
68
78
|
|
69
79
|
return d
|
70
80
|
|
@@ -5,12 +5,15 @@ from ..util import DispatcherMixin, exceptions_to_xmpp_errors
|
|
5
5
|
|
6
6
|
|
7
7
|
class ChatStateMixin(DispatcherMixin):
|
8
|
+
__slots__: list[str] = []
|
9
|
+
|
8
10
|
def __init__(self, xmpp) -> None:
|
9
11
|
super().__init__(xmpp)
|
10
12
|
xmpp.add_event_handler("chatstate_active", self.on_chatstate_active)
|
11
13
|
xmpp.add_event_handler("chatstate_inactive", self.on_chatstate_inactive)
|
12
14
|
xmpp.add_event_handler("chatstate_composing", self.on_chatstate_composing)
|
13
15
|
xmpp.add_event_handler("chatstate_paused", self.on_chatstate_paused)
|
16
|
+
xmpp.add_event_handler("chatstate_gone", self.on_chatstate_gone)
|
14
17
|
|
15
18
|
@exceptions_to_xmpp_errors
|
16
19
|
async def on_chatstate_active(self, msg: StanzaBase) -> None:
|
@@ -18,23 +21,29 @@ class ChatStateMixin(DispatcherMixin):
|
|
18
21
|
if msg["body"]:
|
19
22
|
# if there is a body, it's handled in on_legacy_message()
|
20
23
|
return
|
21
|
-
session,
|
22
|
-
await session.on_active(
|
24
|
+
session, recipient, thread = await self._get_session_recipient_thread(msg)
|
25
|
+
await session.on_active(recipient, thread)
|
23
26
|
|
24
27
|
@exceptions_to_xmpp_errors
|
25
28
|
async def on_chatstate_inactive(self, msg: StanzaBase) -> None:
|
26
29
|
assert isinstance(msg, Message)
|
27
|
-
session,
|
28
|
-
await session.on_inactive(
|
30
|
+
session, recipient, thread = await self._get_session_recipient_thread(msg)
|
31
|
+
await session.on_inactive(recipient, thread)
|
29
32
|
|
30
33
|
@exceptions_to_xmpp_errors
|
31
34
|
async def on_chatstate_composing(self, msg: StanzaBase) -> None:
|
32
35
|
assert isinstance(msg, Message)
|
33
|
-
session,
|
34
|
-
await session.on_composing(
|
36
|
+
session, recipient, thread = await self._get_session_recipient_thread(msg)
|
37
|
+
await session.on_composing(recipient, thread)
|
35
38
|
|
36
39
|
@exceptions_to_xmpp_errors
|
37
40
|
async def on_chatstate_paused(self, msg: StanzaBase) -> None:
|
38
41
|
assert isinstance(msg, Message)
|
39
|
-
session,
|
40
|
-
await session.on_paused(
|
42
|
+
session, recipient, thread = await self._get_session_recipient_thread(msg)
|
43
|
+
await session.on_paused(recipient, thread)
|
44
|
+
|
45
|
+
@exceptions_to_xmpp_errors
|
46
|
+
async def on_chatstate_gone(self, msg: StanzaBase) -> None:
|
47
|
+
assert isinstance(msg, Message)
|
48
|
+
session, recipient, thread = await self._get_session_recipient_thread(msg)
|
49
|
+
await session.on_gone(recipient, thread)
|
@@ -3,10 +3,12 @@ from slixmpp.xmlstream import StanzaBase
|
|
3
3
|
|
4
4
|
from ....group.room import LegacyMUC
|
5
5
|
from ....util.types import Recipient
|
6
|
-
from ..util import DispatcherMixin,
|
6
|
+
from ..util import DispatcherMixin, exceptions_to_xmpp_errors, get_recipient
|
7
7
|
|
8
8
|
|
9
9
|
class MarkerMixin(DispatcherMixin):
|
10
|
+
__slots__: list[str] = []
|
11
|
+
|
10
12
|
def __init__(self, xmpp) -> None:
|
11
13
|
super().__init__(xmpp)
|
12
14
|
xmpp.add_event_handler("marker_displayed", self.on_marker_displayed)
|
@@ -20,11 +22,11 @@ class MarkerMixin(DispatcherMixin):
|
|
20
22
|
assert isinstance(msg, Message)
|
21
23
|
session = await self._get_session(msg)
|
22
24
|
|
23
|
-
e: Recipient = await
|
25
|
+
e: Recipient = await get_recipient(session, msg)
|
24
26
|
legacy_thread = await self._xmpp_to_legacy_thread(session, msg, e)
|
25
27
|
displayed_msg_id = msg["displayed"]["id"]
|
26
28
|
if not isinstance(e, LegacyMUC) and self.xmpp.MARK_ALL_MESSAGES:
|
27
|
-
to_mark = e.get_msg_xmpp_id_up_to(displayed_msg_id)
|
29
|
+
to_mark = e.get_msg_xmpp_id_up_to(displayed_msg_id)
|
28
30
|
if to_mark is None:
|
29
31
|
session.log.debug("Can't mark all messages up to %s", displayed_msg_id)
|
30
32
|
to_mark = [displayed_msg_id]
|
@@ -32,7 +34,7 @@ class MarkerMixin(DispatcherMixin):
|
|
32
34
|
to_mark = [displayed_msg_id]
|
33
35
|
for xmpp_id in to_mark:
|
34
36
|
await session.on_displayed(
|
35
|
-
e, self._xmpp_msg_id_to_legacy(session, xmpp_id), legacy_thread
|
37
|
+
e, self._xmpp_msg_id_to_legacy(session, xmpp_id, e), legacy_thread
|
36
38
|
)
|
37
39
|
if isinstance(e, LegacyMUC):
|
38
40
|
await e.echo(msg, None)
|
@@ -58,5 +60,5 @@ class MarkerMixin(DispatcherMixin):
|
|
58
60
|
|
59
61
|
stanza_id = msg["pubsub_event"]["items"]["item"]["displayed"]["stanza_id"]["id"]
|
60
62
|
await session.on_displayed(
|
61
|
-
chat, self._xmpp_msg_id_to_legacy(session, stanza_id)
|
63
|
+
chat, self._xmpp_msg_id_to_legacy(session, stanza_id, chat)
|
62
64
|
)
|