slidge 0.3.1__py3-none-any.whl → 0.3.2__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/contact/contact.py +1 -1
- slidge/contact/roster.py +6 -1
- slidge/core/config.py +43 -9
- slidge/core/dispatcher/message/message.py +1 -0
- slidge/core/dispatcher/presence.py +1 -0
- slidge/core/dispatcher/registration.py +1 -1
- slidge/core/dispatcher/search.py +1 -5
- slidge/core/dispatcher/util.py +3 -1
- slidge/core/gateway.py +12 -1
- slidge/core/mixins/attachment.py +62 -8
- slidge/core/mixins/message_maker.py +1 -1
- slidge/core/mixins/message_text.py +1 -1
- slidge/core/mixins/presence.py +26 -8
- slidge/core/session.py +2 -0
- slidge/db/models.py +1 -1
- slidge/db/store.py +29 -1
- slidge/group/room.py +5 -2
- slidge/util/test.py +3 -2
- slidge/util/util.py +6 -4
- {slidge-0.3.1.dist-info → slidge-0.3.2.dist-info}/METADATA +1 -1
- {slidge-0.3.1.dist-info → slidge-0.3.2.dist-info}/RECORD +25 -25
- {slidge-0.3.1.dist-info → slidge-0.3.2.dist-info}/WHEEL +0 -0
- {slidge-0.3.1.dist-info → slidge-0.3.2.dist-info}/entry_points.txt +0 -0
- {slidge-0.3.1.dist-info → slidge-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {slidge-0.3.1.dist-info → slidge-0.3.2.dist-info}/top_level.txt +0 -0
slidge/contact/contact.py
CHANGED
@@ -411,7 +411,7 @@ class LegacyContact(
|
|
411
411
|
# we only broadcast pubsub events for contacts added to the roster
|
412
412
|
# so if something was set before, we need to push it now
|
413
413
|
self.added_to_roster = True
|
414
|
-
self.send_last_presence()
|
414
|
+
self.send_last_presence(force=True)
|
415
415
|
|
416
416
|
async def __broadcast_pubsub_items(self) -> None:
|
417
417
|
if not self.is_friend:
|
slidge/contact/roster.py
CHANGED
@@ -4,7 +4,7 @@ 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, XMPPError
|
8
8
|
from sqlalchemy.orm import Session
|
9
9
|
from sqlalchemy.orm import Session as OrmSession
|
10
10
|
|
@@ -92,6 +92,10 @@ class LegacyRoster(
|
|
92
92
|
# :return:
|
93
93
|
# """
|
94
94
|
username = contact_jid.node
|
95
|
+
if not username:
|
96
|
+
raise XMPPError(
|
97
|
+
"bad-request", "Contacts must have a local part in their JID"
|
98
|
+
)
|
95
99
|
contact_jid = JID(contact_jid.bare)
|
96
100
|
async with self.lock(("username", username)):
|
97
101
|
legacy_id = await self.jid_username_to_legacy_id(username)
|
@@ -239,6 +243,7 @@ class LegacyRoster(
|
|
239
243
|
warnings.warn(f"Could not add to roster: {e}")
|
240
244
|
else:
|
241
245
|
contact.added_to_roster = True
|
246
|
+
contact.send_last_presence(force=True)
|
242
247
|
orm.commit()
|
243
248
|
self.__filling = False
|
244
249
|
|
slidge/core/config.py
CHANGED
@@ -1,22 +1,26 @@
|
|
1
|
-
from datetime import timedelta
|
2
1
|
from pathlib import Path
|
3
|
-
from typing import Optional
|
2
|
+
from typing import Optional
|
4
3
|
|
5
4
|
from slixmpp import JID as JIDType
|
6
5
|
|
6
|
+
# REQUIRED, so not default value
|
7
7
|
|
8
|
-
class _TimedeltaSeconds(timedelta):
|
9
|
-
def __new__(cls, s: str) -> Self:
|
10
|
-
return super().__new__(cls, seconds=int(s))
|
11
8
|
|
9
|
+
class _Categories:
|
10
|
+
MANDATORY = (0, "Mandatory settings")
|
11
|
+
BASE = (10, "Basic configuration")
|
12
|
+
ATTACHMENTS = (20, "Attachments")
|
13
|
+
LOG = (30, "Logging")
|
14
|
+
ADVANCED = (40, "Advanced settings")
|
12
15
|
|
13
|
-
# REQUIRED, so not default value
|
14
16
|
|
15
17
|
LEGACY_MODULE: str
|
16
18
|
LEGACY_MODULE__DOC = (
|
17
|
-
"Importable python module containing (at least) "
|
18
|
-
"
|
19
|
+
"Importable python module containing (at least) a BaseGateway and a LegacySession subclass. "
|
20
|
+
"NB: this is not needed if you use a gateway-specific entrypoint, e.g., `slidgram` or "
|
21
|
+
"`python -m slidgram`."
|
19
22
|
)
|
23
|
+
LEGACY_MODULE__CATEGORY = _Categories.BASE
|
20
24
|
|
21
25
|
SERVER: str = "localhost"
|
22
26
|
SERVER__DOC = (
|
@@ -27,17 +31,21 @@ SERVER__DOC = (
|
|
27
31
|
"you change this."
|
28
32
|
)
|
29
33
|
SERVER__SHORT = "s"
|
34
|
+
SERVER__CATEGORY = _Categories.BASE
|
30
35
|
|
31
36
|
SECRET: str
|
32
37
|
SECRET__DOC = "The gateway component's secret (required to connect to the XMPP server)"
|
38
|
+
SECRET__CATEGORY = _Categories.MANDATORY
|
33
39
|
|
34
40
|
JID: JIDType
|
35
41
|
JID__DOC = "The gateway component's JID"
|
36
42
|
JID__SHORT = "j"
|
43
|
+
JID__CATEGORY = _Categories.MANDATORY
|
37
44
|
|
38
45
|
PORT: str = "5347"
|
39
46
|
PORT__DOC = "The XMPP server's port for incoming component connections"
|
40
47
|
PORT__SHORT = "p"
|
48
|
+
PORT__CATEGORY = _Categories.BASE
|
41
49
|
|
42
50
|
# Dynamic default (depends on other values)
|
43
51
|
|
@@ -47,6 +55,7 @@ HOME_DIR__DOC = (
|
|
47
55
|
"Defaults to /var/lib/slidge/${SLIDGE_JID}. "
|
48
56
|
)
|
49
57
|
HOME_DIR__DYNAMIC_DEFAULT = True
|
58
|
+
HOME_DIR__CATEGORY = _Categories.BASE
|
50
59
|
|
51
60
|
DB_URL: str
|
52
61
|
DB_URL__DOC = (
|
@@ -54,6 +63,7 @@ DB_URL__DOC = (
|
|
54
63
|
"Defaults to sqlite:///${HOME_DIR}/slidge.sqlite"
|
55
64
|
)
|
56
65
|
DB_URL__DYNAMIC_DEFAULT = True
|
66
|
+
DB_URL__CATEGORY = _Categories.ADVANCED
|
57
67
|
|
58
68
|
USER_JID_VALIDATOR: str
|
59
69
|
USER_JID_VALIDATOR__DOC = (
|
@@ -62,11 +72,13 @@ USER_JID_VALIDATOR__DOC = (
|
|
62
72
|
"you probably want to change that to .*@example.com"
|
63
73
|
)
|
64
74
|
USER_JID_VALIDATOR__DYNAMIC_DEFAULT = True
|
75
|
+
USER_JID_VALIDATOR__CATEGORY = _Categories.BASE
|
65
76
|
|
66
77
|
# Optional, so default value + type hint if default is None
|
67
78
|
|
68
79
|
ADMINS: tuple[JIDType, ...] = ()
|
69
80
|
ADMINS__DOC = "JIDs of the gateway admins"
|
81
|
+
ADMINS__CATEGORY = _Categories.BASE
|
70
82
|
|
71
83
|
UPLOAD_SERVICE: Optional[str] = None
|
72
84
|
UPLOAD_SERVICE__DOC = (
|
@@ -74,11 +86,13 @@ UPLOAD_SERVICE__DOC = (
|
|
74
86
|
"This is optional, as it should be automatically determined via service"
|
75
87
|
"discovery."
|
76
88
|
)
|
89
|
+
UPLOAD_SERVICE__CATEGORY = _Categories.ATTACHMENTS
|
77
90
|
|
78
91
|
AVATAR_SIZE = 200
|
79
92
|
AVATAR_SIZE__DOC = (
|
80
93
|
"Maximum image size (width and height), image ratio will be preserved"
|
81
94
|
)
|
95
|
+
AVATAR_SIZE__CATEGORY = _Categories.ADVANCED
|
82
96
|
|
83
97
|
USE_ATTACHMENT_ORIGINAL_URLS = False
|
84
98
|
USE_ATTACHMENT_ORIGINAL_URLS__DOC = (
|
@@ -86,11 +100,13 @@ USE_ATTACHMENT_ORIGINAL_URLS__DOC = (
|
|
86
100
|
"let XMPP clients directly download them from this URL. Note that this will "
|
87
101
|
"probably leak your client IP to the legacy network."
|
88
102
|
)
|
103
|
+
USE_ATTACHMENT_ORIGINAL_URLS__CATEGORY = _Categories.ATTACHMENTS
|
89
104
|
|
90
105
|
UPLOAD_REQUESTER: Optional[str] = None
|
91
106
|
UPLOAD_REQUESTER__DOC = (
|
92
107
|
"Set which JID should request the upload slots. Defaults to the component JID."
|
93
108
|
)
|
109
|
+
UPLOAD_REQUESTER__CATEGORY = _Categories.ATTACHMENTS
|
94
110
|
|
95
111
|
NO_UPLOAD_PATH: Optional[str] = None
|
96
112
|
NO_UPLOAD_PATH__DOC = (
|
@@ -98,38 +114,45 @@ NO_UPLOAD_PATH__DOC = (
|
|
98
114
|
"You need to set NO_UPLOAD_URL_PREFIX too if you use this option, and configure "
|
99
115
|
"an web server to serve files in this dir."
|
100
116
|
)
|
117
|
+
NO_UPLOAD_PATH__CATEGORY = _Categories.ATTACHMENTS
|
101
118
|
|
102
119
|
NO_UPLOAD_URL_PREFIX: Optional[str] = None
|
103
120
|
NO_UPLOAD_URL_PREFIX__DOC = (
|
104
121
|
"Base URL that servers files in the dir set in the no-upload-path option, "
|
105
122
|
"eg https://example.com:666/slidge-attachments/"
|
106
123
|
)
|
124
|
+
NO_UPLOAD_URL_PREFIX__CATEGORY = _Categories.ATTACHMENTS
|
107
125
|
|
108
126
|
NO_UPLOAD_METHOD: str = "copy"
|
109
127
|
NO_UPLOAD_METHOD__DOC = (
|
110
128
|
"Whether to 'copy', 'move', 'hardlink' or 'symlink' the files in no-upload-path."
|
111
129
|
)
|
130
|
+
NO_UPLOAD_METHOD__CATEGORY = _Categories.ATTACHMENTS
|
112
131
|
|
113
132
|
NO_UPLOAD_FILE_READ_OTHERS = False
|
114
133
|
NO_UPLOAD_FILE_READ_OTHERS__DOC = (
|
115
134
|
"After writing a file in NO_UPLOAD_PATH, change its permission so that 'others' can"
|
116
135
|
" read it."
|
117
136
|
)
|
137
|
+
NO_UPLOAD_FILE_READ_OTHERS__CATEGORY = _Categories.ATTACHMENTS
|
118
138
|
|
119
|
-
IGNORE_DELAY_THRESHOLD =
|
139
|
+
IGNORE_DELAY_THRESHOLD = 300
|
120
140
|
IGNORE_DELAY_THRESHOLD__DOC = (
|
121
141
|
"Threshold, in seconds, below which the <delay> information is stripped "
|
122
142
|
"out of emitted stanzas."
|
123
143
|
)
|
144
|
+
IGNORE_DELAY_THRESHOLD__CATEGORY = _Categories.ADVANCED
|
124
145
|
|
125
146
|
PARTIAL_REGISTRATION_TIMEOUT = 3600
|
126
147
|
PARTIAL_REGISTRATION_TIMEOUT__DOC = (
|
127
148
|
"Timeout before registration and login. Only useful for legacy networks where "
|
128
149
|
"a single step registration process is not enough."
|
129
150
|
)
|
151
|
+
PARTIAL_REGISTRATION_TIMEOUT__CATEGORY = _Categories.ADVANCED
|
130
152
|
|
131
153
|
QR_TIMEOUT = 60
|
132
154
|
QR_TIMEOUT__DOC = "Timeout for QR code flashing confirmation."
|
155
|
+
QR_TIMEOUT__CATEGORY = _Categories.ADVANCED
|
133
156
|
|
134
157
|
FIX_FILENAME_SUFFIX_MIME_TYPE = False
|
135
158
|
FIX_FILENAME_SUFFIX_MIME_TYPE__DOC = (
|
@@ -138,9 +161,11 @@ FIX_FILENAME_SUFFIX_MIME_TYPE__DOC = (
|
|
138
161
|
" Therefore the MIME Type of the file is checked, if the suffix is not valid for"
|
139
162
|
" that MIME Type, a valid one will be picked."
|
140
163
|
)
|
164
|
+
FIX_FILENAME_SUFFIX_MIME_TYPE__CATEGORY = _Categories.ATTACHMENTS
|
141
165
|
|
142
166
|
LOG_FILE: Optional[Path] = None
|
143
167
|
LOG_FILE__DOC = "Log to a file instead of stdout/err"
|
168
|
+
LOG_FILE__CATEGORY = _Categories.LOG
|
144
169
|
|
145
170
|
LOG_FORMAT: str = "%(levelname)s:%(name)s:%(message)s"
|
146
171
|
LOG_FORMAT__DOC = (
|
@@ -148,41 +173,50 @@ LOG_FORMAT__DOC = (
|
|
148
173
|
"https://docs.python.org/3/library/logging.html#logrecord-attributes "
|
149
174
|
"for available options."
|
150
175
|
)
|
176
|
+
LOG_FORMAT__CATEGORY = _Categories.LOG
|
151
177
|
|
152
178
|
MAM_MAX_DAYS = 7
|
153
179
|
MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
|
180
|
+
MAM_MAX_DAYS__CATEGORY = _Categories.BASE
|
154
181
|
|
155
182
|
ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH = 200
|
156
183
|
ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH__DOC = (
|
157
184
|
"Some legacy network provide ridiculously long filenames, strip above this limit, "
|
158
185
|
"preserving suffix."
|
159
186
|
)
|
187
|
+
ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH__CATEGORY = _Categories.ATTACHMENTS
|
160
188
|
|
161
189
|
AVATAR_RESAMPLING_THREADS = 2
|
162
190
|
AVATAR_RESAMPLING_THREADS__DOC = (
|
163
191
|
"Number of additional threads to use for avatar resampling. Even in a single-core "
|
164
192
|
"context, this makes avatar resampling non-blocking."
|
165
193
|
)
|
194
|
+
AVATAR_RESAMPLING_THREADS__CATEGORY = _Categories.ADVANCED
|
166
195
|
|
167
196
|
DEV_MODE = False
|
168
197
|
DEV_MODE__DOC = (
|
169
198
|
"Enables an interactive python shell via chat commands, for admins."
|
170
199
|
"Not safe to use in prod, but great during dev."
|
171
200
|
)
|
201
|
+
DEV_MODE__CATEGORY = _Categories.ADVANCED
|
202
|
+
|
172
203
|
|
173
204
|
STRIP_LEADING_EMOJI_ADHOC = False
|
174
205
|
STRIP_LEADING_EMOJI_ADHOC__DOC = (
|
175
206
|
"Strip the leading emoji in ad-hoc command names, if present, in case you "
|
176
207
|
"are a emoji-hater."
|
177
208
|
)
|
209
|
+
STRIP_LEADING_EMOJI_ADHOC__CATEGORY = _Categories.ADVANCED
|
178
210
|
|
179
211
|
COMPONENT_NAME: Optional[str] = None
|
180
212
|
COMPONENT_NAME__DOC = (
|
181
213
|
"Overrides the default component name with a custom one. This is seen in service discovery and as the nickname "
|
182
214
|
"of the component in chat windows."
|
183
215
|
)
|
216
|
+
COMPONENT_NAME__CATEGORY = _Categories.ADVANCED
|
184
217
|
|
185
218
|
WELCOME_MESSAGE: Optional[str] = None
|
186
219
|
WELCOME_MESSAGE__DOC = (
|
187
220
|
"Overrides the default welcome message received by newly registered users."
|
188
221
|
)
|
222
|
+
WELCOME_MESSAGE__CATEGORY = _Categories.ADVANCED
|
slidge/core/dispatcher/search.py
CHANGED
@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING
|
|
3
3
|
from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
|
4
4
|
from slixmpp.exceptions import XMPPError
|
5
5
|
|
6
|
-
from ...db.models import GatewayUser
|
7
6
|
from .util import DispatcherMixin, exceptions_to_xmpp_errors
|
8
7
|
|
9
8
|
if TYPE_CHECKING:
|
@@ -32,10 +31,7 @@ class SearchMixin(DispatcherMixin):
|
|
32
31
|
"""
|
33
32
|
Prepare the search form using :attr:`.BaseSession.SEARCH_FIELDS`
|
34
33
|
"""
|
35
|
-
|
36
|
-
user = orm.query(GatewayUser).one_or_none()
|
37
|
-
if user is None:
|
38
|
-
raise XMPPError(text="Search is only allowed for registered users")
|
34
|
+
await self._get_session(iq)
|
39
35
|
|
40
36
|
xmpp = self.xmpp
|
41
37
|
|
slidge/core/dispatcher/util.py
CHANGED
@@ -172,7 +172,9 @@ def exceptions_to_xmpp_errors(cb: HandlerType) -> HandlerType:
|
|
172
172
|
except NotImplementedError:
|
173
173
|
log.debug("NotImplementedError raised in %s", cb)
|
174
174
|
raise XMPPError(
|
175
|
-
"feature-not-implemented",
|
175
|
+
"feature-not-implemented",
|
176
|
+
f"{cb.__name__} is not implemented by the legacy module",
|
177
|
+
clear=False,
|
176
178
|
)
|
177
179
|
except Exception as e:
|
178
180
|
log.error("Failed to handle incoming stanza: %s", args, exc_info=e)
|
slidge/core/gateway.py
CHANGED
@@ -330,6 +330,7 @@ class BaseGateway(
|
|
330
330
|
self.jid_validator: re.Pattern = re.compile(config.USER_JID_VALIDATOR)
|
331
331
|
self.qr_pending_registrations = dict[str, asyncio.Future[Optional[dict]]]()
|
332
332
|
|
333
|
+
self.register_plugins()
|
333
334
|
self.__setup_legacy_module_subclasses()
|
334
335
|
|
335
336
|
self.get_session_from_stanza: Callable[
|
@@ -339,7 +340,6 @@ class BaseGateway(
|
|
339
340
|
self._session_cls.from_user
|
340
341
|
)
|
341
342
|
|
342
|
-
self.register_plugins()
|
343
343
|
self.__register_slixmpp_events()
|
344
344
|
self.__register_slixmpp_api()
|
345
345
|
self.roster.set_backend(RosterBackend(self))
|
@@ -390,6 +390,16 @@ class BaseGateway(
|
|
390
390
|
bookmarks_cls = LegacyBookmarks.get_self_or_unique_subclass()
|
391
391
|
roster_cls = LegacyRoster.get_self_or_unique_subclass()
|
392
392
|
|
393
|
+
if contact_cls.REACTIONS_SINGLE_EMOJI: # type:ignore[attr-defined]
|
394
|
+
form = Form()
|
395
|
+
form["type"] = "result"
|
396
|
+
form.add_field(
|
397
|
+
"FORM_TYPE", "hidden", value="urn:xmpp:reactions:0:restrictions"
|
398
|
+
)
|
399
|
+
form.add_field("max_reactions_per_user", value="1", type="number")
|
400
|
+
form.add_field("scope", value="domain")
|
401
|
+
self.plugin["xep_0128"].add_extended_info(data=form)
|
402
|
+
|
393
403
|
session_cls.xmpp = self # type:ignore[attr-defined]
|
394
404
|
contact_cls.xmpp = self # type:ignore[attr-defined]
|
395
405
|
muc_cls.xmpp = self # type:ignore[attr-defined]
|
@@ -1002,6 +1012,7 @@ SLIXMPP_PLUGINS = [
|
|
1002
1012
|
"xep_0106", # JID Escaping
|
1003
1013
|
"xep_0115", # Entity capabilities
|
1004
1014
|
"xep_0122", # Data Forms Validation
|
1015
|
+
"xep_0128", # Service Discovery Extensions
|
1005
1016
|
"xep_0153", # vCard-Based Avatars (for MUC avatars)
|
1006
1017
|
"xep_0172", # User nickname
|
1007
1018
|
"xep_0184", # Message Delivery Receipts
|
slidge/core/mixins/attachment.py
CHANGED
@@ -162,15 +162,12 @@ class AttachmentMixin(TextMessageMixin):
|
|
162
162
|
legacy_file_id=None
|
163
163
|
if attachment.legacy_file_id is None
|
164
164
|
else str(attachment.legacy_file_id),
|
165
|
-
url=attachment.url,
|
165
|
+
url=attachment.url if config.USE_ATTACHMENT_ORIGINAL_URLS else None,
|
166
166
|
)
|
167
167
|
|
168
168
|
async def __get_url(
|
169
169
|
self, attachment: LegacyAttachment, stored: Attachment
|
170
170
|
) -> tuple[bool, Optional[Path], str]:
|
171
|
-
if attachment.url and config.USE_ATTACHMENT_ORIGINAL_URLS:
|
172
|
-
return False, None, attachment.url
|
173
|
-
|
174
171
|
file_name = attachment.name
|
175
172
|
content_type = attachment.content_type
|
176
173
|
file_path = attachment.path
|
@@ -221,7 +218,9 @@ class AttachmentMixin(TextMessageMixin):
|
|
221
218
|
|
222
219
|
assert isinstance(file_path, Path)
|
223
220
|
if config.FIX_FILENAME_SUFFIX_MIME_TYPE:
|
224
|
-
file_name =
|
221
|
+
file_name, content_type = fix_suffix(file_path, content_type, file_name)
|
222
|
+
attachment.content_type = content_type
|
223
|
+
attachment.name = file_name
|
225
224
|
|
226
225
|
if config.NO_UPLOAD_PATH:
|
227
226
|
local_path, new_url = await self.__no_upload(
|
@@ -312,6 +311,50 @@ class AttachmentMixin(TextMessageMixin):
|
|
312
311
|
stored.sfs = str(sfs)
|
313
312
|
msg.append(sfs)
|
314
313
|
|
314
|
+
async def __set_sfs_and_sims_without_download(
|
315
|
+
self, msg: Message, attachment: LegacyAttachment
|
316
|
+
) -> None:
|
317
|
+
assert attachment.url is not None
|
318
|
+
|
319
|
+
if not any(
|
320
|
+
(
|
321
|
+
attachment.content_type,
|
322
|
+
attachment.name,
|
323
|
+
attachment.disposition,
|
324
|
+
)
|
325
|
+
):
|
326
|
+
return
|
327
|
+
|
328
|
+
sims = self.xmpp.plugin["xep_0385"].stanza.Sims()
|
329
|
+
ref = self.xmpp["xep_0372"].stanza.Reference()
|
330
|
+
|
331
|
+
ref["uri"] = attachment.url
|
332
|
+
ref["type"] = "data"
|
333
|
+
sims["sources"].append(ref)
|
334
|
+
sims.enable("file")
|
335
|
+
|
336
|
+
xep_0447_stanza = self.xmpp.plugin["xep_0447"].stanza
|
337
|
+
sfs = xep_0447_stanza.StatelessFileSharing()
|
338
|
+
url_data = xep_0447_stanza.UrlData()
|
339
|
+
url_data["target"] = attachment.url
|
340
|
+
sfs["sources"].append(url_data)
|
341
|
+
sfs.enable("file")
|
342
|
+
|
343
|
+
if attachment.content_type:
|
344
|
+
sims["file"]["media-type"] = attachment.content_type
|
345
|
+
sfs["file"]["media-type"] = attachment.content_type
|
346
|
+
if attachment.caption:
|
347
|
+
sims["file"]["desc"] = attachment.caption
|
348
|
+
sfs["file"]["desc"] = attachment.caption
|
349
|
+
if attachment.name:
|
350
|
+
sims["file"]["name"] = attachment.name
|
351
|
+
sfs["file"]["name"] = attachment.name
|
352
|
+
if attachment.disposition:
|
353
|
+
sfs["disposition"] = attachment.disposition
|
354
|
+
|
355
|
+
msg.append(sims)
|
356
|
+
msg.append(sfs)
|
357
|
+
|
315
358
|
def __send_url(
|
316
359
|
self,
|
317
360
|
msg: Message,
|
@@ -325,6 +368,9 @@ class AttachmentMixin(TextMessageMixin):
|
|
325
368
|
) -> list[Message]:
|
326
369
|
msg["oob"]["url"] = uploaded_url
|
327
370
|
msg["body"] = uploaded_url
|
371
|
+
if msg.get_plugin("sfs", check=True):
|
372
|
+
msg["fallback"].enable("body")
|
373
|
+
msg["fallback"]["for"] = self.xmpp.plugin["xep_0447"].stanza.NAMESPACE
|
328
374
|
if caption:
|
329
375
|
m1 = self._send(msg, carbon=carbon, **kwargs)
|
330
376
|
m2 = self.send_text(
|
@@ -465,7 +511,10 @@ class AttachmentMixin(TextMessageMixin):
|
|
465
511
|
new_url = stored.url
|
466
512
|
else:
|
467
513
|
is_temp, local_path, new_url = await self.__get_url(attachment, stored)
|
468
|
-
if new_url is None
|
514
|
+
if new_url is None or (
|
515
|
+
local_path is not None and local_path.stat().st_size == 0
|
516
|
+
):
|
517
|
+
log.warning("Something went wrong with this attachment: %s", attachment)
|
469
518
|
msg["body"] = (
|
470
519
|
"I tried to send a file, but something went wrong. "
|
471
520
|
"Tell your slidge admin to check the logs."
|
@@ -474,8 +523,13 @@ class AttachmentMixin(TextMessageMixin):
|
|
474
523
|
return None, [self._send(msg, **kwargs)]
|
475
524
|
|
476
525
|
stored.url = new_url
|
477
|
-
|
478
|
-
|
526
|
+
if config.USE_ATTACHMENT_ORIGINAL_URLS and attachment.url:
|
527
|
+
await self.__set_sfs_and_sims_without_download(msg, attachment)
|
528
|
+
else:
|
529
|
+
thumbnail = await self.__set_sims(
|
530
|
+
msg, new_url, local_path, attachment, stored
|
531
|
+
)
|
532
|
+
self.__set_sfs(msg, new_url, local_path, attachment, stored, thumbnail)
|
479
533
|
|
480
534
|
if self.session is not NotImplemented:
|
481
535
|
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
@@ -112,7 +112,7 @@ class MessageMaker(BaseSender):
|
|
112
112
|
if when.tzinfo is None:
|
113
113
|
when = when.astimezone(timezone.utc)
|
114
114
|
if self.STRIP_SHORT_DELAY:
|
115
|
-
delay = datetime.now().astimezone(timezone.utc) - when
|
115
|
+
delay = (datetime.now().astimezone(timezone.utc) - when).seconds
|
116
116
|
if delay < config.IGNORE_DELAY_THRESHOLD:
|
117
117
|
return
|
118
118
|
msg["delay"].set_stamp(when)
|
@@ -191,7 +191,7 @@ class TextMessageMixin(MessageMaker):
|
|
191
191
|
xmpp_id = kwargs.pop("xmpp_id", None)
|
192
192
|
if not xmpp_id:
|
193
193
|
xmpp_id = self._legacy_to_xmpp(legacy_msg_id)
|
194
|
-
self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=emojis)
|
194
|
+
self.xmpp["xep_0444"].set_reactions(msg, to_id=xmpp_id, reactions=set(emojis))
|
195
195
|
self.__add_reaction_fallback(msg, legacy_msg_id, emojis)
|
196
196
|
self._send(msg, **kwargs)
|
197
197
|
|
slidge/core/mixins/presence.py
CHANGED
@@ -46,6 +46,8 @@ def _clear_last_seen_task(contact_pk: int, _task) -> None:
|
|
46
46
|
class PresenceMixin(BaseSender, DBMixin):
|
47
47
|
_ONLY_SEND_PRESENCE_CHANGES = False
|
48
48
|
|
49
|
+
# this attribute actually only exists for contacts and not participants
|
50
|
+
_updating_info: bool
|
49
51
|
stored: Contact | Participant
|
50
52
|
|
51
53
|
def __init__(self, *a, **k) -> None:
|
@@ -55,15 +57,24 @@ class PresenceMixin(BaseSender, DBMixin):
|
|
55
57
|
# to DB at the end of update_info()
|
56
58
|
self.cached_presence: Optional[CachedPresence] = None
|
57
59
|
|
60
|
+
def __is_contact(self) -> bool:
|
61
|
+
return isinstance(self.stored, Contact)
|
62
|
+
|
58
63
|
def __stored(self) -> Contact | None:
|
59
|
-
if
|
64
|
+
if self.__is_contact():
|
65
|
+
assert isinstance(self.stored, Contact)
|
60
66
|
return self.stored
|
61
67
|
else:
|
68
|
+
assert isinstance(self.stored, Participant)
|
62
69
|
try:
|
63
70
|
return self.stored.contact
|
64
71
|
except DetachedInstanceError:
|
65
72
|
with self.xmpp.store.session() as orm:
|
66
73
|
orm.add(self.stored)
|
74
|
+
if self.stored.contact is None:
|
75
|
+
return None
|
76
|
+
orm.refresh(self.stored.contact)
|
77
|
+
orm.merge(self.stored)
|
67
78
|
return self.stored.contact
|
68
79
|
|
69
80
|
@property
|
@@ -85,15 +96,22 @@ class PresenceMixin(BaseSender, DBMixin):
|
|
85
96
|
)
|
86
97
|
|
87
98
|
def _store_last_presence(self, new: CachedPresence) -> None:
|
88
|
-
|
89
|
-
if
|
90
|
-
|
91
|
-
|
92
|
-
|
99
|
+
stored_contact = self.__stored()
|
100
|
+
if stored_contact is None:
|
101
|
+
return
|
102
|
+
stored_contact.cached_presence = True
|
103
|
+
for k, v in new._asdict().items():
|
104
|
+
setattr(stored_contact, k, v)
|
105
|
+
if self.__is_contact() and self._updating_info:
|
106
|
+
return
|
107
|
+
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
93
108
|
try:
|
94
|
-
|
109
|
+
orm.add(stored_contact)
|
95
110
|
except InvalidRequestError:
|
96
|
-
|
111
|
+
stored_contact = orm.merge(stored_contact)
|
112
|
+
orm.add(stored_contact)
|
113
|
+
|
114
|
+
orm.commit()
|
97
115
|
|
98
116
|
def _make_presence(
|
99
117
|
self,
|
slidge/core/session.py
CHANGED
slidge/db/models.py
CHANGED
@@ -7,7 +7,7 @@ import sqlalchemy as sa
|
|
7
7
|
from slixmpp import JID
|
8
8
|
from slixmpp.types import MucAffiliation, MucRole
|
9
9
|
from sqlalchemy import JSON, ForeignKey, Index, UniqueConstraint
|
10
|
-
from sqlalchemy.orm import Mapped,
|
10
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
11
11
|
|
12
12
|
from ..util.types import ClientType, MucType
|
13
13
|
from .meta import Base, JSONSerializable, JSONSerializableTypes
|
slidge/db/store.py
CHANGED
@@ -7,9 +7,10 @@ from datetime import datetime, timedelta, timezone
|
|
7
7
|
from mimetypes import guess_extension
|
8
8
|
from typing import Collection, Iterator, Optional, Type
|
9
9
|
|
10
|
+
import sqlalchemy as sa
|
10
11
|
from slixmpp.exceptions import XMPPError
|
11
12
|
from slixmpp.plugins.xep_0231.stanza import BitsOfBinary
|
12
|
-
from sqlalchemy import Engine, delete, select, update
|
13
|
+
from sqlalchemy import Engine, delete, event, select, update
|
13
14
|
from sqlalchemy.exc import InvalidRequestError
|
14
15
|
from sqlalchemy.orm import Session, attributes, sessionmaker
|
15
16
|
|
@@ -20,6 +21,7 @@ from .meta import Base
|
|
20
21
|
from .models import (
|
21
22
|
ArchivedMessage,
|
22
23
|
ArchivedMessageSource,
|
24
|
+
Avatar,
|
23
25
|
Bob,
|
24
26
|
Contact,
|
25
27
|
ContactSent,
|
@@ -213,6 +215,7 @@ class ContactStore(UpdatedMixin):
|
|
213
215
|
def __init__(self, session: Session) -> None:
|
214
216
|
super().__init__(session)
|
215
217
|
session.execute(update(Contact).values(cached_presence=False))
|
218
|
+
session.execute(update(Contact).values(caps_ver=None))
|
216
219
|
|
217
220
|
@staticmethod
|
218
221
|
def add_to_sent(session: Session, contact_pk: int, msg_id: str) -> None:
|
@@ -589,4 +592,29 @@ class BobStore:
|
|
589
592
|
session.add(row)
|
590
593
|
|
591
594
|
|
595
|
+
@event.listens_for(sa.orm.Session, "after_flush")
|
596
|
+
def _check_avatar_orphans(session, flush_context):
|
597
|
+
if not session.deleted:
|
598
|
+
return
|
599
|
+
|
600
|
+
potentially_orphaned = set()
|
601
|
+
for obj in session.deleted:
|
602
|
+
if isinstance(obj, (Contact, Room)) and obj.avatar_id:
|
603
|
+
potentially_orphaned.add(obj.avatar_id)
|
604
|
+
if not potentially_orphaned:
|
605
|
+
return
|
606
|
+
|
607
|
+
result = session.execute(
|
608
|
+
sa.delete(Avatar).where(
|
609
|
+
sa.and_(
|
610
|
+
Avatar.id.in_(potentially_orphaned),
|
611
|
+
sa.not_(sa.exists().where(Contact.avatar_id == Avatar.id)),
|
612
|
+
sa.not_(sa.exists().where(Room.avatar_id == Avatar.id)),
|
613
|
+
)
|
614
|
+
)
|
615
|
+
)
|
616
|
+
deleted_count = result.rowcount
|
617
|
+
log.debug(f"Auto-deleted %s orphaned avatars", deleted_count)
|
618
|
+
|
619
|
+
|
592
620
|
log = logging.getLogger(__name__)
|
slidge/group/room.py
CHANGED
@@ -296,7 +296,7 @@ class LegacyMUC(
|
|
296
296
|
self, affiliation: Optional[MucAffiliation] = None
|
297
297
|
) -> AsyncIterator[LegacyParticipantType]:
|
298
298
|
await self.__fill_participants()
|
299
|
-
with self.xmpp.store.session(expire_on_commit=False) as orm:
|
299
|
+
with self.xmpp.store.session(expire_on_commit=False, autoflush=False) as orm:
|
300
300
|
orm.add(self.stored)
|
301
301
|
for db_participant in self.stored.participants:
|
302
302
|
if (
|
@@ -524,6 +524,9 @@ class LegacyMUC(
|
|
524
524
|
if s := self.subject:
|
525
525
|
form.add_field("muc#roominfo_subject", value=s)
|
526
526
|
|
527
|
+
if name := self.name:
|
528
|
+
form.add_field("muc#roomconfig_roomname", value=name)
|
529
|
+
|
527
530
|
if self._set_avatar_task is not None:
|
528
531
|
await self._set_avatar_task
|
529
532
|
avatar = self.get_avatar()
|
@@ -652,7 +655,7 @@ class LegacyMUC(
|
|
652
655
|
with orm.no_autoflush:
|
653
656
|
orm.refresh(self.stored, ["participants"])
|
654
657
|
if not user_participant.is_user:
|
655
|
-
self.log.warning("is_user flag not set
|
658
|
+
self.log.warning("is_user flag not set on user_participant")
|
656
659
|
user_participant.is_user = True
|
657
660
|
user_participant.send_initial_presence(
|
658
661
|
user_full_jid,
|
slidge/util/test.py
CHANGED
@@ -42,7 +42,7 @@ from slidge import (
|
|
42
42
|
|
43
43
|
from ..command import Command
|
44
44
|
from ..core import config
|
45
|
-
from ..core.
|
45
|
+
from ..core.session import _sessions
|
46
46
|
from ..db import SlidgeStore
|
47
47
|
from ..db.avatar import avatar_cache
|
48
48
|
from ..db.meta import Base
|
@@ -206,7 +206,7 @@ class SlidgeTest(SlixTestPlus):
|
|
206
206
|
user_jid_validator = ".*"
|
207
207
|
admins: list[str] = []
|
208
208
|
upload_requester = None
|
209
|
-
ignore_delay_threshold =
|
209
|
+
ignore_delay_threshold = 300
|
210
210
|
|
211
211
|
@classmethod
|
212
212
|
def setUpClass(cls) -> None:
|
@@ -281,6 +281,7 @@ class SlidgeTest(SlixTestPlus):
|
|
281
281
|
self.db_engine.echo = False
|
282
282
|
super().tearDown()
|
283
283
|
Base.metadata.drop_all(self.xmpp.store._engine)
|
284
|
+
_sessions.clear()
|
284
285
|
|
285
286
|
def setup_logged_session(self, n_contacts: int = 0) -> None:
|
286
287
|
with self.xmpp.store.session() as orm:
|
slidge/util/util.py
CHANGED
@@ -34,7 +34,9 @@ except ImportError as e:
|
|
34
34
|
)
|
35
35
|
|
36
36
|
|
37
|
-
def fix_suffix(
|
37
|
+
def fix_suffix(
|
38
|
+
path: Path, mime_type: Optional[str], file_name: Optional[str]
|
39
|
+
) -> tuple[str, str]:
|
38
40
|
guessed = magic.from_file(path, mime=True)
|
39
41
|
if guessed == mime_type:
|
40
42
|
log.debug("Magic and given MIME match")
|
@@ -53,15 +55,15 @@ def fix_suffix(path: Path, mime_type: Optional[str], file_name: Optional[str]) -
|
|
53
55
|
|
54
56
|
if suffix in valid_suffix_list:
|
55
57
|
log.debug("Suffix %s is in %s", suffix, valid_suffix_list)
|
56
|
-
return name
|
58
|
+
return str(name), guessed
|
57
59
|
|
58
60
|
valid_suffix = mimetypes.guess_extension(mime_type.split(";")[0], strict=False)
|
59
61
|
if valid_suffix is None:
|
60
62
|
log.debug("No valid suffix found")
|
61
|
-
return name
|
63
|
+
return str(name), guessed
|
62
64
|
|
63
65
|
log.debug("Changing suffix of %s to %s", file_name or path.name, valid_suffix)
|
64
|
-
return name.with_suffix(valid_suffix)
|
66
|
+
return str(name.with_suffix(valid_suffix)), guessed
|
65
67
|
|
66
68
|
|
67
69
|
class SubclassableOnce(type):
|
@@ -12,26 +12,26 @@ slidge/command/chat_command.py,sha256=r_qalygOCsEoS-OgWw8IIlAzTufhXNjduONbeoepUI
|
|
12
12
|
slidge/command/register.py,sha256=BduDI31Kx8CbWWEdjybimTA5Wcfhn-Jkt8sSPsySCpo,6724
|
13
13
|
slidge/command/user.py,sha256=xt7NTb8Fdj_Mx91BgiszSii6QF67LtsyowQ1vhBJSIU,12660
|
14
14
|
slidge/contact/__init__.py,sha256=WMMaHk7UW7YT9EH2LtPdkU0bHQaOp4ikBhbBQskmoc8,191
|
15
|
-
slidge/contact/contact.py,sha256=
|
16
|
-
slidge/contact/roster.py,sha256=
|
15
|
+
slidge/contact/contact.py,sha256=ASNjGj-fxueC7rReMw4qMDUQxJ58W1F8i4Ipu_wfsTE,19617
|
16
|
+
slidge/contact/roster.py,sha256=ZbhPC_5opMXdKrdCaQZiHTErxQD4EyWn5Susl6W7M6k,10003
|
17
17
|
slidge/core/__init__.py,sha256=RG7Jj5JCJERjhqJ31lOLYV-7bH_oblClQD1KF9LsTXo,68
|
18
|
-
slidge/core/config.py,sha256=
|
19
|
-
slidge/core/gateway.py,sha256=
|
18
|
+
slidge/core/config.py,sha256=yFD1RGBZ6Xl115oMqDiF7DFkShlNjF9HEB56Eb_BR_E,7664
|
19
|
+
slidge/core/gateway.py,sha256=G_ZX8ibKFe-oEXXOY-eRVkZjqRb_VdadIhTezwZEUCA,41698
|
20
20
|
slidge/core/pubsub.py,sha256=2Em3PvYz-PX-WM7ZqkEU9estNYCyQ--JdJq22DhrUlA,12145
|
21
|
-
slidge/core/session.py,sha256=
|
21
|
+
slidge/core/session.py,sha256=XJRZrtAmwqqjaqkLEUPK8xUBxdJrKeraubYYNiiuT7I,29897
|
22
22
|
slidge/core/dispatcher/__init__.py,sha256=1EXcjXietUKlxEqdrCWCV3xZ3q_DSsjHoqWrPMbtYao,84
|
23
23
|
slidge/core/dispatcher/caps.py,sha256=gISaHtFwFDXtkSrSsAkZfPiHQyXfmXg3v_YYU0w9iDg,2254
|
24
24
|
slidge/core/dispatcher/disco.py,sha256=xVPyBFnnkON-JjjM1oydRa-dqnDbwAZER2MGQACRhVk,2309
|
25
|
-
slidge/core/dispatcher/presence.py,sha256=
|
26
|
-
slidge/core/dispatcher/registration.py,sha256=
|
27
|
-
slidge/core/dispatcher/search.py,sha256=
|
25
|
+
slidge/core/dispatcher/presence.py,sha256=MkSOY7uZQnujTwhIU_nVxygTul63VBaPjTT3X4iTMu4,7090
|
26
|
+
slidge/core/dispatcher/registration.py,sha256=R4bsyiR8elbLKiFMYv2E54VAUg6qTo-_2CzdbCOZjrY,3419
|
27
|
+
slidge/core/dispatcher/search.py,sha256=j7LGht2M36FfhXRzcdZiV-8OdCVsVHb2vJb6p3pIbpk,3202
|
28
28
|
slidge/core/dispatcher/session_dispatcher.py,sha256=ysgPhns7NgUxhmkgEwRv-yDkSnUIXEdc-FsgqDqQAkE,3466
|
29
|
-
slidge/core/dispatcher/util.py,sha256=
|
29
|
+
slidge/core/dispatcher/util.py,sha256=yxNevqjjLOzApLpqIRFHNQmpsahwZn55qCsNkOSXohc,6225
|
30
30
|
slidge/core/dispatcher/vcard.py,sha256=qHZZShq3Iyvgh1FkcAgGhdKXF5m1VUqeb4EWkY0cbFw,5203
|
31
31
|
slidge/core/dispatcher/message/__init__.py,sha256=gNeZZ0wtCI9JBqMe6tpumwV1TjY0mnPWTJc94uFTN-I,244
|
32
32
|
slidge/core/dispatcher/message/chat_state.py,sha256=RbtM_nlZyvOHusZkDEP0TXA4wMp_N435490eE4wW8U0,2143
|
33
33
|
slidge/core/dispatcher/message/marker.py,sha256=WZyf72_SM6sDGPMEOzhu93o2KbgxSxNF25jwsiM7h2A,2439
|
34
|
-
slidge/core/dispatcher/message/message.py,sha256=
|
34
|
+
slidge/core/dispatcher/message/message.py,sha256=h-oNEC6In3dH2TpMgRVyMAvZjvWDczaq8nowOzVz_R4,16231
|
35
35
|
slidge/core/dispatcher/muc/__init__.py,sha256=60YUr0i8PCZEPyNNTynJueRbbxF5pqzdyVf8z_XFXmM,290
|
36
36
|
slidge/core/dispatcher/muc/admin.py,sha256=1tDZ9hHD6q5SqCjsYOpDimPB3Iyl21YO5RnK1goEGso,3284
|
37
37
|
slidge/core/dispatcher/muc/mam.py,sha256=7vfmMI_mJOIrc9KCbtTibJSowhZTBBFwXWc84Ikpu3I,2994
|
@@ -39,21 +39,21 @@ slidge/core/dispatcher/muc/misc.py,sha256=DgUCSVwcv7kD5xmkS59E-TGf9yWDZiu6NBHgVS
|
|
39
39
|
slidge/core/dispatcher/muc/owner.py,sha256=dDAxpRaA8H_NJQNIyBNixck2oG4GHZeEQqPhKd7MmDQ,3359
|
40
40
|
slidge/core/dispatcher/muc/ping.py,sha256=EgKKS9AvMnW-vINGcoGbtk6NdbN9A7zVaGfT5T7F6YE,1699
|
41
41
|
slidge/core/mixins/__init__.py,sha256=Zea39CCwjJU5XfHwcYPEZ9Sin8z1BZxoV68G2RwC3nE,386
|
42
|
-
slidge/core/mixins/attachment.py,sha256=
|
42
|
+
slidge/core/mixins/attachment.py,sha256=k-4nwteql4TmhsZDLqPVgzVyuaRQzk-JEOG-k7w-VxM,23938
|
43
43
|
slidge/core/mixins/avatar.py,sha256=0E0mQxdTUcJQrYXlBkYqkNl4bYuga4cIC1s4XA2rED8,5559
|
44
44
|
slidge/core/mixins/base.py,sha256=getXMptzJwIc4fEbeMoJCSKcC3awi8UbKnx5FVCthjc,783
|
45
45
|
slidge/core/mixins/db.py,sha256=lryMfRB-1-St2f7TZqW4sCl4xrWPWNlwgvn37T8wQPE,2678
|
46
46
|
slidge/core/mixins/disco.py,sha256=mrYhWO9qpnLMAVtKKqwbDh6CNOH2dPNERpyfmWzZGg8,3684
|
47
47
|
slidge/core/mixins/message.py,sha256=xk4bgiJF9ATe-rgtH4sHU8hUwecBF4KjGIujm90mbXQ,8014
|
48
|
-
slidge/core/mixins/message_maker.py,sha256=
|
49
|
-
slidge/core/mixins/message_text.py,sha256
|
50
|
-
slidge/core/mixins/presence.py,sha256=
|
48
|
+
slidge/core/mixins/message_maker.py,sha256=jdY0dhpN_RcKwajI0XrV194ruNW120Gfdq333B1Fv1o,6556
|
49
|
+
slidge/core/mixins/message_text.py,sha256=Unzmwr8TnPC2uuZ2fFiIalOGpQF7YYyT1AbB8i0l6Qc,9637
|
50
|
+
slidge/core/mixins/presence.py,sha256=hNttLzQ83q6jTQumG-ATpTj85DiUpF50FebYQV_Nazg,10530
|
51
51
|
slidge/core/mixins/recipient.py,sha256=b0uFnpym-hOFgYxGjXT1xQcZ4YRbDSBftPcNWLzSwEI,1336
|
52
52
|
slidge/db/__init__.py,sha256=EBDH1JSEhgqYcli2Bw11CRC749wJk8AOucgBzmhDSvU,105
|
53
53
|
slidge/db/avatar.py,sha256=MXFd1oe0eL5CCUYbc5CpsIcbio3cY3xVoKt39RAoj9I,8240
|
54
54
|
slidge/db/meta.py,sha256=NtjGWcqPfG7uPfwR_cC6_23zyo8ftqgKX8CbP9IBq6U,2185
|
55
|
-
slidge/db/models.py,sha256=
|
56
|
-
slidge/db/store.py,sha256=
|
55
|
+
slidge/db/models.py,sha256=HMSiNY0j-sm0VsikkVED1swPhX687613uW7NEHB8UTs,12826
|
56
|
+
slidge/db/store.py,sha256=yhvYgoXpbueprjVUXBmA25wFyEbzDXMxVzqv8aZi5tw,20308
|
57
57
|
slidge/db/alembic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
58
58
|
slidge/db/alembic/env.py,sha256=hsBlRNs0zF5diSHGRSa8Fi3qRVQDA2rJdR41AEIdvxc,1642
|
59
59
|
slidge/db/alembic/script.py.mako,sha256=MEqL-2qATlST9TAOeYgscMn1uy6HUS9NFvDgl93dMj8,635
|
@@ -62,7 +62,7 @@ slidge/group/__init__.py,sha256=yFt7cHqeaKIMN6f9ZyhhspOcJJvBtLedGv-iICG7lto,258
|
|
62
62
|
slidge/group/archive.py,sha256=gNHpGlUstPtBcnBgOarSYzkZHo9E8bNxl_ZY3wM6wHg,6108
|
63
63
|
slidge/group/bookmarks.py,sha256=eiXtlgirE59dBi1BT1349wCrGuHDkCoak1phCepzkqI,7653
|
64
64
|
slidge/group/participant.py,sha256=2yDJL2bL0w-EiWjq6V96UEhXUgmih5-GpKsoKfG2zdY,18734
|
65
|
-
slidge/group/room.py,sha256=
|
65
|
+
slidge/group/room.py,sha256=QXNEbJBY5gbSpVbr1-u-M2YOoIU1qGhWM7SvtdyiETs,49583
|
66
66
|
slidge/slixfix/__init__.py,sha256=LvaYZQYjr4l_45AYYpod1dB3MUaZye18vKF-4H8Bm20,4758
|
67
67
|
slidge/slixfix/delivery_receipt.py,sha256=JmogxsiXYEbTmCM4fvC5wkQs0jBsaJtKl4j_B_18riE,1415
|
68
68
|
slidge/slixfix/roster.py,sha256=DjjHQqCsKsPChUxV7S0Pm4IAgjfrwgm5tcTdJi3N_gY,1670
|
@@ -84,12 +84,12 @@ slidge/util/archive_msg.py,sha256=hGNquu38ouSWSc-kz_oAYPXwjhUVZNSedIpwkrXHSd0,18
|
|
84
84
|
slidge/util/conf.py,sha256=Wv-xr1fQfz6jDCBpj2e5Nm-igMpdIjsYsVfoY8grJoo,7380
|
85
85
|
slidge/util/jid_escaping.py,sha256=QJ2Yj_j1gTmiO9g2r187iVCu7kia_O5ABhRiLAO2TG4,1073
|
86
86
|
slidge/util/lock.py,sha256=ZnUi3LGiz271-YeYKo9JzxovJCoSwlP9P65pNyHIO9o,1029
|
87
|
-
slidge/util/test.py,sha256=
|
87
|
+
slidge/util/test.py,sha256=o86wX1ksnvAgN47k5facDPc4zgiK7J5oWyThgJsv_vQ,13986
|
88
88
|
slidge/util/types.py,sha256=nilphTeJU3yb0MSqb86tZeWXis495oDvHSDDBs0hn_4,5747
|
89
|
-
slidge/util/util.py,sha256=
|
90
|
-
slidge-0.3.
|
91
|
-
slidge-0.3.
|
92
|
-
slidge-0.3.
|
93
|
-
slidge-0.3.
|
94
|
-
slidge-0.3.
|
95
|
-
slidge-0.3.
|
89
|
+
slidge/util/util.py,sha256=g6jFu5xgAtRA9FBCIBJT3079t_bQKZYOUvi9L5pTbbk,9595
|
90
|
+
slidge-0.3.2.dist-info/licenses/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
91
|
+
slidge-0.3.2.dist-info/METADATA,sha256=Ptqr6X39gL1nyceti9rOscoPzsVcCBb8u93QsqRV6zU,5054
|
92
|
+
slidge-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
93
|
+
slidge-0.3.2.dist-info/entry_points.txt,sha256=py3_x834fFJ2TEzPd18Wt2DnysdAfuVqJ5zzBrXbAZs,44
|
94
|
+
slidge-0.3.2.dist-info/top_level.txt,sha256=2LRjDYHaGZ5ieCMF8xy58JIiabRMzX-MGMbCZwfE17c,7
|
95
|
+
slidge-0.3.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|