feat: support XEP-0492 (Chat Notification Settings)

This commit is contained in:
nicoco 2025-01-23 13:15:18 +01:00 committed by mathieui
parent 8d984cd8a1
commit 2e736bc715
9 changed files with 360 additions and 1 deletions

View File

@ -917,6 +917,14 @@
<xmpp:since>1.8.6</xmpp:since>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0492.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.0</xmpp:version>
<xmpp:since>1.8.7</xmpp:since>
</xmpp:SupportedXep>
</implements>
<release>
<Version>

View File

@ -94,3 +94,4 @@ Plugin index
xep_0439
xep_0441
xep_0444
xep_0492

View File

@ -0,0 +1,18 @@
XEP-0492: Chat Notification Settings
===========================
.. module:: slixmpp.plugins.xep_0492
.. autoclass:: XEP_0492
:members:
:exclude-members: session_bind, plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0492.stanza
:members:
:undoc-members:

View File

@ -122,6 +122,7 @@ PLUGINS = [
'xep_0461', # Message Replies
'xep_0469', # Bookmarks Pinning
'xep_0490', # Message Displayed Synchronization
'xep_0492', # Chat Notification Settings
# Meant to be imported by plugins
]

View File

@ -0,0 +1,13 @@
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2025 nicoco
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
from slixmpp.plugins.base import register_plugin
from . import stanza
from .notify import XEP_0492
register_plugin(XEP_0492)
__all__ = ["stanza", "XEP_0492"]

View File

@ -0,0 +1,21 @@
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2025 nicoco
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
from slixmpp.plugins import BasePlugin
from . import stanza
class XEP_0492(BasePlugin):
"""
XEP-0492: Chat notification settings
"""
name = "xep_0492"
description = "XEP-0492: Chat notification settings"
dependencies = {"xep_0402"}
stanza = stanza
def plugin_init(self):
stanza.register_plugin()

View File

@ -0,0 +1,106 @@
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2025 nicoco
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
from typing import Literal, Optional, cast
from slixmpp import register_stanza_plugin
from slixmpp.plugins.xep_0402.stanza import Extensions
from slixmpp.types import ClientTypes
from slixmpp.xmlstream import ElementBase
NS = "urn:xmpp:notification-settings:0"
WhenLiteral = Literal["never", "always", "on-mention"]
class Notify(ElementBase):
"""
Chat notification settings element
To enable it on a Conference element, use configure() like this:
.. code-block::python
# C being a Conference element
C['extensions']["notify"].configure("always", client_type="pc")
Which will add the <notify> element to the <extensions> element.
"""
namespace = NS
name = "notify"
plugin_attrib = "notify"
interfaces = {"notify"}
def configure(self, when: WhenLiteral, client_type: Optional[ClientTypes] = None) -> None:
"""
Configure the chat notification settings for this bookmark.
This method ensures that there are no conflicting settings, e.g.,
both a <never /> and a <always /> element.
"""
cls = _CLASS_MAP[when]
element = cls()
if client_type is not None:
element["client-type"] = client_type
match = client_type if client_type is not None else ""
for child in self:
if isinstance(child, _Base) and child["client-type"] == match:
self.xml.remove(child.xml)
self.append(element)
def get_config(
self, client_type: Optional[ClientTypes] = None
) -> Optional[WhenLiteral]:
"""
Get the chat notification settings for this bookmark.
:param client_type: Optionally, get the notification for a specific client type.
If unset, returns the global notification setting.
:return: The chat notification setting as a string, or None if unset.
"""
match = client_type if client_type is not None else ""
for child in self:
if isinstance(child, _Base) and child["client-type"] == match:
return cast(WhenLiteral, child.name)
return None
class _Base(ElementBase):
namespace = NS
interfaces = {"client-type"}
class Never(_Base):
name = "never"
class Always(_Base):
name = "always"
class OnMention(_Base):
name = "on-mention"
class Advanced(ElementBase):
namespace = NS
name = plugin_attrib = "advanced"
_CLASS_MAP = {
"never": Never,
"always": Always,
"on-mention": OnMention,
}
def register_plugin():
register_stanza_plugin(Extensions, Notify)
register_stanza_plugin(Notify, Advanced)

View File

@ -109,8 +109,21 @@ ErrorConditions = Literal[
"unexpected-request",
]
# https://xmpp.org/registrar/disco-categories.html#client
ClientTypes = Literal[
"bot",
"console",
"game",
"handheld",
"pc",
"phone",
"sms",
"tablet",
"web",
]
__all__ = [
'Protocol', 'TypedDict', 'Literal', 'OptJid', 'OptJidStr', 'JidStr', 'MAMDefault',
'PresenceTypes', 'PresenceShows', 'MessageTypes', 'IqTypes', 'MucRole',
'MucAffiliation', 'FilterString', 'ErrorConditions', 'ErrorTypes'
'MucAffiliation', 'FilterString', 'ErrorConditions', 'ErrorTypes', 'ClientTypes'
]

