slidge 0.3.1__tar.gz → 0.3.3__tar.gz

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 (191) hide show
  1. {slidge-0.3.1 → slidge-0.3.3}/PKG-INFO +1 -1
  2. slidge-0.3.3/docs/source/admin/config/index.rst +57 -0
  3. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/privilege.rst +5 -6
  4. {slidge-0.3.1 → slidge-0.3.3}/docs/source/conf.py +1 -1
  5. {slidge-0.3.1 → slidge-0.3.3}/pyproject.toml +3 -0
  6. {slidge-0.3.1 → slidge-0.3.3}/slidge/command/user.py +22 -2
  7. {slidge-0.3.1 → slidge-0.3.3}/slidge/contact/contact.py +6 -13
  8. {slidge-0.3.1 → slidge-0.3.3}/slidge/contact/roster.py +6 -1
  9. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/config.py +43 -9
  10. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/caps.py +1 -2
  11. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/message/message.py +1 -0
  12. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/presence.py +1 -0
  13. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/registration.py +1 -1
  14. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/search.py +1 -5
  15. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/util.py +3 -1
  16. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/gateway.py +12 -1
  17. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/attachment.py +62 -8
  18. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/db.py +15 -0
  19. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/message_maker.py +1 -1
  20. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/message_text.py +1 -1
  21. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/presence.py +20 -10
  22. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/session.py +2 -0
  23. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/models.py +3 -3
  24. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/store.py +29 -1
  25. {slidge-0.3.1 → slidge-0.3.3}/slidge/group/room.py +17 -24
  26. {slidge-0.3.1 → slidge-0.3.3}/slidge/util/test.py +3 -2
  27. {slidge-0.3.1 → slidge-0.3.3}/slidge/util/types.py +2 -0
  28. {slidge-0.3.1 → slidge-0.3.3}/slidge/util/util.py +6 -4
  29. {slidge-0.3.1 → slidge-0.3.3}/slidge.egg-info/PKG-INFO +1 -1
  30. {slidge-0.3.1 → slidge-0.3.3}/slidge.egg-info/SOURCES.txt +1 -0
  31. {slidge-0.3.1 → slidge-0.3.3}/tests/conftest.py +9 -5
  32. {slidge-0.3.1 → slidge-0.3.3}/tests/test_attachment.py +131 -8
  33. {slidge-0.3.1 → slidge-0.3.3}/tests/test_backfill.py +0 -1
  34. {slidge-0.3.1 → slidge-0.3.3}/tests/test_config.py +1 -1
  35. {slidge-0.3.1 → slidge-0.3.3}/tests/test_db/test_store.py +41 -2
  36. slidge-0.3.3/tests/test_gateway_wide_reaction_restrictions.py +187 -0
  37. {slidge-0.3.1 → slidge-0.3.3}/tests/test_muc.py +31 -8
  38. {slidge-0.3.1 → slidge-0.3.3}/tests/test_muc_subject.py +0 -4
  39. {slidge-0.3.1 → slidge-0.3.3}/tests/test_session.py +0 -4
  40. {slidge-0.3.1 → slidge-0.3.3}/tests/test_session_2.py +0 -4
  41. {slidge-0.3.1 → slidge-0.3.3}/tests/test_set_name_before_fill.py +0 -4
  42. {slidge-0.3.1 → slidge-0.3.3}/tests/test_shakespeare.py +10 -11
  43. {slidge-0.3.1 → slidge-0.3.3}/tests/test_util.py +2 -2
  44. {slidge-0.3.1 → slidge-0.3.3}/tests/test_vcard.py +0 -4
  45. slidge-0.3.3/uv.lock +2258 -0
  46. slidge-0.3.1/docs/source/admin/config/index.rst +0 -35
  47. slidge-0.3.1/uv.lock +0 -1843
  48. {slidge-0.3.1 → slidge-0.3.3}/.gitignore +0 -0
  49. {slidge-0.3.1 → slidge-0.3.3}/.pre-commit-config.yaml +0 -0
  50. {slidge-0.3.1 → slidge-0.3.3}/.woodpecker/container-ci.yaml +0 -0
  51. {slidge-0.3.1 → slidge-0.3.3}/.woodpecker/docs.yaml +0 -0
  52. {slidge-0.3.1 → slidge-0.3.3}/.woodpecker/package.yaml +0 -0
  53. {slidge-0.3.1 → slidge-0.3.3}/.woodpecker/test.yaml +0 -0
  54. {slidge-0.3.1 → slidge-0.3.3}/Dockerfile +0 -0
  55. {slidge-0.3.1 → slidge-0.3.3}/LICENSE +0 -0
  56. {slidge-0.3.1 → slidge-0.3.3}/README.md +0 -0
  57. {slidge-0.3.1 → slidge-0.3.3}/commitlint.config.js +0 -0
  58. {slidge-0.3.1 → slidge-0.3.3}/dev/assets/5x5.png +0 -0
  59. {slidge-0.3.1 → slidge-0.3.3}/dev/assets/slidge-color-small.png +0 -0
  60. {slidge-0.3.1 → slidge-0.3.3}/dev/assets/slidge-color.png +0 -0
  61. {slidge-0.3.1 → slidge-0.3.3}/dev/assets/slidge-mono-black.png +0 -0
  62. {slidge-0.3.1 → slidge-0.3.3}/dev/assets/slidge-mono-white.png +0 -0
  63. {slidge-0.3.1 → slidge-0.3.3}/dev/assets/slidge.svg +0 -0
  64. {slidge-0.3.1 → slidge-0.3.3}/dev/confs/movim.env +0 -0
  65. {slidge-0.3.1 → slidge-0.3.3}/dev/confs/nginx.conf +0 -0
  66. {slidge-0.3.1 → slidge-0.3.3}/dev/confs/slidge-dev.ini +0 -0
  67. {slidge-0.3.1 → slidge-0.3.3}/dev/confs/slidge-example.ini +0 -0
  68. {slidge-0.3.1 → slidge-0.3.3}/dev/hot-reload.sh +0 -0
  69. {slidge-0.3.1 → slidge-0.3.3}/dev/prettify_tests.py +0 -0
  70. {slidge-0.3.1 → slidge-0.3.3}/doap.xml +0 -0
  71. {slidge-0.3.1 → slidge-0.3.3}/docker-compose.yml +0 -0
  72. {slidge-0.3.1 → slidge-0.3.3}/docs/Makefile +0 -0
  73. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/attachments.rst +0 -0
  74. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/component.rst +0 -0
  75. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/daemon.rst +0 -0
  76. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/examples/ejabberd.yaml +0 -0
  77. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/examples/index.rst +0 -0
  78. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/examples/prosody.cfg.lua +0 -0
  79. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/index.rst +0 -0
  80. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/install.rst +0 -0
  81. {slidge-0.3.1 → slidge-0.3.3}/docs/source/admin/note.rst +0 -0
  82. {slidge-0.3.1 → slidge-0.3.3}/docs/source/codeberg.svg +0 -0
  83. {slidge-0.3.1 → slidge-0.3.3}/docs/source/dev/contributing.rst +0 -0
  84. {slidge-0.3.1 → slidge-0.3.3}/docs/source/dev/design.rst +0 -0
  85. {slidge-0.3.1 → slidge-0.3.3}/docs/source/dev/howto.rst +0 -0
  86. {slidge-0.3.1 → slidge-0.3.3}/docs/source/dev/index.rst +0 -0
  87. {slidge-0.3.1 → slidge-0.3.3}/docs/source/dev/tutorial.rst +0 -0
  88. {slidge-0.3.1 → slidge-0.3.3}/docs/source/glossary.rst +0 -0
  89. {slidge-0.3.1 → slidge-0.3.3}/docs/source/index.rst +0 -0
  90. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/commands.rst +0 -0
  91. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/contacts.rst +0 -0
  92. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/foxyproxy.png +0 -0
  93. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/gajim.png +0 -0
  94. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/index.rst +0 -0
  95. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/low_profile.rst +0 -0
  96. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/movim1.png +0 -0
  97. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/movim2.png +0 -0
  98. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/note.rst +0 -0
  99. {slidge-0.3.1 → slidge-0.3.3}/docs/source/user/register.rst +0 -0
  100. {slidge-0.3.1 → slidge-0.3.3}/setup.cfg +0 -0
  101. {slidge-0.3.1 → slidge-0.3.3}/slidge/__init__.py +0 -0
  102. {slidge-0.3.1 → slidge-0.3.3}/slidge/__main__.py +0 -0
  103. {slidge-0.3.1 → slidge-0.3.3}/slidge/command/__init__.py +0 -0
  104. {slidge-0.3.1 → slidge-0.3.3}/slidge/command/adhoc.py +0 -0
  105. {slidge-0.3.1 → slidge-0.3.3}/slidge/command/admin.py +0 -0
  106. {slidge-0.3.1 → slidge-0.3.3}/slidge/command/base.py +0 -0
  107. {slidge-0.3.1 → slidge-0.3.3}/slidge/command/categories.py +0 -0
  108. {slidge-0.3.1 → slidge-0.3.3}/slidge/command/chat_command.py +0 -0
  109. {slidge-0.3.1 → slidge-0.3.3}/slidge/command/register.py +0 -0
  110. {slidge-0.3.1 → slidge-0.3.3}/slidge/contact/__init__.py +0 -0
  111. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/__init__.py +0 -0
  112. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/__init__.py +0 -0
  113. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/disco.py +0 -0
  114. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/message/__init__.py +0 -0
  115. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/message/chat_state.py +0 -0
  116. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/message/marker.py +0 -0
  117. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/muc/__init__.py +0 -0
  118. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/muc/admin.py +0 -0
  119. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/muc/mam.py +0 -0
  120. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/muc/misc.py +0 -0
  121. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/muc/owner.py +0 -0
  122. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/muc/ping.py +0 -0
  123. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/session_dispatcher.py +0 -0
  124. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/dispatcher/vcard.py +0 -0
  125. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/__init__.py +0 -0
  126. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/avatar.py +0 -0
  127. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/base.py +0 -0
  128. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/disco.py +0 -0
  129. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/message.py +0 -0
  130. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/mixins/recipient.py +0 -0
  131. {slidge-0.3.1 → slidge-0.3.3}/slidge/core/pubsub.py +0 -0
  132. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/__init__.py +0 -0
  133. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/alembic/__init__.py +0 -0
  134. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/alembic/env.py +0 -0
  135. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/alembic/script.py.mako +0 -0
  136. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +0 -0
  137. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/avatar.py +0 -0
  138. {slidge-0.3.1 → slidge-0.3.3}/slidge/db/meta.py +0 -0
  139. {slidge-0.3.1 → slidge-0.3.3}/slidge/group/__init__.py +0 -0
  140. {slidge-0.3.1 → slidge-0.3.3}/slidge/group/archive.py +0 -0
  141. {slidge-0.3.1 → slidge-0.3.3}/slidge/group/bookmarks.py +0 -0
  142. {slidge-0.3.1 → slidge-0.3.3}/slidge/group/participant.py +0 -0
  143. {slidge-0.3.1 → slidge-0.3.3}/slidge/main.py +0 -0
  144. {slidge-0.3.1 → slidge-0.3.3}/slidge/migration.py +0 -0
  145. {slidge-0.3.1 → slidge-0.3.3}/slidge/py.typed +0 -0
  146. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/__init__.py +0 -0
  147. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/delivery_receipt.py +0 -0
  148. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/link_preview/__init__.py +0 -0
  149. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/link_preview/link_preview.py +0 -0
  150. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/link_preview/stanza.py +0 -0
  151. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/roster.py +0 -0
  152. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0077/__init__.py +0 -0
  153. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0077/register.py +0 -0
  154. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0077/stanza.py +0 -0
  155. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0100/__init__.py +0 -0
  156. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0100/gateway.py +0 -0
  157. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0100/stanza.py +0 -0
  158. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0153/__init__.py +0 -0
  159. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
  160. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0292/__init__.py +0 -0
  161. {slidge-0.3.1 → slidge-0.3.3}/slidge/slixfix/xep_0292/vcard4.py +0 -0
  162. {slidge-0.3.1 → slidge-0.3.3}/slidge/util/__init__.py +0 -0
  163. {slidge-0.3.1 → slidge-0.3.3}/slidge/util/archive_msg.py +0 -0
  164. {slidge-0.3.1 → slidge-0.3.3}/slidge/util/conf.py +0 -0
  165. {slidge-0.3.1 → slidge-0.3.3}/slidge/util/jid_escaping.py +0 -0
  166. {slidge-0.3.1 → slidge-0.3.3}/slidge/util/lock.py +0 -0
  167. {slidge-0.3.1 → slidge-0.3.3}/slidge.egg-info/dependency_links.txt +0 -0
  168. {slidge-0.3.1 → slidge-0.3.3}/slidge.egg-info/entry_points.txt +0 -0
  169. {slidge-0.3.1 → slidge-0.3.3}/slidge.egg-info/requires.txt +0 -0
  170. {slidge-0.3.1 → slidge-0.3.3}/slidge.egg-info/top_level.txt +0 -0
  171. {slidge-0.3.1 → slidge-0.3.3}/superduper/__init__.py +0 -0
  172. {slidge-0.3.1 → slidge-0.3.3}/superduper/__main__.py +0 -0
  173. {slidge-0.3.1 → slidge-0.3.3}/superduper/contact.py +0 -0
  174. {slidge-0.3.1 → slidge-0.3.3}/superduper/gateway.py +0 -0
  175. {slidge-0.3.1 → slidge-0.3.3}/superduper/group.py +0 -0
  176. {slidge-0.3.1 → slidge-0.3.3}/superduper/legacy_client.py +0 -0
  177. {slidge-0.3.1 → slidge-0.3.3}/superduper/session.py +0 -0
  178. {slidge-0.3.1 → slidge-0.3.3}/superduper/util.py +0 -0
  179. {slidge-0.3.1 → slidge-0.3.3}/tests/test_adhoc/test_access.py +0 -0
  180. {slidge-0.3.1 → slidge-0.3.3}/tests/test_adhoc/test_confirmation.py +0 -0
  181. {slidge-0.3.1 → slidge-0.3.3}/tests/test_adhoc/test_form.py +0 -0
  182. {slidge-0.3.1 → slidge-0.3.3}/tests/test_adhoc/test_reported.py +0 -0
  183. {slidge-0.3.1 → slidge-0.3.3}/tests/test_avatar.py +0 -0
  184. {slidge-0.3.1 → slidge-0.3.3}/tests/test_chat_commands.py +0 -0
  185. {slidge-0.3.1 → slidge-0.3.3}/tests/test_db/test_user.py +0 -0
  186. {slidge-0.3.1 → slidge-0.3.3}/tests/test_feature_restriction.py +0 -0
  187. {slidge-0.3.1 → slidge-0.3.3}/tests/test_mam_archivable.py +0 -0
  188. {slidge-0.3.1 → slidge-0.3.3}/tests/test_mds.py +0 -0
  189. {slidge-0.3.1 → slidge-0.3.3}/tests/test_resourceprep.py +0 -0
  190. {slidge-0.3.1 → slidge-0.3.3}/tests/test_stanza_link_preview.py +0 -0
  191. {slidge-0.3.1 → slidge-0.3.3}/tests/test_type_conversion.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slidge
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: XMPP bridging framework
5
5
  Author-email: Nicolas Cedilnik <nicoco@nicoco.fr>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -0,0 +1,57 @@
