slidge 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. slidge/__version__.py +1 -1
  2. slidge/command/register.py +1 -3
  3. slidge/contact/contact.py +1 -1
  4. slidge/core/dispatcher/message/message.py +4 -1
  5. slidge/core/dispatcher/muc/admin.py +3 -4
  6. slidge/core/dispatcher/presence.py +3 -3
  7. slidge/core/gateway.py +2 -0
  8. slidge/core/mixins/attachment.py +1 -1
  9. slidge/core/mixins/avatar.py +6 -0
  10. slidge/core/mixins/message.py +13 -2
  11. slidge/core/mixins/recipient.py +2 -2
  12. slidge/db/store.py +9 -3
  13. slidge/group/archive.py +3 -3
  14. slidge/group/participant.py +11 -5
  15. slidge/group/room.py +21 -1
  16. slidge/main.py +1 -1
  17. slidge/slixfix/__init__.py +69 -13
  18. slidge/slixfix/xep_0153/__init__.py +0 -1
  19. slidge/slixfix/xep_0153/vcard_avatar.py +1 -10
  20. slidge/slixfix/xep_0492/__init__.py +8 -0
  21. slidge/slixfix/xep_0492/notify.py +16 -0
  22. slidge/slixfix/xep_0492/stanza.py +102 -0
  23. slidge/util/test.py +9 -2
  24. slidge-0.2.4.dist-info/METADATA +793 -0
  25. {slidge-0.2.2.dist-info → slidge-0.2.4.dist-info}/RECORD +41 -54
  26. {slidge-0.2.2.dist-info → slidge-0.2.4.dist-info}/WHEEL +2 -1
  27. slidge-0.2.4.dist-info/entry_points.txt +2 -0
  28. slidge-0.2.4.dist-info/top_level.txt +1 -0
  29. slidge/slixfix/xep_0153/stanza.py +0 -25
  30. slidge/slixfix/xep_0264/__init__.py +0 -5
  31. slidge/slixfix/xep_0264/stanza.py +0 -36
  32. slidge/slixfix/xep_0264/thumbnail.py +0 -23
  33. slidge/slixfix/xep_0313/__init__.py +0 -12
  34. slidge/slixfix/xep_0313/mam.py +0 -262
  35. slidge/slixfix/xep_0313/stanza.py +0 -359
  36. slidge/slixfix/xep_0317/__init__.py +0 -5
  37. slidge/slixfix/xep_0317/hats.py +0 -17
  38. slidge/slixfix/xep_0317/stanza.py +0 -28
  39. slidge/slixfix/xep_0424/__init__.py +0 -9
  40. slidge/slixfix/xep_0424/retraction.py +0 -77
  41. slidge/slixfix/xep_0424/stanza.py +0 -28
  42. slidge/slixfix/xep_0490/__init__.py +0 -8
  43. slidge/slixfix/xep_0490/mds.py +0 -47
  44. slidge/slixfix/xep_0490/stanza.py +0 -17
  45. slidge-0.2.2.dist-info/LICENSE +0 -661
  46. slidge-0.2.2.dist-info/METADATA +0 -116
  47. slidge-0.2.2.dist-info/entry_points.txt +0 -3
slidge/__version__.py CHANGED
@@ -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.2"
5
+ __version__ = "v0.2.4"
@@ -119,9 +119,7 @@ class Register(Command):
119
119
  elif self.xmpp.REGISTRATION_TYPE == RegistrationType.QRCODE:
120
120
  self.xmpp.qr_pending_registrations[ # type:ignore
121
121
  user.jid.bare
122
- ] = (
123
- self.xmpp.loop.create_future()
124
- )
122
+ ] = self.xmpp.loop.create_future()
125
123
  qr_text = await self.xmpp.get_qr_text(user)
126
124
  qr = qrcode.make(qr_text)
127
125
  with tempfile.NamedTemporaryFile(
slidge/contact/contact.py CHANGED
@@ -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.im/core/admin/privilege.html for"
457
+ " to https://slidge.codeberg.page/docs/main/admin/privilege.html for"
458
458
  " more info."
459
459
  )
