Merge branch 'mix-implementation' into 'master'

First try at a MIX implementation

See merge request poezio/slixmpp!63
This commit is contained in:
mathieui 2020-12-02 19:19:14 +01:00
commit 4d5586f4a1
16 changed files with 1000 additions and 0 deletions

View file

@ -85,7 +85,11 @@ __all__ = [
'xep_0323', # IoT Systems Sensor Data
'xep_0325', # IoT Systems Control
'xep_0332', # HTTP Over XMPP Transport
'xep_0369', # MIX-CORE
'xep_0377', # Spam reporting
'xep_0403', # MIX-Presence
'xep_0404', # MIX-Anon
'xep_0405', # MIX-PAM
'xep_0421', # Anonymous unique occupant identifiers for MUCs
'xep_0444', # Message Reactions
]

View file

@ -0,0 +1,13 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0369.stanza import *
from slixmpp.plugins.xep_0369.mix_core import XEP_0369
register_plugin(XEP_0369)

View file

@ -0,0 +1,288 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from typing import (
Any,
Dict,
List,
Optional,
Set,
Tuple,
)
from datetime import datetime
from slixmpp import JID, Iq
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.plugins import BasePlugin
from slixmpp.plugins.xep_0369 import stanza
from slixmpp.xmlstream.handler import Callback
from slixmpp.xmlstream.matcher import MatchXPath
try:
from typing import TypedDict
InfoType = TypedDict(
'InfoType',
{
'Name': str,
'Description': str,
'Contact': Optional[List[JID]],
'modified': datetime
},
total=False,
)
except ImportError:
# Placeholder until we drop python < 3.8
InfoType = Dict[str, Any]
BASE_NODES = [
'urn:xmpp:mix:nodes:messages',
'urn:xmpp:mix:nodes:participants',
'urn:xmpp:mix:nodes:info',
]
class XEP_0369(BasePlugin):
'''XEP-0369: MIX-CORE'''
name = 'xep_0369'
description = 'MIX-CORE'
dependencies = {'xep_0030', 'xep_0060', 'xep_0082', 'xep_0004'}
stanza = stanza
namespace = stanza.NS
def plugin_init(self) -> None:
stanza.register_plugins()
self.xmpp.register_handler(
Callback(
"MIX message received",
MatchXPath('{%s}message[@type="groupchat"]/{%s}mix' % (
self.xmpp.default_ns, self.namespace
)),
self._handle_mix_message,
)
)
def _handle_mix_message(self, message):
self.xmpp.event('mix_message', message)
def session_bind(self, jid):
self.xmpp.plugin['xep_0030'].add_feature(stanza.NS)
def plugin_end(self):
self.xmpp.plugin['xep_0030'].del_feature(feature=stanza.NS)
async def get_channel_info(self, channel: JID) -> InfoType:
""""
Get the contents of the channel info node.
:param JID channel: The MIX channel
:returns: a dict containing the last modified time and form contents
(Name, Description, Contact per the spec, YMMV)
"""
info = await self.xmpp['xep_0060'].get_items(channel, 'urn:xmpp:mix:nodes:info')
for item in info['pubsub']['items']:
time = item['id']
fields = item['form'].get_values()
del fields['FORM_TYPE']
fields['modified'] = self.xmpp['xep_0082'].parse(time)
contact = fields.get('Contact')
if contact:
if isinstance(contact, str):
contact = [contact]
elif isinstance(contact, list):
contact = [JID(cont) for cont in contact]
fields['Contact'] = contact
return fields
async def join_channel(self, channel: JID, nick: str, subscribe: Optional[Set[str]] = None, *,
ifrom: Optional[JID] = None, **iqkwargs) -> Set[str]:
"""
Join a MIX channel.
:param JID channel: JID of the MIX channel
:param str nick: Desired nickname on that channel
:param Set[str] subscribe: Set of notes to subscribe to when joining.
If empty, all nodes will be subscribed by default.
:rtype: Set[str]
:return: The nodes that failed to subscribe, if any
"""
if not subscribe:
subscribe = set(BASE_NODES)
iq = self.xmpp.make_iq_set(ito=channel, ifrom=ifrom)
iq['mix_join']['nick'] = nick
for node in subscribe:
sub = stanza.Subscribe()
sub['node'] = node
iq['mix_join']['subscribe'].append(sub)
result = await iq.send(**iqkwargs)
result_nodes = {sub['node'] for sub in result['mix_join']}
return result_nodes.difference(subscribe)
async def update_subscription(self, channel: JID,
subscribe: Optional[Set[str]] = None,
unsubscribe: Optional[Set[str]] = None, *,
ifrom: Optional[JID] = None, **iqkwargs) -> Tuple[Set[str], Set[str]]:
"""
Update a MIX channel subscription.
:param JID channel: JID of the MIX channel
:param Set[str] subscribe: Set of notes to subscribe to additionally.
:param Set[str] unsubscribe: Set of notes to unsubscribe from.
:rtype: Tuple[Set[str], Set[str]]
:return: A tuple containing the set of nodes that failed to subscribe
and the set of nodes that failed to unsubscribe.
"""
if not subscribe and not unsubscribe:
raise ValueError("No nodes were provided.")
unsubscribe = unsubscribe or set()
subscribe = subscribe or set()
iq = self.xmpp.make_iq_set(ito=channel, ifrom=ifrom)
iq.enable('mix_updatesub')
for node in subscribe:
sub = stanza.Subscribe()
sub['node'] = node
iq['mix_updatesub'].append(sub)
for node in unsubscribe:
unsub = stanza.Unsubscribe()
unsub['node'] = node
iq['mix_updatesub'].append(unsub)
result = await iq.send(**iqkwargs)
for item in result['mix_updatesub']:
if isinstance(item, stanza.Subscribe):
subscribe.discard(item['node'])
elif isinstance(item, stanza.Unsubscribe):
unsubscribe.discard(item['node'])
return (subscribe, unsubscribe)
async def leave_channel(self, channel: JID, *,
ifrom: Optional[JID] = None, **iqkwargs) -> None:
""""
Leave a MIX channel
:param JID channel: JID of the channel to leave
"""
iq = self.xmpp.make_iq_set(ito=channel, ifrom=ifrom)
iq.enable('mix_leave')
await iq.send(**iqkwargs)
async def set_nick(self, channel: JID, nick: str, *,
ifrom: Optional[JID] = None, **iqkwargs) -> str:
"""
Set your nick on a channel. The returned nick MAY be different
from the one provided, depending on service configuration.
:param JID channel: MIX channel JID
:param str nick: desired nick
:rtype: str
:return: The nick saved on the MIX channel
"""
iq = self.xmpp.make_iq_set(ito=channel, ifrom=ifrom)
iq['mix_setnick']['nick'] = nick
result = await iq.send(**iqkwargs)
result_nick = result['mix_setnick']['nick']
return result_nick
async def can_create_channel(self, service: JID) -> bool:
"""
Check if the current user can create a channel on the MIX service
:param JID service: MIX service jid
:rtype: bool
"""
results_stanza = await self.xmpp['xep_0030'].get_info(service.server)
features = results_stanza['disco_info']['features']
return 'urn:xmpp:mix:core:1#create-channel' in features
async def create_channel(self, service: JID, channel: Optional[str] = None, *,
ifrom: Optional[JID] = None, **iqkwargs) -> str:
"""
Create a MIX channel.
:param JID service: MIX service JID
:param Optional[str] channel: Channel name (or leave empty to let
the service generate it)
:returns: The channel name, as created by the service
"""
if '#' in channel:
raise ValueError("A channel name cannot contain hashes")
iq = self.xmpp.make_iq_set(ito=service.server, ifrom=ifrom)
iq.enable('mix_create')
if channel is not None:
iq['mix_create']['channel'] = channel
result = await iq.send(**iqkwargs)
return result['mix_create']['channel']
async def destroy_channel(self, channel: JID, *,
ifrom: Optional[JID] = None, **iqkwargs):
"""
Destroy a MIX channel.
:param JID channel: MIX channelJID
"""
iq = self.xmpp.make_iq_set(ito=channel.server, ifrom=ifrom)
iq['mix_destroy'] = channel.user
await iq.send(**iqkwargs)
async def list_mix_nodes(self, channel: JID,
ifrom: Optional[JID] = None, **discokwargs) -> Set[str]:
"""
List mix nodes for a channel.
:param JID channel: The MIX channel
:returns: List of nodes available
"""
result = await self.xmpp['xep_0030'].get_items(
channel,
node='mix',
ifrom=ifrom,
**discokwargs,
)
nodes = set()
for item in result['disco_items']:
nodes.add(item['node'])
return nodes
async def list_participants(self, channel: JID, *,
ifrom: Optional[JID] = None, **pubsubkwargs) -> List[Tuple[str, str, Optional[JID]]]:
"""
List the participants of a MIX channel
:param JID channel: The MIX channel
:returns: A list of tuples containing the participant id, nick, and jid (if available)
"""
info = await self.xmpp['xep_0060'].get_items(
channel,
'urn:xmpp:mix:nodes:participants',
ifrom=ifrom,
**pubsubkwargs
)
participants = list()
for item in info['pubsub']['items']:
identifier = item['id']
nick = item['mix_participant']['nick']
jid = item['mix_participant']['jid'] or None
participants.append(
(identifier, nick, jid),
)
return participants
async def list_channels(self, service: JID, *,
ifrom: Optional[JID] =None, **discokwargs) -> List[Tuple[JID, str]]:
"""
List the channels on a MIX service
:param JID service: MIX service JID
:returns: A list of channels with their JID and name
"""
results_stanza = await self.xmpp['xep_0030'].get_items(
service.server,
ifrom=ifrom,
**discokwargs,
)
results = []
for result in results_stanza['disco_items']:
results.append((result['jid'], result['name']))
return results

