slidge 0.1.3__tar.gz → 0.2.0a1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. {slidge-0.1.3 → slidge-0.2.0a1}/PKG-INFO +4 -4
  2. {slidge-0.1.3 → slidge-0.2.0a1}/pyproject.toml +4 -2
  3. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/__init__.py +3 -5
  4. slidge-0.2.0a1/slidge/__main__.py +4 -0
  5. slidge-0.2.0a1/slidge/__version__.py +5 -0
  6. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/command/adhoc.py +8 -1
  7. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/command/admin.py +6 -7
  8. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/command/base.py +1 -2
  9. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/command/register.py +32 -16
  10. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/command/user.py +85 -6
  11. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/contact/contact.py +165 -49
  12. slidge-0.2.0a1/slidge/contact/roster.py +267 -0
  13. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/config.py +14 -11
  14. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/base.py +148 -36
  15. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/caps.py +7 -5
  16. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/disco.py +2 -4
  17. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/mam.py +1 -4
  18. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/muc_admin.py +1 -1
  19. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/ping.py +2 -3
  20. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/presence.py +1 -1
  21. slidge-0.2.0a1/slidge/core/gateway/registration.py +64 -0
  22. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/search.py +3 -5
  23. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/session_dispatcher.py +120 -57
  24. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/vcard_temp.py +7 -5
  25. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/__init__.py +11 -1
  26. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/attachment.py +32 -14
  27. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/avatar.py +90 -25
  28. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/base.py +8 -2
  29. slidge-0.2.0a1/slidge/core/mixins/db.py +18 -0
  30. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/disco.py +0 -10
  31. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/message.py +18 -8
  32. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/message_maker.py +17 -9
  33. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/presence.py +17 -4
  34. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/pubsub.py +54 -220
  35. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/session.py +69 -34
  36. slidge-0.2.0a1/slidge/db/__init__.py +4 -0
  37. slidge-0.2.0a1/slidge/db/alembic/env.py +64 -0
  38. slidge-0.2.0a1/slidge/db/alembic/script.py.mako +26 -0
  39. slidge-0.2.0a1/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  40. slidge-0.2.0a1/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
  41. slidge-0.2.0a1/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  42. slidge-0.2.0a1/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
  43. slidge-0.2.0a1/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
  44. slidge-0.2.0a1/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
  45. slidge-0.2.0a1/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +85 -0
  46. slidge-0.2.0a1/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  47. slidge-0.2.0a1/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +48 -0
  48. slidge-0.2.0a1/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
  49. slidge-0.2.0a1/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  50. slidge-0.2.0a1/slidge/db/avatar.py +235 -0
  51. slidge-0.2.0a1/slidge/db/meta.py +65 -0
  52. slidge-0.2.0a1/slidge/db/models.py +375 -0
  53. slidge-0.2.0a1/slidge/db/store.py +1078 -0
  54. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/group/archive.py +58 -14
  55. slidge-0.2.0a1/slidge/group/bookmarks.py +178 -0
  56. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/group/participant.py +87 -28
  57. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/group/room.py +369 -211
  58. slidge-0.1.3/slidge/__main__.py → slidge-0.2.0a1/slidge/main.py +18 -15
  59. slidge-0.2.0a1/slidge/migration.py +48 -0
  60. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/__init__.py +35 -2
  61. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/roster.py +11 -4
  62. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0292/vcard4.py +3 -0
  63. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/util/archive_msg.py +2 -1
  64. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/util/db.py +1 -47
  65. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/util/test.py +71 -4
  66. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/util/types.py +29 -4
  67. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/util/util.py +22 -0
  68. slidge-0.1.3/slidge/contact/roster.py +0 -192
  69. slidge-0.1.3/slidge/core/cache.py +0 -183
  70. slidge-0.1.3/slidge/core/gateway/registration.py +0 -53
  71. slidge-0.1.3/slidge/group/bookmarks.py +0 -163
  72. slidge-0.1.3/slidge/migration.py +0 -18
  73. slidge-0.1.3/slidge/util/schema.sql +0 -126
  74. slidge-0.1.3/slidge/util/sql.py +0 -508
  75. {slidge-0.1.3 → slidge-0.2.0a1}/LICENSE +0 -0
  76. {slidge-0.1.3 → slidge-0.2.0a1}/README.md +0 -0
  77. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/command/__init__.py +0 -0
  78. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/command/categories.py +0 -0
  79. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/command/chat_command.py +0 -0
  80. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/contact/__init__.py +0 -0
  81. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/__init__.py +0 -0
  82. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/__init__.py +0 -0
  83. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/gateway/delivery_receipt.py +0 -0
  84. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/lock.py +0 -0
  85. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/core/mixins/recipient.py +0 -0
  86. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/group/__init__.py +0 -0
  87. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/py.typed +0 -0
  88. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/link_preview/__init__.py +0 -0
  89. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/link_preview/link_preview.py +0 -0
  90. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/link_preview/stanza.py +0 -0
  91. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0077/__init__.py +0 -0
  92. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0077/register.py +0 -0
  93. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0077/stanza.py +0 -0
  94. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0100/__init__.py +0 -0
  95. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0100/gateway.py +0 -0
  96. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0100/stanza.py +0 -0
  97. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0153/__init__.py +0 -0
  98. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0153/stanza.py +0 -0
  99. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
  100. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0264/__init__.py +0 -0
  101. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0264/stanza.py +0 -0
  102. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
  103. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0292/__init__.py +0 -0
  104. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0313/__init__.py +0 -0
  105. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0313/mam.py +0 -0
  106. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0313/stanza.py +0 -0
  107. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0317/__init__.py +0 -0
  108. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0317/hats.py +0 -0
  109. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0317/stanza.py +0 -0
  110. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
  111. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
  112. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
  113. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0424/__init__.py +0 -0
  114. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0424/retraction.py +0 -0
  115. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0424/stanza.py +0 -0
  116. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0490/__init__.py +0 -0
  117. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0490/mds.py +0 -0
  118. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/slixfix/xep_0490/stanza.py +0 -0
  119. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/util/__init__.py +0 -0
  120. {slidge-0.1.3 → slidge-0.2.0a1}/slidge/util/conf.py +0 -0
