slidge 0.1.2__tar.gz → 0.2.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {slidge-0.1.2 → slidge-0.2.0}/PKG-INFO +10 -5
- {slidge-0.1.2 → slidge-0.2.0}/pyproject.toml +17 -7
- {slidge-0.1.2 → slidge-0.2.0}/slidge/__init__.py +3 -5
- slidge-0.2.0/slidge/__main__.py +3 -0
- slidge-0.2.0/slidge/__version__.py +5 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/command/adhoc.py +40 -17
- {slidge-0.1.2 → slidge-0.2.0}/slidge/command/admin.py +24 -12
- {slidge-0.1.2 → slidge-0.2.0}/slidge/command/base.py +10 -8
- slidge-0.2.0/slidge/command/categories.py +13 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/command/chat_command.py +29 -2
- {slidge-0.1.2 → slidge-0.2.0}/slidge/command/register.py +32 -16
- {slidge-0.1.2 → slidge-0.2.0}/slidge/command/user.py +106 -13
- {slidge-0.1.2 → slidge-0.2.0}/slidge/contact/contact.py +254 -50
- slidge-0.2.0/slidge/contact/roster.py +263 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/config.py +19 -13
- slidge-0.2.0/slidge/core/dispatcher/__init__.py +3 -0
- {slidge-0.1.2/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher}/caps.py +12 -8
- {slidge-0.1.2/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher}/disco.py +10 -18
- slidge-0.2.0/slidge/core/dispatcher/message/__init__.py +10 -0
- slidge-0.2.0/slidge/core/dispatcher/message/chat_state.py +40 -0
- slidge-0.2.0/slidge/core/dispatcher/message/marker.py +62 -0
- slidge-0.2.0/slidge/core/dispatcher/message/message.py +397 -0
- slidge-0.2.0/slidge/core/dispatcher/muc/__init__.py +12 -0
- slidge-0.2.0/slidge/core/dispatcher/muc/admin.py +98 -0
- {slidge-0.1.2/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher/muc}/mam.py +25 -17
- slidge-0.2.0/slidge/core/dispatcher/muc/misc.py +121 -0
- slidge-0.2.0/slidge/core/dispatcher/muc/owner.py +96 -0
- {slidge-0.1.2/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher/muc}/ping.py +11 -17
- slidge-0.2.0/slidge/core/dispatcher/presence.py +176 -0
- slidge-0.2.0/slidge/core/dispatcher/registration.py +85 -0
- {slidge-0.1.2/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher}/search.py +9 -16
- slidge-0.2.0/slidge/core/dispatcher/session_dispatcher.py +84 -0
- slidge-0.2.0/slidge/core/dispatcher/util.py +174 -0
- slidge-0.1.2/slidge/core/gateway/vcard_temp.py → slidge-0.2.0/slidge/core/dispatcher/vcard.py +35 -19
- slidge-0.1.2/slidge/core/gateway/base.py → slidge-0.2.0/slidge/core/gateway.py +176 -153
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/__init__.py +11 -1
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/attachment.py +106 -67
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/avatar.py +94 -25
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/base.py +10 -4
- slidge-0.2.0/slidge/core/mixins/db.py +18 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/disco.py +0 -10
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/lock.py +10 -8
- slidge-0.2.0/slidge/core/mixins/message.py +214 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/message_maker.py +17 -9
- slidge-0.2.0/slidge/core/mixins/message_text.py +211 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/presence.py +17 -4
- slidge-0.2.0/slidge/core/pubsub.py +351 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/session.py +101 -40
- slidge-0.2.0/slidge/db/__init__.py +4 -0
- slidge-0.2.0/slidge/db/alembic/env.py +64 -0
- slidge-0.1.2/slidge/util/db.py → slidge-0.2.0/slidge/db/alembic/old_user_store.py +1 -47
- slidge-0.2.0/slidge/db/alembic/script.py.mako +26 -0
- slidge-0.2.0/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
- slidge-0.2.0/slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +85 -0
- slidge-0.2.0/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
- slidge-0.2.0/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
- slidge-0.2.0/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
- slidge-0.2.0/slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +52 -0
- slidge-0.2.0/slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
- slidge-0.2.0/slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +61 -0
- slidge-0.2.0/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
- slidge-0.2.0/slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
- slidge-0.2.0/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +139 -0
- slidge-0.2.0/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +101 -0
- slidge-0.2.0/slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +79 -0
- slidge-0.2.0/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
- slidge-0.2.0/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +52 -0
- slidge-0.2.0/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
- slidge-0.2.0/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
- slidge-0.2.0/slidge/db/avatar.py +205 -0
- slidge-0.2.0/slidge/db/meta.py +72 -0
- slidge-0.2.0/slidge/db/models.py +405 -0
- slidge-0.2.0/slidge/db/store.py +1257 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/group/archive.py +58 -14
- slidge-0.2.0/slidge/group/bookmarks.py +187 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/group/participant.py +111 -44
- {slidge-0.1.2 → slidge-0.2.0}/slidge/group/room.py +402 -213
- slidge-0.1.2/slidge/__main__.py → slidge-0.2.0/slidge/main.py +24 -20
- slidge-0.2.0/slidge/migration.py +62 -0
- slidge-0.2.0/slidge/py.typed +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/__init__.py +31 -1
- {slidge-0.1.2/slidge/core/gateway → slidge-0.2.0/slidge/slixfix}/delivery_receipt.py +1 -1
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/roster.py +13 -4
- slidge-0.2.0/slidge/slixfix/xep_0292/vcard4.py +14 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/util/archive_msg.py +2 -1
- slidge-0.2.0/slidge/util/db.py +5 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/util/test.py +91 -4
- {slidge-0.1.2 → slidge-0.2.0}/slidge/util/types.py +39 -4
- {slidge-0.1.2 → slidge-0.2.0}/slidge/util/util.py +45 -2
- slidge-0.1.2/slidge/command/categories.py +0 -3
- slidge-0.1.2/slidge/contact/roster.py +0 -192
- slidge-0.1.2/slidge/core/cache.py +0 -183
- slidge-0.1.2/slidge/core/gateway/__init__.py +0 -3
- slidge-0.1.2/slidge/core/gateway/muc_admin.py +0 -35
- slidge-0.1.2/slidge/core/gateway/presence.py +0 -95
- slidge-0.1.2/slidge/core/gateway/registration.py +0 -53
- slidge-0.1.2/slidge/core/gateway/session_dispatcher.py +0 -795
- slidge-0.1.2/slidge/core/mixins/message.py +0 -398
- slidge-0.1.2/slidge/core/pubsub.py +0 -525
- slidge-0.1.2/slidge/group/bookmarks.py +0 -163
- slidge-0.1.2/slidge/migration.py +0 -18
- slidge-0.1.2/slidge/slixfix/xep_0292/vcard4.py +0 -100
- slidge-0.1.2/slidge/util/schema.sql +0 -126
- slidge-0.1.2/slidge/util/sql.py +0 -508
- {slidge-0.1.2 → slidge-0.2.0}/LICENSE +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/README.md +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/command/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/contact/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/core/mixins/recipient.py +0 -0
- /slidge-0.1.2/slidge/py.typed → /slidge-0.2.0/slidge/db/alembic/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/group/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/link_preview/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/link_preview/link_preview.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/link_preview/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0077/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0077/register.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0077/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0100/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0100/gateway.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0100/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0153/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0153/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0264/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0264/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0292/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0313/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0313/mam.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0313/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0317/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0317/hats.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0317/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0424/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0424/retraction.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0424/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0490/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0490/mds.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/slixfix/xep_0490/stanza.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/util/__init__.py +0 -0
- {slidge-0.1.2 → slidge-0.2.0}/slidge/util/conf.py +0 -0
@@ -1,25 +1,30 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: slidge
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.0
|
4
4
|
Summary: XMPP bridging framework
|
5
5
|
Home-page: https://sr.ht/~nicoco/slidge/
|
6
6
|
License: AGPL-3.0-or-later
|
7
7
|
Author: Nicolas Cedilnik
|
8
8
|
Author-email: nicoco@nicoco.fr
|
9
|
-
Requires-Python: >=3.
|
9
|
+
Requires-Python: >=3.11,<4.0
|
10
10
|
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
12
|
-
Classifier: Programming Language :: Python :: 3.9
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
14
12
|
Classifier: Programming Language :: Python :: 3.11
|
13
|
+
Classifier: Topic :: Internet :: XMPP
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
15
15
|
Requires-Dist: ConfigArgParse (>=1.5.3,<2.0.0)
|
16
16
|
Requires-Dist: Pillow (>=10,<11)
|
17
17
|
Requires-Dist: aiohttp[speedups] (>=3.8.3,<4.0.0)
|
18
|
-
Requires-Dist:
|
18
|
+
Requires-Dist: alembic (>=1.13.1,<2.0.0)
|
19
|
+
Requires-Dist: defusedxml (>=0.7.1,<0.8.0)
|
19
20
|
Requires-Dist: pickle-secure (>=0.99.9,<0.100.0)
|
20
21
|
Requires-Dist: python-magic (>=0.4.27,<0.5.0)
|
21
22
|
Requires-Dist: qrcode (>=7.4.1,<8.0.0)
|
22
23
|
Requires-Dist: slixmpp (>=1.8.5,<2.0.0)
|
24
|
+
Requires-Dist: sqlalchemy (>=2.0.29,<3.0.0)
|
25
|
+
Requires-Dist: thumbhash (>=0.1.2,<0.2.0)
|
26
|
+
Project-URL: Bug tracker, https://todo.sr.ht/~nicoco/slidge
|
27
|
+
Project-URL: Chat room, https://conference.nicoco.fr:5281/muc_log/slidge/
|
23
28
|
Project-URL: Documentation, https://slidge.im/
|
24
29
|
Project-URL: Repository, https://git.sr.ht/~nicoco/slidge/
|
25
30
|
Description-Content-Type: text/markdown
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "slidge"
|
3
|
-
version = "0.
|
3
|
+
version = "0.2.0"
|
4
4
|
description = "XMPP bridging framework"
|
5
5
|
authors = ["Nicolas Cedilnik <nicoco@nicoco.fr>"]
|
6
6
|
readme = "README.md"
|
@@ -8,10 +8,17 @@ license = "AGPL-3.0-or-later"
|
|
8
8
|
homepage = "https://sr.ht/~nicoco/slidge/"
|
9
9
|
repository = "https://git.sr.ht/~nicoco/slidge/"
|
10
10
|
documentation = "https://slidge.im/"
|
11
|
-
|
11
|
+
classifiers = [
|
12
|
+
"Topic :: Internet :: XMPP",
|
13
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
14
|
+
]
|
15
|
+
|
16
|
+
[tool.poetry.urls]
|
17
|
+
"Bug tracker" = "https://todo.sr.ht/~nicoco/slidge"
|
18
|
+
"Chat room" = "https://conference.nicoco.fr:5281/muc_log/slidge/"
|
12
19
|
|
13
20
|
[tool.poetry.dependencies]
|
14
|
-
python = "^3.
|
21
|
+
python = "^3.11"
|
15
22
|
qrcode = "^7.4.1"
|
16
23
|
Pillow = "^10"
|
17
24
|
aiohttp = {version = "^3.8.3", extras = ["speedups"]}
|
@@ -19,7 +26,10 @@ ConfigArgParse = "^1.5.3"
|
|
19
26
|
pickle-secure = "^0.99.9"
|
20
27
|
python-magic = "^0.4.27"
|
21
28
|
slixmpp = "^1.8.5"
|
22
|
-
|
29
|
+
sqlalchemy = "^2.0.29"
|
30
|
+
alembic = "^1.13.1"
|
31
|
+
thumbhash = "^0.1.2"
|
32
|
+
defusedxml = "^0.7.1"
|
23
33
|
|
24
34
|
[build-system]
|
25
35
|
requires = ["poetry-core>=1.0.0"]
|
@@ -41,13 +51,14 @@ xmldiff = "^2.5"
|
|
41
51
|
types-pillow = "^9.5.0.0"
|
42
52
|
pre-commit = "^3.3.0"
|
43
53
|
coverage = "^7.2.7"
|
54
|
+
emoji = "*"
|
44
55
|
|
45
56
|
[tool.poetry.group.dev.dependencies.slidge-dev-helpers]
|
46
57
|
git = "https://git.sr.ht/~nicoco/slidge-dev-helpers"
|
47
58
|
branch = "master"
|
48
59
|
|
49
60
|
[tool.poetry.scripts]
|
50
|
-
slidge = 'slidge.
|
61
|
+
slidge = 'slidge.main:main'
|
51
62
|
|
52
63
|
[tool.mypy]
|
53
64
|
check_untyped_defs = true
|
@@ -56,7 +67,7 @@ exclude = ["tests", "slidge.slixfix.*"]
|
|
56
67
|
|
57
68
|
[[tool.mypy.overrides]]
|
58
69
|
module = [
|
59
|
-
"
|
70
|
+
"thumbhash",
|
60
71
|
"configargparse",
|
61
72
|
"qrcode",
|
62
73
|
]
|
@@ -79,7 +90,6 @@ exclude_lines = [
|
|
79
90
|
]
|
80
91
|
|
81
92
|
[tool.pytest.ini_options]
|
82
|
-
log_cli = true
|
83
93
|
log_level = "DEBUG"
|
84
94
|
asyncio_mode = "strict"
|
85
95
|
filterwarnings = [
|
@@ -13,13 +13,12 @@ from .contact import LegacyContact, LegacyRoster # noqa: F401
|
|
13
13
|
from .core import config as global_config # noqa: F401
|
14
14
|
from .core.gateway import BaseGateway # noqa: F401
|
15
15
|
from .core.session import BaseSession # noqa: F401
|
16
|
+
from .db import GatewayUser # noqa: F401
|
16
17
|
from .group import LegacyBookmarks, LegacyMUC, LegacyParticipant # noqa: F401
|
17
|
-
from .
|
18
|
+
from .main import main as main_func
|
18
19
|
from .util.types import MucType # noqa: F401
|
19
20
|
from .util.util import addLoggingLevel
|
20
21
|
|
21
|
-
from .__main__ import main # isort: skip
|
22
|
-
|
23
22
|
|
24
23
|
def entrypoint(module_name: str) -> None:
|
25
24
|
"""
|
@@ -29,7 +28,7 @@ def entrypoint(module_name: str) -> None:
|
|
29
28
|
:param module_name: An importable :term:`Legacy Module`.
|
30
29
|
"""
|
31
30
|
sys.argv.extend(["--legacy", module_name])
|
32
|
-
|
31
|
+
main_func()
|
33
32
|
|
34
33
|
|
35
34
|
def formatwarning(message, category, filename, lineno, line=""):
|
@@ -54,7 +53,6 @@ __all__ = [
|
|
54
53
|
# "FormField",
|
55
54
|
# "SearchResult",
|
56
55
|
"entrypoint",
|
57
|
-
"user_store",
|
58
56
|
"global_config",
|
59
57
|
]
|
60
58
|
|
@@ -9,11 +9,14 @@ from slixmpp.exceptions import XMPPError
|
|
9
9
|
from slixmpp.plugins.xep_0004 import Form as SlixForm # type: ignore[attr-defined]
|
10
10
|
from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
|
11
11
|
|
12
|
+
from ..core import config
|
13
|
+
from ..util.util import strip_leading_emoji
|
12
14
|
from . import Command, CommandResponseType, Confirmation, Form, TableResult
|
13
15
|
from .base import FormField
|
16
|
+
from .categories import CommandCategory
|
14
17
|
|
15
18
|
if TYPE_CHECKING:
|
16
|
-
from ..core.gateway
|
19
|
+
from ..core.gateway import BaseGateway
|
17
20
|
from ..core.session import BaseSession
|
18
21
|
|
19
22
|
|
@@ -46,20 +49,27 @@ class AdhocProvider:
|
|
46
49
|
return await self.__handle_result(session, result, adhoc_session)
|
47
50
|
|
48
51
|
async def __handle_category_list(
|
49
|
-
self, category:
|
52
|
+
self, category: CommandCategory, iq: Iq, adhoc_session: AdhocSessionType
|
50
53
|
) -> AdhocSessionType:
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
+
try:
|
55
|
+
session = self.xmpp.get_session_from_stanza(iq)
|
56
|
+
except XMPPError:
|
57
|
+
session = None
|
58
|
+
commands: dict[str, Command] = {}
|
59
|
+
for command in self._categories[category.node]:
|
54
60
|
try:
|
55
61
|
command.raise_if_not_authorized(iq.get_from())
|
56
62
|
except XMPPError:
|
57
63
|
continue
|
58
|
-
commands.
|
64
|
+
commands[command.NODE] = command
|
65
|
+
if len(commands) == 0:
|
66
|
+
raise XMPPError(
|
67
|
+
"not-authorized", "There is no command you can run in this category"
|
68
|
+
)
|
59
69
|
return await self.__handle_result(
|
60
70
|
session,
|
61
71
|
Form(
|
62
|
-
category,
|
72
|
+
category.name,
|
63
73
|
"",
|
64
74
|
[
|
65
75
|
FormField(
|
@@ -67,8 +77,11 @@ class AdhocProvider:
|
|
67
77
|
label="Command",
|
68
78
|
type="list-single",
|
69
79
|
options=[
|
70
|
-
{
|
71
|
-
|
80
|
+
{
|
81
|
+
"label": strip_leading_emoji_if_needed(command.NAME),
|
82
|
+
"value": command.NODE,
|
83
|
+
}
|
84
|
+
for command in commands.values()
|
72
85
|
],
|
73
86
|
)
|
74
87
|
],
|
@@ -79,12 +92,12 @@ class AdhocProvider:
|
|
79
92
|
|
80
93
|
async def __handle_category_choice(
|
81
94
|
self,
|
82
|
-
commands:
|
95
|
+
commands: dict[str, Command],
|
83
96
|
form_values: dict[str, str],
|
84
97
|
session: "BaseSession[Any, Any]",
|
85
98
|
jid: JID,
|
86
99
|
):
|
87
|
-
command = commands[
|
100
|
+
command = commands[form_values["command"]]
|
88
101
|
result = await self.__wrap_handler(command.run, session, jid)
|
89
102
|
return result
|
90
103
|
|
@@ -200,19 +213,23 @@ class AdhocProvider:
|
|
200
213
|
self.xmpp.plugin["xep_0050"].add_command( # type: ignore[no-untyped-call]
|
201
214
|
jid=jid,
|
202
215
|
node=command.NODE,
|
203
|
-
name=command.NAME,
|
216
|
+
name=strip_leading_emoji_if_needed(command.NAME),
|
204
217
|
handler=partial(self.__wrap_initial_handler, command),
|
205
218
|
)
|
206
219
|
else:
|
207
|
-
if category
|
208
|
-
|
220
|
+
if isinstance(category, str):
|
221
|
+
category = CommandCategory(category, category)
|
222
|
+
node = category.node
|
223
|
+
name = category.name
|
224
|
+
if node not in self._categories:
|
225
|
+
self._categories[node] = list[Command]()
|
209
226
|
self.xmpp.plugin["xep_0050"].add_command( # type: ignore[no-untyped-call]
|
210
227
|
jid=jid,
|
211
|
-
node=
|
212
|
-
name=
|
228
|
+
node=node,
|
229
|
+
name=strip_leading_emoji_if_needed(name),
|
213
230
|
handler=partial(self.__handle_category_list, category),
|
214
231
|
)
|
215
|
-
self._categories[
|
232
|
+
self._categories[node].append(command)
|
216
233
|
|
217
234
|
async def get_items(self, jid: JID, node: str, iq: Iq) -> DiscoItems:
|
218
235
|
"""
|
@@ -255,4 +272,10 @@ class AdhocProvider:
|
|
255
272
|
return filtered_items
|
256
273
|
|
257
274
|
|
275
|
+
def strip_leading_emoji_if_needed(text: str) -> str:
|
276
|
+
if config.STRIP_LEADING_EMOJI_ADHOC:
|
277
|
+
return strip_leading_emoji(text)
|
278
|
+
return text
|
279
|
+
|
280
|
+
|
258
281
|
log = logging.getLogger(__name__)
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# Commands only accessible for slidge admins
|
2
2
|
import functools
|
3
|
+
import importlib
|
3
4
|
import logging
|
4
5
|
from datetime import datetime
|
5
6
|
from typing import Any, Optional
|
@@ -7,9 +8,10 @@ from typing import Any, Optional
|
|
7
8
|
from slixmpp import JID
|
8
9
|
from slixmpp.exceptions import XMPPError
|
9
10
|
|
10
|
-
from ..
|
11
|
+
from ..core import config
|
11
12
|
from ..util.types import AnyBaseSession
|
12
13
|
from .base import (
|
14
|
+
NODE_PREFIX,
|
13
15
|
Command,
|
14
16
|
CommandAccess,
|
15
17
|
Confirmation,
|
@@ -20,6 +22,8 @@ from .base import (
|
|
20
22
|
)
|
21
23
|
from .categories import ADMINISTRATION
|
22
24
|
|
25
|
+
NODE_PREFIX = NODE_PREFIX + "admin/"
|
26
|
+
|
23
27
|
|
24
28
|
class AdminCommand(Command):
|
25
29
|
ACCESS = CommandAccess.ADMIN_ONLY
|
@@ -29,17 +33,18 @@ class AdminCommand(Command):
|
|
29
33
|
class ListUsers(AdminCommand):
|
30
34
|
NAME = "👤 List registered users"
|
31
35
|
HELP = "List the users registered to this gateway"
|
32
|
-
|
36
|
+
CHAT_COMMAND = "list_users"
|
37
|
+
NODE = NODE_PREFIX + CHAT_COMMAND
|
33
38
|
|
34
39
|
async def run(self, _session, _ifrom, *_):
|
35
40
|
items = []
|
36
|
-
for u in
|
41
|
+
for u in self.xmpp.store.users.get_all():
|
37
42
|
d = u.registration_date
|
38
43
|
if d is None:
|
39
44
|
joined = ""
|
40
45
|
else:
|
41
46
|
joined = d.isoformat(timespec="seconds")
|
42
|
-
items.append({"jid": u.
|
47
|
+
items.append({"jid": u.jid.bare, "joined": joined})
|
43
48
|
return TableResult(
|
44
49
|
description="List of registered users",
|
45
50
|
fields=[FormField("jid", type="jid-single"), FormField("joined")],
|
@@ -50,13 +55,14 @@ class ListUsers(AdminCommand):
|
|
50
55
|
class SlidgeInfo(AdminCommand):
|
51
56
|
NAME = "ℹ️ Server information"
|
52
57
|
HELP = "List the users registered to this gateway"
|
53
|
-
|
58
|
+
CHAT_COMMAND = "info"
|
59
|
+
NODE = NODE_PREFIX + CHAT_COMMAND
|
54
60
|
ACCESS = CommandAccess.ANY
|
55
61
|
|
56
62
|
async def run(self, _session, _ifrom, *_):
|
57
|
-
from
|
63
|
+
from slidge.__version__ import __version__
|
58
64
|
|
59
|
-
start = self.xmpp.datetime_started
|
65
|
+
start = self.xmpp.datetime_started # type:ignore
|
60
66
|
uptime = datetime.now() - start
|
61
67
|
|
62
68
|
if uptime.days:
|
@@ -91,8 +97,12 @@ class SlidgeInfo(AdminCommand):
|
|
91
97
|
[a for a in (days_ago, hours_ago, minutes_ago, seconds_ago) if a]
|
92
98
|
)
|
93
99
|
|
100
|
+
legacy_module = importlib.import_module(config.LEGACY_MODULE)
|
101
|
+
version = getattr(legacy_module, "__version__", "No version")
|
102
|
+
|
94
103
|
return (
|
95
|
-
f"{self.xmpp.COMPONENT_NAME}
|
104
|
+
f"{self.xmpp.COMPONENT_NAME} (slidge core {__version__},"
|
105
|
+
f" {config.LEGACY_MODULE} {version})\n"
|
96
106
|
f"Up since {start:%Y-%m-%d %H:%M} ({ago} ago)"
|
97
107
|
)
|
98
108
|
|
@@ -100,7 +110,8 @@ class SlidgeInfo(AdminCommand):
|
|
100
110
|
class DeleteUser(AdminCommand):
|
101
111
|
NAME = "❌ Delete a user"
|
102
112
|
HELP = "Unregister a user from the gateway"
|
103
|
-
|
113
|
+
CHAT_COMMAND = "delete_user"
|
114
|
+
NODE = NODE_PREFIX + CHAT_COMMAND
|
104
115
|
|
105
116
|
async def run(self, _session, _ifrom, *_):
|
106
117
|
return Form(
|
@@ -114,7 +125,7 @@ class DeleteUser(AdminCommand):
|
|
114
125
|
self, form_values: FormValues, _session: AnyBaseSession, _ifrom: JID
|
115
126
|
) -> Confirmation:
|
116
127
|
jid: JID = form_values.get("jid") # type:ignore
|
117
|
-
user =
|
128
|
+
user = self.xmpp.store.users.get(jid)
|
118
129
|
if user is None:
|
119
130
|
raise XMPPError("item-not-found", text=f"There is no user '{jid}'")
|
120
131
|
|
@@ -127,7 +138,7 @@ class DeleteUser(AdminCommand):
|
|
127
138
|
async def finish(
|
128
139
|
self, _session: Optional[AnyBaseSession], _ifrom: JID, jid: JID
|
129
140
|
) -> None:
|
130
|
-
user =
|
141
|
+
user = self.xmpp.store.users.get(jid)
|
131
142
|
if user is None:
|
132
143
|
raise XMPPError("bad-request", f"{jid} has no account here!")
|
133
144
|
await self.xmpp.unregister_user(user)
|
@@ -136,7 +147,8 @@ class DeleteUser(AdminCommand):
|
|
136
147
|
class ChangeLoglevel(AdminCommand):
|
137
148
|
NAME = "📋 Change the verbosity of the logs"
|
138
149
|
HELP = "Set the logging level"
|
139
|
-
|
150
|
+
CHAT_COMMAND = "loglevel"
|
151
|
+
NODE = NODE_PREFIX + CHAT_COMMAND
|
140
152
|
|
141
153
|
async def run(self, _session, _ifrom, *_):
|
142
154
|
return Form(
|
@@ -6,9 +6,9 @@ from typing import (
|
|
6
6
|
Any,
|
7
7
|
Awaitable,
|
8
8
|
Callable,
|
9
|
-
Collection,
|
10
9
|
Iterable,
|
11
10
|
Optional,
|
11
|
+
Sequence,
|
12
12
|
Type,
|
13
13
|
TypedDict,
|
14
14
|
Union,
|
@@ -23,12 +23,14 @@ from slixmpp.plugins.xep_0004 import (
|
|
23
23
|
from slixmpp.types import JidStr
|
24
24
|
|
25
25
|
from ..core import config
|
26
|
-
from ..util.db import user_store
|
27
26
|
from ..util.types import AnyBaseSession, FieldType
|
28
27
|
|
28
|
+
NODE_PREFIX = "https://slidge.im/command/core/"
|
29
|
+
|
29
30
|
if TYPE_CHECKING:
|
30
31
|
from ..core.gateway import BaseGateway
|
31
32
|
from ..core.session import BaseSession
|
33
|
+
from .categories import CommandCategory
|
32
34
|
|
33
35
|
|
34
36
|
HandlerType = Union[
|
@@ -55,11 +57,11 @@ class TableResult:
|
|
55
57
|
Structured data as the result of a command
|
56
58
|
"""
|
57
59
|
|
58
|
-
fields:
|
60
|
+
fields: Sequence["FormField"]
|
59
61
|
"""
|
60
62
|
The 'columns names' of the table.
|
61
63
|
"""
|
62
|
-
items:
|
64
|
+
items: Sequence[dict[str, Union[str, JID]]]
|
63
65
|
"""
|
64
66
|
The rows of the table. Each row is a dict where keys are the fields ``var``
|
65
67
|
attribute.
|
@@ -150,7 +152,7 @@ class Form:
|
|
150
152
|
|
151
153
|
title: str
|
152
154
|
instructions: str
|
153
|
-
fields:
|
155
|
+
fields: Sequence["FormField"]
|
154
156
|
handler: FormHandlerType
|
155
157
|
handler_args: Iterable[Any] = field(default_factory=list)
|
156
158
|
handler_kwargs: dict[str, Any] = field(default_factory=dict)
|
@@ -179,8 +181,8 @@ class Form:
|
|
179
181
|
"""
|
180
182
|
form = SlixForm() # type: ignore[no-untyped-call]
|
181
183
|
form["type"] = "form"
|
182
|
-
form["instructions"] = self.instructions
|
183
184
|
form["title"] = self.title
|
185
|
+
form["instructions"] = self.instructions
|
184
186
|
for fi in self.fields:
|
185
187
|
form.append(fi.get_xml())
|
186
188
|
return form
|
@@ -348,7 +350,7 @@ class Command(ABC):
|
|
348
350
|
Who can use this command
|
349
351
|
"""
|
350
352
|
|
351
|
-
CATEGORY: Optional[str] = None
|
353
|
+
CATEGORY: Optional[Union[str, "CommandCategory"]] = None
|
352
354
|
"""
|
353
355
|
If used, the command will be under this top-level category.
|
354
356
|
Use the same string for several commands to group them.
|
@@ -382,7 +384,7 @@ class Command(ABC):
|
|
382
384
|
raise XMPPError("feature-not-implemented")
|
383
385
|
|
384
386
|
def _get_session(self, jid: JID) -> Optional["BaseSession[Any, Any]"]:
|
385
|
-
user =
|
387
|
+
user = self.xmpp.store.users.get(jid)
|
386
388
|
if user is None:
|
387
389
|
return None
|
388
390
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
from typing import NamedTuple
|
2
|
+
|
3
|
+
from .base import NODE_PREFIX
|
4
|
+
|
5
|
+
|
6
|
+
class CommandCategory(NamedTuple):
|
7
|
+
name: str
|
8
|
+
node: str
|
9
|
+
|
10
|
+
|
11
|
+
ADMINISTRATION = CommandCategory("🛷️ Slidge administration", NODE_PREFIX + "admin")
|
12
|
+
CONTACTS = CommandCategory("👤 Contacts", NODE_PREFIX + "contacts")
|
13
|
+
GROUPS = CommandCategory("👥 Groups", NODE_PREFIX + "groups")
|
@@ -13,6 +13,7 @@ from slixmpp.exceptions import XMPPError
|
|
13
13
|
from slixmpp.types import JidStr, MessageTypes
|
14
14
|
|
15
15
|
from . import Command, CommandResponseType, Confirmation, Form, TableResult
|
16
|
+
from .categories import CommandCategory
|
16
17
|
|
17
18
|
if TYPE_CHECKING:
|
18
19
|
from ..core.gateway import BaseGateway
|
@@ -153,6 +154,9 @@ class ChatCommandProvider:
|
|
153
154
|
self.xmpp.delivery_receipt.ack(msg)
|
154
155
|
return await self._handle_result(result, msg, session)
|
155
156
|
|
157
|
+
def __make_uri(self, body: str) -> str:
|
158
|
+
return f"xmpp:{self.xmpp.boundjid.bare}?message;body={body}"
|
159
|
+
|
156
160
|
async def _handle_result(self, result: CommandResponseType, msg: Message, session):
|
157
161
|
if isinstance(result, str) or result is None:
|
158
162
|
reply = msg.reply()
|
@@ -175,9 +179,14 @@ class ChatCommandProvider:
|
|
175
179
|
).send()
|
176
180
|
if f.options:
|
177
181
|
for o in f.options:
|
178
|
-
msg.reply(
|
182
|
+
msg.reply(
|
183
|
+
f"{o['label']}: {self.__make_uri(o['value'])}"
|
184
|
+
).send()
|
179
185
|
if f.value:
|
180
186
|
msg.reply(f"Default: {f.value}").send()
|
187
|
+
if f.type == "boolean":
|
188
|
+
msg.reply("yes: " + self.__make_uri("yes")).send()
|
189
|
+
msg.reply("no: " + self.__make_uri("no")).send()
|
181
190
|
|
182
191
|
ans = await self.xmpp.input(
|
183
192
|
msg.get_from(), (f.label or f.var) + "? (or 'abort')"
|
@@ -186,6 +195,12 @@ class ChatCommandProvider:
|
|
186
195
|
return await self._handle_result(
|
187
196
|
"Command aborted", msg, session
|
188
197
|
)
|
198
|
+
if f.type == "boolean":
|
199
|
+
if ans.lower() == "yes":
|
200
|
+
ans = "true"
|
201
|
+
else:
|
202
|
+
ans = "false"
|
203
|
+
|
189
204
|
if f.type.endswith("multi"):
|
190
205
|
form_values[f.var] = f.validate(ans.split(" "))
|
191
206
|
else:
|
@@ -266,7 +281,19 @@ class ChatCommandProvider:
|
|
266
281
|
def _help(self, mfrom: JID):
|
267
282
|
msg = "Available commands:"
|
268
283
|
for c in sorted(
|
269
|
-
self._commands.values(),
|
284
|
+
self._commands.values(),
|
285
|
+
key=lambda co: (
|
286
|
+
(
|
287
|
+
co.CATEGORY
|
288
|
+
if isinstance(co.CATEGORY, str)
|
289
|
+
else (
|
290
|
+
co.CATEGORY.name
|
291
|
+
if isinstance(co.CATEGORY, CommandCategory)
|
292
|
+
else ""
|
293
|
+
)
|
294
|
+
),
|
295
|
+
co.CHAT_COMMAND,
|
296
|
+
),
|
270
297
|
):
|
271
298
|
try:
|
272
299
|
c.raise_if_not_authorized(mfrom)
|
@@ -6,7 +6,6 @@ step for a JID to become a slidge :term:`User`.
|
|
6
6
|
import asyncio
|
7
7
|
import functools
|
8
8
|
import tempfile
|
9
|
-
from datetime import datetime
|
10
9
|
from enum import IntEnum
|
11
10
|
from typing import Any
|
12
11
|
|
@@ -15,8 +14,10 @@ from slixmpp import JID, Iq
|
|
15
14
|
from slixmpp.exceptions import XMPPError
|
16
15
|
|
17
16
|
from ..core import config
|
18
|
-
from ..
|
17
|
+
from ..db import GatewayUser
|
18
|
+
from ..util.types import UserPreferences
|
19
19
|
from .base import Command, CommandAccess, Form, FormField, FormValues
|
20
|
+
from .user import Preferences
|
20
21
|
|
21
22
|
|
22
23
|
class RegistrationType(IntEnum):
|
@@ -66,9 +67,12 @@ class Register(Command):
|
|
66
67
|
|
67
68
|
SUCCESS_MESSAGE = "Success, welcome!"
|
68
69
|
|
69
|
-
def _finalize(
|
70
|
-
user
|
71
|
-
|
70
|
+
def _finalize(
|
71
|
+
self, form_values: UserPreferences, _session, ifrom: JID, user: GatewayUser, *_
|
72
|
+
) -> str:
|
73
|
+
user.preferences = form_values # type: ignore
|
74
|
+
self.xmpp.store.users.update(user)
|
75
|
+
self.xmpp.event("user_register", Iq(sfrom=ifrom.bare))
|
72
76
|
return self.SUCCESS_MESSAGE
|
73
77
|
|
74
78
|
async def run(self, _session, _ifrom, *_):
|
@@ -82,26 +86,26 @@ class Register(Command):
|
|
82
86
|
async def register(self, form_values: dict[str, Any], _session, ifrom: JID):
|
83
87
|
two_fa_needed = True
|
84
88
|
try:
|
85
|
-
await self.xmpp.user_prevalidate(ifrom, form_values)
|
89
|
+
data = await self.xmpp.user_prevalidate(ifrom, form_values)
|
86
90
|
except ValueError as e:
|
87
91
|
raise XMPPError("bad-request", str(e))
|
88
92
|
except TwoFactorNotRequired:
|
93
|
+
data = None
|
89
94
|
if self.xmpp.REGISTRATION_TYPE == RegistrationType.TWO_FACTOR_CODE:
|
90
95
|
two_fa_needed = False
|
91
96
|
else:
|
92
97
|
raise
|
93
98
|
|
94
99
|
user = GatewayUser(
|
95
|
-
|
96
|
-
|
97
|
-
registration_date=datetime.now(),
|
100
|
+
jid=ifrom.bare,
|
101
|
+
legacy_module_data=form_values if data is None else data,
|
98
102
|
)
|
99
103
|
|
100
104
|
if self.xmpp.REGISTRATION_TYPE == RegistrationType.SINGLE_STEP_FORM or (
|
101
105
|
self.xmpp.REGISTRATION_TYPE == RegistrationType.TWO_FACTOR_CODE
|
102
106
|
and not two_fa_needed
|
103
107
|
):
|
104
|
-
return self.
|
108
|
+
return await self.preferences(user)
|
105
109
|
|
106
110
|
if self.xmpp.REGISTRATION_TYPE == RegistrationType.TWO_FACTOR_CODE:
|
107
111
|
return Form(
|
@@ -113,7 +117,7 @@ class Register(Command):
|
|
113
117
|
|
114
118
|
elif self.xmpp.REGISTRATION_TYPE == RegistrationType.QRCODE:
|
115
119
|
self.xmpp.qr_pending_registrations[ # type:ignore
|
116
|
-
user.
|
120
|
+
user.jid.bare
|
117
121
|
] = (
|
118
122
|
self.xmpp.loop.create_future()
|
119
123
|
)
|
@@ -159,13 +163,15 @@ class Register(Command):
|
|
159
163
|
self, form_values: FormValues, _session, _ifrom, user: GatewayUser
|
160
164
|
):
|
161
165
|
assert isinstance(form_values["code"], str)
|
162
|
-
await self.xmpp.validate_two_factor_code(user, form_values["code"])
|
163
|
-
|
166
|
+
data = await self.xmpp.validate_two_factor_code(user, form_values["code"])
|
167
|
+
if data is not None:
|
168
|
+
user.legacy_module_data.update(data)
|
169
|
+
return await self.preferences(user)
|
164
170
|
|
165
171
|
async def qr(self, _form_values: FormValues, _session, _ifrom, user: GatewayUser):
|
166
172
|
try:
|
167
|
-
await asyncio.wait_for(
|
168
|
-
self.xmpp.qr_pending_registrations[user.
|
173
|
+
data = await asyncio.wait_for(
|
174
|
+
self.xmpp.qr_pending_registrations[user.jid.bare], # type:ignore
|
169
175
|
config.QR_TIMEOUT,
|
170
176
|
)
|
171
177
|
except asyncio.TimeoutError:
|
@@ -176,4 +182,14 @@ class Register(Command):
|
|
176
182
|
"or you took too much time"
|
177
183
|
),
|
178
184
|
)
|
179
|
-
|
185
|
+
if data is not None:
|
186
|
+
user.legacy_module_data.update(data)
|
187
|
+
return await self.preferences(user)
|
188
|
+
|
189
|
+
async def preferences(self, user: GatewayUser) -> Form:
|
190
|
+
return Form(
|
191
|
+
title="Preferences",
|
192
|
+
instructions=Preferences.HELP,
|
193
|
+
fields=self.xmpp.PREFERENCES,
|
194
|
+
handler=functools.partial(self._finalize, user=user), # type:ignore
|
195
|
+
)
|