slidge 0.2.11__py3-none-any.whl → 0.3.0__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 +123 -210
- slidge/contact/roster.py +108 -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 +120 -93
- 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 +26 -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 +177 -87
- slidge/core/mixins/__init__.py +1 -11
- slidge/core/mixins/attachment.py +200 -147
- slidge/core/mixins/avatar.py +105 -177
- slidge/core/mixins/base.py +3 -1
- slidge/core/mixins/db.py +50 -2
- slidge/core/mixins/disco.py +1 -1
- slidge/core/mixins/message.py +19 -17
- slidge/core/mixins/message_maker.py +29 -15
- slidge/core/mixins/message_text.py +67 -30
- slidge/core/mixins/presence.py +94 -37
- slidge/core/pubsub.py +42 -47
- slidge/core/session.py +95 -60
- slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +361 -0
- slidge/db/avatar.py +150 -119
- slidge/db/meta.py +33 -22
- slidge/db/models.py +69 -117
- slidge/db/store.py +414 -1094
- slidge/group/archive.py +65 -55
- slidge/group/bookmarks.py +96 -59
- slidge/group/participant.py +150 -144
- slidge/group/room.py +351 -328
- slidge/main.py +34 -22
- slidge/migration.py +17 -29
- 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 +12 -2
- slidge/util/archive_msg.py +11 -5
- slidge/util/conf.py +27 -21
- 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 +24 -18
- slidge/util/util.py +26 -22
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/METADATA +1 -1
- slidge-0.3.0.dist-info/RECORD +95 -0
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/WHEEL +1 -1
- slidge/db/alembic/versions/04cf35e3cf85_add_participant_nickname_no_illegal.py +0 -33
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -36
- slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +0 -85
- slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -36
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -37
- slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -41
- slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +0 -52
- slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -42
- slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -61
- slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -48
- slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -43
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +0 -139
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -50
- slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +0 -79
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -214
- slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +0 -52
- slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -34
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -26
- slidge-0.2.11.dist-info/RECORD +0 -112
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/entry_points.txt +0 -0
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {slidge-0.2.11.dist-info → slidge-0.3.0.dist-info}/top_level.txt +0 -0
slidge/util/conf.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import logging
|
2
2
|
from functools import cached_property
|
3
3
|
from types import GenericAlias
|
4
|
-
from typing import Optional, Union, get_args, get_origin, get_type_hints
|
4
|
+
from typing import Any, Optional, Union, cast, get_args, get_origin, get_type_hints
|
5
5
|
|
6
6
|
import configargparse
|
7
7
|
|
@@ -11,31 +11,31 @@ class Option:
|
|
11
11
|
DYNAMIC_DEFAULT_SUFFIX = "__DYNAMIC_DEFAULT"
|
12
12
|
SHORT_SUFFIX = "__SHORT"
|
13
13
|
|
14
|
-
def __init__(self, parent: "ConfigModule", name: str):
|
14
|
+
def __init__(self, parent: "ConfigModule", name: str) -> None:
|
15
15
|
self.parent = parent
|
16
16
|
self.config_obj = parent.config_obj
|
17
17
|
self.name = name
|
18
18
|
|
19
19
|
@cached_property
|
20
|
-
def doc(self):
|
21
|
-
return getattr(self.config_obj, self.name + self.DOC_SUFFIX)
|
20
|
+
def doc(self) -> str:
|
21
|
+
return getattr(self.config_obj, self.name + self.DOC_SUFFIX) # type:ignore
|
22
22
|
|
23
23
|
@cached_property
|
24
|
-
def required(self):
|
24
|
+
def required(self) -> bool:
|
25
25
|
return not hasattr(
|
26
26
|
self.config_obj, self.name + self.DYNAMIC_DEFAULT_SUFFIX
|
27
27
|
) and not hasattr(self.config_obj, self.name)
|
28
28
|
|
29
29
|
@cached_property
|
30
|
-
def default(self):
|
30
|
+
def default(self) -> Any:
|
31
31
|
return getattr(self.config_obj, self.name, None)
|
32
32
|
|
33
33
|
@cached_property
|
34
|
-
def short(self):
|
34
|
+
def short(self) -> str | None:
|
35
35
|
return getattr(self.config_obj, self.name + self.SHORT_SUFFIX, None)
|
36
36
|
|
37
37
|
@cached_property
|
38
|
-
def nargs(self):
|
38
|
+
def nargs(self) -> str | int | None:
|
39
39
|
type_ = get_type_hints(self.config_obj).get(self.name, type(self.default))
|
40
40
|
|
41
41
|
if isinstance(type_, GenericAlias):
|
@@ -44,9 +44,10 @@ class Option:
|
|
44
44
|
return "*"
|
45
45
|
else:
|
46
46
|
return len(args)
|
47
|
+
return None
|
47
48
|
|
48
49
|
@cached_property
|
49
|
-
def type(self):
|
50
|
+
def type(self) -> Any:
|
50
51
|
type_ = get_type_hints(self.config_obj).get(self.name, type(self.default))
|
51
52
|
|
52
53
|
if _is_optional(type_):
|
@@ -58,14 +59,14 @@ class Option:
|
|
58
59
|
return type_
|
59
60
|
|
60
61
|
@cached_property
|
61
|
-
def names(self):
|
62
|
+
def names(self) -> list[str]:
|
62
63
|
res = ["--" + self.name.lower().replace("_", "-")]
|
63
64
|
if s := self.short:
|
64
65
|
res.append("-" + s)
|
65
66
|
return res
|
66
67
|
|
67
68
|
@cached_property
|
68
|
-
def kwargs(self):
|
69
|
+
def kwargs(self) -> dict[str, Any]:
|
69
70
|
kwargs = dict(
|
70
71
|
required=self.required,
|
71
72
|
help=self.doc,
|
@@ -87,7 +88,7 @@ class Option:
|
|
87
88
|
kwargs["nargs"] = n
|
88
89
|
return kwargs
|
89
90
|
|
90
|
-
def name_to_env_var(self):
|
91
|
+
def name_to_env_var(self) -> str:
|
91
92
|
return self.parent.ENV_VAR_PREFIX + self.name
|
92
93
|
|
93
94
|
|
@@ -96,10 +97,10 @@ class ConfigModule:
|
|
96
97
|
|
97
98
|
def __init__(
|
98
99
|
self,
|
99
|
-
config_obj,
|
100
|
+
config_obj: Any,
|
100
101
|
parser: Optional[configargparse.ArgumentParser] = None,
|
101
102
|
skip_options: tuple[str, ...] = (),
|
102
|
-
):
|
103
|
+
) -> None:
|
103
104
|
self.config_obj = config_obj
|
104
105
|
if parser is None:
|
105
106
|
parser = configargparse.ArgumentParser()
|
@@ -108,7 +109,7 @@ class ConfigModule:
|
|
108
109
|
self.skip_options = skip_options
|
109
110
|
self.add_options_to_parser(skip_options)
|
110
111
|
|
111
|
-
def _list_options(self):
|
112
|
+
def _list_options(self) -> set[str]:
|
112
113
|
return {
|
113
114
|
o
|
114
115
|
for o in (set(dir(self.config_obj)) | set(get_type_hints(self.config_obj)))
|
@@ -118,7 +119,9 @@ class ConfigModule:
|
|
118
119
|
and o.lower() not in self.skip_options
|
119
120
|
}
|
120
121
|
|
121
|
-
def set_conf(
|
122
|
+
def set_conf(
|
123
|
+
self, argv: Optional[list[str]] = None
|
124
|
+
) -> tuple[configargparse.Namespace, list[str]]:
|
122
125
|
if argv is not None:
|
123
126
|
# this is ugly, but necessary because for plugin config, we used
|
124
127
|
# remaining argv.
|
@@ -156,7 +159,10 @@ class ConfigModule:
|
|
156
159
|
upper = _argv_to_option_name(a)
|
157
160
|
opt = options_long.get(upper)
|
158
161
|
if opt and opt.type is bool:
|
159
|
-
if
|
162
|
+
if (
|
163
|
+
not aa.startswith("-")
|
164
|
+
and _argv_to_option_name(aa) not in options_long
|
165
|
+
):
|
160
166
|
log.debug("Removing %s from argv", aa)
|
161
167
|
skip_next = True
|
162
168
|
|
@@ -186,7 +192,7 @@ class ConfigModule:
|
|
186
192
|
res.append(Option(self, opt))
|
187
193
|
return res
|
188
194
|
|
189
|
-
def add_options_to_parser(self, skip_options: tuple[str, ...]):
|
195
|
+
def add_options_to_parser(self, skip_options: tuple[str, ...]) -> None:
|
190
196
|
skip_options = tuple(o.lower() for o in skip_options)
|
191
197
|
p = self.parser
|
192
198
|
for o in sorted(self.options, key=lambda x: (not x.required, x.name)):
|
@@ -194,11 +200,11 @@ class ConfigModule:
|
|
194
200
|
continue
|
195
201
|
p.add_argument(*o.names, **o.kwargs)
|
196
202
|
|
197
|
-
def update_dynamic_defaults(self, args):
|
203
|
+
def update_dynamic_defaults(self, args: configargparse.Namespace) -> None:
|
198
204
|
pass
|
199
205
|
|
200
206
|
|
201
|
-
def _is_optional(t):
|
207
|
+
def _is_optional(t: Any) -> bool:
|
202
208
|
if get_origin(t) is Union:
|
203
209
|
args = get_args(t)
|
204
210
|
if len(args) == 2 and isinstance(None, args[1]):
|
@@ -206,7 +212,7 @@ def _is_optional(t):
|
|
206
212
|
return False
|
207
213
|
|
208
214
|
|
209
|
-
def _argv_to_option_name(arg: str):
|
215
|
+
def _argv_to_option_name(arg: str) -> str:
|
210
216
|
return arg.upper().removeprefix("--").replace("-", "_")
|
211
217
|
|
212
218
|
|
slidge/util/jid_escaping.py
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
import asyncio
|
2
2
|
import logging
|
3
3
|
from contextlib import asynccontextmanager
|
4
|
-
from typing import Hashable
|
4
|
+
from typing import Any, AsyncIterator, Hashable
|
5
5
|
|
6
6
|
|
7
7
|
class NamedLockMixin:
|
8
|
-
def __init__(self, *a, **k):
|
8
|
+
def __init__(self, *a: Any, **k: Any) -> None:
|
9
9
|
super().__init__(*a, **k)
|
10
10
|
self.__locks = dict[Hashable, asyncio.Lock]()
|
11
11
|
|
12
12
|
@asynccontextmanager
|
13
|
-
async def lock(self, id_: Hashable):
|
13
|
+
async def lock(self, id_: Hashable) -> AsyncIterator[None]:
|
14
14
|
log.trace("getting %s", id_) # type:ignore
|
15
15
|
locks = self.__locks
|
16
16
|
if not locks.get(id_):
|
@@ -21,13 +21,13 @@ class NamedLockMixin:
|
|
21
21
|
yield
|
22
22
|
finally:
|
23
23
|
log.trace("releasing %s", id_) # type:ignore
|
24
|
-
waiters = locks[id_]._waiters
|
24
|
+
waiters = locks[id_]._waiters
|
25
25
|
if not waiters:
|
26
26
|
del locks[id_]
|
27
27
|
log.trace("erasing %s", id_) # type:ignore
|
28
28
|
|
29
|
-
def get_lock(self, id_: Hashable):
|
29
|
+
def get_lock(self, id_: Hashable) -> asyncio.Lock | None:
|
30
30
|
return self.__locks.get(id_)
|
31
31
|
|
32
32
|
|
33
|
-
log = logging.getLogger(__name__)
|
33
|
+
log = logging.getLogger(__name__)
|
slidge/util/test.py
CHANGED
@@ -43,19 +43,25 @@ from slidge import (
|
|
43
43
|
from ..command import Command
|
44
44
|
from ..core import config
|
45
45
|
from ..core.config import _TimedeltaSeconds
|
46
|
-
from ..core.pubsub import PepAvatar, PepNick
|
47
46
|
from ..db import SlidgeStore
|
48
47
|
from ..db.avatar import avatar_cache
|
49
48
|
from ..db.meta import Base
|
50
|
-
from ..db.models import Contact
|
49
|
+
from ..db.models import Contact, GatewayUser
|
51
50
|
|
52
51
|
|
53
52
|
class SlixTestPlus(SlixTest):
|
54
|
-
def setUp(self):
|
53
|
+
def setUp(self) -> None:
|
55
54
|
super().setUp()
|
56
55
|
Error.namespace = "jabber:component:accept"
|
57
56
|
|
58
|
-
def check(
|
57
|
+
def check(
|
58
|
+
self,
|
59
|
+
stanza,
|
60
|
+
criteria,
|
61
|
+
method: str = "exact",
|
62
|
+
defaults=None,
|
63
|
+
use_values: bool = True,
|
64
|
+
):
|
59
65
|
"""
|
60
66
|
Create and compare several stanza objects to a correct XML string.
|
61
67
|
|
@@ -174,7 +180,9 @@ class SlixTestPlus(SlixTest):
|
|
174
180
|
)
|
175
181
|
self.assertTrue(result, debug)
|
176
182
|
|
177
|
-
def next_sent(
|
183
|
+
def next_sent(
|
184
|
+
self, timeout: float = 0.05
|
185
|
+
) -> Optional[Union[Message, Iq, Presence]]:
|
178
186
|
self.wait_for_send_queue()
|
179
187
|
sent = self.xmpp.socket.next_sent(timeout=timeout)
|
180
188
|
if sent is None:
|
@@ -197,13 +205,11 @@ class SlidgeTest(SlixTestPlus):
|
|
197
205
|
home_dir = Path(tempfile.mkdtemp())
|
198
206
|
user_jid_validator = ".*"
|
199
207
|
admins: list[str] = []
|
200
|
-
no_roster_push = False
|
201
208
|
upload_requester = None
|
202
209
|
ignore_delay_threshold = _TimedeltaSeconds("300")
|
203
|
-
last_seen_fallback = True
|
204
210
|
|
205
211
|
@classmethod
|
206
|
-
def setUpClass(cls):
|
212
|
+
def setUpClass(cls) -> None:
|
207
213
|
for k, v in vars(cls.Config).items():
|
208
214
|
setattr(config, k.upper(), v)
|
209
215
|
|
@@ -237,8 +243,6 @@ class SlidgeTest(SlixTestPlus):
|
|
237
243
|
except Exception:
|
238
244
|
raise
|
239
245
|
self.xmpp.TEST_MODE = True
|
240
|
-
PepNick.contact_store = self.xmpp.store.contacts
|
241
|
-
PepAvatar.store = self.xmpp.store
|
242
246
|
avatar_cache.store = self.xmpp.store.avatars
|
243
247
|
avatar_cache.set_dir(Path(tempfile.mkdtemp()))
|
244
248
|
self.xmpp._always_send_everything = True
|
@@ -273,22 +277,20 @@ class SlidgeTest(SlixTestPlus):
|
|
273
277
|
self.xmpp.use_presence_ids = False
|
274
278
|
Error.namespace = "jabber:component:accept"
|
275
279
|
|
276
|
-
def tearDown(self):
|
280
|
+
def tearDown(self) -> None:
|
277
281
|
self.db_engine.echo = False
|
278
282
|
super().tearDown()
|
279
|
-
import slidge.db.store
|
280
|
-
|
281
|
-
if slidge.db.store._session is not None:
|
282
|
-
slidge.db.store._session.commit()
|
283
|
-
slidge.db.store._session = None
|
284
283
|
Base.metadata.drop_all(self.xmpp.store._engine)
|
285
284
|
|
286
|
-
def setup_logged_session(self, n_contacts=0):
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
285
|
+
def setup_logged_session(self, n_contacts: int = 0) -> None:
|
286
|
+
with self.xmpp.store.session() as orm:
|
287
|
+
user = GatewayUser(
|
288
|
+
jid=JID("romeo@montague.lit/gajim").bare,
|
289
|
+
legacy_module_data={"username": "romeo", "city": ""},
|
290
|
+
preferences={"sync_avatar": True, "sync_presence": True},
|
291
|
+
)
|
292
|
+
orm.add(user)
|
293
|
+
orm.commit()
|
292
294
|
|
293
295
|
with self.xmpp.store.session() as session:
|
294
296
|
session.execute(delete(Contact))
|
@@ -308,9 +310,8 @@ class SlidgeTest(SlixTestPlus):
|
|
308
310
|
if BaseGateway.get_self_or_unique_subclass().GROUPS:
|
309
311
|
stanza = self.next_sent()
|
310
312
|
assert "syncing groups" in stanza["status"].lower(), stanza
|
311
|
-
|
312
|
-
|
313
|
-
assert probe.get_type() == "probe"
|
313
|
+
probe = self.next_sent()
|
314
|
+
assert probe.get_type() == "probe"
|
314
315
|
stanza = self.next_sent()
|
315
316
|
assert "yup" in stanza["status"].lower(), stanza
|
316
317
|
self.romeo: BaseSession = BaseSession.get_self_or_unique_subclass().from_jid(
|
@@ -338,7 +339,7 @@ class SlidgeTest(SlixTestPlus):
|
|
338
339
|
)
|
339
340
|
|
340
341
|
@classmethod
|
341
|
-
def tearDownClass(cls):
|
342
|
+
def tearDownClass(cls) -> None:
|
342
343
|
reset_subclasses()
|
343
344
|
|
344
345
|
|
@@ -348,7 +349,7 @@ def format_stanza(stanza):
|
|
348
349
|
)
|
349
350
|
|
350
351
|
|
351
|
-
def find_subclass(o, parent, base_ok=False):
|
352
|
+
def find_subclass(o, parent, base_ok: bool = False):
|
352
353
|
try:
|
353
354
|
vals = vars(o).values()
|
354
355
|
except TypeError:
|
@@ -366,7 +367,7 @@ def find_subclass(o, parent, base_ok=False):
|
|
366
367
|
raise RuntimeError
|
367
368
|
|
368
369
|
|
369
|
-
def reset_subclasses():
|
370
|
+
def reset_subclasses() -> None:
|
370
371
|
"""
|
371
372
|
Reset registered subclasses between test classes.
|
372
373
|
|
@@ -383,7 +384,7 @@ def reset_subclasses():
|
|
383
384
|
# reset_commands()
|
384
385
|
|
385
386
|
|
386
|
-
def reset_commands():
|
387
|
+
def reset_commands() -> None:
|
387
388
|
Command.subclasses = [
|
388
389
|
c for c in Command.subclasses if str(c).startswith("<class 'slidge.core")
|
389
390
|
]
|
slidge/util/types.py
CHANGED
@@ -10,6 +10,7 @@ from typing import (
|
|
10
10
|
IO,
|
11
11
|
TYPE_CHECKING,
|
12
12
|
Any,
|
13
|
+
AsyncIterator,
|
13
14
|
Generic,
|
14
15
|
Hashable,
|
15
16
|
Literal,
|
@@ -25,19 +26,15 @@ from slixmpp.types import PresenceShows, PresenceTypes, ResourceDict
|
|
25
26
|
|
26
27
|
if TYPE_CHECKING:
|
27
28
|
from ..contact import LegacyContact
|
28
|
-
from ..core.pubsub import PepItem
|
29
29
|
from ..core.session import BaseSession
|
30
|
-
from ..group
|
30
|
+
from ..group import LegacyMUC
|
31
|
+
from ..group.participant import LegacyParticipant
|
31
32
|
|
32
33
|
AnyBaseSession = BaseSession[Any, Any]
|
33
34
|
else:
|
34
35
|
AnyBaseSession = None
|
35
36
|
|
36
37
|
|
37
|
-
class URL(str):
|
38
|
-
pass
|
39
|
-
|
40
|
-
|
41
38
|
LegacyGroupIdType = TypeVar("LegacyGroupIdType", bound=Hashable)
|
42
39
|
"""
|
43
40
|
Type of the unique identifier for groups, usually a str or an int,
|
@@ -47,18 +44,14 @@ LegacyMessageType = TypeVar("LegacyMessageType", bound=Hashable)
|
|
47
44
|
LegacyThreadType = TypeVar("LegacyThreadType", bound=Hashable)
|
48
45
|
LegacyUserIdType = TypeVar("LegacyUserIdType", bound=Hashable)
|
49
46
|
|
50
|
-
LegacyContactType = TypeVar("LegacyContactType", bound="LegacyContact")
|
51
|
-
LegacyMUCType = TypeVar("LegacyMUCType", bound="LegacyMUC")
|
47
|
+
LegacyContactType = TypeVar("LegacyContactType", bound="LegacyContact[Any]")
|
48
|
+
LegacyMUCType = TypeVar("LegacyMUCType", bound="LegacyMUC[Any, Any, Any, Any]")
|
52
49
|
LegacyParticipantType = TypeVar("LegacyParticipantType", bound="LegacyParticipant")
|
53
50
|
|
54
|
-
|
55
|
-
|
56
|
-
Recipient = Union["LegacyMUC", "LegacyContact"]
|
51
|
+
Recipient = Union["LegacyMUC[Any, Any, Any, Any]", "LegacyContact[Any]"]
|
57
52
|
RecipientType = TypeVar("RecipientType", bound=Recipient)
|
58
|
-
Sender = Union["LegacyContact", "LegacyParticipant"]
|
59
|
-
AvatarType = Union[bytes, str, Path]
|
53
|
+
Sender = Union["LegacyContact[Any]", "LegacyParticipant"]
|
60
54
|
LegacyFileIdType = Union[int, str]
|
61
|
-
AvatarIdType = Union[LegacyFileIdType, URL]
|
62
55
|
|
63
56
|
ChatState = Literal["active", "composing", "gone", "inactive", "paused"]
|
64
57
|
ProcessingHint = Literal["no-store", "markable", "store"]
|
@@ -79,6 +72,7 @@ MucRole = Literal["visitor", "participant", "moderator", "none"]
|
|
79
72
|
ClientType = Literal[
|
80
73
|
"bot", "console", "game", "handheld", "pc", "phone", "sms", "tablet", "web"
|
81
74
|
]
|
75
|
+
AttachmentDisposition = Literal["attachment", "inline"]
|
82
76
|
|
83
77
|
|
84
78
|
@dataclass
|
@@ -116,21 +110,26 @@ class LegacyAttachment:
|
|
116
110
|
path: Optional[Union[Path, str]] = None
|
117
111
|
name: Optional[Union[str]] = None
|
118
112
|
stream: Optional[IO[bytes]] = None
|
113
|
+
aio_stream: Optional[AsyncIterator[bytes]] = None
|
119
114
|
data: Optional[bytes] = None
|
120
115
|
content_type: Optional[str] = None
|
121
116
|
legacy_file_id: Optional[Union[str, int]] = None
|
122
117
|
url: Optional[str] = None
|
123
118
|
caption: Optional[str] = None
|
119
|
+
disposition: Optional[AttachmentDisposition] = None
|
124
120
|
"""
|
125
121
|
A caption for this specific image. For a global caption for a list of attachments,
|
126
122
|
use the ``body`` parameter of :meth:`.AttachmentMixin.send_files`
|
127
123
|
"""
|
128
124
|
|
129
|
-
def __post_init__(self):
|
130
|
-
if
|
131
|
-
x is
|
125
|
+
def __post_init__(self) -> None:
|
126
|
+
if all(
|
127
|
+
x is None
|
128
|
+
for x in (self.path, self.stream, self.data, self.url, self.aio_stream)
|
132
129
|
):
|
133
130
|
raise TypeError("There is not data in this attachment", self)
|
131
|
+
if isinstance(self.path, str):
|
132
|
+
self.path = Path(self.path)
|
134
133
|
|
135
134
|
|
136
135
|
class MucType(IntEnum):
|
@@ -171,7 +170,7 @@ class LinkPreview(NamedTuple):
|
|
171
170
|
|
172
171
|
|
173
172
|
class Mention(NamedTuple):
|
174
|
-
contact: "LegacyContact"
|
173
|
+
contact: "LegacyContact[Any]"
|
175
174
|
start: int
|
176
175
|
end: int
|
177
176
|
|
@@ -207,3 +206,10 @@ class Sticker(NamedTuple):
|
|
207
206
|
path: Path
|
208
207
|
content_type: Optional[str]
|
209
208
|
hashes: dict[str, str]
|
209
|
+
|
210
|
+
|
211
|
+
class Avatar(NamedTuple):
|
212
|
+
path: Optional[Path] = None
|
213
|
+
unique_id: Optional[str | int] = None
|
214
|
+
url: Optional[str] = None
|
215
|
+
data: Optional[bytes] = None
|
slidge/util/util.py
CHANGED
@@ -7,7 +7,7 @@ from abc import ABCMeta
|
|
7
7
|
from functools import wraps
|
8
8
|
from pathlib import Path
|
9
9
|
from time import time
|
10
|
-
from typing import TYPE_CHECKING, Callable, NamedTuple, Optional, Type, TypeVar
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, NamedTuple, Optional, Type, TypeVar
|
11
11
|
|
12
12
|
try:
|
13
13
|
import emoji
|
@@ -34,7 +34,7 @@ except ImportError as e:
|
|
34
34
|
)
|
35
35
|
|
36
36
|
|
37
|
-
def fix_suffix(path: Path, mime_type: Optional[str], file_name: Optional[str]):
|
37
|
+
def fix_suffix(path: Path, mime_type: Optional[str], file_name: Optional[str]) -> Path:
|
38
38
|
guessed = magic.from_file(path, mime=True)
|
39
39
|
if guessed == mime_type:
|
40
40
|
log.debug("Magic and given MIME match")
|
@@ -65,9 +65,15 @@ def fix_suffix(path: Path, mime_type: Optional[str], file_name: Optional[str]):
|
|
65
65
|
|
66
66
|
|
67
67
|
class SubclassableOnce(type):
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
# To allow importing everything, including plugins, during tests
|
69
|
+
TEST_MODE: bool = False
|
70
|
+
|
71
|
+
def __init__(
|
72
|
+
cls,
|
73
|
+
name: str,
|
74
|
+
bases: tuple[Type[Any], ...],
|
75
|
+
dct: dict[str, Any],
|
76
|
+
) -> None:
|
71
77
|
for b in bases:
|
72
78
|
if type(b) in (SubclassableOnce, ABCSubclassableOnceAtMost):
|
73
79
|
if hasattr(b, "_subclass") and not cls.TEST_MODE:
|
@@ -84,19 +90,19 @@ class SubclassableOnce(type):
|
|
84
90
|
|
85
91
|
super().__init__(name, bases, dct)
|
86
92
|
|
87
|
-
def get_self_or_unique_subclass(cls):
|
93
|
+
def get_self_or_unique_subclass(cls) -> "SubclassableOnce":
|
88
94
|
try:
|
89
95
|
return cls.get_unique_subclass()
|
90
96
|
except AttributeError:
|
91
97
|
return cls
|
92
98
|
|
93
|
-
def get_unique_subclass(cls):
|
99
|
+
def get_unique_subclass(cls) -> "SubclassableOnce":
|
94
100
|
r = getattr(cls, "_subclass", None)
|
95
101
|
if r is None:
|
96
102
|
raise AttributeError("Could not find any subclass", cls)
|
97
103
|
return r
|
98
104
|
|
99
|
-
def reset_subclass(cls):
|
105
|
+
def reset_subclass(cls) -> None:
|
100
106
|
try:
|
101
107
|
log.debug("Resetting subclass of %s", cls)
|
102
108
|
delattr(cls, "_subclass")
|
@@ -156,7 +162,7 @@ ILLEGAL_XML_CHARS_RE = re.compile(XML_ILLEGAL_CHARACTER_REGEX)
|
|
156
162
|
# from https://stackoverflow.com/a/35804945/5902284
|
157
163
|
def addLoggingLevel(
|
158
164
|
levelName: str = "TRACE", levelNum: int = logging.DEBUG - 5, methodName=None
|
159
|
-
):
|
165
|
+
) -> None:
|
160
166
|
"""
|
161
167
|
Comprehensively adds a new logging level to the `logging` module and the
|
162
168
|
currently configured logging class.
|
@@ -197,11 +203,11 @@ def addLoggingLevel(
|
|
197
203
|
# This method was inspired by the answers to Stack Overflow post
|
198
204
|
# http://stackoverflow.com/q/2183233/2988730, especially
|
199
205
|
# http://stackoverflow.com/a/13638084/2988730
|
200
|
-
def logForLevel(self, message, *args, **kwargs):
|
206
|
+
def logForLevel(self, message, *args, **kwargs) -> None:
|
201
207
|
if self.isEnabledFor(levelNum):
|
202
208
|
self._log(levelNum, message, args, **kwargs)
|
203
209
|
|
204
|
-
def logToRoot(message, *args, **kwargs):
|
210
|
+
def logToRoot(message, *args, **kwargs) -> None:
|
205
211
|
logging.log(levelNum, message, *args, **kwargs)
|
206
212
|
|
207
213
|
logging.addLevelName(levelNum, levelName)
|
@@ -211,7 +217,7 @@ def addLoggingLevel(
|
|
211
217
|
|
212
218
|
|
213
219
|
class SlidgeLogger(logging.Logger):
|
214
|
-
def trace(self):
|
220
|
+
def trace(self) -> None:
|
215
221
|
pass
|
216
222
|
|
217
223
|
|
@@ -294,15 +300,6 @@ def replace_mentions(
|
|
294
300
|
return "".join(pieces)
|
295
301
|
|
296
302
|
|
297
|
-
def with_session(func):
|
298
|
-
@wraps(func)
|
299
|
-
async def wrapped(self, *args, **kwargs):
|
300
|
-
with self.xmpp.store.session():
|
301
|
-
return await func(self, *args, **kwargs)
|
302
|
-
|
303
|
-
return wrapped
|
304
|
-
|
305
|
-
|
306
303
|
def timeit(func):
|
307
304
|
@wraps(func)
|
308
305
|
async def wrapped(self, *args, **kwargs):
|
@@ -325,5 +322,12 @@ def strip_leading_emoji(text: str) -> str:
|
|
325
322
|
return text
|
326
323
|
|
327
324
|
|
328
|
-
async def noop_coro():
|
325
|
+
async def noop_coro() -> None:
|
329
326
|
pass
|
327
|
+
|
328
|
+
|
329
|
+
def add_quote_prefix(text: str):
|
330
|
+
"""
|
331
|
+
Return multi-line text with leading quote marks (i.e. the ">" character).
|
332
|
+
"""
|
333
|
+
return "\n".join(("> " + x).strip() for x in text.split("\n")).strip()
|