@@ -1,25 +1,25 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slidge
3
- Version: 0.1.3
3
+ Version: 0.2.0a1
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
15
13
  Requires-Dist: ConfigArgParse (>=1.5.3,<2.0.0)
16
14
  Requires-Dist: Pillow (>=10,<11)
17
15
  Requires-Dist: aiohttp[speedups] (>=3.8.3,<4.0.0)
16
+ Requires-Dist: alembic (>=1.13.1,<2.0.0)
18
17
  Requires-Dist: blurhash-python (>=1.2.1,<2.0.0)
19
18
  Requires-Dist: pickle-secure (>=0.99.9,<0.100.0)
20
19
  Requires-Dist: python-magic (>=0.4.27,<0.5.0)
21
20
  Requires-Dist: qrcode (>=7.4.1,<8.0.0)
22
21
  Requires-Dist: slixmpp (>=1.8.5,<2.0.0)
22
+ Requires-Dist: sqlalchemy (>=2.0.29,<3.0.0)
23
23
  Project-URL: Documentation, https://slidge.im/
24
24
  Project-URL: Repository, https://git.sr.ht/~nicoco/slidge/
25
25
  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.0alpha1"
4
4
  description = "XMPP bridging framework"
5
5
  authors = ["Nicolas Cedilnik <nicoco@nicoco.fr>"]
6
6
  readme = "README.md"
@@ -11,7 +11,7 @@ documentation = "https://slidge.im/"
11
11
  include = ["slidge/util/schema.sql"]
12
12
 
13
13
  [tool.poetry.dependencies]
14
- python = "^3.9"
14
+ python = "^3.11"
15
15
  qrcode = "^7.4.1"
16
16
  Pillow = "^10"
17
17
  aiohttp = {version = "^3.8.3", extras = ["speedups"]}
@@ -20,6 +20,8 @@ pickle-secure = "^0.99.9"
20
20
  python-magic = "^0.4.27"
21
21
  slixmpp = "^1.8.5"
22
22
  blurhash-python = "^1.2.1"
23
+ sqlalchemy = "^2.0.29"
24
+ alembic = "^1.13.1"
23
25
 
24
26
  [build-system]
25
27
  requires = ["poetry-core>=1.0.0"]
@@ -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,4 @@
1
+ if __name__ == "__main__":
2
+ from slidge.main import main
3
+
4
+ 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.0alpha1"
@@ -48,7 +48,10 @@ class AdhocProvider:
48
48
  async def __handle_category_list(
49
49
  self, category: str, iq: Iq, adhoc_session: AdhocSessionType
50
50
  ) -> AdhocSessionType:
51
- session = self.xmpp.get_session_from_stanza(iq)
51
+ try:
52
+ session = self.xmpp.get_session_from_stanza(iq)
53
+ except XMPPError:
54
+ session = None
52
55
  commands = []
53
56
  for command in self._categories[category]:
54
57
  try:
@@ -56,6 +59,10 @@ class AdhocProvider:
56
59
  except XMPPError:
57
60
  continue
58
61
  commands.append(command)
