diff --git a/doap.xml b/doap.xml
index 027edf93..70b72373 100644
--- a/doap.xml
+++ b/doap.xml
@@ -512,6 +512,14 @@
unknown
+
+
+
+ complete
+ 0.3
+ NEXT
+
+
diff --git a/slixmpp/plugins/__init__.py b/slixmpp/plugins/__init__.py
index 91f062a3..4c3235f1 100644
--- a/slixmpp/plugins/__init__.py
+++ b/slixmpp/plugins/__init__.py
@@ -85,6 +85,7 @@ __all__ = [
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
'xep_0332', # HTTP Over XMPP Transport
+ 'xep_0353', # Jingle Message Initiation
'xep_0369', # MIX-CORE
'xep_0377', # Spam reporting
'xep_0403', # MIX-Presence
diff --git a/slixmpp/plugins/xep_0353/__init__.py b/slixmpp/plugins/xep_0353/__init__.py
new file mode 100644
index 00000000..89d86c51
--- /dev/null
+++ b/slixmpp/plugins/xep_0353/__init__.py
@@ -0,0 +1,14 @@
+"""
+ slixmpp: The Slick XMPP Library
+ Copyright (C) 2020 Emmanuel Gil Peyrot
+ This file is part of slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from slixmpp.plugins.base import register_plugin
+
+from slixmpp.plugins.xep_0353.stanza import Propose, Retract, Accept, Proceed, Reject
+from slixmpp.plugins.xep_0353.jingle_message import XEP_0353
+
+register_plugin(XEP_0353)
diff --git a/slixmpp/plugins/xep_0353/jingle_message.py b/slixmpp/plugins/xep_0353/jingle_message.py
new file mode 100644
index 00000000..07395241
--- /dev/null
+++ b/slixmpp/plugins/xep_0353/jingle_message.py
@@ -0,0 +1,101 @@
+"""
+ slixmpp: The Slick XMPP Library
+ Copyright (C) 2020 Emmanuel Gil Peyrot
+ This file is part of slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+import logging
+
+from typing import Iterable, Tuple, Optional
+
+from slixmpp import JID, Message
+from slixmpp.plugins import BasePlugin
+from slixmpp.xmlstream import register_stanza_plugin
+from slixmpp.xmlstream.handler import Callback
+from slixmpp.xmlstream.matcher import StanzaPath
+from slixmpp.plugins.xep_0353 import stanza, Propose, Retract, Accept, Proceed, Reject
+
+log = logging.getLogger(__name__)
+
+class XEP_0353(BasePlugin):
+
+ name = 'xep_0353'
+ description = 'XEP-0353: Jingle Message Initiation'
+ stanza = stanza
+
+ def plugin_init(self):
+ register_stanza_plugin(Message, Propose)
+ register_stanza_plugin(Message, Retract)
+ register_stanza_plugin(Message, Accept)
+ register_stanza_plugin(Message, Proceed)
+ register_stanza_plugin(Message, Reject)
+
+ self.xmpp.register_handler(
+ Callback('Indicating Intent to Start a Session',
+ StanzaPath('message/jingle_propose'),
+ self._handle_propose))
+ self.xmpp.register_handler(
+ Callback('Disavowing Intent to Start a Session',
+ StanzaPath('message/jingle_retract'),
+ self._handle_retract))
+ self.xmpp.register_handler(
+ Callback('Accepting Intent to Start a Session',
+ StanzaPath('message/jingle_accept'),
+ self._handle_accept))
+ self.xmpp.register_handler(
+ Callback('Proceed',
+ StanzaPath('message/jingle_proceed'),
+ self._handle_accept))
+ self.xmpp.register_handler(
+ Callback('Rejecting Intent to Start a Session',
+ StanzaPath('message/jingle_reject'),
+ self._handle_reject))
+
+ def session_bind(self, jid):
+ self.xmpp.plugin['xep_0030'].add_feature(stanza.JingleMessage.namespace)
+
+ def plugin_end(self):
+ self.xmpp.plugin['xep_0030'].del_feature(feature=stanza.JingleMessage.namespace)
+
+ def _handle_propose(self, message):
+ self.xmpp.event('jingle_message_propose', message)
+
+ def _handle_retract(self, message):
+ self.xmpp.event('jingle_message_retract', message)
+
+ def _handle_accept(self, message):
+ self.xmpp.event('jingle_message_accept', message)
+
+ def _handle_proceed(self, message):
+ self.xmpp.event('jingle_message_proceed', message)
+
+ def _handle_reject(self, message):
+ self.xmpp.event('jingle_message_reject', message)
+
+ def propose(self, mto: JID, sid: str, descriptions: Iterable[Tuple[str, str]], *, mfrom: Optional[JID] = None):
+ msg = self.xmpp.make_message(mto, mfrom=mfrom)
+ msg['jingle_propose']['id'] = sid
+ msg['jingle_propose']['descriptions'] = descriptions
+ msg.send()
+
+ def retract(self, mto: JID, sid: str, *, mfrom: Optional[JID] = None):
+ msg = self.xmpp.make_message(mto, mfrom=mfrom)
+ msg['jingle_retract']['id'] = sid
+ msg.send()
+
+ def accept(self, mto: JID, sid: str, *, mfrom: Optional[JID] = None):
+ msg = self.xmpp.make_message(mto, mfrom=mfrom)
+ msg['jingle_accept']['id'] = sid
+ msg.send()
+
+ def proceed(self, mto: JID, sid: str, *, mfrom: Optional[JID] = None):
+ msg = self.xmpp.make_message(mto, mfrom=mfrom)
+ msg['jingle_proceed']['id'] = sid
+ msg.send()
+
+ def reject(self, mto: JID, sid: str, *, mfrom: Optional[JID] = None):
+ msg = self.xmpp.make_message(mto, mfrom=mfrom)
+ msg['jingle_reject']['id'] = sid
+ msg.send()
diff --git a/slixmpp/plugins/xep_0353/stanza.py b/slixmpp/plugins/xep_0353/stanza.py
new file mode 100644
index 00000000..48046274
--- /dev/null
+++ b/slixmpp/plugins/xep_0353/stanza.py
@@ -0,0 +1,55 @@
+"""
+ slixmpp: The Slick XMPP Library
+ Copyright (C) 2020 Emmanuel Gil Peyrot
+ This file is part of slixmpp.
+
+ See the file LICENSE for copying permission.
+"""
+
+from typing import Iterable, List, Tuple
+
+from slixmpp.xmlstream import ElementBase, ET
+
+class JingleMessage(ElementBase):
+ namespace = 'urn:xmpp:jingle-message:0'
+ interfaces = {'id'}
+
+class Propose(JingleMessage):
+ name = 'propose'
+ plugin_attrib = 'jingle_propose'
+ interfaces = {'id', 'descriptions'}
+
+ def get_descriptions(self) -> List[Tuple[str, str]]:
+ result = []
+ for desc in self.xml:
+ namespace = desc.tag.split('}')[0][1:]
+ media = desc.attrib['media']
+ result.append((namespace, media))
+ return result
+
+ def set_descriptions(self, descriptions: Iterable[Tuple[str, str]]):
+ self.del_descriptions()
+ for namespace, media in descriptions:
+ desc = ET.Element('{%s}description' % namespace)
+ desc.attrib['media'] = media
+ self.xml.append(desc)
+
+ def del_descriptions(self):
+ for desc in self.xml.findall('{*}description'):
+ self.xml.remove(desc)
+
+class Retract(JingleMessage):
+ name = 'retract'
+ plugin_attrib = 'jingle_retract'
+
+class Accept(JingleMessage):
+ name = 'accept'
+ plugin_attrib = 'jingle_accept'
+
+class Proceed(JingleMessage):
+ name = 'proceed'
+ plugin_attrib = 'jingle_proceed'
+
+class Reject(JingleMessage):
+ name = 'reject'
+ plugin_attrib = 'jingle_reject'