View file

@ -0,0 +1,121 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permissio
"""
import xml.etree.ElementTree as ET
from slixmpp import JID
from slixmpp.stanza import (
Iq,
Message,
)
from slixmpp.xmlstream import (
ElementBase,
register_stanza_plugin,
)
from slixmpp.plugins.xep_0004.stanza import (
Form,
)
from slixmpp.plugins.xep_0060.stanza import (
EventItem,
Item,
)
NS = 'urn:xmpp:mix:core:1'
class MIX(ElementBase):
name = 'mix'
namespace = NS
plugin_attrib = 'mix'
interfaces = {'nick', 'jid'}
sub_interfaces = {'nick', 'jid'}
class Setnick(ElementBase):
name = 'setnick'
namespace = NS
plugin_attrib = 'mix_setnick'
interfaces = {'nick'}
sub_interfaces = {'nick'}
class Join(ElementBase):
namespace = NS
name = 'join'
plugin_attrib = 'mix_join'
interfaces = {'nick', 'id'}
sub_interfaces = {'nick'}
class Leave(ElementBase):
namespace = NS
name = 'leave'
plugin_attrib = 'mix_leave'
class Subscribe(ElementBase):
namespace = NS
name = 'subscribe'
plugin_attrib = 'subscribe'
interfaces = {'node'}
class Unsubscribe(ElementBase):
namespace = NS
name = 'unsubscribe'
plugin_attrib = 'unsubscribe'
interfaces = {'node'}
class UpdateSubscription(ElementBase):
namespace = NS
name = 'update-subscription'
plugin_attrib = 'mix_updatesub'
interfaces = {'jid'}
class Create(ElementBase):
name = 'create'
plugin_attrib = 'mix_create'
namespace = NS
interfaces = {'channel'}
class Participant(ElementBase):
namespace = NS
name = 'participant'
plugin_attrib = 'mix_participant'
interfaces = {'nick', 'jid'}
sub_interfaces = {'nick', 'jid'}
class Destroy(ElementBase):
name = 'destroy'
plugin_attrib = 'mix_destroy'
namespace = NS
interfaces = {'channel'}
def register_plugins():
register_stanza_plugin(Item, Form)
register_stanza_plugin(EventItem, Form)
register_stanza_plugin(EventItem, Participant)
register_stanza_plugin(Item, Participant)
register_stanza_plugin(Join, Subscribe, iterable=True)
register_stanza_plugin(Iq, Join)
register_stanza_plugin(UpdateSubscription, Subscribe, iterable=True)
register_stanza_plugin(UpdateSubscription, Unsubscribe, iterable=True)
register_stanza_plugin(Iq, UpdateSubscription)
register_stanza_plugin(Iq, Leave)
register_stanza_plugin(Iq, Create)
register_stanza_plugin(Iq, Setnick)
register_stanza_plugin(Message, MIX)

View file

@ -0,0 +1,13 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0403.stanza import *
from slixmpp.plugins.xep_0403.mix_presence import XEP_0403
register_plugin(XEP_0403)

View file

@ -0,0 +1,47 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from typing import (
Optional,
Set,
)
from slixmpp import JID, Iq
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.plugins import BasePlugin
from slixmpp.plugins.xep_0403 import stanza
from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream.handler import Callback
NODES = [
'urn:xmpp:mix:nodes:presence'
]
class XEP_0403(BasePlugin):
'''XEP-0403: MIX-Presence'''
name = 'xep_0403'
description = 'MIX-Presence'
dependencies = {'xep_0369'}
stanza = stanza
namespace = stanza.NS
def plugin_init(self) -> None:
stanza.register_plugins()
self.xmpp.register_handler(
Callback(
'MIX Presence received',
MatchXPath('{%s}presence/{%s}mix' % (self.xmpp.default_ns, stanza.NS)),
self._handle_mix_presence,
)
)
def _handle_mix_presence(self, presence):
self.xmpp.event('mix_presence', presence)

View file

@ -0,0 +1,37 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permissio
"""
from xml.etree import ElementTree as ET
from slixmpp import JID
from slixmpp.stanza import Presence
from slixmpp.xmlstream import (
register_stanza_plugin,
ElementBase,
)
from slixmpp.plugins.xep_0060.stanza import (
Item,
EventItem,
)
NS = 'urn:xmpp:mix:presence:0'
class MIXPresence(ElementBase):
namespace = NS
name = 'mix'
plugin_attrib = 'mix'
interfaces = {'jid', 'nick'}
sub_interfaces = {'jid', 'nick'}
def register_plugins():
register_stanza_plugin(Presence, MIXPresence)
register_stanza_plugin(Item, Presence)
register_stanza_plugin(EventItem, Presence)

