slidge 0.1.2__py3-none-any.whl → 0.2.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidge/__init__.py +3 -5
- slidge/__main__.py +2 -196
- slidge/__version__.py +5 -0
- slidge/command/adhoc.py +8 -1
- slidge/command/admin.py +5 -6
- slidge/command/base.py +1 -2
- slidge/command/register.py +32 -16
- slidge/command/user.py +85 -5
- slidge/contact/contact.py +93 -31
- slidge/contact/roster.py +54 -39
- slidge/core/config.py +13 -7
- slidge/core/gateway/base.py +139 -34
- slidge/core/gateway/disco.py +2 -4
- slidge/core/gateway/mam.py +1 -4
- slidge/core/gateway/ping.py +2 -3
- slidge/core/gateway/presence.py +1 -1
- slidge/core/gateway/registration.py +32 -21
- slidge/core/gateway/search.py +3 -5
- slidge/core/gateway/session_dispatcher.py +109 -51
- slidge/core/gateway/vcard_temp.py +6 -4
- slidge/core/mixins/__init__.py +11 -1
- slidge/core/mixins/attachment.py +15 -10
- slidge/core/mixins/avatar.py +66 -18
- slidge/core/mixins/base.py +8 -2
- slidge/core/mixins/message.py +11 -7
- slidge/core/mixins/message_maker.py +17 -9
- slidge/core/mixins/presence.py +14 -4
- slidge/core/pubsub.py +54 -212
- slidge/core/session.py +65 -33
- slidge/db/__init__.py +4 -0
- slidge/db/alembic/env.py +64 -0
- slidge/db/alembic/script.py.mako +26 -0
- slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
- slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
- slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +133 -0
- slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +76 -0
- slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
- slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
- slidge/db/avatar.py +224 -0
- slidge/db/meta.py +65 -0
- slidge/db/models.py +365 -0
- slidge/db/store.py +976 -0
- slidge/group/archive.py +13 -14
- slidge/group/bookmarks.py +59 -56
- slidge/group/participant.py +81 -29
- slidge/group/room.py +242 -142
- slidge/main.py +201 -0
- slidge/migration.py +30 -0
- slidge/slixfix/__init__.py +35 -2
- slidge/slixfix/roster.py +11 -4
- slidge/slixfix/xep_0292/vcard4.py +1 -0
- slidge/util/db.py +1 -47
- slidge/util/test.py +21 -4
- slidge/util/types.py +24 -4
- {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/METADATA +3 -1
- slidge-0.2.0a0.dist-info/RECORD +108 -0
- slidge/core/cache.py +0 -183
- slidge/util/schema.sql +0 -126
- slidge/util/sql.py +0 -508
- slidge-0.1.2.dist-info/RECORD +0 -96
- {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/LICENSE +0 -0
- {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/WHEEL +0 -0
- {slidge-0.1.2.dist-info → slidge-0.2.0a0.dist-info}/entry_points.txt +0 -0
slidge/__init__.py
CHANGED
@@ -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
|
|
slidge/__main__.py
CHANGED
@@ -1,198 +1,4 @@
|
|
1
|
-
"""
|
2
|
-
Slidge can be configured via CLI args, environment variables and/or INI files.
|
3
|
-
|
4
|
-
To use env vars, use this convention: ``--home-dir`` becomes ``HOME_DIR``.
|
5
|
-
|
6
|
-
Everything in ``/etc/slidge/conf.d/*`` is automatically used.
|
7
|
-
To use a plugin-specific INI file, put it in another dir,
|
8
|
-
and launch slidge with ``-c /path/to/plugin-specific.conf``.
|
9
|
-
Use the long version of the CLI arg without the double dash prefix inside this
|
10
|
-
INI file, eg ``debug=true``.
|
11
|
-
|
12
|
-
An example configuration file is available at
|
13
|
-
https://git.sr.ht/~nicoco/slidge/tree/master/item/dev/confs/slidge-example.ini
|
14
|
-
"""
|
15
|
-
|
16
|
-
import asyncio
|
17
|
-
import importlib
|
18
|
-
import logging
|
19
|
-
import os
|
20
|
-
import signal
|
21
|
-
from pathlib import Path
|
22
|
-
|
23
|
-
import configargparse
|
24
|
-
|
25
|
-
from slidge import BaseGateway
|
26
|
-
from slidge.core import config
|
27
|
-
from slidge.core.cache import avatar_cache
|
28
|
-
from slidge.migration import migrate
|
29
|
-
from slidge.util.conf import ConfigModule
|
30
|
-
from slidge.util.db import user_store
|
31
|
-
from slidge.util.util import get_version # noqa: F401
|
32
|
-
|
33
|
-
|
34
|
-
class MainConfig(ConfigModule):
|
35
|
-
def update_dynamic_defaults(self, args):
|
36
|
-
# force=True is needed in case we call a logger before this is reached,
|
37
|
-
# or basicConfig has no effect
|
38
|
-
logging.basicConfig(
|
39
|
-
level=args.loglevel,
|
40
|
-
filename=args.log_file,
|
41
|
-
force=True,
|
42
|
-
format=args.log_format,
|
43
|
-
)
|
44
|
-
|
45
|
-
if args.home_dir is None:
|
46
|
-
args.home_dir = Path("/var/lib/slidge") / str(args.jid)
|
47
|
-
|
48
|
-
if args.user_jid_validator is None:
|
49
|
-
args.user_jid_validator = ".*@" + args.server
|
50
|
-
|
51
|
-
|
52
|
-
class SigTermInterrupt(Exception):
|
53
|
-
pass
|
54
|
-
|
55
|
-
|
56
|
-
def get_configurator():
|
57
|
-
p = configargparse.ArgumentParser(
|
58
|
-
default_config_files=os.getenv(
|
59
|
-
"SLIDGE_CONF_DIR", "/etc/slidge/conf.d/*.conf"
|
60
|
-
).split(":"),
|
61
|
-
description=__doc__,
|
62
|
-
)
|
63
|
-
p.add_argument(
|
64
|
-
"-c",
|
65
|
-
"--config",
|
66
|
-
help="Path to a INI config file.",
|
67
|
-
env_var="SLIDGE_CONFIG",
|
68
|
-
is_config_file=True,
|
69
|
-
)
|
70
|
-
p.add_argument(
|
71
|
-
"-q",
|
72
|
-
"--quiet",
|
73
|
-
help="loglevel=WARNING",
|
74
|
-
action="store_const",
|
75
|
-
dest="loglevel",
|
76
|
-
const=logging.WARNING,
|
77
|
-
default=logging.INFO,
|
78
|
-
env_var="SLIDGE_QUIET",
|
79
|
-
)
|
80
|
-
p.add_argument(
|
81
|
-
"-d",
|
82
|
-
"--debug",
|
83
|
-
help="loglevel=DEBUG",
|
84
|
-
action="store_const",
|
85
|
-
dest="loglevel",
|
86
|
-
const=logging.DEBUG,
|
87
|
-
env_var="SLIDGE_DEBUG",
|
88
|
-
)
|
89
|
-
p.add_argument(
|
90
|
-
"--version",
|
91
|
-
action="version",
|
92
|
-
version=f"%(prog)s {__version__}",
|
93
|
-
)
|
94
|
-
configurator = MainConfig(config, p)
|
95
|
-
return configurator
|
96
|
-
|
97
|
-
|
98
|
-
def get_parser():
|
99
|
-
return get_configurator().parser
|
100
|
-
|
101
|
-
|
102
|
-
def configure():
|
103
|
-
configurator = get_configurator()
|
104
|
-
args, unknown_argv = configurator.set_conf()
|
105
|
-
|
106
|
-
if not (h := config.HOME_DIR).exists():
|
107
|
-
logging.info("Creating directory '%s'", h)
|
108
|
-
h.mkdir()
|
109
|
-
|
110
|
-
db_file = config.HOME_DIR / "slidge.db"
|
111
|
-
user_store.set_file(db_file, args.secret_key)
|
112
|
-
|
113
|
-
avatar_cache.set_dir(h / "slidge_avatars_v2")
|
114
|
-
|
115
|
-
config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare
|
116
|
-
|
117
|
-
return unknown_argv
|
118
|
-
|
119
|
-
|
120
|
-
def handle_sigterm(_signum, _frame):
|
121
|
-
logging.info("Caught SIGTERM")
|
122
|
-
raise SigTermInterrupt
|
123
|
-
|
124
|
-
|
125
|
-
def main():
|
126
|
-
signal.signal(signal.SIGTERM, handle_sigterm)
|
127
|
-
|
128
|
-
unknown_argv = configure()
|
129
|
-
logging.info("Starting slidge version %s", __version__)
|
130
|
-
|
131
|
-
legacy_module = importlib.import_module(config.LEGACY_MODULE)
|
132
|
-
logging.debug("Legacy module: %s", dir(legacy_module))
|
133
|
-
logging.info(
|
134
|
-
"Starting legacy module: '%s' version %s",
|
135
|
-
config.LEGACY_MODULE,
|
136
|
-
getattr(legacy_module, "__version__", "No version"),
|
137
|
-
)
|
138
|
-
|
139
|
-
if plugin_config_obj := getattr(
|
140
|
-
legacy_module, "config", getattr(legacy_module, "Config", None)
|
141
|
-
):
|
142
|
-
logging.debug("Found a config object in plugin: %r", plugin_config_obj)
|
143
|
-
ConfigModule.ENV_VAR_PREFIX += (
|
144
|
-
f"_{config.LEGACY_MODULE.split('.')[-1].upper()}_"
|
145
|
-
)
|
146
|
-
logging.debug("Env var prefix: %s", ConfigModule.ENV_VAR_PREFIX)
|
147
|
-
ConfigModule(plugin_config_obj).set_conf(unknown_argv)
|
148
|
-
else:
|
149
|
-
if unknown_argv:
|
150
|
-
raise RuntimeError("Some arguments have not been recognized", unknown_argv)
|
151
|
-
|
152
|
-
migrate()
|
153
|
-
|
154
|
-
gateway: BaseGateway = BaseGateway.get_unique_subclass()()
|
155
|
-
avatar_cache.http = gateway.http
|
156
|
-
gateway.connect()
|
157
|
-
|
158
|
-
return_code = 0
|
159
|
-
try:
|
160
|
-
gateway.loop.run_forever()
|
161
|
-
except KeyboardInterrupt:
|
162
|
-
logging.debug("Received SIGINT")
|
163
|
-
except SigTermInterrupt:
|
164
|
-
logging.debug("Received SIGTERM")
|
165
|
-
except SystemExit as e:
|
166
|
-
return_code = e.code # type: ignore
|
167
|
-
logging.debug("Exit called")
|
168
|
-
except Exception as e:
|
169
|
-
return_code = 2
|
170
|
-
logging.exception("Exception in __main__")
|
171
|
-
logging.exception(e)
|
172
|
-
finally:
|
173
|
-
if gateway.has_crashed:
|
174
|
-
if return_code != 0:
|
175
|
-
logging.warning("Return code has been set twice. Please report this.")
|
176
|
-
return_code = 3
|
177
|
-
if gateway.is_connected():
|
178
|
-
logging.debug("Gateway is connected, cleaning up")
|
179
|
-
gateway.loop.run_until_complete(asyncio.gather(*gateway.shutdown()))
|
180
|
-
gateway.disconnect()
|
181
|
-
gateway.loop.run_until_complete(gateway.disconnected)
|
182
|
-
else:
|
183
|
-
logging.debug("Gateway is not connected, no need to clean up")
|
184
|
-
user_store.close()
|
185
|
-
avatar_cache.close()
|
186
|
-
gateway.loop.run_until_complete(gateway.http.close())
|
187
|
-
logging.info("Successful clean shut down")
|
188
|
-
logging.debug("Exiting with code %s", return_code)
|
189
|
-
exit(return_code)
|
190
|
-
|
191
|
-
|
192
|
-
# this should be modified before publish, but if someone cloned from the repo,
|
193
|
-
# it can help
|
194
|
-
__version__ = get_version()
|
195
|
-
|
196
|
-
|
197
1
|
if __name__ == "__main__":
|
2
|
+
from slidge.main import main
|
3
|
+
|
198
4
|
main()
|
slidge/__version__.py
ADDED
slidge/command/adhoc.py
CHANGED
@@ -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
|
-
|
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(
|
slidge/command/admin.py
CHANGED
@@ -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
|
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.
|
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,7 +53,7 @@ class SlidgeInfo(AdminCommand):
|
|
54
53
|
ACCESS = CommandAccess.ANY
|
55
54
|
|
56
55
|
async def run(self, _session, _ifrom, *_):
|
57
|
-
from
|
56
|
+
from slidge.__version__ import __version__
|
58
57
|
|
59
58
|
start = self.xmpp.datetime_started
|
60
59
|
uptime = datetime.now() - start
|
@@ -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 =
|
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 =
|
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)
|
slidge/command/base.py
CHANGED
@@ -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 =
|
384
|
+
user = self.xmpp.store.users.get(jid)
|
386
385
|
if user is None:
|
387
386
|
return None
|
388
387
|
|
slidge/command/register.py
CHANGED
@@ -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
|
+
)
|
slidge/command/user.py
CHANGED
@@ -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 ..
|
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.
|
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.
|
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.
|
101
|
+
session.user_jid,
|
100
102
|
{
|
101
103
|
item["jid"]: {
|
102
104
|
"subscription": item["subscription"],
|
@@ -229,6 +231,39 @@ class CreateGroup(Command):
|
|
229
231
|
)
|
230
232
|
|
231
233
|
|
234
|
+
class Preferences(Command):
|
235
|
+
NAME = "⚙️ Preferences"
|
236
|
+
HELP = "Customize the gateway behaviour to your liking"
|
237
|
+
NODE = CHAT_COMMAND = "preferences"
|
238
|
+
ACCESS = CommandAccess.USER
|
239
|
+
|
240
|
+
async def run(
|
241
|
+
self, session: Optional[AnyBaseSession], _ifrom: JID, *_: Any
|
242
|
+
) -> Form:
|
243
|
+
fields = deepcopy(self.xmpp.PREFERENCES)
|
244
|
+
assert session is not None
|
245
|
+
current = session.user.preferences
|
246
|
+
for field in fields:
|
247
|
+
field.value = current.get(field.var) # type:ignore
|
248
|
+
return Form(
|
249
|
+
title="Preferences",
|
250
|
+
instructions=self.HELP,
|
251
|
+
fields=fields,
|
252
|
+
handler=self.finish, # type:ignore
|
253
|
+
)
|
254
|
+
|
255
|
+
async def finish(
|
256
|
+
self, form_values: UserPreferences, session: Optional[AnyBaseSession], *_
|
257
|
+
) -> str:
|
258
|
+
assert session is not None
|
259
|
+
user = session.user
|
260
|
+
user.preferences.update(form_values) # type:ignore
|
261
|
+
self.xmpp.store.users.update(user)
|
262
|
+
if form_values["sync_avatar"]:
|
263
|
+
await self.xmpp.fetch_user_avatar(session)
|
264
|
+
return "Your preferences have been updated."
|
265
|
+
|
266
|
+
|
232
267
|
class Unregister(Command):
|
233
268
|
NAME = "❌ Unregister from the gateway"
|
234
269
|
HELP = "Unregister from the gateway"
|
@@ -246,5 +281,50 @@ class Unregister(Command):
|
|
246
281
|
|
247
282
|
async def unregister(self, session: Optional[AnyBaseSession], _ifrom: JID) -> str:
|
248
283
|
assert session is not None
|
249
|
-
|
284
|
+
user = self.xmpp.store.users.get(session.user_jid)
|
285
|
+
assert user is not None
|
286
|
+
await self.xmpp.unregister_user(user)
|
250
287
|
return "OK"
|
288
|
+
|
289
|
+
|
290
|
+
class LeaveGroup(Command):
|
291
|
+
NAME = HELP = "❌ Leave a legacy group"
|
292
|
+
NODE = CHAT_COMMAND = "leave-group"
|
293
|
+
ACCESS = CommandAccess.USER_LOGGED
|
294
|
+
CATEGORY = GROUPS
|
295
|
+
|
296
|
+
async def run(self, session, _ifrom, *_):
|
297
|
+
assert session is not None
|
298
|
+
await session.bookmarks.fill()
|
299
|
+
groups = sorted(session.bookmarks, key=lambda g: g.DISCO_NAME.casefold())
|
300
|
+
return Form(
|
301
|
+
title="Leave a group",
|
302
|
+
instructions="Select the group you want to leave",
|
303
|
+
fields=[
|
304
|
+
FormField(
|
305
|
+
"group",
|
306
|
+
"Group name",
|
307
|
+
options=[{"label": g.name, "value": g.name} for g in groups],
|
308
|
+
)
|
309
|
+
],
|
310
|
+
handler=self.confirm, # type:ignore
|
311
|
+
handler_args=(groups,),
|
312
|
+
)
|
313
|
+
|
314
|
+
async def confirm(
|
315
|
+
self,
|
316
|
+
form_values: FormValues,
|
317
|
+
_session: AnyBaseSession,
|
318
|
+
_ifrom,
|
319
|
+
groups: list[LegacyMUC],
|
320
|
+
):
|
321
|
+
group = groups[int(form_values["group"])] # type:ignore
|
322
|
+
return Confirmation(
|
323
|
+
prompt=f"Are you sure you want to leave the group '{group.name}'?",
|
324
|
+
handler=self.finish, # type:ignore
|
325
|
+
handler_args=(group,),
|
326
|
+
)
|
327
|
+
|
328
|
+
@staticmethod
|
329
|
+
async def finish(session: AnyBaseSession, _ifrom, group: LegacyMUC):
|
330
|
+
await session.on_leave_group(group.legacy_id)
|