460
460
  if config.ROSTER_PUSH_PRESENCE_SUBSCRIPTION_REQUEST_FALLBACK:
@@ -296,7 +296,10 @@ class MessageContentMixin(DispatcherMixin):
296
296
  # no need to carbon for groups, we just don't echo the stanza
297
297
  entity.react(legacy_id, carbon=True) # type: ignore
298
298
  await session.on_react(entity, legacy_id, [], thread=thread)
299
- raise XMPPError("not-acceptable", text=error_msg)
299
+ raise XMPPError(
300
+ "policy-violation", # type:ignore
301
+ text=error_msg,
302
+ )
300
303
 
301
304
  await session.on_react(entity, legacy_id, emojis, thread=thread)
302
305
  if isinstance(entity, LegacyMUC):
@@ -11,7 +11,7 @@ class MucAdminMixin(DispatcherMixin):
11
11
  self.xmpp.register_handler(
12
12
  CoroutineCallback(
13
13
  "MUCModerate",
14
- StanzaPath("iq/apply_to/moderate"),
14
+ StanzaPath("iq/moderate"),
15
15
  self.on_user_moderation,
16
16
  )
17
17
  )
@@ -35,12 +35,11 @@ class MucAdminMixin(DispatcherMixin):
35
35
  assert isinstance(iq, Iq)
36
36
  muc = await self.get_muc_from_stanza(iq)
37
37
 
38
- apply_to = iq["apply_to"]
39
- xmpp_id = apply_to["id"]
38
+ moderate = iq["moderate"]
39
+ xmpp_id = iq["moderate"]["id"]
40
40
  if not xmpp_id:
41
41
  raise XMPPError("bad-request", "Missing moderated message ID")
42
42
 
43
- moderate = apply_to["moderate"]
44
43
  if not moderate["retract"]:
45
44
  raise XMPPError(
46
45
  "feature-not-implemented",
@@ -165,9 +165,9 @@ class PresenceHandlerMixin(DispatcherMixin):
165
165
  error_stanza["error"]["type"] = "cancel"
166
166
  error_stanza["error"]["by"] = muc.jid
167
167
  error_stanza["error"]["condition"] = "not-acceptable"
168
- error_stanza["error"][
169
- "text"
170
- ] = "Slidge does not let you change your nickname in groups."
168
+ error_stanza["error"]["text"] = (
169
+ "Slidge does not let you change your nickname in groups."
170
+ )
171
171
  error_stanza.send()
172
172
 
173
173
 
slidge/core/gateway.py CHANGED
@@ -913,7 +913,9 @@ SLIXMPP_PLUGINS = [
913
913
  "xep_0444", # Message reactions
914
914
  "xep_0447", # Stateless File Sharing
915
915
  "xep_0461", # Message replies
916
+ "xep_0469", # Bookmark Pinning
916
917
  "xep_0490", # Message Displayed Synchronization
918
+ "xep_0492", # Chat Notification Settings
917
919
  ]
918
920
 
919
921
  LOG_STRIP_ELEMENTS = ["data", "binval"]
@@ -20,11 +20,11 @@ import thumbhash
20
20
  from PIL import Image, ImageOps
21
21
  from slixmpp import JID, Message
22
22
  from slixmpp.exceptions import IqError, IqTimeout
23
+ from slixmpp.plugins.xep_0264.stanza import Thumbnail
23
24
  from slixmpp.plugins.xep_0363 import FileUploadError
24
25
  from slixmpp.plugins.xep_0447.stanza import StatelessFileSharing
25
26
 
26
27
  from ...db.avatar import avatar_cache
27
- from ...slixfix.xep_0264.stanza import Thumbnail
28
28
  from ...util.types import (
29
29
  LegacyAttachment,
30
30
  LegacyMessageType,
@@ -3,6 +3,7 @@ from hashlib import sha1
3
3
  from pathlib import Path
4
4
  from typing import TYPE_CHECKING, Optional
5
5
 
6
+ from PIL import UnidentifiedImageError
6
7
  from slixmpp import JID
7
8
 
8
9
  from ...db.avatar import CachedAvatar, avatar_cache
@@ -100,6 +101,11 @@ class AvatarMixin:
100
101
  else:
101
102
  try:
102
103
  cached_avatar = await avatar_cache.convert_or_get(a)
104
+ except UnidentifiedImageError:
105
+ self.session.log.warning("%s is not a valid avatar", a)
106
+ self._avatar_pk = None
107
+ self.__avatar_unique_id = uid
108
+ return
103
109
  except Exception as e:
104
110
  self.session.log.error("Failed to set avatar %s", a, exc_info=e)
105
111
  self._avatar_pk = None
@@ -4,8 +4,8 @@ import warnings
4
4
  from typing import TYPE_CHECKING, Optional
5
5
 
6
6
  from slixmpp import Iq, Message
7
+ from slixmpp.plugins.xep_0004 import Form
7
8
 
8
- from ...slixfix.xep_0490.mds import PUBLISH_OPTIONS
9
9
  from ...util.types import ChatState, LegacyMessageType, Marker
10
10
  from .attachment import AttachmentMixin
11
11
  from .message_maker import MessageMaker
@@ -14,6 +14,17 @@ from .message_text import TextMessageMixin
14
14
  if TYPE_CHECKING:
15
15
  from ...group import LegacyMUC
16
16
 
17
+ # this is for MDS
18
+ PUBLISH_OPTIONS = Form()
19
+ PUBLISH_OPTIONS["type"] = "submit"
20
+ PUBLISH_OPTIONS.add_field(
21
+ "FORM_TYPE", "hidden", value="http://jabber.org/protocol/pubsub#publish-options"
22
+ )
23
+ PUBLISH_OPTIONS.add_field("pubsub#persist_items", value="true")
24
+ PUBLISH_OPTIONS.add_field("pubsub#max_items", value="max")
25
+ PUBLISH_OPTIONS.add_field("pubsub#send_last_published_item", value="never")
26
+ PUBLISH_OPTIONS.add_field("pubsub#access_model", value="whitelist")
27
+
17
28
 
18
29
  class ChatStateMixin(MessageMaker):
19
30
  def __init__(self):
@@ -172,7 +183,7 @@ class CarbonMessageMixin(ContentMessageMixin, MarkerMixin):
172
183
  warnings.warn(
173
184
  "Slidge does not have privileges to send message on behalf of"
174
185
  " user.Refer to"
175
- " https://slidge.im/core/admin/privilege.html"
186
+ " https://slidge.codeberg.page/docs/main/admin/privilege.html"
176
187
  " for more info."
177
188
  )
178
189
 
@@ -21,9 +21,9 @@ class ReactionRecipientMixin:
21
21
  form["type"] = "result"
22
22
  form.add_field("FORM_TYPE", "hidden", value="urn:xmpp:reactions:0:restrictions")
23
23
  if self.REACTIONS_SINGLE_EMOJI:
24
- form.add_field("max_reactions_per_user", value="1")
24
+ form.add_field("max_reactions_per_user", value="1", type="number")
25
25
  if available:
26
- form.add_field("allowlist", value=list(available))
26
+ form.add_field("allowlist", value=list(available), type="text-multi")
27
27
  return form
28
28
 
29
29
  async def available_emojis(
slidge/db/store.py CHANGED
@@ -18,9 +18,16 @@ from sqlalchemy.sql.functions import count
18
18
 
19
19
  from ..core import config
20
20
  from ..util.archive_msg import HistoryMessage
21
- from ..util.types import URL, CachedPresence, ClientType
21
+ from ..util.types import (
22
+ URL,
23
+ CachedPresence,
24
+ ClientType,
25
+ MamMetadata,
26
+ MucAffiliation,
27
+ MucRole,
28
+ Sticker,
29
+ )
22
30
  from ..util.types import Hat as HatTuple
23
- from ..util.types import MamMetadata, MucAffiliation, MucRole, Sticker
24
31
  from .meta import Base
25
32
  from .models import (
26
33
  ArchivedMessage,
@@ -545,7 +552,6 @@ class MAMStore(EngineMixin):
545
552
  sender: Optional[str] = None,
546
553
  flip=False,
547
554
  ) -> Iterator[HistoryMessage]:
548
-
549
555
  with self.session() as session:
550
556
  q = select(ArchivedMessage).where(ArchivedMessage.room_id == room_pk)
551
557
  if start_date is not None:
slidge/group/archive.py CHANGED
@@ -49,9 +49,9 @@ class MessageArchive:
49
49
  new_msg["muc"]["jid"] = participant.muc.jid
50
50
  else:
51
51
  log.warning("No real JID for participant in this group")
52
- new_msg["muc"][
53
- "jid"
54
- ] = f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
52
+ new_msg["muc"]["jid"] = (
53
+ f"{uuid.uuid4()}@{participant.xmpp.boundjid.bare}"
54
+ )
55
55
 
56
56
  self.__store.add_message(
57
57
  self.room_pk,
@@ -322,7 +322,10 @@ class LegacyParticipant(
322
322
  legacy_msg_id=None,
323
323
  **send_kwargs,
324
324
  ) -> MessageOrPresenceTypeVar:
325
- stanza["occupant-id"]["id"] = self.__occupant_id
325
+ if stanza.get_from().resource:
326
+ stanza["occupant-id"]["id"] = self.__occupant_id
327
+ else:
328
+ stanza["occupant-id"]["id"] = "room"
326
329
  self.__add_nick_element(stanza)
327
330
  if not self.is_user and isinstance(stanza, Presence):
328
331
  if stanza["type"] == "unavailable" and not self._presence_sent:
@@ -459,11 +462,14 @@ class LegacyParticipant(
459
462
 
460
463
  for i in msg_ids:
461
464
  m = self.muc.get_system_participant()._make_message()
462
- m["apply_to"]["id"] = i
463
- m["apply_to"]["moderated"].enable("retract")
464
- m["apply_to"]["moderated"]["by"] = self.jid
465
+ m["retract"]["id"] = i
466
+ if self.is_system:
467
+ m["retract"].enable("moderated")
468
+ else:
469
+ m["retract"]["moderated"]["by"] = self.jid
470
+ m["retract"]["moderated"]["occupant-id"]["id"] = self.__occupant_id
465
471
  if reason:
466
- m["apply_to"]["moderated"]["reason"] = reason
472
+ m["retract"]["reason"] = reason
467
473
  self._send(m)
468
474
 
469
475
  def set_room_subject(
slidge/group/room.py CHANGED
@@ -26,6 +26,7 @@ from ..core.mixins.disco import ChatterDiscoMixin
26
26
  from ..core.mixins.lock import NamedLockMixin
27
27
  from ..core.mixins.recipient import ReactionRecipientMixin, ThreadRecipientMixin
28
28
  from ..db.models import Room
29
+ from ..slixfix.xep_0492.stanza import WhenLiteral
29
30
  from ..util import ABCSubclassableOnceAtMost
30
31
  from ..util.types import (
31
32
  HoleBound,
@@ -994,7 +995,14 @@ class LegacyMUC(
994
995
  p["muc"]["status_codes"] = {110, 333}
995
996
  p.send()
996
997
 
997
- async def add_to_bookmarks(self, auto_join=True, invite=False, preserve=True):
998
+ async def add_to_bookmarks(
999
+ self,
1000
+ auto_join=True,
1001
+ invite=False,
1002
+ preserve=True,
1003
+ pin: bool | None = None,
1004
+ notify: WhenLiteral | None = None,
1005
+ ):
998
1006
  """
999
1007
  Add the MUC to the user's XMPP bookmarks (:xep:`0402')
1000
1008
 
@@ -1012,6 +1020,11 @@ class LegacyMUC(
1012
1020
  settings.
1013
1021
  :param preserve: preserve auto-join and bookmarks extensions
1014
1022
  set by the user outside slidge
1023
+ :param pin: Pin the group chat bookmark :xep:`0469`. Requires privileged entity.
1024
+ If set to ``None`` (default), the bookmark pinning status will be untouched.
1025
+ :param notify: Chat notification setting: :xep:`0492`. Requires privileged entity.
1026
+ If set to ``None`` (default), the setting will be untouched. Only the "global"
1027
+ notification setting is supported (ie, per client type is not possible).
1015
1028
  """
1016
1029
  item = Item()
1017
1030
  item["id"] = self.jid
@@ -1046,6 +1059,13 @@ class LegacyMUC(
1046
1059
  item["conference"]["autojoin"] = auto_join
1047
1060
 
1048
1061
  item["conference"]["nick"] = self.user_nick
1062
+
1063
+ if pin is not None:
1064
+ item["conference"]["extensions"]["pinned"] = pin
1065
+
1066
+ if notify is not None:
1067
+ item["conference"]["extensions"]["notify"].configure(notify)
1068
+
1049
1069
  iq = Iq(stype="set", sfrom=self.user_jid, sto=self.user_jid)
1050
1070
  iq["pubsub"]["publish"]["node"] = self.xmpp["xep_0402"].stanza.NS
1051
1071
  iq["pubsub"]["publish"].append(item)
slidge/main.py CHANGED
@@ -10,7 +10,7 @@ Use the long version of the CLI arg without the double dash prefix inside this
10
10
  INI file, eg ``debug=true``.
11
11
 
12
12
  An example configuration file is available at
13
- https://git.sr.ht/~nicoco/slidge/tree/master/item/dev/confs/slidge-example.ini
13
+ https://codeberg.org/slidge/slidge/src/branch/main/dev/confs/slidge-example.ini
14
14
  """
15
15
 
16
16
  import asyncio
@@ -5,25 +5,30 @@
5
5
 
6
6
  import slixmpp.plugins
7
7
  from slixmpp import Iq, Message
8
- from slixmpp.exceptions import XMPPError
8
+ from slixmpp.exceptions import _DEFAULT_ERROR_TYPES, XMPPError
9
+ from slixmpp.plugins.xep_0004.stanza.field import FormField
9
10
  from slixmpp.plugins.xep_0050 import XEP_0050, Command
10
11
  from slixmpp.plugins.xep_0231 import XEP_0231
12
+ from slixmpp.plugins.xep_0425.stanza import Moderate
13
+ from slixmpp.plugins.xep_0469.stanza import NS as PINNED_NS
14
+ from slixmpp.plugins.xep_0469.stanza import Pinned
11
15
  from slixmpp.xmlstream import StanzaBase
12
16
 
13
- from . import ( # xep_0356,
17
+ from . import (
14
18
  link_preview,
15
19
  xep_0077,
16
20
  xep_0100,
17
21
  xep_0153,
18
- xep_0264,
19
22
  xep_0292,
20
- xep_0313,
21
- xep_0317,
22
23
  xep_0356_old,
23
- xep_0424,
24
- xep_0490,
24
+ xep_0492,
25
25
  )
26
26
 
27
+ # TODO: remove this when the fix is included in slixmpp
28
+ Moderate.interfaces.add("id")
29
+
30
+ _DEFAULT_ERROR_TYPES["policy-violation"] = "modify" # type:ignore
31
+
27
32
 
28
33
  async def _handle_bob_iq(self, iq: Iq):
29
34
  cid = iq["bob"]["cid"]
@@ -50,6 +55,17 @@ async def _handle_bob_iq(self, iq: Iq):
50
55
  iq.send()
51
56
 
52
57
 
58
+ def set_pinned(self, val: bool):
59
+ extensions = self.parent()
60
+ if val:
61
+ extensions.enable("pinned")
62
+ else:
63
+ extensions._del_sub(f"{{{PINNED_NS}}}pinned")
64
+
65
+
66
+ Pinned.set_pinned = set_pinned
67
+
68
+
53
69
  XEP_0231._handle_bob_iq = _handle_bob_iq
54
70
 
55
71
 
@@ -83,16 +99,56 @@ def reply(self, body=None, clear=True):
83
99
  return new_message
84
100
 
85
101
 
102
+ Message.reply = reply # type: ignore
103
+
104
+
105
+ FormField.set_value_base = FormField.set_value # type:ignore
106
+
107
+
108
+ def set_value(self: FormField, value):
109
+ if not self._type:
110
+ if isinstance(value, bool):
111
+ self._type = "boolean"
112
+ elif isinstance(value, str):
113
+ self._type = "text-single"
114
+ elif isinstance(value, (list, tuple)):
115
+ self._type = "text-multi"
116
+
117
+ FormField.set_value_base(self, value)
118
+
119
+
120
+ def get_value(self, convert=True, convert_list=False):
121
+ valsXML = self.xml.findall("{%s}value" % self.namespace)
122
+ if len(valsXML) == 0:
123
+ return None
124
+ elif self._type == "boolean":
125
+ if convert:
126
+ return valsXML[0].text in self.true_values
127
+ return valsXML[0].text
128
+ elif self._type in self.multi_value_types or len(valsXML) > 1:
129
+ values = []
130
+ for valXML in valsXML:
131
+ if valXML.text is None:
132
+ valXML.text = ""
133
+ values.append(valXML.text)
134
+ if self._type == "text-multi" and convert_list:
135
+ values = "\n".join(values)
136
+ return values
137
+ else:
138
+ if valsXML[0].text is None:
139
+ return ""
140
+ return valsXML[0].text
141
+
142
+
143
+ FormField.set_value = set_value # type:ignore
144
+ FormField.get_value = get_value # type:ignore
145
+
146
+
86
147
  slixmpp.plugins.PLUGINS.extend(
87
148
  [
88
149
  "link_preview",
89
- "xep_0264",
90
150
  "xep_0292_provider",
91
- "xep_0317",
92
151
  "xep_0356_old",
93
- "xep_0490",
152
+ "xep_0492",
94
153
  ]
95
154
  )
96
-
97
-
98
- Message.reply = reply # type: ignore
@@ -4,7 +4,6 @@
4
4
  # See the file LICENSE for copying permission.
5
5
  from slixmpp.plugins.base import register_plugin
6
6
 
7
- from .stanza import VCardTempUpdate
8
7
  from .vcard_avatar import XEP_0153
9
8
 
10
9
  register_plugin(XEP_0153)
@@ -1,17 +1,8 @@
1
- # Slixmpp: The Slick XMPP Library
2
- # Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
3
- # This file is part of Slixmpp.
4
- # See the file LICENSE for copying permission.
5
- import logging
6
-
7
1
  from slixmpp.plugins.base import BasePlugin
2
+ from slixmpp.plugins.xep_0153 import VCardTempUpdate, stanza
8
3
  from slixmpp.stanza import Presence
9
4
  from slixmpp.xmlstream import register_stanza_plugin
10
5
 
11
- from . import VCardTempUpdate, stanza
12
-
13
- log = logging.getLogger(__name__)
14
-
15
6
 
16
7
  class XEP_0153(BasePlugin):
17
8
  name = "xep_0153"
@@ -0,0 +1,8 @@
1
+ from slixmpp.plugins.base import register_plugin
2
+
3
+ from . import stanza
4
+ from .notify import XEP_0492
5
+
6
+ register_plugin(XEP_0492)
7
+
8
+ __all__ = ["stanza", "XEP_0492"]
@@ -0,0 +1,16 @@
1
+ from slixmpp.plugins import BasePlugin
2
+ from . import stanza
3
+
4
+
5
+ class XEP_0492(BasePlugin):
6
+ """
7
+ XEP-0492: Chat notification settings
8
+ """
9
+
10
+ name = "xep_0492"
11
+ description = "XEP-0492: Chat notification settings"
12
+ dependencies = {"xep_0402"}
13
+ stanza = stanza
14
+
15
+ def plugin_init(self):
16
+ stanza.register_plugin()
@@ -0,0 +1,102 @@
1
+ from typing import Literal, Optional, cast
2
+
3
+ from slixmpp import register_stanza_plugin
4
+ from slixmpp.plugins.xep_0402.stanza import Extensions
5
+ from slixmpp.xmlstream import ElementBase
6
+
7
+ from ...util.types import ClientType
8
+
9
+ NS = "urn:xmpp:notification-settings:0"
10
+
11
+ WhenLiteral = Literal["never", "always", "on-mention"]
12
+
13
+
14
+ class Notify(ElementBase):
15
+ """
16
+ Chat notification settings element
17
+
18
+
19
+ To enable it on a Conference element, use configure() like this:
20
+
21
+ .. code-block::python
22
+
23
+ # C being a Conference element
24
+ C['extensions']["notify"].configure("always", client_type="pc")
25
+
26
+ Which will add the <notify> element to the <extensions> element.
27
+ """
28
+
29
+ namespace = NS
30
+ name = "notify"
31
+ plugin_attrib = "notify"
32
+ interfaces = {"notify"}
33
+
34
+ def configure(self, when: WhenLiteral, client_type: Optional[ClientType] = None) -> None:
35
+ """
36
+ Configure the chat notification settings for this bookmark.
37
+
38
+ This method ensures that there are no conflicting settings, e.g.,
39
+ both a <never /> and a <always /> element.
40
+ """
41
+ cls = _CLASS_MAP[when]
42
+ element = cls()
43
+ if client_type is not None:
44
+ element["client-type"] = client_type
45
+
46
+ match = client_type if client_type is not None else ""
47
+ for child in self:
48
+ if isinstance(child, _Base) and child["client-type"] == match:
49
+ self.xml.remove(child.xml)
50
+
51
+ self.append(element)
52
+
53
+ def get_config(
54
+ self, client_type: Optional[ClientType] = None
55
+ ) -> Optional[WhenLiteral]:
56
+ """
57
+ Get the chat notification settings for this bookmark.
58
+
59
+ :param client_type: Optionally, get the notification for a specific client type.
60
+ If unset, returns the global notification setting.
61
+
62
+ :return: The chat notification setting as a string, or None if unset.
63
+ """
64
+ match = client_type if client_type is not None else ""
65
+ for child in self:
66
+ if isinstance(child, _Base) and child["client-type"] == match:
67
+ return cast(WhenLiteral, child.name)
68
+ return None
69
+
70
+
71
+ class _Base(ElementBase):
72
+ namespace = NS
73
+ interfaces = {"client-type"}
74
+
75
+
76
+ class Never(_Base):
77
+ name = "never"
78
+
79
+
80
+ class Always(_Base):
81
+ name = "always"
82
+
83
+
84
+ class OnMention(_Base):
85
+ name = "on-mention"
86
+
87
+
88
+ class Advanced(ElementBase):
89
+ namespace = NS
90
+ name = plugin_attrib = "advanced"
91
+
92
+
93
+ _CLASS_MAP = {
94
+ "never": Never,
95
+ "always": Always,
96
+ "on-mention": OnMention,
97
+ }
98
+
99
+
100
+ def register_plugin():
101
+ register_stanza_plugin(Extensions, Notify)
102
+ register_stanza_plugin(Notify, Advanced)
slidge/util/test.py CHANGED
@@ -5,7 +5,14 @@ from pathlib import Path
5
5
  from typing import Optional, Union
6
6
  from xml.dom.minidom import parseString
7
7
 
8
- import xmldiff.main
8
+ try:
9
+ import xmldiff.main
10
+
11
+ XML_DIFF_PRESENT = True
12
+ except ImportError:
13
+ xmldiff = None
14
+ XML_DIFF_PRESENT = False
15
+
9
16
  from slixmpp import (
10
17
  JID,
11
18
  ElementBase,
@@ -157,7 +164,7 @@ class SlixTestPlus(SlixTest):
157
164
  result = self.compare(xml, stanza.xml, stanza2.xml)
158
165
  stanza_class.namespace = old_ns
159
166
 
160
- if not result:
167
+ if XML_DIFF_PRESENT and not result:
161
168
  debug += str(
162
169
  xmldiff.main.diff_texts(tostring(xml), tostring(stanza.xml))
163
170
  )