View file

@ -0,0 +1,13 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0404.stanza import Participant
from slixmpp.plugins.xep_0404.mix_anon import XEP_0404
register_plugin(XEP_0404)

View file

@ -0,0 +1,101 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from typing import (
Dict,
Optional,
Set,
Tuple,
)
from slixmpp import JID, Message, Iq
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.plugins import BasePlugin
from slixmpp.xmlstream import register_stanza_plugin
from slixmpp.xmlstream.matcher import MatchXPath
from slixmpp.xmlstream.handler import Callback
from slixmpp.plugins.xep_0404 import stanza
from slixmpp.plugins.xep_0004.stanza import Form
NODES = [
'urn:xmpp:mix:nodes:jidmap',
]
class XEP_0404(BasePlugin):
'''XEP-0404: MIX JID Hidden Channels'''
name = 'xep_0404'
description = 'MIX-ANON'
dependencies = {'xep_0369'}
stanza = stanza
namespace = stanza.NS
def plugin_init(self) -> None:
stanza.register_plugins()
async def get_anon_raw(self, channel: JID, *,
ifrom: Optional[JID] = None, **pubsubkwargs) -> Iq:
"""
Get the jid-participant mapping result (raw).
:param JID channel: MIX channel JID
"""
return await self.xmpp['xep_0030'].get_items(
channel.bare,
ifrom=ifrom,
**pubsubkwargs
)
async def get_anon_by_jid(self, channel: JID, *,
ifrom: Optional[JID] = None, **pubsubkwargs) -> Dict[JID, str]:
"""
Get the jid-participant mapping, by JID
:param JID channel: MIX channel JID
"""
raw = await self.get_anon_raw(channel, ifrom=ifrom, **pubsubkwargs)
mapping = {}
for item in raw['pubsub']['items']:
mapping[item['anon_participant']['jid']] = item['id']
return mapping
async def get_anon_by_id(self, channel: JID, *,
ifrom: Optional[JID] = None, **pubsubkwargs) -> Dict[str, JID]:
"""
Get the jid-participant mapping, by participant id
:param JID channel: MIX channel JID
"""
raw = await self.get_anon_raw(channel, ifrom=ifrom, **pubsubkwargs)
mapping = {}
for item in raw['pubsub']['items']:
mapping[item['id']] = item['anon_participant']['jid']
return mapping
async def get_preferences(self, channel: JID, *,
ifrom: Optional[JID] = None, **iqkwargs) -> Form:
"""
Get channel preferences with default values.
:param JID channel: MIX channel JID
"""
iq = self.xmpp.make_iq_get(ito=channel.bare, ifrom=ifrom)
iq.enable('user_preference')
prefs_stanza = await iq.send(**iqkwargs)
return prefs_stanza['user_preference']['form']
async def set_preferences(self, channel: JID, form: Form, *,
ifrom: Optional[JID] = None, **iqkwargs) -> Form:
"""
Set channel preferences
:param JID channel: MIX channel JID
:param Form form: A 0004 form with updated preferences
"""
iq = self.xmpp.make_iq_set(ito=channel.bare, ifrom=ifrom)
iq['user_preference']['form'] = form
prefs_result = await iq.send(**iqkwargs)
return prefs_result['user_preference']['form']