View File

@ -0,0 +1,178 @@
# Slixmpp: The Slick XMPP Library
# Copyright (C) 2025 nicoco
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
import unittest
from slixmpp import register_stanza_plugin, ElementBase
from slixmpp.test import SlixTest
from slixmpp.plugins.xep_0492 import stanza
from slixmpp.plugins.xep_0402 import stanza as b_stanza
class TestNotificationSetting(SlixTest):
def setUp(self):
b_stanza.register_plugin()
stanza.register_plugin()
def test_never(self):
bookmark = b_stanza.Conference()
bookmark["extensions"]["notify"].configure("never")
self.check(
bookmark,
"""
<conference xmlns='urn:xmpp:bookmarks:1'>
<extensions>
<notify xmlns='urn:xmpp:notification-settings:0'>
<never />
</notify>
</extensions>
</conference>
""",
use_values=False,
)
def test_always(self):
bookmark = b_stanza.Conference()
bookmark["extensions"]["notify"].configure("always")
self.check(
bookmark,
"""
<conference xmlns='urn:xmpp:bookmarks:1'>
<extensions>
<notify xmlns='urn:xmpp:notification-settings:0'>
<always />
</notify>
</extensions>
</conference>
""",
use_values=False,
)
def test_on_mention(self):
bookmark = b_stanza.Conference()
bookmark["extensions"]["notify"].configure("on-mention")
self.check(
bookmark,
"""
<conference xmlns='urn:xmpp:bookmarks:1'>
<extensions>
<notify xmlns='urn:xmpp:notification-settings:0'>
<on-mention />
</notify>
</extensions>
</conference>
""",
use_values=False,
)
def test_advanced(self):
bookmark = b_stanza.Conference()
bookmark["extensions"]["notify"].configure("never", client_type="pc")
bookmark["extensions"]["notify"].configure("on-mention", client_type="mobile")
register_stanza_plugin(stanza.Advanced, AdvancedExtension)
bookmark["extensions"]["notify"]["advanced"].enable("cool")
bookmark["extensions"]["notify"]["advanced"]["cool"]["attrib"] = "cool-attrib"
bookmark["extensions"]["notify"]["advanced"]["cool"]["content"] = "cool-content"
self.check(
bookmark,
"""
<conference xmlns='urn:xmpp:bookmarks:1'>
<extensions>
<notify xmlns='urn:xmpp:notification-settings:0'>
<never client-type="pc" />
<on-mention client-type="mobile" />
<advanced>
<cool xmlns="cool-ns" attrib="cool-attrib">cool-content</cool>
</advanced>
</notify>
</extensions>
</conference>
""",
use_values=False,
)
def test_change_config(self):
bookmark = b_stanza.Conference()
bookmark["extensions"]["notify"].configure("never")
bookmark["extensions"]["notify"].configure("never", client_type="pc")
bookmark["extensions"]["notify"].configure("on-mention", client_type="mobile")
self.check(
bookmark,
"""
<conference xmlns='urn:xmpp:bookmarks:1'>
<extensions>
<notify xmlns='urn:xmpp:notification-settings:0'>
<never />
<never client-type="pc" />
<on-mention client-type="mobile" />
</notify>
</extensions>
</conference>
""",
use_values=False,
)
bookmark["extensions"]["notify"].configure("always")
self.check(
bookmark,
"""
<conference xmlns='urn:xmpp:bookmarks:1'>
<extensions>
<notify xmlns='urn:xmpp:notification-settings:0'>
<always />
<never client-type="pc" />
<on-mention client-type="mobile" />
</notify>
</extensions>
</conference>
""",
use_values=False,
)
bookmark["extensions"]["notify"].configure("always", "mobile")
self.check(
bookmark,
"""
<conference xmlns='urn:xmpp:bookmarks:1'>
<extensions>
<notify xmlns='urn:xmpp:notification-settings:0'>
<always />
<never client-type="pc" />
<always client-type="mobile" />
</notify>
</extensions>
</conference>
""",
use_values=False,
)
def test_get_config(self):
bookmark = b_stanza.Conference()
bookmark["extensions"]["notify"].configure("never")
bookmark["extensions"]["notify"].configure("never", client_type="pc")
bookmark["extensions"]["notify"].configure("on-mention", client_type="mobile")
self.assertEqual(bookmark["extensions"]["notify"].get_config(), "never")
self.assertEqual(bookmark["extensions"]["notify"].get_config("pc"), "never")
self.assertEqual(
bookmark["extensions"]["notify"].get_config("mobile"), "on-mention"
)
class AdvancedExtension(ElementBase):
namespace = "cool-ns"
name = "cool"
plugin_attrib = name
interfaces = {"attrib", "content"}
def set_content(self, content: str):
self.xml.text = content
suite = unittest.TestLoader().loadTestsFromTestCase(TestNotificationSetting)