slidge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- slidge/__init__.py +61 -0
- slidge/__main__.py +192 -0
- slidge/command/__init__.py +28 -0
- slidge/command/adhoc.py +258 -0
- slidge/command/admin.py +193 -0
- slidge/command/base.py +441 -0
- slidge/command/categories.py +3 -0
- slidge/command/chat_command.py +288 -0
- slidge/command/register.py +179 -0
- slidge/command/user.py +250 -0
- slidge/contact/__init__.py +8 -0
- slidge/contact/contact.py +452 -0
- slidge/contact/roster.py +192 -0
- slidge/core/__init__.py +3 -0
- slidge/core/cache.py +183 -0
- slidge/core/config.py +209 -0
- slidge/core/gateway/__init__.py +3 -0
- slidge/core/gateway/base.py +892 -0
- slidge/core/gateway/caps.py +63 -0
- slidge/core/gateway/delivery_receipt.py +52 -0
- slidge/core/gateway/disco.py +80 -0
- slidge/core/gateway/mam.py +75 -0
- slidge/core/gateway/muc_admin.py +35 -0
- slidge/core/gateway/ping.py +66 -0
- slidge/core/gateway/presence.py +95 -0
- slidge/core/gateway/registration.py +53 -0
- slidge/core/gateway/search.py +102 -0
- slidge/core/gateway/session_dispatcher.py +757 -0
- slidge/core/gateway/vcard_temp.py +130 -0
- slidge/core/mixins/__init__.py +19 -0
- slidge/core/mixins/attachment.py +506 -0
- slidge/core/mixins/avatar.py +167 -0
- slidge/core/mixins/base.py +31 -0
- slidge/core/mixins/disco.py +130 -0
- slidge/core/mixins/lock.py +31 -0
- slidge/core/mixins/message.py +398 -0
- slidge/core/mixins/message_maker.py +154 -0
- slidge/core/mixins/presence.py +217 -0
- slidge/core/mixins/recipient.py +43 -0
- slidge/core/pubsub.py +525 -0
- slidge/core/session.py +752 -0
- slidge/group/__init__.py +10 -0
- slidge/group/archive.py +125 -0
- slidge/group/bookmarks.py +163 -0
- slidge/group/participant.py +440 -0
- slidge/group/room.py +1095 -0
- slidge/migration.py +18 -0
- slidge/py.typed +0 -0
- slidge/slixfix/__init__.py +68 -0
- slidge/slixfix/link_preview/__init__.py +10 -0
- slidge/slixfix/link_preview/link_preview.py +17 -0
- slidge/slixfix/link_preview/stanza.py +99 -0
- slidge/slixfix/roster.py +60 -0
- slidge/slixfix/xep_0077/__init__.py +10 -0
- slidge/slixfix/xep_0077/register.py +289 -0
- slidge/slixfix/xep_0077/stanza.py +104 -0
- slidge/slixfix/xep_0100/__init__.py +5 -0
- slidge/slixfix/xep_0100/gateway.py +121 -0
- slidge/slixfix/xep_0100/stanza.py +9 -0
- slidge/slixfix/xep_0153/__init__.py +10 -0
- slidge/slixfix/xep_0153/stanza.py +25 -0
- slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
- slidge/slixfix/xep_0264/__init__.py +5 -0
- slidge/slixfix/xep_0264/stanza.py +36 -0
- slidge/slixfix/xep_0264/thumbnail.py +23 -0
- slidge/slixfix/xep_0292/__init__.py +5 -0
- slidge/slixfix/xep_0292/vcard4.py +100 -0
- slidge/slixfix/xep_0313/__init__.py +12 -0
- slidge/slixfix/xep_0313/mam.py +262 -0
- slidge/slixfix/xep_0313/stanza.py +359 -0
- slidge/slixfix/xep_0317/__init__.py +5 -0
- slidge/slixfix/xep_0317/hats.py +17 -0
- slidge/slixfix/xep_0317/stanza.py +28 -0
- slidge/slixfix/xep_0356_old/__init__.py +7 -0
- slidge/slixfix/xep_0356_old/privilege.py +167 -0
- slidge/slixfix/xep_0356_old/stanza.py +44 -0
- slidge/slixfix/xep_0424/__init__.py +9 -0
- slidge/slixfix/xep_0424/retraction.py +77 -0
- slidge/slixfix/xep_0424/stanza.py +28 -0
- slidge/slixfix/xep_0490/__init__.py +8 -0
- slidge/slixfix/xep_0490/mds.py +47 -0
- slidge/slixfix/xep_0490/stanza.py +17 -0
- slidge/util/__init__.py +15 -0
- slidge/util/archive_msg.py +61 -0
- slidge/util/conf.py +206 -0
- slidge/util/db.py +229 -0
- slidge/util/schema.sql +126 -0
- slidge/util/sql.py +508 -0
- slidge/util/test.py +295 -0
- slidge/util/types.py +180 -0
- slidge/util/util.py +295 -0
- slidge-0.1.0.dist-info/LICENSE +661 -0
- slidge-0.1.0.dist-info/METADATA +109 -0
- slidge-0.1.0.dist-info/RECORD +96 -0
- slidge-0.1.0.dist-info/WHEEL +4 -0
- slidge-0.1.0.dist-info/entry_points.txt +3 -0
slidge/__init__.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
"""
|
2
|
+
The main slidge package.
|
3
|
+
|
4
|
+
Contains importable classes for a minimal function :term:`Legacy Module`.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import sys
|
8
|
+
import warnings
|
9
|
+
|
10
|
+
from . import slixfix # noqa: F401
|
11
|
+
from .command import FormField, SearchResult # noqa: F401
|
12
|
+
from .contact import LegacyContact, LegacyRoster # noqa: F401
|
13
|
+
from .core import config as global_config # noqa: F401
|
14
|
+
from .core.gateway import BaseGateway # noqa: F401
|
15
|
+
from .core.session import BaseSession # noqa: F401
|
16
|
+
from .group import LegacyBookmarks, LegacyMUC, LegacyParticipant # noqa: F401
|
17
|
+
from .util.db import GatewayUser, user_store # noqa: F401
|
18
|
+
from .util.types import MucType # noqa: F401
|
19
|
+
from .util.util import addLoggingLevel
|
20
|
+
|
21
|
+
from .__main__ import main # isort: skip
|
22
|
+
|
23
|
+
|
24
|
+
def entrypoint(module_name: str) -> None:
|
25
|
+
"""
|
26
|
+
Entrypoint to be used in ``__main__.py`` of
|
27
|
+
:term:`legacy modules <Legacy Module>`.
|
28
|
+
|
29
|
+
:param module_name: An importable :term:`Legacy Module`.
|
30
|
+
"""
|
31
|
+
sys.argv.extend(["--legacy", module_name])
|
32
|
+
main()
|
33
|
+
|
34
|
+
|
35
|
+
def formatwarning(message, category, filename, lineno, line=""):
|
36
|
+
return f"{filename}:{lineno}:{category.__name__}:{message}\n"
|
37
|
+
|
38
|
+
|
39
|
+
warnings.formatwarning = formatwarning
|
40
|
+
|
41
|
+
|
42
|
+
__all__ = [
|
43
|
+
"BaseGateway",
|
44
|
+
"BaseSession",
|
45
|
+
# For backwards compatibility, these names are still importable from the
|
46
|
+
# top-level slidge module, but this is deprecated.
|
47
|
+
# "GatewayUser",
|
48
|
+
# "LegacyBookmarks",
|
49
|
+
# "LegacyMUC",
|
50
|
+
# "LegacyContact",
|
51
|
+
# "LegacyParticipant",
|
52
|
+
# "LegacyRoster",
|
53
|
+
# "MucType",
|
54
|
+
# "FormField",
|
55
|
+
# "SearchResult",
|
56
|
+
"entrypoint",
|
57
|
+
"user_store",
|
58
|
+
"global_config",
|
59
|
+
]
|
60
|
+
|
61
|
+
addLoggingLevel()
|
slidge/__main__.py
ADDED
@@ -0,0 +1,192 @@
|
|
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 importlib
|
17
|
+
import logging
|
18
|
+
import os
|
19
|
+
import signal
|
20
|
+
from pathlib import Path
|
21
|
+
|
22
|
+
import configargparse
|
23
|
+
|
24
|
+
from slidge import BaseGateway
|
25
|
+
from slidge.core import config
|
26
|
+
from slidge.core.cache import avatar_cache
|
27
|
+
from slidge.migration import migrate
|
28
|
+
from slidge.util.conf import ConfigModule
|
29
|
+
from slidge.util.db import user_store
|
30
|
+
from slidge.util.util import get_version # noqa: F401
|
31
|
+
|
32
|
+
|
33
|
+
class MainConfig(ConfigModule):
|
34
|
+
def update_dynamic_defaults(self, args):
|
35
|
+
# force=True is needed in case we call a logger before this is reached,
|
36
|
+
# or basicConfig has no effect
|
37
|
+
logging.basicConfig(level=args.loglevel, filename=args.log_file, force=True)
|
38
|
+
|
39
|
+
if args.home_dir is None:
|
40
|
+
args.home_dir = Path("/var/lib/slidge") / str(args.jid)
|
41
|
+
|
42
|
+
if args.user_jid_validator is None:
|
43
|
+
args.user_jid_validator = ".*@" + args.server
|
44
|
+
|
45
|
+
|
46
|
+
class SigTermInterrupt(Exception):
|
47
|
+
pass
|
48
|
+
|
49
|
+
|
50
|
+
def get_configurator():
|
51
|
+
p = configargparse.ArgumentParser(
|
52
|
+
default_config_files=os.getenv(
|
53
|
+
"SLIDGE_CONF_DIR", "/etc/slidge/conf.d/*.conf"
|
54
|
+
).split(":"),
|
55
|
+
description=__doc__,
|
56
|
+
)
|
57
|
+
p.add_argument(
|
58
|
+
"-c",
|
59
|
+
"--config",
|
60
|
+
help="Path to a INI config file.",
|
61
|
+
env_var="SLIDGE_CONFIG",
|
62
|
+
is_config_file=True,
|
63
|
+
)
|
64
|
+
p.add_argument(
|
65
|
+
"-q",
|
66
|
+
"--quiet",
|
67
|
+
help="loglevel=WARNING",
|
68
|
+
action="store_const",
|
69
|
+
dest="loglevel",
|
70
|
+
const=logging.WARNING,
|
71
|
+
default=logging.INFO,
|
72
|
+
env_var="SLIDGE_QUIET",
|
73
|
+
)
|
74
|
+
p.add_argument(
|
75
|
+
"-d",
|
76
|
+
"--debug",
|
77
|
+
help="loglevel=DEBUG",
|
78
|
+
action="store_const",
|
79
|
+
dest="loglevel",
|
80
|
+
const=logging.DEBUG,
|
81
|
+
env_var="SLIDGE_DEBUG",
|
82
|
+
)
|
83
|
+
p.add_argument(
|
84
|
+
"--version",
|
85
|
+
action="version",
|
86
|
+
version=f"%(prog)s {__version__}",
|
87
|
+
)
|
88
|
+
configurator = MainConfig(config, p)
|
89
|
+
return configurator
|
90
|
+
|
91
|
+
|
92
|
+
def get_parser():
|
93
|
+
return get_configurator().parser
|
94
|
+
|
95
|
+
|
96
|
+
def configure():
|
97
|
+
configurator = get_configurator()
|
98
|
+
args, unknown_argv = configurator.set_conf()
|
99
|
+
|
100
|
+
if not (h := config.HOME_DIR).exists():
|
101
|
+
logging.info("Creating directory '%s'", h)
|
102
|
+
h.mkdir()
|
103
|
+
|
104
|
+
db_file = config.HOME_DIR / "slidge.db"
|
105
|
+
user_store.set_file(db_file, args.secret_key)
|
106
|
+
|
107
|
+
avatar_cache.set_dir(h / "slidge_avatars_v2")
|
108
|
+
|
109
|
+
config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare
|
110
|
+
|
111
|
+
return unknown_argv
|
112
|
+
|
113
|
+
|
114
|
+
def handle_sigterm(_signum, _frame):
|
115
|
+
logging.info("Caught SIGTERM")
|
116
|
+
raise SigTermInterrupt
|
117
|
+
|
118
|
+
|
119
|
+
def main():
|
120
|
+
signal.signal(signal.SIGTERM, handle_sigterm)
|
121
|
+
|
122
|
+
unknown_argv = configure()
|
123
|
+
logging.info("Starting slidge version %s", __version__)
|
124
|
+
|
125
|
+
legacy_module = importlib.import_module(config.LEGACY_MODULE)
|
126
|
+
logging.debug("Legacy module: %s", dir(legacy_module))
|
127
|
+
logging.info(
|
128
|
+
"Starting legacy module: '%s' version %s",
|
129
|
+
config.LEGACY_MODULE,
|
130
|
+
getattr(legacy_module, "__version__", "No version"),
|
131
|
+
)
|
132
|
+
|
133
|
+
if plugin_config_obj := getattr(
|
134
|
+
legacy_module, "config", getattr(legacy_module, "Config", None)
|
135
|
+
):
|
136
|
+
logging.debug("Found a config object in plugin: %r", plugin_config_obj)
|
137
|
+
ConfigModule.ENV_VAR_PREFIX += (
|
138
|
+
f"_{config.LEGACY_MODULE.split('.')[-1].upper()}_"
|
139
|
+
)
|
140
|
+
logging.debug("Env var prefix: %s", ConfigModule.ENV_VAR_PREFIX)
|
141
|
+
ConfigModule(plugin_config_obj).set_conf(unknown_argv)
|
142
|
+
else:
|
143
|
+
if unknown_argv:
|
144
|
+
raise RuntimeError("Some arguments have not been recognized", unknown_argv)
|
145
|
+
|
146
|
+
migrate()
|
147
|
+
|
148
|
+
gateway: BaseGateway = BaseGateway.get_unique_subclass()()
|
149
|
+
avatar_cache.http = gateway.http
|
150
|
+
gateway.connect()
|
151
|
+
|
152
|
+
return_code = 0
|
153
|
+
try:
|
154
|
+
gateway.loop.run_forever()
|
155
|
+
except KeyboardInterrupt:
|
156
|
+
logging.debug("Received SIGINT")
|
157
|
+
except SigTermInterrupt:
|
158
|
+
logging.debug("Received SIGTERM")
|
159
|
+
except SystemExit as e:
|
160
|
+
return_code = e.code # type: ignore
|
161
|
+
logging.debug("Exit called")
|
162
|
+
except Exception as e:
|
163
|
+
return_code = 2
|
164
|
+
logging.exception("Exception in __main__")
|
165
|
+
logging.exception(e)
|
166
|
+
finally:
|
167
|
+
if gateway.has_crashed:
|
168
|
+
if return_code != 0:
|
169
|
+
logging.warning("Return code has been set twice. Please report this.")
|
170
|
+
return_code = 3
|
171
|
+
if gateway.is_connected():
|
172
|
+
logging.debug("Gateway is connected, cleaning up")
|
173
|
+
gateway.shutdown()
|
174
|
+
gateway.disconnect()
|
175
|
+
gateway.loop.run_until_complete(gateway.disconnected)
|
176
|
+
else:
|
177
|
+
logging.debug("Gateway is not connected, no need to clean up")
|
178
|
+
user_store.close()
|
179
|
+
avatar_cache.close()
|
180
|
+
gateway.loop.run_until_complete(gateway.http.close())
|
181
|
+
logging.info("Successful clean shut down")
|
182
|
+
logging.debug("Exiting with code %s", return_code)
|
183
|
+
exit(return_code)
|
184
|
+
|
185
|
+
|
186
|
+
# this should be modified before publish, but if someone cloned from the repo,
|
187
|
+
# it can help
|
188
|
+
__version__ = get_version()
|
189
|
+
|
190
|
+
|
191
|
+
if __name__ == "__main__":
|
192
|
+
main()
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"""
|
2
|
+
This module implements an unified API to define :term:`adhoc <Ad-hoc Command>`
|
3
|
+
or :term:`chatbot <Chatbot Command>` commands. Just subclass a :class:`Command`,
|
4
|
+
and make sures it is imported in your legacy module's ``__init__.py``.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from . import admin, register, user # noqa: F401
|
8
|
+
from .base import (
|
9
|
+
Command,
|
10
|
+
CommandAccess,
|
11
|
+
CommandResponseType,
|
12
|
+
Confirmation,
|
13
|
+
Form,
|
14
|
+
FormField,
|
15
|
+
SearchResult,
|
16
|
+
TableResult,
|
17
|
+
)
|
18
|
+
|
19
|
+
__all__ = (
|
20
|
+
"Command",
|
21
|
+
"CommandAccess",
|
22
|
+
"CommandResponseType",
|
23
|
+
"Confirmation",
|
24
|
+
"Form",
|
25
|
+
"FormField",
|
26
|
+
"SearchResult",
|
27
|
+
"TableResult",
|
28
|
+
)
|
slidge/command/adhoc.py
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
import asyncio
|
2
|
+
import functools
|
3
|
+
import logging
|
4
|
+
from functools import partial
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
6
|
+
|
7
|
+
from slixmpp import JID, Iq # type: ignore[attr-defined]
|
8
|
+
from slixmpp.exceptions import XMPPError
|
9
|
+
from slixmpp.plugins.xep_0004 import Form as SlixForm # type: ignore[attr-defined]
|
10
|
+
from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
|
11
|
+
|
12
|
+
from . import Command, CommandResponseType, Confirmation, Form, TableResult
|
13
|
+
from .base import FormField
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from ..core.gateway.base import BaseGateway
|
17
|
+
from ..core.session import BaseSession
|
18
|
+
|
19
|
+
|
20
|
+
AdhocSessionType = dict[str, Any]
|
21
|
+
|
22
|
+
|
23
|
+
class AdhocProvider:
|
24
|
+
"""
|
25
|
+
A slixmpp-like plugin to handle adhoc commands, with less boilerplate and
|
26
|
+
untyped dict values than slixmpp.
|
27
|
+
"""
|
28
|
+
|
29
|
+
def __init__(self, xmpp: "BaseGateway") -> None:
|
30
|
+
self.xmpp = xmpp
|
31
|
+
self._commands = dict[str, Command]()
|
32
|
+
self._categories = dict[str, list[Command]]()
|
33
|
+
xmpp.plugin["xep_0030"].set_node_handler(
|
34
|
+
"get_items",
|
35
|
+
jid=xmpp.boundjid,
|
36
|
+
node=self.xmpp.plugin["xep_0050"].stanza.Command.namespace,
|
37
|
+
handler=self.get_items,
|
38
|
+
)
|
39
|
+
|
40
|
+
async def __wrap_initial_handler(
|
41
|
+
self, command: Command, iq: Iq, adhoc_session: AdhocSessionType
|
42
|
+
) -> AdhocSessionType:
|
43
|
+
ifrom = iq.get_from()
|
44
|
+
session = command.raise_if_not_authorized(ifrom)
|
45
|
+
result = await self.__wrap_handler(command.run, session, ifrom)
|
46
|
+
return await self.__handle_result(session, result, adhoc_session)
|
47
|
+
|
48
|
+
async def __handle_category_list(
|
49
|
+
self, category: str, iq: Iq, adhoc_session: AdhocSessionType
|
50
|
+
) -> AdhocSessionType:
|
51
|
+
session = self.xmpp.get_session_from_stanza(iq)
|
52
|
+
commands = []
|
53
|
+
for command in self._categories[category]:
|
54
|
+
try:
|
55
|
+
command.raise_if_not_authorized(iq.get_from())
|
56
|
+
except XMPPError:
|
57
|
+
continue
|
58
|
+
commands.append(command)
|
59
|
+
return await self.__handle_result(
|
60
|
+
session,
|
61
|
+
Form(
|
62
|
+
category,
|
63
|
+
"",
|
64
|
+
[
|
65
|
+
FormField(
|
66
|
+
var="command",
|
67
|
+
label="Command",
|
68
|
+
type="list-single",
|
69
|
+
options=[
|
70
|
+
{"label": command.NAME, "value": str(i)}
|
71
|
+
for i, command in enumerate(commands)
|
72
|
+
],
|
73
|
+
)
|
74
|
+
],
|
75
|
+
partial(self.__handle_category_choice, commands),
|
76
|
+
),
|
77
|
+
adhoc_session,
|
78
|
+
)
|
79
|
+
|
80
|
+
async def __handle_category_choice(
|
81
|
+
self,
|
82
|
+
commands: list[Command],
|
83
|
+
form_values: dict[str, str],
|
84
|
+
session: "BaseSession[Any, Any]",
|
85
|
+
jid: JID,
|
86
|
+
):
|
87
|
+
command = commands[int(form_values["command"])]
|
88
|
+
result = await self.__wrap_handler(command.run, session, jid)
|
89
|
+
return result
|
90
|
+
|
91
|
+
async def __handle_result(
|
92
|
+
self,
|
93
|
+
session: Optional["BaseSession[Any, Any]"],
|
94
|
+
result: CommandResponseType,
|
95
|
+
adhoc_session: AdhocSessionType,
|
96
|
+
) -> AdhocSessionType:
|
97
|
+
if isinstance(result, str) or result is None:
|
98
|
+
adhoc_session["has_next"] = False
|
99
|
+
adhoc_session["next"] = None
|
100
|
+
adhoc_session["payload"] = None
|
101
|
+
adhoc_session["notes"] = [("info", result or "Success!")]
|
102
|
+
return adhoc_session
|
103
|
+
|
104
|
+
if isinstance(result, Form):
|
105
|
+
adhoc_session["next"] = partial(self.__wrap_form_handler, session, result)
|
106
|
+
adhoc_session["has_next"] = True
|
107
|
+
adhoc_session["payload"] = result.get_xml()
|
108
|
+
return adhoc_session
|
109
|
+
|
110
|
+
if isinstance(result, Confirmation):
|
111
|
+
adhoc_session["next"] = partial(self.__wrap_confirmation, session, result)
|
112
|
+
adhoc_session["has_next"] = True
|
113
|
+
adhoc_session["payload"] = result.get_form()
|
114
|
+
adhoc_session["next"] = partial(self.__wrap_confirmation, session, result)
|
115
|
+
return adhoc_session
|
116
|
+
|
117
|
+
if isinstance(result, TableResult):
|
118
|
+
adhoc_session["next"] = None
|
119
|
+
adhoc_session["has_next"] = False
|
120
|
+
adhoc_session["payload"] = result.get_xml()
|
121
|
+
return adhoc_session
|
122
|
+
|
123
|
+
raise XMPPError("internal-server-error", text="OOPS!")
|
124
|
+
|
125
|
+
@staticmethod
|
126
|
+
async def __wrap_handler(f: Union[Callable, functools.partial], *a, **k): # type: ignore
|
127
|
+
try:
|
128
|
+
if asyncio.iscoroutinefunction(f):
|
129
|
+
return await f(*a, **k)
|
130
|
+
elif hasattr(f, "func") and asyncio.iscoroutinefunction(f.func):
|
131
|
+
return await f(*a, **k)
|
132
|
+
else:
|
133
|
+
return f(*a, **k)
|
134
|
+
except Exception as e:
|
135
|
+
log.debug("Exception in %s", f, exc_info=e)
|
136
|
+
raise XMPPError("internal-server-error", text=str(e))
|
137
|
+
|
138
|
+
async def __wrap_form_handler(
|
139
|
+
self,
|
140
|
+
session: Optional["BaseSession[Any, Any]"],
|
141
|
+
result: Form,
|
142
|
+
form: SlixForm,
|
143
|
+
adhoc_session: AdhocSessionType,
|
144
|
+
) -> AdhocSessionType:
|
145
|
+
form_values = result.get_values(form)
|
146
|
+
new_result = await self.__wrap_handler(
|
147
|
+
result.handler,
|
148
|
+
form_values,
|
149
|
+
session,
|
150
|
+
adhoc_session["from"],
|
151
|
+
*result.handler_args,
|
152
|
+
**result.handler_kwargs,
|
153
|
+
)
|
154
|
+
|
155
|
+
return await self.__handle_result(session, new_result, adhoc_session)
|
156
|
+
|
157
|
+
async def __wrap_confirmation(
|
158
|
+
self,
|
159
|
+
session: Optional["BaseSession[Any, Any]"],
|
160
|
+
confirmation: Confirmation,
|
161
|
+
form: SlixForm,
|
162
|
+
adhoc_session: AdhocSessionType,
|
163
|
+
) -> AdhocSessionType:
|
164
|
+
if form.get_values().get("confirm"): # type: ignore[no-untyped-call]
|
165
|
+
result = await self.__wrap_handler(
|
166
|
+
confirmation.handler,
|
167
|
+
session,
|
168
|
+
adhoc_session["from"],
|
169
|
+
*confirmation.handler_args,
|
170
|
+
**confirmation.handler_kwargs,
|
171
|
+
)
|
172
|
+
if confirmation.success:
|
173
|
+
result = confirmation.success
|
174
|
+
else:
|
175
|
+
result = "You canceled the operation"
|
176
|
+
|
177
|
+
return await self.__handle_result(session, result, adhoc_session)
|
178
|
+
|
179
|
+
def register(self, command: Command, jid: Optional[JID] = None) -> None:
|
180
|
+
"""
|
181
|
+
Register a command as a adhoc command.
|
182
|
+
|
183
|
+
this does not need to be called manually, ``BaseGateway`` takes care of
|
184
|
+
that.
|
185
|
+
|
186
|
+
:param command:
|
187
|
+
:param jid:
|
188
|
+
"""
|
189
|
+
if jid is None:
|
190
|
+
jid = self.xmpp.boundjid
|
191
|
+
elif not isinstance(jid, JID):
|
192
|
+
jid = JID(jid)
|
193
|
+
|
194
|
+
if (category := command.CATEGORY) is None:
|
195
|
+
if command.NODE in self._commands:
|
196
|
+
raise RuntimeError(
|
197
|
+
"There is already a command for the node '%s'", command.NODE
|
198
|
+
)
|
199
|
+
self._commands[command.NODE] = command
|
200
|
+
self.xmpp.plugin["xep_0050"].add_command( # type: ignore[no-untyped-call]
|
201
|
+
jid=jid,
|
202
|
+
node=command.NODE,
|
203
|
+
name=command.NAME,
|
204
|
+
handler=partial(self.__wrap_initial_handler, command),
|
205
|
+
)
|
206
|
+
else:
|
207
|
+
if category not in self._categories:
|
208
|
+
self._categories[category] = list[Command]()
|
209
|
+
self.xmpp.plugin["xep_0050"].add_command( # type: ignore[no-untyped-call]
|
210
|
+
jid=jid,
|
211
|
+
node=category,
|
212
|
+
name=category,
|
213
|
+
handler=partial(self.__handle_category_list, category),
|
214
|
+
)
|
215
|
+
self._categories[category].append(command)
|
216
|
+
|
217
|
+
async def get_items(self, jid: JID, node: str, iq: Iq) -> DiscoItems:
|
218
|
+
"""
|
219
|
+
Get items for a disco query
|
220
|
+
|
221
|
+
:param jid: who is requesting the disco
|
222
|
+
:param node: which command node is requested
|
223
|
+
:param iq: the disco query IQ
|
224
|
+
:return: commands accessible to the given JID will be listed
|
225
|
+
"""
|
226
|
+
all_items = self.xmpp.plugin["xep_0030"].static.get_items(jid, node, None, None)
|
227
|
+
log.debug("Static items: %r", all_items)
|
228
|
+
if not all_items:
|
229
|
+
return DiscoItems()
|
230
|
+
|
231
|
+
ifrom = iq.get_from()
|
232
|
+
|
233
|
+
filtered_items = DiscoItems()
|
234
|
+
filtered_items["node"] = self.xmpp.plugin["xep_0050"].stanza.Command.namespace
|
235
|
+
for item in all_items:
|
236
|
+
authorized = True
|
237
|
+
if item["node"] in self._categories:
|
238
|
+
for command in self._categories[item["node"]]:
|
239
|
+
try:
|
240
|
+
command.raise_if_not_authorized(ifrom)
|
241
|
+
except XMPPError:
|
242
|
+
authorized = False
|
243
|
+
else:
|
244
|
+
authorized = True
|
245
|
+
break
|
246
|
+
else:
|
247
|
+
try:
|
248
|
+
self._commands[item["node"]].raise_if_not_authorized(ifrom)
|
249
|
+
except XMPPError:
|
250
|
+
authorized = False
|
251
|
+
|
252
|
+
if authorized:
|
253
|
+
filtered_items.append(item)
|
254
|
+
|
255
|
+
return filtered_items
|
256
|
+
|
257
|
+
|
258
|
+
log = logging.getLogger(__name__)
|