View file

@ -0,0 +1,43 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permissio
"""
from slixmpp.xmlstream import (
ElementBase,
register_stanza_plugin,
)
from slixmpp import Iq
from slixmpp.plugins.xep_0004.stanza import Form
from slixmpp.plugins.xep_0060.stanza import (
EventItem,
Item,
)
NS = 'urn:xmpp:mix:anon:0'
class Participant(ElementBase):
namespace = NS
name = 'participant'
plugin_attrib = 'anon_participant'
interfaces = {'jid'}
sub_interfaces = {'jid'}
class UserPreference(ElementBase):
namespace = NS
name = 'user-preference'
plugin_attrib = 'user_preference'
def register_plugins():
register_stanza_plugin(EventItem, Participant)
register_stanza_plugin(Item, Participant)
register_stanza_plugin(Iq, UserPreference)
register_stanza_plugin(UserPreference, Form)

View file

@ -0,0 +1,13 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from slixmpp.plugins.base import register_plugin
from slixmpp.plugins.xep_0405.stanza import *
from slixmpp.plugins.xep_0405.mix_pam import XEP_0405
register_plugin(XEP_0405)

View file

@ -0,0 +1,88 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permission.
"""
from typing import (
Optional,
Set,
)
from slixmpp import JID, Iq
from slixmpp.exceptions import IqError, IqTimeout
from slixmpp.plugins import BasePlugin
from slixmpp.plugins.xep_0405 import stanza
from slixmpp.plugins.xep_0369 import stanza as mix_stanza
BASE_NODES = [
'urn:xmpp:mix:nodes:messages',
'urn:xmpp:mix:nodes:participants',
'urn:xmpp:mix:nodes:info',
]
class XEP_0405(BasePlugin):
'''XEP-0405: MIX-PAM'''
name = 'xep_0405'
description = 'MIX-PAM'
dependencies = {'xep_0369'}
stanza = stanza
namespace = stanza.NS
def plugin_init(self) -> None:
stanza.register_plugins()
async def check_server_capability(self) -> bool:
"""Check if the server is MIX-PAM capable"""
result = await self.xmpp.plugin['xep_0030'].get_info(jid=self.xmpp.boundjid.bare)
features = result['disco_info']['features']
return stanza.NS in features
async def join_channel(self, room: JID, nick: str, subscribe: Optional[Set[str]] = None, *,
ito: Optional[JID] = None,
ifrom: Optional[JID] = None,
**iqkwargs) -> Set[str]:
"""
Join a MIX channel.
:param JID room: JID of the MIX channel
:param str nick: Desired nickname on that channel
:param Set[str] subscribe: Set of nodes to subscribe to when joining.
If empty, all nodes will be subscribed by default.
:rtype: Set[str]
:return: The nodes that failed to subscribe, if any
"""
if subscribe is None:
subscribe = set(BASE_NODES)
if ito is None:
ito = self.xmpp.boundjid.bare
iq = self.xmpp.make_iq_set(ito=ito, ifrom=ifrom)
iq['client_join']['channel'] = room
iq['client_join']['mix_join']['nick'] = nick
for node in subscribe:
sub = mix_stanza.Subscribe()
sub['node'] = node
iq['client_join']['mix_join'].append(sub)
result = await iq.send(**iqkwargs)
result_nodes = {sub['node'] for sub in result['client_join']['mix_join']}
return result_nodes.difference(subscribe)
async def leave_channel(self, room: JID, *,
ito: Optional[JID] = None,
ifrom: Optional[JID] = None,
**iqkwargs) -> Iq:
""""
Leave a MIX channel
:param JID room: JID of the channel to leave
"""
if ito is None:
ito = self.xmpp.boundjid.bare
iq = self.xmpp.make_iq_set(ito=ito, ifrom=ifrom)
iq['client_leave']['channel'] = room
iq['client_leave'].enable('mix_leave')
return await iq.send(**iqkwargs)

