slidge 0.3.2__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 (190) hide show
  1. {slidge-0.3.2 → slidge-0.3.3}/PKG-INFO +1 -1
  2. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/privilege.rst +5 -6
  3. {slidge-0.3.2 → slidge-0.3.3}/slidge/command/user.py +22 -2
  4. {slidge-0.3.2 → slidge-0.3.3}/slidge/contact/contact.py +5 -12
  5. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/caps.py +1 -2
  6. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/db.py +15 -0
  7. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/presence.py +7 -15
  8. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/models.py +2 -2
  9. {slidge-0.3.2 → slidge-0.3.3}/slidge/group/room.py +12 -22
  10. {slidge-0.3.2 → slidge-0.3.3}/slidge/util/types.py +2 -0
  11. {slidge-0.3.2 → slidge-0.3.3}/slidge.egg-info/PKG-INFO +1 -1
  12. {slidge-0.3.2 → slidge-0.3.3}/tests/test_muc.py +8 -4
  13. slidge-0.3.3/uv.lock +2258 -0
  14. slidge-0.3.2/uv.lock +0 -1859
  15. {slidge-0.3.2 → slidge-0.3.3}/.gitignore +0 -0
  16. {slidge-0.3.2 → slidge-0.3.3}/.pre-commit-config.yaml +0 -0
  17. {slidge-0.3.2 → slidge-0.3.3}/.woodpecker/container-ci.yaml +0 -0
  18. {slidge-0.3.2 → slidge-0.3.3}/.woodpecker/docs.yaml +0 -0
  19. {slidge-0.3.2 → slidge-0.3.3}/.woodpecker/package.yaml +0 -0
  20. {slidge-0.3.2 → slidge-0.3.3}/.woodpecker/test.yaml +0 -0
  21. {slidge-0.3.2 → slidge-0.3.3}/Dockerfile +0 -0
  22. {slidge-0.3.2 → slidge-0.3.3}/LICENSE +0 -0
  23. {slidge-0.3.2 → slidge-0.3.3}/README.md +0 -0
  24. {slidge-0.3.2 → slidge-0.3.3}/commitlint.config.js +0 -0
  25. {slidge-0.3.2 → slidge-0.3.3}/dev/assets/5x5.png +0 -0
  26. {slidge-0.3.2 → slidge-0.3.3}/dev/assets/slidge-color-small.png +0 -0
  27. {slidge-0.3.2 → slidge-0.3.3}/dev/assets/slidge-color.png +0 -0
  28. {slidge-0.3.2 → slidge-0.3.3}/dev/assets/slidge-mono-black.png +0 -0
  29. {slidge-0.3.2 → slidge-0.3.3}/dev/assets/slidge-mono-white.png +0 -0
  30. {slidge-0.3.2 → slidge-0.3.3}/dev/assets/slidge.svg +0 -0
  31. {slidge-0.3.2 → slidge-0.3.3}/dev/confs/movim.env +0 -0
  32. {slidge-0.3.2 → slidge-0.3.3}/dev/confs/nginx.conf +0 -0
  33. {slidge-0.3.2 → slidge-0.3.3}/dev/confs/slidge-dev.ini +0 -0
  34. {slidge-0.3.2 → slidge-0.3.3}/dev/confs/slidge-example.ini +0 -0
  35. {slidge-0.3.2 → slidge-0.3.3}/dev/hot-reload.sh +0 -0
  36. {slidge-0.3.2 → slidge-0.3.3}/dev/prettify_tests.py +0 -0
  37. {slidge-0.3.2 → slidge-0.3.3}/doap.xml +0 -0
  38. {slidge-0.3.2 → slidge-0.3.3}/docker-compose.yml +0 -0
  39. {slidge-0.3.2 → slidge-0.3.3}/docs/Makefile +0 -0
  40. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/attachments.rst +0 -0
  41. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/component.rst +0 -0
  42. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/config/index.rst +0 -0
  43. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/daemon.rst +0 -0
  44. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/examples/ejabberd.yaml +0 -0
  45. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/examples/index.rst +0 -0
  46. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/examples/prosody.cfg.lua +0 -0
  47. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/index.rst +0 -0
  48. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/install.rst +0 -0
  49. {slidge-0.3.2 → slidge-0.3.3}/docs/source/admin/note.rst +0 -0
  50. {slidge-0.3.2 → slidge-0.3.3}/docs/source/codeberg.svg +0 -0
  51. {slidge-0.3.2 → slidge-0.3.3}/docs/source/conf.py +0 -0
  52. {slidge-0.3.2 → slidge-0.3.3}/docs/source/dev/contributing.rst +0 -0
  53. {slidge-0.3.2 → slidge-0.3.3}/docs/source/dev/design.rst +0 -0
  54. {slidge-0.3.2 → slidge-0.3.3}/docs/source/dev/howto.rst +0 -0
  55. {slidge-0.3.2 → slidge-0.3.3}/docs/source/dev/index.rst +0 -0
  56. {slidge-0.3.2 → slidge-0.3.3}/docs/source/dev/tutorial.rst +0 -0
  57. {slidge-0.3.2 → slidge-0.3.3}/docs/source/glossary.rst +0 -0
  58. {slidge-0.3.2 → slidge-0.3.3}/docs/source/index.rst +0 -0
  59. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/commands.rst +0 -0
  60. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/contacts.rst +0 -0
  61. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/foxyproxy.png +0 -0
  62. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/gajim.png +0 -0
  63. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/index.rst +0 -0
  64. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/low_profile.rst +0 -0
  65. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/movim1.png +0 -0
  66. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/movim2.png +0 -0
  67. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/note.rst +0 -0
  68. {slidge-0.3.2 → slidge-0.3.3}/docs/source/user/register.rst +0 -0
  69. {slidge-0.3.2 → slidge-0.3.3}/pyproject.toml +0 -0
  70. {slidge-0.3.2 → slidge-0.3.3}/setup.cfg +0 -0
  71. {slidge-0.3.2 → slidge-0.3.3}/slidge/__init__.py +0 -0
  72. {slidge-0.3.2 → slidge-0.3.3}/slidge/__main__.py +0 -0
  73. {slidge-0.3.2 → slidge-0.3.3}/slidge/command/__init__.py +0 -0
  74. {slidge-0.3.2 → slidge-0.3.3}/slidge/command/adhoc.py +0 -0
  75. {slidge-0.3.2 → slidge-0.3.3}/slidge/command/admin.py +0 -0
  76. {slidge-0.3.2 → slidge-0.3.3}/slidge/command/base.py +0 -0
  77. {slidge-0.3.2 → slidge-0.3.3}/slidge/command/categories.py +0 -0
  78. {slidge-0.3.2 → slidge-0.3.3}/slidge/command/chat_command.py +0 -0
  79. {slidge-0.3.2 → slidge-0.3.3}/slidge/command/register.py +0 -0
  80. {slidge-0.3.2 → slidge-0.3.3}/slidge/contact/__init__.py +0 -0
  81. {slidge-0.3.2 → slidge-0.3.3}/slidge/contact/roster.py +0 -0
  82. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/__init__.py +0 -0
  83. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/config.py +0 -0
  84. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/__init__.py +0 -0
  85. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/disco.py +0 -0
  86. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/message/__init__.py +0 -0
  87. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/message/chat_state.py +0 -0
  88. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/message/marker.py +0 -0
  89. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/message/message.py +0 -0
  90. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/muc/__init__.py +0 -0
  91. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/muc/admin.py +0 -0
  92. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/muc/mam.py +0 -0
  93. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/muc/misc.py +0 -0
  94. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/muc/owner.py +0 -0
  95. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/muc/ping.py +0 -0
  96. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/presence.py +0 -0
  97. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/registration.py +0 -0
  98. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/search.py +0 -0
  99. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/session_dispatcher.py +0 -0
  100. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/util.py +0 -0
  101. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/dispatcher/vcard.py +0 -0
  102. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/gateway.py +0 -0
  103. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/__init__.py +0 -0
  104. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/attachment.py +0 -0
  105. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/avatar.py +0 -0
  106. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/base.py +0 -0
  107. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/disco.py +0 -0
  108. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/message.py +0 -0
  109. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/message_maker.py +0 -0
  110. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/message_text.py +0 -0
  111. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/mixins/recipient.py +0 -0
  112. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/pubsub.py +0 -0
  113. {slidge-0.3.2 → slidge-0.3.3}/slidge/core/session.py +0 -0
  114. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/__init__.py +0 -0
  115. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/alembic/__init__.py +0 -0
  116. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/alembic/env.py +0 -0
  117. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/alembic/script.py.mako +0 -0
  118. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/alembic/versions/cef02a8b1451_initial_schema.py +0 -0
  119. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/avatar.py +0 -0
  120. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/meta.py +0 -0
  121. {slidge-0.3.2 → slidge-0.3.3}/slidge/db/store.py +0 -0
  122. {slidge-0.3.2 → slidge-0.3.3}/slidge/group/__init__.py +0 -0
  123. {slidge-0.3.2 → slidge-0.3.3}/slidge/group/archive.py +0 -0
  124. {slidge-0.3.2 → slidge-0.3.3}/slidge/group/bookmarks.py +0 -0
  125. {slidge-0.3.2 → slidge-0.3.3}/slidge/group/participant.py +0 -0
  126. {slidge-0.3.2 → slidge-0.3.3}/slidge/main.py +0 -0
  127. {slidge-0.3.2 → slidge-0.3.3}/slidge/migration.py +0 -0
  128. {slidge-0.3.2 → slidge-0.3.3}/slidge/py.typed +0 -0
  129. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/__init__.py +0 -0
  130. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/delivery_receipt.py +0 -0
  131. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/link_preview/__init__.py +0 -0
  132. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/link_preview/link_preview.py +0 -0
  133. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/link_preview/stanza.py +0 -0
  134. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/roster.py +0 -0
  135. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0077/__init__.py +0 -0
  136. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0077/register.py +0 -0
  137. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0077/stanza.py +0 -0
  138. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0100/__init__.py +0 -0
  139. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0100/gateway.py +0 -0
  140. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0100/stanza.py +0 -0
  141. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0153/__init__.py +0 -0
  142. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
  143. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0292/__init__.py +0 -0
  144. {slidge-0.3.2 → slidge-0.3.3}/slidge/slixfix/xep_0292/vcard4.py +0 -0
  145. {slidge-0.3.2 → slidge-0.3.3}/slidge/util/__init__.py +0 -0
  146. {slidge-0.3.2 → slidge-0.3.3}/slidge/util/archive_msg.py +0 -0
  147. {slidge-0.3.2 → slidge-0.3.3}/slidge/util/conf.py +0 -0
  148. {slidge-0.3.2 → slidge-0.3.3}/slidge/util/jid_escaping.py +0 -0
  149. {slidge-0.3.2 → slidge-0.3.3}/slidge/util/lock.py +0 -0
  150. {slidge-0.3.2 → slidge-0.3.3}/slidge/util/test.py +0 -0
  151. {slidge-0.3.2 → slidge-0.3.3}/slidge/util/util.py +0 -0
  152. {slidge-0.3.2 → slidge-0.3.3}/slidge.egg-info/SOURCES.txt +0 -0
  153. {slidge-0.3.2 → slidge-0.3.3}/slidge.egg-info/dependency_links.txt +0 -0
  154. {slidge-0.3.2 → slidge-0.3.3}/slidge.egg-info/entry_points.txt +0 -0
  155. {slidge-0.3.2 → slidge-0.3.3}/slidge.egg-info/requires.txt +0 -0
  156. {slidge-0.3.2 → slidge-0.3.3}/slidge.egg-info/top_level.txt +0 -0
  157. {slidge-0.3.2 → slidge-0.3.3}/superduper/__init__.py +0 -0
  158. {slidge-0.3.2 → slidge-0.3.3}/superduper/__main__.py +0 -0
  159. {slidge-0.3.2 → slidge-0.3.3}/superduper/contact.py +0 -0
  160. {slidge-0.3.2 → slidge-0.3.3}/superduper/gateway.py +0 -0
  161. {slidge-0.3.2 → slidge-0.3.3}/superduper/group.py +0 -0
  162. {slidge-0.3.2 → slidge-0.3.3}/superduper/legacy_client.py +0 -0
  163. {slidge-0.3.2 → slidge-0.3.3}/superduper/session.py +0 -0
  164. {slidge-0.3.2 → slidge-0.3.3}/superduper/util.py +0 -0
  165. {slidge-0.3.2 → slidge-0.3.3}/tests/conftest.py +0 -0
  166. {slidge-0.3.2 → slidge-0.3.3}/tests/test_adhoc/test_access.py +0 -0
  167. {slidge-0.3.2 → slidge-0.3.3}/tests/test_adhoc/test_confirmation.py +0 -0
  168. {slidge-0.3.2 → slidge-0.3.3}/tests/test_adhoc/test_form.py +0 -0
  169. {slidge-0.3.2 → slidge-0.3.3}/tests/test_adhoc/test_reported.py +0 -0
  170. {slidge-0.3.2 → slidge-0.3.3}/tests/test_attachment.py +0 -0
  171. {slidge-0.3.2 → slidge-0.3.3}/tests/test_avatar.py +0 -0
  172. {slidge-0.3.2 → slidge-0.3.3}/tests/test_backfill.py +0 -0
  173. {slidge-0.3.2 → slidge-0.3.3}/tests/test_chat_commands.py +0 -0
  174. {slidge-0.3.2 → slidge-0.3.3}/tests/test_config.py +0 -0
  175. {slidge-0.3.2 → slidge-0.3.3}/tests/test_db/test_store.py +0 -0
  176. {slidge-0.3.2 → slidge-0.3.3}/tests/test_db/test_user.py +0 -0
  177. {slidge-0.3.2 → slidge-0.3.3}/tests/test_feature_restriction.py +0 -0
  178. {slidge-0.3.2 → slidge-0.3.3}/tests/test_gateway_wide_reaction_restrictions.py +0 -0
  179. {slidge-0.3.2 → slidge-0.3.3}/tests/test_mam_archivable.py +0 -0
  180. {slidge-0.3.2 → slidge-0.3.3}/tests/test_mds.py +0 -0
  181. {slidge-0.3.2 → slidge-0.3.3}/tests/test_muc_subject.py +0 -0
  182. {slidge-0.3.2 → slidge-0.3.3}/tests/test_resourceprep.py +0 -0
  183. {slidge-0.3.2 → slidge-0.3.3}/tests/test_session.py +0 -0
  184. {slidge-0.3.2 → slidge-0.3.3}/tests/test_session_2.py +0 -0
  185. {slidge-0.3.2 → slidge-0.3.3}/tests/test_set_name_before_fill.py +0 -0
  186. {slidge-0.3.2 → slidge-0.3.3}/tests/test_shakespeare.py +0 -0
  187. {slidge-0.3.2 → slidge-0.3.3}/tests/test_stanza_link_preview.py +0 -0
  188. {slidge-0.3.2 → slidge-0.3.3}/tests/test_type_conversion.py +0 -0
  189. {slidge-0.3.2 → slidge-0.3.3}/tests/test_util.py +0 -0
  190. {slidge-0.3.2 → slidge-0.3.3}/tests/test_vcard.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slidge
3
- Version: 0.3.2
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
@@ -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
@@ -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(),
@@ -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
 
@@ -2,6 +2,8 @@ import logging
2
2
  import typing
3
3
  from contextlib import contextmanager
4
4
 
5
+ import sqlalchemy as sa
6
+
5
7
  from ...db.models import Base, Contact, Room
6
8
 
7
9
  if typing.TYPE_CHECKING:
@@ -86,3 +88,16 @@ class UpdateInfoMixin(DBMixin):
86
88
  else:
87
89
  self.stored.extra_attributes = self.serialize_extra_attributes()
88
90
  super().commit(merge=merge)
91
+
92
+ def update_stored_attribute(self, **kwargs) -> None:
93
+ for key, value in kwargs.items():
94
+ setattr(self.stored, key, value)
95
+ if self._updating_info:
96
+ return
97
+ with self.xmpp.store.session() as orm:
98
+ orm.execute(
99
+ sa.update(self.stored.__class__)
100
+ .where(self.stored.__class__.id == self.stored.id)
101
+ .values(**kwargs)
102
+ )
103
+ orm.commit()
@@ -96,22 +96,14 @@ class PresenceMixin(BaseSender, DBMixin):
96
96
  )
97
97
 
98
98
  def _store_last_presence(self, new: CachedPresence) -> None:
99
- stored_contact = self.__stored()
100
- if stored_contact is None:
101
- return
102
- stored_contact.cached_presence = True
103
- for k, v in new._asdict().items():
104
- setattr(stored_contact, k, v)
105
- if self.__is_contact() and self._updating_info:
99
+ if self.__is_contact():
100
+ contact = self
101
+ elif (contact := getattr(self, "contact", None)) is None: # type:ignore[assignment]
106
102
  return
107
- with self.xmpp.store.session(expire_on_commit=False) as orm:
108
- try:
109
- orm.add(stored_contact)
110
- except InvalidRequestError:
111
- stored_contact = orm.merge(stored_contact)
112
- orm.add(stored_contact)
113
-
114
- orm.commit()
103
+ contact.update_stored_attribute( # type:ignore[attr-defined]
104
+ cached_presence=True,
105
+ **new._asdict(),
106
+ )
115
107
 
116
108
  def _make_presence(
117
109
  self,
@@ -9,7 +9,7 @@ from slixmpp.types import MucAffiliation, MucRole
9
9
  from sqlalchemy import JSON, ForeignKey, Index, UniqueConstraint
10
10
  from sqlalchemy.orm import Mapped, mapped_column, relationship
11
11
 
12
- from ..util.types import ClientType, MucType
12
+ from ..util.types import ClientType, Hat, MucType
13
13
  from .meta import Base, JSONSerializable, JSONSerializableTypes
14
14
 
15
15
 
@@ -344,7 +344,7 @@ class Participant(Base):
344
344
  nickname: Mapped[str] = mapped_column(nullable=False, default=None)
345
345
  nickname_no_illegal: Mapped[str] = mapped_column(nullable=False, default=None)
346
346
 
347
- hats: Mapped[list[tuple[str, str]]] = mapped_column(JSON, default=list)
347
+ hats: Mapped[list[Hat]] = mapped_column(JSON, default=list)
348
348
 
349
349
  extra_attributes: Mapped[Optional[JSONSerializable]] = mapped_column(default=None)
350
350
 
@@ -169,8 +169,7 @@ class LegacyMUC(
169
169
  def type(self, type_: MucType) -> None:
170
170
  if self.type == type_:
171
171
  return
172
- self.stored.muc_type = type_
173
- self.commit()
172
+ self.update_stored_attribute(muc_type=type_)
174
173
 
175
174
  @property
176
175
  def n_participants(self):
@@ -180,8 +179,7 @@ class LegacyMUC(
180
179
  def n_participants(self, n_participants: Optional[int]) -> None:
181
180
  if self.stored.n_participants == n_participants:
182
181
  return
183
- self.stored.n_participants = n_participants
184
- self.commit()
182
+ self.update_stored_attribute(n_participants=n_participants)
185
183
 
186
184
  @property
187
185
  def user_jid(self):
@@ -203,8 +201,7 @@ class LegacyMUC(
203
201
  def subject_date(self, when: Optional[datetime]) -> None:
204
202
  if self.subject_date == when:
205
203
  return
206
- self.stored.subject_date = when
207
- self.commit()
204
+ self.update_stored_attribute(subject_date=when)
208
205
 
209
206
  def __send_configuration_change(self, codes) -> None:
210
207
  part = self.get_system_participant()
@@ -222,18 +219,16 @@ class LegacyMUC(
222
219
  def user_nick(self, nick: str) -> None:
223
220
  if nick == self.user_nick:
224
221
  return
225
- self.stored.user_nick = nick
226
- self.commit()
222
+ self.update_stored_attribute(user_nick=nick)
227
223
 
228
224
  def add_user_resource(self, resource: str) -> None:
229
225
  stored_set = self.get_user_resources()
230
226
  if resource in stored_set:
231
227
  return
232
228
  stored_set.add(resource)
233
- self.stored.user_resources = (
234
- json.dumps(list(stored_set)) if stored_set else None
229
+ self.update_stored_attribute(
230
+ user_resources=(json.dumps(list(stored_set)) if stored_set else None)
235
231
  )
236
- self.commit()
237
232
 
238
233
  def get_user_resources(self) -> set[str]:
239
234
  stored_str = self.stored.user_resources
@@ -246,10 +241,9 @@ class LegacyMUC(
246
241
  if resource not in stored_set:
247
242
  return
248
243
  stored_set.remove(resource)
249
- self.stored.user_resources = (
250
- json.dumps(list(stored_set)) if stored_set else None
244
+ self.update_stored_attribute(
245
+ user_resources=(json.dumps(list(stored_set)) if stored_set else None)
251
246
  )
252
- self.commit()
253
247
 
254
248
  @asynccontextmanager
255
249
  async def lock(self, id_: str) -> AsyncIterator[None]:
@@ -346,8 +340,7 @@ class LegacyMUC(
346
340
  def name(self, n: str | None) -> None:
347
341
  if self.name == n:
348
342
  return
349
- self.stored.name = n
350
- self.commit()
343
+ self.update_stored_attribute(name=n)
351
344
  self._set_logger()
352
345
  self.__send_configuration_change((104,))
353
346
 
@@ -359,8 +352,7 @@ class LegacyMUC(
359
352
  def description(self, d: str) -> None:
360
353
  if self.description == d:
361
354
  return
362
- self.stored.description = d
363
- self.commit()
355
+ self.update_stored_attribute(description=d)
364
356
  self.__send_configuration_change((104,))
365
357
 
366
358
  def on_presence_unavailable(self, p: Presence) -> None:
@@ -440,8 +432,7 @@ class LegacyMUC(
440
432
  if s == self.subject:
441
433
  return
442
434
 
443
- self.stored.subject = s
444
- self.commit()
435
+ self.update_stored_attribute(subject=s)
445
436
  self.__get_subject_setter_participant().set_room_subject(
446
437
  s, None, self.subject_date, False
447
438
  )
@@ -464,8 +455,7 @@ class LegacyMUC(
464
455
  if subject_setter == self.subject_setter:
465
456
  return
466
457
  assert isinstance(subject_setter, str | None)
467
- self.stored.subject_setter = subject_setter
468
- self.commit()
458
+ self.update_stored_attribute(subject_setter=subject_setter)
469
459
 
470
460
  def __get_subject_setter_participant(self) -> LegacyParticipant:
471
461
  if self.subject_setter is None:
@@ -178,6 +178,8 @@ class Mention(NamedTuple):
178
178
  class Hat(NamedTuple):
179
179
  uri: str
180
180
  title: str
181
+ hue: float = 0.0 # Default value is not great here, but an OK workaround for the
182
+ # slixmpp 1.11→1.12 API update
181
183
 
182
184
 
183
185
  class UserPreferences(TypedDict):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: slidge
3
- Version: 0.3.2
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
@@ -4525,9 +4525,11 @@ class TestHats(Base):
4525
4525
  </x>
4526
4526
  <hats xmlns="urn:xmpp:hats:0">
4527
4527
  <hat uri="uri1"
4528
- title="title1" />
4528
+ title="title1"
4529
+ hue="0.0" />
4529
4530
  <hat uri="uri2"
4530
- title="title2" />
4531
+ title="title2"
4532
+ hue="0.0" />
4531
4533
  </hats>
4532
4534
  <occupant-id xmlns="urn:xmpp:occupant-id:0"
4533
4535
  id="uuid" />
@@ -4546,9 +4548,11 @@ class TestHats(Base):
4546
4548
  </x>
4547
4549
  <hats xmlns="urn:xmpp:hats:0">
4548
4550
  <hat uri="uri1"
4549
- title="title1" />
4551
+ title="title1"
4552
+ hue="0.0" />
4550
4553
  <hat uri="uri3"
4551
- title="title3" />
4554
+ title="title3"
4555
+ hue="0.0" />
4552
4556
  </hats>
4553
4557
  <occupant-id xmlns="urn:xmpp:occupant-id:0"
4554
4558
  id="uuid" />