62
+ if len(commands) == 0:
63
+ raise XMPPError(
64
+ "not-authorized", "There is no command you can run in this category"
65
+ )
59
66
  return await self.__handle_result(
60
67
  session,
61
68
  Form(
@@ -7,7 +7,6 @@ from typing import Any, Optional
7
7
  from slixmpp import JID
8
8
  from slixmpp.exceptions import XMPPError
9
9
 
10
- from ..util.db import user_store
11
10
  from ..util.types import AnyBaseSession
12
11
  from .base import (
13
12
  Command,
@@ -33,13 +32,13 @@ class ListUsers(AdminCommand):
33
32
 
34
33
  async def run(self, _session, _ifrom, *_):
35
34
  items = []
36
- for u in user_store.get_all():
35
+ for u in self.xmpp.store.users.get_all():
37
36
  d = u.registration_date
38
37
  if d is None:
39
38
  joined = ""
40
39
  else:
41
40
  joined = d.isoformat(timespec="seconds")
42
- items.append({"jid": u.bare_jid, "joined": joined})
41
+ items.append({"jid": u.jid.bare, "joined": joined})
43
42
  return TableResult(
44
43
  description="List of registered users",
45
44
  fields=[FormField("jid", type="jid-single"), FormField("joined")],
@@ -54,9 +53,9 @@ class SlidgeInfo(AdminCommand):
54
53
  ACCESS = CommandAccess.ANY
55
54
 
56
55
  async def run(self, _session, _ifrom, *_):
57
- from ..__main__ import __version__
56
+ from slidge.__version__ import __version__
58
57
 
59
- start = self.xmpp.datetime_started
58
+ start = self.xmpp.datetime_started # type:ignore
60
59
  uptime = datetime.now() - start
61
60
 
62
61
  if uptime.days:
@@ -114,7 +113,7 @@ class DeleteUser(AdminCommand):
114
113
  self, form_values: FormValues, _session: AnyBaseSession, _ifrom: JID
115
114
  ) -> Confirmation:
116
115
  jid: JID = form_values.get("jid") # type:ignore
117
- user = user_store.get_by_jid(jid)
116
+ user = self.xmpp.store.users.get(jid)
118
117
  if user is None:
119
118
  raise XMPPError("item-not-found", text=f"There is no user '{jid}'")
120
119
 
@@ -127,7 +126,7 @@ class DeleteUser(AdminCommand):
127
126
  async def finish(
128
127
  self, _session: Optional[AnyBaseSession], _ifrom: JID, jid: JID
129
128
  ) -> None:
130
- user = user_store.get_by_jid(jid)
129
+ user = self.xmpp.store.users.get(jid)
131
130
  if user is None:
132
131
  raise XMPPError("bad-request", f"{jid} has no account here!")
133
132
  await self.xmpp.unregister_user(user)
@@ -23,7 +23,6 @@ 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
 
29
28
  if TYPE_CHECKING:
@@ -382,7 +381,7 @@ class Command(ABC):
382
381
  raise XMPPError("feature-not-implemented")
383
382
 
384
383
  def _get_session(self, jid: JID) -> Optional["BaseSession[Any, Any]"]:
385
- user = user_store.get_by_jid(jid)
384
+ user = self.xmpp.store.users.get(jid)
386
385
  if user is None:
387
386
  return None
388
387
 
@@ -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
+ )
@@ -1,10 +1,12 @@
1
1
  # Commands available to users
2
+ from copy import deepcopy
2
3
  from typing import TYPE_CHECKING, Any, Optional, Union, cast
3
4
 
4
5
  from slixmpp import JID # type:ignore[attr-defined]
5
6
  from slixmpp.exceptions import XMPPError
6
7
 
7
- from ..util.types import AnyBaseSession, LegacyGroupIdType
8
+ from ..group.room import LegacyMUC
9
+ from ..util.types import AnyBaseSession, LegacyGroupIdType, UserPreferences
8
10
  from .base import (
9
11
  Command,
10
12
  CommandAccess,
@@ -76,7 +78,7 @@ class SyncContacts(Command):
76
78
  async def sync(self, session: Optional[AnyBaseSession], _ifrom: JID) -> str:
77
79
  if session is None:
78
80
  raise RuntimeError
79
- roster_iq = await self.xmpp["xep_0356"].get_roster(session.user.bare_jid)
81
+ roster_iq = await self.xmpp["xep_0356"].get_roster(session.user_jid.bare)
80
82
 
81
83
  contacts = session.contacts.known_contacts()
82
84
 
@@ -90,13 +92,13 @@ class SyncContacts(Command):
90
92
  if contact is None:
91
93
  if len(groups) == 1:
92
94
  await self.xmpp["xep_0356"].set_roster(
93
- session.user.jid, {item["jid"]: {"subscription": "remove"}}
95
+ session.user_jid, {item["jid"]: {"subscription": "remove"}}
94
96
  )
95
97
  removed += 1
96
98
  else:
97
99
  groups.remove(self.xmpp.ROSTER_GROUP)
98
100
  await self.xmpp["xep_0356"].set_roster(
99
- session.user.jid,
101
+ session.user_jid,
100
102
  {
101
103
  item["jid"]: {
102
104
  "subscription": item["subscription"],
@@ -129,7 +131,6 @@ class ListContacts(Command):
129
131
  self, session: Optional[AnyBaseSession], _ifrom: JID, *_
130
132
  ) -> TableResult:
131
133
  assert session is not None
132
- await session.contacts.fill()
133
134
  contacts = sorted(
134
135
  session.contacts, key=lambda c: c.name.casefold() if c.name else ""
135
136
  )
@@ -229,6 +230,39 @@ class CreateGroup(Command):
229
230
  )
230
231
 
231
232
 
233
+ class Preferences(Command):
234
+ NAME = "⚙️ Preferences"
235
+ HELP = "Customize the gateway behaviour to your liking"
236
+ NODE = CHAT_COMMAND = "preferences"
237
+ ACCESS = CommandAccess.USER
238
+
239
+ async def run(
240
+ self, session: Optional[AnyBaseSession], _ifrom: JID, *_: Any
241
+ ) -> Form:
242
+ fields = deepcopy(self.xmpp.PREFERENCES)
243
+ assert session is not None
244
+ current = session.user.preferences
245
+ for field in fields:
246
+ field.value = current.get(field.var) # type:ignore
247
+ return Form(
248
+ title="Preferences",
249
+ instructions=self.HELP,
250
+ fields=fields,
251
+ handler=self.finish, # type:ignore
252
+ )
253
+
254
+ async def finish(
255
+ self, form_values: UserPreferences, session: Optional[AnyBaseSession], *_
256
+ ) -> str:
257
+ assert session is not None
258
+ user = session.user
259
+ user.preferences.update(form_values) # type:ignore
260
+ self.xmpp.store.users.update(user)
261
+ if form_values["sync_avatar"]:
262
+ await self.xmpp.fetch_user_avatar(session)
263
+ return "Your preferences have been updated."
264
+
265
+
232
266
  class Unregister(Command):
233
267
  NAME = "❌ Unregister from the gateway"
234
268
  HELP = "Unregister from the gateway"
@@ -246,5 +280,50 @@ class Unregister(Command):
246
280
 
247
281
  async def unregister(self, session: Optional[AnyBaseSession], _ifrom: JID) -> str:
248
282
  assert session is not None
249
- await self.xmpp.unregister_user(session.user)
283
+ user = self.xmpp.store.users.get(session.user_jid)
284
+ assert user is not None
285
+ await self.xmpp.unregister_user(user)
250
286
  return "OK"
287
+
288
+
289
+ class LeaveGroup(Command):
290
+ NAME = HELP = "❌ Leave a legacy group"
291
+ NODE = CHAT_COMMAND = "leave-group"
292
+ ACCESS = CommandAccess.USER_LOGGED
293
+ CATEGORY = GROUPS
294
+
295
+ async def run(self, session, _ifrom, *_):
296
+ assert session is not None
297
+ await session.bookmarks.fill()
298
+ groups = sorted(session.bookmarks, key=lambda g: g.DISCO_NAME.casefold())
299
+ return Form(
300
+ title="Leave a group",
301
+ instructions="Select the group you want to leave",
302
+ fields=[
303
+ FormField(
304
+ "group",
305
+ "Group name",
306
+ options=[{"label": g.name, "value": g.name} for g in groups],
307
+ )
308
+ ],
309
+ handler=self.confirm, # type:ignore
310
+ handler_args=(groups,),
311
+ )
312
+
313
+ async def confirm(
314
+ self,
315
+ form_values: FormValues,
316
+ _session: AnyBaseSession,
317
+ _ifrom,
318
+ groups: list[LegacyMUC],
319
+ ):
320
+ group = groups[int(form_values["group"])] # type:ignore
321
+ return Confirmation(
322
+ prompt=f"Are you sure you want to leave the group '{group.name}'?",
323
+ handler=self.finish, # type:ignore
324
+ handler_args=(group,),
325
+ )
326
+
327
+ @staticmethod
328
+ async def finish(session: AnyBaseSession, _ifrom, group: LegacyMUC):
329
+ await session.on_leave_group(group.legacy_id)