1
+ Configuration
2
+ =============
3
+
4
+ .. include:: ../note.rst
5
+
6
+ By default, slidge uses all config files found in ``/etc/slidge/conf.d/*``.
7
+ You can change this using the ``SLIDGE_CONF_DIR`` env var, eg
8
+ ``SLIDGE_CONF_DIR=/path/dir1:/path/dir2:/path/dir3``.
9
+
10
+ It is recommended to use ``/etc/slidge/conf.d/`` to store configuration options
11
+ common to all slidge components (eg, attachment handling, logging options,
12
+ etc.), and to specify a plugin-specific file on startup, eg:
13
+
14
+ .. code-block:: bash
15
+
16
+ slidge -c /etc/slidge/superduper.conf
17
+
18
+ .. note::
19
+ For the debian unofficial package, just edit the ``/etc/slidge/conf.d/common.conf`` and
20
+ ``/etc/slidge/*.conf`` files, and use :ref:`Debian packages (systemd)` to
21
+ launch slidge.
22
+
23
+ Command-line arguments
24
+ ----------------------
25
+
26
+ .. code-block:: text
27
+
28
+ -h, --help show this help message and exit
29
+ -c, --config CONFIG Path to a INI config file. [env var: SLIDGE_CONFIG]
30
+ --log-config LOG_CONFIG
31
+ Path to a INI config file to personalise logging output.
32
+ -q, --quiet loglevel=WARNING (unused if --log-config is specified) [env var: SLIDGE_QUIET]
33
+ -d, --debug loglevel=DEBUG (unused if --log-config is specified) [env var: SLIDGE_DEBUG]
34
+ --version show program's version number and exit
35
+
36
+
37
+ Regarding the ``--log-config`` argument, refer to the `official python documentation
38
+ <https://docs.python.org/3/library/logging.config.html#configuration-file-format>`_
39
+ for the syntax.
40
+
41
+ Other options
42
+ -------------
43
+
44
+ .. warning::
45
+
46
+ Because of an ugly mess that will soon™ be fixed, it is impossible to use
47
+ the config file to turn off boolean arguments that are true by default.
48
+ As a workaround, use CLI args instead, e.g., ``--some-opt=false``.
49
+
50
+ The following options can be used:
51
+
52
+ * in a config file (see top of this page);
53
+ * as command line arguments, prepended with ``--``, e.g., ``--some-option=value``;
54
+ * as environment variables, upper case, prepended with ``SLIDGE_``,
55
+ and with dashes substituted with underscores, e.g., ``SLIDGE_SOME_OPTION=value``.
56
+
57
+ .. config-obj:: slidge.core.config
@@ -79,11 +79,10 @@ Privileges with ejabberd
79
79
  both: slidge_rule
80
80
  message:
81
81
  outgoing: slidge_rule
82
+ iq:
83
+ "http://jabber.org/protocol/pubsub":
84
+ both: slidge_rule
85
+ "http://jabber.org/protocol/pubsub#owner":
86
+ set: slidge_rule
82
87
  mod_roster:
83
88
  versioning: true
84
-
85
- iq:
86
- "http://jabber.org/protocol/pubsub":
87
- both: slidge_rule
88
- "http://jabber.org/protocol/pubsub#owner":
89
- set: slidge_rule
@@ -39,9 +39,9 @@ extensions = [
39
39
  "sphinx.ext.extlinks",
40
40
  # "sphinx.ext.viewcode", # crashes build unfortunately
41
41
  "sphinx.ext.autodoc.typehints",
42
- "sphinx.ext.autosectionlabel",
43
42
  "sphinxarg.ext",
44
43
  "autoapi.extension",
44
+ "slidge_sphinx_extensions.config_obj",
45
45
  ]
46
46
 
47
47
  autodoc_typehints = "description"
@@ -63,6 +63,7 @@ dev = [
63
63
  "types-pillow>=10.2.0.20240822",
64
64
  "utidylib>=0.10",
65
65
  "xmldiff>=2.7.0",
66
+ "slidge-sphinx-extensions>=0.2.1,<0.3",
66
67
  ]
67
68
 
68
69
  [tool.mypy]
@@ -106,6 +107,8 @@ filterwarnings = [
106
107
  "ignore::UserWarning:slidge",
107
108
  "ignore:coroutine 'XMLStream._end_stream_wait' was never awaited.*:RuntimeWarning"
108
109
  ]
110
+ log_cli = true
111
+ log_level = "DEBUG"
109
112
 
110
113
  [[tool.uv.index]]
111
114
  name = "codeberg"
@@ -6,7 +6,7 @@ from slixmpp import JID
6
6
  from slixmpp.exceptions import XMPPError
7
7
 
8
8
  from ..group.room import LegacyMUC
9
- from ..util.types import AnyBaseSession, LegacyGroupIdType, UserPreferences
9
+ from ..util.types import AnyBaseSession, LegacyGroupIdType, MucType, UserPreferences
10
10
  from .base import (
11
11
  Command,
12
12
  CommandAccess,
@@ -157,7 +157,9 @@ class ListGroups(Command):
157
157
  async def run(self, session, _ifrom, *_):
158
158
  assert session is not None
159
159
  await session.ready
160
- groups = sorted(session.bookmarks, key=lambda g: g.DISCO_NAME.casefold())
160
+ groups = sorted(
161
+ session.bookmarks, key=lambda g: (g.name or g.jid.localpart).casefold()
162
+ )
161
163
  return TableResult(
162
164
  description="Your groups",
163
165
  fields=[FormField("name"), FormField("jid", type="jid-single")],
@@ -364,3 +366,21 @@ class LeaveGroup(Command):
364
366
  async def finish(session: AnyBaseSession, _ifrom, group: LegacyMUC) -> None:
365
367
  await session.on_leave_group(group.legacy_id)
366
368
  await session.bookmarks.remove(group, reason="You left this group via slidge.")
369
+
370
+
371
+ class InviteInGroups(Command):
372
+ NAME = "💌 Re-invite me in my groups"
373
+ HELP = "Ask the gateway to send invitations for all your private groups"
374
+ CHAT_COMMAND = "re-invite"
375
+ NODE = GROUPS.node + "/" + CHAT_COMMAND
376
+ ACCESS = CommandAccess.USER_LOGGED
377
+ CATEGORY = GROUPS
378
+
379
+ async def run(self, session, _ifrom, *_):
380
+ assert session is not None
381
+ await session.ready
382
+ for muc in session.bookmarks:
383
+ if muc.type == MucType.GROUP:
384
+ session.send_gateway_invite(
385
+ muc, reason="You asked to be re-invited in all groups."
386
+ )
@@ -120,8 +120,7 @@ class LegacyContact(
120
120
  def is_friend(self, value: bool) -> None:
121
121
  if value == self.is_friend:
122
122
  return
123
- self.stored.is_friend = value
124
- self.commit()
123
+ self.update_stored_attribute(is_friend=value)
125
124
 
126
125
  @property
127
126
  def added_to_roster(self) -> bool:
@@ -131,8 +130,7 @@ class LegacyContact(
131
130
  def added_to_roster(self, value: bool) -> None:
132
131
  if value == self.added_to_roster:
133
132
  return
134
- self.stored.added_to_roster = value
135
- self.commit()
133
+ self.update_stored_attribute(added_to_roster=value)
136
134
 
137
135
  @property
138
136
  def participants(self) -> Iterator["LegacyParticipant"]:
@@ -170,8 +168,7 @@ class LegacyContact(
170
168
  def client_type(self, value: ClientType) -> None:
171
169
  if self.stored.client_type == value:
172
170
  return
173
- self.stored.client_type = value
174
- self.commit()
171
+ self.update_stored_attribute(client_type=value)
175
172
 
176
173
  def _set_logger(self) -> None:
177
174
  self.log = logging.getLogger(f"{self.user_jid.bare}:contact:{self}")
@@ -290,13 +287,12 @@ class LegacyContact(
290
287
  def name(self, n: Optional[str]) -> None:
291
288
  if self.stored.nick == n:
292
289
  return
293
- self.stored.nick = n
290
+ self.update_stored_attribute(nick=n)
294
291
  self._set_logger()
295
292
  if self.is_friend and self.added_to_roster:
296
293
  self.xmpp.pubsub.broadcast_nick(
297
294
  user_jid=self.user_jid, jid=self.jid.bare, nick=n
298
295
  )
299
- self.commit()
300
296
  for p in self.participants:
301
297
  p.nickname = n or str(self.legacy_id)
302
298
 
@@ -361,14 +357,11 @@ class LegacyContact(
361
357
  if pronouns:
362
358
  vcard["pronouns"]["text"] = pronouns
363
359
 
364
- self.stored.vcard = str(vcard)
365
- self.stored.vcard_fetched = True
360
+ self.update_stored_attribute(vcard=str(vcard), vcard_fetched=True)
366
361
  self.session.create_task(
367
362
  self.xmpp.pubsub.broadcast_vcard_event(self.jid, self.user_jid, vcard)
368
363
  )
369
364
 
370
- self.commit()
371
-
372
365
  def get_roster_item(self):
373
366
  item = {
374
367
  "subscription": self.__get_subscription_string(),
@@ -411,7 +404,7 @@ class LegacyContact(
411
404
  # we only broadcast pubsub events for contacts added to the roster
412
405
  # so if something was set before, we need to push it now
413
406
  self.added_to_roster = True
414
- self.send_last_presence()
407
+ self.send_last_presence(force=True)
415
408
 
416
409
  async def __broadcast_pubsub_items(self) -> None:
417
410
  if not self.is_friend:
@@ -4,7 +4,7 @@ import warnings
4
4
  from typing import TYPE_CHECKING, AsyncIterator, Generic, Iterator, Optional, Type
5
5
 
6
6
  from slixmpp import JID
7
- from slixmpp.exceptions import IqError, IqTimeout
7
+ from slixmpp.exceptions import IqError, IqTimeout, XMPPError
8
8
  from sqlalchemy.orm import Session
9
9
  from sqlalchemy.orm import Session as OrmSession
10
10
 
@@ -92,6 +92,10 @@ class LegacyRoster(
92
92
  # :return:
93
93
  # """
94
94
  username = contact_jid.node
95
+ if not username:
96
+ raise XMPPError(
97
+ "bad-request", "Contacts must have a local part in their JID"
98
+ )
95
99
  contact_jid = JID(contact_jid.bare)
96
100
  async with self.lock(("username", username)):
97
101
  legacy_id = await self.jid_username_to_legacy_id(username)
@@ -239,6 +243,7 @@ class LegacyRoster(
239
243
  warnings.warn(f"Could not add to roster: {e}")
240
244
  else:
241
245
  contact.added_to_roster = True
246
+ contact.send_last_presence(force=True)
242
247
  orm.commit()
243
248
  self.__filling = False
244
249
 
@@ -1,22 +1,26 @@
1
- from datetime import timedelta
2
1
  from pathlib import Path
3
- from typing import Optional, Self
2
+ from typing import Optional
4
3
 
5
4
  from slixmpp import JID as JIDType
6
5
 
6
+ # REQUIRED, so not default value
7
7
 
8
- class _TimedeltaSeconds(timedelta):
9
- def __new__(cls, s: str) -> Self:
10
- return super().__new__(cls, seconds=int(s))
11
8
 
9
+ class _Categories:
10
+ MANDATORY = (0, "Mandatory settings")
11
+ BASE = (10, "Basic configuration")
12
+ ATTACHMENTS = (20, "Attachments")
13
+ LOG = (30, "Logging")
14
+ ADVANCED = (40, "Advanced settings")
12
15
 
13
- # REQUIRED, so not default value
14
16
 
15
17
  LEGACY_MODULE: str
16
18
  LEGACY_MODULE__DOC = (
17
- "Importable python module containing (at least) "
18
- "a BaseGateway and a LegacySession subclass"
19
+ "Importable python module containing (at least) a BaseGateway and a LegacySession subclass. "
20
+ "NB: this is not needed if you use a gateway-specific entrypoint, e.g., `slidgram` or "
21
+ "`python -m slidgram`."
19
22
  )
23
+ LEGACY_MODULE__CATEGORY = _Categories.BASE
20
24
 
21
25
  SERVER: str = "localhost"
22
26
  SERVER__DOC = (
@@ -27,17 +31,21 @@ SERVER__DOC = (
27
31
  "you change this."
28
32
  )
29
33
  SERVER__SHORT = "s"
34
+ SERVER__CATEGORY = _Categories.BASE
30
35
 
31
36
  SECRET: str
32
37
  SECRET__DOC = "The gateway component's secret (required to connect to the XMPP server)"
38
+ SECRET__CATEGORY = _Categories.MANDATORY
33
39
 
34
40
  JID: JIDType
35
41
  JID__DOC = "The gateway component's JID"
36
42
  JID__SHORT = "j"
43
+ JID__CATEGORY = _Categories.MANDATORY
37
44
 
38
45
  PORT: str = "5347"
39
46
  PORT__DOC = "The XMPP server's port for incoming component connections"
40
47
  PORT__SHORT = "p"
48
+ PORT__CATEGORY = _Categories.BASE
41
49
 
42
50
  # Dynamic default (depends on other values)
43
51
 
@@ -47,6 +55,7 @@ HOME_DIR__DOC = (
47
55
  "Defaults to /var/lib/slidge/${SLIDGE_JID}. "
48
56
  )
49
57
  HOME_DIR__DYNAMIC_DEFAULT = True
58
+ HOME_DIR__CATEGORY = _Categories.BASE
50
59
 
51
60
  DB_URL: str
52
61
  DB_URL__DOC = (
@@ -54,6 +63,7 @@ DB_URL__DOC = (
54
63
  "Defaults to sqlite:///${HOME_DIR}/slidge.sqlite"
55
64
  )
56
65
  DB_URL__DYNAMIC_DEFAULT = True
66
+ DB_URL__CATEGORY = _Categories.ADVANCED
57
67
 
58
68
  USER_JID_VALIDATOR: str
59
69
  USER_JID_VALIDATOR__DOC = (
@@ -62,11 +72,13 @@ USER_JID_VALIDATOR__DOC = (
62
72
  "you probably want to change that to .*@example.com"
63
73
  )
64
74
  USER_JID_VALIDATOR__DYNAMIC_DEFAULT = True
75
+ USER_JID_VALIDATOR__CATEGORY = _Categories.BASE
65
76
 
66
77
  # Optional, so default value + type hint if default is None
67
78
 
68
79
  ADMINS: tuple[JIDType, ...] = ()
69
80
  ADMINS__DOC = "JIDs of the gateway admins"
81
+ ADMINS__CATEGORY = _Categories.BASE
70
82
 
71
83
  UPLOAD_SERVICE: Optional[str] = None
72
84
  UPLOAD_SERVICE__DOC = (
@@ -74,11 +86,13 @@ UPLOAD_SERVICE__DOC = (
74
86
  "This is optional, as it should be automatically determined via service"
75
87
  "discovery."
76
88
  )
89
+ UPLOAD_SERVICE__CATEGORY = _Categories.ATTACHMENTS
77
90
 
78
91
  AVATAR_SIZE = 200
79
92
  AVATAR_SIZE__DOC = (
80
93
  "Maximum image size (width and height), image ratio will be preserved"
81
94
  )
95
+ AVATAR_SIZE__CATEGORY = _Categories.ADVANCED
82
96
 
83
97
  USE_ATTACHMENT_ORIGINAL_URLS = False
84
98
  USE_ATTACHMENT_ORIGINAL_URLS__DOC = (
@@ -86,11 +100,13 @@ USE_ATTACHMENT_ORIGINAL_URLS__DOC = (
86
100
  "let XMPP clients directly download them from this URL. Note that this will "
87
101
  "probably leak your client IP to the legacy network."
88
102
  )
103
+ USE_ATTACHMENT_ORIGINAL_URLS__CATEGORY = _Categories.ATTACHMENTS
89
104
 
90
105
  UPLOAD_REQUESTER: Optional[str] = None
91
106
  UPLOAD_REQUESTER__DOC = (
92
107
  "Set which JID should request the upload slots. Defaults to the component JID."
93
108
  )
109
+ UPLOAD_REQUESTER__CATEGORY = _Categories.ATTACHMENTS
94
110
 
95
111
  NO_UPLOAD_PATH: Optional[str] = None
96
112
  NO_UPLOAD_PATH__DOC = (
@@ -98,38 +114,45 @@ NO_UPLOAD_PATH__DOC = (
98
114
  "You need to set NO_UPLOAD_URL_PREFIX too if you use this option, and configure "
99
115
  "an web server to serve files in this dir."
100
116
  )
117
+ NO_UPLOAD_PATH__CATEGORY = _Categories.ATTACHMENTS
101
118
 
102
119
  NO_UPLOAD_URL_PREFIX: Optional[str] = None
103
120
  NO_UPLOAD_URL_PREFIX__DOC = (
104
121
  "Base URL that servers files in the dir set in the no-upload-path option, "
105
122
  "eg https://example.com:666/slidge-attachments/"
106
123
  )
124
+ NO_UPLOAD_URL_PREFIX__CATEGORY = _Categories.ATTACHMENTS
107
125
 
108
126
  NO_UPLOAD_METHOD: str = "copy"
109
127
  NO_UPLOAD_METHOD__DOC = (
110
128
  "Whether to 'copy', 'move', 'hardlink' or 'symlink' the files in no-upload-path."
111
129
  )
130
+ NO_UPLOAD_METHOD__CATEGORY = _Categories.ATTACHMENTS
112
131
 
113
132
  NO_UPLOAD_FILE_READ_OTHERS = False
114
133
  NO_UPLOAD_FILE_READ_OTHERS__DOC = (
115
134
  "After writing a file in NO_UPLOAD_PATH, change its permission so that 'others' can"
116
135
  " read it."
117
136
  )
137
+ NO_UPLOAD_FILE_READ_OTHERS__CATEGORY = _Categories.ATTACHMENTS
118
138
 
119
- IGNORE_DELAY_THRESHOLD = _TimedeltaSeconds("300")
139
+ IGNORE_DELAY_THRESHOLD = 300
120
140
  IGNORE_DELAY_THRESHOLD__DOC = (
121
141
  "Threshold, in seconds, below which the <delay> information is stripped "
122
142
  "out of emitted stanzas."
123
143
  )
144
+ IGNORE_DELAY_THRESHOLD__CATEGORY = _Categories.ADVANCED
124
145
 
125
146
  PARTIAL_REGISTRATION_TIMEOUT = 3600
126
147
  PARTIAL_REGISTRATION_TIMEOUT__DOC = (
127
148
  "Timeout before registration and login. Only useful for legacy networks where "
128
149
  "a single step registration process is not enough."
129
150
  )
151
+ PARTIAL_REGISTRATION_TIMEOUT__CATEGORY = _Categories.ADVANCED
130
152
 
131
153
  QR_TIMEOUT = 60
132
154
  QR_TIMEOUT__DOC = "Timeout for QR code flashing confirmation."
155
+ QR_TIMEOUT__CATEGORY = _Categories.ADVANCED
133
156
 
134
157
  FIX_FILENAME_SUFFIX_MIME_TYPE = False
135
158
  FIX_FILENAME_SUFFIX_MIME_TYPE__DOC = (
@@ -138,9 +161,11 @@ FIX_FILENAME_SUFFIX_MIME_TYPE__DOC = (
138
161
  " Therefore the MIME Type of the file is checked, if the suffix is not valid for"
139
162
  " that MIME Type, a valid one will be picked."
140
163
  )
164
+ FIX_FILENAME_SUFFIX_MIME_TYPE__CATEGORY = _Categories.ATTACHMENTS
141
165
 
142
166
  LOG_FILE: Optional[Path] = None
143
167
  LOG_FILE__DOC = "Log to a file instead of stdout/err"
168
+ LOG_FILE__CATEGORY = _Categories.LOG
144
169
 
145
170
  LOG_FORMAT: str = "%(levelname)s:%(name)s:%(message)s"
146
171
  LOG_FORMAT__DOC = (
@@ -148,41 +173,50 @@ LOG_FORMAT__DOC = (
148
173
  "https://docs.python.org/3/library/logging.html#logrecord-attributes "
149
174
  "for available options."
150
175
  )
176
+ LOG_FORMAT__CATEGORY = _Categories.LOG
151
177
 
152
178
  MAM_MAX_DAYS = 7
153
179
  MAM_MAX_DAYS__DOC = "Maximum number of days for group archive retention."
180
+ MAM_MAX_DAYS__CATEGORY = _Categories.BASE
154
181
 
155
182
  ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH = 200
156
183
  ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH__DOC = (
157
184
  "Some legacy network provide ridiculously long filenames, strip above this limit, "
158
185
  "preserving suffix."
159
186
  )
187
+ ATTACHMENT_MAXIMUM_FILE_NAME_LENGTH__CATEGORY = _Categories.ATTACHMENTS
160
188
 
161
189
  AVATAR_RESAMPLING_THREADS = 2
162
190
  AVATAR_RESAMPLING_THREADS__DOC = (
163
191
  "Number of additional threads to use for avatar resampling. Even in a single-core "
164
192
  "context, this makes avatar resampling non-blocking."
165
193
  )
194
+ AVATAR_RESAMPLING_THREADS__CATEGORY = _Categories.ADVANCED
166
195
 
167
196
  DEV_MODE = False
168
197
  DEV_MODE__DOC = (
169
198
  "Enables an interactive python shell via chat commands, for admins."
170
199
  "Not safe to use in prod, but great during dev."
171
200
  )
201
+ DEV_MODE__CATEGORY = _Categories.ADVANCED
202
+
172
203
 
173
204
  STRIP_LEADING_EMOJI_ADHOC = False
174
205
  STRIP_LEADING_EMOJI_ADHOC__DOC = (
175
206
  "Strip the leading emoji in ad-hoc command names, if present, in case you "
176
207
  "are a emoji-hater."
177
208
  )
209
+ STRIP_LEADING_EMOJI_ADHOC__CATEGORY = _Categories.ADVANCED
178
210
 
179
211
  COMPONENT_NAME: Optional[str] = None
180
212
  COMPONENT_NAME__DOC = (
181
213
  "Overrides the default component name with a custom one. This is seen in service discovery and as the nickname "
182
214
  "of the component in chat windows."
183
215
  )
216
+ COMPONENT_NAME__CATEGORY = _Categories.ADVANCED
184
217
 
185
218
  WELCOME_MESSAGE: Optional[str] = None
186
219
  WELCOME_MESSAGE__DOC = (
187
220
  "Overrides the default welcome message received by newly registered users."
188
221
  )
222
+ WELCOME_MESSAGE__CATEGORY = _Categories.ADVANCED
@@ -57,8 +57,7 @@ class CapsMixin(DispatcherMixin):
57
57
  ver = contact.stored.caps_ver
58
58
  else:
59
59
  ver = await contact.get_caps_ver(pfrom)
60
- contact.stored.caps_ver = ver
61
- contact.commit()
60
+ contact.update_stored_attribute(caps_ver=ver)
62
61
  else:
63
62
  ver = await caps.get_verstring(pfrom)
64
63
 
@@ -315,6 +315,7 @@ class MessageContentMixin(DispatcherMixin):
315
315
  raise XMPPError(
316
316
  "policy-violation",
317
317
  text=error_msg,
318
+ clear=False,
318
319
  )
319
320
 
320
321
  await session.on_react(recipient, legacy_id, emojis, thread=thread)
@@ -71,6 +71,7 @@ class PresenceHandlerMixin(DispatcherMixin):
71
71
  return
72
72
 
73
73
  await contact.on_friend_accept()
74
+ contact.send_last_presence(force=True)
74
75
 
75
76
  @exceptions_to_xmpp_errors
76
77
  async def _handle_unsubscribed(self, pres: Presence) -> None:
@@ -60,7 +60,7 @@ class RegistrationMixin(DispatcherMixin):
60
60
  )
61
61
  orm.add(user)
62
62
  orm.commit()
63
- log.info("New user: %s", user)
63
+ log.info("New user: %s", user)
64
64
 
65
65
  async def _user_modify(
66
66
  self, _gateway_jid, _node, ifrom: JID, form_dict: dict[str, Optional[str]]
@@ -3,7 +3,6 @@ from typing import TYPE_CHECKING
3
3
  from slixmpp import JID, CoroutineCallback, Iq, StanzaPath
4
4
  from slixmpp.exceptions import XMPPError
5
5
 
6
- from ...db.models import GatewayUser
7
6
  from .util import DispatcherMixin, exceptions_to_xmpp_errors
8
7
 
9
8
  if TYPE_CHECKING:
@@ -32,10 +31,7 @@ class SearchMixin(DispatcherMixin):
32
31
  """
33
32
  Prepare the search form using :attr:`.BaseSession.SEARCH_FIELDS`
34
33
  """
35
- with self.xmpp.store.session() as orm:
36
- user = orm.query(GatewayUser).one_or_none()
37
- if user is None:
38
- raise XMPPError(text="Search is only allowed for registered users")
34
+ await self._get_session(iq)
39
35
 
40
36
  xmpp = self.xmpp
41
37
 
@@ -172,7 +172,9 @@ def exceptions_to_xmpp_errors(cb: HandlerType) -> HandlerType:
172
172
  except NotImplementedError:
173
173
  log.debug("NotImplementedError raised in %s", cb)
174
174
  raise XMPPError(
175
- "feature-not-implemented", "Not implemented by the legacy module"
175
+ "feature-not-implemented",
176
+ f"{cb.__name__} is not implemented by the legacy module",
177
+ clear=False,
176
178
  )
177
179
  except Exception as e:
178
180
  log.error("Failed to handle incoming stanza: %s", args, exc_info=e)
@@ -330,6 +330,7 @@ class BaseGateway(
330
330
  self.jid_validator: re.Pattern = re.compile(config.USER_JID_VALIDATOR)
331
331
  self.qr_pending_registrations = dict[str, asyncio.Future[Optional[dict]]]()
332
332
 
333
+ self.register_plugins()
333
334
  self.__setup_legacy_module_subclasses()
334
335
 
335
336
  self.get_session_from_stanza: Callable[
@@ -339,7 +340,6 @@ class BaseGateway(
339
340
  self._session_cls.from_user
340
341
  )
341
342
 
342
- self.register_plugins()
343
343
  self.__register_slixmpp_events()
344
344
  self.__register_slixmpp_api()
345
345
  self.roster.set_backend(RosterBackend(self))
@@ -390,6 +390,16 @@ class BaseGateway(
390
390
  bookmarks_cls = LegacyBookmarks.get_self_or_unique_subclass()
391
391
  roster_cls = LegacyRoster.get_self_or_unique_subclass()
392
392
 
393
+ if contact_cls.REACTIONS_SINGLE_EMOJI: # type:ignore[attr-defined]
394
+ form = Form()
395
+ form["type"] = "result"
396
+ form.add_field(
397
+ "FORM_TYPE", "hidden", value="urn:xmpp:reactions:0:restrictions"
398
+ )
399
+ form.add_field("max_reactions_per_user", value="1", type="number")
400
+ form.add_field("scope", value="domain")
401
+ self.plugin["xep_0128"].add_extended_info(data=form)
402
+
393
403
  session_cls.xmpp = self # type:ignore[attr-defined]
394
404
  contact_cls.xmpp = self # type:ignore[attr-defined]
395
405
  muc_cls.xmpp = self # type:ignore[attr-defined]
@@ -1002,6 +1012,7 @@ SLIXMPP_PLUGINS = [
1002
1012
  "xep_0106", # JID Escaping
1003
1013
  "xep_0115", # Entity capabilities
1004
1014
  "xep_0122", # Data Forms Validation
1015
+ "xep_0128", # Service Discovery Extensions
1005
1016
  "xep_0153", # vCard-Based Avatars (for MUC avatars)
1006
1017
  "xep_0172", # User nickname
1007
1018
  "xep_0184", # Message Delivery Receipts