XEP-0482: add initial support
This commit is contained in:
parent
3de8ee97b5
commit
a30f76892b
8
doap.xml
8
doap.xml
@ -941,6 +941,14 @@
|
||||
<xmpp:since>1.8.6</xmpp:since>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0482.html"/>
|
||||
<xmpp:status>complete</xmpp:status>
|
||||
<xmpp:version>0.1.0</xmpp:version>
|
||||
<xmpp:since>1.8.7</xmpp:since>
|
||||
</xmpp:SupportedXep>
|
||||
</implements>
|
||||
<implements>
|
||||
<xmpp:SupportedXep>
|
||||
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0490.html"/>
|
||||
|
@ -122,6 +122,7 @@ PLUGINS = [
|
||||
'xep_0447', # Stateless file sharing
|
||||
'xep_0461', # Message Replies
|
||||
'xep_0469', # Bookmarks Pinning
|
||||
'xep_0482', # Call Invites
|
||||
'xep_0490', # Message Displayed Synchronization
|
||||
'xep_0492', # Chat Notification Settings
|
||||
# Meant to be imported by plugins
|
||||
|
11
slixmpp/plugins/xep_0482/__init__.py
Normal file
11
slixmpp/plugins/xep_0482/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
# Slixmpp: The Slick XMPP Library
|
||||
# Copyright (C) 2025 Mathieu Pasquet
|
||||
# This file is part of Slixmpp.
|
||||
# See the file LICENSE for copying permissio
|
||||
from slixmpp.plugins.base import register_plugin
|
||||
|
||||
from slixmpp.plugins.xep_0482 import stanza
|
||||
from slixmpp.plugins.xep_0482.call_invites import XEP_0482
|
||||
|
||||
|
||||
register_plugin(XEP_0482)
|
55
slixmpp/plugins/xep_0482/call_invites.py
Normal file
55
slixmpp/plugins/xep_0482/call_invites.py
Normal file
@ -0,0 +1,55 @@
|
||||
# Slixmpp: The Slick XMPP Library
|
||||
# Copyright (C) 2025 Mathieu Pasquet
|
||||
# This file is part of Slixmpp.
|
||||
# See the file LICENSE for copying permissio
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from slixmpp.stanza import Message
|
||||
from slixmpp.jid import JID
|
||||
from slixmpp.xmlstream.handler import Callback
|
||||
from slixmpp.xmlstream.matcher import StanzaPath
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
from slixmpp.plugins import BasePlugin
|
||||
from slixmpp.plugins.xep_0482 import stanza
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XEP_0482(BasePlugin):
|
||||
|
||||
"""
|
||||
XEP-0482: Call Invites
|
||||
|
||||
This plugin defines the stanza elements for Call Invites, as well as new
|
||||
events:
|
||||
|
||||
- `call-invite`
|
||||
- `call-reject`
|
||||
- `call-retract`
|
||||
- `call-leave`
|
||||
- `call-left`
|
||||
"""
|
||||
|
||||
name = 'xep_0482'
|
||||
description = 'XEP-0482: Call Invites'
|
||||
dependencies = set()
|
||||
stanza = stanza
|
||||
|
||||
def plugin_init(self):
|
||||
stanza.register_plugins()
|
||||
|
||||
for event in ('invite', 'reject', 'retract', 'leave', 'left'):
|
||||
self.xmpp.register_handler(
|
||||
Callback(f'Call {event}',
|
||||
StanzaPath(f'message/call-{event}'),
|
||||
self._handle_event))
|
||||
def _handle_event(self, message):
|
||||
for event in ('invite', 'reject', 'retract', 'leave', 'left'):
|
||||
if message.get_plugin(f'call-{event}', check=True):
|
||||
self.xmpp.event(f'call-{event}')
|
||||
|
||||
def plugin_end(self):
|
||||
for event in ('invite', 'reject', 'retract', 'leave', 'left'):
|
||||
self.xmpp.remove_handler(f'Call {event}')
|
102
slixmpp/plugins/xep_0482/stanza.py
Normal file
102
slixmpp/plugins/xep_0482/stanza.py
Normal file
@ -0,0 +1,102 @@
|
||||
# Slixmpp: The Slick XMPP Library
|
||||
# Copyright (C) 2025 Mathieu Pasquet
|
||||
# This file is part of Slixmpp.
|
||||
# See the file LICENSE for copying permission
|
||||
|
||||
from typing import Tuple, List, Optional
|
||||
from slixmpp import Message
|
||||
from slixmpp.jid import JID
|
||||
from slixmpp.xmlstream import ElementBase, register_stanza_plugin
|
||||
|
||||
NS = 'urn:xmpp:call-invites:0'
|
||||
|
||||
|
||||
class Jingle(ElementBase):
|
||||
name = 'jingle'
|
||||
namespace = NS
|
||||
plugin_attrib = 'jingle'
|
||||
plugin_multi_attrib = 'jingles'
|
||||
interfaces = {'sid', 'jid'}
|
||||
|
||||
def set_jid(self, value: JID) -> None:
|
||||
if not isinstance(value, JID):
|
||||
try:
|
||||
value = JID(value)
|
||||
except ValueError:
|
||||
raise ValueError(f'"jid" must be a valid JID object')
|
||||
self.xml.attrib['jid'] = value.full
|
||||
|
||||
def get_jid(self) -> Optional[JID]:
|
||||
try:
|
||||
return JID(self.xml.attrib.get('jid', ''))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
class External(ElementBase):
|
||||
name = 'external'
|
||||
namespace = NS
|
||||
plugin_attrib = 'external'
|
||||
plugin_multi_attrib = 'externals'
|
||||
interfaces = {'uri'}
|
||||
|
||||
|
||||
class Invite(ElementBase):
|
||||
name = 'invite'
|
||||
namespace = NS
|
||||
plugin_attrib = 'call-invite'
|
||||
interfaces = {'video'}
|
||||
|
||||
def get_methods(self) -> Tuple[List[Jingle], List[External]]:
|
||||
return (self['jingles'], self['externals'])
|
||||
|
||||
def set_video(self, value: bool) -> None:
|
||||
if not isinstance(value, bool):
|
||||
raise ValueError(f'Invalid value for the video attribute: {value}')
|
||||
self.xml.attrib['video'] = str(value).lower()
|
||||
|
||||
def get_video(self) -> bool:
|
||||
vid = self.xml.attrib.get('video', 'false').lower()
|
||||
return vid == 'true'
|
||||
|
||||
|
||||
class Retract(ElementBase):
|
||||
name = 'retract'
|
||||
namespace = NS
|
||||
plugin_attrib = 'call-retract'
|
||||
interfaces = {'id'}
|
||||
|
||||
|
||||
class Accept(ElementBase):
|
||||
name = 'accept'
|
||||
namespace = NS
|
||||
plugin_attrib = 'call-accept'
|
||||
interfaces = {'id'}
|
||||
|
||||
|
||||
class Reject(ElementBase):
|
||||
name = 'reject'
|
||||
namespace = NS
|
||||
plugin_attrib = 'call-reject'
|
||||
interfaces = {'id'}
|
||||
|
||||
|
||||
class Left(ElementBase):
|
||||
name = 'left'
|
||||
namespace = NS
|
||||
plugin_attrib = 'call-left'
|
||||
interfaces = {'id'}
|
||||
|
||||
|
||||
def register_plugins() -> None:
|
||||
register_stanza_plugin(Message, Invite)
|
||||
register_stanza_plugin(Message, Retract)
|
||||
register_stanza_plugin(Message, Accept)
|
||||
register_stanza_plugin(Message, Reject)
|
||||
register_stanza_plugin(Message, Left)
|
||||
|
||||
register_stanza_plugin(Invite, Jingle, iterable=True)
|
||||
register_stanza_plugin(Invite, External, iterable=True)
|
||||
|
||||
register_stanza_plugin(Accept, Jingle)
|
||||
register_stanza_plugin(Accept, External)
|
42
tests/test_stanza_xep_0482.py
Normal file
42
tests/test_stanza_xep_0482.py
Normal file
@ -0,0 +1,42 @@
|
||||
import unittest
|
||||
from slixmpp import Message
|
||||
from slixmpp.jid import JID
|
||||
from slixmpp.test import SlixTest
|
||||
from slixmpp.plugins.xep_0482 import stanza
|
||||
from slixmpp.plugins.xep_0482.stanza import External, Jingle
|
||||
from slixmpp.xmlstream import register_stanza_plugin
|
||||
|
||||
|
||||
class TestCallInviteStanza(SlixTest):
|
||||
|
||||
def setUp(self):
|
||||
stanza.register_plugins()
|
||||
|
||||
def test_invite(self):
|
||||
"""Test that the element is created correctly."""
|
||||
msg = Message()
|
||||
msg['call-invite']['video'] = True
|
||||
jingle = Jingle()
|
||||
jingle['sid'] = 'toto'
|
||||
jingle['jid'] = JID('toto@example.com/m')
|
||||
external = External()
|
||||
external['uri'] = "https://example.com/call"
|
||||
msg['call-invite'].append(jingle)
|
||||
msg['call-invite'].append(external)
|
||||
|
||||
self.check(msg, """
|
||||
<message>
|
||||
<invite xmlns="urn:xmpp:call-invites:0" video="true">
|
||||
<jingle sid="toto" jid="toto@example.com/m" />
|
||||
<external uri="https://example.com/call" />
|
||||
</invite>
|
||||
</message>
|
||||
""")
|
||||
|
||||
self.assertEqual(
|
||||
msg['call-invite'].get_methods(),
|
||||
([jingle], [external]),
|
||||
)
|
||||
|
||||
|
||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestCallInviteStanza)
|
Loading…
Reference in New Issue
Block a user