slidge 0.2.0b0__tar.gz → 0.2.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. {slidge-0.2.0b0 → slidge-0.2.1}/PKG-INFO +1 -1
  2. {slidge-0.2.0b0 → slidge-0.2.1}/pyproject.toml +2 -1
  3. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/__version__.py +1 -1
  4. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/command/adhoc.py +31 -15
  5. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/command/admin.py +11 -4
  6. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/command/base.py +5 -2
  7. slidge-0.2.1/slidge/command/categories.py +13 -0
  8. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/command/chat_command.py +14 -1
  9. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/command/register.py +2 -1
  10. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/command/user.py +17 -9
  11. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/contact/contact.py +3 -3
  12. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/contact/roster.py +3 -3
  13. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/config.py +6 -0
  14. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/presence.py +4 -5
  15. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/session_dispatcher.py +2 -2
  16. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/gateway.py +3 -3
  17. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/attachment.py +2 -2
  18. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/message.py +1 -1
  19. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +2 -0
  20. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +1 -1
  21. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +6 -0
  22. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +7 -6
  23. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +4 -0
  24. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/models.py +4 -2
  25. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/group/room.py +2 -2
  26. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/main.py +1 -1
  27. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/__init__.py +29 -32
  28. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/util/util.py +18 -0
  29. slidge-0.2.0b0/slidge/command/categories.py +0 -3
  30. {slidge-0.2.0b0 → slidge-0.2.1}/LICENSE +0 -0
  31. {slidge-0.2.0b0 → slidge-0.2.1}/README.md +0 -0
  32. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/__init__.py +0 -0
  33. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/__main__.py +0 -0
  34. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/command/__init__.py +0 -0
  35. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/contact/__init__.py +0 -0
  36. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/__init__.py +0 -0
  37. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/__init__.py +0 -0
  38. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/caps.py +0 -0
  39. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/disco.py +0 -0
  40. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/message/__init__.py +0 -0
  41. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/message/chat_state.py +0 -0
  42. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/message/marker.py +0 -0
  43. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/message/message.py +0 -0
  44. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/muc/__init__.py +0 -0
  45. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/muc/admin.py +0 -0
  46. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/muc/mam.py +0 -0
  47. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/muc/misc.py +0 -0
  48. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/muc/owner.py +0 -0
  49. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/muc/ping.py +0 -0
  50. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/registration.py +0 -0
  51. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/search.py +0 -0
  52. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/util.py +0 -0
  53. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/dispatcher/vcard.py +0 -0
  54. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/__init__.py +0 -0
  55. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/avatar.py +0 -0
  56. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/base.py +0 -0
  57. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/db.py +0 -0
  58. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/disco.py +0 -0
  59. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/lock.py +0 -0
  60. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/message_maker.py +0 -0
  61. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/message_text.py +0 -0
  62. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/presence.py +0 -0
  63. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/mixins/recipient.py +0 -0
  64. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/pubsub.py +0 -0
  65. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/core/session.py +0 -0
  66. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/__init__.py +0 -0
  67. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/__init__.py +0 -0
  68. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/env.py +0 -0
  69. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/old_user_store.py +0 -0
  70. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/script.py.mako +0 -0
  71. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +0 -0
  72. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +0 -0
  73. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +0 -0
  74. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +0 -0
  75. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/45c24cc73c91_add_bob.py +0 -0
  76. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +0 -0
  77. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +0 -0
  78. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +0 -0
  79. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +0 -0
  80. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +0 -0
  81. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +0 -0
  82. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +0 -0
  83. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/avatar.py +0 -0
  84. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/meta.py +0 -0
  85. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/db/store.py +2 -2
  86. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/group/__init__.py +0 -0
  87. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/group/archive.py +0 -0
  88. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/group/bookmarks.py +0 -0
  89. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/group/participant.py +0 -0
  90. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/migration.py +0 -0
  91. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/py.typed +0 -0
  92. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/delivery_receipt.py +0 -0
  93. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/link_preview/__init__.py +0 -0
  94. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/link_preview/link_preview.py +0 -0
  95. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/link_preview/stanza.py +0 -0
  96. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/roster.py +0 -0
  97. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0077/__init__.py +0 -0
  98. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0077/register.py +0 -0
  99. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0077/stanza.py +0 -0
  100. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0100/__init__.py +0 -0
  101. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0100/gateway.py +0 -0
  102. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0100/stanza.py +0 -0
  103. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0153/__init__.py +0 -0
  104. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0153/stanza.py +0 -0
  105. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0153/vcard_avatar.py +0 -0
  106. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0264/__init__.py +0 -0
  107. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0264/stanza.py +0 -0
  108. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0264/thumbnail.py +0 -0
  109. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0292/__init__.py +0 -0
  110. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0292/vcard4.py +0 -0
  111. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0313/__init__.py +0 -0
  112. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0313/mam.py +0 -0
  113. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0313/stanza.py +0 -0
  114. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0317/__init__.py +0 -0
  115. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0317/hats.py +0 -0
  116. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0317/stanza.py +0 -0
  117. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0356_old/__init__.py +0 -0
  118. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0356_old/privilege.py +0 -0
  119. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0356_old/stanza.py +0 -0
  120. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0424/__init__.py +0 -0
  121. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0424/retraction.py +0 -0
  122. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0424/stanza.py +0 -0
  123. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0490/__init__.py +0 -0
  124. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0490/mds.py +0 -0
  125. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/slixfix/xep_0490/stanza.py +0 -0
  126. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/util/__init__.py +0 -0
  127. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/util/archive_msg.py +0 -0
  128. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/util/conf.py +0 -0
  129. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/util/db.py +0 -0
  130. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/util/test.py +0 -0
  131. {slidge-0.2.0b0 → slidge-0.2.1}/slidge/util/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: slidge
