slidge 0.1.3__tar.gz → 0.2.0__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. {slidge-0.1.3 → slidge-0.2.0}/PKG-INFO +10 -5
  2. {slidge-0.1.3 → slidge-0.2.0}/pyproject.toml +17 -7
  3. {slidge-0.1.3 → slidge-0.2.0}/slidge/__init__.py +3 -5
  4. slidge-0.2.0/slidge/__main__.py +3 -0
  5. slidge-0.2.0/slidge/__version__.py +5 -0
  6. {slidge-0.1.3 → slidge-0.2.0}/slidge/command/adhoc.py +40 -17
  7. {slidge-0.1.3 → slidge-0.2.0}/slidge/command/admin.py +24 -12
  8. {slidge-0.1.3 → slidge-0.2.0}/slidge/command/base.py +10 -8
  9. slidge-0.2.0/slidge/command/categories.py +13 -0
  10. {slidge-0.1.3 → slidge-0.2.0}/slidge/command/chat_command.py +29 -2
  11. {slidge-0.1.3 → slidge-0.2.0}/slidge/command/register.py +32 -16
  12. {slidge-0.1.3 → slidge-0.2.0}/slidge/command/user.py +106 -13
  13. {slidge-0.1.3 → slidge-0.2.0}/slidge/contact/contact.py +254 -50
  14. slidge-0.2.0/slidge/contact/roster.py +263 -0
  15. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/config.py +19 -13
  16. slidge-0.2.0/slidge/core/dispatcher/__init__.py +3 -0
  17. {slidge-0.1.3/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher}/caps.py +12 -8
  18. {slidge-0.1.3/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher}/disco.py +10 -18
  19. slidge-0.2.0/slidge/core/dispatcher/message/__init__.py +10 -0
  20. slidge-0.2.0/slidge/core/dispatcher/message/chat_state.py +40 -0
  21. slidge-0.2.0/slidge/core/dispatcher/message/marker.py +62 -0
  22. slidge-0.2.0/slidge/core/dispatcher/message/message.py +397 -0
  23. slidge-0.2.0/slidge/core/dispatcher/muc/__init__.py +12 -0
  24. slidge-0.2.0/slidge/core/dispatcher/muc/admin.py +98 -0
  25. {slidge-0.1.3/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher/muc}/mam.py +25 -17
  26. slidge-0.2.0/slidge/core/dispatcher/muc/misc.py +121 -0
  27. slidge-0.2.0/slidge/core/dispatcher/muc/owner.py +96 -0
  28. {slidge-0.1.3/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher/muc}/ping.py +11 -17
  29. slidge-0.2.0/slidge/core/dispatcher/presence.py +176 -0
  30. slidge-0.2.0/slidge/core/dispatcher/registration.py +85 -0
  31. {slidge-0.1.3/slidge/core/gateway → slidge-0.2.0/slidge/core/dispatcher}/search.py +9 -16
  32. slidge-0.2.0/slidge/core/dispatcher/session_dispatcher.py +84 -0
  33. slidge-0.2.0/slidge/core/dispatcher/util.py +174 -0
  34. slidge-0.1.3/slidge/core/gateway/vcard_temp.py → slidge-0.2.0/slidge/core/dispatcher/vcard.py +35 -19
  35. slidge-0.1.3/slidge/core/gateway/base.py → slidge-0.2.0/slidge/core/gateway.py +176 -153
  36. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/__init__.py +11 -1
  37. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/attachment.py +106 -67
  38. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/avatar.py +94 -25
  39. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/base.py +10 -4
  40. slidge-0.2.0/slidge/core/mixins/db.py +18 -0
  41. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/disco.py +0 -10
  42. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/lock.py +10 -8
  43. slidge-0.2.0/slidge/core/mixins/message.py +214 -0
  44. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/message_maker.py +17 -9
  45. slidge-0.2.0/slidge/core/mixins/message_text.py +211 -0
  46. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/presence.py +17 -4
  47. slidge-0.2.0/slidge/core/pubsub.py +351 -0
  48. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/session.py +101 -40
  49. slidge-0.2.0/slidge/db/__init__.py +4 -0
  50. slidge-0.2.0/slidge/db/alembic/env.py +64 -0
  51. slidge-0.1.3/slidge/util/db.py → slidge-0.2.0/slidge/db/alembic/old_user_store.py +1 -47
  52. slidge-0.2.0/slidge/db/alembic/script.py.mako +26 -0
  53. slidge-0.2.0/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  54. slidge-0.2.0/slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +85 -0
  55. slidge-0.2.0/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
  56. slidge-0.2.0/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  57. slidge-0.2.0/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
  58. slidge-0.2.0/slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +52 -0
  59. slidge-0.2.0/slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
  60. slidge-0.2.0/slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +61 -0
  61. slidge-0.2.0/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
  62. slidge-0.2.0/slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
  63. slidge-0.2.0/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +139 -0
  64. slidge-0.2.0/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +101 -0
  65. slidge-0.2.0/slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +79 -0
  66. slidge-0.2.0/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  67. slidge-0.2.0/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +52 -0
  68. slidge-0.2.0/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
  69. slidge-0.2.0/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  70. slidge-0.2.0/slidge/db/avatar.py +205 -0
  71. slidge-0.2.0/slidge/db/meta.py +72 -0
  72. slidge-0.2.0/slidge/db/models.py +405 -0
  73. slidge-0.2.0/slidge/db/store.py +1257 -0
  74. {slidge-0.1.3 → slidge-0.2.0}/slidge/group/archive.py +58 -14
  75. slidge-0.2.0/slidge/group/bookmarks.py +187 -0
  76. {slidge-0.1.3 → slidge-0.2.0}/slidge/group/participant.py +107 -40
  77. {slidge-0.1.3 → slidge-0.2.0}/slidge/group/room.py +402 -213
  78. slidge-0.1.3/slidge/__main__.py → slidge-0.2.0/slidge/main.py +24 -20
  79. slidge-0.2.0/slidge/migration.py +62 -0
  80. slidge-0.2.0/slidge/py.typed +0 -0
  81. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/__init__.py +31 -1
  82. {slidge-0.1.3/slidge/core/gateway → slidge-0.2.0/slidge/slixfix}/delivery_receipt.py +1 -1
  83. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/roster.py +13 -4
  84. slidge-0.2.0/slidge/slixfix/xep_0292/vcard4.py +14 -0
  85. {slidge-0.1.3 → slidge-0.2.0}/slidge/util/archive_msg.py +2 -1
  86. slidge-0.2.0/slidge/util/db.py +5 -0
  87. {slidge-0.1.3 → slidge-0.2.0}/slidge/util/test.py +91 -4
  88. {slidge-0.1.3 → slidge-0.2.0}/slidge/util/types.py +39 -4
  89. {slidge-0.1.3 → slidge-0.2.0}/slidge/util/util.py +45 -2
  90. slidge-0.1.3/slidge/command/categories.py +0 -3
  91. slidge-0.1.3/slidge/contact/roster.py +0 -192
  92. slidge-0.1.3/slidge/core/cache.py +0 -183
  93. slidge-0.1.3/slidge/core/gateway/__init__.py +0 -3
  94. slidge-0.1.3/slidge/core/gateway/muc_admin.py +0 -35
  95. slidge-0.1.3/slidge/core/gateway/presence.py +0 -95
  96. slidge-0.1.3/slidge/core/gateway/registration.py +0 -53
  97. slidge-0.1.3/slidge/core/gateway/session_dispatcher.py +0 -804
  98. slidge-0.1.3/slidge/core/mixins/message.py +0 -398
  99. slidge-0.1.3/slidge/core/pubsub.py +0 -525
  100. slidge-0.1.3/slidge/group/bookmarks.py +0 -163
  101. slidge-0.1.3/slidge/migration.py +0 -18
  102. slidge-0.1.3/slidge/slixfix/xep_0292/vcard4.py +0 -100
  103. slidge-0.1.3/slidge/util/schema.sql +0 -126
  104. slidge-0.1.3/slidge/util/sql.py +0 -508
  105. {slidge-0.1.3 → slidge-0.2.0}/LICENSE +0 -0
  106. {slidge-0.1.3 → slidge-0.2.0}/README.md +0 -0
  107. {slidge-0.1.3 → slidge-0.2.0}/slidge/command/__init__.py +0 -0
  108. {slidge-0.1.3 → slidge-0.2.0}/slidge/contact/__init__.py +0 -0
  109. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/__init__.py +0 -0
  110. {slidge-0.1.3 → slidge-0.2.0}/slidge/core/mixins/recipient.py +0 -0
  111. /slidge-0.1.3/slidge/py.typed → /slidge-0.2.0/slidge/db/alembic/__init__.py +0 -0
  112. {slidge-0.1.3 → slidge-0.2.0}/slidge/group/__init__.py +0 -0
  113. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/link_preview/__init__.py +0 -0
  114. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/link_preview/link_preview.py +0 -0
  115. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/link_preview/stanza.py +0 -0
  116. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0077/__init__.py +0 -0
  117. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0077/register.py +0 -0
  118. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0077/stanza.py +0 -0
  119. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0100/__init__.py +0 -0
  120. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0100/gateway.py +0 -0
  121. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0100/stanza.py +0 -0
  122. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0153/__init__.py +0 -0
  123. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0153/stanza.py +0 -0
  124. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
  125. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0264/__init__.py +0 -0
  126. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0264/stanza.py +0 -0
  127. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
  128. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0292/__init__.py +0 -0
  129. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0313/__init__.py +0 -0
  130. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0313/mam.py +0 -0
  131. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0313/stanza.py +0 -0
  132. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0317/__init__.py +0 -0
  133. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0317/hats.py +0 -0
  134. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0317/stanza.py +0 -0
  135. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
  136. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
  137. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
  138. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0424/__init__.py +0 -0
  139. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0424/retraction.py +0 -0
  140. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0424/stanza.py +0 -0
  141. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0490/__init__.py +0 -0
  142. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0490/mds.py +0 -0
  143. {slidge-0.1.3 → slidge-0.2.0}/slidge/slixfix/xep_0490/stanza.py +0 -0
  144. {slidge-0.1.3 → slidge-0.2.0}/slidge/util/__init__.py +0 -0
  145. {slidge-0.1.3 → 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.1.3
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,<4.0
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: blurhash-python (>=1.2.1,<2.0.0)
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.1.3"
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
- include = ["slidge/util/schema.sql"]
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.9"
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
- blurhash-python = "^1.2.1"
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.__main__:main'
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
- "blurhash",
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 .util.db import GatewayUser, user_store # noqa: F401
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
- main()
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
 
@@ -0,0 +1,3 @@
1
+ from slidge.main import main
2
+
3
+ main()
@@ -0,0 +1,5 @@
1
+ from slidge.util.util import get_version # noqa: F401
2
+
3
+ # this is modified before publish, but if someone cloned from the repo,
4
+ # it can help
5
+ __version__ = "0.2.0"
@@ -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.base import BaseGateway
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: str, iq: Iq, adhoc_session: AdhocSessionType
52
+ self, category: CommandCategory, iq: Iq, adhoc_session: AdhocSessionType
50
53
  ) -> AdhocSessionType:
51
- session = self.xmpp.get_session_from_stanza(iq)
52
- commands = []
53
- for command in self._categories[category]:
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.append(command)
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
- {"label": command.NAME, "value": str(i)}
71
- for i, command in enumerate(commands)
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: list[Command],
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[int(form_values["command"])]
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 not in self._categories:
208
- self._categories[category] = list[Command]()
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=category,
212
- name=category,
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[category].append(command)
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 ..util.db import user_store
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
- NODE = CHAT_COMMAND = "list_users"
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 user_store.get_all():
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.bare_jid, "joined": joined})
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
- NODE = CHAT_COMMAND = "info"
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 ..__main__ import __version__
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} version {__version__}\n"
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
- NODE = CHAT_COMMAND = "delete_user"
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 = user_store.get_by_jid(jid)
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 = user_store.get_by_jid(jid)
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
- NODE = CHAT_COMMAND = "loglevel"
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: Collection["FormField"]
60
+ fields: Sequence["FormField"]
59
61
  """
60
62
  The 'columns names' of the table.
61
63
  """
62
- items: Collection[dict[str, Union[str, JID]]]
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: Collection["FormField"]
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 = user_store.get_by_jid(jid)
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(f"{o['value']} -- {o['label']}").send()
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(), key=lambda co: (co.CATEGORY or "", co.CHAT_COMMAND)
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 ..util.db import GatewayUser
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(self, user: GatewayUser):
70
- user.commit()
71
- self.xmpp.event("user_register", Iq(sfrom=user.jid))
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
- bare_jid=ifrom.bare,
96
- registration_form=form_values,
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._finalize(user)
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.bare_jid
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
- return self._finalize(user)
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.bare_jid], # type:ignore
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
- return self._finalize(user)
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
+ )