View file

@ -0,0 +1,43 @@
"""
Slixmpp: The Slick XMPP Library
Copyright (C) 2020 Mathieu Pasquet <mathieui@mathieui.net>
This file is part of Slixmpp.
See the file LICENSE for copying permissio
"""
from slixmpp import JID
from slixmpp.stanza import Iq
from slixmpp.xmlstream import (
ElementBase,
register_stanza_plugin,
)
from slixmpp.plugins.xep_0369.stanza import (
Join,
Leave,
)
NS = 'urn:xmpp:mix:pam:2'
class ClientJoin(ElementBase):
namespace = NS
name = 'client-join'
plugin_attrib = 'client_join'
interfaces = {'channel'}
class ClientLeave(ElementBase):
namespace = NS
name = 'client-leave'
plugin_attrib = 'client_leave'
interfaces = {'channel'}
def register_plugins():
register_stanza_plugin(Iq, ClientJoin)
register_stanza_plugin(ClientJoin, Join)
register_stanza_plugin(Iq, ClientLeave)
register_stanza_plugin(ClientLeave, Leave)

View file

@ -745,6 +745,8 @@ class ElementBase(object):
getattr(self, set_method)(value, **kwargs)
else:
if attrib in self.sub_interfaces:
if isinstance(value, JID):
value = str(value)
if lang == '*':
return self._set_all_sub_text(attrib,
value,
@ -863,6 +865,8 @@ class ElementBase(object):
if value is None or value == '':
self.__delitem__(name)
else:
if isinstance(value, JID):
value = str(value)
self.xml.attrib[name] = value
def _del_attr(self, name):

View file

@ -0,0 +1,117 @@
import unittest
from slixmpp import Iq, Message, JID
from slixmpp.test import SlixTest
from slixmpp.plugins.xep_0369 import stanza
from slixmpp.plugins.xep_0060 import stanza as pstanza
from slixmpp.plugins.xep_0369.mix_core import BASE_NODES
class TestMIXStanza(SlixTest):
def setUp(self):
stanza.register_plugins()
def testMIXJoin(self):
"""Test that data is converted to base64"""
iq = Iq()
iq['type'] = 'set'
for node in BASE_NODES:
sub = stanza.Subscribe()
sub['node'] = node
iq['mix_join'].append(sub)
iq['mix_join']['nick'] = 'Toto'
self.check(iq, """
<iq type="set">
<join xmlns='urn:xmpp:mix:core:1'>
<subscribe node='urn:xmpp:mix:nodes:messages'/>
<subscribe node='urn:xmpp:mix:nodes:participants'/>
<subscribe node='urn:xmpp:mix:nodes:info'/>
<nick>Toto</nick>
</join>
</iq>
""")
def testMIXUpdateSub(self):
iq = Iq()
iq['type'] = 'set'
iq.enable('mix_updatesub')
sub = stanza.Subscribe()
sub['node'] = 'urn:xmpp:mix:nodes:someothernode'
iq['mix_updatesub'].append(sub)
self.check(iq, """
<iq type="set">
<update-subscription xmlns='urn:xmpp:mix:core:1'>
<subscribe node='urn:xmpp:mix:nodes:someothernode'/>
</update-subscription>
</iq>
""")
def testMIXLeave(self):
iq = Iq()
iq['type'] = 'set'
iq.enable('mix_leave')
self.check(iq, """
<iq type="set">
<leave xmlns='urn:xmpp:mix:core:1'/>
</iq>
""")
def testMIXSetNick(self):
iq = Iq()
iq['type'] = 'set'
iq['mix_setnick']['nick'] = 'A nick'
self.check(iq, """
<iq type="set">
<setnick xmlns='urn:xmpp:mix:core:1'>
<nick>A nick</nick>
</setnick>
</iq>
""")
def testMIXMessage(self):
msg = Message()
msg['type'] = 'groupchat'
msg['body'] = 'This is a message body'
msg['mix']['nick'] = 'A nick'
msg['mix']['jid'] = JID('toto@example.com')
self.check(msg, """
<message type="groupchat">
<body>This is a message body</body>
<mix xmlns="urn:xmpp:mix:core:1">
<nick>A nick</nick>
<jid>toto@example.com</jid>
</mix>
</message>
""")
def testMIXNewParticipant(self):
msg = Message()
msg['pubsub_event']['items']['node'] = 'urn:xmpp:mix:nodes:participants'
item = pstanza.EventItem()
item['id'] = '123456'
item['mix_participant']['jid'] = JID('titi@example.com')
item['mix_participant']['nick'] = 'Titi'
msg['pubsub_event']['items'].append(item)
self.check(msg, """
<message>
<event xmlns='http://jabber.org/protocol/pubsub#event'>
<items node='urn:xmpp:mix:nodes:participants'>
<item id='123456'>
<participant xmlns='urn:xmpp:mix:core:1'>
<jid>titi@example.com</jid>
<nick>Titi</nick>
</participant>
</item>
</items>
</event>
</message>
""", use_values=False)
suite = unittest.TestLoader().loadTestsFromTestCase(TestMIXStanza)

View file

@ -0,0 +1,55 @@
import unittest
from slixmpp import Iq, Message, JID
from slixmpp.test import SlixTest
from slixmpp.plugins.xep_0405 import stanza
from slixmpp.plugins.xep_0369 import stanza as mstanza
from slixmpp.plugins.xep_0405.mix_pam import BASE_NODES
class TestMIXPAMStanza(SlixTest):
def setUp(self):
stanza.register_plugins()
mstanza.register_plugins()
def testMIXPAMJoin(self):
"""Test that data is converted to base64"""
iq = Iq()
iq['type'] = 'set'
iq['client_join']['channel'] = JID('mix@example.com')
for node in BASE_NODES:
sub = mstanza.Subscribe()
sub['node'] = node
iq['client_join']['mix_join'].append(sub)
iq['client_join']['mix_join']['nick'] = 'Toto'
self.check(iq, """
<iq type="set">
<client-join xmlns='urn:xmpp:mix:pam:2' channel='mix@example.com'>
<join xmlns='urn:xmpp:mix:core:1'>
<subscribe node='urn:xmpp:mix:nodes:messages'/>
<subscribe node='urn:xmpp:mix:nodes:participants'/>
<subscribe node='urn:xmpp:mix:nodes:info'/>
<nick>Toto</nick>
</join>
</client-join>
</iq>
""")
def testMIXPAMLeave(self):
iq = Iq()
iq['type'] = 'set'
iq['client_leave']['channel'] = JID('mix@example.com')
iq['client_leave'].enable('mix_leave')
self.check(iq, """
<iq type="set">
<client-leave xmlns='urn:xmpp:mix:pam:2' channel='mix@example.com'>
<leave xmlns='urn:xmpp:mix:core:1'/>
</client-leave>
</iq>
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestMIXPAMStanza)