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

Sign up to get free protection for your applications and to get access to all the features.
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
  )