3
- Version: 0.2.0b0
3
+ Version: 0.2.1
4
4
  Summary: XMPP bridging framework
5
5
  Home-page: https://sr.ht/~nicoco/slidge/
6
6
  License: AGPL-3.0-or-later
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "slidge"
3
- version = "0.2.0beta0"
3
+ version = "0.2.1"
4
4
  description = "XMPP bridging framework"
5
5
  authors = ["Nicolas Cedilnik <nicoco@nicoco.fr>"]
6
6
  readme = "README.md"
@@ -51,6 +51,7 @@ xmldiff = "^2.5"
51
51
  types-pillow = "^9.5.0.0"
52
52
  pre-commit = "^3.3.0"
53
53
  coverage = "^7.2.7"
54
+ emoji = "*"
54
55
 
55
56
  [tool.poetry.group.dev.dependencies.slidge-dev-helpers]
56
57
  git = "https://git.sr.ht/~nicoco/slidge-dev-helpers"
@@ -2,4 +2,4 @@ from slidge.util.util import get_version # noqa: F401
2
2
 
3
3
  # this is modified before publish, but if someone cloned from the repo,
4
4
  # it can help
5
- __version__ = "0.2.0beta0"
5
+ __version__ = "0.2.1"
@@ -9,8 +9,11 @@ from slixmpp.exceptions import XMPPError
9
9
  from slixmpp.plugins.xep_0004 import Form as SlixForm # type: ignore[attr-defined]
10
10
  from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
11
11
 
12
+ from ..core import config
13
+ from ..util.util import strip_leading_emoji
12
14
  from . import Command, CommandResponseType, Confirmation, Form, TableResult
13
15
  from .base import FormField
16
+ from .categories import CommandCategory
14
17
 
15
18
  if TYPE_CHECKING:
16
19
  from ..core.gateway import BaseGateway
@@ -46,19 +49,19 @@ class AdhocProvider:
46
49
  return await self.__handle_result(session, result, adhoc_session)
47
50
 
48
51
  async def __handle_category_list(
49
- self, category: str, iq: Iq, adhoc_session: AdhocSessionType
52
+ self, category: CommandCategory, iq: Iq, adhoc_session: AdhocSessionType
50
53
  ) -> AdhocSessionType:
51
54
  try:
52
55
  session = self.xmpp.get_session_from_stanza(iq)
53
56
  except XMPPError:
54
57
  session = None
55
- commands = []
56
- for command in self._categories[category]:
58
+ commands: dict[str, Command] = {}
59
+ for command in self._categories[category.node]:
57
60
  try:
58
61
  command.raise_if_not_authorized(iq.get_from())
59
62
  except XMPPError:
60
63
  continue
61
- commands.append(command)
64
+ commands[command.NODE] = command
62
65
  if len(commands) == 0:
63
66
  raise XMPPError(
64
67
  "not-authorized", "There is no command you can run in this category"
@@ -66,7 +69,7 @@ class AdhocProvider:
66
69
  return await self.__handle_result(
67
70
  session,
68
71
  Form(
69
- category,
72
+ category.name,
70
73
  "",
71
74
  [
72
75
  FormField(
@@ -74,8 +77,11 @@ class AdhocProvider:
74
77
  label="Command",
75
78
  type="list-single",
76
79
  options=[
77
- {"label": command.NAME, "value": str(i)}
78
- for i, command in enumerate(commands)
80
+ {
81
+ "label": strip_leading_emoji_if_needed(command.NAME),
82
+ "value": command.NODE,
83
+ }
84
+ for command in commands.values()
79
85
  ],
80
86
  )
81
87
  ],
@@ -86,12 +92,12 @@ class AdhocProvider:
86
92
 
87
93
  async def __handle_category_choice(
88
94
  self,
89
- commands: list[Command],
95
+ commands: dict[str, Command],
90
96
  form_values: dict[str, str],
91
97
  session: "BaseSession[Any, Any]",
92
98
  jid: JID,
93
99
  ):
94
- command = commands[int(form_values["command"])]
100
+ command = commands[form_values["command"]]
95
101
  result = await self.__wrap_handler(command.run, session, jid)
96
102
  return result
97
103
 
@@ -207,19 +213,23 @@ class AdhocProvider:
207
213
  self.xmpp.plugin["xep_0050"].add_command( # type: ignore[no-untyped-call]
208
214
  jid=jid,
209
215
  node=command.NODE,
210
- name=command.NAME,
216
+ name=strip_leading_emoji_if_needed(command.NAME),
211
217
  handler=partial(self.__wrap_initial_handler, command),
212
218
  )
213
219
  else:
214
- if category not in self._categories:
215
- self._categories[category] = list[Command]()
220
+ if isinstance(category, str):
221
+ category = CommandCategory(category, category)
222
+ node = category.node
223
+ name = category.name
224
+ if node not in self._categories:
225
+ self._categories[node] = list[Command]()
216
226
  self.xmpp.plugin["xep_0050"].add_command( # type: ignore[no-untyped-call]
217
227
  jid=jid,
218
- node=category,
219
- name=category,
228
+ node=node,
229
+ name=strip_leading_emoji_if_needed(name),
220
230
  handler=partial(self.__handle_category_list, category),
221
231
  )
222
- self._categories[category].append(command)
232
+ self._categories[node].append(command)
223
233
 
224
234
  async def get_items(self, jid: JID, node: str, iq: Iq) -> DiscoItems:
225
235
  """
@@ -262,4 +272,10 @@ class AdhocProvider:
262
272
  return filtered_items
263
273
 
264
274
 
275
+ def strip_leading_emoji_if_needed(text: str) -> str:
276
+ if config.STRIP_LEADING_EMOJI_ADHOC:
277
+ return strip_leading_emoji(text)
278
+ return text
279
+
280
+
265
281
  log = logging.getLogger(__name__)
@@ -11,6 +11,7 @@ from slixmpp.exceptions import XMPPError
11
11
  from ..core import config
12
12
  from ..util.types import AnyBaseSession
13
13
  from .base import (
14
+ NODE_PREFIX,
14
15
  Command,
15
16
  CommandAccess,
16
17
  Confirmation,
@@ -21,6 +22,8 @@ from .base import (
21
22
  )
22
23
  from .categories import ADMINISTRATION
23
24
 
25
+ NODE_PREFIX = NODE_PREFIX + "admin/"
26
+
24
27
 
25
28
  class AdminCommand(Command):
26
29
  ACCESS = CommandAccess.ADMIN_ONLY
@@ -30,7 +33,8 @@ class AdminCommand(Command):
30
33
  class ListUsers(AdminCommand):
31
34
  NAME = "👤 List registered users"
32
35
  HELP = "List the users registered to this gateway"
33
- NODE = CHAT_COMMAND = "list_users"
36
+ CHAT_COMMAND = "list_users"
37
+ NODE = NODE_PREFIX + CHAT_COMMAND
34
38
 
35
39
  async def run(self, _session, _ifrom, *_):
36
40
  items = []
@@ -51,7 +55,8 @@ class ListUsers(AdminCommand):
51
55
  class SlidgeInfo(AdminCommand):
52
56
  NAME = "ℹ️ Server information"
53
57
  HELP = "List the users registered to this gateway"
54
- NODE = CHAT_COMMAND = "info"
58
+ CHAT_COMMAND = "info"
59
+ NODE = NODE_PREFIX + CHAT_COMMAND
55
60
  ACCESS = CommandAccess.ANY
56
61
 
57
62
  async def run(self, _session, _ifrom, *_):
@@ -105,7 +110,8 @@ class SlidgeInfo(AdminCommand):
105
110
  class DeleteUser(AdminCommand):
106
111
  NAME = "❌ Delete a user"
107
112
  HELP = "Unregister a user from the gateway"
108
- NODE = CHAT_COMMAND = "delete_user"
113
+ CHAT_COMMAND = "delete_user"
114
+ NODE = NODE_PREFIX + CHAT_COMMAND
109
115
 
110
116
  async def run(self, _session, _ifrom, *_):
111
117
  return Form(
@@ -141,7 +147,8 @@ class DeleteUser(AdminCommand):
141
147
  class ChangeLoglevel(AdminCommand):
142
148
  NAME = "📋 Change the verbosity of the logs"
143
149
  HELP = "Set the logging level"
144
- NODE = CHAT_COMMAND = "loglevel"
150
+ CHAT_COMMAND = "loglevel"
151
+ NODE = NODE_PREFIX + CHAT_COMMAND
145
152
 
146
153
  async def run(self, _session, _ifrom, *_):
147
154
  return Form(
@@ -25,9 +25,12 @@ from slixmpp.types import JidStr
25
25
  from ..core import config
26
26
  from ..util.types import AnyBaseSession, FieldType
27
27
 
28
+ NODE_PREFIX = "https://slidge.im/command/core/"
29
+
28
30
  if TYPE_CHECKING:
29
31
  from ..core.gateway import BaseGateway
30
32
  from ..core.session import BaseSession
33
+ from .categories import CommandCategory
31
34
 
32
35
 
33
36
  HandlerType = Union[
@@ -178,8 +181,8 @@ class Form:
178
181
  """
179
182
  form = SlixForm() # type: ignore[no-untyped-call]
180
183
  form["type"] = "form"
181
- form["instructions"] = self.instructions
182
184
  form["title"] = self.title
185
+ form["instructions"] = self.instructions
183
186
  for fi in self.fields:
184
187
  form.append(fi.get_xml())
185
188
  return form
@@ -347,7 +350,7 @@ class Command(ABC):
347
350
  Who can use this command
348
351
  """
349
352
 
350
- CATEGORY: Optional[str] = None
353
+ CATEGORY: Optional[Union[str, "CommandCategory"]] = None
351
354
  """
352
355
  If used, the command will be under this top-level category.
353
356
  Use the same string for several commands to group them.
@@ -0,0 +1,13 @@
1
+ from typing import NamedTuple
2
+
3
+ from .base import NODE_PREFIX
4
+
5
+
6
+ class CommandCategory(NamedTuple):
7
+ name: str
8
+ node: str
9
+
10
+
11
+ ADMINISTRATION = CommandCategory("🛷️ Slidge administration", NODE_PREFIX + "admin")
12
+ CONTACTS = CommandCategory("👤 Contacts", NODE_PREFIX + "contacts")
13
+ GROUPS = CommandCategory("👥 Groups", NODE_PREFIX + "groups")
@@ -13,6 +13,7 @@ from slixmpp.exceptions import XMPPError
13
13
  from slixmpp.types import JidStr, MessageTypes
14
14
 
15
15
  from . import Command, CommandResponseType, Confirmation, Form, TableResult
16
+ from .categories import CommandCategory
16
17
 
17
18
  if TYPE_CHECKING:
18
19
  from ..core.gateway import BaseGateway
@@ -280,7 +281,19 @@ class ChatCommandProvider:
280
281
  def _help(self, mfrom: JID):
281
282
  msg = "Available commands:"
282
283
  for c in sorted(
283
- self._commands.values(), key=lambda co: (co.CATEGORY or "", co.CHAT_COMMAND)
284
+ self._commands.values(),
285
+ key=lambda co: (
286
+ (
287
+ co.CATEGORY
288
+ if isinstance(co.CATEGORY, str)
289
+ else (
290
+ co.CATEGORY.name
291
+ if isinstance(co.CATEGORY, CommandCategory)
292
+ else ""
293
+ )
294
+ ),
295
+ co.CHAT_COMMAND,
296
+ ),
284
297
  ):
285
298
  try:
286
299
  c.raise_if_not_authorized(mfrom)
@@ -75,7 +75,8 @@ class Register(Command):
75
75
  self.xmpp.event("user_register", Iq(sfrom=ifrom.bare))
76
76
  return self.SUCCESS_MESSAGE
77
77
 
78
- async def run(self, _session, _ifrom, *_):
78
+ async def run(self, _session, ifrom: JID, *_):
79
+ self.xmpp.raise_if_not_allowed_jid(ifrom)
79
80
  return Form(
80
81
  title=f"Registration to '{self.xmpp.COMPONENT_NAME}'",
81
82
  instructions=self.xmpp.REGISTRATION_INSTRUCTIONS,
@@ -26,8 +26,8 @@ if TYPE_CHECKING:
26
26
  class Search(Command):
27
27
  NAME = "🔎 Search for contacts"
28
28
  HELP = "Search for contacts via this gateway"
29
- NODE = "search"
30
29
  CHAT_COMMAND = "find"
30
+ NODE = CONTACTS.node + "/" + CHAT_COMMAND
31
31
  ACCESS = CommandAccess.USER_LOGGED
32
32
  CATEGORY = CONTACTS
33
33
 
@@ -64,7 +64,8 @@ class SyncContacts(Command):
64
64
  "Synchronize your XMPP roster with your legacy contacts. "
65
65
  "Slidge will only add/remove/modify contacts in its dedicated roster group"
66
66
  )
67
- NODE = CHAT_COMMAND = "sync-contacts"
67
+ CHAT_COMMAND = "sync-contacts"
68
+ NODE = CONTACTS.node + "/" + CHAT_COMMAND
68
69
  ACCESS = CommandAccess.USER_LOGGED
69
70
  CATEGORY = CONTACTS
70
71
 
@@ -123,7 +124,8 @@ class SyncContacts(Command):
123
124
 
124
125
  class ListContacts(Command):
125
126
  NAME = HELP = "👤 List your legacy contacts"
126
- NODE = CHAT_COMMAND = "contacts"
127
+ CHAT_COMMAND = "contacts"
128
+ NODE = CONTACTS.node + "/" + CHAT_COMMAND
127
129
  ACCESS = CommandAccess.USER_LOGGED
128
130
  CATEGORY = CONTACTS
129
131
 
@@ -143,7 +145,8 @@ class ListContacts(Command):
143
145
 
144
146
  class ListGroups(Command):
145
147
  NAME = HELP = "👥 List your legacy groups"
146
- NODE = CHAT_COMMAND = "groups"
148
+ CHAT_COMMAND = "groups"
149
+ NODE = GROUPS.node + "/" + CHAT_COMMAND
147
150
  ACCESS = CommandAccess.USER_LOGGED
148
151
  CATEGORY = GROUPS
149
152
 
@@ -162,7 +165,8 @@ class ListGroups(Command):
162
165
  class Login(Command):
163
166
  NAME = "🔐 Re-login to the legacy network"
164
167
  HELP = "Login to the legacy service"
165
- NODE = CHAT_COMMAND = "re-login"
168
+ CHAT_COMMAND = "re-login"
169
+ NODE = "https://slidge.im/command/core/" + CHAT_COMMAND
166
170
 
167
171
  ACCESS = CommandAccess.USER_NON_LOGGED
168
172
 
@@ -184,7 +188,8 @@ class Login(Command):
184
188
  class CreateGroup(Command):
185
189
  NAME = "🆕 New legacy group"
186
190
  HELP = "Create a group on the legacy service"
187
- NODE = CHAT_COMMAND = "create-group"
191
+ CHAT_COMMAND = "create-group"
192
+ NODE = GROUPS.node + "/" + CHAT_COMMAND
188
193
  CATEGORY = GROUPS
189
194
 
190
195
  ACCESS = CommandAccess.USER_LOGGED
@@ -233,7 +238,8 @@ class CreateGroup(Command):
233
238
  class Preferences(Command):
234
239
  NAME = "⚙️ Preferences"
235
240
  HELP = "Customize the gateway behaviour to your liking"
236
- NODE = CHAT_COMMAND = "preferences"
241
+ CHAT_COMMAND = "preferences"
242
+ NODE = "https://slidge.im/command/core/preferences"
237
243
  ACCESS = CommandAccess.USER
238
244
 
239
245
  async def run(
@@ -268,7 +274,8 @@ class Preferences(Command):
268
274
  class Unregister(Command):
269
275
  NAME = "❌ Unregister from the gateway"
270
276
  HELP = "Unregister from the gateway"
271
- NODE = CHAT_COMMAND = "unregister"
277
+ CHAT_COMMAND = "unregister"
278
+ NODE = "https://slidge.im/command/core/unregister"
272
279
  ACCESS = CommandAccess.USER
273
280
 
274
281
  async def run(
@@ -290,7 +297,8 @@ class Unregister(Command):
290
297
 
291
298
  class LeaveGroup(Command):
292
299
  NAME = HELP = "❌ Leave a legacy group"
293
- NODE = CHAT_COMMAND = "leave-group"
300
+ CHAT_COMMAND = "leave-group"
301
+ NODE = GROUPS.node + "/" + CHAT_COMMAND
294
302
  ACCESS = CommandAccess.USER_LOGGED
295
303
  CATEGORY = GROUPS
296
304
 
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Generic, Iterable, Optional, Self, Union
6
6
  from xml.etree import ElementTree as ET
7
7
 
8
8
  from slixmpp import JID, Message, Presence
9
- from slixmpp.exceptions import IqError
9
+ from slixmpp.exceptions import IqError, IqTimeout
10
10
  from slixmpp.plugins.xep_0292.stanza import VCard4
11
11
  from slixmpp.types import MessageTypes
12
12
 
@@ -454,7 +454,7 @@ class LegacyContact(
454
454
  except PermissionError:
455
455
  warnings.warn(
456
456
  "Slidge does not have privileges to add contacts to the roster. Refer"
457
- " to https://slidge.readthedocs.io/en/latest/admin/xmpp_server.html for"
457
+ " to https://slidge.im/core/admin/privilege.html for"
458
458
  " more info."
459
459
  )
460
460
  if config.ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK:
@@ -463,7 +463,7 @@ class LegacyContact(
463
463
  "slidge is not allowed to manage your roster."
464
464
  )
465
465
  return
466
- except IqError as e:
466
+ except (IqError, IqTimeout) as e:
467
467
  self.log.warning("Could not add to roster", exc_info=e)
468
468
  else:
469
469
  # we only broadcast pubsub events for contacts added to the roster
@@ -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, XMPPError
7
+ from slixmpp.exceptions import IqError, IqTimeout, XMPPError
8
8
  from slixmpp.jid import JID_UNESCAPE_TRANSFORMATIONS, _unescape_node
9
9
 
10
10
  from ..core.mixins.lock import NamedLockMixin
@@ -210,7 +210,7 @@ class LegacyRoster(
210
210
  self.session.user_jid.bare
211
211
  )
212
212
  user_roster = iq["roster"]["items"]
213
- except (PermissionError, IqError):
213
+ except (PermissionError, IqError, IqTimeout):
214
214
  user_roster = None
215
215
 
216
216
  with self.__store.session() as orm:
@@ -232,7 +232,7 @@ class LegacyRoster(
232
232
  self.session.user_jid.bare,
233
233
  item,
234
234
  )
235
- except (PermissionError, IqError) as e:
235
+ except (PermissionError, IqError, IqTimeout) as e:
236
236
  warnings.warn(f"Could not add to roster: {e}")
237
237
  else:
238
238
  contact._added_to_roster = True
@@ -214,3 +214,9 @@ DEV_MODE__DOC = (
214
214
  "Enables an interactive python shell via chat commands, for admins."
215
215
  "Not safe to use in prod, but great during dev."
216
216
  )
217
+
218
+ STRIP_LEADING_EMOJI_ADHOC = False
219
+ STRIP_LEADING_EMOJI_ADHOC__DOC = (
220
+ "Strip the leading emoji in ad-hoc command names, if present, in case you "
221
+ "are a emoji-hater."
222
+ )
@@ -154,14 +154,13 @@ class PresenceHandlerMixin(DispatcherMixin):
154
154
  # when setting a status message, or going away, etc.
155
155
  return
156
156
 
157
- # We can't use XMPPError here because from must be room@slidge/VALID-USER-NICK
157
+ # We can't use XMPPError here because XMPPError does not have a way to
158
+ # add the <x xmlns="http://jabber.org/protocol/muc" /> element
158
159
 
159
- error_from = JID(muc.jid)
160
- error_from.resource = muc.user_nick
161
160
  error_stanza = p.error()
162
161
  error_stanza.set_to(p.get_from())
163
- error_stanza.set_from(error_from)
164
- error_stanza.enable("muc_join")
162
+ error_stanza.set_from(pto)
163
+ error_stanza.enable("muc_join") # <x xmlns="http://jabber.org/protocol/muc" />
165
164
  error_stanza.enable("error")
166
165
  error_stanza["error"]["type"] = "cancel"
167
166
  error_stanza["error"]["by"] = muc.jid
@@ -2,7 +2,7 @@ import logging
2
2
  from typing import TYPE_CHECKING
3
3
 
4
4
  from slixmpp import Message
5
- from slixmpp.exceptions import IqError
5
+ from slixmpp.exceptions import IqError, IqTimeout
6
6
  from slixmpp.plugins.xep_0084.stanza import Info
7
7
 
8
8
  from ..session import BaseSession
@@ -59,7 +59,7 @@ class SessionDispatcher(
59
59
  iq = await self.xmpp.plugin["xep_0084"].retrieve_avatar(
60
60
  session.user_jid, hash_, ifrom=self.xmpp.boundjid.bare
61
61
  )
62
- except IqError as e:
62
+ except (IqError, IqTimeout) as e:
63
63
  session.log.warning("Could not fetch the user's avatar: %s", e)
64
64
  return
65
65
  bytes_ = iq["pubsub"]["items"]["item"]["avatar_data"]["value"]
@@ -429,7 +429,7 @@ class BaseGateway(
429
429
  try:
430
430
  await self["xep_0100"].add_component_to_roster(user.jid)
431
431
  await self.__add_component_to_mds_whitelist(user.jid)
432
- except IqError as e:
432
+ except (IqError, IqTimeout) as e:
433
433
  # TODO: remove the user when this happens? or at least
434
434
  # this can happen when the user has unsubscribed from the XMPP server
435
435
  log.warning(
@@ -464,7 +464,7 @@ class BaseGateway(
464
464
  "create the MDS node of %s",
465
465
  user_jid,
466
466
  )
467
- except IqError as e:
467
+ except (IqError, IqTimeout) as e:
468
468
  # conflict this means the node already exists, we can ignore that
469
469
  if e.condition != "conflict":
470
470
  log.exception(
@@ -548,7 +548,7 @@ class BaseGateway(
548
548
  self.xmpp.plugin["xep_0084"].stanza.MetaData.namespace,
549
549
  ifrom=self.boundjid.bare,
550
550
  )
551
- except IqError:
551
+ except (IqError, IqTimeout):
552
552
  self.xmpp.store.users.set_avatar_hash(session.user_pk, None)
553
553
  return
554
554
  await self.__dispatcher.on_avatar_metadata_info(
@@ -19,7 +19,7 @@ from xml.etree import ElementTree as ET
19
19
  import thumbhash
20
20
  from PIL import Image, ImageOps
21
21
  from slixmpp import JID, Message
22
- from slixmpp.exceptions import IqError
22
+ from slixmpp.exceptions import IqError, IqTimeout
23
23
  from slixmpp.plugins.xep_0363 import FileUploadError
24
24
  from slixmpp.plugins.xep_0447.stanza import StatelessFileSharing
25
25
 
@@ -65,7 +65,7 @@ class AttachmentMixin(TextMessageMixin):
65
65
  ifrom=config.UPLOAD_REQUESTER or self.xmpp.boundjid,
66
66
  domain=JID(domain),
67
67
  )
68
- except (FileUploadError, IqError) as e:
68
+ except (FileUploadError, IqError, IqTimeout) as e:
69
69
  warnings.warn(f"Something is wrong with the upload service: {e!r}")
70
70
  return None
71
71
  finally:
@@ -172,7 +172,7 @@ class CarbonMessageMixin(ContentMessageMixin, MarkerMixin):
172
172
  warnings.warn(
173
173
  "Slidge does not have privileges to send message on behalf of"
174
174
  " user.Refer to"
175
- " https://slidge.readthedocs.io/en/latest/admin/xmpp_server.html"
175
+ " https://slidge.im/core/admin/privilege.html"
176
176
  " for more info."
177
177
  )
178
178
 
@@ -59,6 +59,8 @@ room_table = sa.Table(
59
59
 
60
60
 
61
61
  def upgrade() -> None:
62
+ if op.get_bind().engine.name == "postgresql":
63
+ return
62
64
  with op.batch_alter_table(
63
65
  "room",
64
66
  schema=None,
@@ -37,7 +37,7 @@ def upgrade() -> None:
37
37
  native_enum=False,
38
38
  ),
39
39
  nullable=False,
40
- server_default=sa.text("pc"),
40
+ server_default=sa.literal("pc"),
41
41
  )
42
42
  )
43
43
 
@@ -95,6 +95,12 @@ def upgrade() -> None:
95
95
  op.add_column("room", sa.Column("description", sa.String(), nullable=True))
96
96
  op.add_column("room", sa.Column("subject", sa.String(), nullable=True))
97
97
  op.add_column("room", sa.Column("subject_date", sa.DateTime(), nullable=True))
98
+
99
+ if op.get_bind().engine.name == "postgresql":
100
+ op.execute(
101
+ "CREATE TYPE muctype AS ENUM ('GROUP', 'CHANNEL', 'CHANNEL_NON_ANONYMOUS')"
102
+ )
103
+
98
104
  op.add_column(
99
105
  "room",
100
106
  sa.Column(
@@ -37,12 +37,13 @@ def upgrade() -> None:
37
37
 
38
38
  with op.batch_alter_table("room", schema=None) as batch_op:
39
39
  batch_op.add_column(sa.Column("avatar_legacy_id", sa.String(), nullable=True))
40
- batch_op.create_unique_constraint(
41
- "uq_room_user_account_id_jid", ["user_account_id", "jid"]
42
- )
43
- batch_op.create_unique_constraint(
44
- "uq_room_user_account_id_legacy_id", ["user_account_id", "legacy_id"]
45
- )
40
+ if op.get_bind().engine.name != "postgresql":
41
+ batch_op.create_unique_constraint(
42
+ "uq_room_user_account_id_jid", ["user_account_id", "jid"]
43
+ )
44
+ batch_op.create_unique_constraint(
45
+ "uq_room_user_account_id_legacy_id", ["user_account_id", "legacy_id"]
46
+ )
46
47
 
47
48
  for room_pk, avatar_legacy_id in room_avatars:
48
49
  conn.execute(
@@ -24,6 +24,10 @@ def upgrade() -> None:
24
24
  # since we don't want source to be nullable, we drop all rows first.
25
25
  # This is what you get by using alpha versions!
26
26
  op.execute(sa.delete(ArchivedMessage))
27
+
28
+ if op.get_bind().engine.name == "postgresql":
29
+ op.execute("CREATE TYPE archivedmessagesource AS ENUM ('LIVE', 'BACKFILL')")
30
+
27
31
  # ### commands auto generated by Alembic - please adjust! ###
28
32
 
29
33
  with op.batch_alter_table("mam", schema=None) as batch_op:
@@ -235,7 +235,9 @@ class Room(Base):
235
235
  updated: Mapped[bool] = mapped_column(default=False)
236
236
 
237
237
  participants: Mapped[list["Participant"]] = relationship(
238
- back_populates="room", primaryjoin="Participant.room_id == Room.id"
238
+ back_populates="room",
239
+ primaryjoin="Participant.room_id == Room.id",
240
+ cascade="all, delete-orphan",
239
241
  )
240
242
 
241
243
  avatar_legacy_id: Mapped[Optional[str]] = mapped_column(nullable=True)
@@ -317,7 +319,7 @@ class LegacyIdsMulti(Base):
317
319
 
318
320
  legacy_id: Mapped[str] = mapped_column(nullable=False)
319
321
  xmpp_ids: Mapped[list["XmppIdsMulti"]] = relationship(
320
- back_populates="legacy_ids_multi"
322
+ back_populates="legacy_ids_multi", cascade="all, delete-orphan"
321
323
  )
322
324
 
323
325
 
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, AsyncIterator, Generic, Optional, Self, Union
9
9
  from uuid import uuid4
10
10
 
11
11
  from slixmpp import JID, Iq, Message, Presence
12
- from slixmpp.exceptions import IqError, XMPPError
12
+ from slixmpp.exceptions import IqError, IqTimeout, XMPPError
13
13
  from slixmpp.jid import _unescape_node
14
14
  from slixmpp.plugins.xep_0004 import Form
15
15
  from slixmpp.plugins.xep_0060.stanza import Item
@@ -1029,7 +1029,7 @@ class LegacyMUC(
1029
1029
  # (slixmpp annoying magic)
1030
1030
  item = ans["pubsub"]["items"]["item"]
1031
1031
  item["id"] = self.jid
1032
- except IqError:
1032
+ except (IqError, IqTimeout):
1033
1033
  item["conference"]["autojoin"] = auto_join
1034
1034
  except PermissionError:
1035
1035
  warnings.warn(
@@ -112,7 +112,7 @@ def configure():
112
112
 
113
113
  if not (h := config.HOME_DIR).exists():
114
114
  logging.info("Creating directory '%s'", h)
115
- h.mkdir()
115
+ os.makedirs(h)
116
116
 
117
117
  config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare
118
118
 
@@ -1,12 +1,13 @@
1
1
  # This module contains patches for slixmpp; some have pending requests upstream
2
2
  # and should be removed on the next slixmpp release.
3
- import logging
4
- from collections import defaultdict
3
+
4
+ # ruff: noqa: F401
5
5
 
6
6
  import slixmpp.plugins
7
- from slixmpp import Message
7
+ from slixmpp import Iq, Message
8
+ from slixmpp.exceptions import XMPPError
8
9
  from slixmpp.plugins.xep_0050 import XEP_0050, Command
9
- from slixmpp.plugins.xep_0356.privilege import _VALID_ACCESSES, XEP_0356
10
+ from slixmpp.plugins.xep_0231 import XEP_0231
10
11
  from slixmpp.xmlstream import StanzaBase
11
12
 
12
13
  from . import ( # xep_0356,
@@ -23,36 +24,33 @@ from . import ( # xep_0356,
23
24
  xep_0490,
24
25
  )
25
26
 
26
- # ruff: noqa: F401
27
27
 
28
+ async def _handle_bob_iq(self, iq: Iq):
29
+ cid = iq["bob"]["cid"]
28
30
 
29
- # TODO: Remove me once https://codeberg.org/poezio/slixmpp/pulls/3541 makes it
30
- # to a slixmpp release
31
- def _handle_privilege(self, msg: StanzaBase):
32
- """
33
- Called when the XMPP server advertise the component's privileges.
31
+ if iq["type"] == "result":
32
+ await self.api["set_bob"](iq["from"], None, iq["to"], args=iq["bob"])
33
+ self.xmpp.event("bob", iq)
34
+ elif iq["type"] == "get":
35
+ data = await self.api["get_bob"](iq["to"], None, iq["from"], args=cid)
34
36
 
35
- Stores the privileges in this instance's granted_privileges attribute (a dict)
36
- and raises the privileges_advertised event
37
- """
38
- permissions = self.granted_privileges[msg.get_from()]
39
- for perm in msg["privilege"]["perms"]:
40
- access = perm["access"]
41
- if access == "iq":
42
- if not perm.get_plugin("namespace", check=True):
43
- permissions.iq = defaultdict(lambda: perm["type"])
44
- else:
45
- for ns in perm["namespaces"]:
46
- permissions.iq[ns["ns"]] = ns["type"]
47
- elif access in _VALID_ACCESSES:
48
- setattr(permissions, access, perm["type"])
49
- else:
50
- log.warning("Received an invalid privileged access: %s", access)
51
- log.debug("Privileges: %s", self.granted_privileges)
52
- self.xmpp.event("privileges_advertised")
53
-
54
-
55
- XEP_0356._handle_privilege = _handle_privilege
37
+ if data is None:
38
+ raise XMPPError(
39
+ "item-not-found",
40
+ f"Bits of binary '{cid}' is not available.",
41
+ )
42
+
43
+ if isinstance(data, Iq):
44
+ data["id"] = iq["id"]
45
+ data.send()
46
+ return
47
+
48
+ iq = iq.reply()
49
+ iq.append(data)
50
+ iq.send()
51
+
52
+
53
+ XEP_0231._handle_bob_iq = _handle_bob_iq
56
54
 
57
55
 
58
56
  def session_bind(self, jid):
@@ -98,4 +96,3 @@ slixmpp.plugins.PLUGINS.extend(
98
96
 
99
97
 
100
98
  Message.reply = reply # type: ignore
101
- log = logging.getLogger(__name__)
@@ -9,6 +9,13 @@ from pathlib import Path
9
9
  from time import time
10
10
  from typing import TYPE_CHECKING, Callable, NamedTuple, Optional, Type, TypeVar
11
11
 
12
+ try:
13
+ import emoji
14
+ except ImportError:
15
+ EMOJI_LIB_AVAILABLE = False
16
+ else:
17
+ EMOJI_LIB_AVAILABLE = True
18
+
12
19
  from .types import Mention, ResourceDict
13
20
 
14
21
  if TYPE_CHECKING:
@@ -318,3 +325,14 @@ def timeit(func):
318
325
  return r
319
326
 
320
327
  return wrapped
328
+
329
+
330
+ def strip_leading_emoji(text: str) -> str:
331
+ if not EMOJI_LIB_AVAILABLE:
332
+ return text
333
+ words = text.split(" ")
334
+ # is_emoji returns False for 🛷️ for obscure reasons,
335
+ # purely_emoji seems better
336
+ if len(words) > 1 and emoji.purely_emoji(words[0]):
337
+ return " ".join(words[1:])
338
+ return text
@@ -1,3 +0,0 @@
1
- ADMINISTRATION = "🛷️ Slidge administration"
2
- CONTACTS = "👤 Contacts"
3
- GROUPS = "👥 Groups"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1001,9 +1001,9 @@ class ParticipantStore(EngineMixin):
1001
1001
  def __init__(self, *a, **kw):
1002
1002
  super().__init__(*a, **kw)
1003
1003
  with self.session() as session:
1004
- session.execute(delete(Participant))
1005
- session.execute(delete(Hat))
1006
1004
  session.execute(delete(participant_hats))
1005
+ session.execute(delete(Hat))
1006
+ session.execute(delete(Participant))
1007
1007
  session.commit()
1008
1008
 
1009
1009
  def add(self, room_pk: int, nickname: str) -> int:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes