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.
Files changed (96) hide show
  1. slidge/__init__.py +61 -0
  2. slidge/__main__.py +192 -0
  3. slidge/command/__init__.py +28 -0
  4. slidge/command/adhoc.py +258 -0
  5. slidge/command/admin.py +193 -0
  6. slidge/command/base.py +441 -0
  7. slidge/command/categories.py +3 -0
  8. slidge/command/chat_command.py +288 -0
  9. slidge/command/register.py +179 -0
  10. slidge/command/user.py +250 -0
  11. slidge/contact/__init__.py +8 -0
  12. slidge/contact/contact.py +452 -0
  13. slidge/contact/roster.py +192 -0
  14. slidge/core/__init__.py +3 -0
  15. slidge/core/cache.py +183 -0
  16. slidge/core/config.py +209 -0
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +892 -0
  19. slidge/core/gateway/caps.py +63 -0
  20. slidge/core/gateway/delivery_receipt.py +52 -0
  21. slidge/core/gateway/disco.py +80 -0
  22. slidge/core/gateway/mam.py +75 -0
  23. slidge/core/gateway/muc_admin.py +35 -0
  24. slidge/core/gateway/ping.py +66 -0
  25. slidge/core/gateway/presence.py +95 -0
  26. slidge/core/gateway/registration.py +53 -0
  27. slidge/core/gateway/search.py +102 -0
  28. slidge/core/gateway/session_dispatcher.py +757 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +19 -0
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +31 -0
  34. slidge/core/mixins/disco.py +130 -0
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +398 -0
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +217 -0
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +525 -0
  41. slidge/core/session.py +752 -0
  42. slidge/group/__init__.py +10 -0
  43. slidge/group/archive.py +125 -0
  44. slidge/group/bookmarks.py +163 -0
  45. slidge/group/participant.py +440 -0
  46. slidge/group/room.py +1095 -0
  47. slidge/migration.py +18 -0
  48. slidge/py.typed +0 -0
  49. slidge/slixfix/__init__.py +68 -0
  50. slidge/slixfix/link_preview/__init__.py +10 -0
  51. slidge/slixfix/link_preview/link_preview.py +17 -0
  52. slidge/slixfix/link_preview/stanza.py +99 -0
  53. slidge/slixfix/roster.py +60 -0
  54. slidge/slixfix/xep_0077/__init__.py +10 -0
  55. slidge/slixfix/xep_0077/register.py +289 -0
  56. slidge/slixfix/xep_0077/stanza.py +104 -0
  57. slidge/slixfix/xep_0100/__init__.py +5 -0
  58. slidge/slixfix/xep_0100/gateway.py +121 -0
  59. slidge/slixfix/xep_0100/stanza.py +9 -0
  60. slidge/slixfix/xep_0153/__init__.py +10 -0
  61. slidge/slixfix/xep_0153/stanza.py +25 -0
  62. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  63. slidge/slixfix/xep_0264/__init__.py +5 -0
  64. slidge/slixfix/xep_0264/stanza.py +36 -0
  65. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  66. slidge/slixfix/xep_0292/__init__.py +5 -0
  67. slidge/slixfix/xep_0292/vcard4.py +100 -0
  68. slidge/slixfix/xep_0313/__init__.py +12 -0
  69. slidge/slixfix/xep_0313/mam.py +262 -0
  70. slidge/slixfix/xep_0313/stanza.py +359 -0
  71. slidge/slixfix/xep_0317/__init__.py +5 -0
  72. slidge/slixfix/xep_0317/hats.py +17 -0
  73. slidge/slixfix/xep_0317/stanza.py +28 -0
  74. slidge/slixfix/xep_0356_old/__init__.py +7 -0
  75. slidge/slixfix/xep_0356_old/privilege.py +167 -0
  76. slidge/slixfix/xep_0356_old/stanza.py +44 -0
  77. slidge/slixfix/xep_0424/__init__.py +9 -0
  78. slidge/slixfix/xep_0424/retraction.py +77 -0
  79. slidge/slixfix/xep_0424/stanza.py +28 -0
  80. slidge/slixfix/xep_0490/__init__.py +8 -0
  81. slidge/slixfix/xep_0490/mds.py +47 -0
  82. slidge/slixfix/xep_0490/stanza.py +17 -0
  83. slidge/util/__init__.py +15 -0
  84. slidge/util/archive_msg.py +61 -0
  85. slidge/util/conf.py +206 -0
  86. slidge/util/db.py +229 -0
  87. slidge/util/schema.sql +126 -0
  88. slidge/util/sql.py +508 -0
  89. slidge/util/test.py +295 -0
  90. slidge/util/types.py +180 -0
  91. slidge/util/util.py +295 -0
  92. slidge-0.1.0.dist-info/LICENSE +661 -0
  93. slidge-0.1.0.dist-info/METADATA +109 -0
  94. slidge-0.1.0.dist-info/RECORD +96 -0
  95. slidge-0.1.0.dist-info/WHEEL +4 -0
  96. 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
+ )
@@ -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__)